Compare commits

...
Author SHA1 Message Date
patternihaandClaude Opus 5.5 23f37b571c TUN inbound: IPv6 entries in dns keep the TUN from starting on Windows when IPv6 cannot be set up, as in gateway and autoSystemRoutingTable
Follow-up to #7325: an IPv6 server in dns now asks for IPv6 as well.
When the adapter has no IPv6 interface, or refuses its IPv6 DNS servers,
the TUN fails to start instead of starting without them. Without IPv6 in
any of the three, both are still skipped: the IPv6 DNS servers that Xray
sets on every start are none then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:13:11 +03:30
MinyandGitHub 858c44f410 UdpHop finalmask: Avoid spurious error logs on closed hop sockets (#7319)
https://github.com/XTLS/Xray-core/pull/7319#issuecomment-6628950766
2026-11-04 11:01:44 +00:00
Hossin AsaadiandGitHub fff4acac52 WireGuard proxy: Give up on the endpoint lookup at the connection's deadline (#7323)
https://github.com/XTLS/Xray-core/pull/7323#issuecomment-6623261000
2026-11-04 10:53:08 +00:00
ad98ffd42b TUN inbound: Ignore dns IPv6 setup error when neither gateway nor autoSystemRoutingTable has an IPv6 entry on Windows (#7325)
https://github.com/XTLS/Xray-core/pull/7325#issuecomment-6619158295

---------

Co-authored-by: patterniha <71074308+patterniha@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:20:31 +00:00
MeowandGitHub 63ca2e0c1d Direct/Freedom outbound's finalRules: Add logLevel; blockDelay->delay; Apply default safe policy on MASQUE as well (#7329)
https://github.com/XTLS/Xray-core/pull/7281#issuecomment-6482226042
https://github.com/XTLS/Xray-core/issues/7240#issuecomment-6601762396
https://github.com/XTLS/Xray-core/pull/7324#issuecomment-6621358849
https://github.com/XTLS/Xray-core/pull/7329#issuecomment-6622083462
https://github.com/XTLS/Xray-core/pull/7329#issuecomment-6625219097
https://github.com/XTLS/Xray-core/pull/7329#issuecomment-6625305192
2026-11-04 08:34:58 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ab88e0118f Bump github.com/klauspost/cpuid/v2 from 2.4.1 to 2.4.2 (#7328)
Bumps [github.com/klauspost/cpuid/v2](https://github.com/klauspost/cpuid) from 2.4.1 to 2.4.2.
- [Release notes](https://github.com/klauspost/cpuid/releases)
- [Commits](https://github.com/klauspost/cpuid/compare/v2.4.1...v2.4.2)

---
updated-dependencies:
- dependency-name: github.com/klauspost/cpuid/v2
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-04 02:41:21 +00:00
556767e40b XDRIVE transport: Retry "local" Get on Windows sharing violations (#7316)
https://github.com/XTLS/Xray-core/pull/7316#issuecomment-6575472342

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:02:23 +00:00
967ade1e03 TUN inbound's autoSystemWfpBlockLeak: Add "all", "allexceptlan", port alone, port range, "tcp"/"udp" alone or with port range, "icmp", and a CIDR in front of any value; Always allow loopback, DHCP and IPv6 neighbor discovery (#7315)
https://github.com/XTLS/Xray-core/pull/7288#issuecomment-6559954865
https://github.com/XTLS/Xray-core/pull/7315#issuecomment-6582481840
https://github.com/XTLS/Xray-core/pull/7315#issuecomment-6588253141

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:40:36 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7c8dbcb4bb Bump google.golang.org/grpc from 1.84.0 to 1.85.0 (#7311)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.84.0 to 1.85.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.84.0...v1.85.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.85.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 02:03:49 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8ab161a2b0 Bump golang.org/x/net from 0.61.0 to 0.62.0 (#7312)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.61.0 to 0.62.0.
- [Commits](https://github.com/golang/net/compare/v0.61.0...v0.62.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.62.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-02 02:03:35 +00:00
RPRXandGitHub a4a3dff5b2 Xray-core v26.10.31
Sponsor & Donation & NFTs: https://github.com/XTLS/Xray-core/issues/3668
Project X Channel: https://t.me/projectXtls

Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2026-10-31 13:20:19 +00:00
6cdd323ad8 TUN inbound's autoSystemWfpBlockLeak: Support "tcp/port" and "udp/port"; "dns" covers "tcp/853" and "udp/853" as well (#7288)
https://github.com/XTLS/Xray-core/pull/6853#issuecomment-6492838227
https://github.com/XTLS/Xray-core/pull/7288#issuecomment-6507229727
https://github.com/XTLS/Xray-core/pull/7288#issuecomment-6516762634

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:16:31 +00:00
316e07a538 XHTTP server: Add h2Params (#7295)
https://github.com/XTLS/Xray-core/pull/7282#issuecomment-6523157944
https://github.com/XTLS/Xray-core/pull/7295#issuecomment-6543501751

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

---------

Co-authored-by: pqhaz3925 <195911022+pqhaz3925@users.noreply.github.com>
2026-10-31 12:52:14 +00:00
MeowandRPRX 0c3402d778 Geodata: Support streaming gzip decompression when downloading assets (#7294)
https://github.com/XTLS/Xray-core/pull/7294#issuecomment-6542074150
2026-10-31 11:56:59 +00:00
bc7c895278 HTTP inbound: Forward short 100 Continue responses promptly (#7298)
https://github.com/XTLS/Xray-core/pull/7298#issuecomment-6542585254

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-31 11:29:02 +00:00
konstpicandRPRX e7a69fa85a XHTTP server: Accept OPTIONS as uplinkHTTPMethod (#7290)
https://github.com/XTLS/Xray-core/pull/7290#issuecomment-6505845186
2026-10-31 08:43:57 +00:00
9b8190f52c QUIC sniffer: Don't accept ClientHello with gaps in CRYPTO data (#7292)
https://github.com/XTLS/Xray-core/pull/7292#pullrequestreview-5712194488

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-31 08:31:30 +00:00
6ca6d8a693 Commands: Fix xray api bi balancer1 balancer2 (#7299)
https://github.com/XTLS/Xray-core/pull/7279#issuecomment-6468250523

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-10-31 07:21:32 +00:00
风扇滑翔翼andGitHub d1140de273 Chore: Regenerate *.pb.go files (#7287)
https://github.com/XTLS/Xray-core/pull/7263#issuecomment-6464669635
2026-10-31 07:13:55 +00:00
风扇滑翔翼andGitHub 6ea0ab5d3a DefaultListener: Disable MPTCP by default (#7291)
Fixes https://github.com/XTLS/Xray-core/pull/7291#issuecomment-6540309985
2026-10-31 07:05:32 +00:00
RPRXandGitHub 0afec66085 Xray-core v26.10.29
Sponsor & Donation & NFTs: https://github.com/XTLS/Xray-core/issues/3668
Project X Channel: https://t.me/projectXtls

Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2026-10-29 08:38:50 +00:00
55eecaed16 Config: Move inbound module initialization to the end (#7284)
Fixes https://github.com/XTLS/Xray-core/pull/7284#issuecomment-6491545028

---------

Co-authored-by: Copilot <copilot@github.com>
2026-10-29 08:34:16 +00:00
2dfee4b842 XDRIVE transport: Carry a Google Drive session in a ring of slots (#7217)
https://github.com/XTLS/Xray-core/pull/7217#issuecomment-6489554617

---------

Co-authored-by: Ahmad-2213 <106820058+Ahmad-2213@users.noreply.github.com>
2026-10-29 06:47:42 +00:00
Жора ЗмейкинandGitHub b4123b5748 XDRIVE transport: Add "s3" storage backend (#7231)
https://github.com/XTLS/Xray-core/pull/7231#issuecomment-6488077212
2026-10-29 04:44:41 +00:00
ec124ff9e0 WebSocket transport: Treat normal close frames as io.EOF (#7236)
Fixes https://github.com/XTLS/Xray-core/issues/7235#issuecomment-6353389899

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-29 03:42:11 +00:00
SolomonandGitHub 8a3c0cbf1d Sockopt: Fix swapped TCP keepalive idle and interval on Darwin (#7219)
https://github.com/XTLS/Xray-core/pull/7219#pullrequestreview-5691960668
2026-10-29 02:54:53 +00:00
78b5a6ec5c GitHub Action CI: Enforce stricter checks on generated protobuf files (#7263)
https://github.com/XTLS/Xray-core/pull/7263#issuecomment-6447862934
https://github.com/XTLS/Xray-core/pull/7263#issuecomment-6484719865
https://github.com/XTLS/Xray-core/pull/7263#issuecomment-6485805671

---------

Co-authored-by: Fangliding <Fangliding.fshxy@outlook.com>
2026-10-29 02:34:47 +00:00
5865e3ac3d DNS outbound: Keep hijacked A/AAAA replies within the buffer (#7225)
https://github.com/XTLS/Xray-core/pull/7225#pullrequestreview-5613859264

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-29 00:42:19 +00:00
48f62b801a Geodata: Limit download size, file extensions, and update frequency (#7267)
https://github.com/XTLS/Xray-core/pull/7265#issuecomment-6464588139

---------

Co-authored-by: xqzr <34030394+xqzr@users.noreply.github.com>
2026-10-29 00:22:51 +00:00
MeowandGitHub 43c29f8eb1 Geodata: Update built-in generalized "private" IP ranges (#7249)
And https://github.com/XTLS/Xray-core/pull/7249#issuecomment-6483478811

https://github.com/XTLS/Xray-core/pull/7249#issuecomment-6391494484
https://github.com/XTLS/Xray-core/pull/7249#issuecomment-6483591550
2026-10-28 23:45:22 +00:00
MeowandGitHub 613d8147bb Direct/Freedom outbound: Warn about migrating fragment and noises to Finalmask (#7242)
https://github.com/XTLS/Xray-core/issues/7240#issuecomment-6361862358
2026-10-28 22:49:30 +00:00
38f090255c Commands: Fix xray api adrules c1.json c2.json (#7276)
https://github.com/XTLS/Xray-core/pull/7276#pullrequestreview-5680949792

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-28 21:59:27 +00:00
35a3121a83 File loader: Check file extensions without resolving symlinks (#7255)
https://github.com/XTLS/Xray-core/pull/7255#issuecomment-6481194940
https://github.com/XTLS/Xray-core/pull/7250#issuecomment-6398816842

Fixes https://github.com/XTLS/Xray-core/issues/7239#issuecomment-6356122816

---------

Co-authored-by: Eljees <3.14hell@gmail.com>
2026-10-28 21:59:03 +00:00
风扇滑翔翼andGitHub bca08ac080 Routing: Fix Linux IPv6 process (#7232)
Fixes https://github.com/XTLS/Xray-core/issues/7227
2026-10-28 20:49:08 +00:00
6a4274ab75 TUN inbound: autoOutboundsInterface should not set IP_BOUND_IF on IPv6 sockets on Darwin (mainly macOS) (#7230)
Fixes https://github.com/XTLS/Xray-core/issues/7228

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:34:40 +00:00
solodecodeandGitHub 229799a7a3 Direct/Freedom outbound: Fix blockDelay for DispatchLink() inbounds such as VLESS, Hysteria, etc. (#7238)
https://github.com/XTLS/Xray-core/pull/7238#pullrequestreview-5627787937
https://github.com/XTLS/Xray-core/pull/7238#issuecomment-6467092152
https://github.com/XTLS/Xray-core/pull/6060#issuecomment-4384511163
2026-10-28 11:02:55 +00:00
3071fbfa25 Sockopt: Handle single-family interface binding on Windows, including TUN inbound's autoOutboundsInterface (#7214)
And https://github.com/XTLS/Xray-core/pull/7214#issuecomment-6329118637

https://github.com/XTLS/Xray-core/pull/7201#issuecomment-6314453444

Fixes https://github.com/XTLS/Xray-core/issues/7160#issuecomment-6246944012

---------

Co-authored-by: patterniha <71074308+patterniha@users.noreply.github.com>
2026-10-28 10:15:58 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1576e79563 Bump github.com/pires/go-proxyproto from 0.15.0 to 0.16.0 (#7237)
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-27 04:31:46 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
67e7f82ca1 Bump docker/login-action from 4.6.0 to 4.7.0 (#7257)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.6.0 to 4.7.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v4.6.0...v4.7.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-27 04:31:31 +00:00
RPRXandGitHub 1b1647cd9d Xray-core v26.10.21
Sponsor & Donation & NFTs: https://github.com/XTLS/Xray-core/issues/3668
Project X Channel: https://t.me/projectXtls

Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2026-10-21 11:59:30 +00:00
Artem LytkinandGitHub 557657b576 TUN inbound: Read large TCP segments straight into Xray-core's buffers on Linux (#7179)
https://github.com/XTLS/Xray-core/pull/7130#issuecomment-6119897360
https://github.com/XTLS/Xray-core/pull/7156#issuecomment-6205035216
https://github.com/XTLS/Xray-core/pull/7179#issuecomment-6250145934
2026-10-21 11:49:36 +00:00
Artem LytkinandGitHub fc5ba8e6a2 XDRIVE transport: Support h3 (Chrome CH by default) and XMUX as well; Send Chrome HTTP headers (User-Agent, etc.) by default (#7187)
https://github.com/XTLS/Xray-core/pull/7181#issuecomment-6229694351
https://github.com/XTLS/Xray-core/pull/7187#issuecomment-6250200781
https://github.com/XTLS/Xray-core/pull/7187#issuecomment-6314784068
2026-10-21 11:33:55 +00:00
genkauzenandRPRX 00c0277787 XDRIVE transport: Support two-step "template" operations (hrefPath) (#7188)
https://github.com/XTLS/Xray-core/pull/7188#issuecomment-6250146420
2026-10-21 10:55:36 +00:00
MeowandGitHub 3653dcb4fc Finalmask: Add filter for inbound (TCP & UDP, source IP filtering) (#7206)
https://github.com/XTLS/Xray-core/pull/7206#issuecomment-6312634417
2026-10-21 10:48:15 +00:00
061c2df291 HTTP outbound: Fix hanging on server-first traffic (#7193)
https://github.com/XTLS/Xray-core/pull/7193#issuecomment-6312885274

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-21 10:33:27 +00:00
23e2292344 Transport: Pass UDP destination to dialer controllers (#7201)
https://github.com/XTLS/Xray-core/pull/7201#issuecomment-6275732621

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-10-21 10:20:35 +00:00
41797c234d Socks inbound: Initialize TempUDPConn's Timer at the beginning (#7203)
Fixes https://github.com/XTLS/Xray-core/pull/7200

---------

Co-authored-by: Eliot the Cougar <1861169+eliotcougar@users.noreply.github.com>
2026-10-21 09:17:34 +00:00
MeowandGitHub 9424810830 Docker: Restrict log & conf permissions (#7204)
https://github.com/XTLS/Xray-core/pull/7204#issuecomment-6292218518
2026-10-21 08:55:32 +00:00
12b7f85de3 Routing: Fix Linux UDP process (#7202)
Fixes https://github.com/XTLS/Xray-core/issues/7192 and https://github.com/XTLS/Xray-core/pull/7195

---------

Co-authored-by: Faraquic! <141701604+faraquic@users.noreply.github.com>
2026-10-21 08:35:52 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇andGitHub de1bbbff88 GitHub Actions: Switch geodata to XTLS/Xray-rules-dat (#7185)
https://github.com/XTLS/Xray-core/pull/7185#issuecomment-6247177356
2026-10-21 08:18:32 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ec73b73f5e Bump github.com/klauspost/cpuid/v2 from 2.4.0 to 2.4.1 (#7197)
Bumps [github.com/klauspost/cpuid/v2](https://github.com/klauspost/cpuid) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/klauspost/cpuid/releases)
- [Commits](https://github.com/klauspost/cpuid/compare/v2.4.0...v2.4.1)

---
updated-dependencies:
- dependency-name: github.com/klauspost/cpuid/v2
  dependency-version: 2.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-21 08:07:31 +00:00
风扇滑翔翼andRPRX 6307fa1265 TLS client: Update built-in RootCAs to remove newly low-credit certs (#7212)
Fixes https://github.com/XTLS/Xray-core/pull/7212#issuecomment-6310150214

... https://github.com/XTLS/Xray-core/pull/7105#issuecomment-6056053599

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-10-21 07:55:00 +00:00
200 changed files with 7188 additions and 1405 deletions
+6 -12
View File
@@ -9,8 +9,8 @@ ARG TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
# Download geodat into a staging directory # Download geodat into a staging directory
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geoip/geoip-full.dat /tmp/geodat/geoip.dat
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geosite/geosite-expand.dat /tmp/geodat/geosite.dat
RUN mkdir -p /tmp/empty RUN mkdir -p /tmp/empty
@@ -20,7 +20,7 @@ RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
{ {
"log": { "log": {
"error": "/var/log/xray/error.log", "error": "/var/log/xray/error.log",
"loglevel": "warning", "logLevel": "warning",
"access": "none", "access": "none",
"dnsLog": false "dnsLog": false
} }
@@ -40,21 +40,15 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
# Create log files
RUN mkdir -p /tmp/var/log/xray && touch \
/tmp/var/log/xray/access.log \
/tmp/var/log/xray/error.log
# Build finally image # Build finally image
FROM gcr.io/distroless/static:nonroot FROM gcr.io/distroless/static:nonroot
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray
COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/ COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray COPY --from=build --chown=0:65532 --chmod=750 /tmp/empty /usr/local/etc/xray
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/ COPY --from=build --chown=0:65532 --chmod=640 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray COPY --from=build --chown=65532:65532 --chmod=700 /tmp/empty /var/log/xray
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
VOLUME /usr/local/etc/xray VOLUME /usr/local/etc/xray
VOLUME /var/log/xray VOLUME /var/log/xray
+6 -12
View File
@@ -9,8 +9,8 @@ ARG TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags "-s -w -buildid=" ./main
# Download geodat into a staging directory # Download geodat into a staging directory
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat /tmp/geodat/geoip.dat ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geoip/geoip-full.dat /tmp/geodat/geoip.dat
ADD https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat /tmp/geodat/geosite.dat ADD https://raw.githubusercontent.com/XTLS/Xray-rules-dat/release/geosite/geosite-expand.dat /tmp/geodat/geosite.dat
RUN mkdir -p /tmp/empty RUN mkdir -p /tmp/empty
@@ -20,7 +20,7 @@ RUN cat <<EOF >/tmp/usr/local/etc/xray/00_log.json
{ {
"log": { "log": {
"error": "/var/log/xray/error.log", "error": "/var/log/xray/error.log",
"loglevel": "warning", "logLevel": "warning",
"access": "none", "access": "none",
"dnsLog": false "dnsLog": false
} }
@@ -40,11 +40,6 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
# Create log files
RUN mkdir -p /tmp/var/log/xray && touch \
/tmp/var/log/xray/access.log \
/tmp/var/log/xray/error.log
# Build finally image # Build finally image
# Note on Distroless Base Image and Architecture Support: # Note on Distroless Base Image and Architecture Support:
# - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux: # - The official 'gcr.io/distroless/static' image provided by Google only supports a limited set of architectures for Linux:
@@ -60,10 +55,9 @@ FROM --platform=linux/amd64 gcr.io/distroless/static:nonroot
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray
COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/ COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray COPY --from=build --chown=0:65532 --chmod=750 /tmp/empty /usr/local/etc/xray
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/ COPY --from=build --chown=0:65532 --chmod=640 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray COPY --from=build --chown=65532:65532 --chmod=700 /tmp/empty /var/log/xray
COPY --from=build --chown=65532:65532 --chmod=600 /tmp/var/log/xray/*.log /var/log/xray/
VOLUME /usr/local/etc/xray VOLUME /usr/local/etc/xray
VOLUME /var/log/xray VOLUME /var/log/xray
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
uses: docker/setup-buildx-action@v4 uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v4.6.0 uses: docker/login-action@v4.7.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -40,7 +40,7 @@ jobs:
max_attempts: 60 max_attempts: 60
command: | command: |
[ -d 'resources' ] || mkdir resources [ -d 'resources' ] || mkdir resources
LIST=('Loyalsoldier v2ray-rules-dat geoip geoip' 'Loyalsoldier v2ray-rules-dat geosite geosite') LIST=('XTLS Xray-rules-dat geoip/geoip-lite geoip' 'XTLS Xray-rules-dat geosite/geosite geosite')
for i in "${LIST[@]}" for i in "${LIST[@]}"
do do
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}')) INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3,$4}'))
+3 -18
View File
@@ -35,24 +35,7 @@ jobs:
if: steps.check-assets.outputs.missing == 'true' if: steps.check-assets.outputs.missing == 'true'
run: sleep 90 run: sleep 90
check-proto: check-format-and-proto:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout codebase
uses: actions/checkout@v7
- name: Check Proto Version Header
run: |
head -n 4 core/config.pb.go > ref.txt
find . -name "*.pb.go" ! -name "*_grpc.pb.go" -print0 | while IFS= read -r -d '' file; do
if ! cmp -s ref.txt <(head -n 4 "$file"); then
echo "Error: Header mismatch in $file"
head -n 4 "$file"
exit 1
fi
done
check-format:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
permissions: permissions:
@@ -68,6 +51,8 @@ jobs:
cache: false cache: false
- name: Check Format - name: Check Format
run: go run ./infra/vformat/main.go -mode check -pwd ./ run: go run ./infra/vformat/main.go -mode check -pwd ./
- name: Check Protobuf
run: go run ./infra/vprotogen/main.go -mode check -pwd ./
test: test:
needs: check-assets needs: check-assets
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/commander/config.proto // source: app/commander/config.proto
package commander package commander
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/dispatcher/config.proto // source: app/dispatcher/config.proto
package dispatcher package dispatcher
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/dns/config.proto // source: app/dns/config.proto
package dns package dns
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/dns/fakedns/fakedns.proto // source: app/dns/fakedns/fakedns.proto
package fakedns package fakedns
+4 -4
View File
@@ -128,7 +128,7 @@ func TestDNSScriptGeoIPFallback(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources")) t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
script := ` script := `
local servers = require("xray.dns").Servers local servers = require("xray.dns").Servers
local us_ips = require("xray.geodata").BuildIPMatcher("geoip:us") local ru_ips = require("xray.geodata").BuildIPMatcher("geoip:ru")
local by_id = {} local by_id = {}
for _, server in ipairs(servers) do for _, server in ipairs(servers) do
@@ -138,7 +138,7 @@ assert(by_id.primary and by_id.fallback, "primary and fallback DNS servers are r
function HandleDNSQuery(domain, ipv4, ipv6, fake) function HandleDNSQuery(domain, ipv4, ipv6, fake)
local ips, ttl, err = by_id.primary:Query(domain, ipv4, ipv6, fake) local ips, ttl, err = by_id.primary:Query(domain, ipv4, ipv6, fake)
if not err and us_ips:AnyMatch(ips) then if not err and ru_ips:AnyMatch(ips) then
return ips, ttl, nil return ips, ttl, nil
end end
return by_id.fallback:Query(domain, ipv4, ipv6, fake) return by_id.fallback:Query(domain, ipv4, ipv6, fake)
@@ -152,7 +152,7 @@ end
primary := &scriptNameServer{ primary := &scriptNameServer{
name: "primary", name: "primary",
answers: map[string]net.IP{ answers: map[string]net.IP{
"us.example": net.ParseIP("2001:4860:4860::8888"), "ru.example": net.ParseIP("2a02:6b8::feed:0ff"),
"other.example": net.ParseIP("127.0.0.1"), "other.example": net.ParseIP("127.0.0.1"),
}, },
ttl: 30, ttl: 30,
@@ -191,7 +191,7 @@ end
ip net.IP ip net.IP
ttl uint32 ttl uint32
}{ }{
{"Us.Example.", net.ParseIP("2001:4860:4860::8888"), 30}, {"Ru.Example.", net.ParseIP("2a02:6b8::feed:0ff"), 30},
{"other.example", net.ParseIP("9.9.9.9"), 60}, {"other.example", net.ParseIP("9.9.9.9"), 60},
} { } {
ips, ttl, err := server.LookupIP(tc.domain, option) ips, ttl, err := server.LookupIP(tc.domain, option)
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/geodata/config.proto // source: app/geodata/config.proto
package geodata package geodata
+55 -58
View File
@@ -1,6 +1,8 @@
package geodata package geodata
import ( import (
"bufio"
"compress/gzip"
"context" "context"
"crypto/tls" "crypto/tls"
go_errors "errors" go_errors "errors"
@@ -21,7 +23,10 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
const idleTimeout = 30 * time.Second const (
idleTimeout = 30 * time.Second
maxDownloadSize int64 = 256 * 1024 * 1024
)
// DO NOT EDIT. Ignored by geodata, rejected by Lua. // DO NOT EDIT. Ignored by geodata, rejected by Lua.
const luaGuardHeader = "\x12\x01\x00" const luaGuardHeader = "\x12\x01\x00"
@@ -33,7 +38,6 @@ type stage struct {
type downloader struct { type downloader struct {
ctx context.Context ctx context.Context
httpClient *http.Client
httpsClient *http.Client httpsClient *http.Client
} }
@@ -61,12 +65,11 @@ func (c *idleConn) Write(b []byte) (int, error) {
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader { func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
return &downloader{ return &downloader{
ctx: ctx, ctx: ctx,
httpClient: newClient(ctx, dispatcher, outbound, false), httpsClient: newClient(ctx, dispatcher, outbound),
httpsClient: newClient(ctx, dispatcher, outbound, true),
} }
} }
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client { func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client {
dial := func(ctx context.Context, network, address string) (net.Conn, error) { dial := func(ctx context.Context, network, address string) (net.Conn, error) {
var conn net.Conn var conn net.Conn
err := task.Run(ctx, func() error { err := task.Run(ctx, func() error {
@@ -91,53 +94,33 @@ func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound
Conn: conn, Conn: conn,
}, nil }, nil
} }
if isHTTPS { return &http.Client{
return &http.Client{ Transport: &http2.Transport{
Transport: &http2.Transport{ DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) { conn, err := dial(ctx, network, address)
conn, err := dial(ctx, network, address) if err != nil {
if err != nil { return nil, err
return nil, err
}
host, _, _ := net.SplitHostPort(address)
tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
} }
if len(via) >= 10 { host, _, _ := net.SplitHostPort(address)
return errors.New("stopped after 10 redirects") tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
conn.Close()
return nil, err
} }
return nil return tlsConn, nil
}, },
} },
} else { CheckRedirect: func(req *http.Request, via []*http.Request) error {
return &http.Client{ if req.URL.Scheme != "https" {
Transport: &http.Transport{ return errors.New("redirected to non-https URL: ", req.URL.String())
Proxy: nil, }
DisableKeepAlives: true, if len(via) >= 20 {
DialContext: dial, return errors.New("stopped after 20 redirects")
ResponseHeaderTimeout: idleTimeout, }
}, return nil
CheckRedirect: func(req *http.Request, via []*http.Request) error { },
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
} }
} }
@@ -203,27 +186,41 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
} }
utils.TryDefaultHeadersWith(req.Header, "nav") utils.TryDefaultHeadersWith(req.Header, "nav")
var client *http.Client resp, err := d.httpsClient.Do(req)
if req.URL.Scheme == "https" {
client = d.httpsClient
} else {
client = d.httpClient
}
resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
io.Copy(io.Discard, resp.Body)
return errors.New("unexpected status code: ", resp.StatusCode) return errors.New("unexpected status code: ", resp.StatusCode)
} }
if resp.ContentLength > maxDownloadSize {
return errors.New("geodata download exceeds 256 MiB")
}
n, err := io.Copy(writer, resp.Body) br := bufio.NewReader(resp.Body)
header, err := br.Peek(2)
if err != nil && err != io.EOF {
return err
}
var body io.Reader = br
if len(header) == 2 && header[0] == 0x1f && header[1] == 0x8b {
zr, err := gzip.NewReader(br)
if err != nil {
return err
}
defer zr.Close()
body = zr
}
n, err := io.Copy(writer, io.LimitReader(body, maxDownloadSize+1))
if err != nil { if err != nil {
return err return err
} }
if n > maxDownloadSize {
return errors.New("geodata download exceeds 256 MiB")
}
if n == 0 { if n == 0 {
return errors.New("empty response body") return errors.New("empty response body")
} }
+55 -2
View File
@@ -7,10 +7,13 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
commongeodata "github.com/xtls/xray-core/common/geodata" commongeodata "github.com/xtls/xray-core/common/geodata"
xlua "github.com/xtls/xray-core/common/lua" xlua "github.com/xtls/xray-core/common/lua"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core"
"github.com/yuin/gopher-lua/parse" "github.com/yuin/gopher-lua/parse"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -31,11 +34,11 @@ func TestDownloadOneLuaGuard(t *testing.T) {
if err := os.WriteFile(filepath.Join(dir, "asset.lua"), nil, 0o600); err != nil { if err := os.WriteFile(filepath.Join(dir, "asset.lua"), nil, 0o600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(tc.body)) _, _ = w.Write([]byte(tc.body))
})) }))
defer server.Close() defer server.Close()
d := &downloader{ctx: context.Background(), httpClient: server.Client()} d := &downloader{ctx: context.Background(), httpsClient: server.Client()}
staged, err := d.downloadOne(&Asset{Url: server.URL, File: "asset.lua"}) staged, err := d.downloadOne(&Asset{Url: server.URL, File: "asset.lua"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -71,3 +74,53 @@ func TestDownloadOneLuaGuard(t *testing.T) {
}) })
} }
} }
func TestProtobufConfigValidation(t *testing.T) {
dir := t.TempDir()
t.Setenv("xray.location.asset", dir)
for _, name := range []string{"asset.dat", "asset.DAT"} {
if err := os.WriteFile(filepath.Join(dir, name), nil, 0o600); err != nil {
t.Fatal(err)
}
}
if err := os.Mkdir(filepath.Join(dir, "directory.dat"), 0o700); err != nil {
t.Fatal(err)
}
server, err := core.New(&core.Config{})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = server.Close() })
for _, tc := range []struct {
name string
config *Config
wantError string
}{
{"minutely cron", &Config{Cron: "* * * * *"}, ""},
{"minimum interval", &Config{Cron: "@every 1m"}, ""},
{"short interval", &Config{Cron: "@every 59s"}, "interval must be at least 1 minute"},
{"fractional interval", &Config{Cron: "@every 59.999s"}, "interval must be at least 1 minute"},
{"invalid cron", &Config{Cron: "invalid"}, "invalid geodata cron"},
{"dat extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.dat"}}}, ""},
{"uppercase extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.DAT"}}}, ""},
{"invalid extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset.lua"}}}, "extension must be .dat"},
{"missing extension", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "asset"}}}, "extension must be .dat"},
{"missing file", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "missing.dat"}}}, "invalid geodata asset file"},
{"directory", &Config{Cron: "@daily", Assets: []*Asset{{Url: "https://example.com/asset.dat", File: "directory.dat"}}}, "asset is not a regular file"},
} {
t.Run(tc.name, func(t *testing.T) {
config, err := serial.ToTypedMessage(tc.config).GetInstance()
if err != nil {
t.Fatal(err)
}
_, err = core.CreateObject(server, config)
if tc.wantError == "" {
if err != nil {
t.Fatal(err)
}
} else if err == nil || !strings.Contains(err.Error(), tc.wantError) {
t.Fatalf("CreateObject() error = %v; want %q", err, tc.wantError)
}
})
}
}
+23 -3
View File
@@ -2,12 +2,16 @@ package geodata
import ( import (
"context" "context"
"path/filepath"
"strings"
"sync" "sync"
"time"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"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"
commongeodata "github.com/xtls/xray-core/common/geodata" commongeodata "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/platform/filesystem"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
) )
@@ -25,6 +29,24 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
if config.Cron == "" { if config.Cron == "" {
return &Instance{}, nil return &Instance{}, nil
} }
if len(config.Assets) > 100 {
return nil, errors.New("geodata supports at most 100 assets")
}
for _, asset := range config.Assets {
if !strings.EqualFold(filepath.Ext(asset.GetFile()), ".dat") {
return nil, errors.New("geodata asset file extension must be .dat")
}
if _, err := filesystem.ResolveAsset(asset.GetFile()); err != nil {
return nil, errors.New("invalid geodata asset file: ", asset.GetFile()).Base(err)
}
}
schedule, err := cron.ParseStandard(config.Cron)
if err != nil {
return nil, errors.New("invalid geodata cron").Base(err)
}
if interval, ok := schedule.(cron.ConstantDelaySchedule); ok && interval.Delay < time.Minute {
return nil, errors.New("geodata cron interval must be at least 1 minute")
}
g := &Instance{ g := &Instance{
assets: config.Assets, assets: config.Assets,
@@ -44,9 +66,7 @@ func New(ctx context.Context, config *Config) (*Instance, error) {
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)), cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
cron.WithLogger(cron.DiscardLogger), cron.WithLogger(cron.DiscardLogger),
) )
if _, err := g.tasker.AddFunc(config.Cron, g.execute); err != nil { g.tasker.Schedule(schedule, cron.FuncJob(g.execute))
return nil, errors.New("invalid geodata cron").Base(err)
}
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron) errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
return g, nil return g, nil
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/log/command/config.proto // source: app/log/command/config.proto
package command package command
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.2
// - protoc v6.33.5 // - protoc (unknown)
// source: app/log/command/config.proto // source: app/log/command/config.proto
package command package command
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/log/config.proto // source: app/log/config.proto
package log package log
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/metrics/config.proto // source: app/metrics/config.proto
package metrics package metrics
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/observatory/burst/config.proto // source: app/observatory/burst/config.proto
package burst package burst
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/observatory/command/command.proto // source: app/observatory/command/command.proto
package command package command
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.2
// - protoc v6.33.5 // - protoc (unknown)
// source: app/observatory/command/command.proto // source: app/observatory/command/command.proto
package command package command
+14 -14
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/observatory/config.proto // source: app/observatory/config.proto
package observatory package observatory
@@ -152,23 +152,23 @@ func (x *HealthPingMeasurementResult) GetMin() int64 {
type OutboundStatus struct { type OutboundStatus struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// @Document Whether this outbound is usable // @Document Whether this outbound is usable
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
// @Document The time for probe request to finish. // @Document The time for probe request to finish.
// @Type time.ms //@Type time.ms
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
// @Document The last error caused this outbound failed to relay probe request // @Document The last error caused this outbound failed to relay probe request
// @Restriction NotMachineReadable //@Restriction NotMachineReadable
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
// @Document The outbound tag for this Server // @Document The outbound tag for this Server
// @Type id.outboundTag //@Type id.outboundTag
OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"` OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"`
// @Document The time this outbound is known to be alive // @Document The time this outbound is known to be alive
// @Type id.outboundTag //@Type id.outboundTag
LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"` LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"`
// @Document The time this outbound is tried // @Document The time this outbound is tried
// @Type id.outboundTag //@Type id.outboundTag
LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"` LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"`
HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"` HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
@@ -257,14 +257,14 @@ func (x *OutboundStatus) GetHealthPing() *HealthPingMeasurementResult {
type ProbeResult struct { type ProbeResult struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// @Document Whether this outbound is usable // @Document Whether this outbound is usable
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"`
// @Document The time for probe request to finish. // @Document The time for probe request to finish.
// @Type time.ms //@Type time.ms
// @Restriction ReadOnlyForUser //@Restriction ReadOnlyForUser
Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"`
// @Document The error caused this outbound failed to relay probe request // @Document The error caused this outbound failed to relay probe request
// @Restriction NotMachineReadable //@Restriction NotMachineReadable
LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -324,7 +324,7 @@ func (x *ProbeResult) GetLastErrorReason() string {
type Intensity struct { type Intensity struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// @Document The time interval for a probe request in ms. // @Document The time interval for a probe request in ms.
// @Type time.ms //@Type time.ms
ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"` ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/policy/config.proto // source: app/policy/config.proto
package policy package policy
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/proxyman/command/command.proto // source: app/proxyman/command/command.proto
package command package command
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.2
// - protoc v6.33.5 // - protoc (unknown)
// source: app/proxyman/command/command.proto // source: app/proxyman/command/command.proto
package command package command
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/proxyman/config.proto // source: app/proxyman/config.proto
package proxyman package proxyman
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/reverse/config.proto // source: app/reverse/config.proto
package reverse package reverse
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/router/command/command.proto // source: app/router/command/command.proto
package command package command
+13 -12
View File
@@ -34,18 +34,19 @@ message RoutingContext {
// opened by xray-core. // opened by xray-core.
// * FieldSelectors selects a subset of fields in routing statistics to return. // * FieldSelectors selects a subset of fields in routing statistics to return.
// Valid selectors: // Valid selectors:
// - inbound: Selects connection's inbound tag. // - inbound: Selects connection's inbound tag.
// - network: Selects connection's network. // - network: Selects connection's network.
// - ip: Equivalent as "ip_source" and "ip_target", selects both source and // - ip: Equivalent as "ip_source" and "ip_target", selects both source and
// target IP. // target IP.
// - port: Equivalent as "port_source" and "port_target", selects both source // - port: Equivalent as "port_source" and "port_target", selects both source
// and target port. // and target port.
// - domain: Selects target domain. // - domain: Selects target domain.
// - protocol: Select connection's protocol. // - protocol: Select connection's protocol.
// - user: Select connection's inbound user email. // - user: Select connection's inbound user email.
// - attributes: Select connection's additional attributes. // - attributes: Select connection's additional attributes.
// - outbound: Equivalent as "outbound" and "outbound_group", select both // - outbound: Equivalent as "outbound" and "outbound_group", select both
// outbound tag and outbound group tags. // outbound tag and outbound group tags.
//
// * If FieldSelectors is left empty, all fields will be returned. // * If FieldSelectors is left empty, all fields will be returned.
message SubscribeRoutingStatsRequest { message SubscribeRoutingStatsRequest {
repeated string FieldSelectors = 1; repeated string FieldSelectors = 1;
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.2
// - protoc v6.33.5 // - protoc (unknown)
// source: app/router/command/command.proto // source: app/router/command/command.proto
package command package command
+1 -1
View File
@@ -415,7 +415,7 @@ func BenchmarkMphDomainMatcher(b *testing.B) {
func BenchmarkMultiGeoIPMatcher(b *testing.B) { func BenchmarkMultiGeoIPMatcher(b *testing.B) {
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources")) b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
rules, err := geodata.ParseIPRules([]string{"geoip:cn", "geoip:jp", "geoip:ca", "geoip:us"}) rules, err := geodata.ParseIPRules([]string{"geoip:cn", "geoip:ir", "geoip:ru"})
common.Must(err) common.Must(err)
matcher, err := NewIPMatcher(rules, MatcherAsType_Target) matcher, err := NewIPMatcher(rules, MatcherAsType_Target)
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/router/config.proto // source: app/router/config.proto
package router package router
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/stats/command/command.proto // source: app/stats/command/command.proto
package command package command
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.6.0 // - protoc-gen-go-grpc v1.6.2
// - protoc v6.33.5 // - protoc (unknown)
// source: app/stats/command/command.proto // source: app/stats/command/command.proto
package command package command
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/stats/config.proto // source: app/stats/config.proto
package stats package stats
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: app/version/config.proto // source: app/version/config.proto
package version package version
+59 -17
View File
@@ -8,23 +8,65 @@ import (
var privateIPMatcher = sync.OnceValue(func() IPMatcher { var privateIPMatcher = sync.OnceValue(func() IPMatcher {
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{ return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
"0.0.0.0/8", // https://www.iana.org/assignments/iana-ipv4-special-registry
"10.0.0.0/8", "0.0.0.0/8", // "This network"
"100.64.0.0/10", "0.0.0.0/32", // "This host on this network"
"127.0.0.0/8", "10.0.0.0/8", // Private-Use
"169.254.0.0/16", "100.64.0.0/10", // Shared Address Space
"172.16.0.0/12", "127.0.0.0/8", // Loopback
"192.0.0.0/24", "169.254.0.0/16", // Link Local
"192.0.2.0/24", "172.16.0.0/12", // Private-Use
"192.88.99.0/24", // "192.0.0.0/24", // IETF Protocol Assignments
"192.168.0.0/16", "192.0.0.0/29", // IPv4 Service Continuity Prefix
"198.18.0.0/15", "192.0.0.8/32", // IPv4 dummy address
"198.51.100.0/24", "192.0.0.9/32", // Port Control Protocol Anycast
"203.0.113.0/24", "192.0.0.10/32", // Traversal Using Relays around NAT Anycast
"224.0.0.0/3", "192.0.0.170/32", // NAT64/DNS64 Discovery
"::/127", "192.0.0.171/32", // NAT64/DNS64 Discovery
"fc00::/7", "192.0.2.0/24", // Documentation (TEST-NET-1)
"fe80::/10", // "192.31.196.0/24", // AS112-v4
"192.52.193.0/24", // AMT
// "192.88.99.0/24", // Deprecated (6to4 Relay Anycast)
"192.88.99.2/32", // 6a44-relay anycast address
"192.168.0.0/16", // Private-Use
// "192.175.48.0/24", // Direct Delegation AS112 Service
"198.18.0.0/15", // Benchmarking
"198.51.100.0/24", // Documentation (TEST-NET-2)
"203.0.113.0/24", // Documentation (TEST-NET-3)
"240.0.0.0/4", // Reserved
"255.255.255.255/32", // Limited Broadcast
// https://www.iana.org/assignments/multicast-addresses
"224.0.0.0/4",
// https://www.iana.org/assignments/iana-ipv6-special-registry
"::1/128", // Loopback Address
"::/128", // Unspecified Address
// "::ffff:0:0/96", // IPv4-mapped Address
"64:ff9b::/96", // IPv4-IPv6 Translat.
"64:ff9b:1::/48", // IPv4-IPv6 Translat.
"100::/64", // Discard-Only Address Block
"100:0:0:1::/64", // Dummy IPv6 Prefix
// "2001::/23", // IETF Protocol Assignments
"2001::/32", // TEREDO
"2001:1::1/128", // Port Control Protocol Anycast
"2001:1::2/128", // Traversal Using Relays around NAT Anycast
"2001:1::3/128", // DNS-SD Service Registration Protocol Anycast
"2001:2::/48", // Benchmarking
"2001:3::/32", // AMT
// "2001:4:112::/48", // AS112-v6
// "2001:10::/28", // Deprecated (previously ORCHID)
"2001:20::/28", // ORCHIDv2
"2001:30::/28", // Drone Remote ID Protocol Entity Tags (DETs) Prefix
"2001:db8::/32", // Documentation
"2002::/16", // 6to4
// "2620:4f:8000::/48", // Direct Delegation AS112 Service
"3fff::/20", // Documentation
"5f00::/16", // Segment Routing (SRv6) SIDs
"fc00::/7", // Unique-Local
"fe80::/10", // Link-Local Unicast
// https://www.iana.org/assignments/ipv6-multicast-addresses
"ff00::/8", "ff00::/8",
})))) }))))
}) })
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/geodata/geodat.proto // source: common/geodata/geodat.proto
package geodata package geodata
+7 -7
View File
@@ -400,13 +400,13 @@ func TestIPMatcher4CN(t *testing.T) {
} }
} }
func TestIPMatcher6US(t *testing.T) { func TestIPMatcher6RU(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources")) t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
matcher := buildIPMatcher("geoip:us") matcher := buildIPMatcher("geoip:ru")
if !matcher.Match(xnet.ParseAddress("2001:4860:4860::8888").IP()) { if !matcher.Match(xnet.ParseAddress("2a02:6b8::feed:0ff").IP()) {
t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not") t.Error("expect RU geoip contain 2a02:6b8::feed:0ff, but actually not")
} }
} }
@@ -423,11 +423,11 @@ func BenchmarkIPMatcher4CN(b *testing.B) {
} }
} }
func BenchmarkIPMatcher6US(b *testing.B) { func BenchmarkIPMatcher6RU(b *testing.B) {
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources")) b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
matcher := buildIPMatcher("geoip:us") matcher := buildIPMatcher("geoip:ru")
ip := xnet.ParseAddress("2001:4860:4860::8888").IP() ip := xnet.ParseAddress("2a02:6b8::feed:0ff").IP()
b.ResetTimer() b.ResetTimer()
+3 -3
View File
@@ -11,7 +11,7 @@ func TestParseIPRules(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources")) t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
rules := []string{ rules := []string{
"geoip:us", "geoip:ru",
"geoip:cn", "geoip:cn",
"!geoip:cn", "!geoip:cn",
"!!geoip:cn", "!!geoip:cn",
@@ -20,9 +20,9 @@ func TestParseIPRules(t *testing.T) {
"!geoip:!cn", "!geoip:!cn",
"ext:geoip.dat:!cn", "ext:geoip.dat:!cn",
"ext:geoip.dat:!!cn", "ext:geoip.dat:!!cn",
"ext:geoip.dat:ca", "ext:geoip.dat:ir",
"ext-ip:geoip.dat:!cn", "ext-ip:geoip.dat:!cn",
"ext-ip:geoip.dat:!ca", "ext-ip:geoip.dat:!ir",
"192.168.0.0/24", "192.168.0.0/24",
"!192.168.0.0/24", "!192.168.0.0/24",
"!!192.168.0.0/24", "!!192.168.0.0/24",
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/log/log.proto // source: common/log/log.proto
package log package log
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"github.com/xtls/xray-core/common/base64" "github.com/xtls/xray-core/common/base64"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform/filesystem"
glua "github.com/yuin/gopher-lua" glua "github.com/yuin/gopher-lua"
"github.com/yuin/gopher-lua/parse" "github.com/yuin/gopher-lua/parse"
) )
@@ -36,7 +36,7 @@ func Compile(script string) (*Program, error) {
// TODO: Restore inline script support and re-enable the related tests. // TODO: Restore inline script support and re-enable the related tests.
return nil, errors.New("currently unavailable") return nil, errors.New("currently unavailable")
code, err = base64.Decode(script) code, err = base64.Decode(script)
} else if name, err = platform.ResolveLuaFile(script); err == nil { } else if name, err = filesystem.ResolveLuaFile(script); err == nil {
code, err = os.ReadFile(name) code, err = os.ReadFile(name)
// TODO: Restore privileged signature authorization and re-enable the related tests and CLI entries. // TODO: Restore privileged signature authorization and re-enable the related tests and CLI entries.
// if err == nil { // if err == nil {
+8 -11
View File
@@ -187,19 +187,20 @@ func TestCompileInlineDiagnostics(t *testing.T) {
*/ */
func TestCompileFileExtension(t *testing.T) { func TestCompileFileExtension(t *testing.T) {
for _, name := range []string{"script", "script.dat", "script.lua.txt", "script.LUA"} { for _, name := range []string{"script", "script.dat", "script.lua.txt"} {
if program, err := Compile(name); program != nil || err == nil || !strings.Contains(err.Error(), "Lua file extension must be .lua") {
t.Fatalf("Compile(%q) = %v, %v; want nil program and .lua extension error", name, program, err)
}
}
for _, name := range []string{"script.lua", "script.LUA", "script.Lua", "script.lUa"} {
path := filepath.Join(t.TempDir(), name) path := filepath.Join(t.TempDir(), name)
if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil { if err := os.WriteFile(path, []byte("value = 42"), 0o600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if program, err := Compile(path); program != nil || err == nil || !strings.Contains(err.Error(), "Lua file extension must be .lua") { if program, err := Compile(path); program == nil || err != nil {
t.Fatalf("Compile(%q) = %v, %v; want nil program and .lua extension error", path, program, err) t.Fatalf("Compile(%q) = %v, %v; want successful compilation", path, program, err)
} }
} }
path := writeTestScript(t, "value = 42") + string(filepath.Separator) + "."
if program, err := Compile(path); program == nil || err != nil {
t.Fatalf("Compile(%q) = %v, %v; want acceptance of the resolved .lua path", path, program, err)
}
} }
func TestCompileRejectsReadFailure(t *testing.T) { func TestCompileRejectsReadFailure(t *testing.T) {
@@ -234,10 +235,6 @@ func writeTestScript(t *testing.T, code string) string {
if err := os.WriteFile(path, []byte(code), 0o600); err != nil { if err := os.WriteFile(path, []byte(code), 0o600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
path, err := filepath.EvalSymlinks(path)
if err != nil {
t.Fatal(err)
}
return path return path
} }
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/net/address.proto // source: common/net/address.proto
package net package net
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/net/destination.proto // source: common/net/destination.proto
package net package net
+55 -44
View File
@@ -4,7 +4,7 @@ package net
import ( import (
"bufio" "bufio"
"encoding/hex" "encoding/binary"
"fmt" "fmt"
"net" "net"
"os" "os"
@@ -26,36 +26,38 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
panic("Unsupported network type for process lookup.") panic("Unsupported network type for process lookup.")
} }
var procFile string var procFiles []string
switch network { switch network {
case "tcp": case "tcp":
if net.ParseIP(srcIP).To4() != nil { if net.ParseIP(srcIP).To4() != nil {
procFile = "/proc/net/tcp" procFiles = []string{"/proc/net/tcp"}
} else { } else {
procFile = "/proc/net/tcp6" procFiles = []string{"/proc/net/tcp6"}
} }
case "udp": case "udp":
if net.ParseIP(srcIP).To4() != nil { if net.ParseIP(srcIP).To4() != nil {
procFile = "/proc/net/udp" procFiles = []string{"/proc/net/udp", "/proc/net/udp6"}
} else { } else {
procFile = "/proc/net/udp6" procFiles = []string{"/proc/net/udp6", "/proc/net/udp"}
} }
default: default:
panic("Unsupported network type for process lookup.") panic("Unsupported network type for process lookup.")
} }
targetHexAddr, err := formatLittleEndianString(net.ParseIP(srcIP), Port(srcPort)) targetHexAddr := formatProcNetAddress(net.ParseIP(srcIP), Port(srcPort))
if err != nil {
return 0, "", "", errors.New("failed to format address: ", err) portFallback := ""
if network == "udp" {
portFallback = fmt.Sprintf(":%04X", srcPort)
} }
inode, err := findInodeInFile(procFile, targetHexAddr) inode, err := findInodeInFiles(procFiles, targetHexAddr, portFallback)
if err != nil { if err != nil {
return 0, "", "", errors.New("could not search in ", procFile).Base(err) return 0, "", "", errors.New("could not search in ", strings.Join(procFiles, " and ")).Base(err)
} }
if inode == "" { if inode == "" {
return 0, "", "", errors.New("connection for ", srcIP, ":", srcPort, " not found in ", procFile) return 0, "", "", errors.New("connection for ", srcIP, ":", srcPort, " not found in ", strings.Join(procFiles, " or "))
} }
pidStr, err := findPidByInode(inode) pidStr, err := findPidByInode(inode)
@@ -82,51 +84,60 @@ func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort
return pid, procName, absPath, nil return pid, procName, absPath, nil
} }
func formatLittleEndianString(addr net.IP, port Port) (string, error) { func formatProcNetAddress(addr net.IP, port Port) string {
ip := addr
var ipBytes []byte var ipBytes []byte
if ip.To4() != nil { if ipv4 := addr.To4(); ipv4 != nil {
ipBytes = ip.To4() ipBytes = ipv4
} else { } else {
ipBytes = ip.To16() ipBytes = addr.To16()
}
if ipBytes == nil {
return "", errors.New("invalid IP format for ", addr, ": ", ip)
} }
for i, j := 0, len(ipBytes)-1; i < j; i, j = i+1, j-1 { var formatted strings.Builder
ipBytes[i], ipBytes[j] = ipBytes[j], ipBytes[i] formatted.Grow(len(ipBytes)*2 + 5)
for i := 0; i < len(ipBytes); i += 4 {
fmt.Fprintf(&formatted, "%08X", binary.NativeEndian.Uint32(ipBytes[i:i+4]))
} }
portHex := fmt.Sprintf("%04X", uint16(port)) fmt.Fprintf(&formatted, ":%04X", uint16(port))
ipHex := strings.ToUpper(hex.EncodeToString(ipBytes)) return formatted.String()
return fmt.Sprintf("%s:%s", ipHex, portHex), nil
} }
func findInodeInFile(filePath, targetHexAddr string) (string, error) { func findInodeInFiles(filePaths []string, targetHexAddr, portFallback string) (string, error) {
file, err := os.Open(filePath) onlyMatchedPort := ""
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file) for i, filePath := range filePaths {
file, err := os.Open(filePath)
for scanner.Scan() { if err != nil {
line := scanner.Text() if i > 0 && os.IsNotExist(err) {
fields := strings.Fields(line) continue
}
if len(fields) < 10 { return "", err
continue
} }
defer file.Close()
localAddress := fields[1] scanner := bufio.NewScanner(file)
if localAddress == targetHexAddr { for scanner.Scan() {
inode := fields[9] fields := strings.Fields(scanner.Text())
return inode, nil if len(fields) < 10 {
continue
}
localAddress := fields[1]
if localAddress == targetHexAddr {
return fields[9], nil
}
if portFallback != "" && strings.HasSuffix(localAddress, portFallback) {
wildcardIP := strings.TrimSuffix(localAddress, portFallback)
if (len(wildcardIP) == 8 || len(wildcardIP) == 32) && strings.Trim(wildcardIP, "0") == "" {
onlyMatchedPort = fields[9]
}
}
}
if err := scanner.Err(); err != nil {
return "", err
} }
} }
return "", scanner.Err() return onlyMatchedPort, nil
} }
func findPidByInode(inode string) (string, error) { func findPidByInode(inode string) (string, error) {
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/net/network.proto // source: common/net/network.proto
package net package net
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/net/port.proto // source: common/net/port.proto
package net package net
+24 -24
View File
@@ -5,6 +5,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"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"
@@ -26,14 +27,6 @@ func ReadFile(path string) ([]byte, error) {
return buf.ReadAllToBytes(reader) return buf.ReadAllToBytes(reader)
} }
func ReadAsset(file string) ([]byte, error) {
path, _, err := getAssetFileLocation(file)
if err != nil {
return nil, err
}
return ReadFile(path)
}
func OpenAsset(file string) (io.ReadCloser, int64, error) { func OpenAsset(file string) (io.ReadCloser, int64, error) {
path, info, err := getAssetFileLocation(file) path, info, err := getAssetFileLocation(file)
if err != nil { if err != nil {
@@ -43,11 +36,6 @@ func OpenAsset(file string) (io.ReadCloser, int64, error) {
return r, info.Size(), err return r, info.Size(), err
} }
func StatAsset(file string) (os.FileInfo, error) {
_, info, err := getAssetFileLocation(file)
return info, err
}
func ResolveAsset(file string) (string, error) { func ResolveAsset(file string) (string, error) {
path, _, err := getAssetFileLocation(file) path, _, err := getAssetFileLocation(file)
return path, err return path, err
@@ -79,17 +67,29 @@ func ReadCert(file string) ([]byte, error) {
return ReadFile(platform.GetCertLocation(file)) return ReadFile(platform.GetCertLocation(file))
} }
func CopyFile(dst string, src string) error { // ResolveLuaFile finds a local .lua script and returns its absolute path.
bytes, err := ReadFile(src) // Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir.
if err != nil { func ResolveLuaFile(path string) (string, error) {
return err if !strings.EqualFold(filepath.Ext(path), ".lua") {
return "", errors.New("Lua file extension must be .lua")
} }
f, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o644) if filepath.IsAbs(path) {
if err != nil { return path, nil
return err
} }
defer f.Close() for _, dir := range []string{
platform.GetConfDirPath(),
_, err = f.Write(bytes) platform.NewEnvFlag(platform.ConfigLocation).GetValue(func() string { return "" }),
return err ".",
platform.GetExecutableDir(),
} {
if dir != "" {
resolved := filepath.Join(dir, path)
if _, err := os.Stat(resolved); err == nil {
return filepath.Abs(resolved)
} else if !os.IsNotExist(err) {
return "", err
}
}
}
return "", os.ErrNotExist
} }
+47 -2
View File
@@ -1,13 +1,17 @@
package filesystem_test package filesystem_test
import ( import (
"errors"
"os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/platform"
. "github.com/xtls/xray-core/common/platform/filesystem" . "github.com/xtls/xray-core/common/platform/filesystem"
) )
func TestStatAssetRejectsInvalidPath(t *testing.T) { func TestResolveAssetRejectsInvalidPath(t *testing.T) {
for _, file := range []string{ for _, file := range []string{
"", "",
".", ".",
@@ -25,8 +29,49 @@ func TestStatAssetRejectsInvalidPath(t *testing.T) {
`nested\..\geoip.dat`, `nested\..\geoip.dat`,
filepath.Join(t.TempDir(), "geoip.dat"), filepath.Join(t.TempDir(), "geoip.dat"),
} { } {
if _, err := StatAsset(file); err == nil { if _, err := ResolveAsset(file); err == nil {
t.Fatalf("expected error for %q", file) t.Fatalf("expected error for %q", file)
} }
} }
} }
func TestResolveLuaFile(t *testing.T) {
t.Chdir(t.TempDir())
workingDir, err := os.Getwd()
common.Must(err)
executable, err := os.Executable()
common.Must(err)
file, err := os.CreateTemp(filepath.Dir(executable), "lua-*.lua")
common.Must(err)
common.Must(file.Close())
defer os.Remove(file.Name())
name := filepath.Base(file.Name())
paths := []string{
filepath.Join(t.TempDir(), name),
filepath.Join(t.TempDir(), name),
filepath.Join(workingDir, name),
file.Name(),
}
t.Setenv(platform.ConfdirLocation, filepath.Dir(paths[0]))
t.Setenv(platform.ConfigLocation, filepath.Dir(paths[1]))
for _, path := range paths[:3] {
common.Must(os.WriteFile(path, nil, 0o600))
}
if got, err := ResolveLuaFile(paths[2]); err != nil || got != paths[2] {
t.Fatalf("absolute path = %q, %v; want %q", got, err, paths[2])
}
for i, want := range paths {
if i == 2 {
t.Setenv(platform.ConfdirLocation, "")
t.Setenv(platform.ConfigLocation, "")
}
if got, err := ResolveLuaFile(name); err != nil || got != want {
t.Fatalf("resolved path = %q, %v; want %q", got, err, want)
}
common.Must(os.Remove(want))
}
if _, err := ResolveLuaFile(name); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("missing file error = %v", err)
}
}
+2 -2
View File
@@ -14,7 +14,7 @@ func LineSeparator() string {
// GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations // GetAssetLocation searches for `file` in the env dir, the executable dir, and certain locations
func GetAssetLocation(file string) string { func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
defPath := filepath.Join(assetPath, file) defPath := filepath.Join(assetPath, file)
for _, p := range []string{ for _, p := range []string{
defPath, defPath,
@@ -36,6 +36,6 @@ func GetAssetLocation(file string) string {
// GetCertLocation searches for `file` in the env dir and the executable dir // GetCertLocation searches for `file` in the env dir and the executable dir
func GetCertLocation(file string) string { func GetCertLocation(file string) string {
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir) certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
return filepath.Join(certPath, file) return filepath.Join(certPath, file)
} }
+2 -65
View File
@@ -1,8 +1,6 @@
package platform // import "github.com/xtls/xray-core/common/platform" package platform // import "github.com/xtls/xray-core/common/platform"
import ( import (
"errors"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@@ -74,7 +72,7 @@ func NormalizeEnvName(name string) string {
return strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(name)), ".", "_") return strings.ReplaceAll(strings.ToUpper(strings.TrimSpace(name)), ".", "_")
} }
func getExecutableDir() string { func GetExecutableDir() string {
exec, err := os.Executable() exec, err := os.Executable()
if err != nil { if err != nil {
return "" return ""
@@ -83,7 +81,7 @@ func getExecutableDir() string {
} }
func GetConfigurationPath() string { func GetConfigurationPath() string {
configPath := NewEnvFlag(ConfigLocation).GetValue(getExecutableDir) configPath := NewEnvFlag(ConfigLocation).GetValue(GetExecutableDir)
return filepath.Join(configPath, "config.json") return filepath.Join(configPath, "config.json")
} }
@@ -92,64 +90,3 @@ func GetConfDirPath() string {
configPath := NewEnvFlag(ConfdirLocation).GetValue(func() string { return "" }) configPath := NewEnvFlag(ConfdirLocation).GetValue(func() string { return "" })
return configPath return configPath
} }
// ResolveLuaFile finds a local .lua script and returns its resolved absolute path.
// Relative paths: XRAY_LOCATION_CONFDIR > XRAY_LOCATION_CONFIG > working dir > executable dir.
func ResolveLuaFile(path string) (string, error) {
if path == "" {
return "", errors.New("Lua file path is empty")
}
paths := []string{path}
if !filepath.IsAbs(path) {
paths = nil
for _, dir := range []string{
GetConfDirPath(),
NewEnvFlag(ConfigLocation).GetValue(func() string { return "" }),
".",
getExecutableDir(),
} {
if dir != "" {
paths = append(paths, filepath.Join(dir, path))
}
}
}
path, err := resolveFile(paths)
if err != nil {
return "", err
}
return resolveLuaTarget(path)
}
func resolveLuaTarget(path string) (string, error) {
path, err := filepath.EvalSymlinks(path)
if err != nil {
return "", fmt.Errorf("failed to resolve Lua file symlinks: %w", err)
}
if filepath.Ext(path) != ".lua" {
return "", errors.New("Lua file extension must be .lua")
}
return path, nil
}
func resolveFile(paths []string) (string, error) {
var tried []string
for _, path := range paths {
path, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("failed to resolve file path: %w", err)
}
tried = append(tried, path)
info, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
continue
}
if err != nil {
return "", fmt.Errorf("failed to inspect file %q: %w", path, err)
}
if !info.Mode().IsRegular() {
return "", fmt.Errorf("file is not a regular file: %s", path)
}
return path, nil
}
return "", fmt.Errorf("file not found; tried %q: %w", tried, os.ErrNotExist)
}
-88
View File
@@ -1,11 +1,9 @@
package platform_test package platform_test
import ( import (
"errors"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"testing" "testing"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -66,89 +64,3 @@ func TestGetAssetLocation(t *testing.T) {
} }
} }
} }
func TestResolveLuaFile(t *testing.T) {
workingDir := t.TempDir()
t.Chdir(workingDir)
executable, err := os.Executable()
common.Must(err)
file, err := os.CreateTemp(filepath.Dir(executable), "lua-*.lua")
common.Must(err)
common.Must(file.Close())
defer os.Remove(file.Name())
name := filepath.Base(file.Name())
paths := []string{
filepath.Join(t.TempDir(), name),
filepath.Join(t.TempDir(), name),
filepath.Join(workingDir, name),
file.Name(),
}
t.Setenv(ConfdirLocation, filepath.Dir(paths[0]))
t.Setenv(ConfigLocation, filepath.Dir(paths[1]))
for _, path := range paths[:3] {
common.Must(os.WriteFile(path, nil, 0o600))
}
for i, path := range paths {
paths[i], err = filepath.EvalSymlinks(path)
common.Must(err)
}
if got, err := ResolveLuaFile(paths[2]); err != nil || got != paths[2] {
t.Fatalf("absolute path = %q, %v; want %q", got, err, paths[2])
}
for i, want := range paths {
if i == 2 {
t.Setenv(ConfdirLocation, "")
t.Setenv(ConfigLocation, "")
}
if got, err := ResolveLuaFile(name); err != nil || got != want {
t.Fatalf("resolved path = %q, %v; want %q", got, err, want)
}
common.Must(os.Remove(want))
}
if _, err := ResolveLuaFile(name); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("missing file error = %v", err)
}
t.Setenv(ConfdirLocation, filepath.Dir(paths[0]))
t.Setenv(ConfigLocation, filepath.Dir(paths[1]))
common.Must(os.Mkdir(paths[0], 0o700))
common.Must(os.WriteFile(paths[1], nil, 0o600))
for _, path := range []string{"", name, filepath.Join(t.TempDir(), name)} {
if _, err := ResolveLuaFile(path); err == nil {
t.Fatalf("accepted invalid path %q", path)
}
}
}
func TestResolveLuaFileSymlink(t *testing.T) {
for _, tc := range []struct {
link, target string
valid bool
}{
{"alias.lua", "data.dat", false},
{"alias.dat", "script.lua", true},
} {
t.Run(tc.link, func(t *testing.T) {
dir, err := filepath.EvalSymlinks(t.TempDir())
common.Must(err)
target := filepath.Join(dir, tc.target)
common.Must(os.WriteFile(target, nil, 0o600))
link := filepath.Join(dir, tc.link)
if err := os.Symlink(tc.target, link); err != nil {
if runtime.GOOS == "windows" {
t.Skipf("cannot create symlinks: %v", err)
}
t.Fatal(err)
}
got, err := ResolveLuaFile(link)
if tc.valid {
if err != nil || got != target {
t.Fatalf("ResolveLuaFile(%q) = %q, %v; want %q", link, got, err, target)
}
} else if err == nil || got != "" || !strings.Contains(err.Error(), "Lua file extension must be .lua") {
t.Fatalf("ResolveLuaFile(%q) = %q, %v; want .lua extension error", link, got, err)
}
})
}
}
+2 -2
View File
@@ -11,12 +11,12 @@ func LineSeparator() string {
// GetAssetLocation searches for `file` in the env dir and the executable dir // GetAssetLocation searches for `file` in the env dir and the executable dir
func GetAssetLocation(file string) string { func GetAssetLocation(file string) string {
assetPath := NewEnvFlag(AssetLocation).GetValue(getExecutableDir) assetPath := NewEnvFlag(AssetLocation).GetValue(GetExecutableDir)
return filepath.Join(assetPath, file) return filepath.Join(assetPath, file)
} }
// GetCertLocation searches for `file` in the env dir and the executable dir // GetCertLocation searches for `file` in the env dir and the executable dir
func GetCertLocation(file string) string { func GetCertLocation(file string) string {
certPath := NewEnvFlag(CertLocation).GetValue(getExecutableDir) certPath := NewEnvFlag(CertLocation).GetValue(GetExecutableDir)
return filepath.Join(certPath, file) return filepath.Join(certPath, file)
} }
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/protocol/headers.proto // source: common/protocol/headers.proto
package protocol package protocol
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/protocol/server_spec.proto // source: common/protocol/server_spec.proto
package protocol package protocol
+4
View File
@@ -101,6 +101,10 @@ func ReadClientHello(data []byte, h *SniffHeader) error {
return errNotClientHello return errNotClientHello
} }
if nameType == 0 { if nameType == 0 {
// RFC 6066 requires a non-empty HostName.
if nameLen == 0 {
return errNotClientHello
}
// QUIC separated across packets // QUIC separated across packets
// May cause the serverName to be incomplete // May cause the serverName to be incomplete
b := byte(0) b := byte(0)
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/protocol/user.proto // source: common/protocol/user.proto
package protocol package protocol
+1 -1
View File
@@ -134,7 +134,7 @@ func TestMarshalConfigJson(t *testing.T) {
func getConfig() string { func getConfig() string {
return `{ return `{
"log": { "log": {
"loglevel": "debug" "logLevel": "debug"
}, },
"stats": {}, "stats": {},
"policy": { "policy": {
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: common/serial/typed_message.proto // source: common/serial/typed_message.proto
package serial package serial
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: core/config.proto // source: core/config.proto
package core package core
+1 -1
View File
@@ -20,7 +20,7 @@ import (
var ( var (
Version_x byte = 26 Version_x byte = 26
Version_y byte = 10 Version_y byte = 10
Version_z byte = 17 Version_z byte = 31
) )
var ( var (
-2
View File
@@ -1,5 +1,3 @@
package core package core
//go:generate go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
//go:generate go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
//go:generate go run ../infra/vprotogen/main.go -pwd ./.. //go:generate go run ../infra/vprotogen/main.go -pwd ./..
+10
View File
@@ -2,6 +2,7 @@ package localdns
import ( import (
"context" "context"
"net/netip"
"syscall" "syscall"
"time" "time"
@@ -100,6 +101,15 @@ func New() *Client {
if internet.IsSkippedDNSServer(address) { if internet.IsSkippedDNSServer(address) {
return nil, errors.New("skipped DNS server ", address) return nil, errors.New("skipped DNS server ", address)
} }
// A socket of the server's IP version only needs that version bound
// to the interface, which a dual-stack one would need both of.
if ap, err := netip.ParseAddrPort(address); err == nil && (network == "udp" || network == "tcp") {
if ap.Addr().Unmap().Is4() {
network += "4"
} else {
network += "6"
}
}
return d.DialContext(ctx, network, address) return d.DialContext(ctx, network, address)
}, },
} }
+13 -6
View File
@@ -4,17 +4,18 @@ go 1.27
require ( require (
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e
github.com/bufbuild/protocompile v0.14.1
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1 github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/klauspost/cpuid/v2 v2.4.0 github.com/klauspost/cpuid/v2 v2.4.2
github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138
github.com/miekg/dns v1.1.73 github.com/miekg/dns v1.1.73
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pion/stun/v3 v3.1.7 github.com/pion/stun/v3 v3.1.7
github.com/pires/go-proxyproto v0.15.0 github.com/pires/go-proxyproto v0.16.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/stretchr/testify v1.12.1 github.com/stretchr/testify v1.12.1
@@ -23,15 +24,15 @@ require (
github.com/yuin/gopher-lua v1.1.2 github.com/yuin/gopher-lua v1.1.2
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.58.0 golang.org/x/crypto v0.58.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20261005185213-c3db4df58582 golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.61.0 golang.org/x/net v0.62.0
golang.org/x/sync v0.24.0 golang.org/x/sync v0.24.0
golang.org/x/sys v0.49.0 golang.org/x/sys v0.49.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.1.1 golang.zx2c4.com/wireguard/windows v1.1.1
google.golang.org/grpc v1.84.0 google.golang.org/grpc v1.85.0
google.golang.org/protobuf v1.36.12 google.golang.org/protobuf v1.36.12
h12.io/socks v1.0.3 h12.io/socks v1.0.3
layeh.com/gopher-luar v1.0.11 layeh.com/gopher-luar v1.0.11
@@ -57,7 +58,13 @@ require (
go.yaml.in/yaml/v3 v3.0.5 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/text v0.43.0 // indirect golang.org/x/text v0.43.0 // indirect
golang.org/x/tools v0.51.0 // indirect golang.org/x/tools v0.51.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
) )
tool (
google.golang.org/grpc/cmd/protoc-gen-go-grpc
google.golang.org/protobuf/cmd/protoc-gen-go
)
+16 -12
View File
@@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.2.3 h1:8H1qwOkl2LPfjf3YezB90JnCliZb6SInJ/OJkEbA
github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.3/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg= github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg=
github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8= github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -33,8 +35,8 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= github.com/klauspost/cpuid/v2 v2.4.2 h1:xRw7IZEsAgVCp6trS/DgWbTAqpixhmywgVgBjYGg1bQ=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/klauspost/cpuid/v2 v2.4.2/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -59,8 +61,8 @@ github.com/pion/stun/v3 v3.1.7 h1:uRXMTlGLf89WgItGNyZ6aR5jMTX0NBbybXADpQCzn+E=
github.com/pion/stun/v3 v3.1.7/go.mod h1:Nq77RW4aRrSNrltf2ksUJLjxWeipj4lnlgdsYIxC8g8= github.com/pion/stun/v3 v3.1.7/go.mod h1:Nq77RW4aRrSNrltf2ksUJLjxWeipj4lnlgdsYIxC8g8=
github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ= github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ=
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI= github.com/pires/go-proxyproto v0.16.0 h1:9KYT/fM3wpjT8bdMEj/7FBcoiUG1KyzEjRYnPu+vz7A=
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24= github.com/pires/go-proxyproto v0.16.0/go.mod h1:n7yhg8yIfNl3KgYDCwBzmeUHNpKTVnUhcWpc7rV4RW4=
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
@@ -97,8 +99,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/crypto/x509roots/fallback v0.0.0-20261005185213-c3db4df58582 h1:wjDBrGbfLuifgrVLFEWUBJYAfh5Q1wkMc5t0FY0tCbs= golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a h1:V5oicZXS0oqZx30gY/apgSVD7YvNBfrIOdcMnlelJY4=
golang.org/x/crypto/x509roots/fallback v0.0.0-20261005185213-c3db4df58582/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E= golang.org/x/crypto/x509roots/fallback v0.0.0-20261020153306-9f2b28d4331a/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -107,8 +109,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
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.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
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.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
@@ -148,10 +150,12 @@ golang.zx2c4.com/wireguard/windows v1.1.1 h1:8/H97U1v1PNDNcBsMZgU3KFuND9MQdTsU2N
golang.zx2c4.com/wireguard/windows v1.1.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= golang.zx2c4.com/wireguard/windows v1.1.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw=
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+35 -3
View File
@@ -9,6 +9,7 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/log"
xnet "github.com/xtls/xray-core/common/net" xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/freedom" "github.com/xtls/xray-core/proxy/freedom"
@@ -48,6 +49,8 @@ type FreedomFinalRuleConfig struct {
Network *NetworkList `json:"network"` Network *NetworkList `json:"network"`
Port *PortList `json:"port"` Port *PortList `json:"port"`
IP *StringList `json:"ip"` IP *StringList `json:"ip"`
Delay *Int32Range `json:"delay"`
LogLevel string `json:"logLevel"`
BlockDelay *Int32Range `json:"blockDelay"` BlockDelay *Int32Range `json:"blockDelay"`
} }
@@ -91,6 +94,7 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
} }
if c.Fragment != nil { if c.Fragment != nil {
errors.PrintDeprecatedFeatureWarning(`"fragment" in Freedom`, `"fragment" in "streamSettings.finalmask.tcp"`)
config.Fragment = new(freedom.Fragment) config.Fragment = new(freedom.Fragment)
switch strings.ToLower(c.Fragment.Packets) { switch strings.ToLower(c.Fragment.Packets) {
@@ -147,6 +151,7 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
} }
if c.Noises != nil { if c.Noises != nil {
errors.PrintDeprecatedFeatureWarning(`"noises" in Freedom`, `"noise" in "streamSettings.finalmask.udp"`)
for _, n := range c.Noises { for _, n := range c.Noises {
NConfig, err := ParseNoise(n) NConfig, err := ParseNoise(n)
if err != nil { if err != nil {
@@ -278,10 +283,37 @@ func (c *FreedomFinalRuleConfig) Build() (*freedom.FinalRuleConfig, error) {
} }
if c.BlockDelay != nil { if c.BlockDelay != nil {
rule.BlockDelay = &freedom.Range{ errors.PrintDeprecatedFeatureWarning("finalRules.blockDelay", "finalRules.delay")
Min: uint64(c.BlockDelay.From), c.Delay = c.BlockDelay
Max: uint64(c.BlockDelay.To), }
if rule.Action != freedom.RuleAction_Block {
if c.Delay != nil || c.LogLevel != "" {
return nil, errors.New("delay and logLevel are only supported for block finalRules")
} }
return rule, nil
}
if c.Delay != nil {
rule.Delay = &freedom.Range{
Min: uint64(c.Delay.From),
Max: uint64(c.Delay.To),
}
}
switch strings.ToLower(c.LogLevel) {
case "", "info":
rule.LogLevel = log.Severity_Info.Enum()
case "none":
rule.LogLevel = log.Severity_Unknown.Enum()
case "debug":
rule.LogLevel = log.Severity_Debug.Enum()
case "warning":
rule.LogLevel = log.Severity_Warning.Enum()
case "error":
rule.LogLevel = log.Severity_Error.Enum()
default:
return nil, errors.New("unknown finalRules.logLevel: ", c.LogLevel)
} }
return rule, nil return rule, nil
+5 -5
View File
@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/xtls/xray-core/common/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
. "github.com/xtls/xray-core/infra/conf" . "github.com/xtls/xray-core/infra/conf"
@@ -46,7 +47,8 @@ func TestFreedomConfig(t *testing.T) {
"network": "tcp,udp", "network": "tcp,udp",
"port": "53,443", "port": "53,443",
"ip": ["10.0.0.0/8", "2001:db8::/32"], "ip": ["10.0.0.0/8", "2001:db8::/32"],
"blockDelay": "30-60" "delay": "30-60",
"logLevel": "debug"
}, { }, {
"action": "allow", "action": "allow",
"network": ["udp"] "network": ["udp"]
@@ -86,10 +88,8 @@ func TestFreedomConfig(t *testing.T) {
}, },
}, },
}, },
BlockDelay: &freedom.Range{ Delay: &freedom.Range{Min: 30, Max: 60},
Min: 30, LogLevel: log.Severity_Debug.Enum(),
Max: 60,
},
}, },
{ {
Action: freedom.RuleAction_Allow, Action: freedom.RuleAction_Allow,
-4
View File
@@ -6,7 +6,6 @@ import (
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"github.com/xtls/xray-core/app/geodata" "github.com/xtls/xray-core/app/geodata"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform/filesystem"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -19,9 +18,6 @@ func (c *GeodataAssetConfig) Build() (*geodata.Asset, error) {
if err := validateHTTPS(c.URL); err != nil { if err := validateHTTPS(c.URL); err != nil {
return nil, errors.New("invalid geodata asset url: ", c.URL).Base(err) return nil, errors.New("invalid geodata asset url: ", c.URL).Base(err)
} }
if _, err := filesystem.StatAsset(c.File); err != nil {
return nil, errors.New("invalid geodata asset file: ", c.File).Base(err)
}
return &geodata.Asset{ return &geodata.Asset{
Url: c.URL, Url: c.URL,
File: c.File, File: c.File,
-23
View File
@@ -1,7 +1,6 @@
package conf_test package conf_test
import ( import (
"path/filepath"
"testing" "testing"
"github.com/xtls/xray-core/app/geodata" "github.com/xtls/xray-core/app/geodata"
@@ -9,8 +8,6 @@ import (
) )
func TestGeodataConfig(t *testing.T) { func TestGeodataConfig(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
creator := func() Buildable { creator := func() Buildable {
return new(GeodataConfig) return new(GeodataConfig)
} }
@@ -38,27 +35,7 @@ func TestGeodataConfig(t *testing.T) {
}) })
} }
func TestGeodataAssetConfig(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
if _, err := (&GeodataAssetConfig{
URL: "https://example.com/geoip.dat",
File: "geoip.dat",
}).Build(); err != nil {
t.Fatal(err)
}
if _, err := (&GeodataAssetConfig{
URL: "https://example.com/geoip.dat",
File: "missing.dat",
}).Build(); err == nil {
t.Fatal("expected error")
}
}
func TestGeodataAssetConfigInvalidURL(t *testing.T) { func TestGeodataAssetConfigInvalidURL(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
for _, rawURL := range []string{ for _, rawURL := range []string{
"", "",
"http://example.com/geoip.dat", "http://example.com/geoip.dat",
+1 -1
View File
@@ -18,7 +18,7 @@ func DefaultLogConfig() *log.Config {
type LogConfig struct { type LogConfig struct {
AccessLog string `json:"access"` AccessLog string `json:"access"`
ErrorLog string `json:"error"` ErrorLog string `json:"error"`
LogLevel string `json:"loglevel"` LogLevel string `json:"logLevel"`
DNSLog bool `json:"dnsLog"` DNSLog bool `json:"dnsLog"`
MaskAddress string `json:"maskAddress"` MaskAddress string `json:"maskAddress"`
} }
+2 -2
View File
@@ -18,7 +18,7 @@ func TestLoaderError(t *testing.T) {
"log": { "log": {
// abcd // abcd
0, 0,
"loglevel": "info" "logLevel": "info"
} }
}`, }`,
Output: "line 4 char 6", Output: "line 4 char 6",
@@ -27,7 +27,7 @@ func TestLoaderError(t *testing.T) {
Input: `{ Input: `{
"log": { "log": {
// abcd // abcd
"loglevel": "info", "logLevel": "info",
} }
}`, }`,
Output: "line 5 char 5", Output: "line 5 char 5",
+21
View File
@@ -15,9 +15,11 @@ import (
googleuuid "github.com/google/uuid" googleuuid "github.com/google/uuid"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata"
xlua "github.com/xtls/xray-core/common/lua" xlua "github.com/xtls/xray-core/common/lua"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask/filter"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment" "github.com/xtls/xray-core/transport/internet/finalmask/fragment"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm" "github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
@@ -74,6 +76,7 @@ var (
customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) customVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ tcpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"filter": func() interface{} { return new(Filter) },
"header-custom": func() interface{} { return new(HeaderCustomTCP) }, "header-custom": func() interface{} { return new(HeaderCustomTCP) },
"fragment": func() interface{} { return new(FragmentMask) }, "fragment": func() interface{} { return new(FragmentMask) },
"sudoku": func() interface{} { return new(Sudoku) }, "sudoku": func() interface{} { return new(Sudoku) },
@@ -81,6 +84,7 @@ var (
}, "type", "settings") }, "type", "settings")
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"filter": func() interface{} { return new(Filter) },
"header-custom": func() interface{} { return new(HeaderCustomUDP) }, "header-custom": func() interface{} { return new(HeaderCustomUDP) },
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) }, "mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
"noise": func() interface{} { return new(NoiseMask) }, "noise": func() interface{} { return new(NoiseMask) },
@@ -93,6 +97,23 @@ var (
}, "type", "settings") }, "type", "settings")
) )
type Filter struct {
BlockedSourceIPs []string `json:"blockedSourceIPs"`
AllowedSourceIPs []string `json:"allowedSourceIPs"`
}
func (c *Filter) Build() (proto.Message, error) {
rules, err := geodata.ParseIPRules(c.BlockedSourceIPs)
if err != nil {
return nil, errors.New(`invalid "blockedSourceIPs"`).Base(err)
}
allowed, err := geodata.ParseIPRules(c.AllowedSourceIPs)
if err != nil {
return nil, errors.New(`invalid "allowedSourceIPs"`).Base(err)
}
return &filter.Config{BlockedSourceIps: rules, AllowedSourceIps: allowed}, nil
}
type TCPItem struct { type TCPItem struct {
Delay Int32Range `json:"delay"` Delay Int32Range `json:"delay"`
Rand int32 `json:"rand"` Rand int32 `json:"rand"`
@@ -0,0 +1,79 @@
package conf_test
import (
"encoding/json"
"fmt"
"net"
"strings"
"testing"
"github.com/xtls/xray-core/common/geodata"
. "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask/filter"
)
func TestFinalMaskFilter(t *testing.T) {
for _, network := range []string{"tcp", "udp"} {
for _, test := range []struct {
name, settings string
blocked, allowed int
}{
{"blacklist", `{"blockedSourceIPs":["192.0.2.0/24"]}`, 1, 0},
{"whitelist", `{"allowedSourceIPs":["192.0.2.0/24"]}`, 0, 1},
{"both", `{"blockedSourceIPs":["192.0.2.0/24"],"allowedSourceIPs":["192.0.2.0/24"]}`, 1, 1},
{"empty_whitelist", `{"allowedSourceIPs":[]}`, 0, 0},
{"empty_blacklist", `{"blockedSourceIPs":[]}`, 0, 0},
} {
t.Run(network+"/"+test.name, func(t *testing.T) {
input := fmt.Sprintf(`{"finalmask":{"%s":[{"type":"filter","settings":%s}]}}`, network, test.settings)
var config StreamConfig
if err := json.Unmarshal([]byte(input), &config); err != nil {
t.Fatal(err)
}
built, err := config.Build()
if err != nil {
t.Fatal(err)
}
masks := built.Tcpmasks
if network == "udp" {
masks = built.Udpmasks
}
if len(masks) != 1 {
t.Fatalf("expected one mask, got %d", len(masks))
}
instance, err := masks[0].GetInstance()
if err != nil {
t.Fatal(err)
}
filter, ok := instance.(*filter.Config)
if !ok {
t.Fatalf("unexpected mask type: %T", instance)
}
if len(filter.BlockedSourceIps) != test.blocked || len(filter.AllowedSourceIps) != test.allowed {
t.Fatalf("unexpected filter policy: %v", filter)
}
for _, rules := range [][]*geodata.IPRule{filter.BlockedSourceIps, filter.AllowedSourceIps} {
for _, rule := range rules {
cidr := rule.GetCustom().GetCidr()
if cidr.GetPrefix() != 24 || !net.IP(cidr.GetIp()).Equal(net.IPv4(192, 0, 2, 0)) {
t.Fatalf("unexpected IP rule: %v", cidr)
}
}
}
if _, err := internet.ToMemoryStreamConfig(built); err != nil {
t.Fatalf("failed to initialize runtime finalmask: %v", err)
}
})
}
}
for _, field := range []string{"blockedSourceIPs", "allowedSourceIPs"} {
var config Filter
if err := json.Unmarshal([]byte(fmt.Sprintf(`{"%s":["192.0.2.0/33"]}`, field)), &config); err != nil {
t.Fatal(err)
}
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), field) {
t.Errorf("expected invalid %s, got %v", field, err)
}
}
}
+18
View File
@@ -90,6 +90,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
config.ProtocolName = protocol config.ProtocolName = protocol
} }
var xdriveH3 bool
switch strings.ToLower(c.Security) { switch strings.ToLower(c.Security) {
case "", "none": case "", "none":
case "tls": case "tls":
@@ -104,6 +105,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
tm := serial.ToTypedMessage(ts) tm := serial.ToTypedMessage(ts)
config.SecuritySettings = append(config.SecuritySettings, tm) config.SecuritySettings = append(config.SecuritySettings, tm)
config.SecurityType = tm.Type config.SecurityType = tm.Type
xdriveH3 = config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"h3"})
if config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"http/1.1"}) && tlsSettings.ECHConfigList == "" { if config.ProtocolName == "xdrive" && tlsSettings.ALPN != nil && slices.Equal(*tlsSettings.ALPN, StringList{"http/1.1"}) && tlsSettings.ECHConfigList == "" {
// a fingerprint puts its own ALPN into the hello, except Go TLS and the // a fingerprint puts its own ALPN into the hello, except Go TLS and the
// randomized ones, which take it from the config or send none // randomized ones, which take it from the config or send none
@@ -219,6 +221,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
}) })
} }
if c.XDRIVESettings != nil { if c.XDRIVESettings != nil {
c.XDRIVESettings.h3 = xdriveH3
xs, err := c.XDRIVESettings.Build() xs, err := c.XDRIVESettings.Build()
if err != nil { if err != nil {
return nil, errors.New("Failed to build XDRIVE config.").Base(err) return nil, errors.New("Failed to build XDRIVE config.").Base(err)
@@ -260,5 +263,20 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
} }
} }
if xdriveH3 && c.FinalMask != nil {
if c.XDRIVESettings != nil && c.XDRIVESettings.Service == "google-drive" {
// noise only sends datagrams of its own ahead of the QUIC ones, the
// other masks change those or need a peer that undoes them
for _, mask := range c.FinalMask.Udp {
if strings.ToLower(mask.Type) != "noise" {
return nil, errors.New(`XDRIVE: over h3 to Google Drive the only "udp" mask that works is "noise", not "`, mask.Type, `"`)
}
}
}
if config.QuicParams != nil && (config.QuicParams.Congestion == "brutal" || config.QuicParams.Congestion == "force-brutal") {
return nil, errors.New(`XDRIVE: brutal is for Hysteria servers, over h3 "congestion" is "bbr" or "reno", not "`, config.QuicParams.Congestion, `"`)
}
}
return config, nil return config, nil
} }
+53 -1
View File
@@ -293,6 +293,7 @@ type SplitHTTPConfig struct {
ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"` ServerMaxHeaderBytes int32 `json:"serverMaxHeaderBytes"`
Xmux XmuxConfig `json:"xmux"` Xmux XmuxConfig `json:"xmux"`
DownloadSettings *StreamConfig `json:"downloadSettings"` DownloadSettings *StreamConfig `json:"downloadSettings"`
H2Params json.RawMessage `json:"h2Params"`
Extra json.RawMessage `json:"extra"` Extra json.RawMessage `json:"extra"`
} }
@@ -495,6 +496,7 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes, ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
SessionIDTable: c.SessionIDTable, SessionIDTable: c.SessionIDTable,
SessionIDLength: newRangeConfig(c.SessionIDLength), SessionIDLength: newRangeConfig(c.SessionIDLength),
H2Params: c.H2Params,
Xmux: &splithttp.XmuxConfig{ Xmux: &splithttp.XmuxConfig{
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency), MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
MaxConnections: newRangeConfig(c.Xmux.MaxConnections), MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
@@ -997,6 +999,18 @@ type XDriveConfig struct {
EagerWindowMs uint32 `json:"eagerWindowMs"` EagerWindowMs uint32 `json:"eagerWindowMs"`
HoleTimeoutMs uint32 `json:"holeTimeoutMs"` HoleTimeoutMs uint32 `json:"holeTimeoutMs"`
Template json.RawMessage `json:"template"` Template json.RawMessage `json:"template"`
S3 *XDriveS3Config `json:"s3"`
Xmux XmuxConfig `json:"xmux"`
// set by StreamConfig when "alpn" is ["h3"]
h3 bool
}
type XDriveS3Config struct {
Endpoint string `json:"endpoint"`
Region string `json:"region"`
Bucket string `json:"bucket"`
PathStyle *bool `json:"pathStyle"`
} }
// Build implements Buildable. // Build implements Buildable.
@@ -1015,8 +1029,27 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
if len(c.Template) == 0 { if len(c.Template) == 0 {
return nil, errors.New(`service "template" needs a "template" object`) return nil, errors.New(`service "template" needs a "template" object`)
} }
case "s3":
if c.S3 == nil {
return nil, errors.New(`service "s3" needs an "s3" object`)
}
default: default:
return nil, errors.New(`unknown "service": "`, c.Service, `", use "google-drive", "template" or "local"`) return nil, errors.New(`unknown "service": "`, c.Service, `", use "google-drive", "template", "s3" or "local"`)
}
if c.Xmux.MaxConnections.To > 0 && c.Xmux.MaxConcurrency.To > 0 {
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
}
if c.Xmux == (XmuxConfig{}) {
c.Xmux.MaxConnections.From = 1
c.Xmux.MaxConnections.To = 1
if c.Service == "google-drive" && !c.h3 {
// Google gives an h2 connection 1 MiB of window, which a few sessions on
// a long rtt would share, an h3 connection has no such limit
c.Xmux.MaxConnections.From = 4
c.Xmux.MaxConnections.To = 4
}
c.Xmux.HMaxReusableSecs.From = 1800
c.Xmux.HMaxReusableSecs.To = 3000
} }
config := &xdrive.Config{ config := &xdrive.Config{
RemoteFolder: c.RemoteFolder, RemoteFolder: c.RemoteFolder,
@@ -1031,6 +1064,25 @@ func (c *XDriveConfig) Build() (proto.Message, error) {
EagerWindowMs: c.EagerWindowMs, EagerWindowMs: c.EagerWindowMs,
HoleTimeoutMs: c.HoleTimeoutMs, HoleTimeoutMs: c.HoleTimeoutMs,
Template: string(c.Template), Template: string(c.Template),
Xmux: &splithttp.XmuxConfig{
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
CMaxReuseTimes: newRangeConfig(c.Xmux.CMaxReuseTimes),
HMaxRequestTimes: newRangeConfig(c.Xmux.HMaxRequestTimes),
HMaxReusableSecs: newRangeConfig(c.Xmux.HMaxReusableSecs),
HKeepAlivePeriod: c.Xmux.HKeepAlivePeriod,
},
}
if c.Service == "s3" {
config.S3 = &xdrive.S3Config{
Endpoint: c.S3.Endpoint,
Region: c.S3.Region,
Bucket: c.S3.Bucket,
PathStyle: c.S3.PathStyle == nil || *c.S3.PathStyle,
}
if err := xdrive.ValidateS3Config(config); err != nil {
return nil, err
}
} }
return config, nil return config, nil
} }
+124 -1
View File
@@ -9,6 +9,7 @@ import (
. "github.com/xtls/xray-core/infra/conf" . "github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
finalmaskcustom "github.com/xtls/xray-core/transport/internet/finalmask/header/custom" finalmaskcustom "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
"github.com/xtls/xray-core/transport/internet/splithttp"
"github.com/xtls/xray-core/transport/internet/xdrive" "github.com/xtls/xray-core/transport/internet/xdrive"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -323,7 +324,7 @@ func TestXDriveRejectsUnknownService(t *testing.T) {
if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil { if err := json.Unmarshal([]byte(`{"remoteFolder": "/tmp/xdrive", "service": "Dropbox"}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err) t.Fatalf("Unmarshal: %v", err)
} }
if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), `use "google-drive", "template" or "local"`) { if _, err := config.Build(); err == nil || !strings.Contains(err.Error(), `use "google-drive", "template", "s3" or "local"`) {
t.Fatalf("expected an unknown service error, got %v", err) t.Fatalf("expected an unknown service error, got %v", err)
} }
} }
@@ -424,6 +425,81 @@ func TestXDriveHTTP1ALPNWarning(t *testing.T) {
} }
} }
// over h3 Google Drive takes only the noise mask, and no service takes brutal
func TestXDriveH3Checks(t *testing.T) {
const (
drive = `"method": "xdrive", "xdriveSettings": {"remoteFolder": "f", "service": "google-drive", "secrets": ["a", "b", "c"]}`
local = `"method": "xdrive", "xdriveSettings": {"remoteFolder": "/tmp/xdrive", "service": "local"}`
noise = `{"type": "Noise", "settings": {"noise": [{"rand": "50-100"}]}}`
salamander = `{"type": "salamander", "settings": {"password": "p"}}`
forceBrutal = `{"quicParams": {"congestion": "force-brutal", "brutalUp": "10 mbps"}}`
)
for _, c := range []struct {
alpn, transport, finalmask string
ok bool
}{
{`["h3"]`, drive, `{"udp": [` + noise + `]}`, true},
{`["h3"]`, drive, `{"udp": [` + salamander + `]}`, false},
{`["h3"]`, drive, `{"udp": [` + noise + `, ` + salamander + `]}`, false},
{`["h3"]`, local, `{"udp": [` + salamander + `]}`, true},
{`["h3"]`, `"method": "xdrive"`, `{"udp": [` + salamander + `]}`, true},
{`["h2"]`, drive, `{"udp": [` + salamander + `]}`, true},
{`null`, drive, `{"udp": [` + salamander + `]}`, true},
{`["h3"]`, drive, `null`, true},
{`["h3"]`, drive, `{"quicParams": {"congestion": "bbr"}}`, true},
{`["h3"]`, drive, `{"quicParams": {"congestion": "brutal"}}`, false},
{`["h3"]`, local, forceBrutal, false},
{`["h3", "h2"]`, drive, `{"quicParams": {"congestion": "brutal"}}`, true},
{`["h3"]`, `"method": "xhttp"`, forceBrutal, true},
} {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
`+c.transport+`,
"security": "tls",
"tlsSettings": {"alpn": `+c.alpn+`},
"finalmask": `+c.finalmask+`
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); (err == nil) != c.ok {
t.Errorf("alpn %s, %s, finalmask %s: %v", c.alpn, c.transport, c.finalmask, err)
}
}
}
// one h3 connection is not held back by a window like h2, so h3 takes one by default
func TestXDriveH3Connections(t *testing.T) {
for _, c := range []struct {
alpn string
want int32
}{
{`["h3"]`, 1},
{`["h2"]`, 4},
{`null`, 4},
} {
config := new(StreamConfig)
if err := json.Unmarshal([]byte(`{
"method": "xdrive",
"xdriveSettings": {"remoteFolder": "f", "service": "google-drive", "secrets": ["a", "b", "c"]},
"security": "tls",
"tlsSettings": {"alpn": `+c.alpn+`}
}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("alpn %s: %v", c.alpn, err)
}
xs, err := built.TransportSettings[0].Settings.GetInstance()
if err != nil {
t.Fatalf("alpn %s: %v", c.alpn, err)
}
if got := xs.(*xdrive.Config).Xmux.MaxConnections; got.From != c.want || got.To != c.want {
t.Errorf("alpn %s: maxConnections is %d-%d, want %d", c.alpn, got.From, got.To, c.want)
}
}
}
func TestXDriveTemplateNeedsTemplate(t *testing.T) { func TestXDriveTemplateNeedsTemplate(t *testing.T) {
config := new(XDriveConfig) config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil { if err := json.Unmarshal([]byte(`{"remoteFolder": "f", "service": "template"}`), config); err != nil {
@@ -513,3 +589,50 @@ func TestXDriveInboundConfig(t *testing.T) {
} }
} }
} }
func TestXDriveXmux(t *testing.T) {
defaults := func(connections int32) *splithttp.XmuxConfig {
return &splithttp.XmuxConfig{
MaxConcurrency: &splithttp.RangeConfig{},
MaxConnections: &splithttp.RangeConfig{From: connections, To: connections},
CMaxReuseTimes: &splithttp.RangeConfig{},
HMaxRequestTimes: &splithttp.RangeConfig{},
HMaxReusableSecs: &splithttp.RangeConfig{From: 1800, To: 3000},
}
}
for _, c := range []struct {
input string
want *splithttp.XmuxConfig
}{
{`{"service": "google-drive", "secrets": ["a", "b", "c"]}`, defaults(4)},
{`{"service": "template", "template": {}}`, defaults(1)},
{`{"service": "google-drive", "secrets": ["a", "b", "c"], "xmux": {"maxConcurrency": "1-2", "hMaxRequestTimes": 100, "hKeepAlivePeriod": -1}}`, &splithttp.XmuxConfig{
MaxConcurrency: &splithttp.RangeConfig{From: 1, To: 2},
MaxConnections: &splithttp.RangeConfig{},
CMaxReuseTimes: &splithttp.RangeConfig{},
HMaxRequestTimes: &splithttp.RangeConfig{From: 100, To: 100},
HMaxReusableSecs: &splithttp.RangeConfig{},
HKeepAlivePeriod: -1,
}},
} {
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(c.input), config); err != nil {
t.Fatalf("%s: Unmarshal: %v", c.input, err)
}
built, err := config.Build()
if err != nil {
t.Fatalf("%s: Build: %v", c.input, err)
}
if got := built.(*xdrive.Config).Xmux; !proto.Equal(got, c.want) {
t.Errorf("%s: xmux is %v, want %v", c.input, got, c.want)
}
}
config := new(XDriveConfig)
if err := json.Unmarshal([]byte(`{"service": "template", "template": {}, "xmux": {"maxConnections": 2, "maxConcurrency": 8}}`), config); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, err := config.Build(); err == nil {
t.Error("Build accepted maxConnections with maxConcurrency")
}
}
+10 -15
View File
@@ -6,9 +6,7 @@ import (
"math/big" "math/big"
"net" "net"
"runtime" "runtime"
"slices"
"strconv" "strconv"
"strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/proxy/tun" "github.com/xtls/xray-core/proxy/tun"
@@ -39,25 +37,22 @@ func (v *TunConfig) Build() (proto.Message, error) {
AutoSystemRoutingTable: v.AutoSystemRoutingTable, AutoSystemRoutingTable: v.AutoSystemRoutingTable,
AutoSystemDnsToGateway: v.AutoSystemDnsToGateway, AutoSystemDnsToGateway: v.AutoSystemDnsToGateway,
} }
for _, leak := range v.AutoSystemWfpBlockLeak { leaks, err := tun.ExpandWfpBlockLeak(v.AutoSystemWfpBlockLeak)
switch leak := strings.ToLower(leak); leak { if err != nil {
case "dns", "misconfigtun": return nil, err
config.AutoSystemWfpBlockLeak = append(config.AutoSystemWfpBlockLeak, leak)
default:
return nil, errors.New("unknown autoSystemWfpBlockLeak value: ", leak)
}
} }
config.AutoSystemWfpBlockLeak = leaks
// Each option needs other settings on the system it takes effect on: the // Each option needs other settings on the system it takes effect on: the
// filters go along with the routes of autoSystemRoutingTable, "dns" lets // filters go along with the routes of autoSystemRoutingTable, keeping the
// DNS through the TUN only, and autoSystemDnsToGateway points the system // system's DNS inside the TUN needs DNS servers on it, and
// DNS at the gateway. // autoSystemDnsToGateway points the system DNS at the gateway.
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
if len(config.AutoSystemWfpBlockLeak) > 0 && len(v.AutoSystemRoutingTable) == 0 { if len(leaks) > 0 && len(v.AutoSystemRoutingTable) == 0 {
return nil, errors.New("autoSystemWfpBlockLeak needs autoSystemRoutingTable to be set") return nil, errors.New("autoSystemWfpBlockLeak needs autoSystemRoutingTable to be set")
} }
if slices.Contains(config.AutoSystemWfpBlockLeak, "dns") && len(v.DNS) == 0 { if tun.BlocksSystemDNS(leaks) && len(v.DNS) == 0 {
return nil, errors.New(`autoSystemWfpBlockLeak "dns" needs dns to be set`) return nil, errors.New(`autoSystemWfpBlockLeak "all", "allexceptlan", "dns", "windnsclient", or TCP or UDP port 53, needs dns to be set`)
} }
case "linux", "freebsd": case "linux", "freebsd":
if v.AutoSystemDnsToGateway && len(v.Gateway) == 0 { if v.AutoSystemDnsToGateway && len(v.Gateway) == 0 {
+60 -6
View File
@@ -29,12 +29,37 @@ func TestTunConfigAutoSystem(t *testing.T) {
{ {
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns", "misconfigtun"]}`, Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns", "misconfigtun"]}`,
Parser: loadJSON(creator), Parser: loadJSON(creator),
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"dns", "misconfigtun"}}, Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"53", "853", "windnsclient", "misconfigtun"}},
}, },
{ {
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["DNS"]}`, Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["DNS"]}`,
Parser: loadJSON(creator), Parser: loadJSON(creator),
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"dns"}}, Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"53", "853", "windnsclient"}},
},
{
Input: `{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["TCP/853", "udp/853", "tcp/0853"]}`,
Parser: loadJSON(creator),
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"tcp/853", "udp/853"}},
},
{
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["All", "UDP", "tcp/8000-9000", "icmp"]}`,
Parser: loadJSON(creator),
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"all", "udp", "tcp/8000-9000", "icmp"}},
},
{
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["AllExceptLAN"]}`,
Parser: loadJSON(creator),
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"allexceptlan", "53", "853", "windnsclient"}},
},
{
Input: `{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["1.1.1.1/24:TCP/853", "[2001:DB8::/32]:udp/10-20", "[::/0]:ICMP", "853"]}`,
Parser: loadJSON(creator),
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"1.1.1.0/24:tcp/853", "[2001:db8::/32]:udp/10-20", "[::/0]:icmp", "853"}},
},
{
Input: `{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["0.0.0.0/0:dns", "[::/0]:All", "53"]}`,
Parser: loadJSON(creator),
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"0.0.0.0/0:53", "0.0.0.0/0:853", "0.0.0.0/0:windnsclient", "[::/0]:all", "53"}},
}, },
}) })
} }
@@ -49,6 +74,24 @@ func TestTunConfigAutoSystemNeeds(t *testing.T) {
{`{"name": "xray0", "autoSystemWfpBlockLeak": ["misconfigtun"]}`, []string{"windows"}}, {`{"name": "xray0", "autoSystemWfpBlockLeak": ["misconfigtun"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["misconfigtun"]}`, nil}, {`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["misconfigtun"]}`, nil},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns"]}`, []string{"windows"}}, {`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["udp/53"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["windnsclient"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/853", "udp/853"]}`, nil},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["all"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["udp"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/50-60"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/54-60", "icmp"]}`, nil},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["53"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["50-60"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["[::/0]:udp/53"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["1.1.1.1/32:dns"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["[2001:db8::/32]:all"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["0.0.0.0/0:windnsclient"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["0.0.0.0/0:tcp/853", "1.1.1.0/24:icmp", "[::/0]:misconfigtun", "54-60"]}`, nil},
{`{"name": "xray0", "autoSystemWfpBlockLeak": ["[::/0]:misconfigtun"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemWfpBlockLeak": ["all"]}`, []string{"windows"}},
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["allexceptlan"]}`, []string{"windows"}},
{`{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["allexceptlan"]}`, nil},
{`{"name": "xray0", "autoSystemDnsToGateway": true}`, []string{"linux", "freebsd"}}, {`{"name": "xray0", "autoSystemDnsToGateway": true}`, []string{"linux", "freebsd"}},
} { } {
config := new(TunConfig) config := new(TunConfig)
@@ -62,11 +105,22 @@ func TestTunConfigAutoSystemNeeds(t *testing.T) {
} }
func TestTunConfigAutoSystemWfpBlockLeakUnknown(t *testing.T) { func TestTunConfigAutoSystemWfpBlockLeakUnknown(t *testing.T) {
config := new(TunConfig) // With the settings the values need on Windows, so that only an unknown
if err := json.Unmarshal([]byte(`{"name": "xray0", "autoSystemWfpBlockLeak": ["dns", "ip"]}`), config); err != nil { // one fails.
build := func(value string) error {
config := new(TunConfig)
if err := json.Unmarshal([]byte(`{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["dns", "`+value+`"]}`), config); err != nil {
t.Fatal(err)
}
_, err := config.Build()
return err
}
if err := build("1.1.1.0/24:53"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := config.Build(); err == nil { for _, value := range []string{"ip", "tcp/0", "tcp/0-10", "icmp/1", "udp/20-10", "0", "20-10", "53/tcp", "tcpv4", "ipv4:udp", "1.1.1.1:53", "2001:db8::/32:53", "[1.1.1.0/24]:53", "[::ffff:1.1.1.0/120]:53", "1.1.1.0/24"} {
t.Error("an unknown autoSystemWfpBlockLeak value was accepted") if build(value) == nil {
t.Errorf("autoSystemWfpBlockLeak value %q was accepted", value)
}
} }
} }
+6 -3
View File
@@ -237,10 +237,11 @@ type OutboundDetourConfig struct {
func requiresTransportSecurity(address *Address) bool { func requiresTransportSecurity(address *Address) bool {
if address == nil || address.Address == nil { if address == nil || address.Address == nil {
return false return true
} }
if address.Family().IsIP() { if address.Family().IsIP() {
return !geodata.GetPrivateIPMatcher().Match(address.IP()) ip := address.IP()
return !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
} }
domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".") domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".")
return !geodata.GetPrivateDomainMatcher().MatchAny(domain) return !geodata.GetPrivateDomainMatcher().MatchAny(domain)
@@ -608,7 +609,6 @@ func (c *Config) Build() (*core.Config, error) {
config := &core.Config{ config := &core.Config{
App: []*serial.TypedMessage{ App: []*serial.TypedMessage{
serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}),
}, },
} }
@@ -718,6 +718,9 @@ func (c *Config) Build() (*core.Config, error) {
config.App = append(config.App, serial.ToTypedMessage(r)) config.App = append(config.App, serial.ToTypedMessage(r))
} }
// Initialize the inbound module last to ensure inbound connections are handled correctly.
config.App = append(config.App, serial.ToTypedMessage(&proxyman.InboundConfig{}))
var inbounds []InboundDetourConfig var inbounds []InboundDetourConfig
if len(c.InboundConfigs) > 0 { if len(c.InboundConfigs) > 0 {
+2 -2
View File
@@ -42,7 +42,7 @@ func TestXrayConfig(t *testing.T) {
Input: `{ Input: `{
"log": { "log": {
"access": "/var/log/xray/access.log", "access": "/var/log/xray/access.log",
"loglevel": "error", "logLevel": "error",
"error": "/var/log/xray/error.log" "error": "/var/log/xray/error.log"
}, },
"inbounds": [{ "inbounds": [{
@@ -90,7 +90,6 @@ func TestXrayConfig(t *testing.T) {
AccessLogPath: "/var/log/xray/access.log", AccessLogPath: "/var/log/xray/access.log",
}), }),
serial.ToTypedMessage(&dispatcher.Config{}), serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}), serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(&router.Config{ serial.ToTypedMessage(&router.Config{
DomainStrategy: router.Config_AsIs, DomainStrategy: router.Config_AsIs,
@@ -111,6 +110,7 @@ func TestXrayConfig(t *testing.T) {
}, },
}, },
}), }),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
}, },
Inbound: []*core.InboundHandlerConfig{ Inbound: []*core.InboundHandlerConfig{
{ {
+174 -197
View File
@@ -1,149 +1,87 @@
package main package main
import ( import (
"errors" "bytes"
"context"
"flag" "flag"
"fmt" "fmt"
"go/build" "io/fs"
"io"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"runtime"
"strconv"
"strings" "strings"
"github.com/bufbuild/protocompile"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/pluginpb"
) )
var directory = flag.String("pwd", "", "Working directory of Xray vprotogen.") var (
directory = flag.String("pwd", "", "Working directory of Xray vprotogen.")
mode = flag.String("mode", "generate", "Execution mode. 'generate' (default) compiles and saves changes. 'check' reports out-of-date files and exits with non-zero.")
)
// envFile returns the name of the Go environment configuration file. // topoSort sorts file descriptors so each dependency appears before files importing it.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166 func topoSort(files []protoreflect.FileDescriptor) []*descriptorpb.FileDescriptorProto {
func envFile() (string, error) { fileMap := make(map[string]protoreflect.FileDescriptor)
if file := os.Getenv("GOENV"); file != "" { for _, f := range files {
if file == "off" { fileMap[f.Path()] = f
return "", errors.New("GOENV=off") }
visited := make(map[string]bool)
var sorted []*descriptorpb.FileDescriptorProto
var visit func(fd protoreflect.FileDescriptor)
visit = func(fd protoreflect.FileDescriptor) {
if visited[fd.Path()] {
return
} }
return file, nil visited[fd.Path()] = true
}
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
if dir == "" {
return "", errors.New("missing user-config dir")
}
return filepath.Join(dir, "go", "env"), nil
}
// GetRuntimeEnv returns the value of runtime environment variable, imports := fd.Imports()
// that is set by running following command: `go env -w key=value`. for i := 0; i < imports.Len(); i++ {
func GetRuntimeEnv(key string) (string, error) { if dep, ok := fileMap[imports.Get(i).Path()]; ok {
file, err := envFile() visit(dep)
if err != nil { }
return "", err
}
if file == "" {
return "", errors.New("missing runtime env file")
}
var data []byte
var runtimeEnv string
data, readErr := os.ReadFile(file)
if readErr != nil {
return "", readErr
}
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
runtimeEnv = strings.TrimSpace(envKeyValue[1])
} }
sorted = append(sorted, protodesc.ToFileDescriptorProto(fd))
} }
return runtimeEnv, nil
for _, f := range files {
visit(f)
}
return sorted
} }
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty. func runPlugin(pluginName, pwd string, req *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse, error) {
func GetGOBIN() string { reqBytes, err := proto.Marshal(req)
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os.Getenv("GOBIN")
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN, err = GetRuntimeEnv("GOBIN")
if err != nil {
// The default one that Golang uses
return filepath.Join(build.Default.GOPATH, "bin")
}
if GOBIN == "" {
return filepath.Join(build.Default.GOPATH, "bin")
}
return GOBIN
}
return GOBIN
}
func whichProtoc(suffix, targetedVersion string) (string, error) {
protoc := "protoc" + suffix
path, err := exec.LookPath(protoc)
if err != nil { if err != nil {
return "", fmt.Errorf(` return nil, err
Command "%s" not found.
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
`, protoc, protoc, protoc, targetedVersion)
} }
return path, nil
}
func getProjectProtocVersion(url string) (string, error) { cmd := exec.Command("go", "tool", pluginName)
resp, err := http.Get(url) cmd.Dir = pwd
if err != nil { cmd.Stdin = bytes.NewReader(reqBytes)
return "", errors.New("can not get the version of protobuf used in xray project") cmd.Env = os.Environ()
} var stdout, stderr bytes.Buffer
defer resp.Body.Close() cmd.Stdout = &stdout
body, err := io.ReadAll(resp.Body) cmd.Stderr = &stderr
if err != nil {
return "", errors.New("can not read from body")
}
versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v\d+\.(\d+\.\d+)`)
matched := versionRegexp.FindStringSubmatch(string(body))
return matched[1], nil
}
func getInstalledProtocVersion(protocPath string) (string, error) { if err := cmd.Run(); err != nil {
cmd := exec.Command(protocPath, "--version") return nil, fmt.Errorf("%v: %s", err, stderr.String())
cmd.Env = append(cmd.Env, os.Environ()...)
output, cmdErr := cmd.CombinedOutput()
if cmdErr != nil {
return "", cmdErr
} }
versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+)`)
matched := versionRegexp.FindStringSubmatch(string(output))
return matched[1], nil
}
func parseVersion(s string, width int) int64 { var resp pluginpb.CodeGeneratorResponse
strList := strings.Split(s, ".") if err := proto.Unmarshal(stdout.Bytes(), &resp); err != nil {
format := fmt.Sprintf("%%s%%0%ds", width) return nil, fmt.Errorf("failed to unmarshal response: %w", err)
v := ""
for _, value := range strList {
v = fmt.Sprintf(format, v, value)
} }
var result int64 if resp.GetError() != "" {
var err error return nil, fmt.Errorf("%s", resp.GetError())
if result, err = strconv.ParseInt(v, 10, 64); err != nil {
return 0
} }
return result return &resp, nil
}
func needToUpdate(targetedVersion, installedVersion string) bool {
vt := parseVersion(targetedVersion, 4)
vi := parseVersion(installedVersion, 4)
return vt > vi
} }
func main() { func main() {
@@ -154,106 +92,145 @@ func main() {
flag.Parse() flag.Parse()
if !filepath.IsAbs(*directory) { if !filepath.IsAbs(*directory) {
pwd, wdErr := os.Getwd() pwd, err := os.Getwd()
if wdErr != nil { if err != nil {
fmt.Println("Can not get current working directory.") fmt.Println("Can not get current working directory.")
os.Exit(1) os.Exit(1)
} }
*directory = filepath.Join(pwd, *directory) *directory = filepath.Join(pwd, *directory)
} }
pwd := *directory pwd := *directory
GOBIN := GetGOBIN()
binPath := os.Getenv("PATH")
pathSlice := []string{pwd, GOBIN, binPath}
binPath = strings.Join(pathSlice, string(os.PathListSeparator))
os.Setenv("PATH", binPath)
suffix := "" isCheck := false
if runtime.GOOS == "windows" { switch *mode {
suffix = ".exe" case "generate", "":
} isCheck = false
case "check":
/* isCheck = true
targetedVersion, err := getProjectProtocVersion("https://raw.githubusercontent.com/XTLS/Xray-core/HEAD/core/config.pb.go") default:
if err != nil { fmt.Fprintf(os.Stderr, "Unrecognized 'mode': %s. Use 'generate' or 'check'.\n", *mode)
fmt.Println(err)
os.Exit(1)
}
*/
targetedVersion := ""
protoc, err := whichProtoc(suffix, targetedVersion)
if err != nil {
fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
installedVersion, err := getInstalledProtocVersion(protoc) if isCheck {
if err != nil { fmt.Println("Checking for out-of-date Protobuf generated files...")
fmt.Println(err)
os.Exit(1)
} }
if needToUpdate(targetedVersion, installedVersion) { var protoFiles []string
fmt.Printf(` var existingPbFiles []string
You are using an old protobuf version, please update to v%s or later. err := filepath.WalkDir(pwd, func(path string, d fs.DirEntry, err error) error {
Download it from https://github.com/protocolbuffers/protobuf/releases if err != nil || d.IsDir() {
* Protobuf version used in xray project: v%s
* Protobuf version you have installed: v%s
`, targetedVersion, targetedVersion, installedVersion)
os.Exit(1)
}
protoFilesMap := make(map[string][]string)
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return err return err
} }
if strings.HasSuffix(d.Name(), ".proto") {
if info.IsDir() { rel, _ := filepath.Rel(pwd, path)
return nil protoFiles = append(protoFiles, filepath.ToSlash(rel))
} }
if strings.HasSuffix(d.Name(), ".pb.go") {
dir := filepath.Dir(path) existingPbFiles = append(existingPbFiles, path)
filename := filepath.Base(path)
if strings.HasSuffix(filename, ".proto") {
path = path[len(pwd)+1:]
protoFilesMap[dir] = append(protoFilesMap[dir], path)
} }
return nil return nil
}) })
if walkErr != nil { if err != nil {
fmt.Println(walkErr) fmt.Println("Walk error:", err)
os.Exit(1) os.Exit(1)
} }
for _, files := range protoFilesMap { compiler := protocompile.Compiler{
for _, relProtoFile := range files { Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
args := []string{ ImportPaths: []string{pwd},
"--go_out", pwd, }),
"--go_opt", "paths=source_relative", SourceInfoMode: protocompile.SourceInfoStandard,
"--go-grpc_out", pwd, }
"--go-grpc_opt", "paths=source_relative",
"--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix), linkedFiles, err := compiler.Compile(context.Background(), protoFiles...)
"--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix), if err != nil {
fmt.Fprintf(os.Stderr, "Protobuf compilation failed:\n%v\n", err)
os.Exit(1)
}
var fdList []protoreflect.FileDescriptor
for _, f := range linkedFiles {
fdList = append(fdList, f)
}
req := &pluginpb.CodeGeneratorRequest{
FileToGenerate: protoFiles,
Parameter: new("paths=source_relative"),
ProtoFile: topoSort(fdList),
}
var diffCount int
generatedFiles := make(map[string]struct{})
for _, plugin := range []string{"protoc-gen-go", "protoc-gen-go-grpc"} {
resp, err := runPlugin(plugin, pwd, req)
if err != nil {
fmt.Fprintf(os.Stderr, "%s failed: %v\n", plugin, err)
os.Exit(1)
}
for _, f := range resp.File {
targetPath := filepath.Join(pwd, filepath.FromSlash(f.GetName()))
generatedFiles[targetPath] = struct{}{}
content := []byte(f.GetContent())
hasDiff := false
existing, err := os.ReadFile(targetPath)
if err != nil {
hasDiff = true
} else {
oldNorm := bytes.ReplaceAll(existing, []byte("\r\n"), []byte("\n"))
newNorm := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
if !bytes.Equal(oldNorm, newNorm) {
hasDiff = true
}
} }
args = append(args, relProtoFile)
cmd := exec.Command(protoc, args...) if hasDiff {
cmd.Env = append(cmd.Env, os.Environ()...) diffCount++
cmd.Dir = pwd fmt.Println(targetPath)
output, cmdErr := cmd.CombinedOutput()
if len(output) > 0 { if isCheck {
fmt.Println(string(output)) continue
} }
if cmdErr != nil {
fmt.Println(cmdErr) if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
os.Exit(1) fmt.Fprintf(os.Stderr, "Error creating directory: %v\n", err)
os.Exit(1)
}
mode := os.FileMode(0o644)
if stat, err := os.Stat(targetPath); err == nil {
mode = stat.Mode().Perm()
}
if err := os.WriteFile(targetPath, content, mode); err != nil {
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", targetPath, err)
os.Exit(1)
}
} }
} }
} }
for _, path := range existingPbFiles {
if _, ok := generatedFiles[path]; !ok {
diffCount++
if isCheck {
fmt.Printf("Orphaned Protobuf file: %s\n", path)
} else {
fmt.Printf("Removing orphaned Protobuf file: %s\n", path)
if err := os.Remove(path); err != nil {
fmt.Fprintf(os.Stderr, "Error removing %s: %v\n", path, err)
os.Exit(1)
}
}
}
}
if isCheck {
if diffCount > 0 {
fmt.Println("Protobuf generated file problem(s) found.")
fmt.Println("Please run 'go run ./infra/vprotogen' to regenerate them.")
os.Exit(1)
}
fmt.Println("All Protobuf generated file check has been passed.")
}
} }
+11 -10
View File
@@ -47,18 +47,19 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
conn, ctx, close := dialAPIServer() conn, ctx, close := dialAPIServer()
defer close() defer close()
client := routerService.NewRoutingServiceClient(conn) client := routerService.NewRoutingServiceClient(conn)
r := &routerService.GetBalancerInfoRequest{Tag: unnamedArgs[0]} for _, tag := range unnamedArgs {
resp, err := client.GetBalancerInfo(ctx, r) r := &routerService.GetBalancerInfoRequest{Tag: tag}
if err != nil { resp, err := client.GetBalancerInfo(ctx, r)
base.Fatalf("failed to get health information: %s", err) if err != nil {
} base.Fatalf("failed to get health information: %s", err)
}
if apiJSON { if apiJSON {
showJSONResponse(resp) showJSONResponse(resp)
return } else {
showBalancerInfo(resp.Balancer)
}
} }
showBalancerInfo(resp.Balancer)
} }
func showBalancerInfo(b *routerService.BalancerMsg) { func showBalancerInfo(b *routerService.BalancerMsg) {
+2
View File
@@ -92,6 +92,8 @@ func executeAddRules(cmd *base.Command, args []string) {
if err != nil { if err != nil {
base.Fatalf("failed to perform AddRule: %s", err) base.Fatalf("failed to perform AddRule: %s", err)
} }
// if multiple files are specified, use append to merge them instead of overriding previous one
shouldAppend = true
showJSONResponse(resp) showJSONResponse(resp)
} }
} }
+4 -11
View File
@@ -3,6 +3,7 @@ package lua
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
xlua "github.com/xtls/xray-core/common/lua" xlua "github.com/xtls/xray-core/common/lua"
"github.com/xtls/xray-core/main/commands/base" "github.com/xtls/xray-core/main/commands/base"
@@ -32,14 +33,13 @@ func executeSign(cmd *base.Command, args []string) {
cmd.Usage() cmd.Usage()
} }
if !strings.EqualFold(filepath.Ext(args[0]), ".lua") {
base.Fatalf("Lua file extension must be .lua")
}
info, err := os.Stat(args[0]) info, err := os.Stat(args[0])
if err != nil { if err != nil {
base.Fatalf("failed to inspect Lua file: %s", err) base.Fatalf("failed to inspect Lua file: %s", err)
} }
if !info.Mode().IsRegular() {
base.Fatalf("Lua input is not a regular file: %s", args[0])
}
code, err := os.ReadFile(args[0]) code, err := os.ReadFile(args[0])
if err != nil { if err != nil {
base.Fatalf("failed to read Lua file: %s", err) base.Fatalf("failed to read Lua file: %s", err)
@@ -67,14 +67,7 @@ func executeSign(cmd *base.Command, args []string) {
func writeSignedFile(path string, code []byte, mode os.FileMode) error { func writeSignedFile(path string, code []byte, mode os.FileMode) error {
if info, err := os.Stat(path); err == nil { if info, err := os.Stat(path); err == nil {
if !info.Mode().IsRegular() {
return &os.PathError{Op: "write", Path: path, Err: os.ErrInvalid}
}
mode = info.Mode().Perm() mode = info.Mode().Perm()
path, err = filepath.EvalSymlinks(path)
if err != nil {
return err
}
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {
return err return err
} }
+2 -10
View File
@@ -58,18 +58,10 @@ func readConfigFile(file *os.File) ([]byte, error) {
return nil, err return nil, err
} }
if info.Mode().IsRegular() { if info.Mode().IsRegular() {
path, err := filepath.Abs(file.Name()) switch strings.ToLower(filepath.Ext(file.Name())) {
if err != nil {
return nil, err
}
path, err = filepath.EvalSymlinks(path)
if err != nil {
return nil, err
}
switch strings.ToLower(filepath.Ext(path)) {
case ".jsonc", ".json", ".yml", ".yaml", ".toml", ".pb", ".protobuf": case ".jsonc", ".json", ".yml", ".yaml", ".toml", ".pb", ".protobuf":
default: default:
return nil, errors.New("unsupported config file extension: ", path) return nil, errors.New("unsupported config file extension: ", file.Name())
} }
} }
return io.ReadAll(file) return io.ReadAll(file)
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/blackhole/config.proto // source: proxy/blackhole/config.proto
package blackhole package blackhole
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/dns/config.proto // source: proxy/dns/config.proto
package dns package dns
+7
View File
@@ -381,6 +381,13 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
errors.LogInfoInner(context.Background(), err, "pack message") errors.LogInfoInner(context.Background(), err, "pack message")
b.Release() b.Release()
timer.SetTimeout(0) timer.SetTimeout(0)
return
}
if len(msgBytes) > buf.Size {
b.Release()
timer.SetTimeout(0)
errors.LogError(context.Background(), "DNS response message too large")
return
} }
b.Resize(0, int32(len(msgBytes))) b.Resize(0, int32(len(msgBytes)))
+24 -24
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/dokodemo/config.proto // source: proxy/dokodemo/config.proto
package dokodemo package dokodemo
@@ -23,14 +23,14 @@ const (
) )
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,omitempty"`
RewritePort uint32 `protobuf:"varint,2,opt,name=rewrite_port,json=rewritePort,proto3" json:"rewrite_port,omitempty"`
PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
// List of networks that the Dokodemo accepts. // List of networks that the Dokodemo accepts.
AllowedNetworks []net.Network `protobuf:"varint,7,rep,packed,name=allowed_networks,json=allowedNetworks,proto3,enum=xray.common.net.Network" json:"allowed_networks,omitempty"` AllowedNetworks []net.Network `protobuf:"varint,7,rep,packed,name=allowed_networks,json=allowedNetworks,proto3,enum=xray.common.net.Network" json:"allowed_networks,omitempty"`
FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"` RewriteAddress *net.IPOrDomain `protobuf:"bytes,1,opt,name=rewrite_address,json=rewriteAddress,proto3" json:"rewrite_address,omitempty"`
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` RewritePort uint32 `protobuf:"varint,2,opt,name=rewrite_port,json=rewritePort,proto3" json:"rewrite_port,omitempty"`
PortMap map[string]string `protobuf:"bytes,3,rep,name=port_map,json=portMap,proto3" json:"port_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
FollowRedirect bool `protobuf:"varint,5,opt,name=follow_redirect,json=followRedirect,proto3" json:"follow_redirect,omitempty"`
UserLevel uint32 `protobuf:"varint,6,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -65,6 +65,13 @@ func (*Config) Descriptor() ([]byte, []int) {
return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0} return file_proxy_dokodemo_config_proto_rawDescGZIP(), []int{0}
} }
func (x *Config) GetAllowedNetworks() []net.Network {
if x != nil {
return x.AllowedNetworks
}
return nil
}
func (x *Config) GetRewriteAddress() *net.IPOrDomain { func (x *Config) GetRewriteAddress() *net.IPOrDomain {
if x != nil { if x != nil {
return x.RewriteAddress return x.RewriteAddress
@@ -86,13 +93,6 @@ func (x *Config) GetPortMap() map[string]string {
return nil return nil
} }
func (x *Config) GetAllowedNetworks() []net.Network {
if x != nil {
return x.AllowedNetworks
}
return nil
}
func (x *Config) GetFollowRedirect() bool { func (x *Config) GetFollowRedirect() bool {
if x != nil { if x != nil {
return x.FollowRedirect return x.FollowRedirect
@@ -112,11 +112,11 @@ var File_proxy_dokodemo_config_proto protoreflect.FileDescriptor
const file_proxy_dokodemo_config_proto_rawDesc = "" + const file_proxy_dokodemo_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xff\x02\n" + "\x1bproxy/dokodemo/config.proto\x12\x13xray.proxy.dokodemo\x1a\x18common/net/address.proto\x1a\x18common/net/network.proto\"\xff\x02\n" +
"\x06Config\x12D\n" + "\x06Config\x12C\n" +
"\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12D\n" +
"\x0frewrite_address\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x0erewriteAddress\x12!\n" + "\x0frewrite_address\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x0erewriteAddress\x12!\n" +
"\frewrite_port\x18\x02 \x01(\rR\vrewritePort\x12C\n" + "\frewrite_port\x18\x02 \x01(\rR\vrewritePort\x12C\n" +
"\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12C\n" + "\bport_map\x18\x03 \x03(\v2(.xray.proxy.dokodemo.Config.PortMapEntryR\aportMap\x12'\n" +
"\x10allowed_networks\x18\a \x03(\x0e2\x18.xray.common.net.NetworkR\x0fallowedNetworks\x12'\n" +
"\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" + "\x0ffollow_redirect\x18\x05 \x01(\bR\x0efollowRedirect\x12\x1d\n" +
"\n" + "\n" +
"user_level\x18\x06 \x01(\rR\tuserLevel\x1a:\n" + "user_level\x18\x06 \x01(\rR\tuserLevel\x1a:\n" +
@@ -141,13 +141,13 @@ var file_proxy_dokodemo_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_dokodemo_config_proto_goTypes = []any{ var file_proxy_dokodemo_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.proxy.dokodemo.Config (*Config)(nil), // 0: xray.proxy.dokodemo.Config
nil, // 1: xray.proxy.dokodemo.Config.PortMapEntry nil, // 1: xray.proxy.dokodemo.Config.PortMapEntry
(*net.IPOrDomain)(nil), // 2: xray.common.net.IPOrDomain (net.Network)(0), // 2: xray.common.net.Network
(net.Network)(0), // 3: xray.common.net.Network (*net.IPOrDomain)(nil), // 3: xray.common.net.IPOrDomain
} }
var file_proxy_dokodemo_config_proto_depIdxs = []int32{ var file_proxy_dokodemo_config_proto_depIdxs = []int32{
2, // 0: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain 2, // 0: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network
1, // 1: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry 3, // 1: xray.proxy.dokodemo.Config.rewrite_address:type_name -> xray.common.net.IPOrDomain
3, // 2: xray.proxy.dokodemo.Config.allowed_networks:type_name -> xray.common.net.Network 1, // 2: xray.proxy.dokodemo.Config.port_map:type_name -> xray.proxy.dokodemo.Config.PortMapEntry
3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type 3, // [3:3] 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 type_name
+36 -22
View File
@@ -1,13 +1,14 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/freedom/config.proto // source: proxy/freedom/config.proto
package freedom package freedom
import ( import (
geodata "github.com/xtls/xray-core/common/geodata" geodata "github.com/xtls/xray-core/common/geodata"
log "github.com/xtls/xray-core/common/log"
net "github.com/xtls/xray-core/common/net" net "github.com/xtls/xray-core/common/net"
protocol "github.com/xtls/xray-core/common/protocol" protocol "github.com/xtls/xray-core/common/protocol"
internet "github.com/xtls/xray-core/transport/internet" internet "github.com/xtls/xray-core/transport/internet"
@@ -357,7 +358,8 @@ type FinalRuleConfig struct {
Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"` Networks []net.Network `protobuf:"varint,2,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"` PortList *net.PortList `protobuf:"bytes,3,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"` Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"`
BlockDelay *Range `protobuf:"bytes,5,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"` Delay *Range `protobuf:"bytes,5,opt,name=delay,proto3" json:"delay,omitempty"`
LogLevel *log.Severity `protobuf:"varint,6,opt,name=log_level,json=logLevel,proto3,enum=xray.common.log.Severity,oneof" json:"log_level,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -420,13 +422,20 @@ func (x *FinalRuleConfig) GetIp() []*geodata.IPRule {
return nil return nil
} }
func (x *FinalRuleConfig) GetBlockDelay() *Range { func (x *FinalRuleConfig) GetDelay() *Range {
if x != nil { if x != nil {
return x.BlockDelay return x.Delay
} }
return nil return nil
} }
func (x *FinalRuleConfig) GetLogLevel() log.Severity {
if x != nil && x.LogLevel != nil {
return *x.LogLevel
}
return log.Severity(0)
}
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"` DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
@@ -523,7 +532,7 @@ var File_proxy_freedom_config_proto protoreflect.FileDescriptor
const file_proxy_freedom_config_proto_rawDesc = "" + const file_proxy_freedom_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"S\n" + "\x1aproxy/freedom/config.proto\x12\x12xray.proxy.freedom\x1a!common/protocol/server_spec.proto\x1a\x1ftransport/internet/config.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\x1a\x14common/log/log.proto\"S\n" +
"\x13DestinationOverride\x12<\n" + "\x13DestinationOverride\x12<\n" +
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" + "\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06server\"\x98\x02\n" +
"\bFragment\x12!\n" + "\bFragment\x12!\n" +
@@ -549,14 +558,16 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" + "\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" +
"\x05Range\x12\x10\n" + "\x05Range\x12\x10\n" +
"\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" + "\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" +
"\x03max\x18\x02 \x01(\x04R\x03max\"\xa0\x02\n" + "\x03max\x18\x02 \x01(\x04R\x03max\"\xe0\x02\n" +
"\x0fFinalRuleConfig\x126\n" + "\x0fFinalRuleConfig\x126\n" +
"\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" + "\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" +
"\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" + "\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" +
"\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" + "\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" +
"\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12:\n" + "\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12/\n" +
"\vblock_delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\n" + "\x05delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\x05delay\x12;\n" +
"blockDelay\"\xaf\x03\n" + "\tlog_level\x18\x06 \x01(\x0e2\x19.xray.common.log.SeverityH\x00R\blogLevel\x88\x01\x01B\f\n" +
"\n" +
"_log_level\"\xaf\x03\n" +
"\x06Config\x12P\n" + "\x06Config\x12P\n" +
"\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" + "\x0fdomain_strategy\x18\x01 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0edomainStrategy\x12Z\n" +
"\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" + "\x14destination_override\x18\x03 \x01(\v2'.xray.proxy.freedom.DestinationOverrideR\x13destinationOverride\x12\x1d\n" +
@@ -599,7 +610,8 @@ var file_proxy_freedom_config_proto_goTypes = []any{
(net.Network)(0), // 8: xray.common.net.Network (net.Network)(0), // 8: xray.common.net.Network
(*net.PortList)(nil), // 9: xray.common.net.PortList (*net.PortList)(nil), // 9: xray.common.net.PortList
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule (*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
(internet.DomainStrategy)(0), // 11: xray.transport.internet.DomainStrategy (log.Severity)(0), // 11: xray.common.log.Severity
(internet.DomainStrategy)(0), // 12: xray.transport.internet.DomainStrategy
} }
var file_proxy_freedom_config_proto_depIdxs = []int32{ var file_proxy_freedom_config_proto_depIdxs = []int32{
7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint 7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
@@ -607,17 +619,18 @@ var file_proxy_freedom_config_proto_depIdxs = []int32{
8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network 8, // 2: xray.proxy.freedom.FinalRuleConfig.networks:type_name -> xray.common.net.Network
9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList 9, // 3: xray.proxy.freedom.FinalRuleConfig.port_list:type_name -> xray.common.net.PortList
10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule 10, // 4: xray.proxy.freedom.FinalRuleConfig.ip:type_name -> xray.common.geodata.IPRule
4, // 5: xray.proxy.freedom.FinalRuleConfig.block_delay:type_name -> xray.proxy.freedom.Range 4, // 5: xray.proxy.freedom.FinalRuleConfig.delay:type_name -> xray.proxy.freedom.Range
11, // 6: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy 11, // 6: xray.proxy.freedom.FinalRuleConfig.log_level:type_name -> xray.common.log.Severity
1, // 7: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride 12, // 7: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
2, // 8: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment 1, // 8: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
3, // 9: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise 2, // 9: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
5, // 10: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig 3, // 10: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
11, // [11:11] is the sub-list for method output_type 5, // 11: xray.proxy.freedom.Config.final_rules:type_name -> xray.proxy.freedom.FinalRuleConfig
11, // [11:11] is the sub-list for method input_type 12, // [12:12] is the sub-list for method output_type
11, // [11:11] is the sub-list for extension type_name 12, // [12:12] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension extendee 12, // [12:12] is the sub-list for extension type_name
0, // [0:11] is the sub-list for field type_name 12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
} }
func init() { file_proxy_freedom_config_proto_init() } func init() { file_proxy_freedom_config_proto_init() }
@@ -625,6 +638,7 @@ func file_proxy_freedom_config_proto_init() {
if File_proxy_freedom_config_proto != nil { if File_proxy_freedom_config_proto != nil {
return return
} }
file_proxy_freedom_config_proto_msgTypes[4].OneofWrappers = []any{}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
+3 -1
View File
@@ -11,6 +11,7 @@ import "transport/internet/config.proto";
import "common/net/port.proto"; import "common/net/port.proto";
import "common/net/network.proto"; import "common/net/network.proto";
import "common/geodata/geodat.proto"; import "common/geodata/geodat.proto";
import "common/log/log.proto";
message DestinationOverride { message DestinationOverride {
xray.common.protocol.ServerEndpoint server = 1; xray.common.protocol.ServerEndpoint server = 1;
@@ -51,7 +52,8 @@ message FinalRuleConfig {
repeated xray.common.net.Network networks = 2; repeated xray.common.net.Network networks = 2;
xray.common.net.PortList port_list = 3; xray.common.net.PortList port_list = 3;
repeated xray.common.geodata.IPRule ip = 4; repeated xray.common.geodata.IPRule ip = 4;
Range block_delay = 5; Range delay = 5;
optional xray.common.log.Severity log_level = 6;
} }
message Config { message Config {
+52 -25
View File
@@ -15,6 +15,7 @@ import (
"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/geodata" "github.com/xtls/xray-core/common/geodata"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
"github.com/xtls/xray-core/common/retry" "github.com/xtls/xray-core/common/retry"
@@ -72,23 +73,26 @@ func init() {
} }
defaultBlockPrivateRule = &FinalRule{ defaultBlockPrivateRule = &FinalRule{
action: RuleAction_Block, action: RuleAction_Block,
network: allNetworks, network: allNetworks,
ip: geodata.GetPrivateIPMatcher(), ip: geodata.GetPrivateIPMatcher(),
logLevel: log.Severity_Warning.Enum(),
} }
defaultBlockAllRule = &FinalRule{ defaultBlockAllRule = &FinalRule{
action: RuleAction_Block, action: RuleAction_Block,
network: allNetworks, network: allNetworks,
logLevel: log.Severity_Warning.Enum(),
} }
} }
type FinalRule struct { type FinalRule struct {
action RuleAction action RuleAction
network [8]bool network [8]bool
port net.MemoryPortList port net.MemoryPortList
ip geodata.IPMatcher ip geodata.IPMatcher
blockDelay *Range delay *Range
logLevel *log.Severity
} }
// Handler handles Freedom connections. // Handler handles Freedom connections.
@@ -102,8 +106,9 @@ type Handler struct {
func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) { func buildFinalRule(config *FinalRuleConfig) (*FinalRule, error) {
rule := &FinalRule{ rule := &FinalRule{
action: config.GetAction(), action: config.GetAction(),
blockDelay: config.GetBlockDelay(), delay: config.GetDelay(),
logLevel: config.LogLevel,
} }
if len(config.Networks) == 0 { if len(config.Networks) == 0 {
@@ -164,7 +169,7 @@ func getDefaultFinalRule(inbound *session.Inbound) *FinalRule {
switch inbound.Name { switch inbound.Name {
case "vless-reverse": case "vless-reverse":
return defaultBlockAllRule return defaultBlockAllRule
case "vless", "vmess", "trojan", "hysteria", "wireguard": case "vless", "vmess", "trojan", "hysteria", "wireguard", "masque":
return defaultBlockPrivateRule return defaultBlockPrivateRule
default: default:
if strings.HasPrefix(inbound.Name, "shadowsocks") { if strings.HasPrefix(inbound.Name, "shadowsocks") {
@@ -212,12 +217,12 @@ func (h *Handler) policy() policy.Session {
return p return p
} }
func (h *Handler) blockDelay(rule *FinalRule) time.Duration { func (r *FinalRule) blockDuration() time.Duration {
min := uint64(30) min := uint64(30)
max := uint64(90) max := uint64(90)
if rule.blockDelay != nil { if r.delay != nil {
min = rule.blockDelay.Min min = r.delay.Min
max = rule.blockDelay.Max max = r.delay.Max
} }
span := max - min span := max - min
if max < min { if max < min {
@@ -226,17 +231,39 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second return time.Duration(min+uint64(dice.Roll(int(span+1)))) * time.Second
} }
func (r *FinalRule) log(ctx context.Context, msg ...interface{}) {
level := log.Severity_Info
if r.logLevel != nil {
level = *r.logLevel
}
switch level {
case log.Severity_Debug:
errors.LogDebug(ctx, msg...)
case log.Severity_Info:
errors.LogInfo(ctx, msg...)
case log.Severity_Warning:
errors.LogWarning(ctx, msg...)
case log.Severity_Error:
errors.LogError(ctx, msg...)
}
}
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error { func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
delay := h.blockDelay(rule) delay := rule.blockDuration()
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay) rule.log(ctx, "target blocked by a finalrule: ", *dest, ", blackholing connection for ", delay)
timer := time.AfterFunc(delay, func() { defer common.Close(output)
done := make(chan struct{})
go func() { // a DispatchLink inbound's reader ignores Interrupt, so don't wait for this copy
_ = buf.Copy(input, buf.Discard)
close(done)
}()
select {
case <-done:
case <-time.After(delay):
common.Interrupt(input) common.Interrupt(input)
common.Interrupt(output) common.Interrupt(output)
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest) rule.log(ctx, "closed blackholed connection to blocked target: ", *dest)
}) }
defer timer.Stop()
defer common.Close(output)
_ = buf.Copy(input, buf.Discard)
return nil return nil
} }
+30 -2
View File
@@ -10,6 +10,7 @@ import (
"net/url" "net/url"
"sync" "sync"
"text/template" "text/template"
"time"
"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"
@@ -86,7 +87,13 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
user := server.User user := server.User
var conn stat.Connection var conn stat.Connection
mbuf, _ := link.Reader.ReadMultiBuffer() var mbuf buf.MultiBuffer
if reader, ok := link.Reader.(buf.TimeoutReader); ok {
// Don't wait forever: server-first protocols (SMTP, FTP, MySQL...) send nothing until the server greets.
mbuf, _ = reader.ReadMultiBufferTimeout(time.Millisecond * 500)
} else {
mbuf, _ = link.Reader.ReadMultiBuffer()
}
len := mbuf.Len() len := mbuf.Len()
firstPayload := bytespool.Alloc(len) firstPayload := bytespool.Alloc(len)
mbuf, _ = buf.SplitBytes(mbuf, firstPayload) mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
@@ -234,7 +241,8 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
return nil, err return nil, err
} }
resp, err := http.ReadResponse(bufio.NewReader(rawConn), req) reader := bufio.NewReader(rawConn)
resp, err := http.ReadResponse(reader, req)
if err != nil { if err != nil {
rawConn.Close() rawConn.Close()
return nil, err return nil, err
@@ -245,6 +253,11 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
rawConn.Close() rawConn.Close()
return nil, errors.New("Proxy responded with non 200 code: " + resp.Status) return nil, errors.New("Proxy responded with non 200 code: " + resp.Status)
} }
// Tunneled bytes (e.g. a server-first greeting) may arrive in the same read as the 200 response.
if n := reader.Buffered(); n > 0 {
early, _ := reader.Peek(n)
return &earlyDataConn{Conn: rawConn, early: early}, nil
}
return rawConn, nil return rawConn, nil
} }
@@ -375,6 +388,21 @@ func (h *http2Conn) Close() error {
return h.out.Close() return h.out.Close()
} }
// earlyDataConn returns the bytes read ahead together with the CONNECT response before reading from Conn.
type earlyDataConn struct {
net.Conn
early []byte
}
func (c *earlyDataConn) Read(p []byte) (int, error) {
if len(c.early) > 0 {
n := copy(p, c.early)
c.early = c.early[n:]
return n, nil
}
return c.Conn.Read(p)
}
func init() { func init() {
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewClient(ctx, config.(*ClientConfig)) return NewClient(ctx, config.(*ClientConfig))
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/http/config.proto // source: proxy/http/config.proto
package http package http
+3 -2
View File
@@ -312,8 +312,9 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
// Sometimes, server might send 1xx response to client // Sometimes, server might send 1xx response to client
// it should not be processed by http proxy handler, just forward it 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) { func readResponseAndHandle100Continue(r *bufio.Reader, req *http.Request, writer io.Writer) (*http.Response, error) {
// have a little look of response // Wait for the HTTP/1.x status prefix before inspecting the buffered response.
peekBytes, err := r.Peek(56) r.Peek(12)
peekBytes, err := r.Peek(min(56, r.Buffered()))
if err == nil || err == bufio.ErrBufferFull { if err == nil || err == bufio.ErrBufferFull {
str := string(peekBytes) str := string(peekBytes)
ResponseLine := strings.Split(str, "\r\n")[0] ResponseLine := strings.Split(str, "\r\n")[0]
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
"testing/iotest"
) )
// A malformed upstream response containing a bare '\n' before the real // A malformed upstream response containing a bare '\n' before the real
@@ -29,7 +30,7 @@ func TestReadResponseAndHandle100ContinueDoesNotPanicOnEarlyNewline(t *testing.T
func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testing.T) { func TestReadResponseAndHandle100ContinueForwardsAndParsesFinalResponse(t *testing.T) {
payload := "HTTP/1.1 100 Continue\r\n\r\n" + payload := "HTTP/1.1 100 Continue\r\n\r\n" +
"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello" "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello"
r := bufio.NewReader(bytes.NewReader([]byte(payload))) r := bufio.NewReader(iotest.OneByteReader(bytes.NewReader([]byte(payload))))
req, err := http.NewRequest("GET", "http://example.com/", nil) req, err := http.NewRequest("GET", "http://example.com/", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/hysteria/account/config.proto // source: proxy/hysteria/account/config.proto
package account package account
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/hysteria/config.proto // source: proxy/hysteria/config.proto
package hysteria package hysteria
+2 -2
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.11 // protoc-gen-go v1.36.12
// protoc v6.33.5 // protoc (unknown)
// source: proxy/loopback/config.proto // source: proxy/loopback/config.proto
package loopback package loopback

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