Compare commits

..
Author SHA1 Message Date
风扇滑翔翼 d0bb7a37e6 Close conn on cancel 2026-01-08 13:10:32 +00:00
Hossin Asaadi 36425d2a6e Tests: Improve geosite & geoip tests (#5502)
https://github.com/XTLS/Xray-core/pull/5488#issuecomment-3711843548
2026-01-08 08:00:49 +00:00
Owersunandyuhan6665 39ba1f7952 Proxy: Add TUN inbound for Windows & Linux, including Android (#5464)
* Proxy: Implement tun raw network interface inbound support for Linux

* Proxy: Tun. Include "android" as build condition for build of tun_default implementation

* Proxy: Tun. Add .Close() cleanup calls to Handler.Init() where needed

* Proxy: Add Tun for Android

* Proxy: Tun. Implement Windows support

---------

Co-authored-by: yuhan6665 <1588741+yuhan6665@users.noreply.github.com>
2026-01-07 22:05:08 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇 394e117998 GitHub Actions: Add wintun.dll into Windows zips; Workflow refinement (#5501)
For https://github.com/XTLS/Xray-core/pull/5464
2026-01-07 12:59:23 +00:00
风扇滑翔翼 446df149bd Routing config: Replace processName with process (full-name/abs-path/abs-folder) (#5496)
About `self/` & `xray/`: https://github.com/XTLS/Xray-core/pull/5496#issuecomment-3714620380

Replaces https://github.com/XTLS/Xray-core/pull/5489
2026-01-06 14:03:21 +00:00
风扇滑翔翼 d9025857fe transport/pipe/impl.go: Remove runtime.Gosched() in WriteMultiBuffer() (#5467) 2026-01-06 12:43:08 +00:00
Alireza Ahmand ced3e75bf3 README.md: Add TX-UI to Web Panels (#4981) 2026-01-06 12:24:32 +00:00
Hossin Asaadi 961c352127 DNS: Fix parse domain and geoip (#5499)
Fixes https://github.com/XTLS/Xray-core/pull/5488#issuecomment-3712856715
2026-01-06 12:21:50 +00:00
Hossin Asaadi c715154309 Routing: Reduce peak memory usage (#5488)
https://github.com/XTLS/Xray-core/pull/5488#issuecomment-3711430369

For https://github.com/XTLS/Xray-core/issues/4422
2026-01-05 23:02:40 +00:00
RPRX b38a41249f README.md: Re-add 3X-UI to Web Panels
https://github.com/XTLS/Xray-core/pull/3884#issuecomment-3678495173

https://github.com/XTLS/Xray-core/issues/5478#issuecomment-3700567911
2026-01-05 08:42:35 +00:00
风扇滑翔翼 7265b5ac3f Routing config: Add processName (#5489) 2026-01-05 01:12:13 +00:00
fanymagnet e7c72c011f XHTTP server: Fix ScStreamUpServerSecs' non-default value (#5486) 2026-01-05 01:07:00 +00:00
yuhan6665 a54e1f2be4 Remove redundant stats in mux and bridge dispatcher (#5466)
Fixes https://github.com/XTLS/Xray-core/issues/5446
2025-12-31 11:00:45 +00:00
Hossin Asaadi 5d94a62a83 Geofiles: Implement mmap in filesystem to reduce ram usage (#5480)
For https://github.com/XTLS/Xray-core/issues/4422
2025-12-31 08:50:30 +00:00
Maxim Plotnikov ad468e462d API: Add GetAllOnlineUsers RPC to StatsService for retrieving online users (#5080) 2025-12-26 16:07:06 -05:00
ari-ahm 6738ecf68e common/uuid: fix panic when parsing 32-len invalid UUID string. (#5468)
* common/uuid: fix panic when parsing 32-len invalid UUID string.

* fix: removed typo
2025-12-26 15:17:24 -05:00
dependabot[bot] 36968909a1 Bump google.golang.org/grpc from 1.77.0 to 1.78.0 (#5469)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.77.0 to 1.78.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.77.0...v1.78.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.78.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>
2025-12-24 20:45:54 -05:00
Meow 7f6ceb39f7 DomainMatcher: Prevent illegal domain rules from causing core startup failures (#5430)
Closes https://github.com/XTLS/Xray-core/issues/5429
2025-12-23 10:14:42 +00:00
风扇滑翔翼 fa64775f07 Tunnel/Dokodemo: Fix stats conn unwrap (#5440)
Fixes https://github.com/XTLS/Xray-core/issues/5439
2025-12-23 09:44:54 +00:00
patternihaand风扇滑翔翼 a6792dda69 TLS ECH: Increase DOH timeout (#5455)
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-12-23 09:41:01 +00:00
ari-ahmandRPRX 3572209cbd REALITY client: Clearer log when receiving real certificate (#5427)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-12-23 08:58:43 +00:00
xtlseeandRPRX dd757ca27c VLESS inbound: Print invalid UUID string (#5426)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-12-23 08:27:40 +00:00
dependabot[bot] 04b433dd97 Bump github.com/cloudflare/circl from 1.6.1 to 1.6.2 (#5465)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.1 to 1.6.2.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.1...v1.6.2)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.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>
2025-12-23 08:13:09 +00:00
dependabot[bot] 6bf0376773 Bump github.com/quic-go/quic-go from 0.57.1 to 0.58.0 (#5459)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.57.1 to 0.58.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.57.1...v0.58.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.58.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>
2025-12-21 22:28:33 -05:00
dependabot[bot] 74df63add2 Bump actions/upload-artifact from 5 to 6 (#5425)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-14 22:01:02 -05:00
dependabot[bot] c40326dfd7 Bump google.golang.org/protobuf from 1.36.10 to 1.36.11 (#5424)
Bumps google.golang.org/protobuf from 1.36.10 to 1.36.11.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.11
  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>
2025-12-14 22:00:53 -05:00
Meow a3ba3eefb6 Wireguard: Decouple server endpoint DNS from address option (#5417)
* Wireguard: Decouple server endpoint DNS from address option

Previously, Wireguard server endpoint's domain resolution was incorrectly constrained by the local `address` option. For example, `ForceIPv6v4` might fail to resolve AAAA records for the server domain if no IPv6 was explicitly configured in the `address` option.

This commit decouples the server endpoint's domain resolution from the local `address` configuration. It ensures the Wireguard server address is resolved independently, allowing its `domainStrategy` to function correctly without being limited by the client's local network or `address` settings.

* Delete code instead of commenting it out
2025-12-14 10:13:47 -05:00
dependabot[bot] 9cf22114a1 Bump github.com/miekg/dns from 1.1.68 to 1.1.69 (#5410)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.68 to 1.1.69.
- [Commits](https://github.com/miekg/dns/compare/v1.1.68...v1.1.69)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-version: 1.1.69
  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>
2025-12-12 06:01:44 +00:00
dependabot[bot] a903e80356 Bump actions/cache from 4 to 5 (#5411)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  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>
2025-12-12 06:01:24 +00:00
风扇滑翔翼 a610a4c89a Chore: Remove all double gonet import (#5402) 2025-12-10 07:17:29 +00:00
dependabot[bot] b451f8929d Bump golang.org/x/net from 0.47.0 to 0.48.0 (#5397)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.47.0 to 0.48.0.
- [Commits](https://github.com/golang/net/compare/v0.47.0...v0.48.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.48.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>
2025-12-10 07:16:37 +00:00
RPRX 81f8f398c7 v25.12.8
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
2025-12-08 13:35:16 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇 aea123842b Chore: Remove ctlcmd and leftover envvar (#5392)
https://github.com/v2fly/v2ray-core/issues/360
2025-12-08 13:27:22 +00:00
RPRX bd7503d506 XTLS Vision: LogInfo() -> LogDebug()
https://t.me/projectXray/4543105
2025-12-08 13:19:59 +00:00
yuhan6665 903214a0f0 XTLS Vision: Fix enabled uplink splice flag by mistake (#5391)
Fixes https://github.com/XTLS/Xray-core/issues/5379
2025-12-08 13:13:43 +00:00
RPRX e403abe360 v25.12.2
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
2025-12-02 13:53:08 +00:00
RPRX c123f163c2 XTLS Vision: Discard expired pre-connect conn automatically
https://t.me/projectXray/4538408

https://github.com/XTLS/Xray-core/pull/5270#issuecomment-3602122299
2025-12-02 13:44:27 +00:00
风扇滑翔翼 93312d29e5 XTLS Vision: Fix IsCompleteRecord() (#5365)
Fixes https://github.com/XTLS/Xray-core/pull/5179
2025-12-02 13:01:44 +00:00
RPRX 36cb0f00bd v25.12.1
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
2025-12-01 13:27:50 +00:00
RPRXand风扇滑翔翼 cadcb47074 XTLS Vision: Add testpre (outbound pre-connect) and testseed (outbound & inbound) (#5270)
https://t.me/projectXtls/1034

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-12-01 13:27:27 +00:00
yuhan6665 c6afcd5fb6 XTLS Vision: Check TLS record isComplete (#5179)
Fixes https://github.com/XTLS/Xray-core/discussions/5169#discussioncomment-14482684
2025-11-30 10:28:01 +00:00
Meow ed5f7e7af5 fix(dns): inheritance issue with disableCache (#5351) 2025-11-27 02:12:37 +00:00
dependabot[bot] 9d3401b6f0 Bump github.com/quic-go/quic-go from 0.57.0 to 0.57.1 (#5352)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.57.0 to 0.57.1.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.57.0...v0.57.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.57.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>
2025-11-27 02:11:33 +00:00
dependabot[bot] d60ef656cc Bump github.com/quic-go/quic-go from 0.56.0 to 0.57.0 (#5335)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.56.0 to 0.57.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.56.0...v0.57.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 00:55:50 +00:00
RPRX a83253f3d7 VLESS Reverse Proxy: Forbid reverse-proxy UUID using forward-proxy, enabled by default
https://t.me/projectXtls/1070

https://github.com/XTLS/Xray-core/pull/5101#issuecomment-3567464144
2025-11-23 04:23:48 +00:00
RPRX 2969a189e6 Sockopt config: Add trustedXForwardedFor (for XHTTP, WS, HU inbounds) (#5331)
Fixes https://github.com/XTLS/Xray-core/pull/5101#issuecomment-3404979909
2025-11-23 01:09:49 +00:00
Meow d41840132a Router: Remove the deprecated UseIP option (#5323) 2025-11-21 05:46:41 +00:00
Meow f14fd1cbee feat(dns): add parallel query (#5239) 2025-11-21 05:45:42 +00:00
Meow cd51f57535 feat(dns): add optimistic caching (#5237) 2025-11-21 05:38:06 +00:00
Meow 4956e65824 perf(dns): cache network capability check (#5244) 2025-11-21 05:35:45 +00:00
Meow 2185a730d2 perf(router): adjust the order of rules to optimize performance (#5267) 2025-11-21 05:30:16 +00:00
Meow fcfb0a302a perf(GeoIPMatcher): faster heuristic matching with reduced memory usage (#5289) 2025-11-21 02:54:01 +00:00
Meow b40bf56e4e refactor(dns): enhance cache safety, optimize performance, and refactor query logic (#5248) 2025-11-21 02:45:09 +00:00
vanseroxand风扇滑翔翼 27ad487545 DNS: Fix wrong protocol parse (#5232)
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-11-21 02:31:34 +00:00
hax0r31337 e914183996 HTTP outbound: Read negotiated protocol from uTLS (#5251) 2025-11-21 02:29:25 +00:00
dependabot[bot] acfda31e59 Bump actions/checkout from 5 to 6 (#5324)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-21 00:13:10 +00:00
风扇滑翔翼 f9dd3aef72 Refactor WrapLink logic (#5288)
https://github.com/XTLS/Xray-core/pull/5133
https://github.com/XTLS/Xray-core/pull/5286
2025-11-20 15:53:42 +00:00
Meow b24ef88a80 Docker: Use more aggressive inlining for higher efficiency (#5242)
Syncs https://github.com/XTLS/Xray-core/pull/5026
2025-11-20 13:20:53 +00:00
vemneyyand风扇滑翔翼 b16a5f03fe Socks: Fix buffer full panic when encoding large UDP packets (#5252)
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-11-20 12:21:22 +00:00
Aron Yang 2f1fabb318 README.md: Add v2rayN to macOS & Linux Clients (#5271) 2025-11-20 12:08:39 +00:00
dependabot[bot] 1ec2966433 Bump golang.org/x/net (#5318)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.46.1-0.20251013234738-63d1a5100f82 to 0.47.0.
- [Commits](https://github.com/golang/net/commits/v0.47.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.47.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>
2025-11-20 00:11:02 +00:00
RPRX 8a4b0a9eb0 README.md: Add TRX & TON & BTC & XMR & SOL to Donation & NFTs
https://t.me/projectXtls/1145
2025-11-19 12:39:32 +00:00
RPRX 4e8ee302a6 README.md: Add Remnawave & Happ to Sponsors
Sponsor Xray-core: https://github.com/XTLS/Xray-core/issues/3668
2025-11-19 12:36:25 +00:00
Exclude0122 18a4104737 Fix wireguard not discarding broken connection on android (#5304)
Fixes https://github.com/XTLS/Xray-core/issues/5303
2025-11-19 04:53:54 +00:00
RPRXand风扇滑翔翼 1a32d18c16 REALITY config: Return error when short id is too long (#5276)
Closes https://github.com/XTLS/Xray-core/issues/5273

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-11-19 03:25:32 +00:00
dependabot[bot] 412bc17c12 Bump google.golang.org/grpc from 1.76.0 to 1.77.0 (#5308)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.76.0 to 1.77.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.76.0...v1.77.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.77.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>
2025-11-19 02:43:07 +00:00
dependabot[bot] 9491b67f3c Bump github.com/quic-go/quic-go from 0.55.0 to 0.56.0 (#5292)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.55.0 to 0.56.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.55.0...v0.56.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-14 20:51:29 -05:00
dependabot[bot] cb4f943f50 Bump actions/upload-artifact from 4 to 5 (#5259)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  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>
2025-11-01 11:07:19 +00:00
129 changed files with 5749 additions and 1829 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ WORKDIR /src
COPY . . COPY . .
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
# Download geodat into a staging directory # Download geodat into a staging directory
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
+1 -1
View File
@@ -6,7 +6,7 @@ WORKDIR /src
COPY . . COPY . .
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
# Download geodat into a staging directory # Download geodat into a staging directory
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
echo "LATEST=$LATEST" >>${GITHUB_ENV} echo "LATEST=$LATEST" >>${GITHUB_ENV}
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5 uses: actions/checkout@v6
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
+49 -17
View File
@@ -13,11 +13,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
with:
path: resources
key: xray-wintun-
- name: Check Assets Existence - name: Check Assets Existence
id: check-assets id: check-assets
run: | run: |
@@ -34,6 +40,18 @@ jobs:
break break
fi fi
done done
LIST=('amd64' 'x86')
for ARCHITECTURE in "${LIST[@]}"
do
echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
echo -e "wintun.dll for ${ARCHITECTURE} exists."
else
echo -e "wintun.dll for ${ARCHITECTURE} is missing."
echo "missing=true" >> $GITHUB_OUTPUT
break
fi
done
- name: Sleep for 90 seconds if Assets Missing - name: Sleep for 90 seconds if Assets Missing
if: steps.check-assets.outputs.missing == 'true' if: steps.check-assets.outputs.missing == 'true'
@@ -63,7 +81,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v5 uses: actions/checkout@v6
- name: Show workflow information - name: Show workflow information
run: | run: |
@@ -95,20 +113,38 @@ jobs:
COMMID=$(git describe --always --dirty) COMMID=$(git describe --always --dirty)
echo 'Building Xray for Windows 7...' echo 'Building Xray for Windows 7...'
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference. # The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main # go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
with:
path: resources
key: xray-wintun-
- name: Add additional assets into package
run: |
mv -f resources/geo* build_assets/
if [[ ${GOOS} == 'windows' ]]; then
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
if [[ ${GOARCH} == 'amd64' ]]; then
mv resources/wintun/bin/amd64/wintun.dll build_assets/
fi
if [[ ${GOARCH} == '386' ]]; then
mv resources/wintun/bin/x86/wintun.dll build_assets/
fi
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
fi
- name: Copy README.md & LICENSE - name: Copy README.md & LICENSE
run: | run: |
mv -f resources/* build_assets
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
@@ -127,17 +163,6 @@ jobs:
openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
done done
- name: Change the name
run: |
mv build_assets Xray-${{ env.ASSET_NAME }}
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
with:
name: Xray-${{ env.ASSET_NAME }}
path: |
./Xray-${{ env.ASSET_NAME }}/*
- name: Upload binaries to release - name: Upload binaries to release
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2
if: github.event_name == 'release' if: github.event_name == 'release'
@@ -146,3 +171,10 @@ jobs:
file: ./Xray-${{ env.ASSET_NAME }}.zip* file: ./Xray-${{ env.ASSET_NAME }}.zip*
tag: ${{ github.ref }} tag: ${{ github.ref }}
file_glob: true file_glob: true
- name: Upload files to Artifacts
uses: actions/upload-artifact@v6
with:
name: Xray-${{ env.ASSET_NAME }}
path: |
./build_assets/*
+56 -17
View File
@@ -13,11 +13,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
with:
path: resources
key: xray-wintun-
- name: Check Assets Existence - name: Check Assets Existence
id: check-assets id: check-assets
run: | run: |
@@ -34,6 +40,18 @@ jobs:
break break
fi fi
done done
LIST=('amd64' 'x86' 'arm64' 'arm')
for ARCHITECTURE in "${LIST[@]}"
do
echo -e "Checking wintun.dll for ${ARCHITECTURE}..."
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
echo -e "wintun.dll for ${ARCHITECTURE} exists."
else
echo -e "wintun.dll for ${ARCHITECTURE} is missing."
echo "missing=true" >> $GITHUB_OUTPUT
break
fi
done
- name: Trigger Asset Update Workflow if Assets Missing - name: Trigger Asset Update Workflow if Assets Missing
if: steps.check-assets.outputs.missing == 'true' if: steps.check-assets.outputs.missing == 'true'
@@ -153,7 +171,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v5 uses: actions/checkout@v6
- name: Set up NDK - name: Set up NDK
if: matrix.goos == 'android' if: matrix.goos == 'android'
@@ -191,8 +209,6 @@ jobs:
if [[ ${GOOS} == 'windows' ]]; then if [[ ${GOOS} == 'windows' ]]; then
echo 'Building Xray for Windows...' echo 'Building Xray for Windows...'
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference. # The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main # go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
else else
@@ -207,14 +223,41 @@ jobs:
fi fi
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
- name: Restore Wintun Cache
if: matrix.goos == 'windows'
uses: actions/cache/restore@v5
with:
path: resources
key: xray-wintun-
- name: Add additional assets into package
run: |
mv -f resources/geo* build_assets/
if [[ ${GOOS} == 'windows' ]]; then
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
if [[ ${GOARCH} == 'amd64' ]]; then
mv resources/wintun/bin/amd64/wintun.dll build_assets/
fi
if [[ ${GOARCH} == '386' ]]; then
mv resources/wintun/bin/x86/wintun.dll build_assets/
fi
if [[ ${GOARCH} == 'arm64' ]]; then
mv resources/wintun/bin/arm64/wintun.dll build_assets/
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
fi
- name: Copy README.md & LICENSE - name: Copy README.md & LICENSE
run: | run: |
mv -f resources/* build_assets
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
@@ -233,17 +276,6 @@ jobs:
openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
done done
- name: Change the name
run: |
mv build_assets Xray-${{ env.ASSET_NAME }}
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
with:
name: Xray-${{ env.ASSET_NAME }}
path: |
./Xray-${{ env.ASSET_NAME }}/*
- name: Upload binaries to release - name: Upload binaries to release
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2
if: github.event_name == 'release' if: github.event_name == 'release'
@@ -252,3 +284,10 @@ jobs:
file: ./Xray-${{ env.ASSET_NAME }}.zip* file: ./Xray-${{ env.ASSET_NAME }}.zip*
tag: ${{ github.ref }} tag: ${{ github.ref }}
file_glob: true file_glob: true
- name: Upload files to Artifacts
uses: actions/upload-artifact@v6
with:
name: Xray-${{ env.ASSET_NAME }}
path: |
./build_assets/*
+67 -3
View File
@@ -4,6 +4,7 @@ name: Scheduled assets update
# routine manner, for example: GeoIP/GeoSite. # routine manner, for example: GeoIP/GeoSite.
# Currently updating: # Currently updating:
# - Geodat (GeoIP/Geosite) # - Geodat (GeoIP/Geosite)
# - Wintun (wintun.dll)
on: on:
workflow_dispatch: workflow_dispatch:
@@ -21,11 +22,11 @@ on:
jobs: jobs:
geodat: geodat:
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push'|| github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
@@ -58,8 +59,71 @@ jobs:
done done
- name: Save Geodat Cache - name: Save Geodat Cache
uses: actions/cache/save@v4 uses: actions/cache/save@v5
if: ${{ steps.update.outputs.unhit }} if: ${{ steps.update.outputs.unhit }}
with: with:
path: resources path: resources
key: xray-geodat-${{ github.sha }}-${{ github.run_number }} key: xray-geodat-${{ github.sha }}-${{ github.run_number }}
wintun:
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
with:
path: resources
key: xray-wintun-
- name: Force downloading if run manually or on file update
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push'
run: |
echo "FORCE_UPDATE=true" >> $GITHUB_ENV
- name: Update Wintun
id: update
uses: nick-fields/retry@v3
with:
timeout_minutes: 60
retry_wait_seconds: 60
max_attempts: 60
command: |
[ -d 'resources' ] || mkdir resources
LIST=('amd64' 'x86' 'arm64' 'arm')
for ARCHITECTURE in "${LIST[@]}"
do
FILE_PATH="resources/wintun/bin/${ARCHITECTURE}/wintun.dll"
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
echo -e "wintun.dll for ${ARCHITECTURE} exists"
continue
else
echo -e "wintun.dll for ${ARCHITECTURE} is missing"
missing=true
fi
done
if [ -s "./resources/wintun/LICENSE.txt" ]; then
echo -e "LICENSE for Wintun exists"
else
echo -e "LICENSE for Wintun is missing"
missing=true
fi
if [[ -v FORCE_UPDATE ]]; then
missing=true
fi
if [[ "$missing" == true ]]; then
FILENAME=wintun.zip
DOWNLOAD_FILE=wintun-0.14.1.zip
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
echo -e "Unpacking wintun..."
unzip -u ${FILENAME} -d resources/
echo "unhit=true" >> $GITHUB_OUTPUT
fi
- name: Save Wintun Cache
uses: actions/cache/save@v5
if: ${{ steps.update.outputs.unhit }}
with:
path: resources
key: xray-wintun-${{ github.sha }}-${{ github.run_number }}
+3 -3
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
@@ -45,14 +45,14 @@ jobs:
os: [windows-latest, ubuntu-latest, macos-latest] os: [windows-latest, ubuntu-latest, macos-latest]
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v5 uses: actions/checkout@v6
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v5
with: with:
path: resources path: resources
key: xray-geodat- key: xray-geodat-
+21 -4
View File
@@ -4,12 +4,25 @@
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls). [README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
## Sponsors
[![Remnawave](https://github.com/user-attachments/assets/a22d34ae-01ee-441c-843a-85356748ed1e)](https://docs.rw)
[![Happ](https://github.com/user-attachments/assets/14055dab-e8bb-48bd-89e8-962709e4098e)](https://happ.su)
[**Sponsor Xray-core**](https://github.com/XTLS/Xray-core/issues/3668)
## Donation & NFTs ## Donation & NFTs
### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1) ### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
[<img alt="Project X NFT" width="150px" src="https://raw2.seadn.io/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/7fa9ce900fb39b44226348db330e32/8b7fa9ce900fb39b44226348db330e32.svg" />](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1) [<img alt="Project X NFT" width="150px" src="https://raw2.seadn.io/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/7fa9ce900fb39b44226348db330e32/8b7fa9ce900fb39b44226348db330e32.svg" />](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
- **TRX(Tron)/USDT/USDC: `TNrDh5VSfwd4RPrwsohr6poyNTfFefNYan`**
- **TON: `UQApeV-u2gm43aC1uP76xAC1m6vCylstaN1gpfBmre_5IyTH`**
- **BTC: `1JpqcziZZuqv3QQJhZGNGBVdCBrGgkL6cT`**
- **XMR: `4ABHQZ3yJZkBnLoqiKvb3f8eqUnX4iMPb6wdant5ZLGQELctcerceSGEfJnoCk6nnyRZm73wrwSgvZ2WmjYLng6R7sR67nq`**
- **SOL/USDT/USDC: `3x5NuXHzB5APG6vRinPZcsUv5ukWUY1tBGRSJiEJWtZa`**
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`** - **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
- **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1** - **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1**
- **VLESS NFT: https://opensea.io/collection/vless** - **VLESS NFT: https://opensea.io/collection/vless**
@@ -43,13 +56,15 @@
- [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**) - [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**)
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray) - [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker) - [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
- Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:** - Web Panel
- [X-Panel](https://github.com/xeefei/X-Panel)
- [PasarGuard](https://github.com/PasarGuard/panel)
- [Remnawave](https://github.com/remnawave/panel) - [Remnawave](https://github.com/remnawave/panel)
- [Marzban](https://github.com/Gozargah/Marzban) - [3X-UI](https://github.com/MHSanaei/3x-ui)
- [PasarGuard](https://github.com/PasarGuard/panel)
- [Xray-UI](https://github.com/qist/xray-ui) - [Xray-UI](https://github.com/qist/xray-ui)
- [X-Panel](https://github.com/xeefei/X-Panel)
- [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)
- 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)
@@ -108,12 +123,14 @@
- [OneXray](https://github.com/OneXray/OneXray) - [OneXray](https://github.com/OneXray/OneXray)
- [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)
- 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)
- [GorzRay](https://github.com/ketetefid/GorzRay) - [GorzRay](https://github.com/ketetefid/GorzRay)
- [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)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX... ## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
+7 -6
View File
@@ -196,7 +196,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
return inboundLink, outboundLink return inboundLink, outboundLink
} }
func (d *DefaultDispatcher) WrapLink(ctx context.Context, link *transport.Link) *transport.Link { func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager stats.Manager, link *transport.Link) *transport.Link {
sessionInbound := session.InboundFromContext(ctx) sessionInbound := session.InboundFromContext(ctx)
var user *protocol.MemoryUser var user *protocol.MemoryUser
if sessionInbound != nil { if sessionInbound != nil {
@@ -206,16 +206,16 @@ func (d *DefaultDispatcher) WrapLink(ctx context.Context, link *transport.Link)
link.Reader = &buf.TimeoutWrapperReader{Reader: link.Reader} link.Reader = &buf.TimeoutWrapperReader{Reader: link.Reader}
if user != nil && len(user.Email) > 0 { if user != nil && len(user.Email) > 0 {
p := d.policy.ForLevel(user.Level) p := policyManager.ForLevel(user.Level)
if p.Stats.UserUplink { if p.Stats.UserUplink {
name := "user>>>" + user.Email + ">>>traffic>>>uplink" name := "user>>>" + user.Email + ">>>traffic>>>uplink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
link.Reader.(*buf.TimeoutWrapperReader).Counter = c link.Reader.(*buf.TimeoutWrapperReader).Counter = c
} }
} }
if p.Stats.UserDownlink { if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink" name := "user>>>" + user.Email + ">>>traffic>>>downlink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
link.Writer = &SizeStatWriter{ link.Writer = &SizeStatWriter{
Counter: c, Counter: c,
Writer: link.Writer, Writer: link.Writer,
@@ -224,7 +224,7 @@ func (d *DefaultDispatcher) WrapLink(ctx context.Context, link *transport.Link)
} }
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(statsManager, name); om != nil {
sessionInbounds := session.InboundFromContext(ctx) sessionInbounds := session.InboundFromContext(ctx)
userIP := sessionInbounds.Source.Address.String() userIP := sessionInbounds.Source.Address.String()
om.AddIP(userIP) om.AddIP(userIP)
@@ -357,7 +357,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
content = new(session.Content) content = new(session.Content)
ctx = session.ContextWithContent(ctx, content) ctx = session.ContextWithContent(ctx, content)
} }
outbound = d.WrapLink(ctx, outbound) outbound = WrapLink(ctx, d.policy, d.stats, outbound)
sniffingRequest := content.SniffingRequest sniffingRequest := content.SniffingRequest
if !sniffingRequest.Enabled { if !sniffingRequest.Enabled {
d.routedDispatch(ctx, outbound, destination) d.routedDispatch(ctx, outbound, destination)
@@ -449,6 +449,7 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
} }
return contentResult, contentErr return contentResult, contentErr
} }
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) { func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
+259 -108
View File
@@ -3,36 +3,55 @@ package dns
import ( import (
"context" "context"
go_errors "errors" go_errors "errors"
"runtime"
"sync"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/signal/pubsub" "github.com/xtls/xray-core/common/signal/pubsub"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
dns_feature "github.com/xtls/xray-core/features/dns" dns_feature "github.com/xtls/xray-core/features/dns"
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
"sync" "golang.org/x/sync/singleflight"
"time" )
const (
minSizeForEmptyRebuild = 512
shrinkAbsoluteThreshold = 10240
shrinkRatioThreshold = 0.65
migrationBatchSize = 4096
) )
type CacheController struct { type CacheController struct {
name string
disableCache bool
serveStale bool
serveExpiredTTL int32
ips map[string]*record
dirtyips map[string]*record
sync.RWMutex sync.RWMutex
ips map[string]*record pub *pubsub.Service
pub *pubsub.Service cacheCleanup *task.Periodic
cacheCleanup *task.Periodic highWatermark int
name string requestGroup singleflight.Group
disableCache bool
} }
func NewCacheController(name string, disableCache bool) *CacheController { func NewCacheController(name string, disableCache bool, serveStale bool, serveExpiredTTL uint32) *CacheController {
c := &CacheController{ c := &CacheController{
name: name, name: name,
disableCache: disableCache, disableCache: disableCache,
ips: make(map[string]*record), serveStale: serveStale,
pub: pubsub.NewService(), serveExpiredTTL: -int32(serveExpiredTTL),
ips: make(map[string]*record),
pub: pubsub.NewService(),
} }
c.cacheCleanup = &task.Periodic{ c.cacheCleanup = &task.Periodic{
Interval: time.Minute, Interval: 300 * time.Second,
Execute: c.CacheCleanup, Execute: c.CacheCleanup,
} }
return c return c
@@ -40,131 +59,263 @@ func NewCacheController(name string, disableCache bool) *CacheController {
// CacheCleanup clears expired items from cache // CacheCleanup clears expired items from cache
func (c *CacheController) CacheCleanup() error { func (c *CacheController) CacheCleanup() error {
now := time.Now() expiredKeys, err := c.collectExpiredKeys()
c.Lock() if err != nil {
defer c.Unlock() return err
if len(c.ips) == 0 {
return errors.New("nothing to do. stopping...")
} }
if len(expiredKeys) == 0 {
for domain, record := range c.ips { return nil
if record.A != nil && record.A.Expire.Before(now) {
record.A = nil
}
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
record.AAAA = nil
}
if record.A == nil && record.AAAA == nil {
errors.LogDebug(context.Background(), c.name, "cache cleanup ", domain)
delete(c.ips, domain)
} else {
c.ips[domain] = record
}
} }
c.writeAndShrink(expiredKeys)
if len(c.ips) == 0 {
c.ips = make(map[string]*record)
}
return nil return nil
} }
func (c *CacheController) updateIP(req *dnsRequest, ipRec *IPRecord) { func (c *CacheController) collectExpiredKeys() ([]string, error) {
elapsed := time.Since(req.start) c.RLock()
defer c.RUnlock()
c.Lock() if len(c.ips) == 0 {
rec, found := c.ips[req.domain] return nil, errors.New("nothing to do. stopping...")
if !found {
rec = &record{}
} }
switch req.reqType { // skip collection if a migration is in progress
case dnsmessage.TypeA: if c.dirtyips != nil {
rec.A = ipRec return nil, nil
case dnsmessage.TypeAAAA:
rec.AAAA = ipRec
} }
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed) now := time.Now()
c.ips[req.domain] = rec if c.serveStale && c.serveExpiredTTL != 0 {
now = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)
}
switch req.reqType { expiredKeys := make([]string, 0, len(c.ips)/4) // pre-allocate
case dnsmessage.TypeA:
c.pub.Publish(req.domain+"4", nil) for domain, rec := range c.ips {
if !c.disableCache { if (rec.A != nil && rec.A.Expire.Before(now)) ||
_, _, err := rec.AAAA.getIPs() (rec.AAAA != nil && rec.AAAA.Expire.Before(now)) {
if !go_errors.Is(err, errRecordNotFound) { expiredKeys = append(expiredKeys, domain)
c.pub.Publish(req.domain+"6", nil)
}
}
case dnsmessage.TypeAAAA:
c.pub.Publish(req.domain+"6", nil)
if !c.disableCache {
_, _, err := rec.A.getIPs()
if !go_errors.Is(err, errRecordNotFound) {
c.pub.Publish(req.domain+"4", nil)
}
} }
} }
c.Unlock() return expiredKeys, nil
common.Must(c.cacheCleanup.Start())
} }
func (c *CacheController) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { func (c *CacheController) writeAndShrink(expiredKeys []string) {
c.Lock()
defer c.Unlock()
// double check to prevent upper call multiple cleanup tasks
if c.dirtyips != nil {
return
}
lenBefore := len(c.ips)
if lenBefore > c.highWatermark {
c.highWatermark = lenBefore
}
now := time.Now()
if c.serveStale && c.serveExpiredTTL != 0 {
now = now.Add(time.Duration(c.serveExpiredTTL) * time.Second)
}
for _, domain := range expiredKeys {
rec := c.ips[domain]
if rec == nil {
continue
}
if rec.A != nil && rec.A.Expire.Before(now) {
rec.A = nil
}
if rec.AAAA != nil && rec.AAAA.Expire.Before(now) {
rec.AAAA = nil
}
if rec.A == nil && rec.AAAA == nil {
delete(c.ips, domain)
}
}
lenAfter := len(c.ips)
if lenAfter == 0 {
if c.highWatermark >= minSizeForEmptyRebuild {
errors.LogDebug(context.Background(), c.name,
" rebuilding empty cache map to reclaim memory.",
" size_before_cleanup=", lenBefore,
" peak_size_before_rebuild=", c.highWatermark,
)
c.ips = make(map[string]*record)
c.highWatermark = 0
}
return
}
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
errors.LogDebug(context.Background(), c.name,
" shrinking cache map to reclaim memory.",
" new_size=", lenAfter,
" peak_size_before_shrink=", c.highWatermark,
" reduction_since_peak=", reductionFromPeak,
)
c.dirtyips = c.ips
c.ips = make(map[string]*record, int(float64(lenAfter)*1.1))
c.highWatermark = lenAfter
go c.migrate()
}
}
type migrationEntry struct {
key string
value *record
}
func (c *CacheController) migrate() {
defer func() {
if r := recover(); r != nil {
errors.LogError(context.Background(), c.name, " panic during cache migration: ", r)
c.Lock()
c.dirtyips = nil
// c.ips = make(map[string]*record)
// c.highWatermark = 0
c.Unlock()
}
}()
c.RLock() c.RLock()
record, found := c.ips[domain] dirtyips := c.dirtyips
c.RUnlock() c.RUnlock()
if !found { // double check to prevent upper call multiple cleanup tasks
return nil, 0, errRecordNotFound if dirtyips == nil {
return
} }
var errs []error errors.LogDebug(context.Background(), c.name, " starting background cache migration for ", len(dirtyips), " items")
var allIPs []net.IP
var rTTL uint32 = dns_feature.DefaultTTL
mergeReq := option.IPv4Enable && option.IPv6Enable batch := make([]migrationEntry, 0, migrationBatchSize)
for domain, recD := range dirtyips {
batch = append(batch, migrationEntry{domain, recD})
if option.IPv4Enable { if len(batch) >= migrationBatchSize {
ips, ttl, err := record.A.getIPs() c.flush(batch)
if !mergeReq || go_errors.Is(err, errRecordNotFound) { batch = batch[:0]
return ips, ttl, err runtime.Gosched()
} }
if ttl < rTTL { }
rTTL = ttl if len(batch) > 0 {
} c.flush(batch)
if len(ips) > 0 { }
allIPs = append(allIPs, ips...)
c.Lock()
c.dirtyips = nil
c.Unlock()
errors.LogDebug(context.Background(), c.name, " cache migration completed")
}
func (c *CacheController) flush(batch []migrationEntry) {
c.Lock()
defer c.Unlock()
for _, dirty := range batch {
if cur := c.ips[dirty.key]; cur != nil {
merge := &record{}
if cur.A == nil {
merge.A = dirty.value.A
} else {
merge.A = cur.A
}
if cur.AAAA == nil {
merge.AAAA = dirty.value.AAAA
} else {
merge.AAAA = cur.AAAA
}
c.ips[dirty.key] = merge
} else { } else {
errs = append(errs, err) c.ips[dirty.key] = dirty.value
}
}
}
func (c *CacheController) updateRecord(req *dnsRequest, rep *IPRecord) {
rtt := time.Since(req.start)
switch req.reqType {
case dnsmessage.TypeA:
c.pub.Publish(req.domain+"4", rep)
case dnsmessage.TypeAAAA:
c.pub.Publish(req.domain+"6", rep)
}
if c.disableCache {
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", rep.IP, ", rtt: ", rtt)
return
}
c.Lock()
lockWait := time.Since(req.start) - rtt
newRec := &record{}
oldRec := c.ips[req.domain]
var dirtyRec *record
if c.dirtyips != nil {
dirtyRec = c.dirtyips[req.domain]
}
var pubRecord *IPRecord
var pubSuffix string
switch req.reqType {
case dnsmessage.TypeA:
newRec.A = rep
if oldRec != nil && oldRec.AAAA != nil {
newRec.AAAA = oldRec.AAAA
pubRecord = oldRec.AAAA
} else if dirtyRec != nil && dirtyRec.AAAA != nil {
pubRecord = dirtyRec.AAAA
}
pubSuffix = "6"
case dnsmessage.TypeAAAA:
newRec.AAAA = rep
if oldRec != nil && oldRec.A != nil {
newRec.A = oldRec.A
pubRecord = oldRec.A
} else if dirtyRec != nil && dirtyRec.A != nil {
pubRecord = dirtyRec.A
}
pubSuffix = "4"
}
c.ips[req.domain] = newRec
c.Unlock()
if pubRecord != nil {
_, ttl, err := pubRecord.getIPs()
if ttl > 0 && !go_errors.Is(err, errRecordNotFound) {
c.pub.Publish(req.domain+pubSuffix, pubRecord)
} }
} }
if option.IPv6Enable { errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", rep.IP, ", rtt: ", rtt, ", lock: ", lockWait)
ips, ttl, err := record.AAAA.getIPs()
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
return ips, ttl, err
}
if ttl < rTTL {
rTTL = ttl
}
if len(ips) > 0 {
allIPs = append(allIPs, ips...)
} else {
errs = append(errs, err)
}
}
if len(allIPs) > 0 { if !c.serveStale || c.serveExpiredTTL != 0 {
return allIPs, rTTL, nil common.Must(c.cacheCleanup.Start())
} }
if go_errors.Is(errs[0], errs[1]) { }
return nil, rTTL, errs[0]
func (c *CacheController) findRecords(domain string) *record {
c.RLock()
defer c.RUnlock()
rec := c.ips[domain]
if rec == nil && c.dirtyips != nil {
rec = c.dirtyips[domain]
} }
return nil, rTTL, errors.Combine(errs...) return rec
} }
func (c *CacheController) registerSubscribers(domain string, option dns_feature.IPOption) (sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) { func (c *CacheController) registerSubscribers(domain string, option dns_feature.IPOption) (sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
+140 -72
View File
@@ -141,10 +141,13 @@ type NameServer struct {
ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"` ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"`
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"` TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
DisableCache bool `protobuf:"varint,11,opt,name=disableCache,proto3" json:"disableCache,omitempty"` DisableCache *bool `protobuf:"varint,11,opt,name=disableCache,proto3,oneof" json:"disableCache,omitempty"`
ServeStale *bool `protobuf:"varint,15,opt,name=serveStale,proto3,oneof" json:"serveStale,omitempty"`
ServeExpiredTTL *uint32 `protobuf:"varint,16,opt,name=serveExpiredTTL,proto3,oneof" json:"serveExpiredTTL,omitempty"`
FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"` FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"`
UnexpectedGeoip []*router.GeoIP `protobuf:"bytes,13,rep,name=unexpected_geoip,json=unexpectedGeoip,proto3" json:"unexpected_geoip,omitempty"` UnexpectedGeoip []*router.GeoIP `protobuf:"bytes,13,rep,name=unexpected_geoip,json=unexpectedGeoip,proto3" json:"unexpected_geoip,omitempty"`
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"` ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
PolicyID uint32 `protobuf:"varint,17,opt,name=policyID,proto3" json:"policyID,omitempty"`
} }
func (x *NameServer) Reset() { func (x *NameServer) Reset() {
@@ -248,12 +251,26 @@ func (x *NameServer) GetTimeoutMs() uint64 {
} }
func (x *NameServer) GetDisableCache() bool { func (x *NameServer) GetDisableCache() bool {
if x != nil { if x != nil && x.DisableCache != nil {
return x.DisableCache return *x.DisableCache
} }
return false return false
} }
func (x *NameServer) GetServeStale() bool {
if x != nil && x.ServeStale != nil {
return *x.ServeStale
}
return false
}
func (x *NameServer) GetServeExpiredTTL() uint32 {
if x != nil && x.ServeExpiredTTL != nil {
return *x.ServeExpiredTTL
}
return 0
}
func (x *NameServer) GetFinalQuery() bool { func (x *NameServer) GetFinalQuery() bool {
if x != nil { if x != nil {
return x.FinalQuery return x.FinalQuery
@@ -275,6 +292,13 @@ func (x *NameServer) GetActUnprior() bool {
return false return false
} }
func (x *NameServer) GetPolicyID() uint32 {
if x != nil {
return x.PolicyID
}
return 0
}
type Config struct { type Config struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -291,9 +315,12 @@ type Config struct {
Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
// DisableCache disables DNS cache // DisableCache disables DNS cache
DisableCache bool `protobuf:"varint,8,opt,name=disableCache,proto3" json:"disableCache,omitempty"` DisableCache bool `protobuf:"varint,8,opt,name=disableCache,proto3" json:"disableCache,omitempty"`
ServeStale bool `protobuf:"varint,12,opt,name=serveStale,proto3" json:"serveStale,omitempty"`
ServeExpiredTTL uint32 `protobuf:"varint,13,opt,name=serveExpiredTTL,proto3" json:"serveExpiredTTL,omitempty"`
QueryStrategy QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"` QueryStrategy QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
DisableFallback bool `protobuf:"varint,10,opt,name=disableFallback,proto3" json:"disableFallback,omitempty"` DisableFallback bool `protobuf:"varint,10,opt,name=disableFallback,proto3" json:"disableFallback,omitempty"`
DisableFallbackIfMatch bool `protobuf:"varint,11,opt,name=disableFallbackIfMatch,proto3" json:"disableFallbackIfMatch,omitempty"` DisableFallbackIfMatch bool `protobuf:"varint,11,opt,name=disableFallbackIfMatch,proto3" json:"disableFallbackIfMatch,omitempty"`
EnableParallelQuery bool `protobuf:"varint,14,opt,name=enableParallelQuery,proto3" json:"enableParallelQuery,omitempty"`
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@@ -361,6 +388,20 @@ func (x *Config) GetDisableCache() bool {
return false return false
} }
func (x *Config) GetServeStale() bool {
if x != nil {
return x.ServeStale
}
return false
}
func (x *Config) GetServeExpiredTTL() uint32 {
if x != nil {
return x.ServeExpiredTTL
}
return 0
}
func (x *Config) GetQueryStrategy() QueryStrategy { func (x *Config) GetQueryStrategy() QueryStrategy {
if x != nil { if x != nil {
return x.QueryStrategy return x.QueryStrategy
@@ -382,6 +423,13 @@ func (x *Config) GetDisableFallbackIfMatch() bool {
return false return false
} }
func (x *Config) GetEnableParallelQuery() bool {
if x != nil {
return x.EnableParallelQuery
}
return false
}
type NameServer_PriorityDomain struct { type NameServer_PriorityDomain struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -567,7 +615,7 @@ var file_app_dns_config_proto_rawDesc = []byte{
0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x1c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb6, 0x06, 0x0a, 0x0a, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdf, 0x07, 0x0a, 0x0a,
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e,
@@ -599,74 +647,93 @@ var file_app_dns_config_proto_rawDesc = []byte{
0x50, 0x72, 0x69, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x09, 0x20, 0x01, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x09, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x6f,
0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0c, 0x64,
0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x88, 0x01, 0x01, 0x12, 0x23,
0x61, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x18, 0x0f, 0x20, 0x01,
0x69, 0x6e, 0x61, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x10, 0x75, 0x6e, 0x65, 0x28, 0x08, 0x48, 0x01, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65,
0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0d, 0x20, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69,
0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x0f,
0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0f, 0x75, 0x6e, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x88,
0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x1e, 0x0a, 0x0a, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79,
0x61, 0x63, 0x74, 0x55, 0x6e, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x51, 0x75, 0x65,
0x52, 0x0a, 0x61, 0x63, 0x74, 0x55, 0x6e, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x1a, 0x5e, 0x0a, 0x0e, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x10, 0x75, 0x6e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78,
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47,
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0f, 0x75, 0x6e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x55, 0x6e, 0x70, 0x72,
0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x69, 0x6f, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x55, 0x6e,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x36, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49,
0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49,
0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x44, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d,
0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x73, 0x69, 0x7a, 0x65, 0x22, 0x9c, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73,
0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d,
0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x6e, 0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20,
0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x69,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73,
0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x73, 0x65,
0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x22, 0x98, 0x05,
0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65,
0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d,
0x68, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x76, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70,
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70,
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73,
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73,
0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63,
0x12, 0x36, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01,
0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62,
0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64,
0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73,
0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x53, 0x74, 0x61, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x73,
0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x18, 0x0d,
0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72,
0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x64, 0x54, 0x54, 0x4c, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,
0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65,
0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72,
0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73,
0x07, 0x10, 0x08, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01,
0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62,
0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x61, 0x63, 0x6b, 0x12, 0x36, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61,
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x10, 0x02, 0x12, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0b, 0x20,
0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0d, 0x51, 0x75, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c,
0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x30, 0x0a, 0x13, 0x65,
0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x51, 0x75, 0x65,
0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x72, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x53, 0x59, 0x53, 0x10, 0x03, 0x42, 0x46, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x92, 0x01,
0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a,
0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72,
0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70,
0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08,
0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f,
0x72, 0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x2a,
0x42, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07,
0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
0x5f, 0x49, 0x50, 0x36, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x53, 0x59,
0x53, 0x10, 0x03, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d,
0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58,
0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
} }
var ( var (
@@ -718,6 +785,7 @@ func file_app_dns_config_proto_init() {
if File_app_dns_config_proto != nil { if File_app_dns_config_proto != nil {
return return
} }
file_app_dns_config_proto_msgTypes[0].OneofWrappers = []any{}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
+8 -1
View File
@@ -31,10 +31,13 @@ message NameServer {
bool actPrior = 8; bool actPrior = 8;
string tag = 9; string tag = 9;
uint64 timeoutMs = 10; uint64 timeoutMs = 10;
bool disableCache = 11; optional bool disableCache = 11;
optional bool serveStale = 15;
optional uint32 serveExpiredTTL = 16;
bool finalQuery = 12; bool finalQuery = 12;
repeated xray.app.router.GeoIP unexpected_geoip = 13; repeated xray.app.router.GeoIP unexpected_geoip = 13;
bool actUnprior = 14; bool actUnprior = 14;
uint32 policyID = 17;
} }
enum DomainMatchingType { enum DomainMatchingType {
@@ -80,9 +83,13 @@ message Config {
// DisableCache disables DNS cache // DisableCache disables DNS cache
bool disableCache = 8; bool disableCache = 8;
bool serveStale = 12;
uint32 serveExpiredTTL = 13;
QueryStrategy query_strategy = 9; QueryStrategy query_strategy = 9;
bool disableFallback = 10; bool disableFallback = 10;
bool disableFallbackIfMatch = 11; bool disableFallbackIfMatch = 11;
bool enableParallelQuery = 14;
} }
+389 -60
View File
@@ -5,16 +5,22 @@ import (
"context" "context"
go_errors "errors" go_errors "errors"
"fmt" "fmt"
"os"
"runtime"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time"
router "github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/strmatcher" "github.com/xtls/xray-core/common/strmatcher"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"google.golang.org/protobuf/proto"
) )
// DNS is a DNS rely server. // DNS is a DNS rely server.
@@ -22,6 +28,7 @@ type DNS struct {
sync.Mutex sync.Mutex
disableFallback bool disableFallback bool
disableFallbackIfMatch bool disableFallbackIfMatch bool
enableParallelQuery bool
ipOption *dns.IPOption ipOption *dns.IPOption
hosts *StaticHosts hosts *StaticHosts
clients []*Client clients []*Client
@@ -93,6 +100,25 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
} }
for _, ns := range config.NameServer { for _, ns := range config.NameServer {
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
err := parseDomains(ns)
if err != nil {
return nil, errors.New("failed to parse dns domain rules: ").Base(err)
}
expectedGeoip, err := router.GetGeoIPList(ns.ExpectedGeoip)
if err != nil {
return nil, errors.New("failed to parse dns expectIPs rules: ").Base(err)
}
ns.ExpectedGeoip = expectedGeoip
unexpectedGeoip, err := router.GetGeoIPList(ns.UnexpectedGeoip)
if err != nil {
return nil, errors.New("failed to parse dns unexpectedGeoip rules: ").Base(err)
}
ns.UnexpectedGeoip = unexpectedGeoip
}
domainRuleCount += len(ns.PrioritizedDomain) domainRuleCount += len(ns.PrioritizedDomain)
} }
@@ -102,13 +128,12 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
for _, ns := range config.NameServer { for _, ns := range config.NameServer {
clientIdx := len(clients) clientIdx := len(clients)
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) error { updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) {
midx := domainMatcher.Add(domainRule) midx := domainMatcher.Add(domainRule)
matcherInfos[midx] = &DomainMatcherInfo{ matcherInfos[midx] = &DomainMatcherInfo{
clientIdx: uint16(clientIdx), clientIdx: uint16(clientIdx),
domainRuleIdx: uint16(originalRuleIdx), domainRuleIdx: uint16(originalRuleIdx),
} }
return nil
} }
myClientIP := clientIP myClientIP := clientIP
@@ -117,7 +142,20 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
myClientIP = net.IP(ns.ClientIp) myClientIP = net.IP(ns.ClientIp)
} }
disableCache := config.DisableCache || ns.DisableCache disableCache := config.DisableCache
if ns.DisableCache != nil {
disableCache = *ns.DisableCache
}
serveStale := config.ServeStale
if ns.ServeStale != nil {
serveStale = *ns.ServeStale
}
serveExpiredTTL := config.ServeExpiredTTL
if ns.ServeExpiredTTL != nil {
serveExpiredTTL = *ns.ServeExpiredTTL
}
var tag = defaultTag var tag = defaultTag
if len(ns.Tag) > 0 { if len(ns.Tag) > 0 {
@@ -128,7 +166,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
return nil, errors.New("no QueryStrategy available for ", ns.Address) return nil, errors.New("no QueryStrategy available for ", ns.Address)
} }
client, err := NewClient(ctx, ns, myClientIP, disableCache, tag, clientIPOption, &matcherInfos, updateDomain) client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, &matcherInfos, updateDomain)
if err != nil { if err != nil {
return nil, errors.New("failed to create client").Base(err) return nil, errors.New("failed to create client").Base(err)
} }
@@ -149,6 +187,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
matcherInfos: matcherInfos, matcherInfos: matcherInfos,
disableFallback: config.DisableFallback, disableFallback: config.DisableFallback,
disableFallbackIfMatch: config.DisableFallbackIfMatch, disableFallbackIfMatch: config.DisableFallbackIfMatch,
enableParallelQuery: config.EnableParallelQuery,
checkSystem: checkSystem, checkSystem: checkSystem,
}, nil }, nil
} }
@@ -191,7 +230,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
} }
if s.checkSystem { if s.checkSystem {
supportIPv4, supportIPv6 := checkSystemNetwork() supportIPv4, supportIPv6 := checkRoutes()
option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv4Enable = option.IPv4Enable && supportIPv4
option.IPv6Enable = option.IPv6Enable && supportIPv6 option.IPv6Enable = option.IPv6Enable && supportIPv6
} else { } else {
@@ -227,45 +266,11 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
} }
// Name servers lookup // Name servers lookup
var errs []error if s.enableParallelQuery {
for _, client := range s.sortClients(domain) { return s.parallelQuery(domain, option)
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") { } else {
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name()) return s.serialQuery(domain, option)
continue
}
ips, ttl, err := client.QueryIP(s.ctx, domain, option)
if len(ips) > 0 {
if ttl == 0 {
ttl = 1
}
return ips, ttl, nil
}
errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name())
if err == nil {
err = dns.ErrEmptyResponse
}
errs = append(errs, err)
if client.IsFinalQuery() {
break
}
} }
if len(errs) > 0 {
allErrs := errors.Combine(errs...)
err0 := errs[0]
if errors.AllEqual(err0, allErrs) {
if go_errors.Is(err0, dns.ErrEmptyResponse) {
return nil, 0, dns.ErrEmptyResponse
}
return nil, 0, errors.New("returning nil for domain ", domain).Base(err0)
}
return nil, 0, errors.New("returning nil for domain ", domain).Base(allErrs)
}
return nil, 0, dns.ErrEmptyResponse
} }
func (s *DNS) sortClients(domain string) []*Client { func (s *DNS) sortClients(domain string) []*Client {
@@ -292,6 +297,9 @@ func (s *DNS) sortClients(domain string) []*Client {
clients = append(clients, client) clients = append(clients, client)
clientNames = append(clientNames, client.Name()) clientNames = append(clientNames, client.Name())
hasMatch = true hasMatch = true
if client.finalQuery {
return clients
}
} }
if !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) { if !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) {
@@ -303,6 +311,9 @@ func (s *DNS) sortClients(domain string) []*Client {
clientUsed[idx] = true clientUsed[idx] = true
clients = append(clients, client) clients = append(clients, client)
clientNames = append(clientNames, client.Name()) clientNames = append(clientNames, client.Name())
if client.finalQuery {
return clients
}
} }
} }
@@ -314,35 +325,353 @@ func (s *DNS) sortClients(domain string) []*Client {
} }
if len(clients) == 0 { if len(clients) == 0 {
clients = append(clients, s.clients[0]) if len(s.clients) > 0 {
clientNames = append(clientNames, s.clients[0].Name()) clients = append(clients, s.clients[0])
errors.LogDebug(s.ctx, "domain ", domain, " will use the first DNS: ", clientNames) clientNames = append(clientNames, s.clients[0].Name())
errors.LogWarning(s.ctx, "domain ", domain, " will use the first DNS: ", clientNames)
} else {
errors.LogError(s.ctx, "no DNS clients available for domain ", domain, " and no default clients configured")
}
} }
return clients return clients
} }
func mergeQueryErrors(domain string, errs []error) error {
if len(errs) == 0 {
return dns.ErrEmptyResponse
}
var noRNF error
for _, err := range errs {
if go_errors.Is(err, errRecordNotFound) {
continue // server no response, ignore
} else if noRNF == nil {
noRNF = err
} else if !go_errors.Is(err, noRNF) {
return errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
}
}
if go_errors.Is(noRNF, dns.ErrEmptyResponse) {
return dns.ErrEmptyResponse
}
if noRNF == nil {
noRNF = errRecordNotFound
}
return errors.New("returning nil for domain ", domain).Base(noRNF)
}
func (s *DNS) serialQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
var errs []error
for _, client := range s.sortClients(domain) {
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
continue
}
ips, ttl, err := client.QueryIP(s.ctx, domain, option)
if len(ips) > 0 {
return ips, ttl, nil
}
errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name(), " in serial query mode")
if err == nil {
err = dns.ErrEmptyResponse
}
errs = append(errs, err)
}
return nil, 0, mergeQueryErrors(domain, errs)
}
func (s *DNS) parallelQuery(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
var errs []error
clients := s.sortClients(domain)
resultsChan := asyncQueryAll(domain, option, clients, s.ctx)
groups, groupOf := makeGroups( /*s.ctx,*/ clients)
results := make([]*queryResult, len(clients))
pending := make([]int, len(groups))
for gi, g := range groups {
pending[gi] = g.end - g.start + 1
}
nextGroup := 0
for range clients {
result := <-resultsChan
results[result.index] = &result
gi := groupOf[result.index]
pending[gi]--
for nextGroup < len(groups) {
g := groups[nextGroup]
// group race, minimum rtt -> return
for j := g.start; j <= g.end; j++ {
r := results[j]
if r != nil && r.err == nil && len(r.ips) > 0 {
return r.ips, r.ttl, nil
}
}
// current group is incomplete and no one success -> continue pending
if pending[nextGroup] > 0 {
break
}
// all failed -> log and continue next group
for j := g.start; j <= g.end; j++ {
r := results[j]
e := r.err
if e == nil {
e = dns.ErrEmptyResponse
}
errors.LogInfoInner(s.ctx, e, "failed to lookup ip for domain ", domain, " at server ", clients[j].Name(), " in parallel query mode")
errs = append(errs, e)
}
nextGroup++
}
}
return nil, 0, mergeQueryErrors(domain, errs)
}
type queryResult struct {
ips []net.IP
ttl uint32
err error
index int
}
func asyncQueryAll(domain string, option dns.IPOption, clients []*Client, ctx context.Context) chan queryResult {
if len(clients) == 0 {
ch := make(chan queryResult)
close(ch)
return ch
}
ch := make(chan queryResult, len(clients))
for i, client := range clients {
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
errors.LogDebug(ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
ch <- queryResult{err: dns.ErrEmptyResponse, index: i}
continue
}
go func(i int, c *Client) {
qctx := ctx
if !c.server.IsDisableCache() {
nctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), c.timeoutMs*2)
qctx = nctx
defer cancel()
}
ips, ttl, err := c.QueryIP(qctx, domain, option)
ch <- queryResult{ips: ips, ttl: ttl, err: err, index: i}
}(i, client)
}
return ch
}
type group struct{ start, end int }
// merge only adjacent and rule-equivalent Client into a single group
func makeGroups( /*ctx context.Context,*/ clients []*Client) ([]group, []int) {
n := len(clients)
if n == 0 {
return nil, nil
}
groups := make([]group, 0, n)
groupOf := make([]int, n)
s, e := 0, 0
for i := 1; i < n; i++ {
if clients[i-1].policyID == clients[i].policyID {
e = i
} else {
for k := s; k <= e; k++ {
groupOf[k] = len(groups)
}
groups = append(groups, group{start: s, end: e})
s, e = i, i
}
}
for k := s; k <= e; k++ {
groupOf[k] = len(groups)
}
groups = append(groups, group{start: s, end: e})
// var b strings.Builder
// b.WriteString("dns grouping: total clients=")
// b.WriteString(strconv.Itoa(n))
// b.WriteString(", groups=")
// b.WriteString(strconv.Itoa(len(groups)))
// for gi, g := range groups {
// b.WriteString("\n [")
// b.WriteString(strconv.Itoa(g.start))
// b.WriteString("..")
// b.WriteString(strconv.Itoa(g.end))
// b.WriteString("] gid=")
// b.WriteString(strconv.Itoa(gi))
// b.WriteString(" pid=")
// b.WriteString(strconv.FormatUint(uint64(clients[g.start].policyID), 10))
// b.WriteString(" members: ")
// for i := g.start; i <= g.end; i++ {
// if i > g.start {
// b.WriteString(", ")
// }
// b.WriteString(strconv.Itoa(i))
// b.WriteByte(':')
// b.WriteString(clients[i].Name())
// }
// }
// errors.LogDebug(ctx, b.String())
return groups, groupOf
}
func init() { func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return New(ctx, config.(*Config)) return New(ctx, config.(*Config))
})) }))
} }
func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) { func probeRoutes() (ipv4 bool, ipv6 bool) {
conn4, err4 := net.Dial("udp4", "192.33.4.12:53") if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
if err4 != nil { ipv4 = true
supportIPv4 = false conn.Close()
} else {
supportIPv4 = true
conn4.Close()
} }
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
conn6, err6 := net.Dial("udp6", "[2001:500:2::c]:53") ipv6 = true
if err6 != nil { conn.Close()
supportIPv6 = false
} else {
supportIPv6 = true
conn6.Close()
} }
return return
} }
var routeCache struct {
sync.Once
sync.RWMutex
expire time.Time
ipv4, ipv6 bool
}
func checkRoutes() (bool, bool) {
if !isGUIPlatform {
routeCache.Once.Do(func() {
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
})
return routeCache.ipv4, routeCache.ipv6
}
routeCache.RWMutex.RLock()
now := time.Now()
if routeCache.expire.After(now) {
routeCache.RWMutex.RUnlock()
return routeCache.ipv4, routeCache.ipv6
}
routeCache.RWMutex.RUnlock()
routeCache.RWMutex.Lock()
defer routeCache.RWMutex.Unlock()
now = time.Now()
if routeCache.expire.After(now) { // double-check
return routeCache.ipv4, routeCache.ipv6
}
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
return routeCache.ipv4, routeCache.ipv6
}
var isGUIPlatform = detectGUIPlatform()
func detectGUIPlatform() bool {
switch runtime.GOOS {
case "android", "ios", "windows", "darwin":
return true
case "linux", "freebsd", "openbsd":
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
return true
}
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
return true
}
}
return false
}
func parseDomains(ns *NameServer) error {
pureDomains := []*router.Domain{}
// convert to pure domain
for _, pd := range ns.PrioritizedDomain {
pureDomains = append(pureDomains, &router.Domain{
Type: router.Domain_Type(pd.Type),
Value: pd.Domain,
})
}
domainList := []*router.Domain{}
for _, domain := range pureDomains {
val := strings.Split(domain.Value, "_")
if len(val) >= 2 {
fileName := val[0]
code := val[1]
bs, err := filesystem.ReadAsset(fileName)
if err != nil {
return errors.New("failed to load file: ", fileName).Base(err)
}
bs = filesystem.Find(bs, []byte(code))
var geosite router.GeoSite
if err := proto.Unmarshal(bs, &geosite); err != nil {
return errors.New("failed Unmarshal :").Base(err)
}
// parse attr
if len(val) == 3 {
siteWithAttr := strings.Split(val[2], ",")
attrs := router.ParseAttrs(siteWithAttr)
if !attrs.IsEmpty() {
filteredDomains := make([]*router.Domain, 0, len(pureDomains))
for _, domain := range geosite.Domain {
if attrs.Match(domain) {
filteredDomains = append(filteredDomains, domain)
}
}
geosite.Domain = filteredDomains
}
}
domainList = append(domainList, geosite.Domain...)
// update ns.OriginalRules Size
ruleTag := strings.Join(val, ":")
for i, oRule := range ns.OriginalRules {
if oRule.Rule == strings.ToLower(ruleTag) {
ns.OriginalRules[i].Size = uint32(len(geosite.Domain))
}
}
} else {
domainList = append(domainList, domain)
}
}
// convert back to NameServer_PriorityDomain
ns.PrioritizedDomain = []*NameServer_PriorityDomain{}
for _, pd := range domainList {
ns.PrioritizedDomain = append(ns.PrioritizedDomain, &NameServer_PriorityDomain{
Type: ToDomainMatchingType(pd.Type),
Domain: pd.Value,
})
}
return nil
}
+4 -4
View File
@@ -541,7 +541,7 @@ func TestIPMatch(t *testing.T) {
}, },
ExpectedGeoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ {
CountryCode: "local", // local
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{ {
// inner ip, will not match // inner ip, will not match
@@ -565,7 +565,7 @@ func TestIPMatch(t *testing.T) {
}, },
ExpectedGeoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ {
CountryCode: "test", // test
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{ {
Ip: []byte{8, 8, 8, 8}, Ip: []byte{8, 8, 8, 8},
@@ -574,7 +574,7 @@ func TestIPMatch(t *testing.T) {
}, },
}, },
{ {
CountryCode: "test", // test
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{ {
Ip: []byte{8, 8, 8, 4}, Ip: []byte{8, 8, 8, 4},
@@ -669,7 +669,7 @@ func TestLocalDomain(t *testing.T) {
}, },
ExpectedGeoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ // Will match localhost, localhost-a and localhost-b, { // Will match localhost, localhost-a and localhost-b,
CountryCode: "local", // local
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{Ip: []byte{127, 0, 0, 2}, Prefix: 32}, {Ip: []byte{127, 0, 0, 2}, Prefix: 32},
{Ip: []byte{127, 0, 0, 3}, Prefix: 32}, {Ip: []byte{127, 0, 0, 3}, Prefix: 32},
+7 -9
View File
@@ -3,6 +3,7 @@ package dns
import ( import (
"context" "context"
"encoding/binary" "encoding/binary"
"math"
"strings" "strings"
"time" "time"
@@ -13,10 +14,12 @@ import (
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
dns_feature "github.com/xtls/xray-core/features/dns" dns_feature "github.com/xtls/xray-core/features/dns"
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
) )
// Fqdn normalizes domain make sure it ends with '.' // Fqdn normalizes domain make sure it ends with '.'
// case-sensitive
func Fqdn(domain string) string { func Fqdn(domain string) string {
if len(domain) > 0 && strings.HasSuffix(domain, ".") { if len(domain) > 0 && strings.HasSuffix(domain, ".") {
return domain return domain
@@ -38,19 +41,14 @@ type IPRecord struct {
RawHeader *dnsmessage.Header RawHeader *dnsmessage.Header
} }
func (r *IPRecord) getIPs() ([]net.IP, uint32, error) { func (r *IPRecord) getIPs() ([]net.IP, int32, error) {
if r == nil { if r == nil {
return nil, 0, errRecordNotFound return nil, 0, errRecordNotFound
} }
untilExpire := time.Until(r.Expire).Seconds()
if untilExpire <= 0 {
return nil, 0, errRecordNotFound
}
ttl := uint32(untilExpire) + 1 untilExpire := time.Until(r.Expire).Seconds()
if ttl == 1 { ttl := int32(math.Ceil(untilExpire))
r.Expire = time.Now().Add(time.Second) // To ensure that two consecutive requests get the same result
}
if r.RCode != dnsmessage.RCodeSuccess { if r.RCode != dnsmessage.RCodeSuccess {
return nil, ttl, dns_feature.RCodeError(r.RCode) return nil, ttl, dns_feature.RCodeError(r.RCode)
} }
+3 -4
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"math" "math"
"math/big" "math/big"
gonet "net"
"sync" "sync"
"time" "time"
@@ -17,7 +16,7 @@ import (
type Holder struct { type Holder struct {
domainToIP cache.Lru domainToIP cache.Lru
ipRange *gonet.IPNet ipRange *net.IPNet
mu *sync.Mutex mu *sync.Mutex
config *FakeDnsPool config *FakeDnsPool
@@ -79,10 +78,10 @@ func (fkdns *Holder) initializeFromConfig() error {
} }
func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error { func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
var ipRange *gonet.IPNet var ipRange *net.IPNet
var err error var err error
if _, ipRange, err = gonet.ParseCIDR(ipPoolCidr); err != nil { if _, ipRange, err = net.ParseCIDR(ipPoolCidr); err != nil {
return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError() return errors.New("Unable to parse CIDR for Fake DNS IP assignment").Base(err).AtError()
} }
+2 -3
View File
@@ -1,7 +1,6 @@
package fakedns package fakedns
import ( import (
gonet "net"
"strconv" "strconv"
"testing" "testing"
@@ -155,7 +154,7 @@ func TestFakeDNSMulti(t *testing.T) {
assert.True(t, inPool) assert.True(t, inPool)
}) })
t.Run("ipv6", func(t *testing.T) { t.Run("ipv6", func(t *testing.T) {
ip, err := gonet.ResolveIPAddr("ip", "fddd:c5b4:ff5f:f4f0::5") ip, err := net.ResolveIPAddr("ip", "fddd:c5b4:ff5f:f4f0::5")
assert.Nil(t, err) assert.Nil(t, err)
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP)) inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
assert.True(t, inPool) assert.True(t, inPool)
@@ -165,7 +164,7 @@ func TestFakeDNSMulti(t *testing.T) {
assert.False(t, inPool) assert.False(t, inPool)
}) })
t.Run("ipv6_inverse", func(t *testing.T) { t.Run("ipv6_inverse", func(t *testing.T) {
ip, err := gonet.ResolveIPAddr("ip", "fcdd:c5b4:ff5f:f4f0::5") ip, err := net.ResolveIPAddr("ip", "fcdd:c5b4:ff5f:f4f0::5")
assert.Nil(t, err) assert.Nil(t, err)
inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP)) inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
assert.False(t, inPool) assert.False(t, inPool)
+7 -2
View File
@@ -27,7 +27,8 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
for _, mapping := range hosts { for _, mapping := range hosts {
matcher, err := toStrMatcher(mapping.Type, mapping.Domain) matcher, err := toStrMatcher(mapping.Type, mapping.Domain)
if err != nil { if err != nil {
return nil, errors.New("failed to create domain matcher").Base(err) errors.LogErrorInner(context.Background(), err, "failed to create domain matcher, ignore domain rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
continue
} }
id := g.Add(matcher) id := g.Add(matcher)
ips := make([]net.Address, 0, len(mapping.Ip)+1) ips := make([]net.Address, 0, len(mapping.Ip)+1)
@@ -46,10 +47,14 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
for _, ip := range mapping.Ip { for _, ip := range mapping.Ip {
addr := net.IPAddress(ip) addr := net.IPAddress(ip)
if addr == nil { if addr == nil {
return nil, errors.New("invalid IP address in static hosts: ", ip).AtWarning() errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
continue
} }
ips = append(ips, addr) ips = append(ips, addr)
} }
if len(ips) == 0 {
continue
}
} }
sh.ips[id] = ips sh.ips[id] = ips
+54 -42
View File
@@ -20,6 +20,9 @@ import (
type Server interface { type Server interface {
// Name of the Client. // Name of the Client.
Name() string Name() string
IsDisableCache() bool
// QueryIP sends IP queries to its configured server. // QueryIP sends IP queries to its configured server.
QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error)
} }
@@ -29,8 +32,8 @@ type Client struct {
server Server server Server
skipFallback bool skipFallback bool
domains []string domains []string
expectedIPs []*router.GeoIPMatcher expectedIPs router.GeoIPMatcher
unexpectedIPs []*router.GeoIPMatcher unexpectedIPs router.GeoIPMatcher
actPrior bool actPrior bool
actUnprior bool actUnprior bool
tag string tag string
@@ -38,10 +41,11 @@ type Client struct {
finalQuery bool finalQuery bool
ipOption *dns.IPOption ipOption *dns.IPOption
checkSystem bool checkSystem bool
policyID uint32
} }
// NewServer creates a name server object according to the network destination url. // NewServer creates a name server object according to the network destination url.
func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, disableCache bool, clientIP net.IP) (Server, error) { func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (Server, error) {
if address := dest.Address; address.Family().IsDomain() { if address := dest.Address; address.Family().IsDomain() {
u, err := url.Parse(address.Domain()) u, err := url.Parse(address.Domain())
if err != nil { if err != nil {
@@ -51,19 +55,19 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
case strings.EqualFold(u.String(), "localhost"): case strings.EqualFold(u.String(), "localhost"):
return NewLocalNameServer(), nil return NewLocalNameServer(), nil
case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
return NewDoHNameServer(u, dispatcher, false, disableCache, clientIP), nil return NewDoHNameServer(u, dispatcher, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil
case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
return NewDoHNameServer(u, dispatcher, true, disableCache, clientIP), nil return NewDoHNameServer(u, dispatcher, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil
case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode case strings.EqualFold(u.Scheme, "https+local"): // DNS-over-HTTPS Local mode
return NewDoHNameServer(u, nil, false, disableCache, clientIP), nil return NewDoHNameServer(u, nil, false, disableCache, serveStale, serveExpiredTTL, clientIP), nil
case strings.EqualFold(u.Scheme, "h2c+local"): // DNS-over-HTTPS h2c Local mode case strings.EqualFold(u.Scheme, "h2c+local"): // DNS-over-HTTPS h2c Local mode
return NewDoHNameServer(u, nil, true, disableCache, clientIP), nil return NewDoHNameServer(u, nil, true, disableCache, serveStale, serveExpiredTTL, clientIP), nil
case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
return NewQUICNameServer(u, disableCache, clientIP) return NewQUICNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)
case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
return NewTCPNameServer(u, dispatcher, disableCache, clientIP) return NewTCPNameServer(u, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
return NewTCPLocalNameServer(u, disableCache, clientIP) return NewTCPLocalNameServer(u, disableCache, serveStale, serveExpiredTTL, clientIP)
case strings.EqualFold(u.String(), "fakedns"): case strings.EqualFold(u.String(), "fakedns"):
var fd dns.FakeDNSEngine var fd dns.FakeDNSEngine
err = core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) { err = core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
@@ -79,7 +83,7 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
dest.Network = net.Network_UDP dest.Network = net.Network_UDP
} }
if dest.Network == net.Network_UDP { // UDP classic DNS mode if dest.Network == net.Network_UDP { // UDP classic DNS mode
return NewClassicNameServer(dest, dispatcher, disableCache, clientIP), nil return NewClassicNameServer(dest, dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP), nil
} }
return nil, errors.New("No available name server could be created from ", dest).AtWarning() return nil, errors.New("No available name server could be created from ", dest).AtWarning()
} }
@@ -89,17 +93,17 @@ func NewClient(
ctx context.Context, ctx context.Context,
ns *NameServer, ns *NameServer,
clientIP net.IP, clientIP net.IP,
disableCache bool, disableCache bool, serveStale bool, serveExpiredTTL uint32,
tag string, tag string,
ipOption dns.IPOption, ipOption dns.IPOption,
matcherInfos *[]*DomainMatcherInfo, matcherInfos *[]*DomainMatcherInfo,
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error, updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo),
) (*Client, error) { ) (*Client, error) {
client := &Client{} client := &Client{}
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error { err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
// Create a new server for each client for now // Create a new server for each client for now
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, clientIP) server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
if err != nil { if err != nil {
return errors.New("failed to create nameserver").Base(err).AtWarning() return errors.New("failed to create nameserver").Base(err).AtWarning()
} }
@@ -130,7 +134,8 @@ func NewClient(
for _, domain := range ns.PrioritizedDomain { for _, domain := range ns.PrioritizedDomain {
domainRule, err := toStrMatcher(domain.Type, domain.Domain) domainRule, err := toStrMatcher(domain.Type, domain.Domain)
if err != nil { if err != nil {
return errors.New("failed to create prioritized domain").Base(err).AtWarning() errors.LogErrorInner(ctx, err, "failed to create domain matcher, ignore domain rule [type: ", domain.Type, ", domain: ", domain.Domain, "]")
domainRule, _ = toStrMatcher(DomainMatchingType_Full, "hack.fix.index.for.illegal.domain.rule")
} }
originalRuleIdx := ruleCurr originalRuleIdx := ruleCurr
if ruleCurr < len(ns.OriginalRules) { if ruleCurr < len(ns.OriginalRules) {
@@ -147,30 +152,25 @@ func NewClient(
rules = append(rules, domainRule.String()) rules = append(rules, domainRule.String())
ruleCurr++ ruleCurr++
} }
err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos) updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
if err != nil {
return errors.New("failed to create prioritized domain").Base(err).AtWarning()
}
} }
// Establish expected IPs // Establish expected IPs
var expectedMatchers []*router.GeoIPMatcher var expectedMatcher router.GeoIPMatcher
for _, geoip := range ns.ExpectedGeoip { if len(ns.ExpectedGeoip) > 0 {
matcher, err := router.GlobalGeoIPContainer.Add(geoip) expectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)
if err != nil { if err != nil {
return errors.New("failed to create expected ip matcher").Base(err).AtWarning() return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
} }
expectedMatchers = append(expectedMatchers, matcher)
} }
// Establish unexpected IPs // Establish unexpected IPs
var unexpectedMatchers []*router.GeoIPMatcher var unexpectedMatcher router.GeoIPMatcher
for _, geoip := range ns.UnexpectedGeoip { if len(ns.UnexpectedGeoip) > 0 {
matcher, err := router.GlobalGeoIPContainer.Add(geoip) unexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)
if err != nil { if err != nil {
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning() return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
} }
unexpectedMatchers = append(unexpectedMatchers, matcher)
} }
if len(clientIP) > 0 { if len(clientIP) > 0 {
@@ -192,8 +192,8 @@ func NewClient(
client.server = server client.server = server
client.skipFallback = ns.SkipFallback client.skipFallback = ns.SkipFallback
client.domains = rules client.domains = rules
client.expectedIPs = expectedMatchers client.expectedIPs = expectedMatcher
client.unexpectedIPs = unexpectedMatchers client.unexpectedIPs = unexpectedMatcher
client.actPrior = ns.ActPrior client.actPrior = ns.ActPrior
client.actUnprior = ns.ActUnprior client.actUnprior = ns.ActUnprior
client.tag = tag client.tag = tag
@@ -201,6 +201,7 @@ func NewClient(
client.finalQuery = ns.FinalQuery client.finalQuery = ns.FinalQuery
client.ipOption = &ipOption client.ipOption = &ipOption
client.checkSystem = checkSystem client.checkSystem = checkSystem
client.policyID = ns.PolicyID
return nil return nil
}) })
return client, err return client, err
@@ -211,14 +212,10 @@ func (c *Client) Name() string {
return c.server.Name() return c.server.Name()
} }
func (c *Client) IsFinalQuery() bool {
return c.finalQuery
}
// QueryIP sends DNS query to the name server with the client's IP. // QueryIP sends DNS query to the name server with the client's IP.
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) { func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
if c.checkSystem { if c.checkSystem {
supportIPv4, supportIPv6 := checkSystemNetwork() supportIPv4, supportIPv6 := checkRoutes()
option.IPv4Enable = option.IPv4Enable && supportIPv4 option.IPv4Enable = option.IPv4Enable && supportIPv4
option.IPv6Enable = option.IPv6Enable && supportIPv6 option.IPv6Enable = option.IPv6Enable && supportIPv6
} else { } else {
@@ -243,32 +240,32 @@ func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption
return nil, 0, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }
if len(c.expectedIPs) > 0 && !c.actPrior { if c.expectedIPs != nil && !c.actPrior {
ips = router.MatchIPs(c.expectedIPs, ips, false) ips, _ = c.expectedIPs.FilterIPs(ips)
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", ips, " matched at server ", c.Name()) errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", ips, " matched at server ", c.Name())
if len(ips) == 0 { if len(ips) == 0 {
return nil, 0, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }
} }
if len(c.unexpectedIPs) > 0 && !c.actUnprior { if c.unexpectedIPs != nil && !c.actUnprior {
ips = router.MatchIPs(c.unexpectedIPs, ips, true) _, ips = c.unexpectedIPs.FilterIPs(ips)
errors.LogDebug(context.Background(), "domain ", domain, " unexpectedIPs ", ips, " matched at server ", c.Name()) errors.LogDebug(context.Background(), "domain ", domain, " unexpectedIPs ", ips, " matched at server ", c.Name())
if len(ips) == 0 { if len(ips) == 0 {
return nil, 0, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }
} }
if len(c.expectedIPs) > 0 && c.actPrior { if c.expectedIPs != nil && c.actPrior {
ipsNew := router.MatchIPs(c.expectedIPs, ips, false) ipsNew, _ := c.expectedIPs.FilterIPs(ips)
if len(ipsNew) > 0 { if len(ipsNew) > 0 {
ips = ipsNew ips = ipsNew
errors.LogDebug(context.Background(), "domain ", domain, " priorIPs ", ips, " matched at server ", c.Name()) errors.LogDebug(context.Background(), "domain ", domain, " priorIPs ", ips, " matched at server ", c.Name())
} }
} }
if len(c.unexpectedIPs) > 0 && c.actUnprior { if c.unexpectedIPs != nil && c.actUnprior {
ipsNew := router.MatchIPs(c.unexpectedIPs, ips, true) _, ipsNew := c.unexpectedIPs.FilterIPs(ips)
if len(ipsNew) > 0 { if len(ipsNew) > 0 {
ips = ipsNew ips = ipsNew
errors.LogDebug(context.Background(), "domain ", domain, " unpriorIPs ", ips, " matched at server ", c.Name()) errors.LogDebug(context.Background(), "domain ", domain, " unpriorIPs ", ips, " matched at server ", c.Name())
@@ -300,3 +297,18 @@ func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption)
return ipOption return ipOption
} }
} }
func ToDomainMatchingType(t router.Domain_Type) DomainMatchingType {
switch t {
case router.Domain_Domain:
return DomainMatchingType_Subdomain
case router.Domain_Full:
return DomainMatchingType_Full
case router.Domain_Plain:
return DomainMatchingType_Keyword
case router.Domain_Regex:
return DomainMatchingType_Regex
default:
panic("unknown domain type")
}
}
+173
View File
@@ -0,0 +1,173 @@
package dns
import (
"context"
go_errors "errors"
"time"
"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/signal/pubsub"
"github.com/xtls/xray-core/features/dns"
)
type CachedNameserver interface {
getCacheController() *CacheController
sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns.IPOption)
}
// queryIP is called from dns.Server->queryIPTimeout
func queryIP(ctx context.Context, s CachedNameserver, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain)
cache := s.getCacheController()
if !cache.disableCache {
if rec := cache.findRecords(fqdn); rec != nil {
ips, ttl, err := merge(option, rec.A, rec.AAAA)
if !go_errors.Is(err, errRecordNotFound) {
if ttl > 0 {
errors.LogDebugInner(ctx, err, cache.name, " cache HIT ", fqdn, " -> ", ips)
log.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, uint32(ttl), err
}
if cache.serveStale && (cache.serveExpiredTTL == 0 || cache.serveExpiredTTL < ttl) {
errors.LogDebugInner(ctx, err, cache.name, " cache OPTIMISTE ", fqdn, " -> ", ips)
log.Record(&log.DNSLog{Server: cache.name, Domain: fqdn, Result: ips, Status: log.DNSCacheOptimiste, Elapsed: 0, Error: err})
go pull(ctx, s, fqdn, option)
return ips, 1, err
}
}
}
} else {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", fqdn, " at ", cache.name)
}
return fetch(ctx, s, fqdn, option)
}
func pull(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) {
nctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 8*time.Second)
defer cancel()
fetch(nctx, s, fqdn, option)
}
func fetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) ([]net.IP, uint32, error) {
key := fqdn
switch {
case option.IPv4Enable && option.IPv6Enable:
key = key + "46"
case option.IPv4Enable:
key = key + "4"
case option.IPv6Enable:
key = key + "6"
}
v, _, _ := s.getCacheController().requestGroup.Do(key, func() (any, error) {
return doFetch(ctx, s, fqdn, option), nil
})
ret := v.(result)
return ret.ips, ret.ttl, ret.error
}
type result struct {
ips []net.IP
ttl uint32
error
}
func doFetch(ctx context.Context, s CachedNameserver, fqdn string, option dns.IPOption) result {
sub4, sub6 := s.getCacheController().registerSubscribers(fqdn, option)
defer closeSubscribers(sub4, sub6)
noResponseErrCh := make(chan error, 2)
onEvent := func(sub *pubsub.Subscriber) (*IPRecord, error) {
if sub == nil {
return nil, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case err := <-noResponseErrCh:
return nil, err
case msg := <-sub.Wait():
sub.Close()
return msg.(*IPRecord), nil // should panic
}
}
start := time.Now()
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
rec4, err4 := onEvent(sub4)
rec6, err6 := onEvent(sub6)
var errs []error
if err4 != nil {
errs = append(errs, err4)
}
if err6 != nil {
errs = append(errs, err6)
}
ips, ttl, err := merge(option, rec4, rec6, errs...)
var rTTL uint32
if ttl > 0 {
rTTL = uint32(ttl)
} else if ttl == 0 && go_errors.Is(err, errRecordNotFound) {
rTTL = 0
} else { // edge case: where a fast rep's ttl expires during the rtt of a slower, parallel query
rTTL = 1
}
log.Record(&log.DNSLog{Server: s.getCacheController().name, Domain: fqdn, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return result{ips, rTTL, err}
}
func merge(option dns.IPOption, rec4 *IPRecord, rec6 *IPRecord, errs ...error) ([]net.IP, int32, error) {
var allIPs []net.IP
var rTTL int32 = dns.DefaultTTL
mergeReq := option.IPv4Enable && option.IPv6Enable
if option.IPv4Enable {
ips, ttl, err := rec4.getIPs() // it's safe
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
return ips, ttl, err
}
if ttl < rTTL {
rTTL = ttl
}
if len(ips) > 0 {
allIPs = append(allIPs, ips...)
} else {
errs = append(errs, err)
}
}
if option.IPv6Enable {
ips, ttl, err := rec6.getIPs() // it's safe
if !mergeReq || go_errors.Is(err, errRecordNotFound) {
return ips, ttl, err
}
if ttl < rTTL {
rTTL = ttl
}
if len(ips) > 0 {
allIPs = append(allIPs, ips...)
} else {
errs = append(errs, err)
}
}
if len(allIPs) > 0 {
return allIPs, rTTL, nil
}
if len(errs) == 2 && go_errors.Is(errs[0], errs[1]) {
return nil, rTTL, errs[0]
}
return nil, rTTL, errors.Combine(errs...)
}
+36 -61
View File
@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
go_errors "errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -38,7 +37,7 @@ type DoHNameServer struct {
} }
// NewDoHNameServer creates DOH/DOHL client object for remote/local resolving. // NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, clientIP net.IP) *DoHNameServer { func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *DoHNameServer {
url.Scheme = "https" url.Scheme = "https"
mode := "DOH" mode := "DOH"
if dispatcher == nil { if dispatcher == nil {
@@ -46,7 +45,7 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, dis
} }
errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c) errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
s := &DoHNameServer{ s := &DoHNameServer{
cacheController: NewCacheController(mode+"//"+url.Host, disableCache), cacheController: NewCacheController(mode+"//"+url.Host, disableCache, serveStale, serveExpiredTTL),
dohURL: url.String(), dohURL: url.String(),
clientIP: clientIP, clientIP: clientIP,
} }
@@ -117,22 +116,35 @@ func (s *DoHNameServer) Name() string {
return s.cacheController.name return s.cacheController.name
} }
// IsDisableCache implements Server.
func (s *DoHNameServer) IsDisableCache() bool {
return s.cacheController.disableCache
}
func (s *DoHNameServer) newReqID() uint16 { func (s *DoHNameServer) newReqID() uint16 {
return 0 return 0
} }
func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) { // getCacheController implements CachedNameserver.
errors.LogInfo(ctx, s.Name(), " querying: ", domain) func (s *DoHNameServer) getCacheController() *CacheController {
return s.cacheController
}
if s.Name()+"." == "DOH//"+domain { // sendQuery implements CachedNameserver.
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead.") func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name()) errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
if s.Name()+"." == "DOH//"+fqdn {
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
if noResponseErrCh != nil {
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
}
return return
} }
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467 // As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all // Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300)))) reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
var deadline time.Time var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
@@ -166,23 +178,29 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
b, err := dns.PackMessage(r.msg) b, err := dns.PackMessage(r.msg)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to pack dns query for ", domain) errors.LogErrorInner(ctx, err, "failed to pack dns query for ", fqdn)
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes()) resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to retrieve response for ", domain) errors.LogErrorInner(ctx, err, "failed to retrieve response for ", fqdn)
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
rec, err := parseResponse(resp) rec, err := parseResponse(resp)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", domain) errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", fqdn)
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
s.cacheController.updateIP(r, rec) s.cacheController.updateRecord(r, rec)
}(req) }(req)
} }
} }
@@ -216,49 +234,6 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
} }
// QueryIP implements Server. // QueryIP implements Server.
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { // nolint: dupl func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) return queryIP(ctx, s, domain, option)
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
defer closeSubscribers(sub4, sub6)
if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else {
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err
}
}
noResponseErrCh := make(chan error, 2)
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
start := time.Now()
if sub4 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub4.Wait():
sub4.Close()
}
}
if sub6 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub6.Wait():
sub6.Close()
}
}
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
} }
+4 -4
View File
@@ -17,7 +17,7 @@ func TestDOHNameServer(t *testing.T) {
url, err := url.Parse("https+local://1.1.1.1/dns-query") url, err := url.Parse("https+local://1.1.1.1/dns-query")
common.Must(err) common.Must(err)
s := NewDoHNameServer(url, nil, false, false, net.IP(nil)) s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
@@ -34,7 +34,7 @@ func TestDOHNameServerWithCache(t *testing.T) {
url, err := url.Parse("https+local://1.1.1.1/dns-query") url, err := url.Parse("https+local://1.1.1.1/dns-query")
common.Must(err) common.Must(err)
s := NewDoHNameServer(url, nil, false, false, net.IP(nil)) s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
@@ -62,7 +62,7 @@ func TestDOHNameServerWithIPv4Override(t *testing.T) {
url, err := url.Parse("https+local://1.1.1.1/dns-query") url, err := url.Parse("https+local://1.1.1.1/dns-query")
common.Must(err) common.Must(err)
s := NewDoHNameServer(url, nil, false, false, net.IP(nil)) s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
@@ -85,7 +85,7 @@ func TestDOHNameServerWithIPv6Override(t *testing.T) {
url, err := url.Parse("https+local://1.1.1.1/dns-query") url, err := url.Parse("https+local://1.1.1.1/dns-query")
common.Must(err) common.Must(err)
s := NewDoHNameServer(url, nil, false, false, net.IP(nil)) s := NewDoHNameServer(url, nil, false, false, false, 0, net.IP(nil))
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: false, IPv4Enable: false,
+5
View File
@@ -20,6 +20,11 @@ func (FakeDNSServer) Name() string {
return "FakeDNS" return "FakeDNS"
} }
// IsDisableCache implements Server.
func (s *FakeDNSServer) IsDisableCache() bool {
return true
}
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) { func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, opt dns.IPOption) ([]net.IP, uint32, error) {
if f.fakeDNSEngine == nil { if f.fakeDNSEngine == nil {
return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError() return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError()
+5
View File
@@ -35,6 +35,11 @@ func (s *LocalNameServer) Name() string {
return "localhost" return "localhost"
} }
// IsDisableCache implements Server.
func (s *LocalNameServer) IsDisableCache() bool {
return true
}
// NewLocalNameServer creates localdns server object for directly lookup in system DNS. // NewLocalNameServer creates localdns server object for directly lookup in system DNS.
func NewLocalNameServer() *LocalNameServer { func NewLocalNameServer() *LocalNameServer {
errors.LogInfo(context.Background(), "DNS: created localhost client") errors.LogInfo(context.Background(), "DNS: created localhost client")
+47 -65
View File
@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/binary" "encoding/binary"
go_errors "errors"
"net/url" "net/url"
"sync" "sync"
"time" "time"
@@ -37,9 +36,7 @@ type QUICNameServer struct {
} }
// NewQUICNameServer creates DNS-over-QUIC client object for local resolving // NewQUICNameServer creates DNS-over-QUIC client object for local resolving
func NewQUICNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*QUICNameServer, error) { func NewQUICNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*QUICNameServer, error) {
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String())
var err error var err error
port := net.Port(853) port := net.Port(853)
if url.Port() != "" { if url.Port() != "" {
@@ -51,27 +48,37 @@ func NewQUICNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*QUICN
dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port) dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)
s := &QUICNameServer{ s := &QUICNameServer{
cacheController: NewCacheController(url.String(), disableCache), cacheController: NewCacheController(url.String(), disableCache, serveStale, serveExpiredTTL),
destination: &dest, destination: &dest,
clientIP: clientIP, clientIP: clientIP,
} }
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String())
return s, nil return s, nil
} }
// Name returns client name // Name implements Server.
func (s *QUICNameServer) Name() string { func (s *QUICNameServer) Name() string {
return s.cacheController.name return s.cacheController.name
} }
// IsDisableCache implements Server.
func (s *QUICNameServer) IsDisableCache() bool {
return s.cacheController.disableCache
}
func (s *QUICNameServer) newReqID() uint16 { func (s *QUICNameServer) newReqID() uint16 {
return 0 return 0
} }
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) { // getCacheController implements CachedNameServer.
errors.LogInfo(ctx, s.Name(), " querying: ", domain) func (s *QUICNameServer) getCacheController() *CacheController { return s.cacheController }
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, 0)) // sendQuery implements CachedNameServer.
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
var deadline time.Time var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
@@ -103,7 +110,9 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- e
b, err := dns.PackMessage(r.msg) b, err := dns.PackMessage(r.msg)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to pack dns query") errors.LogErrorInner(ctx, err, "failed to pack dns query")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
@@ -111,13 +120,17 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- e
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len())) err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "binary write failed") errors.LogErrorInner(ctx, err, "binary write failed")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
_, err = dnsReqBuf.Write(b.Bytes()) _, err = dnsReqBuf.Write(b.Bytes())
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "buffer write failed") errors.LogErrorInner(ctx, err, "buffer write failed")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
b.Release() b.Release()
@@ -125,14 +138,18 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- e
conn, err := s.openStream(dnsCtx) conn, err := s.openStream(dnsCtx)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to open quic connection") errors.LogErrorInner(ctx, err, "failed to open quic connection")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
_, err = conn.Write(dnsReqBuf.Bytes()) _, err = conn.Write(dnsReqBuf.Bytes())
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to send query") errors.LogErrorInner(ctx, err, "failed to send query")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
@@ -143,81 +160,46 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- e
n, err := respBuf.ReadFullFrom(conn, 2) n, err := respBuf.ReadFullFrom(conn, 2)
if err != nil && n == 0 { if err != nil && n == 0 {
errors.LogErrorInner(ctx, err, "failed to read response length") errors.LogErrorInner(ctx, err, "failed to read response length")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
var length int16 var length uint16
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length) err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to parse response length") errors.LogErrorInner(ctx, err, "failed to parse response length")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
respBuf.Clear() respBuf.Clear()
n, err = respBuf.ReadFullFrom(conn, int32(length)) n, err = respBuf.ReadFullFrom(conn, int32(length))
if err != nil && n == 0 { if err != nil && n == 0 {
errors.LogErrorInner(ctx, err, "failed to read response length") errors.LogErrorInner(ctx, err, "failed to read response length")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
rec, err := parseResponse(respBuf.Bytes()) rec, err := parseResponse(respBuf.Bytes())
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to handle response") errors.LogErrorInner(ctx, err, "failed to handle response")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
s.cacheController.updateIP(r, rec) s.cacheController.updateRecord(r, rec)
}(req) }(req)
} }
} }
// QueryIP is called from dns.Server->queryIPTimeout // QueryIP implements Server.
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) return queryIP(ctx, s, domain, option)
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
defer closeSubscribers(sub4, sub6)
if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else {
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err
}
}
noResponseErrCh := make(chan error, 2)
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
start := time.Now()
if sub4 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub4.Wait():
sub4.Close()
}
}
if sub6 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub6.Wait():
sub6.Close()
}
}
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
} }
func isActive(s *quic.Conn) bool { func isActive(s *quic.Conn) bool {
+3 -3
View File
@@ -16,7 +16,7 @@ import (
func TestQUICNameServer(t *testing.T) { func TestQUICNameServer(t *testing.T) {
url, err := url.Parse("quic://dns.adguard-dns.com") url, err := url.Parse("quic://dns.adguard-dns.com")
common.Must(err) common.Must(err)
s, err := NewQUICNameServer(url, false, net.IP(nil)) s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
common.Must(err) common.Must(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
@@ -43,7 +43,7 @@ func TestQUICNameServer(t *testing.T) {
func TestQUICNameServerWithIPv4Override(t *testing.T) { func TestQUICNameServerWithIPv4Override(t *testing.T) {
url, err := url.Parse("quic://dns.adguard-dns.com") url, err := url.Parse("quic://dns.adguard-dns.com")
common.Must(err) common.Must(err)
s, err := NewQUICNameServer(url, false, net.IP(nil)) s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
common.Must(err) common.Must(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
@@ -66,7 +66,7 @@ func TestQUICNameServerWithIPv4Override(t *testing.T) {
func TestQUICNameServerWithIPv6Override(t *testing.T) { func TestQUICNameServerWithIPv6Override(t *testing.T) {
url, err := url.Parse("quic://dns.adguard-dns.com") url, err := url.Parse("quic://dns.adguard-dns.com")
common.Must(err) common.Must(err)
s, err := NewQUICNameServer(url, false, net.IP(nil)) s, err := NewQUICNameServer(url, false, false, 0, net.IP(nil))
common.Must(err) common.Must(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
+52 -66
View File
@@ -4,14 +4,12 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/binary" "encoding/binary"
go_errors "errors"
"net/url" "net/url"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/protocol/dns" "github.com/xtls/xray-core/common/protocol/dns"
@@ -34,10 +32,10 @@ type TCPNameServer struct {
func NewTCPNameServer( func NewTCPNameServer(
url *url.URL, url *url.URL,
dispatcher routing.Dispatcher, dispatcher routing.Dispatcher,
disableCache bool, disableCache bool, serveStale bool, serveExpiredTTL uint32,
clientIP net.IP, clientIP net.IP,
) (*TCPNameServer, error) { ) (*TCPNameServer, error) {
s, err := baseTCPNameServer(url, "TCP", disableCache, clientIP) s, err := baseTCPNameServer(url, "TCP", disableCache, serveStale, serveExpiredTTL, clientIP)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -54,12 +52,13 @@ func NewTCPNameServer(
), nil ), nil
} }
errors.LogInfo(context.Background(), "DNS: created TCP client initialized for ", url.String())
return s, nil return s, nil
} }
// NewTCPLocalNameServer creates DNS over TCP client object for local resolving // NewTCPLocalNameServer creates DNS over TCP client object for local resolving
func NewTCPLocalNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*TCPNameServer, error) { func NewTCPLocalNameServer(url *url.URL, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {
s, err := baseTCPNameServer(url, "TCPL", disableCache, clientIP) s, err := baseTCPNameServer(url, "TCPL", disableCache, serveStale, serveExpiredTTL, clientIP)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -68,10 +67,11 @@ func NewTCPLocalNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*T
return internet.DialSystem(ctx, *s.destination, nil) return internet.DialSystem(ctx, *s.destination, nil)
} }
errors.LogInfo(context.Background(), "DNS: created Local TCP client initialized for ", url.String())
return s, nil return s, nil
} }
func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, clientIP net.IP) (*TCPNameServer, error) { func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) (*TCPNameServer, error) {
port := net.Port(53) port := net.Port(53)
if url.Port() != "" { if url.Port() != "" {
var err error var err error
@@ -82,7 +82,7 @@ func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, clientIP
dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port) dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)
s := &TCPNameServer{ s := &TCPNameServer{
cacheController: NewCacheController(prefix+"//"+dest.NetAddr(), disableCache), cacheController: NewCacheController(prefix+"//"+dest.NetAddr(), disableCache, serveStale, serveExpiredTTL),
destination: &dest, destination: &dest,
clientIP: clientIP, clientIP: clientIP,
} }
@@ -95,14 +95,25 @@ func (s *TCPNameServer) Name() string {
return s.cacheController.name return s.cacheController.name
} }
// IsDisableCache implements Server.
func (s *TCPNameServer) IsDisableCache() bool {
return s.cacheController.disableCache
}
func (s *TCPNameServer) newReqID() uint16 { func (s *TCPNameServer) newReqID() uint16 {
return uint16(atomic.AddUint32(&s.reqID, 1)) return uint16(atomic.AddUint32(&s.reqID, 1))
} }
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) { // getCacheController implements CachedNameserver.
errors.LogDebug(ctx, s.Name(), " querying DNS for: ", domain) func (s *TCPNameServer) getCacheController() *CacheController {
return s.cacheController
}
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, 0)) // sendQuery implements CachedNameserver.
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
var deadline time.Time var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
@@ -131,14 +142,18 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
b, err := dns.PackMessage(r.msg) b, err := dns.PackMessage(r.msg)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to pack dns query") errors.LogErrorInner(ctx, err, "failed to pack dns query")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
conn, err := s.dial(dnsCtx) conn, err := s.dial(dnsCtx)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to dial namesever") errors.LogErrorInner(ctx, err, "failed to dial namesever")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
defer conn.Close() defer conn.Close()
@@ -146,13 +161,17 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len())) err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "binary write failed") errors.LogErrorInner(ctx, err, "binary write failed")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
_, err = dnsReqBuf.Write(b.Bytes()) _, err = dnsReqBuf.Write(b.Bytes())
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "buffer write failed") errors.LogErrorInner(ctx, err, "buffer write failed")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
b.Release() b.Release()
@@ -160,7 +179,9 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
_, err = conn.Write(dnsReqBuf.Bytes()) _, err = conn.Write(dnsReqBuf.Bytes())
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to send query") errors.LogErrorInner(ctx, err, "failed to send query")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
dnsReqBuf.Release() dnsReqBuf.Release()
@@ -170,80 +191,45 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
n, err := respBuf.ReadFullFrom(conn, 2) n, err := respBuf.ReadFullFrom(conn, 2)
if err != nil && n == 0 { if err != nil && n == 0 {
errors.LogErrorInner(ctx, err, "failed to read response length") errors.LogErrorInner(ctx, err, "failed to read response length")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
var length int16 var length uint16
err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length) err = binary.Read(bytes.NewReader(respBuf.Bytes()), binary.BigEndian, &length)
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to parse response length") errors.LogErrorInner(ctx, err, "failed to parse response length")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
respBuf.Clear() respBuf.Clear()
n, err = respBuf.ReadFullFrom(conn, int32(length)) n, err = respBuf.ReadFullFrom(conn, int32(length))
if err != nil && n == 0 { if err != nil && n == 0 {
errors.LogErrorInner(ctx, err, "failed to read response length") errors.LogErrorInner(ctx, err, "failed to read response length")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
rec, err := parseResponse(respBuf.Bytes()) rec, err := parseResponse(respBuf.Bytes())
if err != nil { if err != nil {
errors.LogErrorInner(ctx, err, "failed to parse DNS over TCP response") errors.LogErrorInner(ctx, err, "failed to parse DNS over TCP response")
noResponseErrCh <- err if noResponseErrCh != nil {
noResponseErrCh <- err
}
return return
} }
s.cacheController.updateIP(r, rec) s.cacheController.updateRecord(r, rec)
}(req) }(req)
} }
} }
// QueryIP implements Server. // QueryIP implements Server.
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) return queryIP(ctx, s, domain, option)
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
defer closeSubscribers(sub4, sub6)
if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else {
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err
}
}
noResponseErrCh := make(chan error, 2)
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
start := time.Now()
if sub4 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub4.Wait():
sub4.Close()
}
}
if sub6 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub6.Wait():
sub6.Close()
}
}
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
} }
+4 -4
View File
@@ -16,7 +16,7 @@ import (
func TestTCPLocalNameServer(t *testing.T) { func TestTCPLocalNameServer(t *testing.T) {
url, err := url.Parse("tcp+local://8.8.8.8") url, err := url.Parse("tcp+local://8.8.8.8")
common.Must(err) common.Must(err)
s, err := NewTCPLocalNameServer(url, false, net.IP(nil)) s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
common.Must(err) common.Must(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
@@ -33,7 +33,7 @@ func TestTCPLocalNameServer(t *testing.T) {
func TestTCPLocalNameServerWithCache(t *testing.T) { func TestTCPLocalNameServerWithCache(t *testing.T) {
url, err := url.Parse("tcp+local://8.8.8.8") url, err := url.Parse("tcp+local://8.8.8.8")
common.Must(err) common.Must(err)
s, err := NewTCPLocalNameServer(url, false, net.IP(nil)) s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
common.Must(err) common.Must(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
@@ -61,7 +61,7 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
func TestTCPLocalNameServerWithIPv4Override(t *testing.T) { func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
url, err := url.Parse("tcp+local://8.8.8.8") url, err := url.Parse("tcp+local://8.8.8.8")
common.Must(err) common.Must(err)
s, err := NewTCPLocalNameServer(url, false, net.IP(nil)) s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
common.Must(err) common.Must(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
@@ -85,7 +85,7 @@ func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
func TestTCPLocalNameServerWithIPv6Override(t *testing.T) { func TestTCPLocalNameServerWithIPv6Override(t *testing.T) {
url, err := url.Parse("tcp+local://8.8.8.8") url, err := url.Parse("tcp+local://8.8.8.8")
common.Must(err) common.Must(err)
s, err := NewTCPLocalNameServer(url, false, net.IP(nil)) s, err := NewTCPLocalNameServer(url, false, false, 0, net.IP(nil))
common.Must(err) common.Must(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
+21 -54
View File
@@ -2,7 +2,6 @@ package dns
import ( import (
"context" "context"
go_errors "errors"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -10,7 +9,6 @@ import (
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol/dns" "github.com/xtls/xray-core/common/protocol/dns"
udp_proto "github.com/xtls/xray-core/common/protocol/udp" udp_proto "github.com/xtls/xray-core/common/protocol/udp"
@@ -39,14 +37,14 @@ type udpDnsRequest struct {
} }
// NewClassicNameServer creates udp server object for remote resolving. // NewClassicNameServer creates udp server object for remote resolving.
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, disableCache bool, clientIP net.IP) *ClassicNameServer { func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, disableCache bool, serveStale bool, serveExpiredTTL uint32, clientIP net.IP) *ClassicNameServer {
// default to 53 if unspecific // default to 53 if unspecific
if address.Port == 0 { if address.Port == 0 {
address.Port = net.Port(53) address.Port = net.Port(53)
} }
s := &ClassicNameServer{ s := &ClassicNameServer{
cacheController: NewCacheController(strings.ToUpper(address.String()), disableCache), cacheController: NewCacheController(strings.ToUpper(address.String()), disableCache, serveStale, serveExpiredTTL),
address: &address, address: &address,
requests: make(map[uint16]*udpDnsRequest), requests: make(map[uint16]*udpDnsRequest),
clientIP: clientIP, clientIP: clientIP,
@@ -56,6 +54,7 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
Execute: s.RequestsCleanup, Execute: s.RequestsCleanup,
} }
s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse) s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
errors.LogInfo(context.Background(), "DNS: created UDP client initialized for ", address.NetAddr()) errors.LogInfo(context.Background(), "DNS: created UDP client initialized for ", address.NetAddr())
return s return s
} }
@@ -65,6 +64,11 @@ func (s *ClassicNameServer) Name() string {
return s.cacheController.name return s.cacheController.name
} }
// IsDisableCache implements Server.
func (s *ClassicNameServer) IsDisableCache() bool {
return s.cacheController.disableCache
}
// RequestsCleanup clears expired items from cache // RequestsCleanup clears expired items from cache
func (s *ClassicNameServer) RequestsCleanup() error { func (s *ClassicNameServer) RequestsCleanup() error {
now := time.Now() now := time.Now()
@@ -94,7 +98,7 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
ipRec, err := parseResponse(payload.Bytes()) ipRec, err := parseResponse(payload.Bytes())
payload.Release() payload.Release()
if err != nil { if err != nil {
errors.LogError(ctx, s.Name(), " fail to parse responded DNS udp") errors.LogErrorInner(ctx, err, s.Name(), " fail to parse responded DNS udp")
return return
} }
@@ -107,7 +111,7 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
} }
s.Unlock() s.Unlock()
if !ok { if !ok {
errors.LogError(ctx, s.Name(), " cannot find the pending request") errors.LogErrorInner(ctx, err, s.Name(), " cannot find the pending request")
return return
} }
@@ -134,7 +138,7 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
} }
} }
s.cacheController.updateIP(&req.dnsRequest, ipRec) s.cacheController.updateRecord(&req.dnsRequest, ipRec)
} }
func (s *ClassicNameServer) newReqID() uint16 { func (s *ClassicNameServer) newReqID() uint16 {
@@ -150,10 +154,16 @@ func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {
common.Must(s.requestsCleanup.Start()) common.Must(s.requestsCleanup.Start())
} }
func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, domain string, option dns_feature.IPOption) { // getCacheController implements CachedNameserver.
errors.LogDebug(ctx, s.Name(), " querying DNS for: ", domain) func (s *ClassicNameServer) getCacheController() *CacheController {
return s.cacheController
}
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, 0)) // sendQuery implements CachedNameserver.
func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
for _, req := range reqs { for _, req := range reqs {
udpReq := &udpDnsRequest{ udpReq := &udpDnsRequest{
@@ -170,48 +180,5 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, domai
// QueryIP implements Server. // QueryIP implements Server.
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) return queryIP(ctx, s, domain, option)
sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
defer closeSubscribers(sub4, sub6)
if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else {
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err
}
}
noResponseErrCh := make(chan error, 2)
s.sendQuery(ctx, noResponseErrCh, fqdn, option)
start := time.Now()
if sub4 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub4.Wait():
sub4.Close()
}
}
if sub6 != nil {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case err := <-noResponseErrCh:
return nil, 0, err
case <-sub6.Wait():
sub6.Close()
}
}
ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
} }
+2 -3
View File
@@ -2,7 +2,6 @@ package inbound
import ( import (
"context" "context"
gonet "net"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -565,12 +564,12 @@ func (w *dsWorker) Close() error {
} }
func IsLocal(ip net.IP) bool { func IsLocal(ip net.IP) bool {
addrs, err := gonet.InterfaceAddrs() addrs, err := net.InterfaceAddrs()
if err != nil { if err != nil {
return false return false
} }
for _, addr := range addrs { for _, addr := range addrs {
if ipnet, ok := addr.(*gonet.IPNet); ok { if ipnet, ok := addr.(*net.IPNet); ok {
if ipnet.IP.Equal(ip) { if ipnet.IP.Equal(ip) {
return true return true
} }
+8 -5
View File
@@ -6,7 +6,6 @@ import (
goerrors "errors" goerrors "errors"
"io" "io"
"math/big" "math/big"
gonet "net"
"os" "os"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
@@ -317,8 +316,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
conn, err := internet.Dial(ctx, dest, h.streamSettings) conn, err := internet.Dial(ctx, dest, h.streamSettings)
conn = h.getStatCouterConnection(conn) conn = h.getStatCouterConnection(conn)
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] if outbounds != nil {
ob.Conn = conn ob := outbounds[len(outbounds)-1]
ob.Conn = conn
} else {
// for Vision's pre-connect
}
return conn, err return conn, err
} }
@@ -394,7 +397,7 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
func ParseRandomIP(addr net.Address, prefix string) net.Address { func ParseRandomIP(addr net.Address, prefix string) net.Address {
_, ipnet, _ := gonet.ParseCIDR(addr.IP().String() + "/" + prefix) _, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
ones, bits := ipnet.Mask.Size() ones, bits := ipnet.Mask.Size()
subnetSize := new(big.Int).Lsh(big.NewInt(1), uint(bits-ones)) subnetSize := new(big.Int).Lsh(big.NewInt(1), uint(bits-ones))
@@ -408,5 +411,5 @@ func ParseRandomIP(addr net.Address, prefix string) net.Address {
padded := make([]byte, len(ipnet.IP)) padded := make([]byte, len(ipnet.IP))
copy(padded[len(padded)-len(rndBytes):], rndBytes) copy(padded[len(padded)-len(rndBytes):], rndBytes)
return net.ParseAddress(gonet.IP(padded).String()) return net.ParseAddress(net.IP(padded).String())
} }
-3
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"time" "time"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/mux" "github.com/xtls/xray-core/common/mux"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
@@ -230,8 +229,6 @@ func (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, l
} }
return w.Dispatcher.DispatchLink(ctx, dest, link) return w.Dispatcher.DispatchLink(ctx, dest, link)
} }
link = w.Dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link)
w.handleInternalConn(link) w.handleInternalConn(link)
return nil return nil
+170 -55
View File
@@ -1,7 +1,11 @@
package router package router
import ( import (
"context"
"os"
"path/filepath"
"regexp" "regexp"
"slices"
"strings" "strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -47,20 +51,6 @@ var matcherTypeMap = map[Domain_Type]strmatcher.Type{
Domain_Full: strmatcher.Full, Domain_Full: strmatcher.Full,
} }
func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
matcherType, f := matcherTypeMap[domain.Type]
if !f {
return nil, errors.New("unsupported domain type", domain.Type)
}
matcher, err := matcherType.New(domain.Value)
if err != nil {
return nil, errors.New("failed to create domain matcher").Base(err)
}
return matcher, nil
}
type DomainMatcher struct { type DomainMatcher struct {
matchers strmatcher.IndexMatcher matchers strmatcher.IndexMatcher
} }
@@ -70,11 +60,13 @@ func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
for _, d := range domains { for _, d := range domains {
matcherType, f := matcherTypeMap[d.Type] matcherType, f := matcherTypeMap[d.Type]
if !f { if !f {
return nil, errors.New("unsupported domain type", d.Type) errors.LogError(context.Background(), "ignore unsupported domain type ", d.Type, " of rule ", d.Value)
continue
} }
_, err := g.AddPattern(d.Value, matcherType) _, err := g.AddPattern(d.Value, matcherType)
if err != nil { if err != nil {
return nil, err errors.LogErrorInner(context.Background(), err, "ignore domain rule ", d.Type, " ", d.Value)
continue
} }
} }
g.Build() g.Build()
@@ -96,61 +88,53 @@ func (m *DomainMatcher) Apply(ctx routing.Context) bool {
return m.ApplyDomain(domain) return m.ApplyDomain(domain)
} }
type MultiGeoIPMatcher struct { type MatcherAsType byte
matchers []*GeoIPMatcher
asType string // local, source, target const (
MatcherAsType_Local MatcherAsType = iota
MatcherAsType_Source
MatcherAsType_Target
MatcherAsType_VlessRoute // for port
)
type IPMatcher struct {
matcher GeoIPMatcher
asType MatcherAsType
} }
func NewMultiGeoIPMatcher(geoips []*GeoIP, asType string) (*MultiGeoIPMatcher, error) { func NewIPMatcher(geoips []*GeoIP, asType MatcherAsType) (*IPMatcher, error) {
var matchers []*GeoIPMatcher matcher, err := BuildOptimizedGeoIPMatcher(geoips...)
for _, geoip := range geoips { if err != nil {
matcher, err := GlobalGeoIPContainer.Add(geoip) return nil, err
if err != nil {
return nil, err
}
matchers = append(matchers, matcher)
} }
return &IPMatcher{matcher: matcher, asType: asType}, nil
matcher := &MultiGeoIPMatcher{
matchers: matchers,
asType: asType,
}
return matcher, nil
} }
// Apply implements Condition. // Apply implements Condition.
func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool { func (m *IPMatcher) Apply(ctx routing.Context) bool {
var ips []net.IP var ips []net.IP
switch m.asType { switch m.asType {
case "local": case MatcherAsType_Local:
ips = ctx.GetLocalIPs() ips = ctx.GetLocalIPs()
case "source": case MatcherAsType_Source:
ips = ctx.GetSourceIPs() ips = ctx.GetSourceIPs()
case "target": case MatcherAsType_Target:
ips = ctx.GetTargetIPs() ips = ctx.GetTargetIPs()
default: default:
panic("unreachable, asType should be local or source or target") panic("unk asType")
} }
for _, ip := range ips { return m.matcher.AnyMatch(ips)
for _, matcher := range m.matchers {
if matcher.Match(ip) {
return true
}
}
}
return false
} }
type PortMatcher struct { type PortMatcher struct {
port net.MemoryPortList port net.MemoryPortList
asType string // local, source, target asType MatcherAsType
} }
// NewPortMatcher create a new port matcher that can match source or local or destination port // NewPortMatcher create a new port matcher that can match source or local or destination port
func NewPortMatcher(list *net.PortList, asType string) *PortMatcher { func NewPortMatcher(list *net.PortList, asType MatcherAsType) *PortMatcher {
return &PortMatcher{ return &PortMatcher{
port: net.PortListFromProto(list), port: net.PortListFromProto(list),
asType: asType, asType: asType,
@@ -160,18 +144,17 @@ func NewPortMatcher(list *net.PortList, asType string) *PortMatcher {
// Apply implements Condition. // Apply implements Condition.
func (v *PortMatcher) Apply(ctx routing.Context) bool { func (v *PortMatcher) Apply(ctx routing.Context) bool {
switch v.asType { switch v.asType {
case "local": case MatcherAsType_Local:
return v.port.Contains(ctx.GetLocalPort()) return v.port.Contains(ctx.GetLocalPort())
case "source": case MatcherAsType_Source:
return v.port.Contains(ctx.GetSourcePort()) return v.port.Contains(ctx.GetSourcePort())
case "target": case MatcherAsType_Target:
return v.port.Contains(ctx.GetTargetPort()) return v.port.Contains(ctx.GetTargetPort())
case "vlessRoute": case MatcherAsType_VlessRoute:
return v.port.Contains(ctx.GetVlessRoute()) return v.port.Contains(ctx.GetVlessRoute())
default: default:
panic("unreachable, asType should be local or source or target") panic("unk asType")
} }
} }
type NetworkMatcher struct { type NetworkMatcher struct {
@@ -325,3 +308,135 @@ func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
} }
return m.Match(attributes) return m.Match(attributes)
} }
// Geo attribute
type GeoAttributeMatcher interface {
Match(*Domain) bool
}
type GeoBooleanMatcher string
func (m GeoBooleanMatcher) Match(domain *Domain) bool {
for _, attr := range domain.Attribute {
if attr.Key == string(m) {
return true
}
}
return false
}
type GeoAttributeList struct {
Matcher []GeoAttributeMatcher
}
func (al *GeoAttributeList) Match(domain *Domain) bool {
for _, matcher := range al.Matcher {
if !matcher.Match(domain) {
return false
}
}
return true
}
func (al *GeoAttributeList) IsEmpty() bool {
return len(al.Matcher) == 0
}
func ParseAttrs(attrs []string) *GeoAttributeList {
al := new(GeoAttributeList)
for _, attr := range attrs {
lc := strings.ToLower(attr)
al.Matcher = append(al.Matcher, GeoBooleanMatcher(lc))
}
return al
}
type ProcessNameMatcher struct {
ProcessNames []string
AbsPaths []string
Folders []string
MatchXraySelf bool
}
func NewProcessNameMatcher(names []string) *ProcessNameMatcher {
processNames := []string{}
folders := []string{}
absPaths := []string{}
matchXraySelf := false
for _, name := range names {
if name == "self/" {
matchXraySelf = true
continue
}
// replace xray/ with self executable path
if name == "xray/" {
xrayPath, err := os.Executable()
if err != nil {
errors.LogError(context.Background(), "Failed to get xray executable path: ", err)
continue
}
name = xrayPath
}
name := filepath.ToSlash(name)
// /usr/bin/
if strings.HasSuffix(name, "/") {
folders = append(folders, name)
continue
}
// /usr/bin/curl
if strings.Contains(name, "/") {
absPaths = append(absPaths, name)
continue
}
// curl.exe or curl
processNames = append(processNames, strings.TrimSuffix(name, ".exe"))
}
return &ProcessNameMatcher{
ProcessNames: processNames,
AbsPaths: absPaths,
Folders: folders,
MatchXraySelf: matchXraySelf,
}
}
func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
srcPort := ctx.GetSourcePort().String()
srcIP := ctx.GetSourceIPs()[0].String()
var network string
switch ctx.GetNetwork() {
case net.Network_TCP:
network = "tcp"
case net.Network_UDP:
network = "udp"
default:
return false
}
src, err := net.ParseDestination(strings.Join([]string{network, srcIP, srcPort}, ":"))
if err != nil {
return false
}
pid, name, absPath, err := net.FindProcess(src)
if err != nil {
if err != net.ErrNotLocal {
errors.LogError(context.Background(), "Unables to find local process name: ", err)
}
return false
}
if m.MatchXraySelf {
if pid == os.Getpid() {
return true
}
}
if slices.Contains(m.ProcessNames, name) {
return true
}
if slices.Contains(m.AbsPaths, absPath) {
return true
}
for _, f := range m.Folders {
if strings.HasPrefix(absPath, f) {
return true
}
}
return false
}
File diff suppressed because it is too large Load Diff
+44 -86
View File
@@ -1,67 +1,17 @@
package router_test package router_test
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"testing" "testing"
"github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/common/platform/filesystem"
"google.golang.org/protobuf/proto"
) )
func getAssetPath(file string) (string, error) {
path := platform.GetAssetLocation(file)
_, err := os.Stat(path)
if os.IsNotExist(err) {
path := filepath.Join("..", "..", "resources", file)
_, err := os.Stat(path)
if os.IsNotExist(err) {
return "", fmt.Errorf("can't find %s in standard asset locations or {project_root}/resources", file)
}
if err != nil {
return "", fmt.Errorf("can't stat %s: %v", path, err)
}
return path, nil
}
if err != nil {
return "", fmt.Errorf("can't stat %s: %v", path, err)
}
return path, nil
}
func TestGeoIPMatcherContainer(t *testing.T) {
container := &router.GeoIPMatcherContainer{}
m1, err := container.Add(&router.GeoIP{
CountryCode: "CN",
})
common.Must(err)
m2, err := container.Add(&router.GeoIP{
CountryCode: "US",
})
common.Must(err)
m3, err := container.Add(&router.GeoIP{
CountryCode: "CN",
})
common.Must(err)
if m1 != m3 {
t.Error("expect same matcher for same geoip, but not")
}
if m1 == m2 {
t.Error("expect different matcher for different geoip, but actually same")
}
}
func TestGeoIPMatcher(t *testing.T) { func TestGeoIPMatcher(t *testing.T) {
cidrList := []*router.CIDR{ cidrList := []*router.CIDR{
{Ip: []byte{0, 0, 0, 0}, Prefix: 8}, {Ip: []byte{0, 0, 0, 0}, Prefix: 8},
@@ -80,8 +30,10 @@ func TestGeoIPMatcher(t *testing.T) {
{Ip: []byte{91, 108, 4, 0}, Prefix: 16}, {Ip: []byte{91, 108, 4, 0}, Prefix: 16},
} }
matcher := &router.GeoIPMatcher{} matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
common.Must(matcher.Init(cidrList)) Cidr: cidrList,
})
common.Must(err)
testCases := []struct { testCases := []struct {
Input string Input string
@@ -140,8 +92,10 @@ func TestGeoIPMatcherRegression(t *testing.T) {
{Ip: []byte{98, 108, 20, 0}, Prefix: 23}, {Ip: []byte{98, 108, 20, 0}, Prefix: 23},
} }
matcher := &router.GeoIPMatcher{} matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
common.Must(matcher.Init(cidrList)) Cidr: cidrList,
})
common.Must(err)
testCases := []struct { testCases := []struct {
Input string Input string
@@ -171,9 +125,11 @@ func TestGeoIPReverseMatcher(t *testing.T) {
{Ip: []byte{8, 8, 8, 8}, Prefix: 32}, {Ip: []byte{8, 8, 8, 8}, Prefix: 32},
{Ip: []byte{91, 108, 4, 0}, Prefix: 16}, {Ip: []byte{91, 108, 4, 0}, Prefix: 16},
} }
matcher := &router.GeoIPMatcher{} matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
matcher.SetReverseMatch(true) // Reverse match Cidr: cidrList,
common.Must(matcher.Init(cidrList)) })
common.Must(err)
matcher.SetReverse(true) // Reverse match
testCases := []struct { testCases := []struct {
Input string Input string
@@ -203,11 +159,12 @@ func TestGeoIPReverseMatcher(t *testing.T) {
} }
func TestGeoIPMatcher4CN(t *testing.T) { func TestGeoIPMatcher4CN(t *testing.T) {
ips, err := loadGeoIP("CN") geo := "geoip:cn"
geoip, err := loadGeoIP(geo)
common.Must(err) common.Must(err)
matcher := &router.GeoIPMatcher{} matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
common.Must(matcher.Init(ips)) common.Must(err)
if matcher.Match([]byte{8, 8, 8, 8}) { if matcher.Match([]byte{8, 8, 8, 8}) {
t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does") t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does")
@@ -215,47 +172,47 @@ func TestGeoIPMatcher4CN(t *testing.T) {
} }
func TestGeoIPMatcher6US(t *testing.T) { func TestGeoIPMatcher6US(t *testing.T) {
ips, err := loadGeoIP("US") geo := "geoip:us"
geoip, err := loadGeoIP(geo)
common.Must(err) common.Must(err)
matcher := &router.GeoIPMatcher{} matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
common.Must(matcher.Init(ips)) common.Must(err)
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) { if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not") t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not")
} }
} }
func loadGeoIP(country string) ([]*router.CIDR, error) { func loadGeoIP(geo string) (*router.GeoIP, error) {
path, err := getAssetPath("geoip.dat") os.Setenv("XRAY_LOCATION_ASSET", filepath.Join("..", "..", "resources"))
if err != nil {
return nil, err geoip, err := conf.ToCidrList([]string{geo})
}
geoipBytes, err := filesystem.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var geoipList router.GeoIPList if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { geoip, err = router.GetGeoIPList(geoip)
return nil, err if err != nil {
} return nil, err
for _, geoip := range geoipList.Entry {
if geoip.CountryCode == country {
return geoip.Cidr, nil
} }
} }
panic("country not found: " + country) if len(geoip) == 0 {
panic("country not found: " + geo)
}
return geoip[0], nil
} }
func BenchmarkGeoIPMatcher4CN(b *testing.B) { func BenchmarkGeoIPMatcher4CN(b *testing.B) {
ips, err := loadGeoIP("CN") geo := "geoip:cn"
geoip, err := loadGeoIP(geo)
common.Must(err) common.Must(err)
matcher := &router.GeoIPMatcher{} matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
common.Must(matcher.Init(ips)) common.Must(err)
b.ResetTimer() b.ResetTimer()
@@ -265,11 +222,12 @@ func BenchmarkGeoIPMatcher4CN(b *testing.B) {
} }
func BenchmarkGeoIPMatcher6US(b *testing.B) { func BenchmarkGeoIPMatcher6US(b *testing.B) {
ips, err := loadGeoIP("US") geo := "geoip:us"
geoip, err := loadGeoIP(geo)
common.Must(err) common.Must(err)
matcher := &router.GeoIPMatcher{} matcher, err := router.BuildOptimizedGeoIPMatcher(geoip)
common.Must(matcher.Init(ips)) common.Must(err)
b.ResetTimer() b.ResetTimer()
+66 -29
View File
@@ -1,20 +1,22 @@
package router_test package router_test
import ( import (
"os"
"path/filepath"
"runtime"
"strconv" "strconv"
"testing" "testing"
"github.com/xtls/xray-core/app/router"
. "github.com/xtls/xray-core/app/router" . "github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/protocol/http"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
routing_session "github.com/xtls/xray-core/features/routing/session" routing_session "github.com/xtls/xray-core/features/routing/session"
"google.golang.org/protobuf/proto" "github.com/xtls/xray-core/infra/conf"
) )
func withBackground() routing.Context { func withBackground() routing.Context {
@@ -300,32 +302,25 @@ func TestRoutingRule(t *testing.T) {
} }
} }
func loadGeoSite(country string) ([]*Domain, error) { func loadGeoSiteDomains(geo string) ([]*Domain, error) {
path, err := getAssetPath("geosite.dat") os.Setenv("XRAY_LOCATION_ASSET", filepath.Join("..", "..", "resources"))
if err != nil {
return nil, err domains, err := conf.ParseDomainRule(geo)
}
geositeBytes, err := filesystem.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var geositeList GeoSiteList if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { domains, err = router.GetDomainList(domains)
return nil, err if err != nil {
} return nil, err
for _, site := range geositeList.Entry {
if site.CountryCode == country {
return site.Domain, nil
} }
} }
return domains, nil
return nil, errors.New("country not found: " + country)
} }
func TestChinaSites(t *testing.T) { func TestChinaSites(t *testing.T) {
domains, err := loadGeoSite("CN") domains, err := loadGeoSiteDomains("geosite:cn")
common.Must(err) common.Must(err)
acMatcher, err := NewMphMatcherGroup(domains) acMatcher, err := NewMphMatcherGroup(domains)
@@ -366,8 +361,50 @@ func TestChinaSites(t *testing.T) {
} }
} }
func TestChinaSitesWithAttrs(t *testing.T) {
domains, err := loadGeoSiteDomains("geosite:google@cn")
common.Must(err)
acMatcher, err := NewMphMatcherGroup(domains)
common.Must(err)
type TestCase struct {
Domain string
Output bool
}
testCases := []TestCase{
{
Domain: "google.cn",
Output: true,
},
{
Domain: "recaptcha.net",
Output: true,
},
{
Domain: "164.com",
Output: false,
},
{
Domain: "164.com",
Output: false,
},
}
for i := 0; i < 1024; i++ {
testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
}
for _, testCase := range testCases {
r := acMatcher.ApplyDomain(testCase.Domain)
if r != testCase.Output {
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
}
}
}
func BenchmarkMphDomainMatcher(b *testing.B) { func BenchmarkMphDomainMatcher(b *testing.B) {
domains, err := loadGeoSite("CN") domains, err := loadGeoSiteDomains("geosite:cn")
common.Must(err) common.Must(err)
matcher, err := NewMphMatcherGroup(domains) matcher, err := NewMphMatcherGroup(domains)
@@ -412,11 +449,11 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
var geoips []*GeoIP var geoips []*GeoIP
{ {
ips, err := loadGeoIP("CN") ips, err := loadGeoIP("geoip:cn")
common.Must(err) common.Must(err)
geoips = append(geoips, &GeoIP{ geoips = append(geoips, &GeoIP{
CountryCode: "CN", CountryCode: "CN",
Cidr: ips, Cidr: ips.Cidr,
}) })
} }
@@ -425,29 +462,29 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
common.Must(err) common.Must(err)
geoips = append(geoips, &GeoIP{ geoips = append(geoips, &GeoIP{
CountryCode: "JP", CountryCode: "JP",
Cidr: ips, Cidr: ips.Cidr,
}) })
} }
{ {
ips, err := loadGeoIP("CA") ips, err := loadGeoIP("geoip:ca")
common.Must(err) common.Must(err)
geoips = append(geoips, &GeoIP{ geoips = append(geoips, &GeoIP{
CountryCode: "CA", CountryCode: "CA",
Cidr: ips, Cidr: ips.Cidr,
}) })
} }
{ {
ips, err := loadGeoIP("US") ips, err := loadGeoIP("geoip:us")
common.Must(err) common.Must(err)
geoips = append(geoips, &GeoIP{ geoips = append(geoips, &GeoIP{
CountryCode: "US", CountryCode: "US",
Cidr: ips, Cidr: ips.Cidr,
}) })
} }
matcher, err := NewMultiGeoIPMatcher(geoips, "target") matcher, err := NewIPMatcher(geoips, MatcherAsType_Target)
common.Must(err) common.Must(err)
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}) ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
+155 -54
View File
@@ -3,11 +3,14 @@ package router
import ( import (
"context" "context"
"regexp" "regexp"
"runtime"
"strings" "strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"google.golang.org/protobuf/proto"
) )
type Rule struct { type Rule struct {
@@ -32,72 +35,38 @@ func (r *Rule) Apply(ctx routing.Context) bool {
func (rr *RoutingRule) BuildCondition() (Condition, error) { func (rr *RoutingRule) BuildCondition() (Condition, error) {
conds := NewConditionChan() conds := NewConditionChan()
if len(rr.Domain) > 0 {
matcher, err := NewMphMatcherGroup(rr.Domain)
if err != nil {
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
}
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)")
conds.Add(matcher)
}
if len(rr.UserEmail) > 0 {
conds.Add(NewUserMatcher(rr.UserEmail))
}
if rr.VlessRouteList != nil {
conds.Add(NewPortMatcher(rr.VlessRouteList, "vlessRoute"))
}
if len(rr.InboundTag) > 0 { if len(rr.InboundTag) > 0 {
conds.Add(NewInboundTagMatcher(rr.InboundTag)) conds.Add(NewInboundTagMatcher(rr.InboundTag))
} }
if rr.PortList != nil {
conds.Add(NewPortMatcher(rr.PortList, "target"))
}
if rr.SourcePortList != nil {
conds.Add(NewPortMatcher(rr.SourcePortList, "source"))
}
if rr.LocalPortList != nil {
conds.Add(NewPortMatcher(rr.LocalPortList, "local"))
}
if len(rr.Networks) > 0 { if len(rr.Networks) > 0 {
conds.Add(NewNetworkMatcher(rr.Networks)) conds.Add(NewNetworkMatcher(rr.Networks))
} }
if len(rr.Geoip) > 0 {
cond, err := NewMultiGeoIPMatcher(rr.Geoip, "target")
if err != nil {
return nil, err
}
conds.Add(cond)
}
if len(rr.SourceGeoip) > 0 {
cond, err := NewMultiGeoIPMatcher(rr.SourceGeoip, "source")
if err != nil {
return nil, err
}
conds.Add(cond)
}
if len(rr.LocalGeoip) > 0 {
cond, err := NewMultiGeoIPMatcher(rr.LocalGeoip, "local")
if err != nil {
return nil, err
}
conds.Add(cond)
errors.LogWarning(context.Background(), "Due to some limitations, in UDP connections, localIP is always equal to listen interface IP, so \"localIP\" rule condition does not work properly on UDP inbound connections that listen on all interfaces")
}
if len(rr.Protocol) > 0 { if len(rr.Protocol) > 0 {
conds.Add(NewProtocolMatcher(rr.Protocol)) conds.Add(NewProtocolMatcher(rr.Protocol))
} }
if rr.PortList != nil {
conds.Add(NewPortMatcher(rr.PortList, MatcherAsType_Target))
}
if rr.SourcePortList != nil {
conds.Add(NewPortMatcher(rr.SourcePortList, MatcherAsType_Source))
}
if rr.LocalPortList != nil {
conds.Add(NewPortMatcher(rr.LocalPortList, MatcherAsType_Local))
}
if rr.VlessRouteList != nil {
conds.Add(NewPortMatcher(rr.VlessRouteList, MatcherAsType_VlessRoute))
}
if len(rr.UserEmail) > 0 {
conds.Add(NewUserMatcher(rr.UserEmail))
}
if len(rr.Attributes) > 0 { if len(rr.Attributes) > 0 {
configuredKeys := make(map[string]*regexp.Regexp) configuredKeys := make(map[string]*regexp.Regexp)
for key, value := range rr.Attributes { for key, value := range rr.Attributes {
@@ -106,6 +75,61 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
conds.Add(&AttributeMatcher{configuredKeys}) conds.Add(&AttributeMatcher{configuredKeys})
} }
if len(rr.Geoip) > 0 {
geoip := rr.Geoip
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
var err error
geoip, err = GetGeoIPList(rr.Geoip)
if err != nil {
return nil, errors.New("failed to build geoip from mmap").Base(err)
}
}
cond, err := NewIPMatcher(geoip, MatcherAsType_Target)
if err != nil {
return nil, err
}
conds.Add(cond)
}
if len(rr.SourceGeoip) > 0 {
cond, err := NewIPMatcher(rr.SourceGeoip, MatcherAsType_Source)
if err != nil {
return nil, err
}
conds.Add(cond)
}
if len(rr.LocalGeoip) > 0 {
cond, err := NewIPMatcher(rr.LocalGeoip, MatcherAsType_Local)
if err != nil {
return nil, err
}
conds.Add(cond)
errors.LogWarning(context.Background(), "Due to some limitations, in UDP connections, localIP is always equal to listen interface IP, so \"localIP\" rule condition does not work properly on UDP inbound connections that listen on all interfaces")
}
if len(rr.Domain) > 0 {
domains := rr.Domain
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
var err error
domains, err = GetDomainList(rr.Domain)
if err != nil {
return nil, errors.New("failed to build domains from mmap").Base(err)
}
}
matcher, err := NewMphMatcherGroup(domains)
if err != nil {
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
}
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(domains), " domain rule(s)")
conds.Add(matcher)
}
if len(rr.Process) > 0 {
conds.Add(NewProcessNameMatcher(rr.Process))
}
if conds.Len() == 0 { if conds.Len() == 0 {
return nil, errors.New("this rule has no effective fields").AtWarning() return nil, errors.New("this rule has no effective fields").AtWarning()
} }
@@ -159,3 +183,80 @@ func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatch
return nil, errors.New("unrecognized balancer type") return nil, errors.New("unrecognized balancer type")
} }
} }
func GetGeoIPList(ips []*GeoIP) ([]*GeoIP, error) {
geoipList := []*GeoIP{}
for _, ip := range ips {
if ip.CountryCode != "" {
val := strings.Split(ip.CountryCode, "_")
fileName := "geoip.dat"
if len(val) == 2 {
fileName = strings.ToLower(val[0])
}
bs, err := filesystem.ReadAsset(fileName)
if err != nil {
return nil, errors.New("failed to load file: ", fileName).Base(err)
}
bs = filesystem.Find(bs, []byte(ip.CountryCode))
var geoip GeoIP
if err := proto.Unmarshal(bs, &geoip); err != nil {
return nil, errors.New("failed Unmarshal :").Base(err)
}
geoipList = append(geoipList, &geoip)
} else {
geoipList = append(geoipList, ip)
}
}
return geoipList, nil
}
func GetDomainList(domains []*Domain) ([]*Domain, error) {
domainList := []*Domain{}
for _, domain := range domains {
val := strings.Split(domain.Value, "_")
if len(val) >= 2 {
fileName := val[0]
code := val[1]
bs, err := filesystem.ReadAsset(fileName)
if err != nil {
return nil, errors.New("failed to load file: ", fileName).Base(err)
}
bs = filesystem.Find(bs, []byte(code))
var geosite GeoSite
if err := proto.Unmarshal(bs, &geosite); err != nil {
return nil, errors.New("failed Unmarshal :").Base(err)
}
// parse attr
if len(val) == 3 {
siteWithAttr := strings.Split(val[2], ",")
attrs := ParseAttrs(siteWithAttr)
if !attrs.IsEmpty() {
filteredDomains := make([]*Domain, 0, len(domains))
for _, domain := range geosite.Domain {
if attrs.Match(domain) {
filteredDomains = append(filteredDomains, domain)
}
}
geosite.Domain = filteredDomains
}
}
domainList = append(domainList, geosite.Domain...)
} else {
domainList = append(domainList, domain)
}
}
return domainList, nil
}
+71 -66
View File
@@ -84,8 +84,6 @@ type Config_DomainStrategy int32
const ( const (
// Use domain as is. // Use domain as is.
Config_AsIs Config_DomainStrategy = 0 Config_AsIs Config_DomainStrategy = 0
// Always resolve IP for domains.
Config_UseIp Config_DomainStrategy = 1
// Resolve to IP if the domain doesn't match any rules. // Resolve to IP if the domain doesn't match any rules.
Config_IpIfNonMatch Config_DomainStrategy = 2 Config_IpIfNonMatch Config_DomainStrategy = 2
// Resolve to IP if any rule requires IP matching. // Resolve to IP if any rule requires IP matching.
@@ -96,13 +94,11 @@ const (
var ( var (
Config_DomainStrategy_name = map[int32]string{ Config_DomainStrategy_name = map[int32]string{
0: "AsIs", 0: "AsIs",
1: "UseIp",
2: "IpIfNonMatch", 2: "IpIfNonMatch",
3: "IpOnDemand", 3: "IpOnDemand",
} }
Config_DomainStrategy_value = map[string]int32{ Config_DomainStrategy_value = map[string]int32{
"AsIs": 0, "AsIs": 0,
"UseIp": 1,
"IpIfNonMatch": 2, "IpIfNonMatch": 2,
"IpOnDemand": 3, "IpOnDemand": 3,
} }
@@ -494,6 +490,7 @@ type RoutingRule struct {
LocalGeoip []*GeoIP `protobuf:"bytes,17,rep,name=local_geoip,json=localGeoip,proto3" json:"local_geoip,omitempty"` LocalGeoip []*GeoIP `protobuf:"bytes,17,rep,name=local_geoip,json=localGeoip,proto3" json:"local_geoip,omitempty"`
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"`
} }
func (x *RoutingRule) Reset() { func (x *RoutingRule) Reset() {
@@ -645,6 +642,13 @@ func (x *RoutingRule) GetVlessRouteList() *net.PortList {
return nil return nil
} }
func (x *RoutingRule) GetProcess() []string {
if x != nil {
return x.Process
}
return nil
}
type isRoutingRule_TargetTag interface { type isRoutingRule_TargetTag interface {
isRoutingRule_TargetTag() isRoutingRule_TargetTag()
} }
@@ -1085,7 +1089,7 @@ var file_app_router_config_proto_rawDesc = []byte{
0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74,
0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69,
0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xe8, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x82, 0x07, 0x0a, 0x0b, 0x52, 0x6f,
0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a,
0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c,
@@ -1135,67 +1139,68 @@ var file_app_router_config_proto_rawDesc = []byte{
0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50,
0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x75, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65,
0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x73, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x5f, 0x74, 0x61, 0x67, 0x22, 0xdc, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0xdc,
0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65,
0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74,
0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73,
0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f,
0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12,
0x79, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x73, 0x65, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x4d, 0x0a, 0x11, 0x73,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65,
0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x61, 0x67, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61,
0x54, 0x61, 0x67, 0x22, 0x54, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x52, 0x0b, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x54, 0x61, 0x67, 0x22, 0x54, 0x0a,
0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x14, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12,
0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68,
0x28, 0x02, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc0, 0x01, 0x0a, 0x17, 0x53, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a,
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x76, 0x61,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x6c, 0x75, 0x65, 0x22, 0xc0, 0x01, 0x0a, 0x17, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57, 0x35, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52,
0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69,
0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c,
0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x12, 0x1c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64,
0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x9b, 0x02, 0x0a, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65,
0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c,
0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x90, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61,
0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04,
0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e,
0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78,
0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42,
0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61,
0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x3c, 0x0a, 0x0e, 0x44,
0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a,
0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e,
0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f,
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d,
0x72, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70,
0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e,
0x74, 0x6f, 0x33, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
} }
var ( var (
+3 -2
View File
@@ -113,6 +113,7 @@ message RoutingRule {
xray.common.net.PortList local_port_list = 18; xray.common.net.PortList local_port_list = 18;
xray.common.net.PortList vless_route_list = 20; xray.common.net.PortList vless_route_list = 20;
repeated string process = 21;
} }
message BalancingRule { message BalancingRule {
@@ -147,8 +148,8 @@ message Config {
// Use domain as is. // Use domain as is.
AsIs = 0; AsIs = 0;
// Always resolve IP for domains. // [Deprecated] Always resolve IP for domains.
UseIp = 1; // UseIp = 1;
// Resolve to IP if the domain doesn't match any rules. // Resolve to IP if the domain doesn't match any rules.
IpIfNonMatch = 2; IpIfNonMatch = 2;
+6
View File
@@ -80,6 +80,12 @@ func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStat
}, nil }, nil
} }
func (s *statsServer) GetAllOnlineUsers(ctx context.Context, request *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error) {
return &GetAllOnlineUsersResponse{
Users: s.stats.GetAllOnlineUsers(),
}, nil
}
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) { func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
matcher, err := strmatcher.Substr.New(request.Pattern) matcher, err := strmatcher.Substr.New(request.Pattern)
if err != nil { if err != nil {
+245 -217
View File
@@ -1,8 +1,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.35.1 // protoc-gen-go v1.36.8
// protoc v5.28.2 // protoc v6.32.0
// source: app/stats/command/command.proto // source: command.proto
package command package command
@@ -11,6 +11,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@@ -21,19 +22,18 @@ const (
) )
type GetStatsRequest struct { type GetStatsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Name of the stat counter. // Name of the stat counter.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Whether or not to reset the counter to fetching its value. // Whether or not to reset the counter to fetching its value.
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *GetStatsRequest) Reset() { func (x *GetStatsRequest) Reset() {
*x = GetStatsRequest{} *x = GetStatsRequest{}
mi := &file_app_stats_command_command_proto_msgTypes[0] mi := &file_command_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -45,7 +45,7 @@ func (x *GetStatsRequest) String() string {
func (*GetStatsRequest) ProtoMessage() {} func (*GetStatsRequest) ProtoMessage() {}
func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { func (x *GetStatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[0] mi := &file_command_proto_msgTypes[0]
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 {
@@ -58,7 +58,7 @@ func (x *GetStatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead.
func (*GetStatsRequest) Descriptor() ([]byte, []int) { func (*GetStatsRequest) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{0} return file_command_proto_rawDescGZIP(), []int{0}
} }
func (x *GetStatsRequest) GetName() string { func (x *GetStatsRequest) GetName() string {
@@ -76,17 +76,16 @@ func (x *GetStatsRequest) GetReset_() bool {
} }
type Stat struct { type Stat struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
} }
func (x *Stat) Reset() { func (x *Stat) Reset() {
*x = Stat{} *x = Stat{}
mi := &file_app_stats_command_command_proto_msgTypes[1] mi := &file_command_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -98,7 +97,7 @@ func (x *Stat) String() string {
func (*Stat) ProtoMessage() {} func (*Stat) ProtoMessage() {}
func (x *Stat) ProtoReflect() protoreflect.Message { func (x *Stat) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[1] mi := &file_command_proto_msgTypes[1]
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 {
@@ -111,7 +110,7 @@ func (x *Stat) ProtoReflect() protoreflect.Message {
// Deprecated: Use Stat.ProtoReflect.Descriptor instead. // Deprecated: Use Stat.ProtoReflect.Descriptor instead.
func (*Stat) Descriptor() ([]byte, []int) { func (*Stat) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{1} return file_command_proto_rawDescGZIP(), []int{1}
} }
func (x *Stat) GetName() string { func (x *Stat) GetName() string {
@@ -129,16 +128,15 @@ func (x *Stat) GetValue() int64 {
} }
type GetStatsResponse struct { type GetStatsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
} }
func (x *GetStatsResponse) Reset() { func (x *GetStatsResponse) Reset() {
*x = GetStatsResponse{} *x = GetStatsResponse{}
mi := &file_app_stats_command_command_proto_msgTypes[2] mi := &file_command_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -150,7 +148,7 @@ func (x *GetStatsResponse) String() string {
func (*GetStatsResponse) ProtoMessage() {} func (*GetStatsResponse) ProtoMessage() {}
func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { func (x *GetStatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[2] mi := &file_command_proto_msgTypes[2]
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 {
@@ -163,7 +161,7 @@ func (x *GetStatsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead.
func (*GetStatsResponse) Descriptor() ([]byte, []int) { func (*GetStatsResponse) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{2} return file_command_proto_rawDescGZIP(), []int{2}
} }
func (x *GetStatsResponse) GetStat() *Stat { func (x *GetStatsResponse) GetStat() *Stat {
@@ -174,17 +172,16 @@ func (x *GetStatsResponse) GetStat() *Stat {
} }
type QueryStatsRequest struct { type QueryStatsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
} }
func (x *QueryStatsRequest) Reset() { func (x *QueryStatsRequest) Reset() {
*x = QueryStatsRequest{} *x = QueryStatsRequest{}
mi := &file_app_stats_command_command_proto_msgTypes[3] mi := &file_command_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -196,7 +193,7 @@ func (x *QueryStatsRequest) String() string {
func (*QueryStatsRequest) ProtoMessage() {} func (*QueryStatsRequest) ProtoMessage() {}
func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message { func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[3] mi := &file_command_proto_msgTypes[3]
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 {
@@ -209,7 +206,7 @@ func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use QueryStatsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use QueryStatsRequest.ProtoReflect.Descriptor instead.
func (*QueryStatsRequest) Descriptor() ([]byte, []int) { func (*QueryStatsRequest) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{3} return file_command_proto_rawDescGZIP(), []int{3}
} }
func (x *QueryStatsRequest) GetPattern() string { func (x *QueryStatsRequest) GetPattern() string {
@@ -227,16 +224,15 @@ func (x *QueryStatsRequest) GetReset_() bool {
} }
type QueryStatsResponse struct { type QueryStatsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"`
} }
func (x *QueryStatsResponse) Reset() { func (x *QueryStatsResponse) Reset() {
*x = QueryStatsResponse{} *x = QueryStatsResponse{}
mi := &file_app_stats_command_command_proto_msgTypes[4] mi := &file_command_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -248,7 +244,7 @@ func (x *QueryStatsResponse) String() string {
func (*QueryStatsResponse) ProtoMessage() {} func (*QueryStatsResponse) ProtoMessage() {}
func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message { func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[4] mi := &file_command_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 {
@@ -261,7 +257,7 @@ func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use QueryStatsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use QueryStatsResponse.ProtoReflect.Descriptor instead.
func (*QueryStatsResponse) Descriptor() ([]byte, []int) { func (*QueryStatsResponse) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{4} return file_command_proto_rawDescGZIP(), []int{4}
} }
func (x *QueryStatsResponse) GetStat() []*Stat { func (x *QueryStatsResponse) GetStat() []*Stat {
@@ -272,14 +268,14 @@ func (x *QueryStatsResponse) GetStat() []*Stat {
} }
type SysStatsRequest struct { type SysStatsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *SysStatsRequest) Reset() { func (x *SysStatsRequest) Reset() {
*x = SysStatsRequest{} *x = SysStatsRequest{}
mi := &file_app_stats_command_command_proto_msgTypes[5] mi := &file_command_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -291,7 +287,7 @@ func (x *SysStatsRequest) String() string {
func (*SysStatsRequest) ProtoMessage() {} func (*SysStatsRequest) ProtoMessage() {}
func (x *SysStatsRequest) ProtoReflect() protoreflect.Message { func (x *SysStatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[5] mi := &file_command_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 {
@@ -304,29 +300,28 @@ func (x *SysStatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead.
func (*SysStatsRequest) Descriptor() ([]byte, []int) { func (*SysStatsRequest) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{5} return file_command_proto_rawDescGZIP(), []int{5}
} }
type SysStatsResponse struct { type SysStatsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"`
NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"`
Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"`
TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"`
Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"`
Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"`
Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"`
LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"`
PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"`
Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"`
NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"`
Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"`
TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"`
Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"`
Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"`
Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"`
LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"`
PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"`
Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"`
} }
func (x *SysStatsResponse) Reset() { func (x *SysStatsResponse) Reset() {
*x = SysStatsResponse{} *x = SysStatsResponse{}
mi := &file_app_stats_command_command_proto_msgTypes[6] mi := &file_command_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -338,7 +333,7 @@ func (x *SysStatsResponse) String() string {
func (*SysStatsResponse) ProtoMessage() {} func (*SysStatsResponse) ProtoMessage() {}
func (x *SysStatsResponse) ProtoReflect() protoreflect.Message { func (x *SysStatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[6] mi := &file_command_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 {
@@ -351,7 +346,7 @@ func (x *SysStatsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead.
func (*SysStatsResponse) Descriptor() ([]byte, []int) { func (*SysStatsResponse) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{6} return file_command_proto_rawDescGZIP(), []int{6}
} }
func (x *SysStatsResponse) GetNumGoroutine() uint32 { func (x *SysStatsResponse) GetNumGoroutine() uint32 {
@@ -425,17 +420,16 @@ func (x *SysStatsResponse) GetUptime() uint32 {
} }
type GetStatsOnlineIpListResponse struct { type GetStatsOnlineIpListResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Ips map[string]int64 `protobuf:"bytes,2,rep,name=ips,proto3" json:"ips,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Ips map[string]int64 `protobuf:"bytes,2,rep,name=ips,proto3" json:"ips,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
} }
func (x *GetStatsOnlineIpListResponse) Reset() { func (x *GetStatsOnlineIpListResponse) Reset() {
*x = GetStatsOnlineIpListResponse{} *x = GetStatsOnlineIpListResponse{}
mi := &file_app_stats_command_command_proto_msgTypes[7] mi := &file_command_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -447,7 +441,7 @@ func (x *GetStatsOnlineIpListResponse) String() string {
func (*GetStatsOnlineIpListResponse) ProtoMessage() {} func (*GetStatsOnlineIpListResponse) ProtoMessage() {}
func (x *GetStatsOnlineIpListResponse) ProtoReflect() protoreflect.Message { func (x *GetStatsOnlineIpListResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[7] mi := &file_command_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 {
@@ -460,7 +454,7 @@ func (x *GetStatsOnlineIpListResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetStatsOnlineIpListResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetStatsOnlineIpListResponse.ProtoReflect.Descriptor instead.
func (*GetStatsOnlineIpListResponse) Descriptor() ([]byte, []int) { func (*GetStatsOnlineIpListResponse) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{7} return file_command_proto_rawDescGZIP(), []int{7}
} }
func (x *GetStatsOnlineIpListResponse) GetName() string { func (x *GetStatsOnlineIpListResponse) GetName() string {
@@ -477,15 +471,95 @@ func (x *GetStatsOnlineIpListResponse) GetIps() map[string]int64 {
return nil return nil
} }
type Config struct { type GetAllOnlineUsersRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetAllOnlineUsersRequest) Reset() {
*x = GetAllOnlineUsersRequest{}
mi := &file_command_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetAllOnlineUsersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAllOnlineUsersRequest) ProtoMessage() {}
func (x *GetAllOnlineUsersRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[8]
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 GetAllOnlineUsersRequest.ProtoReflect.Descriptor instead.
func (*GetAllOnlineUsersRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{8}
}
type GetAllOnlineUsersResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Users []string `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetAllOnlineUsersResponse) Reset() {
*x = GetAllOnlineUsersResponse{}
mi := &file_command_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetAllOnlineUsersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAllOnlineUsersResponse) ProtoMessage() {}
func (x *GetAllOnlineUsersResponse) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[9]
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 GetAllOnlineUsersResponse.ProtoReflect.Descriptor instead.
func (*GetAllOnlineUsersResponse) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{9}
}
func (x *GetAllOnlineUsersResponse) GetUsers() []string {
if x != nil {
return x.Users
}
return nil
}
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
mi := &file_app_stats_command_command_proto_msgTypes[8] mi := &file_command_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -497,7 +571,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_stats_command_command_proto_msgTypes[8] mi := &file_command_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 {
@@ -510,125 +584,76 @@ 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_stats_command_command_proto_rawDescGZIP(), []int{8} return file_command_proto_rawDescGZIP(), []int{10}
} }
var File_app_stats_command_command_proto protoreflect.FileDescriptor var File_command_proto protoreflect.FileDescriptor
var file_app_stats_command_command_proto_rawDesc = []byte{ const file_command_proto_rawDesc = "" +
0x0a, 0x1f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, "\n" +
0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, "\rcommand.proto\x12\x16xray.app.stats.command\";\n" +
0x6f, 0x12, 0x16, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, "\x0fGetStatsRequest\x12\x12\n" +
0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x3b, 0x0a, 0x0f, 0x47, 0x65, 0x74, "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, "\x05reset\x18\x02 \x01(\bR\x05reset\"0\n" +
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, "\x04Stat\x12\x12\n" +
0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22, 0x30, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, "\x05value\x18\x02 \x01(\x03R\x05value\"D\n" +
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, "\x10GetStatsResponse\x120\n" +
0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, "\x04stat\x18\x01 \x01(\v2\x1c.xray.app.stats.command.StatR\x04stat\"C\n" +
0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x44, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, "\x11QueryStatsRequest\x12\x18\n" +
0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x04, "\apattern\x18\x01 \x01(\tR\apattern\x12\x14\n" +
0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, "\x05reset\x18\x02 \x01(\bR\x05reset\"F\n" +
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, "\x12QueryStatsResponse\x120\n" +
0x61, 0x6e, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x43, "\x04stat\x18\x01 \x03(\v2\x1c.xray.app.stats.command.StatR\x04stat\"\x11\n" +
0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, "\x0fSysStatsRequest\"\xa2\x02\n" +
0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, "\x10SysStatsResponse\x12\"\n" +
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, "\fNumGoroutine\x18\x01 \x01(\rR\fNumGoroutine\x12\x14\n" +
0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, "\x05NumGC\x18\x02 \x01(\rR\x05NumGC\x12\x14\n" +
0x73, 0x65, 0x74, 0x22, 0x46, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, "\x05Alloc\x18\x03 \x01(\x04R\x05Alloc\x12\x1e\n" +
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x73, 0x74, 0x61, "\n" +
0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, "TotalAlloc\x18\x04 \x01(\x04R\n" +
0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, "TotalAlloc\x12\x10\n" +
0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, 0x0a, 0x0f, 0x53, "\x03Sys\x18\x05 \x01(\x04R\x03Sys\x12\x18\n" +
0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa2, "\aMallocs\x18\x06 \x01(\x04R\aMallocs\x12\x14\n" +
0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, "\x05Frees\x18\a \x01(\x04R\x05Frees\x12 \n" +
0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, "\vLiveObjects\x18\b \x01(\x04R\vLiveObjects\x12\"\n" +
0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, "\fPauseTotalNs\x18\t \x01(\x04R\fPauseTotalNs\x12\x16\n" +
0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, "\x06Uptime\x18\n" +
0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, 0x12, 0x14, 0x0a, " \x01(\rR\x06Uptime\"\xbb\x01\n" +
0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x41, 0x6c, "\x1cGetStatsOnlineIpListResponse\x12\x12\n" +
0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, "\x04name\x18\x01 \x01(\tR\x04name\x12O\n" +
0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, "\x03ips\x18\x02 \x03(\v2=.xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntryR\x03ips\x1a6\n" +
0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, "\bIpsEntry\x12\x10\n" +
0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x73, 0x12, "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"\x1a\n" +
0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, "\x18GetAllOnlineUsersRequest\"1\n" +
0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, "\x19GetAllOnlineUsersResponse\x12\x14\n" +
0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, 0x69, 0x76, 0x65, "\x05users\x18\x01 \x03(\tR\x05users\"\b\n" +
0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, "\x06Config2\x96\x05\n" +
0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x50, "\fStatsService\x12_\n" +
0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, "\bGetStats\x12'.xray.app.stats.command.GetStatsRequest\x1a(.xray.app.stats.command.GetStatsResponse\"\x00\x12e\n" +
0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x70, 0x74, "\x0eGetStatsOnline\x12'.xray.app.stats.command.GetStatsRequest\x1a(.xray.app.stats.command.GetStatsResponse\"\x00\x12e\n" +
0x69, 0x6d, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, "\n" +
0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, "QueryStats\x12).xray.app.stats.command.QueryStatsRequest\x1a*.xray.app.stats.command.QueryStatsResponse\"\x00\x12b\n" +
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, "\vGetSysStats\x12'.xray.app.stats.command.SysStatsRequest\x1a(.xray.app.stats.command.SysStatsResponse\"\x00\x12w\n" +
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4f, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18, "\x14GetStatsOnlineIpList\x12'.xray.app.stats.command.GetStatsRequest\x1a4.xray.app.stats.command.GetStatsOnlineIpListResponse\"\x00\x12z\n" +
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, "\x11GetAllOnlineUsers\x120.xray.app.stats.command.GetAllOnlineUsersRequest\x1a1.xray.app.stats.command.GetAllOnlineUsersResponse\"\x00Bd\n" +
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, "\x1acom.xray.app.stats.commandP\x01Z+github.com/xtls/xray-core/app/stats/command\xaa\x02\x16Xray.App.Stats.Commandb\x06proto3"
0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x70, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x69, 0x70, 0x73, 0x1a, 0x36, 0x0a, 0x08, 0x49, 0x70, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0x9a, 0x04, 0x0a, 0x0c,
0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x08,
0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61,
0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74,
0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a,
0x0e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12,
0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61,
0x74, 0x73, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74,
0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72,
0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0b, 0x47,
0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73,
0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73,
0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x77, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e,
0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x34, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74,
0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61,
0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x64, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63,
0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x16, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var ( var (
file_app_stats_command_command_proto_rawDescOnce sync.Once file_command_proto_rawDescOnce sync.Once
file_app_stats_command_command_proto_rawDescData = file_app_stats_command_command_proto_rawDesc file_command_proto_rawDescData []byte
) )
func file_app_stats_command_command_proto_rawDescGZIP() []byte { func file_command_proto_rawDescGZIP() []byte {
file_app_stats_command_command_proto_rawDescOnce.Do(func() { file_command_proto_rawDescOnce.Do(func() {
file_app_stats_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_stats_command_command_proto_rawDescData) file_command_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_command_proto_rawDesc), len(file_command_proto_rawDesc)))
}) })
return file_app_stats_command_command_proto_rawDescData return file_command_proto_rawDescData
} }
var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_command_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_app_stats_command_command_proto_goTypes = []any{ var file_command_proto_goTypes = []any{
(*GetStatsRequest)(nil), // 0: xray.app.stats.command.GetStatsRequest (*GetStatsRequest)(nil), // 0: xray.app.stats.command.GetStatsRequest
(*Stat)(nil), // 1: xray.app.stats.command.Stat (*Stat)(nil), // 1: xray.app.stats.command.Stat
(*GetStatsResponse)(nil), // 2: xray.app.stats.command.GetStatsResponse (*GetStatsResponse)(nil), // 2: xray.app.stats.command.GetStatsResponse
@@ -637,51 +662,54 @@ var file_app_stats_command_command_proto_goTypes = []any{
(*SysStatsRequest)(nil), // 5: xray.app.stats.command.SysStatsRequest (*SysStatsRequest)(nil), // 5: xray.app.stats.command.SysStatsRequest
(*SysStatsResponse)(nil), // 6: xray.app.stats.command.SysStatsResponse (*SysStatsResponse)(nil), // 6: xray.app.stats.command.SysStatsResponse
(*GetStatsOnlineIpListResponse)(nil), // 7: xray.app.stats.command.GetStatsOnlineIpListResponse (*GetStatsOnlineIpListResponse)(nil), // 7: xray.app.stats.command.GetStatsOnlineIpListResponse
(*Config)(nil), // 8: xray.app.stats.command.Config (*GetAllOnlineUsersRequest)(nil), // 8: xray.app.stats.command.GetAllOnlineUsersRequest
nil, // 9: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry (*GetAllOnlineUsersResponse)(nil), // 9: xray.app.stats.command.GetAllOnlineUsersResponse
(*Config)(nil), // 10: xray.app.stats.command.Config
nil, // 11: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
} }
var file_app_stats_command_command_proto_depIdxs = []int32{ var file_command_proto_depIdxs = []int32{
1, // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat 1, // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat
1, // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat 1, // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat
9, // 2: xray.app.stats.command.GetStatsOnlineIpListResponse.ips:type_name -> xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry 11, // 2: xray.app.stats.command.GetStatsOnlineIpListResponse.ips:type_name -> xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
0, // 3: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest 0, // 3: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest
0, // 4: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest 0, // 4: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest
3, // 5: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest 3, // 5: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest
5, // 6: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest 5, // 6: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest
0, // 7: xray.app.stats.command.StatsService.GetStatsOnlineIpList:input_type -> xray.app.stats.command.GetStatsRequest 0, // 7: xray.app.stats.command.StatsService.GetStatsOnlineIpList:input_type -> xray.app.stats.command.GetStatsRequest
2, // 8: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse 8, // 8: xray.app.stats.command.StatsService.GetAllOnlineUsers:input_type -> xray.app.stats.command.GetAllOnlineUsersRequest
2, // 9: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse 2, // 9: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse
4, // 10: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse 2, // 10: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse
6, // 11: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse 4, // 11: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse
7, // 12: xray.app.stats.command.StatsService.GetStatsOnlineIpList:output_type -> xray.app.stats.command.GetStatsOnlineIpListResponse 6, // 12: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse
8, // [8:13] is the sub-list for method output_type 7, // 13: xray.app.stats.command.StatsService.GetStatsOnlineIpList:output_type -> xray.app.stats.command.GetStatsOnlineIpListResponse
3, // [3:8] is the sub-list for method input_type 9, // 14: xray.app.stats.command.StatsService.GetAllOnlineUsers:output_type -> xray.app.stats.command.GetAllOnlineUsersResponse
3, // [3:3] is the sub-list for extension type_name 9, // [9:15] is the sub-list for method output_type
3, // [3:3] is the sub-list for extension extendee 3, // [3:9] is the sub-list for method input_type
0, // [0:3] is the sub-list for field type_name 3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
} }
func init() { file_app_stats_command_command_proto_init() } func init() { file_command_proto_init() }
func file_app_stats_command_command_proto_init() { func file_command_proto_init() {
if File_app_stats_command_command_proto != nil { if File_command_proto != nil {
return return
} }
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_app_stats_command_command_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_command_proto_rawDesc), len(file_command_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 10, NumMessages: 12,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
GoTypes: file_app_stats_command_command_proto_goTypes, GoTypes: file_command_proto_goTypes,
DependencyIndexes: file_app_stats_command_command_proto_depIdxs, DependencyIndexes: file_command_proto_depIdxs,
MessageInfos: file_app_stats_command_command_proto_msgTypes, MessageInfos: file_command_proto_msgTypes,
}.Build() }.Build()
File_app_stats_command_command_proto = out.File File_command_proto = out.File
file_app_stats_command_command_proto_rawDesc = nil file_command_proto_goTypes = nil
file_app_stats_command_command_proto_goTypes = nil file_command_proto_depIdxs = nil
file_app_stats_command_command_proto_depIdxs = nil
} }
+7
View File
@@ -51,12 +51,19 @@ message GetStatsOnlineIpListResponse {
map<string, int64> ips = 2; map<string, int64> ips = 2;
} }
message GetAllOnlineUsersRequest {}
message GetAllOnlineUsersResponse {
repeated string users = 1;
}
service StatsService { service StatsService {
rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {} rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}
rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {} rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {}
rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {} rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}
rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {} rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}
rpc GetStatsOnlineIpList(GetStatsRequest) returns (GetStatsOnlineIpListResponse) {} rpc GetStatsOnlineIpList(GetStatsRequest) returns (GetStatsOnlineIpListResponse) {}
rpc GetAllOnlineUsers(GetAllOnlineUsersRequest) returns (GetAllOnlineUsersResponse) {}
} }
message Config {} message Config {}
+41 -3
View File
@@ -1,8 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.28.2 // - protoc v6.32.0
// source: app/stats/command/command.proto // source: command.proto
package command package command
@@ -24,6 +24,7 @@ const (
StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats" StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats"
StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats" StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats"
StatsService_GetStatsOnlineIpList_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnlineIpList" StatsService_GetStatsOnlineIpList_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnlineIpList"
StatsService_GetAllOnlineUsers_FullMethodName = "/xray.app.stats.command.StatsService/GetAllOnlineUsers"
) )
// StatsServiceClient is the client API for StatsService service. // StatsServiceClient is the client API for StatsService service.
@@ -35,6 +36,7 @@ type StatsServiceClient interface {
QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error)
GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error)
GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error) GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error)
GetAllOnlineUsers(ctx context.Context, in *GetAllOnlineUsersRequest, opts ...grpc.CallOption) (*GetAllOnlineUsersResponse, error)
} }
type statsServiceClient struct { type statsServiceClient struct {
@@ -95,6 +97,16 @@ func (c *statsServiceClient) GetStatsOnlineIpList(ctx context.Context, in *GetSt
return out, nil return out, nil
} }
func (c *statsServiceClient) GetAllOnlineUsers(ctx context.Context, in *GetAllOnlineUsersRequest, opts ...grpc.CallOption) (*GetAllOnlineUsersResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetAllOnlineUsersResponse)
err := c.cc.Invoke(ctx, StatsService_GetAllOnlineUsers_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// StatsServiceServer is the server API for StatsService service. // StatsServiceServer is the server API for StatsService service.
// All implementations must embed UnimplementedStatsServiceServer // All implementations must embed UnimplementedStatsServiceServer
// for forward compatibility. // for forward compatibility.
@@ -104,6 +116,7 @@ type StatsServiceServer interface {
QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error)
GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error)
GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error)
GetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error)
mustEmbedUnimplementedStatsServiceServer() mustEmbedUnimplementedStatsServiceServer()
} }
@@ -129,6 +142,9 @@ func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsReq
func (UnimplementedStatsServiceServer) GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) { func (UnimplementedStatsServiceServer) GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStatsOnlineIpList not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetStatsOnlineIpList not implemented")
} }
func (UnimplementedStatsServiceServer) GetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAllOnlineUsers not implemented")
}
func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {}
func (UnimplementedStatsServiceServer) testEmbeddedByValue() {} func (UnimplementedStatsServiceServer) testEmbeddedByValue() {}
@@ -240,6 +256,24 @@ func _StatsService_GetStatsOnlineIpList_Handler(srv interface{}, ctx context.Con
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _StatsService_GetAllOnlineUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetAllOnlineUsersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StatsServiceServer).GetAllOnlineUsers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StatsService_GetAllOnlineUsers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StatsServiceServer).GetAllOnlineUsers(ctx, req.(*GetAllOnlineUsersRequest))
}
return interceptor(ctx, in, info, handler)
}
// StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service. // StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@@ -267,7 +301,11 @@ var StatsService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetStatsOnlineIpList", MethodName: "GetStatsOnlineIpList",
Handler: _StatsService_GetStatsOnlineIpList_Handler, Handler: _StatsService_GetStatsOnlineIpList_Handler,
}, },
{
MethodName: "GetAllOnlineUsers",
Handler: _StatsService_GetAllOnlineUsers_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "app/stats/command/command.proto", Metadata: "command.proto",
} }
+15
View File
@@ -161,6 +161,21 @@ func (m *Manager) GetChannel(name string) stats.Channel {
return nil return nil
} }
// GetAllOnlineUsers implements stats.Manager.
func (m *Manager) GetAllOnlineUsers() []string {
m.access.Lock()
defer m.access.Unlock()
usersOnline := make([]string, 0, len(m.onlineMap))
for user, onlineMap := range m.onlineMap {
if len(onlineMap.IpTimeMap()) > 0 {
usersOnline = append(usersOnline, user)
}
}
return usersOnline
}
// Start implements common.Runnable. // Start implements common.Runnable.
func (m *Manager) Start() error { func (m *Manager) Start() error {
m.access.Lock() m.access.Lock()
+3 -2
View File
@@ -43,8 +43,9 @@ func (l *DNSLog) String() string {
type dnsStatus string type dnsStatus string
var ( var (
DNSQueried = dnsStatus("got answer:") DNSQueried = dnsStatus("got answer:")
DNSCacheHit = dnsStatus("cache HIT:") DNSCacheHit = dnsStatus("cache HIT:")
DNSCacheOptimiste = dnsStatus("cache OPTIMISTE:")
) )
func joinNetIP(ips []net.IP) string { func joinNetIP(ips []net.IP) string {
-2
View File
@@ -5,7 +5,6 @@ import (
"io" "io"
"time" "time"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -64,7 +63,6 @@ func (s *Server) DispatchLink(ctx context.Context, dest net.Destination, link *t
if dest.Address != muxCoolAddress { if dest.Address != muxCoolAddress {
return s.dispatcher.DispatchLink(ctx, dest, link) return s.dispatcher.DispatchLink(ctx, dest, link)
} }
link = s.dispatcher.(*dispatcher.DefaultDispatcher).WrapLink(ctx, link)
worker, err := NewServerWorker(ctx, s.dispatcher, link) worker, err := NewServerWorker(ctx, s.dispatcher, link)
if err != nil { if err != nil {
return err return err
+176
View File
@@ -0,0 +1,176 @@
//go:build linux
package net
import (
"bufio"
"encoding/hex"
"fmt"
"os"
"strconv"
"strings"
"github.com/xtls/xray-core/common/errors"
)
func FindProcess(dest Destination) (PID int, Name string, AbsolutePath string, err error) {
isLocal, err := IsLocal(dest.Address.IP())
if err != nil {
return 0, "", "", errors.New("failed to determine if address is local: ", err)
}
if !isLocal {
return 0, "", "", ErrNotLocal
}
if dest.Network != Network_TCP && dest.Network != Network_UDP {
panic("Unsupported network type for process lookup.")
}
// the core should never has a domain as source(?
if dest.Address.Family() == AddressFamilyDomain {
panic("Domain addresses are not supported for process lookup.")
}
var procFile string
switch dest.Network {
case Network_TCP:
if dest.Address.Family() == AddressFamilyIPv4 {
procFile = "/proc/net/tcp"
}
if dest.Address.Family() == AddressFamilyIPv6 {
procFile = "/proc/net/tcp6"
}
case Network_UDP:
if dest.Address.Family() == AddressFamilyIPv4 {
procFile = "/proc/net/udp"
}
if dest.Address.Family() == AddressFamilyIPv6 {
procFile = "/proc/net/udp6"
}
default:
panic("Unsupported network type for process lookup.")
}
targetHexAddr, err := formatLittleEndianString(dest.Address, dest.Port)
if err != nil {
return 0, "", "", errors.New("failed to format address: ", err)
}
inode, err := findInodeInFile(procFile, targetHexAddr)
if err != nil {
return 0, "", "", errors.New("could not search in ", procFile).Base(err)
}
if inode == "" {
return 0, "", "", errors.New("connection for ", dest.Address, ":", dest.Port, " not found in ", procFile)
}
pidStr, err := findPidByInode(inode)
if err != nil {
return 0, "", "", errors.New("could not find PID for inode ", inode, ": ", err)
}
if pidStr == "" {
return 0, "", "", errors.New("no process found for inode ", inode)
}
absPath, err := getAbsPath(pidStr)
if err != nil {
return 0, "", "", errors.New("could not get process name for PID ", pidStr, ":", err)
}
nameSplit := strings.Split(absPath, "/")
procName := nameSplit[len(nameSplit)-1]
pid, err := strconv.Atoi(pidStr)
if err != nil {
return 0, "", "", errors.New("failed to parse PID: ", err)
}
return pid, procName, absPath, nil
}
func formatLittleEndianString(addr Address, port Port) (string, error) {
ip := addr.IP()
var ipBytes []byte
if addr.Family() == AddressFamilyIPv4 {
ipBytes = ip.To4()
} else {
ipBytes = ip.To16()
}
if ipBytes == nil {
return "", errors.New("invalid IP format for ", addr.Family(), ": ", ip)
}
for i, j := 0, len(ipBytes)-1; i < j; i, j = i+1, j-1 {
ipBytes[i], ipBytes[j] = ipBytes[j], ipBytes[i]
}
portHex := fmt.Sprintf("%04X", uint16(port))
ipHex := strings.ToUpper(hex.EncodeToString(ipBytes))
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
}
func findInodeInFile(filePath, targetHexAddr string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 10 {
continue
}
localAddress := fields[1]
if localAddress == targetHexAddr {
inode := fields[9]
return inode, nil
}
}
return "", scanner.Err()
}
func findPidByInode(inode string) (string, error) {
procDir, err := os.ReadDir("/proc")
if err != nil {
return "", err
}
targetLink := "socket:[" + inode + "]"
for _, entry := range procDir {
if !entry.IsDir() {
continue
}
pid := entry.Name()
if _, err := strconv.Atoi(pid); err != nil {
continue
}
fdPath := fmt.Sprintf("/proc/%s/fd", pid)
fdDir, err := os.ReadDir(fdPath)
if err != nil {
continue
}
for _, fdEntry := range fdDir {
linkPath := fmt.Sprintf("%s/%s", fdPath, fdEntry.Name())
linkTarget, err := os.Readlink(linkPath)
if err != nil {
continue
}
if linkTarget == targetLink {
return pid, nil
}
}
}
return "", nil
}
func getAbsPath(pid string) (string, error) {
path := fmt.Sprintf("/proc/%s/exe", pid)
return os.Readlink(path)
}
+11
View File
@@ -0,0 +1,11 @@
//go:build !windows && !linux
package net
import (
"github.com/xtls/xray-core/common/errors"
)
func FindProcess(dest Destination) (int, string, string, error) {
return 0, "", "", errors.New("process lookup is not supported on this platform")
}
+243
View File
@@ -0,0 +1,243 @@
//go:build windows
package net
import (
"net/netip"
"path/filepath"
"strings"
"sync"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"github.com/xtls/xray-core/common/errors"
)
const (
tcpTableFunc = "GetExtendedTcpTable"
tcpTablePidConn = 4
udpTableFunc = "GetExtendedUdpTable"
udpTablePid = 1
)
var (
getExTCPTable uintptr
getExUDPTable uintptr
once sync.Once
initErr error
)
func initWin32API() error {
h, err := windows.LoadLibrary("iphlpapi.dll")
if err != nil {
return errors.New("LoadLibrary iphlpapi.dll failed").Base(err)
}
getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
if err != nil {
return errors.New("GetProcAddress of ", tcpTableFunc, " failed").Base(err)
}
getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
if err != nil {
return errors.New("GetProcAddress of ", udpTableFunc, " failed").Base(err)
}
return nil
}
func FindProcess(dest Destination) (PID int, Name string, AbsolutePath string, err error) {
once.Do(func() {
initErr = initWin32API()
})
if initErr != nil {
return 0, "", "", initErr
}
isLocal, err := IsLocal(dest.Address.IP())
if err != nil {
return 0, "", "", errors.New("failed to determine if address is local: ", err)
}
if !isLocal {
return 0, "", "", ErrNotLocal
}
if dest.Network != Network_TCP && dest.Network != Network_UDP {
panic("Unsupported network type for process lookup.")
}
// the core should never has a domain as source(?
if dest.Address.Family() == AddressFamilyDomain {
panic("Domain addresses are not supported for process lookup.")
}
var class int
var fn uintptr
switch dest.Network {
case Network_TCP:
fn = getExTCPTable
class = tcpTablePidConn
case Network_UDP:
fn = getExUDPTable
class = udpTablePid
default:
panic("Unsupported network type for process lookup.")
}
ip := dest.Address.IP()
port := int(dest.Port)
addr, ok := netip.AddrFromSlice(ip)
if !ok {
return 0, "", "", errors.New("invalid IP address")
}
addr = addr.Unmap()
family := windows.AF_INET
if addr.Is6() {
family = windows.AF_INET6
}
buf, err := getTransportTable(fn, family, class)
if err != nil {
return 0, "", "", err
}
s := newSearcher(dest.Network, dest.Address.Family())
pid, err := s.Search(buf, addr, uint16(port))
if err != nil {
return 0, "", "", err
}
NameWithPath, err := getExecPathFromPID(pid)
NameWithPath = filepath.ToSlash(NameWithPath)
// drop .exe and path
nameSplit := strings.Split(NameWithPath, "/")
procName := nameSplit[len(nameSplit)-1]
procName = strings.TrimSuffix(procName, ".exe")
return int(pid), procName, NameWithPath, err
}
type searcher struct {
itemSize int
port int
ip int
ipSize int
pid int
tcpState int
}
func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {
n := int(readNativeUint32(b[:4]))
itemSize := s.itemSize
for i := range n {
row := b[4+itemSize*i : 4+itemSize*(i+1)]
if s.tcpState >= 0 {
tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
// MIB_TCP_STATE_ESTAB, only check established connections for TCP
if tcpState != 5 {
continue
}
}
// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
// this field can be illustrated as follows depends on different machine endianess:
// little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
// big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB)
// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
if srcPort != port {
continue
}
srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
srcIP = srcIP.Unmap()
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue
}
pid := readNativeUint32(row[s.pid : s.pid+4])
return pid, nil
}
return 0, errors.New("not found")
}
func newSearcher(network Network, family AddressFamily) *searcher {
var itemSize, port, ip, ipSize, pid int
tcpState := -1
switch network {
case Network_TCP:
if family == AddressFamilyIPv4 {
// struct MIB_TCPROW_OWNER_PID
itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
}
if family == AddressFamilyIPv6 {
// struct MIB_TCP6ROW_OWNER_PID
itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
}
case Network_UDP:
if family == AddressFamilyIPv4 {
// struct MIB_UDPROW_OWNER_PID
itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
}
if family == AddressFamilyIPv6 {
// struct MIB_UDP6ROW_OWNER_PID
itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
}
}
return &searcher{
itemSize: itemSize,
port: port,
ip: ip,
ipSize: ipSize,
pid: pid,
tcpState: tcpState,
}
}
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
for size, buf := uint32(8), make([]byte, 8); ; {
ptr := unsafe.Pointer(&buf[0])
err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
switch err {
case 0:
return buf, nil
case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
buf = make([]byte, size)
default:
return nil, errors.New("syscall error: ", int(err))
}
}
}
func readNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0]))
}
func getExecPathFromPID(pid uint32) (string, error) {
// kernel process starts with a colon in order to distinguish with normal processes
switch pid {
case 0:
// reserved pid for system idle process
return ":System Idle Process", nil
case 4:
// reserved pid for windows kernel image
return ":System", nil
}
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
if err != nil {
return "", err
}
defer windows.CloseHandle(h)
buf := make([]uint16, syscall.MAX_LONG_PATH)
size := uint32(len(buf))
err = windows.QueryFullProcessImageName(h, 0, &buf[0], &size)
if err != nil {
return "", err
}
return syscall.UTF16ToString(buf[:size]), nil
}
+41 -1
View File
@@ -1,7 +1,13 @@
// Package net is a drop-in replacement to Golang's net package, with some more functionalities. // Package net is a drop-in replacement to Golang's net package, with some more functionalities.
package net // import "github.com/xtls/xray-core/common/net" package net // import "github.com/xtls/xray-core/common/net"
import "time" import (
"net"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/errors"
)
// defines the maximum time an idle TCP session can survive in the tunnel, so // defines the maximum time an idle TCP session can survive in the tunnel, so
// it should be consistent across HTTP versions and with other transports. // it should be consistent across HTTP versions and with other transports.
@@ -12,3 +18,37 @@ const QuicgoH3KeepAlivePeriod = 10 * time.Second
// consistent with chrome // consistent with chrome
const ChromeH2KeepAlivePeriod = 45 * time.Second const ChromeH2KeepAlivePeriod = 45 * time.Second
var ErrNotLocal = errors.New("the source address is not from local machine.")
type localIPCacheEntry struct {
addrs []net.Addr
lastUpdate time.Time
}
var localIPCache = atomic.Pointer[localIPCacheEntry]{}
func IsLocal(ip net.IP) (bool, error) {
var addrs []net.Addr
if entry := localIPCache.Load(); entry == nil || time.Since(entry.lastUpdate) > time.Minute {
var err error
addrs, err = net.InterfaceAddrs()
if err != nil {
return false, err
}
localIPCache.Store(&localIPCacheEntry{
addrs: addrs,
lastUpdate: time.Now(),
})
} else {
addrs = entry.addrs
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok {
if ipnet.IP.Equal(ip) {
return true, nil
}
}
}
return false, nil
}
+18
View File
@@ -12,6 +12,8 @@ var (
type ListenConfig = net.ListenConfig type ListenConfig = net.ListenConfig
type KeepAliveConfig = net.KeepAliveConfig
var ( var (
Listen = net.Listen Listen = net.Listen
ListenTCP = net.ListenTCP ListenTCP = net.ListenTCP
@@ -26,6 +28,12 @@ var FileConn = net.FileConn
// ParseIP is an alias of net.ParseIP // ParseIP is an alias of net.ParseIP
var ParseIP = net.ParseIP var ParseIP = net.ParseIP
var ParseCIDR = net.ParseCIDR
var ResolveIPAddr = net.ResolveIPAddr
var InterfaceByName = net.InterfaceByName
var SplitHostPort = net.SplitHostPort var SplitHostPort = net.SplitHostPort
var CIDRMask = net.CIDRMask var CIDRMask = net.CIDRMask
@@ -51,6 +59,8 @@ type (
UnixConn = net.UnixConn UnixConn = net.UnixConn
) )
type IPAddr = net.IPAddr
// IP is an alias for net.IP. // IP is an alias for net.IP.
type ( type (
IP = net.IP IP = net.IP
@@ -82,3 +92,11 @@ var (
) )
type Resolver = net.Resolver type Resolver = net.Resolver
var DefaultResolver = net.DefaultResolver
var JoinHostPort = net.JoinHostPort
var InterfaceAddrs = net.InterfaceAddrs
var Interfaces = net.Interfaces
-10
View File
@@ -1,10 +0,0 @@
//go:build !windows
// +build !windows
package ctlcmd
import "syscall"
func getSysProcAttr() *syscall.SysProcAttr {
return nil
}
-12
View File
@@ -1,12 +0,0 @@
//go:build windows
// +build windows
package ctlcmd
import "syscall"
func getSysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
HideWindow: true,
}
}
-50
View File
@@ -1,50 +0,0 @@
package ctlcmd
import (
"context"
"io"
"os"
"os/exec"
"strings"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
)
func Run(args []string, input io.Reader) (buf.MultiBuffer, error) {
xctl := platform.GetToolLocation("xctl")
if _, err := os.Stat(xctl); err != nil {
return nil, errors.New("xctl doesn't exist").Base(err)
}
var errBuffer buf.MultiBufferContainer
var outBuffer buf.MultiBufferContainer
cmd := exec.Command(xctl, args...)
cmd.Stderr = &errBuffer
cmd.Stdout = &outBuffer
cmd.SysProcAttr = getSysProcAttr()
if input != nil {
cmd.Stdin = input
}
if err := cmd.Start(); err != nil {
return nil, errors.New("failed to start xctl").Base(err)
}
if err := cmd.Wait(); err != nil {
msg := "failed to execute xctl"
if errBuffer.Len() > 0 {
msg += ": \n" + strings.TrimSpace(errBuffer.MultiBuffer.String())
}
return nil, errors.New(msg).Base(err)
}
// log stderr, info message
if !errBuffer.IsEmpty() {
errors.LogInfo(context.Background(), "<xctl message> \n", strings.TrimSpace(errBuffer.MultiBuffer.String()))
}
return outBuffer.MultiBuffer, nil
}
+52
View File
@@ -0,0 +1,52 @@
package filesystem
func DecodeVarint(buf []byte) (x uint64, n int) {
for shift := uint(0); shift < 64; shift += 7 {
if n >= len(buf) {
return 0, 0
}
b := uint64(buf[n])
n++
x |= (b & 0x7F) << shift
if (b & 0x80) == 0 {
return x, n
}
}
// The number is too large to represent in a 64-bit value.
return 0, 0
}
func Find(data, code []byte) []byte {
codeL := len(code)
if codeL == 0 {
return nil
}
for {
dataL := len(data)
if dataL < 2 {
return nil
}
x, y := DecodeVarint(data[1:])
if x == 0 && y == 0 {
return nil
}
headL, bodyL := 1+y, int(x)
dataL -= headL
if dataL < bodyL {
return nil
}
data = data[headL:]
if int(data[1]) == codeL {
for i := 0; i < codeL && data[2+i] == code[i]; i++ {
if i+1 == codeL {
return data[:bodyL]
}
}
}
if dataL == bodyL {
return nil
}
data = data[bodyL:]
}
}
+26
View File
@@ -1,9 +1,12 @@
//go:build !windows && !wasm
package filesystem package filesystem
import ( import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"syscall"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
@@ -16,6 +19,29 @@ var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) {
} }
func ReadFile(path string) ([]byte, error) { func ReadFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return nil, err
}
size := stat.Size()
if size == 0 {
return []byte{}, nil
}
// use mmap to save RAM
bs, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
if err == nil {
return bs, nil
}
// fallback
reader, err := NewFileReader(path) reader, err := NewFileReader(path)
if err != nil { if err != nil {
return nil, err return nil, err
+54
View File
@@ -0,0 +1,54 @@
//go:build windows || wasm
package filesystem
import (
"io"
"os"
"path/filepath"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/platform"
)
type FileReaderFunc func(path string) (io.ReadCloser, error)
var NewFileReader FileReaderFunc = func(path string) (io.ReadCloser, error) {
return os.Open(path)
}
func ReadFile(path string) ([]byte, error) {
reader, err := NewFileReader(path)
if err != nil {
return nil, err
}
defer reader.Close()
return buf.ReadAllToBytes(reader)
}
func ReadAsset(file string) ([]byte, error) {
return ReadFile(platform.GetAssetLocation(file))
}
func ReadCert(file string) ([]byte, error) {
if filepath.IsAbs(file) {
return ReadFile(file)
}
return ReadFile(platform.GetCertLocation(file))
}
func CopyFile(dst string, src string) error {
bytes, err := ReadFile(src)
if err != nil {
return err
}
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(bytes)
return err
}
-9
View File
@@ -8,19 +8,10 @@ import (
"path/filepath" "path/filepath"
) )
func ExpandEnv(s string) string {
return os.ExpandEnv(s)
}
func LineSeparator() string { func LineSeparator() string {
return "\n" return "\n"
} }
func GetToolLocation(file string) string {
toolPath := NewEnvFlag(ToolLocation).GetValue(getExecutableDir)
return filepath.Join(toolPath, file)
}
// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations // GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
func GetAssetLocation(file string) string { func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
+2 -13
View File
@@ -8,10 +8,8 @@ import (
) )
const ( const (
PluginLocation = "xray.location.plugin"
ConfigLocation = "xray.location.config" ConfigLocation = "xray.location.config"
ConfdirLocation = "xray.location.confdir" ConfdirLocation = "xray.location.confdir"
ToolLocation = "xray.location.tool"
AssetLocation = "xray.location.asset" AssetLocation = "xray.location.asset"
CertLocation = "xray.location.cert" CertLocation = "xray.location.cert"
@@ -24,6 +22,8 @@ const (
BrowserDialerAddress = "xray.browser.dialer" BrowserDialerAddress = "xray.browser.dialer"
XUDPLog = "xray.xudp.show" XUDPLog = "xray.xudp.show"
XUDPBaseKey = "xray.xudp.basekey" XUDPBaseKey = "xray.xudp.basekey"
TunFdKey = "xray.tun.fd"
) )
type EnvFlag struct { type EnvFlag struct {
@@ -79,17 +79,6 @@ func getExecutableDir() string {
return filepath.Dir(exec) return filepath.Dir(exec)
} }
func getExecutableSubDir(dir string) func() string {
return func() string {
return filepath.Join(getExecutableDir(), dir)
}
}
func GetPluginDirectory() string {
pluginDir := NewEnvFlag(PluginLocation).GetValue(getExecutableSubDir("plugins"))
return pluginDir
}
func GetConfigurationPath() string { func GetConfigurationPath() string {
configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir) configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir)
return filepath.Join(configPath, "config.json") return filepath.Join(configPath, "config.json")
+4 -11
View File
@@ -3,25 +3,18 @@
package platform package platform
import "path/filepath" import (
"path/filepath"
func ExpandEnv(s string) string { )
// TODO
return s
}
func LineSeparator() string { func LineSeparator() string {
return "\r\n" return "\r\n"
} }
func GetToolLocation(file string) string {
toolPath := NewEnvFlag(ToolLocation).GetValue(getExecutableDir)
return filepath.Join(toolPath, file+".exe")
}
// GetAssetLocation searches for `file` in the env dir and the executable dir // GetAssetLocation searches for `file` in the env dir and the executable dir
func GetAssetLocation(file string) string { func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
return filepath.Join(assetPath, file) return filepath.Join(assetPath, file)
} }
+2 -1
View File
@@ -1,6 +1,7 @@
package strmatcher package strmatcher
import ( import (
"errors"
"regexp" "regexp"
) )
@@ -44,7 +45,7 @@ func (t Type) New(pattern string) (Matcher, error) {
pattern: r, pattern: r,
}, nil }, nil
default: default:
panic("Unknown type") return nil, errors.New("unk type")
} }
} }
+5 -1
View File
@@ -85,10 +85,14 @@ func ParseString(str string) (UUID, error) {
b := uuid.Bytes() b := uuid.Bytes()
for _, byteGroup := range byteGroups { for _, byteGroup := range byteGroups {
if text[0] == '-' { if len(text) > 0 && text[0] == '-' {
text = text[1:] text = text[1:]
} }
if len(text) < byteGroup {
return uuid, errors.New("invalid UUID: ", str)
}
if _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil { if _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil {
return uuid, err return uuid, err
} }
+5
View File
@@ -44,6 +44,11 @@ func TestParseString(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("Expect error but nil") t.Fatal("Expect error but nil")
} }
_, err = ParseString("2418d087-648d-4990-86e8-19dca1d0")
if err == nil {
t.Fatal("Expect error but nil")
}
} }
func TestNewUUID(t *testing.T) { func TestNewUUID(t *testing.T) {
+3 -3
View File
@@ -64,7 +64,7 @@ func GetMergedConfig(args cmdarg.Arg) (string, error) {
var files []*ConfigSource var files []*ConfigSource
supported := []string{"json", "yaml", "toml"} supported := []string{"json", "yaml", "toml"}
for _, file := range args { for _, file := range args {
format := getFormat(file) format := GetFormat(file)
if slices.Contains(supported, format) { if slices.Contains(supported, format) {
files = append(files, &ConfigSource{ files = append(files, &ConfigSource{
Name: file, Name: file,
@@ -98,7 +98,7 @@ func getExtension(filename string) string {
return filename[idx+1:] return filename[idx+1:]
} }
func getFormat(filename string) string { func GetFormat(filename string) string {
return GetFormatByExtension(getExtension(filename)) return GetFormatByExtension(getExtension(filename))
} }
@@ -112,7 +112,7 @@ func LoadConfig(formatName string, input interface{}) (*Config, error) {
if formatName == "auto" { if formatName == "auto" {
if file != "stdin:" { if file != "stdin:" {
f = getFormat(file) f = GetFormat(file)
} else { } else {
f = "json" f = "json"
} }
+2 -2
View File
@@ -18,8 +18,8 @@ import (
var ( var (
Version_x byte = 25 Version_x byte = 25
Version_y byte = 10 Version_y byte = 12
Version_z byte = 15 Version_z byte = 8
) )
var ( var (
+1 -2
View File
@@ -68,6 +68,5 @@ func RCodeFromError(err error) uint16 {
if r, ok := cause.(RCodeError); ok { if r, ok := cause.(RCodeError); ok {
return uint16(r) return uint16(r)
} }
// unknown error return 0
return 2
} }
+8
View File
@@ -98,6 +98,9 @@ type Manager interface {
UnregisterChannel(string) error UnregisterChannel(string) error
// GetChannel returns a channel by its identifier. // GetChannel returns a channel by its identifier.
GetChannel(string) Channel GetChannel(string) Channel
// GetAllOnlineUsers returns all online users from all OnlineMaps.
GetAllOnlineUsers() []string
} }
// GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter. // GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter.
@@ -190,6 +193,11 @@ func (NoopManager) GetChannel(string) Channel {
return nil return nil
} }
// GetAllOnlineUsers implements Manager.
func (NoopManager) GetAllOnlineUsers() []string {
return nil
}
// Start implements common.Runnable. // Start implements common.Runnable.
func (NoopManager) Start() error { return nil } func (NoopManager) Start() error { return nil }
+16 -16
View File
@@ -3,15 +3,15 @@ module github.com/xtls/xray-core
go 1.25 go 1.25
require ( require (
github.com/cloudflare/circl v1.6.1 github.com/cloudflare/circl v1.6.2
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/miekg/dns v1.1.68 github.com/miekg/dns v1.1.69
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.8.1 github.com/pires/go-proxyproto v0.8.1
github.com/quic-go/quic-go v0.55.0 github.com/quic-go/quic-go v0.58.0
github.com/refraction-networking/utls v1.8.1 github.com/refraction-networking/utls v1.8.1
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
@@ -21,13 +21,13 @@ require (
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-20251014195629-e4eec4520535
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.43.0 golang.org/x/crypto v0.46.0
golang.org/x/net v0.46.0 golang.org/x/net v0.48.0
golang.org/x/sync v0.17.0 golang.org/x/sync v0.19.0
golang.org/x/sys v0.37.0 golang.org/x/sys v0.39.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.76.0 google.golang.org/grpc v1.78.0
google.golang.org/protobuf v1.36.10 google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5
h12.io/socks v1.0.3 h12.io/socks v1.0.3
lukechampine.com/blake3 v1.4.1 lukechampine.com/blake3 v1.4.1
@@ -41,17 +41,17 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/kr/pretty v0.3.1 // 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.5.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/mod v0.28.0 // indirect golang.org/x/mod v0.30.0 // indirect
golang.org/x/text v0.30.0 // indirect golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.37.0 // indirect golang.org/x/tools v0.39.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // 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 -45
View File
@@ -1,7 +1,7 @@
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/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.2/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=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -38,27 +38,26 @@ 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=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
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.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug=
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo= github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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=
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
@@ -78,38 +77,38 @@ github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZla
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU= github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU=
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
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.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
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.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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=
@@ -117,21 +116,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
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=
@@ -141,12 +140,12 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= 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-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.10/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=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+109 -43
View File
@@ -16,19 +16,21 @@ import (
) )
type NameServerConfig struct { type NameServerConfig struct {
Address *Address `json:"address"` Address *Address `json:"address"`
ClientIP *Address `json:"clientIp"` ClientIP *Address `json:"clientIp"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
SkipFallback bool `json:"skipFallback"` SkipFallback bool `json:"skipFallback"`
Domains []string `json:"domains"` Domains []string `json:"domains"`
ExpectedIPs StringList `json:"expectedIPs"` ExpectedIPs StringList `json:"expectedIPs"`
ExpectIPs StringList `json:"expectIPs"` ExpectIPs StringList `json:"expectIPs"`
QueryStrategy string `json:"queryStrategy"` QueryStrategy string `json:"queryStrategy"`
Tag string `json:"tag"` Tag string `json:"tag"`
TimeoutMs uint64 `json:"timeoutMs"` TimeoutMs uint64 `json:"timeoutMs"`
DisableCache bool `json:"disableCache"` DisableCache *bool `json:"disableCache"`
FinalQuery bool `json:"finalQuery"` ServeStale *bool `json:"serveStale"`
UnexpectedIPs StringList `json:"unexpectedIPs"` ServeExpiredTTL *uint32 `json:"serveExpiredTTL"`
FinalQuery bool `json:"finalQuery"`
UnexpectedIPs StringList `json:"unexpectedIPs"`
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
@@ -40,19 +42,21 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
} }
var advanced struct { var advanced struct {
Address *Address `json:"address"` Address *Address `json:"address"`
ClientIP *Address `json:"clientIp"` ClientIP *Address `json:"clientIp"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
SkipFallback bool `json:"skipFallback"` SkipFallback bool `json:"skipFallback"`
Domains []string `json:"domains"` Domains []string `json:"domains"`
ExpectedIPs StringList `json:"expectedIPs"` ExpectedIPs StringList `json:"expectedIPs"`
ExpectIPs StringList `json:"expectIPs"` ExpectIPs StringList `json:"expectIPs"`
QueryStrategy string `json:"queryStrategy"` QueryStrategy string `json:"queryStrategy"`
Tag string `json:"tag"` Tag string `json:"tag"`
TimeoutMs uint64 `json:"timeoutMs"` TimeoutMs uint64 `json:"timeoutMs"`
DisableCache bool `json:"disableCache"` DisableCache *bool `json:"disableCache"`
FinalQuery bool `json:"finalQuery"` ServeStale *bool `json:"serveStale"`
UnexpectedIPs StringList `json:"unexpectedIPs"` ServeExpiredTTL *uint32 `json:"serveExpiredTTL"`
FinalQuery bool `json:"finalQuery"`
UnexpectedIPs StringList `json:"unexpectedIPs"`
} }
if err := json.Unmarshal(data, &advanced); err == nil { if err := json.Unmarshal(data, &advanced); err == nil {
c.Address = advanced.Address c.Address = advanced.Address
@@ -66,6 +70,8 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
c.Tag = advanced.Tag c.Tag = advanced.Tag
c.TimeoutMs = advanced.TimeoutMs c.TimeoutMs = advanced.TimeoutMs
c.DisableCache = advanced.DisableCache c.DisableCache = advanced.DisableCache
c.ServeStale = advanced.ServeStale
c.ServeExpiredTTL = advanced.ServeExpiredTTL
c.FinalQuery = advanced.FinalQuery c.FinalQuery = advanced.FinalQuery
c.UnexpectedIPs = advanced.UnexpectedIPs c.UnexpectedIPs = advanced.UnexpectedIPs
return nil return nil
@@ -74,21 +80,6 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
return errors.New("failed to parse name server: ", string(data)) return errors.New("failed to parse name server: ", string(data))
} }
func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
switch t {
case router.Domain_Domain:
return dns.DomainMatchingType_Subdomain
case router.Domain_Full:
return dns.DomainMatchingType_Full
case router.Domain_Plain:
return dns.DomainMatchingType_Keyword
case router.Domain_Regex:
return dns.DomainMatchingType_Regex
default:
panic("unknown domain type")
}
}
func (c *NameServerConfig) Build() (*dns.NameServer, error) { func (c *NameServerConfig) Build() (*dns.NameServer, error) {
if c.Address == nil { if c.Address == nil {
return nil, errors.New("NameServer address is not specified.") return nil, errors.New("NameServer address is not specified.")
@@ -98,14 +89,14 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
var originalRules []*dns.NameServer_OriginalRule var originalRules []*dns.NameServer_OriginalRule
for _, rule := range c.Domains { for _, rule := range c.Domains {
parsedDomain, err := parseDomainRule(rule) parsedDomain, err := ParseDomainRule(rule)
if err != nil { if err != nil {
return nil, errors.New("invalid domain rule: ", rule).Base(err) return nil, errors.New("invalid domain rule: ", rule).Base(err)
} }
for _, pd := range parsedDomain { for _, pd := range parsedDomain {
domains = append(domains, &dns.NameServer_PriorityDomain{ domains = append(domains, &dns.NameServer_PriorityDomain{
Type: toDomainMatchingType(pd.Type), Type: dns.ToDomainMatchingType(pd.Type),
Domain: pd.Value, Domain: pd.Value,
}) })
} }
@@ -173,6 +164,8 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
Tag: c.Tag, Tag: c.Tag,
TimeoutMs: c.TimeoutMs, TimeoutMs: c.TimeoutMs,
DisableCache: c.DisableCache, DisableCache: c.DisableCache,
ServeStale: c.ServeStale,
ServeExpiredTTL: c.ServeExpiredTTL,
FinalQuery: c.FinalQuery, FinalQuery: c.FinalQuery,
UnexpectedGeoip: unexpectedGeoipList, UnexpectedGeoip: unexpectedGeoipList,
ActUnprior: actUnprior, ActUnprior: actUnprior,
@@ -194,8 +187,11 @@ type DNSConfig struct {
Tag string `json:"tag"` Tag string `json:"tag"`
QueryStrategy string `json:"queryStrategy"` QueryStrategy string `json:"queryStrategy"`
DisableCache bool `json:"disableCache"` DisableCache bool `json:"disableCache"`
ServeStale bool `json:"serveStale"`
ServeExpiredTTL uint32 `json:"serveExpiredTTL"`
DisableFallback bool `json:"disableFallback"` DisableFallback bool `json:"disableFallback"`
DisableFallbackIfMatch bool `json:"disableFallbackIfMatch"` DisableFallbackIfMatch bool `json:"disableFallbackIfMatch"`
EnableParallelQuery bool `json:"enableParallelQuery"`
UseSystemHosts bool `json:"useSystemHosts"` UseSystemHosts bool `json:"useSystemHosts"`
} }
@@ -391,8 +387,11 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
config := &dns.Config{ config := &dns.Config{
Tag: c.Tag, Tag: c.Tag,
DisableCache: c.DisableCache, DisableCache: c.DisableCache,
ServeStale: c.ServeStale,
ServeExpiredTTL: c.ServeExpiredTTL,
DisableFallback: c.DisableFallback, DisableFallback: c.DisableFallback,
DisableFallbackIfMatch: c.DisableFallbackIfMatch, DisableFallbackIfMatch: c.DisableFallbackIfMatch,
EnableParallelQuery: c.EnableParallelQuery,
QueryStrategy: resolveQueryStrategy(c.QueryStrategy), QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
} }
@@ -403,11 +402,78 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
config.ClientIp = []byte(c.ClientIP.IP()) config.ClientIp = []byte(c.ClientIP.IP())
} }
// Build PolicyID
policyMap := map[string]uint32{}
nextPolicyID := uint32(1)
buildPolicyID := func(nsc *NameServerConfig) uint32 {
var sb strings.Builder
// ClientIP
if nsc.ClientIP != nil {
sb.WriteString("client=")
sb.WriteString(nsc.ClientIP.String())
sb.WriteByte('|')
} else {
sb.WriteString("client=none|")
}
// SkipFallback
if nsc.SkipFallback {
sb.WriteString("skip=1|")
} else {
sb.WriteString("skip=0|")
}
// QueryStrategy
sb.WriteString("qs=")
sb.WriteString(strings.ToLower(strings.TrimSpace(nsc.QueryStrategy)))
sb.WriteByte('|')
// Tag
sb.WriteString("tag=")
sb.WriteString(strings.ToLower(strings.TrimSpace(nsc.Tag)))
sb.WriteByte('|')
// []string helper
writeList := func(tag string, lst []string) {
if len(lst) == 0 {
sb.WriteString(tag)
sb.WriteString("=[]|")
return
}
cp := make([]string, len(lst))
for i, s := range lst {
cp[i] = strings.TrimSpace(strings.ToLower(s))
}
sort.Strings(cp)
sb.WriteString(tag)
sb.WriteByte('=')
sb.WriteString(strings.Join(cp, ","))
sb.WriteByte('|')
}
writeList("domains", nsc.Domains)
writeList("expected", nsc.ExpectedIPs)
writeList("expect", nsc.ExpectIPs)
writeList("unexpected", nsc.UnexpectedIPs)
key := sb.String()
if id, ok := policyMap[key]; ok {
return id
}
id := nextPolicyID
nextPolicyID++
policyMap[key] = id
return id
}
for _, server := range c.Servers { for _, server := range c.Servers {
ns, err := server.Build() ns, err := server.Build()
if err != nil { if err != nil {
return nil, errors.New("failed to build nameserver").Base(err) return nil, errors.New("failed to build nameserver").Base(err)
} }
ns.PolicyID = buildPolicyID(server)
config.NameServer = append(config.NameServer, ns) config.NameServer = append(config.NameServer, ns)
} }
+12 -2
View File
@@ -20,7 +20,8 @@ func TestDNSConfigParsing(t *testing.T) {
return config.Build() return config.Build()
} }
} }
expectedServeStale := true
expectedServeExpiredTTL := uint32(172800)
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {
Input: `{ Input: `{
@@ -28,7 +29,9 @@ func TestDNSConfigParsing(t *testing.T) {
"address": "8.8.8.8", "address": "8.8.8.8",
"port": 5353, "port": 5353,
"skipFallback": true, "skipFallback": true,
"domains": ["domain:example.com"] "domains": ["domain:example.com"],
"serveStale": true,
"serveExpiredTTL": 172800
}], }],
"hosts": { "hosts": {
"domain:example.com": "google.com", "domain:example.com": "google.com",
@@ -40,6 +43,8 @@ func TestDNSConfigParsing(t *testing.T) {
"clientIp": "10.0.0.1", "clientIp": "10.0.0.1",
"queryStrategy": "UseIPv4", "queryStrategy": "UseIPv4",
"disableCache": true, "disableCache": true,
"serveStale": false,
"serveExpiredTTL": 86400,
"disableFallback": true "disableFallback": true
}`, }`,
Parser: parserCreator(), Parser: parserCreator(),
@@ -68,6 +73,9 @@ func TestDNSConfigParsing(t *testing.T) {
Size: 1, Size: 1,
}, },
}, },
ServeStale: &expectedServeStale,
ServeExpiredTTL: &expectedServeExpiredTTL,
PolicyID: 1, // Servers with certain identical fields share this ID, incrementing starting from 1. See: Build PolicyID
}, },
}, },
StaticHosts: []*dns.Config_HostMapping{ StaticHosts: []*dns.Config_HostMapping{
@@ -100,6 +108,8 @@ func TestDNSConfigParsing(t *testing.T) {
ClientIp: []byte{10, 0, 0, 1}, ClientIp: []byte{10, 0, 0, 1},
QueryStrategy: dns.QueryStrategy_USE_IP4, QueryStrategy: dns.QueryStrategy_USE_IP4,
DisableCache: true, DisableCache: true,
ServeStale: false,
ServeExpiredTTL: 86400,
DisableFallback: true, DisableFallback: true,
}, },
}, },
+50 -118
View File
@@ -83,8 +83,6 @@ func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
} }
switch strings.ToLower(ds) { switch strings.ToLower(ds) {
case "alwaysip":
return router.Config_UseIp
case "ipifnonmatch": case "ipifnonmatch":
return router.Config_IpIfNonMatch return router.Config_IpIfNonMatch
case "ipondemand": case "ipondemand":
@@ -205,17 +203,23 @@ func loadFile(file string) ([]byte, error) {
func loadIP(file, code string) ([]*router.CIDR, error) { func loadIP(file, code string) ([]*router.CIDR, error) {
index := file + ":" + code index := file + ":" + code
if IPCache[index] == nil { if IPCache[index] == nil {
bs, err := loadFile(file)
if err != nil {
return nil, errors.New("failed to load file: ", file).Base(err)
}
bs = find(bs, []byte(code))
if bs == nil {
return nil, errors.New("code not found in ", file, ": ", code)
}
var geoip router.GeoIP var geoip router.GeoIP
if err := proto.Unmarshal(bs, &geoip); err != nil {
return nil, errors.New("error unmarshal IP in ", file, ": ", code).Base(err) if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
// dont pass code becuase we have country code in top level router.GeoIP
geoip = router.GeoIP{Cidr: []*router.CIDR{}}
} else {
bs, err := loadFile(file)
if err != nil {
return nil, errors.New("failed to load file: ", file).Base(err)
}
bs = filesystem.Find(bs, []byte(code))
if bs == nil {
return nil, errors.New("code not found in ", file, ": ", code)
}
if err := proto.Unmarshal(bs, &geoip); err != nil {
return nil, errors.New("error unmarshal IP in ", file, ": ", code).Base(err)
}
} }
defer runtime.GC() // or debug.FreeOSMemory() defer runtime.GC() // or debug.FreeOSMemory()
return geoip.Cidr, nil // do not cache geoip return geoip.Cidr, nil // do not cache geoip
@@ -227,18 +231,28 @@ func loadIP(file, code string) ([]*router.CIDR, error) {
func loadSite(file, code string) ([]*router.Domain, error) { func loadSite(file, code string) ([]*router.Domain, error) {
index := file + ":" + code index := file + ":" + code
if SiteCache[index] == nil { if SiteCache[index] == nil {
bs, err := loadFile(file)
if err != nil {
return nil, errors.New("failed to load file: ", file).Base(err)
}
bs = find(bs, []byte(code))
if bs == nil {
return nil, errors.New("list not found in ", file, ": ", code)
}
var geosite router.GeoSite var geosite router.GeoSite
if err := proto.Unmarshal(bs, &geosite); err != nil {
return nil, errors.New("error unmarshal Site in ", file, ": ", code).Base(err) if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
// pass file:code so can build optimized matcher later
domain := router.Domain{Value: file + "_" + code}
geosite = router.GeoSite{Domain: []*router.Domain{&domain}}
} else {
bs, err := loadFile(file)
if err != nil {
return nil, errors.New("failed to load file: ", file).Base(err)
}
bs = filesystem.Find(bs, []byte(code))
if bs == nil {
return nil, errors.New("list not found in ", file, ": ", code)
}
if err := proto.Unmarshal(bs, &geosite); err != nil {
return nil, errors.New("error unmarshal Site in ", file, ": ", code).Base(err)
}
} }
defer runtime.GC() // or debug.FreeOSMemory() defer runtime.GC() // or debug.FreeOSMemory()
return geosite.Domain, nil // do not cache geosite return geosite.Domain, nil // do not cache geosite
SiteCache[index] = &geosite SiteCache[index] = &geosite
@@ -246,105 +260,13 @@ func loadSite(file, code string) ([]*router.Domain, error) {
return SiteCache[index].Domain, nil return SiteCache[index].Domain, nil
} }
func DecodeVarint(buf []byte) (x uint64, n int) {
for shift := uint(0); shift < 64; shift += 7 {
if n >= len(buf) {
return 0, 0
}
b := uint64(buf[n])
n++
x |= (b & 0x7F) << shift
if (b & 0x80) == 0 {
return x, n
}
}
// The number is too large to represent in a 64-bit value.
return 0, 0
}
func find(data, code []byte) []byte {
codeL := len(code)
if codeL == 0 {
return nil
}
for {
dataL := len(data)
if dataL < 2 {
return nil
}
x, y := DecodeVarint(data[1:])
if x == 0 && y == 0 {
return nil
}
headL, bodyL := 1+y, int(x)
dataL -= headL
if dataL < bodyL {
return nil
}
data = data[headL:]
if int(data[1]) == codeL {
for i := 0; i < codeL && data[2+i] == code[i]; i++ {
if i+1 == codeL {
return data[:bodyL]
}
}
}
if dataL == bodyL {
return nil
}
data = data[bodyL:]
}
}
type AttributeMatcher interface {
Match(*router.Domain) bool
}
type BooleanMatcher string
func (m BooleanMatcher) Match(domain *router.Domain) bool {
for _, attr := range domain.Attribute {
if attr.Key == string(m) {
return true
}
}
return false
}
type AttributeList struct {
matcher []AttributeMatcher
}
func (al *AttributeList) Match(domain *router.Domain) bool {
for _, matcher := range al.matcher {
if !matcher.Match(domain) {
return false
}
}
return true
}
func (al *AttributeList) IsEmpty() bool {
return len(al.matcher) == 0
}
func parseAttrs(attrs []string) *AttributeList {
al := new(AttributeList)
for _, attr := range attrs {
lc := strings.ToLower(attr)
al.matcher = append(al.matcher, BooleanMatcher(lc))
}
return al
}
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) { func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
parts := strings.Split(siteWithAttr, "@") parts := strings.Split(siteWithAttr, "@")
if len(parts) == 0 { if len(parts) == 0 {
return nil, errors.New("empty site") return nil, errors.New("empty site")
} }
country := strings.ToUpper(parts[0]) country := strings.ToUpper(parts[0])
attrs := parseAttrs(parts[1:]) attrs := router.ParseAttrs(parts[1:])
domains, err := loadSite(file, country) domains, err := loadSite(file, country)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -354,6 +276,11 @@ func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, er
return domains, nil return domains, nil
} }
if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
domains[0].Value = domains[0].Value + "_" + strings.Join(parts[1:], ",")
return domains, nil
}
filteredDomains := make([]*router.Domain, 0, len(domains)) filteredDomains := make([]*router.Domain, 0, len(domains))
for _, domain := range domains { for _, domain := range domains {
if attrs.Match(domain) { if attrs.Match(domain) {
@@ -364,7 +291,7 @@ func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, er
return filteredDomains, nil return filteredDomains, nil
} }
func parseDomainRule(domain string) ([]*router.Domain, error) { func ParseDomainRule(domain string) ([]*router.Domain, error) {
if strings.HasPrefix(domain, "geosite:") { if strings.HasPrefix(domain, "geosite:") {
country := strings.ToUpper(domain[8:]) country := strings.ToUpper(domain[8:])
domains, err := loadGeositeWithAttr("geosite.dat", country) domains, err := loadGeositeWithAttr("geosite.dat", country)
@@ -537,6 +464,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
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"`
} }
rawFieldRule := new(RawFieldRule) rawFieldRule := new(RawFieldRule)
err := json.Unmarshal(msg, rawFieldRule) err := json.Unmarshal(msg, rawFieldRule)
@@ -561,7 +489,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
if rawFieldRule.Domain != nil { if rawFieldRule.Domain != nil {
for _, domain := range *rawFieldRule.Domain { for _, domain := range *rawFieldRule.Domain {
rules, err := parseDomainRule(domain) rules, err := ParseDomainRule(domain)
if err != nil { if err != nil {
return nil, errors.New("failed to parse domain rule: ", domain).Base(err) return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
} }
@@ -571,7 +499,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
if rawFieldRule.Domains != nil { if rawFieldRule.Domains != nil {
for _, domain := range *rawFieldRule.Domains { for _, domain := range *rawFieldRule.Domains {
rules, err := parseDomainRule(domain) rules, err := ParseDomainRule(domain)
if err != nil { if err != nil {
return nil, errors.New("failed to parse domain rule: ", domain).Base(err) return nil, errors.New("failed to parse domain rule: ", domain).Base(err)
} }
@@ -649,6 +577,10 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
rule.Attributes = rawFieldRule.Attributes rule.Attributes = rawFieldRule.Attributes
} }
if rawFieldRule.Process != nil && len(*rawFieldRule.Process) > 0 {
rule.Process = *rawFieldRule.Process
}
return rule, nil return rule, nil
} }
+8
View File
@@ -628,6 +628,9 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
} }
config.ShortIds = make([][]byte, len(c.ShortIds)) config.ShortIds = make([][]byte, len(c.ShortIds))
for i, s := range c.ShortIds { for i, s := range c.ShortIds {
if len(s) > 16 {
return nil, errors.New(`too long "shortIds[`, i, `]": `, s)
}
config.ShortIds[i] = make([]byte, 8) config.ShortIds[i] = make([]byte, 8)
if _, err = hex.Decode(config.ShortIds[i], []byte(s)); err != nil { if _, err = hex.Decode(config.ShortIds[i], []byte(s)); err != nil {
return nil, errors.New(`invalid "shortIds[`, i, `]": `, s) return nil, errors.New(`invalid "shortIds[`, i, `]": `, s)
@@ -679,6 +682,9 @@ 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 {
return nil, errors.New(`too long "shortId": `, c.ShortId)
}
config.ShortId = make([]byte, 8) config.ShortId = make([]byte, 8)
if _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil { if _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil {
return nil, errors.New(`invalid "shortId": `, c.ShortId) return nil, errors.New(`invalid "shortId": `, c.ShortId)
@@ -804,6 +810,7 @@ type SocketConfig struct {
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"` CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
AddressPortStrategy string `json:"addressPortStrategy"` AddressPortStrategy string `json:"addressPortStrategy"`
HappyEyeballsSettings *HappyEyeballsConfig `json:"happyEyeballs"` HappyEyeballsSettings *HappyEyeballsConfig `json:"happyEyeballs"`
TrustedXForwardedFor []string `json:"trustedXForwardedFor"`
} }
// Build implements Buildable. // Build implements Buildable.
@@ -923,6 +930,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
CustomSockopt: customSockopts, CustomSockopt: customSockopts,
AddressPortStrategy: addressPortStrategy, AddressPortStrategy: addressPortStrategy,
HappyEyeballs: happyEyeballs, HappyEyeballs: happyEyeballs,
TrustedXForwardedFor: c.TrustedXForwardedFor,
}, nil }, nil
} }
+30
View File
@@ -0,0 +1,30 @@
package conf
import (
"github.com/xtls/xray-core/proxy/tun"
"google.golang.org/protobuf/proto"
)
type TunConfig struct {
Name string `json:"name"`
MTU uint32 `json:"MTU"`
UserLevel uint32 `json:"userLevel"`
}
func (v *TunConfig) Build() (proto.Message, error) {
config := &tun.Config{
Name: v.Name,
MTU: v.MTU,
UserLevel: v.UserLevel,
}
if v.Name == "" {
config.Name = "xray0"
}
if v.MTU == 0 {
config.MTU = 1500
}
return config, nil
}
+9
View File
@@ -34,6 +34,7 @@ type VLessInboundConfig struct {
Decryption string `json:"decryption"` Decryption string `json:"decryption"`
Fallbacks []*VLessInboundFallback `json:"fallbacks"` Fallbacks []*VLessInboundFallback `json:"fallbacks"`
Flow string `json:"flow"` Flow string `json:"flow"`
Testseed []uint32 `json:"testseed"`
} }
// Build implements Buildable // Build implements Buildable
@@ -73,6 +74,10 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
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 len(account.Testseed) < 4 {
account.Testseed = c.Testseed
}
if account.Encryption != "" { if account.Encryption != "" {
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`)
} }
@@ -212,6 +217,8 @@ type VLessOutboundConfig struct {
Seed string `json:"seed"` Seed string `json:"seed"`
Encryption string `json:"encryption"` Encryption string `json:"encryption"`
Reverse *vless.Reverse `json:"reverse"` Reverse *vless.Reverse `json:"reverse"`
Testpre uint32 `json:"testpre"`
Testseed []uint32 `json:"testseed"`
Vnext []*VLessOutboundVnext `json:"vnext"` Vnext []*VLessOutboundVnext `json:"vnext"`
} }
@@ -258,6 +265,8 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
//account.Seed = c.Seed //account.Seed = c.Seed
account.Encryption = c.Encryption account.Encryption = c.Encryption
account.Reverse = c.Reverse account.Reverse = c.Reverse
account.Testpre = c.Testpre
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)
+1 -4
View File
@@ -3,8 +3,6 @@ package conf
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"log"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -30,6 +28,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} },
"tun": func() interface{} { return new(TunConfig) },
}, "protocol", "settings") }, "protocol", "settings")
outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
@@ -47,8 +46,6 @@ var (
"dns": func() interface{} { return new(DNSOutboundConfig) }, "dns": func() interface{} { return new(DNSOutboundConfig) },
"wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} }, "wireguard": func() interface{} { return &WireGuardConfig{IsClient: true} },
}, "protocol", "settings") }, "protocol", "settings")
ctllog = log.New(os.Stderr, "xctl> ", 0)
) )
type SniffingConfig struct { type SniffingConfig struct {
+1
View File
@@ -32,5 +32,6 @@ var CmdAPI = &base.Command{
cmdSourceIpBlock, cmdSourceIpBlock,
cmdOnlineStats, cmdOnlineStats,
cmdOnlineStatsIpList, cmdOnlineStatsIpList,
cmdGetAllOnlineUsers,
}, },
} }
@@ -0,0 +1,43 @@
package api
import (
statsService "github.com/xtls/xray-core/app/stats/command"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdGetAllOnlineUsers = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api statsgetallonlineusers [--server=127.0.0.1:8080]",
Short: "Retrieve array of all online users",
Long: `
Retrieve array of all online users.
Arguments:
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080
-t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080"
`,
Run: executeGetAllOnlineUsers,
}
func executeGetAllOnlineUsers(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
conn, ctx, close := dialAPIServer()
defer close()
client := statsService.NewStatsServiceClient(conn)
r := &statsService.GetAllOnlineUsersRequest{}
resp, err := client.GetAllOnlineUsers(ctx, r)
if err != nil {
base.Fatalf("failed to get stats: %s", err)
}
showJSONResponse(resp)
}
+1 -10
View File
@@ -3,7 +3,6 @@ package convert
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"github.com/xtls/xray-core/common/cmdarg" "github.com/xtls/xray-core/common/cmdarg"
creflect "github.com/xtls/xray-core/common/reflect" creflect "github.com/xtls/xray-core/common/reflect"
@@ -61,7 +60,7 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
} }
if len(optFile) > 0 { if len(optFile) > 0 {
switch core.GetFormatByExtension(getFileExtension(optFile)){ switch core.GetFormat(optFile){
case "protobuf", "": case "protobuf", "":
fmt.Println("Output ProtoBuf file is ", optFile) fmt.Println("Output ProtoBuf file is ", optFile)
default: default:
@@ -106,11 +105,3 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
} }
} }
} }
func getFileExtension(filename string) string {
idx := strings.LastIndexByte(filename, '.')
if idx == -1 {
return ""
}
return filename[idx+1:]
}
-12
View File
@@ -10,12 +10,10 @@ import (
type ( type (
configFileLoader func(string) (io.Reader, error) configFileLoader func(string) (io.Reader, error)
extconfigLoader func([]string, io.Reader) (io.Reader, error)
) )
var ( var (
EffectiveConfigFileLoader configFileLoader EffectiveConfigFileLoader configFileLoader
EffectiveExtConfigLoader extconfigLoader
) )
// LoadConfig reads from a path/url/stdin // LoadConfig reads from a path/url/stdin
@@ -27,13 +25,3 @@ func LoadConfig(file string) (io.Reader, error) {
} }
return EffectiveConfigFileLoader(file) return EffectiveConfigFileLoader(file)
} }
// LoadExtConfig calls xctl to handle multiple config
// the actual work also in external module
func LoadExtConfig(files []string, reader io.Reader) (io.Reader, error) {
if EffectiveExtConfigLoader == nil {
return nil, errors.New("external config module not loaded").AtError()
}
return EffectiveExtConfigLoader(files, reader)
}
-11
View File
@@ -13,7 +13,6 @@ import (
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform/ctlcmd"
"github.com/xtls/xray-core/main/confloader" "github.com/xtls/xray-core/main/confloader"
) )
@@ -129,16 +128,6 @@ func FetchUnixSocketHTTPContent(target string) ([]byte, error) {
return content, nil return content, nil
} }
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader)
if err != nil {
return nil, err
}
return strings.NewReader(buf.String()), nil
}
func init() { func init() {
confloader.EffectiveConfigFileLoader = ConfigLoader confloader.EffectiveConfigFileLoader = ConfigLoader
confloader.EffectiveExtConfigLoader = ExtConfigLoader
} }
+2 -1
View File
@@ -111,7 +111,8 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
destinationOverridden = true destinationOverridden = true
} }
} }
if tlsConn, ok := conn.(tls.Interface); ok && !destinationOverridden { iConn := stat.TryUnwrapStatsConn(conn)
if tlsConn, ok := iConn.(tls.Interface); ok && !destinationOverridden {
if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" { if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" {
dest.Address = net.DomainAddress(serverName) dest.Address = net.DomainAddress(serverName)
destinationOverridden = true destinationOverridden = true
+7 -4
View File
@@ -296,10 +296,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
return nil, err return nil, err
} }
iConn := rawConn iConn := stat.TryUnwrapStatsConn(rawConn)
if statConn, ok := iConn.(*stat.CounterConnection); ok {
iConn = statConn.Connection
}
nextProto := "" nextProto := ""
if tlsConn, ok := iConn.(*tls.Conn); ok { if tlsConn, ok := iConn.(*tls.Conn); ok {
@@ -308,6 +305,12 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
return nil, err return nil, err
} }
nextProto = tlsConn.ConnectionState().NegotiatedProtocol nextProto = tlsConn.ConnectionState().NegotiatedProtocol
} else if tlsConn, ok := iConn.(*tls.UConn); ok {
if err := tlsConn.HandshakeContext(ctx); err != nil {
rawConn.Close()
return nil, err
}
nextProto = tlsConn.ConnectionState().NegotiatedProtocol
} }
switch nextProto { switch nextProto {
+91 -33
View File
@@ -248,7 +248,7 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
*withinPaddingBuffers = false *withinPaddingBuffers = false
*switchToDirectCopy = true *switchToDirectCopy = true
} else { } else {
errors.LogInfo(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len()) errors.LogDebug(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len())
} }
} }
if w.trafficState.NumberOfPacketToFilter > 0 { if w.trafficState.NumberOfPacketToFilter > 0 {
@@ -269,9 +269,9 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
w.rawInput = nil w.rawInput = nil
if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil { if inbound := session.InboundFromContext(w.ctx); inbound != nil && inbound.Conn != nil {
if w.isUplink && inbound.CanSpliceCopy == 2 { // if w.isUplink && inbound.CanSpliceCopy == 2 { // TODO: enable uplink splice
inbound.CanSpliceCopy = 1 // inbound.CanSpliceCopy = 1
} // }
if !w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can have more than one ob if !w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can have more than one ob
w.ob.CanSpliceCopy = 1 w.ob.CanSpliceCopy = 1
} }
@@ -296,11 +296,16 @@ type VisionWriter struct {
// internal // internal
writeOnceUserUUID []byte writeOnceUserUUID []byte
directWriteCounter stats.Counter directWriteCounter stats.Counter
testseed []uint32
} }
func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound) *VisionWriter { func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink bool, ctx context.Context, conn net.Conn, ob *session.Outbound, testseed []uint32) *VisionWriter {
w := make([]byte, len(trafficState.UserUUID)) w := make([]byte, len(trafficState.UserUUID))
copy(w, trafficState.UserUUID) copy(w, trafficState.UserUUID)
if len(testseed) < 4 {
testseed = []uint32{900, 500, 900, 256}
}
return &VisionWriter{ return &VisionWriter{
Writer: writer, Writer: writer,
trafficState: trafficState, trafficState: trafficState,
@@ -309,6 +314,7 @@ func NewVisionWriter(writer buf.Writer, trafficState *TrafficState, isUplink boo
isUplink: isUplink, isUplink: isUplink,
conn: conn, conn: conn,
ob: ob, ob: ob,
testseed: testseed,
} }
} }
@@ -328,9 +334,9 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if !w.isUplink && inbound.CanSpliceCopy == 2 { if !w.isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1 inbound.CanSpliceCopy = 1
} }
if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // if w.isUplink && w.ob != nil && w.ob.CanSpliceCopy == 2 { // TODO: enable uplink splice
w.ob.CanSpliceCopy = 1 // w.ob.CanSpliceCopy = 1
} // }
} }
rawConn, _, writerCounter := UnwrapRawConn(w.conn) rawConn, _, writerCounter := UnwrapRawConn(w.conn)
w.Writer = buf.NewWriter(rawConn) w.Writer = buf.NewWriter(rawConn)
@@ -347,13 +353,14 @@ 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) // 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) return w.Writer.WriteMultiBuffer(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)) { 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
} }
@@ -364,13 +371,13 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
command = CommandPaddingDirect command = CommandPaddingDirect
} }
} }
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx) mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx, w.testseed)
*isPadding = false // padding going to end *isPadding = false // padding going to end
longPadding = false longPadding = false
continue continue
} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early } else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
*isPadding = false *isPadding = false
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx) mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
break break
} }
var command byte = CommandPaddingContinue var command byte = CommandPaddingContinue
@@ -380,12 +387,66 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
command = CommandPaddingDirect command = CommandPaddingDirect
} }
} }
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx) mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, longPadding, w.ctx, w.testseed)
} }
} }
return w.Writer.WriteMultiBuffer(mb) return w.Writer.WriteMultiBuffer(mb)
} }
// IsCompleteRecord Is complete tls data record
func IsCompleteRecord(buffer buf.MultiBuffer) bool {
b := make([]byte, buffer.Len())
if buffer.Copy(b) != int(buffer.Len()) {
panic("impossible bytes allocation")
}
var headerLen int = 5
var recordLen int
totalLen := len(b)
i := 0
for i < totalLen {
// record header: 0x17 0x3 0x3 + 2 bytes length
if headerLen > 0 {
data := b[i]
i++
switch headerLen {
case 5:
if data != 0x17 {
return false
}
case 4:
if data != 0x03 {
return false
}
case 3:
if data != 0x03 {
return false
}
case 2:
recordLen = int(data) << 8
case 1:
recordLen = recordLen | int(data)
}
headerLen--
} else if recordLen > 0 {
remaining := totalLen - i
if remaining < recordLen {
return false
} else {
i += recordLen
recordLen = 0
headerLen = 5
}
} else {
return false
}
}
if headerLen == 5 && recordLen == 0 {
return true
}
return false
}
// ReshapeMultiBuffer prepare multi buffer for padding structure (max 21 bytes) // ReshapeMultiBuffer prepare multi buffer for padding structure (max 21 bytes)
func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer { func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBuffer {
needReshape := 0 needReshape := 0
@@ -417,25 +478,25 @@ func ReshapeMultiBuffer(ctx context.Context, buffer buf.MultiBuffer) buf.MultiBu
buffer[i] = nil buffer[i] = nil
} }
buffer = buffer[:0] buffer = buffer[:0]
errors.LogInfo(ctx, "ReshapeMultiBuffer ", toPrint) errors.LogDebug(ctx, "ReshapeMultiBuffer ", toPrint)
return mb2 return mb2
} }
// XtlsPadding add padding to eliminate length signature during tls handshake // XtlsPadding add padding to eliminate length signature during tls handshake
func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context) *buf.Buffer { func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool, ctx context.Context, testseed []uint32) *buf.Buffer {
var contentLen int32 = 0 var contentLen int32 = 0
var paddingLen int32 = 0 var paddingLen int32 = 0
if b != nil { if b != nil {
contentLen = b.Len() contentLen = b.Len()
} }
if contentLen < 900 && longPadding { if contentLen < int32(testseed[0]) && longPadding {
l, err := rand.Int(rand.Reader, big.NewInt(500)) l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[1])))
if err != nil { if err != nil {
errors.LogDebugInner(ctx, err, "failed to generate padding") errors.LogDebugInner(ctx, err, "failed to generate padding")
} }
paddingLen = int32(l.Int64()) + 900 - contentLen paddingLen = int32(l.Int64()) + int32(testseed[2]) - contentLen
} else { } else {
l, err := rand.Int(rand.Reader, big.NewInt(256)) l, err := rand.Int(rand.Reader, big.NewInt(int64(testseed[3])))
if err != nil { if err != nil {
errors.LogDebugInner(ctx, err, "failed to generate padding") errors.LogDebugInner(ctx, err, "failed to generate padding")
} }
@@ -456,7 +517,7 @@ func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool
b = nil b = nil
} }
newbuffer.Extend(paddingLen) newbuffer.Extend(paddingLen)
errors.LogInfo(ctx, "XtlsPadding ", contentLen, " ", paddingLen, " ", command) errors.LogDebug(ctx, "XtlsPadding ", contentLen, " ", paddingLen, " ", command)
return newbuffer return newbuffer
} }
@@ -503,7 +564,7 @@ func XtlsUnpadding(b *buf.Buffer, s *TrafficState, isUplink bool, ctx context.Co
*remainingPadding = int32(data) << 8 *remainingPadding = int32(data) << 8
case 1: case 1:
*remainingPadding = *remainingPadding | int32(data) *remainingPadding = *remainingPadding | int32(data)
errors.LogInfo(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand) errors.LogDebug(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand)
} }
*remainingCommand-- *remainingCommand--
} else if *remainingContent > 0 { } else if *remainingContent > 0 {
@@ -562,11 +623,11 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
cipherSuite := b.BytesRange(43+sessionIdLen+1, 43+sessionIdLen+3) cipherSuite := b.BytesRange(43+sessionIdLen+1, 43+sessionIdLen+3)
trafficState.Cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1]) trafficState.Cipher = uint16(cipherSuite[0])<<8 | uint16(cipherSuite[1])
} else { } else {
errors.LogInfo(ctx, "XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello) errors.LogDebug(ctx, "XtlsFilterTls short server hello, tls 1.2 or older? ", b.Len(), " ", trafficState.RemainingServerHello)
} }
} else if bytes.Equal(TlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == TlsHandshakeTypeClientHello { } else if bytes.Equal(TlsClientHandShakeStart, startsBytes[:2]) && startsBytes[5] == TlsHandshakeTypeClientHello {
trafficState.IsTLS = true trafficState.IsTLS = true
errors.LogInfo(ctx, "XtlsFilterTls found tls client hello! ", buffer.Len()) errors.LogDebug(ctx, "XtlsFilterTls found tls client hello! ", buffer.Len())
} }
} }
if trafficState.RemainingServerHello > 0 { if trafficState.RemainingServerHello > 0 {
@@ -582,18 +643,18 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
} else if v != "TLS_AES_128_CCM_8_SHA256" { } else if v != "TLS_AES_128_CCM_8_SHA256" {
trafficState.EnableXtls = true trafficState.EnableXtls = true
} }
errors.LogInfo(ctx, "XtlsFilterTls found tls 1.3! ", b.Len(), " ", v) errors.LogDebug(ctx, "XtlsFilterTls found tls 1.3! ", b.Len(), " ", v)
trafficState.NumberOfPacketToFilter = 0 trafficState.NumberOfPacketToFilter = 0
return return
} else if trafficState.RemainingServerHello <= 0 { } else if trafficState.RemainingServerHello <= 0 {
errors.LogInfo(ctx, "XtlsFilterTls found tls 1.2! ", b.Len()) errors.LogDebug(ctx, "XtlsFilterTls found tls 1.2! ", b.Len())
trafficState.NumberOfPacketToFilter = 0 trafficState.NumberOfPacketToFilter = 0
return return
} }
errors.LogInfo(ctx, "XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello) errors.LogDebug(ctx, "XtlsFilterTls inconclusive server hello ", b.Len(), " ", trafficState.RemainingServerHello)
} }
if trafficState.NumberOfPacketToFilter <= 0 { if trafficState.NumberOfPacketToFilter <= 0 {
errors.LogInfo(ctx, "XtlsFilterTls stop filtering", buffer.Len()) errors.LogDebug(ctx, "XtlsFilterTls stop filtering", buffer.Len())
} }
} }
} }
@@ -675,7 +736,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
} }
} }
if splice { if splice {
errors.LogInfo(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 time.Sleep(time.Millisecond) // without this, there will be a rare ssl error for freedom splice
@@ -718,7 +779,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
} }
func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error { func readV(ctx context.Context, reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdater, readCounter stats.Counter) error {
errors.LogInfo(ctx, "CopyRawConn (maybe) readv") errors.LogDebug(ctx, "CopyRawConn (maybe) readv")
if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil { if err := buf.Copy(reader, writer, buf.UpdateActivity(timer), buf.AddToStatCounter(readCounter)); err != nil {
return errors.New("failed to process response").Base(err) return errors.New("failed to process response").Base(err)
} }
@@ -726,10 +787,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 := conn iConn := stat.TryUnwrapStatsConn(conn)
if statConn, ok := iConn.(*stat.CounterConnection); ok {
iConn = statConn.Connection
}
_, 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
View File
@@ -353,6 +353,11 @@ func EncodeUDPPacket(request *protocol.RequestHeader, data []byte) (*buf.Buffer,
b.Release() b.Release()
return nil, err return nil, err
} }
// if data is too large, return an empty buffer (drop too big data)
if b.Available() < int32(len(data)) {
b.Clear()
return b, nil
}
common.Must2(b.Write(data)) common.Must2(b.Write(data))
return b, nil return b, nil
} }
+1 -5
View File
@@ -147,11 +147,7 @@ func (s *Server) Network() []net.Network {
// Process implements proxy.Inbound.Process(). // Process implements proxy.Inbound.Process().
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
iConn := conn iConn := stat.TryUnwrapStatsConn(conn)
statConn, ok := iConn.(*stat.CounterConnection)
if ok {
iConn = statConn.Connection
}
sessionPolicy := s.policyManager.ForLevel(0) sessionPolicy := s.policyManager.ForLevel(0)
if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil { if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
+174
View File
@@ -0,0 +1,174 @@
# TUN network layer 3 input support
TUN interface support bridges the gap between network layer 3 and layer 7, introducing raw network input.
This functionality is targeted to assist applications/end devices that don't have proxy support, or can't run external applications (like Smart TV's). Making it possible to run Xray proxy right on network edge devices (routers) with support to route raw network traffic. \
Primary targets are Linux based router devices. Like most popular OpenWRT option. \
Although support for Windows is also implemented (see below).
## PLEASE READ FOLLOWING CAREFULLY
If you are not sure what this is and do you need it or not - you don't. \
This functionality is intended to be configured by network professionals, who understand the deployment case and scenarios. \
Plainly enabling it in the config probably will result nothing, or lock your router up in infinite network loop.
## DETAILS
Current implementation does not contain options to configure network level addresses, routing or rules.
Enabling the feature will result only tun interface up, and that's it. \
This is explicit decision, significantly simplifying implementation, and allowing any number of custom configurations, consumers could come up with. Network interface is OS level entity, and OS is what should manage it. \
Working configuration, is tun enabled in Xray config with specific name (e.g. xray0), and OS level configuration to manage "xray0" interface, applying routing and rules on interface up.
This way consistency of system level routing and rules is ensured from single place of responsibility - the OS itself. \
Examples of how to achieve this on a simple Linux system (Ubuntu with systemd-networkd) can be found at the end of this README.
Due to this inbound not actually being a proxy, the configuration ignore required listen and port options, and never listen on any port. \
Here is simple Xray config snippet to enable the inbound:
```
{
"inbounds": [
{
"port": 0,
"protocol": "tun",
"settings": {
"name": "xray0",
"MTU": 1492
}
}
],
```
## SUPPORTED FEATURES
- IPv4 and IPv6
- TCP and UDP
## LIMITATION
- No ICMP support
- Connections are established to any host, as connection success is only a mark of successful accepting packet for proxying. Hosts that are not accepting connections or don't even exists, will look like they opened a connection (SYN-ACK), and never send back a single byte, closing connection (RST) after some time. This is the side effect of the whole process actually being a proxy, and not real network layer 3 vpn
## CONSIDERATIONS
This feature being network level interface that require raw routing, bring some ambiguities that need to be taken in account. \
Xray-core itself is connecting to its uplinks on a network level, therefore, it's really simple to lock network up in an infinite loop, when trying to pass "everything through Xray". \
You can't just route 0.0.0.0/0 through xray0 interface, as that will result Xray-core itself try to reach its uplink through xray0 interface, resulting infinite network loop.
There are several ways to address this:
- Approach 1: \
Add precise static route to Xray upstreams, having them always routed through static internet gateway.
E.g. when 123.123.123.123 is the Xray VLESS uplink, this network configuration will work just fine:
```
ip route add 123.123.123.123/32 via <provider internet gateway ip>
ip route add 0.0.0.0/0 dev xray0
```
This has disadvantages, - a lot of conditions must be kept static: internet gateway address, xray uplink ip address, and so on.
- Approach 1-b: \
Route only specific networks through Xray, keeping the default gateway unchanged.
This can be done in many different ways, using ip sets, routing daemons like BGP peers, etc... All you need to do is to route the paths through xray0 dev.
The disadvantage in this case is smaller, - you need to make sure the uplink will not become part of those sets and that's it. Can easily be done with route metric priorities.
- Approach 2: \
Separate main route table and Xray route table with default gateways pointing to different destinations.
This way you can achieve full protection of hosts behind the router, keeping router configuration as flexible as desired. \
There are two ways to do that: \
Either configure xray0 interface to appear and operate as default gateway in a separate route table, e.g. 1001. Then mark and route protected traffic by ip rules to that table. \
It's a simplest way to make a "non-damaging" configuration, when the only thing you need to do to enable/disable proxying is to flip the ip rules off. Which is also a disadvantage of itself - if by accident ip rules will get disabled, the traffic will spill out of the internet interface unprotected. \
Or, other way around, move default routing to a separate route table, so that all usual routing information is set in e.g. route table 1000,
and Xray interface operate in the main route table. This will allow proper flexibility, but you need to ensure traffic from the Xray process, running on the router, is marked to get routed through table 1000. This again can be achieved in same ways, using ip rules and iptable rules combinations. \
Big advantage of that, is that traffic of the route itself is going to be wrapped into the proxy, including DNS queries, without any additional effort. Although, the disadvantage of that is, that in any case proxying stops (uplink dies, Xray hangs, encryption start to flap), it will result complete internet inaccessibility. \
Any approach is applicable, and if you know what you are doing (which is expected, if you read until here) you do understand which one you want and can manage. \
### Important:
TUN is network level entity, therefore communication through it, is always ip to ip, there are no host names. \
Therefore, DNS resolution will always happen before traffic even enter the interface (it will be separate ip-to-ip packets/connections to resolve hostnames). \
You always need to consider that DNS queries in any configuration you chose, most likely, will originate from the router itself (hosts behind the router access router DNS, router DNS fire queries to the outside).
Without proper addressing that, DNS queries will expose actual destinations/websites accessed through the router. \
To address that you can as ignore (not use) DNS of the router (just delegate some public DNS in DHCP configuration to your devices), or make sure routing rules are configured the way, DNS resolution of the router itself runs through Xray interface/routing table.
You also need to remember that local traffic of the router (e.g. DNS, firmware updates, etc.), is subject of firewall rules as outcoming/incoming traffic (not forward).
If you have restrictive firewall, you need to allow input/output traffic through xray0 interface, for it to properly dispatch and reach the OS back.
Additionally, working with two route tables is not taken lightly by Linux, and sometimes make it panic about "martian packets", which it calls the packets arriving through interfaces it does not expect they could arrive from. \
It was just a warning until recent kernel versions, but now traffic is often dropped. \
In simple case this can be just disabled with
```
/usr/sbin/sysctl -w net.ipv4.conf.all.rp_filter=0
```
But proper approach should be defining your route tables fully and consistently, adding all routes corresponding to traffic that flow through them.
## EXAMPLES
systemd-networkd \
configuration file you can place in /etc/systemd/networkd as 90-xray0.network
which will configure xray0 interface and routing using route table 1001, when the interface will appear in the system (Xray starts). And deconfigure when disappears.
```
[Match]
Name = xray0
[Network]
KeepConfiguration = yes
[Link]
ActivationPolicy = manual
RequiredForOnline = no
[Route]
Table = 1001
Destination = 0.0.0.0/0
[RoutingPolicyRule]
From = 192.168.0.0/24
Table = 1001
```
RoutingPolicyRule will add the record into ip rules, that will funnel all traffic from 192.168.0.0/24 through the table 1001 \
Please note that for ideal configuration of the routing you will also need to add the route to 192.168.0.0/24 to the route table 1001.
You can do that e.g. in the file, describing your adapter serving local network (e.g. 10-br0.network), additionally to its native properties like:
```
[Match]
Name = br0
Type = bridge
[Network]
...skip...
Address = 192.168.0.1/24
...skip...
[Route]
Table = 1001
Destination = 192.168.0.0/24
PreferredSource = 192.168.0.1
Scope = link
```
All in all systemd-networkd and its derivatives (like netplan or NetworkManager) provide all means to configure your networking, according to your wish, that will ensure network consistency of xray0 interface coming up and down, relative to other network configuration like internet interfaces, nat rules and so on.
## WINDOWS SUPPORT
Windows version of the same functionality is implemented through Wintun library. \
To make it start, wintun.dll specific for your Windows/arch must be present next to Xray.exe binary.
After the start network adapter with the name you chose in the config will be created in the system, and exist while Xray is running.
You can give the adapter ip address manually, you can live Windows to give it autogenerated ip address (which take few seconds), it doesn't matter, the traffic going _through_ the interface will be forwarded into the app for proxying. \
Minimal configuration that will work for local machine is routing passing the traffic on-link through the interface.
You will need the interface id for that, unfortunately it is going to change with every Xray start due to implementation ambiguity between Xray and wintun driver.
You can find the interface id with the command
```
route print
```
it will be in the list of interfaces on the top of the output
```
===========================================================================
Interface List
8...cc cc cc cc cc cc ......Realtek PCIe GbE Family Controller
47...........................Xray Tunnel
1...........................Software Loopback Interface 1
===========================================================================
```
In this case the interface id is "47". \
Then you can add on-link route through the adapter with (example) command
```
route add 1.1.1.1 mask 255.0.0.0 0.0.0.0 if 47
```
Note on ipv6 support. \
Despite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \
So everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine
+1
View File
@@ -0,0 +1 @@
package tun
+149
View File
@@ -0,0 +1,149 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc v6.30.2
// source: proxy/tun/config.proto
package tun
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
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
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
MTU uint32 `protobuf:"varint,2,opt,name=MTU,proto3" json:"MTU,omitempty"`
UserLevel uint32 `protobuf:"varint,3,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_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_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_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Config) GetMTU() uint32 {
if x != nil {
return x.MTU
}
return 0
}
func (x *Config) GetUserLevel() uint32 {
if x != nil {
return x.UserLevel
}
return 0
}
var File_config_proto protoreflect.FileDescriptor
var file_config_proto_rawDesc = []byte{
0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x74, 0x75, 0x6e, 0x22, 0x4d,
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03,
0x4d, 0x54, 0x55, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x4d, 0x54, 0x55, 0x12, 0x1d,
0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01,
0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x4c, 0x0a,
0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
0x74, 0x75, 0x6e, 0x50, 0x01, 0x5a, 0x23, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x75, 0x6e, 0xaa, 0x02, 0x0e, 0x58, 0x72, 0x61,
0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x54, 0x75, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_config_proto_rawDescOnce sync.Once
file_config_proto_rawDescData = file_config_proto_rawDesc
)
func file_config_proto_rawDescGZIP() []byte {
file_config_proto_rawDescOnce.Do(func() {
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
})
return file_config_proto_rawDescData
}
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.proxy.tun.Config
}
var file_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_config_proto_init() }
func file_config_proto_init() {
if File_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_config_proto_goTypes,
DependencyIndexes: file_config_proto_depIdxs,
MessageInfos: file_config_proto_msgTypes,
}.Build()
File_config_proto = out.File
file_config_proto_rawDesc = nil
file_config_proto_goTypes = nil
file_config_proto_depIdxs = nil
}
+13
View File
@@ -0,0 +1,13 @@
syntax = "proto3";
package xray.proxy.tun;
option csharp_namespace = "Xray.Proxy.Tun";
option go_package = "github.com/xtls/xray-core/proxy/tun";
option java_package = "com.xray.proxy.tun";
option java_multiple_files = true;
message Config {
string name = 1;
uint32 MTU = 2;
uint32 user_level = 3;
}
+142
View File
@@ -0,0 +1,142 @@
package tun
import (
"context"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors"
"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/transport"
"github.com/xtls/xray-core/transport/internet/stat"
)
// Handler is managing object that tie together tun interface, ip stack and dispatch connections to the routing
type Handler struct {
ctx context.Context
config *Config
stack Stack
policyManager policy.Manager
dispatcher routing.Dispatcher
}
// ConnectionHandler interface with the only method that stack is going to push new connections to
type ConnectionHandler interface {
HandleConnection(conn net.Conn, destination net.Destination)
}
// Handler implements ConnectionHandler
var _ ConnectionHandler = (*Handler)(nil)
func (t *Handler) policy() policy.Session {
p := t.policyManager.ForLevel(t.config.UserLevel)
return p
}
// Init the Handler instance with necessary parameters
func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {
var err error
t.ctx = core.ToBackgroundDetachedContext(ctx)
t.policyManager = pm
t.dispatcher = dispatcher
tunName := t.config.Name
tunOptions := TunOptions{
Name: tunName,
MTU: t.config.MTU,
}
tunInterface, err := NewTun(tunOptions)
if err != nil {
return err
}
errors.LogInfo(t.ctx, tunName, " created")
tunStackOptions := StackOptions{
Tun: tunInterface,
IdleTimeout: pm.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
}
tunStack, err := NewStack(t.ctx, tunStackOptions, t)
if err != nil {
_ = tunInterface.Close()
return err
}
err = tunStack.Start()
if err != nil {
_ = tunStack.Close()
_ = tunInterface.Close()
return err
}
err = tunInterface.Start()
if err != nil {
_ = tunStack.Close()
_ = tunInterface.Close()
return err
}
t.stack = tunStack
errors.LogInfo(t.ctx, tunName, " up")
return nil
}
// HandleConnection pass the connection coming from the ip stack to the routing dispatcher
func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
sid := session.NewID()
ctx := c.ContextWithID(t.ctx, sid)
errors.LogInfo(ctx, "processing connection from: ", conn.RemoteAddr())
inbound := session.Inbound{}
inbound.Name = "tun"
inbound.CanSpliceCopy = 1
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
inbound.User = &protocol.MemoryUser{
Level: t.config.UserLevel,
}
ctx = session.ContextWithInbound(ctx, &inbound)
ctx = session.SubContextFromMuxInbound(ctx)
link := &transport.Link{
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
Writer: buf.NewWriter(conn),
}
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
errors.LogError(ctx, errors.New("connection closed").Base(err))
return
}
errors.LogInfo(ctx, "connection completed")
}
// Network implements proxy.Inbound
// and exists only to comply to proxy interface, declaring it doesn't listen on any network,
// making the process not open any port for this inbound (input will be network interface)
func (t *Handler) Network() []net.Network {
return []net.Network{}
}
// Process implements proxy.Inbound
// and exists only to comply to proxy interface, which should never get any inputs due to no listening ports
func (t *Handler) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
return nil
}
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
t := &Handler{config: config.(*Config)}
err := core.RequireFeatures(ctx, func(pm policy.Manager, dispatcher routing.Dispatcher) error {
return t.Init(ctx, pm, dispatcher)
})
return t, err
}))
}
+17
View File
@@ -0,0 +1,17 @@
package tun
import (
"time"
)
// Stack interface implement ip protocol stack, bridging raw network packets and data streams
type Stack interface {
Start() error
Close() error
}
// StackOptions for the stack implementation
type StackOptions struct {
Tun Tun
IdleTimeout time.Duration
}
+206
View File
@@ -0,0 +1,206 @@
package tun
import (
"context"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
)
const (
defaultNIC tcpip.NICID = 1
tcpRXBufMinSize = tcp.MinBufferSize
tcpRXBufDefSize = tcp.DefaultSendBufferSize
tcpRXBufMaxSize = 8 << 20 // 8MiB
tcpTXBufMinSize = tcp.MinBufferSize
tcpTXBufDefSize = tcp.DefaultReceiveBufferSize
tcpTXBufMaxSize = 6 << 20 // 6MiB
)
// stackGVisor is ip stack implemented by gVisor package
type stackGVisor struct {
ctx context.Context
tun GVisorTun
idleTimeout time.Duration
handler *Handler
stack *stack.Stack
endpoint stack.LinkEndpoint
}
// GVisorTun implements a bridge to connect gVisor ip stack to tun interface
type GVisorTun interface {
newEndpoint() (stack.LinkEndpoint, error)
}
// NewStack builds new ip stack (using gVisor)
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
gStack := &stackGVisor{
ctx: ctx,
tun: options.Tun.(GVisorTun),
idleTimeout: options.IdleTimeout,
handler: handler,
}
return gStack, nil
}
// Start is called by Handler to bring stack to life
func (t *stackGVisor) Start() error {
linkEndpoint, err := t.tun.newEndpoint()
if err != nil {
return err
}
ipStack, err := createStack(linkEndpoint)
if err != nil {
return err
}
tcpForwarder := tcp.NewForwarder(ipStack, 0, 65535, func(r *tcp.ForwarderRequest) {
go func(r *tcp.ForwarderRequest) {
var wq waiter.Queue
var id = r.ID()
// Perform a TCP three-way handshake.
ep, err := r.CreateEndpoint(&wq)
if err != nil {
errors.LogError(t.ctx, err.String())
r.Complete(true)
return
}
options := ep.SocketOptions()
options.SetKeepAlive(false)
options.SetReuseAddress(true)
options.SetReusePort(true)
t.handler.HandleConnection(
gonet.NewTCPConn(&wq, ep),
// local address on the gVisor side is connection destination
net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)),
)
// close the socket
ep.Close()
// send connection complete upstream
r.Complete(false)
}(r)
})
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
udpForwarder := udp.NewForwarder(ipStack, func(r *udp.ForwarderRequest) {
go func(r *udp.ForwarderRequest) {
var wq waiter.Queue
var id = r.ID()
ep, err := r.CreateEndpoint(&wq)
if err != nil {
errors.LogError(t.ctx, err.String())
return
}
options := ep.SocketOptions()
options.SetReuseAddress(true)
options.SetReusePort(true)
t.handler.HandleConnection(
gonet.NewUDPConn(&wq, ep),
// local address on the gVisor side is connection destination
net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)),
)
// close the socket
ep.Close()
}(r)
})
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
t.stack = ipStack
t.endpoint = linkEndpoint
return nil
}
// Close is called by Handler to shut down the stack
func (t *stackGVisor) Close() error {
if t.stack == nil {
return nil
}
t.endpoint.Attach(nil)
t.stack.Close()
for _, endpoint := range t.stack.CleanupEndpoints() {
endpoint.Abort()
}
return nil
}
// createStack configure gVisor ip stack
func createStack(ep stack.LinkEndpoint) (*stack.Stack, error) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
HandleLocal: false,
}
gStack := stack.New(opts)
err := gStack.CreateNIC(defaultNIC, ep)
if err != nil {
return nil, errors.New(err.String())
}
gStack.SetRouteTable([]tcpip.Route{
{Destination: header.IPv4EmptySubnet, NIC: defaultNIC},
{Destination: header.IPv6EmptySubnet, NIC: defaultNIC},
})
err = gStack.SetSpoofing(defaultNIC, true)
if err != nil {
return nil, errors.New(err.String())
}
err = gStack.SetPromiscuousMode(defaultNIC, true)
if err != nil {
return nil, errors.New(err.String())
}
cOpt := tcpip.CongestionControlOption("cubic")
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &cOpt)
sOpt := tcpip.TCPSACKEnabled(true)
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
mOpt := tcpip.TCPModerateReceiveBufferOption(true)
gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)
tcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{
Min: tcpRXBufMinSize,
Default: tcpRXBufDefSize,
Max: tcpRXBufMaxSize,
}
err = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpRXBufOpt)
if err != nil {
return nil, errors.New(err.String())
}
tcpTXBufOpt := tcpip.TCPSendBufferSizeRangeOption{
Min: tcpTXBufMinSize,
Default: tcpTXBufDefSize,
Max: tcpTXBufMaxSize,
}
err = gStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpTXBufOpt)
if err != nil {
return nil, errors.New(err.String())
}
return gStack, nil
}
+13
View File
@@ -0,0 +1,13 @@
package tun
// Tun interface implements tun interface interaction
type Tun interface {
Start() error
Close() error
}
// TunOptions for tun interface implementation
type TunOptions struct {
Name string
MTU uint32
}
+58
View File
@@ -0,0 +1,58 @@
//go:build android
package tun
import (
"context"
"strconv"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
type AndroidTun struct {
tunFd int
options TunOptions
}
// DefaultTun implements Tun
var _ Tun = (*AndroidTun)(nil)
// DefaultTun implements GVisorTun
var _ GVisorTun = (*AndroidTun)(nil)
// NewTun builds new tun interface handler
func NewTun(options TunOptions) (Tun, error) {
fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" }))
errors.LogInfo(context.Background(), "read Android Tun Fd ", fd, err)
err = unix.SetNonblock(fd, true)
if err != nil {
_ = unix.Close(fd)
return nil, err
}
return &AndroidTun{
tunFd: fd,
options: options,
}, nil
}
func (t *AndroidTun) Start() error {
return nil
}
func (t *AndroidTun) Close() error {
return nil
}
func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd},
MTU: t.options.MTU,
RXChecksumOffload: true,
})
}
+34
View File
@@ -0,0 +1,34 @@
//go:build !linux && !windows && !android
package tun
import (
"github.com/xtls/xray-core/common/errors"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
type DefaultTun struct {
}
// DefaultTun implements Tun
var _ Tun = (*DefaultTun)(nil)
// DefaultTun implements GVisorTun
var _ GVisorTun = (*DefaultTun)(nil)
// NewTun builds new tun interface handler
func NewTun(options TunOptions) (Tun, error) {
return nil, errors.New("Tun is not supported on your platform")
}
func (t *DefaultTun) Start() error {
return errors.New("Tun is not supported on your platform")
}
func (t *DefaultTun) Close() error {
return errors.New("Tun is not supported on your platform")
}
func (t *DefaultTun) newEndpoint() (stack.LinkEndpoint, error) {
return nil, errors.New("Tun is not supported on your platform")
}
+120
View File
@@ -0,0 +1,120 @@
//go:build linux && !android
package tun
import (
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// LinuxTun is an object that handles tun network interface on linux
// current version is heavily stripped to do nothing more,
// then create a network interface, to be provided as file descriptor to gVisor ip stack
type LinuxTun struct {
tunFd int
tunLink netlink.Link
options TunOptions
}
// LinuxTun implements Tun
var _ Tun = (*LinuxTun)(nil)
// LinuxTun implements GVisorTun
var _ GVisorTun = (*LinuxTun)(nil)
// NewTun builds new tun interface handler (linux specific)
func NewTun(options TunOptions) (Tun, error) {
tunFd, err := open(options.Name)
if err != nil {
return nil, err
}
tunLink, err := setup(options.Name, int(options.MTU))
if err != nil {
_ = unix.Close(tunFd)
return nil, err
}
linuxTun := &LinuxTun{
tunFd: tunFd,
tunLink: tunLink,
options: options,
}
return linuxTun, nil
}
// open the file that implements tun interface in the OS
func open(name string) (int, error) {
fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
if err != nil {
return -1, err
}
ifr, err := unix.NewIfreq(name)
if err != nil {
_ = unix.Close(fd)
return 0, err
}
flags := unix.IFF_TUN | unix.IFF_NO_PI
ifr.SetUint16(uint16(flags))
err = unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)
if err != nil {
_ = unix.Close(fd)
return 0, err
}
err = unix.SetNonblock(fd, true)
if err != nil {
_ = unix.Close(fd)
return 0, err
}
return fd, nil
}
// setup the interface through netlink socket
func setup(name string, MTU int) (netlink.Link, error) {
tunLink, err := netlink.LinkByName(name)
if err != nil {
return nil, err
}
err = netlink.LinkSetMTU(tunLink, MTU)
if err != nil {
_ = netlink.LinkSetDown(tunLink)
return nil, err
}
return tunLink, nil
}
// Start is called by handler to bring tun interface to life
func (t *LinuxTun) Start() error {
err := netlink.LinkSetUp(t.tunLink)
if err != nil {
return err
}
return nil
}
// Close is called to shut down the tun interface
func (t *LinuxTun) Close() error {
_ = netlink.LinkSetDown(t.tunLink)
_ = unix.Close(t.tunFd)
return nil
}
// newEndpoint builds new gVisor stack.LinkEndpoint from the tun interface file descriptor
func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd},
MTU: t.options.MTU,
RXChecksumOffload: true,
})
}
+84
View File
@@ -0,0 +1,84 @@
//go:build windows
package tun
import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wintun"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// WindowsTun is an object that handles tun network interface on Windows
// current version is heavily stripped to do nothing more,
// then create a network interface, to be provided as endpoint to gVisor ip stack
type WindowsTun struct {
options TunOptions
adapter *wintun.Adapter
session wintun.Session
MTU uint32
}
// WindowsTun implements Tun
var _ Tun = (*WindowsTun)(nil)
// WindowsTun implements GVisorTun
var _ GVisorTun = (*WindowsTun)(nil)
// NewTun creates a Wintun interface with the given name. Should a Wintun
// interface with the same name exist, it tried to be reused.
func NewTun(options TunOptions) (Tun, error) {
// instantiate wintun adapter
adapter, err := open(options.Name)
if err != nil {
return nil, err
}
// start the interface with ring buffer capacity of 8 MiB
session, err := adapter.StartSession(0x800000)
if err != nil {
_ = adapter.Close()
return nil, err
}
tun := &WindowsTun{
options: options,
adapter: adapter,
session: session,
// there is currently no iphndl.dll support, which is the netlink library for windows
// so there is nowhere to change MTU for the Wintun interface, and we take its default value
MTU: wintun.PacketSizeMax,
}
return tun, nil
}
func open(name string) (*wintun.Adapter, error) {
var guid *windows.GUID
// try to open existing adapter by name
adapter, err := wintun.OpenAdapter(name)
if err == nil {
return adapter, nil
}
// try to create adapter anew
adapter, err = wintun.CreateAdapter(name, "Xray", guid)
if err == nil {
return adapter, nil
}
return nil, err
}
func (t *WindowsTun) Start() error {
return nil
}
func (t *WindowsTun) Close() error {
t.session.End()
_ = t.adapter.Close()
return nil
}
// newEndpoint builds new gVisor stack.LinkEndpoint (WintunEndpoint) on top of WindowsTun
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
return &WintunEndpoint{tun: t}, nil
}
+180
View File
@@ -0,0 +1,180 @@
//go:build windows
package tun
import (
"context"
"errors"
_ "unsafe"
"golang.org/x/sys/windows"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// WintunEndpoint implements GVisor stack.LinkEndpoint
var _ stack.LinkEndpoint = (*WintunEndpoint)(nil)
type WintunEndpoint struct {
tun *WindowsTun
dispatcherCancel context.CancelFunc
}
var ErrUnsupportedNetworkProtocol = errors.New("unsupported ip version")
//go:linkname procyield runtime.procyield
func procyield(cycles uint32)
func (e *WintunEndpoint) MTU() uint32 {
return e.tun.MTU
}
func (e *WintunEndpoint) SetMTU(mtu uint32) {
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
}
func (e *WintunEndpoint) MaxHeaderLength() uint16 {
return 0
}
func (e *WintunEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (e *WintunEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
}
func (e *WintunEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityRXChecksumOffload
}
func (e *WintunEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
if e.dispatcherCancel != nil {
e.dispatcherCancel()
e.dispatcherCancel = nil
}
if dispatcher != nil {
ctx, cancel := context.WithCancel(context.Background())
go e.dispatchLoop(ctx, dispatcher)
e.dispatcherCancel = cancel
}
}
func (e *WintunEndpoint) IsAttached() bool {
return e.dispatcherCancel != nil
}
func (e *WintunEndpoint) Wait() {
}
func (e *WintunEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (e *WintunEndpoint) AddHeader(buffer *stack.PacketBuffer) {
// tun interface doesn't have link layer header, it will be added by the OS
}
func (e *WintunEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
return true
}
func (e *WintunEndpoint) Close() {
if e.dispatcherCancel != nil {
e.dispatcherCancel()
e.dispatcherCancel = nil
}
}
func (e *WintunEndpoint) SetOnCloseAction(f func()) {
}
func (e *WintunEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
var n int
// for all packets in the list to send
for _, packetBuffer := range packetBufferList.AsSlice() {
// request buffer from Wintun
packet, err := e.tun.session.AllocateSendPacket(packetBuffer.Size())
if err != nil {
return n, &tcpip.ErrAborted{}
}
// copy the bytes of slices that compose the packet into the allocated buffer
var index int
for _, packetElement := range packetBuffer.AsSlices() {
index += copy(packet[index:], packetElement)
}
// signal Wintun to send that buffer as the packet
e.tun.session.SendPacket(packet)
n++
}
return n, nil
}
func (e *WintunEndpoint) readPacket() (tcpip.NetworkProtocolNumber, *stack.PacketBuffer, error) {
packet, err := e.tun.session.ReceivePacket()
if err != nil {
return 0, nil, err
}
var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(packet) {
case header.IPv4Version:
networkProtocol = header.IPv4ProtocolNumber
case header.IPv6Version:
networkProtocol = header.IPv6ProtocolNumber
default:
e.tun.session.ReleaseReceivePacket(packet)
return 0, nil, ErrUnsupportedNetworkProtocol
}
packetBuffer := buffer.MakeWithView(buffer.NewViewWithData(packet))
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: packetBuffer,
IsForwardedPacket: true,
OnRelease: func() {
e.tun.session.ReleaseReceivePacket(packet)
},
})
return networkProtocol, pkt, nil
}
func (e *WintunEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
readWait := e.tun.session.ReadWaitEvent()
for {
select {
case <-ctx.Done():
return
default:
networkProtocolNumber, packet, err := e.readPacket()
// read queue empty, yield slightly, wait for the spinlock, retry
if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
procyield(1)
_, _ = windows.WaitForSingleObject(readWait, windows.INFINITE)
continue
}
// discard unknown network protocol packet
if errors.Is(err, ErrUnsupportedNetworkProtocol) {
continue
}
// stop dispatcher loop on any other interface failure
if err != nil {
e.Attach(nil)
continue
}
// dispatch the buffer to the stack
dispatcher.DeliverNetworkPacket(networkProtocolNumber, packet)
// signal the buffer that it can be released
packet.DecRef()
}
}
}
+7
View File
@@ -22,6 +22,8 @@ func (a *Account) AsAccount() (protocol.Account, error) {
Seconds: a.Seconds, Seconds: a.Seconds,
Padding: a.Padding, Padding: a.Padding,
Reverse: a.Reverse, Reverse: a.Reverse,
Testpre: a.Testpre,
Testseed: a.Testseed,
}, nil }, nil
} }
@@ -38,6 +40,9 @@ type MemoryAccount struct {
Padding string Padding string
Reverse *Reverse Reverse *Reverse
Testpre uint32
Testseed []uint32
} }
// Equals implements protocol.Account.Equals(). // Equals implements protocol.Account.Equals().
@@ -58,5 +63,7 @@ func (a *MemoryAccount) ToProto() proto.Message {
Seconds: a.Seconds, Seconds: a.Seconds,
Padding: a.Padding, Padding: a.Padding,
Reverse: a.Reverse, Reverse: a.Reverse,
Testpre: a.Testpre,
Testseed: a.Testseed,
} }
} }

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