Compare commits

...

139 Commits

Author SHA1 Message Date
RPRX
bd86732f68 v25.8.3
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-08-03 11:09:00 +00:00
风扇滑翔翼
d4f11e6d68 UDS: Check address before listen (#4945)
Fixes https://github.com/XTLS/Xray-core/issues/4944
2025-08-03 11:02:55 +00:00
风扇滑翔翼
00f3147242 app/proxyman/inbound/inbound.go: Fix ListHandlers() (#4976)
Fixes https://github.com/XTLS/Xray-core/issues/4956
2025-08-03 11:02:08 +00:00
风扇滑翔翼
7cbf5b004c TLS ECH client: echForceQuery "full" / "half" / "none" (default) (#4973)
https://github.com/XTLS/Xray-core/pull/4971#issuecomment-3148113203
2025-08-03 10:15:42 +00:00
patterniha
87fff12fd9 Root config: Add version config (min and max) (#4970)
https://github.com/XTLS/Xray-core/pull/4968#issuecomment-3146592323
2025-08-03 01:06:47 +00:00
patterniha
a02723e63f TLS ECH client: Use chrome-fingerprint and add padding; Add "h2c" and echSockopt; Fix some issues (#4949)
Completes https://github.com/XTLS/Xray-core/pull/3813
2025-08-02 16:05:00 +00:00
RPRX
146b14ab55 Tunnel inbound: Add portMap config (local listening port -> remote specified address/port)
Completes https://github.com/XTLS/Xray-core/pull/4968
2025-08-02 14:58:56 +00:00
风扇滑翔翼
b2829219a0 TLS ECH client: Add echForceQuery config (#4947)
https://github.com/XTLS/Xray-core/pull/4947#issuecomment-3124359776
2025-08-01 11:25:15 +00:00
RPRX
116cd70a3a Dokodemo-door: Add simple tunnel config (alias and default values) (#4968)
dokodemo-door -> tunnel
freedom -> direct
blackhole -> block

https://github.com/XTLS/Xray-core/discussions/4966#discussioncomment-13948546
https://github.com/XTLS/Xray-core/pull/4967#issuecomment-3143951306
2025-08-01 11:20:53 +00:00
yuhan6665
c569f478af Update readme 2025-07-27 19:02:27 -04:00
RPRX
b6b51c51c8 v25.7.26
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-07-26 09:07:56 +00:00
风扇滑翔翼
fb7a9d8d61 TLS client & server: Support Encrypted Client Hello (ECH) (#3813)
b9a72a4a26

---------

Co-authored-by: yuhan6665 <1588741+yuhan6665@users.noreply.github.com>
2025-07-26 08:47:27 +00:00
nobody
3fe02a658a Commands: Add adu/rmu inbound user management to API (#4943)
Co-authored-by: nobody <nobody@nowhere.mars>
2025-07-26 08:40:04 +00:00
RPRX
5f93ff6c3a REALITY config: mldsa65Seed and privateKey can not be the same value
Otherwise the point of using ML-DSA-65 is lost.
2025-07-26 08:36:58 +00:00
patterniha
10376f5b4d Freedom UDP: Fix some cone uses like STUN,... when address is domain (#4942)
https://github.com/XTLS/Xray-core/issues/2962#issuecomment-3120472154
2025-07-26 01:59:15 +00:00
风扇滑翔翼
1ea00fad81 UDP listener: Allow listening on "localhost" (#4940)
Fixes https://github.com/XTLS/Xray-core/issues/4939
2025-07-26 01:27:35 +00:00
RPRX
cfcf2a63d1 v25.7.25
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-07-25 14:59:43 +00:00
Meow
66025f2889 Workflows: Fix github.ref_name sometimes is empty when building Docker images (#4937)
From https://github.com/XTLS/Xray-core/pull/4809
2025-07-25 14:47:12 +00:00
风扇滑翔翼
c9cd26d6d3 UDP: Remove removeRay()'s error log (#4936)
From https://github.com/XTLS/Xray-core/pull/4899
2025-07-25 14:40:26 +00:00
RPRX
caee152adf Update github.com/xtls/reality to 20250725142056
5b52a03d4f
2025-07-25 14:28:47 +00:00
风扇滑翔翼
eb433d9462 Inbounds & Outbounds: TCP KeepAlive better default value (#4931)
From https://github.com/XTLS/Xray-core/pull/4927
2025-07-25 12:06:05 +00:00
风扇滑翔翼
87d8b97d9a Commands: Output certificate chain's total length in tls ping (#4933)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-07-25 10:18:26 +00:00
风扇滑翔翼
9d15ecf1f9 REALITY client: Fix log when printing "is using X25519MLKEM768..." (#4929) 2025-07-25 07:11:35 +00:00
RPRX
4f45c5faa5 v25.7.24
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-07-24 02:19:08 +00:00
RPRX
26de58933f README.md: Update Donation & NFTs
https://opensea.io/collection/xtls
2025-07-24 02:08:03 +00:00
RPRX
31b508d372 REALITY config: Convert mldsa65Seed to its private key later 2025-07-24 01:31:07 +00:00
风扇滑翔翼
955a569181 REALITY config: Allow mldsa65 fields to be empty (#4924) 2025-07-24 01:15:48 +00:00
RPRX
d141d01d0c v25.7.23
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-07-23 12:55:05 +00:00
patterniha
4e826abebf Chore: Three small fixes (#4922) 2025-07-23 12:53:37 +00:00
RPRX
4433641e30 Update github.com/xtls/reality to 20250723121014
c6320729d9
2025-07-23 12:39:10 +00:00
RPRX
a196a16c55 README.md: Add Project X NFT's image & link
https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1
2025-07-23 12:21:40 +00:00
Stan B.
8c0bf15901 README.md: Add GoXRay to macOS & Linux Clients (#4260)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-07-23 12:17:18 +00:00
Happ-dev
dbd9125686 README.md: Add Happ to macOS x64 & tvOS Clients (#4921)
Co-authored-by: y.sivushkin <y.sivushkin@corp.101xp.com>
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-07-23 10:52:59 +00:00
j2rong4cn
923b5d7229 DNS hosts: Support returning RCode (#4681) 2025-07-23 10:11:43 +00:00
风扇滑翔翼
f90fae22aa UDP: Fix removeRay will close a connEntry that not belongs to it (#4899) 2025-07-23 10:10:12 +00:00
patterniha
b065595f58 Reverse: portal-worker should not be closed before making sure there is at least one other active worker (#4869) 2025-07-23 10:09:17 +00:00
patterniha
308f8a7459 MUX: Refine and Fix some occasional problems (#4861)
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-07-23 10:09:04 +00:00
patterniha
050f596e8f Freedom: Fix UDP reply mismatch-address (#4816) 2025-07-23 10:07:24 +00:00
风扇滑翔翼
3b47d0846e Freedom: Cache UDP resolve result (#4804) 2025-07-23 10:02:27 +00:00
dependabot[bot]
7f23a1cb65 Bump google.golang.org/grpc from 1.73.0 to 1.74.2 (#4919)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.73.0 to 1.74.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.73.0...v1.74.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.74.2
  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-07-23 02:37:58 +00:00
RPRX
446315cf1f REALITY protocol: Add optional Post-Quantum ML-DSA-65 verification for cert's ExtraExtensions (#4915)
00881f6740
2025-07-23 02:29:11 +00:00
RPRX
eed05549fc Revert "Bump google.golang.org/grpc from 1.73.0 to 1.74.0 (#4905)" (#4914)
This reverts commit 6afd721ced.
2025-07-21 04:55:43 +00:00
dependabot[bot]
2b4a8d235b Bump github.com/quic-go/quic-go from 0.53.0 to 0.54.0 (#4913)
---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 02:19:47 +00:00
风扇滑翔翼
83686ebfaa DNS outbound: Prevent panic from rejecting invalid domain (#4903)
Fixes https://github.com/XTLS/Xray-core/pull/4824#issuecomment-3078811352
2025-07-19 01:52:26 +00:00
Meow
79c6f99384 Workflows: Cleaner Docker builds, support for manual exec and pre-release (#4809) 2025-07-19 01:29:25 +00:00
Random Guy
ca8ef209a7 Stats API: Return status "not found" instead of "unknown" (#4860) 2025-07-19 01:21:18 +00:00
风扇滑翔翼
cbcab89c7e Commands: Display Post-Quantum key exchange in tls ping (#4857)
https://github.com/XTLS/Xray-core/pull/4857#issuecomment-3064964301
2025-07-19 01:14:56 +00:00
xqzr
abd551e9f7 VLESS fallbacks: dest defaults to "127.0.0.1" -> "localhost" (#4840)
https://github.com/XTLS/Xray-examples/issues/234#issuecomment-3091319391
2025-07-19 00:47:43 +00:00
o_O
10dbeb4335 README.md: Add AnyPortal to GUI Clients (#4902) 2025-07-19 00:33:46 +00:00
dependabot[bot]
6afd721ced Bump google.golang.org/grpc from 1.73.0 to 1.74.0 (#4905)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.73.0 to 1.74.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.73.0...v1.74.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.74.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-07-19 00:27:18 +00:00
dependabot[bot]
5c0bc361d3 Bump golang.org/x/net from 0.41.0 to 0.42.0 (#4892)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.41.0 to 0.42.0.
- [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.42.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-07-19 00:27:00 +00:00
xDragonZ
3a2ac9d0bf Bump quic-go to v0.53.0 & update codes (#4906) 2025-07-19 00:21:18 +00:00
RPRX
1785178762 REALITY server: Three types of ALPN for post-handshake records detection & imitation; Two fixes
https://github.com/XTLS/Xray-core/issues/4778#issuecomment-3072047745

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

---------

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

Fixes https://github.com/XTLS/Xray-core/issues/4845
2025-07-19 00:06:59 +00:00
dependabot[bot]
1976d02ec9 Bump golang.org/x/sys from 0.33.0 to 0.34.0 (#4882)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.33.0 to 0.34.0.
- [Commits](https://github.com/golang/sys/compare/v0.33.0...v0.34.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.34.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-07-11 13:01:47 -04:00
dependabot[bot]
3ba733079e Bump golang.org/x/crypto from 0.39.0 to 0.40.0 (#4885)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/crypto/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.40.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-07-11 13:01:32 -04:00
dependabot[bot]
6a8a85f83a Bump golang.org/x/sync from 0.15.0 to 0.16.0 (#4881)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/sync/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.16.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-07-11 12:55:05 -04:00
dependabot[bot]
409e4e8f12 Bump github.com/miekg/dns from 1.1.66 to 1.1.67 (#4880)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.66 to 1.1.67.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.66...v1.1.67)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-version: 1.1.67
  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-07-11 12:54:33 -04:00
Jesus
486d005986 API: add option to fetch only tags from ListInbounds (#4870)
* feat: add new method for get only inbound tags from core. ListTags.

* refactor: simplify creating response.

* refactor: move getting tags in already exist method via option.
2025-07-11 12:52:51 -04:00
isluckys
cb1afb33e6 common: fix task leak in timer (#4831)
signal包里面SetTimeout方法并发时可能会出现task close以后执行start导致泄露
2025-06-23 08:47:27 -04:00
风扇滑翔翼
38ed2cc387 DNS: Add new nonIPQuery "reject" (#4824) 2025-06-22 22:48:24 -04:00
fL1pSt3r
b043db8260 API: Fix issue with inbounduser not finding emails with uppercase letters (#4818) 2025-06-22 21:57:02 -04:00
Jesus
27742da2c6 BurstObservatory: add option to set http method for burst check (#4835)
* feat: add options to set method for burst check.

* chore: gen proto.

* chore: change protoc-gen-go to latest.

* revert

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-06-22 21:48:49 -04:00
RPRX
fbae89d017 v25.6.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

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-06-08 13:53:03 +00:00
ketetefid
58c28b4aeb README.md: Add GorzRay to Linux Clients (#4767) 2025-06-08 13:51:39 +00:00
Yury Kastov
ca1c4b63f6 README.md: Add xtls-sdk to Xray Wrapper (#4793)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-06-08 13:47:23 +00:00
Meow
18ab291e0c REALITY: Add rate limiting to fallback handling via token bucket (#4553)
https://github.com/XTLS/REALITY/pull/12
2025-06-08 13:43:55 +00:00
RPRX
e011b746dc RAW transport hub.go: Call REALITY's DetectPostHandshakeRecordsLens() in advance
https://github.com/XTLS/Xray-core/issues/4778#issuecomment-2953455751
2025-06-08 05:01:31 +00:00
TonyMa1
f4b23c6565 README.md: Add DeepWiki badge to Contributing (#4777)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-06-08 04:51:10 +00:00
Meow
7d36cad3e0 Docker: Fix geodata directory permissions issue (#4790)
Fixes https://github.com/XTLS/Xray-core/issues/4747#issuecomment-2953519012
2025-06-08 04:49:26 +00:00
Meow
a576a4b183 Tests: Real fix for TestCommanderListHandlers (#4792)
Completes 402067d281
2025-06-08 04:47:04 +00:00
RPRX
f38d3f786a v25.6.7
Announcement of NFTs by Project X: #3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: #4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-06-07 13:37:24 +00:00
Meow
402067d281 Tests: Fix TestCommanderListHandlers (#4789) 2025-06-07 13:24:39 +00:00
patterniha
97fdcb4228 New feature: Happy Eyeballs (RFC 8305) (#4667)
Closes https://github.com/XTLS/Xray-core/issues/4473
2025-06-07 13:20:06 +00:00
RPRX
bfbccc2721 Update github.com/xtls/reality to 20250607105625
https://github.com/XTLS/Xray-core/issues/4778#issuecomment-2952355740

https://github.com/XTLS/Xray-core/issues/4741#issuecomment-2919838784
2025-06-07 11:22:12 +00:00
lhear
f67c70c4a2 README.md: Add SimpleXray to Android Clients (#4761) 2025-06-07 09:49:13 +00:00
vpainless
83e5fa4f3c README.md: Add VPainLess to One Click (#4782)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-06-07 09:33:20 +00:00
patterniha
4e1a6f0fd1 Sniffer-destOverride: Remove fakedns+others option (#4739)
https://github.com/XTLS/Xray-core/pull/4726#issuecomment-2886494540
2025-06-07 09:01:57 +00:00
RPRX
ab0b9a6220 REALITY practice: Detect & imitate target's post-handshake records
Fixes https://github.com/XTLS/Xray-core/issues/4778
2025-06-07 08:59:39 +00:00
yuhan6665
b80e319655 README.md: Add Amnezia VPN to Others (#4718) 2025-06-06 02:04:17 +00:00
patterniha
6232e230d9 DNS New Features: disableCache, finalQuery, unexpectedIPs, "*", UseSystem-queryStrategy, useSystemHosts (#4666) 2025-06-06 01:59:16 +00:00
Aubrey Yang
028e1114e6 Outbound: Fix sendthrough srcip precheck (#4750)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-06-06 01:54:15 +00:00
Sergey Gorbunov
af7a76da67 API: Add ListInbounds and ListOutbounds (#4723) 2025-06-06 01:51:48 +00:00
Meow
d44c78b819 Workflows: Refactor docker (#4738)
* Workflows: Rewrite Docker image build process

* Docker: Merge Multi-Arch Manifests
2025-06-06 01:50:05 +00:00
Tamim Hossain
d0c80fc80d Core: Export the running bool (#4775) 2025-06-06 01:48:19 +00:00
dependabot[bot]
1f49bfc6a5 Bump golang.org/x/net from 0.40.0 to 0.41.0 (#4786)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.41.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-06-06 01:46:30 +00:00
dependabot[bot]
dd52732e1f Bump google.golang.org/grpc from 1.72.2 to 1.73.0 (#4783)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.2 to 1.73.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.72.2...v1.73.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.73.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-06-06 01:46:16 +00:00
dependabot[bot]
4af53ab364 Bump google.golang.org/grpc from 1.72.1 to 1.72.2 (#4763)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.1 to 1.72.2.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.72.1...v1.72.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.72.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-06-04 13:51:39 +00:00
风扇滑翔翼
462bc3cfba BurstObservatory: Fix nil panic when pingConfig is missing (#4757)
Fixes https://github.com/XTLS/Xray-core/issues/4756
2025-06-04 12:37:43 +00:00
yuhan6665
84c8e24a6c Update reality to 20250527 2025-05-26 21:55:30 -04:00
dependabot[bot]
9fe4ee75b7 Bump github.com/quic-go/quic-go from 0.51.0 to 0.52.0 (#4752)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.51.0 to 0.52.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.51.0...v0.52.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-26 01:33:50 +00:00
yiguodev
f1116cee60 README.md: Rename FoXray to OneXray in GUI Clients (#4754)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-05-26 01:31:19 +00:00
RPRX
800b8b50cc v25.5.16
Announcement of NFTs by Project X: #3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: #4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-05-16 12:18:34 +00:00
风扇滑翔翼
ef1c165cc5 QUIC sniffer: Fix potential slice panic (#4732)
Fixes https://github.com/XTLS/Xray-core/issues/3914#issuecomment-2853392827
2025-05-16 12:08:29 +00:00
patterniha
bb0e561caf Sniffer: Fix potential infinite loop (#4726)
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-05-16 12:04:54 +00:00
RPRX
09d84c42e9 REALITY protocol: Remove ChaCha20-Poly1305 support for REALITY's session id auth
https://github.com/XTLS/Xray-core/pull/3813#issuecomment-2885686468

https://github.com/XTLS/REALITY/pull/4#issuecomment-2885815235

Reverts 65b467e448
2025-05-16 07:29:05 +00:00
RPRX
7ddc4a2525 REALITY practice: Support X25519MLKEM768 for TLS' communication
https://github.com/XTLS/Xray-core/pull/3813#issuecomment-2873889724
2025-05-16 04:08:38 +00:00
patterniha
882975ce5a DNS Host: Removing code that was not being executed and should not be executed. (#4721)
* Removing a piece of code that was not being executed and should not be executed.

* Remove d.dns

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-05-15 10:15:03 -04:00
dependabot[bot]
30bf7be429 Bump google.golang.org/grpc from 1.72.0 to 1.72.1 (#4729)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.72.0 to 1.72.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.72.0...v1.72.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.72.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-05-15 10:12:49 -04:00
dependabot[bot]
c53c1bfc51 Bump github.com/refraction-networking/utls from 1.7.2 to 1.7.3 (#4730)
Bumps [github.com/refraction-networking/utls](https://github.com/refraction-networking/utls) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/refraction-networking/utls/releases)
- [Commits](https://github.com/refraction-networking/utls/compare/v1.7.2...v1.7.3)

---
updated-dependencies:
- dependency-name: github.com/refraction-networking/utls
  dependency-version: 1.7.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-15 10:11:39 -04:00
dependabot[bot]
72170d8b6b Bump github.com/vishvananda/netlink from 1.3.0 to 1.3.1 (#4719)
Bumps [github.com/vishvananda/netlink](https://github.com/vishvananda/netlink) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/vishvananda/netlink/releases)
- [Commits](https://github.com/vishvananda/netlink/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/vishvananda/netlink
  dependency-version: 1.3.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-05-11 20:55:45 -04:00
dependabot[bot]
e9b3c53a0d Bump github.com/refraction-networking/utls from 1.7.1 to 1.7.2 (#4710)
Bumps [github.com/refraction-networking/utls](https://github.com/refraction-networking/utls) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/refraction-networking/utls/releases)
- [Commits](https://github.com/refraction-networking/utls/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: github.com/refraction-networking/utls
  dependency-version: 1.7.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-05-09 10:32:00 -04:00
dependabot[bot]
7afed1e74d Bump lukechampine.com/blake3 from 1.4.0 to 1.4.1 (#4709)
Bumps [lukechampine.com/blake3](https://github.com/lukechampine/blake3) from 1.4.0 to 1.4.1.
- [Commits](https://github.com/lukechampine/blake3/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: lukechampine.com/blake3
  dependency-version: 1.4.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-05-09 10:31:28 -04:00
dependabot[bot]
6ed636840b Bump actions/github-script from 6 to 7 (#4708)
Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-09 10:29:52 -04:00
Meow
f0dfbc2e66 Workflows: Ensure Geodat exists (#4680) 2025-05-08 15:44:54 -04:00
dependabot[bot]
0d0fe7ef7a Bump github.com/miekg/dns from 1.1.65 to 1.1.66 (#4707)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.65 to 1.1.66.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.65...v1.1.66)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-version: 1.1.66
  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-05-08 14:48:12 -04:00
patterniha
59aa5e1b88 DNS: temporary appending hosts results (#4702) 2025-05-06 18:11:08 -04:00
dependabot[bot]
3e52f73e3c Bump golang.org/x/net from 0.39.0 to 0.40.0 (#4698)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/net/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.40.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-05-06 17:56:07 -04:00
dependabot[bot]
d4ca42715a Bump github.com/pires/go-proxyproto from 0.8.0 to 0.8.1 (#4695)
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.8.0...v0.8.1)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.8.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-05-06 17:55:42 -04:00
A1lo
c847c21f3b Workflows: Authenticating the GitHub API call with GitHub token (#4703) 2025-05-06 17:54:14 -04:00
RPRX
87ab8e5128 v25.4.30
Announcement of NFTs by Project X: #3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: #4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-04-30 04:40:58 +00:00
patterniha
54c6513fd4 DNS: Extend hosts Abilities (#4673) 2025-04-30 04:32:52 +00:00
Aubrey Yang
5e6a5ae01d Improve random IP compatibility: support IPv4, add srcip option, and sync client source IP via sendthrough (#4671) 2025-04-30 04:32:02 +00:00
Pk-web6936
19ba9cbe91 Chore: Update gVisor to the latest version; Fmt .go files (#4663) 2025-04-29 11:32:23 +00:00
j2rong4cn
16641fc4b5 Workflows: Build Android(7+) using NDK; Add Android(7+) amd64 build (#4664)
Makes net.LookupIP() work on Android builds
2025-04-29 11:29:52 +00:00
patterniha
aa4134f4a6 DNS: Fix some bugs; Refactors; Optimizations (#4659) 2025-04-29 08:39:57 +00:00
patterniha
1c4e246788 Sockopt: Fix some domainStrategy & dialerProxy bugs (#4661) 2025-04-29 08:33:36 +00:00
j2rong4cn
d9ebb9b2dc QUIC sniffer: Optimize the code (#4655)
Based on 2eed70e17d
2025-04-29 08:04:04 +00:00
RPRX
2eed70e17d buffer.go: Ensure extended part by Extend() & Resize() are all-zero
https://github.com/XTLS/Xray-core/pull/4655#issuecomment-2837693439
2025-04-29 07:33:37 +00:00
j2rong4cn
58c48664e2 QUIC sniffer: Full support for handling multiple initial packets (#4642)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
Co-authored-by: Vigilans <vigilans@foxmail.com>
Co-authored-by: Shelikhoo <xiaokangwang@outlook.com>
Co-authored-by: dyhkwong <50692134+dyhkwong@users.noreply.github.com>
2025-04-28 10:03:03 +00:00
yuhan6665
a608c5a1db uTLS: Add new fingerprints
PSK extension, Post-Quantum Key Agreement, ML-KEM
2025-04-26 12:31:41 -04:00
dependabot[bot]
0dd74cf072 Bump github.com/refraction-networking/utls from 1.6.7 to 1.7.1
Bumps [github.com/refraction-networking/utls](https://github.com/refraction-networking/utls) from 1.6.7 to 1.7.1.
- [Release notes](https://github.com/refraction-networking/utls/releases)
- [Commits](https://github.com/refraction-networking/utls/compare/v1.6.7...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/refraction-networking/utls
  dependency-version: 1.7.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-25 00:14:52 +00:00
peter zhang
922ae98a4a DNS log: Optimize IP address display (#4630) 2025-04-23 03:10:20 +00:00
dependabot[bot]
800b33c626 Bump github.com/quic-go/quic-go from 0.50.1 to 0.51.0 (#4634)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.50.1 to 0.51.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.50.1...v0.51.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-23 02:58:09 +00:00
dependabot[bot]
0563c9750e Bump google.golang.org/grpc from 1.71.1 to 1.72.0 (#4640)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.71.1 to 1.72.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.71.1...v1.72.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.72.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-04-23 02:57:57 +00:00
xqzr
907a182f64 Sockopt: Fix Windows IP_MULTICAST_IF & IPV6_MULTICAST_IF (#4627)
Completes https://github.com/XTLS/Xray-core/pull/4568
2025-04-19 09:30:12 +00:00
RPRX
0995fa41fe XHTTP client: Set packet-up as the default mode (auto) when using TLS
https://t.me/projectXtls/929
2025-04-18 06:18:51 +00:00
RPRX
2916b1b977 README.md: Rename Clash.Meta to mihomo in Others 2025-04-18 03:57:14 +00:00
RPRX
8212325980 README.md: Add Loon to Others
https://t.me/LoonNews/1662
2025-04-18 03:18:38 +00:00
风扇滑翔翼
5f3ae64f0c Sockopt: Allow customSockopt work for Windows & Darwin (#4576)
* Sockopt: Add custom sockopt on Windows & Darwin

* fix windows udp by the way

* use resolved addr

https://github.com/XTLS/Xray-core/pull/4504#issuecomment-2769153797
2025-04-18 02:30:47 +00:00
Cl-He-O
7a2f42f8d5 Fix issues related to android client (#4616)
* WireGuard: Fix tunnel not closed

* Dialer: Apply controllers in lc.Control
2025-04-14 11:08:26 -04:00
dependabot[bot]
53552d73cc Bump github.com/cloudflare/circl from 1.6.0 to 1.6.1 (#4602)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.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-04-13 00:55:00 -04:00
dependabot[bot]
19e884bebf Bump golang.org/x/net from 0.38.0 to 0.39.0 (#4598)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.38.0 to 0.39.0.
- [Commits](https://github.com/golang/net/compare/v0.38.0...v0.39.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.39.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-04-08 11:17:36 -04:00
xqzr
78a1e37e89 Sockopt: Use Windows syscall (#4581) 2025-04-07 11:50:07 -04:00
Ragavendaran Puliyadi
2d3126b752 Config: Implement missing MarshalJSON for structs having custom UnmarshalJSON (#4585)
* conf: implement MarshalJSON for FakeDNSConfig

* conf: Rewrite MarshalJSON for PortList
decouple PortRange from PortList.

* conf: implement MarshalJSON for HostAddress

* conf: Add MarshalJSON comments and use pointers.
2025-04-07 11:46:49 -04:00
dependabot[bot]
0dbab7bcd7 Bump golang.org/x/crypto from 0.36.0 to 0.37.0 (#4597)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.36.0 to 0.37.0.
- [Commits](https://github.com/golang/crypto/compare/v0.36.0...v0.37.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.37.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-04-07 10:56:44 -04:00
dependabot[bot]
ab15822ee3 Bump golang.org/x/sync from 0.12.0 to 0.13.0 (#4596)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/sync/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.13.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-04-07 10:56:34 -04:00
dependabot[bot]
8b2fe32a33 Bump golang.org/x/sys from 0.31.0 to 0.32.0 (#4595)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.31.0 to 0.32.0.
- [Commits](https://github.com/golang/sys/compare/v0.31.0...v0.32.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.32.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-04-07 10:52:54 -04:00
dependabot[bot]
dd2a40e64d Bump google.golang.org/grpc from 1.71.0 to 1.71.1 (#4577)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.71.0 to 1.71.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.71.0...v1.71.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  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-04-07 10:30:33 -04:00
dependabot[bot]
33a4336b1d Bump github.com/miekg/dns from 1.1.64 to 1.1.65 (#4594)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.64 to 1.1.65.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.64...v1.1.65)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-version: 1.1.65
  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-04-07 10:29:49 -04:00
137 changed files with 5955 additions and 2283 deletions

View File

@@ -1,28 +1,62 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:latest
FROM --platform=$BUILDPLATFORM golang:alpine AS build FROM --platform=$BUILDPLATFORM golang:latest AS build
# Build xray-core
WORKDIR /src 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 -ldflags "-s -w -buildid=" ./main
ADD https://github.com/v2fly/geoip/releases/latest/download/geoip.dat /v2fly/geoip.dat
ADD https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat /v2fly/geosite.dat
ADD https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat /loyalsoldier/geoip.dat
ADD https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat /loyalsoldier/geosite.dat
# chainguard/static contains only tzdata and ca-certificates, can be built with multiarch static binaries. # Download geodat into a staging directory
FROM --platform=linux/amd64 chainguard/static:latest ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat
WORKDIR /var/log/xray ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat
COPY .github/docker/files/config.json /etc/xray/config.json
COPY --from=build --chmod=755 /src/xray /usr/bin/xray
USER root RUN mkdir -p /tmp/empty
WORKDIR /root
VOLUME /etc/xray # Create config files with empty JSON content
ARG TZ=Asia/Shanghai RUN mkdir -p /tmp/usr/local/etc/xray
RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
{
"log": {
"error": "/var/log/xray/error.log",
"loglevel": "warning",
"access": "none",
"dnsLog": false
}
}
EOF
RUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json
RUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
# Create log files
RUN mkdir -p /tmp/var/log/xray && touch \
/tmp/var/log/xray/access.log \
/tmp/var/log/xray/error.log
# Build finally image
FROM gcr.io/distroless/static:nonroot
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
VOLUME /usr/local/etc/xray
VOLUME /var/log/xray
ARG TZ=Etc/UTC
ENV TZ=$TZ ENV TZ=$TZ
ENTRYPOINT [ "/usr/bin/xray" ]
CMD [ "-confdir", "/etc/xray/" ]
ARG flavor=v2fly ENTRYPOINT [ "/usr/local/bin/xray" ]
COPY --from=build --chmod=644 /$flavor /usr/share/xray CMD [ "-confdir", "/usr/local/etc/xray/" ]

71
.github/docker/Dockerfile.usa vendored Normal file
View File

@@ -0,0 +1,71 @@
# syntax=docker/dockerfile:latest
FROM --platform=$BUILDPLATFORM golang:latest AS build
# Build xray-core
WORKDIR /src
COPY . .
ARG TARGETOS
ARG TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
# 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/geosite.dat /tmp/geodat/geosite.dat
RUN mkdir -p /tmp/empty
# Create config files with empty JSON content
RUN mkdir -p /tmp/usr/local/etc/xray
RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
{
"log": {
"error": "/var/log/xray/error.log",
"loglevel": "warning",
"access": "none",
"dnsLog": false
}
}
EOF
RUN echo '{}' >/tmp/usr/local/etc/xray/01_api.json
RUN echo '{}' >/tmp/usr/local/etc/xray/02_dns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
RUN echo '{}' >/tmp/usr/local/etc/xray/07_transport.json
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
# Create log files
RUN mkdir -p /tmp/var/log/xray && touch \
/tmp/var/log/xray/access.log \
/tmp/var/log/xray/error.log
# Build finally image
# Note on Distroless Base Image and Architecture Support:
# - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux:
# - linux/amd64
# - linux/arm/v7
# - linux/arm64/v8
# - linux/ppc64le
# - linux/s390x
# - Upon inspection, the blob contents of the Distroless images across these architectures are nearly identical, with only minor differences in metadata (e.g., 'Architecture' field in the manifest).
# - Due to this similarity in content, it is feasible to forcibly specify a single platform (e.g., '--platform=linux/amd64') for unsupported architectures, as the core image content remains compatible with statically compiled binaries like Go applications.
FROM --platform=linux/amd64 gcr.io/distroless/static:nonroot
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
VOLUME /usr/local/etc/xray
VOLUME /var/log/xray
ARG TZ=Etc/UTC
ENV TZ=$TZ
ENTRYPOINT [ "/usr/local/bin/xray" ]
CMD [ "-confdir", "/usr/local/etc/xray/" ]

View File

@@ -1,18 +0,0 @@
{
"inbounds": [{
"port": 9000,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "1eb6e917-774b-4a84-aff6-b058577c60a5",
"level": 1
}
]
}
}],
"outbounds": [{
"protocol": "freedom",
"settings": {}
}]
}

View File

@@ -1,76 +1,133 @@
name: Build docker image name: Build and Push Docker Image
on: on:
release: release:
types: [published] types:
push: - published
branches: - released
- main
workflow_dispatch:
inputs:
tag:
description: "Docker image tag:"
required: true
latest:
description: "Set to latest"
type: boolean
default: false
jobs: jobs:
build-image: build-and-push:
if: (github.event.action != 'published') || (github.event.action == 'published' && github.event.release.prerelease == true)
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@v4 - name: Set repository and image name to lowercase
- name: Docker metadata env:
id: meta IMAGE_NAME: "${{ github.repository }}"
uses: docker/metadata-action@v5 run: |
with: echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV}
images: ghcr.io/${{ github.repository_owner }}/xray-core echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV}
flavor: latest=auto
tags: | - name: Validate and extract tag
type=sha run: |
type=ref,event=branch SOURCE_TAG="${{ github.event.inputs.tag }}"
type=ref,event=pr if [[ -z "$SOURCE_TAG" ]]; then
type=semver,pattern={{version}} SOURCE_TAG="${{ github.ref_name }}"
- name: Docker metadata Loyalsoldier flavor fi
id: loyalsoldier if [[ -z "$SOURCE_TAG" ]]; then
uses: docker/metadata-action@v5 SOURCE_TAG="${{ github.event.release.tag_name }}"
with: fi
images: ghcr.io/${{ github.repository_owner }}/xray-core
flavor: | if [[ -z "$SOURCE_TAG" ]]; then
latest=auto echo "Error: Could not determine a valid tag source. Input tag and context tag (github.ref_name) are both empty."
suffix=-ls,onlatest=true exit 1
tags: | fi
type=sha
type=ref,event=branch if [[ "$SOURCE_TAG" =~ ^v[0-9]+\.[0-9] ]]; then
type=ref,event=pr IMAGE_TAG="${SOURCE_TAG#v}"
type=semver,pattern={{version}} else
IMAGE_TAG="$SOURCE_TAG"
fi
echo "Docker image tag: '$IMAGE_TAG'."
echo "IMAGE_TAG=$IMAGE_TAG" >>${GITHUB_ENV}
LATEST=false
if [[ "${{ github.event_name }}" == "release" && "${{ github.event.release.prerelease }}" == "false" ]] || [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.latest }}" == "true" ]]; then
LATEST=true
fi
echo "Latest: '$LATEST'."
echo "LATEST=$LATEST" >>${GITHUB_ENV}
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 - name: Build Docker image (main architectures)
- name: Build and push id: build_main_arches
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: .github/docker/Dockerfile
platforms: | platforms: |
linux/amd64 linux/amd64
linux/arm64 linux/arm/v7
linux/loong64 linux/arm64/v8
linux/riscv64 linux/ppc64le
linux/s390x
provenance: false provenance: false
file: .github/docker/Dockerfile outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
push: true
tags: ${{ steps.meta.outputs.tags }} - name: Build Docker image (additional architectures)
- name: Build and push Loyalsoldier flavor id: build_additional_arches
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: .github/docker/Dockerfile.usa
platforms: | platforms: |
linux/amd64 linux/386
linux/arm64 linux/arm/v6
linux/loong64
linux/riscv64 linux/riscv64
linux/loong64
provenance: false provenance: false
file: .github/docker/Dockerfile outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
build-args: flavor=loyalsoldier
push: true - name: Create manifest list and push
tags: | run: |
${{ steps.loyalsoldier.outputs.tags }} echo "Creating multi-arch manifest with tag: '${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}'."
docker buildx imagetools create \
--tag ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
${{ env.FULL_IMAGE_NAME }}@${{ steps.build_main_arches.outputs.digest }} \
${{ env.FULL_IMAGE_NAME }}@${{ steps.build_additional_arches.outputs.digest }}
if [[ "${{ env.LATEST }}" == "true" ]]; then
echo "Adding 'latest' tag to manifest: '${{ env.FULL_IMAGE_NAME }}:latest'."
docker buildx imagetools create \
--tag ${{ env.FULL_IMAGE_NAME }}:latest \
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
fi
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
if [[ "${{ env.LATEST }}" == "true" ]]; then
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
fi

View File

@@ -9,7 +9,38 @@ on:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
jobs: jobs:
check-assets:
runs-on: ubuntu-latest
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v4
with:
path: resources
key: xray-geodat-
- name: Check Assets Existence
id: check-assets
run: |
[ -d 'resources' ] || mkdir resources
LIST=('geoip.dat' 'geosite.dat')
for FILE_NAME in "${LIST[@]}"
do
echo -e "Checking ${FILE_NAME}..."
if [ -s "./resources/${FILE_NAME}" ]; then
echo -e "${FILE_NAME} exists."
else
echo -e "${FILE_NAME} does not exist."
echo "missing=true" >> $GITHUB_OUTPUT
break
fi
done
- name: Sleep for 90 seconds if Assets Missing
if: steps.check-assets.outputs.missing == 'true'
run: sleep 90
build: build:
needs: check-assets
permissions: permissions:
contents: write contents: write
strategy: strategy:
@@ -51,7 +82,7 @@ jobs:
GOSDK=$(go env GOROOT) GOSDK=$(go env GOROOT)
rm -r $GOSDK/* rm -r $GOSDK/*
cd $GOSDK cd $GOSDK
curl -O -L https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip curl -O -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip
unzip ./go-for-win7-linux-amd64.zip -d $GOSDK unzip ./go-for-win7-linux-amd64.zip -d $GOSDK
rm ./go-for-win7-linux-amd64.zip rm ./go-for-win7-linux-amd64.zip

View File

@@ -9,7 +9,53 @@ on:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
jobs: jobs:
check-assets:
runs-on: ubuntu-latest
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v4
with:
path: resources
key: xray-geodat-
- name: Check Assets Existence
id: check-assets
run: |
[ -d 'resources' ] || mkdir resources
LIST=('geoip.dat' 'geosite.dat')
for FILE_NAME in "${LIST[@]}"
do
echo -e "Checking ${FILE_NAME}..."
if [ -s "./resources/${FILE_NAME}" ]; then
echo -e "${FILE_NAME} exists."
else
echo -e "${FILE_NAME} does not exist."
echo "missing=true" >> $GITHUB_OUTPUT
break
fi
done
- name: Trigger Asset Update Workflow if Assets Missing
if: steps.check-assets.outputs.missing == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
await github.rest.actions.createWorkflowDispatch({
owner,
repo,
workflow_id: 'scheduled-assets-update.yml',
ref: context.ref
});
console.log('Triggered scheduled-assets-update.yml due to missing assets on branch:', context.ref);
- name: Sleep for 90 seconds if Assets Missing
if: steps.check-assets.outputs.missing == 'true'
run: sleep 90
build: build:
needs: check-assets
permissions: permissions:
contents: write contents: write
strategy: strategy:
@@ -42,6 +88,11 @@ jobs:
- goos: android - goos: android
goarch: arm64 goarch: arm64
# END Android ARM 8 # END Android ARM 8
# BEGIN Android AMD64
- goos: android
goarch: amd64
patch-assetname: android-amd64
# END Android AMD64
# Windows ARM # Windows ARM
- goos: windows - goos: windows
goarch: arm64 goarch: arm64
@@ -104,6 +155,19 @@ jobs:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up NDK
if: matrix.goos == 'android'
run: |
wget -qO android-ndk.zip https://dl.google.com/android/repository/android-ndk-r28b-linux.zip
unzip android-ndk.zip
rm android-ndk.zip
declare -A arches=(
["amd64"]="x86_64-linux-android24-clang"
["arm64"]="aarch64-linux-android24-clang"
)
echo CC="$(realpath android-ndk-*/toolchains/llvm/prebuilt/linux-x86_64/bin)/${arches[${{ matrix.goarch }}]}" >> $GITHUB_ENV
echo CGO_ENABLED=1 >> $GITHUB_ENV
- name: Show workflow information - name: Show workflow information
run: | run: |
_NAME=${{ matrix.patch-assetname }} _NAME=${{ matrix.patch-assetname }}
@@ -119,7 +183,7 @@ jobs:
- name: Get project dependencies - name: Get project dependencies
run: go mod download run: go mod download
- name: Build Xray - name: Build Xray
run: | run: |
mkdir -p build_assets mkdir -p build_assets

View File

@@ -1,6 +1,6 @@
name: Scheduled assets update name: Scheduled assets update
# NOTE: This Github Actions is required by other actions, for preparing other packaging assets in a # NOTE: This Github Actions is required by other actions, for preparing other packaging assets in a
# routine manner, for example: GeoIP/GeoSite. # routine manner, for example: GeoIP/GeoSite.
# Currently updating: # Currently updating:
# - Geodat (GeoIP/Geosite) # - Geodat (GeoIP/Geosite)
@@ -9,7 +9,7 @@ on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
# Update GeoData on every day (22:30 UTC) # Update GeoData on every day (22:30 UTC)
- cron: '30 22 * * *' - cron: "30 22 * * *"
push: push:
# Prevent triggering update request storm # Prevent triggering update request storm
paths: paths:
@@ -45,12 +45,12 @@ jobs:
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}')) INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
FILE_NAME="${INFO[3]}.dat" FILE_NAME="${INFO[3]}.dat"
echo -e "Verifying HASH key..." echo -e "Verifying HASH key..."
HASH="$(curl -sL "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat.sha256sum" | awk -F ' ' '{print $1}')" HASH="$(curl -sL -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then if [ -s "./resources/${FILE_NAME}" ] && [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ]; then
continue continue
else else
echo -e "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..." echo -e "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..."
curl -L "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat" -o ./resources/${FILE_NAME} curl -L -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat" -o ./resources/${FILE_NAME}
echo -e "Verifying HASH key..." echo -e "Verifying HASH key..."
[ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; } [ "$(sha256sum "./resources/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
echo "unhit=true" >> $GITHUB_OUTPUT echo "unhit=true" >> $GITHUB_OUTPUT

View File

@@ -6,7 +6,36 @@ on:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
jobs: jobs:
check-assets:
runs-on: ubuntu-latest
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v4
with:
path: resources
key: xray-geodat-
- name: Check Assets Existence
id: check-assets
run: |
[ -d 'resources' ] || mkdir resources
LIST=('geoip.dat' 'geosite.dat')
for FILE_NAME in "${LIST[@]}"
do
echo -e "Checking ${FILE_NAME}..."
if [ -s "./resources/${FILE_NAME}" ]; then
echo -e "${FILE_NAME} exists."
else
echo -e "${FILE_NAME} does not exist."
echo "missing=true" >> $GITHUB_OUTPUT
break
fi
done
- name: Sleep for 90 seconds if Assets Missing
if: steps.check-assets.outputs.missing == 'true'
run: sleep 90
test: test:
needs: check-assets
permissions: permissions:
contents: read contents: read
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View File

@@ -6,9 +6,13 @@
## Donation & NFTs ## Donation & NFTs
### [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)
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`** - **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
- **Project X NFT: [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)** - **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**
- **REALITY NFT: [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)** - **Related links: https://opensea.io/collection/xtls, [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
## License ## License
@@ -44,7 +48,7 @@
- [Hiddify](https://github.com/hiddify/Hiddify-Manager) - [Hiddify](https://github.com/hiddify/Hiddify-Manager)
- 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) - [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU) - [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk - Magisk
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk) - [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
@@ -81,32 +85,43 @@
- [v2rayN](https://github.com/2dust/v2rayN) - [v2rayN](https://github.com/2dust/v2rayN)
- [Furious](https://github.com/LorenEteval/Furious) - [Furious](https://github.com/LorenEteval/Furious)
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient) - [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- Android - Android
- [v2rayNG](https://github.com/2dust/v2rayNG) - [v2rayNG](https://github.com/2dust/v2rayNG)
- [X-flutter](https://github.com/XTLS/X-flutter) - [X-flutter](https://github.com/XTLS/X-flutter)
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray) - [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
- iOS & macOS arm64 - [SimpleXray](https://github.com/lhear/SimpleXray)
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) - [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [FoXray](https://apps.apple.com/app/foxray/id6448898396) - iOS & macOS arm64 & tvOS
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) ([tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274))
- [Streisand](https://apps.apple.com/app/streisand/id6450534064) - [Streisand](https://apps.apple.com/app/streisand/id6450534064)
- [OneXray](https://github.com/OneXray/OneXray)
- macOS arm64 & x64 - macOS arm64 & x64
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
- [V2rayU](https://github.com/yanue/V2rayU) - [V2rayU](https://github.com/yanue/V2rayU)
- [V2RayXS](https://github.com/tzmax/V2RayXS) - [V2RayXS](https://github.com/tzmax/V2RayXS)
- [Furious](https://github.com/LorenEteval/Furious) - [Furious](https://github.com/LorenEteval/Furious)
- [FoXray](https://apps.apple.com/app/foxray/id6448898396) - [OneXray](https://github.com/OneXray/OneXray)
- [GoXRay](https://github.com/goxray/desktop)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- 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)
- [GoXRay](https://github.com/goxray/desktop)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX... ## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
- iOS & macOS arm64 - iOS & macOS arm64 & tvOS
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118) - [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
- Xray Tools - Xray Tools
- [xray-knife](https://github.com/lilendian0x00/xray-knife) - [xray-knife](https://github.com/lilendian0x00/xray-knife)
- [xray-checker](https://github.com/kutovoys/xray-checker) - [xray-checker](https://github.com/kutovoys/xray-checker)
- Xray Wrapper - Xray Wrapper
- [XTLS/libXray](https://github.com/XTLS/libXray) - [XTLS/libXray](https://github.com/XTLS/libXray)
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
- [xtlsapi](https://github.com/hiddify/xtlsapi) - [xtlsapi](https://github.com/hiddify/xtlsapi)
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite) - [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python) - [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
@@ -114,15 +129,17 @@
- [XrayR](https://github.com/XrayR-project/XrayR) - [XrayR](https://github.com/XrayR-project/XrayR)
- [XrayR-release](https://github.com/XrayR-project/XrayR-release) - [XrayR-release](https://github.com/XrayR-project/XrayR-release)
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board) - [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) - Cores
- [clashN](https://github.com/2dust/clashN) - [Amnezia VPN](https://github.com/amnezia-vpn)
- [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid) - [mihomo](https://github.com/MetaCubeX/mihomo)
- [sing-box](https://github.com/SagerNet/sing-box) - [sing-box](https://github.com/SagerNet/sing-box)
## Contributing ## Contributing
[Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md) [Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/XTLS/Xray-core)
## Credits ## Credits
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases). - [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).

View File

@@ -8,6 +8,7 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/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/serial"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
) )
@@ -108,3 +109,13 @@ func (co *Outbound) Close() error {
co.closed = true co.closed = true
return co.listener.Close() return co.listener.Close()
} }
// SenderSettings implements outbound.Handler.
func (co *Outbound) SenderSettings() *serial.TypedMessage {
return nil
}
// ProxySettings implements outbound.Handler.
func (co *Outbound) ProxySettings() *serial.TypedMessage {
return nil
}

View File

@@ -33,23 +33,21 @@ type cachedReader struct {
cache buf.MultiBuffer cache buf.MultiBuffer
} }
func (r *cachedReader) Cache(b *buf.Buffer) { func (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100) mb, err := r.reader.ReadMultiBufferTimeout(deadline)
if err != nil {
return err
}
r.Lock() r.Lock()
if !mb.IsEmpty() { if !mb.IsEmpty() {
r.cache, _ = buf.MergeMulti(r.cache, mb) r.cache, _ = buf.MergeMulti(r.cache, mb)
} }
cacheLen := r.cache.Len() b.Clear()
if cacheLen <= b.Cap() { rawBytes := b.Extend(min(r.cache.Len(), b.Cap()))
b.Clear()
} else {
b.Release()
*b = *buf.NewWithSize(cacheLen)
}
rawBytes := b.Extend(cacheLen)
n := r.cache.Copy(rawBytes) n := r.cache.Copy(rawBytes)
b.Resize(0, int32(n)) b.Resize(0, int32(n))
r.Unlock() r.Unlock()
return nil
} }
func (r *cachedReader) readInternal() buf.MultiBuffer { func (r *cachedReader) readInternal() buf.MultiBuffer {
@@ -98,7 +96,6 @@ type DefaultDispatcher struct {
router routing.Router router routing.Router
policy policy.Manager policy policy.Manager
stats stats.Manager stats stats.Manager
dns dns.Client
fdns dns.FakeDNSEngine fdns dns.FakeDNSEngine
} }
@@ -109,7 +106,7 @@ func init() {
core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) { core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {
d.fdns = fdns d.fdns = fdns
}) })
return d.Init(config.(*Config), om, router, pm, sm, dc) return d.Init(config.(*Config), om, router, pm, sm)
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
@@ -118,12 +115,11 @@ func init() {
} }
// Init initializes DefaultDispatcher. // Init initializes DefaultDispatcher.
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error { func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
d.ohm = om d.ohm = om
d.router = router d.router = router
d.policy = pm d.policy = pm
d.stats = sm d.stats = sm
d.dns = dns
return nil return nil
} }
@@ -355,7 +351,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
} }
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) { func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
payload := buf.New() payload := buf.NewWithSize(32767)
defer payload.Release() defer payload.Release()
sniffer := NewSniffer(ctx) sniffer := NewSniffer(ctx)
@@ -367,26 +363,36 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
} }
contentResult, contentErr := func() (SniffResult, error) { contentResult, contentErr := func() (SniffResult, error) {
cacheDeadline := 200 * time.Millisecond
totalAttempt := 0 totalAttempt := 0
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
default: default:
totalAttempt++ cachingStartingTimeStamp := time.Now()
if totalAttempt > 2 { err := cReader.Cache(payload, cacheDeadline)
return nil, errSniffingTimeout if err != nil {
return nil, err
} }
cachingTimeElapsed := time.Since(cachingStartingTimeStamp)
cacheDeadline -= cachingTimeElapsed
cReader.Cache(payload)
if !payload.IsEmpty() { if !payload.IsEmpty() {
result, err := sniffer.Sniff(ctx, payload.Bytes(), network) result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
if err != common.ErrNoClue { switch err {
case common.ErrNoClue: // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not
totalAttempt++
case protocol.ErrProtoNeedMoreData: // Protocol Need More Data: protocol matches, but need more data to complete sniffing
// in this case, do not add totalAttempt(allow to read until timeout)
default:
return result, err return result, err
} }
} else {
totalAttempt++
} }
if payload.IsFull() { if totalAttempt >= 2 || cacheDeadline <= 0 {
return nil, errUnknownContent return nil, errSniffingTimeout
} }
} }
} }
@@ -402,18 +408,6 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
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]
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
proxied := hosts.LookupHosts(ob.Target.String())
if proxied != nil {
ro := ob.RouteTarget == destination
destination.Address = *proxied
if ro {
ob.RouteTarget = destination
} else {
ob.Target = destination
}
}
}
var handler outbound.Handler var handler outbound.Handler

View File

@@ -6,6 +6,7 @@ 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/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/protocol/bittorrent" "github.com/xtls/xray-core/common/protocol/bittorrent"
"github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/protocol/http"
"github.com/xtls/xray-core/common/protocol/quic" "github.com/xtls/xray-core/common/protocol/quic"
@@ -58,14 +59,17 @@ var errUnknownContent = errors.New("unknown content")
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) { func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
var pendingSniffer []protocolSnifferWithMetadata var pendingSniffer []protocolSnifferWithMetadata
for _, si := range s.sniffer { for _, si := range s.sniffer {
s := si.protocolSniffer protocolSniffer := si.protocolSniffer
if si.metadataSniffer || si.network != network { if si.metadataSniffer || si.network != network {
continue continue
} }
result, err := s(c, payload) result, err := protocolSniffer(c, payload)
if err == common.ErrNoClue { if err == common.ErrNoClue {
pendingSniffer = append(pendingSniffer, si) pendingSniffer = append(pendingSniffer, si)
continue continue
} else if err == protocol.ErrProtoNeedMoreData { // Sniffer protocol matched, but need more data to complete sniffing
s.sniffer = []protocolSnifferWithMetadata{si}
return nil, err
} }
if err == nil && result != nil { if err == nil && result != nil {

188
app/dns/cache_controller.go Normal file
View File

@@ -0,0 +1,188 @@
package dns
import (
"context"
go_errors "errors"
"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/signal/pubsub"
"github.com/xtls/xray-core/common/task"
dns_feature "github.com/xtls/xray-core/features/dns"
"golang.org/x/net/dns/dnsmessage"
"sync"
"time"
)
type CacheController struct {
sync.RWMutex
ips map[string]*record
pub *pubsub.Service
cacheCleanup *task.Periodic
name string
disableCache bool
}
func NewCacheController(name string, disableCache bool) *CacheController {
c := &CacheController{
name: name,
disableCache: disableCache,
ips: make(map[string]*record),
pub: pubsub.NewService(),
}
c.cacheCleanup = &task.Periodic{
Interval: time.Minute,
Execute: c.CacheCleanup,
}
return c
}
// CacheCleanup clears expired items from cache
func (c *CacheController) CacheCleanup() error {
now := time.Now()
c.Lock()
defer c.Unlock()
if len(c.ips) == 0 {
return errors.New("nothing to do. stopping...")
}
for domain, record := range c.ips {
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
}
}
if len(c.ips) == 0 {
c.ips = make(map[string]*record)
}
return nil
}
func (c *CacheController) updateIP(req *dnsRequest, ipRec *IPRecord) {
elapsed := time.Since(req.start)
c.Lock()
rec, found := c.ips[req.domain]
if !found {
rec = &record{}
}
switch req.reqType {
case dnsmessage.TypeA:
rec.A = ipRec
case dnsmessage.TypeAAAA:
rec.AAAA = ipRec
}
errors.LogInfo(context.Background(), c.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
c.ips[req.domain] = rec
switch req.reqType {
case dnsmessage.TypeA:
c.pub.Publish(req.domain+"4", nil)
if !c.disableCache {
_, _, err := rec.AAAA.getIPs()
if !go_errors.Is(err, errRecordNotFound) {
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()
common.Must(c.cacheCleanup.Start())
}
func (c *CacheController) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
c.RLock()
record, found := c.ips[domain]
c.RUnlock()
if !found {
return nil, 0, errRecordNotFound
}
var errs []error
var allIPs []net.IP
var rTTL uint32 = dns_feature.DefaultTTL
mergeReq := option.IPv4Enable && option.IPv6Enable
if option.IPv4Enable {
ips, ttl, err := record.A.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 option.IPv6Enable {
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 {
return allIPs, rTTL, nil
}
if go_errors.Is(errs[0], errs[1]) {
return nil, rTTL, errs[0]
}
return nil, rTTL, errors.Combine(errs...)
}
func (c *CacheController) registerSubscribers(domain string, option dns_feature.IPOption) (sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
// ipv4 and ipv6 belong to different subscription groups
if option.IPv4Enable {
sub4 = c.pub.Subscribe(domain + "4")
}
if option.IPv6Enable {
sub6 = c.pub.Subscribe(domain + "6")
}
return
}
func closeSubscribers(sub4 *pubsub.Subscriber, sub6 *pubsub.Subscriber) {
if sub4 != nil {
sub4.Close()
}
if sub6 != nil {
sub6.Close()
}
}

View File

@@ -80,6 +80,7 @@ const (
QueryStrategy_USE_IP QueryStrategy = 0 QueryStrategy_USE_IP QueryStrategy = 0
QueryStrategy_USE_IP4 QueryStrategy = 1 QueryStrategy_USE_IP4 QueryStrategy = 1
QueryStrategy_USE_IP6 QueryStrategy = 2 QueryStrategy_USE_IP6 QueryStrategy = 2
QueryStrategy_USE_SYS QueryStrategy = 3
) )
// Enum value maps for QueryStrategy. // Enum value maps for QueryStrategy.
@@ -88,11 +89,13 @@ var (
0: "USE_IP", 0: "USE_IP",
1: "USE_IP4", 1: "USE_IP4",
2: "USE_IP6", 2: "USE_IP6",
3: "USE_SYS",
} }
QueryStrategy_value = map[string]int32{ QueryStrategy_value = map[string]int32{
"USE_IP": 0, "USE_IP": 0,
"USE_IP4": 1, "USE_IP4": 1,
"USE_IP6": 2, "USE_IP6": 2,
"USE_SYS": 3,
} }
) )
@@ -128,16 +131,20 @@ type NameServer struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"` SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"` PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"` ExpectedGeoip []*router.GeoIP `protobuf:"bytes,3,rep,name=expected_geoip,json=expectedGeoip,proto3" json:"expected_geoip,omitempty"`
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"` OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"` QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
AllowUnexpectedIPs bool `protobuf:"varint,8,opt,name=allowUnexpectedIPs,proto3" json:"allowUnexpectedIPs,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"`
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"`
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
} }
func (x *NameServer) Reset() { func (x *NameServer) Reset() {
@@ -198,9 +205,9 @@ func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
return nil return nil
} }
func (x *NameServer) GetGeoip() []*router.GeoIP { func (x *NameServer) GetExpectedGeoip() []*router.GeoIP {
if x != nil { if x != nil {
return x.Geoip return x.ExpectedGeoip
} }
return nil return nil
} }
@@ -219,9 +226,9 @@ func (x *NameServer) GetQueryStrategy() QueryStrategy {
return QueryStrategy_USE_IP return QueryStrategy_USE_IP
} }
func (x *NameServer) GetAllowUnexpectedIPs() bool { func (x *NameServer) GetActPrior() bool {
if x != nil { if x != nil {
return x.AllowUnexpectedIPs return x.ActPrior
} }
return false return false
} }
@@ -240,6 +247,34 @@ func (x *NameServer) GetTimeoutMs() uint64 {
return 0 return 0
} }
func (x *NameServer) GetDisableCache() bool {
if x != nil {
return x.DisableCache
}
return false
}
func (x *NameServer) GetFinalQuery() bool {
if x != nil {
return x.FinalQuery
}
return false
}
func (x *NameServer) GetUnexpectedGeoip() []*router.GeoIP {
if x != nil {
return x.UnexpectedGeoip
}
return nil
}
func (x *NameServer) GetActUnprior() bool {
if x != nil {
return x.ActUnprior
}
return false
}
type Config struct { type Config struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -532,7 +567,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, 0x92, 0x05, 0x0a, 0x0a, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb6, 0x06, 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,
@@ -546,81 +581,92 @@ var file_app_dns_config_proto_rawDesc = []byte{
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a,
0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3d, 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x65,
0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0d, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,
0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x65, 0x64, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69,
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e,
0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e,
0x75, 0x6c, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c,
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73,
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,
0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72,
0x77, 0x55, 0x6e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x49, 0x50, 0x73, 0x18, 0x08, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x63, 0x74,
0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x6e, 0x65, 0x78, 0x70, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x63, 0x74,
0x65, 0x63, 0x74, 0x65, 0x64, 0x49, 0x50, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x09, 0x20, 0x01,
0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x6f,
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x69, 0x73,
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6e,
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x61, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66,
0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x69, 0x6e, 0x61, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x41, 0x0a, 0x10, 0x75, 0x6e, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0d, 0x20,
0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72, 0x69, 0x67, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0f, 0x75, 0x6e, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x1e, 0x0a, 0x0a,
0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x61, 0x63, 0x74, 0x55, 0x6e, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08,
0x22, 0x9c, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x55, 0x6e, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x1a, 0x5e, 0x0a, 0x0e,
0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34,
0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78,
0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02,
0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x36, 0x0a, 0x0c,
0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04,
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65,
0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04,
0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x9c, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x42, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a,
0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c,
0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63,
0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69,
0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e,
0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e,
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52,
0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x36, 0x0a, 0x16, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03,
0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22,
0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x64, 0x69, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63,
0x61, 0x74, 0x63, 0x68, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x68, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61,
0x70, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61,
0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53,
0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74,
0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c,
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52,
0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x12, 0x36, 0x0a, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62,
0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x61, 0x63, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08,
0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x52, 0x16, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63,
0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x6b, 0x49, 0x66, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73,
0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63,
0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16,
0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x01, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03,
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x02, 0x42, 0x46, 0x0a, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65,
0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
0x73, 0x50, 0x01, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08,
0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x07, 0x10, 0x08, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74,
0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c,
0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 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 (
@@ -651,19 +697,20 @@ var file_app_dns_config_proto_goTypes = []any{
var file_app_dns_config_proto_depIdxs = []int32{ var file_app_dns_config_proto_depIdxs = []int32{
7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint 7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
4, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain 4, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain
8, // 2: xray.app.dns.NameServer.geoip:type_name -> xray.app.router.GeoIP 8, // 2: xray.app.dns.NameServer.expected_geoip:type_name -> xray.app.router.GeoIP
5, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule 5, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
1, // 4: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy 1, // 4: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy
2, // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer 8, // 5: xray.app.dns.NameServer.unexpected_geoip:type_name -> xray.app.router.GeoIP
6, // 6: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping 2, // 6: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
1, // 7: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy 6, // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
0, // 8: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType 1, // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
0, // 9: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType 0, // 9: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
10, // [10:10] is the sub-list for method output_type 0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
10, // [10:10] is the sub-list for method input_type 11, // [11:11] is the sub-list for method output_type
10, // [10:10] is the sub-list for extension type_name 11, // [11:11] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension extendee 11, // [11:11] is the sub-list for extension type_name
0, // [0:10] is the sub-list for field type_name 11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
} }
func init() { file_app_dns_config_proto_init() } func init() { file_app_dns_config_proto_init() }

View File

@@ -25,12 +25,16 @@ message NameServer {
} }
repeated PriorityDomain prioritized_domain = 2; repeated PriorityDomain prioritized_domain = 2;
repeated xray.app.router.GeoIP geoip = 3; repeated xray.app.router.GeoIP expected_geoip = 3;
repeated OriginalRule original_rules = 4; repeated OriginalRule original_rules = 4;
QueryStrategy query_strategy = 7; QueryStrategy query_strategy = 7;
bool allowUnexpectedIPs = 8; bool actPrior = 8;
string tag = 9; string tag = 9;
uint64 timeoutMs = 10; uint64 timeoutMs = 10;
bool disableCache = 11;
bool finalQuery = 12;
repeated xray.app.router.GeoIP unexpected_geoip = 13;
bool actUnprior = 14;
} }
enum DomainMatchingType { enum DomainMatchingType {
@@ -44,6 +48,7 @@ enum QueryStrategy {
USE_IP = 0; USE_IP = 0;
USE_IP4 = 1; USE_IP4 = 1;
USE_IP6 = 2; USE_IP6 = 2;
USE_SYS = 3;
} }
message Config { message Config {

View File

@@ -3,12 +3,12 @@ package dns
import ( import (
"context" "context"
go_errors "errors"
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"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"
@@ -20,8 +20,6 @@ import (
// DNS is a DNS rely server. // DNS is a DNS rely server.
type DNS struct { type DNS struct {
sync.Mutex sync.Mutex
tag string
disableCache bool
disableFallback bool disableFallback bool
disableFallbackIfMatch bool disableFallbackIfMatch bool
ipOption *dns.IPOption ipOption *dns.IPOption
@@ -30,6 +28,7 @@ type DNS struct {
ctx context.Context ctx context.Context
domainMatcher strmatcher.IndexMatcher domainMatcher strmatcher.IndexMatcher
matcherInfos []*DomainMatcherInfo matcherInfos []*DomainMatcherInfo
checkSystem bool
} }
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
@@ -40,13 +39,6 @@ type DomainMatcherInfo struct {
// New creates a new DNS server with given configuration. // New creates a new DNS server with given configuration.
func New(ctx context.Context, config *Config) (*DNS, error) { func New(ctx context.Context, config *Config) (*DNS, error) {
var tag string
if len(config.Tag) > 0 {
tag = config.Tag
} else {
tag = generateRandomTag()
}
var clientIP net.IP var clientIP net.IP
switch len(config.ClientIp) { switch len(config.ClientIp) {
case 0, net.IPv4len, net.IPv6len: case 0, net.IPv4len, net.IPv6len:
@@ -55,26 +47,36 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
return nil, errors.New("unexpected client IP length ", len(config.ClientIp)) return nil, errors.New("unexpected client IP length ", len(config.ClientIp))
} }
var ipOption *dns.IPOption var ipOption dns.IPOption
checkSystem := false
switch config.QueryStrategy { switch config.QueryStrategy {
case QueryStrategy_USE_IP: case QueryStrategy_USE_IP:
ipOption = &dns.IPOption{ ipOption = dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
} }
case QueryStrategy_USE_SYS:
ipOption = dns.IPOption{
IPv4Enable: true,
IPv6Enable: true,
FakeEnable: false,
}
checkSystem = true
case QueryStrategy_USE_IP4: case QueryStrategy_USE_IP4:
ipOption = &dns.IPOption{ ipOption = dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: false, IPv6Enable: false,
FakeEnable: false, FakeEnable: false,
} }
case QueryStrategy_USE_IP6: case QueryStrategy_USE_IP6:
ipOption = &dns.IPOption{ ipOption = dns.IPOption{
IPv4Enable: false, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
} }
default:
return nil, errors.New("unexpected query strategy ", config.QueryStrategy)
} }
hosts, err := NewStaticHosts(config.StaticHosts) hosts, err := NewStaticHosts(config.StaticHosts)
@@ -82,8 +84,14 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
return nil, errors.New("failed to create hosts").Base(err) return nil, errors.New("failed to create hosts").Base(err)
} }
clients := []*Client{} var clients []*Client
domainRuleCount := 0 domainRuleCount := 0
var defaultTag = config.Tag
if len(config.Tag) == 0 {
defaultTag = generateRandomTag()
}
for _, ns := range config.NameServer { for _, ns := range config.NameServer {
domainRuleCount += len(ns.PrioritizedDomain) domainRuleCount += len(ns.PrioritizedDomain)
} }
@@ -91,7 +99,6 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1 // MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1) matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
domainMatcher := &strmatcher.MatcherGroup{} domainMatcher := &strmatcher.MatcherGroup{}
geoipContainer := router.GeoIPMatcherContainer{}
for _, ns := range config.NameServer { for _, ns := range config.NameServer {
clientIdx := len(clients) clientIdx := len(clients)
@@ -109,7 +116,19 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
case net.IPv4len, net.IPv6len: case net.IPv4len, net.IPv6len:
myClientIP = net.IP(ns.ClientIp) myClientIP = net.IP(ns.ClientIp)
} }
client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain)
disableCache := config.DisableCache || ns.DisableCache
var tag = defaultTag
if len(ns.Tag) > 0 {
tag = ns.Tag
}
clientIPOption := ResolveIpOptionOverride(ns.QueryStrategy, ipOption)
if !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {
return nil, errors.New("no QueryStrategy available for ", ns.Address)
}
client, err := NewClient(ctx, ns, myClientIP, disableCache, 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)
} }
@@ -118,20 +137,19 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
// If there is no DNS client in config, add a `localhost` DNS client // If there is no DNS client in config, add a `localhost` DNS client
if len(clients) == 0 { if len(clients) == 0 {
clients = append(clients, NewLocalDNSClient()) clients = append(clients, NewLocalDNSClient(ipOption))
} }
return &DNS{ return &DNS{
tag: tag,
hosts: hosts, hosts: hosts,
ipOption: ipOption, ipOption: &ipOption,
clients: clients, clients: clients,
ctx: ctx, ctx: ctx,
domainMatcher: domainMatcher, domainMatcher: domainMatcher,
matcherInfos: matcherInfos, matcherInfos: matcherInfos,
disableCache: config.DisableCache,
disableFallback: config.DisableFallback, disableFallback: config.DisableFallback,
disableFallbackIfMatch: config.DisableFallbackIfMatch, disableFallbackIfMatch: config.DisableFallbackIfMatch,
checkSystem: checkSystem,
}, nil }, nil
} }
@@ -153,27 +171,45 @@ func (s *DNS) Close() error {
// IsOwnLink implements proxy.dns.ownLinkVerifier // IsOwnLink implements proxy.dns.ownLinkVerifier
func (s *DNS) IsOwnLink(ctx context.Context) bool { func (s *DNS) IsOwnLink(ctx context.Context) bool {
inbound := session.InboundFromContext(ctx) inbound := session.InboundFromContext(ctx)
return inbound != nil && inbound.Tag == s.tag if inbound == nil {
return false
}
for _, client := range s.clients {
if client.tag == inbound.Tag {
return true
}
}
return false
} }
// LookupIP implements dns.Client. // LookupIP implements dns.Client.
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) { func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
// Normalize the FQDN form query
domain = strings.TrimSuffix(domain, ".")
if domain == "" { if domain == "" {
return nil, 0, errors.New("empty domain name") return nil, 0, errors.New("empty domain name")
} }
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable if s.checkSystem {
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable supportIPv4, supportIPv6 := checkSystemNetwork()
option.IPv4Enable = option.IPv4Enable && supportIPv4
option.IPv6Enable = option.IPv6Enable && supportIPv6
} else {
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
}
if !option.IPv4Enable && !option.IPv6Enable { if !option.IPv4Enable && !option.IPv6Enable {
return nil, 0, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }
// Normalize the FQDN form query
domain = strings.TrimSuffix(domain, ".")
// Static host lookup // Static host lookup
switch addrs := s.hosts.Lookup(domain, option); { switch addrs, err := s.hosts.Lookup(domain, option); {
case err != nil:
if go_errors.Is(err, dns.ErrEmptyResponse) {
return nil, 0, dns.ErrEmptyResponse
}
return nil, 0, errors.New("returning nil for domain ", domain).Base(err)
case addrs == nil: // Domain not recorded in static host case addrs == nil: // Domain not recorded in static host
break break
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled) case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
@@ -184,64 +220,52 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
default: // Successfully found ip records in static host default: // Successfully found ip records in static host
errors.LogInfo(s.ctx, "returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs) errors.LogInfo(s.ctx, "returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs)
ips, err := toNetIP(addrs) ips, err := toNetIP(addrs)
return ips, 10, err // Hosts ttl is 10 if err != nil {
return nil, 0, err
}
return ips, 10, nil // Hosts ttl is 10
} }
// Name servers lookup // Name servers lookup
errs := []error{} var errs []error
ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
for _, client := range s.sortClients(domain) { for _, client := range s.sortClients(domain) {
if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") { if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name()) errors.LogDebug(s.ctx, "skip DNS resolution for domain ", domain, " at server ", client.Name())
continue continue
} }
ips, ttl, err := client.QueryIP(ctx, domain, option, s.disableCache)
ips, ttl, err := client.QueryIP(s.ctx, domain, option)
if len(ips) > 0 { if len(ips) > 0 {
if ttl == 0 {
ttl = 1
}
return ips, ttl, nil return ips, ttl, nil
} }
if err != nil {
errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name()) errors.LogInfoInner(s.ctx, err, "failed to lookup ip for domain ", domain, " at server ", client.Name())
errs = append(errs, err) if err == nil {
err = dns.ErrEmptyResponse
} }
// 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size errs = append(errs, err)
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 {
return nil, 0, err if client.IsFinalQuery() {
break
} }
} }
return nil, 0, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...)) if len(errs) > 0 {
} allErrs := errors.Combine(errs...)
err0 := errs[0]
// LookupHosts implements dns.HostsLookup. if errors.AllEqual(err0, allErrs) {
func (s *DNS) LookupHosts(domain string) *net.Address { if go_errors.Is(err0, dns.ErrEmptyResponse) {
domain = strings.TrimSuffix(domain, ".") return nil, 0, dns.ErrEmptyResponse
if domain == "" { }
return nil return nil, 0, errors.New("returning nil for domain ", domain).Base(err0)
}
return nil, 0, errors.New("returning nil for domain ", domain).Base(allErrs)
} }
// Normalize the FQDN form query return nil, 0, dns.ErrEmptyResponse
addrs := s.hosts.Lookup(domain, *s.ipOption)
if len(addrs) > 0 {
errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].String())
return &addrs[0]
}
return nil
}
// GetIPOption implements ClientWithIPOption.
func (s *DNS) GetIPOption() *dns.IPOption {
return s.ipOption
}
// SetQueryOption implements ClientWithIPOption.
func (s *DNS) SetQueryOption(isIPv4Enable, isIPv6Enable bool) {
s.ipOption.IPv4Enable = isIPv4Enable
s.ipOption.IPv6Enable = isIPv6Enable
}
// SetFakeDNSOption implements ClientWithIPOption.
func (s *DNS) SetFakeDNSOption(isFakeEnable bool) {
s.ipOption.FakeEnable = isFakeEnable
} }
func (s *DNS) sortClients(domain string) []*Client { func (s *DNS) sortClients(domain string) []*Client {
@@ -303,3 +327,22 @@ func init() {
return New(ctx, config.(*Config)) return New(ctx, config.(*Config))
})) }))
} }
func checkSystemNetwork() (supportIPv4 bool, supportIPv6 bool) {
conn4, err4 := net.Dial("udp4", "8.8.8.8:53")
if err4 != nil {
supportIPv4 = false
} else {
supportIPv4 = true
conn4.Close()
}
conn6, err6 := net.Dial("udp6", "[2001:4860:4860::8888]:53")
if err6 != nil {
supportIPv6 = false
} else {
supportIPv6 = true
conn6.Close()
}
return
}

View File

@@ -76,6 +76,9 @@ func (*staticHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA: case q.Name == "notexist.google.com." && q.Qtype == dns.TypeAAAA:
ans.MsgHdr.Rcode = dns.RcodeNameError ans.MsgHdr.Rcode = dns.RcodeNameError
case q.Name == "notexist.google.com." && q.Qtype == dns.TypeA:
ans.MsgHdr.Rcode = dns.RcodeNameError
case q.Name == "hostname." && q.Qtype == dns.TypeA: case q.Name == "hostname." && q.Qtype == dns.TypeA:
rr, _ := dns.NewRR("hostname. IN A 127.0.0.1") rr, _ := dns.NewRR("hostname. IN A 127.0.0.1")
ans.Answer = append(ans.Answer, rr) ans.Answer = append(ans.Answer, rr)
@@ -117,7 +120,6 @@ func TestUDPServerSubnet(t *testing.T) {
Handler: &staticHandler{}, Handler: &staticHandler{},
UDPSize: 1200, UDPSize: 1200,
} }
go dnsServer.ListenAndServe() go dnsServer.ListenAndServe()
time.Sleep(time.Second) time.Sleep(time.Second)
@@ -537,7 +539,7 @@ func TestIPMatch(t *testing.T) {
}, },
Port: uint32(port), Port: uint32(port),
}, },
Geoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ {
CountryCode: "local", CountryCode: "local",
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
@@ -561,7 +563,7 @@ func TestIPMatch(t *testing.T) {
}, },
Port: uint32(port), Port: uint32(port),
}, },
Geoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ {
CountryCode: "test", CountryCode: "test",
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
@@ -665,7 +667,7 @@ func TestLocalDomain(t *testing.T) {
// Equivalent of dotless:localhost // Equivalent of dotless:localhost
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"}, {Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
}, },
Geoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ // Will match localhost, localhost-a and localhost-b, { // Will match localhost, localhost-a and localhost-b,
CountryCode: "local", CountryCode: "local",
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
@@ -895,7 +897,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
Domain: "google.com", Domain: "google.com",
}, },
}, },
Geoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ // Will only match 8.8.8.8 and 8.8.4.4 { // Will only match 8.8.8.8 and 8.8.4.4
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{Ip: []byte{8, 8, 8, 8}, Prefix: 32}, {Ip: []byte{8, 8, 8, 8}, Prefix: 32},
@@ -920,7 +922,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
Domain: "google.com", Domain: "google.com",
}, },
}, },
Geoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ // Will match 8.8.8.8 and 8.8.8.7, etc { // Will match 8.8.8.8 and 8.8.8.7, etc
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{Ip: []byte{8, 8, 8, 7}, Prefix: 24}, {Ip: []byte{8, 8, 8, 7}, Prefix: 24},
@@ -944,7 +946,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
Domain: "api.google.com", Domain: "api.google.com",
}, },
}, },
Geoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ // Will only match 8.8.7.7 (api.google.com) { // Will only match 8.8.7.7 (api.google.com)
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{Ip: []byte{8, 8, 7, 7}, Prefix: 32}, {Ip: []byte{8, 8, 7, 7}, Prefix: 32},
@@ -968,7 +970,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
Domain: "v2.api.google.com", Domain: "v2.api.google.com",
}, },
}, },
Geoip: []*router.GeoIP{ ExpectedGeoip: []*router.GeoIP{
{ // Will only match 8.8.7.8 (v2.api.google.com) { // Will only match 8.8.7.8 (v2.api.google.com)
Cidr: []*router.CIDR{ Cidr: []*router.CIDR{
{Ip: []byte{8, 8, 7, 8}, Prefix: 32}, {Ip: []byte{8, 8, 7, 8}, Prefix: 32},

View File

@@ -32,31 +32,30 @@ type record struct {
// IPRecord is a cacheable item for a resolved domain // IPRecord is a cacheable item for a resolved domain
type IPRecord struct { type IPRecord struct {
ReqID uint16 ReqID uint16
IP []net.Address IP []net.IP
Expire time.Time Expire time.Time
RCode dnsmessage.RCode RCode dnsmessage.RCode
RawHeader *dnsmessage.Header RawHeader *dnsmessage.Header
} }
func (r *IPRecord) getIPs() ([]net.Address, uint32, error) { func (r *IPRecord) getIPs() ([]net.IP, uint32, error) {
if r == nil || r.Expire.Before(time.Now()) { if r == nil {
return nil, 0, errRecordNotFound return nil, 0, errRecordNotFound
} }
if r.RCode != dnsmessage.RCodeSuccess { untilExpire := time.Until(r.Expire)
return nil, 0, dns_feature.RCodeError(r.RCode) if untilExpire <= 0 {
return nil, 0, errRecordNotFound
} }
ttl := uint32(time.Until(r.Expire) / time.Second)
return r.IP, ttl, nil
}
func isNewer(baseRec *IPRecord, newRec *IPRecord) bool { ttl := uint32(untilExpire/time.Second) + uint32(1)
if newRec == nil { if r.RCode != dnsmessage.RCodeSuccess {
return false return nil, ttl, dns_feature.RCodeError(r.RCode)
} }
if baseRec == nil { if len(r.IP) == 0 {
return true return nil, ttl, dns_feature.ErrEmptyResponse
} }
return baseRec.Expire.Before(newRec.Expire)
return r.IP, ttl, nil
} }
var errRecordNotFound = errors.New("record not found") var errRecordNotFound = errors.New("record not found")
@@ -193,7 +192,7 @@ func parseResponse(payload []byte) (*IPRecord, error) {
ipRecord := &IPRecord{ ipRecord := &IPRecord{
ReqID: h.ID, ReqID: h.ID,
RCode: h.RCode, RCode: h.RCode,
Expire: now.Add(time.Second * 600), Expire: now.Add(time.Second * dns_feature.DefaultTTL),
RawHeader: &h, RawHeader: &h,
} }
@@ -209,7 +208,7 @@ L:
ttl := ah.TTL ttl := ah.TTL
if ttl == 0 { if ttl == 0 {
ttl = 600 ttl = 1
} }
expire := now.Add(time.Duration(ttl) * time.Second) expire := now.Add(time.Duration(ttl) * time.Second)
if ipRecord.Expire.After(expire) { if ipRecord.Expire.After(expire) {
@@ -223,14 +222,17 @@ L:
errors.LogInfoInner(context.Background(), err, "failed to parse A record for domain: ", ah.Name) errors.LogInfoInner(context.Background(), err, "failed to parse A record for domain: ", ah.Name)
break L break L
} }
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:])) ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.A[:]).IP())
case dnsmessage.TypeAAAA: case dnsmessage.TypeAAAA:
ans, err := parser.AAAAResource() ans, err := parser.AAAAResource()
if err != nil { if err != nil {
errors.LogInfoInner(context.Background(), err, "failed to parse AAAA record for domain: ", ah.Name) errors.LogInfoInner(context.Background(), err, "failed to parse AAAA record for domain: ", ah.Name)
break L break L
} }
ipRecord.IP = append(ipRecord.IP, net.IPAddress(ans.AAAA[:])) newIP := net.IPAddress(ans.AAAA[:]).IP()
if len(newIP) == net.IPv6len {
ipRecord.IP = append(ipRecord.IP, newIP)
}
default: default:
if err := parser.SkipAnswer(); err != nil { if err := parser.SkipAnswer(); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to skip answer") errors.LogInfoInner(context.Background(), err, "failed to skip answer")

View File

@@ -51,7 +51,7 @@ func Test_parseResponse(t *testing.T) {
}{ }{
{ {
"empty", "empty",
&IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess, nil}, &IPRecord{0, []net.IP(nil), time.Time{}, dnsmessage.RCodeSuccess, nil},
false, false,
}, },
{ {
@@ -63,7 +63,7 @@ func Test_parseResponse(t *testing.T) {
"a record", "a record",
&IPRecord{ &IPRecord{
1, 1,
[]net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")}, []net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4")},
time.Time{}, time.Time{},
dnsmessage.RCodeSuccess, dnsmessage.RCodeSuccess,
nil, nil,
@@ -72,7 +72,7 @@ func Test_parseResponse(t *testing.T) {
}, },
{ {
"aaaa record", "aaaa record",
&IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil}, &IPRecord{2, []net.IP{net.ParseIP("2001::123:8888"), net.ParseIP("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
false, false,
}, },
} }

View File

@@ -2,6 +2,7 @@ package dns
import ( import (
"context" "context"
"strconv"
"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"
@@ -32,7 +33,15 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
ips := make([]net.Address, 0, len(mapping.Ip)+1) ips := make([]net.Address, 0, len(mapping.Ip)+1)
switch { switch {
case len(mapping.ProxiedDomain) > 0: case len(mapping.ProxiedDomain) > 0:
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain)) if mapping.ProxiedDomain[0] == '#' {
rcode, err := strconv.Atoi(mapping.ProxiedDomain[1:])
if err != nil {
return nil, err
}
ips = append(ips, dns.RCodeError(rcode))
} else {
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
}
case len(mapping.Ip) > 0: case len(mapping.Ip) > 0:
for _, ip := range mapping.Ip { for _, ip := range mapping.Ip {
addr := net.IPAddress(ip) addr := net.IPAddress(ip)
@@ -41,8 +50,6 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
} }
ips = append(ips, addr) ips = append(ips, addr)
} }
default:
return nil, errors.New("neither IP address nor proxied domain specified for domain: ", mapping.Domain).AtWarning()
} }
sh.ips[id] = ips sh.ips[id] = ips
@@ -61,33 +68,51 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
return filtered return filtered
} }
func (h *StaticHosts) lookupInternal(domain string) []net.Address { func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
var ips []net.Address ips := make([]net.Address, 0)
found := false
for _, id := range h.matchers.Match(domain) { for _, id := range h.matchers.Match(domain) {
for _, v := range h.ips[id] {
if err, ok := v.(dns.RCodeError); ok {
if uint16(err) == 0 {
return nil, dns.ErrEmptyResponse
}
return nil, err
}
}
ips = append(ips, h.ips[id]...) ips = append(ips, h.ips[id]...)
found = true
} }
return ips if !found {
return nil, nil
}
return ips, nil
} }
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address { func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
switch addrs := h.lookupInternal(domain); { switch addrs, err := h.lookupInternal(domain); {
case err != nil:
return nil, err
case len(addrs) == 0: // Not recorded in static hosts, return nil case len(addrs) == 0: // Not recorded in static hosts, return nil
return nil return addrs, nil
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it") errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
if maxDepth > 0 { if maxDepth > 0 {
unwrapped := h.lookup(addrs[0].Domain(), option, maxDepth-1) unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1)
if err != nil {
return nil, err
}
if unwrapped != nil { if unwrapped != nil {
return unwrapped return unwrapped, nil
} }
} }
return addrs return addrs, nil
default: // IP record found, return a non-nil IP array default: // IP record found, return a non-nil IP array
return filterIP(addrs, option) return filterIP(addrs, option), nil
} }
} }
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts. // Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) []net.Address { func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
return h.lookup(domain, option, 5) return h.lookup(domain, option, 5)
} }

View File

@@ -12,6 +12,11 @@ import (
func TestStaticHosts(t *testing.T) { func TestStaticHosts(t *testing.T) {
pb := []*Config_HostMapping{ pb := []*Config_HostMapping{
{
Type: DomainMatchingType_Subdomain,
Domain: "lan",
ProxiedDomain: "#3",
},
{ {
Type: DomainMatchingType_Full, Type: DomainMatchingType_Full,
Domain: "example.com", Domain: "example.com",
@@ -54,7 +59,14 @@ func TestStaticHosts(t *testing.T) {
common.Must(err) common.Must(err)
{ {
ips := hosts.Lookup("example.com", dns.IPOption{ _, err := hosts.Lookup("example.com.lan", dns.IPOption{})
if dns.RCodeFromError(err) != 3 {
t.Error(err)
}
}
{
ips, _ := hosts.Lookup("example.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}) })
@@ -67,7 +79,7 @@ func TestStaticHosts(t *testing.T) {
} }
{ {
domain := hosts.Lookup("proxy.xray.com", dns.IPOption{ domain, _ := hosts.Lookup("proxy.xray.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: false, IPv6Enable: false,
}) })
@@ -80,7 +92,7 @@ func TestStaticHosts(t *testing.T) {
} }
{ {
domain := hosts.Lookup("proxy2.xray.com", dns.IPOption{ domain, _ := hosts.Lookup("proxy2.xray.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: false, IPv6Enable: false,
}) })
@@ -93,7 +105,7 @@ func TestStaticHosts(t *testing.T) {
} }
{ {
ips := hosts.Lookup("www.example.cn", dns.IPOption{ ips, _ := hosts.Lookup("www.example.cn", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}) })
@@ -106,7 +118,7 @@ func TestStaticHosts(t *testing.T) {
} }
{ {
ips := hosts.Lookup("baidu.com", dns.IPOption{ ips, _ := hosts.Lookup("baidu.com", dns.IPOption{
IPv4Enable: false, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
}) })

View File

@@ -21,25 +21,27 @@ type Server interface {
// Name of the Client. // Name of the Client.
Name() string Name() string
// QueryIP sends IP queries to its configured server. // QueryIP sends IP queries to its configured server.
QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, uint32, error) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error)
} }
// Client is the interface for DNS client. // Client is the interface for DNS client.
type Client struct { type Client struct {
server Server server Server
clientIP net.IP skipFallback bool
skipFallback bool domains []string
domains []string expectedIPs []*router.GeoIPMatcher
expectedIPs []*router.GeoIPMatcher unexpectedIPs []*router.GeoIPMatcher
allowUnexpectedIPs bool actPrior bool
tag string actUnprior bool
timeoutMs time.Duration tag string
timeoutMs time.Duration
finalQuery bool
ipOption *dns.IPOption
checkSystem bool
} }
var errExpectedIPNonMatch = errors.New("expectedIPs not match")
// 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, queryStrategy QueryStrategy) (Server, error) { func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dispatcher, disableCache bool, 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 {
@@ -47,26 +49,29 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
} }
switch { switch {
case strings.EqualFold(u.String(), "localhost"): case strings.EqualFold(u.String(), "localhost"):
return NewLocalNameServer(queryStrategy), 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, queryStrategy, dispatcher, false), nil return NewDoHNameServer(u, dispatcher, false, disableCache, 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, queryStrategy, dispatcher, true), nil return NewDoHNameServer(u, dispatcher, true, disableCache, 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, queryStrategy, nil, false), nil return NewDoHNameServer(u, nil, false, disableCache, 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, queryStrategy, nil, true), nil return NewDoHNameServer(u, nil, true, disableCache, 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, queryStrategy) return NewQUICNameServer(u, disableCache, 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, queryStrategy) return NewTCPNameServer(u, dispatcher, disableCache, 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, queryStrategy) return NewTCPLocalNameServer(u, disableCache, clientIP)
case strings.EqualFold(u.String(), "fakedns"): case strings.EqualFold(u.String(), "fakedns"):
var fd dns.FakeDNSEngine var fd dns.FakeDNSEngine
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) { err = core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
fd = fdns fd = fdns
}) })
if err != nil {
return nil, err
}
return NewFakeDNSServer(fd), nil return NewFakeDNSServer(fd), nil
} }
} }
@@ -74,7 +79,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, queryStrategy), nil return NewClassicNameServer(dest, dispatcher, disableCache, 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()
} }
@@ -84,7 +89,9 @@ func NewClient(
ctx context.Context, ctx context.Context,
ns *NameServer, ns *NameServer,
clientIP net.IP, clientIP net.IP,
container router.GeoIPMatcherContainer, disableCache bool,
tag string,
ipOption dns.IPOption,
matcherInfos *[]*DomainMatcherInfo, matcherInfos *[]*DomainMatcherInfo,
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error, updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error,
) (*Client, error) { ) (*Client, error) {
@@ -92,7 +99,7 @@ func NewClient(
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, ns.GetQueryStrategy()) server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, 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()
} }
@@ -147,13 +154,23 @@ func NewClient(
} }
// Establish expected IPs // Establish expected IPs
var matchers []*router.GeoIPMatcher var expectedMatchers []*router.GeoIPMatcher
for _, geoip := range ns.Geoip { for _, geoip := range ns.ExpectedGeoip {
matcher, err := container.Add(geoip) matcher, err := router.GlobalGeoIPContainer.Add(geoip)
if err != nil { if err != nil {
return errors.New("failed to create ip matcher").Base(err).AtWarning() return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
} }
matchers = append(matchers, matcher) expectedMatchers = append(expectedMatchers, matcher)
}
// Establish unexpected IPs
var unexpectedMatchers []*router.GeoIPMatcher
for _, geoip := range ns.UnexpectedGeoip {
matcher, err := router.GlobalGeoIPContainer.Add(geoip)
if err != nil {
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
}
unexpectedMatchers = append(unexpectedMatchers, matcher)
} }
if len(clientIP) > 0 { if len(clientIP) > 0 {
@@ -161,7 +178,7 @@ func NewClient(
case *net.IPOrDomain_Domain: case *net.IPOrDomain_Domain:
errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()) errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String())
case *net.IPOrDomain_Ip: case *net.IPOrDomain_Ip:
errors.LogInfo(ctx, "DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()) errors.LogInfo(ctx, "DNS: client ", net.IP(ns.Address.Address.GetIp()), " uses clientIP ", clientIP.String())
} }
} }
@@ -169,15 +186,21 @@ func NewClient(
if ns.TimeoutMs > 0 { if ns.TimeoutMs > 0 {
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
} }
checkSystem := ns.QueryStrategy == QueryStrategy_USE_SYS
client.server = server client.server = server
client.clientIP = clientIP
client.skipFallback = ns.SkipFallback client.skipFallback = ns.SkipFallback
client.domains = rules client.domains = rules
client.expectedIPs = matchers client.expectedIPs = expectedMatchers
client.allowUnexpectedIPs = ns.AllowUnexpectedIPs client.unexpectedIPs = unexpectedMatchers
client.tag = ns.Tag client.actPrior = ns.ActPrior
client.actUnprior = ns.ActUnprior
client.tag = tag
client.timeoutMs = timeoutMs client.timeoutMs = timeoutMs
client.finalQuery = ns.FinalQuery
client.ipOption = &ipOption
client.checkSystem = checkSystem
return nil return nil
}) })
return client, err return client, err
@@ -188,54 +211,79 @@ 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, disableCache bool) ([]net.IP, uint32, error) { func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeoutMs) if c.checkSystem {
if len(c.tag) != 0 { supportIPv4, supportIPv6 := checkSystemNetwork()
content := session.InboundFromContext(ctx) option.IPv4Enable = option.IPv4Enable && supportIPv4
errors.LogDebug(ctx, "DNS: client override tag from ", content.Tag, " to ", c.tag) option.IPv6Enable = option.IPv6Enable && supportIPv6
// create a new context to override the tag } else {
// do not direct set *content.Tag, it might be used by other clients option.IPv4Enable = option.IPv4Enable && c.ipOption.IPv4Enable
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag}) option.IPv6Enable = option.IPv6Enable && c.ipOption.IPv6Enable
} }
ips, ttl, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
if !option.IPv4Enable && !option.IPv6Enable {
return nil, 0, dns.ErrEmptyResponse
}
ctx, cancel := context.WithTimeout(ctx, c.timeoutMs)
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag})
ips, ttl, err := c.server.QueryIP(ctx, domain, option)
cancel() cancel()
if err != nil { if err != nil {
return ips, ttl, err return nil, 0, err
} }
netips, err := c.MatchExpectedIPs(domain, ips)
return netips, ttl, err
}
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones. if len(ips) == 0 {
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) { return nil, 0, dns.ErrEmptyResponse
if len(c.expectedIPs) == 0 {
return ips, nil
} }
newIps := []net.IP{}
for _, ip := range ips { if len(c.expectedIPs) > 0 && !c.actPrior {
for _, matcher := range c.expectedIPs { ips = router.MatchIPs(c.expectedIPs, ips, false)
if matcher.Match(ip) { errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", ips, " matched at server ", c.Name())
newIps = append(newIps, ip) if len(ips) == 0 {
break return nil, 0, dns.ErrEmptyResponse
}
} }
} }
if len(newIps) == 0 {
if c.allowUnexpectedIPs { if len(c.unexpectedIPs) > 0 && !c.actUnprior {
return ips, nil ips = router.MatchIPs(c.unexpectedIPs, ips, true)
errors.LogDebug(context.Background(), "domain ", domain, " unexpectedIPs ", ips, " matched at server ", c.Name())
if len(ips) == 0 {
return nil, 0, dns.ErrEmptyResponse
} }
return nil, errExpectedIPNonMatch
} }
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", newIps, " matched at server ", c.Name())
return newIps, nil if len(c.expectedIPs) > 0 && c.actPrior {
ipsNew := router.MatchIPs(c.expectedIPs, ips, false)
if len(ipsNew) > 0 {
ips = ipsNew
errors.LogDebug(context.Background(), "domain ", domain, " priorIPs ", ips, " matched at server ", c.Name())
}
}
if len(c.unexpectedIPs) > 0 && c.actUnprior {
ipsNew := router.MatchIPs(c.unexpectedIPs, ips, true)
if len(ipsNew) > 0 {
ips = ipsNew
errors.LogDebug(context.Background(), "domain ", domain, " unpriorIPs ", ips, " matched at server ", c.Name())
}
}
return ips, ttl, nil
} }
func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption { func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {
switch queryStrategy { switch queryStrategy {
case QueryStrategy_USE_IP: case QueryStrategy_USE_IP:
return ipOption return ipOption
case QueryStrategy_USE_SYS:
return ipOption
case QueryStrategy_USE_IP4: case QueryStrategy_USE_IP4:
return dns.IPOption{ return dns.IPOption{
IPv4Enable: ipOption.IPv4Enable, IPv4Enable: ipOption.IPv4Enable,

View File

@@ -4,12 +4,12 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
go_errors "errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync"
"time" "time"
utls "github.com/refraction-networking/utls" utls "github.com/refraction-networking/utls"
@@ -21,12 +21,9 @@ import (
"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"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal/pubsub"
"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"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"golang.org/x/net/dns/dnsmessage"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -34,18 +31,14 @@ import (
// which is compatible with traditional dns over udp(RFC1035), // which is compatible with traditional dns over udp(RFC1035),
// thus most of the DOH implementation is copied from udpns.go // thus most of the DOH implementation is copied from udpns.go
type DoHNameServer struct { type DoHNameServer struct {
sync.RWMutex cacheController *CacheController
ips map[string]*record httpClient *http.Client
pub *pubsub.Service dohURL string
cleanup *task.Periodic clientIP net.IP
httpClient *http.Client
dohURL string
name string
queryStrategy QueryStrategy
} }
// 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, queryStrategy QueryStrategy, dispatcher routing.Dispatcher, h2c bool) *DoHNameServer { func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, clientIP net.IP) *DoHNameServer {
url.Scheme = "https" url.Scheme = "https"
mode := "DOH" mode := "DOH"
if dispatcher == nil { if dispatcher == nil {
@@ -53,15 +46,9 @@ func NewDoHNameServer(url *url.URL, queryStrategy QueryStrategy, dispatcher rout
} }
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{
ips: make(map[string]*record), cacheController: NewCacheController(mode+"//"+url.Host, disableCache),
pub: pubsub.NewService(), dohURL: url.String(),
name: mode + "//" + url.Host, clientIP: clientIP,
dohURL: url.String(),
queryStrategy: queryStrategy,
}
s.cleanup = &task.Periodic{
Interval: time.Minute,
Execute: s.Cleanup,
} }
s.httpClient = &http.Client{ s.httpClient = &http.Client{
Transport: &http2.Transport{ Transport: &http2.Transport{
@@ -127,101 +114,25 @@ func NewDoHNameServer(url *url.URL, queryStrategy QueryStrategy, dispatcher rout
// Name implements Server. // Name implements Server.
func (s *DoHNameServer) Name() string { func (s *DoHNameServer) Name() string {
return s.name return s.cacheController.name
}
// Cleanup clears expired items from cache
func (s *DoHNameServer) Cleanup() error {
now := time.Now()
s.Lock()
defer s.Unlock()
if len(s.ips) == 0 {
return errors.New("nothing to do. stopping...")
}
for domain, record := range s.ips {
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(), s.name, " cleanup ", domain)
delete(s.ips, domain)
} else {
s.ips[domain] = record
}
}
if len(s.ips) == 0 {
s.ips = make(map[string]*record)
}
return nil
}
func (s *DoHNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
elapsed := time.Since(req.start)
s.Lock()
rec, found := s.ips[req.domain]
if !found {
rec = &record{}
}
updated := false
switch req.reqType {
case dnsmessage.TypeA:
if isNewer(rec.A, ipRec) {
rec.A = ipRec
updated = true
}
case dnsmessage.TypeAAAA:
addr := make([]net.Address, 0, len(ipRec.IP))
for _, ip := range ipRec.IP {
if len(ip.IP()) == net.IPv6len {
addr = append(addr, ip)
}
}
ipRec.IP = addr
if isNewer(rec.AAAA, ipRec) {
rec.AAAA = ipRec
updated = true
}
}
errors.LogInfo(context.Background(), s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
if updated {
s.ips[req.domain] = rec
}
switch req.reqType {
case dnsmessage.TypeA:
s.pub.Publish(req.domain+"4", nil)
case dnsmessage.TypeAAAA:
s.pub.Publish(req.domain+"6", nil)
}
s.Unlock()
common.Must(s.cleanup.Start())
} }
func (s *DoHNameServer) newReqID() uint16 { func (s *DoHNameServer) newReqID() uint16 {
return 0 return 0
} }
func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) { func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.name, " querying: ", domain) errors.LogInfo(ctx, s.Name(), " querying: ", domain)
if s.name+"." == "DOH//"+domain { if s.Name()+"." == "DOH//"+domain {
errors.LogError(ctx, s.name, " tries to resolve itself! Use IP or set \"hosts\" instead.") errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead.")
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(clientIP, int(crypto.RandBetween(100, 300)))) reqs := buildReqMsgs(domain, 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 {
@@ -256,19 +167,22 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
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 ", domain)
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 ", domain)
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 ", domain)
noResponseErrCh <- err
return return
} }
s.updateIP(r, rec) s.cacheController.updateIP(r, rec)
}(req) }(req)
} }
} }
@@ -301,109 +215,50 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
return io.ReadAll(resp.Body) return io.ReadAll(resp.Body)
} }
func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock()
record, found := s.ips[domain]
s.RUnlock()
if !found {
return nil, 0, errRecordNotFound
}
var err4 error
var err6 error
var ips []net.Address
var ip6 []net.Address
var ttl uint32
if option.IPv4Enable {
ips, ttl, err4 = record.A.getIPs()
}
if option.IPv6Enable {
ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...)
}
if len(ips) > 0 {
netips, err := toNetIP(ips)
return netips, ttl, err
}
if err4 != nil {
return nil, 0, err4
}
if err6 != nil {
return nil, 0, err6
}
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
return nil, 0, dns_feature.ErrEmptyResponse
}
return nil, 0, errRecordNotFound
}
// QueryIP implements Server. // QueryIP implements Server.
func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) { // nolint: dupl func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { // nolint: dupl
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
if !option.IPv4Enable && !option.IPv6Enable { defer closeSubscribers(sub4, sub6)
return nil, 0, dns_feature.ErrEmptyResponse
}
if disableCache { if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name) errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else { } else {
ips, ttl, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 { if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips) 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}) log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err return ips, ttl, err
} }
} }
// ipv4 and ipv6 belong to different subscription groups noResponseErrCh := make(chan error, 2)
var sub4, sub6 *pubsub.Subscriber s.sendQuery(ctx, noResponseErrCh, fqdn, option)
if option.IPv4Enable {
sub4 = s.pub.Subscribe(fqdn + "4")
defer sub4.Close()
}
if option.IPv6Enable {
sub6 = s.pub.Subscribe(fqdn + "6")
defer sub6.Close()
}
done := make(chan interface{})
go func() {
if sub4 != nil {
select {
case <-sub4.Wait():
case <-ctx.Done():
}
}
if sub6 != nil {
select {
case <-sub6.Wait():
case <-ctx.Done():
}
}
close(done)
}()
s.sendQuery(ctx, fqdn, clientIP, option)
start := time.Now() start := time.Now()
for { if sub4 != nil {
ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound {
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
}
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, 0, ctx.Err() return nil, 0, ctx.Err()
case <-done: 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
} }

View File

@@ -17,12 +17,12 @@ 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, QueryStrategy_USE_IP, nil, false) s := NewDoHNameServer(url, nil, false, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {
@@ -34,12 +34,12 @@ 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, QueryStrategy_USE_IP, nil, false) s := NewDoHNameServer(url, nil, false, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {
@@ -47,10 +47,10 @@ func TestDOHNameServerWithCache(t *testing.T) {
} }
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips2, _, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{ ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, true) })
cancel() cancel()
common.Must(err) common.Must(err)
if r := cmp.Diff(ips2, ips); r != "" { if r := cmp.Diff(ips2, ips); r != "" {
@@ -62,12 +62,12 @@ 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, QueryStrategy_USE_IP4, nil, false) s := NewDoHNameServer(url, nil, false, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: false,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {
@@ -85,12 +85,12 @@ 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, QueryStrategy_USE_IP6, nil, false) s := NewDoHNameServer(url, nil, false, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {

View File

@@ -20,7 +20,7 @@ func (FakeDNSServer) Name() string {
return "FakeDNS" return "FakeDNS"
} }
func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]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()
} }

View File

@@ -2,7 +2,6 @@ package dns
import ( import (
"context" "context"
"strings"
"time" "time"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -14,26 +13,15 @@ import (
// LocalNameServer is an wrapper over local DNS feature. // LocalNameServer is an wrapper over local DNS feature.
type LocalNameServer struct { type LocalNameServer struct {
client *localdns.Client client *localdns.Client
queryStrategy QueryStrategy
} }
const errEmptyResponse = "No address associated with hostname"
// QueryIP implements Server. // QueryIP implements Server.
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) (ips []net.IP, ttl uint32, err error) { func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
option = ResolveIpOptionOverride(s.queryStrategy, option)
if !option.IPv4Enable && !option.IPv6Enable {
return nil, 0, dns.ErrEmptyResponse
}
start := time.Now() start := time.Now()
ips, ttl, err = s.client.LookupIP(domain, option) ips, ttl, err = s.client.LookupIP(domain, option)
if err != nil && strings.HasSuffix(err.Error(), errEmptyResponse) {
err = dns.ErrEmptyResponse
}
if len(ips) > 0 { if len(ips) > 0 {
errors.LogInfo(ctx, "Localhost got answer: ", domain, " -> ", ips) errors.LogInfo(ctx, "Localhost got answer: ", domain, " -> ", ips)
log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err}) log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
@@ -48,15 +36,14 @@ func (s *LocalNameServer) Name() string {
} }
// NewLocalNameServer creates localdns server object for directly lookup in system DNS. // NewLocalNameServer creates localdns server object for directly lookup in system DNS.
func NewLocalNameServer(queryStrategy QueryStrategy) *LocalNameServer { func NewLocalNameServer() *LocalNameServer {
errors.LogInfo(context.Background(), "DNS: created localhost client") errors.LogInfo(context.Background(), "DNS: created localhost client")
return &LocalNameServer{ return &LocalNameServer{
queryStrategy: queryStrategy, client: localdns.New(),
client: localdns.New(),
} }
} }
// NewLocalDNSClient creates localdns client object for directly lookup in system DNS. // NewLocalDNSClient creates localdns client object for directly lookup in system DNS.
func NewLocalDNSClient() *Client { func NewLocalDNSClient(ipOption dns.IPOption) *Client {
return &Client{server: NewLocalNameServer(QueryStrategy_USE_IP)} return &Client{server: NewLocalNameServer(), ipOption: &ipOption}
} }

View File

@@ -7,18 +7,17 @@ import (
. "github.com/xtls/xray-core/app/dns" . "github.com/xtls/xray-core/app/dns"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
) )
func TestLocalNameServer(t *testing.T) { func TestLocalNameServer(t *testing.T) {
s := NewLocalNameServer(QueryStrategy_USE_IP) s := NewLocalNameServer()
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", net.IP{}, dns.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {

View File

@@ -4,23 +4,20 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/binary" "encoding/binary"
go_errors "errors"
"net/url" "net/url"
"sync" "sync"
"time" "time"
"github.com/quic-go/quic-go" "github.com/quic-go/quic-go"
"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"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol/dns" "github.com/xtls/xray-core/common/protocol/dns"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal/pubsub"
"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"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
"golang.org/x/net/dns/dnsmessage"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -33,17 +30,14 @@ const handshakeTimeout = time.Second * 8
// QUICNameServer implemented DNS over QUIC // QUICNameServer implemented DNS over QUIC
type QUICNameServer struct { type QUICNameServer struct {
sync.RWMutex sync.RWMutex
ips map[string]*record cacheController *CacheController
pub *pubsub.Service destination *net.Destination
cleanup *task.Periodic connection *quic.Conn
name string clientIP net.IP
destination *net.Destination
connection quic.Connection
queryStrategy QueryStrategy
} }
// 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, queryStrategy QueryStrategy) (*QUICNameServer, error) { func NewQUICNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*QUICNameServer, error) {
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String()) errors.LogInfo(context.Background(), "DNS: created Local DNS-over-QUIC client for ", url.String())
var err error var err error
@@ -57,15 +51,9 @@ func NewQUICNameServer(url *url.URL, queryStrategy QueryStrategy) (*QUICNameServ
dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port) dest := net.UDPDestination(net.ParseAddress(url.Hostname()), port)
s := &QUICNameServer{ s := &QUICNameServer{
ips: make(map[string]*record), cacheController: NewCacheController(url.String(), disableCache),
pub: pubsub.NewService(), destination: &dest,
name: url.String(), clientIP: clientIP,
destination: &dest,
queryStrategy: queryStrategy,
}
s.cleanup = &task.Periodic{
Interval: time.Minute,
Execute: s.Cleanup,
} }
return s, nil return s, nil
@@ -73,94 +61,17 @@ func NewQUICNameServer(url *url.URL, queryStrategy QueryStrategy) (*QUICNameServ
// Name returns client name // Name returns client name
func (s *QUICNameServer) Name() string { func (s *QUICNameServer) Name() string {
return s.name return s.cacheController.name
}
// Cleanup clears expired items from cache
func (s *QUICNameServer) Cleanup() error {
now := time.Now()
s.Lock()
defer s.Unlock()
if len(s.ips) == 0 {
return errors.New("nothing to do. stopping...")
}
for domain, record := range s.ips {
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(), s.name, " cleanup ", domain)
delete(s.ips, domain)
} else {
s.ips[domain] = record
}
}
if len(s.ips) == 0 {
s.ips = make(map[string]*record)
}
return nil
}
func (s *QUICNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
elapsed := time.Since(req.start)
s.Lock()
rec, found := s.ips[req.domain]
if !found {
rec = &record{}
}
updated := false
switch req.reqType {
case dnsmessage.TypeA:
if isNewer(rec.A, ipRec) {
rec.A = ipRec
updated = true
}
case dnsmessage.TypeAAAA:
addr := make([]net.Address, 0)
for _, ip := range ipRec.IP {
if len(ip.IP()) == net.IPv6len {
addr = append(addr, ip)
}
}
ipRec.IP = addr
if isNewer(rec.AAAA, ipRec) {
rec.AAAA = ipRec
updated = true
}
}
errors.LogInfo(context.Background(), s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
if updated {
s.ips[req.domain] = rec
}
switch req.reqType {
case dnsmessage.TypeA:
s.pub.Publish(req.domain+"4", nil)
case dnsmessage.TypeAAAA:
s.pub.Publish(req.domain+"6", nil)
}
s.Unlock()
common.Must(s.cleanup.Start())
} }
func (s *QUICNameServer) newReqID() uint16 { func (s *QUICNameServer) newReqID() uint16 {
return 0 return 0
} }
func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) { func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.name, " querying: ", domain) errors.LogInfo(ctx, s.Name(), " querying: ", domain)
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0)) reqs := buildReqMsgs(domain, 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 {
@@ -192,23 +103,36 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP
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
return return
} }
dnsReqBuf := buf.New() dnsReqBuf := buf.New()
binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len())) err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
dnsReqBuf.Write(b.Bytes()) if err != nil {
errors.LogErrorInner(ctx, err, "binary write failed")
noResponseErrCh <- err
return
}
_, err = dnsReqBuf.Write(b.Bytes())
if err != nil {
errors.LogErrorInner(ctx, err, "buffer write failed")
noResponseErrCh <- err
return
}
b.Release() b.Release()
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
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
return return
} }
@@ -219,139 +143,84 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP
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
return return
} }
var length int16 var length int16
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
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
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
return return
} }
s.updateIP(r, rec) s.cacheController.updateIP(r, rec)
}(req) }(req)
} }
} }
func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock()
record, found := s.ips[domain]
s.RUnlock()
if !found {
return nil, 0, errRecordNotFound
}
var err4 error
var err6 error
var ips []net.Address
var ip6 []net.Address
var ttl uint32
if option.IPv4Enable {
ips, ttl, err4 = record.A.getIPs()
}
if option.IPv6Enable {
ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...)
}
if len(ips) > 0 {
netips, err := toNetIP(ips)
return netips, ttl, err
}
if err4 != nil {
return nil, 0, err4
}
if err6 != nil {
return nil, 0, err6
}
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
return nil, 0, dns_feature.ErrEmptyResponse
}
return nil, 0, errRecordNotFound
}
// QueryIP is called from dns.Server->queryIPTimeout // QueryIP is called from dns.Server->queryIPTimeout
func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) { func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
if !option.IPv4Enable && !option.IPv6Enable { defer closeSubscribers(sub4, sub6)
return nil, 0, dns_feature.ErrEmptyResponse
}
if disableCache { if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name) errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else { } else {
ips, ttl, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 { if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips) 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}) log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err return ips, ttl, err
} }
} }
// ipv4 and ipv6 belong to different subscription groups noResponseErrCh := make(chan error, 2)
var sub4, sub6 *pubsub.Subscriber s.sendQuery(ctx, noResponseErrCh, fqdn, option)
if option.IPv4Enable {
sub4 = s.pub.Subscribe(fqdn + "4")
defer sub4.Close()
}
if option.IPv6Enable {
sub6 = s.pub.Subscribe(fqdn + "6")
defer sub6.Close()
}
done := make(chan interface{})
go func() {
if sub4 != nil {
select {
case <-sub4.Wait():
case <-ctx.Done():
}
}
if sub6 != nil {
select {
case <-sub6.Wait():
case <-ctx.Done():
}
}
close(done)
}()
s.sendQuery(ctx, fqdn, clientIP, option)
start := time.Now() start := time.Now()
for { if sub4 != nil {
ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound {
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
}
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, 0, ctx.Err() return nil, 0, ctx.Err()
case <-done: 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.Connection) bool { func isActive(s *quic.Conn) bool {
select { select {
case <-s.Context().Done(): case <-s.Context().Done():
return false return false
@@ -360,8 +229,8 @@ func isActive(s quic.Connection) bool {
} }
} }
func (s *QUICNameServer) getConnection() (quic.Connection, error) { func (s *QUICNameServer) getConnection() (*quic.Conn, error) {
var conn quic.Connection var conn *quic.Conn
s.RLock() s.RLock()
conn = s.connection conn = s.connection
if conn != nil && isActive(conn) { if conn != nil && isActive(conn) {
@@ -394,7 +263,7 @@ func (s *QUICNameServer) getConnection() (quic.Connection, error) {
return conn, nil return conn, nil
} }
func (s *QUICNameServer) openConnection() (quic.Connection, error) { func (s *QUICNameServer) openConnection() (*quic.Conn, error) {
tlsConfig := tls.Config{} tlsConfig := tls.Config{}
quicConfig := &quic.Config{ quicConfig := &quic.Config{
HandshakeIdleTimeout: handshakeTimeout, HandshakeIdleTimeout: handshakeTimeout,
@@ -414,7 +283,7 @@ func (s *QUICNameServer) openConnection() (quic.Connection, error) {
return conn, nil return conn, nil
} }
func (s *QUICNameServer) openStream(ctx context.Context) (quic.Stream, error) { func (s *QUICNameServer) openStream(ctx context.Context) (*quic.Stream, error) {
conn, err := s.getConnection() conn, err := s.getConnection()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -16,24 +16,23 @@ 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, QueryStrategy_USE_IP) s, err := NewQUICNameServer(url, false, 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", net.IP(nil), dns.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {
t.Error("expect some ips, but got 0") t.Error("expect some ips, but got 0")
} }
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips2, _, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns.IPOption{ ips2, _, err := s.QueryIP(ctx2, "google.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, true) })
cancel() cancel()
common.Must(err) common.Must(err)
if r := cmp.Diff(ips2, ips); r != "" { if r := cmp.Diff(ips2, ips); r != "" {
@@ -44,13 +43,13 @@ 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, QueryStrategy_USE_IP4) s, err := NewQUICNameServer(url, false, 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", net.IP(nil), dns.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: false,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {
@@ -67,13 +66,13 @@ 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, QueryStrategy_USE_IP6) s, err := NewQUICNameServer(url, false, 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", net.IP(nil), dns.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns.IPOption{
IPv4Enable: true, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {

View File

@@ -4,12 +4,11 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/binary" "encoding/binary"
go_errors "errors"
"net/url" "net/url"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
"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"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
@@ -17,34 +16,28 @@ import (
"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"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal/pubsub"
"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"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"golang.org/x/net/dns/dnsmessage"
) )
// TCPNameServer implemented DNS over TCP (RFC7766). // TCPNameServer implemented DNS over TCP (RFC7766).
type TCPNameServer struct { type TCPNameServer struct {
sync.RWMutex cacheController *CacheController
name string destination *net.Destination
destination *net.Destination reqID uint32
ips map[string]*record dial func(context.Context) (net.Conn, error)
pub *pubsub.Service clientIP net.IP
cleanup *task.Periodic
reqID uint32
dial func(context.Context) (net.Conn, error)
queryStrategy QueryStrategy
} }
// NewTCPNameServer creates DNS over TCP server object for remote resolving. // NewTCPNameServer creates DNS over TCP server object for remote resolving.
func NewTCPNameServer( func NewTCPNameServer(
url *url.URL, url *url.URL,
dispatcher routing.Dispatcher, dispatcher routing.Dispatcher,
queryStrategy QueryStrategy, disableCache bool,
clientIP net.IP,
) (*TCPNameServer, error) { ) (*TCPNameServer, error) {
s, err := baseTCPNameServer(url, "TCP", queryStrategy) s, err := baseTCPNameServer(url, "TCP", disableCache, clientIP)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -65,8 +58,8 @@ func NewTCPNameServer(
} }
// 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, queryStrategy QueryStrategy) (*TCPNameServer, error) { func NewTCPLocalNameServer(url *url.URL, disableCache bool, clientIP net.IP) (*TCPNameServer, error) {
s, err := baseTCPNameServer(url, "TCPL", queryStrategy) s, err := baseTCPNameServer(url, "TCPL", disableCache, clientIP)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -78,7 +71,7 @@ func NewTCPLocalNameServer(url *url.URL, queryStrategy QueryStrategy) (*TCPNameS
return s, nil return s, nil
} }
func baseTCPNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy) (*TCPNameServer, error) { func baseTCPNameServer(url *url.URL, prefix string, disableCache bool, 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
@@ -89,15 +82,9 @@ func baseTCPNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy)
dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port) dest := net.TCPDestination(net.ParseAddress(url.Hostname()), port)
s := &TCPNameServer{ s := &TCPNameServer{
destination: &dest, cacheController: NewCacheController(prefix+"//"+dest.NetAddr(), disableCache),
ips: make(map[string]*record), destination: &dest,
pub: pubsub.NewService(), clientIP: clientIP,
name: prefix + "//" + dest.NetAddr(),
queryStrategy: queryStrategy,
}
s.cleanup = &task.Periodic{
Interval: time.Minute,
Execute: s.Cleanup,
} }
return s, nil return s, nil
@@ -105,94 +92,17 @@ func baseTCPNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy)
// Name implements Server. // Name implements Server.
func (s *TCPNameServer) Name() string { func (s *TCPNameServer) Name() string {
return s.name return s.cacheController.name
}
// Cleanup clears expired items from cache
func (s *TCPNameServer) Cleanup() error {
now := time.Now()
s.Lock()
defer s.Unlock()
if len(s.ips) == 0 {
return errors.New("nothing to do. stopping...")
}
for domain, record := range s.ips {
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(), s.name, " cleanup ", domain)
delete(s.ips, domain)
} else {
s.ips[domain] = record
}
}
if len(s.ips) == 0 {
s.ips = make(map[string]*record)
}
return nil
}
func (s *TCPNameServer) updateIP(req *dnsRequest, ipRec *IPRecord) {
elapsed := time.Since(req.start)
s.Lock()
rec, found := s.ips[req.domain]
if !found {
rec = &record{}
}
updated := false
switch req.reqType {
case dnsmessage.TypeA:
if isNewer(rec.A, ipRec) {
rec.A = ipRec
updated = true
}
case dnsmessage.TypeAAAA:
addr := make([]net.Address, 0)
for _, ip := range ipRec.IP {
if len(ip.IP()) == net.IPv6len {
addr = append(addr, ip)
}
}
ipRec.IP = addr
if isNewer(rec.AAAA, ipRec) {
rec.AAAA = ipRec
updated = true
}
}
errors.LogInfo(context.Background(), s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
if updated {
s.ips[req.domain] = rec
}
switch req.reqType {
case dnsmessage.TypeA:
s.pub.Publish(req.domain+"4", nil)
case dnsmessage.TypeAAAA:
s.pub.Publish(req.domain+"6", nil)
}
s.Unlock()
common.Must(s.cleanup.Start())
} }
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, domain string, clientIP net.IP, option dns_feature.IPOption) { func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) {
errors.LogDebug(ctx, s.name, " querying DNS for: ", domain) errors.LogDebug(ctx, s.Name(), " querying DNS for: ", domain)
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0)) reqs := buildReqMsgs(domain, 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 {
@@ -221,23 +131,36 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP n
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
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
return return
} }
defer conn.Close() defer conn.Close()
dnsReqBuf := buf.New() dnsReqBuf := buf.New()
binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len())) err = binary.Write(dnsReqBuf, binary.BigEndian, uint16(b.Len()))
dnsReqBuf.Write(b.Bytes()) if err != nil {
errors.LogErrorInner(ctx, err, "binary write failed")
noResponseErrCh <- err
return
}
_, err = dnsReqBuf.Write(b.Bytes())
if err != nil {
errors.LogErrorInner(ctx, err, "buffer write failed")
noResponseErrCh <- err
return
}
b.Release() b.Release()
_, 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
return return
} }
dnsReqBuf.Release() dnsReqBuf.Release()
@@ -247,131 +170,80 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP n
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
return return
} }
var length int16 var length int16
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
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
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
return return
} }
s.updateIP(r, rec) s.cacheController.updateIP(r, rec)
}(req) }(req)
} }
} }
func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock()
record, found := s.ips[domain]
s.RUnlock()
if !found {
return nil, 0, errRecordNotFound
}
var err4 error
var err6 error
var ips []net.Address
var ip6 []net.Address
var ttl uint32
if option.IPv4Enable {
ips, ttl, err4 = record.A.getIPs()
}
if option.IPv6Enable {
ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...)
}
if len(ips) > 0 {
netips, err := toNetIP(ips)
return netips, ttl, err
}
if err4 != nil {
return nil, 0, err4
}
if err6 != nil {
return nil, 0, err6
}
return nil, 0, dns_feature.ErrEmptyResponse
}
// QueryIP implements Server. // QueryIP implements Server.
func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) { func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
if !option.IPv4Enable && !option.IPv6Enable { defer closeSubscribers(sub4, sub6)
return nil, 0, dns_feature.ErrEmptyResponse
}
if disableCache { if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name) errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else { } else {
ips, ttl, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 { if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips) 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}) log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err return ips, ttl, err
} }
} }
// ipv4 and ipv6 belong to different subscription groups noResponseErrCh := make(chan error, 2)
var sub4, sub6 *pubsub.Subscriber s.sendQuery(ctx, noResponseErrCh, fqdn, option)
if option.IPv4Enable {
sub4 = s.pub.Subscribe(fqdn + "4")
defer sub4.Close()
}
if option.IPv6Enable {
sub6 = s.pub.Subscribe(fqdn + "6")
defer sub6.Close()
}
done := make(chan interface{})
go func() {
if sub4 != nil {
select {
case <-sub4.Wait():
case <-ctx.Done():
}
}
if sub6 != nil {
select {
case <-sub6.Wait():
case <-ctx.Done():
}
}
close(done)
}()
s.sendQuery(ctx, fqdn, clientIP, option)
start := time.Now() start := time.Now()
for { if sub4 != nil {
ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound {
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
}
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, 0, ctx.Err() return nil, 0, ctx.Err()
case <-done: 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
} }

View File

@@ -16,13 +16,13 @@ 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, QueryStrategy_USE_IP) s, err := NewTCPLocalNameServer(url, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {
@@ -33,13 +33,13 @@ 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, QueryStrategy_USE_IP) s, err := NewTCPLocalNameServer(url, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
if len(ips) == 0 { if len(ips) == 0 {
@@ -47,10 +47,10 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
} }
ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx2, cancel := context.WithTimeout(context.Background(), time.Second*5)
ips2, _, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{ ips2, _, err := s.QueryIP(ctx2, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, true) })
cancel() cancel()
common.Must(err) common.Must(err)
if r := cmp.Diff(ips2, ips); r != "" { if r := cmp.Diff(ips2, ips); r != "" {
@@ -61,13 +61,13 @@ 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, QueryStrategy_USE_IP4) s, err := NewTCPLocalNameServer(url, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: false,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)
@@ -85,13 +85,13 @@ 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, QueryStrategy_USE_IP6) s, err := NewTCPLocalNameServer(url, false, 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", net.IP(nil), dns_feature.IPOption{ ips, _, err := s.QueryIP(ctx, "google.com", dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
}, false) })
cancel() cancel()
common.Must(err) common.Must(err)

View File

@@ -2,6 +2,7 @@ package dns
import ( import (
"context" "context"
go_errors "errors"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -13,7 +14,6 @@ import (
"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"
"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"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
@@ -24,15 +24,13 @@ import (
// ClassicNameServer implemented traditional UDP DNS. // ClassicNameServer implemented traditional UDP DNS.
type ClassicNameServer struct { type ClassicNameServer struct {
sync.RWMutex sync.RWMutex
name string cacheController *CacheController
address *net.Destination address *net.Destination
ips map[string]*record requests map[uint16]*udpDnsRequest
requests map[uint16]*udpDnsRequest udpServer *udp.Dispatcher
pub *pubsub.Service requestsCleanup *task.Periodic
udpServer *udp.Dispatcher reqID uint32
cleanup *task.Periodic clientIP net.IP
reqID uint32
queryStrategy QueryStrategy
} }
type udpDnsRequest struct { type udpDnsRequest struct {
@@ -41,23 +39,21 @@ 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, queryStrategy QueryStrategy) *ClassicNameServer { func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher, disableCache bool, 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{
address: &address, cacheController: NewCacheController(strings.ToUpper(address.String()), disableCache),
ips: make(map[string]*record), address: &address,
requests: make(map[uint16]*udpDnsRequest), requests: make(map[uint16]*udpDnsRequest),
pub: pubsub.NewService(), clientIP: clientIP,
name: strings.ToUpper(address.String()),
queryStrategy: queryStrategy,
} }
s.cleanup = &task.Periodic{ s.requestsCleanup = &task.Periodic{
Interval: time.Minute, Interval: time.Minute,
Execute: s.Cleanup, 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())
@@ -66,37 +62,17 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
// Name implements Server. // Name implements Server.
func (s *ClassicNameServer) Name() string { func (s *ClassicNameServer) Name() string {
return s.name return s.cacheController.name
} }
// Cleanup clears expired items from cache // RequestsCleanup clears expired items from cache
func (s *ClassicNameServer) Cleanup() error { func (s *ClassicNameServer) RequestsCleanup() error {
now := time.Now() now := time.Now()
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
if len(s.ips) == 0 && len(s.requests) == 0 { if len(s.requests) == 0 {
return errors.New(s.name, " nothing to do. stopping...") return errors.New(s.Name(), " nothing to do. stopping...")
}
for domain, record := range s.ips {
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(), s.name, " cleanup ", domain)
delete(s.ips, domain)
} else {
s.ips[domain] = record
}
}
if len(s.ips) == 0 {
s.ips = make(map[string]*record)
} }
for id, req := range s.requests { for id, req := range s.requests {
@@ -116,7 +92,7 @@ func (s *ClassicNameServer) Cleanup() error {
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) { func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
ipRec, err := parseResponse(packet.Payload.Bytes()) ipRec, err := parseResponse(packet.Payload.Bytes())
if err != nil { if err != nil {
errors.LogError(ctx, s.name, " fail to parse responded DNS udp") errors.LogError(ctx, s.Name(), " fail to parse responded DNS udp")
return return
} }
@@ -129,14 +105,14 @@ 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.LogError(ctx, s.Name(), " cannot find the pending request")
return return
} }
// if truncated, retry with EDNS0 option(udp payload size: 1350) // if truncated, retry with EDNS0 option(udp payload size: 1350)
if ipRec.RawHeader.Truncated { if ipRec.RawHeader.Truncated {
// if already has EDNS0 option, no need to retry // if already has EDNS0 option, no need to retry
if ok && len(req.msg.Additionals) == 0 { if len(req.msg.Additionals) == 0 {
// copy necessary meta data from original request // copy necessary meta data from original request
// and add EDNS0 option // and add EDNS0 option
opt := new(dnsmessage.Resource) opt := new(dnsmessage.Resource)
@@ -154,51 +130,7 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
} }
} }
var rec record s.cacheController.updateIP(&req.dnsRequest, ipRec)
switch req.reqType {
case dnsmessage.TypeA:
rec.A = ipRec
case dnsmessage.TypeAAAA:
rec.AAAA = ipRec
}
elapsed := time.Since(req.start)
errors.LogInfo(ctx, s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed)
if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) {
s.updateIP(req.domain, &rec)
}
}
func (s *ClassicNameServer) updateIP(domain string, newRec *record) {
s.Lock()
rec, found := s.ips[domain]
if !found {
rec = &record{}
}
updated := false
if isNewer(rec.A, newRec.A) {
rec.A = newRec.A
updated = true
}
if isNewer(rec.AAAA, newRec.AAAA) {
rec.AAAA = newRec.AAAA
updated = true
}
if updated {
errors.LogDebug(context.Background(), s.name, " updating IP records for domain:", domain)
s.ips[domain] = rec
}
if newRec.A != nil {
s.pub.Publish(domain+"4", nil)
}
if newRec.AAAA != nil {
s.pub.Publish(domain+"6", nil)
}
s.Unlock()
common.Must(s.cleanup.Start())
} }
func (s *ClassicNameServer) newReqID() uint16 { func (s *ClassicNameServer) newReqID() uint16 {
@@ -207,17 +139,17 @@ func (s *ClassicNameServer) newReqID() uint16 {
func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) { func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {
s.Lock() s.Lock()
defer s.Unlock()
id := req.msg.ID id := req.msg.ID
req.expire = time.Now().Add(time.Second * 8) req.expire = time.Now().Add(time.Second * 8)
s.requests[id] = req s.requests[id] = req
s.Unlock()
common.Must(s.requestsCleanup.Start())
} }
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) { func (s *ClassicNameServer) sendQuery(ctx context.Context, _ chan<- error, domain string, option dns_feature.IPOption) {
errors.LogDebug(ctx, s.name, " querying DNS for: ", domain) errors.LogDebug(ctx, s.Name(), " querying DNS for: ", domain)
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
for _, req := range reqs { for _, req := range reqs {
udpReq := &udpDnsRequest{ udpReq := &udpDnsRequest{
@@ -230,105 +162,50 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, client
} }
} }
func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock()
record, found := s.ips[domain]
s.RUnlock()
if !found {
return nil, 0, errRecordNotFound
}
var err4 error
var err6 error
var ips []net.Address
var ip6 []net.Address
var ttl uint32
if option.IPv4Enable {
ips, ttl, err4 = record.A.getIPs()
}
if option.IPv6Enable {
ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...)
}
if len(ips) > 0 {
netips, err := toNetIP(ips)
return netips, ttl, err
}
if err4 != nil {
return nil, 0, err4
}
if err6 != nil {
return nil, 0, err6
}
return nil, 0, dns_feature.ErrEmptyResponse
}
// QueryIP implements Server. // QueryIP implements Server.
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) { func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
if !option.IPv4Enable && !option.IPv6Enable { defer closeSubscribers(sub4, sub6)
return nil, 0, dns_feature.ErrEmptyResponse
}
if disableCache { if s.cacheController.disableCache {
errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.name) errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
} else { } else {
ips, ttl, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 { if !go_errors.Is(err, errRecordNotFound) {
errors.LogDebugInner(ctx, err, s.name, " cache HIT ", domain, " -> ", ips) 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}) log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
return ips, ttl, err return ips, ttl, err
} }
} }
// ipv4 and ipv6 belong to different subscription groups noResponseErrCh := make(chan error, 2)
var sub4, sub6 *pubsub.Subscriber s.sendQuery(ctx, noResponseErrCh, fqdn, option)
if option.IPv4Enable {
sub4 = s.pub.Subscribe(fqdn + "4")
defer sub4.Close()
}
if option.IPv6Enable {
sub6 = s.pub.Subscribe(fqdn + "6")
defer sub6.Close()
}
done := make(chan interface{})
go func() {
if sub4 != nil {
select {
case <-sub4.Wait():
case <-ctx.Done():
}
}
if sub6 != nil {
select {
case <-sub6.Wait():
case <-ctx.Done():
}
}
close(done)
}()
s.sendQuery(ctx, fqdn, clientIP, option)
start := time.Now() start := time.Now()
for { if sub4 != nil {
ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound {
log.Record(&log.DNSLog{Server: s.name, Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
return ips, ttl, err
}
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, 0, ctx.Err() return nil, 0, ctx.Err()
case <-done: 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
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/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/serial"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
) )
@@ -108,3 +109,13 @@ func (co *Outbound) Close() error {
co.closed = true co.closed = true
return co.listener.Close() return co.listener.Close()
} }
// SenderSettings implements outbound.Handler.
func (co *Outbound) SenderSettings() *serial.TypedMessage {
return nil
}
// ProxySettings implements outbound.Handler.
func (co *Outbound) ProxySettings() *serial.TypedMessage {
return nil
}

View File

@@ -90,6 +90,8 @@ type HealthPingConfig struct {
SamplingCount int32 `protobuf:"varint,4,opt,name=samplingCount,proto3" json:"samplingCount,omitempty"` SamplingCount int32 `protobuf:"varint,4,opt,name=samplingCount,proto3" json:"samplingCount,omitempty"`
// ping timeout, int64 values of time.Duration // ping timeout, int64 values of time.Duration
Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"`
// http method to make request
HttpMethod string `protobuf:"bytes,6,opt,name=httpMethod,proto3" json:"httpMethod,omitempty"`
} }
func (x *HealthPingConfig) Reset() { func (x *HealthPingConfig) Reset() {
@@ -157,6 +159,13 @@ func (x *HealthPingConfig) GetTimeout() int64 {
return 0 return 0
} }
func (x *HealthPingConfig) GetHttpMethod() string {
if x != nil {
return x.HttpMethod
}
return ""
}
var File_app_observatory_burst_config_proto protoreflect.FileDescriptor var File_app_observatory_burst_config_proto protoreflect.FileDescriptor
var file_app_observatory_burst_config_proto_rawDesc = []byte{ var file_app_observatory_burst_config_proto_rawDesc = []byte{
@@ -173,7 +182,7 @@ var file_app_observatory_burst_config_proto_rawDesc = []byte{
0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72,
0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22,
0xb4, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0xd4, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69,
0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
@@ -184,7 +193,9 @@ var file_app_observatory_burst_config_proto_rawDesc = []byte{
0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73,
0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07,
0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74,
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x42, 0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x4d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x74, 0x74, 0x70,
0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x42, 0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f,
0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x73, 0x74, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x73, 0x74, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,

View File

@@ -26,4 +26,7 @@ message HealthPingConfig {
int32 samplingCount = 4; int32 samplingCount = 4;
// ping timeout, int64 values of time.Duration // ping timeout, int64 values of time.Duration
int64 timeout = 5; int64 timeout = 5;
// http method to make request
string httpMethod = 6;
} }

View File

@@ -19,6 +19,7 @@ type HealthPingSettings struct {
Interval time.Duration `json:"interval"` Interval time.Duration `json:"interval"`
SamplingCount int `json:"sampling"` SamplingCount int `json:"sampling"`
Timeout time.Duration `json:"timeout"` Timeout time.Duration `json:"timeout"`
HttpMethod string `json:"httpMethod"`
} }
// HealthPing is the health checker for balancers // HealthPing is the health checker for balancers
@@ -37,12 +38,21 @@ type HealthPing struct {
func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *HealthPingConfig) *HealthPing { func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *HealthPingConfig) *HealthPing {
settings := &HealthPingSettings{} settings := &HealthPingSettings{}
if config != nil { if config != nil {
var httpMethod string
if config.HttpMethod == "" {
httpMethod = "HEAD"
} else {
httpMethod = strings.TrimSpace(config.HttpMethod)
}
settings = &HealthPingSettings{ settings = &HealthPingSettings{
Connectivity: strings.TrimSpace(config.Connectivity), Connectivity: strings.TrimSpace(config.Connectivity),
Destination: strings.TrimSpace(config.Destination), Destination: strings.TrimSpace(config.Destination),
Interval: time.Duration(config.Interval), Interval: time.Duration(config.Interval),
SamplingCount: int(config.SamplingCount), SamplingCount: int(config.SamplingCount),
Timeout: time.Duration(config.Timeout), Timeout: time.Duration(config.Timeout),
HttpMethod: httpMethod,
} }
} }
if settings.Destination == "" { if settings.Destination == "" {
@@ -164,7 +174,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
} }
time.AfterFunc(delay, func() { time.AfterFunc(delay, func() {
errors.LogDebug(h.ctx, "checking ", handler) errors.LogDebug(h.ctx, "checking ", handler)
delay, err := client.MeasureDelay() delay, err := client.MeasureDelay(h.Settings.HttpMethod)
if err == nil { if err == nil {
ch <- &rtt{ ch <- &rtt{
handler: handler, handler: handler,
@@ -251,7 +261,7 @@ func (h *HealthPing) checkConnectivity() bool {
h.Settings.Connectivity, h.Settings.Connectivity,
h.Settings.Timeout, h.Settings.Timeout,
) )
if _, err := tester.MeasureDelay(); err != nil { if _, err := tester.MeasureDelay(h.Settings.HttpMethod); err != nil {
return false return false
} }
return true return true

View File

@@ -2,6 +2,7 @@ package burst
import ( import (
"context" "context"
"io"
"net/http" "net/http"
"time" "time"
@@ -51,20 +52,28 @@ func newHTTPClient(ctxv context.Context, dispatcher routing.Dispatcher, handler
} }
// MeasureDelay returns the delay time of the request to dest // MeasureDelay returns the delay time of the request to dest
func (s *pingClient) MeasureDelay() (time.Duration, error) { func (s *pingClient) MeasureDelay(httpMethod string) (time.Duration, error) {
if s.httpClient == nil { if s.httpClient == nil {
panic("pingClient not initialized") panic("pingClient not initialized")
} }
req, err := http.NewRequest(http.MethodHead, s.destination, nil)
req, err := http.NewRequest(httpMethod, s.destination, nil)
if err != nil { if err != nil {
return rttFailed, err return rttFailed, err
} }
start := time.Now() start := time.Now()
resp, err := s.httpClient.Do(req) resp, err := s.httpClient.Do(req)
if err != nil { if err != nil {
return rttFailed, err return rttFailed, err
} }
// don't wait for body if httpMethod == http.MethodGet {
_, err = io.Copy(io.Discard, resp.Body)
if err != nil {
return rttFailed, err
}
}
resp.Body.Close() resp.Body.Close()
return time.Since(start), nil return time.Since(start), nil
} }

View File

@@ -3,6 +3,7 @@ package command
import ( import (
"context" "context"
"github.com/xtls/xray-core/app/commander"
"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/protocol" "github.com/xtls/xray-core/common/protocol"
@@ -99,6 +100,28 @@ func (s *handlerServer) AlterInbound(ctx context.Context, request *AlterInboundR
return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler) return &AlterInboundResponse{}, operation.ApplyInbound(ctx, handler)
} }
func (s *handlerServer) ListInbounds(ctx context.Context, request *ListInboundsRequest) (*ListInboundsResponse, error) {
handlers := s.ihm.ListHandlers(ctx)
response := &ListInboundsResponse{}
if request.GetIsOnlyTags() {
for _, handler := range handlers {
response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{
Tag: handler.Tag(),
})
}
} else {
for _, handler := range handlers {
response.Inbounds = append(response.Inbounds, &core.InboundHandlerConfig{
Tag: handler.Tag(),
ReceiverSettings: handler.ReceiverSettings(),
ProxySettings: handler.ProxySettings(),
})
}
}
return response, nil
}
func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUserResponse, error) { func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInboundUserRequest) (*GetInboundUserResponse, error) {
handler, err := s.ihm.GetHandler(ctx, request.Tag) handler, err := s.ihm.GetHandler(ctx, request.Tag)
if err != nil { if err != nil {
@@ -164,6 +187,23 @@ func (s *handlerServer) AlterOutbound(ctx context.Context, request *AlterOutboun
return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler) return &AlterOutboundResponse{}, operation.ApplyOutbound(ctx, handler)
} }
func (s *handlerServer) ListOutbounds(ctx context.Context, request *ListOutboundsRequest) (*ListOutboundsResponse, error) {
handlers := s.ohm.ListHandlers(ctx)
response := &ListOutboundsResponse{}
for _, handler := range handlers {
// Ignore gRPC outbound
if _, ok := handler.(*commander.Outbound); ok {
continue
}
response.Outbounds = append(response.Outbounds, &core.OutboundHandlerConfig{
Tag: handler.Tag(),
SenderSettings: handler.SenderSettings(),
ProxySettings: handler.ProxySettings(),
})
}
return response, nil
}
func (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {} func (s *handlerServer) mustEmbedUnimplementedHandlerServiceServer() {}
type service struct { type service struct {

View File

@@ -364,6 +364,96 @@ func (*AlterInboundResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{7} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{7}
} }
type ListInboundsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
IsOnlyTags bool `protobuf:"varint,1,opt,name=isOnlyTags,proto3" json:"isOnlyTags,omitempty"`
}
func (x *ListInboundsRequest) Reset() {
*x = ListInboundsRequest{}
mi := &file_app_proxyman_command_command_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListInboundsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListInboundsRequest) ProtoMessage() {}
func (x *ListInboundsRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_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 ListInboundsRequest.ProtoReflect.Descriptor instead.
func (*ListInboundsRequest) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{8}
}
func (x *ListInboundsRequest) GetIsOnlyTags() bool {
if x != nil {
return x.IsOnlyTags
}
return false
}
type ListInboundsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Inbounds []*core.InboundHandlerConfig `protobuf:"bytes,1,rep,name=inbounds,proto3" json:"inbounds,omitempty"`
}
func (x *ListInboundsResponse) Reset() {
*x = ListInboundsResponse{}
mi := &file_app_proxyman_command_command_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListInboundsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListInboundsResponse) ProtoMessage() {}
func (x *ListInboundsResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_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 ListInboundsResponse.ProtoReflect.Descriptor instead.
func (*ListInboundsResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{9}
}
func (x *ListInboundsResponse) GetInbounds() []*core.InboundHandlerConfig {
if x != nil {
return x.Inbounds
}
return nil
}
type GetInboundUserRequest struct { type GetInboundUserRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -375,7 +465,7 @@ type GetInboundUserRequest struct {
func (x *GetInboundUserRequest) Reset() { func (x *GetInboundUserRequest) Reset() {
*x = GetInboundUserRequest{} *x = GetInboundUserRequest{}
mi := &file_app_proxyman_command_command_proto_msgTypes[8] mi := &file_app_proxyman_command_command_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -387,7 +477,7 @@ func (x *GetInboundUserRequest) String() string {
func (*GetInboundUserRequest) ProtoMessage() {} func (*GetInboundUserRequest) ProtoMessage() {}
func (x *GetInboundUserRequest) ProtoReflect() protoreflect.Message { func (x *GetInboundUserRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[8] mi := &file_app_proxyman_command_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 {
@@ -400,7 +490,7 @@ func (x *GetInboundUserRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetInboundUserRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetInboundUserRequest.ProtoReflect.Descriptor instead.
func (*GetInboundUserRequest) Descriptor() ([]byte, []int) { func (*GetInboundUserRequest) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{8} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{10}
} }
func (x *GetInboundUserRequest) GetTag() string { func (x *GetInboundUserRequest) GetTag() string {
@@ -427,7 +517,7 @@ type GetInboundUserResponse struct {
func (x *GetInboundUserResponse) Reset() { func (x *GetInboundUserResponse) Reset() {
*x = GetInboundUserResponse{} *x = GetInboundUserResponse{}
mi := &file_app_proxyman_command_command_proto_msgTypes[9] mi := &file_app_proxyman_command_command_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -439,7 +529,7 @@ func (x *GetInboundUserResponse) String() string {
func (*GetInboundUserResponse) ProtoMessage() {} func (*GetInboundUserResponse) ProtoMessage() {}
func (x *GetInboundUserResponse) ProtoReflect() protoreflect.Message { func (x *GetInboundUserResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[9] mi := &file_app_proxyman_command_command_proto_msgTypes[11]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -452,7 +542,7 @@ func (x *GetInboundUserResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetInboundUserResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetInboundUserResponse.ProtoReflect.Descriptor instead.
func (*GetInboundUserResponse) Descriptor() ([]byte, []int) { func (*GetInboundUserResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{9} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{11}
} }
func (x *GetInboundUserResponse) GetUsers() []*protocol.User { func (x *GetInboundUserResponse) GetUsers() []*protocol.User {
@@ -472,7 +562,7 @@ type GetInboundUsersCountResponse struct {
func (x *GetInboundUsersCountResponse) Reset() { func (x *GetInboundUsersCountResponse) Reset() {
*x = GetInboundUsersCountResponse{} *x = GetInboundUsersCountResponse{}
mi := &file_app_proxyman_command_command_proto_msgTypes[10] mi := &file_app_proxyman_command_command_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -484,7 +574,7 @@ func (x *GetInboundUsersCountResponse) String() string {
func (*GetInboundUsersCountResponse) ProtoMessage() {} func (*GetInboundUsersCountResponse) ProtoMessage() {}
func (x *GetInboundUsersCountResponse) ProtoReflect() protoreflect.Message { func (x *GetInboundUsersCountResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[10] mi := &file_app_proxyman_command_command_proto_msgTypes[12]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -497,7 +587,7 @@ func (x *GetInboundUsersCountResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetInboundUsersCountResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetInboundUsersCountResponse.ProtoReflect.Descriptor instead.
func (*GetInboundUsersCountResponse) Descriptor() ([]byte, []int) { func (*GetInboundUsersCountResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{10} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{12}
} }
func (x *GetInboundUsersCountResponse) GetCount() int64 { func (x *GetInboundUsersCountResponse) GetCount() int64 {
@@ -517,7 +607,7 @@ type AddOutboundRequest struct {
func (x *AddOutboundRequest) Reset() { func (x *AddOutboundRequest) Reset() {
*x = AddOutboundRequest{} *x = AddOutboundRequest{}
mi := &file_app_proxyman_command_command_proto_msgTypes[11] mi := &file_app_proxyman_command_command_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -529,7 +619,7 @@ func (x *AddOutboundRequest) String() string {
func (*AddOutboundRequest) ProtoMessage() {} func (*AddOutboundRequest) ProtoMessage() {}
func (x *AddOutboundRequest) ProtoReflect() protoreflect.Message { func (x *AddOutboundRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[11] mi := &file_app_proxyman_command_command_proto_msgTypes[13]
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 {
@@ -542,7 +632,7 @@ func (x *AddOutboundRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddOutboundRequest.ProtoReflect.Descriptor instead. // Deprecated: Use AddOutboundRequest.ProtoReflect.Descriptor instead.
func (*AddOutboundRequest) Descriptor() ([]byte, []int) { func (*AddOutboundRequest) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{11} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{13}
} }
func (x *AddOutboundRequest) GetOutbound() *core.OutboundHandlerConfig { func (x *AddOutboundRequest) GetOutbound() *core.OutboundHandlerConfig {
@@ -560,7 +650,7 @@ type AddOutboundResponse struct {
func (x *AddOutboundResponse) Reset() { func (x *AddOutboundResponse) Reset() {
*x = AddOutboundResponse{} *x = AddOutboundResponse{}
mi := &file_app_proxyman_command_command_proto_msgTypes[12] mi := &file_app_proxyman_command_command_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -572,7 +662,7 @@ func (x *AddOutboundResponse) String() string {
func (*AddOutboundResponse) ProtoMessage() {} func (*AddOutboundResponse) ProtoMessage() {}
func (x *AddOutboundResponse) ProtoReflect() protoreflect.Message { func (x *AddOutboundResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[12] mi := &file_app_proxyman_command_command_proto_msgTypes[14]
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 {
@@ -585,7 +675,7 @@ func (x *AddOutboundResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddOutboundResponse.ProtoReflect.Descriptor instead. // Deprecated: Use AddOutboundResponse.ProtoReflect.Descriptor instead.
func (*AddOutboundResponse) Descriptor() ([]byte, []int) { func (*AddOutboundResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{12} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{14}
} }
type RemoveOutboundRequest struct { type RemoveOutboundRequest struct {
@@ -598,7 +688,7 @@ type RemoveOutboundRequest struct {
func (x *RemoveOutboundRequest) Reset() { func (x *RemoveOutboundRequest) Reset() {
*x = RemoveOutboundRequest{} *x = RemoveOutboundRequest{}
mi := &file_app_proxyman_command_command_proto_msgTypes[13] mi := &file_app_proxyman_command_command_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -610,7 +700,7 @@ func (x *RemoveOutboundRequest) String() string {
func (*RemoveOutboundRequest) ProtoMessage() {} func (*RemoveOutboundRequest) ProtoMessage() {}
func (x *RemoveOutboundRequest) ProtoReflect() protoreflect.Message { func (x *RemoveOutboundRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[13] mi := &file_app_proxyman_command_command_proto_msgTypes[15]
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 {
@@ -623,7 +713,7 @@ func (x *RemoveOutboundRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RemoveOutboundRequest.ProtoReflect.Descriptor instead. // Deprecated: Use RemoveOutboundRequest.ProtoReflect.Descriptor instead.
func (*RemoveOutboundRequest) Descriptor() ([]byte, []int) { func (*RemoveOutboundRequest) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{13} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{15}
} }
func (x *RemoveOutboundRequest) GetTag() string { func (x *RemoveOutboundRequest) GetTag() string {
@@ -641,7 +731,7 @@ type RemoveOutboundResponse struct {
func (x *RemoveOutboundResponse) Reset() { func (x *RemoveOutboundResponse) Reset() {
*x = RemoveOutboundResponse{} *x = RemoveOutboundResponse{}
mi := &file_app_proxyman_command_command_proto_msgTypes[14] mi := &file_app_proxyman_command_command_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -653,7 +743,7 @@ func (x *RemoveOutboundResponse) String() string {
func (*RemoveOutboundResponse) ProtoMessage() {} func (*RemoveOutboundResponse) ProtoMessage() {}
func (x *RemoveOutboundResponse) ProtoReflect() protoreflect.Message { func (x *RemoveOutboundResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[14] mi := &file_app_proxyman_command_command_proto_msgTypes[16]
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 {
@@ -666,7 +756,7 @@ func (x *RemoveOutboundResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RemoveOutboundResponse.ProtoReflect.Descriptor instead. // Deprecated: Use RemoveOutboundResponse.ProtoReflect.Descriptor instead.
func (*RemoveOutboundResponse) Descriptor() ([]byte, []int) { func (*RemoveOutboundResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{14} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{16}
} }
type AlterOutboundRequest struct { type AlterOutboundRequest struct {
@@ -680,7 +770,7 @@ type AlterOutboundRequest struct {
func (x *AlterOutboundRequest) Reset() { func (x *AlterOutboundRequest) Reset() {
*x = AlterOutboundRequest{} *x = AlterOutboundRequest{}
mi := &file_app_proxyman_command_command_proto_msgTypes[15] mi := &file_app_proxyman_command_command_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -692,7 +782,7 @@ func (x *AlterOutboundRequest) String() string {
func (*AlterOutboundRequest) ProtoMessage() {} func (*AlterOutboundRequest) ProtoMessage() {}
func (x *AlterOutboundRequest) ProtoReflect() protoreflect.Message { func (x *AlterOutboundRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[15] mi := &file_app_proxyman_command_command_proto_msgTypes[17]
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 {
@@ -705,7 +795,7 @@ func (x *AlterOutboundRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AlterOutboundRequest.ProtoReflect.Descriptor instead. // Deprecated: Use AlterOutboundRequest.ProtoReflect.Descriptor instead.
func (*AlterOutboundRequest) Descriptor() ([]byte, []int) { func (*AlterOutboundRequest) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{15} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{17}
} }
func (x *AlterOutboundRequest) GetTag() string { func (x *AlterOutboundRequest) GetTag() string {
@@ -730,7 +820,7 @@ type AlterOutboundResponse struct {
func (x *AlterOutboundResponse) Reset() { func (x *AlterOutboundResponse) Reset() {
*x = AlterOutboundResponse{} *x = AlterOutboundResponse{}
mi := &file_app_proxyman_command_command_proto_msgTypes[16] mi := &file_app_proxyman_command_command_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -742,7 +832,7 @@ func (x *AlterOutboundResponse) String() string {
func (*AlterOutboundResponse) ProtoMessage() {} func (*AlterOutboundResponse) ProtoMessage() {}
func (x *AlterOutboundResponse) ProtoReflect() protoreflect.Message { func (x *AlterOutboundResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[16] mi := &file_app_proxyman_command_command_proto_msgTypes[18]
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 {
@@ -755,7 +845,88 @@ func (x *AlterOutboundResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AlterOutboundResponse.ProtoReflect.Descriptor instead. // Deprecated: Use AlterOutboundResponse.ProtoReflect.Descriptor instead.
func (*AlterOutboundResponse) Descriptor() ([]byte, []int) { func (*AlterOutboundResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{16} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{18}
}
type ListOutboundsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListOutboundsRequest) Reset() {
*x = ListOutboundsRequest{}
mi := &file_app_proxyman_command_command_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListOutboundsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListOutboundsRequest) ProtoMessage() {}
func (x *ListOutboundsRequest) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[19]
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 ListOutboundsRequest.ProtoReflect.Descriptor instead.
func (*ListOutboundsRequest) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{19}
}
type ListOutboundsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Outbounds []*core.OutboundHandlerConfig `protobuf:"bytes,1,rep,name=outbounds,proto3" json:"outbounds,omitempty"`
}
func (x *ListOutboundsResponse) Reset() {
*x = ListOutboundsResponse{}
mi := &file_app_proxyman_command_command_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListOutboundsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListOutboundsResponse) ProtoMessage() {}
func (x *ListOutboundsResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_proxyman_command_command_proto_msgTypes[20]
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 ListOutboundsResponse.ProtoReflect.Descriptor instead.
func (*ListOutboundsResponse) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{20}
}
func (x *ListOutboundsResponse) GetOutbounds() []*core.OutboundHandlerConfig {
if x != nil {
return x.Outbounds
}
return nil
} }
type Config struct { type Config struct {
@@ -766,7 +937,7 @@ type Config struct {
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
mi := &file_app_proxyman_command_command_proto_msgTypes[17] mi := &file_app_proxyman_command_command_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -778,7 +949,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_proxyman_command_command_proto_msgTypes[17] mi := &file_app_proxyman_command_command_proto_msgTypes[21]
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 {
@@ -791,7 +962,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
// Deprecated: Use Config.ProtoReflect.Descriptor instead. // Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) { func (*Config) Descriptor() ([]byte, []int) {
return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{17} return file_app_proxyman_command_command_proto_rawDescGZIP(), []int{21}
} }
var File_app_proxyman_command_command_proto protoreflect.FileDescriptor var File_app_proxyman_command_command_proto protoreflect.FileDescriptor
@@ -831,61 +1002,84 @@ var file_app_proxyman_command_command_proto_rawDesc = []byte{
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70,
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62,
0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3f, 0x0a, 0x15, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x13,
0x47, 0x65, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x54, 0x61, 0x67,
0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x54,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x4a, 0x0a, 0x61, 0x67, 0x73, 0x22, 0x53, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75,
0x16, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x69,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08,
0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x34, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22, 0x3f, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x49,
0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x74, 0x61, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01,
0x52, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x4a, 0x0a, 0x16, 0x47, 0x65, 0x74,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05,
0x75, 0x6e, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x34, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x62, 0x6f,
0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x0a, 0x15, 0x52, 0x65, 0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73,
0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x52, 0x0a, 0x12, 0x41,
0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22,
0x15, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x0a, 0x15, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61,
0x67, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f,
0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x68, 0x0a, 0x14, 0x41,
0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x18, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x3e, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x68, 0x0a, 0x14, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x3e, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x17, 0x0a, 0x15, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x4f, 0x75,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52,
0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x09, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x75,
0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x17, 0x0a, 0x15, 0x41, 0x6c, 0x74, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x3e, 0x0a, 0x09, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03,
0x73, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xc5, 0x07, 0x0a, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f,
0x0e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f,
0x6b, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2c, 0x2e, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xae, 0x09, 0x0a, 0x0e, 0x48, 0x61,
0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x0a,
0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x78, 0x72, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2c, 0x2e, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x52, 0x65, 0x6d,
0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2f, 0x2e, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62,
0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e,
0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2f, 0x2e, 0x71, 0x0a, 0x0c, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74, 0x65,
0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74, 0x65,
0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x71, 0x0a, 0x0c, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x22, 0x00, 0x12, 0x71, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
0x6e, 0x64, 0x12, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x64, 0x73, 0x12, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72,
0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4c,
0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72,
0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4c,
0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x78, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x62, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x78, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x62, 0x6f,
0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x75, 0x6e, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
@@ -924,14 +1118,22 @@ var file_app_proxyman_command_command_proto_rawDesc = []byte{
0x1a, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x1a, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78,
0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x41, 0x6c, 0x74,
0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x73, 0x65, 0x22, 0x00, 0x12, 0x74, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x75, 0x74, 0x62,
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x52,
0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x19, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6e, 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73,
0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f,
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x6d, 0x61, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2e, 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, 0x70, 0x72, 0x6f,
0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x19,
0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61,
0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
} }
var ( var (
@@ -946,7 +1148,7 @@ func file_app_proxyman_command_command_proto_rawDescGZIP() []byte {
return file_app_proxyman_command_command_proto_rawDescData return file_app_proxyman_command_command_proto_rawDescData
} }
var file_app_proxyman_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_app_proxyman_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 22)
var file_app_proxyman_command_command_proto_goTypes = []any{ var file_app_proxyman_command_command_proto_goTypes = []any{
(*AddUserOperation)(nil), // 0: xray.app.proxyman.command.AddUserOperation (*AddUserOperation)(nil), // 0: xray.app.proxyman.command.AddUserOperation
(*RemoveUserOperation)(nil), // 1: xray.app.proxyman.command.RemoveUserOperation (*RemoveUserOperation)(nil), // 1: xray.app.proxyman.command.RemoveUserOperation
@@ -956,49 +1158,59 @@ var file_app_proxyman_command_command_proto_goTypes = []any{
(*RemoveInboundResponse)(nil), // 5: xray.app.proxyman.command.RemoveInboundResponse (*RemoveInboundResponse)(nil), // 5: xray.app.proxyman.command.RemoveInboundResponse
(*AlterInboundRequest)(nil), // 6: xray.app.proxyman.command.AlterInboundRequest (*AlterInboundRequest)(nil), // 6: xray.app.proxyman.command.AlterInboundRequest
(*AlterInboundResponse)(nil), // 7: xray.app.proxyman.command.AlterInboundResponse (*AlterInboundResponse)(nil), // 7: xray.app.proxyman.command.AlterInboundResponse
(*GetInboundUserRequest)(nil), // 8: xray.app.proxyman.command.GetInboundUserRequest (*ListInboundsRequest)(nil), // 8: xray.app.proxyman.command.ListInboundsRequest
(*GetInboundUserResponse)(nil), // 9: xray.app.proxyman.command.GetInboundUserResponse (*ListInboundsResponse)(nil), // 9: xray.app.proxyman.command.ListInboundsResponse
(*GetInboundUsersCountResponse)(nil), // 10: xray.app.proxyman.command.GetInboundUsersCountResponse (*GetInboundUserRequest)(nil), // 10: xray.app.proxyman.command.GetInboundUserRequest
(*AddOutboundRequest)(nil), // 11: xray.app.proxyman.command.AddOutboundRequest (*GetInboundUserResponse)(nil), // 11: xray.app.proxyman.command.GetInboundUserResponse
(*AddOutboundResponse)(nil), // 12: xray.app.proxyman.command.AddOutboundResponse (*GetInboundUsersCountResponse)(nil), // 12: xray.app.proxyman.command.GetInboundUsersCountResponse
(*RemoveOutboundRequest)(nil), // 13: xray.app.proxyman.command.RemoveOutboundRequest (*AddOutboundRequest)(nil), // 13: xray.app.proxyman.command.AddOutboundRequest
(*RemoveOutboundResponse)(nil), // 14: xray.app.proxyman.command.RemoveOutboundResponse (*AddOutboundResponse)(nil), // 14: xray.app.proxyman.command.AddOutboundResponse
(*AlterOutboundRequest)(nil), // 15: xray.app.proxyman.command.AlterOutboundRequest (*RemoveOutboundRequest)(nil), // 15: xray.app.proxyman.command.RemoveOutboundRequest
(*AlterOutboundResponse)(nil), // 16: xray.app.proxyman.command.AlterOutboundResponse (*RemoveOutboundResponse)(nil), // 16: xray.app.proxyman.command.RemoveOutboundResponse
(*Config)(nil), // 17: xray.app.proxyman.command.Config (*AlterOutboundRequest)(nil), // 17: xray.app.proxyman.command.AlterOutboundRequest
(*protocol.User)(nil), // 18: xray.common.protocol.User (*AlterOutboundResponse)(nil), // 18: xray.app.proxyman.command.AlterOutboundResponse
(*core.InboundHandlerConfig)(nil), // 19: xray.core.InboundHandlerConfig (*ListOutboundsRequest)(nil), // 19: xray.app.proxyman.command.ListOutboundsRequest
(*serial.TypedMessage)(nil), // 20: xray.common.serial.TypedMessage (*ListOutboundsResponse)(nil), // 20: xray.app.proxyman.command.ListOutboundsResponse
(*core.OutboundHandlerConfig)(nil), // 21: xray.core.OutboundHandlerConfig (*Config)(nil), // 21: xray.app.proxyman.command.Config
(*protocol.User)(nil), // 22: xray.common.protocol.User
(*core.InboundHandlerConfig)(nil), // 23: xray.core.InboundHandlerConfig
(*serial.TypedMessage)(nil), // 24: xray.common.serial.TypedMessage
(*core.OutboundHandlerConfig)(nil), // 25: xray.core.OutboundHandlerConfig
} }
var file_app_proxyman_command_command_proto_depIdxs = []int32{ var file_app_proxyman_command_command_proto_depIdxs = []int32{
18, // 0: xray.app.proxyman.command.AddUserOperation.user:type_name -> xray.common.protocol.User 22, // 0: xray.app.proxyman.command.AddUserOperation.user:type_name -> xray.common.protocol.User
19, // 1: xray.app.proxyman.command.AddInboundRequest.inbound:type_name -> xray.core.InboundHandlerConfig 23, // 1: xray.app.proxyman.command.AddInboundRequest.inbound:type_name -> xray.core.InboundHandlerConfig
20, // 2: xray.app.proxyman.command.AlterInboundRequest.operation:type_name -> xray.common.serial.TypedMessage 24, // 2: xray.app.proxyman.command.AlterInboundRequest.operation:type_name -> xray.common.serial.TypedMessage
18, // 3: xray.app.proxyman.command.GetInboundUserResponse.users:type_name -> xray.common.protocol.User 23, // 3: xray.app.proxyman.command.ListInboundsResponse.inbounds:type_name -> xray.core.InboundHandlerConfig
21, // 4: xray.app.proxyman.command.AddOutboundRequest.outbound:type_name -> xray.core.OutboundHandlerConfig 22, // 4: xray.app.proxyman.command.GetInboundUserResponse.users:type_name -> xray.common.protocol.User
20, // 5: xray.app.proxyman.command.AlterOutboundRequest.operation:type_name -> xray.common.serial.TypedMessage 25, // 5: xray.app.proxyman.command.AddOutboundRequest.outbound:type_name -> xray.core.OutboundHandlerConfig
2, // 6: xray.app.proxyman.command.HandlerService.AddInbound:input_type -> xray.app.proxyman.command.AddInboundRequest 24, // 6: xray.app.proxyman.command.AlterOutboundRequest.operation:type_name -> xray.common.serial.TypedMessage
4, // 7: xray.app.proxyman.command.HandlerService.RemoveInbound:input_type -> xray.app.proxyman.command.RemoveInboundRequest 25, // 7: xray.app.proxyman.command.ListOutboundsResponse.outbounds:type_name -> xray.core.OutboundHandlerConfig
6, // 8: xray.app.proxyman.command.HandlerService.AlterInbound:input_type -> xray.app.proxyman.command.AlterInboundRequest 2, // 8: xray.app.proxyman.command.HandlerService.AddInbound:input_type -> xray.app.proxyman.command.AddInboundRequest
8, // 9: xray.app.proxyman.command.HandlerService.GetInboundUsers:input_type -> xray.app.proxyman.command.GetInboundUserRequest 4, // 9: xray.app.proxyman.command.HandlerService.RemoveInbound:input_type -> xray.app.proxyman.command.RemoveInboundRequest
8, // 10: xray.app.proxyman.command.HandlerService.GetInboundUsersCount:input_type -> xray.app.proxyman.command.GetInboundUserRequest 6, // 10: xray.app.proxyman.command.HandlerService.AlterInbound:input_type -> xray.app.proxyman.command.AlterInboundRequest
11, // 11: xray.app.proxyman.command.HandlerService.AddOutbound:input_type -> xray.app.proxyman.command.AddOutboundRequest 8, // 11: xray.app.proxyman.command.HandlerService.ListInbounds:input_type -> xray.app.proxyman.command.ListInboundsRequest
13, // 12: xray.app.proxyman.command.HandlerService.RemoveOutbound:input_type -> xray.app.proxyman.command.RemoveOutboundRequest 10, // 12: xray.app.proxyman.command.HandlerService.GetInboundUsers:input_type -> xray.app.proxyman.command.GetInboundUserRequest
15, // 13: xray.app.proxyman.command.HandlerService.AlterOutbound:input_type -> xray.app.proxyman.command.AlterOutboundRequest 10, // 13: xray.app.proxyman.command.HandlerService.GetInboundUsersCount:input_type -> xray.app.proxyman.command.GetInboundUserRequest
3, // 14: xray.app.proxyman.command.HandlerService.AddInbound:output_type -> xray.app.proxyman.command.AddInboundResponse 13, // 14: xray.app.proxyman.command.HandlerService.AddOutbound:input_type -> xray.app.proxyman.command.AddOutboundRequest
5, // 15: xray.app.proxyman.command.HandlerService.RemoveInbound:output_type -> xray.app.proxyman.command.RemoveInboundResponse 15, // 15: xray.app.proxyman.command.HandlerService.RemoveOutbound:input_type -> xray.app.proxyman.command.RemoveOutboundRequest
7, // 16: xray.app.proxyman.command.HandlerService.AlterInbound:output_type -> xray.app.proxyman.command.AlterInboundResponse 17, // 16: xray.app.proxyman.command.HandlerService.AlterOutbound:input_type -> xray.app.proxyman.command.AlterOutboundRequest
9, // 17: xray.app.proxyman.command.HandlerService.GetInboundUsers:output_type -> xray.app.proxyman.command.GetInboundUserResponse 19, // 17: xray.app.proxyman.command.HandlerService.ListOutbounds:input_type -> xray.app.proxyman.command.ListOutboundsRequest
10, // 18: xray.app.proxyman.command.HandlerService.GetInboundUsersCount:output_type -> xray.app.proxyman.command.GetInboundUsersCountResponse 3, // 18: xray.app.proxyman.command.HandlerService.AddInbound:output_type -> xray.app.proxyman.command.AddInboundResponse
12, // 19: xray.app.proxyman.command.HandlerService.AddOutbound:output_type -> xray.app.proxyman.command.AddOutboundResponse 5, // 19: xray.app.proxyman.command.HandlerService.RemoveInbound:output_type -> xray.app.proxyman.command.RemoveInboundResponse
14, // 20: xray.app.proxyman.command.HandlerService.RemoveOutbound:output_type -> xray.app.proxyman.command.RemoveOutboundResponse 7, // 20: xray.app.proxyman.command.HandlerService.AlterInbound:output_type -> xray.app.proxyman.command.AlterInboundResponse
16, // 21: xray.app.proxyman.command.HandlerService.AlterOutbound:output_type -> xray.app.proxyman.command.AlterOutboundResponse 9, // 21: xray.app.proxyman.command.HandlerService.ListInbounds:output_type -> xray.app.proxyman.command.ListInboundsResponse
14, // [14:22] is the sub-list for method output_type 11, // 22: xray.app.proxyman.command.HandlerService.GetInboundUsers:output_type -> xray.app.proxyman.command.GetInboundUserResponse
6, // [6:14] is the sub-list for method input_type 12, // 23: xray.app.proxyman.command.HandlerService.GetInboundUsersCount:output_type -> xray.app.proxyman.command.GetInboundUsersCountResponse
6, // [6:6] is the sub-list for extension type_name 14, // 24: xray.app.proxyman.command.HandlerService.AddOutbound:output_type -> xray.app.proxyman.command.AddOutboundResponse
6, // [6:6] is the sub-list for extension extendee 16, // 25: xray.app.proxyman.command.HandlerService.RemoveOutbound:output_type -> xray.app.proxyman.command.RemoveOutboundResponse
0, // [0:6] is the sub-list for field type_name 18, // 26: xray.app.proxyman.command.HandlerService.AlterOutbound:output_type -> xray.app.proxyman.command.AlterOutboundResponse
20, // 27: xray.app.proxyman.command.HandlerService.ListOutbounds:output_type -> xray.app.proxyman.command.ListOutboundsResponse
18, // [18:28] is the sub-list for method output_type
8, // [8:18] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
} }
func init() { file_app_proxyman_command_command_proto_init() } func init() { file_app_proxyman_command_command_proto_init() }
@@ -1012,7 +1224,7 @@ func file_app_proxyman_command_command_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_app_proxyman_command_command_proto_rawDesc, RawDescriptor: file_app_proxyman_command_command_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 18, NumMessages: 22,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@@ -37,6 +37,14 @@ message AlterInboundRequest {
message AlterInboundResponse {} message AlterInboundResponse {}
message ListInboundsRequest {
bool isOnlyTags = 1;
}
message ListInboundsResponse {
repeated core.InboundHandlerConfig inbounds = 1;
}
message GetInboundUserRequest { message GetInboundUserRequest {
string tag = 1; string tag = 1;
string email = 2; string email = 2;
@@ -69,6 +77,12 @@ message AlterOutboundRequest {
message AlterOutboundResponse {} message AlterOutboundResponse {}
message ListOutboundsRequest {}
message ListOutboundsResponse {
repeated core.OutboundHandlerConfig outbounds = 1;
}
service HandlerService { service HandlerService {
rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {} rpc AddInbound(AddInboundRequest) returns (AddInboundResponse) {}
@@ -76,6 +90,8 @@ service HandlerService {
rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {} rpc AlterInbound(AlterInboundRequest) returns (AlterInboundResponse) {}
rpc ListInbounds(ListInboundsRequest) returns (ListInboundsResponse) {}
rpc GetInboundUsers(GetInboundUserRequest) returns (GetInboundUserResponse) {} rpc GetInboundUsers(GetInboundUserRequest) returns (GetInboundUserResponse) {}
rpc GetInboundUsersCount(GetInboundUserRequest) returns (GetInboundUsersCountResponse) {} rpc GetInboundUsersCount(GetInboundUserRequest) returns (GetInboundUsersCountResponse) {}
@@ -85,6 +101,8 @@ service HandlerService {
rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {} rpc RemoveOutbound(RemoveOutboundRequest) returns (RemoveOutboundResponse) {}
rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {} rpc AlterOutbound(AlterOutboundRequest) returns (AlterOutboundResponse) {}
rpc ListOutbounds(ListOutboundsRequest) returns (ListOutboundsResponse) {}
} }
message Config {} message Config {}

View File

@@ -22,11 +22,13 @@ const (
HandlerService_AddInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddInbound" HandlerService_AddInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddInbound"
HandlerService_RemoveInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveInbound" HandlerService_RemoveInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveInbound"
HandlerService_AlterInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterInbound" HandlerService_AlterInbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterInbound"
HandlerService_ListInbounds_FullMethodName = "/xray.app.proxyman.command.HandlerService/ListInbounds"
HandlerService_GetInboundUsers_FullMethodName = "/xray.app.proxyman.command.HandlerService/GetInboundUsers" HandlerService_GetInboundUsers_FullMethodName = "/xray.app.proxyman.command.HandlerService/GetInboundUsers"
HandlerService_GetInboundUsersCount_FullMethodName = "/xray.app.proxyman.command.HandlerService/GetInboundUsersCount" HandlerService_GetInboundUsersCount_FullMethodName = "/xray.app.proxyman.command.HandlerService/GetInboundUsersCount"
HandlerService_AddOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddOutbound" HandlerService_AddOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AddOutbound"
HandlerService_RemoveOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveOutbound" HandlerService_RemoveOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/RemoveOutbound"
HandlerService_AlterOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterOutbound" HandlerService_AlterOutbound_FullMethodName = "/xray.app.proxyman.command.HandlerService/AlterOutbound"
HandlerService_ListOutbounds_FullMethodName = "/xray.app.proxyman.command.HandlerService/ListOutbounds"
) )
// HandlerServiceClient is the client API for HandlerService service. // HandlerServiceClient is the client API for HandlerService service.
@@ -36,11 +38,13 @@ type HandlerServiceClient interface {
AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error) AddInbound(ctx context.Context, in *AddInboundRequest, opts ...grpc.CallOption) (*AddInboundResponse, error)
RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error) RemoveInbound(ctx context.Context, in *RemoveInboundRequest, opts ...grpc.CallOption) (*RemoveInboundResponse, error)
AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error) AlterInbound(ctx context.Context, in *AlterInboundRequest, opts ...grpc.CallOption) (*AlterInboundResponse, error)
ListInbounds(ctx context.Context, in *ListInboundsRequest, opts ...grpc.CallOption) (*ListInboundsResponse, error)
GetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error) GetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error)
GetInboundUsersCount(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUsersCountResponse, error) GetInboundUsersCount(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUsersCountResponse, error)
AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error) AddOutbound(ctx context.Context, in *AddOutboundRequest, opts ...grpc.CallOption) (*AddOutboundResponse, error)
RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error) RemoveOutbound(ctx context.Context, in *RemoveOutboundRequest, opts ...grpc.CallOption) (*RemoveOutboundResponse, error)
AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error) AlterOutbound(ctx context.Context, in *AlterOutboundRequest, opts ...grpc.CallOption) (*AlterOutboundResponse, error)
ListOutbounds(ctx context.Context, in *ListOutboundsRequest, opts ...grpc.CallOption) (*ListOutboundsResponse, error)
} }
type handlerServiceClient struct { type handlerServiceClient struct {
@@ -81,6 +85,16 @@ func (c *handlerServiceClient) AlterInbound(ctx context.Context, in *AlterInboun
return out, nil return out, nil
} }
func (c *handlerServiceClient) ListInbounds(ctx context.Context, in *ListInboundsRequest, opts ...grpc.CallOption) (*ListInboundsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListInboundsResponse)
err := c.cc.Invoke(ctx, HandlerService_ListInbounds_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *handlerServiceClient) GetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error) { func (c *handlerServiceClient) GetInboundUsers(ctx context.Context, in *GetInboundUserRequest, opts ...grpc.CallOption) (*GetInboundUserResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetInboundUserResponse) out := new(GetInboundUserResponse)
@@ -131,6 +145,16 @@ func (c *handlerServiceClient) AlterOutbound(ctx context.Context, in *AlterOutbo
return out, nil return out, nil
} }
func (c *handlerServiceClient) ListOutbounds(ctx context.Context, in *ListOutboundsRequest, opts ...grpc.CallOption) (*ListOutboundsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListOutboundsResponse)
err := c.cc.Invoke(ctx, HandlerService_ListOutbounds_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// HandlerServiceServer is the server API for HandlerService service. // HandlerServiceServer is the server API for HandlerService service.
// All implementations must embed UnimplementedHandlerServiceServer // All implementations must embed UnimplementedHandlerServiceServer
// for forward compatibility. // for forward compatibility.
@@ -138,11 +162,13 @@ type HandlerServiceServer interface {
AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error) AddInbound(context.Context, *AddInboundRequest) (*AddInboundResponse, error)
RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error) RemoveInbound(context.Context, *RemoveInboundRequest) (*RemoveInboundResponse, error)
AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error)
ListInbounds(context.Context, *ListInboundsRequest) (*ListInboundsResponse, error)
GetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error) GetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error)
GetInboundUsersCount(context.Context, *GetInboundUserRequest) (*GetInboundUsersCountResponse, error) GetInboundUsersCount(context.Context, *GetInboundUserRequest) (*GetInboundUsersCountResponse, error)
AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error) AddOutbound(context.Context, *AddOutboundRequest) (*AddOutboundResponse, error)
RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error) RemoveOutbound(context.Context, *RemoveOutboundRequest) (*RemoveOutboundResponse, error)
AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error)
ListOutbounds(context.Context, *ListOutboundsRequest) (*ListOutboundsResponse, error)
mustEmbedUnimplementedHandlerServiceServer() mustEmbedUnimplementedHandlerServiceServer()
} }
@@ -162,6 +188,9 @@ func (UnimplementedHandlerServiceServer) RemoveInbound(context.Context, *RemoveI
func (UnimplementedHandlerServiceServer) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) { func (UnimplementedHandlerServiceServer) AlterInbound(context.Context, *AlterInboundRequest) (*AlterInboundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AlterInbound not implemented") return nil, status.Errorf(codes.Unimplemented, "method AlterInbound not implemented")
} }
func (UnimplementedHandlerServiceServer) ListInbounds(context.Context, *ListInboundsRequest) (*ListInboundsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListInbounds not implemented")
}
func (UnimplementedHandlerServiceServer) GetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error) { func (UnimplementedHandlerServiceServer) GetInboundUsers(context.Context, *GetInboundUserRequest) (*GetInboundUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetInboundUsers not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetInboundUsers not implemented")
} }
@@ -177,6 +206,9 @@ func (UnimplementedHandlerServiceServer) RemoveOutbound(context.Context, *Remove
func (UnimplementedHandlerServiceServer) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) { func (UnimplementedHandlerServiceServer) AlterOutbound(context.Context, *AlterOutboundRequest) (*AlterOutboundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AlterOutbound not implemented") return nil, status.Errorf(codes.Unimplemented, "method AlterOutbound not implemented")
} }
func (UnimplementedHandlerServiceServer) ListOutbounds(context.Context, *ListOutboundsRequest) (*ListOutboundsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListOutbounds not implemented")
}
func (UnimplementedHandlerServiceServer) mustEmbedUnimplementedHandlerServiceServer() {} func (UnimplementedHandlerServiceServer) mustEmbedUnimplementedHandlerServiceServer() {}
func (UnimplementedHandlerServiceServer) testEmbeddedByValue() {} func (UnimplementedHandlerServiceServer) testEmbeddedByValue() {}
@@ -252,6 +284,24 @@ func _HandlerService_AlterInbound_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _HandlerService_ListInbounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListInboundsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HandlerServiceServer).ListInbounds(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: HandlerService_ListInbounds_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HandlerServiceServer).ListInbounds(ctx, req.(*ListInboundsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _HandlerService_GetInboundUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _HandlerService_GetInboundUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetInboundUserRequest) in := new(GetInboundUserRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -342,6 +392,24 @@ func _HandlerService_AlterOutbound_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _HandlerService_ListOutbounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListOutboundsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HandlerServiceServer).ListOutbounds(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: HandlerService_ListOutbounds_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HandlerServiceServer).ListOutbounds(ctx, req.(*ListOutboundsRequest))
}
return interceptor(ctx, in, info, handler)
}
// HandlerService_ServiceDesc is the grpc.ServiceDesc for HandlerService service. // HandlerService_ServiceDesc is the grpc.ServiceDesc for HandlerService 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)
@@ -361,6 +429,10 @@ var HandlerService_ServiceDesc = grpc.ServiceDesc{
MethodName: "AlterInbound", MethodName: "AlterInbound",
Handler: _HandlerService_AlterInbound_Handler, Handler: _HandlerService_AlterInbound_Handler,
}, },
{
MethodName: "ListInbounds",
Handler: _HandlerService_ListInbounds_Handler,
},
{ {
MethodName: "GetInboundUsers", MethodName: "GetInboundUsers",
Handler: _HandlerService_GetInboundUsers_Handler, Handler: _HandlerService_GetInboundUsers_Handler,
@@ -381,6 +453,10 @@ var HandlerService_ServiceDesc = grpc.ServiceDesc{
MethodName: "AlterOutbound", MethodName: "AlterOutbound",
Handler: _HandlerService_AlterOutbound_Handler, Handler: _HandlerService_AlterOutbound_Handler,
}, },
{
MethodName: "ListOutbounds",
Handler: _HandlerService_ListOutbounds_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "app/proxyman/command/command.proto", Metadata: "app/proxyman/command/command.proto",

View File

@@ -9,11 +9,13 @@ import (
"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"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/stats" "github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"google.golang.org/protobuf/proto"
) )
func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) { func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {
@@ -42,10 +44,12 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
} }
type AlwaysOnInboundHandler struct { type AlwaysOnInboundHandler struct {
proxy proxy.Inbound proxyConfig interface{}
workers []worker receiverConfig *proxyman.ReceiverConfig
mux *mux.Server proxy proxy.Inbound
tag string workers []worker
mux *mux.Server
tag string
} }
func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) { func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
@@ -59,9 +63,11 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
} }
h := &AlwaysOnInboundHandler{ h := &AlwaysOnInboundHandler{
proxy: p, receiverConfig: receiverConfig,
mux: mux.NewServer(ctx), proxyConfig: proxyConfig,
tag: tag, proxy: p,
mux: mux.NewServer(ctx),
tag: tag,
} }
uplinkCounter, downlinkCounter := getStatCounter(core.MustFromContext(ctx), tag) uplinkCounter, downlinkCounter := getStatCounter(core.MustFromContext(ctx), tag)
@@ -187,3 +193,16 @@ func (h *AlwaysOnInboundHandler) Tag() string {
func (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound { func (h *AlwaysOnInboundHandler) GetInbound() proxy.Inbound {
return h.proxy return h.proxy
} }
// ReceiverSettings implements inbound.Handler.
func (h *AlwaysOnInboundHandler) ReceiverSettings() *serial.TypedMessage {
return serial.ToTypedMessage(h.receiverConfig)
}
// ProxySettings implements inbound.Handler.
func (h *AlwaysOnInboundHandler) ProxySettings() *serial.TypedMessage {
if v, ok := h.proxyConfig.(proto.Message); ok {
return serial.ToTypedMessage(v)
}
return nil
}

View File

@@ -10,10 +10,12 @@ import (
"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"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"google.golang.org/protobuf/proto"
) )
type DynamicInboundHandler struct { type DynamicInboundHandler struct {
@@ -205,3 +207,16 @@ func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port,
func (h *DynamicInboundHandler) Tag() string { func (h *DynamicInboundHandler) Tag() string {
return h.tag return h.tag
} }
// ReceiverSettings implements inbound.Handler.
func (h *DynamicInboundHandler) ReceiverSettings() *serial.TypedMessage {
return serial.ToTypedMessage(h.receiverConfig)
}
// ProxySettings implements inbound.Handler.
func (h *DynamicInboundHandler) ProxySettings() *serial.TypedMessage {
if v, ok := h.proxyConfig.(proto.Message); ok {
return serial.ToTypedMessage(v)
}
return nil
}

View File

@@ -17,7 +17,7 @@ import (
// Manager manages all inbound handlers. // Manager manages all inbound handlers.
type Manager struct { type Manager struct {
access sync.RWMutex access sync.RWMutex
untaggedHandler []inbound.Handler untaggedHandlers []inbound.Handler
taggedHandlers map[string]inbound.Handler taggedHandlers map[string]inbound.Handler
running bool running bool
} }
@@ -47,7 +47,7 @@ func (m *Manager) AddHandler(ctx context.Context, handler inbound.Handler) error
} }
m.taggedHandlers[tag] = handler m.taggedHandlers[tag] = handler
} else { } else {
m.untaggedHandler = append(m.untaggedHandler, handler) m.untaggedHandlers = append(m.untaggedHandlers, handler)
} }
if m.running { if m.running {
@@ -89,6 +89,21 @@ func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
return common.ErrNoClue return common.ErrNoClue
} }
// ListHandlers implements inbound.Manager.
func (m *Manager) ListHandlers(ctx context.Context) []inbound.Handler {
m.access.RLock()
defer m.access.RUnlock()
response := make([]inbound.Handler, len(m.untaggedHandlers))
copy(response, m.untaggedHandlers)
for _, v := range m.taggedHandlers {
response = append(response, v)
}
return response
}
// Start implements common.Runnable. // Start implements common.Runnable.
func (m *Manager) Start() error { func (m *Manager) Start() error {
m.access.Lock() m.access.Lock()
@@ -102,7 +117,7 @@ func (m *Manager) Start() error {
} }
} }
for _, handler := range m.untaggedHandler { for _, handler := range m.untaggedHandlers {
if err := handler.Start(); err != nil { if err := handler.Start(); err != nil {
return err return err
} }
@@ -123,7 +138,7 @@ func (m *Manager) Close() error {
errs = append(errs, err) errs = append(errs, err)
} }
} }
for _, handler := range m.untaggedHandler { for _, handler := range m.untaggedHandlers {
if err := handler.Close(); err != nil { if err := handler.Close(); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }

View File

@@ -161,6 +161,7 @@ type udpConn struct {
uplink stats.Counter uplink stats.Counter
downlink stats.Counter downlink stats.Counter
inactive bool inactive bool
cancel context.CancelFunc
} }
func (c *udpConn) setInactive() { func (c *udpConn) setInactive() {
@@ -203,6 +204,9 @@ func (c *udpConn) Write(buf []byte) (int, error) {
} }
func (c *udpConn) Close() error { func (c *udpConn) Close() error {
if c.cancel != nil {
c.cancel()
}
common.Must(c.done.Close()) common.Must(c.done.Close())
common.Must(common.Close(c.writer)) common.Must(common.Close(c.writer))
return nil return nil
@@ -259,6 +263,7 @@ func (w *udpWorker) getConnection(id connID) (*udpConn, bool) {
defer w.Unlock() defer w.Unlock()
if conn, found := w.activeConn[id]; found && !conn.done.Done() { if conn, found := w.activeConn[id]; found && !conn.done.Done() {
conn.updateActivity()
return conn, true return conn, true
} }
@@ -306,7 +311,8 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
common.Must(w.checker.Start()) common.Must(w.checker.Start())
go func() { go func() {
ctx := w.ctx ctx, cancel := context.WithCancel(w.ctx)
conn.cancel = cancel
sid := session.NewID() sid := session.NewID()
ctx = c.ContextWithID(ctx, sid) ctx = c.ContextWithID(ctx, sid)

View File

@@ -16,6 +16,7 @@ import (
"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"
"github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/serial"
"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"
"github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/outbound"
@@ -27,6 +28,7 @@ import (
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/tls"
"github.com/xtls/xray-core/transport/pipe" "github.com/xtls/xray-core/transport/pipe"
"google.golang.org/protobuf/proto"
) )
func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) { func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {
@@ -59,6 +61,7 @@ type Handler struct {
tag string tag string
senderSettings *proxyman.SenderConfig senderSettings *proxyman.SenderConfig
streamSettings *internet.MemoryStreamConfig streamSettings *internet.MemoryStreamConfig
proxyConfig proto.Message
proxy proxy.Outbound proxy proxy.Outbound
outboundManager outbound.Manager outboundManager outbound.Manager
mux *mux.ClientManager mux *mux.ClientManager
@@ -101,6 +104,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
if err != nil { if err != nil {
return nil, err return nil, err
} }
h.proxyConfig = proxyConfig
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig) rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
if err != nil { if err != nil {
@@ -241,7 +245,9 @@ func (h *Handler) DestIpAddress() net.IP {
// Dial implements internet.Dialer. // Dial implements internet.Dialer.
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) { func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if h.senderSettings != nil { if h.senderSettings != nil {
if h.senderSettings.ProxySettings.HasTag() { if h.senderSettings.ProxySettings.HasTag() {
tag := h.senderSettings.ProxySettings.Tag tag := h.senderSettings.ProxySettings.Tag
handler := h.outboundManager.GetHandler(tag) handler := h.outboundManager.GetHandler(tag)
if handler != nil { if handler != nil {
@@ -270,22 +276,44 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
} }
if h.senderSettings.Via != nil { if h.senderSettings.Via != nil {
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
if h.senderSettings.ViaCidr == "" { var domain string
if h.senderSettings.Via.AsAddress().Family().IsDomain() && h.senderSettings.Via.AsAddress().Domain() == "origin" { addr := h.senderSettings.Via.AsAddress()
if inbound := session.InboundFromContext(ctx); inbound != nil { domain = h.senderSettings.Via.GetDomain()
switch {
case h.senderSettings.ViaCidr != "":
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
case domain == "origin":
if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.Conn != nil {
origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String()) origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String())
if err == nil { if err == nil {
ob.Gateway = net.ParseAddress(origin) ob.Gateway = net.ParseAddress(origin)
errors.LogDebug(ctx, "use receive package ip as snedthrough: ", origin)
} }
} }
} else {
ob.Gateway = h.senderSettings.Via.AsAddress()
} }
} else { //Get a random address. case domain == "srcip":
ob.Gateway = ParseRandomIPv6(h.senderSettings.Via.AsAddress(), h.senderSettings.ViaCidr) if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.Conn != nil {
clientaddr, _, err := net.SplitHostPort(inbound.Conn.RemoteAddr().String())
if err == nil {
ob.Gateway = net.ParseAddress(clientaddr)
errors.LogDebug(ctx, "use client src ip as snedthrough: ", clientaddr)
}
}
}
//case addr.Family().IsDomain():
default:
ob.Gateway = addr
} }
} }
} }
@@ -325,23 +353,35 @@ func (h *Handler) Start() error {
// Close implements common.Closable. // Close implements common.Closable.
func (h *Handler) Close() error { func (h *Handler) Close() error {
common.Close(h.mux) common.Close(h.mux)
common.Close(h.proxy)
return nil return nil
} }
func ParseRandomIPv6(address net.Address, prefix string) net.Address { // SenderSettings implements outbound.Handler.
_, network, _ := gonet.ParseCIDR(address.IP().String() + "/" + prefix) func (h *Handler) SenderSettings() *serial.TypedMessage {
return serial.ToTypedMessage(h.senderSettings)
maskSize, totalBits := network.Mask.Size() }
subnetSize := big.NewInt(1).Lsh(big.NewInt(1), uint(totalBits-maskSize))
// ProxySettings implements outbound.Handler.
// random func (h *Handler) ProxySettings() *serial.TypedMessage {
randomBigInt, _ := rand.Int(rand.Reader, subnetSize) return serial.ToTypedMessage(h.proxyConfig)
}
startIPBigInt := big.NewInt(0).SetBytes(network.IP.To16())
randomIPBigInt := big.NewInt(0).Add(startIPBigInt, randomBigInt) func ParseRandomIP(addr net.Address, prefix string) net.Address {
randomIPBytes := randomIPBigInt.Bytes() _, ipnet, _ := gonet.ParseCIDR(addr.IP().String() + "/" + prefix)
randomIPBytes = append(make([]byte, 16-len(randomIPBytes)), randomIPBytes...)
ones, bits := ipnet.Mask.Size()
return net.ParseAddress(gonet.IP(randomIPBytes).String()) subnetSize := new(big.Int).Lsh(big.NewInt(1), uint(bits-ones))
rnd, _ := rand.Int(rand.Reader, subnetSize)
startInt := new(big.Int).SetBytes(ipnet.IP)
rndInt := new(big.Int).Add(startInt, rnd)
rndBytes := rndInt.Bytes()
padded := make([]byte, len(ipnet.IP))
copy(padded[len(padded)-len(rndBytes):], rndBytes)
return net.ParseAddress(gonet.IP(padded).String())
} }

View File

@@ -145,6 +145,21 @@ func (m *Manager) RemoveHandler(ctx context.Context, tag string) error {
return nil return nil
} }
// ListHandlers implements outbound.Manager.
func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
m.access.RLock()
defer m.access.RUnlock()
response := make([]outbound.Handler, len(m.untaggedHandlers))
copy(response, m.untaggedHandlers)
for _, v := range m.taggedHandler {
response = append(response, v)
}
return response
}
// Select implements outbound.HandlerSelector. // Select implements outbound.HandlerSelector.
func (m *Manager) Select(selectors []string) []string { func (m *Manager) Select(selectors []string) []string {

View File

@@ -9,6 +9,7 @@ import (
func (c *Control) FillInRandom() { func (c *Control) FillInRandom() {
randomLength := dice.Roll(64) randomLength := dice.Roll(64)
randomLength++
c.Random = make([]byte, randomLength) c.Random = make([]byte, randomLength)
io.ReadFull(rand.Reader, c.Random) io.ReadFull(rand.Reader, c.Random)
} }

View File

@@ -10,6 +10,7 @@ import (
"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"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/outbound"
@@ -111,6 +112,16 @@ func (o *Outbound) Close() error {
return nil return nil
} }
// SenderSettings implements outbound.Handler.
func (o *Outbound) SenderSettings() *serial.TypedMessage {
return nil
}
// ProxySettings implements outbound.Handler.
func (o *Outbound) ProxySettings() *serial.TypedMessage {
return nil
}
type StaticMuxPicker struct { type StaticMuxPicker struct {
access sync.Mutex access sync.Mutex
workers []*PortalWorker workers []*PortalWorker
@@ -159,7 +170,7 @@ func (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) {
if w.draining { if w.draining {
continue continue
} }
if w.client.Closed() { if w.IsFull() {
continue continue
} }
if w.client.ActiveConnections() < minConn { if w.client.ActiveConnections() < minConn {
@@ -200,6 +211,7 @@ type PortalWorker struct {
writer buf.Writer writer buf.Writer
reader buf.Reader reader buf.Reader
draining bool draining bool
counter uint32
} }
func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) { func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
@@ -233,7 +245,7 @@ func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
} }
func (w *PortalWorker) heartbeat() error { func (w *PortalWorker) heartbeat() error {
if w.client.Closed() { if w.Closed() {
return errors.New("client worker stopped") return errors.New("client worker stopped")
} }
@@ -249,16 +261,21 @@ func (w *PortalWorker) heartbeat() error {
msg.State = Control_DRAIN msg.State = Control_DRAIN
defer func() { defer func() {
w.client.GetTimer().Reset(time.Second * 16)
common.Close(w.writer) common.Close(w.writer)
common.Interrupt(w.reader) common.Interrupt(w.reader)
w.writer = nil w.writer = nil
}() }()
} }
b, err := proto.Marshal(msg) w.counter = (w.counter + 1) % 5
common.Must(err) if w.draining || w.counter == 1 {
mb := buf.MergeBytes(nil, b) b, err := proto.Marshal(msg)
return w.writer.WriteMultiBuffer(mb) common.Must(err)
mb := buf.MergeBytes(nil, b)
return w.writer.WriteMultiBuffer(mb)
}
return nil
} }
func (w *PortalWorker) IsFull() bool { func (w *PortalWorker) IsFull() bool {

View File

@@ -119,7 +119,7 @@ type MultiGeoIPMatcher struct {
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) { func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
var matchers []*GeoIPMatcher var matchers []*GeoIPMatcher
for _, geoip := range geoips { for _, geoip := range geoips {
matcher, err := globalGeoIPContainer.Add(geoip) matcher, err := GlobalGeoIPContainer.Add(geoip)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -115,4 +115,30 @@ func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
return m, nil return m, nil
} }
var globalGeoIPContainer GeoIPMatcherContainer var GlobalGeoIPContainer GeoIPMatcherContainer
func MatchIPs(matchers []*GeoIPMatcher, ips []net.IP, reverse bool) []net.IP {
if len(matchers) == 0 {
panic("GeoIP matchers should not be empty to avoid ambiguity")
}
newIPs := make([]net.IP, 0, len(ips))
var isFound bool
for _, ip := range ips {
isFound = false
for _, matcher := range matchers {
if matcher.Match(ip) {
isFound = true
break
}
}
if isFound && !reverse {
newIPs = append(newIPs, ip)
continue
}
if !isFound && reverse {
newIPs = append(newIPs, ip)
continue
}
}
return newIPs
}

View File

@@ -12,6 +12,8 @@ import (
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
feature_stats "github.com/xtls/xray-core/features/stats" feature_stats "github.com/xtls/xray-core/features/stats"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
) )
// statsServer is an implementation of StatsService. // statsServer is an implementation of StatsService.
@@ -30,7 +32,7 @@ func NewStatsServer(manager feature_stats.Manager) StatsServiceServer {
func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
c := s.stats.GetCounter(request.Name) c := s.stats.GetCounter(request.Name)
if c == nil { if c == nil {
return nil, errors.New(request.Name, " not found.") return nil, status.Error(codes.NotFound, request.Name+" not found.")
} }
var value int64 var value int64
if request.Reset_ { if request.Reset_ {
@@ -49,7 +51,7 @@ func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*
func (s *statsServer) GetStatsOnline(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { func (s *statsServer) GetStatsOnline(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
c := s.stats.GetOnlineMap(request.Name) c := s.stats.GetOnlineMap(request.Name)
if c == nil { if c == nil {
return nil, errors.New(request.Name, " not found.") return nil, status.Error(codes.NotFound, request.Name+" not found.")
} }
value := int64(c.Count()) value := int64(c.Count())
return &GetStatsResponse{ return &GetStatsResponse{
@@ -64,7 +66,7 @@ func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStat
c := s.stats.GetOnlineMap(request.Name) c := s.stats.GetOnlineMap(request.Name)
if c == nil { if c == nil {
return nil, errors.New(request.Name, " not found.") return nil, status.Error(codes.NotFound, request.Name+" not found.")
} }
ips := make(map[string]int64) ips := make(map[string]int64)

152
app/version/config.pb.go Normal file
View File

@@ -0,0 +1,152 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc v5.28.2
// source: app/version/config.proto
package version
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
CoreVersion string `protobuf:"bytes,1,opt,name=core_version,json=coreVersion,proto3" json:"core_version,omitempty"`
MinVersion string `protobuf:"bytes,2,opt,name=min_version,json=minVersion,proto3" json:"min_version,omitempty"`
MaxVersion string `protobuf:"bytes,3,opt,name=max_version,json=maxVersion,proto3" json:"max_version,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
mi := &file_app_version_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_app_version_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_app_version_config_proto_rawDescGZIP(), []int{0}
}
func (x *Config) GetCoreVersion() string {
if x != nil {
return x.CoreVersion
}
return ""
}
func (x *Config) GetMinVersion() string {
if x != nil {
return x.MinVersion
}
return ""
}
func (x *Config) GetMaxVersion() string {
if x != nil {
return x.MaxVersion
}
return ""
}
var File_app_version_config_proto protoreflect.FileDescriptor
var file_app_version_config_proto_rawDesc = []byte{
0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2f, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x06,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f,
0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x6e,
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x6d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61,
0x78, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14, 0x63,
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x50, 0x01, 0x5a, 0x25, 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, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0xaa, 0x02, 0x10, 0x58,
0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_app_version_config_proto_rawDescOnce sync.Once
file_app_version_config_proto_rawDescData = file_app_version_config_proto_rawDesc
)
func file_app_version_config_proto_rawDescGZIP() []byte {
file_app_version_config_proto_rawDescOnce.Do(func() {
file_app_version_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_version_config_proto_rawDescData)
})
return file_app_version_config_proto_rawDescData
}
var file_app_version_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_app_version_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.app.version.Config
}
var file_app_version_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_app_version_config_proto_init() }
func file_app_version_config_proto_init() {
if File_app_version_config_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_app_version_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_app_version_config_proto_goTypes,
DependencyIndexes: file_app_version_config_proto_depIdxs,
MessageInfos: file_app_version_config_proto_msgTypes,
}.Build()
File_app_version_config_proto = out.File
file_app_version_config_proto_rawDesc = nil
file_app_version_config_proto_goTypes = nil
file_app_version_config_proto_depIdxs = nil
}

14
app/version/config.proto Normal file
View File

@@ -0,0 +1,14 @@
syntax = "proto3";
package xray.app.version;
option csharp_namespace = "Xray.App.Version";
option go_package = "github.com/xtls/xray-core/app/version";
option java_package = "com.xray.app.version";
option java_multiple_files = true;
message Config {
string core_version = 1;
string min_version = 2;
string max_version = 3;
}

77
app/version/version.go Normal file
View File

@@ -0,0 +1,77 @@
package version
import (
"context"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"strconv"
"strings"
)
type Version struct {
config *Config
ctx context.Context
}
func New(ctx context.Context, config *Config) (*Version, error) {
if config.MinVersion != "" {
result, err := compareVersions(config.MinVersion, config.CoreVersion)
if err != nil {
return nil, err
}
if result > 0 {
return nil, errors.New("this config must be run on version ", config.MinVersion, " or higher")
}
}
if config.MaxVersion != "" {
result, err := compareVersions(config.MaxVersion, config.CoreVersion)
if err != nil {
return nil, err
}
if result < 0 {
return nil, errors.New("this config should be run on version ", config.MaxVersion, " or lower")
}
}
return &Version{config: config, ctx: ctx}, nil
}
func compareVersions(v1, v2 string) (int, error) {
// Split version strings into components
v1Parts := strings.Split(v1, ".")
v2Parts := strings.Split(v2, ".")
// Pad shorter versions with zeros
for len(v1Parts) < len(v2Parts) {
v1Parts = append(v1Parts, "0")
}
for len(v2Parts) < len(v1Parts) {
v2Parts = append(v2Parts, "0")
}
// Compare each part
for i := 0; i < len(v1Parts); i++ {
// Convert parts to integers
n1, err := strconv.Atoi(v1Parts[i])
if err != nil {
return 0, errors.New("invalid version component ", v1Parts[i], " in ", v1)
}
n2, err := strconv.Atoi(v2Parts[i])
if err != nil {
return 0, errors.New("invalid version component ", v2Parts[i], " in ", v2)
}
if n1 < n2 {
return -1, nil // v1 < v2
}
if n1 > n2 {
return 1, nil // v1 > v2
}
}
return 0, nil // v1 == v2
}
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return New(ctx, config.(*Config))
}))
}

View File

@@ -13,8 +13,19 @@ const (
Size = 8192 Size = 8192
) )
var zero = [Size * 10]byte{0}
var pool = bytespool.GetPool(Size) var pool = bytespool.GetPool(Size)
// ownership represents the data owner of the buffer.
type ownership uint8
const (
managed ownership = iota
unmanaged
bytespools
)
// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles // Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
// the buffer into an internal buffer pool, in order to recreate a buffer more // the buffer into an internal buffer pool, in order to recreate a buffer more
// quickly. // quickly.
@@ -22,11 +33,11 @@ type Buffer struct {
v []byte v []byte
start int32 start int32
end int32 end int32
unmanaged bool ownership ownership
UDP *net.Destination UDP *net.Destination
} }
// New creates a Buffer with 0 length and 8K capacity. // New creates a Buffer with 0 length and 8K capacity, managed.
func New() *Buffer { func New() *Buffer {
buf := pool.Get().([]byte) buf := pool.Get().([]byte)
if cap(buf) >= Size { if cap(buf) >= Size {
@@ -40,7 +51,7 @@ func New() *Buffer {
} }
} }
// NewExisted creates a managed, standard size Buffer with an existed bytearray // NewExisted creates a standard size Buffer with an existed bytearray, managed.
func NewExisted(b []byte) *Buffer { func NewExisted(b []byte) *Buffer {
if cap(b) < Size { if cap(b) < Size {
panic("Invalid buffer") panic("Invalid buffer")
@@ -57,16 +68,16 @@ func NewExisted(b []byte) *Buffer {
} }
} }
// FromBytes creates a Buffer with an existed bytearray // FromBytes creates a Buffer with an existed bytearray, unmanaged.
func FromBytes(b []byte) *Buffer { func FromBytes(b []byte) *Buffer {
return &Buffer{ return &Buffer{
v: b, v: b,
end: int32(len(b)), end: int32(len(b)),
unmanaged: true, ownership: unmanaged,
} }
} }
// StackNew creates a new Buffer object on stack. // StackNew creates a new Buffer object on stack, managed.
// This method is for buffers that is released in the same function. // This method is for buffers that is released in the same function.
func StackNew() Buffer { func StackNew() Buffer {
buf := pool.Get().([]byte) buf := pool.Get().([]byte)
@@ -81,9 +92,17 @@ func StackNew() Buffer {
} }
} }
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's.
func NewWithSize(size int32) *Buffer {
return &Buffer{
v: bytespool.Alloc(size),
ownership: bytespools,
}
}
// Release recycles the buffer into an internal buffer pool. // Release recycles the buffer into an internal buffer pool.
func (b *Buffer) Release() { func (b *Buffer) Release() {
if b == nil || b.v == nil || b.unmanaged { if b == nil || b.v == nil || b.ownership == unmanaged {
return return
} }
@@ -91,8 +110,13 @@ func (b *Buffer) Release() {
b.v = nil b.v = nil
b.Clear() b.Clear()
if cap(p) == Size { switch b.ownership {
pool.Put(p) case managed:
if cap(p) == Size {
pool.Put(p)
}
case bytespools:
bytespool.Free(p)
} }
b.UDP = nil b.UDP = nil
} }
@@ -128,6 +152,7 @@ func (b *Buffer) Extend(n int32) []byte {
} }
ext := b.v[b.end:end] ext := b.v[b.end:end]
b.end = end b.end = end
copy(ext, zero[:])
return ext return ext
} }
@@ -176,6 +201,7 @@ func (b *Buffer) Check() {
// Resize cuts the buffer at the given position. // Resize cuts the buffer at the given position.
func (b *Buffer) Resize(from, to int32) { func (b *Buffer) Resize(from, to int32) {
oldEnd := b.end
if from < 0 { if from < 0 {
from += b.Len() from += b.Len()
} }
@@ -188,6 +214,9 @@ func (b *Buffer) Resize(from, to int32) {
b.end = b.start + to b.end = b.start + to
b.start += from b.start += from
b.Check() b.Check()
if b.end > oldEnd {
copy(b.v[oldEnd:b.end], zero[:])
}
} }
// Advance cuts the buffer at the given position. // Advance cuts the buffer at the given position.
@@ -215,13 +244,6 @@ func (b *Buffer) Cap() int32 {
return int32(len(b.v)) return int32(len(b.v))
} }
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size.
func NewWithSize(size int32) *Buffer {
return &Buffer{
v: bytespool.Alloc(size),
}
}
// IsEmpty returns true if the buffer is empty. // IsEmpty returns true if the buffer is empty.
func (b *Buffer) IsEmpty() bool { func (b *Buffer) IsEmpty() bool {
return b.Len() == 0 return b.Len() == 0

View File

@@ -1,6 +1,7 @@
package errors package errors
import ( import (
"errors"
"strings" "strings"
) )
@@ -36,12 +37,12 @@ func AllEqual(expected error, actual error) bool {
return false return false
} }
for _, err := range errs { for _, err := range errs {
if err != expected { if !errors.Is(err, expected) {
return false return false
} }
} }
return true return true
default: default:
return errs == expected return errors.Is(errs, expected)
} }
} }

View File

@@ -173,6 +173,7 @@ type ClientWorker struct {
sessionManager *SessionManager sessionManager *SessionManager
link transport.Link link transport.Link
done *done.Instance done *done.Instance
timer *time.Ticker
strategy ClientStrategy strategy ClientStrategy
} }
@@ -187,6 +188,7 @@ func NewClientWorker(stream transport.Link, s ClientStrategy) (*ClientWorker, er
sessionManager: NewSessionManager(), sessionManager: NewSessionManager(),
link: stream, link: stream,
done: done.New(), done: done.New(),
timer: time.NewTicker(time.Second * 16),
strategy: s, strategy: s,
} }
@@ -209,9 +211,12 @@ func (m *ClientWorker) Closed() bool {
return m.done.Done() return m.done.Done()
} }
func (m *ClientWorker) GetTimer() *time.Ticker {
return m.timer
}
func (m *ClientWorker) monitor() { func (m *ClientWorker) monitor() {
timer := time.NewTicker(time.Second * 16) defer m.timer.Stop()
defer timer.Stop()
for { for {
select { select {
@@ -220,7 +225,7 @@ func (m *ClientWorker) monitor() {
common.Close(m.link.Writer) common.Close(m.link.Writer)
common.Interrupt(m.link.Reader) common.Interrupt(m.link.Reader)
return return
case <-timer.C: case <-m.timer.C:
size := m.sessionManager.Size() size := m.sessionManager.Size()
if size == 0 && m.sessionManager.CloseIfNoSession() { if size == 0 && m.sessionManager.CloseIfNoSession() {
common.Must(m.done.Close()) common.Must(m.done.Close())
@@ -276,6 +281,8 @@ func (m *ClientWorker) IsClosing() bool {
return false return false
} }
// IsFull returns true if this ClientWorker is unable to accept more connections.
// it might be because it is closing, or the number of connections has reached the limit.
func (m *ClientWorker) IsFull() bool { func (m *ClientWorker) IsFull() bool {
if m.IsClosing() || m.Closed() { if m.IsClosing() || m.Closed() {
return true return true
@@ -289,12 +296,12 @@ func (m *ClientWorker) IsFull() bool {
} }
func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool { func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool {
if m.IsFull() || m.Closed() { if m.IsFull() {
return false return false
} }
sm := m.sessionManager sm := m.sessionManager
s := sm.Allocate() s := sm.Allocate(&m.strategy)
if s == nil { if s == nil {
return false return false
} }

View File

@@ -201,11 +201,12 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata,
transferType: protocol.TransferTypePacket, transferType: protocol.TransferTypePacket,
XUDP: x, XUDP: x,
} }
go handle(ctx, x.Mux, w.link.Writer)
x.Status = Active x.Status = Active
if !w.sessionManager.Add(x.Mux) { if !w.sessionManager.Add(x.Mux) {
x.Mux.Close(false) x.Mux.Close(false)
return errors.New("failed to add new session")
} }
go handle(ctx, x.Mux, w.link.Writer)
return nil return nil
} }
@@ -226,18 +227,23 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata,
if meta.Target.Network == net.Network_UDP { if meta.Target.Network == net.Network_UDP {
s.transferType = protocol.TransferTypePacket s.transferType = protocol.TransferTypePacket
} }
w.sessionManager.Add(s) if !w.sessionManager.Add(s) {
s.Close(false)
return errors.New("failed to add new session")
}
go handle(ctx, s, w.link.Writer) go handle(ctx, s, w.link.Writer)
if !meta.Option.Has(OptionData) { if !meta.Option.Has(OptionData) {
return nil return nil
} }
rr := s.NewReader(reader, &meta.Target) rr := s.NewReader(reader, &meta.Target)
if err := buf.Copy(rr, s.output); err != nil { err = buf.Copy(rr, s.output)
buf.Copy(rr, buf.Discard)
return s.Close(false) if err != nil && buf.IsWriteError(err) {
s.Close(false)
return buf.Copy(rr, buf.Discard)
} }
return nil return err
} }
func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error { func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error {
@@ -304,10 +310,11 @@ func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedRead
} }
func (w *ServerWorker) run(ctx context.Context) { func (w *ServerWorker) run(ctx context.Context) {
input := w.link.Reader reader := &buf.BufferedReader{Reader: w.link.Reader}
reader := &buf.BufferedReader{Reader: input}
defer w.sessionManager.Close() defer w.sessionManager.Close()
defer common.Close(w.link.Writer)
defer common.Interrupt(w.link.Reader)
for { for {
select { select {
@@ -318,7 +325,6 @@ func (w *ServerWorker) run(ctx context.Context) {
if err != nil { if err != nil {
if errors.Cause(err) != io.EOF { if errors.Cause(err) != io.EOF {
errors.LogInfoInner(ctx, err, "unexpected EOF") errors.LogInfoInner(ctx, err, "unexpected EOF")
common.Interrupt(input)
} }
return return
} }

View File

@@ -50,11 +50,14 @@ func (m *SessionManager) Count() int {
return int(m.count) return int(m.count)
} }
func (m *SessionManager) Allocate() *Session { func (m *SessionManager) Allocate(Strategy *ClientStrategy) *Session {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
MaxConcurrency := int(Strategy.MaxConcurrency)
MaxConnection := uint16(Strategy.MaxConnection)
if m.closed { if m.closed || (MaxConcurrency > 0 && len(m.sessions) >= MaxConcurrency) || (MaxConnection > 0 && m.count >= MaxConnection) {
return nil return nil
} }

View File

@@ -9,7 +9,7 @@ import (
func TestSessionManagerAdd(t *testing.T) { func TestSessionManagerAdd(t *testing.T) {
m := NewSessionManager() m := NewSessionManager()
s := m.Allocate() s := m.Allocate(&ClientStrategy{})
if s.ID != 1 { if s.ID != 1 {
t.Error("id: ", s.ID) t.Error("id: ", s.ID)
} }
@@ -17,7 +17,7 @@ func TestSessionManagerAdd(t *testing.T) {
t.Error("size: ", m.Size()) t.Error("size: ", m.Size())
} }
s = m.Allocate() s = m.Allocate(&ClientStrategy{})
if s.ID != 2 { if s.ID != 2 {
t.Error("id: ", s.ID) t.Error("id: ", s.ID)
} }
@@ -39,7 +39,7 @@ func TestSessionManagerAdd(t *testing.T) {
func TestSessionManagerClose(t *testing.T) { func TestSessionManagerClose(t *testing.T) {
m := NewSessionManager() m := NewSessionManager()
s := m.Allocate() s := m.Allocate(&ClientStrategy{})
if m.CloseIfNoSession() { if m.CloseIfNoSession() {
t.Error("able to close") t.Error("able to close")

View File

@@ -1 +1,7 @@
package protocol // import "github.com/xtls/xray-core/common/protocol" package protocol // import "github.com/xtls/xray-core/common/protocol"
import (
"errors"
)
var ErrProtoNeedMoreData = errors.New("protocol matches, but need more data to complete sniffing")

View File

@@ -1,7 +1,6 @@
package quic package quic
import ( import (
"context"
"crypto" "crypto"
"crypto/aes" "crypto/aes"
"crypto/tls" "crypto/tls"
@@ -11,8 +10,8 @@ import (
"github.com/quic-go/quic-go/quicvarint" "github.com/quic-go/quic-go/quicvarint"
"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/bytespool"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
ptls "github.com/xtls/xray-core/common/protocol/tls" ptls "github.com/xtls/xray-core/common/protocol/tls"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
) )
@@ -47,22 +46,17 @@ var (
errNotQuicInitial = errors.New("not initial packet") errNotQuicInitial = errors.New("not initial packet")
) )
func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) { func SniffQUIC(b []byte) (*SniffHeader, error) {
// In extremely rare cases, this sniffer may cause slice error if len(b) == 0 {
// and we set recover() here to prevent crash. return nil, common.ErrNoClue
// TODO: Thoroughly fix this panic }
defer func() {
if r := recover(); r != nil {
errors.LogError(context.Background(), "Failed to sniff QUIC: ", r)
resultReturn = nil
errorReturn = common.ErrNoClue
}
}()
// Crypto data separated across packets // Crypto data separated across packets
cryptoLen := 0 cryptoLen := int32(0)
cryptoData := bytespool.Alloc(int32(len(b))) cryptoDataBuf := buf.NewWithSize(32767)
defer bytespool.Free(cryptoData) defer cryptoDataBuf.Release()
cache := buf.New()
defer cache.Release()
// Parse QUIC packets // Parse QUIC packets
for len(b) > 0 { for len(b) > 0 {
@@ -105,19 +99,25 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
return nil, errNotQuic return nil, errNotQuic
} }
tokenLen, err := quicvarint.Read(buffer) if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
if err != nil || tokenLen > uint64(len(b)) { tokenLen, err := quicvarint.Read(buffer)
return nil, errNotQuic if err != nil || tokenLen > uint64(len(b)) {
} return nil, errNotQuic
}
if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil { if _, err = buffer.ReadBytes(int32(tokenLen)); err != nil {
return nil, errNotQuic return nil, errNotQuic
}
} }
packetLen, err := quicvarint.Read(buffer) packetLen, err := quicvarint.Read(buffer)
if err != nil { if err != nil {
return nil, errNotQuic return nil, errNotQuic
} }
// packetLen is impossible to be shorter than this
if packetLen < 4 {
return nil, errNotQuic
}
hdrLen := len(b) - int(buffer.Len()) hdrLen := len(b) - int(buffer.Len())
if len(b) < hdrLen+int(packetLen) { if len(b) < hdrLen+int(packetLen) {
@@ -130,9 +130,6 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
continue continue
} }
origPNBytes := make([]byte, 4)
copy(origPNBytes, b[hdrLen:hdrLen+4])
var salt []byte var salt []byte
if versionNumber == version1 { if versionNumber == version1 {
salt = quicSalt salt = quicSalt
@@ -147,44 +144,34 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
return nil, err return nil, err
} }
cache := buf.New() cache.Clear()
defer cache.Release()
mask := cache.Extend(int32(block.BlockSize())) mask := cache.Extend(int32(block.BlockSize()))
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16]) block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
b[0] ^= mask[0] & 0xf b[0] ^= mask[0] & 0xf
for i := range b[hdrLen : hdrLen+4] { packetNumberLength := int(b[0]&0x3 + 1)
for i := range packetNumberLength {
b[hdrLen+i] ^= mask[i+1] b[hdrLen+i] ^= mask[i+1]
} }
packetNumberLength := b[0]&0x3 + 1
if packetNumberLength != 1 {
return nil, errNotQuicInitial
}
var packetNumber uint32
{
n, err := buffer.ReadByte()
if err != nil {
return nil, err
}
packetNumber = uint32(n)
}
extHdrLen := hdrLen + int(packetNumberLength)
copy(b[extHdrLen:hdrLen+4], origPNBytes[packetNumberLength:])
data := b[extHdrLen : int(packetLen)+hdrLen]
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16) key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12) iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
cipher := AEADAESGCMTLS13(key, iv) cipher := AEADAESGCMTLS13(key, iv)
nonce := cache.Extend(int32(cipher.NonceSize())) nonce := cache.Extend(int32(cipher.NonceSize()))
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber)) _, err = buffer.Read(nonce[len(nonce)-packetNumberLength:])
if err != nil {
return nil, err
}
extHdrLen := hdrLen + packetNumberLength
data := b[extHdrLen : int(packetLen)+hdrLen]
decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen]) decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen])
if err != nil { if err != nil {
return nil, err return nil, err
} }
buffer = buf.FromBytes(decrypted) buffer = buf.FromBytes(decrypted)
for i := 0; !buffer.IsEmpty(); i++ { for !buffer.IsEmpty() {
frameType := byte(0x0) // Default to PADDING frame frameType, _ := buffer.ReadByte()
for frameType == 0x0 && !buffer.IsEmpty() { for frameType == 0x0 && !buffer.IsEmpty() {
frameType, _ = buffer.ReadByte() frameType, _ = buffer.ReadByte()
} }
@@ -233,16 +220,15 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
if err != nil || length > uint64(buffer.Len()) { if err != nil || length > uint64(buffer.Len()) {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
if cryptoLen < int(offset+length) { currentCryptoLen := int32(offset + length)
cryptoLen = int(offset + length) if cryptoLen < currentCryptoLen {
if len(cryptoData) < cryptoLen { if cryptoDataBuf.Cap() < currentCryptoLen {
newCryptoData := bytespool.Alloc(int32(cryptoLen)) return nil, io.ErrShortBuffer
copy(newCryptoData, cryptoData)
bytespool.Free(cryptoData)
cryptoData = newCryptoData
} }
cryptoDataBuf.Extend(currentCryptoLen - cryptoLen)
cryptoLen = currentCryptoLen
} }
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data if _, err := buffer.Read(cryptoDataBuf.BytesRange(int32(offset), currentCryptoLen)); err != nil { // Field: Crypto Data
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
@@ -267,7 +253,7 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
} }
tlsHdr := &ptls.SniffHeader{} tlsHdr := &ptls.SniffHeader{}
err = ptls.ReadClientHello(cryptoData[:cryptoLen], tlsHdr) err = ptls.ReadClientHello(cryptoDataBuf.BytesRange(0, cryptoLen), tlsHdr)
if err != nil { if err != nil {
// The crypto data may have not been fully recovered in current packets, // The crypto data may have not been fully recovered in current packets,
// So we continue to sniff rest packets. // So we continue to sniff rest packets.
@@ -276,7 +262,8 @@ func SniffQUIC(b []byte) (resultReturn *SniffHeader, errorReturn error) {
} }
return &SniffHeader{domain: tlsHdr.Domain()}, nil return &SniffHeader{domain: tlsHdr.Domain()}, nil
} }
return nil, common.ErrNoClue // All payload is parsed as valid QUIC packets, but we need more packets for crypto data to read client hello.
return nil, protocol.ErrProtoNeedMoreData
} }
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte { func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {

File diff suppressed because one or more lines are too long

View File

@@ -3,9 +3,9 @@ package tls
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/protocol"
) )
type SniffHeader struct { type SniffHeader struct {
@@ -59,9 +59,6 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
} }
data = data[1+compressionMethodsLen:] data = data[1+compressionMethodsLen:]
if len(data) == 0 {
return errNotClientHello
}
if len(data) < 2 { if len(data) < 2 {
return errNotClientHello return errNotClientHello
} }
@@ -104,13 +101,21 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
return errNotClientHello return errNotClientHello
} }
if nameType == 0 { if nameType == 0 {
serverName := string(d[:nameLen]) // QUIC separated across packets
// May cause the serverName to be incomplete
b := byte(0)
for _, b = range d[:nameLen] {
if b <= ' ' {
return protocol.ErrProtoNeedMoreData
}
}
// An SNI value may not include a // An SNI value may not include a
// trailing dot. See // trailing dot. See
// https://tools.ietf.org/html/rfc6066#section-3. // https://tools.ietf.org/html/rfc6066#section-3.
if strings.HasSuffix(serverName, ".") { if b == '.' {
return errNotClientHello return errNotClientHello
} }
serverName := string(d[:nameLen])
h.domain = serverName h.domain = serverName
return nil return nil
} }

View File

@@ -67,9 +67,9 @@ func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
t.checkTask.Close() t.checkTask.Close()
} }
t.checkTask = checkTask t.checkTask = checkTask
t.Unlock()
t.Update() t.Update()
common.Must(checkTask.Start()) common.Must(checkTask.Start())
t.Unlock()
} }
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer { func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {

View File

@@ -0,0 +1,112 @@
package utils
import (
"sync"
)
// TypedSyncMap is a wrapper of sync.Map that provides type-safe for keys and values.
// No need to use type assertions every time, so you can have more time to enjoy other things like GochiUsa
// If sync.Map methods returned nil, it will return the zero value of the type V.
type TypedSyncMap[K, V any] struct {
syncMap *sync.Map
}
// NewTypedSyncMap creates a new TypedSyncMap
// K is key type, V is value type
// It is recommended to use pointer types for V because sync.Map might return nil
// If sync.Map methods really returned nil, it will return the zero value of the type V
func NewTypedSyncMap[K any, V any]() *TypedSyncMap[K, V] {
return &TypedSyncMap[K, V]{
syncMap: &sync.Map{},
}
}
// Clear deletes all the entries, resulting in an empty Map.
func (m *TypedSyncMap[K, V]) Clear() {
m.syncMap.Clear()
}
// CompareAndDelete deletes the entry for key if its value is equal to old.
// The old value must be of a comparable type.
//
// If there is no current value for key in the map, CompareAndDelete
// returns false (even if the old value is the nil interface value).
func (m *TypedSyncMap[K, V]) CompareAndDelete(key K, old V) (deleted bool) {
return m.syncMap.CompareAndDelete(key, old)
}
// CompareAndSwap swaps the old and new values for key
// if the value stored in the map is equal to old.
// The old value must be of a comparable type.
func (m *TypedSyncMap[K, V]) CompareAndSwap(key K, old V, new V) (swapped bool) {
return m.syncMap.CompareAndSwap(key, old, new)
}
// Delete deletes the value for a key.
func (m *TypedSyncMap[K, V]) Delete(key K) {
m.syncMap.Delete(key)
}
// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *TypedSyncMap[K, V]) Load(key K) (value V, ok bool) {
anyValue, ok := m.syncMap.Load(key)
// anyValue might be nil
if anyValue != nil {
value = anyValue.(V)
}
return value, ok
}
// LoadAndDelete deletes the value for a key, returning the previous value if any.
// The loaded result reports whether the key was present.
func (m *TypedSyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
anyValue, loaded := m.syncMap.LoadAndDelete(key)
if anyValue != nil {
value = anyValue.(V)
}
return value, loaded
}
// LoadOrStore returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
// The loaded result is true if the value was loaded, false if stored.
func (m *TypedSyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
anyActual, loaded := m.syncMap.LoadOrStore(key, value)
if anyActual != nil {
actual = anyActual.(V)
}
return actual, loaded
}
// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
//
// Range does not necessarily correspond to any consistent snapshot of the Map's
// contents: no key will be visited more than once, but if the value for any key
// is stored or deleted concurrently (including by f), Range may reflect any
// mapping for that key from any point during the Range call. Range does not
// block other methods on the receiver; even f itself may call any method on m.
//
// Range may be O(N) with the number of elements in the map even if f returns
// false after a constant number of calls.
func (m *TypedSyncMap[K, V]) Range(f func(key K, value V) bool) {
m.syncMap.Range(func(key, value any) bool {
return f(key.(K), value.(V))
})
}
// Store sets the value for a key.
func (m *TypedSyncMap[K, V]) Store(key K, value V) {
m.syncMap.Store(key, value)
}
// Swap swaps the value for a key and returns the previous value if any. The loaded result reports whether the key was present.
func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
anyPrevious, loaded := m.syncMap.Swap(key, value)
if anyPrevious != nil {
previous = anyPrevious.(V)
}
return previous, loaded
}

View File

@@ -18,8 +18,8 @@ import (
var ( var (
Version_x byte = 25 Version_x byte = 25
Version_y byte = 3 Version_y byte = 8
Version_z byte = 31 Version_z byte = 3
) )
var ( var (

View File

@@ -90,6 +90,11 @@ type Instance struct {
ctx context.Context ctx context.Context
} }
// Instance state
func (server *Instance) IsRunning() bool {
return server.running
}
func AddInboundHandler(server *Instance, config *InboundHandlerConfig) error { func AddInboundHandler(server *Instance, config *InboundHandlerConfig) error {
inboundManager := server.GetFeature(inbound.ManagerType()).(inbound.Manager) inboundManager := server.GetFeature(inbound.ManagerType()).(inbound.Manager)
rawHandler, err := CreateObject(server, config) rawHandler, err := CreateObject(server, config)

View File

@@ -24,10 +24,6 @@ type Client interface {
LookupIP(domain string, option IPOption) ([]net.IP, uint32, error) LookupIP(domain string, option IPOption) ([]net.IP, uint32, error)
} }
type HostsLookup interface {
LookupHosts(domain string) *net.Address
}
// ClientType returns the type of Client interface. Can be used for implementing common.HasType. // ClientType returns the type of Client interface. Can be used for implementing common.HasType.
// //
// xray:api:beta // xray:api:beta
@@ -38,12 +34,32 @@ func ClientType() interface{} {
// ErrEmptyResponse indicates that DNS query succeeded but no answer was returned. // ErrEmptyResponse indicates that DNS query succeeded but no answer was returned.
var ErrEmptyResponse = errors.New("empty response") var ErrEmptyResponse = errors.New("empty response")
const DefaultTTL = 300
type RCodeError uint16 type RCodeError uint16
func (e RCodeError) Error() string { func (e RCodeError) Error() string {
return serial.Concat("rcode: ", uint16(e)) return serial.Concat("rcode: ", uint16(e))
} }
func (RCodeError) IP() net.IP {
panic("Calling IP() on a RCodeError.")
}
func (RCodeError) Domain() string {
panic("Calling Domain() on a RCodeError.")
}
func (RCodeError) Family() net.AddressFamily {
panic("Calling Family() on a RCodeError.")
}
func (e RCodeError) String() string {
return e.Error()
}
var _ net.Address = (*RCodeError)(nil)
func RCodeFromError(err error) uint16 { func RCodeFromError(err error) uint16 {
if err == nil { if err == nil {
return 0 return 0

View File

@@ -30,29 +30,31 @@ func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, err
ipv6 := make([]net.IP, 0, len(ips)) ipv6 := make([]net.IP, 0, len(ips))
for _, ip := range ips { for _, ip := range ips {
parsed := net.IPAddress(ip) parsed := net.IPAddress(ip)
if parsed != nil { if parsed == nil {
parsedIPs = append(parsedIPs, parsed.IP()) continue
} }
if len(ip) == net.IPv4len { parsedIP := parsed.IP()
ipv4 = append(ipv4, ip) parsedIPs = append(parsedIPs, parsedIP)
}
if len(ip) == net.IPv6len { if len(parsedIP) == net.IPv4len {
ipv6 = append(ipv6, ip) ipv4 = append(ipv4, parsedIP)
} else {
ipv6 = append(ipv6, parsedIP)
} }
} }
// Local DNS ttl is 600
switch { switch {
case option.IPv4Enable && option.IPv6Enable: case option.IPv4Enable && option.IPv6Enable:
if len(parsedIPs) > 0 { if len(parsedIPs) > 0 {
return parsedIPs, 600, nil return parsedIPs, dns.DefaultTTL, nil
} }
case option.IPv4Enable: case option.IPv4Enable:
if len(ipv4) > 0 { if len(ipv4) > 0 {
return ipv4, 600, nil return ipv4, dns.DefaultTTL, nil
} }
case option.IPv6Enable: case option.IPv6Enable:
if len(ipv6) > 0 { if len(ipv6) > 0 {
return ipv6, 600, nil return ipv6, dns.DefaultTTL, nil
} }
} }
return nil, 0, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse

View File

@@ -5,6 +5,7 @@ import (
"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/serial"
"github.com/xtls/xray-core/features" "github.com/xtls/xray-core/features"
) )
@@ -15,6 +16,10 @@ type Handler interface {
common.Runnable common.Runnable
// The tag of this handler. // The tag of this handler.
Tag() string Tag() string
// Returns the active receiver settings.
ReceiverSettings() *serial.TypedMessage
// Returns the active proxy settings.
ProxySettings() *serial.TypedMessage
// Deprecated: Do not use in new code. // Deprecated: Do not use in new code.
GetRandomInboundProxy() (interface{}, net.Port, int) GetRandomInboundProxy() (interface{}, net.Port, int)
@@ -32,6 +37,9 @@ type Manager interface {
// RemoveHandler removes a handler from Manager. // RemoveHandler removes a handler from Manager.
RemoveHandler(ctx context.Context, tag string) error RemoveHandler(ctx context.Context, tag string) error
// ListHandlers returns a list of inbound.Handler.
ListHandlers(ctx context.Context) []Handler
} }
// ManagerType returns the type of Manager interface. Can be used for implementing common.HasType. // ManagerType returns the type of Manager interface. Can be used for implementing common.HasType.

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/features" "github.com/xtls/xray-core/features"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
) )
@@ -15,6 +16,8 @@ type Handler interface {
common.Runnable common.Runnable
Tag() string Tag() string
Dispatch(ctx context.Context, link *transport.Link) Dispatch(ctx context.Context, link *transport.Link)
SenderSettings() *serial.TypedMessage
ProxySettings() *serial.TypedMessage
} }
type HandlerSelector interface { type HandlerSelector interface {
@@ -35,6 +38,9 @@ type Manager interface {
// RemoveHandler removes a handler from outbound.Manager. // RemoveHandler removes a handler from outbound.Manager.
RemoveHandler(ctx context.Context, tag string) error RemoveHandler(ctx context.Context, tag string) error
// ListHandlers returns a list of outbound.Handler.
ListHandlers(ctx context.Context) []Handler
} }
// ManagerType returns the type of Manager interface. Can be used to implement common.HasType. // ManagerType returns the type of Manager interface. Can be used to implement common.HasType.

53
go.mod
View File

@@ -3,59 +3,56 @@ module github.com/xtls/xray-core
go 1.24 go 1.24
require ( require (
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 github.com/cloudflare/circl v1.6.1
github.com/cloudflare/circl v1.6.0
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.64 github.com/miekg/dns v1.1.67
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.8.0 github.com/pires/go-proxyproto v0.8.1
github.com/quic-go/quic-go v0.50.1 github.com/quic-go/quic-go v0.54.0
github.com/refraction-networking/utls v1.6.7 github.com/refraction-networking/utls v1.8.0
github.com/sagernet/sing v0.5.1 github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
github.com/vishvananda/netlink v1.3.0 github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.40.0
golang.org/x/net v0.38.0 golang.org/x/net v0.42.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.16.0
golang.org/x/sys v0.31.0 golang.org/x/sys v0.34.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.71.0 google.golang.org/grpc v1.74.2
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.6
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 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.0 lukechampine.com/blake3 v1.4.1
) )
require ( require (
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qpack v0.5.1 // 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.4 // indirect github.com/vishvananda/netns v0.0.5 // indirect
go.uber.org/mock v0.5.0 // indirect go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.25.0 // indirect
golang.org/x/mod v0.23.0 // indirect golang.org/x/text v0.27.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.30.0 // indirect golang.org/x/tools v0.34.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-20250115164207-1a7da9e5054f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // 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
) )

141
go.sum
View File

@@ -1,23 +1,19 @@
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -26,40 +22,43 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
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.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0= github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY= 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.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/refraction-networking/utls v1.8.0/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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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=
@@ -72,70 +71,67 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg= github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0=
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
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.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.5.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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
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.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=
@@ -143,14 +139,15 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
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=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -158,9 +155,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0= gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h1:sfK5nHuG7lRFZ2FdTT3RimOqWBg8IrVm+/Vko1FVOsk=
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo= h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck= h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=

View File

@@ -8,11 +8,13 @@ import (
type Duration int64 type Duration int64
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (d *Duration) MarshalJSON() ([]byte, error) { func (d *Duration) MarshalJSON() ([]byte, error) {
dr := time.Duration(*d) dr := time.Duration(*d)
return json.Marshal(dr.String()) return json.Marshal(dr.String())
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (d *Duration) UnmarshalJSON(b []byte) error { func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{} var v interface{}
if err := json.Unmarshal(b, &v); err != nil { if err := json.Unmarshal(b, &v); err != nil {

View File

@@ -23,6 +23,7 @@ func (v StringList) Len() int {
return len(v) return len(v)
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *StringList) UnmarshalJSON(data []byte) error { func (v *StringList) UnmarshalJSON(data []byte) error {
var strarray []string var strarray []string
if err := json.Unmarshal(data, &strarray); err == nil { if err := json.Unmarshal(data, &strarray); err == nil {
@@ -43,10 +44,12 @@ type Address struct {
net.Address net.Address
} }
func (v Address) MarshalJSON() ([]byte, error) { // MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *Address) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Address.String()) return json.Marshal(v.Address.String())
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *Address) UnmarshalJSON(data []byte) error { func (v *Address) UnmarshalJSON(data []byte) error {
var rawStr string var rawStr string
if err := json.Unmarshal(data, &rawStr); err != nil { if err := json.Unmarshal(data, &rawStr); err != nil {
@@ -81,6 +84,7 @@ func (v Network) Build() net.Network {
type NetworkList []Network type NetworkList []Network
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *NetworkList) UnmarshalJSON(data []byte) error { func (v *NetworkList) UnmarshalJSON(data []byte) error {
var strarray []Network var strarray []Network
if err := json.Unmarshal(data, &strarray); err == nil { if err := json.Unmarshal(data, &strarray); err == nil {
@@ -169,6 +173,19 @@ func (v *PortRange) Build() *net.PortRange {
} }
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *PortRange) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func (port *PortRange) String() string {
if port.From == port.To {
return strconv.Itoa(int(port.From))
} else {
return fmt.Sprintf("%d-%d", port.From, port.To)
}
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *PortRange) UnmarshalJSON(data []byte) error { func (v *PortRange) UnmarshalJSON(data []byte) error {
port, err := parseIntPort(data) port, err := parseIntPort(data)
@@ -203,20 +220,21 @@ func (list *PortList) Build() *net.PortList {
return portList return portList
} }
func (v PortList) MarshalJSON() ([]byte, error) { // MarshalJSON implements encoding/json.Marshaler.MarshalJSON
return json.Marshal(v.String()) func (v *PortList) MarshalJSON() ([]byte, error) {
portStr := v.String()
port, err := strconv.Atoi(portStr)
if err == nil {
return json.Marshal(port)
} else {
return json.Marshal(portStr)
}
} }
func (v PortList) String() string { func (v PortList) String() string {
ports := []string{} ports := []string{}
for _, port := range v.Range { for _, port := range v.Range {
if port.From == port.To { ports = append(ports, port.String())
p := strconv.Itoa(int(port.From))
ports = append(ports, p)
} else {
p := fmt.Sprintf("%d-%d", port.From, port.To)
ports = append(ports, p)
}
} }
return strings.Join(ports, ",") return strings.Join(ports, ",")
} }
@@ -277,7 +295,8 @@ type Int32Range struct {
To int32 To int32
} }
func (v Int32Range) MarshalJSON() ([]byte, error) { // MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (v *Int32Range) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String()) return json.Marshal(v.String())
} }
@@ -289,6 +308,7 @@ func (v Int32Range) String() string {
} }
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (v *Int32Range) UnmarshalJSON(data []byte) error { func (v *Int32Range) UnmarshalJSON(data []byte) error {
defer v.ensureOrder() defer v.ensureOrder()
var str string var str string

View File

@@ -1,7 +1,11 @@
package conf package conf
import ( import (
"bufio"
"encoding/json" "encoding/json"
"os"
"path/filepath"
"runtime"
"sort" "sort"
"strings" "strings"
@@ -12,19 +16,22 @@ 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"`
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"` Tag string `json:"tag"`
Tag string `json:"tag"` TimeoutMs uint64 `json:"timeoutMs"`
TimeoutMs uint64 `json:"timeoutMs"` DisableCache bool `json:"disableCache"`
FinalQuery bool `json:"finalQuery"`
UnexpectedIPs StringList `json:"unexpectedIPs"`
} }
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (c *NameServerConfig) UnmarshalJSON(data []byte) error { func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
var address Address var address Address
if err := json.Unmarshal(data, &address); err == nil { if err := json.Unmarshal(data, &address); err == nil {
@@ -33,17 +40,19 @@ 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"`
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"` Tag string `json:"tag"`
Tag string `json:"tag"` TimeoutMs uint64 `json:"timeoutMs"`
TimeoutMs uint64 `json:"timeoutMs"` DisableCache bool `json:"disableCache"`
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
@@ -54,9 +63,11 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
c.ExpectedIPs = advanced.ExpectedIPs c.ExpectedIPs = advanced.ExpectedIPs
c.ExpectIPs = advanced.ExpectIPs c.ExpectIPs = advanced.ExpectIPs
c.QueryStrategy = advanced.QueryStrategy c.QueryStrategy = advanced.QueryStrategy
c.AllowUnexpectedIPs = advanced.AllowUnexpectedIPs
c.Tag = advanced.Tag c.Tag = advanced.Tag
c.TimeoutMs = advanced.TimeoutMs c.TimeoutMs = advanced.TimeoutMs
c.DisableCache = advanced.DisableCache
c.FinalQuery = advanced.FinalQuery
c.UnexpectedIPs = advanced.UnexpectedIPs
return nil return nil
} }
@@ -104,13 +115,38 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
}) })
} }
var expectedIPs = c.ExpectedIPs if len(c.ExpectedIPs) == 0 {
if len(expectedIPs) == 0 { c.ExpectedIPs = c.ExpectIPs
expectedIPs = c.ExpectIPs
} }
geoipList, err := ToCidrList(expectedIPs)
actPrior := false
var newExpectedIPs StringList
for _, s := range c.ExpectedIPs {
if s == "*" {
actPrior = true
} else {
newExpectedIPs = append(newExpectedIPs, s)
}
}
actUnprior := false
var newUnexpectedIPs StringList
for _, s := range c.UnexpectedIPs {
if s == "*" {
actUnprior = true
} else {
newUnexpectedIPs = append(newUnexpectedIPs, s)
}
}
expectedGeoipList, err := ToCidrList(newExpectedIPs)
if err != nil { if err != nil {
return nil, errors.New("invalid IP rule: ", expectedIPs).Base(err) return nil, errors.New("invalid expected IP rule: ", c.ExpectedIPs).Base(err)
}
unexpectedGeoipList, err := ToCidrList(newUnexpectedIPs)
if err != nil {
return nil, errors.New("invalid unexpected IP rule: ", c.UnexpectedIPs).Base(err)
} }
var myClientIP []byte var myClientIP []byte
@@ -127,15 +163,19 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
Address: c.Address.Build(), Address: c.Address.Build(),
Port: uint32(c.Port), Port: uint32(c.Port),
}, },
ClientIp: myClientIP, ClientIp: myClientIP,
SkipFallback: c.SkipFallback, SkipFallback: c.SkipFallback,
PrioritizedDomain: domains, PrioritizedDomain: domains,
Geoip: geoipList, ExpectedGeoip: expectedGeoipList,
OriginalRules: originalRules, OriginalRules: originalRules,
QueryStrategy: resolveQueryStrategy(c.QueryStrategy), QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
AllowUnexpectedIPs: c.AllowUnexpectedIPs, ActPrior: actPrior,
Tag: c.Tag, Tag: c.Tag,
TimeoutMs: c.TimeoutMs, TimeoutMs: c.TimeoutMs,
DisableCache: c.DisableCache,
FinalQuery: c.FinalQuery,
UnexpectedGeoip: unexpectedGeoipList,
ActUnprior: actUnprior,
}, nil }, nil
} }
@@ -156,6 +196,7 @@ type DNSConfig struct {
DisableCache bool `json:"disableCache"` DisableCache bool `json:"disableCache"`
DisableFallback bool `json:"disableFallback"` DisableFallback bool `json:"disableFallback"`
DisableFallbackIfMatch bool `json:"disableFallbackIfMatch"` DisableFallbackIfMatch bool `json:"disableFallbackIfMatch"`
UseSystemHosts bool `json:"useSystemHosts"`
} }
type HostAddress struct { type HostAddress struct {
@@ -163,6 +204,18 @@ type HostAddress struct {
addrs []*Address addrs []*Address
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (h *HostAddress) MarshalJSON() ([]byte, error) {
if (h.addr != nil) != (h.addrs != nil) {
if h.addr != nil {
return json.Marshal(h.addr)
} else if h.addrs != nil {
return json.Marshal(h.addrs)
}
}
return nil, errors.New("unexpected config state")
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (h *HostAddress) UnmarshalJSON(data []byte) error { func (h *HostAddress) UnmarshalJSON(data []byte) error {
addr := new(Address) addr := new(Address)
@@ -208,6 +261,11 @@ func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
} }
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (m *HostsWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(m.Hosts)
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (m *HostsWrapper) UnmarshalJSON(data []byte) error { func (m *HostsWrapper) UnmarshalJSON(data []byte) error {
hosts := make(map[string]*HostAddress) hosts := make(map[string]*HostAddress)
@@ -360,6 +418,15 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
} }
config.StaticHosts = append(config.StaticHosts, staticHosts...) config.StaticHosts = append(config.StaticHosts, staticHosts...)
} }
if c.UseSystemHosts {
systemHosts, err := readSystemHosts()
if err != nil {
return nil, errors.New("failed to read system hosts").Base(err)
}
for domain, ips := range systemHosts {
config.StaticHosts = append(config.StaticHosts, &dns.Config_HostMapping{Ip: ips, Domain: domain, Type: dns.DomainMatchingType_Full})
}
}
return config, nil return config, nil
} }
@@ -372,7 +439,57 @@ func resolveQueryStrategy(queryStrategy string) dns.QueryStrategy {
return dns.QueryStrategy_USE_IP4 return dns.QueryStrategy_USE_IP4
case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6": case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
return dns.QueryStrategy_USE_IP6 return dns.QueryStrategy_USE_IP6
case "usesys", "usesystem", "use_sys", "use_system", "use-sys", "use-system":
return dns.QueryStrategy_USE_SYS
default: default:
return dns.QueryStrategy_USE_IP return dns.QueryStrategy_USE_IP
} }
} }
func readSystemHosts() (map[string][][]byte, error) {
var hostsPath string
switch runtime.GOOS {
case "windows":
hostsPath = filepath.Join(os.Getenv("SystemRoot"), "System32", "drivers", "etc", "hosts")
default:
hostsPath = "/etc/hosts"
}
file, err := os.Open(hostsPath)
if err != nil {
return nil, err
}
defer file.Close()
hostsMap := make(map[string][][]byte)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if i := strings.IndexByte(line, '#'); i >= 0 {
// Discard comments.
line = line[0:i]
}
f := strings.Fields(line)
if len(f) < 2 {
continue
}
addr := net.ParseAddress(f[0])
if addr.Family().IsDomain() {
continue
}
ip := addr.IP()
for i := 1; i < len(f); i++ {
domain := strings.TrimSuffix(f[i], ".")
domain = strings.ToLower(domain)
if v, ok := hostsMap[domain]; ok {
hostsMap[domain] = append(v, ip)
} else {
hostsMap[domain] = [][]byte{ip}
}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return hostsMap, nil
}

View File

@@ -30,7 +30,7 @@ func (c *DNSOutboundConfig) Build() (proto.Message, error) {
switch c.NonIPQuery { switch c.NonIPQuery {
case "": case "":
c.NonIPQuery = "drop" c.NonIPQuery = "drop"
case "drop", "skip": case "drop", "skip", "reject":
default: default:
return nil, errors.New(`unknown "nonIPQuery": `, c.NonIPQuery) return nil, errors.New(`unknown "nonIPQuery": `, c.NonIPQuery)
} }

View File

@@ -1,26 +1,35 @@
package conf package conf
import ( import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/proxy/dokodemo" "github.com/xtls/xray-core/proxy/dokodemo"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type DokodemoConfig struct { type DokodemoConfig struct {
Host *Address `json:"address"` Address *Address `json:"address"`
PortValue uint16 `json:"port"` Port uint16 `json:"port"`
NetworkList *NetworkList `json:"network"` PortMap map[string]string `json:"portMap"`
Redirect bool `json:"followRedirect"` Network *NetworkList `json:"network"`
UserLevel uint32 `json:"userLevel"` FollowRedirect bool `json:"followRedirect"`
UserLevel uint32 `json:"userLevel"`
} }
func (v *DokodemoConfig) Build() (proto.Message, error) { func (v *DokodemoConfig) Build() (proto.Message, error) {
config := new(dokodemo.Config) config := new(dokodemo.Config)
if v.Host != nil { if v.Address != nil {
config.Address = v.Host.Build() config.Address = v.Address.Build()
} }
config.Port = uint32(v.PortValue) config.Port = uint32(v.Port)
config.Networks = v.NetworkList.Build() config.PortMap = v.PortMap
config.FollowRedirect = v.Redirect for _, v := range config.PortMap {
if _, _, err := net.SplitHostPort(v); err != nil {
return nil, errors.New("invalid portMap: ", v).Base(err)
}
}
config.Networks = v.Network.Build()
config.FollowRedirect = v.FollowRedirect
config.UserLevel = v.UserLevel config.UserLevel = v.UserLevel
return config, nil return config, nil
} }

View File

@@ -20,6 +20,18 @@ type FakeDNSConfig struct {
pools []*FakeDNSPoolElementConfig pools []*FakeDNSPoolElementConfig
} }
// MarshalJSON implements encoding/json.Marshaler.MarshalJSON
func (f *FakeDNSConfig) MarshalJSON() ([]byte, error) {
if (f.pool != nil) != (f.pools != nil) {
if f.pool != nil {
return json.Marshal(f.pool)
} else if f.pools != nil {
return json.Marshal(f.pools)
}
}
return nil, errors.New("unexpected config state")
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error { func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error {
var pool FakeDNSPoolElementConfig var pool FakeDNSPoolElementConfig

View File

@@ -5,6 +5,7 @@ import (
"github.com/xtls/xray-core/app/observatory" "github.com/xtls/xray-core/app/observatory"
"github.com/xtls/xray-core/app/observatory/burst" "github.com/xtls/xray-core/app/observatory/burst"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/infra/conf/cfgcommon/duration" "github.com/xtls/xray-core/infra/conf/cfgcommon/duration"
) )
@@ -26,6 +27,9 @@ type BurstObservatoryConfig struct {
} }
func (b BurstObservatoryConfig) Build() (proto.Message, error) { func (b BurstObservatoryConfig) Build() (proto.Message, error) {
if b.HealthCheck == nil {
return nil, errors.New("BurstObservatory requires a valid pingConfig")
}
if result, err := b.HealthCheck.Build(); err == nil { if result, err := b.HealthCheck.Build(); err == nil {
return &burst.Config{SubjectSelector: b.SubjectSelector, PingConfig: result.(*burst.HealthPingConfig)}, nil return &burst.Config{SubjectSelector: b.SubjectSelector, PingConfig: result.(*burst.HealthPingConfig)}, nil
} else { } else {

View File

@@ -2,6 +2,7 @@ package conf
import ( import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"strings"
"github.com/xtls/xray-core/app/observatory/burst" "github.com/xtls/xray-core/app/observatory/burst"
"github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/app/router"
@@ -51,15 +52,23 @@ type healthCheckSettings struct {
Interval duration.Duration `json:"interval"` Interval duration.Duration `json:"interval"`
SamplingCount int `json:"sampling"` SamplingCount int `json:"sampling"`
Timeout duration.Duration `json:"timeout"` Timeout duration.Duration `json:"timeout"`
HttpMethod string `json:"httpMethod"`
} }
func (h healthCheckSettings) Build() (proto.Message, error) { func (h healthCheckSettings) Build() (proto.Message, error) {
var httpMethod string
if h.HttpMethod == "" {
httpMethod = "HEAD"
} else {
httpMethod = strings.TrimSpace(h.HttpMethod)
}
return &burst.HealthPingConfig{ return &burst.HealthPingConfig{
Destination: h.Destination, Destination: h.Destination,
Connectivity: h.Connectivity, Connectivity: h.Connectivity,
Interval: int64(h.Interval), Interval: int64(h.Interval),
Timeout: int64(h.Timeout), Timeout: int64(h.Timeout),
SamplingCount: int32(h.SamplingCount), SamplingCount: int32(h.SamplingCount),
HttpMethod: httpMethod,
}, nil }, nil
} }

View File

@@ -412,6 +412,10 @@ type TLSConfig struct {
MasterKeyLog string `json:"masterKeyLog"` MasterKeyLog string `json:"masterKeyLog"`
ServerNameToVerify string `json:"serverNameToVerify"` ServerNameToVerify string `json:"serverNameToVerify"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
ECHServerKeys string `json:"echServerKeys"`
ECHConfigList string `json:"echConfigList"`
ECHForceQuery string `json:"echForceQuery"`
ECHSocketSettings *SocketConfig `json:"echSockopt"`
} }
// Build implements Buildable. // Build implements Buildable.
@@ -435,7 +439,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
} }
if len(config.NextProtocol) > 1 { if len(config.NextProtocol) > 1 {
for _, p := range config.NextProtocol { for _, p := range config.NextProtocol {
if tcp.IsFromMitm(p) { if tls.IsFromMitm(p) {
return nil, errors.New(`only one element is allowed in "alpn" when using "fromMitm" in it`) return nil, errors.New(`only one element is allowed in "alpn" when using "fromMitm" in it`)
} }
} }
@@ -483,9 +487,38 @@ func (c *TLSConfig) Build() (proto.Message, error) {
} }
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
if c.ECHServerKeys != "" {
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)
if err != nil {
return nil, errors.New("invalid ECH Config", c.ECHServerKeys)
}
config.EchServerKeys = EchPrivateKey
}
switch c.ECHForceQuery {
case "none", "half", "full", "":
config.EchForceQuery = c.ECHForceQuery
default:
return nil, errors.New(`invalid "echForceQuery": `, c.ECHForceQuery)
}
config.EchForceQuery = c.ECHForceQuery
config.EchConfigList = c.ECHConfigList
if c.ECHSocketSettings != nil {
ss, err := c.ECHSocketSettings.Build()
if err != nil {
return nil, errors.New("Failed to build ech sockopt.").Base(err)
}
config.EchSocketSettings = ss
}
return config, nil return config, nil
} }
type LimitFallback struct {
AfterBytes uint64
BytesPerSec uint64
BurstBytesPerSec uint64
}
type REALITYConfig struct { type REALITYConfig struct {
MasterKeyLog string `json:"masterKeyLog"` MasterKeyLog string `json:"masterKeyLog"`
Show bool `json:"show"` Show bool `json:"show"`
@@ -499,13 +532,18 @@ type REALITYConfig struct {
MaxClientVer string `json:"maxClientVer"` MaxClientVer string `json:"maxClientVer"`
MaxTimeDiff uint64 `json:"maxTimeDiff"` MaxTimeDiff uint64 `json:"maxTimeDiff"`
ShortIds []string `json:"shortIds"` ShortIds []string `json:"shortIds"`
Mldsa65Seed string `json:"mldsa65Seed"`
Fingerprint string `json:"fingerprint"` LimitFallbackUpload LimitFallback `json:"limitFallbackUpload"`
ServerName string `json:"serverName"` LimitFallbackDownload LimitFallback `json:"limitFallbackDownload"`
Password string `json:"password"`
PublicKey string `json:"publicKey"` Fingerprint string `json:"fingerprint"`
ShortId string `json:"shortId"` ServerName string `json:"serverName"`
SpiderX string `json:"spiderX"` Password string `json:"password"`
PublicKey string `json:"publicKey"`
ShortId string `json:"shortId"`
Mldsa65Verify string `json:"mldsa65Verify"`
SpiderX string `json:"spiderX"`
} }
func (c *REALITYConfig) Build() (proto.Message, error) { func (c *REALITYConfig) Build() (proto.Message, error) {
@@ -535,7 +573,7 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
} }
default: default:
if _, err = strconv.Atoi(s); err == nil { if _, err = strconv.Atoi(s); err == nil {
s = "127.0.0.1:" + s s = "localhost:" + s
} }
if _, _, err = net.SplitHostPort(s); err == nil { if _, _, err = net.SplitHostPort(s); err == nil {
c.Type = "tcp" c.Type = "tcp"
@@ -600,6 +638,24 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
config.Xver = c.Xver config.Xver = c.Xver
config.ServerNames = c.ServerNames config.ServerNames = c.ServerNames
config.MaxTimeDiff = c.MaxTimeDiff config.MaxTimeDiff = c.MaxTimeDiff
if c.Mldsa65Seed != "" {
if c.Mldsa65Seed == c.PrivateKey {
return nil, errors.New(`"mldsa65Seed" and "privateKey" can not be the same value: `, c.Mldsa65Seed)
}
if config.Mldsa65Seed, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Seed); err != nil || len(config.Mldsa65Seed) != 32 {
return nil, errors.New(`invalid "mldsa65Seed": `, c.Mldsa65Seed)
}
}
config.LimitFallbackUpload = new(reality.LimitFallback)
config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes
config.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec
config.LimitFallbackUpload.BurstBytesPerSec = c.LimitFallbackUpload.BurstBytesPerSec
config.LimitFallbackDownload = new(reality.LimitFallback)
config.LimitFallbackDownload.AfterBytes = c.LimitFallbackDownload.AfterBytes
config.LimitFallbackDownload.BytesPerSec = c.LimitFallbackDownload.BytesPerSec
config.LimitFallbackDownload.BurstBytesPerSec = c.LimitFallbackDownload.BurstBytesPerSec
} else { } else {
config.Fingerprint = strings.ToLower(c.Fingerprint) config.Fingerprint = strings.ToLower(c.Fingerprint)
if config.Fingerprint == "unsafe" || config.Fingerprint == "hellogolang" { if config.Fingerprint == "unsafe" || config.Fingerprint == "hellogolang" {
@@ -627,6 +683,11 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
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)
} }
if c.Mldsa65Verify != "" {
if config.Mldsa65Verify, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Verify); err != nil || len(config.Mldsa65Verify) != 1952 {
return nil, errors.New(`invalid "mldsa65Verify": `, c.Mldsa65Verify)
}
}
if c.SpiderX == "" { if c.SpiderX == "" {
c.SpiderX = "/" c.SpiderX = "/"
} }
@@ -691,6 +752,7 @@ func (p TransportProtocol) Build() (string, error) {
} }
type CustomSockoptConfig struct { type CustomSockoptConfig struct {
Syetem string `json:"system"`
Network string `json:"network"` Network string `json:"network"`
Level string `json:"level"` Level string `json:"level"`
Opt string `json:"opt"` Opt string `json:"opt"`
@@ -698,25 +760,50 @@ type CustomSockoptConfig struct {
Type string `json:"type"` Type string `json:"type"`
} }
type HappyEyeballsConfig struct {
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
TryDelayMs uint64 `json:"tryDelayMs"`
Interleave uint32 `json:"interleave"`
MaxConcurrentTry uint32 `json:"maxConcurrentTry"`
}
func (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error {
var innerHappyEyeballsConfig = struct {
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
TryDelayMs uint64 `json:"tryDelayMs"`
Interleave uint32 `json:"interleave"`
MaxConcurrentTry uint32 `json:"maxConcurrentTry"`
}{PrioritizeIPv6: false, Interleave: 1, TryDelayMs: 0, MaxConcurrentTry: 4}
if err := json.Unmarshal(data, &innerHappyEyeballsConfig); err != nil {
return err
}
h.PrioritizeIPv6 = innerHappyEyeballsConfig.PrioritizeIPv6
h.TryDelayMs = innerHappyEyeballsConfig.TryDelayMs
h.Interleave = innerHappyEyeballsConfig.Interleave
h.MaxConcurrentTry = innerHappyEyeballsConfig.MaxConcurrentTry
return nil
}
type SocketConfig struct { type SocketConfig struct {
Mark int32 `json:"mark"` Mark int32 `json:"mark"`
TFO interface{} `json:"tcpFastOpen"` TFO interface{} `json:"tcpFastOpen"`
TProxy string `json:"tproxy"` TProxy string `json:"tproxy"`
AcceptProxyProtocol bool `json:"acceptProxyProtocol"` AcceptProxyProtocol bool `json:"acceptProxyProtocol"`
DomainStrategy string `json:"domainStrategy"` DomainStrategy string `json:"domainStrategy"`
DialerProxy string `json:"dialerProxy"` DialerProxy string `json:"dialerProxy"`
TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"` TCPKeepAliveInterval int32 `json:"tcpKeepAliveInterval"`
TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"` TCPKeepAliveIdle int32 `json:"tcpKeepAliveIdle"`
TCPCongestion string `json:"tcpCongestion"` TCPCongestion string `json:"tcpCongestion"`
TCPWindowClamp int32 `json:"tcpWindowClamp"` TCPWindowClamp int32 `json:"tcpWindowClamp"`
TCPMaxSeg int32 `json:"tcpMaxSeg"` TCPMaxSeg int32 `json:"tcpMaxSeg"`
Penetrate bool `json:"penetrate"` Penetrate bool `json:"penetrate"`
TCPUserTimeout int32 `json:"tcpUserTimeout"` TCPUserTimeout int32 `json:"tcpUserTimeout"`
V6only bool `json:"v6only"` V6only bool `json:"v6only"`
Interface string `json:"interface"` Interface string `json:"interface"`
TcpMptcp bool `json:"tcpMptcp"` TcpMptcp bool `json:"tcpMptcp"`
CustomSockopt []*CustomSockoptConfig `json:"customSockopt"` CustomSockopt []*CustomSockoptConfig `json:"customSockopt"`
AddressPortStrategy string `json:"addressPortStrategy"` AddressPortStrategy string `json:"addressPortStrategy"`
HappyEyeballsSettings *HappyEyeballsConfig `json:"happyEyeballs"`
} }
// Build implements Buildable. // Build implements Buildable.
@@ -778,6 +865,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
for _, copt := range c.CustomSockopt { for _, copt := range c.CustomSockopt {
customSockopt := &internet.CustomSockopt{ customSockopt := &internet.CustomSockopt{
System: copt.Syetem,
Network: copt.Network, Network: copt.Network,
Level: copt.Level, Level: copt.Level,
Opt: copt.Opt, Opt: copt.Opt,
@@ -807,6 +895,14 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy) return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
} }
var happyEyeballs = &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}
if c.HappyEyeballsSettings != nil {
happyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6
happyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave
happyEyeballs.TryDelayMs = c.HappyEyeballsSettings.TryDelayMs
happyEyeballs.MaxConcurrentTry = c.HappyEyeballsSettings.MaxConcurrentTry
}
return &internet.SocketConfig{ return &internet.SocketConfig{
Mark: c.Mark, Mark: c.Mark,
Tfo: tfo, Tfo: tfo,
@@ -826,6 +922,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
TcpMptcp: c.TcpMptcp, TcpMptcp: c.TcpMptcp,
CustomSockopt: customSockopts, CustomSockopt: customSockopts,
AddressPortStrategy: addressPortStrategy, AddressPortStrategy: addressPortStrategy,
HappyEyeballs: happyEyeballs,
}, nil }, nil
} }

View File

@@ -26,6 +26,7 @@ func TestSocketConfig(t *testing.T) {
Tfo: 256, Tfo: 256,
DomainStrategy: internet.DomainStrategy_USE_IP, DomainStrategy: internet.DomainStrategy_USE_IP,
DialerProxy: "tag", DialerProxy: "tag",
HappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},
} }
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {
@@ -45,8 +46,9 @@ func TestSocketConfig(t *testing.T) {
// test "tcpFastOpen": false, disabled TFO is expected // test "tcpFastOpen": false, disabled TFO is expected
expectedOutput = &internet.SocketConfig{ expectedOutput = &internet.SocketConfig{
Mark: 0, Mark: 0,
Tfo: -1, Tfo: -1,
HappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},
} }
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {
@@ -63,8 +65,9 @@ func TestSocketConfig(t *testing.T) {
// test "tcpFastOpen": 65535, queue length 65535 is expected // test "tcpFastOpen": 65535, queue length 65535 is expected
expectedOutput = &internet.SocketConfig{ expectedOutput = &internet.SocketConfig{
Mark: 0, Mark: 0,
Tfo: 65535, Tfo: 65535,
HappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},
} }
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {
@@ -81,8 +84,9 @@ func TestSocketConfig(t *testing.T) {
// test "tcpFastOpen": -65535, disable TFO is expected // test "tcpFastOpen": -65535, disable TFO is expected
expectedOutput = &internet.SocketConfig{ expectedOutput = &internet.SocketConfig{
Mark: 0, Mark: 0,
Tfo: -65535, Tfo: -65535,
HappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},
} }
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {
@@ -99,8 +103,9 @@ func TestSocketConfig(t *testing.T) {
// test "tcpFastOpen": 0, no operation is expected // test "tcpFastOpen": 0, no operation is expected
expectedOutput = &internet.SocketConfig{ expectedOutput = &internet.SocketConfig{
Mark: 0, Mark: 0,
Tfo: 0, Tfo: 0,
HappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},
} }
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {
@@ -117,8 +122,9 @@ func TestSocketConfig(t *testing.T) {
// test omit "tcpFastOpen", no operation is expected // test omit "tcpFastOpen", no operation is expected
expectedOutput = &internet.SocketConfig{ expectedOutput = &internet.SocketConfig{
Mark: 0, Mark: 0,
Tfo: 0, Tfo: 0,
HappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},
} }
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {
@@ -133,8 +139,9 @@ func TestSocketConfig(t *testing.T) {
// test "tcpFastOpen": null, no operation is expected // test "tcpFastOpen": null, no operation is expected
expectedOutput = &internet.SocketConfig{ expectedOutput = &internet.SocketConfig{
Mark: 0, Mark: 0,
Tfo: 0, Tfo: 0,
HappyEyeballs: &internet.HappyEyeballsConfig{Interleave: 1, TryDelayMs: 0, PrioritizeIpv6: false, MaxConcurrentTry: 4},
} }
runMultiTestCase(t, []TestCase{ runMultiTestCase(t, []TestCase{
{ {

View File

@@ -155,7 +155,7 @@ func (c *TrojanServerConfig) Build() (proto.Message, error) {
} }
} else { } else {
if _, err := strconv.Atoi(fb.Dest); err == nil { if _, err := strconv.Atoi(fb.Dest); err == nil {
fb.Dest = "127.0.0.1:" + fb.Dest fb.Dest = "localhost:" + fb.Dest
} }
if _, _, err := net.SplitHostPort(fb.Dest); err == nil { if _, _, err := net.SplitHostPort(fb.Dest); err == nil {
fb.Type = "tcp" fb.Type = "tcp"

22
infra/conf/version.go Normal file
View File

@@ -0,0 +1,22 @@
package conf
import (
"github.com/xtls/xray-core/app/version"
"github.com/xtls/xray-core/core"
"strconv"
)
type VersionConfig struct {
MinVersion string `json:"min"`
MaxVersion string `json:"max"`
}
func (c *VersionConfig) Build() (*version.Config, error) {
coreVersion := strconv.Itoa(int(core.Version_x)) + "." + strconv.Itoa(int(core.Version_y)) + "." + strconv.Itoa(int(core.Version_z))
return &version.Config{
CoreVersion: coreVersion,
MinVersion: c.MinVersion,
MaxVersion: c.MaxVersion,
}, nil
}

View File

@@ -111,7 +111,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
} }
} else { } else {
if _, err := strconv.Atoi(fb.Dest); err == nil { if _, err := strconv.Atoi(fb.Dest); err == nil {
fb.Dest = "127.0.0.1:" + fb.Dest fb.Dest = "localhost:" + fb.Dest
} }
if _, _, err := net.SplitHostPort(fb.Dest); err == nil { if _, _, err := net.SplitHostPort(fb.Dest); err == nil {
fb.Type = "tcp" fb.Type = "tcp"

View File

@@ -110,7 +110,7 @@ func TestVLessInbound(t *testing.T) {
Alpn: "", Alpn: "",
Path: "", Path: "",
Type: "tcp", Type: "tcp",
Dest: "127.0.0.1:80", Dest: "localhost:80",
Xver: 0, Xver: 0,
}, },
{ {

View File

@@ -21,6 +21,7 @@ import (
var ( var (
inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ inboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
"tunnel": func() interface{} { return new(DokodemoConfig) },
"dokodemo-door": func() interface{} { return new(DokodemoConfig) }, "dokodemo-door": func() interface{} { return new(DokodemoConfig) },
"http": func() interface{} { return new(HTTPServerConfig) }, "http": func() interface{} { return new(HTTPServerConfig) },
"shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksServerConfig) },
@@ -33,8 +34,10 @@ var (
}, "protocol", "settings") }, "protocol", "settings")
outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ outboundConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
"block": func() interface{} { return new(BlackholeConfig) },
"blackhole": func() interface{} { return new(BlackholeConfig) }, "blackhole": func() interface{} { return new(BlackholeConfig) },
"loopback": func() interface{} { return new(LoopbackConfig) }, "loopback": func() interface{} { return new(LoopbackConfig) },
"direct": func() interface{} { return new(FreedomConfig) },
"freedom": func() interface{} { return new(FreedomConfig) }, "freedom": func() interface{} { return new(FreedomConfig) },
"http": func() interface{} { return new(HTTPClientConfig) }, "http": func() interface{} { return new(HTTPClientConfig) },
"shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) }, "shadowsocks": func() interface{} { return new(ShadowsocksClientConfig) },
@@ -69,10 +72,8 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) {
p = append(p, "tls") p = append(p, "tls")
case "quic": case "quic":
p = append(p, "quic") p = append(p, "quic")
case "fakedns": case "fakedns", "fakedns+others":
p = append(p, "fakedns") p = append(p, "fakedns")
case "fakedns+others":
p = append(p, "fakedns+others")
default: default:
return nil, errors.New("unknown protocol: ", protocol) return nil, errors.New("unknown protocol: ", protocol)
} }
@@ -244,7 +245,7 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
return nil, errors.New("failed to load inbound detour config for protocol ", c.Protocol).Base(err) return nil, errors.New("failed to load inbound detour config for protocol ", c.Protocol).Base(err)
} }
if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok { if dokodemoConfig, ok := rawConfig.(*DokodemoConfig); ok {
receiverSettings.ReceiveOriginalDestination = dokodemoConfig.Redirect receiverSettings.ReceiveOriginalDestination = dokodemoConfig.FollowRedirect
} }
ts, err := rawConfig.(Buildable).Build() ts, err := rawConfig.(Buildable).Build()
if err != nil { if err != nil {
@@ -292,7 +293,8 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1] senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1]
} else { } else {
if address.Family().IsDomain() { if address.Family().IsDomain() {
if address.Address.Domain() != "origin" { domain := address.Address.Domain()
if domain != "origin" && domain != "srcip" {
return nil, errors.New("unable to send through: " + address.String()) return nil, errors.New("unable to send through: " + address.String())
} }
} }
@@ -381,6 +383,7 @@ type Config struct {
FakeDNS *FakeDNSConfig `json:"fakeDns"` FakeDNS *FakeDNSConfig `json:"fakeDns"`
Observatory *ObservatoryConfig `json:"observatory"` Observatory *ObservatoryConfig `json:"observatory"`
BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"` BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"`
Version *VersionConfig `json:"version"`
} }
func (c *Config) findInboundTag(tag string) int { func (c *Config) findInboundTag(tag string) int {
@@ -449,6 +452,10 @@ func (c *Config) Override(o *Config, fn string) {
c.BurstObservatory = o.BurstObservatory c.BurstObservatory = o.BurstObservatory
} }
if o.Version != nil {
c.Version = o.Version
}
// update the Inbound in slice if the only one in override config has same tag // update the Inbound in slice if the only one in override config has same tag
if len(o.InboundConfigs) > 0 { if len(o.InboundConfigs) > 0 {
for i := range o.InboundConfigs { for i := range o.InboundConfigs {
@@ -589,6 +596,14 @@ func (c *Config) Build() (*core.Config, error) {
config.App = append(config.App, serial.ToTypedMessage(r)) config.App = append(config.App, serial.ToTypedMessage(r))
} }
if c.Version != nil {
r, err := c.Version.Build()
if err != nil {
return nil, errors.New("failed to build version configuration").Base(err)
}
config.App = append(config.App, serial.ToTypedMessage(r))
}
var inbounds []InboundDetourConfig var inbounds []InboundDetourConfig
if len(c.InboundConfigs) > 0 { if len(c.InboundConfigs) > 0 {

View File

@@ -21,6 +21,10 @@ var CmdAPI = &base.Command{
cmdAddOutbounds, cmdAddOutbounds,
cmdRemoveInbounds, cmdRemoveInbounds,
cmdRemoveOutbounds, cmdRemoveOutbounds,
cmdListInbounds,
cmdListOutbounds,
cmdAddInboundUsers,
cmdRemoveInboundUsers,
cmdInboundUser, cmdInboundUser,
cmdInboundUserCount, cmdInboundUserCount,
cmdAddRules, cmdAddRules,

View File

@@ -0,0 +1,144 @@
package api
import (
"context"
"fmt"
"github.com/xtls/xray-core/common/protocol"
handlerService "github.com/xtls/xray-core/app/proxyman/command"
cserial "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/infra/conf/serial"
"github.com/xtls/xray-core/proxy/shadowsocks"
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
"github.com/xtls/xray-core/proxy/trojan"
vlessin "github.com/xtls/xray-core/proxy/vless/inbound"
vmessin "github.com/xtls/xray-core/proxy/vmess/inbound"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdAddInboundUsers = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api adu [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
Short: "Add users to inbounds",
Long: `
Add users to inbounds.
Arguments:
-s, -server
The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
`,
Run: executeAddInboundUsers,
}
func executeAddInboundUsers(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
unnamedArgs := cmd.Flag.Args()
inbs := extractInboundsConfig(unnamedArgs)
conn, ctx, close := dialAPIServer()
defer close()
client := handlerService.NewHandlerServiceClient(conn)
success := 0
for _, inb := range inbs {
success += executeInboundUserAction(ctx, client, inb, addInboundUserAction)
}
fmt.Println("Added", success, "user(s) in total.")
}
func addInboundUserAction(ctx context.Context, client handlerService.HandlerServiceClient, tag string, user *protocol.User) error {
fmt.Println("add user:", user.Email)
_, err := client.AlterInbound(ctx, &handlerService.AlterInboundRequest{
Tag: tag,
Operation: cserial.ToTypedMessage(
&handlerService.AddUserOperation{
User: user,
}),
})
return err
}
func extractInboundUsers(inb *core.InboundHandlerConfig) []*protocol.User {
if inb == nil {
return nil
}
inst, err := inb.ProxySettings.GetInstance()
if err != nil || inst == nil {
fmt.Println("failed to get inbound instance:", err)
return nil
}
switch ty := inst.(type) {
case *vmessin.Config:
return ty.User
case *vlessin.Config:
return ty.Clients
case *trojan.ServerConfig:
return ty.Users
case *shadowsocks.ServerConfig:
return ty.Users
case *shadowsocks_2022.MultiUserServerConfig:
return ty.Users
default:
fmt.Println("unsupported inbound type")
}
return nil
}
func extractInboundsConfig(unnamedArgs []string) []conf.InboundDetourConfig {
ins := make([]conf.InboundDetourConfig, 0)
for _, arg := range unnamedArgs {
r, err := loadArg(arg)
if err != nil {
base.Fatalf("failed to load %s: %s", arg, err)
}
conf, err := serial.DecodeJSONConfig(r)
if err != nil {
base.Fatalf("failed to decode %s: %s", arg, err)
}
ins = append(ins, conf.InboundConfigs...)
}
return ins
}
func executeInboundUserAction(ctx context.Context, client handlerService.HandlerServiceClient, inb conf.InboundDetourConfig, action func(ctx context.Context, client handlerService.HandlerServiceClient, tag string, user *protocol.User) error) int {
success := 0
tag := inb.Tag
if len(tag) < 1 {
return success
}
fmt.Println("processing inbound:", tag)
built, err := inb.Build()
if err != nil {
fmt.Println("failed to build config:", err)
return success
}
users := extractInboundUsers(built)
if users == nil {
return success
}
for _, user := range users {
if len(user.Email) < 1 {
continue
}
if err := action(ctx, client, inb.Tag, user); err == nil {
fmt.Println("result: ok")
success += 1
} else {
fmt.Println(err)
}
}
return success
}

View File

@@ -0,0 +1,62 @@
package api
import (
"fmt"
handlerService "github.com/xtls/xray-core/app/proxyman/command"
cserial "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdRemoveInboundUsers = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api rmu [--server=127.0.0.1:8080] -tag=tag <email1> [email2]...",
Short: "Remove users from inbounds",
Long: `
Remove users from inbounds.
Arguments:
-s, -server
The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3
-tag
Inbound tag
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="vless-in" "xray@love.com" ...
`,
Run: executeRemoveUsers,
}
func executeRemoveUsers(cmd *base.Command, args []string) {
setSharedFlags(cmd)
var tag string
cmd.Flag.StringVar(&tag, "tag", "", "")
cmd.Flag.Parse(args)
emails := cmd.Flag.Args()
if len(tag) < 1 {
base.Fatalf("inbound tag not specified")
}
conn, ctx, close := dialAPIServer()
defer close()
client := handlerService.NewHandlerServiceClient(conn)
success := 0
for _, email := range emails {
fmt.Println("remove user:", email)
_, err := client.AlterInbound(ctx, &handlerService.AlterInboundRequest{
Tag: tag,
Operation: cserial.ToTypedMessage(
&handlerService.RemoveUserOperation{
Email: email,
}),
})
if err == nil {
success += 1
} else {
fmt.Println(err)
}
}
fmt.Println("Removed", success, "user(s) in total.")
}

View File

@@ -0,0 +1,47 @@
package api
import (
handlerService "github.com/xtls/xray-core/app/proxyman/command"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdListInbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api lsi [--server=127.0.0.1:8080] [--isOnlyTags=true]",
Short: "List inbounds",
Long: `
List inbounds in Xray.
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: executeListInbounds,
}
func executeListInbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
var isOnlyTagsStr string
cmd.Flag.StringVar(&isOnlyTagsStr, "isOnlyTags", "", "")
cmd.Flag.Parse(args)
isOnlyTags := isOnlyTagsStr == "true"
conn, ctx, close := dialAPIServer()
defer close()
client := handlerService.NewHandlerServiceClient(conn)
resp, err := client.ListInbounds(ctx, &handlerService.ListInboundsRequest{IsOnlyTags: isOnlyTags})
if err != nil {
base.Fatalf("failed to list inbounds: %s", err)
}
showJSONResponse(resp)
}

View File

@@ -0,0 +1,43 @@
package api
import (
handlerService "github.com/xtls/xray-core/app/proxyman/command"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdListOutbounds = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api lso [--server=127.0.0.1:8080]",
Short: "List outbounds",
Long: `
List outbounds in Xray.
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: executeListOutbounds,
}
func executeListOutbounds(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
conn, ctx, close := dialAPIServer()
defer close()
client := handlerService.NewHandlerServiceClient(conn)
resp, err := client.ListOutbounds(ctx, &handlerService.ListOutboundsRequest{})
if err != nil {
base.Fatalf("failed to list outbounds: %s", err)
}
showJSONResponse(resp)
}

View File

@@ -16,5 +16,6 @@ func init() {
cmdUUID, cmdUUID,
cmdX25519, cmdX25519,
cmdWG, cmdWG,
cmdMLDSA65,
) )
} }

View File

@@ -0,0 +1,42 @@
package all
import (
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdMLDSA65 = &base.Command{
UsageLine: `{{.Exec}} mldsa65 [-i "seed (base64.RawURLEncoding)"]`,
Short: `Generate key pair for ML-DSA-65 post-quantum signature`,
Long: `
Generate key pair for ML-DSA-65 post-quantum signature.
Random: {{.Exec}} mldsa65
From seed: {{.Exec}} mldsa65 -i "seed (base64.RawURLEncoding)"
`,
}
func init() {
cmdMLDSA65.Run = executeMLDSA65 // break init loop
}
var input_seed = cmdMLDSA65.Flag.String("i", "", "")
func executeMLDSA65(cmd *base.Command, args []string) {
var seed [32]byte
if len(*input_seed) > 0 {
s, _ := base64.RawURLEncoding.DecodeString(*input_seed)
seed = [32]byte(s)
} else {
rand.Read(seed[:])
}
pub, _ := mldsa65.NewKeyFromSeed(&seed)
fmt.Printf("Seed: %v\nVerify: %v",
base64.RawURLEncoding.EncodeToString(seed[:]),
base64.RawURLEncoding.EncodeToString(pub.Bytes()))
}

View File

@@ -1,25 +1,26 @@
package tls package tls
import ( import (
"encoding/json" "encoding/base64"
"encoding/pem" "encoding/pem"
"os" "os"
"strings"
"github.com/OmarTariq612/goech" "github.com/xtls/reality/hpke"
"github.com/cloudflare/circl/hpke"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/main/commands/base" "github.com/xtls/xray-core/main/commands/base"
"github.com/xtls/xray-core/transport/internet/tls"
"golang.org/x/crypto/cryptobyte"
) )
var cmdECH = &base.Command{ var cmdECH = &base.Command{
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--json]`, UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--pem] [-i "ECHServerKeys (base64.StdEncoding)"]`,
Short: `Generate TLS-ECH certificates`, Short: `Generate TLS-ECH certificates`,
Long: ` Long: `
Generate TLS-ECH certificates. Generate TLS-ECH certificates.
Set serverName to your custom string: {{.Exec}} tls ech --serverName (string) Set serverName to your custom string: {{.Exec}} tls ech --serverName (string)
Generate into json format: {{.Exec}} tls ech --json Generate into pem format: {{.Exec}} tls ech --pem
Restore ECHConfigs from ECHServerKeys: {{.Exec}} tls ech -i "ECHServerKeys (base64.StdEncoding)"
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled `, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
} }
@@ -27,43 +28,66 @@ func init() {
cmdECH.Run = executeECH cmdECH.Run = executeECH
} }
var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "") var input_echServerKeys = cmdECH.Flag.String("i", "", "ECHServerKeys (base64.StdEncoding)")
// var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "") var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
var input_json = cmdECH.Flag.Bool("json", false, "True == turn on json output") var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
func executeECH(cmd *base.Command, args []string) { func executeECH(cmd *base.Command, args []string) {
var kem hpke.KEM var kem uint16
if *input_pqSignatureSchemesEnabled { // if *input_pqSignatureSchemesEnabled {
kem = hpke.KEM_X25519_KYBER768_DRAFT00 // kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00
} else { // } else {
kem = hpke.KEM_X25519_HKDF_SHA256 kem = hpke.DHKEM_X25519_HKDF_SHA256
} // }
echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem) echConfig, priv, err := tls.GenerateECHKeySet(0, *input_serverName, kem)
common.Must(err) common.Must(err)
configBuffer, _ := echKeySet.ECHConfig.MarshalBinary() var configBuffer, keyBuffer []byte
keyBuffer, _ := echKeySet.MarshalBinary() if *input_echServerKeys == "" {
configBytes, _ := tls.MarshalBinary(echConfig)
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer})) var b cryptobyte.Builder
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer})) b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
if *input_json { child.AddBytes(configBytes)
jECHConfigs := map[string]interface{}{ })
"configs": strings.Split(strings.TrimSpace(string(configPEM)), "\n"), configBuffer, _ = b.Bytes()
} var b2 cryptobyte.Builder
jECHKey := map[string]interface{}{ b2.AddUint16(uint16(len(priv)))
"key": strings.Split(strings.TrimSpace(string(keyPEM)), "\n"), b2.AddBytes(priv)
} b2.AddUint16(uint16(len(configBytes)))
b2.AddBytes(configBytes)
for _, i := range []map[string]interface{}{jECHConfigs, jECHKey} { keyBuffer, _ = b2.Bytes()
content, err := json.MarshalIndent(i, "", " ")
common.Must(err)
os.Stdout.Write(content)
os.Stdout.WriteString("\n")
}
} else { } else {
keySetsByte, err := base64.StdEncoding.DecodeString(*input_echServerKeys)
if err != nil {
os.Stdout.WriteString("Failed to decode ECHServerKeys: " + err.Error() + "\n")
return
}
keyBuffer = keySetsByte
KeySets, err := tls.ConvertToGoECHKeys(keySetsByte)
if err != nil {
os.Stdout.WriteString("Failed to decode ECHServerKeys: " + err.Error() + "\n")
return
}
var b cryptobyte.Builder
for _, keySet := range KeySets {
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
child.AddBytes(keySet.Config)
})
}
configBuffer, _ = b.Bytes()
}
if *input_pem {
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
os.Stdout.WriteString(configPEM) os.Stdout.WriteString(configPEM)
os.Stdout.WriteString(keyPEM) os.Stdout.WriteString(keyPEM)
} else {
os.Stdout.WriteString("ECH config list: \n" + base64.StdEncoding.EncodeToString(configBuffer) + "\n")
os.Stdout.WriteString("ECH server keys: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n")
} }
} }

View File

@@ -6,6 +6,9 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net" "net"
"reflect"
"strconv"
"unsafe"
"github.com/xtls/xray-core/main/commands/base" "github.com/xtls/xray-core/main/commands/base"
. "github.com/xtls/xray-core/transport/internet/tls" . "github.com/xtls/xray-core/transport/internet/tls"
@@ -36,8 +39,15 @@ func executePing(cmd *base.Command, args []string) {
base.Fatalf("domain not specified") base.Fatalf("domain not specified")
} }
domain := cmdPing.Flag.Arg(0) domainWithPort := cmdPing.Flag.Arg(0)
fmt.Println("Tls ping: ", domain) fmt.Println("TLS ping: ", domainWithPort)
TargetPort := 443
domain, port, err := net.SplitHostPort(domainWithPort)
if err != nil {
domain = domainWithPort
} else {
TargetPort, _ = strconv.Atoi(port)
}
var ip net.IP var ip net.IP
if len(*pingIPStr) > 0 { if len(*pingIPStr) > 0 {
@@ -53,19 +63,19 @@ func executePing(cmd *base.Command, args []string) {
} }
ip = v.IP ip = v.IP
} }
fmt.Println("Using IP: ", ip.String()) fmt.Println("Using IP: ", ip.String()+":"+strconv.Itoa(TargetPort))
fmt.Println("-------------------") fmt.Println("-------------------")
fmt.Println("Pinging without SNI") fmt.Println("Pinging without SNI")
{ {
tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: 443}) tcpConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ip, Port: TargetPort})
if err != nil { if err != nil {
base.Fatalf("Failed to dial tcp: %s", err) base.Fatalf("Failed to dial tcp: %s", err)
} }
tlsConn := gotls.Client(tcpConn, &gotls.Config{ tlsConn := gotls.Client(tcpConn, &gotls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
NextProtos: []string{"http/1.1"}, NextProtos: []string{"h2", "http/1.1"},
MaxVersion: gotls.VersionTLS12, MaxVersion: gotls.VersionTLS13,
MinVersion: gotls.VersionTLS12, MinVersion: gotls.VersionTLS12,
// Do not release tool before v5's refactor // Do not release tool before v5's refactor
// VerifyPeerCertificate: showCert(), // VerifyPeerCertificate: showCert(),
@@ -75,6 +85,7 @@ func executePing(cmd *base.Command, args []string) {
fmt.Println("Handshake failure: ", err) fmt.Println("Handshake failure: ", err)
} else { } else {
fmt.Println("Handshake succeeded") fmt.Println("Handshake succeeded")
printTLSConnDetail(tlsConn)
printCertificates(tlsConn.ConnectionState().PeerCertificates) printCertificates(tlsConn.ConnectionState().PeerCertificates)
} }
tlsConn.Close() tlsConn.Close()
@@ -89,31 +100,58 @@ func executePing(cmd *base.Command, args []string) {
} }
tlsConn := gotls.Client(tcpConn, &gotls.Config{ tlsConn := gotls.Client(tcpConn, &gotls.Config{
ServerName: domain, ServerName: domain,
NextProtos: []string{"http/1.1"}, NextProtos: []string{"h2", "http/1.1"},
MaxVersion: gotls.VersionTLS12, MaxVersion: gotls.VersionTLS13,
MinVersion: gotls.VersionTLS12, MinVersion: gotls.VersionTLS12,
// Do not release tool before v5's refactor // Do not release tool before v5's refactor
// VerifyPeerCertificate: showCert(), // VerifyPeerCertificate: showCert(),
}) })
err = tlsConn.Handshake() err = tlsConn.Handshake()
if err != nil { if err != nil {
fmt.Println("handshake failure: ", err) fmt.Println("Handshake failure: ", err)
} else { } else {
fmt.Println("handshake succeeded") fmt.Println("Handshake succeeded")
printTLSConnDetail(tlsConn)
printCertificates(tlsConn.ConnectionState().PeerCertificates) printCertificates(tlsConn.ConnectionState().PeerCertificates)
} }
tlsConn.Close() tlsConn.Close()
} }
fmt.Println("Tls ping finished") fmt.Println("-------------------")
fmt.Println("TLS ping finished")
} }
func printCertificates(certs []*x509.Certificate) { func printCertificates(certs []*x509.Certificate) {
var leaf *x509.Certificate
var length int
for _, cert := range certs { for _, cert := range certs {
if len(cert.DNSNames) == 0 { length += len(cert.Raw)
continue if len(cert.DNSNames) != 0 {
leaf = cert
} }
fmt.Println("Allowed domains: ", cert.DNSNames) }
fmt.Println("Certificate chain's total length: ", length, "(certs count: "+strconv.Itoa(len(certs))+")")
if leaf != nil {
fmt.Println("Cert's signature algorithm: ", leaf.SignatureAlgorithm.String())
fmt.Println("Cert's publicKey algorithm: ", leaf.PublicKeyAlgorithm.String())
fmt.Println("Cert's allowed domains: ", leaf.DNSNames)
}
}
func printTLSConnDetail(tlsConn *gotls.Conn) {
var tlsVersion string
if tlsConn.ConnectionState().Version == gotls.VersionTLS13 {
tlsVersion = "TLS 1.3"
} else if tlsConn.ConnectionState().Version == gotls.VersionTLS12 {
tlsVersion = "TLS 1.2"
}
fmt.Println("TLS Version: ", tlsVersion)
curveID := *(*gotls.CurveID)(unsafe.Pointer(reflect.ValueOf(tlsConn).Elem().FieldByName("curveID").UnsafeAddr()))
if curveID != 0 {
PostQuantum := (curveID == gotls.X25519MLKEM768)
fmt.Println("TLS Post-Quantum key exchange: ", PostQuantum, "("+curveID.String()+")")
} else {
fmt.Println("TLS Post-Quantum key exchange: false (RSA Exchange)")
} }
} }

View File

@@ -2,6 +2,7 @@ package dns
import ( import (
"context" "context"
go_errors "errors"
"io" "io"
"sync" "sync"
"time" "time"
@@ -186,6 +187,9 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
if len(h.blockTypes) > 0 { if len(h.blockTypes) > 0 {
for _, blocktype := range h.blockTypes { for _, blocktype := range h.blockTypes {
if blocktype == int32(qType) { if blocktype == int32(qType) {
if h.nonIPQuery == "reject" {
go h.rejectNonIPQuery(id, qType, domain, writer)
}
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain) errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
return nil return nil
} }
@@ -198,6 +202,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
b.Release() b.Release()
continue continue
} }
if h.nonIPQuery == "reject" {
go h.rejectNonIPQuery(id, qType, domain, writer)
b.Release()
continue
}
} }
if err := connWriter.WriteMessage(b); err != nil { if err := connWriter.WriteMessage(b); err != nil {
@@ -255,7 +264,7 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
} }
rcode := dns.RCodeFromError(err) rcode := dns.RCodeFromError(err)
if rcode == 0 && len(ips) == 0 && !errors.AllEqual(dns.ErrEmptyResponse, errors.Cause(err)) { if rcode == 0 && len(ips) == 0 && !go_errors.Is(err, dns.ErrEmptyResponse) {
errors.LogInfoInner(context.Background(), err, "ip query") errors.LogInfoInner(context.Background(), err, "ip query")
return return
} }
@@ -316,6 +325,43 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
} }
} }
func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) {
b := buf.New()
rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
ID: id,
RCode: dnsmessage.RCodeRefused,
RecursionAvailable: true,
RecursionDesired: true,
Response: true,
Authoritative: true,
})
builder.EnableCompression()
common.Must(builder.StartQuestions())
err := builder.Question(dnsmessage.Question{
Name: dnsmessage.MustNewName(domain),
Class: dnsmessage.ClassINET,
Type: qType,
})
if err != nil {
errors.LogInfo(context.Background(), "unexpected domain ", domain, " when building reject message: ", err)
b.Release()
return
}
msgBytes, err := builder.Finish()
if err != nil {
errors.LogInfoInner(context.Background(), err, "pack reject message")
b.Release()
return
}
b.Resize(0, int32(len(msgBytes)))
if err := writer.WriteMessage(b); err != nil {
errors.LogInfoInner(context.Background(), err, "write reject answer")
}
}
type outboundConn struct { type outboundConn struct {
access sync.Mutex access sync.Mutex
dialer func() (stat.Connection, error) dialer func() (stat.Connection, error)

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