Compare commits

...

78 Commits

Author SHA1 Message Date
RPRX
ab5d7cf3d2 v25.3.31
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-03-31 12:33:38 +00:00
xqzr
2abeda9c42 Sockopt: Allow listen v6only work for Windows & Darwin (#4571)
Completes https://github.com/XTLS/Xray-core/pull/1677

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-03-31 12:22:16 +00:00
92613hjh
63eb0539b3 Sockopt: Fix Darwin (macOS, iOS...) UDP interface bind (#4530)
Fixes https://github.com/XTLS/Xray-core/issues/4007
2025-03-31 11:19:06 +00:00
RPRX
8284a0ef8f Dialer: Do not use ListenSystemPacket() when dialing UDP
https://github.com/XTLS/Xray-core/pull/4530#issuecomment-2765744961
2025-03-31 10:09:33 +00:00
Ilya Gulya
17207fc5e4 WireGuard: Improve config error handling; Prevent panic in case of errors during server initialization (#4566)
https://github.com/XTLS/Xray-core/pull/4566#issuecomment-2764779273
2025-03-30 22:55:21 +00:00
xqzr
52a2c63682 Sockopt: Fix Windows Multicast interface bind (#4568)
https://github.com/XTLS/Xray-core/pull/4568#issuecomment-2763492336
2025-03-30 20:51:36 +00:00
patterniha
1685c61e44 DNS: Use cache for NXDOMAIN (rcode 3 error) (#4560)
https://github.com/XTLS/Xray-core/pull/4558#issuecomment-2759007356
2025-03-29 14:29:48 +00:00
Pk-web6936
681e943848 Chore: Optimize .gitignore (#4564) 2025-03-29 13:46:26 +00:00
dependabot[bot]
8530bdb0de Bump golang.org/x/net from 0.37.0 to 0.38.0 (#4561)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.37.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.37.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  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-03-29 13:42:20 +00:00
风扇滑翔翼
29e37e8a82 HTTP inbound: Directly forward plain HTTP 1xx response header (#4547)
Fixes https://github.com/XTLS/Xray-core/issues/4545
2025-03-26 12:30:48 +00:00
dependabot[bot]
e129b1e90d Bump google.golang.org/protobuf from 1.36.5 to 1.36.6 (#4549)
Bumps google.golang.org/protobuf from 1.36.5 to 1.36.6.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  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-03-26 12:20:17 +00:00
patterniha
5922caff89 DNS: Add expectedIPs as an alias of expectIPs (#4551)
https://github.com/XTLS/Xray-core/discussions/1903#discussioncomment-5543921
2025-03-26 12:18:58 +00:00
Meo597
4afe2d0cff DNS: Support returning upstream TTL to clients (#4526)
Closes https://github.com/XTLS/Xray-core/issues/4527
2025-03-24 13:33:56 +00:00
patterniha
2d3210e4b8 Env: Add XRAY_LOCATION_CERT variable (#4536)
https://github.com/XTLS/Xray-core/issues/4531#issuecomment-2746155941

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

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-03-24 13:02:35 +00:00
dependabot[bot]
673a9ae063 Bump github.com/quic-go/quic-go from 0.50.0 to 0.50.1 (#4539)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.50.0 to 0.50.1.
- [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.0...v0.50.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  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-03-24 09:49:58 +00:00
RPRX
607c2a6d31 DNS DoH: Use EDNS0 with 100-300 padding by default (body padding)
https://github.com/XTLS/Xray-core/pull/4516#issuecomment-2744093003
2025-03-21 20:13:09 +00:00
风扇滑翔翼
b585b26f29 Sockopt: Fix Windows UDP interface bind; Allow customSockopt work for UDP (#4504) 2025-03-21 10:48:46 +00:00
patterniha
1067171e6a DNS: Add timeoutMs for DnsServerObject (#4523) 2025-03-21 10:24:58 +00:00
风扇滑翔翼
86a225cda1 DNS: Retry with EDNS0 when response is truncated (#4516) 2025-03-21 08:58:51 +00:00
风扇滑翔翼
6f8e253dec DNS: Add tag for DnsServerObject (#4515)
Closes https://github.com/XTLS/Xray-core/issues/4505
2025-03-21 08:58:24 +00:00
patterniha
6a211a0bb9 DNS: Add allowUnexpectedIPs for DnsServerObject (#4497)
Closes https://github.com/XTLS/Xray-core/issues/4424
2025-03-20 12:09:02 +00:00
风扇滑翔翼
335845a9b2 DNS: Ensure order for DNS server match (#4510)
Fixes https://github.com/XTLS/Xray-core/issues/4508
2025-03-20 11:21:21 +00:00
风扇滑翔翼
b70d0abebe API: Fix data race in online ipList (#4513)
Fixes https://github.com/XTLS/Xray-core/issues/4512
2025-03-20 11:12:36 +00:00
dependabot[bot]
3d21128159 Bump github.com/miekg/dns from 1.1.63 to 1.1.64 (#4506)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.63 to 1.1.64.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.63...v1.1.64)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  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-03-20 11:06:51 +00:00
Nikita Korotaev
eca99771ca README.md: Add Remnawave to Web Panels (#4498) 2025-03-20 10:50:43 +00:00
RPRX
2cba2c4d59 v25.3.6
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-03-06 13:50:15 +00:00
Happ-dev
306fa51475 README.md: Add Happ to iOS clients (#4465) 2025-03-06 12:27:25 +00:00
dependabot[bot]
6d6f1c6967 Bump golang.org/x/net from 0.36.0 to 0.37.0 (#4469)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.37.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.37.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  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-03-06 10:59:49 +00:00
dependabot[bot]
152959824f Bump google.golang.org/grpc from 1.70.0 to 1.71.0 (#4463)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.70.0 to 1.71.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.70.0...v1.71.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  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-03-05 09:23:38 +00:00
dependabot[bot]
a977b6357e Bump golang.org/x/net from 0.35.0 to 0.36.0 (#4462)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  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-03-05 09:23:31 +00:00
RPRX
16eee1b89c Mux server: Clone the session.Content attached on ctx as well
https://github.com/XTLS/Xray-core/pull/3908#issuecomment-2696911994

Fixes https://github.com/XTLS/Xray-core/pull/3904 for good
2025-03-04 11:01:03 +00:00
RPRX
dde0a4f272 REALITY config: Add password as an alias of publicKey
https://github.com/XTLS/Xray-core/issues/4458#issuecomment-2696181682
2025-03-04 05:29:03 +00:00
RPRX
e15dff94b5 v25.3.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-03-03 15:30:22 +00:00
RPRX
e466b0497c DNS DoH: Use Chrome's fingerprint & keepAlivePeriod, Add header padding by default
https://github.com/XTLS/Xray-core/discussions/4430#discussioncomment-12374292
2025-03-03 14:45:12 +00:00
j3l11234
b9cb93d3c2 Sockopt: Add addressPortStrategy (query SRV or TXT) (#4416)
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2025-03-02 13:07:55 +00:00
RPRX
8d46f7e14c TLS fingerprints: Refine "random" & "randomized", Add "randomizednoalpn"
https://github.com/XTLS/Xray-core/issues/4436#issuecomment-2687801214
2025-03-02 13:02:47 +00:00
A1lo
4b616f5cd0 XHTTP server: Switch to Go 1.24 native h2c support (#4451) 2025-03-02 13:02:40 +00:00
dependabot[bot]
06b4a7ce4d Bump lukechampine.com/blake3 from 1.3.0 to 1.4.0 (#4439)
Bumps [lukechampine.com/blake3](https://github.com/lukechampine/blake3) from 1.3.0 to 1.4.0.
- [Commits](https://github.com/lukechampine/blake3/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: lukechampine.com/blake3
  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-02-25 13:26:54 +00:00
dependabot[bot]
4c12e1686b Bump golang.org/x/crypto from 0.33.0 to 0.35.0 (#4438)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.33.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.33.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  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-02-25 13:26:32 +00:00
dependabot[bot]
225d151cd3 Bump github.com/google/go-cmp from 0.6.0 to 0.7.0 (#4433)
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  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-02-25 13:26:00 +00:00
风扇滑翔翼
d451078e72 Chore: Fix tests (#4440) 2025-02-25 13:11:37 +00:00
RPRX
ce2384cccc Use Go 1.24 2025-02-25 11:35:25 +00:00
RPRX
be43f66b63 v25.2.21
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-02-21 07:58:31 +00:00
dependabot[bot]
71a6d89c23 Bump github.com/quic-go/quic-go from 0.49.0 to 0.50.0 (#4420)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.49.0 to 0.50.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.49.0...v0.50.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  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-02-21 06:34:33 +00:00
lastrise
89792aee9d Outbound: Add outbound sendThrough origin behavior (#4349)
* added support of sending through origin for outbounds

* added strings package import

* usage of net.SplitHostPort instead of manual splitting

---------

Co-authored-by: poly <poly@>
2025-02-20 15:15:59 -05:00
RPRX
b786a50aee XHTTP server: Fix stream-up "single POST problem", Use united httpServerConn instead of recover()
https://github.com/XTLS/Xray-core/issues/4373#issuecomment-2671795675

https://github.com/XTLS/Xray-core/issues/4406#issuecomment-2668041926
2025-02-20 16:28:06 +00:00
风扇滑翔翼
b38a53e629 UDS: Use UnixListenerWrapper & UnixConnWrapper (#4413)
Fixes https://github.com/XTLS/Xray-core/issues/4411

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-02-19 11:31:29 +00:00
RPRX
52381a3c03 v25.2.18
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-02-18 11:55:07 +00:00
风扇滑翔翼
4b01eb4398 Metrics: Add direct listen (#4409) 2025-02-18 11:32:48 +00:00
RPRX
c5de08bea6 XHTTP client: Revert "Add back minimal path padding for compatibility"
Reverts efdc70fbf7
2025-02-18 11:11:36 +00:00
RPRX
8cb63db6c0 XHTTP server: Set remoteAddr & localAddr correctly
Completes 22c50a70c6
2025-02-18 10:50:50 +00:00
yuhan6665
eef74b2c7d XTLS: More separate uplink/downlink flags for splice copy (#4407)
- In 03131c72db new flags were added for uplink/downlink, but that was not suffcient
- Now that the traffic state contains all possible info
- Each inbound and outbound is responsible to set their own CanSpliceCopy flag. Note that this also open up more splice usage. E.g. socks in -> freedom out
- Fixes https://github.com/XTLS/Xray-core/issues/4033
2025-02-18 08:37:52 +00:00
Bill Zhong
a1714cc4ce API: Improve cli usage descriptions (#4401) 2025-02-18 08:36:39 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇
958b13ebb5 Build: End of the easily mistaken 'Makefile' (#4395)
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-02-18 08:33:05 +00:00
风扇滑翔翼
22c50a70c6 UDS: Make all remote addr 0.0.0.0 (#4390)
https://github.com/XTLS/Xray-core/pull/4389#issuecomment-2656360673

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2025-02-13 14:01:33 +00:00
yiguous
94c7970fd6 Config: Correctly marshal PortList and NameServerConfig to JSON (#4386) 2025-02-12 14:55:16 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇
a71762b5da Workflows: Fix Actions' manual dispatch for assets update (#4378) 2025-02-11 13:19:03 +00:00
dependabot[bot]
5033cbceea Bump golang.org/x/net from 0.34.0 to 0.35.0 (#4382)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.34.0 to 0.35.0.
- [Commits](https://github.com/golang/net/compare/v0.34.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  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-02-11 13:14:22 +00:00
RPRX
dcd7e92c45 XHTTP server: Finish stream-up's HTTP POST when its request.Body is closed
https://github.com/XTLS/Xray-core/issues/4373#issuecomment-2647908310

Fixes https://github.com/XTLS/Xray-core/issues/4373
2025-02-10 13:56:13 +00:00
dependabot[bot]
2d7ca4a6a6 Bump golang.org/x/crypto from 0.32.0 to 0.33.0 (#4375)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.32.0 to 0.33.0.
- [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  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-02-10 08:30:42 +00:00
RPRX
925a985cc0 Commands: Use ".crt" & ".key" suffixes when generating TLS certificates
https://github.com/XTLS/Xray-core/issues/4313#issuecomment-2645844058
2025-02-08 18:29:54 +00:00
RPRX
613c63b165 DNS DoH h2c Remote: Add verifyPeerCertInNames "fromMitm" support
https://github.com/XTLS/Xray-core/issues/4313#issuecomment-2645838663
2025-02-08 18:05:41 +00:00
RPRX
d4c7cd02fd MITM freedom RAW TLS: Allow "fromMitm" to be written at any position in verifyPeerCertInNames, Add checking for alpn "fromMitm"
https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2643340434
2025-02-08 12:11:25 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇
db5f18b98c Workflows: Reduce Geodata update frequency (#4369) 2025-02-08 08:07:46 +00:00
RPRX
c81d8e488a Geofiles: Switch to Loyalsoldier's v2ray-rules-dat
https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2643351198
2025-02-08 04:47:43 +00:00
Daniel Lavrushin
1d9e6bc2f3 README.md: Add XrayUI to Asuswrt-Merlin clients (#4355) 2025-02-07 13:57:52 +00:00
Artur Melanchyk
ae327eb7e6 Chore: Make some Maps into real Sets (#4362) 2025-02-07 13:48:33 +00:00
Maxim Plotnikov
e893fa1828 API: Add user IPs and access times tracking (#4360) 2025-02-07 12:19:47 +00:00
dependabot[bot]
1982c2366e Bump google.golang.org/protobuf from 1.36.4 to 1.36.5 (#4363)
Bumps google.golang.org/protobuf from 1.36.4 to 1.36.5.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  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-02-07 10:09:50 +00:00
RPRX
117de1fd3c MITM freedom RAW TLS: Report website with unexpected Negotiated Protocol / invalid Domain Fronting certificate
https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2639965524

Needs `"alpn": ["fromMitm"]` / `"verifyPeerCertInNames": ["fromMitm", ...]`.
2025-02-07 08:15:40 +00:00
dependabot[bot]
07c35ed52a Bump github.com/cloudflare/circl from 1.5.0 to 1.6.0 (#4352)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  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-02-06 11:31:13 +00:00
dependabot[bot]
e17c068821 Bump golang.org/x/sync from 0.10.0 to 0.11.0 (#4351)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/sync/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  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-02-06 11:30:58 +00:00
dependabot[bot]
88d40d6367 Bump golang.org/x/sys from 0.29.0 to 0.30.0 (#4350)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.29.0 to 0.30.0.
- [Commits](https://github.com/golang/sys/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  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-02-06 11:28:02 +00:00
RPRX
527caa3711 Log: Add microseconds for golang's standard logger
Completes 5679d717ee
2025-02-06 07:50:48 +00:00
RPRX
c6a31f457c MITM: Allow using local received SNI in the outgoing serverName & verifyPeerCertInNames
https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2637370175

Local received SNI was sent by browser/app.

In freedom RAW's `tlsSettings`, set `"serverName": "fromMitm"` to forward it to the real website.

In freedom RAW's `tlsSettings`, set `"verifyPeerCertInNames": ["fromMitm"]` to use all possible names to verify the certificate.
2025-02-06 07:37:30 +00:00
RPRX
9b7841178a MITM: Allow forwarding local negotiated ALPN http/1.1 to the real website
https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2633656408

https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2633865039

Local negotiated ALPN http/1.1 was sent by browser/app or is written in dokodemo-door RAW's `tlsSettings`.

Set `"alpn": ["fromMitm"]` in freedom RAW's `tlsSettings` to forward it to the real website.
2025-02-04 15:10:08 +00:00
RPRX
480c7d7db7 README.md: Add Project XHTTP (Persian) to Telegram
https://t.me/projectXhttp
2025-02-01 16:51:28 +00:00
auvred
c2f6c89987 Commands: Fix ambiguous printing of private x25519 key (#4343) 2025-02-01 14:45:34 +00:00
122 changed files with 2421 additions and 1431 deletions

View File

@@ -1,11 +1,5 @@
name: Build and Release for Windows 7 name: Build and Release for Windows 7
# NOTE: This Github Actions file depends on the Makefile.
# Building the correct package requires the correct binaries generated by the Makefile. To
# ensure the correct output, the Makefile must accept the appropriate input and compile the
# correct file with the correct name. If you need to modify this file, please ensure it won't
# disrupt the Makefile.
on: on:
workflow_dispatch: workflow_dispatch:
release: release:
@@ -37,6 +31,9 @@ jobs:
GOARCH: ${{ matrix.goarch }} GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase
uses: actions/checkout@v4
- name: Show workflow information - name: Show workflow information
run: | run: |
_NAME=${{ matrix.assetname }} _NAME=${{ matrix.assetname }}
@@ -46,18 +43,17 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: stable go-version-file: go.mod
check-latest: true check-latest: true
- name: Setup patched builder - name: Setup patched builder
run: | run: |
GOSDK=$(go env GOROOT) GOSDK=$(go env GOROOT)
curl -O -L https://github.com/XTLS/go-win7/releases/latest/download/go-for-win7-linux-amd64.zip
rm -r $GOSDK/* rm -r $GOSDK/*
cd $GOSDK
curl -O -L 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
- name: Checkout codebase
uses: actions/checkout@v4
- name: Get project dependencies - name: Get project dependencies
run: go mod download run: go mod download
@@ -65,8 +61,13 @@ jobs:
- name: Build Xray - name: Build Xray
run: | run: |
mkdir -p build_assets mkdir -p build_assets
make COMMID=$(git describe --always --dirty)
find . -maxdepth 1 -type f -regex './\(wxray\|xray\).exe' -exec mv {} ./build_assets/ \; echo 'Building Xray for Windows 7...'
go build -o build_assets/xray.exe -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4

View File

@@ -1,11 +1,5 @@
name: Build and Release name: Build and Release
# NOTE: This Github Actions file depends on the Makefile.
# Building the correct package requires the correct binaries generated by the Makefile. To
# ensure the correct output, the Makefile must accept the appropriate input and compile the
# correct file with the correct name. If you need to modify this file, please ensure it won't
# disrupt the Makefile.
on: on:
workflow_dispatch: workflow_dispatch:
release: release:
@@ -129,8 +123,22 @@ jobs:
- name: Build Xray - name: Build Xray
run: | run: |
mkdir -p build_assets mkdir -p build_assets
make COMMID=$(git describe --always --dirty)
find . -maxdepth 1 -type f -regex './\(wxray\|xray\|xray_softfloat\)\(\|.exe\)' -exec mv {} ./build_assets/ \; if [[ ${GOOS} == 'windows' ]]; then
echo 'Building Xray for Windows...'
go build -o build_assets/xray.exe -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
else
echo 'Building Xray...'
go build -o build_assets/xray -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
echo 'Building soft-float Xray for MIPS/MIPSLE 32-bit...'
GOMIPS=softfloat go build -o build_assets/xray_softfloat -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
fi
fi
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4

View File

@@ -1,4 +1,4 @@
name: Timely 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.
@@ -8,19 +8,20 @@ name: Timely assets update
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
# Update assets on every hour (xx:30) # Update GeoData on every day (22:30 UTC)
- cron: '30 * * * *' - cron: '30 22 * * *'
push: push:
# Prevent triggering update request storm # Prevent triggering update request storm
paths: paths:
- ".github/workflows/hourly-prepare.yml" - ".github/workflows/scheduled-assets-update.yml"
pull_request: pull_request:
# Prevent triggering update request storm # Prevent triggering update request storm
paths: paths:
- ".github/workflows/hourly-prepare.yml" - ".github/workflows/scheduled-assets-update.yml"
jobs: jobs:
geodat: geodat:
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push'|| github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
@@ -38,18 +39,18 @@ jobs:
max_attempts: 60 max_attempts: 60
command: | command: |
[ -d 'resources' ] || mkdir resources [ -d 'resources' ] || mkdir resources
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite') LIST=('Loyalsoldier v2ray-rules-dat geoip geoip' 'Loyalsoldier v2ray-rules-dat geosite geosite')
for i in "${LIST[@]}" for i in "${LIST[@]}"
do do
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}')) INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
FILE_NAME="${INFO[2]}.dat" FILE_NAME="${INFO[3]}.dat"
echo -e "Verifying HASH key..." echo -e "Verifying HASH key..."
HASH="$(curl -sL "https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')" HASH="$(curl -sL "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/v2fly/${INFO[0]}/release/${INFO[1]}.dat..." echo -e "Downloading https://raw.githubusercontent.com/${INFO[0]}/${INFO[1]}/release/${INFO[2]}.dat..."
curl -L "https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat" -o ./resources/${FILE_NAME} curl -L "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

17
.gitignore vendored
View File

@@ -14,10 +14,18 @@
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
# macOS specific files
*.DS_Store *.DS_Store
.idea
# IDE specific files
.idea/
.vscode/
# Archive files
*.zip *.zip
*.tar.gz *.tar.gz
# Binaries
xray xray
xray_softfloat xray_softfloat
mockgen mockgen
@@ -26,8 +34,13 @@ vprotogen
errorgen errorgen
!common/errors/errorgen/ !common/errors/errorgen/
*.dat *.dat
.vscode
# Build assets
/build_assets /build_assets
# Output from dlv test # Output from dlv test
**/debug.* **/debug.*
# Certificates
*.crt
*.key

View File

@@ -1,37 +0,0 @@
NAME = xray
VERSION=$(shell git describe --always --dirty)
# NOTE: This MAKEFILE can be used to build Xray-core locally and in Automatic workflows. It is \
provided for convenience in automatic building and functions as a part of it.
# NOTE: If you need to modify this file, please be aware that:\
- This file is not the main Makefile; it only accepts environment variables and builds the \
binary.\
- Automatic building expects the correct binaries to be built by this Makefile. If you \
intend to propose a change to this Makefile, carefully review the file below and ensure \
that the change will not accidentally break the automatic building:\
.github/workflows/release.yml \
Otherwise it is recommended to contact the project maintainers.
LDFLAGS = -X github.com/xtls/xray-core/core.build=$(VERSION) -s -w -buildid=
PARAMS = -trimpath -ldflags "$(LDFLAGS)" -v
MAIN = ./main
PREFIX ?= $(shell go env GOPATH)
ifeq ($(GOOS),windows)
OUTPUT = $(NAME).exe
ADDITION = go build -o w$(NAME).exe -trimpath -ldflags "-H windowsgui $(LDFLAGS)" -v $(MAIN)
else
OUTPUT = $(NAME)
endif
ifeq ($(shell echo "$(GOARCH)" | grep -Eq "(mips|mipsle)" && echo true),true) #
ADDITION = GOMIPS=softfloat go build -o $(NAME)_softfloat -trimpath -ldflags "$(LDFLAGS)" -v $(MAIN)
endif
.PHONY: clean build
build:
go build -o $(OUTPUT) $(PARAMS) $(MAIN)
$(ADDITION)
clean:
go clean -v -i $(PWD)
rm -f xray xray.exe wxray.exe xray_softfloat

View File

@@ -24,7 +24,9 @@
[Project X Channel](https://t.me/projectXtls) [Project X Channel](https://t.me/projectXtls)
[Project VLESS](https://t.me/projectVless) (non-Chinese) [Project VLESS](https://t.me/projectVless) (Русский)
[Project XHTTP](https://t.me/projectXhttp) (Persian)
## Installation ## Installation
@@ -36,6 +38,7 @@
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray) - [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker) - [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
- Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:** - Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:**
- [Remnawave](https://github.com/remnawave/panel)
- [Marzban](https://github.com/Gozargah/Marzban) - [Marzban](https://github.com/Gozargah/Marzban)
- [Xray-UI](https://github.com/qist/xray-ui) - [Xray-UI](https://github.com/qist/xray-ui)
- [Hiddify](https://github.com/hiddify/Hiddify-Manager) - [Hiddify](https://github.com/hiddify/Hiddify-Manager)
@@ -72,6 +75,8 @@
- [PassWall](https://github.com/xiaorouji/openwrt-passwall), [PassWall 2](https://github.com/xiaorouji/openwrt-passwall2) - [PassWall](https://github.com/xiaorouji/openwrt-passwall), [PassWall 2](https://github.com/xiaorouji/openwrt-passwall2)
- [ShadowSocksR Plus+](https://github.com/fw876/helloworld) - [ShadowSocksR Plus+](https://github.com/fw876/helloworld)
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray)) - [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
- Asuswrt-Merlin
- [XRAYUI](https://github.com/DanielLavrushin/asuswrt-merlin-xrayui)
- Windows - Windows
- [v2rayN](https://github.com/2dust/v2rayN) - [v2rayN](https://github.com/2dust/v2rayN)
- [Furious](https://github.com/LorenEteval/Furious) - [Furious](https://github.com/LorenEteval/Furious)
@@ -81,6 +86,7 @@
- [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 - iOS & macOS arm64
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
- [FoXray](https://apps.apple.com/app/foxray/id6448898396) - [FoXray](https://apps.apple.com/app/foxray/id6448898396)
- [Streisand](https://apps.apple.com/app/streisand/id6450534064) - [Streisand](https://apps.apple.com/app/streisand/id6450534064)
- macOS arm64 & x64 - macOS arm64 & x64
@@ -122,25 +128,27 @@
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases). - [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod). - For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
## Compilation ## One-line Compilation
### Windows (PowerShell) ### Windows (PowerShell)
```powershell ```powershell
$env:CGO_ENABLED=0 $env:CGO_ENABLED=0
go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main go build -o xray.exe -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main
``` ```
### Linux / macOS ### Linux / macOS
```bash ```bash
CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-s -w -buildid=" -v ./main
``` ```
### Reproducible Releases ### Reproducible Releases
Make sure that you are using the same Go version, and remember to set the git commit id (7 bytes):
```bash ```bash
make CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
``` ```
## Stargazers over time ## Stargazers over time

View File

@@ -135,6 +135,9 @@ type NameServer struct {
Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"` Geoip []*router.GeoIP `protobuf:"bytes,3,rep,name=geoip,proto3" json:"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"`
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
} }
func (x *NameServer) Reset() { func (x *NameServer) Reset() {
@@ -216,6 +219,27 @@ func (x *NameServer) GetQueryStrategy() QueryStrategy {
return QueryStrategy_USE_IP return QueryStrategy_USE_IP
} }
func (x *NameServer) GetAllowUnexpectedIPs() bool {
if x != nil {
return x.AllowUnexpectedIPs
}
return false
}
func (x *NameServer) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *NameServer) GetTimeoutMs() uint64 {
if x != nil {
return x.TimeoutMs
}
return 0
}
type Config struct { type Config struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -508,7 +532,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, 0xb2, 0x04, 0x0a, 0x0a, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x05, 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,
@@ -534,7 +558,13 @@ var file_app_dns_config_proto_rawDesc = []byte{
0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72,
0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79,
0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2e, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f,
0x77, 0x55, 0x6e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x49, 0x50, 0x73, 0x18, 0x08,
0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x55, 0x6e, 0x65, 0x78, 0x70,
0x65, 0x63, 0x74, 0x65, 0x64, 0x49, 0x50, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18,
0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69,
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74,
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x73, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72, 0x69, 0x6f,
0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79,
0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61,

View File

@@ -28,6 +28,9 @@ message NameServer {
repeated xray.app.router.GeoIP geoip = 3; repeated xray.app.router.GeoIP geoip = 3;
repeated OriginalRule original_rules = 4; repeated OriginalRule original_rules = 4;
QueryStrategy query_strategy = 7; QueryStrategy query_strategy = 7;
bool allowUnexpectedIPs = 8;
string tag = 9;
uint64 timeoutMs = 10;
} }
enum DomainMatchingType { enum DomainMatchingType {

View File

@@ -4,6 +4,7 @@ package dns
import ( import (
"context" "context"
"fmt" "fmt"
"sort"
"strings" "strings"
"sync" "sync"
@@ -156,16 +157,16 @@ func (s *DNS) IsOwnLink(ctx context.Context) bool {
} }
// LookupIP implements dns.Client. // LookupIP implements dns.Client.
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) { func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, error) {
if domain == "" { if domain == "" {
return nil, errors.New("empty domain name") return nil, 0, errors.New("empty domain name")
} }
option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable option.IPv4Enable = option.IPv4Enable && s.ipOption.IPv4Enable
option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable option.IPv6Enable = option.IPv6Enable && s.ipOption.IPv6Enable
if !option.IPv4Enable && !option.IPv6Enable { if !option.IPv4Enable && !option.IPv6Enable {
return nil, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }
// Normalize the FQDN form query // Normalize the FQDN form query
@@ -176,13 +177,14 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
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)
return nil, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].Domain()) errors.LogInfo(s.ctx, "domain replaced: ", domain, " -> ", addrs[0].Domain())
domain = addrs[0].Domain() domain = addrs[0].Domain()
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)
return toNetIP(addrs) ips, err := toNetIP(addrs)
return ips, 10, err // Hosts ttl is 10
} }
// Name servers lookup // Name servers lookup
@@ -193,9 +195,9 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
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, err := client.QueryIP(ctx, domain, option, s.disableCache) ips, ttl, err := client.QueryIP(ctx, domain, option, s.disableCache)
if len(ips) > 0 { if len(ips) > 0 {
return ips, nil return ips, ttl, nil
} }
if err != 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())
@@ -203,11 +205,11 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
} }
// 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size // 5 for RcodeRefused in miekg/dns, hardcode to reduce binary size
if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 { if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch && err != dns.ErrEmptyResponse && dns.RCodeFromError(err) != 5 {
return nil, err return nil, 0, err
} }
} }
return nil, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...)) return nil, 0, errors.New("returning nil for domain ", domain).Base(errors.Combine(errs...))
} }
// LookupHosts implements dns.HostsLookup. // LookupHosts implements dns.HostsLookup.
@@ -250,7 +252,11 @@ func (s *DNS) sortClients(domain string) []*Client {
// Priority domain matching // Priority domain matching
hasMatch := false hasMatch := false
for _, match := range s.domainMatcher.Match(domain) { MatchSlice := s.domainMatcher.Match(domain)
sort.Slice(MatchSlice, func(i, j int) bool {
return MatchSlice[i] < MatchSlice[j]
})
for _, match := range MatchSlice {
info := s.matcherInfos[match] info := s.matcherInfos[match]
client := s.clients[info.clientIdx] client := s.clients[info.clientIdx]
domainRule := client.domains[info.domainRuleIdx] domainRule := client.domains[info.domainRuleIdx]

View File

@@ -155,7 +155,7 @@ func TestUDPServerSubnet(t *testing.T) {
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
ips, err := client.LookupIP("google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -216,7 +216,7 @@ func TestUDPServer(t *testing.T) {
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
{ {
ips, err := client.LookupIP("google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -231,7 +231,7 @@ func TestUDPServer(t *testing.T) {
} }
{ {
ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("facebook.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -246,7 +246,7 @@ func TestUDPServer(t *testing.T) {
} }
{ {
_, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{ _, _, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -260,7 +260,7 @@ func TestUDPServer(t *testing.T) {
} }
{ {
ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
IPv4Enable: false, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -276,7 +276,7 @@ func TestUDPServer(t *testing.T) {
dnsServer.Shutdown() dnsServer.Shutdown()
{ {
ips, err := client.LookupIP("google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -357,7 +357,7 @@ func TestPrioritizedDomain(t *testing.T) {
startTime := time.Now() startTime := time.Now()
{ {
ips, err := client.LookupIP("google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -423,7 +423,7 @@ func TestUDPServerIPv6(t *testing.T) {
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
{ {
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
IPv4Enable: false, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -492,7 +492,7 @@ func TestStaticHostDomain(t *testing.T) {
client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client) client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
{ {
ips, err := client.LookupIP("example.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("example.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -603,7 +603,7 @@ func TestIPMatch(t *testing.T) {
startTime := time.Now() startTime := time.Now()
{ {
ips, err := client.LookupIP("google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -726,7 +726,7 @@ func TestLocalDomain(t *testing.T) {
startTime := time.Now() startTime := time.Now()
{ // Will match dotless: { // Will match dotless:
ips, err := client.LookupIP("hostname", feature_dns.IPOption{ ips, _, err := client.LookupIP("hostname", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -741,7 +741,7 @@ func TestLocalDomain(t *testing.T) {
} }
{ // Will match domain:local { // Will match domain:local
ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{ ips, _, err := client.LookupIP("hostname.local", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -756,7 +756,7 @@ func TestLocalDomain(t *testing.T) {
} }
{ // Will match static ip { // Will match static ip
ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{ ips, _, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -771,7 +771,7 @@ func TestLocalDomain(t *testing.T) {
} }
{ // Will match domain replacing { // Will match domain replacing
ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{ ips, _, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -785,8 +785,8 @@ func TestLocalDomain(t *testing.T) {
} }
} }
{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless: { // Will match dotless:localhost, but not expectedIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
ips, err := client.LookupIP("localhost", feature_dns.IPOption{ ips, _, err := client.LookupIP("localhost", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -800,8 +800,8 @@ func TestLocalDomain(t *testing.T) {
} }
} }
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 { // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3
ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{ ips, _, err := client.LookupIP("localhost-a", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -815,8 +815,8 @@ func TestLocalDomain(t *testing.T) {
} }
} }
{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3 { // Will match dotless:localhost, and expectedIPs: 127.0.0.2, 127.0.0.3
ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{ ips, _, err := client.LookupIP("localhost-b", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -831,7 +831,7 @@ func TestLocalDomain(t *testing.T) {
} }
{ // Will match dotless: { // Will match dotless:
ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{ ips, _, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -997,7 +997,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
startTime := time.Now() startTime := time.Now()
{ // Will match server 1,2 and server 1 returns expected ip { // Will match server 1,2 and server 1 returns expected ip
ips, err := client.LookupIP("google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -1012,7 +1012,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
} }
{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one { // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: false, IPv6Enable: false,
FakeEnable: false, FakeEnable: false,
@@ -1027,7 +1027,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
} }
{ // Will match server 3,1,2 and server 3 returns expected one { // Will match server 3,1,2 and server 3 returns expected one
ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("api.google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
@@ -1042,7 +1042,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
} }
{ // Will match server 4,3,1,2 and server 4 returns expected one { // Will match server 4,3,1,2 and server 4 returns expected one
ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{ ips, _, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,

View File

@@ -35,16 +35,18 @@ type IPRecord struct {
IP []net.Address IP []net.Address
Expire time.Time Expire time.Time
RCode dnsmessage.RCode RCode dnsmessage.RCode
RawHeader *dnsmessage.Header
} }
func (r *IPRecord) getIPs() ([]net.Address, error) { func (r *IPRecord) getIPs() ([]net.Address, uint32, error) {
if r == nil || r.Expire.Before(time.Now()) { if r == nil || r.Expire.Before(time.Now()) {
return nil, errRecordNotFound return nil, 0, errRecordNotFound
} }
if r.RCode != dnsmessage.RCodeSuccess { if r.RCode != dnsmessage.RCodeSuccess {
return nil, dns_feature.RCodeError(r.RCode) return nil, 0, dns_feature.RCodeError(r.RCode)
} }
return r.IP, nil ttl := uint32(time.Until(r.Expire) / time.Second)
return r.IP, ttl, nil
} }
func isNewer(baseRec *IPRecord, newRec *IPRecord) bool { func isNewer(baseRec *IPRecord, newRec *IPRecord) bool {
@@ -67,11 +69,20 @@ type dnsRequest struct {
msg *dnsmessage.Message msg *dnsmessage.Message
} }
func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource { func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
if len(clientIP) == 0 { if len(clientIP) == 0 && padding == 0 {
return nil return nil
} }
const EDNS0SUBNET = 0x8
const EDNS0PADDING = 0xc
opt := new(dnsmessage.Resource)
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
body := dnsmessage.OPTResource{}
opt.Body = &body
if len(clientIP) != 0 {
var netmask int var netmask int
var family uint16 var family uint16
@@ -98,18 +109,19 @@ func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource {
b = append(b, ip[:needLength]...) b = append(b, ip[:needLength]...)
} }
const EDNS0SUBNET = 0x08 body.Options = append(body.Options,
dnsmessage.Option{
opt := new(dnsmessage.Resource)
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
opt.Body = &dnsmessage.OPTResource{
Options: []dnsmessage.Option{
{
Code: EDNS0SUBNET, Code: EDNS0SUBNET,
Data: b, Data: b,
}, })
}, }
if padding != 0 {
body.Options = append(body.Options,
dnsmessage.Option{
Code: EDNS0PADDING,
Data: make([]byte, padding),
})
} }
return opt return opt
@@ -182,6 +194,7 @@ func parseResponse(payload []byte) (*IPRecord, error) {
ReqID: h.ID, ReqID: h.ID,
RCode: h.RCode, RCode: h.RCode,
Expire: now.Add(time.Second * 600), Expire: now.Add(time.Second * 600),
RawHeader: &h,
} }
L: L:

View File

@@ -51,7 +51,7 @@ func Test_parseResponse(t *testing.T) {
}{ }{
{ {
"empty", "empty",
&IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess}, &IPRecord{0, []net.Address(nil), time.Time{}, dnsmessage.RCodeSuccess, nil},
false, false,
}, },
{ {
@@ -66,12 +66,13 @@ func Test_parseResponse(t *testing.T) {
[]net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")}, []net.Address{net.ParseAddress("8.8.8.8"), net.ParseAddress("8.8.4.4")},
time.Time{}, time.Time{},
dnsmessage.RCodeSuccess, dnsmessage.RCodeSuccess,
nil,
}, },
false, false,
}, },
{ {
"aaaa record", "aaaa record",
&IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess}, &IPRecord{2, []net.Address{net.ParseAddress("2001::123:8888"), net.ParseAddress("2001::123:8844")}, time.Time{}, dnsmessage.RCodeSuccess, nil},
false, false,
}, },
} }
@@ -84,8 +85,9 @@ func Test_parseResponse(t *testing.T) {
} }
if got != nil { if got != nil {
// reset the time // reset the time and RawHeader
got.Expire = time.Time{} got.Expire = time.Time{}
got.RawHeader = nil
} }
if cmp.Diff(got, tt.want) != "" { if cmp.Diff(got, tt.want) != "" {
t.Error(cmp.Diff(got, tt.want)) t.Error(cmp.Diff(got, tt.want))
@@ -154,7 +156,7 @@ func Test_genEDNS0Options(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := genEDNS0Options(tt.args.clientIP); got == nil { if got := genEDNS0Options(tt.args.clientIP, 0); got == nil {
t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want) t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want)
} }
}) })

View File

@@ -9,6 +9,7 @@ import (
"github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/strmatcher" "github.com/xtls/xray-core/common/strmatcher"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
@@ -20,7 +21,7 @@ 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, error) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, uint32, error)
} }
// Client is the interface for DNS client. // Client is the interface for DNS client.
@@ -29,10 +30,13 @@ type Client struct {
clientIP net.IP clientIP net.IP
skipFallback bool skipFallback bool
domains []string domains []string
expectIPs []*router.GeoIPMatcher expectedIPs []*router.GeoIPMatcher
allowUnexpectedIPs bool
tag string
timeoutMs time.Duration
} }
var errExpectedIPNonMatch = errors.New("expectIPs not match") 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, queryStrategy QueryStrategy) (Server, error) {
@@ -45,11 +49,13 @@ func NewServer(ctx context.Context, dest net.Destination, dispatcher routing.Dis
case strings.EqualFold(u.String(), "localhost"): case strings.EqualFold(u.String(), "localhost"):
return NewLocalNameServer(queryStrategy), nil return NewLocalNameServer(queryStrategy), nil
case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode case strings.EqualFold(u.Scheme, "https"): // DNS-over-HTTPS Remote mode
return NewDoHNameServer(u, dispatcher, queryStrategy, false) return NewDoHNameServer(u, queryStrategy, dispatcher, false), nil
case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode case strings.EqualFold(u.Scheme, "h2c"): // DNS-over-HTTPS h2c Remote mode
return NewDoHNameServer(u, dispatcher, queryStrategy, true) return NewDoHNameServer(u, queryStrategy, dispatcher, true), 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 NewDoHLocalNameServer(u, queryStrategy), nil return NewDoHNameServer(u, queryStrategy, nil, false), nil
case strings.EqualFold(u.Scheme, "h2c+local"): // DNS-over-HTTPS h2c Local mode
return NewDoHNameServer(u, queryStrategy, nil, true), 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, queryStrategy)
case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
@@ -159,11 +165,19 @@ func NewClient(
} }
} }
var timeoutMs = 4000 * time.Millisecond
if ns.TimeoutMs > 0 {
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
}
client.server = server client.server = server
client.clientIP = clientIP client.clientIP = clientIP
client.skipFallback = ns.SkipFallback client.skipFallback = ns.SkipFallback
client.domains = rules client.domains = rules
client.expectIPs = matchers client.expectedIPs = matchers
client.allowUnexpectedIPs = ns.AllowUnexpectedIPs
client.tag = ns.Tag
client.timeoutMs = timeoutMs
return nil return nil
}) })
return client, err return client, err
@@ -175,25 +189,33 @@ func (c *Client) Name() string {
} }
// 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, error) { func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, uint32, error) {
ctx, cancel := context.WithTimeout(ctx, 4*time.Second) ctx, cancel := context.WithTimeout(ctx, c.timeoutMs)
ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache) if len(c.tag) != 0 {
content := session.InboundFromContext(ctx)
errors.LogDebug(ctx, "DNS: client override tag from ", content.Tag, " to ", c.tag)
// create a new context to override the tag
// do not direct set *content.Tag, it might be used by other clients
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: c.tag})
}
ips, ttl, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
cancel() cancel()
if err != nil { if err != nil {
return ips, err return ips, ttl, err
} }
return c.MatchExpectedIPs(domain, ips) netips, err := c.MatchExpectedIPs(domain, ips)
return netips, ttl, err
} }
// MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones. // MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) { func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) {
if len(c.expectIPs) == 0 { if len(c.expectedIPs) == 0 {
return ips, nil return ips, nil
} }
newIps := []net.IP{} newIps := []net.IP{}
for _, ip := range ips { for _, ip := range ips {
for _, matcher := range c.expectIPs { for _, matcher := range c.expectedIPs {
if matcher.Match(ip) { if matcher.Match(ip) {
newIps = append(newIps, ip) newIps = append(newIps, ip)
break break
@@ -201,9 +223,12 @@ func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error)
} }
} }
if len(newIps) == 0 { if len(newIps) == 0 {
if c.allowUnexpectedIPs {
return ips, nil
}
return nil, errExpectedIPNonMatch return nil, errExpectedIPNonMatch
} }
errors.LogDebug(context.Background(), "domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()) errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", newIps, " matched at server ", c.Name())
return newIps, nil return newIps, nil
} }

View File

@@ -8,10 +8,13 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"sync" "sync"
"time" "time"
utls "github.com/refraction-networking/utls"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
"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"
@@ -31,7 +34,6 @@ 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 {
dispatcher routing.Dispatcher
sync.RWMutex sync.RWMutex
ips map[string]*record ips map[string]*record
pub *pubsub.Service pub *pubsub.Service
@@ -42,29 +44,50 @@ type DoHNameServer struct {
queryStrategy QueryStrategy queryStrategy QueryStrategy
} }
// NewDoHNameServer creates DOH server object for remote resolving. // NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy QueryStrategy, h2c bool) (*DoHNameServer, error) { func NewDoHNameServer(url *url.URL, queryStrategy QueryStrategy, dispatcher routing.Dispatcher, h2c bool) *DoHNameServer {
url.Scheme = "https" url.Scheme = "https"
errors.LogInfo(context.Background(), "DNS: created Remote DNS-over-HTTPS client for ", url.String(), ", with h2c ", h2c) mode := "DOH"
s := baseDOHNameServer(url, "DOH", queryStrategy) if dispatcher == nil {
mode = "DOHL"
s.dispatcher = dispatcher }
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
s := &DoHNameServer{
ips: make(map[string]*record),
pub: pubsub.NewService(),
name: mode + "//" + url.Host,
dohURL: url.String(),
queryStrategy: queryStrategy,
}
s.cleanup = &task.Periodic{
Interval: time.Minute,
Execute: s.Cleanup,
}
s.httpClient = &http.Client{
Transport: &http2.Transport{
IdleConnTimeout: net.ConnIdleTimeout,
ReadIdleTimeout: net.ChromeH2KeepAlivePeriod,
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
dest, err := net.ParseDestination(network + ":" + addr) dest, err := net.ParseDestination(network + ":" + addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
link, err := s.dispatcher.Dispatch(toDnsContext(ctx, s.dohURL), dest) var conn net.Conn
if dispatcher != nil {
dnsCtx := toDnsContext(ctx, s.dohURL)
if h2c {
dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
}
link, err := dispatcher.Dispatch(dnsCtx, dest)
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
default: default:
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
cc := common.ChainedClosable{} cc := common.ChainedClosable{}
if cw, ok := link.Writer.(common.Closable); ok { if cw, ok := link.Writer.(common.Closable); ok {
cc = append(cc, cw) cc = append(cc, cw)
@@ -72,79 +95,32 @@ func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, queryStrategy
if cr, ok := link.Reader.(common.Closable); ok { if cr, ok := link.Reader.(common.Closable); ok {
cc = append(cc, cr) cc = append(cc, cr)
} }
return cnc.NewConnection( conn = cnc.NewConnection(
cnc.ConnectionInputMulti(link.Writer), cnc.ConnectionInputMulti(link.Writer),
cnc.ConnectionOutputMulti(link.Reader), cnc.ConnectionOutputMulti(link.Reader),
cnc.ConnectionOnClose(cc), cnc.ConnectionOnClose(cc),
), nil )
} } else {
s.httpClient = &http.Client{
Timeout: time.Second * 180,
Transport: &http.Transport{
MaxIdleConns: 30,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 30 * time.Second,
ForceAttemptHTTP2: true,
DialContext: dialContext,
},
}
if h2c {
s.httpClient.Transport = &http2.Transport{
IdleConnTimeout: 90 * time.Second,
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
return dialContext(ctx, network, addr)
},
}
}
return s, nil
}
// NewDoHLocalNameServer creates DOH client object for local resolving
func NewDoHLocalNameServer(url *url.URL, queryStrategy QueryStrategy) *DoHNameServer {
url.Scheme = "https"
s := baseDOHNameServer(url, "DOHL", queryStrategy)
tr := &http.Transport{
IdleConnTimeout: 90 * time.Second,
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dest, err := net.ParseDestination(network + ":" + addr)
if err != nil {
return nil, err
}
conn, err := internet.DialSystem(ctx, dest, nil)
log.Record(&log.AccessMessage{ log.Record(&log.AccessMessage{
From: "DNS", From: "DNS",
To: s.dohURL, To: s.dohURL,
Status: log.AccessAccepted, Status: log.AccessAccepted,
Detour: "local", Detour: "local",
}) })
conn, err = internet.DialSystem(ctx, dest, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
if !h2c {
conn = utls.UClient(conn, &utls.Config{ServerName: url.Hostname()}, utls.HelloChrome_Auto)
if err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {
return nil, err
}
}
return conn, nil return conn, nil
}, },
} },
s.httpClient = &http.Client{
Timeout: time.Second * 180,
Transport: tr,
}
errors.LogInfo(context.Background(), "DNS: created Local DNS-over-HTTPS client for ", url.String())
return s
}
func baseDOHNameServer(url *url.URL, prefix string, queryStrategy QueryStrategy) *DoHNameServer {
s := &DoHNameServer{
ips: make(map[string]*record),
pub: pubsub.NewService(),
name: prefix + "//" + url.Host,
dohURL: url.String(),
queryStrategy: queryStrategy,
}
s.cleanup = &task.Periodic{
Interval: time.Minute,
Execute: s.Cleanup,
} }
return s return s
} }
@@ -243,7 +219,9 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
return return
} }
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP)) // As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(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 {
@@ -305,6 +283,8 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
req.Header.Add("Accept", "application/dns-message") req.Header.Add("Accept", "application/dns-message")
req.Header.Add("Content-Type", "application/dns-message") req.Header.Add("Content-Type", "application/dns-message")
req.Header.Set("X-Padding", strings.Repeat("X", int(crypto.RandBetween(100, 1000))))
hc := s.httpClient hc := s.httpClient
resp, err := hc.Do(req.WithContext(ctx)) resp, err := hc.Do(req.WithContext(ctx))
@@ -321,64 +301,66 @@ 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, error) { func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock() s.RLock()
record, found := s.ips[domain] record, found := s.ips[domain]
s.RUnlock() s.RUnlock()
if !found { if !found {
return nil, errRecordNotFound return nil, 0, errRecordNotFound
} }
var err4 error var err4 error
var err6 error var err6 error
var ips []net.Address var ips []net.Address
var ip6 []net.Address var ip6 []net.Address
var ttl uint32
if option.IPv4Enable { if option.IPv4Enable {
ips, err4 = record.A.getIPs() ips, ttl, err4 = record.A.getIPs()
} }
if option.IPv6Enable { if option.IPv6Enable {
ip6, err6 = record.AAAA.getIPs() ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...) ips = append(ips, ip6...)
} }
if len(ips) > 0 { if len(ips) > 0 {
return toNetIP(ips) netips, err := toNetIP(ips)
return netips, ttl, err
} }
if err4 != nil { if err4 != nil {
return nil, err4 return nil, 0, err4
} }
if err6 != nil { if err6 != nil {
return nil, err6 return nil, 0, err6
} }
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) { if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
return nil, dns_feature.ErrEmptyResponse return nil, 0, dns_feature.ErrEmptyResponse
} }
return nil, errRecordNotFound 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, error) { // nolint: dupl func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) { // nolint: dupl
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) option = ResolveIpOptionOverride(s.queryStrategy, option)
if !option.IPv4Enable && !option.IPv6Enable { if !option.IPv4Enable && !option.IPv6Enable {
return nil, dns_feature.ErrEmptyResponse return nil, 0, dns_feature.ErrEmptyResponse
} }
if disableCache { if 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, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse { if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 {
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, err return ips, ttl, err
} }
} }
@@ -412,15 +394,15 @@ func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net
start := time.Now() start := time.Now()
for { for {
ips, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound { if err != errRecordNotFound {
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})
return ips, err return ips, ttl, err
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, 0, ctx.Err()
case <-done: case <-done:
} }
} }

View File

@@ -17,9 +17,9 @@ 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 := NewDoHLocalNameServer(url, QueryStrategy_USE_IP) s := NewDoHNameServer(url, QueryStrategy_USE_IP, nil, false)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -34,9 +34,9 @@ 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 := NewDoHLocalNameServer(url, QueryStrategy_USE_IP) s := NewDoHNameServer(url, QueryStrategy_USE_IP, nil, false)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -47,7 +47,7 @@ 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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, true) }, true)
@@ -62,9 +62,9 @@ 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 := NewDoHLocalNameServer(url, QueryStrategy_USE_IP4) s := NewDoHNameServer(url, QueryStrategy_USE_IP4, nil, false)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -85,9 +85,9 @@ 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 := NewDoHLocalNameServer(url, QueryStrategy_USE_IP6) s := NewDoHNameServer(url, QueryStrategy_USE_IP6, nil, false)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)

View File

@@ -20,9 +20,9 @@ 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, error) { func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]net.IP, uint32, error) {
if f.fakeDNSEngine == nil { if f.fakeDNSEngine == nil {
return nil, errors.New("Unable to locate a fake DNS Engine").AtError() return nil, 0, errors.New("Unable to locate a fake DNS Engine").AtError()
} }
var ips []net.Address var ips []net.Address
@@ -34,13 +34,13 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, op
netIP, err := toNetIP(ips) netIP, err := toNetIP(ips)
if err != nil { if err != nil {
return nil, errors.New("Unable to convert IP to net ip").Base(err).AtError() return nil, 0, errors.New("Unable to convert IP to net ip").Base(err).AtError()
} }
errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips) errors.LogInfo(ctx, f.Name(), " got answer: ", domain, " -> ", ips)
if len(netIP) > 0 { if len(netIP) > 0 {
return netIP, nil return netIP, 1, nil // fakeIP ttl is 1
} }
return nil, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }

View File

@@ -21,14 +21,14 @@ type LocalNameServer struct {
const errEmptyResponse = "No address associated with hostname" 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, err error) { func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) (ips []net.IP, ttl uint32, err error) {
option = ResolveIpOptionOverride(s.queryStrategy, option) option = ResolveIpOptionOverride(s.queryStrategy, option)
if !option.IPv4Enable && !option.IPv6Enable { if !option.IPv4Enable && !option.IPv6Enable {
return nil, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }
start := time.Now() start := time.Now()
ips, err = s.client.LookupIP(domain, option) ips, ttl, err = s.client.LookupIP(domain, option)
if err != nil && strings.HasSuffix(err.Error(), errEmptyResponse) { if err != nil && strings.HasSuffix(err.Error(), errEmptyResponse) {
err = dns.ErrEmptyResponse err = dns.ErrEmptyResponse

View File

@@ -14,7 +14,7 @@ import (
func TestLocalNameServer(t *testing.T) { func TestLocalNameServer(t *testing.T) {
s := NewLocalNameServer(QueryStrategy_USE_IP) s := NewLocalNameServer(QueryStrategy_USE_IP)
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", net.IP{}, dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,

View File

@@ -160,7 +160,7 @@ func (s *QUICNameServer) newReqID() uint16 {
func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) { func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, 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)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0))
var deadline time.Time var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
@@ -244,64 +244,66 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP
} }
} }
func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) { func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock() s.RLock()
record, found := s.ips[domain] record, found := s.ips[domain]
s.RUnlock() s.RUnlock()
if !found { if !found {
return nil, errRecordNotFound return nil, 0, errRecordNotFound
} }
var err4 error var err4 error
var err6 error var err6 error
var ips []net.Address var ips []net.Address
var ip6 []net.Address var ip6 []net.Address
var ttl uint32
if option.IPv4Enable { if option.IPv4Enable {
ips, err4 = record.A.getIPs() ips, ttl, err4 = record.A.getIPs()
} }
if option.IPv6Enable { if option.IPv6Enable {
ip6, err6 = record.AAAA.getIPs() ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...) ips = append(ips, ip6...)
} }
if len(ips) > 0 { if len(ips) > 0 {
return toNetIP(ips) netips, err := toNetIP(ips)
return netips, ttl, err
} }
if err4 != nil { if err4 != nil {
return nil, err4 return nil, 0, err4
} }
if err6 != nil { if err6 != nil {
return nil, err6 return nil, 0, err6
} }
if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) { if (option.IPv4Enable && record.A != nil) || (option.IPv6Enable && record.AAAA != nil) {
return nil, dns_feature.ErrEmptyResponse return nil, 0, dns_feature.ErrEmptyResponse
} }
return nil, errRecordNotFound 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, error) { func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) option = ResolveIpOptionOverride(s.queryStrategy, option)
if !option.IPv4Enable && !option.IPv6Enable { if !option.IPv4Enable && !option.IPv6Enable {
return nil, dns_feature.ErrEmptyResponse return nil, 0, dns_feature.ErrEmptyResponse
} }
if disableCache { if 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, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse { if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 {
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, err return ips, ttl, err
} }
} }
@@ -335,15 +337,15 @@ func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP ne
start := time.Now() start := time.Now()
for { for {
ips, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound { if err != errRecordNotFound {
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})
return ips, err return ips, ttl, err
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, 0, ctx.Err()
case <-done: case <-done:
} }
} }

View File

@@ -19,7 +19,7 @@ func TestQUICNameServer(t *testing.T) {
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP) s, err := NewQUICNameServer(url, QueryStrategy_USE_IP)
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", net.IP(nil), dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -30,7 +30,7 @@ func TestQUICNameServer(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.IPOption{ ips2, _, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, true) }, true)
@@ -47,7 +47,7 @@ func TestQUICNameServerWithIPv4Override(t *testing.T) {
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP4) s, err := NewQUICNameServer(url, QueryStrategy_USE_IP4)
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", net.IP(nil), dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -70,7 +70,7 @@ func TestQUICNameServerWithIPv6Override(t *testing.T) {
s, err := NewQUICNameServer(url, QueryStrategy_USE_IP6) s, err := NewQUICNameServer(url, QueryStrategy_USE_IP6)
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", net.IP(nil), dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)

View File

@@ -192,7 +192,7 @@ func (s *TCPNameServer) newReqID() uint16 {
func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) { func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, 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)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0))
var deadline time.Time var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
@@ -273,60 +273,62 @@ func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP n
} }
} }
func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) { func (s *TCPNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock() s.RLock()
record, found := s.ips[domain] record, found := s.ips[domain]
s.RUnlock() s.RUnlock()
if !found { if !found {
return nil, errRecordNotFound return nil, 0, errRecordNotFound
} }
var err4 error var err4 error
var err6 error var err6 error
var ips []net.Address var ips []net.Address
var ip6 []net.Address var ip6 []net.Address
var ttl uint32
if option.IPv4Enable { if option.IPv4Enable {
ips, err4 = record.A.getIPs() ips, ttl, err4 = record.A.getIPs()
} }
if option.IPv6Enable { if option.IPv6Enable {
ip6, err6 = record.AAAA.getIPs() ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...) ips = append(ips, ip6...)
} }
if len(ips) > 0 { if len(ips) > 0 {
return toNetIP(ips) netips, err := toNetIP(ips)
return netips, ttl, err
} }
if err4 != nil { if err4 != nil {
return nil, err4 return nil, 0, err4
} }
if err6 != nil { if err6 != nil {
return nil, err6 return nil, 0, err6
} }
return nil, dns_feature.ErrEmptyResponse 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, error) { func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) option = ResolveIpOptionOverride(s.queryStrategy, option)
if !option.IPv4Enable && !option.IPv6Enable { if !option.IPv4Enable && !option.IPv6Enable {
return nil, dns_feature.ErrEmptyResponse return nil, 0, dns_feature.ErrEmptyResponse
} }
if disableCache { if 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, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse { if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 {
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, err return ips, ttl, err
} }
} }
@@ -360,15 +362,15 @@ func (s *TCPNameServer) QueryIP(ctx context.Context, domain string, clientIP net
start := time.Now() start := time.Now()
for { for {
ips, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound { if err != errRecordNotFound {
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})
return ips, err return ips, ttl, err
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, 0, ctx.Err()
case <-done: case <-done:
} }
} }

View File

@@ -19,7 +19,7 @@ func TestTCPLocalNameServer(t *testing.T) {
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP) s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -36,7 +36,7 @@ func TestTCPLocalNameServerWithCache(t *testing.T) {
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP) s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -47,7 +47,7 @@ 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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, true) }, true)
@@ -64,7 +64,7 @@ func TestTCPLocalNameServerWithIPv4Override(t *testing.T) {
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP4) s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP4)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)
@@ -88,7 +88,7 @@ func TestTCPLocalNameServerWithIPv6Override(t *testing.T) {
s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP6) s, err := NewTCPLocalNameServer(url, QueryStrategy_USE_IP6)
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", net.IP(nil), dns_feature.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
}, false) }, false)

View File

@@ -27,7 +27,7 @@ type ClassicNameServer struct {
name string name string
address *net.Destination address *net.Destination
ips map[string]*record ips map[string]*record
requests map[uint16]*dnsRequest requests map[uint16]*udpDnsRequest
pub *pubsub.Service pub *pubsub.Service
udpServer *udp.Dispatcher udpServer *udp.Dispatcher
cleanup *task.Periodic cleanup *task.Periodic
@@ -35,6 +35,11 @@ type ClassicNameServer struct {
queryStrategy QueryStrategy queryStrategy QueryStrategy
} }
type udpDnsRequest struct {
dnsRequest
ctx context.Context
}
// 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, queryStrategy QueryStrategy) *ClassicNameServer {
// default to 53 if unspecific // default to 53 if unspecific
@@ -45,7 +50,7 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
s := &ClassicNameServer{ s := &ClassicNameServer{
address: &address, address: &address,
ips: make(map[string]*record), ips: make(map[string]*record),
requests: make(map[uint16]*dnsRequest), requests: make(map[uint16]*udpDnsRequest),
pub: pubsub.NewService(), pub: pubsub.NewService(),
name: strings.ToUpper(address.String()), name: strings.ToUpper(address.String()),
queryStrategy: queryStrategy, queryStrategy: queryStrategy,
@@ -101,7 +106,7 @@ func (s *ClassicNameServer) Cleanup() error {
} }
if len(s.requests) == 0 { if len(s.requests) == 0 {
s.requests = make(map[uint16]*dnsRequest) s.requests = make(map[uint16]*udpDnsRequest)
} }
return nil return nil
@@ -128,6 +133,27 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
return return
} }
// if truncated, retry with EDNS0 option(udp payload size: 1350)
if ipRec.RawHeader.Truncated {
// if already has EDNS0 option, no need to retry
if ok && len(req.msg.Additionals) == 0 {
// copy necessary meta data from original request
// and add EDNS0 option
opt := new(dnsmessage.Resource)
common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
opt.Body = &dnsmessage.OPTResource{}
newMsg := *req.msg
newReq := *req
newMsg.Additionals = append(newMsg.Additionals, *opt)
newMsg.ID = s.newReqID()
newReq.msg = &newMsg
s.addPendingRequest(&newReq)
b, _ := dns.PackMessage(newReq.msg)
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
return
}
}
var rec record var rec record
switch req.reqType { switch req.reqType {
case dnsmessage.TypeA: case dnsmessage.TypeA:
@@ -179,7 +205,7 @@ func (s *ClassicNameServer) newReqID() uint16 {
return uint16(atomic.AddUint32(&s.reqID, 1)) return uint16(atomic.AddUint32(&s.reqID, 1))
} }
func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) { func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
@@ -191,69 +217,75 @@ func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) {
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) { func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, 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)) reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0))
for _, req := range reqs { for _, req := range reqs {
s.addPendingRequest(req) udpReq := &udpDnsRequest{
dnsRequest: *req,
ctx: ctx,
}
s.addPendingRequest(udpReq)
b, _ := dns.PackMessage(req.msg) b, _ := dns.PackMessage(req.msg)
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b) s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
} }
} }
func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) { func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) {
s.RLock() s.RLock()
record, found := s.ips[domain] record, found := s.ips[domain]
s.RUnlock() s.RUnlock()
if !found { if !found {
return nil, errRecordNotFound return nil, 0, errRecordNotFound
} }
var err4 error var err4 error
var err6 error var err6 error
var ips []net.Address var ips []net.Address
var ip6 []net.Address var ip6 []net.Address
var ttl uint32
if option.IPv4Enable { if option.IPv4Enable {
ips, err4 = record.A.getIPs() ips, ttl, err4 = record.A.getIPs()
} }
if option.IPv6Enable { if option.IPv6Enable {
ip6, err6 = record.AAAA.getIPs() ip6, ttl, err6 = record.AAAA.getIPs()
ips = append(ips, ip6...) ips = append(ips, ip6...)
} }
if len(ips) > 0 { if len(ips) > 0 {
return toNetIP(ips) netips, err := toNetIP(ips)
return netips, ttl, err
} }
if err4 != nil { if err4 != nil {
return nil, err4 return nil, 0, err4
} }
if err6 != nil { if err6 != nil {
return nil, err6 return nil, 0, err6
} }
return nil, dns_feature.ErrEmptyResponse 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, error) { func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, uint32, error) {
fqdn := Fqdn(domain) fqdn := Fqdn(domain)
option = ResolveIpOptionOverride(s.queryStrategy, option) option = ResolveIpOptionOverride(s.queryStrategy, option)
if !option.IPv4Enable && !option.IPv6Enable { if !option.IPv4Enable && !option.IPv6Enable {
return nil, dns_feature.ErrEmptyResponse return nil, 0, dns_feature.ErrEmptyResponse
} }
if disableCache { if 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, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err == nil || err == dns_feature.ErrEmptyResponse { if err == nil || err == dns_feature.ErrEmptyResponse || dns_feature.RCodeFromError(err) == 3 {
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, err return ips, ttl, err
} }
} }
@@ -287,15 +319,15 @@ func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP
start := time.Now() start := time.Now()
for { for {
ips, err := s.findIPsForDomain(fqdn, option) ips, ttl, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound { if err != errRecordNotFound {
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})
return ips, err return ips, ttl, err
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, 0, ctx.Err()
case <-done: case <-done:
} }
} }

View File

@@ -28,6 +28,7 @@ type Config struct {
// Tag of the outbound handler that handles metrics http connections. // Tag of the outbound handler that handles metrics http connections.
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
Listen string `protobuf:"bytes,2,opt,name=listen,proto3" json:"listen,omitempty"`
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@@ -67,20 +68,28 @@ func (x *Config) GetTag() string {
return "" return ""
} }
func (x *Config) GetListen() string {
if x != nil {
return x.Listen
}
return ""
}
var File_app_metrics_config_proto protoreflect.FileDescriptor var File_app_metrics_config_proto protoreflect.FileDescriptor
var file_app_metrics_config_proto_rawDesc = []byte{ var file_app_metrics_config_proto_rawDesc = []byte{
0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x63, 0x6f, 0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x1a, 0x0a, 0x06, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x32, 0x0a, 0x06,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e,
0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x2e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68,
0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
0x6f, 0x74, 0x6f, 0x33, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x65, 0x74,
0x72, 0x69, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

View File

@@ -10,4 +10,5 @@ option java_multiple_files = true;
message Config { message Config {
// Tag of the outbound handler that handles metrics http connections. // Tag of the outbound handler that handles metrics http connections.
string tag = 1; string tag = 1;
string listen = 2;
} }

View File

@@ -24,12 +24,15 @@ type MetricsHandler struct {
statsManager feature_stats.Manager statsManager feature_stats.Manager
observatory extension.Observatory observatory extension.Observatory
tag string tag string
listen string
tcpListener net.Listener
} }
// NewMetricsHandler creates a new MetricsHandler based on the given config. // NewMetricsHandler creates a new MetricsHandler based on the given config.
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) { func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
c := &MetricsHandler{ c := &MetricsHandler{
tag: config.Tag, tag: config.Tag,
listen: config.Listen,
} }
common.Must(core.RequireFeatures(ctx, func(om outbound.Manager, sm feature_stats.Manager) { common.Must(core.RequireFeatures(ctx, func(om outbound.Manager, sm feature_stats.Manager) {
c.statsManager = sm c.statsManager = sm
@@ -87,6 +90,23 @@ func (p *MetricsHandler) Type() interface{} {
} }
func (p *MetricsHandler) Start() error { func (p *MetricsHandler) Start() error {
// direct listen a port if listen is set
if p.listen != "" {
TCPlistener, err := net.Listen("tcp", p.listen)
if err != nil {
return err
}
p.tcpListener = TCPlistener
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
go func() {
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
}
}()
}
listener := &OutboundListener{ listener := &OutboundListener{
buffer: make(chan net.Conn, 4), buffer: make(chan net.Conn, 4),
done: done.New(), done: done.New(),

View File

@@ -23,7 +23,7 @@ type DynamicInboundHandler struct {
receiverConfig *proxyman.ReceiverConfig receiverConfig *proxyman.ReceiverConfig
streamSettings *internet.MemoryStreamConfig streamSettings *internet.MemoryStreamConfig
portMutex sync.Mutex portMutex sync.Mutex
portsInUse map[net.Port]bool portsInUse map[net.Port]struct{}
workerMutex sync.RWMutex workerMutex sync.RWMutex
worker []worker worker []worker
lastRefresh time.Time lastRefresh time.Time
@@ -39,7 +39,7 @@ func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *p
tag: tag, tag: tag,
proxyConfig: proxyConfig, proxyConfig: proxyConfig,
receiverConfig: receiverConfig, receiverConfig: receiverConfig,
portsInUse: make(map[net.Port]bool), portsInUse: make(map[net.Port]struct{}),
mux: mux.NewServer(ctx), mux: mux.NewServer(ctx),
v: v, v: v,
ctx: ctx, ctx: ctx,
@@ -84,7 +84,7 @@ func (h *DynamicInboundHandler) allocatePort() net.Port {
port := net.Port(allPorts[r]) port := net.Port(allPorts[r])
_, used := h.portsInUse[port] _, used := h.portsInUse[port]
if !used { if !used {
h.portsInUse[port] = true h.portsInUse[port] = struct{}{}
return port return port
} }
} }

View File

@@ -2,7 +2,6 @@ package inbound
import ( import (
"context" "context"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -325,6 +324,7 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
if w.sniffingConfig != nil { if w.sniffingConfig != nil {
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
} }
@@ -464,19 +464,8 @@ func (w *dsWorker) callback(conn stat.Connection) {
WriteCounter: w.downlinkCounter, WriteCounter: w.downlinkCounter,
} }
} }
// For most of time, unix obviously have no source addr. But if we leave it empty, it will cause panic.
// So we use gateway as source for log.
// However, there are some special situations where a valid source address might be available.
// Such as the source address parsed from X-Forwarded-For in websocket.
// In that case, we keep it.
var source net.Destination
if !strings.Contains(conn.RemoteAddr().String(), "unix") {
source = net.DestinationFromAddr(conn.RemoteAddr())
} else {
source = net.UnixDestination(w.address)
}
ctx = session.ContextWithInbound(ctx, &session.Inbound{ ctx = session.ContextWithInbound(ctx, &session.Inbound{
Source: source, Source: net.DestinationFromAddr(conn.RemoteAddr()),
Gateway: net.UnixDestination(w.address), Gateway: net.UnixDestination(w.address),
Tag: w.tag, Tag: w.tag,
Conn: conn, Conn: conn,

View File

@@ -273,7 +273,16 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
if h.senderSettings.ViaCidr == "" { if h.senderSettings.ViaCidr == "" {
if h.senderSettings.Via.AsAddress().Family().IsDomain() && h.senderSettings.Via.AsAddress().Domain() == "origin" {
if inbound := session.InboundFromContext(ctx); inbound != nil {
origin, _, err := net.SplitHostPort(inbound.Conn.LocalAddr().String())
if err == nil {
ob.Gateway = net.ParseAddress(origin)
}
}
} else {
ob.Gateway = h.senderSettings.Via.AsAddress() ob.Gateway = h.senderSettings.Via.AsAddress()
}
} else { //Get a random address. } else { //Get a random address.
ob.Gateway = ParseRandomIPv6(h.senderSettings.Via.AsAddress(), h.senderSettings.ViaCidr) ob.Gateway = ParseRandomIPv6(h.senderSettings.Via.AsAddress(), h.senderSettings.ViaCidr)
} }

View File

@@ -177,7 +177,7 @@ func TestIPOnDemand(t *testing.T) {
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() }).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()
r := new(Router) r := new(Router)
common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil)) common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))
@@ -222,7 +222,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,
}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() }).Return([]net.IP{{192, 168, 0, 1}}, uint32(600), nil).AnyTimes()
r := new(Router) r := new(Router)
common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil)) common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil))

View File

@@ -60,6 +60,24 @@ func (s *statsServer) GetStatsOnline(ctx context.Context, request *GetStatsReque
}, nil }, nil
} }
func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) {
c := s.stats.GetOnlineMap(request.Name)
if c == nil {
return nil, errors.New(request.Name, " not found.")
}
ips := make(map[string]int64)
for ip, t := range c.IpTimeMap() {
ips[ip] = t.Unix()
}
return &GetStatsOnlineIpListResponse{
Name: request.Name,
Ips: ips,
}, nil
}
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) { func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
matcher, err := strmatcher.Substr.New(request.Pattern) matcher, err := strmatcher.Substr.New(request.Pattern)
if err != nil { if err != nil {

View File

@@ -424,6 +424,59 @@ func (x *SysStatsResponse) GetUptime() uint32 {
return 0 return 0
} }
type GetStatsOnlineIpListResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Ips map[string]int64 `protobuf:"bytes,2,rep,name=ips,proto3" json:"ips,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
}
func (x *GetStatsOnlineIpListResponse) Reset() {
*x = GetStatsOnlineIpListResponse{}
mi := &file_app_stats_command_command_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetStatsOnlineIpListResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetStatsOnlineIpListResponse) ProtoMessage() {}
func (x *GetStatsOnlineIpListResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetStatsOnlineIpListResponse.ProtoReflect.Descriptor instead.
func (*GetStatsOnlineIpListResponse) Descriptor() ([]byte, []int) {
return file_app_stats_command_command_proto_rawDescGZIP(), []int{7}
}
func (x *GetStatsOnlineIpListResponse) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *GetStatsOnlineIpListResponse) GetIps() map[string]int64 {
if x != nil {
return x.Ips
}
return nil
}
type Config struct { type Config struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -432,7 +485,7 @@ type Config struct {
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
mi := &file_app_stats_command_command_proto_msgTypes[7] mi := &file_app_stats_command_command_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -444,7 +497,7 @@ func (x *Config) String() string {
func (*Config) ProtoMessage() {} func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message { func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_app_stats_command_command_proto_msgTypes[7] mi := &file_app_stats_command_command_proto_msgTypes[8]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -457,7 +510,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_stats_command_command_proto_rawDescGZIP(), []int{7} return file_app_stats_command_command_proto_rawDescGZIP(), []int{8}
} }
var File_app_stats_command_command_proto protoreflect.FileDescriptor var File_app_stats_command_command_proto protoreflect.FileDescriptor
@@ -506,40 +559,60 @@ var file_app_stats_command_command_proto_rawDesc = []byte{
0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x50, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x50,
0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55,
0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x70, 0x74, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x55, 0x70, 0x74,
0x69, 0x6d, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xa1, 0x03, 0x69, 0x6d, 0x65, 0x22, 0xbb, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73,
0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,
0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4f, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18,
0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47,
0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c,
0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x70, 0x73, 0x45,
0x65, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x69, 0x70, 0x73, 0x1a, 0x36, 0x0a, 0x08, 0x49, 0x70, 0x73,
0x65, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0x9a, 0x04, 0x0a, 0x0c,
0x53, 0x74, 0x61, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x08,
0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61,
0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74,
0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a,
0x0e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12,
0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61,
0x74, 0x73, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74,
0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72,
0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e,
0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0b, 0x47,
0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73,
0x74, 0x61, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73,
0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x77, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e,
0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x0b, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x78, 0x1a, 0x34, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74,
0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x74, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x49, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x64, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e,
0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63,
0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x00, 0x42, 0x64, 0x0a, 0x1a, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63,
0x70, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f,
0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x16, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70,
0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06,
0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x16, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x2e,
0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@@ -554,7 +627,7 @@ func file_app_stats_command_command_proto_rawDescGZIP() []byte {
return file_app_stats_command_command_proto_rawDescData return file_app_stats_command_command_proto_rawDescData
} }
var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_app_stats_command_command_proto_goTypes = []any{ var file_app_stats_command_command_proto_goTypes = []any{
(*GetStatsRequest)(nil), // 0: xray.app.stats.command.GetStatsRequest (*GetStatsRequest)(nil), // 0: xray.app.stats.command.GetStatsRequest
(*Stat)(nil), // 1: xray.app.stats.command.Stat (*Stat)(nil), // 1: xray.app.stats.command.Stat
@@ -563,24 +636,29 @@ var file_app_stats_command_command_proto_goTypes = []any{
(*QueryStatsResponse)(nil), // 4: xray.app.stats.command.QueryStatsResponse (*QueryStatsResponse)(nil), // 4: xray.app.stats.command.QueryStatsResponse
(*SysStatsRequest)(nil), // 5: xray.app.stats.command.SysStatsRequest (*SysStatsRequest)(nil), // 5: xray.app.stats.command.SysStatsRequest
(*SysStatsResponse)(nil), // 6: xray.app.stats.command.SysStatsResponse (*SysStatsResponse)(nil), // 6: xray.app.stats.command.SysStatsResponse
(*Config)(nil), // 7: xray.app.stats.command.Config (*GetStatsOnlineIpListResponse)(nil), // 7: xray.app.stats.command.GetStatsOnlineIpListResponse
(*Config)(nil), // 8: xray.app.stats.command.Config
nil, // 9: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
} }
var file_app_stats_command_command_proto_depIdxs = []int32{ var file_app_stats_command_command_proto_depIdxs = []int32{
1, // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat 1, // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat
1, // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat 1, // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat
0, // 2: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest 9, // 2: xray.app.stats.command.GetStatsOnlineIpListResponse.ips:type_name -> xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
0, // 3: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest 0, // 3: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest
3, // 4: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest 0, // 4: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest
5, // 5: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest 3, // 5: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest
2, // 6: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse 5, // 6: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest
2, // 7: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse 0, // 7: xray.app.stats.command.StatsService.GetStatsOnlineIpList:input_type -> xray.app.stats.command.GetStatsRequest
4, // 8: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse 2, // 8: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse
6, // 9: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse 2, // 9: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse
6, // [6:10] is the sub-list for method output_type 4, // 10: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse
2, // [2:6] is the sub-list for method input_type 6, // 11: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse
2, // [2:2] is the sub-list for extension type_name 7, // 12: xray.app.stats.command.StatsService.GetStatsOnlineIpList:output_type -> xray.app.stats.command.GetStatsOnlineIpListResponse
2, // [2:2] is the sub-list for extension extendee 8, // [8:13] is the sub-list for method output_type
0, // [0:2] is the sub-list for field type_name 3, // [3:8] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
} }
func init() { file_app_stats_command_command_proto_init() } func init() { file_app_stats_command_command_proto_init() }
@@ -594,7 +672,7 @@ func file_app_stats_command_command_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_app_stats_command_command_proto_rawDesc, RawDescriptor: file_app_stats_command_command_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 8, NumMessages: 10,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@@ -46,11 +46,17 @@ message SysStatsResponse {
uint32 Uptime = 10; uint32 Uptime = 10;
} }
message GetStatsOnlineIpListResponse {
string name = 1;
map<string, int64> ips = 2;
}
service StatsService { service StatsService {
rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {} rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}
rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {} rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {}
rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {} rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}
rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {} rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}
rpc GetStatsOnlineIpList(GetStatsRequest) returns (GetStatsOnlineIpListResponse) {}
} }
message Config {} message Config {}

View File

@@ -23,6 +23,7 @@ const (
StatsService_GetStatsOnline_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnline" StatsService_GetStatsOnline_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnline"
StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats" StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats"
StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats" StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats"
StatsService_GetStatsOnlineIpList_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnlineIpList"
) )
// StatsServiceClient is the client API for StatsService service. // StatsServiceClient is the client API for StatsService service.
@@ -33,6 +34,7 @@ type StatsServiceClient interface {
GetStatsOnline(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) GetStatsOnline(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error)
QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error)
GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error)
GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error)
} }
type statsServiceClient struct { type statsServiceClient struct {
@@ -83,6 +85,16 @@ func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsReques
return out, nil return out, nil
} }
func (c *statsServiceClient) GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetStatsOnlineIpListResponse)
err := c.cc.Invoke(ctx, StatsService_GetStatsOnlineIpList_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// StatsServiceServer is the server API for StatsService service. // StatsServiceServer is the server API for StatsService service.
// All implementations must embed UnimplementedStatsServiceServer // All implementations must embed UnimplementedStatsServiceServer
// for forward compatibility. // for forward compatibility.
@@ -91,6 +103,7 @@ type StatsServiceServer interface {
GetStatsOnline(context.Context, *GetStatsRequest) (*GetStatsResponse, error) GetStatsOnline(context.Context, *GetStatsRequest) (*GetStatsResponse, error)
QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error)
GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error)
GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error)
mustEmbedUnimplementedStatsServiceServer() mustEmbedUnimplementedStatsServiceServer()
} }
@@ -113,6 +126,9 @@ func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRe
func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) { func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented")
} }
func (UnimplementedStatsServiceServer) GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStatsOnlineIpList not implemented")
}
func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {}
func (UnimplementedStatsServiceServer) testEmbeddedByValue() {} func (UnimplementedStatsServiceServer) testEmbeddedByValue() {}
@@ -206,6 +222,24 @@ func _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _StatsService_GetStatsOnlineIpList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetStatsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StatsService_GetStatsOnlineIpList_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, req.(*GetStatsRequest))
}
return interceptor(ctx, in, info, handler)
}
// StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service. // StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@@ -229,6 +263,10 @@ var StatsService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetSysStats", MethodName: "GetSysStats",
Handler: _StatsService_GetSysStats_Handler, Handler: _StatsService_GetSysStats_Handler,
}, },
{
MethodName: "GetStatsOnlineIpList",
Handler: _StatsService_GetStatsOnlineIpList_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "app/stats/command/command.proto", Metadata: "app/stats/command/command.proto",

View File

@@ -40,11 +40,11 @@ func (c *OnlineMap) AddIP(ip string) {
if ip == "127.0.0.1" { if ip == "127.0.0.1" {
return return
} }
if _, ok := list[ip]; !ok {
c.access.Lock() c.access.Lock()
if _, ok := list[ip]; !ok {
list[ip] = time.Now() list[ip] = time.Now()
c.access.Unlock()
} }
c.access.Unlock()
if time.Since(c.lastCleanup) > c.cleanupPeriod { if time.Since(c.lastCleanup) > c.cleanupPeriod {
list = c.RemoveExpiredIPs(list) list = c.RemoveExpiredIPs(list)
c.lastCleanup = time.Now() c.lastCleanup = time.Now()
@@ -78,3 +78,13 @@ func (c *OnlineMap) RemoveExpiredIPs(list map[string]time.Time) map[string]time.
} }
return list return list
} }
func (c *OnlineMap) IpTimeMap() map[string]time.Time {
list := c.ipList
if time.Since(c.lastCleanup) > c.cleanupPeriod {
list = c.RemoveExpiredIPs(list)
c.lastCleanup = time.Now()
}
return c.ipList
}

View File

@@ -1,2 +1,15 @@
// Package crypto provides common crypto libraries for Xray. // Package crypto provides common crypto libraries for Xray.
package crypto // import "github.com/xtls/xray-core/common/crypto" package crypto // import "github.com/xtls/xray-core/common/crypto"
import (
"crypto/rand"
"math/big"
)
func RandBetween(from int64, to int64) int64 {
if from == to {
return from
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
return from + bigInt.Int64()
}

View File

@@ -120,7 +120,7 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error { func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
// deep-clone outbounds because it is going to be mutated concurrently // deep-clone outbounds because it is going to be mutated concurrently
// (Target and OriginalTarget) // (Target and OriginalTarget)
ctx = session.ContextCloneOutbounds(ctx) ctx = session.ContextCloneOutboundsAndContent(ctx)
errors.LogInfo(ctx, "received request for ", meta.Target) errors.LogInfo(ctx, "received request for ", meta.Target)
{ {
msg := &log.AccessMessage{ msg := &log.AccessMessage{

View File

@@ -89,13 +89,11 @@ func UnixDestination(address Address) Destination {
// NetAddr returns the network address in this Destination in string form. // NetAddr returns the network address in this Destination in string form.
func (d Destination) NetAddr() string { func (d Destination) NetAddr() string {
addr := "" addr := ""
if d.Address != nil {
if d.Network == Network_TCP || d.Network == Network_UDP { if d.Network == Network_TCP || d.Network == Network_UDP {
addr = d.Address.String() + ":" + d.Port.String() addr = d.Address.String() + ":" + d.Port.String()
} else if d.Network == Network_UNIX { } else if d.Network == Network_UNIX {
addr = d.Address.String() addr = d.Address.String()
} }
}
return addr return addr
} }

View File

@@ -1,2 +1,14 @@
// Package net is a drop-in replacement to Golang's net package, with some more functionalities. // Package net is a drop-in replacement to Golang's net package, with some more functionalities.
package net // import "github.com/xtls/xray-core/common/net" package net // import "github.com/xtls/xray-core/common/net"
import "time"
// defines the maximum time an idle TCP session can survive in the tunnel, so
// it should be consistent across HTTP versions and with other transports.
const ConnIdleTimeout = 300 * time.Second
// consistent with quic-go
const QuicgoH3KeepAlivePeriod = 10 * time.Second
// consistent with chrome
const ChromeH2KeepAlivePeriod = 45 * time.Second

View File

@@ -76,8 +76,9 @@ type (
) )
var ( var (
ResolveUnixAddr = net.ResolveUnixAddr ResolveTCPAddr = net.ResolveTCPAddr
ResolveUDPAddr = net.ResolveUDPAddr ResolveUDPAddr = net.ResolveUDPAddr
ResolveUnixAddr = net.ResolveUnixAddr
) )
type Resolver = net.Resolver type Resolver = net.Resolver

View File

@@ -3,6 +3,7 @@ package filesystem
import ( import (
"io" "io"
"os" "os"
"path/filepath"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
@@ -28,6 +29,13 @@ func ReadAsset(file string) ([]byte, error) {
return ReadFile(platform.GetAssetLocation(file)) return ReadFile(platform.GetAssetLocation(file))
} }
func ReadCert(file string) ([]byte, error) {
if filepath.IsAbs(file) {
return ReadFile(file)
}
return ReadFile(platform.GetCertLocation(file))
}
func CopyFile(dst string, src string) error { func CopyFile(dst string, src string) error {
bytes, err := ReadFile(src) bytes, err := ReadFile(src)
if err != nil { if err != nil {

View File

@@ -21,7 +21,7 @@ func GetToolLocation(file string) string {
return filepath.Join(toolPath, file) return filepath.Join(toolPath, file)
} }
// GetAssetLocation searches for `file` in certain locations // GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
func GetAssetLocation(file string) string { func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
defPath := filepath.Join(assetPath, file) defPath := filepath.Join(assetPath, file)
@@ -42,3 +42,9 @@ func GetAssetLocation(file string) string {
// asset not found, let the caller throw out the error // asset not found, let the caller throw out the error
return defPath return defPath
} }
// GetCertLocation searches for `file` in the env dir and the executable dir
func GetCertLocation(file string) string {
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
return filepath.Join(certPath, file)
}

View File

@@ -13,6 +13,7 @@ const (
ConfdirLocation = "xray.location.confdir" ConfdirLocation = "xray.location.confdir"
ToolLocation = "xray.location.tool" ToolLocation = "xray.location.tool"
AssetLocation = "xray.location.asset" AssetLocation = "xray.location.asset"
CertLocation = "xray.location.cert"
UseReadV = "xray.buf.readv" UseReadV = "xray.buf.readv"
UseFreedomSplice = "xray.buf.splice" UseFreedomSplice = "xray.buf.splice"

View File

@@ -19,8 +19,14 @@ func GetToolLocation(file string) string {
return filepath.Join(toolPath, file+".exe") return filepath.Join(toolPath, file+".exe")
} }
// GetAssetLocation searches for `file` in the executable dir // GetAssetLocation searches for `file` in the env dir and the executable dir
func GetAssetLocation(file string) string { func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir)
return filepath.Join(assetPath, file) return filepath.Join(assetPath, file)
} }
// GetCertLocation searches for `file` in the env dir and the executable dir
func GetCertLocation(file string) string {
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir)
return filepath.Join(certPath, file)
}

View File

@@ -63,7 +63,7 @@ func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
ShouldSniffAttr := true ShouldSniffAttr := true
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode. // If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
// It will set attributes, so skip it. // It will set attributes, so skip it.
if content == nil || content.AttributeLen() != 0 { if content == nil || len(content.Attributes) != 0 {
ShouldSniffAttr = false ShouldSniffAttr = false
} }
if err := beginWithHTTPMethod(b); err != nil { if err := beginWithHTTPMethod(b); err != nil {

View File

@@ -1 +0,0 @@
*.pem

View File

@@ -78,9 +78,9 @@ func printJSON(certificate *Certificate) {
func printFile(certificate *Certificate, name string) error { func printFile(certificate *Certificate, name string) error {
certPEM, keyPEM := certificate.ToPEM() certPEM, keyPEM := certificate.ToPEM()
return task.Run(context.Background(), func() error { return task.Run(context.Background(), func() error {
return writeFile(certPEM, name+"_cert.pem") return writeFile(certPEM, name+".crt")
}, func() error { }, func() error {
return writeFile(keyPEM, name+"_key.pem") return writeFile(keyPEM, name+".key")
}) })
} }

View File

@@ -23,6 +23,8 @@ const (
timeoutOnlyKey ctx.SessionKey = 8 timeoutOnlyKey ctx.SessionKey = 8
allowedNetworkKey ctx.SessionKey = 9 allowedNetworkKey ctx.SessionKey = 9
handlerSessionKey ctx.SessionKey = 10 handlerSessionKey ctx.SessionKey = 10
mitmAlpn11Key ctx.SessionKey = 11
mitmServerNameKey ctx.SessionKey = 12
) )
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context { func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
@@ -40,7 +42,7 @@ func ContextWithOutbounds(ctx context.Context, outbounds []*Outbound) context.Co
return context.WithValue(ctx, outboundSessionKey, outbounds) return context.WithValue(ctx, outboundSessionKey, outbounds)
} }
func ContextCloneOutbounds(ctx context.Context) context.Context { func ContextCloneOutboundsAndContent(ctx context.Context) context.Context {
outbounds := OutboundsFromContext(ctx) outbounds := OutboundsFromContext(ctx)
newOutbounds := make([]*Outbound, len(outbounds)) newOutbounds := make([]*Outbound, len(outbounds))
for i, ob := range outbounds { for i, ob := range outbounds {
@@ -53,7 +55,15 @@ func ContextCloneOutbounds(ctx context.Context) context.Context {
newOutbounds[i] = &v newOutbounds[i] = &v
} }
return ContextWithOutbounds(ctx, newOutbounds) content := ContentFromContext(ctx)
newContent := Content{}
if content != nil {
newContent = *content
if content.Attributes != nil {
panic("content.Attributes != nil")
}
}
return ContextWithContent(ContextWithOutbounds(ctx, newOutbounds), &newContent)
} }
func OutboundsFromContext(ctx context.Context) []*Outbound { func OutboundsFromContext(ctx context.Context) []*Outbound {
@@ -162,3 +172,25 @@ func AllowedNetworkFromContext(ctx context.Context) net.Network {
} }
return net.Network_Unknown return net.Network_Unknown
} }
func ContextWithMitmAlpn11(ctx context.Context, alpn11 bool) context.Context {
return context.WithValue(ctx, mitmAlpn11Key, alpn11)
}
func MitmAlpn11FromContext(ctx context.Context) bool {
if val, ok := ctx.Value(mitmAlpn11Key).(bool); ok {
return val
}
return false
}
func ContextWithMitmServerName(ctx context.Context, serverName string) context.Context {
return context.WithValue(ctx, mitmServerNameKey, serverName)
}
func MitmServerNameFromContext(ctx context.Context) string {
if val, ok := ctx.Value(mitmServerNameKey).(string); ok {
return val
}
return ""
}

View File

@@ -4,7 +4,6 @@ package session // import "github.com/xtls/xray-core/common/session"
import ( import (
"context" "context"
"math/rand" "math/rand"
"sync"
c "github.com/xtls/xray-core/common/ctx" c "github.com/xtls/xray-core/common/ctx"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -75,8 +74,8 @@ type Outbound struct {
// SniffingRequest controls the behavior of content sniffing. // SniffingRequest controls the behavior of content sniffing.
type SniffingRequest struct { type SniffingRequest struct {
ExcludeForDomain []string ExcludeForDomain []string // read-only once set
OverrideDestinationForProtocol []string OverrideDestinationForProtocol []string // read-only once set
Enabled bool Enabled bool
MetadataOnly bool MetadataOnly bool
RouteOnly bool RouteOnly bool
@@ -92,10 +91,6 @@ type Content struct {
Attributes map[string]string Attributes map[string]string
SkipDNSResolve bool SkipDNSResolve bool
mu sync.Mutex
isLocked bool
} }
// Sockopt is the settings for socket connection. // Sockopt is the settings for socket connection.
@@ -104,22 +99,8 @@ type Sockopt struct {
Mark int32 Mark int32
} }
// Some how when using mux, there will be a same ctx between different requests
// This will cause problem as it's designed for single request, like concurrent map writes
// Add a Mutex as a temp solution
// SetAttribute attaches additional string attributes to content. // SetAttribute attaches additional string attributes to content.
func (c *Content) SetAttribute(name string, value string) { func (c *Content) SetAttribute(name string, value string) {
if c.isLocked {
errors.LogError(context.Background(), "Multiple goroutines are tring to access one routing content, tring to write ", name, ":", value)
}
c.mu.Lock()
c.isLocked = true
defer func() {
c.isLocked = false
c.mu.Unlock()
}()
if c.Attributes == nil { if c.Attributes == nil {
c.Attributes = make(map[string]string) c.Attributes = make(map[string]string)
} }
@@ -128,24 +109,8 @@ func (c *Content) SetAttribute(name string, value string) {
// Attribute retrieves additional string attributes from content. // Attribute retrieves additional string attributes from content.
func (c *Content) Attribute(name string) string { func (c *Content) Attribute(name string) string {
c.mu.Lock()
c.isLocked = true
defer func() {
c.isLocked = false
c.mu.Unlock()
}()
if c.Attributes == nil { if c.Attributes == nil {
return "" return ""
} }
return c.Attributes[name] return c.Attributes[name]
} }
func (c *Content) AttributeLen() int {
c.mu.Lock()
c.isLocked = true
defer func() {
c.isLocked = false
c.mu.Unlock()
}()
return len(c.Attributes)
}

View File

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

View File

@@ -21,7 +21,7 @@ type Client interface {
features.Feature features.Feature
// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses. // LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
LookupIP(domain string, option IPOption) ([]net.IP, error) LookupIP(domain string, option IPOption) ([]net.IP, uint32, error)
} }
type HostsLookup interface { type HostsLookup interface {

View File

@@ -20,10 +20,10 @@ func (*Client) Start() error { return nil }
func (*Client) Close() error { return nil } func (*Client) Close() error { return nil }
// LookupIP implements Client. // LookupIP implements Client.
func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) { func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) {
ips, err := net.LookupIP(host) ips, err := net.LookupIP(host)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
parsedIPs := make([]net.IP, 0, len(ips)) parsedIPs := make([]net.IP, 0, len(ips))
ipv4 := make([]net.IP, 0, len(ips)) ipv4 := make([]net.IP, 0, len(ips))
@@ -40,21 +40,22 @@ func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) {
ipv6 = append(ipv6, ip) ipv6 = append(ipv6, ip)
} }
} }
// 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, nil return parsedIPs, 600, nil
} }
case option.IPv4Enable: case option.IPv4Enable:
if len(ipv4) > 0 { if len(ipv4) > 0 {
return ipv4, nil return ipv4, 600, nil
} }
case option.IPv6Enable: case option.IPv6Enable:
if len(ipv6) > 0 { if len(ipv6) > 0 {
return ipv6, nil return ipv6, 600, nil
} }
} }
return nil, dns.ErrEmptyResponse return nil, 0, dns.ErrEmptyResponse
} }
// New create a new dns.Client that queries localhost for DNS. // New create a new dns.Client that queries localhost for DNS.

View File

@@ -23,7 +23,7 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
} }
if domain := ctx.GetTargetDomain(); len(domain) != 0 { if domain := ctx.GetTargetDomain(); len(domain) != 0 {
ips, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{ ips, _, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: false, FakeEnable: false,

View File

@@ -2,6 +2,7 @@ package stats
import ( import (
"context" "context"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -30,6 +31,8 @@ type OnlineMap interface {
AddIP(string) AddIP(string)
// List is the current OnlineMap ip list. // List is the current OnlineMap ip list.
List() []string List() []string
// IpTimeMap return client ips and their last access time.
IpTimeMap() map[string]time.Time
} }
// Channel is the interface for stats channel. // Channel is the interface for stats channel.

32
go.mod
View File

@@ -1,18 +1,18 @@
module github.com/xtls/xray-core module github.com/xtls/xray-core
go 1.23 go 1.24
require ( require (
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0
github.com/cloudflare/circl v1.5.0 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.6.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.63 github.com/miekg/dns v1.1.64
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.0
github.com/quic-go/quic-go v0.49.0 github.com/quic-go/quic-go v0.50.1
github.com/refraction-networking/utls v1.6.7 github.com/refraction-networking/utls v1.6.7
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
@@ -22,16 +22,16 @@ require (
github.com/vishvananda/netlink v1.3.0 github.com/vishvananda/netlink v1.3.0
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.32.0 golang.org/x/crypto v0.36.0
golang.org/x/net v0.34.0 golang.org/x/net v0.38.0
golang.org/x/sync v0.10.0 golang.org/x/sync v0.12.0
golang.org/x/sys v0.29.0 golang.org/x/sys v0.31.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.70.0 google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.4 google.golang.org/protobuf v1.36.6
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0
h12.io/socks v1.0.3 h12.io/socks v1.0.3
lukechampine.com/blake3 v1.3.0 lukechampine.com/blake3 v1.4.0
) )
require ( require (
@@ -50,12 +50,12 @@ require (
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // 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/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.21.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/text v0.21.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.26.0 // indirect golang.org/x/tools v0.30.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-20241202173237-19429a94021a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // 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
) )

82
go.sum
View File

@@ -2,8 +2,8 @@ github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJS
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 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=
@@ -24,8 +24,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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=
@@ -38,8 +38,8 @@ github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0N
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
@@ -54,8 +54,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
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.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94= github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s= github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
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=
@@ -79,38 +79,40 @@ github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZla
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg= github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
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/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= 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/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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
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.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.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=
@@ -119,21 +121,21 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
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.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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -141,12 +143,12 @@ 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-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -160,5 +162,5 @@ gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
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.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=

View File

@@ -203,6 +203,24 @@ func (list *PortList) Build() *net.PortList {
return portList return portList
} }
func (v PortList) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func (v PortList) String() string {
ports := []string{}
for _, port := range v.Range {
if port.From == port.To {
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, ",")
}
// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
func (list *PortList) UnmarshalJSON(data []byte) error { func (list *PortList) UnmarshalJSON(data []byte) error {
var listStr string var listStr string

View File

@@ -12,13 +12,17 @@ import (
) )
type NameServerConfig struct { type NameServerConfig struct {
Address *Address Address *Address `json:"address"`
ClientIP *Address ClientIP *Address `json:"clientIp"`
Port uint16 Port uint16 `json:"port"`
SkipFallback bool SkipFallback bool `json:"skipFallback"`
Domains []string Domains []string `json:"domains"`
ExpectIPs StringList ExpectedIPs StringList `json:"expectedIPs"`
QueryStrategy string ExpectIPs StringList `json:"expectIPs"`
QueryStrategy string `json:"queryStrategy"`
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"`
Tag string `json:"tag"`
TimeoutMs uint64 `json:"timeoutMs"`
} }
func (c *NameServerConfig) UnmarshalJSON(data []byte) error { func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
@@ -34,8 +38,12 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
Port uint16 `json:"port"` Port uint16 `json:"port"`
SkipFallback bool `json:"skipFallback"` SkipFallback bool `json:"skipFallback"`
Domains []string `json:"domains"` Domains []string `json:"domains"`
ExpectIPs StringList `json:"expectIps"` ExpectedIPs StringList `json:"expectedIPs"`
ExpectIPs StringList `json:"expectIPs"`
QueryStrategy string `json:"queryStrategy"` QueryStrategy string `json:"queryStrategy"`
AllowUnexpectedIPs bool `json:"allowUnexpectedIps"`
Tag string `json:"tag"`
TimeoutMs uint64 `json:"timeoutMs"`
} }
if err := json.Unmarshal(data, &advanced); err == nil { if err := json.Unmarshal(data, &advanced); err == nil {
c.Address = advanced.Address c.Address = advanced.Address
@@ -43,8 +51,12 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
c.Port = advanced.Port c.Port = advanced.Port
c.SkipFallback = advanced.SkipFallback c.SkipFallback = advanced.SkipFallback
c.Domains = advanced.Domains c.Domains = advanced.Domains
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.TimeoutMs = advanced.TimeoutMs
return nil return nil
} }
@@ -92,9 +104,13 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
}) })
} }
geoipList, err := ToCidrList(c.ExpectIPs) var expectedIPs = c.ExpectedIPs
if len(expectedIPs) == 0 {
expectedIPs = c.ExpectIPs
}
geoipList, err := ToCidrList(expectedIPs)
if err != nil { if err != nil {
return nil, errors.New("invalid IP rule: ", c.ExpectIPs).Base(err) return nil, errors.New("invalid IP rule: ", expectedIPs).Base(err)
} }
var myClientIP []byte var myClientIP []byte
@@ -117,6 +133,9 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
Geoip: geoipList, Geoip: geoipList,
OriginalRules: originalRules, OriginalRules: originalRules,
QueryStrategy: resolveQueryStrategy(c.QueryStrategy), QueryStrategy: resolveQueryStrategy(c.QueryStrategy),
AllowUnexpectedIPs: c.AllowUnexpectedIPs,
Tag: c.Tag,
TimeoutMs: c.TimeoutMs,
}, nil }, nil
} }

View File

@@ -7,14 +7,20 @@ import (
type MetricsConfig struct { type MetricsConfig struct {
Tag string `json:"tag"` Tag string `json:"tag"`
Listen string `json:"listen"`
} }
func (c *MetricsConfig) Build() (*metrics.Config, error) { func (c *MetricsConfig) Build() (*metrics.Config, error) {
if c.Listen == "" && c.Tag == "" {
return nil, errors.New("Metrics must have a tag or listen address.")
}
// If the tag is empty but have "listen" set a default "Metrics" for compatibility.
if c.Tag == "" { if c.Tag == "" {
return nil, errors.New("metrics tag can't be empty.") c.Tag = "Metrics"
} }
return &metrics.Config{ return &metrics.Config{
Tag: c.Tag, Tag: c.Tag,
Listen: c.Listen,
}, nil }, nil
} }

View File

@@ -334,7 +334,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
func readFileOrString(f string, s []string) ([]byte, error) { func readFileOrString(f string, s []string) ([]byte, error) {
if len(f) > 0 { if len(f) > 0 {
return filesystem.ReadFile(f) return filesystem.ReadCert(f)
} }
if len(s) > 0 { if len(s) > 0 {
return []byte(strings.Join(s, "\n")), nil return []byte(strings.Join(s, "\n")), nil
@@ -411,6 +411,7 @@ type TLSConfig struct {
CurvePreferences *StringList `json:"curvePreferences"` CurvePreferences *StringList `json:"curvePreferences"`
MasterKeyLog string `json:"masterKeyLog"` MasterKeyLog string `json:"masterKeyLog"`
ServerNameToVerify string `json:"serverNameToVerify"` ServerNameToVerify string `json:"serverNameToVerify"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
} }
// Build implements Buildable. // Build implements Buildable.
@@ -432,6 +433,13 @@ func (c *TLSConfig) Build() (proto.Message, error) {
if c.ALPN != nil && len(*c.ALPN) > 0 { if c.ALPN != nil && len(*c.ALPN) > 0 {
config.NextProtocol = []string(*c.ALPN) config.NextProtocol = []string(*c.ALPN)
} }
if len(config.NextProtocol) > 1 {
for _, p := range config.NextProtocol {
if tcp.IsFromMitm(p) {
return nil, errors.New(`only one element is allowed in "alpn" when using "fromMitm" in it`)
}
}
}
if c.CurvePreferences != nil && len(*c.CurvePreferences) > 0 { if c.CurvePreferences != nil && len(*c.CurvePreferences) > 0 {
config.CurvePreferences = []string(*c.CurvePreferences) config.CurvePreferences = []string(*c.CurvePreferences)
} }
@@ -442,7 +450,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
config.CipherSuites = c.CipherSuites config.CipherSuites = c.CipherSuites
config.Fingerprint = strings.ToLower(c.Fingerprint) config.Fingerprint = strings.ToLower(c.Fingerprint)
if config.Fingerprint != "unsafe" && tls.GetFingerprint(config.Fingerprint) == nil { if config.Fingerprint != "unsafe" && tls.GetFingerprint(config.Fingerprint) == nil {
return nil, errors.New(`unknown fingerprint: `, config.Fingerprint) return nil, errors.New(`unknown "fingerprint": `, config.Fingerprint)
} }
config.RejectUnknownSni = c.RejectUnknownSNI config.RejectUnknownSni = c.RejectUnknownSNI
@@ -469,10 +477,11 @@ func (c *TLSConfig) Build() (proto.Message, error) {
} }
config.MasterKeyLog = c.MasterKeyLog config.MasterKeyLog = c.MasterKeyLog
config.ServerNameToVerify = c.ServerNameToVerify
if config.ServerNameToVerify != "" && config.Fingerprint == "unsafe" { if c.ServerNameToVerify != "" {
return nil, errors.New(`serverNameToVerify only works with uTLS for now`) return nil, errors.PrintRemovedFeatureError(`"serverNameToVerify"`, `"verifyPeerCertInNames"`)
} }
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
return config, nil return config, nil
} }
@@ -493,6 +502,7 @@ type REALITYConfig struct {
Fingerprint string `json:"fingerprint"` Fingerprint string `json:"fingerprint"`
ServerName string `json:"serverName"` ServerName string `json:"serverName"`
Password string `json:"password"`
PublicKey string `json:"publicKey"` PublicKey string `json:"publicKey"`
ShortId string `json:"shortId"` ShortId string `json:"shortId"`
SpiderX string `json:"spiderX"` SpiderX string `json:"spiderX"`
@@ -601,11 +611,14 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
if len(c.ServerNames) != 0 { if len(c.ServerNames) != 0 {
return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`) return nil, errors.New(`non-empty "serverNames", please use "serverName" instead`)
} }
if c.Password != "" {
c.PublicKey = c.Password
}
if c.PublicKey == "" { if c.PublicKey == "" {
return nil, errors.New(`empty "publicKey"`) return nil, errors.New(`empty "password"`)
} }
if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 { if config.PublicKey, err = base64.RawURLEncoding.DecodeString(c.PublicKey); err != nil || len(config.PublicKey) != 32 {
return nil, errors.New(`invalid "publicKey": `, c.PublicKey) return nil, errors.New(`invalid "password": `, c.PublicKey)
} }
if len(c.ShortIds) != 0 { if len(c.ShortIds) != 0 {
return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`) return nil, errors.New(`non-empty "shortIds", please use "shortId" instead`)
@@ -678,6 +691,7 @@ func (p TransportProtocol) Build() (string, error) {
} }
type CustomSockoptConfig struct { type CustomSockoptConfig struct {
Network string `json:"network"`
Level string `json:"level"` Level string `json:"level"`
Opt string `json:"opt"` Opt string `json:"opt"`
Value string `json:"value"` Value string `json:"value"`
@@ -702,6 +716,7 @@ type SocketConfig struct {
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"`
} }
// Build implements Buildable. // Build implements Buildable.
@@ -763,6 +778,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
for _, copt := range c.CustomSockopt { for _, copt := range c.CustomSockopt {
customSockopt := &internet.CustomSockopt{ customSockopt := &internet.CustomSockopt{
Network: copt.Network,
Level: copt.Level, Level: copt.Level,
Opt: copt.Opt, Opt: copt.Opt,
Value: copt.Value, Value: copt.Value,
@@ -771,6 +787,26 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
customSockopts = append(customSockopts, customSockopt) customSockopts = append(customSockopts, customSockopt)
} }
addressPortStrategy := internet.AddressPortStrategy_None
switch strings.ToLower(c.AddressPortStrategy) {
case "none", "":
addressPortStrategy = internet.AddressPortStrategy_None
case "srvportonly":
addressPortStrategy = internet.AddressPortStrategy_SrvPortOnly
case "srvaddressonly":
addressPortStrategy = internet.AddressPortStrategy_SrvAddressOnly
case "srvportandaddress":
addressPortStrategy = internet.AddressPortStrategy_SrvPortAndAddress
case "txtportonly":
addressPortStrategy = internet.AddressPortStrategy_TxtPortOnly
case "txtaddressonly":
addressPortStrategy = internet.AddressPortStrategy_TxtAddressOnly
case "txtportandaddress":
addressPortStrategy = internet.AddressPortStrategy_TxtPortAndAddress
default:
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
}
return &internet.SocketConfig{ return &internet.SocketConfig{
Mark: c.Mark, Mark: c.Mark,
Tfo: tfo, Tfo: tfo,
@@ -789,6 +825,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
Interface: c.Interface, Interface: c.Interface,
TcpMptcp: c.TcpMptcp, TcpMptcp: c.TcpMptcp,
CustomSockopt: customSockopts, CustomSockopt: customSockopts,
AddressPortStrategy: addressPortStrategy,
}, nil }, nil
} }

View File

@@ -67,7 +67,7 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
var err error var err error
config.SecretKey, err = ParseWireGuardKey(c.SecretKey) config.SecretKey, err = ParseWireGuardKey(c.SecretKey)
if err != nil { if err != nil {
return nil, err return nil, errors.New("invalid WireGuard secret key: %w", err)
} }
if c.Address == nil { if c.Address == nil {
@@ -126,6 +126,10 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
func ParseWireGuardKey(str string) (string, error) { func ParseWireGuardKey(str string) (string, error) {
var err error var err error
if str == "" {
return "", errors.New("key must not be empty")
}
if len(str)%2 == 0 { if len(str)%2 == 0 {
_, err = hex.DecodeString(str) _, err = hex.DecodeString(str)
if err == nil { if err == nil {

View File

@@ -241,14 +241,14 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
} }
rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol) rawConfig, err := inboundConfigLoader.LoadWithID(settings, c.Protocol)
if err != nil { if err != nil {
return nil, errors.New("failed to load inbound detour config.").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.Redirect
} }
ts, err := rawConfig.(Buildable).Build() ts, err := rawConfig.(Buildable).Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build inbound handler for protocol ", c.Protocol).Base(err)
} }
return &core.InboundHandlerConfig{ return &core.InboundHandlerConfig{
@@ -292,16 +292,18 @@ 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" {
return nil, errors.New("unable to send through: " + address.String()) return nil, errors.New("unable to send through: " + address.String())
} }
} }
}
senderSettings.Via = address.Build() senderSettings.Via = address.Build()
} }
if c.StreamSetting != nil { if c.StreamSetting != nil {
ss, err := c.StreamSetting.Build() ss, err := c.StreamSetting.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build stream settings for outbound detour").Base(err)
} }
senderSettings.StreamSettings = ss senderSettings.StreamSettings = ss
} }
@@ -309,7 +311,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.ProxySettings != nil { if c.ProxySettings != nil {
ps, err := c.ProxySettings.Build() ps, err := c.ProxySettings.Build()
if err != nil { if err != nil {
return nil, errors.New("invalid outbound detour proxy settings.").Base(err) return nil, errors.New("invalid outbound detour proxy settings").Base(err)
} }
if ps.TransportLayerProxy { if ps.TransportLayerProxy {
if senderSettings.StreamSettings != nil { if senderSettings.StreamSettings != nil {
@@ -329,7 +331,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.MuxSettings != nil { if c.MuxSettings != nil {
ms, err := c.MuxSettings.Build() ms, err := c.MuxSettings.Build()
if err != nil { if err != nil {
return nil, errors.New("failed to build Mux config.").Base(err) return nil, errors.New("failed to build Mux config").Base(err)
} }
senderSettings.MultiplexSettings = ms senderSettings.MultiplexSettings = ms
} }
@@ -340,11 +342,11 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
} }
rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol) rawConfig, err := outboundConfigLoader.LoadWithID(settings, c.Protocol)
if err != nil { if err != nil {
return nil, errors.New("failed to parse to outbound detour config.").Base(err) return nil, errors.New("failed to load outbound detour config for protocol ", c.Protocol).Base(err)
} }
ts, err := rawConfig.(Buildable).Build() ts, err := rawConfig.(Buildable).Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build outbound handler for protocol ", c.Protocol).Base(err)
} }
return &core.OutboundHandlerConfig{ return &core.OutboundHandlerConfig{
@@ -488,7 +490,7 @@ func (c *Config) Override(o *Config, fn string) {
// Build implements Buildable. // Build implements Buildable.
func (c *Config) Build() (*core.Config, error) { func (c *Config) Build() (*core.Config, error) {
if err := PostProcessConfigureFile(c); err != nil { if err := PostProcessConfigureFile(c); err != nil {
return nil, err return nil, errors.New("failed to post-process configuration file").Base(err)
} }
config := &core.Config{ config := &core.Config{
@@ -502,21 +504,21 @@ func (c *Config) Build() (*core.Config, error) {
if c.API != nil { if c.API != nil {
apiConf, err := c.API.Build() apiConf, err := c.API.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build API configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(apiConf)) config.App = append(config.App, serial.ToTypedMessage(apiConf))
} }
if c.Metrics != nil { if c.Metrics != nil {
metricsConf, err := c.Metrics.Build() metricsConf, err := c.Metrics.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build metrics configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(metricsConf)) config.App = append(config.App, serial.ToTypedMessage(metricsConf))
} }
if c.Stats != nil { if c.Stats != nil {
statsConf, err := c.Stats.Build() statsConf, err := c.Stats.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build stats configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(statsConf)) config.App = append(config.App, serial.ToTypedMessage(statsConf))
} }
@@ -534,7 +536,7 @@ func (c *Config) Build() (*core.Config, error) {
if c.RouterConfig != nil { if c.RouterConfig != nil {
routerConfig, err := c.RouterConfig.Build() routerConfig, err := c.RouterConfig.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build routing configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(routerConfig)) config.App = append(config.App, serial.ToTypedMessage(routerConfig))
} }
@@ -542,7 +544,7 @@ func (c *Config) Build() (*core.Config, error) {
if c.DNSConfig != nil { if c.DNSConfig != nil {
dnsApp, err := c.DNSConfig.Build() dnsApp, err := c.DNSConfig.Build()
if err != nil { if err != nil {
return nil, errors.New("failed to parse DNS config").Base(err) return nil, errors.New("failed to build DNS configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(dnsApp)) config.App = append(config.App, serial.ToTypedMessage(dnsApp))
} }
@@ -550,7 +552,7 @@ func (c *Config) Build() (*core.Config, error) {
if c.Policy != nil { if c.Policy != nil {
pc, err := c.Policy.Build() pc, err := c.Policy.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build policy configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(pc)) config.App = append(config.App, serial.ToTypedMessage(pc))
} }
@@ -558,7 +560,7 @@ func (c *Config) Build() (*core.Config, error) {
if c.Reverse != nil { if c.Reverse != nil {
r, err := c.Reverse.Build() r, err := c.Reverse.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build reverse configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(r)) config.App = append(config.App, serial.ToTypedMessage(r))
} }
@@ -566,7 +568,7 @@ func (c *Config) Build() (*core.Config, error) {
if c.FakeDNS != nil { if c.FakeDNS != nil {
r, err := c.FakeDNS.Build() r, err := c.FakeDNS.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build fake DNS configuration").Base(err)
} }
config.App = append([]*serial.TypedMessage{serial.ToTypedMessage(r)}, config.App...) config.App = append([]*serial.TypedMessage{serial.ToTypedMessage(r)}, config.App...)
} }
@@ -574,7 +576,7 @@ func (c *Config) Build() (*core.Config, error) {
if c.Observatory != nil { if c.Observatory != nil {
r, err := c.Observatory.Build() r, err := c.Observatory.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build observatory configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(r)) config.App = append(config.App, serial.ToTypedMessage(r))
} }
@@ -582,7 +584,7 @@ func (c *Config) Build() (*core.Config, error) {
if c.BurstObservatory != nil { if c.BurstObservatory != nil {
r, err := c.BurstObservatory.Build() r, err := c.BurstObservatory.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build burst observatory configuration").Base(err)
} }
config.App = append(config.App, serial.ToTypedMessage(r)) config.App = append(config.App, serial.ToTypedMessage(r))
} }
@@ -600,7 +602,7 @@ func (c *Config) Build() (*core.Config, error) {
for _, rawInboundConfig := range inbounds { for _, rawInboundConfig := range inbounds {
ic, err := rawInboundConfig.Build() ic, err := rawInboundConfig.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build inbound config with tag ", rawInboundConfig.Tag).Base(err)
} }
config.Inbound = append(config.Inbound, ic) config.Inbound = append(config.Inbound, ic)
} }
@@ -614,7 +616,7 @@ func (c *Config) Build() (*core.Config, error) {
for _, rawOutboundConfig := range outbounds { for _, rawOutboundConfig := range outbounds {
oc, err := rawOutboundConfig.Build() oc, err := rawOutboundConfig.Build()
if err != nil { if err != nil {
return nil, err return nil, errors.New("failed to build outbound config with tag ", rawOutboundConfig.Tag).Base(err)
} }
config.Outbound = append(config.Outbound, oc) config.Outbound = append(config.Outbound, oc)
} }

View File

@@ -89,12 +89,11 @@ func whichProtoc(suffix, targetedVersion string) (string, error) {
path, err := exec.LookPath(protoc) path, err := exec.LookPath(protoc)
if err != nil { if err != nil {
errStr := fmt.Sprintf(` return "", fmt.Errorf(`
Command "%s" not found. Command "%s" not found.
Make sure that %s is in your system path or current path. Make sure that %s is in your system path or current path.
Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases
`, protoc, protoc, protoc, targetedVersion) `, protoc, protoc, protoc, targetedVersion)
return "", fmt.Errorf(errStr)
} }
return path, nil return path, nil
} }

View File

@@ -27,5 +27,6 @@ var CmdAPI = &base.Command{
cmdRemoveRules, cmdRemoveRules,
cmdSourceIpBlock, cmdSourceIpBlock,
cmdOnlineStats, cmdOnlineStats,
cmdOnlineStatsIpList,
}, },
} }

View File

@@ -13,25 +13,20 @@ import (
var cmdBalancerInfo = &base.Command{ var cmdBalancerInfo = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...", UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...",
Short: "balancer information", Short: "Retrieve balancer information",
Long: ` Long: `
Get information of specified balancers, including health, strategy Retrieve information of specified balancers, including health, strategy and selecting.
and selecting. If no balancer tag specified, get information of If no balancer tag specified, information for all balancers is returned.
all balancers.
> Make sure you have "RoutingService" set in "config.api.services" > Ensure that "RoutingService" is enabled under "config.api.services" in the server configuration.
of server config.
Arguments: Arguments:
-json
Use json output.
-s, -server <server:port> -s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout <seconds> -t, -timeout <seconds>
Timeout seconds to call API. Default 3 Timeout in seconds for calling API. Default 3
Example: Example:

View File

@@ -7,31 +7,27 @@ import (
var cmdBalancerOverride = &base.Command{ var cmdBalancerOverride = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> outboundTag", UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> outboundTag <-r>",
Short: "balancer override", Short: "Override balancer",
Long: ` Long: `
Override a balancer's selection. Override the selection target of a balancer.
> Make sure you have "RoutingService" set in "config.api.services" > Ensure that the "RoutingService" is properly configured under "config.api.services" in the server configuration.
of server config.
Once a balancer's selecting is overridden: Once the balancer's selection is overridden:
- The balancer's selection result will always be outboundTag - The balancer's selection result will always be outboundTag
Arguments: Arguments:
-r, -remove -s, -server <server:port>
Remove the overridden
-r, -remove
Remove the override
-s, -server
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout -t, -timeout <seconds>
Timeout seconds to call API. Default 3 Timeout in seconds for calling API. Default 3
-r, -remove
Remove the existing override.
Example: Example:

View File

@@ -8,19 +8,27 @@ import (
var cmdInboundUser = &base.Command{ var cmdInboundUser = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api inbounduser [--server=127.0.0.1:8080] -tag=tag [-email=email]", UsageLine: "{{.Exec}} api inbounduser [--server=127.0.0.1:8080] -tag=tag [-email=email]",
Short: "Get Inbound User", Short: "Retrieve inbound user(s)",
Long: ` Long: `
Get User info from an inbound. Get User info from an inbound.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
-tag -tag
Inbound tag Inbound tag
-email -email
User email. If email is not given, will get all users The user's email address. If not provided, all users will be retrieved.
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name"
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" -email="xray@love.com" {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" -email="xray@love.com"
`, `,
Run: executeInboundUser, Run: executeInboundUser,

View File

@@ -8,17 +8,23 @@ import (
var cmdInboundUserCount = &base.Command{ var cmdInboundUserCount = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api inboundusercount [--server=127.0.0.1:8080] -tag=tag", UsageLine: "{{.Exec}} api inboundusercount [--server=127.0.0.1:8080] -tag=tag",
Short: "Get Inbound User Count", Short: "Retrieve inbound user count",
Long: ` Long: `
Get User count from an inbound. Retrieve the user count for a specified inbound tag.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
-tag -tag
Inbound tag Inbound tag
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name" {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="tag name"
`, `,
Run: executeInboundUserCount, Run: executeInboundUserCount,

View File

@@ -15,12 +15,17 @@ var cmdAddInbounds = &base.Command{
Short: "Add inbounds", Short: "Add inbounds",
Long: ` Long: `
Add inbounds to Xray. Add inbounds to Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
`, `,
Run: executeAddInbounds, Run: executeAddInbounds,

View File

@@ -14,12 +14,17 @@ var cmdRemoveInbounds = &base.Command{
Short: "Remove inbounds", Short: "Remove inbounds",
Long: ` Long: `
Remove inbounds from Xray. Remove inbounds from Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name"
`, `,
Run: executeRemoveInbounds, Run: executeRemoveInbounds,

View File

@@ -11,11 +11,18 @@ var cmdRestartLogger = &base.Command{
Short: "Restart the logger", Short: "Restart the logger",
Long: ` Long: `
Restart the logger of Xray. Restart the logger of Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080
`, `,
Run: executeRestartLogger, Run: executeRestartLogger,
} }

View File

@@ -15,12 +15,17 @@ var cmdAddOutbounds = &base.Command{
Short: "Add outbounds", Short: "Add outbounds",
Long: ` Long: `
Add outbounds to Xray. Add outbounds to Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
`, `,
Run: executeAddOutbounds, Run: executeAddOutbounds,

View File

@@ -14,12 +14,17 @@ var cmdRemoveOutbounds = &base.Command{
Short: "Remove outbounds", Short: "Remove outbounds",
Long: ` Long: `
Remove outbounds from Xray. Remove outbounds from Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name" {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json "tag name"
`, `,
Run: executeRemoveOutbounds, Run: executeRemoveOutbounds,

View File

@@ -16,15 +16,20 @@ var cmdAddRules = &base.Command{
Short: "Add routing rules", Short: "Add routing rules",
Long: ` Long: `
Add routing rules to Xray. Add routing rules to Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
-t, -timeout <seconds>
Timeout seconds to call API. Default 3 Timeout seconds to call API. Default 3
-append -append
append or replace config. Default false Append to the existing configuration instead of replacing it. Default false
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
`, `,
Run: executeAddRules, Run: executeAddRules,

View File

@@ -9,16 +9,21 @@ import (
var cmdRemoveRules = &base.Command{ var cmdRemoveRules = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api rmrules [--server=127.0.0.1:8080] ruleTag1 ruleTag2...", UsageLine: "{{.Exec}} api rmrules [--server=127.0.0.1:8080] [ruleTag]...",
Short: "Remove routing rules by ruleTag", Short: "Remove routing rules by ruleTag",
Long: ` Long: `
Remove routing rules by ruleTag from Xray. Remove routing rules by ruleTag from Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 ruleTag1 ruleTag2 {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 ruleTag1 ruleTag2
`, `,
Run: executeRemoveRules, Run: executeRemoveRules,

View File

@@ -14,25 +14,34 @@ import (
var cmdSourceIpBlock = &base.Command{ var cmdSourceIpBlock = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api sib [--server=127.0.0.1:8080] -outbound=blocked -inbound=socks 1.2.3.4", UsageLine: "{{.Exec}} api sib [--server=127.0.0.1:8080] -outbound=blocked -inbound=socks 1.2.3.4",
Short: "Drop connections by source ip", Short: "Block connections by source IP",
Long: ` Long: `
Drop connections by source ip. Block connections by source IP address.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
-outbound -outbound
route traffic to specific outbound. Specifies the outbound tag.
-inbound -inbound
target traffig from specific inbound. Specifies the inbound tag.
-ruletag -ruletag
set ruleTag. Default sourceIpBlock The ruleTag. Default sourceIpBlock
-reset -reset
remove ruletag and apply new source IPs. Default false remove ruletag and apply new source IPs. Default false
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -outbound=blocked -inbound=socks 1.2.3.4
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -outbound=blocked -inbound=socks 1.2.3.4 -reset
`, `,
Run: executeSourceIpBlock, Run: executeSourceIpBlock,
} }

View File

@@ -8,19 +8,26 @@ import (
var cmdGetStats = &base.Command{ var cmdGetStats = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api stats [--server=127.0.0.1:8080] [-name '']", UsageLine: "{{.Exec}} api stats [--server=127.0.0.1:8080] [-name '']",
Short: "Get statistics", Short: "Retrieve statistics",
Long: ` Long: `
Get statistics from Xray. Retrieve the statistics from Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
-name -name
Name of the stat counter. Name of the counter.
-reset -reset
Reset the counter to fetching its value. Reset the counter after fetching their values. Default false
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -name "inbound>>>statin>>>traffic>>>downlink" {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -name "inbound>>>statin>>>traffic>>>downlink"
`, `,
Run: executeGetStats, Run: executeGetStats,

View File

@@ -7,21 +7,25 @@ import (
var cmdOnlineStats = &base.Command{ var cmdOnlineStats = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api statsonline [--server=127.0.0.1:8080] [-name '']", UsageLine: "{{.Exec}} api statsonline [--server=127.0.0.1:8080] [-email '']",
Short: "Get online user", Short: "Retrieve the online session count for a user",
Long: ` Long: `
Get statistics from Xray. Retrieve the current number of active sessions for a user from Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
-email -email
email of the user. The user's email address.
-reset
Reset the counter to fetching its value.
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email "user1@test.com"
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email "xray@love.com"
`, `,
Run: executeOnlineStats, Run: executeOnlineStats,
} }

View File

@@ -0,0 +1,51 @@
package api
import (
statsService "github.com/xtls/xray-core/app/stats/command"
"github.com/xtls/xray-core/main/commands/base"
)
var cmdOnlineStatsIpList = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api statsonlineiplist [--server=127.0.0.1:8080] [-email '']",
Short: "Retrieve a user's online IP addresses and access times",
Long: `
Retrieve the online IP addresses and corresponding access timestamps for a user from 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
-email
The user's email address.
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -email "xray@love.com"
`,
Run: executeOnlineStatsIpList,
}
func executeOnlineStatsIpList(cmd *base.Command, args []string) {
setSharedFlags(cmd)
email := cmd.Flag.String("email", "", "")
cmd.Flag.Parse(args)
statName := "user>>>" + *email + ">>>online"
conn, ctx, close := dialAPIServer()
defer close()
client := statsService.NewStatsServiceClient(conn)
r := &statsService.GetStatsRequest{
Name: statName,
Reset_: false,
}
resp, err := client.GetStatsOnlineIpList(ctx, r)
if err != nil {
base.Fatalf("failed to get stats: %s", err)
}
showJSONResponse(resp)
}

View File

@@ -11,16 +11,23 @@ var cmdQueryStats = &base.Command{
Short: "Query statistics", Short: "Query statistics",
Long: ` Long: `
Query statistics from Xray. Query statistics from Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
-pattern -pattern
Pattern of the query. Filter pattern for the statistics query.
-reset -reset
Reset the counter to fetching its value. Reset the counter after fetching their values. Default false
Example: Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -pattern "counter_" {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -pattern "counter_"
`, `,
Run: executeQueryStats, Run: executeQueryStats,

View File

@@ -8,14 +8,21 @@ import (
var cmdSysStats = &base.Command{ var cmdSysStats = &base.Command{
CustomFlags: true, CustomFlags: true,
UsageLine: "{{.Exec}} api statssys [--server=127.0.0.1:8080]", UsageLine: "{{.Exec}} api statssys [--server=127.0.0.1:8080]",
Short: "Get system statistics", Short: "Retrieve system statistics",
Long: ` Long: `
Get system statistics from Xray. Retrieve system statistics from Xray.
Arguments: Arguments:
-s, -server
-s, -server <server:port>
The API server address. Default 127.0.0.1:8080 The API server address. Default 127.0.0.1:8080
-t, -timeout
Timeout seconds to call API. Default 3 -t, -timeout <seconds>
Timeout in seconds for calling API. Default 3
Example:
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080
`, `,
Run: executeSysStats, Run: executeSysStats,
} }

View File

@@ -50,17 +50,17 @@ func executeTypedMessageToJson(cmd *base.Command, args []string) {
reader, err := confloader.LoadConfig(cmd.Flag.Arg(0)) reader, err := confloader.LoadConfig(cmd.Flag.Arg(0))
if err != nil { if err != nil {
base.Fatalf(err.Error()) base.Fatalf("failed to load config: %s", err)
} }
b, err := io.ReadAll(reader) b, err := io.ReadAll(reader)
if err != nil { if err != nil {
base.Fatalf(err.Error()) base.Fatalf("failed to read config: %s", err)
} }
tm := cserial.TypedMessage{} tm := cserial.TypedMessage{}
if err = json.Unmarshal(b, &tm); err != nil { if err = json.Unmarshal(b, &tm); err != nil {
base.Fatalf(err.Error()) base.Fatalf("failed to unmarshal config: %s", err)
} }
if j, ok := creflect.MarshalToJson(&tm, injectTypeInfo); ok { if j, ok := creflect.MarshalToJson(&tm, injectTypeInfo); ok {

View File

@@ -53,12 +53,12 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
} }
if len(unnamedArgs) < 1 { if len(unnamedArgs) < 1 {
base.Fatalf("empty config list") base.Fatalf("invalid config list length: %d", len(unnamedArgs))
} }
pbConfig, err := core.LoadConfig("auto", unnamedArgs) pbConfig, err := core.LoadConfig("auto", unnamedArgs)
if err != nil { if err != nil {
base.Fatalf(err.Error()) base.Fatalf("failed to load config: %s", err)
} }
if optDump { if optDump {

View File

@@ -42,7 +42,8 @@ func Curve25519Genkey(StdEncoding bool, input_base64 string) {
// Modify random bytes using algorithm described at: // Modify random bytes using algorithm described at:
// https://cr.yp.to/ecdh.html. // https://cr.yp.to/ecdh.html.
privateKey[0] &= 248 privateKey[0] &= 248
privateKey[31] &= 127 | 64 privateKey[31] &= 127
privateKey[31] |= 64
if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil { if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
output = err.Error() output = err.Error()

View File

@@ -120,9 +120,9 @@ func writeFile(content []byte, name string) error {
func printFile(certificate *cert.Certificate, name string) error { func printFile(certificate *cert.Certificate, name string) error {
certPEM, keyPEM := certificate.ToPEM() certPEM, keyPEM := certificate.ToPEM()
return task.Run(context.Background(), func() error { return task.Run(context.Background(), func() error {
return writeFile(certPEM, name+"_cert.pem") return writeFile(certPEM, name+".crt")
}, func() error { }, func() error {
return writeFile(keyPEM, name+"_key.pem") return writeFile(keyPEM, name+".key")
}) })
} }

View File

@@ -45,6 +45,7 @@ The -dump flag tells Xray to print the merged config.
func init() { func init() {
cmdRun.Run = executeRun // break init loop cmdRun.Run = executeRun // break init loop
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
} }
var ( var (

View File

@@ -236,17 +236,18 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
var ips []net.IP var ips []net.IP
var err error var err error
var ttl uint32 = 600 var ttl4 uint32
var ttl6 uint32
switch qType { switch qType {
case dnsmessage.TypeA: case dnsmessage.TypeA:
ips, err = h.client.LookupIP(domain, dns.IPOption{ ips, ttl4, err = h.client.LookupIP(domain, dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: false, IPv6Enable: false,
FakeEnable: true, FakeEnable: true,
}) })
case dnsmessage.TypeAAAA: case dnsmessage.TypeAAAA:
ips, err = h.client.LookupIP(domain, dns.IPOption{ ips, ttl6, err = h.client.LookupIP(domain, dns.IPOption{
IPv4Enable: false, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: true, FakeEnable: true,
@@ -259,10 +260,6 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
return return
} }
if fkr0, ok := h.fdns.(dns.FakeDNSEngineRev0); ok && len(ips) > 0 && fkr0.IsIPInIPPool(net.IPAddress(ips[0])) {
ttl = 1
}
switch qType { switch qType {
case dnsmessage.TypeA: case dnsmessage.TypeA:
for i, ip := range ips { for i, ip := range ips {
@@ -293,16 +290,17 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
})) }))
common.Must(builder.StartAnswers()) common.Must(builder.StartAnswers())
rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl} rHeader4 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl4}
rHeader6 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl6}
for _, ip := range ips { for _, ip := range ips {
if len(ip) == net.IPv4len { if len(ip) == net.IPv4len {
var r dnsmessage.AResource var r dnsmessage.AResource
copy(r.A[:], ip) copy(r.A[:], ip)
common.Must(builder.AResource(rHeader, r)) common.Must(builder.AResource(rHeader4, r))
} else { } else {
var r dnsmessage.AAAAResource var r dnsmessage.AAAAResource
copy(r.AAAA[:], ip) copy(r.AAAA[:], ip)
common.Must(builder.AAAAResource(rHeader, r)) common.Must(builder.AAAAResource(rHeader6, r))
} }
} }
msgBytes, err := builder.Finish() msgBytes, err := builder.Finish()

View File

@@ -18,6 +18,7 @@ import (
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
) )
func init() { func init() {
@@ -63,10 +64,6 @@ func (d *DokodemoDoor) policy() policy.Session {
return p return p
} }
type hasHandshakeAddressContext interface {
HandshakeAddressContext(ctx context.Context) net.Address
}
// Process implements proxy.Inbound. // Process implements proxy.Inbound.
func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
errors.LogDebug(ctx, "processing connection from: ", conn.RemoteAddr()) errors.LogDebug(ctx, "processing connection from: ", conn.RemoteAddr())
@@ -86,11 +83,14 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
destinationOverridden = true destinationOverridden = true
} }
} }
if handshake, ok := conn.(hasHandshakeAddressContext); ok && !destinationOverridden { if tlsConn, ok := conn.(tls.Interface); ok && !destinationOverridden {
addr := handshake.HandshakeAddressContext(ctx) if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" {
if addr != nil { dest.Address = net.DomainAddress(serverName)
dest.Address = addr
destinationOverridden = true destinationOverridden = true
ctx = session.ContextWithMitmServerName(ctx, serverName)
}
if tlsConn.NegotiatedProtocol() != "h2" {
ctx = session.ContextWithMitmAlpn11(ctx, true)
} }
} }
} }

View File

@@ -4,12 +4,12 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"io" "io"
"math/big"
"time" "time"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
"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/crypto"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
@@ -71,13 +71,13 @@ func (h *Handler) policy() policy.Session {
} }
func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address { func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
ips, err := h.dns.LookupIP(domain, dns.IPOption{ ips, _, err := h.dns.LookupIP(domain, dns.IPOption{
IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && h.config.preferIP4(), IPv4Enable: (localAddr == nil || localAddr.Family().IsIPv4()) && h.config.preferIP4(),
IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && h.config.preferIP6(), IPv6Enable: (localAddr == nil || localAddr.Family().IsIPv6()) && h.config.preferIP6(),
}) })
{ // Resolve fallback { // Resolve fallback
if (len(ips) == 0 || err != nil) && h.config.hasFallback() && localAddr == nil { if (len(ips) == 0 || err != nil) && h.config.hasFallback() && localAddr == nil {
ips, err = h.dns.LookupIP(domain, dns.IPOption{ ips, _, err = h.dns.LookupIP(domain, dns.IPOption{
IPv4Enable: h.config.fallbackIP4(), IPv4Enable: h.config.fallbackIP4(),
IPv6Enable: h.config.fallbackIP6(), IPv6Enable: h.config.fallbackIP6(),
}) })
@@ -414,7 +414,7 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
noise = n.Packet noise = n.Packet
} else { } else {
//Random noise //Random noise
noise, err = GenerateRandomBytes(randBetween(int64(n.LengthMin), noise, err = GenerateRandomBytes(crypto.RandBetween(int64(n.LengthMin),
int64(n.LengthMax))) int64(n.LengthMax)))
} }
if err != nil { if err != nil {
@@ -423,7 +423,7 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(noise)}) w.Writer.WriteMultiBuffer(buf.MultiBuffer{buf.FromBytes(noise)})
if n.DelayMin != 0 || n.DelayMax != 0 { if n.DelayMin != 0 || n.DelayMax != 0 {
time.Sleep(time.Duration(randBetween(int64(n.DelayMin), int64(n.DelayMax))) * time.Millisecond) time.Sleep(time.Duration(crypto.RandBetween(int64(n.DelayMin), int64(n.DelayMax))) * time.Millisecond)
} }
} }
@@ -452,7 +452,7 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
buf := make([]byte, 1024) buf := make([]byte, 1024)
var hello []byte var hello []byte
for from := 0; ; { for from := 0; ; {
to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax))) to := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
if to > len(data) { if to > len(data) {
to = len(data) to = len(data)
} }
@@ -466,7 +466,7 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
hello = append(hello, buf[:5+l]...) hello = append(hello, buf[:5+l]...)
} else { } else {
_, err := f.writer.Write(buf[:5+l]) _, err := f.writer.Write(buf[:5+l])
time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond) time.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@@ -493,13 +493,13 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
return f.writer.Write(b) return f.writer.Write(b)
} }
for from := 0; ; { for from := 0; ; {
to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax))) to := from + int(crypto.RandBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax)))
if to > len(b) { if to > len(b) {
to = len(b) to = len(b)
} }
n, err := f.writer.Write(b[from:to]) n, err := f.writer.Write(b[from:to])
from += n from += n
time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond) time.Sleep(time.Duration(crypto.RandBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond)
if err != nil { if err != nil {
return from, err return from, err
} }
@@ -509,14 +509,6 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
} }
} }
// stolen from github.com/xtls/xray-core/transport/internet/reality
func randBetween(left int64, right int64) int64 {
if left == right {
return left
}
bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left))
return left + bigInt.Int64()
}
func GenerateRandomBytes(n int64) ([]byte, error) { func GenerateRandomBytes(n int64) ([]byte, error) {
b := make([]byte, n) b := make([]byte, n)
_, err := rand.Read(b) _, err := rand.Read(b)

View File

@@ -151,6 +151,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
} }
responseFunc := func() error { responseFunc := func() error {
ob.CanSpliceCopy = 1
defer timer.SetTimeout(p.Timeouts.UplinkOnly) defer timer.SetTimeout(p.Timeouts.UplinkOnly)
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
} }

View File

@@ -207,6 +207,7 @@ func (s *Server) handleConnect(ctx context.Context, _ *http.Request, reader *buf
} }
responseDone := func() error { responseDone := func() error {
inbound.CanSpliceCopy = 1
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
v2writer := buf.NewWriter(conn) v2writer := buf.NewWriter(conn)
@@ -293,7 +294,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
responseDone := func() error { responseDone := func() error {
responseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size) responseReader := bufio.NewReaderSize(&buf.BufferedReader{Reader: link.Reader}, buf.Size)
response, err := http.ReadResponse(responseReader, request) response, err := readResponseAndHandle100Continue(responseReader, request, writer)
if err == nil { if err == nil {
http_proto.RemoveHopByHopHeaders(response.Header) http_proto.RemoveHopByHopHeaders(response.Header)
if response.ContentLength >= 0 { if response.ContentLength >= 0 {
@@ -337,6 +338,38 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
return result return result
} }
// Sometimes, server might send 1xx response to client
// it should not be processed by http proxy handler, just forward it to client
func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) {
// have a little look of response
peekBytes, err := r.Peek(56)
if err == nil || err == bufio.ErrBufferFull {
str := string(peekBytes)
ResponseLine := strings.Split(str, "\r\n")[0]
_, status, _ := strings.Cut(ResponseLine, " ")
// only handle 1xx response
if strings.HasPrefix(status, "1") {
ResponseHeader1xx := []byte{}
// read until \r\n\r\n (end of http response header)
for {
data, err := r.ReadSlice('\n')
if err != nil {
return nil, errors.New("failed to read http 1xx response").Base(err)
}
ResponseHeader1xx = append(ResponseHeader1xx, data...)
if bytes.Equal(ResponseHeader1xx[len(ResponseHeader1xx)-4:], []byte{'\r', '\n', '\r', '\n'}) {
break
}
if len(ResponseHeader1xx) > 1024 {
return nil, errors.New("too big http 1xx response")
}
}
writer.Write(ResponseHeader1xx)
}
}
return http.ReadResponse(r, req)
}
func init() { func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig)) return NewServer(ctx, config.(*ServerConfig))

View File

@@ -107,19 +107,33 @@ type TrafficState struct {
IsTLS bool IsTLS bool
Cipher uint16 Cipher uint16
RemainingServerHello int32 RemainingServerHello int32
Inbound InboundState
Outbound OutboundState
}
type InboundState struct {
// reader link state // reader link state
WithinPaddingBuffers bool WithinPaddingBuffers bool
DownlinkReaderDirectCopy bool
UplinkReaderDirectCopy bool UplinkReaderDirectCopy bool
RemainingCommand int32 RemainingCommand int32
RemainingContent int32 RemainingContent int32
RemainingPadding int32 RemainingPadding int32
CurrentCommand int CurrentCommand int
// write link state // write link state
IsPadding bool IsPadding bool
DownlinkWriterDirectCopy bool DownlinkWriterDirectCopy bool
}
type OutboundState struct {
// reader link state
WithinPaddingBuffers bool
DownlinkReaderDirectCopy bool
RemainingCommand int32
RemainingContent int32
RemainingPadding int32
CurrentCommand int
// write link state
IsPadding bool
UplinkWriterDirectCopy bool UplinkWriterDirectCopy bool
} }
@@ -132,8 +146,8 @@ func NewTrafficState(userUUID []byte) *TrafficState {
IsTLS: false, IsTLS: false,
Cipher: 0, Cipher: 0,
RemainingServerHello: -1, RemainingServerHello: -1,
Inbound: InboundState{
WithinPaddingBuffers: true, WithinPaddingBuffers: true,
DownlinkReaderDirectCopy: false,
UplinkReaderDirectCopy: false, UplinkReaderDirectCopy: false,
RemainingCommand: -1, RemainingCommand: -1,
RemainingContent: -1, RemainingContent: -1,
@@ -141,7 +155,17 @@ func NewTrafficState(userUUID []byte) *TrafficState {
CurrentCommand: 0, CurrentCommand: 0,
IsPadding: true, IsPadding: true,
DownlinkWriterDirectCopy: false, DownlinkWriterDirectCopy: false,
},
Outbound: OutboundState{
WithinPaddingBuffers: true,
DownlinkReaderDirectCopy: false,
RemainingCommand: -1,
RemainingContent: -1,
RemainingPadding: -1,
CurrentCommand: 0,
IsPadding: true,
UplinkWriterDirectCopy: false, UplinkWriterDirectCopy: false,
},
} }
} }
@@ -166,28 +190,43 @@ func NewVisionReader(reader buf.Reader, state *TrafficState, isUplink bool, cont
func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) { func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
buffer, err := w.Reader.ReadMultiBuffer() buffer, err := w.Reader.ReadMultiBuffer()
if !buffer.IsEmpty() { if !buffer.IsEmpty() {
if w.trafficState.WithinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 { var withinPaddingBuffers *bool
var remainingContent *int32
var remainingPadding *int32
var currentCommand *int
var switchToDirectCopy *bool
if w.isUplink {
withinPaddingBuffers = &w.trafficState.Inbound.WithinPaddingBuffers
remainingContent = &w.trafficState.Inbound.RemainingContent
remainingPadding = &w.trafficState.Inbound.RemainingPadding
currentCommand = &w.trafficState.Inbound.CurrentCommand
switchToDirectCopy = &w.trafficState.Inbound.UplinkReaderDirectCopy
} else {
withinPaddingBuffers = &w.trafficState.Outbound.WithinPaddingBuffers
remainingContent = &w.trafficState.Outbound.RemainingContent
remainingPadding = &w.trafficState.Outbound.RemainingPadding
currentCommand = &w.trafficState.Outbound.CurrentCommand
switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy
}
if *withinPaddingBuffers || w.trafficState.NumberOfPacketToFilter > 0 {
mb2 := make(buf.MultiBuffer, 0, len(buffer)) mb2 := make(buf.MultiBuffer, 0, len(buffer))
for _, b := range buffer { for _, b := range buffer {
newbuffer := XtlsUnpadding(b, w.trafficState, w.ctx) newbuffer := XtlsUnpadding(b, w.trafficState, w.isUplink, w.ctx)
if newbuffer.Len() > 0 { if newbuffer.Len() > 0 {
mb2 = append(mb2, newbuffer) mb2 = append(mb2, newbuffer)
} }
} }
buffer = mb2 buffer = mb2
if w.trafficState.RemainingContent > 0 || w.trafficState.RemainingPadding > 0 || w.trafficState.CurrentCommand == 0 { if *remainingContent > 0 || *remainingPadding > 0 || *currentCommand == 0 {
w.trafficState.WithinPaddingBuffers = true *withinPaddingBuffers = true
} else if w.trafficState.CurrentCommand == 1 { } else if *currentCommand == 1 {
w.trafficState.WithinPaddingBuffers = false *withinPaddingBuffers = false
} else if w.trafficState.CurrentCommand == 2 { } else if *currentCommand == 2 {
w.trafficState.WithinPaddingBuffers = false *withinPaddingBuffers = false
if w.isUplink { *switchToDirectCopy = true
w.trafficState.UplinkReaderDirectCopy = true
} else { } else {
w.trafficState.DownlinkReaderDirectCopy = true errors.LogInfo(w.ctx, "XtlsRead unknown command ", *currentCommand, buffer.Len())
}
} else {
errors.LogInfo(w.ctx, "XtlsRead unknown command ", w.trafficState.CurrentCommand, buffer.Len())
} }
} }
if w.trafficState.NumberOfPacketToFilter > 0 { if w.trafficState.NumberOfPacketToFilter > 0 {
@@ -223,7 +262,16 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if w.trafficState.NumberOfPacketToFilter > 0 { if w.trafficState.NumberOfPacketToFilter > 0 {
XtlsFilterTls(mb, w.trafficState, w.ctx) XtlsFilterTls(mb, w.trafficState, w.ctx)
} }
if w.trafficState.IsPadding { var isPadding *bool
var switchToDirectCopy *bool
if w.isUplink {
isPadding = &w.trafficState.Outbound.IsPadding
switchToDirectCopy = &w.trafficState.Outbound.UplinkWriterDirectCopy
} else {
isPadding = &w.trafficState.Inbound.IsPadding
switchToDirectCopy = &w.trafficState.Inbound.DownlinkWriterDirectCopy
}
if *isPadding {
if len(mb) == 1 && mb[0] == nil { if len(mb) == 1 && mb[0] == nil {
mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header mb[0] = XtlsPadding(nil, CommandPaddingContinue, &w.writeOnceUserUUID, true, w.ctx) // we do a long padding to hide vless header
return w.Writer.WriteMultiBuffer(mb) return w.Writer.WriteMultiBuffer(mb)
@@ -233,11 +281,7 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb { for i, b := range mb {
if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) { if w.trafficState.IsTLS && b.Len() >= 6 && bytes.Equal(TlsApplicationDataStart, b.BytesTo(3)) {
if w.trafficState.EnableXtls { if w.trafficState.EnableXtls {
if w.isUplink { *switchToDirectCopy = true
w.trafficState.UplinkWriterDirectCopy = true
} else {
w.trafficState.DownlinkWriterDirectCopy = true
}
} }
var command byte = CommandPaddingContinue var command byte = CommandPaddingContinue
if i == len(mb)-1 { if i == len(mb)-1 {
@@ -247,16 +291,16 @@ func (w *VisionWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
} }
} }
mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx) mb[i] = XtlsPadding(b, command, &w.writeOnceUserUUID, true, w.ctx)
w.trafficState.IsPadding = false // padding going to end *isPadding = false // padding going to end
longPadding = false longPadding = false
continue continue
} else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early } else if !w.trafficState.IsTLS12orAbove && w.trafficState.NumberOfPacketToFilter <= 1 { // For compatibility with earlier vision receiver, we finish padding 1 packet early
w.trafficState.IsPadding = false *isPadding = false
mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx) mb[i] = XtlsPadding(b, CommandPaddingEnd, &w.writeOnceUserUUID, longPadding, w.ctx)
break break
} }
var command byte = CommandPaddingContinue var command byte = CommandPaddingContinue
if i == len(mb)-1 && !w.trafficState.IsPadding { if i == len(mb)-1 && !*isPadding {
command = CommandPaddingEnd command = CommandPaddingEnd
if w.trafficState.EnableXtls { if w.trafficState.EnableXtls {
command = CommandPaddingDirect command = CommandPaddingDirect
@@ -343,38 +387,53 @@ func XtlsPadding(b *buf.Buffer, command byte, userUUID *[]byte, longPadding bool
} }
// XtlsUnpadding remove padding and parse command // XtlsUnpadding remove padding and parse command
func XtlsUnpadding(b *buf.Buffer, s *TrafficState, ctx context.Context) *buf.Buffer { func XtlsUnpadding(b *buf.Buffer, s *TrafficState, isUplink bool, ctx context.Context) *buf.Buffer {
if s.RemainingCommand == -1 && s.RemainingContent == -1 && s.RemainingPadding == -1 { // initial state var remainingCommand *int32
var remainingContent *int32
var remainingPadding *int32
var currentCommand *int
if isUplink {
remainingCommand = &s.Inbound.RemainingCommand
remainingContent = &s.Inbound.RemainingContent
remainingPadding = &s.Inbound.RemainingPadding
currentCommand = &s.Inbound.CurrentCommand
} else {
remainingCommand = &s.Outbound.RemainingCommand
remainingContent = &s.Outbound.RemainingContent
remainingPadding = &s.Outbound.RemainingPadding
currentCommand = &s.Outbound.CurrentCommand
}
if *remainingCommand == -1 && *remainingContent == -1 && *remainingPadding == -1 { // initial state
if b.Len() >= 21 && bytes.Equal(s.UserUUID, b.BytesTo(16)) { if b.Len() >= 21 && bytes.Equal(s.UserUUID, b.BytesTo(16)) {
b.Advance(16) b.Advance(16)
s.RemainingCommand = 5 *remainingCommand = 5
} else { } else {
return b return b
} }
} }
newbuffer := buf.New() newbuffer := buf.New()
for b.Len() > 0 { for b.Len() > 0 {
if s.RemainingCommand > 0 { if *remainingCommand > 0 {
data, err := b.ReadByte() data, err := b.ReadByte()
if err != nil { if err != nil {
return newbuffer return newbuffer
} }
switch s.RemainingCommand { switch *remainingCommand {
case 5: case 5:
s.CurrentCommand = int(data) *currentCommand = int(data)
case 4: case 4:
s.RemainingContent = int32(data) << 8 *remainingContent = int32(data) << 8
case 3: case 3:
s.RemainingContent = s.RemainingContent | int32(data) *remainingContent = *remainingContent | int32(data)
case 2: case 2:
s.RemainingPadding = int32(data) << 8 *remainingPadding = int32(data) << 8
case 1: case 1:
s.RemainingPadding = s.RemainingPadding | int32(data) *remainingPadding = *remainingPadding | int32(data)
errors.LogInfo(ctx, "Xtls Unpadding new block, content ", s.RemainingContent, " padding ", s.RemainingPadding, " command ", s.CurrentCommand) errors.LogInfo(ctx, "Xtls Unpadding new block, content ", *remainingContent, " padding ", *remainingPadding, " command ", *currentCommand)
} }
s.RemainingCommand-- *remainingCommand--
} else if s.RemainingContent > 0 { } else if *remainingContent > 0 {
len := s.RemainingContent len := *remainingContent
if b.Len() < len { if b.Len() < len {
len = b.Len() len = b.Len()
} }
@@ -383,22 +442,22 @@ func XtlsUnpadding(b *buf.Buffer, s *TrafficState, ctx context.Context) *buf.Buf
return newbuffer return newbuffer
} }
newbuffer.Write(data) newbuffer.Write(data)
s.RemainingContent -= len *remainingContent -= len
} else { // remainingPadding > 0 } else { // remainingPadding > 0
len := s.RemainingPadding len := *remainingPadding
if b.Len() < len { if b.Len() < len {
len = b.Len() len = b.Len()
} }
b.Advance(len) b.Advance(len)
s.RemainingPadding -= len *remainingPadding -= len
} }
if s.RemainingCommand <= 0 && s.RemainingContent <= 0 && s.RemainingPadding <= 0 { // this block done if *remainingCommand <= 0 && *remainingContent <= 0 && *remainingPadding <= 0 { // this block done
if s.CurrentCommand == 0 { if *currentCommand == 0 {
s.RemainingCommand = 5 *remainingCommand = 5
} else { } else {
s.RemainingCommand = -1 // set to initial state *remainingCommand = -1 // set to initial state
s.RemainingContent = -1 *remainingContent = -1
s.RemainingPadding = -1 *remainingPadding = -1
if b.Len() > 0 { // shouldn't happen if b.Len() > 0 { // shouldn't happen
newbuffer.Write(b.Bytes()) newbuffer.Write(b.Bytes())
} }
@@ -465,7 +524,7 @@ func XtlsFilterTls(buffer buf.MultiBuffer, trafficState *TrafficState, ctx conte
} }
} }
// UnwrapRawConn support unwrap stats, tls, utls, reality and proxyproto conn and get raw tcp conn from it // UnwrapRawConn support unwrap stats, tls, utls, reality, proxyproto, uds-wrapper conn and get raw tcp/uds conn from it
func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
var readCounter, writerCounter stats.Counter var readCounter, writerCounter stats.Counter
if conn != nil { if conn != nil {
@@ -488,6 +547,9 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) {
conn = pc.Raw() conn = pc.Raw()
// 8192 > 4096, there is no need to process pc's bufReader // 8192 > 4096, there is no need to process pc's bufReader
} }
if uc, ok := conn.(*internet.UnixConnWrapper); ok {
conn = uc.UnixConn
}
} }
return conn, readCounter, writerCounter return conn, readCounter, writerCounter
} }

View File

@@ -146,6 +146,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
} }
responseFunc = func() error { responseFunc = func() error {
ob.CanSpliceCopy = 1
defer timer.SetTimeout(p.Timeouts.UplinkOnly) defer timer.SetTimeout(p.Timeouts.UplinkOnly)
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)) return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
} }
@@ -161,6 +162,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer)) return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
} }
responseFunc = func() error { responseFunc = func() error {
ob.CanSpliceCopy = 1
defer timer.SetTimeout(p.Timeouts.UplinkOnly) defer timer.SetTimeout(p.Timeouts.UplinkOnly)
reader := &UDPReader{Reader: udpConn} reader := &UDPReader{Reader: udpConn}
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer)) return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))

View File

@@ -199,6 +199,7 @@ func (s *Server) transport(ctx context.Context, reader io.Reader, writer io.Writ
} }
responseDone := func() error { responseDone := func() error {
inbound.CanSpliceCopy = 1
defer timer.SetTimeout(plcy.Timeouts.UplinkOnly) defer timer.SetTimeout(plcy.Timeouts.UplinkOnly)
v2writer := buf.NewWriter(writer) v2writer := buf.NewWriter(writer)
@@ -256,6 +257,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
if inbound != nil && inbound.Source.IsValid() { if inbound != nil && inbound.Source.IsValid() {
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source) errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
} }
inbound.CanSpliceCopy = 1
var dest *net.Destination var dest *net.Destination

View File

@@ -175,16 +175,16 @@ func DecodeResponseHeader(reader io.Reader, request *protocol.RequestHeader) (*A
func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error { func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer, conn net.Conn, input *bytes.Reader, rawInput *bytes.Buffer, trafficState *proxy.TrafficState, ob *session.Outbound, isUplink bool, ctx context.Context) error {
err := func() error { err := func() error {
for { for {
if isUplink && trafficState.UplinkReaderDirectCopy || !isUplink && trafficState.DownlinkReaderDirectCopy { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
var writerConn net.Conn var writerConn net.Conn
var inTimer *signal.ActivityTimer var inTimer *signal.ActivityTimer
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil { if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Conn != nil {
writerConn = inbound.Conn writerConn = inbound.Conn
inTimer = inbound.Timer inTimer = inbound.Timer
if inbound.CanSpliceCopy == 2 { if isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1 inbound.CanSpliceCopy = 1
} }
if ob != nil && ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can change if !isUplink && ob != nil && ob.CanSpliceCopy == 2 { // ob need to be passed in due to context can change
ob.CanSpliceCopy = 1 ob.CanSpliceCopy = 1
} }
} }
@@ -193,7 +193,7 @@ func XtlsRead(reader buf.Reader, writer buf.Writer, timer *signal.ActivityTimer,
buffer, err := reader.ReadMultiBuffer() buffer, err := reader.ReadMultiBuffer()
if !buffer.IsEmpty() { if !buffer.IsEmpty() {
timer.Update() timer.Update()
if isUplink && trafficState.UplinkReaderDirectCopy || !isUplink && trafficState.DownlinkReaderDirectCopy { if isUplink && trafficState.Inbound.UplinkReaderDirectCopy || !isUplink && trafficState.Outbound.DownlinkReaderDirectCopy {
// XTLS Vision processes struct TLS Conn's input and rawInput // XTLS Vision processes struct TLS Conn's input and rawInput
if inputBuffer, err := buf.ReadFrom(input); err == nil { if inputBuffer, err := buf.ReadFrom(input); err == nil {
if !inputBuffer.IsEmpty() { if !inputBuffer.IsEmpty() {
@@ -227,12 +227,12 @@ func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdate
var ct stats.Counter var ct stats.Counter
for { for {
buffer, err := reader.ReadMultiBuffer() buffer, err := reader.ReadMultiBuffer()
if isUplink && trafficState.UplinkWriterDirectCopy || !isUplink && trafficState.DownlinkWriterDirectCopy { if isUplink && trafficState.Outbound.UplinkWriterDirectCopy || !isUplink && trafficState.Inbound.DownlinkWriterDirectCopy {
if inbound := session.InboundFromContext(ctx); inbound != nil { if inbound := session.InboundFromContext(ctx); inbound != nil {
if inbound.CanSpliceCopy == 2 { if !isUplink && inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1 inbound.CanSpliceCopy = 1
} }
if ob != nil && ob.CanSpliceCopy == 2 { if isUplink && ob != nil && ob.CanSpliceCopy == 2 {
ob.CanSpliceCopy = 1 ob.CanSpliceCopy = 1
} }
} }
@@ -240,9 +240,9 @@ func XtlsWrite(reader buf.Reader, writer buf.Writer, timer signal.ActivityUpdate
writer = buf.NewWriter(rawConn) writer = buf.NewWriter(rawConn)
ct = writerCounter ct = writerCounter
if isUplink { if isUplink {
trafficState.UplinkWriterDirectCopy = false trafficState.Outbound.UplinkWriterDirectCopy = false
} else { } else {
trafficState.DownlinkWriterDirectCopy = false trafficState.Inbound.DownlinkWriterDirectCopy = false
} }
} }
if !buffer.IsEmpty() { if !buffer.IsEmpty() {

View File

@@ -54,7 +54,7 @@ func (n *netBind) ParseEndpoint(s string) (conn.Endpoint, error) {
addr := xnet.ParseAddress(ipStr) addr := xnet.ParseAddress(ipStr)
if addr.Family() == xnet.AddressFamilyDomain { if addr.Family() == xnet.AddressFamilyDomain {
ips, err := n.dns.LookupIP(addr.Domain(), n.dnsOption) ips, _, err := n.dns.LookupIP(addr.Domain(), n.dnsOption)
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(ips) == 0 { } else if len(ips) == 0 {

View File

@@ -150,13 +150,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
// resolve dns // resolve dns
addr := destination.Address addr := destination.Address
if addr.Family().IsDomain() { if addr.Family().IsDomain() {
ips, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{ ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(), IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(), IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
}) })
{ // Resolve fallback { // Resolve fallback
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() { if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
ips, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{ ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(), IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(), IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
}) })
@@ -284,13 +284,13 @@ func (h *Handler) createIPCRequest() string {
addr = net.ParseAddress(dialerIp.String()) addr = net.ParseAddress(dialerIp.String())
errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr) errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr)
} else { } else {
ips, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{ ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(), IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(), IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
}) })
{ // Resolve fallback { // Resolve fallback
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() { if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
ips, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{ ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(), IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(), IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
}) })

View File

@@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"sync"
"syscall" "syscall"
"golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun"
@@ -33,6 +34,7 @@ type netTun struct {
incomingPacket chan *buffer.View incomingPacket chan *buffer.View
mtu int mtu int
hasV4, hasV6 bool hasV4, hasV6 bool
closeOnce sync.Once
} }
type Net netTun type Net netTun
@@ -174,18 +176,15 @@ func (tun *netTun) Flush() error {
// Close implements tun.Device // Close implements tun.Device
func (tun *netTun) Close() error { func (tun *netTun) Close() error {
tun.closeOnce.Do(func() {
tun.stack.RemoveNIC(1) tun.stack.RemoveNIC(1)
if tun.events != nil {
close(tun.events) close(tun.events)
}
tun.ep.Close() tun.ep.Close()
if tun.incomingPacket != nil {
close(tun.incomingPacket) close(tun.incomingPacket)
} })
return nil return nil
} }

View File

@@ -0,0 +1,52 @@
package wireguard_test
import (
"context"
"github.com/stretchr/testify/assert"
"runtime/debug"
"testing"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/wireguard"
)
// TestWireGuardServerInitializationError verifies that an error during TUN initialization
// (triggered by an empty SecretKey) in the WireGuard server does not cause a panic and returns an error instead.
func TestWireGuardServerInitializationError(t *testing.T) {
// Create a minimal core instance with default features
config := &core.Config{}
instance, err := core.New(config)
if err != nil {
t.Fatalf("Failed to create core instance: %v", err)
}
// Set the Xray instance in the context
ctx := context.WithValue(context.Background(), core.XrayKey(1), instance)
// Define the server configuration with an empty SecretKey to trigger error
conf := &wireguard.DeviceConfig{
IsClient: false,
Endpoint: []string{"10.0.0.1/32"},
Mtu: 1420,
SecretKey: "", // Empty SecretKey to trigger error
Peers: []*wireguard.PeerConfig{
{
PublicKey: "some_public_key",
AllowedIps: []string{"10.0.0.2/32"},
},
},
}
// Use defer to catch any panic and fail the test explicitly
defer func() {
if r := recover(); r != nil {
t.Errorf("TUN initialization panicked: %v", r)
debug.PrintStack()
}
}()
// Attempt to initialize the WireGuard server
_, err = wireguard.NewServer(ctx, conf)
// Check that an error is returned
assert.ErrorContains(t, err, "failed to set private_key: hex string does not fit the slice")
}

View File

@@ -50,12 +50,13 @@ func (mr *DNSClientMockRecorder) Close() *gomock.Call {
} }
// LookupIP mocks base method // LookupIP mocks base method
func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, error) { func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, uint32, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LookupIP", arg0, arg1) ret := m.ctrl.Call(m, "LookupIP", arg0, arg1)
ret0, _ := ret[0].([]net.IP) ret0, _ := ret[0].([]net.IP)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(uint32)
return ret0, ret1 ret2, _ := ret[2].(error)
return ret0, ret1, ret2
} }
// LookupIP indicates an expected call of LookupIP // LookupIP indicates an expected call of LookupIP

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