Compare commits

...
69 Commits
Author SHA1 Message Date
Fangliding 4e819273b7 Origin ech query design 2026-04-28 18:51:35 +08:00
hexband 08301e272c Hysteria inbound: Unwrap stats conn before extracting user (#5870)
Fixes https://github.com/XTLS/Xray-core/issues/5868
2026-04-03 21:26:25 +00:00
风扇滑翔翼 6eccc59728 WireGuard: Use Xray's buffer (#5880)
Fixes https://github.com/XTLS/Xray-core/issues/5878
2026-04-03 21:22:45 +00:00
dependabot[bot] 1e89a8fd98 Bump google.golang.org/grpc from 1.79.3 to 1.80.0 (#5885)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.3 to 1.80.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.3...v1.80.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.80.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-04-03 18:52:07 +00:00
RPRX d2758a023c v26.3.27
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-03-27 17:45:40 +00:00
LjhAUMEM 8aacdbd71b WireGuard inbound: Fix multi-peer; Fix potential routing issue (#5843)
Fixes https://github.com/XTLS/Xray-core/pull/5554

Fixes https://github.com/XTLS/Xray-core/issues/4760
2026-03-27 17:30:21 +00:00
LjhAUMEM 14524cc3b7 Finalmask: Add randRange to "noise" (UDP), as the same as "header-custom"'s (TCP & UDP) (#5850)
https://github.com/XTLS/Xray-core/pull/5812
2026-03-27 17:20:43 +00:00
RPRX cb7bfeb54c v26.3.23
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-03-23 10:21:21 +00:00
风扇滑翔翼 d62f5cfb62 Loopback outbound: Fix potential nil InboundFromContext (#5836)
Fixes https://github.com/XTLS/Xray-core/issues/5710
2026-03-23 10:11:17 +00:00
CopilotandRPRX 755f0a1d12 VLESS Reverse Proxy: Add "sniffing" to outbound's "reverse" (which is actually an inbound) (#5837)
Closes https://github.com/XTLS/Xray-core/issues/5662

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-03-23 09:49:32 +00:00
CopilotandRPRX d8a8629a14 WireGuard outbound: Fix multi-peer's readQueue issue (#5554)
Fixes https://github.com/XTLS/Xray-core/issues/4507

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-03-23 08:08:28 +00:00
风扇滑翔翼 982c95d89a OpenBSD: Disable readV (#5786)
https://github.com/XTLS/Xray-core/pull/5784#issuecomment-4024880917
https://github.com/XTLS/Xray-core/issues/5756#issuecomment-4015530258
https://github.com/XTLS/Xray-core/pull/5824#issuecomment-4103829456
2026-03-23 07:57:35 +00:00
dependabot[bot] ae3ddd1c06 Bump nick-fields/retry from 3 to 4 (#5838)
Bumps [nick-fields/retry](https://github.com/nick-fields/retry) from 3 to 4.
- [Release notes](https://github.com/nick-fields/retry/releases)
- [Commits](https://github.com/nick-fields/retry/compare/v3...v4)

---
updated-dependencies:
- dependency-name: nick-fields/retry
  dependency-version: '4'
  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-03-23 07:40:05 +00:00
HeXis-YS f926ee4aa0 XTLS Vision: Defer Splice handoff until write completes (#5737)
Fixes https://github.com/XTLS/Xray-core/issues/4878
2026-03-22 17:48:33 +00:00
LjhAUMEMandRPRX 67a71adad1 WireGuard: Implement UDP FullCone NAT (#5833)
Fixes https://github.com/XTLS/Xray-core/issues/5601

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-03-22 17:42:40 +00:00
ClickDevTech ce66db7032 README.md: Add CELERITY to Web Panel (#5834) 2026-03-22 17:14:16 +00:00
Boris Kovalskii 7d93062f3d README.md: Add INCY to iOS & macOS Clients (#5832) 2026-03-22 17:10:29 +00:00
RPRX 2320416ca3 Update github.com/xtls/reality to 20260322125925
https://github.com/XTLS/REALITY/commit/9234c772ba8f181f31c3e81dc2b4177322e5a9a9
https://github.com/XTLS/REALITY/commit/ad4fbafc4bf56064c58f6c33ac2264264b76b5a5
https://github.com/XTLS/REALITY/commit/cd53f7d5023741301aabb5a7274ed0dd024fbd2c
2026-03-22 13:35:23 +00:00
RPRX e0ab00f6a8 README.md: Add BlancVPN to Sponsors
Sponsor Xray-core: https://github.com/XTLS/Xray-core/issues/3668
2026-03-21 14:48:21 +00:00
RPRX 157e65b34d REALITY config: Print Warning when user is choosing apple/icloud as the target or listening on non-443 ports
https://t.me/projectXtls/1754
https://github.com/XTLS/BBS/issues/21#issuecomment-4103308607
2026-03-21 13:19:32 +00:00
c1b67a961e XHTTP transport: Some optimizations (#5803)
https://github.com/XTLS/Xray-core/pull/5801
https://github.com/XTLS/Xray-core/pull/5808

---------

Co-authored-by: Sergei Ozeranskii <sergey.ozeranskiy@gmail.com>
Co-authored-by: rufsieus <rufsieus@gmail.com>
2026-03-21 12:48:47 +00:00
Lumière Élevé 9e09399087 Xray-core: More robust browser header masquerading (chrome, firefox, edge) (#5802)
Fixes https://github.com/XTLS/Xray-core/issues/5800
2026-03-21 12:24:08 +00:00
风扇滑翔翼andRPRX bb05684407 VLESS Reverse Proxy: Check burstObservatory immediately after inbound adds new reverse-mux to reverse-outbound (#5752)
Fixes https://github.com/XTLS/Xray-core/issues/5750

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-03-21 11:16:24 +00:00
LjhAUMEM 06dc4cf8bd Finalmask: Refactor header conns to avoid multiple-copy; Add randRange to "header-custom" (TCP & UDP) (#5812)
https://github.com/XTLS/Xray-core/pull/5657#issuecomment-4016760602
https://github.com/XTLS/Xray-core/pull/5657#issuecomment-4052921628
2026-03-21 09:04:22 +00:00
MatthewandRPRX 35800e953e Commands: x25519 outputs "Password" -> "Password (PublicKey)" (#5759)
https://github.com/XTLS/Xray-core/discussions/5084#discussioncomment-14312223
https://github.com/XTLS/Xray-core/discussions/5123#discussioncomment-14364120
...

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-03-19 15:57:27 +00:00
Katana 50fc324728 REALITY config: Fix client's shortId length check (#5738) 2026-03-19 15:50:23 +00:00
WASDetchanand风扇滑翔翼 ec732b0b40 API: Fix potential nil pointer dereference in executeAddRules() (#5749)
Fixes https://github.com/XTLS/Xray-core/issues/5748

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-03-19 10:33:34 +00:00
风扇滑翔翼 85f1234863 TUN inbound: Generate deterministic GUID on Windows (#5811)
Closes https://github.com/XTLS/Xray-core/issues/5810
2026-03-19 10:18:07 +00:00
dependabot[bot] 695a28c424 Bump google.golang.org/grpc from 1.79.2 to 1.79.3 (#5821)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.2 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.2...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  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-03-18 09:18:01 +00:00
dependabot[bot] 9fd3d9a1eb Bump golang.org/x/net from 0.51.0 to 0.52.0 (#5793)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.51.0 to 0.52.0.
- [Commits](https://github.com/golang/net/compare/v0.51.0...v0.52.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.52.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-03-18 09:17:49 +00:00
风扇滑翔翼 e86c365572 TLS ECH: Avoid outer ALPN http/1.1 for WSS & HUS; Change echForceQuery's default value to "full"; Update github.com/refraction-networking/utls to 20260301010127; Add irrelevant tests for uTLS-REALITY (#5725)
https://github.com/XTLS/Xray-core/pull/5725#issuecomment-3982680111
2026-03-09 12:49:49 +00:00
LjhAUMEM 0321cdd0d2 Hysteria & XHTTP/3: Unified Finalmask's quicParams to set congestion, brutalUp, brutalDown, udpHop (ports & interval), etc. (#5772)
https://github.com/XTLS/Xray-core/pull/5772#issuecomment-4023006179
2026-03-09 12:17:32 +00:00
LjhAUMEM 766fa71eb1 Update github.com/apernet/quic-go to 20260217092621 (#5782) 2026-03-09 12:10:03 +00:00
dependabot[bot] 01951163fd Bump google.golang.org/grpc from 1.79.1 to 1.79.2 (#5777)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.1 to 1.79.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.1...v1.79.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.2
  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-03-09 12:05:14 +00:00
saba-futai acb06e831b Finalmask: Add Sudoku (TCP & UDP) (#5685)
https://github.com/SUDOKU-ASCII/sudoku/issues/23#issuecomment-3859972396
2026-03-07 18:21:35 +00:00
LjhAUMEM a204873d79 Finalmask: Add header-custom (TCP & UDP), fragment (TCP), noise (UDP); Support dialer-proxy, XHTTP/3; Fix XDNS, XICMP potential panic (#5657)
https://github.com/XTLS/Xray-core/pull/5657#issuecomment-4016609446
2026-03-07 15:42:18 +00:00
LjhAUMEM ea87941b77 mKCP transport: Make sure ACKs are limited within MTU (#5773)
https://github.com/XTLS/Xray-core/pull/5657#issuecomment-3984236113
2026-03-07 15:21:25 +00:00
patternihaand风扇滑翔翼 88a2589498 mKCP config: Check TTI 10~100 -> Check TTI 10~5000 (#5755)
https://github.com/XTLS/Xray-core/pull/5755#issuecomment-3992400360

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-03-07 14:11:56 +00:00
Жора Змейкин 5138ffcf22 XHTTP transport: Add "bbr" (default) and "force-brutal" congestion control for H3 (#5711)
https://github.com/XTLS/Xray-core/pull/5711#issuecomment-3984037632
2026-03-07 12:46:40 +00:00
26X23 0ac13bd910 XHTTP transport: Bugfixes for obfuscations (#5720)
https://github.com/XTLS/Xray-core/pull/5720#issuecomment-4016290343
2026-03-07 12:34:41 +00:00
Yury Kastov eec280262d API: Fix Online Map (#5732)
https://github.com/XTLS/Xray-core/pull/5732#pullrequestreview-3863990264
2026-03-07 10:56:11 +00:00
Yury Kastov 78fc2865ea Routing: Add webhook to rules (#5722)
https://github.com/XTLS/Xray-core/pull/5722#issuecomment-3953836108
2026-03-07 10:49:46 +00:00
dependabot[bot] ee8eb99bed Bump docker/build-push-action from 6 to 7 (#5765)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  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-03-07 10:33:11 +00:00
dependabot[bot] 52e4abd2ba Bump docker/setup-buildx-action from 3 to 4 (#5764)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  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-03-07 10:32:58 +00:00
dependabot[bot] 1dbac90b22 Bump docker/setup-qemu-action from 3 to 4 (#5761)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  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-03-07 10:32:22 +00:00
dependabot[bot] 0b8ec6804f Bump docker/login-action from 3 to 4 (#5760)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  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-03-07 10:31:09 +00:00
Miny 9514e988d8 VLESS Encryption: Check 17~17000 -> Check 17~16640 (#5698)
https://github.com/XTLS/Xray-core/pull/5698#issuecomment-3938558695
2026-03-03 12:08:02 +00:00
Random Guy 7dada1da2b VLESS config: Remove "with no flow" warning for now (#5671)
https://github.com/XTLS/Xray-core/pull/5671#issuecomment-3891166246
2026-03-03 11:10:19 +00:00
dependabot[bot] 0bffea3390 Bump actions/upload-artifact from 6 to 7 (#5733)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  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-03-01 18:45:58 +00:00
dependabot[bot] 2805774f72 Bump golang.org/x/net from 0.50.0 to 0.51.0 (#5728)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.50.0 to 0.51.0.
- [Commits](https://github.com/golang/net/compare/v0.50.0...v0.51.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.51.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-03-01 18:45:45 +00:00
owo e6207e3a97 README.md: Add XrayFA to Android Clients (#5715) 2026-02-22 07:05:07 +00:00
C O M P Ξ Z f0f765f9eb README.md: Add GenyConnect to Windows & Linux & Android Clients (#5713) 2026-02-22 07:04:15 +00:00
FanjuandRPRX efdf21efb5 README.md: Add NetProxy-Magisk to Magisk & Android Clients (#5708)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-02-21 09:59:07 +00:00
dependabot[bot] 07374ae5a5 Bump google.golang.org/grpc from 1.79.0 to 1.79.1 (#5695)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.0 to 1.79.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.0...v1.79.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.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-02-21 09:48:35 +00:00
dependabot[bot] b6a7609c87 Bump google.golang.org/grpc from 1.78.0 to 1.79.0 (#5686)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.78.0 to 1.79.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.78.0...v1.79.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.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-02-13 19:51:58 +00:00
CopilotandRPRX b43276c6d3 gRPC client: Strip "grpc-go/version" suffix from User-Agent header (#5689)
Fixes https://github.com/XTLS/Xray-core/pull/5658#issuecomment-3894269376

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-02-13 19:49:47 +00:00
LjhAUMEM 6a909b2507 Proxy: Add Hysteria 2 inbound & transport (supports listening port range, Salamander finalmask) (#5679)
https://github.com/XTLS/Xray-core/pull/5679#issuecomment-3888548778

Closes https://github.com/XTLS/Xray-core/issues/5605
2026-02-12 14:56:06 +00:00
风扇滑翔翼 7abad3fac0 HTTPUpgrade server: Fix certain stuck in Handle() (#5661)
https://github.com/XTLS/Xray-core/pull/5661#issuecomment-3890662818
2026-02-12 14:18:38 +00:00
风扇滑翔翼 1fe6d4a0f5 core/core.go: Replace "Custom" with vcs info if available (#5665)
https://github.com/XTLS/Xray-core/pull/5665#issuecomment-3890500863
2026-02-12 14:00:15 +00:00
风扇滑翔翼 d100be5ad5 Chore: Migrate to Go 1.26 (#5680) 2026-02-12 04:08:59 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇 7dc3f87cd8 Build: Remove Windows ARM 32-bit build (#4584) 2026-02-12 04:06:33 +00:00
dependabot[bot] a079890ef0 Bump github.com/klauspost/cpuid/v2 from 2.0.12 to 2.3.0 (#5668)
Bumps [github.com/klauspost/cpuid/v2](https://github.com/klauspost/cpuid) from 2.0.12 to 2.3.0.
- [Release notes](https://github.com/klauspost/cpuid/releases)
- [Commits](https://github.com/klauspost/cpuid/compare/v2.0.12...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/klauspost/cpuid/v2
  dependency-version: 2.3.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-02-12 03:55:39 +00:00
dependabot[bot] fb0fa80c8c Bump github.com/pires/go-proxyproto from 0.9.2 to 0.11.0 (#5678)
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.9.2 to 0.11.0.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.9.2...v0.11.0)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.11.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-02-12 03:55:27 +00:00
dependabot[bot] e3e7b28c08 Bump golang.org/x/net from 0.49.0 to 0.50.0 (#5676)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.49.0 to 0.50.0.
- [Commits](https://github.com/golang/net/compare/v0.49.0...v0.50.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.50.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-02-12 03:55:12 +00:00
RPRX 12ee51e4bb v26.2.6
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-02-06 09:42:41 +00:00
LjhAUMEM 957e5a6b15 XICMP finalmask: Refine seq (#5652)
Example: https://github.com/XTLS/Xray-core/pull/5633#issue-3881559866
2026-02-06 08:44:50 +00:00
风扇滑翔翼 0710c2b195 Workflows: Add simple consistency check for *.pb.go files to test.yml (#5646)
https://github.com/XTLS/Xray-core/commit/d14767d4f307dd42e6b41c4871480bcb85437b21
2026-02-06 08:37:22 +00:00
风扇滑翔翼 4632984b66 TLS client: Simplify cert's verification code (#5656)
Fixes https://github.com/XTLS/Xray-core/issues/5655
2026-02-06 01:57:32 +00:00
b7a22c729b Xray-core: Dynamic Chrome User-Agent for all HTTP requests by default (overwriteable through config) (#5658)
https://github.com/XTLS/Xray-core/issues/4996#issuecomment-3855274627
https://github.com/XTLS/Xray-core/pull/5658#issuecomment-3857332687

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
Co-authored-by: Fangliding <63339210+Fangliding@users.noreply.github.com>
2026-02-06 01:42:31 +00:00
179 changed files with 11011 additions and 3316 deletions
+1 -2
View File
@@ -29,6 +29,5 @@
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" }, "openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
"windows-386": { "friendlyName": "windows-32" }, "windows-386": { "friendlyName": "windows-32" },
"windows-amd64": { "friendlyName": "windows-64" }, "windows-amd64": { "friendlyName": "windows-64" },
"windows-arm64": { "friendlyName": "windows-arm64-v8a" }, "windows-arm64": { "friendlyName": "windows-arm64-v8a" }
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
} }
+5 -5
View File
@@ -68,13 +68,13 @@ jobs:
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -82,7 +82,7 @@ jobs:
- name: Build Docker image (main architectures) - name: Build Docker image (main architectures)
id: build_main_arches id: build_main_arches
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
file: .github/docker/Dockerfile file: .github/docker/Dockerfile
@@ -97,7 +97,7 @@ jobs:
- name: Build Docker image (additional architectures) - name: Build Docker image (additional architectures)
id: build_additional_arches id: build_additional_arches
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
context: . context: .
file: .github/docker/Dockerfile.usa file: .github/docker/Dockerfile.usa
+1 -1
View File
@@ -173,7 +173,7 @@ jobs:
file_glob: true file_glob: true
- name: Upload files to Artifacts - name: Upload files to Artifacts
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: Xray-${{ env.ASSET_NAME }} name: Xray-${{ env.ASSET_NAME }}
path: | path: |
+2 -8
View File
@@ -40,7 +40,7 @@ jobs:
break break
fi fi
done done
LIST=('amd64' 'x86' 'arm64' 'arm') LIST=('amd64' 'x86' 'arm64')
for ARCHITECTURE in "${LIST[@]}" for ARCHITECTURE in "${LIST[@]}"
do do
echo -e "Checking wintun.dll for ${ARCHITECTURE}..." echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
@@ -114,9 +114,6 @@ jobs:
# Windows ARM # Windows ARM
- goos: windows - goos: windows
goarch: arm64 goarch: arm64
- goos: windows
goarch: arm
goarm: 7
# BEGIN Other architectures # BEGIN Other architectures
# BEGIN riscv64 & ARM64 & LOONG64 # BEGIN riscv64 & ARM64 & LOONG64
- goos: linux - goos: linux
@@ -250,9 +247,6 @@ jobs:
if [[ ${GOARCH} == 'arm64' ]]; then if [[ ${GOARCH} == 'arm64' ]]; then
mv resources/wintun/bin/arm64/wintun.dll build_assets/ mv resources/wintun/bin/arm64/wintun.dll build_assets/
fi fi
if [[ ${GOARCH} == 'arm' ]]; then
mv resources/wintun/bin/arm/wintun.dll build_assets/
fi
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
fi fi
@@ -286,7 +280,7 @@ jobs:
file_glob: true file_glob: true
- name: Upload files to Artifacts - name: Upload files to Artifacts
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: Xray-${{ env.ASSET_NAME }} name: Xray-${{ env.ASSET_NAME }}
path: | path: |
@@ -33,7 +33,7 @@ jobs:
- name: Update Geodat - name: Update Geodat
id: update id: update
uses: nick-fields/retry@v3 uses: nick-fields/retry@v4
with: with:
timeout_minutes: 60 timeout_minutes: 60
retry_wait_seconds: 60 retry_wait_seconds: 60
@@ -82,14 +82,14 @@ jobs:
- name: Update Wintun - name: Update Wintun
id: update id: update
uses: nick-fields/retry@v3 uses: nick-fields/retry@v4
with: with:
timeout_minutes: 60 timeout_minutes: 60
retry_wait_seconds: 60 retry_wait_seconds: 60
max_attempts: 60 max_attempts: 60
command: | command: |
[ -d 'resources' ] || mkdir resources [ -d 'resources' ] || mkdir resources
LIST=('amd64' 'x86' 'arm64' 'arm') LIST=('amd64' 'x86' 'arm64')
for ARCHITECTURE in "${LIST[@]}" for ARCHITECTURE in "${LIST[@]}"
do do
FILE_PATH="resources/wintun/bin/${ARCHITECTURE}/wintun.dll" FILE_PATH="resources/wintun/bin/${ARCHITECTURE}/wintun.dll"
+16
View File
@@ -34,6 +34,22 @@ jobs:
if: steps.check-assets.outputs.missing == 'true' if: steps.check-assets.outputs.missing == 'true'
run: sleep 90 run: sleep 90
check-proto:
runs-on: ubuntu-latest
steps:
- name: Checkout codebase
uses: actions/checkout@v6
- name: Check Proto Version Header
run: |
head -n 4 core/config.pb.go > ref.txt
find . -name "*.pb.go" ! -name "*_grpc.pb.go" -print0 | while IFS= read -r -d '' file; do
if ! cmp -s ref.txt <(head -n 4 "$file"); then
echo "Error: Header mismatch in $file"
head -n 4 "$file"
exit 1
fi
done
test: test:
needs: check-assets needs: check-assets
permissions: permissions:
+11
View File
@@ -10,6 +10,8 @@
[![Happ](https://github.com/user-attachments/assets/14055dab-e8bb-48bd-89e8-962709e4098e)](https://happ.su) [![Happ](https://github.com/user-attachments/assets/14055dab-e8bb-48bd-89e8-962709e4098e)](https://happ.su)
[![BlancVPN](https://github.com/user-attachments/assets/9145ea7d-5da3-446e-8143-710dba4292c3)](https://blanc.link/VMTSDqW)
[**Sponsor Xray-core**](https://github.com/XTLS/Xray-core/issues/3668) [**Sponsor Xray-core**](https://github.com/XTLS/Xray-core/issues/3668)
## Donation & NFTs ## Donation & NFTs
@@ -65,11 +67,13 @@
- [Marzban](https://github.com/Gozargah/Marzban) - [Marzban](https://github.com/Gozargah/Marzban)
- [Hiddify](https://github.com/hiddify/Hiddify-Manager) - [Hiddify](https://github.com/hiddify/Hiddify-Manager)
- [TX-UI](https://github.com/AghayeCoder/tx-ui) - [TX-UI](https://github.com/AghayeCoder/tx-ui)
- [CELERITY](https://github.com/ClickDevTech/CELERITY-panel)
- One Click - One Click
- [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz) - [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless) - [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU) - [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk - Magisk
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk) - [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk) - [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
- Homebrew - Homebrew
@@ -106,16 +110,20 @@
- [Furious](https://github.com/LorenEteval/Furious) - [Furious](https://github.com/LorenEteval/Furious)
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient) - [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal) - [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- Android - Android
- [v2rayNG](https://github.com/2dust/v2rayNG) - [v2rayNG](https://github.com/2dust/v2rayNG)
- [X-flutter](https://github.com/XTLS/X-flutter) - [X-flutter](https://github.com/XTLS/X-flutter)
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray) - [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
- [SimpleXray](https://github.com/lhear/SimpleXray) - [SimpleXray](https://github.com/lhear/SimpleXray)
- [XrayFA](https://github.com/Q7DF1/XrayFA)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal) - [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
- iOS & macOS arm64 & tvOS - iOS & macOS arm64 & tvOS
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274) - [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
- [Streisand](https://apps.apple.com/app/streisand/id6450534064) - [Streisand](https://apps.apple.com/app/streisand/id6450534064)
- [OneXray](https://github.com/OneXray/OneXray) - [OneXray](https://github.com/OneXray/OneXray)
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
- macOS arm64 & x64 - macOS arm64 & x64
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) - [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973)
- [V2rayU](https://github.com/yanue/V2rayU) - [V2rayU](https://github.com/yanue/V2rayU)
@@ -125,6 +133,8 @@
- [GoXRay](https://github.com/goxray/desktop) - [GoXRay](https://github.com/goxray/desktop)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal) - [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [v2rayN](https://github.com/2dust/v2rayN) - [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
- Linux - Linux
- [v2rayA](https://github.com/v2rayA/v2rayA) - [v2rayA](https://github.com/v2rayA/v2rayA)
- [Furious](https://github.com/LorenEteval/Furious) - [Furious](https://github.com/LorenEteval/Furious)
@@ -132,6 +142,7 @@
- [GoXRay](https://github.com/goxray/desktop) - [GoXRay](https://github.com/goxray/desktop)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal) - [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [v2rayN](https://github.com/2dust/v2rayN) - [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX... ## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
+4 -9
View File
@@ -183,12 +183,9 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
if p.Stats.UserOnline { if p.Stats.UserOnline {
name := "user>>>" + user.Email + ">>>online" name := "user>>>" + user.Email + ">>>online"
if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil { if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {
sessionInbounds := session.InboundFromContext(ctx) userIP := sessionInbound.Source.Address.String()
userIP := sessionInbounds.Source.Address.String()
om.AddIP(userIP) om.AddIP(userIP)
// log Online user with ips context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
} }
} }
} }
@@ -225,11 +222,9 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
if p.Stats.UserOnline { if p.Stats.UserOnline {
name := "user>>>" + user.Email + ">>>online" name := "user>>>" + user.Email + ">>>online"
if om, _ := stats.GetOrRegisterOnlineMap(statsManager, name); om != nil { if om, _ := stats.GetOrRegisterOnlineMap(statsManager, name); om != nil {
sessionInbounds := session.InboundFromContext(ctx) userIP := sessionInbound.Source.Address.String()
userIP := sessionInbounds.Source.Address.String()
om.AddIP(userIP) om.AddIP(userIP)
// log Online user with ips context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
} }
} }
} }
+1
View File
@@ -214,6 +214,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
req.Header.Add("Accept", "application/dns-message") req.Header.Add("Accept", "application/dns-message")
req.Header.Add("Content-Type", "application/dns-message") req.Header.Add("Content-Type", "application/dns-message")
utils.TryDefaultHeadersWith(req.Header, "fetch")
req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000))) req.Header.Set("X-Padding", utils.H2Base62Pad(crypto.RandBetween(100, 1000)))
hc := s.httpClient hc := s.httpClient
+4
View File
@@ -32,6 +32,10 @@ func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) {
return &observatory.ObservationResult{Status: o.createResult()}, nil return &observatory.ObservationResult{Status: o.createResult()}, nil
} }
func (o *Observer) Check(tag []string) {
o.hp.Check(tag)
}
func (o *Observer) createResult() []*observatory.OutboundStatus { func (o *Observer) createResult() []*observatory.OutboundStatus {
var result []*observatory.OutboundStatus var result []*observatory.OutboundStatus
o.hp.access.Lock() o.hp.access.Lock()
+2
View File
@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet/tagged" "github.com/xtls/xray-core/transport/internet/tagged"
) )
@@ -61,6 +62,7 @@ func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) {
if err != nil { if err != nil {
return rttFailed, err return rttFailed, err
} }
utils.TryDefaultHeadersWith(req.Header, "nav")
start := time.Now() start := time.Now()
resp, err := s.httpClient.Do(req) resp, err := s.httpClient.Do(req)
+4 -1
View File
@@ -15,6 +15,7 @@ import (
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/extension"
"github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/outbound"
@@ -162,7 +163,9 @@ func (o *Observer) probe(outbound string) ProbeResult {
if o.config.ProbeUrl != "" { if o.config.ProbeUrl != "" {
probeURL = o.config.ProbeUrl probeURL = o.config.ProbeUrl
} }
response, err := httpClient.Get(probeURL) req, _ := http.NewRequest(http.MethodGet, probeURL, nil)
utils.TryDefaultHeadersWith(req.Header, "nav")
response, err := httpClient.Do(req)
if err != nil { if err != nil {
return errors.New("outbound failed to relay connection").Base(err) return errors.New("outbound failed to relay connection").Base(err)
} }
+9
View File
@@ -19,6 +19,8 @@ import (
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/hysteria/account"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tcp" "github.com/xtls/xray-core/transport/internet/tcp"
@@ -138,6 +140,13 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
func (w *tcpWorker) Start() error { func (w *tcpWorker) Start() error {
ctx := context.Background() ctx := context.Background()
type HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }
if v, ok := w.proxy.(HysteriaInboundValidator); ok {
ctx = hyCtx.ContextWithRequireDatagram(ctx, true)
ctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())
}
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) { hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
go w.callback(conn) go w.callback(conn)
}) })
+1
View File
@@ -18,6 +18,7 @@ type Rule struct {
RuleTag string RuleTag string
Balancer *Balancer Balancer *Balancer
Condition Condition Condition Condition
Webhook *WebhookNotifier
} }
func (r *Rule) GetTag() (string, error) { func (r *Rule) GetTag() (string, error) {
+127 -47
View File
@@ -129,7 +129,7 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
// Deprecated: Use Config_DomainStrategy.Descriptor instead. // Deprecated: Use Config_DomainStrategy.Descriptor instead.
func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) { func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{10, 0} return file_app_router_config_proto_rawDescGZIP(), []int{11, 0}
} }
// Domain for routing decision. // Domain for routing decision.
@@ -483,6 +483,7 @@ type RoutingRule struct {
LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"` LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"`
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"` VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"` Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -647,6 +648,13 @@ func (x *RoutingRule) GetProcess() []string {
return nil return nil
} }
func (x *RoutingRule) GetWebhook() *WebhookConfig {
if x != nil {
return x.Webhook
}
return nil
}
type isRoutingRule_TargetTag interface { type isRoutingRule_TargetTag interface {
isRoutingRule_TargetTag() isRoutingRule_TargetTag()
} }
@@ -665,6 +673,66 @@ func (*RoutingRule_Tag) isRoutingRule_TargetTag() {}
func (*RoutingRule_BalancingTag) isRoutingRule_TargetTag() {} func (*RoutingRule_BalancingTag) isRoutingRule_TargetTag() {}
type WebhookConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Deduplication uint32 `protobuf:"varint,2,opt,name=deduplication,proto3" json:"deduplication,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WebhookConfig) Reset() {
*x = WebhookConfig{}
mi := &file_app_router_config_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WebhookConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WebhookConfig) ProtoMessage() {}
func (x *WebhookConfig) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WebhookConfig.ProtoReflect.Descriptor instead.
func (*WebhookConfig) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{7}
}
func (x *WebhookConfig) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (x *WebhookConfig) GetDeduplication() uint32 {
if x != nil {
return x.Deduplication
}
return 0
}
func (x *WebhookConfig) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
type BalancingRule struct { type BalancingRule struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
@@ -678,7 +746,7 @@ type BalancingRule struct {
func (x *BalancingRule) Reset() { func (x *BalancingRule) Reset() {
*x = BalancingRule{} *x = BalancingRule{}
mi := &file_app_router_config_proto_msgTypes[7] mi := &file_app_router_config_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -690,7 +758,7 @@ func (x *BalancingRule) String() string {
func (*BalancingRule) ProtoMessage() {} func (*BalancingRule) ProtoMessage() {}
func (x *BalancingRule) ProtoReflect() protoreflect.Message { func (x *BalancingRule) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[7] mi := &file_app_router_config_proto_msgTypes[8]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -703,7 +771,7 @@ func (x *BalancingRule) ProtoReflect() protoreflect.Message {
// Deprecated: Use BalancingRule.ProtoReflect.Descriptor instead. // Deprecated: Use BalancingRule.ProtoReflect.Descriptor instead.
func (*BalancingRule) Descriptor() ([]byte, []int) { func (*BalancingRule) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{7} return file_app_router_config_proto_rawDescGZIP(), []int{8}
} }
func (x *BalancingRule) GetTag() string { func (x *BalancingRule) GetTag() string {
@@ -752,7 +820,7 @@ type StrategyWeight struct {
func (x *StrategyWeight) Reset() { func (x *StrategyWeight) Reset() {
*x = StrategyWeight{} *x = StrategyWeight{}
mi := &file_app_router_config_proto_msgTypes[8] mi := &file_app_router_config_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -764,7 +832,7 @@ func (x *StrategyWeight) String() string {
func (*StrategyWeight) ProtoMessage() {} func (*StrategyWeight) ProtoMessage() {}
func (x *StrategyWeight) ProtoReflect() protoreflect.Message { func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[8] mi := &file_app_router_config_proto_msgTypes[9]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -777,7 +845,7 @@ func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
// Deprecated: Use StrategyWeight.ProtoReflect.Descriptor instead. // Deprecated: Use StrategyWeight.ProtoReflect.Descriptor instead.
func (*StrategyWeight) Descriptor() ([]byte, []int) { func (*StrategyWeight) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{8} return file_app_router_config_proto_rawDescGZIP(), []int{9}
} }
func (x *StrategyWeight) GetRegexp() bool { func (x *StrategyWeight) GetRegexp() bool {
@@ -819,7 +887,7 @@ type StrategyLeastLoadConfig struct {
func (x *StrategyLeastLoadConfig) Reset() { func (x *StrategyLeastLoadConfig) Reset() {
*x = StrategyLeastLoadConfig{} *x = StrategyLeastLoadConfig{}
mi := &file_app_router_config_proto_msgTypes[9] mi := &file_app_router_config_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -831,7 +899,7 @@ func (x *StrategyLeastLoadConfig) String() string {
func (*StrategyLeastLoadConfig) ProtoMessage() {} func (*StrategyLeastLoadConfig) ProtoMessage() {}
func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message { func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[9] mi := &file_app_router_config_proto_msgTypes[10]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -844,7 +912,7 @@ func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use StrategyLeastLoadConfig.ProtoReflect.Descriptor instead. // Deprecated: Use StrategyLeastLoadConfig.ProtoReflect.Descriptor instead.
func (*StrategyLeastLoadConfig) Descriptor() ([]byte, []int) { func (*StrategyLeastLoadConfig) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{9} return file_app_router_config_proto_rawDescGZIP(), []int{10}
} }
func (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight { func (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight {
@@ -893,7 +961,7 @@ type Config struct {
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
mi := &file_app_router_config_proto_msgTypes[10] mi := &file_app_router_config_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -905,7 +973,7 @@ func (x *Config) String() string {
func (*Config) ProtoMessage() {} func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message { func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[10] mi := &file_app_router_config_proto_msgTypes[11]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -918,7 +986,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
// Deprecated: Use Config.ProtoReflect.Descriptor instead. // Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) { func (*Config) Descriptor() ([]byte, []int) {
return file_app_router_config_proto_rawDescGZIP(), []int{10} return file_app_router_config_proto_rawDescGZIP(), []int{11}
} }
func (x *Config) GetDomainStrategy() Config_DomainStrategy { func (x *Config) GetDomainStrategy() Config_DomainStrategy {
@@ -956,7 +1024,7 @@ type Domain_Attribute struct {
func (x *Domain_Attribute) Reset() { func (x *Domain_Attribute) Reset() {
*x = Domain_Attribute{} *x = Domain_Attribute{}
mi := &file_app_router_config_proto_msgTypes[11] mi := &file_app_router_config_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -968,7 +1036,7 @@ func (x *Domain_Attribute) String() string {
func (*Domain_Attribute) ProtoMessage() {} func (*Domain_Attribute) ProtoMessage() {}
func (x *Domain_Attribute) ProtoReflect() protoreflect.Message { func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
mi := &file_app_router_config_proto_msgTypes[11] mi := &file_app_router_config_proto_msgTypes[12]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1066,7 +1134,7 @@ const file_app_router_config_proto_rawDesc = "" +
"\fcountry_code\x18\x01 \x01(\tR\vcountryCode\x12/\n" + "\fcountry_code\x18\x01 \x01(\tR\vcountryCode\x12/\n" +
"\x06domain\x18\x02 \x03(\v2\x17.xray.app.router.DomainR\x06domain\"=\n" + "\x06domain\x18\x02 \x03(\v2\x17.xray.app.router.DomainR\x06domain\"=\n" +
"\vGeoSiteList\x12.\n" + "\vGeoSiteList\x12.\n" +
"\x05entry\x18\x01 \x03(\v2\x18.xray.app.router.GeoSiteR\x05entry\"\x82\a\n" + "\x05entry\x18\x01 \x03(\v2\x18.xray.app.router.GeoSiteR\x05entry\"\xbc\a\n" +
"\vRoutingRule\x12\x12\n" + "\vRoutingRule\x12\x12\n" +
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" + "\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" + "\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
@@ -1090,12 +1158,20 @@ const file_app_router_config_proto_rawDesc = "" +
"localGeoip\x12A\n" + "localGeoip\x12A\n" +
"\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" + "\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" +
"\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" + "\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" +
"\aprocess\x18\x15 \x03(\tR\aprocess\x1a=\n" + "\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x1a=\n" +
"\x0fAttributesEntry\x12\x10\n" + "\x0fAttributesEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\f\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\f\n" +
"\n" + "\n" +
"target_tag\"\xdc\x01\n" + "target_tag\"\xca\x01\n" +
"\rWebhookConfig\x12\x10\n" +
"\x03url\x18\x01 \x01(\tR\x03url\x12$\n" +
"\rdeduplication\x18\x02 \x01(\rR\rdeduplication\x12E\n" +
"\aheaders\x18\x03 \x03(\v2+.xray.app.router.WebhookConfig.HeadersEntryR\aheaders\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xdc\x01\n" +
"\rBalancingRule\x12\x10\n" + "\rBalancingRule\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x12+\n" + "\x03tag\x18\x01 \x01(\tR\x03tag\x12+\n" +
"\x11outbound_selector\x18\x02 \x03(\tR\x10outboundSelector\x12\x1a\n" + "\x11outbound_selector\x18\x02 \x03(\tR\x10outboundSelector\x12\x1a\n" +
@@ -1136,7 +1212,7 @@ func file_app_router_config_proto_rawDescGZIP() []byte {
} }
var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_app_router_config_proto_goTypes = []any{ var file_app_router_config_proto_goTypes = []any{
(Domain_Type)(0), // 0: xray.app.router.Domain.Type (Domain_Type)(0), // 0: xray.app.router.Domain.Type
(Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy (Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy
@@ -1147,43 +1223,47 @@ var file_app_router_config_proto_goTypes = []any{
(*GeoSite)(nil), // 6: xray.app.router.GeoSite (*GeoSite)(nil), // 6: xray.app.router.GeoSite
(*GeoSiteList)(nil), // 7: xray.app.router.GeoSiteList (*GeoSiteList)(nil), // 7: xray.app.router.GeoSiteList
(*RoutingRule)(nil), // 8: xray.app.router.RoutingRule (*RoutingRule)(nil), // 8: xray.app.router.RoutingRule
(*BalancingRule)(nil), // 9: xray.app.router.BalancingRule (*WebhookConfig)(nil), // 9: xray.app.router.WebhookConfig
(*StrategyWeight)(nil), // 10: xray.app.router.StrategyWeight (*BalancingRule)(nil), // 10: xray.app.router.BalancingRule
(*StrategyLeastLoadConfig)(nil), // 11: xray.app.router.StrategyLeastLoadConfig (*StrategyWeight)(nil), // 11: xray.app.router.StrategyWeight
(*Config)(nil), // 12: xray.app.router.Config (*StrategyLeastLoadConfig)(nil), // 12: xray.app.router.StrategyLeastLoadConfig
(*Domain_Attribute)(nil), // 13: xray.app.router.Domain.Attribute (*Config)(nil), // 13: xray.app.router.Config
nil, // 14: xray.app.router.RoutingRule.AttributesEntry (*Domain_Attribute)(nil), // 14: xray.app.router.Domain.Attribute
(*net.PortList)(nil), // 15: xray.common.net.PortList nil, // 15: xray.app.router.RoutingRule.AttributesEntry
(net.Network)(0), // 16: xray.common.net.Network nil, // 16: xray.app.router.WebhookConfig.HeadersEntry
(*serial.TypedMessage)(nil), // 17: xray.common.serial.TypedMessage (*net.PortList)(nil), // 17: xray.common.net.PortList
(net.Network)(0), // 18: xray.common.net.Network
(*serial.TypedMessage)(nil), // 19: xray.common.serial.TypedMessage
} }
var file_app_router_config_proto_depIdxs = []int32{ var file_app_router_config_proto_depIdxs = []int32{
0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type 0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type
13, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute 14, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute
3, // 2: xray.app.router.GeoIP.cidr:type_name -> xray.app.router.CIDR 3, // 2: xray.app.router.GeoIP.cidr:type_name -> xray.app.router.CIDR
4, // 3: xray.app.router.GeoIPList.entry:type_name -> xray.app.router.GeoIP 4, // 3: xray.app.router.GeoIPList.entry:type_name -> xray.app.router.GeoIP
2, // 4: xray.app.router.GeoSite.domain:type_name -> xray.app.router.Domain 2, // 4: xray.app.router.GeoSite.domain:type_name -> xray.app.router.Domain
6, // 5: xray.app.router.GeoSiteList.entry:type_name -> xray.app.router.GeoSite 6, // 5: xray.app.router.GeoSiteList.entry:type_name -> xray.app.router.GeoSite
2, // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain 2, // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain
4, // 7: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP 4, // 7: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP
15, // 8: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList 17, // 8: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
16, // 9: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network 18, // 9: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
4, // 10: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP 4, // 10: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP
15, // 11: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList 17, // 11: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
14, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry 15, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
4, // 13: xray.app.router.RoutingRule.local_geoip:type_name -> xray.app.router.GeoIP 4, // 13: xray.app.router.RoutingRule.local_geoip:type_name -> xray.app.router.GeoIP
15, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList 17, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
15, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList 17, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
17, // 16: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage 9, // 16: xray.app.router.RoutingRule.webhook:type_name -> xray.app.router.WebhookConfig
10, // 17: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight 16, // 17: xray.app.router.WebhookConfig.headers:type_name -> xray.app.router.WebhookConfig.HeadersEntry
1, // 18: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy 19, // 18: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
8, // 19: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule 11, // 19: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
9, // 20: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule 1, // 20: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
21, // [21:21] is the sub-list for method output_type 8, // 21: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
21, // [21:21] is the sub-list for method input_type 10, // 22: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
21, // [21:21] is the sub-list for extension type_name 23, // [23:23] is the sub-list for method output_type
21, // [21:21] is the sub-list for extension extendee 23, // [23:23] is the sub-list for method input_type
0, // [0:21] is the sub-list for field type_name 23, // [23:23] is the sub-list for extension type_name
23, // [23:23] is the sub-list for extension extendee
0, // [0:23] is the sub-list for field type_name
} }
func init() { file_app_router_config_proto_init() } func init() { file_app_router_config_proto_init() }
@@ -1195,7 +1275,7 @@ func file_app_router_config_proto_init() {
(*RoutingRule_Tag)(nil), (*RoutingRule_Tag)(nil),
(*RoutingRule_BalancingTag)(nil), (*RoutingRule_BalancingTag)(nil),
} }
file_app_router_config_proto_msgTypes[11].OneofWrappers = []any{ file_app_router_config_proto_msgTypes[12].OneofWrappers = []any{
(*Domain_Attribute_BoolValue)(nil), (*Domain_Attribute_BoolValue)(nil),
(*Domain_Attribute_IntValue)(nil), (*Domain_Attribute_IntValue)(nil),
} }
@@ -1205,7 +1285,7 @@ func file_app_router_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_config_proto_rawDesc), len(file_app_router_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_config_proto_rawDesc), len(file_app_router_config_proto_rawDesc)),
NumEnums: 2, NumEnums: 2,
NumMessages: 13, NumMessages: 15,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
+7
View File
@@ -114,6 +114,13 @@ message RoutingRule {
xray.common.net.PortList vless_route_list = 20; xray.common.net.PortList vless_route_list = 20;
repeated string process = 21; repeated string process = 21;
WebhookConfig webhook = 22;
}
message WebhookConfig {
string url = 1;
uint32 deduplication = 2;
map<string, string> headers = 3;
} }
message BalancingRule { message BalancingRule {
+60
View File
@@ -57,6 +57,7 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
for _, rule := range config.Rule { for _, rule := range config.Rule {
cond, err := rule.BuildCondition() cond, err := rule.BuildCondition()
if err != nil { if err != nil {
r.closeWebhooks()
return err return err
} }
rr := &Rule{ rr := &Rule{
@@ -64,10 +65,22 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
Tag: rule.GetTag(), Tag: rule.GetTag(),
RuleTag: rule.GetRuleTag(), RuleTag: rule.GetRuleTag(),
} }
if wh := rule.GetWebhook(); wh != nil {
notifier, err := NewWebhookNotifier(wh)
if err != nil {
r.closeWebhooks()
return err
}
rr.Webhook = notifier
}
btag := rule.GetBalancingTag() btag := rule.GetBalancingTag()
if len(btag) > 0 { if len(btag) > 0 {
brule, found := r.balancers[btag] brule, found := r.balancers[btag]
if !found { if !found {
if rr.Webhook != nil {
rr.Webhook.Close()
}
r.closeWebhooks()
return errors.New("balancer ", btag, " not found") return errors.New("balancer ", btag, " not found")
} }
rr.Balancer = brule rr.Balancer = brule
@@ -80,6 +93,7 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
// PickRoute implements routing.Router. // PickRoute implements routing.Router.
func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) { func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
originalCtx := ctx
rule, ctx, err := r.pickRouteInternal(ctx) rule, ctx, err := r.pickRouteInternal(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -88,6 +102,9 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rule.Webhook != nil {
rule.Webhook.Fire(originalCtx, tag)
}
return &Route{Context: ctx, outboundTag: tag, ruleTag: rule.RuleTag}, nil return &Route{Context: ctx, outboundTag: tag, ruleTag: rule.RuleTag}, nil
} }
@@ -109,6 +126,11 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
defer r.mu.Unlock() defer r.mu.Unlock()
if !shouldAppend { if !shouldAppend {
for _, rule := range r.rules {
if rule.Webhook != nil {
rule.Webhook.Close()
}
}
r.balancers = make(map[string]*Balancer, len(config.BalancingRule)) r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
r.rules = make([]*Rule, 0, len(config.Rule)) r.rules = make([]*Rule, 0, len(config.Rule))
} }
@@ -125,12 +147,24 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
r.balancers[rule.Tag] = balancer r.balancers[rule.Tag] = balancer
} }
startIdx := len(r.rules)
closeNewWebhooks := func() {
for i := startIdx; i < len(r.rules); i++ {
if r.rules[i].Webhook != nil {
r.rules[i].Webhook.Close()
}
}
r.rules = r.rules[:startIdx]
}
for _, rule := range config.Rule { for _, rule := range config.Rule {
if r.RuleExists(rule.GetRuleTag()) { if r.RuleExists(rule.GetRuleTag()) {
closeNewWebhooks()
return errors.New("duplicate ruleTag ", rule.GetRuleTag()) return errors.New("duplicate ruleTag ", rule.GetRuleTag())
} }
cond, err := rule.BuildCondition() cond, err := rule.BuildCondition()
if err != nil { if err != nil {
closeNewWebhooks()
return err return err
} }
rr := &Rule{ rr := &Rule{
@@ -138,10 +172,22 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
Tag: rule.GetTag(), Tag: rule.GetTag(),
RuleTag: rule.GetRuleTag(), RuleTag: rule.GetRuleTag(),
} }
if wh := rule.GetWebhook(); wh != nil {
notifier, err := NewWebhookNotifier(wh)
if err != nil {
closeNewWebhooks()
return err
}
rr.Webhook = notifier
}
btag := rule.GetBalancingTag() btag := rule.GetBalancingTag()
if len(btag) > 0 { if len(btag) > 0 {
brule, found := r.balancers[btag] brule, found := r.balancers[btag]
if !found { if !found {
if rr.Webhook != nil {
rr.Webhook.Close()
}
closeNewWebhooks()
return errors.New("balancer ", btag, " not found") return errors.New("balancer ", btag, " not found")
} }
rr.Balancer = brule rr.Balancer = brule
@@ -173,6 +219,8 @@ func (r *Router) RemoveRule(tag string) error {
for _, rule := range r.rules { for _, rule := range r.rules {
if rule.RuleTag != tag { if rule.RuleTag != tag {
newRules = append(newRules, rule) newRules = append(newRules, rule)
} else if rule.Webhook != nil {
rule.Webhook.Close()
} }
} }
r.rules = newRules r.rules = newRules
@@ -233,8 +281,20 @@ func (r *Router) Start() error {
return nil return nil
} }
// closeWebhooks closes all webhook notifiers in the current rule set.
func (r *Router) closeWebhooks() {
for _, rule := range r.rules {
if rule.Webhook != nil {
rule.Webhook.Close()
}
}
}
// Close implements common.Closable. // Close implements common.Closable.
func (r *Router) Close() error { func (r *Router) Close() error {
r.mu.Lock()
defer r.mu.Unlock()
r.closeWebhooks()
return nil return nil
} }
+287
View File
@@ -0,0 +1,287 @@
package router
import (
"bytes"
"context"
"encoding/json"
"io"
"net"
"net/http"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/xtls/xray-core/common/errors"
"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 {
Email *string `json:"email"`
Level *uint32 `json:"level"`
Protocol *string `json:"protocol"`
Network *string `json:"network"`
Source *string `json:"source"`
Destination *string `json:"destination"`
OriginalTarget *string `json:"originalTarget"`
RouteTarget *string `json:"routeTarget"`
InboundTag *string `json:"inboundTag"`
InboundName *string `json:"inboundName"`
InboundLocal *string `json:"inboundLocal"`
OutboundTag *string `json:"outboundTag"`
Timestamp int64 `json:"ts"`
}
type WebhookNotifier struct {
url string
headers map[string]string
deduplication uint32
client *http.Client
seen sync.Map
done chan struct{}
wg sync.WaitGroup
closeOnce sync.Once
}
func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
if cfg == nil || cfg.Url == "" {
return nil, nil
}
httpURL, socketPath := parseURL(cfg.Url)
h := &WebhookNotifier{
url: httpURL,
deduplication: cfg.Deduplication,
client: &http.Client{
Timeout: 5 * time.Second,
},
done: make(chan struct{}),
}
if socketPath != "" {
dialAddr := resolveSocketPath(socketPath)
h.client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", dialAddr)
},
}
}
if len(cfg.Headers) > 0 {
h.headers = make(map[string]string, len(cfg.Headers))
for k, v := range cfg.Headers {
h.headers[k] = v
}
}
if h.deduplication > 0 {
h.wg.Add(1)
go h.cleanupLoop()
}
return h, nil
}
func (h *WebhookNotifier) Fire(ctx routing.Context, outboundTag string) {
ev := buildEvent(ctx, outboundTag)
email := ""
if ev.Email != nil {
email = *ev.Email
}
if h.isDuplicate(email) {
return
}
h.wg.Add(1)
select {
case <-h.done:
h.wg.Done()
return
default:
}
go func() {
defer h.wg.Done()
h.post(ev)
}()
}
func buildEvent(ctx routing.Context, outboundTag string) *event {
ev := &event{
Timestamp: time.Now().Unix(),
OutboundTag: ptr(outboundTag),
InboundTag: ptr(ctx.GetInboundTag()),
Protocol: ptr(ctx.GetProtocol()),
Network: ptr(ctx.GetNetwork().SystemString()),
}
if user := ctx.GetUser(); user != "" {
ev.Email = ptr(user)
}
if srcIPs := ctx.GetSourceIPs(); len(srcIPs) > 0 {
srcPort := ctx.GetSourcePort()
ev.Source = ptr(net.JoinHostPort(srcIPs[0].String(), srcPort.String()))
}
targetPort := ctx.GetTargetPort()
if domain := ctx.GetTargetDomain(); domain != "" {
ev.Destination = ptr(net.JoinHostPort(domain, targetPort.String()))
} else if targetIPs := ctx.GetTargetIPs(); len(targetIPs) > 0 {
ev.Destination = ptr(net.JoinHostPort(targetIPs[0].String(), targetPort.String()))
}
if localIPs := ctx.GetLocalIPs(); len(localIPs) > 0 {
localPort := ctx.GetLocalPort()
ev.InboundLocal = ptr(net.JoinHostPort(localIPs[0].String(), localPort.String()))
}
if sctx, ok := ctx.(*routing_session.Context); ok {
enrichFromSession(ev, sctx)
}
return ev
}
func enrichFromSession(ev *event, sctx *routing_session.Context) {
if sctx.Inbound != nil {
ev.InboundName = ptr(sctx.Inbound.Name)
if sctx.Inbound.User != nil {
ev.Level = ptr(sctx.Inbound.User.Level)
}
}
if sctx.Outbound != nil {
if sctx.Outbound.OriginalTarget.Address != nil {
ev.OriginalTarget = ptr(sctx.Outbound.OriginalTarget.String())
}
if sctx.Outbound.RouteTarget.Address != nil {
ev.RouteTarget = ptr(sctx.Outbound.RouteTarget.String())
}
}
}
func (h *WebhookNotifier) post(ev *event) {
body, err := json.Marshal(ev)
if err != nil {
errors.LogWarning(context.Background(), "webhook: marshal failed: ", err)
return
}
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, h.url, bytes.NewReader(body))
if err != nil {
errors.LogWarning(context.Background(), "webhook: request build failed: ", err)
return
}
req.Header.Set("Content-Type", "application/json")
for k, v := range h.headers {
req.Header.Set(k, v)
}
resp, err := h.client.Do(req)
if err != nil {
errors.LogInfo(context.Background(), "webhook: POST failed: ", err)
return
}
defer func() {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}()
if resp.StatusCode >= 400 {
errors.LogWarning(context.Background(), "webhook: POST returned status ", resp.StatusCode)
}
}
func (h *WebhookNotifier) isDuplicate(email string) bool {
if h.deduplication == 0 || email == "" {
return false
}
ttl := time.Duration(h.deduplication) * time.Second
now := time.Now()
if v, loaded := h.seen.LoadOrStore(email, now); loaded {
if now.Sub(v.(time.Time)) < ttl {
return true
}
h.seen.Store(email, now)
}
return false
}
func (h *WebhookNotifier) cleanupLoop() {
defer h.wg.Done()
ttl := time.Duration(h.deduplication) * time.Second
ticker := time.NewTicker(ttl)
defer ticker.Stop()
for {
select {
case <-h.done:
return
case <-ticker.C:
now := time.Now()
h.seen.Range(func(key, value any) bool {
if now.Sub(value.(time.Time)) >= ttl {
h.seen.Delete(key)
}
return true
})
}
}
}
func (h *WebhookNotifier) Close() error {
h.closeOnce.Do(func() {
close(h.done)
})
h.wg.Wait()
h.client.CloseIdleConnections()
return nil
}
+1 -1
View File
@@ -70,7 +70,7 @@ func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStat
} }
ips := make(map[string]int64) ips := make(map[string]int64)
for ip, t := range c.IpTimeMap() { for ip, t := range c.IPTimeMap() {
ips[ip] = t.Unix() ips[ip] = t.Unix()
} }
+73 -59
View File
@@ -2,84 +2,98 @@ package stats
import ( import (
"sync" "sync"
"sync/atomic"
"time" "time"
) )
// OnlineMap is an implementation of stats.OnlineMap. const (
type OnlineMap struct { localhostIPv4 = "127.0.0.1"
ipList map[string]time.Time localhostIPv6 = "[::1]"
access sync.RWMutex )
lastCleanup time.Time
cleanupPeriod time.Duration type ipEntry struct {
refCount int
lastSeen time.Time
} }
// NewOnlineMap creates a new instance of OnlineMap. // OnlineMap is a refcount-based implementation of stats.OnlineMap.
// IPs are tracked by reference counting: AddIP increments, RemoveIP decrements.
// An IP is removed from the map when its reference count reaches zero.
type OnlineMap struct {
entries map[string]*ipEntry
access sync.Mutex
count atomic.Int64
}
// NewOnlineMap creates a new OnlineMap instance.
func NewOnlineMap() *OnlineMap { func NewOnlineMap() *OnlineMap {
return &OnlineMap{ return &OnlineMap{
ipList: make(map[string]time.Time), entries: make(map[string]*ipEntry),
lastCleanup: time.Now(), }
cleanupPeriod: 10 * time.Second, }
// AddIP implements stats.OnlineMap.
func (om *OnlineMap) AddIP(ip string) {
if ip == localhostIPv4 || ip == localhostIPv6 {
return
}
om.access.Lock()
defer om.access.Unlock()
if e, ok := om.entries[ip]; ok {
e.refCount++
e.lastSeen = time.Now()
} else {
om.entries[ip] = &ipEntry{
refCount: 1,
lastSeen: time.Now(),
}
om.count.Add(1)
}
}
// RemoveIP implements stats.OnlineMap.
func (om *OnlineMap) RemoveIP(ip string) {
om.access.Lock()
defer om.access.Unlock()
e, ok := om.entries[ip]
if !ok {
return
}
e.refCount--
if e.refCount <= 0 {
delete(om.entries, ip)
om.count.Add(-1)
} }
} }
// Count implements stats.OnlineMap. // Count implements stats.OnlineMap.
func (c *OnlineMap) Count() int { func (om *OnlineMap) Count() int {
c.access.RLock() return int(om.count.Load())
defer c.access.RUnlock()
return len(c.ipList)
} }
// List implements stats.OnlineMap. // List implements stats.OnlineMap.
func (c *OnlineMap) List() []string { func (om *OnlineMap) List() []string {
return c.GetKeys() om.access.Lock()
} defer om.access.Unlock()
// AddIP implements stats.OnlineMap. keys := make([]string, 0, len(om.entries))
func (c *OnlineMap) AddIP(ip string) { for ip := range om.entries {
if ip == "127.0.0.1" { keys = append(keys, ip)
return
}
c.access.Lock()
c.ipList[ip] = time.Now()
c.access.Unlock()
if time.Since(c.lastCleanup) > c.cleanupPeriod {
c.RemoveExpiredIPs()
c.lastCleanup = time.Now()
}
}
func (c *OnlineMap) GetKeys() []string {
c.access.RLock()
defer c.access.RUnlock()
keys := []string{}
for k := range c.ipList {
keys = append(keys, k)
} }
return keys return keys
} }
func (c *OnlineMap) RemoveExpiredIPs() { // IPTimeMap implements stats.OnlineMap.
c.access.Lock() func (om *OnlineMap) IPTimeMap() map[string]time.Time {
defer c.access.Unlock() om.access.Lock()
defer om.access.Unlock()
now := time.Now() result := make(map[string]time.Time, len(om.entries))
for k, t := range c.ipList { for ip, e := range om.entries {
diff := now.Sub(t) result[ip] = e.lastSeen
if diff.Seconds() > 20 {
delete(c.ipList, k)
}
} }
} return result
func (c *OnlineMap) IpTimeMap() map[string]time.Time {
if time.Since(c.lastCleanup) > c.cleanupPeriod {
c.RemoveExpiredIPs()
c.lastCleanup = time.Now()
}
return c.ipList
} }
+3 -3
View File
@@ -163,12 +163,12 @@ func (m *Manager) GetChannel(name string) stats.Channel {
// GetAllOnlineUsers implements stats.Manager. // GetAllOnlineUsers implements stats.Manager.
func (m *Manager) GetAllOnlineUsers() []string { func (m *Manager) GetAllOnlineUsers() []string {
m.access.Lock() m.access.RLock()
defer m.access.Unlock() defer m.access.RUnlock()
usersOnline := make([]string, 0, len(m.onlineMap)) usersOnline := make([]string, 0, len(m.onlineMap))
for user, onlineMap := range m.onlineMap { for user, onlineMap := range m.onlineMap {
if len(onlineMap.IpTimeMap()) > 0 { if onlineMap.Count() > 0 {
usersOnline = append(usersOnline, user) usersOnline = append(usersOnline, user)
} }
} }
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !windows && !wasm && !illumos //go:build !windows && !wasm && !illumos && !openbsd
// +build !windows,!wasm,!illumos // +build !windows,!wasm,!illumos,!openbsd
package buf package buf
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !wasm //go:build !wasm && !openbsd
// +build !wasm // +build !wasm,!openbsd
package buf package buf
+17
View File
@@ -0,0 +1,17 @@
//go:build wasm || openbsd
// +build wasm openbsd
package buf
import (
"io"
"syscall"
"github.com/xtls/xray-core/features/stats"
)
const useReadv = false
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
panic("not implemented")
}
-15
View File
@@ -1,15 +0,0 @@
//go:build wasm
// +build wasm
package buf
import (
"io"
"syscall"
)
const useReadv = false
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn) Reader {
panic("not implemented")
}
+2 -2
View File
@@ -1,5 +1,5 @@
//go:build !wasm //go:build !wasm && !openbsd
// +build !wasm // +build !wasm,!openbsd
package buf_test package buf_test
+20
View File
@@ -4,8 +4,11 @@ package crypto // import "github.com/xtls/xray-core/common/crypto"
import ( import (
"crypto/rand" "crypto/rand"
"math/big" "math/big"
"github.com/xtls/xray-core/common"
) )
// [,)
func RandBetween(from int64, to int64) int64 { func RandBetween(from int64, to int64) int64 {
if from == to { if from == to {
return from return from
@@ -16,3 +19,20 @@ func RandBetween(from int64, to int64) int64 {
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from)) bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
return from + bigInt.Int64() return from + bigInt.Int64()
} }
// [,]
func RandBytesBetween(b []byte, from, to byte) {
common.Must2(rand.Read(b))
if from > to {
from, to = to, from
}
if to-from == 255 {
return
}
for i := range b {
b[i] = from + b[i]%(to-from+1)
}
}
+2 -2
View File
@@ -36,7 +36,7 @@ type serverityLogger struct {
func NewLogger(logWriterCreator WriterCreator) Handler { func NewLogger(logWriterCreator WriterCreator) Handler {
return &generalLogger{ return &generalLogger{
creator: logWriterCreator, creator: logWriterCreator,
buffer: make(chan Message, 16), buffer: make(chan Message, 128),
access: semaphore.New(1), access: semaphore.New(1),
done: done.New(), done: done.New(),
} }
@@ -46,7 +46,7 @@ func ReplaceWithSeverityLogger(serverity Severity) {
w := CreateStdoutLogWriter() w := CreateStdoutLogWriter()
g := &generalLogger{ g := &generalLogger{
creator: w, creator: w,
buffer: make(chan Message, 16), buffer: make(chan Message, 128),
access: semaphore.New(1), access: semaphore.New(1),
done: done.New(), done: done.New(),
} }
+21 -21
View File
@@ -10,46 +10,46 @@ import (
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
) )
type ConnectionOption func(*connection) type ConnectionOption func(*Connection)
func ConnectionLocalAddr(a net.Addr) ConnectionOption { func ConnectionLocalAddr(a net.Addr) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.local = a c.local = a
} }
} }
func ConnectionRemoteAddr(a net.Addr) ConnectionOption { func ConnectionRemoteAddr(a net.Addr) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.remote = a c.remote = a
} }
} }
func ConnectionInput(writer io.Writer) ConnectionOption { func ConnectionInput(writer io.Writer) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.writer = buf.NewWriter(writer) c.writer = buf.NewWriter(writer)
} }
} }
func ConnectionInputMulti(writer buf.Writer) ConnectionOption { func ConnectionInputMulti(writer buf.Writer) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.writer = writer c.writer = writer
} }
} }
func ConnectionOutput(reader io.Reader) ConnectionOption { func ConnectionOutput(reader io.Reader) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)} c.reader = &buf.BufferedReader{Reader: buf.NewReader(reader)}
} }
} }
func ConnectionOutputMulti(reader buf.Reader) ConnectionOption { func ConnectionOutputMulti(reader buf.Reader) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.reader = &buf.BufferedReader{Reader: reader} c.reader = &buf.BufferedReader{Reader: reader}
} }
} }
func ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption { func ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.reader = &buf.BufferedReader{ c.reader = &buf.BufferedReader{
Reader: reader, Reader: reader,
Splitter: buf.SplitFirstBytes, Splitter: buf.SplitFirstBytes,
@@ -58,13 +58,13 @@ func ConnectionOutputMultiUDP(reader buf.Reader) ConnectionOption {
} }
func ConnectionOnClose(n io.Closer) ConnectionOption { func ConnectionOnClose(n io.Closer) ConnectionOption {
return func(c *connection) { return func(c *Connection) {
c.onClose = n c.onClose = n
} }
} }
func NewConnection(opts ...ConnectionOption) net.Conn { func NewConnection(opts ...ConnectionOption) net.Conn {
c := &connection{ c := &Connection{
done: done.New(), done: done.New(),
local: &net.TCPAddr{ local: &net.TCPAddr{
IP: []byte{0, 0, 0, 0}, IP: []byte{0, 0, 0, 0},
@@ -83,7 +83,7 @@ func NewConnection(opts ...ConnectionOption) net.Conn {
return c return c
} }
type connection struct { type Connection struct {
reader *buf.BufferedReader reader *buf.BufferedReader
writer buf.Writer writer buf.Writer
done *done.Instance done *done.Instance
@@ -92,17 +92,17 @@ type connection struct {
remote net.Addr remote net.Addr
} }
func (c *connection) Read(b []byte) (int, error) { func (c *Connection) Read(b []byte) (int, error) {
return c.reader.Read(b) return c.reader.Read(b)
} }
// ReadMultiBuffer implements buf.Reader. // ReadMultiBuffer implements buf.Reader.
func (c *connection) ReadMultiBuffer() (buf.MultiBuffer, error) { func (c *Connection) ReadMultiBuffer() (buf.MultiBuffer, error) {
return c.reader.ReadMultiBuffer() return c.reader.ReadMultiBuffer()
} }
// Write implements net.Conn.Write(). // Write implements net.Conn.Write().
func (c *connection) Write(b []byte) (int, error) { func (c *Connection) Write(b []byte) (int, error) {
if c.done.Done() { if c.done.Done() {
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
} }
@@ -113,7 +113,7 @@ func (c *connection) Write(b []byte) (int, error) {
return l, c.writer.WriteMultiBuffer(mb) return l, c.writer.WriteMultiBuffer(mb)
} }
func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error { func (c *Connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
if c.done.Done() { if c.done.Done() {
buf.ReleaseMulti(mb) buf.ReleaseMulti(mb)
return io.ErrClosedPipe return io.ErrClosedPipe
@@ -123,7 +123,7 @@ func (c *connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
} }
// Close implements net.Conn.Close(). // Close implements net.Conn.Close().
func (c *connection) Close() error { func (c *Connection) Close() error {
common.Must(c.done.Close()) common.Must(c.done.Close())
common.Interrupt(c.reader) common.Interrupt(c.reader)
common.Close(c.writer) common.Close(c.writer)
@@ -135,26 +135,26 @@ func (c *connection) Close() error {
} }
// LocalAddr implements net.Conn.LocalAddr(). // LocalAddr implements net.Conn.LocalAddr().
func (c *connection) LocalAddr() net.Addr { func (c *Connection) LocalAddr() net.Addr {
return c.local return c.local
} }
// RemoteAddr implements net.Conn.RemoteAddr(). // RemoteAddr implements net.Conn.RemoteAddr().
func (c *connection) RemoteAddr() net.Addr { func (c *Connection) RemoteAddr() net.Addr {
return c.remote return c.remote
} }
// SetDeadline implements net.Conn.SetDeadline(). // SetDeadline implements net.Conn.SetDeadline().
func (c *connection) SetDeadline(t time.Time) error { func (c *Connection) SetDeadline(t time.Time) error {
return nil return nil
} }
// SetReadDeadline implements net.Conn.SetReadDeadline(). // SetReadDeadline implements net.Conn.SetReadDeadline().
func (c *connection) SetReadDeadline(t time.Time) error { func (c *Connection) SetReadDeadline(t time.Time) error {
return nil return nil
} }
// SetWriteDeadline implements net.Conn.SetWriteDeadline(). // SetWriteDeadline implements net.Conn.SetWriteDeadline().
func (c *connection) SetWriteDeadline(t time.Time) error { func (c *Connection) SetWriteDeadline(t time.Time) error {
return nil return nil
} }
+191
View File
@@ -0,0 +1,191 @@
package utils
import (
"math/rand"
"strconv"
"time"
"net/http"
"strings"
"github.com/klauspost/cpuid/v2"
)
func ChromeVersion() int {
// Use only CPU info as seed for PRNG
seed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine)
rng := rand.New(rand.NewSource(seed))
// Start from Chrome 144 released on 2026.1.13
releaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC)
version := 144
now := time.Now()
// Each version has random 25-45 day interval
for releaseDate.Before(now) {
releaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25)
version++
}
return version - 1
}
// 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}}
func getGreasedChInvalidBrand(seed int) string {
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)][:]
}
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) + "\"")
switch forkName {
case "chrome":
baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"")
case "edge":
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)
shuffledCh := make([]string, len(ungreasedCh))
for i, e := range shuffleMap {
shuffledCh[e] = ungreasedCh[i]
}
return strings.Join(shuffledCh, ", ")
}
// It's better to pin on Firefox ESR releases, and there could be a Firefox ESR version generator later.
// However, if the Firefox fingerprint in uTLS doesn't have its update cadence match that of Firefox ESR, then it's better to update the Firefox version manually instead every time a new major ESR release is available.
var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
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")
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
// Browser-specific.
switch browser {
case "chrome":
header["Sec-CH-UA"] = []string{ChromeUACH}
header["Sec-CH-UA-Mobile"] = []string{"?0"}
header["Sec-CH-UA-Platform"] = []string{"\"Windows\""}
header["DNT"] = []string{"1"}
header.Set("User-Agent", ChromeUA)
header.Set("Accept-Language", "en-US,en;q=0.9")
case "edge":
header["Sec-CH-UA"] = []string{MSEdgeUACH}
header["Sec-CH-UA-Mobile"] = []string{"?0"}
header["Sec-CH-UA-Platform"] = []string{"\"Windows\""}
header["DNT"] = []string{"1"}
header.Set("User-Agent", MSEdgeUA)
header.Set("Accept-Language", "en-US,en;q=0.9")
case "firefox":
header.Set("User-Agent", FirefoxUA)
header["DNT"] = []string{"1"}
header.Set("Accept-Language", "en-US,en;q=0.5")
case "golang":
// Expose the default net/http header.
header.Del("User-Agent")
return
}
// Context-specific.
switch variant {
case "nav":
if header.Get("Cache-Control") == "" {
switch browser {
case "chrome", "edge":
header.Set("Cache-Control", "max-age=0")
}
}
header.Set("Upgrade-Insecure-Requests", "1")
if header.Get("Accept") == "" {
switch browser {
case "chrome", "edge":
header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
case "firefox":
header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
}
}
header.Set("Sec-Fetch-Site", "none")
header.Set("Sec-Fetch-Mode", "navigate")
header.Set("Sec-Fetch-User", "?1")
header.Set("Sec-Fetch-Dest", "document")
header.Set("Priority", "u=0, i")
case "ws":
header.Set("Sec-Fetch-Mode", "websocket")
header.Set("Sec-Fetch-Dest", "empty")
header.Set("Sec-Fetch-Site", "same-origin")
if header.Get("Cache-Control") == "" {
header.Set("Cache-Control", "no-cache")
}
if header.Get("Pragma") == "" {
header.Set("Pragma", "no-cache")
}
if header.Get("Accept") == "" {
header.Set("Accept", "*/*")
}
case "fetch":
header.Set("Sec-Fetch-Mode", "cors")
header.Set("Sec-Fetch-Dest", "empty")
header.Set("Sec-Fetch-Site", "same-origin")
if header.Get("Priority") == "" {
switch browser {
case "chrome", "edge":
header.Set("Priority", "u=1, i")
case "firefox":
header.Set("Priority", "u=4")
}
}
if header.Get("Cache-Control") == "" {
header.Set("Cache-Control", "no-cache")
}
if header.Get("Pragma") == "" {
header.Set("Pragma", "no-cache")
}
if header.Get("Accept") == "" {
header.Set("Accept", "*/*")
}
}
}
func TryDefaultHeadersWith(header http.Header, variant string) {
// The global UA special value handler for transports. Used to be called HandleTransportUASettings.
// Just a FYI to whoever needing to fix this piece of code after some spontaneous event, I tried to make the two methods separate to let the code be cleaner and more organized.
if len(header.Values("User-Agent")) < 1 {
applyMasqueradedHeaders(header, "chrome", variant)
} else {
switch header.Get("User-Agent") {
case "chrome":
applyMasqueradedHeaders(header, "chrome", variant)
case "firefox":
applyMasqueradedHeaders(header, "firefox", variant)
case "edge":
applyMasqueradedHeaders(header, "edge", variant)
case "golang":
applyMasqueradedHeaders(header, "golang", variant)
}
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
return return
} }
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP && 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") { (inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun" || inbound.Name == "wireguard") {
h := blake3.New(8, BaseKey) h := blake3.New(8, BaseKey)
h.Write([]byte(inbound.Source.String())) h.Write([]byte(inbound.Source.String()))
copy(globalID[:], h.Sum(nil)) copy(globalID[:], h.Sum(nil))
+31 -2
View File
@@ -12,14 +12,15 @@ package core
import ( import (
"fmt" "fmt"
"runtime" "runtime"
"runtime/debug"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
) )
var ( var (
Version_x byte = 26 Version_x byte = 26
Version_y byte = 2 Version_y byte = 3
Version_z byte = 4 Version_z byte = 27
) )
var ( var (
@@ -28,6 +29,34 @@ var (
intro = "A unified platform for anti-censorship." intro = "A unified platform for anti-censorship."
) )
func init() {
// Manually injected
if build != "Custom" {
return
}
info, ok := debug.ReadBuildInfo()
if !ok {
return
}
var isDirty bool
var foundBuild bool
for _, setting := range info.Settings {
switch setting.Key {
case "vcs.revision":
if len(setting.Value) < 7 {
return
}
build = setting.Value[:7]
foundBuild = true
case "vcs.modified":
isDirty = setting.Value == "true"
}
}
if isDirty && foundBuild {
build += "-dirty"
}
}
// Version returns Xray's version as a string, in the form of "x.y.z" where x, y and z are numbers. // Version returns Xray's version as a string, in the form of "x.y.z" where x, y and z are numbers.
// ".z" part may be omitted in regular releases. // ".z" part may be omitted in regular releases.
func Version() string { func Version() string {
+5
View File
@@ -13,6 +13,11 @@ type Observatory interface {
GetObservation(ctx context.Context) (proto.Message, error) GetObservation(ctx context.Context) (proto.Message, error)
} }
type BurstObservatory interface {
Observatory
Check(tag []string)
}
func ObservatoryType() interface{} { func ObservatoryType() interface{} {
return (*Observatory)(nil) return (*Observatory)(nil)
} }
+7 -5
View File
@@ -25,14 +25,16 @@ type Counter interface {
// //
// xray:api:stable // xray:api:stable
type OnlineMap interface { type OnlineMap interface {
// Count is the current value of the OnlineMap. // Count returns the number of unique online IPs.
Count() int Count() int
// AddIP adds a ip to the current OnlineMap. // AddIP increments the reference count for the given IP.
AddIP(string) AddIP(string)
// List is the current OnlineMap ip list. // RemoveIP decrements the reference count for the given IP. Deletes at zero.
RemoveIP(string)
// List returns all currently online IPs.
List() []string List() []string
// IpTimeMap return client ips and their last access time. // IPTimeMap returns a snapshot copy of IPs to their last-seen times.
IpTimeMap() map[string]time.Time IPTimeMap() map[string]time.Time
} }
// Channel is the interface for stats channel. // Channel is the interface for stats channel.
+16 -16
View File
@@ -1,32 +1,33 @@
module github.com/xtls/xray-core module github.com/xtls/xray-core
go 1.25.6 go 1.26
require ( require (
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22
github.com/cloudflare/circl v1.6.3 github.com/cloudflare/circl v1.6.3
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1 github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/klauspost/cpuid/v2 v2.3.0
github.com/miekg/dns v1.1.72 github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.9.2 github.com/pires/go-proxyproto v0.11.0
github.com/refraction-networking/utls v1.8.2 github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/sagernet/sing v0.5.1 github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.47.0 golang.org/x/crypto v0.49.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.49.0 golang.org/x/net v0.52.0
golang.org/x/sync v0.19.0 golang.org/x/sync v0.20.0
golang.org/x/sys v0.40.0 golang.org/x/sys v0.42.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
google.golang.org/grpc v1.78.0 google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3 h12.io/socks v1.0.3
@@ -39,16 +40,15 @@ require (
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/juju/ratelimit v1.0.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/mod v0.31.0 // indirect golang.org/x/mod v0.33.0 // indirect
golang.org/x/text v0.33.0 // indirect golang.org/x/text v0.35.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.40.0 // indirect golang.org/x/tools v0.42.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+44 -42
View File
@@ -1,7 +1,9 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E= github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 h1:00ziBGnLWQEcR9LThDwvxOznJJquJ9bYUdmBFnawLMU=
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU= github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22/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 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -31,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/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 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -43,14 +45,14 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U= github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y= github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
@@ -63,43 +65,43 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU= github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -107,34 +109,34 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+1 -1
View File
@@ -10,8 +10,8 @@ import (
v2net "github.com/xtls/xray-core/common/net" v2net "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/freedom" "github.com/xtls/xray-core/proxy/freedom"
"google.golang.org/protobuf/proto"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"google.golang.org/protobuf/proto"
) )
type FreedomConfig struct { type FreedomConfig struct {
+32
View File
@@ -3,7 +3,9 @@ package conf
import ( import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/proxy/hysteria" "github.com/xtls/xray-core/proxy/hysteria"
"github.com/xtls/xray-core/proxy/hysteria/account"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -27,3 +29,33 @@ func (c *HysteriaClientConfig) Build() (proto.Message, error) {
return config, nil return config, nil
} }
type HysteriaUserConfig struct {
Auth string `json:"auth"`
Level uint32 `json:"level"`
Email string `json:"email"`
}
type HysteriaServerConfig struct {
Version int32 `json:"version"`
Users []*HysteriaUserConfig `json:"clients"`
}
func (c *HysteriaServerConfig) Build() (proto.Message, error) {
config := new(hysteria.ServerConfig)
if c.Users != nil {
for _, user := range c.Users {
account := &account.Account{
Auth: user.Auth,
}
config.Users = append(config.Users, &protocol.User{
Email: user.Email,
Level: user.Level,
Account: serial.ToTypedMessage(account),
})
}
}
return config, nil
}
+31 -16
View File
@@ -522,25 +522,32 @@ func ToCidrList(ips StringList) ([]*router.GeoIP, error) {
return geoipList, nil return geoipList, nil
} }
type WebhookRuleConfig struct {
URL string `json:"url"`
Deduplication uint32 `json:"deduplication"`
Headers map[string]string `json:"headers"`
}
func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
type RawFieldRule struct { type RawFieldRule struct {
RouterRule RouterRule
Domain *StringList `json:"domain"` Domain *StringList `json:"domain"`
Domains *StringList `json:"domains"` Domains *StringList `json:"domains"`
IP *StringList `json:"ip"` IP *StringList `json:"ip"`
Port *PortList `json:"port"` Port *PortList `json:"port"`
Network *NetworkList `json:"network"` Network *NetworkList `json:"network"`
SourceIP *StringList `json:"sourceIP"` SourceIP *StringList `json:"sourceIP"`
Source *StringList `json:"source"` Source *StringList `json:"source"`
SourcePort *PortList `json:"sourcePort"` SourcePort *PortList `json:"sourcePort"`
User *StringList `json:"user"` User *StringList `json:"user"`
VlessRoute *PortList `json:"vlessRoute"` VlessRoute *PortList `json:"vlessRoute"`
InboundTag *StringList `json:"inboundTag"` InboundTag *StringList `json:"inboundTag"`
Protocols *StringList `json:"protocol"` Protocols *StringList `json:"protocol"`
Attributes map[string]string `json:"attrs"` Attributes map[string]string `json:"attrs"`
LocalIP *StringList `json:"localIP"` LocalIP *StringList `json:"localIP"`
LocalPort *PortList `json:"localPort"` LocalPort *PortList `json:"localPort"`
Process *StringList `json:"process"` Process *StringList `json:"process"`
Webhook *WebhookRuleConfig `json:"webhook"`
} }
rawFieldRule := new(RawFieldRule) rawFieldRule := new(RawFieldRule)
err := json.Unmarshal(msg, rawFieldRule) err := json.Unmarshal(msg, rawFieldRule)
@@ -657,6 +664,14 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
rule.Process = *rawFieldRule.Process rule.Process = *rawFieldRule.Process
} }
if rawFieldRule.Webhook != nil && rawFieldRule.Webhook.URL != "" {
rule.Webhook = &router.WebhookConfig{
Url: rawFieldRule.Webhook.URL,
Deduplication: rawFieldRule.Webhook.Deduplication,
Headers: rawFieldRule.Webhook.Headers,
}
}
return rule, nil return rule, nil
} }
+31 -5
View File
@@ -4,6 +4,7 @@ import (
"sort" "sort"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/transport/internet/headers/http" "github.com/xtls/xray-core/transport/internet/headers/http"
"github.com/xtls/xray-core/transport/internet/headers/noop" "github.com/xtls/xray-core/transport/internet/headers/noop"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@@ -40,11 +41,36 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
Value: []string{"www.baidu.com", "www.bing.com"}, Value: []string{"www.baidu.com", "www.bing.com"},
}, },
{ {
Name: "User-Agent", Name: "User-Agent",
Value: []string{ Value: []string{utils.ChromeUA},
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", },
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46", {
}, Name: "Sec-CH-UA",
Value: []string{utils.ChromeUACH},
},
{
Name: "Sec-CH-UA-Mobile",
Value: []string{"?0"},
},
{
Name: "Sec-CH-UA-Platform",
Value: []string{"Windows"},
},
{
Name: "Sec-Fetch-Mode",
Value: []string{"no-cors", "cors", "same-origin"},
},
{
Name: "Sec-Fetch-Dest",
Value: []string{"empty"},
},
{
Name: "Sec-Fetch-Site",
Value: []string{"none"},
},
{
Name: "Sec-Fetch-User",
Value: []string{"?1"},
}, },
{ {
Name: "Accept-Encoding", Name: "Accept-Encoding",
+546 -120
View File
@@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"math" "math"
"net/url" "net/url"
"os"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@@ -18,6 +19,8 @@ import (
"github.com/xtls/xray-core/common/platform/filesystem" "github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns" "github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls" "github.com/xtls/xray-core/transport/internet/finalmask/header/dtls"
"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp" "github.com/xtls/xray-core/transport/internet/finalmask/header/srtp"
@@ -26,7 +29,9 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard" "github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
"github.com/xtls/xray-core/transport/internet/finalmask/salamander" "github.com/xtls/xray-core/transport/internet/finalmask/salamander"
finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
"github.com/xtls/xray-core/transport/internet/finalmask/xdns" "github.com/xtls/xray-core/transport/internet/finalmask/xdns"
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp" "github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
"github.com/xtls/xray-core/transport/internet/httpupgrade" "github.com/xtls/xray-core/transport/internet/httpupgrade"
@@ -72,7 +77,7 @@ func (c *KCPConfig) Build() (proto.Message, error) {
} }
if c.Tti != nil { if c.Tti != nil {
tti := *c.Tti tti := *c.Tti
if tti < 10 || tti > 100 { if tti < 10 || tti > 5000 {
return nil, errors.New("invalid mKCP TTI: ", tti).AtError() return nil, errors.New("invalid mKCP TTI: ", tti).AtError()
} }
config.Tti = &kcp.TTI{Value: tti} config.Tti = &kcp.TTI{Value: tti}
@@ -230,13 +235,14 @@ type SplitHTTPConfig struct {
SeqKey string `json:"seqKey"` SeqKey string `json:"seqKey"`
UplinkDataPlacement string `json:"uplinkDataPlacement"` UplinkDataPlacement string `json:"uplinkDataPlacement"`
UplinkDataKey string `json:"uplinkDataKey"` UplinkDataKey string `json:"uplinkDataKey"`
UplinkChunkSize uint32 `json:"uplinkChunkSize"` UplinkChunkSize Int32Range `json:"uplinkChunkSize"`
NoGRPCHeader bool `json:"noGRPCHeader"` NoGRPCHeader bool `json:"noGRPCHeader"`
NoSSEHeader bool `json:"noSSEHeader"` NoSSEHeader bool `json:"noSSEHeader"`
ScMaxEachPostBytes Int32Range `json:"scMaxEachPostBytes"` ScMaxEachPostBytes Int32Range `json:"scMaxEachPostBytes"`
ScMinPostsIntervalMs Int32Range `json:"scMinPostsIntervalMs"` ScMinPostsIntervalMs Int32Range `json:"scMinPostsIntervalMs"`
ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"` ScMaxBufferedPosts int64 `json:"scMaxBufferedPosts"`
ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"` ScStreamUpServerSecs Int32Range `json:"scStreamUpServerSecs"`
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
Xmux XmuxConfig `json:"xmux"` Xmux XmuxConfig `json:"xmux"`
DownloadSettings *StreamConfig `json:"downloadSettings"` DownloadSettings *StreamConfig `json:"downloadSettings"`
Extra json.RawMessage `json:"extra"` Extra json.RawMessage `json:"extra"`
@@ -316,9 +322,9 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
switch c.UplinkDataPlacement { switch c.UplinkDataPlacement {
case "": case "":
c.UplinkDataPlacement = "body" c.UplinkDataPlacement = splithttp.PlacementAuto
case "body": case splithttp.PlacementAuto, splithttp.PlacementBody:
case "cookie", "header": case splithttp.PlacementCookie, splithttp.PlacementHeader:
if c.Mode != "packet-up" { if c.Mode != "packet-up" {
return nil, errors.New("UplinkDataPlacement can be " + c.UplinkDataPlacement + " only in packet-up mode") return nil, errors.New("UplinkDataPlacement can be " + c.UplinkDataPlacement + " only in packet-up mode")
} }
@@ -347,9 +353,6 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
case "": case "":
c.SeqPlacement = "path" c.SeqPlacement = "path"
case "path", "cookie", "header", "query": case "path", "cookie", "header", "query":
if c.SessionPlacement == "path" {
return nil, errors.New("SeqPlacement must be path when SessionPlacement is path")
}
default: default:
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement) return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
} }
@@ -372,24 +375,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
} }
} }
if c.UplinkDataPlacement != "body" && c.UplinkDataKey == "" { if c.UplinkDataPlacement != splithttp.PlacementBody && c.UplinkDataKey == "" {
switch c.UplinkDataPlacement { switch c.UplinkDataPlacement {
case "cookie": case splithttp.PlacementCookie:
c.UplinkDataKey = "x_data" c.UplinkDataKey = "x_data"
case "header": case splithttp.PlacementAuto, splithttp.PlacementHeader:
c.UplinkDataKey = "X-Data" c.UplinkDataKey = "X-Data"
} }
} }
if c.UplinkChunkSize == 0 { if c.ServerMaxHeaderBytes < 0 {
switch c.UplinkDataPlacement { return nil, errors.New("invalid negative value of maxHeaderBytes")
case "cookie":
c.UplinkChunkSize = 3 * 1024 // 3KB
case "header":
c.UplinkChunkSize = 4 * 1024 // 4KB
}
} else if c.UplinkChunkSize < 64 {
c.UplinkChunkSize = 64
} }
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 { if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
@@ -422,13 +418,14 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
SeqKey: c.SeqKey, SeqKey: c.SeqKey,
UplinkDataPlacement: c.UplinkDataPlacement, UplinkDataPlacement: c.UplinkDataPlacement,
UplinkDataKey: c.UplinkDataKey, UplinkDataKey: c.UplinkDataKey,
UplinkChunkSize: c.UplinkChunkSize, UplinkChunkSize: newRangeConfig(c.UplinkChunkSize),
NoGRPCHeader: c.NoGRPCHeader, NoGRPCHeader: c.NoGRPCHeader,
NoSSEHeader: c.NoSSEHeader, NoSSEHeader: c.NoSSEHeader,
ScMaxEachPostBytes: newRangeConfig(c.ScMaxEachPostBytes), ScMaxEachPostBytes: newRangeConfig(c.ScMaxEachPostBytes),
ScMinPostsIntervalMs: newRangeConfig(c.ScMinPostsIntervalMs), ScMinPostsIntervalMs: newRangeConfig(c.ScMinPostsIntervalMs),
ScMaxBufferedPosts: c.ScMaxBufferedPosts, ScMaxBufferedPosts: c.ScMaxBufferedPosts,
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs), ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
Xmux: &splithttp.XmuxConfig{ Xmux: &splithttp.XmuxConfig{
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency), MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
MaxConnections: newRangeConfig(c.Xmux.MaxConnections), MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
@@ -504,25 +501,35 @@ func (b Bandwidth) Bps() (uint64, error) {
} }
type UdpHop struct { type UdpHop struct {
PortList json.RawMessage `json:"port"` PortList json.RawMessage `json:"ports"`
Interval *Int32Range `json:"interval"` Interval *Int32Range `json:"interval"`
} }
type HysteriaConfig struct { type Masquerade struct {
Version int32 `json:"version"` Type string `json:"type"`
Auth string `json:"auth"`
Congestion string `json:"congestion"`
Up Bandwidth `json:"up"`
Down Bandwidth `json:"down"`
UdpHop UdpHop `json:"udphop"`
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"` Dir string `json:"dir"`
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"` Url string `json:"url"`
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"` RewriteHost bool `json:"rewriteHost"`
MaxIdleTimeout int64 `json:"maxIdleTimeout"` Insecure bool `json:"insecure"`
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"` Content string `json:"content"`
Headers map[string]string `json:"headers"`
StatusCode int32 `json:"statusCode"`
}
type HysteriaConfig struct {
Version int32 `json:"version"`
Auth string `json:"auth"`
Congestion *string `json:"congestion"`
Up *Bandwidth `json:"up"`
Down *Bandwidth `json:"down"`
UdpHop *UdpHop `json:"udphop"`
UdpIdleTimeout int64 `json:"udpIdleTimeout"`
Masquerade Masquerade `json:"masquerade"`
} }
func (c *HysteriaConfig) Build() (proto.Message, error) { func (c *HysteriaConfig) Build() (proto.Message, error) {
@@ -530,95 +537,30 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
return nil, errors.New("version != 2") return nil, errors.New("version != 2")
} }
up, err := c.Up.Bps() if c.Congestion != nil || c.Up != nil || c.Down != nil || c.UdpHop != nil {
if err != nil { errors.LogWarning(context.Background(), "congestion & up & down & udphop move to finalmask/quicParams")
return nil, err
}
down, err := c.Down.Bps()
if err != nil {
return nil, err
} }
c.Congestion = strings.ToLower(c.Congestion) if c.UdpIdleTimeout != 0 && (c.UdpIdleTimeout < 2 || c.UdpIdleTimeout > 600) {
if c.Congestion == "force-brutal" && up == 0 { return nil, errors.New("UdpIdleTimeout must be between 2 and 600")
return nil, errors.New("force-brutal require up")
}
var hop *PortList
if err := json.Unmarshal(c.UdpHop.PortList, &hop); err != nil {
hop = &PortList{}
}
var inertvalMin, inertvalMax int64
if c.UdpHop.Interval != nil {
inertvalMin = int64(c.UdpHop.Interval.From)
inertvalMax = int64(c.UdpHop.Interval.To)
}
if up > 0 && up < 65536 {
return nil, errors.New("Up must be at least 65536 Bps")
}
if down > 0 && down < 65536 {
return nil, errors.New("Down must be at least 65536 Bps")
}
if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
return nil, errors.New("Interval must be at least 5")
}
if c.InitStreamReceiveWindow > 0 && c.InitStreamReceiveWindow < 16384 {
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
}
if c.MaxStreamReceiveWindow > 0 && c.MaxStreamReceiveWindow < 16384 {
return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
}
if c.InitConnectionReceiveWindow > 0 && c.InitConnectionReceiveWindow < 16384 {
return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
}
if c.MaxConnectionReceiveWindow > 0 && c.MaxConnectionReceiveWindow < 16384 {
return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
}
if c.MaxIdleTimeout != 0 && (c.MaxIdleTimeout < 4 || c.MaxIdleTimeout > 120) {
return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
}
if c.KeepAlivePeriod != 0 && (c.KeepAlivePeriod < 2 || c.KeepAlivePeriod > 60) {
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
} }
config := &hysteria.Config{} config := &hysteria.Config{}
config.Version = c.Version config.Version = c.Version
config.Auth = c.Auth config.Auth = c.Auth
config.Congestion = c.Congestion config.UdpIdleTimeout = c.UdpIdleTimeout
config.Up = up config.MasqType = c.Masquerade.Type
config.Down = down config.MasqFile = c.Masquerade.Dir
config.Ports = hop.Build().Ports() config.MasqUrl = c.Masquerade.Url
config.IntervalMin = inertvalMin config.MasqUrlRewriteHost = c.Masquerade.RewriteHost
config.IntervalMax = inertvalMax config.MasqUrlInsecure = c.Masquerade.Insecure
config.InitStreamReceiveWindow = c.InitStreamReceiveWindow config.MasqString = c.Masquerade.Content
config.MaxStreamReceiveWindow = c.MaxStreamReceiveWindow config.MasqStringHeaders = c.Masquerade.Headers
config.InitConnReceiveWindow = c.InitConnectionReceiveWindow config.MasqStringStatusCode = c.Masquerade.StatusCode
config.MaxConnReceiveWindow = c.MaxConnectionReceiveWindow
config.MaxIdleTimeout = c.MaxIdleTimeout
config.KeepAlivePeriod = c.KeepAlivePeriod
config.DisablePathMtuDiscovery = c.DisablePathMTUDiscovery
if config.InitStreamReceiveWindow == 0 { if config.UdpIdleTimeout == 0 {
config.InitStreamReceiveWindow = 8388608 config.UdpIdleTimeout = 60
} }
if config.MaxStreamReceiveWindow == 0 {
config.MaxStreamReceiveWindow = 8388608
}
if config.InitConnReceiveWindow == 0 {
config.InitConnReceiveWindow = 8388608 * 5 / 2
}
if config.MaxConnReceiveWindow == 0 {
config.MaxConnReceiveWindow = 8388608 * 5 / 2
}
if config.MaxIdleTimeout == 0 {
config.MaxIdleTimeout = 30
}
// if config.KeepAlivePeriod == 0 {
// config.KeepAlivePeriod = 10
// }
return config, nil return config, nil
} }
@@ -685,6 +627,22 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
return certificate, nil return certificate, nil
} }
type QuicParamsConfig struct {
Congestion string `json:"congestion"`
Debug bool `json:"debug"`
BrutalUp Bandwidth `json:"brutalUp"`
BrutalDown Bandwidth `json:"brutalDown"`
UdpHop UdpHop `json:"udpHop"`
InitStreamReceiveWindow uint64 `json:"initStreamReceiveWindow"`
MaxStreamReceiveWindow uint64 `json:"maxStreamReceiveWindow"`
InitConnectionReceiveWindow uint64 `json:"initConnectionReceiveWindow"`
MaxConnectionReceiveWindow uint64 `json:"maxConnectionReceiveWindow"`
MaxIdleTimeout int64 `json:"maxIdleTimeout"`
KeepAlivePeriod int64 `json:"keepAlivePeriod"`
DisablePathMTUDiscovery bool `json:"disablePathMTUDiscovery"`
MaxIncomingStreams int64 `json:"maxIncomingStreams"`
}
type TLSConfig struct { type TLSConfig struct {
AllowInsecure bool `json:"allowInsecure"` AllowInsecure bool `json:"allowInsecure"`
Certs []*TLSCertConfig `json:"certificates"` Certs []*TLSCertConfig `json:"certificates"`
@@ -951,6 +909,12 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
} }
} }
for _, sn := range config.ServerNames {
if strings.Contains(sn, "apple") || strings.Contains(sn, "icloud") {
errors.LogWarning(context.Background(), `REALITY: Choosing apple, icloud, etc. as the target may get your IP blocked by the GFW`)
}
}
config.LimitFallbackUpload = new(reality.LimitFallback) config.LimitFallbackUpload = new(reality.LimitFallback)
config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes
config.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec config.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec
@@ -982,7 +946,7 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
if len(c.ShortIds) != 0 { if len(c.ShortIds) != 0 {
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`) return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
} }
if len(c.ShortIds) > 16 { if len(c.ShortId) > 16 {
return nil, errors.New(`too long "shortId": `, c.ShortId) return nil, errors.New(`too long "shortId": `, c.ShortId)
} }
config.ShortId = make([]byte, 8) config.ShortId = make([]byte, 8)
@@ -1236,8 +1200,49 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
}, nil }, nil
} }
func PraseByteSlice(data json.RawMessage, typ string) ([]byte, error) {
switch strings.ToLower(typ) {
case "", "array":
if len(data) == 0 {
return data, nil
}
var packet []byte
if err := json.Unmarshal(data, &packet); err != nil {
return nil, err
}
return packet, nil
case "str":
var str string
if err := json.Unmarshal(data, &str); err != nil {
return nil, err
}
return []byte(str), nil
case "hex":
var str string
if err := json.Unmarshal(data, &str); err != nil {
return nil, err
}
return hex.DecodeString(str)
case "base64":
var str string
if err := json.Unmarshal(data, &str); err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(str)
default:
return nil, errors.New("unknown type")
}
}
var ( var (
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
"fragment": func() interface{} { return new(FragmentMask) },
"sudoku": func() interface{} { return new(Sudoku) },
}, "type", "settings")
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
"header-dns": func() interface{} { return new(Dns) }, "header-dns": func() interface{} { return new(Dns) },
"header-dtls": func() interface{} { return new(Dtls) }, "header-dtls": func() interface{} { return new(Dtls) },
"header-srtp": func() interface{} { return new(Srtp) }, "header-srtp": func() interface{} { return new(Srtp) },
@@ -1246,12 +1251,299 @@ var (
"header-wireguard": func() interface{} { return new(Wireguard) }, "header-wireguard": func() interface{} { return new(Wireguard) },
"mkcp-original": func() interface{} { return new(Original) }, "mkcp-original": func() interface{} { return new(Original) },
"mkcp-aes128gcm": func() interface{} { return new(Aes128Gcm) }, "mkcp-aes128gcm": func() interface{} { return new(Aes128Gcm) },
"noise": func() interface{} { return new(NoiseMask) },
"salamander": func() interface{} { return new(Salamander) }, "salamander": func() interface{} { return new(Salamander) },
"sudoku": func() interface{} { return new(Sudoku) },
"xdns": func() interface{} { return new(Xdns) }, "xdns": func() interface{} { return new(Xdns) },
"xicmp": func() interface{} { return new(Xicmp) }, "xicmp": func() interface{} { return new(Xicmp) },
}, "type", "settings") }, "type", "settings")
) )
type TCPItem struct {
Delay Int32Range `json:"delay"`
Rand int32 `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
}
type HeaderCustomTCP struct {
Clients [][]TCPItem `json:"clients"`
Servers [][]TCPItem `json:"servers"`
Errors [][]TCPItem `json:"errors"`
}
func (c *HeaderCustomTCP) Build() (proto.Message, error) {
for _, value := range c.Clients {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
}
for _, value := range c.Servers {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
}
for _, value := range c.Errors {
for _, item := range value {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
}
errInvalidRange := errors.New("invalid randRange")
clients := make([]*custom.TCPSequence, len(c.Clients))
for i, value := range c.Clients {
clients[i] = &custom.TCPSequence{}
for _, item := range value {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errInvalidRange
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
clients[i].Sequence = append(clients[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
})
}
}
servers := make([]*custom.TCPSequence, len(c.Servers))
for i, value := range c.Servers {
servers[i] = &custom.TCPSequence{}
for _, item := range value {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errInvalidRange
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
servers[i].Sequence = append(servers[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
})
}
}
errors := make([]*custom.TCPSequence, len(c.Errors))
for i, value := range c.Errors {
errors[i] = &custom.TCPSequence{}
for _, item := range value {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errInvalidRange
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
errors[i].Sequence = append(errors[i].Sequence, &custom.TCPItem{
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
})
}
}
return &custom.TCPConfig{
Clients: clients,
Servers: servers,
Errors: errors,
}, nil
}
type FragmentMask struct {
Packets string `json:"packets"`
Length Int32Range `json:"length"`
Delay Int32Range `json:"delay"`
MaxSplit Int32Range `json:"maxSplit"`
}
func (c *FragmentMask) Build() (proto.Message, error) {
config := &fragment.Config{}
switch strings.ToLower(c.Packets) {
case "tlshello":
config.PacketsFrom = 0
config.PacketsTo = 1
case "":
config.PacketsFrom = 0
config.PacketsTo = 0
default:
from, to, err := ParseRangeString(c.Packets)
if err != nil {
return nil, errors.New("Invalid PacketsFrom").Base(err)
}
config.PacketsFrom = int64(from)
config.PacketsTo = int64(to)
if config.PacketsFrom == 0 {
return nil, errors.New("PacketsFrom can't be 0")
}
}
config.LengthMin = int64(c.Length.From)
config.LengthMax = int64(c.Length.To)
if config.LengthMin == 0 {
return nil, errors.New("LengthMin can't be 0")
}
config.DelayMin = int64(c.Delay.From)
config.DelayMax = int64(c.Delay.To)
config.MaxSplitMin = int64(c.MaxSplit.From)
config.MaxSplitMax = int64(c.MaxSplit.To)
return config, nil
}
type NoiseItem struct {
Rand Int32Range `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
Delay Int32Range `json:"delay"`
}
type NoiseMask struct {
Reset Int32Range `json:"reset"`
Noise []NoiseItem `json:"noise"`
}
func (c *NoiseMask) Build() (proto.Message, error) {
for _, item := range c.Noise {
if len(item.Packet) > 0 && item.Rand.To > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand.To > 0")
}
}
noiseSlice := make([]*noise.Item, 0, len(c.Noise))
for _, item := range c.Noise {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errors.New("invalid randRange")
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
noiseSlice = append(noiseSlice, &noise.Item{
RandMin: int64(item.Rand.From),
RandMax: int64(item.Rand.To),
RandRangeMin: item.RandRange.From,
RandRangeMax: item.RandRange.To,
Packet: item.Packet,
DelayMin: int64(item.Delay.From),
DelayMax: int64(item.Delay.To),
})
}
return &noise.Config{
ResetMin: int64(c.Reset.From),
ResetMax: int64(c.Reset.To),
Items: noiseSlice,
}, nil
}
type UDPItem struct {
Rand int32 `json:"rand"`
RandRange *Int32Range `json:"randRange"`
Type string `json:"type"`
Packet json.RawMessage `json:"packet"`
}
type HeaderCustomUDP struct {
Client []UDPItem `json:"client"`
Server []UDPItem `json:"server"`
}
func (c *HeaderCustomUDP) Build() (proto.Message, error) {
for _, item := range c.Client {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
for _, item := range c.Server {
if len(item.Packet) > 0 && item.Rand > 0 {
return nil, errors.New("len(item.Packet) > 0 && item.Rand > 0")
}
}
client := make([]*custom.UDPItem, 0, len(c.Client))
for _, item := range c.Client {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errors.New("invalid randRange")
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
client = append(client, &custom.UDPItem{
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
})
}
server := make([]*custom.UDPItem, 0, len(c.Server))
for _, item := range c.Server {
if item.RandRange == nil {
item.RandRange = &Int32Range{From: 0, To: 255}
}
if item.RandRange.From < 0 || item.RandRange.To > 255 {
return nil, errors.New("invalid randRange")
}
var err error
if item.Packet, err = PraseByteSlice(item.Packet, item.Type); err != nil {
return nil, err
}
server = append(server, &custom.UDPItem{
Rand: item.Rand,
RandMin: item.RandRange.From,
RandMax: item.RandRange.To,
Packet: item.Packet,
})
}
return &custom.UDPConfig{
Client: client,
Server: server,
}, nil
}
type Dns struct { type Dns struct {
Domain string `json:"domain"` Domain string `json:"domain"`
} }
@@ -1323,6 +1615,50 @@ func (c *Salamander) Build() (proto.Message, error) {
return config, nil return config, nil
} }
type Sudoku struct {
Password string `json:"password"`
ASCII string `json:"ascii"`
CustomTable string `json:"customTable"`
LegacyCustomTable string `json:"custom_table"`
CustomTables []string `json:"customTables"`
LegacyCustomSets []string `json:"custom_tables"`
PaddingMin uint32 `json:"paddingMin"`
LegacyPaddingMin uint32 `json:"padding_min"`
PaddingMax uint32 `json:"paddingMax"`
LegacyPaddingMax uint32 `json:"padding_max"`
}
func (c *Sudoku) Build() (proto.Message, error) {
customTable := c.CustomTable
if customTable == "" {
customTable = c.LegacyCustomTable
}
customTables := c.CustomTables
if len(customTables) == 0 {
customTables = c.LegacyCustomSets
}
paddingMin := c.PaddingMin
if paddingMin == 0 {
paddingMin = c.LegacyPaddingMin
}
paddingMax := c.PaddingMax
if paddingMax == 0 {
paddingMax = c.LegacyPaddingMax
}
return &finalsudoku.Config{
Password: c.Password,
Ascii: c.ASCII,
CustomTable: customTable,
CustomTables: customTables,
PaddingMin: paddingMin,
PaddingMax: paddingMax,
}, nil
}
type Xdns struct { type Xdns struct {
Domain string `json:"domain"` Domain string `json:"domain"`
} }
@@ -1363,7 +1699,7 @@ type Mask struct {
func (c *Mask) Build(tcp bool) (proto.Message, error) { func (c *Mask) Build(tcp bool) (proto.Message, error) {
loader := udpmaskLoader loader := udpmaskLoader
if tcp { if tcp {
return nil, errors.New("") loader = tcpmaskLoader
} }
settings := []byte("{}") settings := []byte("{}")
@@ -1382,8 +1718,9 @@ func (c *Mask) Build(tcp bool) (proto.Message, error) {
} }
type FinalMask struct { type FinalMask struct {
Tcp []Mask `json:"tcp"` Tcp []Mask `json:"tcp"`
Udp []Mask `json:"udp"` Udp []Mask `json:"udp"`
QuicParams *QuicParamsConfig `json:"quicParams"`
} }
type StreamConfig struct { type StreamConfig struct {
@@ -1556,6 +1893,95 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
} }
config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u)) config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u))
} }
if c.FinalMask.QuicParams != nil {
up, err := c.FinalMask.QuicParams.BrutalUp.Bps()
if err != nil {
return nil, err
}
down, err := c.FinalMask.QuicParams.BrutalDown.Bps()
if err != nil {
return nil, err
}
if up > 0 && up < 65536 {
return nil, errors.New("BrutalUp must be at least 65536 bytes per second")
}
if down > 0 && down < 65536 {
return nil, errors.New("BrutalDown must be at least 65536 bytes per second")
}
c.FinalMask.QuicParams.Congestion = strings.ToLower(c.FinalMask.QuicParams.Congestion)
switch c.FinalMask.QuicParams.Congestion {
case "", "brutal", "reno", "bbr":
case "force-brutal":
if up == 0 {
return nil, errors.New("force-brutal requires up")
}
default:
return nil, errors.New("unknown congestion control: ", c.FinalMask.QuicParams.Congestion, ", valid values: reno, bbr, brutal, force-brutal")
}
var hop *PortList
if err := json.Unmarshal(c.FinalMask.QuicParams.UdpHop.PortList, &hop); err != nil {
hop = &PortList{}
}
var inertvalMin, inertvalMax int64
if c.FinalMask.QuicParams.UdpHop.Interval != nil {
inertvalMin = int64(c.FinalMask.QuicParams.UdpHop.Interval.From)
inertvalMax = int64(c.FinalMask.QuicParams.UdpHop.Interval.To)
}
if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) {
return nil, errors.New("Interval must be at least 5")
}
if c.FinalMask.QuicParams.InitStreamReceiveWindow > 0 && c.FinalMask.QuicParams.InitStreamReceiveWindow < 16384 {
return nil, errors.New("InitStreamReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.MaxStreamReceiveWindow > 0 && c.FinalMask.QuicParams.MaxStreamReceiveWindow < 16384 {
return nil, errors.New("MaxStreamReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.InitConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.InitConnectionReceiveWindow < 16384 {
return nil, errors.New("InitConnectionReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.MaxConnectionReceiveWindow > 0 && c.FinalMask.QuicParams.MaxConnectionReceiveWindow < 16384 {
return nil, errors.New("MaxConnectionReceiveWindow must be at least 16384")
}
if c.FinalMask.QuicParams.MaxIdleTimeout != 0 && (c.FinalMask.QuicParams.MaxIdleTimeout < 4 || c.FinalMask.QuicParams.MaxIdleTimeout > 120) {
return nil, errors.New("MaxIdleTimeout must be between 4 and 120")
}
if c.FinalMask.QuicParams.KeepAlivePeriod != 0 && (c.FinalMask.QuicParams.KeepAlivePeriod < 2 || c.FinalMask.QuicParams.KeepAlivePeriod > 60) {
return nil, errors.New("KeepAlivePeriod must be between 2 and 60")
}
if c.FinalMask.QuicParams.MaxIncomingStreams != 0 && c.FinalMask.QuicParams.MaxIncomingStreams < 8 {
return nil, errors.New("MaxIncomingStreams must be at least 8")
}
if c.FinalMask.QuicParams.Debug {
os.Setenv("HYSTERIA_BBR_DEBUG", "true")
os.Setenv("HYSTERIA_BRUTAL_DEBUG", "true")
}
config.QuicParams = &internet.QuicParams{
Congestion: c.FinalMask.QuicParams.Congestion,
BrutalUp: up,
BrutalDown: down,
UdpHop: &internet.UdpHop{
Ports: hop.Build().Ports(),
IntervalMin: inertvalMin,
IntervalMax: inertvalMax,
},
InitStreamReceiveWindow: c.FinalMask.QuicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: c.FinalMask.QuicParams.MaxStreamReceiveWindow,
InitConnReceiveWindow: c.FinalMask.QuicParams.InitConnectionReceiveWindow,
MaxConnReceiveWindow: c.FinalMask.QuicParams.MaxConnectionReceiveWindow,
MaxIdleTimeout: c.FinalMask.QuicParams.MaxIdleTimeout,
KeepAlivePeriod: c.FinalMask.QuicParams.KeepAlivePeriod,
DisablePathMtuDiscovery: c.FinalMask.QuicParams.DisablePathMTUDiscovery,
MaxIncomingStreams: c.FinalMask.QuicParams.MaxIncomingStreams,
}
}
} }
return config, nil return config, nil
+41 -13
View File
@@ -42,7 +42,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
config := new(inbound.Config) config := new(inbound.Config)
config.Clients = make([]*protocol.User, len(c.Clients)) config.Clients = make([]*protocol.User, len(c.Clients))
switch c.Flow { switch c.Flow {
case "", vless.XRV: case vless.XRV, "":
default: default:
return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`) return nil, errors.New(`VLESS "settings.flow" doesn't support "` + c.Flow + `" in this version`)
} }
@@ -69,9 +69,6 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
default: default:
return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`) return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`)
} }
if account.Flow == "" {
errors.PrintNonRemovalDeprecatedFeatureWarning("VLESS (with no Flow, etc.)", "VLESS with Flow & Seed")
}
if len(account.Testseed) < 4 { if len(account.Testseed) < 4 {
account.Testseed = c.Testseed account.Testseed = c.Testseed
@@ -81,8 +78,13 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`) return nil, errors.New(`VLESS clients: "encryption" should not be in inbound settings`)
} }
if account.Reverse != nil && account.Reverse.Tag == "" { if account.Reverse != nil {
return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`) if account.Reverse.Tag == "" {
return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`)
}
if account.Reverse.Sniffing != nil { // may not be reached: error json unmarshal
return nil, errors.New(`VLESS clients: inbound's "reverse" can't have "sniffing"`)
}
} }
user.Account = serial.ToTypedMessage(account) user.Account = serial.ToTypedMessage(account)
@@ -200,6 +202,28 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
return config, nil return config, nil
} }
type VLessReverseConfig struct {
Tag string `json:"tag"`
Sniffing *SniffingConfig `json:"sniffing"`
}
func (c *VLessReverseConfig) Build() (*vless.Reverse, error) {
if c.Tag == "" {
return nil, errors.New(`VLESS reverse: "tag" can't be empty`)
}
r := &vless.Reverse{
Tag: c.Tag,
}
if c.Sniffing != nil {
sc, err := c.Sniffing.Build()
if err != nil {
return nil, errors.New(`VLESS reverse: invalid "sniffing" config`).Base(err)
}
r.Sniffing = sc
}
return r, nil
}
type VLessOutboundVnext struct { type VLessOutboundVnext struct {
Address *Address `json:"address"` Address *Address `json:"address"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
@@ -215,7 +239,7 @@ type VLessOutboundConfig struct {
Flow string `json:"flow"` Flow string `json:"flow"`
Seed string `json:"seed"` Seed string `json:"seed"`
Encryption string `json:"encryption"` Encryption string `json:"encryption"`
Reverse *vless.Reverse `json:"reverse"` Reverse *VLessReverseConfig `json:"reverse"`
Testpre uint32 `json:"testpre"` Testpre uint32 `json:"testpre"`
Testseed []uint32 `json:"testseed"` Testseed []uint32 `json:"testseed"`
Vnext []*VLessOutboundVnext `json:"vnext"` Vnext []*VLessOutboundVnext `json:"vnext"`
@@ -263,13 +287,22 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
account.Flow = c.Flow account.Flow = c.Flow
//account.Seed = c.Seed //account.Seed = c.Seed
account.Encryption = c.Encryption account.Encryption = c.Encryption
account.Reverse = c.Reverse if c.Reverse != nil {
rvs, err := c.Reverse.Build()
if err != nil {
return nil, err
}
account.Reverse = rvs
}
account.Testpre = c.Testpre account.Testpre = c.Testpre
account.Testseed = c.Testseed account.Testseed = c.Testseed
} else { } else {
if err := json.Unmarshal(rawUser, account); err != nil { if err := json.Unmarshal(rawUser, account); err != nil {
return nil, errors.New(`VLESS users: invalid user`).Base(err) return nil, errors.New(`VLESS users: invalid user`).Base(err)
} }
if account.Reverse != nil { // may not be reached: error json unmarshal
return nil, errors.New(`VLESS users: please use simplified outbound's config style to use "reverse"`)
}
} }
u, err := uuid.ParseString(account.Id) u, err := uuid.ParseString(account.Id)
@@ -280,7 +313,6 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
switch account.Flow { switch account.Flow {
case "": case "":
errors.PrintNonRemovalDeprecatedFeatureWarning("VLESS (with no Flow, etc.)", "VLESS with Flow & Seed")
case vless.XRV, vless.XRV + "-udp443": case vless.XRV, vless.XRV + "-udp443":
default: default:
return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`) return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`)
@@ -330,10 +362,6 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption) return nil, errors.New(`VLESS users: unsupported "encryption": ` + account.Encryption)
} }
if account.Reverse != nil && account.Reverse.Tag == "" {
return nil, errors.New(`VLESS clients: "tag" can't be empty for "reverse"`)
}
user.Account = serial.ToTypedMessage(account) user.Account = serial.ToTypedMessage(account)
spec.User = user spec.User = user
break break
+1 -1
View File
@@ -130,7 +130,7 @@ func ParseWireGuardKey(str string) (string, error) {
return "", errors.New("key must not be empty") return "", errors.New("key must not be empty")
} }
if len(str)%2 == 0 { if len(str) == 64 {
_, err = hex.DecodeString(str) _, err = hex.DecodeString(str)
if err == nil { if err == nil {
return str, nil return str, nil
+5
View File
@@ -33,6 +33,7 @@ var (
"vmess": func() interface{} { return new(VMessInboundConfig) }, "vmess": func() interface{} { return new(VMessInboundConfig) },
"trojan": func() interface{} { return new(TrojanServerConfig) }, "trojan": func() interface{} { return new(TrojanServerConfig) },
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: false} }, "wireguard": func() interface{} { return &WireGuardConfig{IsClient: false} },
"hysteria": func() interface{} { return new(HysteriaServerConfig) },
"tun": func() interface{} { return new(TunConfig) }, "tun": func() interface{} { return new(TunConfig) },
}, "protocol", "settings") }, "protocol", "settings")
@@ -173,6 +174,10 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
return nil, err return nil, err
} }
receiverSettings.StreamSettings = ss receiverSettings.StreamSettings = ss
if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil ||
len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) {
errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`)
}
} }
if c.SniffingConfig != nil { if c.SniffingConfig != nil {
s, err := c.SniffingConfig.Build() s, err := c.SniffingConfig.Build()
+7
View File
@@ -18,6 +18,8 @@ var cmdAddRules = &base.Command{
Add routing rules to Xray. Add routing rules to Xray.
Arguments: Arguments:
<c1.json> [c2.json]...
The configs with the rules to be added. Must be in the xray config format and must have the "routing" field
-s, -server <server:port> -s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
@@ -63,6 +65,11 @@ func executeAddRules(cmd *base.Command, args []string) {
if err != nil { if err != nil {
base.Fatalf("failed to decode %s: %s", arg, err) base.Fatalf("failed to decode %s: %s", arg, err)
} }
if conf.RouterConfig == nil {
base.Fatalf("failed to add routing rule: config did not have \"routing\" field")
}
rcs = append(rcs, *conf.RouterConfig) rcs = append(rcs, *conf.RouterConfig)
} }
if len(rcs) == 0 { if len(rcs) == 0 {
+1 -1
View File
@@ -30,7 +30,7 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
fmt.Println(err) fmt.Println(err)
return return
} }
fmt.Printf("PrivateKey: %v\nPassword: %v\nHash32: %v\n", fmt.Printf("PrivateKey: %v\nPassword (PublicKey): %v\nHash32: %v\n",
encoding.EncodeToString(privateKey), encoding.EncodeToString(privateKey),
encoding.EncodeToString(password), encoding.EncodeToString(password),
encoding.EncodeToString(hash32[:])) encoding.EncodeToString(hash32[:]))
+90 -4
View File
@@ -1,11 +1,14 @@
package tls package tls
import ( import (
"crypto/ecdh"
"crypto/hpke"
"crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
"io"
"os" "os"
"github.com/xtls/reality/hpke"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/main/commands/base" "github.com/xtls/xray-core/main/commands/base"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
@@ -40,15 +43,15 @@ func executeECH(cmd *base.Command, args []string) {
// if *input_pqSignatureSchemesEnabled { // if *input_pqSignatureSchemesEnabled {
// kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00 // kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00
// } else { // } else {
kem = hpke.DHKEM_X25519_HKDF_SHA256 kem = hpke.DHKEM(ecdh.X25519()).ID()
// } // }
echConfig, priv, err := tls.GenerateECHKeySet(0, *input_serverName, kem) echConfig, priv, err := generateECHKeySet(0, *input_serverName, kem)
common.Must(err) common.Must(err)
var configBuffer, keyBuffer []byte var configBuffer, keyBuffer []byte
if *input_echServerKeys == "" { if *input_echServerKeys == "" {
configBytes, _ := tls.MarshalBinary(echConfig) configBytes, _ := marshalBinary(echConfig)
var b cryptobyte.Builder var b cryptobyte.Builder
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) { b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
child.AddBytes(configBytes) child.AddBytes(configBytes)
@@ -91,3 +94,86 @@ func executeECH(cmd *base.Command, args []string) {
os.Stdout.WriteString("ECH server keys: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n") os.Stdout.WriteString("ECH server keys: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n")
} }
} }
type EchConfig struct {
Version uint16
ConfigID uint8
KemID uint16
PublicKey []byte
SymmetricCipherSuite []EchCipher
MaxNameLength uint8
PublicName []byte
Extensions []Extension
}
type EchCipher struct {
KDFID uint16
AEADID uint16
}
type Extension struct {
Type uint16
Data []byte
}
// reference github.com/OmarTariq612/goech
func marshalBinary(ech EchConfig) ([]byte, error) {
var b cryptobyte.Builder
b.AddUint16(ech.Version)
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
child.AddUint8(ech.ConfigID)
child.AddUint16(ech.KemID)
child.AddUint16(uint16(len(ech.PublicKey)))
child.AddBytes(ech.PublicKey)
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
for _, cipherSuite := range ech.SymmetricCipherSuite {
child.AddUint16(cipherSuite.KDFID)
child.AddUint16(cipherSuite.AEADID)
}
})
child.AddUint8(ech.MaxNameLength)
child.AddUint8(uint8(len(ech.PublicName)))
child.AddBytes(ech.PublicName)
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
for _, extention := range ech.Extensions {
child.AddUint16(extention.Type)
child.AddBytes(extention.Data)
}
})
})
return b.Bytes()
}
const ExtensionEncryptedClientHello = 0xfe0d
func generateECHKeySet(configID uint8, domain string, kem uint16) (EchConfig, []byte, error) {
config := EchConfig{
Version: ExtensionEncryptedClientHello,
ConfigID: configID,
PublicName: []byte(domain),
KemID: kem,
SymmetricCipherSuite: []EchCipher{
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES128GCM().ID()},
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.AES256GCM().ID()},
{KDFID: hpke.HKDFSHA256().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES128GCM().ID()},
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.AES256GCM().ID()},
{KDFID: hpke.HKDFSHA384().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES128GCM().ID()},
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.AES256GCM().ID()},
{KDFID: hpke.HKDFSHA512().ID(), AEADID: hpke.ChaCha20Poly1305().ID()},
},
MaxNameLength: 0,
Extensions: nil,
}
// if kem == hpke.DHKEM_X25519_HKDF_SHA256 {
curve := ecdh.X25519()
priv := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, priv)
if err != nil {
return config, nil, err
}
privKey, _ := curve.NewPrivateKey(priv)
config.PublicKey = privKey.PublicKey().Bytes()
return config, priv, nil
}
+78
View File
@@ -0,0 +1,78 @@
package tls
import (
"bytes"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"os"
"text/tabwriter"
"github.com/xtls/xray-core/main/commands/base"
. "github.com/xtls/xray-core/transport/internet/tls"
)
var cmdHash = &base.Command{
UsageLine: "{{.Exec}} tls hash",
Short: "Calculate TLS certificate hash.",
Long: `
xray tls hash --cert <cert.pem>
Calculate TLS certificate hash.
`,
}
func init() {
cmdHash.Run = executeHash // break init loop
}
var input = cmdHash.Flag.String("cert", "fullchain.pem", "The file path of the certificate")
func executeHash(cmd *base.Command, args []string) {
fs := flag.NewFlagSet("hash", flag.ContinueOnError)
if err := fs.Parse(args); err != nil {
fmt.Println(err)
return
}
certContent, err := os.ReadFile(*input)
if err != nil {
fmt.Println(err)
return
}
var certs []*x509.Certificate
if bytes.Contains(certContent, []byte("BEGIN")) {
for {
block, remain := pem.Decode(certContent)
if block == nil {
break
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
fmt.Println("Unable to decode certificate:", err)
return
}
certs = append(certs, cert)
certContent = remain
}
} else {
certs, err = x509.ParseCertificates(certContent)
if err != nil {
fmt.Println("Unable to parse certificates:", err)
return
}
}
if len(certs) == 0 {
fmt.Println("No certificates found")
return
}
tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
for i, cert := range certs {
hash := GenerateCertHashHex(cert)
if i == 0 {
fmt.Fprintf(tabWriter, "Leaf SHA256:\t%s\n", hash)
} else {
fmt.Fprintf(tabWriter, "CA <%s> SHA256:\t%s\n", cert.Subject.CommonName, hash)
}
}
tabWriter.Flush()
}
-44
View File
@@ -1,44 +0,0 @@
package tls
import (
"flag"
"fmt"
"os"
"github.com/xtls/xray-core/main/commands/base"
"github.com/xtls/xray-core/transport/internet/tls"
)
var cmdLeafCertHash = &base.Command{
UsageLine: "{{.Exec}} tls leafCertHash",
Short: "Calculate TLS leaf certificate hash.",
Long: `
xray tls leafCertHash --cert <cert.pem>
Calculate TLS leaf certificate hash.
`,
}
func init() {
cmdLeafCertHash.Run = executeLeafCertHash // break init loop
}
var input = cmdLeafCertHash.Flag.String("cert", "fullchain.pem", "The file path of the leaf certificate")
func executeLeafCertHash(cmd *base.Command, args []string) {
fs := flag.NewFlagSet("leafCertHash", flag.ContinueOnError)
if err := fs.Parse(args); err != nil {
fmt.Println(err)
return
}
certContent, err := os.ReadFile(*input)
if err != nil {
fmt.Println(err)
return
}
certChainHashB64, err := tls.CalculatePEMLeafCertSHA256Hash(certContent)
if err != nil {
fmt.Println("failed to decode cert", err)
return
}
fmt.Println(certChainHashB64)
}
+8 -8
View File
@@ -135,15 +135,15 @@ func printCertificates(tabWriter *tabwriter.Writer, certs []*x509.Certificate) {
CAs = append(CAs, cert) CAs = append(CAs, cert)
} }
} }
fmt.Fprintf(tabWriter, "Certificate chain's total length: \t %d (certs count: %s)\n", length, strconv.Itoa(len(certs))) fmt.Fprintf(tabWriter, "Certificate chain's total length:\t%d (certs count: %s)\n", length, strconv.Itoa(len(certs)))
if leaf != nil { if leaf != nil {
fmt.Fprintf(tabWriter, "Cert's signature algorithm: \t %s\n", leaf.SignatureAlgorithm.String()) fmt.Fprintf(tabWriter, "Cert's signature algorithm:\t%s\n", leaf.SignatureAlgorithm.String())
fmt.Fprintf(tabWriter, "Cert's publicKey algorithm: \t %s\n", leaf.PublicKeyAlgorithm.String()) fmt.Fprintf(tabWriter, "Cert's publicKey algorithm:\t%s\n", leaf.PublicKeyAlgorithm.String())
fmt.Fprintf(tabWriter, "Cert's leaf SHA256: \t %s\n", hex.EncodeToString(GenerateCertHash(leaf))) fmt.Fprintf(tabWriter, "Cert's leaf SHA256:\t%s\n", hex.EncodeToString(GenerateCertHash(leaf)))
for _, ca := range CAs { for _, ca := range CAs {
fmt.Fprintf(tabWriter, "Cert's CA: %s SHA256: \t %s\n", ca.Subject.CommonName, hex.EncodeToString(GenerateCertHash(ca))) fmt.Fprintf(tabWriter, "Cert's CA <%s> SHA256:\t%s\n", ca.Subject.CommonName, hex.EncodeToString(GenerateCertHash(ca)))
} }
fmt.Fprintf(tabWriter, "Cert's allowed domains: \t %v\n", leaf.DNSNames) fmt.Fprintf(tabWriter, "Cert's allowed domains:\t%v\n", leaf.DNSNames)
} }
} }
@@ -156,11 +156,11 @@ func printTLSConnDetail(tabWriter *tabwriter.Writer, tlsConn *utls.UConn) {
case gotls.VersionTLS12: case gotls.VersionTLS12:
tlsVersion = "TLS 1.2" tlsVersion = "TLS 1.2"
} }
fmt.Fprintf(tabWriter, "TLS Version: \t %s\n", tlsVersion) fmt.Fprintf(tabWriter, "TLS Version:\t%s\n", tlsVersion)
curveID := utils.AccessField[utls.CurveID](tlsConn.Conn, "curveID") curveID := utils.AccessField[utls.CurveID](tlsConn.Conn, "curveID")
if curveID != nil { if curveID != nil {
PostQuantum := (*curveID == utls.X25519MLKEM768) PostQuantum := (*curveID == utls.X25519MLKEM768)
fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange: \t %t (%s)\n", PostQuantum, curveID.String()) fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange:\t%t (%s)\n", PostQuantum, curveID.String())
} else { } else {
fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange: false (RSA Exchange)\n") fmt.Fprintf(tabWriter, "TLS Post-Quantum key exchange: false (RSA Exchange)\n")
} }
+1 -1
View File
@@ -13,7 +13,7 @@ var CmdTLS = &base.Command{
Commands: []*base.Command{ Commands: []*base.Command{
cmdCert, cmdCert,
cmdPing, cmdPing,
cmdLeafCertHash, cmdHash,
cmdECH, cmdECH,
}, },
} }
+2
View File
@@ -21,6 +21,7 @@ import (
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
@@ -219,6 +220,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
for _, h := range header { for _, h := range header {
req.Header.Set(h.Key, h.Value) req.Header.Set(h.Key, h.Value)
} }
utils.TryDefaultHeadersWith(req.Header, "nav")
connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) { connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
req.Header.Set("Proxy-Connection", "Keep-Alive") req.Header.Set("Proxy-Connection", "Keep-Alive")
+129
View File
@@ -0,0 +1,129 @@
package account
import (
"sync"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"google.golang.org/protobuf/proto"
)
func (a *Account) AsAccount() (protocol.Account, error) {
return &MemoryAccount{
Auth: a.Auth,
}, nil
}
type MemoryAccount struct {
Auth string
}
func (a *MemoryAccount) Equals(another protocol.Account) bool {
if account, ok := another.(*MemoryAccount); ok {
return a.Auth == account.Auth
}
return false
}
func (a *MemoryAccount) ToProto() proto.Message {
return &Account{
Auth: a.Auth,
}
}
type Validator struct {
emails map[string]struct{}
users map[string]*protocol.MemoryUser
mutex sync.Mutex
}
func NewValidator() *Validator {
return &Validator{
emails: make(map[string]struct{}),
users: make(map[string]*protocol.MemoryUser),
}
}
func (v *Validator) Add(u *protocol.MemoryUser) error {
v.mutex.Lock()
defer v.mutex.Unlock()
if u.Email != "" {
if _, ok := v.emails[u.Email]; ok {
return errors.New("User ", u.Email, " already exists.")
}
v.emails[u.Email] = struct{}{}
}
v.users[u.Account.(*MemoryAccount).Auth] = u
return nil
}
func (v *Validator) Del(email string) error {
if email == "" {
return errors.New("Email must not be empty.")
}
v.mutex.Lock()
defer v.mutex.Unlock()
if _, ok := v.emails[email]; !ok {
return errors.New("User ", email, " not found.")
}
delete(v.emails, email)
for key, user := range v.users {
if user.Email == email {
delete(v.users, key)
break
}
}
return nil
}
func (v *Validator) Get(auth string) *protocol.MemoryUser {
v.mutex.Lock()
defer v.mutex.Unlock()
return v.users[auth]
}
func (v *Validator) GetByEmail(email string) *protocol.MemoryUser {
if email == "" {
return nil
}
v.mutex.Lock()
defer v.mutex.Unlock()
if _, ok := v.emails[email]; ok {
for _, user := range v.users {
if user.Email == email {
return user
}
}
}
return nil
}
func (v *Validator) GetAll() []*protocol.MemoryUser {
v.mutex.Lock()
defer v.mutex.Unlock()
var users = make([]*protocol.MemoryUser, 0, len(v.users))
for _, user := range v.users {
users = append(users, user)
}
return users
}
func (v *Validator) GetCount() int64 {
v.mutex.Lock()
defer v.mutex.Unlock()
return int64(len(v.users))
}
+123
View File
@@ -0,0 +1,123 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: proxy/hysteria/account/config.proto
package account
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Account struct {
state protoimpl.MessageState `protogen:"open.v1"`
Auth string `protobuf:"bytes,1,opt,name=auth,proto3" json:"auth,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Account) Reset() {
*x = Account{}
mi := &file_proxy_hysteria_account_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Account) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Account) ProtoMessage() {}
func (x *Account) ProtoReflect() protoreflect.Message {
mi := &file_proxy_hysteria_account_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Account.ProtoReflect.Descriptor instead.
func (*Account) Descriptor() ([]byte, []int) {
return file_proxy_hysteria_account_config_proto_rawDescGZIP(), []int{0}
}
func (x *Account) GetAuth() string {
if x != nil {
return x.Auth
}
return ""
}
var File_proxy_hysteria_account_config_proto protoreflect.FileDescriptor
const file_proxy_hysteria_account_config_proto_rawDesc = "" +
"\n" +
"#proxy/hysteria/account/config.proto\x12\x1bxray.proxy.hysteria.account\"\x1d\n" +
"\aAccount\x12\x12\n" +
"\x04auth\x18\x01 \x01(\tR\x04authBs\n" +
"\x1fcom.xray.proxy.hysteria.accountP\x01Z0github.com/xtls/xray-core/proxy/hysteria/account\xaa\x02\x1bXray.Proxy.Hysteria.Accountb\x06proto3"
var (
file_proxy_hysteria_account_config_proto_rawDescOnce sync.Once
file_proxy_hysteria_account_config_proto_rawDescData []byte
)
func file_proxy_hysteria_account_config_proto_rawDescGZIP() []byte {
file_proxy_hysteria_account_config_proto_rawDescOnce.Do(func() {
file_proxy_hysteria_account_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proxy_hysteria_account_config_proto_rawDesc), len(file_proxy_hysteria_account_config_proto_rawDesc)))
})
return file_proxy_hysteria_account_config_proto_rawDescData
}
var file_proxy_hysteria_account_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_hysteria_account_config_proto_goTypes = []any{
(*Account)(nil), // 0: xray.proxy.hysteria.account.Account
}
var file_proxy_hysteria_account_config_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_proxy_hysteria_account_config_proto_init() }
func file_proxy_hysteria_account_config_proto_init() {
if File_proxy_hysteria_account_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_account_config_proto_rawDesc), len(file_proxy_hysteria_account_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_hysteria_account_config_proto_goTypes,
DependencyIndexes: file_proxy_hysteria_account_config_proto_depIdxs,
MessageInfos: file_proxy_hysteria_account_config_proto_msgTypes,
}.Build()
File_proxy_hysteria_account_config_proto = out.File
file_proxy_hysteria_account_config_proto_goTypes = nil
file_proxy_hysteria_account_config_proto_depIdxs = nil
}
+11
View File
@@ -0,0 +1,11 @@
syntax = "proto3";
package xray.proxy.hysteria.account;
option csharp_namespace = "Xray.Proxy.Hysteria.Account";
option go_package = "github.com/xtls/xray-core/proxy/hysteria/account";
option java_package = "com.xray.proxy.hysteria.account";
option java_multiple_files = true;
message Account {
string auth = 1;
}
+44 -25
View File
@@ -135,6 +135,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil { if err := buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all UDP request").Base(err) return errors.New("failed to transport all UDP request").Base(err)
} }
return nil return nil
} }
@@ -143,12 +144,14 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
reader := &UDPReader{ reader := &UDPReader{
Reader: conn, Reader: conn,
buf: make([]byte, MaxUDPSize),
df: &Defragger{}, df: &Defragger{},
} }
if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil { if err := buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all UDP response").Base(err) return errors.New("failed to transport all UDP response").Base(err)
} }
return nil return nil
} }
@@ -178,7 +181,6 @@ type UDPWriter struct {
func (w *UDPWriter) sendMsg(msg *UDPMessage) error { func (w *UDPWriter) sendMsg(msg *UDPMessage) error {
msgN := msg.Serialize(w.buf) msgN := msg.Serialize(w.buf)
if msgN < 0 { if msgN < 0 {
// Message larger than buffer, silent drop
return nil return nil
} }
_, err := w.Writer.Write(w.buf[:msgN]) _, err := w.Writer.Write(w.buf[:msgN])
@@ -192,10 +194,12 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if b == nil { if b == nil {
break break
} }
addr := w.addr addr := w.addr
if b.UDP != nil { if b.UDP != nil {
addr = b.UDP.NetAddr() addr = b.UDP.NetAddr()
} }
msg := &UDPMessage{ msg := &UDPMessage{
SessionID: 0, SessionID: 0,
PacketID: 0, PacketID: 0,
@@ -204,47 +208,58 @@ func (w *UDPWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
Addr: addr, Addr: addr,
Data: b.Bytes(), Data: b.Bytes(),
} }
if err := w.sendMsg(msg); err != nil {
var errTooLarge *quic.DatagramTooLargeError err := w.sendMsg(msg)
if go_errors.As(err, &errTooLarge) { var errTooLarge *quic.DatagramTooLargeError
msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1 if go_errors.As(err, &errTooLarge) {
fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize)) msg.PacketID = uint16(rand.Intn(0xFFFF)) + 1
for _, fMsg := range fMsgs { fMsgs := FragUDPMessage(msg, int(errTooLarge.MaxDatagramPayloadSize))
err := w.sendMsg(&fMsg) for _, fMsg := range fMsgs {
if err != nil { err := w.sendMsg(&fMsg)
b.Release() if err != nil {
buf.ReleaseMulti(mb) b.Release()
return err buf.ReleaseMulti(mb)
} return err
} }
} else {
b.Release()
buf.ReleaseMulti(mb)
return err
} }
} else if err != nil {
b.Release()
buf.ReleaseMulti(mb)
return err
} }
b.Release() b.Release()
} }
return nil return nil
} }
type UDPReader struct { type UDPReader struct {
Reader io.Reader Reader io.Reader
df *Defragger buf []byte
df *Defragger
firstMsg *UDPMessage
firstDest *net.Destination
} }
func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) { func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if r.firstMsg != nil {
buffer := buf.New()
buffer.Write(r.firstMsg.Data)
buffer.UDP = r.firstDest
r.firstMsg = nil
return buf.MultiBuffer{buffer}, nil
}
for { for {
b := buf.New() n, err := r.Reader.Read(r.buf)
_, err := b.ReadFrom(r.Reader)
if err != nil { if err != nil {
b.Release()
return nil, err return nil, err
} }
msg, err := ParseUDPMessage(b.Bytes()) msg, err := ParseUDPMessage(r.buf[:n])
if err != nil { if err != nil {
b.Release()
continue continue
} }
@@ -253,7 +268,11 @@ func (r *UDPReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
continue continue
} }
dest, _ := net.ParseDestination("udp:" + dfMsg.Addr) dest, err := net.ParseDestination("udp:" + dfMsg.Addr)
if err != nil {
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
continue
}
buffer := buf.New() buffer := buf.New()
buffer.Write(dfMsg.Data) buffer.Write(dfMsg.Data)
+2 -2
View File
@@ -5,6 +5,6 @@ import (
) )
var ( var (
tcpRequestPadding = padding.Padding{Min: 64, Max: 512} tcpRequestPadding = padding.Padding{Min: 64, Max: 512}
// tcpResponsePadding = padding.Padding{Min: 128, Max: 1024} tcpResponsePadding = padding.Padding{Min: 128, Max: 1024}
) )
+60 -11
View File
@@ -74,14 +74,60 @@ func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
return nil return nil
} }
type ServerConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Users []*protocol.User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ServerConfig) Reset() {
*x = ServerConfig{}
mi := &file_proxy_hysteria_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ServerConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ServerConfig) ProtoMessage() {}
func (x *ServerConfig) ProtoReflect() protoreflect.Message {
mi := &file_proxy_hysteria_config_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.
func (*ServerConfig) Descriptor() ([]byte, []int) {
return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{1}
}
func (x *ServerConfig) GetUsers() []*protocol.User {
if x != nil {
return x.Users
}
return nil
}
var File_proxy_hysteria_config_proto protoreflect.FileDescriptor var File_proxy_hysteria_config_proto protoreflect.FileDescriptor
const file_proxy_hysteria_config_proto_rawDesc = "" + const file_proxy_hysteria_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\"f\n" + "\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\x1a\x1acommon/protocol/user.proto\"f\n" +
"\fClientConfig\x12\x18\n" + "\fClientConfig\x12\x18\n" +
"\aversion\x18\x01 \x01(\x05R\aversion\x12<\n" + "\aversion\x18\x01 \x01(\x05R\aversion\x12<\n" +
"\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06serverB[\n" + "\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"@\n" +
"\fServerConfig\x120\n" +
"\x05users\x18\x01 \x03(\v2\x1a.xray.common.protocol.UserR\x05usersB[\n" +
"\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3" "\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3"
var ( var (
@@ -96,18 +142,21 @@ func file_proxy_hysteria_config_proto_rawDescGZIP() []byte {
return file_proxy_hysteria_config_proto_rawDescData return file_proxy_hysteria_config_proto_rawDescData
} }
var file_proxy_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_hysteria_config_proto_goTypes = []any{ var file_proxy_hysteria_config_proto_goTypes = []any{
(*ClientConfig)(nil), // 0: xray.proxy.hysteria.ClientConfig (*ClientConfig)(nil), // 0: xray.proxy.hysteria.ClientConfig
(*protocol.ServerEndpoint)(nil), // 1: xray.common.protocol.ServerEndpoint (*ServerConfig)(nil), // 1: xray.proxy.hysteria.ServerConfig
(*protocol.ServerEndpoint)(nil), // 2: xray.common.protocol.ServerEndpoint
(*protocol.User)(nil), // 3: xray.common.protocol.User
} }
var file_proxy_hysteria_config_proto_depIdxs = []int32{ var file_proxy_hysteria_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.hysteria.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint 2, // 0: xray.proxy.hysteria.ClientConfig.server:type_name -> xray.common.protocol.ServerEndpoint
1, // [1:1] is the sub-list for method output_type 3, // 1: xray.proxy.hysteria.ServerConfig.users:type_name -> xray.common.protocol.User
1, // [1:1] is the sub-list for method input_type 2, // [2:2] is the sub-list for method output_type
1, // [1:1] is the sub-list for extension type_name 2, // [2:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension extendee 2, // [2:2] is the sub-list for extension type_name
0, // [0:1] is the sub-list for field type_name 2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
} }
func init() { file_proxy_hysteria_config_proto_init() } func init() { file_proxy_hysteria_config_proto_init() }
@@ -121,7 +170,7 @@ func file_proxy_hysteria_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_config_proto_rawDesc), len(file_proxy_hysteria_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_hysteria_config_proto_rawDesc), len(file_proxy_hysteria_config_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 1, NumMessages: 2,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
+5
View File
@@ -7,8 +7,13 @@ option java_package = "com.xray.proxy.hysteria";
option java_multiple_files = true; option java_multiple_files = true;
import "common/protocol/server_spec.proto"; import "common/protocol/server_spec.proto";
import "common/protocol/user.proto";
message ClientConfig { message ClientConfig {
int32 version = 1; int32 version = 1;
xray.common.protocol.ServerEndpoint server = 2; xray.common.protocol.ServerEndpoint server = 2;
} }
message ServerConfig {
repeated xray.common.protocol.User users = 1;
}
+12
View File
@@ -2,12 +2,15 @@ package ctx
import ( import (
"context" "context"
"github.com/xtls/xray-core/proxy/hysteria/account"
) )
type key int type key int
const ( const (
requireDatagram key = iota requireDatagram key = iota
validator
) )
func ContextWithRequireDatagram(ctx context.Context, udp bool) context.Context { func ContextWithRequireDatagram(ctx context.Context, udp bool) context.Context {
@@ -21,3 +24,12 @@ func RequireDatagramFromContext(ctx context.Context) bool {
_, ok := ctx.Value(requireDatagram).(struct{}) _, ok := ctx.Value(requireDatagram).(struct{})
return ok return ok
} }
func ContextWithValidator(ctx context.Context, v *account.Validator) context.Context {
return context.WithValue(ctx, validator, v)
}
func ValidatorFromContext(ctx context.Context) *account.Validator {
v, _ := ctx.Value(validator).(*account.Validator)
return v
}
+52 -7
View File
@@ -11,8 +11,6 @@ import (
) )
const ( const (
FrameTypeTCPRequest = 0x401
// Max length values are for preventing DoS attacks // Max length values are for preventing DoS attacks
MaxAddressLength = 2048 MaxAddressLength = 2048
@@ -28,22 +26,49 @@ const (
) )
// TCPRequest format: // TCPRequest format:
// 0x401 (QUIC varint)
// Address length (QUIC varint) // Address length (QUIC varint)
// Address (bytes) // Address (bytes)
// Padding length (QUIC varint) // Padding length (QUIC varint)
// Padding (bytes) // Padding (bytes)
func ReadTCPRequest(r io.Reader) (string, error) {
bReader := quicvarint.NewReader(r)
addrLen, err := quicvarint.Read(bReader)
if err != nil {
return "", err
}
if addrLen == 0 || addrLen > MaxAddressLength {
return "", errors.New("invalid address length")
}
addrBuf := make([]byte, addrLen)
_, err = io.ReadFull(r, addrBuf)
if err != nil {
return "", err
}
paddingLen, err := quicvarint.Read(bReader)
if err != nil {
return "", err
}
if paddingLen > MaxPaddingLength {
return "", errors.New("invalid padding length")
}
if paddingLen > 0 {
_, err = io.CopyN(io.Discard, r, int64(paddingLen))
if err != nil {
return "", err
}
}
return string(addrBuf), nil
}
func WriteTCPRequest(w io.Writer, addr string) error { func WriteTCPRequest(w io.Writer, addr string) error {
padding := tcpRequestPadding.String() padding := tcpRequestPadding.String()
paddingLen := len(padding) paddingLen := len(padding)
addrLen := len(addr) addrLen := len(addr)
sz := int(quicvarint.Len(FrameTypeTCPRequest)) + sz := int(quicvarint.Len(uint64(addrLen))) + addrLen +
int(quicvarint.Len(uint64(addrLen))) + addrLen +
int(quicvarint.Len(uint64(paddingLen))) + paddingLen int(quicvarint.Len(uint64(paddingLen))) + paddingLen
buf := make([]byte, sz) buf := make([]byte, sz)
i := varintPut(buf, FrameTypeTCPRequest) i := varintPut(buf, uint64(addrLen))
i += varintPut(buf[i:], uint64(addrLen))
i += copy(buf[i:], addr) i += copy(buf[i:], addr)
i += varintPut(buf[i:], uint64(paddingLen)) i += varintPut(buf[i:], uint64(paddingLen))
copy(buf[i:], padding) copy(buf[i:], padding)
@@ -96,6 +121,26 @@ func ReadTCPResponse(r io.Reader) (bool, string, error) {
return status[0] == 0, string(msgBuf), nil return status[0] == 0, string(msgBuf), nil
} }
func WriteTCPResponse(w io.Writer, ok bool, msg string) error {
padding := tcpResponsePadding.String()
paddingLen := len(padding)
msgLen := len(msg)
sz := 1 + int(quicvarint.Len(uint64(msgLen))) + msgLen +
int(quicvarint.Len(uint64(paddingLen))) + paddingLen
buf := make([]byte, sz)
if ok {
buf[0] = 0
} else {
buf[0] = 1
}
i := varintPut(buf[1:], uint64(msgLen))
i += copy(buf[1+i:], msg)
i += varintPut(buf[1+i:], uint64(paddingLen))
copy(buf[1+i:], padding)
_, err := w.Write(buf)
return err
}
// UDPMessage format: // UDPMessage format:
// Session ID (uint32 BE) // Session ID (uint32 BE)
// Packet ID (uint16 BE) // Packet ID (uint16 BE)
+199
View File
@@ -0,0 +1,199 @@
package hysteria
import (
"context"
"io"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/proxy/hysteria/account"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/stat"
)
type Server struct {
config *ServerConfig
validator *account.Validator
policyManager policy.Manager
}
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
validator := account.NewValidator()
for _, user := range config.Users {
u, err := user.ToMemoryUser()
if err != nil {
return nil, errors.New("failed to get hysteria user").Base(err).AtError()
}
if err := validator.Add(u); err != nil {
return nil, errors.New("failed to add user").Base(err).AtError()
}
}
v := core.MustFromContext(ctx)
s := &Server{
config: config,
validator: validator,
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}
return s, nil
}
func (s *Server) HysteriaInboundValidator() *account.Validator {
return s.validator
}
func (s *Server) AddUser(ctx context.Context, u *protocol.MemoryUser) error {
return s.validator.Add(u)
}
func (s *Server) RemoveUser(ctx context.Context, e string) error {
return s.validator.Del(e)
}
func (s *Server) GetUser(ctx context.Context, email string) *protocol.MemoryUser {
return s.validator.GetByEmail(email)
}
func (s *Server) GetUsers(ctx context.Context) []*protocol.MemoryUser {
return s.validator.GetAll()
}
func (s *Server) GetUsersCount(context.Context) int64 {
return s.validator.GetCount()
}
func (s *Server) Network() []net.Network {
return []net.Network{net.Network_TCP}
}
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
inbound := session.InboundFromContext(ctx)
inbound.Name = "hysteria"
inbound.CanSpliceCopy = 3
iConn := stat.TryUnwrapStatsConn(conn)
var useremail string
var userlevel uint32
type User interface{ User() *protocol.MemoryUser }
if v, ok := iConn.(User); ok {
inbound.User = v.User()
if inbound.User != nil {
useremail = inbound.User.Email
userlevel = inbound.User.Level
}
}
if _, ok := iConn.(*hysteria.InterUdpConn); ok {
r := io.Reader(conn)
b := make([]byte, MaxUDPSize)
df := &Defragger{}
var firstMsg *UDPMessage
var firstDest net.Destination
for {
n, err := r.Read(b)
if err != nil {
return err
}
msg, err := ParseUDPMessage(b[:n])
if err != nil {
continue
}
dfMsg := df.Feed(msg)
if dfMsg == nil {
continue
}
firstMsg = dfMsg
firstDest, err = net.ParseDestination("udp:" + firstMsg.Addr)
if err != nil {
errors.LogDebug(context.Background(), dfMsg.Addr, " ParseDestination err ", err)
continue
}
break
}
reader := &UDPReader{
Reader: r,
buf: b,
df: df,
firstMsg: firstMsg,
firstDest: &firstDest,
}
writer := &UDPWriter{
Writer: conn,
buf: make([]byte, MaxUDPSize),
addr: firstMsg.Addr,
}
return dispatcher.DispatchLink(ctx, firstDest, &transport.Link{
Reader: reader,
Writer: writer,
})
} else {
sessionPolicy := s.policyManager.ForLevel(userlevel)
common.Must(conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)))
addr, err := ReadTCPRequest(conn)
if err != nil {
log.Record(&log.AccessMessage{
From: conn.RemoteAddr(),
To: "",
Status: log.AccessRejected,
Reason: err,
})
return errors.New("failed to create request from: ", conn.RemoteAddr()).Base(err)
}
common.Must(conn.SetReadDeadline(time.Time{}))
dest, err := net.ParseDestination("tcp:" + addr)
if err != nil {
return err
}
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: conn.RemoteAddr(),
To: dest,
Status: log.AccessAccepted,
Reason: "",
Email: useremail,
})
errors.LogInfo(ctx, "tunnelling request to ", dest)
bufferedWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
err = WriteTCPResponse(bufferedWriter, true, "")
if err != nil {
return errors.New("failed to write response").Base(err)
}
if err := bufferedWriter.SetBuffered(false); err != nil {
return err
}
return dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: buf.NewReader(conn),
Writer: bufferedWriter,
})
}
}
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))
}))
}
+3
View File
@@ -46,6 +46,9 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
ctx = session.ContextWithContent(ctx, content) ctx = session.ContextWithContent(ctx, content)
inbound := session.InboundFromContext(ctx) inbound := session.InboundFromContext(ctx)
if inbound == nil {
inbound = &session.Inbound{}
}
inbound.Tag = l.config.InboundTag inbound.Tag = l.config.InboundTag
+44 -30
View File
@@ -28,6 +28,7 @@ import (
"github.com/xtls/xray-core/proxy/vless/encryption" "github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/reality" "github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
@@ -321,6 +322,7 @@ func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink boo
func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
var isPadding *bool var isPadding *bool
var switchToDirectCopy *bool var switchToDirectCopy *bool
var spliceReadyInbound *session.Inbound
if w.isUplink { if w.isUplink {
isPadding = &w.trafficState.Outbound.IsPadding isPadding = &w.trafficState.Outbound.IsPadding
switchToDirectCopy = &w.trafficState.Outbound.UplinkWriterDirectCopy switchToDirectCopy = &w.trafficState.Outbound.UplinkWriterDirectCopy
@@ -332,7 +334,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if *switchToDirectCopy { if *switchToDirectCopy {
if inbound := session.InboundFromContext(w.ctx); inbound != nil { if inbound := session.InboundFromContext(w.ctx); inbound != nil {
if !w.isUplink && inbound.CanSpliceCopy == 2 { if !w.isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1 spliceReadyInbound = inbound
} }
// if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice // if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice
// w.ob.CanSpliceCopy = 1 // w.ob.CanSpliceCopy = 1
@@ -354,43 +356,51 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if *isPadding { if *isPadding {
if len(mb) == 1 && mb[0] == nil { if len(mb) == 1 && mb[0] == nil {
mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx, w.testseed) // we do a long padding to hide vless header
return w.Writer.WriteMultiBuffer(mb) } else {
} isComplete := IsCompleteRecord(mb)
isComplete := IsCompleteRecord(mb) mb = ReshapeMultiBuffer(w.ctx, mb)
mb = ReshapeMultiBuffer(w.ctx, mb) longPadding := w.trafficState.IsTLS
longPadding := w.trafficState.IsTLS for i, b := range mb {
for i, b := range mb { if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) && isComplete {
if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) && isComplete { if w.trafficState.EnableXtls {
if w.trafficState.EnableXtls { *switchToDirectCopy = true
*switchToDirectCopy = true }
var command byte = CommandPaddingContinue
if i == len(mb)-1 {
command = CommandPaddingEnd
if w.trafficState.EnableXtls {
command = CommandPaddingDirect
}
}
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed)
*isPadding = false // padding going to end
longPadding = false
continue
} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
*isPadding = false
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
break
} }
var command byte = CommandPaddingContinue var command byte = CommandPaddingContinue
if i == len(mb)-1 { if i == len(mb)-1 && !*isPadding {
command = CommandPaddingEnd command = CommandPaddingEnd
if w.trafficState.EnableXtls { if w.trafficState.EnableXtls {
command = CommandPaddingDirect command = CommandPaddingDirect
} }
} }
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed) mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
*isPadding = false // padding going to end
longPadding = false
continue
} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
*isPadding = false
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
break
} }
var command byte = CommandPaddingContinue
if i == len(mb)-1 && !*isPadding {
command = CommandPaddingEnd
if w.trafficState.EnableXtls {
command = CommandPaddingDirect
}
}
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
} }
} }
return w.Writer.WriteMultiBuffer(mb) if err := w.Writer.WriteMultiBuffer(mb); err != nil {
return err
}
if spliceReadyInbound != nil && spliceReadyInbound.CanSpliceCopy == 2 {
// Enable splice only after this write has completed to avoid racing
// concurrent direct writes to the same TCP connection.
spliceReadyInbound.CanSpliceCopy = 1
}
return nil
} }
// IsCompleteRecord Is complete tls data record // IsCompleteRecord Is complete tls data record
@@ -659,7 +669,7 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
} }
} }
// UnwrapRawConn support unwrap encryption, stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it // UnwrapRawConn support unwrap encryption, stats, mask wrappers, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
var readCounter, writerCounter stats.Counter var readCounter, writerCounter stats.Counter
if conn != nil { if conn != nil {
@@ -676,6 +686,7 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
readCounter = statConn.ReadCounter readCounter = statConn.ReadCounter
writerCounter = statConn.WriteCounter writerCounter = statConn.WriteCounter
} }
if !isEncryption { // avoids double penetration if !isEncryption { // avoids double penetration
if xc, ok := conn.(*tls.Conn); ok { if xc, ok := conn.(*tls.Conn); ok {
conn = xc.NetConn() conn = xc.NetConn()
@@ -687,6 +698,9 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
conn = realityUConn.NetConn() conn = realityUConn.NetConn()
} }
} }
conn = finalmask.UnwrapTcpMask(conn)
if pc, ok := conn.(*proxyproto.Conn); ok { if pc, ok := conn.(*proxyproto.Conn); ok {
conn = pc.Raw() conn = pc.Raw()
// 8192 > 4096, there is no need to process pc's bufReader // 8192 > 4096, there is no need to process pc's bufReader
@@ -739,7 +753,6 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
errors.LogDebug(ctx, "CopyRawConn splice") errors.LogDebug(ctx, "CopyRawConn splice")
statWriter, _ := writer.(*dispatcher.SizeStatWriter) statWriter, _ := writer.(*dispatcher.SizeStatWriter)
//runtime.Gosched() // necessary //runtime.Gosched() // necessary
time.Sleep(time.Millisecond) // without this, there will be a rare ssl error for freedom splice
timer.SetTimeout(24 * time.Hour) // prevent leak, just in case timer.SetTimeout(24 * time.Hour) // prevent leak, just in case
if inTimer != nil { if inTimer != nil {
inTimer.SetTimeout(24 * time.Hour) inTimer.SetTimeout(24 * time.Hour)
@@ -788,6 +801,7 @@ func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer sign
func IsRAWTransportWithoutSecurity(conn stat.Connection) bool { func IsRAWTransportWithoutSecurity(conn stat.Connection) bool {
iConn := stat.TryUnwrapStatsConn(conn) iConn := stat.TryUnwrapStatsConn(conn)
iConn = finalmask.UnwrapTcpMask(iConn)
_, ok1 := iConn.(*proxyproto.Conn) _, ok1 := iConn.(*proxyproto.Conn)
_, ok2 := iConn.(*net.TCPConn) _, ok2 := iConn.(*net.TCPConn)
_, ok3 := iConn.(*internet.UnixConnWrapper) _, ok3 := iConn.(*internet.UnixConnWrapper)
+5 -2
View File
@@ -3,8 +3,9 @@
package tun package tun
import ( import (
"crypto/md5"
"errors" "errors"
_ "unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.zx2c4.com/wintun" "golang.zx2c4.com/wintun"
@@ -66,7 +67,9 @@ func NewTun(options TunOptions) (Tun, error) {
} }
func open(name string) (*wintun.Adapter, error) { func open(name string) (*wintun.Adapter, error) {
var guid *windows.GUID // generate a deterministic GUID from the adapter name
id := md5.Sum([]byte(name))
guid := (*windows.GUID)(unsafe.Pointer(&id[0]))
// try to open existing adapter by name // try to open existing adapter by name
adapter, err := wintun.OpenAdapter(name) adapter, err := wintun.OpenAdapter(name)
if err == nil { if err == nil {
+24 -12
View File
@@ -7,6 +7,7 @@
package vless package vless
import ( import (
proxyman "github.com/xtls/xray-core/app/proxyman"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@@ -22,8 +23,9 @@ const (
) )
type Reverse struct { type Reverse struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
Sniffing *proxyman.SniffingConfig `protobuf:"bytes,2,opt,name=sniffing,proto3" json:"sniffing,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -65,6 +67,13 @@ func (x *Reverse) GetTag() string {
return "" return ""
} }
func (x *Reverse) GetSniffing() *proxyman.SniffingConfig {
if x != nil {
return x.Sniffing
}
return nil
}
type Account struct { type Account struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57". // ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57".
@@ -179,9 +188,10 @@ var File_proxy_vless_account_proto protoreflect.FileDescriptor
const file_proxy_vless_account_proto_rawDesc = "" + const file_proxy_vless_account_proto_rawDesc = "" +
"\n" + "\n" +
"\x19proxy/vless/account.proto\x12\x10xray.proxy.vless\"\x1b\n" + "\x19proxy/vless/account.proto\x12\x10xray.proxy.vless\x1a\x19app/proxyman/config.proto\"Z\n" +
"\aReverse\x12\x10\n" + "\aReverse\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\"\x86\x02\n" + "\x03tag\x18\x01 \x01(\tR\x03tag\x12=\n" +
"\bsniffing\x18\x02 \x01(\v2!.xray.app.proxyman.SniffingConfigR\bsniffing\"\x86\x02\n" +
"\aAccount\x12\x0e\n" + "\aAccount\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04flow\x18\x02 \x01(\tR\x04flow\x12\x1e\n" + "\x04flow\x18\x02 \x01(\tR\x04flow\x12\x1e\n" +
@@ -210,16 +220,18 @@ func file_proxy_vless_account_proto_rawDescGZIP() []byte {
var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_proxy_vless_account_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_vless_account_proto_goTypes = []any{ var file_proxy_vless_account_proto_goTypes = []any{
(*Reverse)(nil), // 0: xray.proxy.vless.Reverse (*Reverse)(nil), // 0: xray.proxy.vless.Reverse
(*Account)(nil), // 1: xray.proxy.vless.Account (*Account)(nil), // 1: xray.proxy.vless.Account
(*proxyman.SniffingConfig)(nil), // 2: xray.app.proxyman.SniffingConfig
} }
var file_proxy_vless_account_proto_depIdxs = []int32{ var file_proxy_vless_account_proto_depIdxs = []int32{
0, // 0: xray.proxy.vless.Account.reverse:type_name -> xray.proxy.vless.Reverse 2, // 0: xray.proxy.vless.Reverse.sniffing:type_name -> xray.app.proxyman.SniffingConfig
1, // [1:1] is the sub-list for method output_type 0, // 1: xray.proxy.vless.Account.reverse:type_name -> xray.proxy.vless.Reverse
1, // [1:1] is the sub-list for method input_type 2, // [2:2] is the sub-list for method output_type
1, // [1:1] is the sub-list for extension type_name 2, // [2:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension extendee 2, // [2:2] is the sub-list for extension type_name
0, // [0:1] is the sub-list for field type_name 2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
} }
func init() { file_proxy_vless_account_proto_init() } func init() { file_proxy_vless_account_proto_init() }
+3
View File
@@ -6,8 +6,11 @@ option go_package = "github.com/xtls/xray-core/proxy/vless";
option java_package = "com.xray.proxy.vless"; option java_package = "com.xray.proxy.vless";
option java_multiple_files = true; option java_multiple_files = true;
import "app/proxyman/config.proto";
message Reverse { message Reverse {
string tag = 1; string tag = 1;
xray.app.proxyman.SniffingConfig sniffing = 2;
} }
message Account { message Account {
+2 -2
View File
@@ -107,7 +107,7 @@ func (c *CommonConn) Read(b []byte) (int, error) {
if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil { if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil {
return 0, err return 0, err
} }
l, err := DecodeHeader(peerHeader[:]) // l: 17~17000 l, err := DecodeHeader(peerHeader[:]) // l: 17~16640
if err != nil { if err != nil {
if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT if c.Client != nil && strings.Contains(err.Error(), "invalid header: ") { // client's 0-RTT
c.Client.RWLock.Lock() c.Client.RWLock.Lock()
@@ -214,7 +214,7 @@ func DecodeHeader(h []byte) (l int, err error) {
if h[0] != 23 || h[1] != 3 || h[2] != 3 { if h[0] != 23 || h[1] != 3 || h[2] != 3 {
l = 0 l = 0
} }
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length if l < 17 || l > 16640 { // TLS 1.3 max record: 16384 + 256 (RFC 8446 §5.2)
err = errors.New("invalid header: " + fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read() err = errors.New("invalid header: " + fmt.Sprintf("%v", h[:5])) // DO NOT CHANGE: relied by client's Read()
} }
return return
+9 -2
View File
@@ -27,7 +27,9 @@ import (
"github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/extension"
feature_inbound "github.com/xtls/xray-core/features/inbound" feature_inbound "github.com/xtls/xray-core/features/inbound"
"github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
@@ -78,6 +80,7 @@ type Handler struct {
validator vless.Validator validator vless.Validator
decryption *encryption.ServerInstance decryption *encryption.ServerInstance
outboundHandlerManager outbound.Manager outboundHandlerManager outbound.Manager
observer features.Feature
defaultDispatcher routing.Dispatcher defaultDispatcher routing.Dispatcher
ctx context.Context ctx context.Context
fallbacks map[string]map[string]map[string]*Fallback // or nil fallbacks map[string]map[string]map[string]*Fallback // or nil
@@ -93,6 +96,7 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
stats: v.GetFeature(stats.ManagerType()).(stats.Manager), stats: v.GetFeature(stats.ManagerType()).(stats.Manager),
validator: validator, validator: validator,
outboundHandlerManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager), outboundHandlerManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
observer: v.GetFeature(extension.ObservatoryType()),
defaultDispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher), defaultDispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),
ctx: ctx, ctx: ctx,
} }
@@ -623,7 +627,7 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
if err != nil { if err != nil {
return err return err
} }
return r.NewMux(ctx, dispatcher.WrapLink(ctx, h.policyManager, h.stats, &transport.Link{Reader: clientReader, Writer: clientWriter})) return r.NewMux(ctx, dispatcher.WrapLink(ctx, h.policyManager, h.stats, &transport.Link{Reader: clientReader, Writer: clientWriter}), h.observer)
} }
if err := dispatch.DispatchLink(ctx, request.Destination(), &transport.Link{ if err := dispatch.DispatchLink(ctx, request.Destination(), &transport.Link{
@@ -645,7 +649,7 @@ func (r *Reverse) Tag() string {
return r.tag return r.tag
} }
func (r *Reverse) NewMux(ctx context.Context, link *transport.Link) error { func (r *Reverse) NewMux(ctx context.Context, link *transport.Link, observer features.Feature) error {
muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{}) muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
if err != nil { if err != nil {
return errors.New("failed to create mux client worker").Base(err).AtWarning() return errors.New("failed to create mux client worker").Base(err).AtWarning()
@@ -655,6 +659,9 @@ func (r *Reverse) NewMux(ctx context.Context, link *transport.Link) error {
return errors.New("failed to create portal worker").Base(err).AtWarning() return errors.New("failed to create portal worker").Base(err).AtWarning()
} }
r.picker.AddWorker(worker) r.picker.AddWorker(worker)
if burstObs, ok := observer.(extension.BurstObservatory); ok {
go burstObs.Check([]string{r.Tag()})
}
select { select {
case <-ctx.Done(): case <-ctx.Done():
case <-muxClient.WaitClosed(): case <-muxClient.WaitClosed():
+17 -5
View File
@@ -97,14 +97,26 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
} }
if a.Reverse != nil { if a.Reverse != nil {
rvsCtx := session.ContextWithInbound(ctx, &session.Inbound{
Tag: a.Reverse.Tag,
User: handler.server.User, // TODO: email
})
if sc := a.Reverse.Sniffing; sc != nil && sc.Enabled {
rvsCtx = session.ContextWithContent(rvsCtx, &session.Content{
SniffingRequest: session.SniffingRequest{
Enabled: sc.Enabled,
OverrideDestinationForProtocol: sc.DestinationOverride,
ExcludeForDomain: sc.DomainsExcluded,
MetadataOnly: sc.MetadataOnly,
RouteOnly: sc.RouteOnly,
},
})
}
handler.reverse = &Reverse{ handler.reverse = &Reverse{
tag: a.Reverse.Tag, tag: a.Reverse.Tag,
dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher), dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),
ctx: session.ContextWithInbound(ctx, &session.Inbound{ ctx: rvsCtx,
Tag: a.Reverse.Tag, handler: handler,
User: handler.server.User, // TODO: email
}),
handler: handler,
} }
handler.reverse.monitorTask = &task.Periodic{ handler.reverse.monitorTask = &task.Periodic{
Execute: handler.reverse.monitor, Execute: handler.reverse.monitor,
+47 -41
View File
@@ -2,27 +2,24 @@ package wireguard
import ( import (
"context" "context"
"errors" gonet "net"
"net/netip" "net/netip"
"runtime"
"strconv" "strconv"
"sync"
"golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
) )
type netReadInfo struct { type netReadInfo struct {
// status buff *buf.Buffer
waiter sync.WaitGroup
// param
buff []byte
// result
bytes int
endpoint conn.Endpoint endpoint conn.Endpoint
err error
} }
// reduce duplicated code // reduce duplicated code
@@ -32,6 +29,7 @@ type netBind struct {
workers int workers int
readQueue chan *netReadInfo readQueue chan *netReadInfo
closedCh chan struct{}
} }
// SetMark implements conn.Bind // SetMark implements conn.Bind
@@ -79,26 +77,24 @@ func (bind *netBind) BatchSize() int {
// Open implements conn.Bind // Open implements conn.Bind
func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) { func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
bind.readQueue = make(chan *netReadInfo) bind.closedCh = make(chan struct{})
errors.LogDebug(context.Background(), "bind opened")
fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) { fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
defer func() { select {
if r := recover(); r != nil { case r := <-bind.readQueue:
n = 0 sizes[0], eps[0] = copy(bufs[0], r.buff.Bytes()), r.endpoint
err = errors.New("channel closed") r.buff.Release()
} return 1, nil
}() case <-bind.closedCh:
errors.LogDebug(context.Background(), "recv func closed")
r := &netReadInfo{ return 0, gonet.ErrClosed
buff: bufs[0],
} }
r.waiter.Add(1)
bind.readQueue <- r
r.waiter.Wait() // wait read goroutine done, or we will miss the result
sizes[0], eps[0] = r.bytes, r.endpoint
return 1, r.err
} }
workers := bind.workers workers := bind.workers
if workers <= 0 {
workers = runtime.NumCPU()
}
if workers <= 0 { if workers <= 0 {
workers = 1 workers = 1
} }
@@ -112,8 +108,9 @@ func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
// Close implements conn.Bind // Close implements conn.Bind
func (bind *netBind) Close() error { func (bind *netBind) Close() error {
if bind.readQueue != nil { errors.LogDebug(context.Background(), "bind closed")
close(bind.readQueue) if bind.closedCh != nil {
close(bind.closedCh)
} }
return nil return nil
} }
@@ -133,30 +130,38 @@ func (bind *netBindClient) connectTo(endpoint *netEndpoint) error {
} }
endpoint.conn = c endpoint.conn = c
go func(readQueue <-chan *netReadInfo, endpoint *netEndpoint) { go func() {
for { for {
v, ok := <-readQueue buff := buf.NewWithSize(device.MaxMessageSize)
if !ok { n, err := buff.ReadFrom(c)
if err != nil {
buff.Release()
endpoint.conn = nil
c.Close()
return return
} }
i, err := c.Read(v.buff)
if i > 3 { rawBytes := buff.Bytes()
v.buff[1] = 0 if n > 3 {
v.buff[2] = 0 rawBytes[1] = 0
v.buff[3] = 0 rawBytes[2] = 0
rawBytes[3] = 0
} }
v.bytes = i select {
v.endpoint = endpoint case bind.readQueue <- &netReadInfo{
v.err = err buff: buff,
v.waiter.Done() endpoint: endpoint,
if err != nil { }:
case <-bind.closedCh:
buff.Release()
endpoint.conn = nil endpoint.conn = nil
c.Close()
return return
} }
} }
}(bind.readQueue, endpoint) }()
return nil return nil
} }
@@ -200,7 +205,8 @@ func (bind *netBindServer) Send(buff [][]byte, endpoint conn.Endpoint) error {
} }
if nend.conn == nil { if nend.conn == nil {
return errors.New("connection not open yet") errors.LogDebug(context.Background(), nend.dst.NetAddr(), " send on closed peer")
return errors.New("peer closed")
} }
for _, buff := range buff { for _, buff := range buff {
+38 -1
View File
@@ -121,7 +121,8 @@ func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer)
IPv4Enable: h.hasIPv4, IPv4Enable: h.hasIPv4,
IPv6Enable: h.hasIPv6, IPv6Enable: h.hasIPv6,
}, },
workers: int(h.conf.NumWorkers), workers: int(h.conf.NumWorkers),
readQueue: make(chan *netReadInfo),
}, },
ctx: ctx, ctx: ctx,
dialer: dialer, dialer: dialer,
@@ -227,6 +228,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
} }
defer conn.Close() defer conn.Close()
conn = &udpConnClient{
Conn: conn,
dest: destination,
}
requestFunc = func() error { requestFunc = func() error {
defer timer.SetTimeout(p.Timeouts.DownlinkOnly) defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
@@ -336,3 +342,34 @@ func (h *Handler) createIPCRequest() string {
return request.String()[:request.Len()] return request.String()[:request.Len()]
} }
type udpConnClient struct {
net.Conn
dest net.Destination
}
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
b.Resize(0, buf.Size)
n, addr, err := c.Conn.(net.PacketConn).ReadFrom(b.Bytes())
if err != nil {
b.Release()
return nil, err
}
if addr == nil { // should never hit
addr = c.dest.RawNetAddr()
}
b.Resize(0, int32(n))
b.UDP = &net.Destination{
Address: net.IPAddress(addr.(*net.UDPAddr).IP),
Port: net.Port(addr.(*net.UDPAddr).Port),
Network: net.Network_UDP,
}
return buf.MultiBuffer{b}, nil
}
func (c *udpConnClient) Write(p []byte) (int, error) {
return c.Conn.(net.PacketConn).WriteTo(p, c.dest.RawNetAddr())
}
+18 -21
View File
@@ -31,6 +31,7 @@ type netTun struct {
ep *channel.Endpoint ep *channel.Endpoint
stack *stack.Stack stack *stack.Stack
events chan tun.Event events chan tun.Event
notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View incomingPacket chan *buffer.View
mtu int mtu int
hasV4, hasV6 bool hasV4, hasV6 bool
@@ -48,12 +49,17 @@ func CreateNetTUN(localAddresses []netip.Addr, mtu int, promiscuousMode bool) (t
dev := &netTun{ dev := &netTun{
ep: channel.New(1024, uint32(mtu), ""), ep: channel.New(1024, uint32(mtu), ""),
stack: stack.New(opts), stack: stack.New(opts),
events: make(chan tun.Event, 1), events: make(chan tun.Event, 10),
incomingPacket: make(chan *buffer.View), incomingPacket: make(chan *buffer.View),
mtu: mtu, mtu: mtu,
} }
dev.ep.AddNotify(dev) sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
tcpipErr := dev.stack.CreateNIC(1, dev.ep) tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
if tcpipErr != nil {
return nil, nil, dev.stack, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
}
dev.notifyHandle = dev.ep.AddNotify(dev)
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
if tcpipErr != nil { if tcpipErr != nil {
return nil, nil, dev.stack, fmt.Errorf("CreateNIC: %v", tcpipErr) return nil, nil, dev.stack, fmt.Errorf("CreateNIC: %v", tcpipErr)
} }
@@ -90,20 +96,10 @@ func CreateNetTUN(localAddresses []netip.Addr, mtu int, promiscuousMode bool) (t
dev.stack.SetSpoofing(1, true) dev.stack.SetSpoofing(1, true)
} }
opt := tcpip.CongestionControlOption("cubic")
if err := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
return nil, nil, dev.stack, fmt.Errorf("SetTransportProtocolOption(%d, &%T(%s)): %s", tcp.ProtocolNumber, opt, opt, err)
}
dev.events <- tun.EventUp dev.events <- tun.EventUp
return dev, (*Net)(dev), dev.stack, nil return dev, (*Net)(dev), dev.stack, nil
} }
// BatchSize implements tun.Device
func (tun *netTun) BatchSize() int {
return 1
}
// Name implements tun.Device // Name implements tun.Device
func (tun *netTun) Name() (string, error) { func (tun *netTun) Name() (string, error) {
return "go", nil return "go", nil
@@ -120,7 +116,6 @@ func (tun *netTun) Events() <-chan tun.Event {
} }
// Read implements tun.Device // Read implements tun.Device
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) { func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
view, ok := <-tun.incomingPacket view, ok := <-tun.incomingPacket
if !ok { if !ok {
@@ -169,20 +164,16 @@ func (tun *netTun) WriteNotify() {
tun.incomingPacket <- view tun.incomingPacket <- view
} }
// Flush implements tun.Device
func (tun *netTun) Flush() error {
return nil
}
// Close implements tun.Device // Close implements tun.Device
func (tun *netTun) Close() error { func (tun *netTun) Close() error {
tun.closeOnce.Do(func() { tun.closeOnce.Do(func() {
tun.stack.RemoveNIC(1) tun.stack.RemoveNIC(1)
tun.stack.Close()
tun.ep.RemoveNotify(tun.notifyHandle)
tun.ep.Close()
close(tun.events) close(tun.events)
tun.ep.Close()
close(tun.incomingPacket) close(tun.incomingPacket)
}) })
return nil return nil
@@ -193,6 +184,11 @@ func (tun *netTun) MTU() (int, error) {
return tun.mtu, nil return tun.mtu, nil
} }
// BatchSize implements tun.Device
func (tun *netTun) BatchSize() int {
return 1
}
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) { func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
var protoNumber tcpip.NetworkProtocolNumber var protoNumber tcpip.NetworkProtocolNumber
if endpoint.Addr().Is4() { if endpoint.Addr().Is4() {
@@ -224,6 +220,7 @@ func (net *Net) DialUDPAddrPort(laddr, raddr netip.AddrPort) (*gonet.UDPConn, er
var addr tcpip.FullAddress var addr tcpip.FullAddress
addr, pn = convertToFullAddr(raddr) addr, pn = convertToFullAddr(raddr)
rfa = &addr rfa = &addr
rfa = nil // do not ep connect
} }
return gonet.DialUDP(net.stack, lfa, rfa, pn) return gonet.DialUDP(net.stack, lfa, rfa, pn)
} }
+37 -55
View File
@@ -2,22 +2,18 @@ package wireguard
import ( import (
"context" "context"
goerrors "errors"
"io"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
c "github.com/xtls/xray-core/common/ctx" c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
) )
@@ -31,10 +27,10 @@ type Server struct {
} }
type routingInfo struct { type routingInfo struct {
ctx context.Context ctx context.Context
dispatcher routing.Dispatcher dispatcher routing.Dispatcher
inboundTag *session.Inbound inboundTag *session.Inbound
contentTag *session.Content contentTag *session.Content
} }
func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) { func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
@@ -53,6 +49,8 @@ func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
IPv4Enable: hasIPv4, IPv4Enable: hasIPv4,
IPv6Enable: hasIPv6, IPv6Enable: hasIPv6,
}, },
workers: int(conf.NumWorkers),
readQueue: make(chan *netReadInfo),
}, },
}, },
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
@@ -95,25 +93,31 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
reader := buf.NewPacketReader(conn) reader := buf.NewPacketReader(conn)
for { for {
mpayload, err := reader.ReadMultiBuffer() mb, err := reader.ReadMultiBuffer()
if err != nil { if err != nil {
nep.conn = nil
buf.ReleaseMulti(mb)
return err return err
} }
for _, payload := range mpayload { for i, b := range mb {
v, ok := <-s.bindServer.readQueue
if !ok {
return nil
}
i, err := payload.Read(v.buff)
v.bytes = i rawBytes := b.Bytes()
v.endpoint = nep if b.Len() > 3 {
v.err = err rawBytes[1] = 0
v.waiter.Done() rawBytes[2] = 0
if err != nil && goerrors.Is(err, io.EOF) { rawBytes[3] = 0
}
select {
case s.bindServer.readQueue <- &netReadInfo{
buff: b,
endpoint: nep,
}:
case <-s.bindServer.closedCh:
nep.conn = nil nep.conn = nil
return nil buf.ReleaseMulti(mb[i:])
return errors.New("bind closed")
} }
} }
} }
@@ -124,7 +128,6 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
errors.LogError(s.info.ctx, "unexpected: dispatcher == nil") errors.LogError(s.info.ctx, "unexpected: dispatcher == nil")
return return
} }
defer conn.Close()
ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx)) ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx))
sid := session.NewID() sid := session.NewID()
@@ -141,14 +144,13 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
// Currently we have no way to link to the original source address // Currently we have no way to link to the original source address
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr()) inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
ctx = session.ContextWithInbound(ctx, &inbound) ctx = session.ContextWithInbound(ctx, &inbound)
content := new(session.Content)
if s.info.contentTag != nil { if s.info.contentTag != nil {
ctx = session.ContextWithContent(ctx, s.info.contentTag) content.SniffingRequest = s.info.contentTag.SniffingRequest
} }
ctx = session.ContextWithContent(ctx, content)
ctx = session.SubContextFromMuxInbound(ctx) ctx = session.SubContextFromMuxInbound(ctx)
plcy := s.policyManager.ForLevel(0)
timer := signal.CancelAfterInactivity(ctx, cancel, plcy.Timeouts.ConnectionIdle)
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: nullDestination, From: nullDestination,
To: dest, To: dest,
@@ -156,35 +158,15 @@ func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) {
Reason: "", Reason: "",
}) })
link, err := s.info.dispatcher.Dispatch(ctx, dest) err := s.info.dispatcher.DispatchLink(ctx, dest, &transport.Link{
Reader: buf.NewReader(conn),
Writer: buf.NewWriter(conn),
})
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "dispatch connection") errors.LogInfoInner(ctx, err, "connection ends")
}
defer cancel()
requestDone := func() error {
defer timer.SetTimeout(plcy.Timeouts.DownlinkOnly)
if err := buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all TCP request").Base(err)
}
return nil
} }
responseDone := func() error { cancel()
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) conn.Close()
if err := buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)); err != nil {
return errors.New("failed to transport all TCP response").Base(err)
}
return nil
}
requestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
common.Interrupt(link.Reader)
common.Interrupt(link.Writer)
errors.LogDebugInner(ctx, err, "connection ends")
return
}
} }
+218 -36
View File
@@ -3,6 +3,7 @@ package wireguard
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/netip" "net/netip"
"runtime" "runtime"
"strconv" "strconv"
@@ -10,12 +11,17 @@ import (
"sync" "sync"
"time" "time"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/proxy/wireguard/gvisortun" "github.com/xtls/xray-core/proxy/wireguard/gvisortun"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp" "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter" "gvisor.dev/gvisor/pkg/waiter"
@@ -138,7 +144,7 @@ func (g *gvisorNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, erro
func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) { func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) {
out := &gvisorNet{} out := &gvisorNet{}
tun, n, stack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil) tun, n, gstack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -147,60 +153,236 @@ func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
// handler is only used for promiscuous mode // handler is only used for promiscuous mode
// capture all packets and send to handler // capture all packets and send to handler
tcpForwarder := tcp.NewForwarder(stack, 0, 65535, func(r *tcp.ForwarderRequest) { tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) {
go func(r *tcp.ForwarderRequest) { go func(r *tcp.ForwarderRequest) {
var ( var wq waiter.Queue
wq waiter.Queue var id = r.ID()
id = r.ID()
)
// Perform a TCP three-way handshake.
ep, err := r.CreateEndpoint(&wq) ep, err := r.CreateEndpoint(&wq)
if err != nil { if err != nil {
errors.LogError(context.Background(), err.String()) errors.LogError(context.Background(), err.String())
r.Complete(true) r.Complete(true)
return return
} }
r.Complete(false)
defer ep.Close()
// enable tcp keep-alive to prevent hanging connections options := ep.SocketOptions()
ep.SocketOptions().SetKeepAlive(true) options.SetKeepAlive(false)
options.SetReuseAddress(true)
options.SetReusePort(true)
// local address is actually destination
handler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep)) handler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep))
ep.Close()
r.Complete(false)
}(r) }(r)
}) })
stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket) gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
udpForwarder := udp.NewForwarder(stack, func(r *udp.ForwarderRequest) bool { manager := &udpManager{
go func(r *udp.ForwarderRequest) { stack: gstack,
var ( handler: handler,
wq waiter.Queue m: make(map[string]*udpConn),
id = r.ID() }
)
ep, err := r.CreateEndpoint(&wq)
if err != nil {
errors.LogError(context.Background(), err.String())
return
}
defer ep.Close()
// prevents hanging connections and ensure timely release
ep.SocketOptions().SetLinger(tcpip.LingerOption{
Enabled: true,
Timeout: 15 * time.Second,
})
handler(net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewUDPConn(&wq, ep))
}(r)
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
data := pkt.Clone().Data().AsRange().ToSlice()
// if len(data) == 0 {
// return false
// }
src := net.UDPDestination(net.IPAddress(id.RemoteAddress.AsSlice()), net.Port(id.RemotePort))
dst := net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))
manager.feed(src, dst, data)
return true return true
}) })
stack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
} }
out.tun, out.net = tun, n out.tun, out.net = tun, n
return out, nil return out, nil
} }
type udpManager struct {
stack *stack.Stack
handler func(dest net.Destination, conn net.Conn)
m map[string]*udpConn
mutex sync.RWMutex
}
func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte) {
m.mutex.RLock()
uc, ok := m.m[src.NetAddr()]
if ok {
select {
case uc.ch <- data:
default:
}
m.mutex.RUnlock()
return
}
m.mutex.RUnlock()
m.mutex.Lock()
defer m.mutex.Unlock()
uc, ok = m.m[src.NetAddr()]
if !ok {
uc = &udpConn{
ch: make(chan []byte, 1024),
src: src,
dst: dst,
}
uc.writeFunc = m.writeRawUDPPacket
uc.closeFunc = func() {
m.mutex.Lock()
m.close(uc)
m.mutex.Unlock()
}
m.m[src.NetAddr()] = uc
go m.handler(dst, uc)
}
select {
case uc.ch <- data:
default:
}
}
func (m *udpManager) close(uc *udpConn) {
if !uc.closed {
uc.closed = true
close(uc.ch)
delete(m.m, uc.src.NetAddr())
}
}
func (m *udpManager) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {
udpLen := header.UDPMinimumSize + len(payload)
srcIP := tcpip.AddrFromSlice(src.Address.IP())
dstIP := tcpip.AddrFromSlice(dst.Address.IP())
// build packet with appropriate IP header size
isIPv4 := dst.Address.Family().IsIPv4()
ipHdrSize := header.IPv6MinimumSize
ipProtocol := header.IPv6ProtocolNumber
if isIPv4 {
ipHdrSize = header.IPv4MinimumSize
ipProtocol = header.IPv4ProtocolNumber
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize,
Payload: buffer.MakeWithData(payload),
})
defer pkt.DecRef()
// Build UDP header
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
udpHdr.Encode(&header.UDPFields{
SrcPort: uint16(src.Port),
DstPort: uint16(dst.Port),
Length: uint16(udpLen),
})
// Calculate and set UDP checksum
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
// Build IP header
if isIPv4 {
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
ipHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(header.IPv4MinimumSize + udpLen),
TTL: 64,
Protocol: uint8(header.UDPProtocolNumber),
SrcAddr: srcIP,
DstAddr: dstIP,
})
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
} else {
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
ipHdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(udpLen),
TransportProtocol: header.UDPProtocolNumber,
HopLimit: 64,
SrcAddr: srcIP,
DstAddr: dstIP,
})
}
// dispatch the packet
err := m.stack.WriteRawPacket(1, ipProtocol, buffer.MakeWithView(pkt.ToView()))
if err != nil {
return errors.New("failed to write raw udp packet back to stack err ", err)
}
return nil
}
type udpConn struct {
ch chan []byte
src net.Destination
dst net.Destination
writeFunc func(payload []byte, src net.Destination, dst net.Destination) error
closeFunc func()
closed bool
}
func (c *udpConn) Read(p []byte) (int, error) {
b, ok := <-c.ch
if !ok {
return 0, io.EOF
}
n := copy(p, b)
if n != len(b) {
return 0, io.ErrShortBuffer
}
return n, nil
}
func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb {
dst := c.dst
if b.UDP != nil {
dst = *b.UDP
}
err := c.writeFunc(b.Bytes(), dst, c.src)
if err != nil {
buf.ReleaseMulti(mb[i:])
return err
}
b.Release()
}
return nil
}
func (c *udpConn) Write(p []byte) (int, error) {
err := c.writeFunc(p, c.dst, c.src)
if err != nil {
return 0, err
}
return len(p), nil
}
func (c *udpConn) Close() error {
c.closeFunc()
return nil
}
func (c *udpConn) LocalAddr() net.Addr {
return c.src.RawNetAddr() // fake
}
func (c *udpConn) RemoteAddr() net.Addr {
return c.src.RawNetAddr() // src
}
func (c *udpConn) SetDeadline(t time.Time) error {
return nil
}
func (c *udpConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *udpConn) SetWriteDeadline(t time.Time) error {
return nil
}
-17
View File
@@ -8,25 +8,8 @@ import (
"strings" "strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/log"
"golang.zx2c4.com/wireguard/device"
) )
var wgLogger = &device.Logger{
Verbosef: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Debug,
Content: fmt.Sprintf(format, args...),
})
},
Errorf: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Error,
Content: fmt.Sprintf(format, args...),
})
},
}
func init() { func init() {
common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
deviceConfig := config.(*DeviceConfig) deviceConfig := config.(*DeviceConfig)
+150
View File
@@ -3,6 +3,7 @@ package scenarios
import ( import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"sync"
"testing" "testing"
"time" "time"
@@ -497,3 +498,152 @@ func TestVlessXtlsVisionReality(t *testing.T) {
t.Error(err) t.Error(err)
} }
} }
// This testing test all known utls fingerprint in tls.PresetFingerprints that support reality (expect unsafe and random*)
// Beacuse figerprint support may be broken after utls/reality update
// Known broken fingerprint: android, 360
func TestVlessRealityFingerprints(t *testing.T) {
TestFingerprint := func(fingerprint string) error {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
dest, err := tcpServer.Start()
common.Must(err)
defer tcpServer.Close()
userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort()
privateKey, _ := base64.RawURLEncoding.DecodeString("aGSYystUbf59_9_6LKRxD27rmSW_-2_nyd9YG_Gwbks")
publicKey, _ := base64.RawURLEncoding.DecodeString("E59WjnvZcQMu7tR7_BgyhycuEdBS-CtKxfImRCdAvFM")
shortIds := make([][]byte, 1)
shortIds[0] = make([]byte, 8)
hex.Decode(shortIds[0], []byte("0123456789abcdef"))
serverConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogType: log.LogType_None,
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
StreamSettings: &internet.StreamConfig{
ProtocolName: "tcp",
SecurityType: serial.GetMessageType(&reality.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&reality.Config{
Show: false,
Dest: "www.google.com:443", // use google for now, may fail in some region
ServerNames: []string{"www.google.com"},
PrivateKey: privateKey,
ShortIds: shortIds,
Type: "tcp",
}),
},
},
}),
ProxySettings: serial.ToTypedMessage(&inbound.Config{
Clients: []*protocol.User{
{
Account: serial.ToTypedMessage(&vless.Account{
Id: userID.String(),
}),
},
},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
clientPort := tcp.PickPort()
clientConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogType: log.LogType_None,
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
Address: net.NewIPOrDomain(dest.Address),
Port: uint32(dest.Port),
Networks: []net.Network{net.Network_TCP},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&outbound.Config{
Vnext: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
Account: serial.ToTypedMessage(&vless.Account{
Id: userID.String(),
}),
},
},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
ProtocolName: "tcp",
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "tcp",
Settings: serial.ToTypedMessage(&transtcp.Config{}),
},
},
SecurityType: serial.GetMessageType(&reality.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&reality.Config{
Show: false,
Fingerprint: fingerprint,
ServerName: "www.google.com",
PublicKey: publicKey,
ShortId: shortIds[0],
SpiderX: "/",
}),
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
common.Must(err)
defer CloseAllServers(servers)
err = testTCPConn(clientPort, 1024*1024, time.Second*15)()
if err != nil {
return err
}
return nil
}
fingerPrints := []string{"chrome", "firefox", "safari", "ios", "edge", "qq"}
wg := sync.WaitGroup{}
wg.Add(len(fingerPrints))
for _, fp := range fingerPrints {
go func() {
err := TestFingerprint(fp)
if err != nil {
t.Errorf("Fingerprint %s test failed: %v", fp, err)
} else {
t.Logf("Fingerprint %s test passed", fp)
}
wg.Done()
}()
}
wg.Wait()
}
+23 -6
View File
@@ -22,6 +22,7 @@ type task struct {
Method string `json:"method"` Method string `json:"method"`
URL string `json:"url"` URL string `json:"url"`
Extra any `json:"extra,omitempty"` Extra any `json:"extra,omitempty"`
StreamResponse bool `json:"streamResponse"`
} }
var conns chan *websocket.Conn var conns chan *websocket.Conn
@@ -52,6 +53,7 @@ func init() {
} }
} }
} else { } else {
w.Header().Set("Access-Control-Allow-Origin", "*");
w.Write(webpage) w.Write(webpage)
} }
})) }))
@@ -70,6 +72,7 @@ func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
task := task{ task := task{
Method: "WS", Method: "WS",
URL: uri, URL: uri,
StreamResponse: true,
} }
if ed != nil { if ed != nil {
@@ -84,9 +87,10 @@ func DialWS(uri string, ed []byte) (*websocket.Conn, error) {
type httpExtra struct { type httpExtra struct {
Referrer string `json:"referrer,omitempty"` Referrer string `json:"referrer,omitempty"`
Headers map[string]string `json:"headers,omitempty"` Headers map[string]string `json:"headers,omitempty"`
Cookies map[string]string `json:"cookies,omitempty"`
} }
func httpExtraFromHeaders(headers http.Header) *httpExtra { func httpExtraFromHeadersAndCookies(headers http.Header, cookies []*http.Cookie) *httpExtra {
if len(headers) == 0 { if len(headers) == 0 {
return nil return nil
} }
@@ -104,24 +108,37 @@ func httpExtraFromHeaders(headers http.Header) *httpExtra {
} }
} }
if len(cookies) > 0 {
extra.Cookies = make(map[string]string)
for _, cookie := range cookies {
extra.Cookies[cookie.Name] = cookie.Value
}
}
return &extra return &extra
} }
func DialGet(uri string, headers http.Header) (*websocket.Conn, error) { func DialGet(uri string, headers http.Header, cookies []*http.Cookie) (*websocket.Conn, error) {
task := task{ task := task{
Method: "GET", Method: "GET",
URL: uri, URL: uri,
Extra: httpExtraFromHeaders(headers), Extra: httpExtraFromHeadersAndCookies(headers, cookies),
StreamResponse: true,
} }
return dialTask(task) return dialTask(task)
} }
func DialPost(uri string, headers http.Header, payload []byte) error { func DialPacket(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
return dialWithBody(method, uri, headers, cookies, payload)
}
func dialWithBody(method string, uri string, headers http.Header, cookies []*http.Cookie, payload []byte) error {
task := task{ task := task{
Method: "POST", Method: method,
URL: uri, URL: uri,
Extra: httpExtraFromHeaders(headers), Extra: httpExtraFromHeadersAndCookies(headers, cookies),
StreamResponse: false,
} }
conn, err := dialTask(task) conn, err := dialTask(task)
+139 -105
View File
@@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title>Browser Dialer</title> <title>Browser Dialer</title>
<link rel="icon" href="data:">
</head> </head>
<body> <body>
<script> <script>
@@ -29,9 +30,37 @@
requestInit.headers = extra.headers; requestInit.headers = extra.headers;
} }
if (extra.cookies) {
requestInit.credentials = 'include';
}
return requestInit; return requestInit;
} }
function setCookiesFromTask(task) {
if (!task.extra.cookies) {
return;
}
const url = new URL(task.url);
for (const [name, value] of Object.entries(task.extra.cookies)) {
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + '; path=' + url.pathname;
}
}
function clearCookiesFromTask(task) {
if (!task.extra.cookies) {
return;
}
const url = new URL(task.url);
for (const [name, value] of Object.entries(task.extra.cookies)) {
document.cookie = encodeURIComponent(name) + '=; path=' + url.pathname + '; Max-Age=0';
}
}
let check = function () { let check = function () {
if (clientIdleCount > 0) { if (clientIdleCount > 0) {
return; return;
@@ -48,116 +77,121 @@
ws.onmessage = function (event) { ws.onmessage = function (event) {
clientIdleCount -= 1; clientIdleCount -= 1;
let task = JSON.parse(event.data); let task = JSON.parse(event.data);
switch (task.method) { if (task.method == "WS") {
case "WS": { upstreamWsCount += 1;
upstreamWsCount += 1; console.log("Dial WS", task.url, task.extra.protocol);
console.log("Dial WS", task.url, task.extra.protocol); const wss = new WebSocket(task.url, task.extra.protocol);
const wss = new WebSocket(task.url, task.extra.protocol); wss.binaryType = "arraybuffer";
wss.binaryType = "arraybuffer"; let opened = false;
let opened = false; ws.onmessage = function (event) {
ws.onmessage = function (event) { wss.send(event.data)
wss.send(event.data) };
}; wss.onopen = function (event) {
wss.onopen = function (event) { opened = true;
opened = true; ws.send("ok")
ws.send("ok") };
}; wss.onmessage = function (event) {
wss.onmessage = function (event) { ws.send(event.data)
ws.send(event.data) };
}; wss.onclose = function (event) {
wss.onclose = function (event) { upstreamWsCount -= 1;
upstreamWsCount -= 1; console.log("Dial WS DONE, remaining: ", upstreamWsCount);
console.log("Dial WS DONE, remaining: ", upstreamWsCount); ws.close()
ws.close() };
}; wss.onerror = function (event) {
wss.onerror = function (event) { !opened && ws.send("fail")
!opened && ws.send("fail") wss.close()
wss.close() };
}; ws.onclose = function (event) {
ws.onclose = function (event) { wss.close()
wss.close() };
}; }
break; else if (task.method == "GET" && task.streamResponse) {
} (async () => {
case "GET": {
(async () => {
const requestInit = prepareRequestInit(task.extra);
console.log("Dial GET", task.url);
ws.send("ok");
const controller = new AbortController();
/*
Aborting a streaming response in JavaScript
requires two levers to be pulled:
First, the streaming read itself has to be cancelled using
reader.cancel(), only then controller.abort() will actually work.
If controller.abort() alone is called while a
reader.read() is ongoing, it will block until the server closes the
response, the page is refreshed or the network connection is lost.
*/
let reader = null;
ws.onclose = (event) => {
try {
reader && reader.cancel();
} catch(e) {}
try {
controller.abort();
} catch(e) {}
};
try {
upstreamGetCount += 1;
requestInit.signal = controller.signal;
const response = await fetch(task.url, requestInit);
const body = await response.body;
reader = body.getReader();
while (true) {
const { done, value } = await reader.read();
if (value) ws.send(value); // don't send back "undefined" string when received nothing
if (done) break;
}
} finally {
upstreamGetCount -= 1;
console.log("Dial GET DONE, remaining: ", upstreamGetCount);
ws.close();
}
})();
break;
}
case "POST": {
upstreamPostCount += 1;
const requestInit = prepareRequestInit(task.extra); const requestInit = prepareRequestInit(task.extra);
requestInit.method = "POST";
console.log("Dial POST", task.url); console.log("Dial GET", task.url);
ws.send("ok"); ws.send("ok");
ws.onmessage = async (event) => { const controller = new AbortController();
/*
Aborting a streaming response in JavaScript
requires two levers to be pulled:
First, the streaming read itself has to be cancelled using
reader.cancel(), only then controller.abort() will actually work.
If controller.abort() alone is called while a
reader.read() is ongoing, it will block until the server closes the
response, the page is refreshed or the network connection is lost.
*/
let reader = null;
ws.onclose = (event) => {
try { try {
requestInit.body = event.data; reader && reader.cancel();
const response = await fetch(task.url, requestInit); } catch(e) {}
if (response.ok) {
ws.send("ok"); try {
} else { controller.abort();
console.error("bad status code"); } catch(e) {}
ws.send("fail");
}
} finally {
upstreamPostCount -= 1;
console.log("Dial POST DONE, remaining: ", upstreamPostCount);
ws.close();
}
}; };
break;
} try {
upstreamGetCount += 1;
requestInit.signal = controller.signal;
setCookiesFromTask(task);
const response = await fetch(task.url, requestInit);
clearCookiesFromTask(task);
const body = await response.body;
reader = body.getReader();
while (true) {
const { done, value } = await reader.read();
if (value) ws.send(value); // don't send back "undefined" string when received nothing
if (done) break;
}
} finally {
upstreamGetCount -= 1;
console.log("Dial GET DONE, remaining: ", upstreamGetCount);
ws.close();
}
})();
}
else if (!task.streamResponse) {
upstreamPostCount += 1;
const requestInit = prepareRequestInit(task.extra);
requestInit.method = task.method;
console.log("Dial", task.method, task.url);
ws.send("ok");
ws.onmessage = async (event) => {
try {
if (event.data.byteLength > 0) {
requestInit.body = event.data;
}
setCookiesFromTask(task);
const response = await fetch(task.url, requestInit);
clearCookiesFromTask(task);
if (response.ok) {
ws.send("ok");
} else {
console.error("bad status code");
ws.send("fail");
}
} finally {
upstreamPostCount -= 1;
console.log("Dial", task.method, "packet DONE, remaining: ", upstreamPostCount);
ws.close();
}
};
}
else {
console.error(`Incorrect task method=${task.method} streamResponse=${task.streamResponse}.`);
ws.close();
} }
check(); check();
+268 -40
View File
@@ -206,7 +206,7 @@ func (x SocketConfig_TProxyMode) Number() protoreflect.EnumNumber {
// Deprecated: Use SocketConfig_TProxyMode.Descriptor instead. // Deprecated: Use SocketConfig_TProxyMode.Descriptor instead.
func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) { func (SocketConfig_TProxyMode) EnumDescriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4, 0} return file_transport_internet_config_proto_rawDescGZIP(), []int{6, 0}
} }
type TransportConfig struct { type TransportConfig struct {
@@ -276,6 +276,7 @@ type StreamConfig struct {
SecuritySettings []*serial.TypedMessage `protobuf:"bytes,4,rep,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"` SecuritySettings []*serial.TypedMessage `protobuf:"bytes,4,rep,name=security_settings,json=securitySettings,proto3" json:"security_settings,omitempty"`
Udpmasks []*serial.TypedMessage `protobuf:"bytes,10,rep,name=udpmasks,proto3" json:"udpmasks,omitempty"` Udpmasks []*serial.TypedMessage `protobuf:"bytes,10,rep,name=udpmasks,proto3" json:"udpmasks,omitempty"`
Tcpmasks []*serial.TypedMessage `protobuf:"bytes,11,rep,name=tcpmasks,proto3" json:"tcpmasks,omitempty"` Tcpmasks []*serial.TypedMessage `protobuf:"bytes,11,rep,name=tcpmasks,proto3" json:"tcpmasks,omitempty"`
QuicParams *QuicParams `protobuf:"bytes,12,opt,name=quic_params,json=quicParams,proto3" json:"quic_params,omitempty"`
SocketSettings *SocketConfig `protobuf:"bytes,6,opt,name=socket_settings,json=socketSettings,proto3" json:"socket_settings,omitempty"` SocketSettings *SocketConfig `protobuf:"bytes,6,opt,name=socket_settings,json=socketSettings,proto3" json:"socket_settings,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -367,6 +368,13 @@ func (x *StreamConfig) GetTcpmasks() []*serial.TypedMessage {
return nil return nil
} }
func (x *StreamConfig) GetQuicParams() *QuicParams {
if x != nil {
return x.QuicParams
}
return nil
}
func (x *StreamConfig) GetSocketSettings() *SocketConfig { func (x *StreamConfig) GetSocketSettings() *SocketConfig {
if x != nil { if x != nil {
return x.SocketSettings return x.SocketSettings
@@ -374,6 +382,198 @@ func (x *StreamConfig) GetSocketSettings() *SocketConfig {
return nil return nil
} }
type UdpHop struct {
state protoimpl.MessageState `protogen:"open.v1"`
Ports []uint32 `protobuf:"varint,1,rep,packed,name=ports,proto3" json:"ports,omitempty"`
IntervalMin int64 `protobuf:"varint,2,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"`
IntervalMax int64 `protobuf:"varint,3,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UdpHop) Reset() {
*x = UdpHop{}
mi := &file_transport_internet_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UdpHop) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UdpHop) ProtoMessage() {}
func (x *UdpHop) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UdpHop.ProtoReflect.Descriptor instead.
func (*UdpHop) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2}
}
func (x *UdpHop) GetPorts() []uint32 {
if x != nil {
return x.Ports
}
return nil
}
func (x *UdpHop) GetIntervalMin() int64 {
if x != nil {
return x.IntervalMin
}
return 0
}
func (x *UdpHop) GetIntervalMax() int64 {
if x != nil {
return x.IntervalMax
}
return 0
}
type QuicParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
BrutalUp uint64 `protobuf:"varint,2,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
BrutalDown uint64 `protobuf:"varint,3,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
UdpHop *UdpHop `protobuf:"bytes,4,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"`
InitStreamReceiveWindow uint64 `protobuf:"varint,5,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
MaxStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
InitConnReceiveWindow uint64 `protobuf:"varint,7,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
MaxConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
MaxIdleTimeout int64 `protobuf:"varint,9,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
KeepAlivePeriod int64 `protobuf:"varint,10,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
DisablePathMtuDiscovery bool `protobuf:"varint,11,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
MaxIncomingStreams int64 `protobuf:"varint,12,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *QuicParams) Reset() {
*x = QuicParams{}
mi := &file_transport_internet_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *QuicParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QuicParams) ProtoMessage() {}
func (x *QuicParams) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QuicParams.ProtoReflect.Descriptor instead.
func (*QuicParams) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{3}
}
func (x *QuicParams) GetCongestion() string {
if x != nil {
return x.Congestion
}
return ""
}
func (x *QuicParams) GetBrutalUp() uint64 {
if x != nil {
return x.BrutalUp
}
return 0
}
func (x *QuicParams) GetBrutalDown() uint64 {
if x != nil {
return x.BrutalDown
}
return 0
}
func (x *QuicParams) GetUdpHop() *UdpHop {
if x != nil {
return x.UdpHop
}
return nil
}
func (x *QuicParams) GetInitStreamReceiveWindow() uint64 {
if x != nil {
return x.InitStreamReceiveWindow
}
return 0
}
func (x *QuicParams) GetMaxStreamReceiveWindow() uint64 {
if x != nil {
return x.MaxStreamReceiveWindow
}
return 0
}
func (x *QuicParams) GetInitConnReceiveWindow() uint64 {
if x != nil {
return x.InitConnReceiveWindow
}
return 0
}
func (x *QuicParams) GetMaxConnReceiveWindow() uint64 {
if x != nil {
return x.MaxConnReceiveWindow
}
return 0
}
func (x *QuicParams) GetMaxIdleTimeout() int64 {
if x != nil {
return x.MaxIdleTimeout
}
return 0
}
func (x *QuicParams) GetKeepAlivePeriod() int64 {
if x != nil {
return x.KeepAlivePeriod
}
return 0
}
func (x *QuicParams) GetDisablePathMtuDiscovery() bool {
if x != nil {
return x.DisablePathMtuDiscovery
}
return false
}
func (x *QuicParams) GetMaxIncomingStreams() int64 {
if x != nil {
return x.MaxIncomingStreams
}
return 0
}
type ProxyConfig struct { type ProxyConfig struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
@@ -384,7 +584,7 @@ type ProxyConfig struct {
func (x *ProxyConfig) Reset() { func (x *ProxyConfig) Reset() {
*x = ProxyConfig{} *x = ProxyConfig{}
mi := &file_transport_internet_config_proto_msgTypes[2] mi := &file_transport_internet_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -396,7 +596,7 @@ func (x *ProxyConfig) String() string {
func (*ProxyConfig) ProtoMessage() {} func (*ProxyConfig) ProtoMessage() {}
func (x *ProxyConfig) ProtoReflect() protoreflect.Message { func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[2] mi := &file_transport_internet_config_proto_msgTypes[4]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -409,7 +609,7 @@ func (x *ProxyConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead. // Deprecated: Use ProxyConfig.ProtoReflect.Descriptor instead.
func (*ProxyConfig) Descriptor() ([]byte, []int) { func (*ProxyConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{2} return file_transport_internet_config_proto_rawDescGZIP(), []int{4}
} }
func (x *ProxyConfig) GetTag() string { func (x *ProxyConfig) GetTag() string {
@@ -440,7 +640,7 @@ type CustomSockopt struct {
func (x *CustomSockopt) Reset() { func (x *CustomSockopt) Reset() {
*x = CustomSockopt{} *x = CustomSockopt{}
mi := &file_transport_internet_config_proto_msgTypes[3] mi := &file_transport_internet_config_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -452,7 +652,7 @@ func (x *CustomSockopt) String() string {
func (*CustomSockopt) ProtoMessage() {} func (*CustomSockopt) ProtoMessage() {}
func (x *CustomSockopt) ProtoReflect() protoreflect.Message { func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[3] mi := &file_transport_internet_config_proto_msgTypes[5]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -465,7 +665,7 @@ func (x *CustomSockopt) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead. // Deprecated: Use CustomSockopt.ProtoReflect.Descriptor instead.
func (*CustomSockopt) Descriptor() ([]byte, []int) { func (*CustomSockopt) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{3} return file_transport_internet_config_proto_rawDescGZIP(), []int{5}
} }
func (x *CustomSockopt) GetSystem() string { func (x *CustomSockopt) GetSystem() string {
@@ -547,7 +747,7 @@ type SocketConfig struct {
func (x *SocketConfig) Reset() { func (x *SocketConfig) Reset() {
*x = SocketConfig{} *x = SocketConfig{}
mi := &file_transport_internet_config_proto_msgTypes[4] mi := &file_transport_internet_config_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -559,7 +759,7 @@ func (x *SocketConfig) String() string {
func (*SocketConfig) ProtoMessage() {} func (*SocketConfig) ProtoMessage() {}
func (x *SocketConfig) ProtoReflect() protoreflect.Message { func (x *SocketConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[4] mi := &file_transport_internet_config_proto_msgTypes[6]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -572,7 +772,7 @@ func (x *SocketConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead. // Deprecated: Use SocketConfig.ProtoReflect.Descriptor instead.
func (*SocketConfig) Descriptor() ([]byte, []int) { func (*SocketConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{4} return file_transport_internet_config_proto_rawDescGZIP(), []int{6}
} }
func (x *SocketConfig) GetMark() int32 { func (x *SocketConfig) GetMark() int32 {
@@ -748,7 +948,7 @@ type HappyEyeballsConfig struct {
func (x *HappyEyeballsConfig) Reset() { func (x *HappyEyeballsConfig) Reset() {
*x = HappyEyeballsConfig{} *x = HappyEyeballsConfig{}
mi := &file_transport_internet_config_proto_msgTypes[5] mi := &file_transport_internet_config_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -760,7 +960,7 @@ func (x *HappyEyeballsConfig) String() string {
func (*HappyEyeballsConfig) ProtoMessage() {} func (*HappyEyeballsConfig) ProtoMessage() {}
func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message { func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_config_proto_msgTypes[5] mi := &file_transport_internet_config_proto_msgTypes[7]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -773,7 +973,7 @@ func (x *HappyEyeballsConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead. // Deprecated: Use HappyEyeballsConfig.ProtoReflect.Descriptor instead.
func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) { func (*HappyEyeballsConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_config_proto_rawDescGZIP(), []int{5} return file_transport_internet_config_proto_rawDescGZIP(), []int{7}
} }
func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool { func (x *HappyEyeballsConfig) GetPrioritizeIpv6() bool {
@@ -811,7 +1011,7 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\x1ftransport/internet/config.proto\x12\x17xray.transport.internet\x1a!common/serial/typed_message.proto\x1a\x18common/net/address.proto\"t\n" + "\x1ftransport/internet/config.proto\x12\x17xray.transport.internet\x1a!common/serial/typed_message.proto\x1a\x18common/net/address.proto\"t\n" +
"\x0fTransportConfig\x12#\n" + "\x0fTransportConfig\x12#\n" +
"\rprotocol_name\x18\x03 \x01(\tR\fprotocolName\x12<\n" + "\rprotocol_name\x18\x03 \x01(\tR\fprotocolName\x12<\n" +
"\bsettings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\bsettings\"\x97\x04\n" + "\bsettings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\bsettings\"\xdd\x04\n" +
"\fStreamConfig\x125\n" + "\fStreamConfig\x125\n" +
"\aaddress\x18\b \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" + "\aaddress\x18\b \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" +
"\x04port\x18\t \x01(\rR\x04port\x12#\n" + "\x04port\x18\t \x01(\rR\x04port\x12#\n" +
@@ -821,8 +1021,32 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\x11security_settings\x18\x04 \x03(\v2 .xray.common.serial.TypedMessageR\x10securitySettings\x12<\n" + "\x11security_settings\x18\x04 \x03(\v2 .xray.common.serial.TypedMessageR\x10securitySettings\x12<\n" +
"\budpmasks\x18\n" + "\budpmasks\x18\n" +
" \x03(\v2 .xray.common.serial.TypedMessageR\budpmasks\x12<\n" + " \x03(\v2 .xray.common.serial.TypedMessageR\budpmasks\x12<\n" +
"\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12N\n" + "\btcpmasks\x18\v \x03(\v2 .xray.common.serial.TypedMessageR\btcpmasks\x12D\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"Q\n" + "\vquic_params\x18\f \x01(\v2#.xray.transport.internet.QuicParamsR\n" +
"quicParams\x12N\n" +
"\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"d\n" +
"\x06UdpHop\x12\x14\n" +
"\x05ports\x18\x01 \x03(\rR\x05ports\x12!\n" +
"\finterval_min\x18\x02 \x01(\x03R\vintervalMin\x12!\n" +
"\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xd1\x04\n" +
"\n" +
"QuicParams\x12\x1e\n" +
"\n" +
"congestion\x18\x01 \x01(\tR\n" +
"congestion\x12\x1b\n" +
"\tbrutal_up\x18\x02 \x01(\x04R\bbrutalUp\x12\x1f\n" +
"\vbrutal_down\x18\x03 \x01(\x04R\n" +
"brutalDown\x128\n" +
"\audp_hop\x18\x04 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" +
"\x1ainit_stream_receive_window\x18\x05 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x19max_stream_receive_window\x18\x06 \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x18init_conn_receive_window\x18\a \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x17max_conn_receive_window\x18\b \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x10max_idle_timeout\x18\t \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\n" +
" \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\v \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
"\x14max_incoming_streams\x18\f \x01(\x03R\x12maxIncomingStreams\"Q\n" +
"\vProxyConfig\x12\x10\n" + "\vProxyConfig\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" + "\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" + "\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
@@ -911,38 +1135,42 @@ func file_transport_internet_config_proto_rawDescGZIP() []byte {
} }
var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3) var file_transport_internet_config_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_transport_internet_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_transport_internet_config_proto_goTypes = []any{ var file_transport_internet_config_proto_goTypes = []any{
(DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy (DomainStrategy)(0), // 0: xray.transport.internet.DomainStrategy
(AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy (AddressPortStrategy)(0), // 1: xray.transport.internet.AddressPortStrategy
(SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode (SocketConfig_TProxyMode)(0), // 2: xray.transport.internet.SocketConfig.TProxyMode
(*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig (*TransportConfig)(nil), // 3: xray.transport.internet.TransportConfig
(*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig (*StreamConfig)(nil), // 4: xray.transport.internet.StreamConfig
(*ProxyConfig)(nil), // 5: xray.transport.internet.ProxyConfig (*UdpHop)(nil), // 5: xray.transport.internet.UdpHop
(*CustomSockopt)(nil), // 6: xray.transport.internet.CustomSockopt (*QuicParams)(nil), // 6: xray.transport.internet.QuicParams
(*SocketConfig)(nil), // 7: xray.transport.internet.SocketConfig (*ProxyConfig)(nil), // 7: xray.transport.internet.ProxyConfig
(*HappyEyeballsConfig)(nil), // 8: xray.transport.internet.HappyEyeballsConfig (*CustomSockopt)(nil), // 8: xray.transport.internet.CustomSockopt
(*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage (*SocketConfig)(nil), // 9: xray.transport.internet.SocketConfig
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain (*HappyEyeballsConfig)(nil), // 10: xray.transport.internet.HappyEyeballsConfig
(*serial.TypedMessage)(nil), // 11: xray.common.serial.TypedMessage
(*net.IPOrDomain)(nil), // 12: xray.common.net.IPOrDomain
} }
var file_transport_internet_config_proto_depIdxs = []int32{ var file_transport_internet_config_proto_depIdxs = []int32{
9, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage 11, // 0: xray.transport.internet.TransportConfig.settings:type_name -> xray.common.serial.TypedMessage
10, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain 12, // 1: xray.transport.internet.StreamConfig.address:type_name -> xray.common.net.IPOrDomain
3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig 3, // 2: xray.transport.internet.StreamConfig.transport_settings:type_name -> xray.transport.internet.TransportConfig
9, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage 11, // 3: xray.transport.internet.StreamConfig.security_settings:type_name -> xray.common.serial.TypedMessage
9, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage 11, // 4: xray.transport.internet.StreamConfig.udpmasks:type_name -> xray.common.serial.TypedMessage
9, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage 11, // 5: xray.transport.internet.StreamConfig.tcpmasks:type_name -> xray.common.serial.TypedMessage
7, // 6: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig 6, // 6: xray.transport.internet.StreamConfig.quic_params:type_name -> xray.transport.internet.QuicParams
2, // 7: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode 9, // 7: xray.transport.internet.StreamConfig.socket_settings:type_name -> xray.transport.internet.SocketConfig
0, // 8: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy 5, // 8: xray.transport.internet.QuicParams.udp_hop:type_name -> xray.transport.internet.UdpHop
6, // 9: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt 2, // 9: xray.transport.internet.SocketConfig.tproxy:type_name -> xray.transport.internet.SocketConfig.TProxyMode
1, // 10: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy 0, // 10: xray.transport.internet.SocketConfig.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
8, // 11: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig 8, // 11: xray.transport.internet.SocketConfig.customSockopt:type_name -> xray.transport.internet.CustomSockopt
12, // [12:12] is the sub-list for method output_type 1, // 12: xray.transport.internet.SocketConfig.address_port_strategy:type_name -> xray.transport.internet.AddressPortStrategy
12, // [12:12] is the sub-list for method input_type 10, // 13: xray.transport.internet.SocketConfig.happy_eyeballs:type_name -> xray.transport.internet.HappyEyeballsConfig
12, // [12:12] is the sub-list for extension type_name 14, // [14:14] is the sub-list for method output_type
12, // [12:12] is the sub-list for extension extendee 14, // [14:14] is the sub-list for method input_type
0, // [0:12] is the sub-list for field type_name 14, // [14:14] is the sub-list for extension type_name
14, // [14:14] is the sub-list for extension extendee
0, // [0:14] is the sub-list for field type_name
} }
func init() { file_transport_internet_config_proto_init() } func init() { file_transport_internet_config_proto_init() }
@@ -956,7 +1184,7 @@ func file_transport_internet_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_config_proto_rawDesc), len(file_transport_internet_config_proto_rawDesc)),
NumEnums: 3, NumEnums: 3,
NumMessages: 6, NumMessages: 8,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
+23
View File
@@ -59,9 +59,32 @@ message StreamConfig {
repeated xray.common.serial.TypedMessage udpmasks = 10; repeated xray.common.serial.TypedMessage udpmasks = 10;
repeated xray.common.serial.TypedMessage tcpmasks = 11; repeated xray.common.serial.TypedMessage tcpmasks = 11;
QuicParams quic_params = 12;
SocketConfig socket_settings = 6; SocketConfig socket_settings = 6;
} }
message UdpHop {
repeated uint32 ports = 1;
int64 interval_min = 2;
int64 interval_max = 3;
}
message QuicParams {
string congestion = 1;
uint64 brutal_up = 2;
uint64 brutal_down = 3;
UdpHop udp_hop = 4;
uint64 init_stream_receive_window = 5;
uint64 max_stream_receive_window = 6;
uint64 init_conn_receive_window = 7;
uint64 max_conn_receive_window = 8;
int64 max_idle_timeout = 9;
int64 keep_alive_period = 10;
bool disable_path_mtu_discovery = 11;
int64 max_incoming_streams = 12;
}
message ProxyConfig { message ProxyConfig {
string tag = 1; string tag = 1;
bool transportLayerProxy = 2; bool transportLayerProxy = 2;
+203 -18
View File
@@ -1,18 +1,18 @@
package finalmask package finalmask
import ( import (
"context"
"net" "net"
) "sync"
type ConnSize interface { "github.com/xtls/xray-core/common/errors"
Size() int32 )
}
type Udpmask interface { type Udpmask interface {
UDP() UDP()
WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error)
} }
type UdpmaskManager struct { type UdpmaskManager struct {
@@ -26,31 +26,165 @@ func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
} }
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) { func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
leaveSize := int32(0) var sizes []int
var err error var conns []net.PacketConn
for i, mask := range m.udpmasks { for i, mask := range m.udpmasks {
raw, err = mask.WrapPacketConnClient(raw, i == len(m.udpmasks)-1, leaveSize, i == 0) if _, ok := mask.(headerConn); ok {
if err != nil { conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
return nil, err if err != nil {
return nil, err
}
sizes = append(sizes, conn.(headerSize).Size())
conns = append(conns, conn)
} else {
if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
var err error
raw, err = mask.WrapPacketConnClient(raw, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
}
} }
leaveSize += raw.(ConnSize).Size() }
if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
} }
return raw, nil return raw, nil
} }
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) { func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
leaveSize := int32(0) var sizes []int
var err error var conns []net.PacketConn
for i, mask := range m.udpmasks { for i, mask := range m.udpmasks {
raw, err = mask.WrapPacketConnServer(raw, i == len(m.udpmasks)-1, leaveSize, i == 0) if _, ok := mask.(headerConn); ok {
if err != nil { conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
return nil, err if err != nil {
return nil, err
}
sizes = append(sizes, conn.(headerSize).Size())
conns = append(conns, conn)
} else {
if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
}
var err error
raw, err = mask.WrapPacketConnServer(raw, i, len(m.udpmasks)-1)
if err != nil {
return nil, err
}
} }
leaveSize += raw.(ConnSize).Size() }
if len(conns) > 0 {
raw = &headerManagerConn{sizes: sizes, conns: conns, PacketConn: raw}
sizes = nil
conns = nil
} }
return raw, nil return raw, nil
} }
const (
UDPSize = 4096
)
type headerConn interface {
HeaderConn()
}
type headerSize interface {
Size() int
}
type headerManagerConn struct {
sizes []int
conns []net.PacketConn
net.PacketConn
m sync.Mutex
writeBuf [UDPSize]byte
}
func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buf := p
if len(buf) < UDPSize {
buf = make([]byte, UDPSize)
}
n, addr, err = c.PacketConn.ReadFrom(buf)
if n == 0 || err != nil {
return 0, addr, err
}
newBuf := buf[:n]
sum := 0
for _, size := range c.sizes {
sum += size
}
if n < sum {
errors.LogDebug(context.Background(), addr, " mask read err short length")
return 0, addr, nil
}
for i := range c.conns {
n, _, err = c.conns[i].ReadFrom(newBuf)
if n == 0 || err != nil {
errors.LogDebug(context.Background(), addr, " mask read err ", err)
return 0, addr, nil
}
newBuf = newBuf[c.sizes[i] : n+c.sizes[i]]
}
if len(p) < n {
errors.LogDebug(context.Background(), addr, " mask read err short buffer")
return 0, addr, nil
}
copy(p, buf[sum:sum+n])
return n, addr, nil
}
func (c *headerManagerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.m.Lock()
defer c.m.Unlock()
sum := 0
for _, size := range c.sizes {
sum += size
}
if sum+len(p) > UDPSize {
errors.LogDebug(context.Background(), addr, " mask write err short write")
return 0, nil
}
n = copy(c.writeBuf[sum:], p)
for i := len(c.conns) - 1; i >= 0; i-- {
n, err = c.conns[i].WriteTo(c.writeBuf[sum-c.sizes[i]:n+sum], nil)
if n == 0 || err != nil {
errors.LogDebug(context.Background(), addr, " mask write err ", err)
return 0, nil
}
sum -= c.sizes[i]
}
n, err = c.PacketConn.WriteTo(c.writeBuf[:n], addr)
if n == 0 || err != nil {
return n, err
}
return len(p), nil
}
type Tcpmask interface { type Tcpmask interface {
TCP() TCP()
@@ -89,3 +223,54 @@ func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
} }
return raw, nil return raw, nil
} }
func (m *TcpmaskManager) WrapListener(l net.Listener) (net.Listener, error) {
return NewTcpListener(m, l)
}
type tcpListener struct {
m *TcpmaskManager
net.Listener
}
func NewTcpListener(m *TcpmaskManager, l net.Listener) (net.Listener, error) {
return &tcpListener{
m: m,
Listener: l,
}, nil
}
func (l *tcpListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return conn, err
}
newConn, err := l.m.WrapConnServer(conn)
if err != nil {
errors.LogDebugInner(context.Background(), err, "mask err")
// conn.Close()
return conn, nil
}
return newConn, nil
}
type TcpMaskConn interface {
TcpMaskConn()
RawConn() net.Conn
Splice() bool
}
func UnwrapTcpMask(conn net.Conn) net.Conn {
for {
if v, ok := conn.(TcpMaskConn); ok {
if !v.Splice() {
return conn
}
conn = v.RawConn()
} else {
return conn
}
}
}
@@ -0,0 +1,14 @@
package fragment
import "net"
func (c *Config) TCP() {
}
func (c *Config) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClient(c, raw, false)
}
func (c *Config) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServer(c, raw, true)
}
@@ -0,0 +1,189 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/finalmask/fragment/config.proto
package fragment
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
PacketsFrom int64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"`
PacketsTo int64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"`
LengthMin int64 `protobuf:"varint,3,opt,name=length_min,json=lengthMin,proto3" json:"length_min,omitempty"`
LengthMax int64 `protobuf:"varint,4,opt,name=length_max,json=lengthMax,proto3" json:"length_max,omitempty"`
DelayMin int64 `protobuf:"varint,5,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,6,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
MaxSplitMin int64 `protobuf:"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3" json:"max_split_min,omitempty"`
MaxSplitMax int64 `protobuf:"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3" json:"max_split_max,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_transport_internet_finalmask_fragment_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_fragment_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_fragment_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetPacketsFrom() int64 {
if x != nil {
return x.PacketsFrom
}
return 0
}
func (x *Config) GetPacketsTo() int64 {
if x != nil {
return x.PacketsTo
}
return 0
}
func (x *Config) GetLengthMin() int64 {
if x != nil {
return x.LengthMin
}
return 0
}
func (x *Config) GetLengthMax() int64 {
if x != nil {
return x.LengthMax
}
return 0
}
func (x *Config) GetDelayMin() int64 {
if x != nil {
return x.DelayMin
}
return 0
}
func (x *Config) GetDelayMax() int64 {
if x != nil {
return x.DelayMax
}
return 0
}
func (x *Config) GetMaxSplitMin() int64 {
if x != nil {
return x.MaxSplitMin
}
return 0
}
func (x *Config) GetMaxSplitMax() int64 {
if x != nil {
return x.MaxSplitMax
}
return 0
}
var File_transport_internet_finalmask_fragment_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_fragment_config_proto_rawDesc = "" +
"\n" +
"2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x8a\x02\n" +
"\x06Config\x12!\n" +
"\fpackets_from\x18\x01 \x01(\x03R\vpacketsFrom\x12\x1d\n" +
"\n" +
"packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\x1d\n" +
"\n" +
"length_min\x18\x03 \x01(\x03R\tlengthMin\x12\x1d\n" +
"\n" +
"length_max\x18\x04 \x01(\x03R\tlengthMax\x12\x1b\n" +
"\tdelay_min\x18\x05 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x06 \x01(\x03R\bdelayMax\x12\"\n" +
"\rmax_split_min\x18\a \x01(\x03R\vmaxSplitMin\x12\"\n" +
"\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMaxB\xa0\x01\n" +
".com.xray.transport.internet.finalmask.fragmentP\x01Z?github.com/xtls/xray-core/transport/internet/finalmask/fragment\xaa\x02*Xray.Transport.Internet.Finalmask.Fragmentb\x06proto3"
var (
file_transport_internet_finalmask_fragment_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_fragment_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_fragment_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_fragment_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_fragment_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_fragment_config_proto_rawDesc), len(file_transport_internet_finalmask_fragment_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_fragment_config_proto_rawDescData
}
var file_transport_internet_finalmask_fragment_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_transport_internet_finalmask_fragment_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.transport.internet.finalmask.fragment.Config
}
var file_transport_internet_finalmask_fragment_config_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_fragment_config_proto_init() }
func file_transport_internet_finalmask_fragment_config_proto_init() {
if File_transport_internet_finalmask_fragment_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_fragment_config_proto_rawDesc), len(file_transport_internet_finalmask_fragment_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_fragment_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_fragment_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_fragment_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_fragment_config_proto = out.File
file_transport_internet_finalmask_fragment_config_proto_goTypes = nil
file_transport_internet_finalmask_fragment_config_proto_depIdxs = nil
}
@@ -0,0 +1,18 @@
syntax = "proto3";
package xray.transport.internet.finalmask.fragment;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Fragment";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/fragment";
option java_package = "com.xray.transport.internet.finalmask.fragment";
option java_multiple_files = true;
message Config {
int64 packets_from = 1;
int64 packets_to = 2;
int64 length_min = 3;
int64 length_max = 4;
int64 delay_min = 5;
int64 delay_max = 6;
int64 max_split_min = 7;
int64 max_split_max = 8;
}
@@ -0,0 +1,125 @@
package fragment
import (
"net"
"time"
"github.com/xtls/xray-core/common/crypto"
)
type fragmentConn struct {
net.Conn
config *Config
count uint64
server bool
}
func NewConnClient(c *Config, raw net.Conn, server bool) (net.Conn, error) {
conn := &fragmentConn{
Conn: raw,
config: c,
server: server,
}
return conn, nil
}
func NewConnServer(c *Config, raw net.Conn, server bool) (net.Conn, error) {
return NewConnClient(c, raw, server)
}
func (c *fragmentConn) TcpMaskConn() {}
func (c *fragmentConn) RawConn() net.Conn {
return c.Conn
}
func (c *fragmentConn) Splice() bool {
if c.server {
return false
}
return true
}
func (c *fragmentConn) Write(p []byte) (n int, err error) {
c.count++
if c.config.PacketsFrom == 0 && c.config.PacketsTo == 1 {
if c.count != 1 || len(p) <= 5 || p[0] != 22 {
return c.Conn.Write(p)
}
recordLen := 5 + ((int(p[3]) << 8) | int(p[4]))
if len(p) < recordLen {
return c.Conn.Write(p)
}
data := p[5:recordLen]
buff := make([]byte, 2048)
var hello []byte
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64
for from := 0; ; {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
splitNum++
if to > len(data) || (maxSplit > 0 && splitNum >= maxSplit) {
to = len(data)
}
l := to - from
if 5+l > len(buff) {
buff = make([]byte, 5+l)
}
copy(buff[:3], p)
copy(buff[5:], data[from:to])
from = to
buff[3] = byte(l >> 8)
buff[4] = byte(l)
if c.config.DelayMax == 0 {
hello = append(hello, buff[:5+l]...)
} else {
_, err := c.Conn.Write(buff[:5+l])
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
if err != nil {
return 0, err
}
}
if from == len(data) {
if len(hello) > 0 {
_, err := c.Conn.Write(hello)
if err != nil {
return 0, err
}
}
if len(p) > recordLen {
n, err := c.Conn.Write(p[recordLen:])
if err != nil {
return recordLen + n, err
}
}
return len(p), nil
}
}
}
if c.config.PacketsFrom != 0 && (c.count < uint64(c.config.PacketsFrom) || c.count > uint64(c.config.PacketsTo)) {
return c.Conn.Write(p)
}
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64
for from := 0; ; {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
splitNum++
if to > len(p) || (maxSplit > 0 && splitNum >= maxSplit) {
to = len(p)
}
n, err := c.Conn.Write(p[from:to])
from += n
if err != nil {
return from, err
}
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
if from >= len(p) {
return from, nil
}
}
}
@@ -0,0 +1,30 @@
package custom
import (
"net"
)
func (c *TCPConfig) TCP() {
}
func (c *TCPConfig) WrapConnClient(raw net.Conn) (net.Conn, error) {
return NewConnClientTCP(c, raw)
}
func (c *TCPConfig) WrapConnServer(raw net.Conn) (net.Conn, error) {
return NewConnServerTCP(c, raw)
}
func (c *UDPConfig) UDP() {
}
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClientUDP(c, raw)
}
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServerUDP(c, raw)
}
func (c *UDPConfig) HeaderConn() {
}
@@ -0,0 +1,416 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v6.33.5
// source: transport/internet/finalmask/header/custom/config.proto
package custom
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type TCPItem struct {
state protoimpl.MessageState `protogen:"open.v1"`
DelayMin int64 `protobuf:"varint,1,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,2,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
Rand int32 `protobuf:"varint,3,opt,name=rand,proto3" json:"rand,omitempty"`
RandMin int32 `protobuf:"varint,4,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
RandMax int32 `protobuf:"varint,5,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
Packet []byte `protobuf:"bytes,6,opt,name=packet,proto3" json:"packet,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPItem) Reset() {
*x = TCPItem{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TCPItem) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TCPItem) ProtoMessage() {}
func (x *TCPItem) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TCPItem.ProtoReflect.Descriptor instead.
func (*TCPItem) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{0}
}
func (x *TCPItem) GetDelayMin() int64 {
if x != nil {
return x.DelayMin
}
return 0
}
func (x *TCPItem) GetDelayMax() int64 {
if x != nil {
return x.DelayMax
}
return 0
}
func (x *TCPItem) GetRand() int32 {
if x != nil {
return x.Rand
}
return 0
}
func (x *TCPItem) GetRandMin() int32 {
if x != nil {
return x.RandMin
}
return 0
}
func (x *TCPItem) GetRandMax() int32 {
if x != nil {
return x.RandMax
}
return 0
}
func (x *TCPItem) GetPacket() []byte {
if x != nil {
return x.Packet
}
return nil
}
type TCPSequence struct {
state protoimpl.MessageState `protogen:"open.v1"`
Sequence []*TCPItem `protobuf:"bytes,1,rep,name=sequence,proto3" json:"sequence,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPSequence) Reset() {
*x = TCPSequence{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TCPSequence) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TCPSequence) ProtoMessage() {}
func (x *TCPSequence) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TCPSequence.ProtoReflect.Descriptor instead.
func (*TCPSequence) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{1}
}
func (x *TCPSequence) GetSequence() []*TCPItem {
if x != nil {
return x.Sequence
}
return nil
}
type TCPConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Clients []*TCPSequence `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
Servers []*TCPSequence `protobuf:"bytes,2,rep,name=servers,proto3" json:"servers,omitempty"`
Errors []*TCPSequence `protobuf:"bytes,3,rep,name=errors,proto3" json:"errors,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *TCPConfig) Reset() {
*x = TCPConfig{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TCPConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TCPConfig) ProtoMessage() {}
func (x *TCPConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TCPConfig.ProtoReflect.Descriptor instead.
func (*TCPConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{2}
}
func (x *TCPConfig) GetClients() []*TCPSequence {
if x != nil {
return x.Clients
}
return nil
}
func (x *TCPConfig) GetServers() []*TCPSequence {
if x != nil {
return x.Servers
}
return nil
}
func (x *TCPConfig) GetErrors() []*TCPSequence {
if x != nil {
return x.Errors
}
return nil
}
type UDPItem struct {
state protoimpl.MessageState `protogen:"open.v1"`
Rand int32 `protobuf:"varint,1,opt,name=rand,proto3" json:"rand,omitempty"`
RandMin int32 `protobuf:"varint,2,opt,name=rand_min,json=randMin,proto3" json:"rand_min,omitempty"`
RandMax int32 `protobuf:"varint,3,opt,name=rand_max,json=randMax,proto3" json:"rand_max,omitempty"`
Packet []byte `protobuf:"bytes,4,opt,name=packet,proto3" json:"packet,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UDPItem) Reset() {
*x = UDPItem{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UDPItem) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UDPItem) ProtoMessage() {}
func (x *UDPItem) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UDPItem.ProtoReflect.Descriptor instead.
func (*UDPItem) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{3}
}
func (x *UDPItem) GetRand() int32 {
if x != nil {
return x.Rand
}
return 0
}
func (x *UDPItem) GetRandMin() int32 {
if x != nil {
return x.RandMin
}
return 0
}
func (x *UDPItem) GetRandMax() int32 {
if x != nil {
return x.RandMax
}
return 0
}
func (x *UDPItem) GetPacket() []byte {
if x != nil {
return x.Packet
}
return nil
}
type UDPConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Client []*UDPItem `protobuf:"bytes,1,rep,name=client,proto3" json:"client,omitempty"`
Server []*UDPItem `protobuf:"bytes,2,rep,name=server,proto3" json:"server,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UDPConfig) Reset() {
*x = UDPConfig{}
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UDPConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UDPConfig) ProtoMessage() {}
func (x *UDPConfig) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_finalmask_header_custom_config_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UDPConfig.ProtoReflect.Descriptor instead.
func (*UDPConfig) Descriptor() ([]byte, []int) {
return file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP(), []int{4}
}
func (x *UDPConfig) GetClient() []*UDPItem {
if x != nil {
return x.Client
}
return nil
}
func (x *UDPConfig) GetServer() []*UDPItem {
if x != nil {
return x.Server
}
return nil
}
var File_transport_internet_finalmask_header_custom_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_header_custom_config_proto_rawDesc = "" +
"\n" +
"7transport/internet/finalmask/header/custom/config.proto\x12/xray.transport.internet.finalmask.header.custom\"\xa5\x01\n" +
"\aTCPItem\x12\x1b\n" +
"\tdelay_min\x18\x01 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x02 \x01(\x03R\bdelayMax\x12\x12\n" +
"\x04rand\x18\x03 \x01(\x05R\x04rand\x12\x19\n" +
"\brand_min\x18\x04 \x01(\x05R\arandMin\x12\x19\n" +
"\brand_max\x18\x05 \x01(\x05R\arandMax\x12\x16\n" +
"\x06packet\x18\x06 \x01(\fR\x06packet\"c\n" +
"\vTCPSequence\x12T\n" +
"\bsequence\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.TCPItemR\bsequence\"\x91\x02\n" +
"\tTCPConfig\x12V\n" +
"\aclients\x18\x01 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aclients\x12V\n" +
"\aservers\x18\x02 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\aservers\x12T\n" +
"\x06errors\x18\x03 \x03(\v2<.xray.transport.internet.finalmask.header.custom.TCPSequenceR\x06errors\"k\n" +
"\aUDPItem\x12\x12\n" +
"\x04rand\x18\x01 \x01(\x05R\x04rand\x12\x19\n" +
"\brand_min\x18\x02 \x01(\x05R\arandMin\x12\x19\n" +
"\brand_max\x18\x03 \x01(\x05R\arandMax\x12\x16\n" +
"\x06packet\x18\x04 \x01(\fR\x06packet\"\xaf\x01\n" +
"\tUDPConfig\x12P\n" +
"\x06client\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06client\x12P\n" +
"\x06server\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06serverB\xaf\x01\n" +
"3com.xray.transport.internet.finalmask.header.customP\x01ZDgithub.com/xtls/xray-core/transport/internet/finalmask/header/custom\xaa\x02/Xray.Transport.Internet.Finalmask.Header.Customb\x06proto3"
var (
file_transport_internet_finalmask_header_custom_config_proto_rawDescOnce sync.Once
file_transport_internet_finalmask_header_custom_config_proto_rawDescData []byte
)
func file_transport_internet_finalmask_header_custom_config_proto_rawDescGZIP() []byte {
file_transport_internet_finalmask_header_custom_config_proto_rawDescOnce.Do(func() {
file_transport_internet_finalmask_header_custom_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)))
})
return file_transport_internet_finalmask_header_custom_config_proto_rawDescData
}
var file_transport_internet_finalmask_header_custom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_transport_internet_finalmask_header_custom_config_proto_goTypes = []any{
(*TCPItem)(nil), // 0: xray.transport.internet.finalmask.header.custom.TCPItem
(*TCPSequence)(nil), // 1: xray.transport.internet.finalmask.header.custom.TCPSequence
(*TCPConfig)(nil), // 2: xray.transport.internet.finalmask.header.custom.TCPConfig
(*UDPItem)(nil), // 3: xray.transport.internet.finalmask.header.custom.UDPItem
(*UDPConfig)(nil), // 4: xray.transport.internet.finalmask.header.custom.UDPConfig
}
var file_transport_internet_finalmask_header_custom_config_proto_depIdxs = []int32{
0, // 0: xray.transport.internet.finalmask.header.custom.TCPSequence.sequence:type_name -> xray.transport.internet.finalmask.header.custom.TCPItem
1, // 1: xray.transport.internet.finalmask.header.custom.TCPConfig.clients:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
1, // 2: xray.transport.internet.finalmask.header.custom.TCPConfig.servers:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
1, // 3: xray.transport.internet.finalmask.header.custom.TCPConfig.errors:type_name -> xray.transport.internet.finalmask.header.custom.TCPSequence
3, // 4: xray.transport.internet.finalmask.header.custom.UDPConfig.client:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
3, // 5: xray.transport.internet.finalmask.header.custom.UDPConfig.server:type_name -> xray.transport.internet.finalmask.header.custom.UDPItem
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
}
func init() { file_transport_internet_finalmask_header_custom_config_proto_init() }
func file_transport_internet_finalmask_header_custom_config_proto_init() {
if File_transport_internet_finalmask_header_custom_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_finalmask_header_custom_config_proto_rawDesc), len(file_transport_internet_finalmask_header_custom_config_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_finalmask_header_custom_config_proto_goTypes,
DependencyIndexes: file_transport_internet_finalmask_header_custom_config_proto_depIdxs,
MessageInfos: file_transport_internet_finalmask_header_custom_config_proto_msgTypes,
}.Build()
File_transport_internet_finalmask_header_custom_config_proto = out.File
file_transport_internet_finalmask_header_custom_config_proto_goTypes = nil
file_transport_internet_finalmask_header_custom_config_proto_depIdxs = nil
}
@@ -0,0 +1,38 @@
syntax = "proto3";
package xray.transport.internet.finalmask.header.custom;
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Header.Custom";
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/header/custom";
option java_package = "com.xray.transport.internet.finalmask.header.custom";
option java_multiple_files = true;
message TCPItem {
int64 delay_min = 1;
int64 delay_max = 2;
int32 rand = 3;
int32 rand_min = 4;
int32 rand_max = 5;
bytes packet = 6;
}
message TCPSequence {
repeated TCPItem sequence = 1;
}
message TCPConfig {
repeated TCPSequence clients = 1;
repeated TCPSequence servers = 2;
repeated TCPSequence errors = 3;
}
message UDPItem {
int32 rand = 1;
int32 rand_min = 2;
int32 rand_max = 3;
bytes packet = 4;
}
message UDPConfig {
repeated UDPItem client = 1;
repeated UDPItem server = 2;
}
@@ -0,0 +1,246 @@
package custom
import (
"bytes"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
)
type tcpCustomClient struct {
clients []*TCPSequence
servers []*TCPSequence
}
type tcpCustomClientConn struct {
net.Conn
header *tcpCustomClient
auth bool
wg sync.WaitGroup
once sync.Once
}
func NewConnClientTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
conn := &tcpCustomClientConn{
Conn: raw,
header: &tcpCustomClient{
clients: c.Clients,
servers: c.Servers,
},
}
conn.wg.Add(1)
return conn, nil
}
func (c *tcpCustomClientConn) TcpMaskConn() {}
func (c *tcpCustomClientConn) RawConn() net.Conn {
// c.wg.Wait()
return c.Conn
}
func (c *tcpCustomClientConn) Splice() bool {
return true
}
func (c *tcpCustomClientConn) Read(p []byte) (n int, err error) {
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Read(p)
}
func (c *tcpCustomClientConn) Write(p []byte) (n int, err error) {
c.once.Do(func() {
i := 0
j := 0
for i = range c.header.clients {
if !writeSequence(c.Conn, c.header.clients[i]) {
c.wg.Done()
return
}
if j < len(c.header.servers) {
if !readSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
}
for j < len(c.header.servers) {
if !readSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
c.auth = true
c.wg.Done()
})
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Write(p)
}
type tcpCustomServer struct {
clients []*TCPSequence
servers []*TCPSequence
errors []*TCPSequence
}
type tcpCustomServerConn struct {
net.Conn
header *tcpCustomServer
auth bool
wg sync.WaitGroup
once sync.Once
}
func NewConnServerTCP(c *TCPConfig, raw net.Conn) (net.Conn, error) {
conn := &tcpCustomServerConn{
Conn: raw,
header: &tcpCustomServer{
clients: c.Clients,
servers: c.Servers,
errors: c.Errors,
},
}
conn.wg.Add(1)
return conn, nil
}
func (c *tcpCustomServerConn) TcpMaskConn() {}
func (c *tcpCustomServerConn) RawConn() net.Conn {
// c.wg.Wait()
return c.Conn
}
func (c *tcpCustomServerConn) Splice() bool {
return true
}
func (c *tcpCustomServerConn) Read(p []byte) (n int, err error) {
c.once.Do(func() {
i := 0
j := 0
for i = range c.header.clients {
if !readSequence(c.Conn, c.header.clients[i]) {
if i < len(c.header.errors) {
writeSequence(c.Conn, c.header.errors[i])
}
c.wg.Done()
return
}
if j < len(c.header.servers) {
if !writeSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
}
for j < len(c.header.servers) {
if !writeSequence(c.Conn, c.header.servers[j]) {
c.wg.Done()
return
}
j++
}
c.auth = true
c.wg.Done()
})
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Read(p)
}
func (c *tcpCustomServerConn) Write(p []byte) (n int, err error) {
c.wg.Wait()
if !c.auth {
return 0, errors.New("header auth failed")
}
return c.Conn.Write(p)
}
func readSequence(r io.Reader, sequence *TCPSequence) bool {
for _, item := range sequence.Sequence {
length := max(int(item.Rand), len(item.Packet))
buf := make([]byte, length)
n, err := io.ReadFull(r, buf)
if err != nil {
return false
}
if item.Rand > 0 && n != length {
return false
}
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, buf[:n]) {
return false
}
}
return true
}
func writeSequence(w io.Writer, sequence *TCPSequence) bool {
var merged []byte
for _, item := range sequence.Sequence {
if item.DelayMax > 0 {
if len(merged) > 0 {
_, err := w.Write(merged)
if err != nil {
return false
}
merged = nil
}
time.Sleep(time.Duration(crypto.RandBetween(item.DelayMin, item.DelayMax)) * time.Millisecond)
}
if item.Rand > 0 {
buf := make([]byte, item.Rand)
crypto.RandBytesBetween(buf, byte(item.RandMin), byte(item.RandMax))
merged = append(merged, buf...)
} else {
merged = append(merged, item.Packet...)
}
}
if len(merged) > 0 {
_, err := w.Write(merged)
if err != nil {
return false
}
merged = nil
}
return true
}
@@ -0,0 +1,183 @@
package custom
import (
"bytes"
"net"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
)
type udpCustomClient struct {
client []*UDPItem
server []*UDPItem
merged []byte
}
func (h *udpCustomClient) Serialize(b []byte) {
index := 0
for _, item := range h.client {
if item.Rand > 0 {
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
index += int(item.Rand)
} else {
index += len(item.Packet)
}
}
copy(b, h.merged)
}
func (h *udpCustomClient) Match(b []byte) bool {
if len(b) < len(h.merged) {
return false
}
data := b
match := true
for _, item := range h.server {
length := max(int(item.Rand), len(item.Packet))
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
match = false
break
}
data = data[length:]
}
return match
}
type udpCustomClientConn struct {
net.PacketConn
header *udpCustomClient
}
func NewConnClientUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
conn := &udpCustomClientConn{
PacketConn: raw,
header: &udpCustomClient{
client: c.Client,
server: c.Server,
},
}
index := 0
for _, item := range conn.header.client {
if item.Rand > 0 {
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
index += int(item.Rand)
} else {
conn.header.merged = append(conn.header.merged, item.Packet...)
index += len(item.Packet)
}
}
return conn, nil
}
func (c *udpCustomClientConn) Size() int {
return len(c.header.merged)
}
func (c *udpCustomClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if !c.header.Match(p) {
return 0, addr, errors.New("header mismatch")
}
return len(p) - len(c.header.merged), addr, nil
}
func (c *udpCustomClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.header.Serialize(p)
return len(p), nil
}
type udpCustomServer struct {
client []*UDPItem
server []*UDPItem
merged []byte
}
func (h *udpCustomServer) Serialize(b []byte) {
index := 0
for _, item := range h.server {
if item.Rand > 0 {
crypto.RandBytesBetween(h.merged[index:index+int(item.Rand)], byte(item.RandMin), byte(item.RandMax))
index += int(item.Rand)
} else {
index += len(item.Packet)
}
}
copy(b, h.merged)
}
func (h *udpCustomServer) Match(b []byte) bool {
if len(b) < len(h.merged) {
return false
}
data := b
match := true
for _, item := range h.client {
length := max(int(item.Rand), len(item.Packet))
if len(item.Packet) > 0 && !bytes.Equal(item.Packet, data[:length]) {
match = false
break
}
data = data[length:]
}
return match
}
type udpCustomServerConn struct {
net.PacketConn
header *udpCustomServer
}
func NewConnServerUDP(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
conn := &udpCustomServerConn{
PacketConn: raw,
header: &udpCustomServer{
client: c.Client,
server: c.Server,
},
}
index := 0
for _, item := range conn.header.server {
if item.Rand > 0 {
conn.header.merged = append(conn.header.merged, make([]byte, item.Rand)...)
index += int(item.Rand)
} else {
conn.header.merged = append(conn.header.merged, item.Packet...)
index += len(item.Packet)
}
}
return conn, nil
}
func (c *udpCustomServerConn) Size() int {
return len(c.header.merged)
}
func (c *udpCustomServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if !c.header.Match(p) {
return 0, addr, errors.New("header mismatch")
}
return len(p) - len(c.header.merged), addr, nil
}
func (c *udpCustomServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.header.Serialize(p)
return len(p), nil
}
@@ -7,10 +7,13 @@ import (
func (c *Config) UDP() { func (c *Config) UDP() {
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize) return NewConnServer(c, raw)
}
func (c *Config) HeaderConn() {
} }
+11 -114
View File
@@ -2,10 +2,7 @@ package dns
import ( import (
"encoding/binary" "encoding/binary"
"io"
"net" "net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -81,8 +78,8 @@ type dns struct {
header []byte header []byte
} }
func (h *dns) Size() int32 { func (h *dns) Size() int {
return int32(len(h.header)) return len(h.header)
} }
func (h *dns) Serialize(b []byte) { func (h *dns) Serialize(b []byte) {
@@ -91,19 +88,11 @@ func (h *dns) Serialize(b []byte) {
} }
type dnsConn struct { type dnsConn struct {
first bool net.PacketConn
leaveSize int32
conn net.PacketConn
header *dns header *dns
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
} }
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
var header []byte var header []byte
header = binary.BigEndian.AppendUint16(header, 0x0000) // Transaction ID header = binary.BigEndian.AppendUint16(header, 0x0000) // Transaction ID
header = binary.BigEndian.AppendUint16(header, 0x0100) // Flags: Standard query header = binary.BigEndian.AppendUint16(header, 0x0100) // Flags: Standard query
@@ -121,121 +110,29 @@ func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (
header = binary.BigEndian.AppendUint16(header, 0x0001) // Class: IN header = binary.BigEndian.AppendUint16(header, 0x0001) // Class: IN
conn := &dnsConn{ conn := &dnsConn{
first: first, PacketConn: raw,
leaveSize: leaveSize,
conn: raw,
header: &dns{ header: &dns{
header: header, header: header,
}, },
} }
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil return conn, nil
} }
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *dnsConn) Size() int32 { func (c *dnsConn) Size() int {
return c.header.Size() return c.header.Size()
} }
func (c *dnsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *dnsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first { return len(p) - c.header.Size(), addr, nil
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, p[c.Size():n])
return n - int(c.Size()), addr, err
} }
func (c *dnsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *dnsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first { c.header.Serialize(p)
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock() return len(p), nil
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
return c.conn.WriteTo(p, addr)
}
func (c *dnsConn) Close() error {
return c.conn.Close()
}
func (c *dnsConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *dnsConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *dnsConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *dnsConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
} }
@@ -7,10 +7,13 @@ import (
func (c *Config) UDP() { func (c *Config) UDP() {
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize) return NewConnServer(c, raw)
}
func (c *Config) HeaderConn() {
} }
+10 -114
View File
@@ -1,13 +1,9 @@
package dtls package dtls
import ( import (
"io"
"net" "net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
) )
type dtls struct { type dtls struct {
@@ -16,7 +12,7 @@ type dtls struct {
sequence uint32 sequence uint32
} }
func (*dtls) Size() int32 { func (*dtls) Size() int {
return 1 + 2 + 2 + 6 + 2 return 1 + 2 + 2 + 6 + 2
} }
@@ -42,24 +38,13 @@ func (h *dtls) Serialize(b []byte) {
} }
type dtlsConn struct { type dtlsConn struct {
first bool net.PacketConn
leaveSize int32
conn net.PacketConn
header *dtls header *dtls
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
} }
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &dtlsConn{ conn := &dtlsConn{
first: first, PacketConn: raw,
leaveSize: leaveSize,
conn: raw,
header: &dtls{ header: &dtls{
epoch: dice.RollUint16(), epoch: dice.RollUint16(),
sequence: 0, sequence: 0,
@@ -67,112 +52,23 @@ func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (
}, },
} }
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil return conn, nil
} }
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *dtlsConn) Size() int32 { func (c *dtlsConn) Size() int {
return c.header.Size() return c.header.Size()
} }
func (c *dtlsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *dtlsConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first { return len(p) - c.header.Size(), addr, nil
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, p[c.Size():n])
return n - int(c.Size()), addr, err
} }
func (c *dtlsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *dtlsConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first { c.header.Serialize(p)
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock() return len(p), nil
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
return c.conn.WriteTo(p, addr)
}
func (c *dtlsConn) Close() error {
return c.conn.Close()
}
func (c *dtlsConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *dtlsConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *dtlsConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *dtlsConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
} }
@@ -7,10 +7,13 @@ import (
func (c *Config) UDP() { func (c *Config) UDP() {
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize) return NewConnServer(c, raw)
}
func (c *Config) HeaderConn() {
} }
+10 -114
View File
@@ -2,13 +2,9 @@ package srtp
import ( import (
"encoding/binary" "encoding/binary"
"io"
"net" "net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
) )
type srtp struct { type srtp struct {
@@ -16,7 +12,7 @@ type srtp struct {
number uint16 number uint16
} }
func (*srtp) Size() int32 { func (*srtp) Size() int {
return 4 return 4
} }
@@ -27,136 +23,36 @@ func (h *srtp) Serialize(b []byte) {
} }
type srtpConn struct { type srtpConn struct {
first bool net.PacketConn
leaveSize int32
conn net.PacketConn
header *srtp header *srtp
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
} }
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &srtpConn{ conn := &srtpConn{
first: first, PacketConn: raw,
leaveSize: leaveSize,
conn: raw,
header: &srtp{ header: &srtp{
header: 0xB5E8, header: 0xB5E8,
number: dice.RollUint16(), number: dice.RollUint16(),
}, },
} }
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil return conn, nil
} }
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *srtpConn) Size() int32 { func (c *srtpConn) Size() int {
return c.header.Size() return c.header.Size()
} }
func (c *srtpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *srtpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first { return len(p) - c.header.Size(), addr, nil
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, p[c.Size():n])
return n - int(c.Size()), addr, err
} }
func (c *srtpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *srtpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first { c.header.Serialize(p)
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock() return len(p), nil
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
return c.conn.WriteTo(p, addr)
}
func (c *srtpConn) Close() error {
return c.conn.Close()
}
func (c *srtpConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *srtpConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *srtpConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *srtpConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
} }
@@ -7,10 +7,13 @@ import (
func (c *Config) UDP() { func (c *Config) UDP() {
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize) return NewConnServer(c, raw)
}
func (c *Config) HeaderConn() {
} }
+10 -114
View File
@@ -2,13 +2,9 @@ package utp
import ( import (
"encoding/binary" "encoding/binary"
"io"
"net" "net"
sync "sync"
"time"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors"
) )
type utp struct { type utp struct {
@@ -17,7 +13,7 @@ type utp struct {
connectionID uint16 connectionID uint16
} }
func (*utp) Size() int32 { func (*utp) Size() int {
return 4 return 4
} }
@@ -28,24 +24,13 @@ func (h *utp) Serialize(b []byte) {
} }
type utpConn struct { type utpConn struct {
first bool net.PacketConn
leaveSize int32
conn net.PacketConn
header *utp header *utp
readBuf []byte
readMutex sync.Mutex
writeBuf []byte
writeMutex sync.Mutex
} }
func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
conn := &utpConn{ conn := &utpConn{
first: first, PacketConn: raw,
leaveSize: leaveSize,
conn: raw,
header: &utp{ header: &utp{
header: 1, header: 1,
extension: 0, extension: 0,
@@ -53,112 +38,23 @@ func NewConnClient(c *Config, raw net.PacketConn, first bool, leaveSize int32) (
}, },
} }
if first {
conn.readBuf = make([]byte, 8192)
conn.writeBuf = make([]byte, 8192)
}
return conn, nil return conn, nil
} }
func NewConnServer(c *Config, raw net.PacketConn, first bool, leaveSize int32) (net.PacketConn, error) { func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *utpConn) Size() int32 { func (c *utpConn) Size() int {
return c.header.Size() return c.header.Size()
} }
func (c *utpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *utpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if c.first { return len(p) - c.header.Size(), addr, nil
c.readMutex.Lock()
n, addr, err = c.conn.ReadFrom(c.readBuf)
if err != nil {
c.readMutex.Unlock()
return n, addr, err
}
if n < int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
if len(p) < n-int(c.Size()) {
c.readMutex.Unlock()
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, c.readBuf[c.Size():n])
c.readMutex.Unlock()
return n - int(c.Size()), addr, err
}
n, addr, err = c.conn.ReadFrom(p)
if err != nil {
return n, addr, err
}
if n < int(c.Size()) {
return 0, addr, errors.New("header").Base(io.ErrShortBuffer)
}
copy(p, p[c.Size():n])
return n - int(c.Size()), addr, err
} }
func (c *utpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *utpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if c.first { c.header.Serialize(p)
if c.leaveSize+c.Size()+int32(len(p)) > 8192 {
return 0, errors.New("too many masks")
}
c.writeMutex.Lock() return len(p), nil
n = copy(c.writeBuf[c.leaveSize+c.Size():], p)
n += int(c.leaveSize) + int(c.Size())
c.header.Serialize(c.writeBuf[c.leaveSize : c.leaveSize+c.Size()])
nn, err := c.conn.WriteTo(c.writeBuf[:n], addr)
if err != nil {
c.writeMutex.Unlock()
return 0, err
}
if nn != n {
c.writeMutex.Unlock()
return 0, errors.New("nn != n")
}
c.writeMutex.Unlock()
return len(p), nil
}
c.header.Serialize(p[c.leaveSize : c.leaveSize+c.Size()])
return c.conn.WriteTo(p, addr)
}
func (c *utpConn) Close() error {
return c.conn.Close()
}
func (c *utpConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *utpConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *utpConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *utpConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
} }
@@ -7,10 +7,13 @@ import (
func (c *Config) UDP() { func (c *Config) UDP() {
} }
func (c *Config) WrapPacketConnClient(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnClient(c, raw, first, leaveSize) return NewConnClient(c, raw)
} }
func (c *Config) WrapPacketConnServer(raw net.PacketConn, first bool, leaveSize int32, end bool) (net.PacketConn, error) { func (c *Config) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
return NewConnServer(c, raw, first, leaveSize) return NewConnServer(c, raw)
}
func (c *Config) HeaderConn() {
} }

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