Compare commits

...
Author SHA1 Message Date
RPRX cc190f2d8e Update handler.go 2026-06-23 10:56:07 +00:00
Fangliding 455197dd91 Fix TUN nil addr 2026-06-23 18:46:05 +08:00
dependabot[bot] fab4bcc1ed Bump github.com/cloudflare/circl from 1.6.3 to 1.6.4 (#6362)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.3 to 1.6.4.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.3...v1.6.4)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.4
  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-06-23 10:34:07 +00:00
RPRX b99c3e5657 Xray-core v26.6.22
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-06-22 18:55:10 +00:00
Omoeba 583bb4a63f XHTTP server: Fix scStreamUpServerSecs when xPaddingObfsMode is true (#6343)
https://github.com/XTLS/Xray-core/pull/6343#issuecomment-4771666966
2026-06-22 18:51:47 +00:00
Жора Змейкин 9cd9382e3d TUN inbound: Support env XRAY_TUN_FD on Linux as well (#6338)
https://github.com/XTLS/Xray-core/pull/6338#issuecomment-4770945163
2026-06-22 17:38:36 +00:00
patternihaandcopilot-swe-agent[bot] 567500c4af Fragment finalmask: Add lengths and delays (#6334)
Usage: https://github.com/XTLS/Xray-core/pull/6334#issue-4685556394

Behavior: https://github.com/XTLS/Xray-core/pull/6334#issuecomment-4751547750

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-22 17:38:12 +00:00
dependabot[bot] 5aefcb41fb Bump github.com/pion/stun/v3 from 3.1.5 to 3.1.6 (#6357)
Bumps [github.com/pion/stun/v3](https://github.com/pion/stun) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/pion/stun/releases)
- [Commits](https://github.com/pion/stun/compare/v3.1.5...v3.1.6)

---
updated-dependencies:
- dependency-name: github.com/pion/stun/v3
  dependency-version: 3.1.6
  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-06-22 16:05:47 +00:00
Meow be8009c625 Geodata: Cleanup unneeded matchers & domain: ignore case (#6342)
Completes https://github.com/XTLS/Xray-core/pull/6139
2026-06-19 12:02:27 +00:00
dependabot[bot] 8734774e4a Bump actions/checkout from 6 to 7 (#6344)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 11:23:41 +00:00
风扇滑翔翼 1e036ce1c5 XHTTP/3 client: Actively close underlying QUIC & UDP (#6332)
Fixes https://github.com/XTLS/Xray-core/issues/6328#issuecomment-4730379021
2026-06-18 23:55:11 +00:00
j2rong4cn c815c2f2df Loopback outbound: Add sniffing (#6326)
Example: https://github.com/XTLS/Xray-core/pull/6326#issue-4659701786
2026-06-18 23:17:01 +00:00
bytecategory 986c512e0f XHTTP client: Avoid panic when host is invalid (#6316)
Fixes https://github.com/XTLS/Xray-core/issues/6315
2026-06-18 22:55:18 +00:00
Meow 711aea4e34 XHTTP & WS & HU & gRPC servers: Require sockopt.trustedXForwardedFor (#6309)
https://github.com/XTLS/Xray-core/pull/6258#issuecomment-4663652131

Behavior: https://github.com/XTLS/Xray-core/pull/6258#issuecomment-4746598275

Replaces https://github.com/XTLS/Xray-core/pull/6159
2026-06-18 22:31:21 +00:00
风扇滑翔翼andRPRX 6412738486 Socks5 inbound: Fix issues in new UDP ASSOCIATE (#6325)
https://github.com/XTLS/Xray-core/pull/6325#issuecomment-4724008713

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

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-17 14:44:40 +00:00
LjhAUMEM ad2e4cb0e1 Finalmask: Fix unexpected order and UDP's buf issue (#6331)
https://github.com/XTLS/Xray-docs-next/pull/866#issuecomment-4729859528

And https://github.com/XTLS/Xray-core/pull/6331#issuecomment-4730527410

Fixes https://github.com/XTLS/Xray-core/issues/6184#issuecomment-4725831023
2026-06-17 14:44:12 +00:00
Hossin AsaadiandLjhAUMEM 829d54d7be Hysteria & XHTTP/3 clients: udpHop supports dialerProxy (#6320)
https://github.com/XTLS/Xray-core/pull/6320#issuecomment-4725679616

Fixes https://github.com/XTLS/Xray-core/pull/6320#issuecomment-4699599655

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-06-17 12:20:28 +00:00
LjhAUMEM 862631172d WireGuard proxy: Refactor (#6287)
And https://github.com/XTLS/Xray-core/pull/6303#issuecomment-4669158076

Fixes https://github.com/XTLS/Xray-core/issues/6257
2026-06-17 12:02:10 +00:00
dependabot[bot] d27b3e46e2 Bump golang.org/x/net from 0.55.0 to 0.56.0 (#6310)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.55.0 to 0.56.0.
- [Commits](https://github.com/golang/net/compare/v0.55.0...v0.56.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-16 11:45:27 +00:00
LjhAUMEM da21a8f77f TUN & WireGuard inbounds: Ignore b.UDP's domain when receiving it from outbound (#6285)
Fixes https://github.com/XTLS/Xray-core/issues/6279
2026-06-09 20:53:55 +00:00
风扇滑翔翼andXXcipherX e10347bf01 XHTTP transport: Add sessionIDTable and sessionIDLength; Rename session* to sessionID* (#6258)
https://github.com/XTLS/Xray-core/pull/6258#issuecomment-4658534046
https://github.com/XTLS/Xray-core/pull/6253#issuecomment-4657704004
https://github.com/XTLS/Xray-core/pull/6251#issuecomment-4612756220

Usage: https://github.com/XTLS/Xray-core/pull/6258#issue-4580617110

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

---------

Co-authored-by: XXcipherX <knazevvv6514@gmail.com>
2026-06-09 15:58:02 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇 26a022c905 GitHub Action CI, README.md: Refinements and add compliance contents (#6283)
And https://github.com/XTLS/Xray-core/pull/6283#issuecomment-4640162879
2026-06-09 12:08:38 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇 95e9816223 Chore: Limit sing* dependencies to shadowsocks_2022 only (#6286)
https://github.com/XTLS/Xray-core/pull/6286#issuecomment-4658968225
2026-06-09 10:55:42 +00:00
风扇滑翔翼 3239d21168 TUN inbound: autoOutboundsInterface bypasses loopback addresses (#6276)
Fixes https://github.com/XTLS/Xray-core/issues/6269
2026-06-09 09:40:55 +00:00
风扇滑翔翼 06b4931743 TUN inbound: Start TUN by AlwaysOnInboundHandler (#6275)
Fixes https://github.com/XTLS/Xray-core/issues/6274
2026-06-09 09:22:33 +00:00
LjhAUMEM 6189d2bfd5 XICMP finalmask: Refine Linux sever (#6272)
Fixes https://github.com/XTLS/Xray-core/pull/6168
2026-06-09 09:14:54 +00:00
风扇滑翔翼andj2rong4cn a0e9347f1b TLS ECH: Handle "h2c://" query correctly (#6261)
Fixes https://github.com/XTLS/Xray-core/issues/6259#issuecomment-4614984919

---------

Co-authored-by: j2rong4cn <36783515+j2rong4cn@users.noreply.github.com>
2026-06-09 09:03:24 +00:00
IconHHw 83cf229909 Salamander finalmask: Replace math/rand with crypto/rand in salt generation (#6228)
And https://github.com/XTLS/Xray-core/pull/6228#issuecomment-4612712100

Fixes https://github.com/XTLS/Xray-core/pull/6228#issuecomment-4599037015
2026-06-08 19:55:06 +00:00
dependabot[bot] 2249f8b5c6 Bump github.com/pion/stun/v3 from 3.1.2 to 3.1.5 (#6291)
Bumps [github.com/pion/stun/v3](https://github.com/pion/stun) from 3.1.2 to 3.1.5.
- [Release notes](https://github.com/pion/stun/releases)
- [Commits](https://github.com/pion/stun/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: github.com/pion/stun/v3
  dependency-version: 3.1.5
  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-06-08 19:29:14 +00:00
89 changed files with 2956 additions and 2002 deletions
+1
View File
@@ -0,0 +1 @@
powershell.exe -ExecutionPolicy Bypass -File ".\xray_no_window.ps1"
+1
View File
@@ -0,0 +1 @@
Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden
+1
View File
@@ -0,0 +1 @@
CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
echo "LATEST=$LATEST" >>${GITHUB_ENV} echo "LATEST=$LATEST" >>${GITHUB_ENV}
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v4 uses: docker/setup-qemu-action@v4
+6 -4
View File
@@ -83,7 +83,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Show workflow information - name: Show workflow information
run: | run: |
@@ -134,15 +134,17 @@ jobs:
run: | run: |
mv -f resources/geo* build_assets/ mv -f resources/geo* build_assets/
if [[ ${GOOS} == 'windows' ]]; then if [[ ${GOOS} == 'windows' ]]; then
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs cp .github/build/windows/* build_assets/
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1 fi
if [[ ${GOOS} == 'windows' ]]; then
echo 'Adding Wintun into packages'
if [[ ${GOARCH} == 'amd64' ]]; then if [[ ${GOARCH} == 'amd64' ]]; then
mv resources/wintun/bin/amd64/wintun.dll build_assets/ mv resources/wintun/bin/amd64/wintun.dll build_assets/
fi fi
if [[ ${GOARCH} == '386' ]]; then if [[ ${GOARCH} == '386' ]]; then
mv resources/wintun/bin/x86/wintun.dll build_assets/ mv resources/wintun/bin/x86/wintun.dll build_assets/
fi fi
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
fi fi
- name: Copy README.md & LICENSE - name: Copy README.md & LICENSE
+6 -4
View File
@@ -170,7 +170,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Set up NDK - name: Set up NDK
if: matrix.goos == 'android' if: matrix.goos == 'android'
@@ -241,8 +241,10 @@ jobs:
run: | run: |
mv -f resources/geo* build_assets/ mv -f resources/geo* build_assets/
if [[ ${GOOS} == 'windows' ]]; then if [[ ${GOOS} == 'windows' ]]; then
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs cp .github/build/windows/* build_assets/
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1 fi
if [[ ${GOOS} == 'windows' ]]; then
echo 'Adding Wintun into packages'
if [[ ${GOARCH} == 'amd64' ]]; then if [[ ${GOARCH} == 'amd64' ]]; then
mv resources/wintun/bin/amd64/wintun.dll build_assets/ mv resources/wintun/bin/amd64/wintun.dll build_assets/
fi fi
@@ -252,7 +254,7 @@ jobs:
if [[ ${GOARCH} == 'arm64' ]]; then if [[ ${GOARCH} == 'arm64' ]]; then
mv resources/wintun/bin/arm64/wintun.dll build_assets/ mv resources/wintun/bin/arm64/wintun.dll build_assets/
fi fi
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
fi fi
- name: Copy README.md & LICENSE - name: Copy README.md & LICENSE
+12 -5
View File
@@ -68,6 +68,9 @@ jobs:
wintun: wintun:
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
ASSETVER: 0.14.1
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
steps: steps:
- name: Restore Wintun Cache - name: Restore Wintun Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
@@ -96,7 +99,6 @@ jobs:
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..." echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
echo -e "wintun.dll for ${ARCHITECTURE} exists" echo -e "wintun.dll for ${ARCHITECTURE} exists"
continue
else else
echo -e "wintun.dll for ${ARCHITECTURE} is missing" echo -e "wintun.dll for ${ARCHITECTURE} is missing"
missing=true missing=true
@@ -113,12 +115,17 @@ jobs:
fi fi
if [[ "$missing" == true ]]; then if [[ "$missing" == true ]]; then
FILENAME=wintun.zip FILENAME=wintun.zip
DOWNLOAD_FILE=wintun-0.14.1.zip DOWNLOAD_FILE=wintun-${ASSETVER}.zip
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..." echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}" curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
echo -e "Unpacking wintun..." if [[ "$(sha256sum "./${FILENAME}" | awk -F ' ' '{print $1}')" == "${ASSETHASH}" ]]; then
unzip -u ${FILENAME} -d resources/ echo -e "Unpacking wintun..."
echo "unhit=true" >> $GITHUB_OUTPUT unzip -u ${FILENAME} -d resources/
echo "unhit=true" >> $GITHUB_OUTPUT
else
echo -e "Digest of ${FILENAME} mismatch."
exit 1
fi
fi fi
- name: Save Wintun Cache - name: Save Wintun Cache
+3 -3
View File
@@ -40,7 +40,7 @@ jobs:
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
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Check Proto Version Header - name: Check Proto Version Header
run: | run: |
head -n 4 core/config.pb.go > ref.txt head -n 4 core/config.pb.go > ref.txt
@@ -59,7 +59,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
@@ -83,7 +83,7 @@ jobs:
os: [windows-latest, ubuntu-latest, macos-latest] os: [windows-latest, ubuntu-latest, macos-latest]
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
+21
View File
@@ -186,6 +186,27 @@
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases). - [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod). - For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
### Bundled Third-Party Components Redistribution
**Certain optional features dynamically load third-party components. These optional components are separate works distributed under their own licenses, and are bundled into the ZIP package for ease of use. Users may replace these components under the licenses from these components.**
These components include:
#### Wintun
This distribution contains unmodified official precompiled and pre-signed Wintun binaries.
- Project: Wintun
- Copyright: Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
- Redistribution License: Prebuilt Binaries License (PBL) bundled with official precompiled and pre-signed binaries from wintun.net
- Component(s): wintun.dll
- Source: https://www.wintun.net/
- Included in:
- Windows x86 (windows-32, win7-32)
- Windows x86-64 (windows-64, win7-64)
- Windows AArch64 (windows-arm64)
- Notes: Wintun is an optional runtime-loaded component only used for TUN inbound functionality on supported Windows platforms.
## One-line Compilation ## One-line Compilation
### Windows (PowerShell) ### Windows (PowerShell)
+22 -14
View File
@@ -57,16 +57,23 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
if err != nil { if err != nil {
return nil, err return nil, err
} }
src := net.TCPDestination(net.AnyIP, 0)
// Set tag and sniffing config in context before creating proxy if receiverConfig.Listen != nil {
// This allows proxies like TUN to access these settings src.Address = receiverConfig.Listen.AsAddress()
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})
if receiverConfig.SniffingSettings != nil {
ctx = session.ContextWithContent(ctx, &session.Content{
SniffingRequest: sniffingRequest,
})
} }
rawProxy, err := common.CreateObject(ctx, proxyConfig) if receiverConfig.PortList != nil && len(receiverConfig.PortList.Range) > 0 {
src.Port = net.Port(receiverConfig.PortList.Range[0].From)
}
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
if err != nil {
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
}
newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src})
newCtx = session.ContextWithContent(newCtx, &session.Content{SniffingRequest: sniffingRequest})
newCtx = session.ContextWithStreamSettings(newCtx, mss)
rawProxy, err := common.CreateObject(newCtx, proxyConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -92,11 +99,6 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
address = net.AnyIP address = net.AnyIP
} }
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
if err != nil {
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
}
if receiverConfig.ReceiveOriginalDestination { if receiverConfig.ReceiveOriginalDestination {
if mss.SocketSettings == nil { if mss.SocketSettings == nil {
mss.SocketSettings = &internet.SocketConfig{} mss.SocketSettings = &internet.SocketConfig{}
@@ -170,6 +172,12 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
// Start implements common.Runnable. // Start implements common.Runnable.
func (h *AlwaysOnInboundHandler) Start() error { func (h *AlwaysOnInboundHandler) Start() error {
// for inbound without worker (TUN)
if run, ok := h.proxy.(common.Runnable); ok {
if err := run.Start(); err != nil {
return errors.New("failed to start proxy").Base(err)
}
}
for _, worker := range h.workers { for _, worker := range h.workers {
if err := worker.Start(); err != nil { if err := worker.Start(); err != nil {
return err return err
+3 -7
View File
@@ -6,7 +6,6 @@ import (
goerrors "errors" goerrors "errors"
"io" "io"
"math/big" "math/big"
"os"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
@@ -109,7 +108,9 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
ctx = session.ContextWithFullHandler(ctx, h) ctx = session.ContextWithFullHandler(ctx, h)
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig) newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -306,11 +307,6 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob) h.SetOutboundGateway(ctx, ob)
} }
}
if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {
return conn, err
} }
conn, err := internet.Dial(ctx, dest, h.streamSettings) conn, err := internet.Dial(ctx, dest, h.streamSettings)
-35
View File
@@ -1,35 +0,0 @@
package outbound
import (
"context"
"os"
"github.com/sagernet/sing/common/uot"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat"
)
func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if dest.Address == nil {
return nil, errors.New("nil destination address")
}
if !dest.Address.Family().IsDomain() {
return nil, os.ErrInvalid
}
var uotVersion int
if dest.Address.Domain() == uot.MagicAddress {
uotVersion = uot.Version
} else if dest.Address.Domain() == uot.LegacyMagicAddress {
uotVersion = uot.LegacyVersion
} else {
return nil, os.ErrInvalid
}
packetConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("unable to listen socket").Base(err)
}
conn := uot.NewServerConn(packetConn, uotVersion)
return h.getStatCouterConnection(conn), nil
}
+1 -1
View File
@@ -220,7 +220,7 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
case Domain_Regex: case Domain_Regex:
return strmatcher.Regex.New(d.Value) return strmatcher.Regex.New(d.Value)
case Domain_Domain: case Domain_Domain:
return strmatcher.Domain.New(d.Value) return strmatcher.Domain.New(strings.ToLower(d.Value))
case Domain_Full: case Domain_Full:
return strmatcher.Full.New(strings.ToLower(d.Value)) return strmatcher.Full.New(strings.ToLower(d.Value))
default: default:
+15 -7
View File
@@ -6,12 +6,14 @@ import (
"sync/atomic" "sync/atomic"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/common/uuid"
) )
type DomainRegistry struct { type DomainRegistry struct {
mu sync.Mutex mu sync.Mutex
factory DomainMatcherFactory factory DomainMatcherFactory
matchers []*DynamicDomainMatcher matchers *utils.WeakCacheMap[uuid.UUID, DynamicDomainMatcher]
} }
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) { func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
@@ -24,7 +26,7 @@ func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher,
} }
d := NewDynamicDomainMatcher(rules, m) d := NewDynamicDomainMatcher(rules, m)
r.matchers = append(r.matchers, d) r.matchers.Store(uuid.New(), d)
return d, nil return d, nil
} }
@@ -32,15 +34,20 @@ func (r *DomainRegistry) Reload() error {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(r.matchers), " domain matcher(s)") var matchers []*DynamicDomainMatcher
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicDomainMatcher) bool {
matchers = append(matchers, matcher)
return true
})
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(matchers), " domain matcher(s)")
factory := newDomainMatcherFactory() factory := newDomainMatcherFactory()
type reloadEntry struct { type reloadEntry struct {
dynamic *DynamicDomainMatcher dynamic *DynamicDomainMatcher
matcher DomainMatcher matcher DomainMatcher
} }
reloaded := make([]reloadEntry, len(r.matchers)) reloaded := make([]reloadEntry, len(matchers))
for i, d := range r.matchers { for i, d := range matchers {
m, err := factory.BuildMatcher(d.rules) m, err := factory.BuildMatcher(d.rules)
if err != nil { if err != nil {
errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i) errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i)
@@ -52,13 +59,14 @@ func (r *DomainRegistry) Reload() error {
entry.dynamic.Reload(entry.matcher) entry.dynamic.Reload(entry.matcher)
} }
r.factory = factory r.factory = factory
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(r.matchers), " domain matcher(s)") errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(matchers), " domain matcher(s)")
return nil return nil
} }
func newDomainRegistry() *DomainRegistry { func newDomainRegistry() *DomainRegistry {
return &DomainRegistry{ return &DomainRegistry{
factory: newDomainMatcherFactory(), factory: newDomainMatcherFactory(),
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicDomainMatcher](),
} }
} }
+19 -11
View File
@@ -7,25 +7,27 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/common/uuid"
) )
type IPRegistry struct { type IPRegistry struct {
mu sync.Mutex mu sync.Mutex
ipsetFactory *IPSetFactory factory *IPSetFactory
matchers []*DynamicIPMatcher matchers *utils.WeakCacheMap[uuid.UUID, DynamicIPMatcher]
} }
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) { func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
m, err := buildOptimizedIPMatcher(r.ipsetFactory, rules) m, err := buildOptimizedIPMatcher(r.factory, rules)
if err != nil { if err != nil {
return nil, err return nil, err
} }
d := NewDynamicIPMatcher(rules, m) d := NewDynamicIPMatcher(rules, m)
r.matchers = append(r.matchers, d) r.matchers.Store(uuid.New(), d)
return d, nil return d, nil
} }
@@ -33,15 +35,20 @@ func (r *IPRegistry) Reload() error {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(r.matchers), " IP matcher(s)") var matchers []*DynamicIPMatcher
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicIPMatcher) bool {
matchers = append(matchers, matcher)
return true
})
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(matchers), " IP matcher(s)")
factory := newIPSetFactory() factory := newIPSetFactory()
type reloadEntry struct { type reloadEntry struct {
dynamic *DynamicIPMatcher dynamic *DynamicIPMatcher
matcher IPMatcher matcher IPMatcher
} }
reloaded := make([]reloadEntry, len(r.matchers)) reloaded := make([]reloadEntry, len(matchers))
for i, d := range r.matchers { for i, d := range matchers {
m, err := buildOptimizedIPMatcher(factory, d.rules) m, err := buildOptimizedIPMatcher(factory, d.rules)
if err != nil { if err != nil {
errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i) errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i)
@@ -52,14 +59,15 @@ func (r *IPRegistry) Reload() error {
for _, entry := range reloaded { for _, entry := range reloaded {
entry.dynamic.Reload(entry.matcher) entry.dynamic.Reload(entry.matcher)
} }
r.ipsetFactory = factory r.factory = factory
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(r.matchers), " IP matcher(s)") errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(matchers), " IP matcher(s)")
return nil return nil
} }
func newIPRegistry() *IPRegistry { func newIPRegistry() *IPRegistry {
return &IPRegistry{ return &IPRegistry{
ipsetFactory: newIPSetFactory(), factory: newIPSetFactory(),
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicIPMatcher](),
} }
} }
+2 -2
View File
@@ -138,7 +138,7 @@ func ParseDomainRule(r string, defaultType Domain_Type) (*DomainRule, error) {
} }
prefix := 0 prefix := 0
for _, ext := range [...]string{"ext:", "ext-domain:"} { for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
if strings.HasPrefix(r, ext) { if strings.HasPrefix(r, ext) {
prefix = len(ext) prefix = len(ext)
break break
@@ -167,7 +167,7 @@ func ParseDomainRules(rules []string, defaultType Domain_Type) ([]*DomainRule, e
} }
prefix := 0 prefix := 0
for _, ext := range [...]string{"ext:", "ext-domain:"} { for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
if strings.HasPrefix(r, ext) { if strings.HasPrefix(r, ext) {
prefix = len(ext) prefix = len(ext)
break break
+26 -10
View File
@@ -1,25 +1,41 @@
package http package http
import ( import (
"context"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
) )
// ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it. // ApplyTrustedXForwardedFor returns remoteAddr overridden by X-Forwarded-For only when a configured trusted header is present.
func ParseXForwardedFor(header http.Header) []net.Address { func ApplyTrustedXForwardedFor(header http.Header, trusted []string, remoteAddr net.Addr) net.Addr {
xff := header.Get("X-Forwarded-For") value := header.Get("X-Forwarded-For")
if xff == "" { if value == "" {
return nil return remoteAddr
} }
list := strings.Split(xff, ",") for _, t := range trusted {
addrs := make([]net.Address, 0, len(list)) if len(header.Values(t)) > 0 {
for _, proxy := range list { if idx := strings.IndexByte(value, ','); idx >= 0 {
addrs = append(addrs, net.ParseAddress(proxy)) value = value[:idx]
}
if addr := net.ParseAddress(value); addr.Family().IsIP() {
return &net.TCPAddr{
IP: addr.IP(),
Port: 0,
}
}
return remoteAddr
}
} }
return addrs if len(trusted) == 0 {
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
} else {
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
}
return remoteAddr
} }
// RemoveHopByHopHeaders removes hop by hop headers in http header list. // RemoveHopByHopHeaders removes hop by hop headers in http header list.
+33 -8
View File
@@ -2,23 +2,48 @@ package http_test
import ( import (
"bufio" "bufio"
gonet "net"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
. "github.com/xtls/xray-core/common/protocol/http" . "github.com/xtls/xray-core/common/protocol/http"
) )
func TestParseXForwardedFor(t *testing.T) { func TestApplyTrustedXForwardedFor(t *testing.T) {
header := http.Header{} remoteAddr := &gonet.TCPAddr{IP: gonet.ParseIP("127.0.0.1"), Port: 12345}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
addrs := ParseXForwardedFor(header) t.Run("ignore X-Forwarded-For without trusted header", func(t *testing.T) {
if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" { header := http.Header{}
t.Error(r) header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
}
if addr := ApplyTrustedXForwardedFor(header, nil, remoteAddr); addr != remoteAddr {
t.Fatalf("unexpected remote address: %v", addr)
}
})
t.Run("trust X-Forwarded-For", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
header.Add("X-Trusted-CDN", "")
addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr)
if addr.String() != "129.78.138.66:0" {
t.Fatalf("unexpected remote address: %v", addr)
}
})
t.Run("ignore non-IP X-Forwarded-For", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "example.com")
header.Add("X-Trusted-CDN", "")
if addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr); addr != remoteAddr {
t.Fatalf("unexpected remote address: %v", addr)
}
})
} }
func TestHopByHopHeadersRemoving(t *testing.T) { func TestHopByHopHeadersRemoving(t *testing.T) {
+10
View File
@@ -26,6 +26,8 @@ const (
fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler
mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer
mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer
streamSettingsKey ctx.SessionKey = 13
) )
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context { func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
@@ -192,3 +194,11 @@ func MitmServerNameFromContext(ctx context.Context) string {
} }
return "" return ""
} }
func ContextWithStreamSettings(ctx context.Context, streamSettings any) context.Context {
return context.WithValue(ctx, streamSettingsKey, streamSettings)
}
func StreamSettingsFromContext(ctx context.Context) any {
return ctx.Value(streamSettingsKey)
}
+14
View File
@@ -1,6 +1,7 @@
package utils package utils
import ( import (
"maps"
"runtime" "runtime"
"sync" "sync"
"weak" "weak"
@@ -43,3 +44,16 @@ func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
} }
}, struct{}{}) }, struct{}{})
} }
func (c *WeakCacheMap[K, V]) Range(f func(K, *V) bool) {
c.mu.Lock()
snapshot := maps.Clone(c.m)
c.mu.Unlock()
for k, v := range snapshot {
if value := v.Value(); value != nil {
if !f(k, value) {
break
}
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ import (
var ( var (
Version_x byte = 26 Version_x byte = 26
Version_y byte = 6 Version_y byte = 6
Version_z byte = 1 Version_z byte = 22
) )
var ( var (
+12 -12
View File
@@ -4,7 +4,7 @@ go 1.26
require ( require (
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
github.com/cloudflare/circl v1.6.3 github.com/cloudflare/circl v1.6.4
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
@@ -12,7 +12,7 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 github.com/klauspost/cpuid/v2 v2.3.0
github.com/miekg/dns v1.1.72 github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pion/stun/v3 v3.1.2 github.com/pion/stun/v3 v3.1.6
github.com/pires/go-proxyproto v0.12.0 github.com/pires/go-proxyproto v0.12.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
@@ -22,11 +22,11 @@ require (
github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.51.0 golang.org/x/crypto v0.53.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.55.0 golang.org/x/net v0.56.0
golang.org/x/sync v0.20.0 golang.org/x/sync v0.21.0
golang.org/x/sys v0.45.0 golang.org/x/sys v0.46.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.0.1 golang.zx2c4.com/wireguard/windows v1.0.1
@@ -44,17 +44,17 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pion/dtls/v3 v3.1.2 // indirect github.com/pion/dtls/v3 v3.1.4 // indirect
github.com/pion/logging v0.2.4 // indirect github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect github.com/pion/transport/v4 v4.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
github.com/wlynxg/anet v0.0.5 // indirect github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/mod v0.35.0 // indirect golang.org/x/mod v0.36.0 // indirect
golang.org/x/text v0.37.0 // indirect golang.org/x/text v0.38.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.44.0 // indirect golang.org/x/tools v0.45.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
+24 -24
View File
@@ -4,8 +4,8 @@ github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkd
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA= github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -45,14 +45,14 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY=
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM= github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI= github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -98,22 +98,22 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
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.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -121,21 +121,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+12 -2
View File
@@ -1,14 +1,24 @@
package conf package conf
import ( import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/proxy/loopback" "github.com/xtls/xray-core/proxy/loopback"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type LoopbackConfig struct { type LoopbackConfig struct {
InboundTag string `json:"inboundTag"` InboundTag string `json:"inboundTag"`
Sniffing *SniffingConfig `json:"sniffing"`
} }
func (l LoopbackConfig) Build() (proto.Message, error) { func (l LoopbackConfig) Build() (proto.Message, error) {
return &loopback.Config{InboundTag: l.InboundTag}, nil c := &loopback.Config{InboundTag: l.InboundTag}
if l.Sniffing != nil {
sc, err := l.Sniffing.Build()
if err != nil {
return nil, errors.New("failed to build sniffing config").Base(err)
}
c.Sniffing = sc
}
return c, nil
} }
+19 -27
View File
@@ -179,26 +179,22 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
} }
type ShadowsocksServerTarget struct { type ShadowsocksServerTarget struct {
Address *Address `json:"address"` Address *Address `json:"address"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
Level byte `json:"level"` Level byte `json:"level"`
Email string `json:"email"` Email string `json:"email"`
Cipher string `json:"method"` Cipher string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
UoT bool `json:"uot"`
UoTVersion int `json:"uotVersion"`
} }
type ShadowsocksClientConfig struct { type ShadowsocksClientConfig struct {
Address *Address `json:"address"` Address *Address `json:"address"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
Level byte `json:"level"` Level byte `json:"level"`
Email string `json:"email"` Email string `json:"email"`
Cipher string `json:"method"` Cipher string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
UoT bool `json:"uot"` Servers []*ShadowsocksServerTarget `json:"servers"`
UoTVersion int `json:"uotVersion"`
Servers []*ShadowsocksServerTarget `json:"servers"`
} }
func (v *ShadowsocksClientConfig) Build() (proto.Message, error) { func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
@@ -207,14 +203,12 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
if v.Address != nil { if v.Address != nil {
v.Servers = []*ShadowsocksServerTarget{ v.Servers = []*ShadowsocksServerTarget{
{ {
Address: v.Address, Address: v.Address,
Port: v.Port, Port: v.Port,
Level: v.Level, Level: v.Level,
Email: v.Email, Email: v.Email,
Cipher: v.Cipher, Cipher: v.Cipher,
Password: v.Password, Password: v.Password,
UoT: v.UoT,
UoTVersion: v.UoTVersion,
}, },
} }
} }
@@ -240,8 +234,6 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
config.Port = uint32(server.Port) config.Port = uint32(server.Port)
config.Method = server.Cipher config.Method = server.Cipher
config.Key = server.Password config.Key = server.Password
config.UdpOverTcp = server.UoT
config.UdpOverTcpVersion = uint32(server.UoTVersion)
return config, nil return config, nil
} }
} }
+79 -24
View File
@@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"math" "math"
"math/big"
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
@@ -219,8 +220,10 @@ type SplitHTTPConfig struct {
XPaddingPlacement string `json:"xPaddingPlacement"` XPaddingPlacement string `json:"xPaddingPlacement"`
XPaddingMethod string `json:"xPaddingMethod"` XPaddingMethod string `json:"xPaddingMethod"`
UplinkHTTPMethod string `json:"uplinkHTTPMethod"` UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
SessionPlacement string `json:"sessionPlacement"` SessionIDPlacement string `json:"sessionIDPlacement"`
SessionKey string `json:"sessionKey"` SessionIDKey string `json:"sessionIDKey"`
SessionIDTable string `json:"sessionIDTable"`
SessionIDLength Int32Range `json:"sessionIDLength"`
SeqPlacement string `json:"seqPlacement"` SeqPlacement string `json:"seqPlacement"`
SeqKey string `json:"seqKey"` SeqKey string `json:"seqKey"`
UplinkDataPlacement string `json:"uplinkDataPlacement"` UplinkDataPlacement string `json:"uplinkDataPlacement"`
@@ -331,12 +334,12 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode") return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode")
} }
switch c.SessionPlacement { switch c.SessionIDPlacement {
case "": case "":
c.SessionPlacement = "path" c.SessionIDPlacement = "path"
case "path", "cookie", "header", "query": case "path", "cookie", "header", "query":
default: default:
return nil, errors.New("unsupported session placement: " + c.SessionPlacement) return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
} }
switch c.SeqPlacement { switch c.SeqPlacement {
@@ -347,12 +350,31 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement) return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
} }
if c.SessionPlacement != "path" && c.SessionKey == "" { if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
switch c.SessionPlacement { switch c.SessionIDPlacement {
case "cookie", "query": case "cookie", "query":
c.SessionKey = "x_session" c.SessionIDKey = "x_session"
case "header": case "header":
c.SessionKey = "X-Session" c.SessionIDKey = "X-Session"
}
}
if c.SessionIDTable != "" {
if predefined, ok := splithttp.PredefinedTable[c.SessionIDTable]; ok {
c.SessionIDTable = predefined
}
room := roomSize(len(c.SessionIDTable), c.SessionIDLength.From, c.SessionIDLength.To)
// 2.1B possiblities should be enough
if room.Cmp(big.NewInt(2<<30)) < 0 {
return nil, errors.New("sessionIDTable or sessionIDLength is too small")
}
if c.SessionIDLength.From <= 0 {
return nil, errors.New("sessionIDLength.from must be greater than 0")
}
for i := 0; i < len(c.SessionIDTable); i++ {
if c.SessionIDTable[i] >= 0x80 {
return nil, errors.New("sessionIDTable must contain only ASCII characters")
}
} }
} }
@@ -402,9 +424,9 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
XPaddingPlacement: c.XPaddingPlacement, XPaddingPlacement: c.XPaddingPlacement,
XPaddingMethod: c.XPaddingMethod, XPaddingMethod: c.XPaddingMethod,
UplinkHTTPMethod: c.UplinkHTTPMethod, UplinkHTTPMethod: c.UplinkHTTPMethod,
SessionPlacement: c.SessionPlacement, SessionIDPlacement: c.SessionIDPlacement,
SeqPlacement: c.SeqPlacement, SeqPlacement: c.SeqPlacement,
SessionKey: c.SessionKey, SessionIDKey: c.SessionIDKey,
SeqKey: c.SeqKey, SeqKey: c.SeqKey,
UplinkDataPlacement: c.UplinkDataPlacement, UplinkDataPlacement: c.UplinkDataPlacement,
UplinkDataKey: c.UplinkDataKey, UplinkDataKey: c.UplinkDataKey,
@@ -416,6 +438,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
ScMaxBufferedPosts: c.ScMaxBufferedPosts, ScMaxBufferedPosts: c.ScMaxBufferedPosts,
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs), ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes, ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
SessionIDTable: c.SessionIDTable,
SessionIDLength: newRangeConfig(c.SessionIDLength),
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),
@@ -439,6 +463,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return config, nil return config, nil
} }
func roomSize(tableSize int, min, max int32) *big.Int {
base := big.NewInt(int64(tableSize))
sum := new(big.Int)
term := new(big.Int)
for k := min; k <= max; k++ {
term.Exp(base, big.NewInt(int64(k)), nil)
sum.Add(sum, term)
}
return sum
}
const ( const (
Byte = 1 Byte = 1
Kilobyte = 1024 * Byte Kilobyte = 1024 * Byte
@@ -1374,10 +1409,12 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
} }
type FragmentMask struct { type FragmentMask struct {
Packets string `json:"packets"` Packets string `json:"packets"`
Length Int32Range `json:"length"` Length Int32Range `json:"length"`
Delay Int32Range `json:"delay"` Delay Int32Range `json:"delay"`
MaxSplit Int32Range `json:"maxSplit"` Lengths []Int32Range `json:"lengths"`
Delays []Int32Range `json:"delays"`
MaxSplit Int32Range `json:"maxSplit"`
} }
func (c *FragmentMask) Build() (proto.Message, error) { func (c *FragmentMask) Build() (proto.Message, error) {
@@ -1402,14 +1439,29 @@ func (c *FragmentMask) Build() (proto.Message, error) {
} }
} }
config.LengthMin = int64(c.Length.From) if len(c.Lengths) > 0 {
config.LengthMax = int64(c.Length.To) for _, r := range c.Lengths {
if config.LengthMin == 0 { config.LengthsMin = append(config.LengthsMin, int64(r.From))
return nil, errors.New("LengthMin can't be 0") config.LengthsMax = append(config.LengthsMax, int64(r.To))
}
} else {
config.LengthsMin = append(config.LengthsMin, int64(c.Length.From))
config.LengthsMax = append(config.LengthsMax, int64(c.Length.To))
} }
config.DelayMin = int64(c.Delay.From) if config.LengthsMin[len(config.LengthsMin)-1] == 0 {
config.DelayMax = int64(c.Delay.To) return nil, errors.New("last lengths entry min can't be 0")
}
if len(c.Delays) > 0 {
for _, r := range c.Delays {
config.DelaysMin = append(config.DelaysMin, int64(r.From))
config.DelaysMax = append(config.DelaysMax, int64(r.To))
}
} else {
config.DelaysMin = append(config.DelaysMin, int64(c.Delay.From))
config.DelaysMax = append(config.DelaysMax, int64(c.Delay.To))
}
config.MaxSplitMin = int64(c.MaxSplit.From) config.MaxSplitMin = int64(c.MaxSplit.From)
config.MaxSplitMax = int64(c.MaxSplit.To) config.MaxSplitMax = int64(c.MaxSplit.To)
@@ -1753,12 +1805,15 @@ func (c *MkcpLegacy) Build() (proto.Message, error) {
} }
type Salamander struct { type Salamander struct {
Password string `json:"password"` Password string `json:"password"`
PacketSize *Int32Range `json:"packetSize"` PacketSize Int32Range `json:"packetSize"`
} }
func (c *Salamander) Build() (proto.Message, error) { func (c *Salamander) Build() (proto.Message, error) {
if c.PacketSize != nil { if c.PacketSize.To > 0 {
if c.PacketSize.From <= 0 || c.PacketSize.To > 2048 {
return nil, errors.New("gecko: invalid min/max packet size")
}
return &salamander.GeckoConfig{ return &salamander.GeckoConfig{
Password: c.Password, Password: c.Password,
MinPacketSize: c.PacketSize.From, MinPacketSize: c.PacketSize.From,
+4 -6
View File
@@ -3,6 +3,7 @@ package conf
import ( import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"strconv"
"strings" "strings"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -37,8 +38,9 @@ func (c *WireGuardPeerConfig) Build() (proto.Message, error) {
} }
config.Endpoint = c.Endpoint config.Endpoint = c.Endpoint
// default 0 if c.KeepAlive != 0 {
config.KeepAlive = c.KeepAlive config.KeepAlive = strconv.FormatUint(uint64(c.KeepAlive), 10)
}
if c.AllowedIPs == nil { if c.AllowedIPs == nil {
config.AllowedIps = []string{"0.0.0.0/0", "::0/0"} config.AllowedIps = []string{"0.0.0.0/0", "::0/0"}
} else { } else {
@@ -56,7 +58,6 @@ type WireGuardConfig struct {
Address []string `json:"address"` Address []string `json:"address"`
Peers []*WireGuardPeerConfig `json:"peers"` Peers []*WireGuardPeerConfig `json:"peers"`
MTU int32 `json:"mtu"` MTU int32 `json:"mtu"`
NumWorkers int32 `json:"workers"`
Reserved []byte `json:"reserved"` Reserved []byte `json:"reserved"`
DomainStrategy string `json:"domainStrategy"` DomainStrategy string `json:"domainStrategy"`
} }
@@ -93,9 +94,6 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
} else { } else {
config.Mtu = c.MTU config.Mtu = c.MTU
} }
// these a fallback code exists in wireguard-go code,
// we don't need to process fallback manually
config.NumWorkers = c.NumWorkers
if len(c.Reserved) != 0 && len(c.Reserved) != 3 { if len(c.Reserved) != 0 && len(c.Reserved) != 3 {
return nil, errors.New(`"reserved" should be empty or 3 bytes`) return nil, errors.New(`"reserved" should be empty or 3 bytes`)
-2
View File
@@ -38,12 +38,10 @@ func TestWireGuardConfig(t *testing.T) {
// also can read from hex form directly // also can read from hex form directly
PublicKey: "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a", PublicKey: "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a",
Endpoint: "127.0.0.1:1234", Endpoint: "127.0.0.1:1234",
KeepAlive: 0,
AllowedIps: []string{"0.0.0.0/0", "::0/0"}, AllowedIps: []string{"0.0.0.0/0", "::0/0"},
}, },
}, },
Mtu: 1300, Mtu: 1300,
NumWorkers: 2,
DomainStrategy: wireguard.DeviceConfig_FORCE_IP64, DomainStrategy: wireguard.DeviceConfig_FORCE_IP64,
NoKernelTun: false, NoKernelTun: false,
}, },
-21
View File
@@ -173,27 +173,6 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
return nil, err return nil, err
} }
receiverSettings.StreamSettings = ss receiverSettings.StreamSettings = ss
// TODO: Actually implement this breaking change
protocol := ss.GetEffectiveProtocol()
if (protocol == "websocket" || protocol == "httpupgrade" || protocol == "splithttp") &&
(c.StreamSetting.SocketSettings == nil || len(c.StreamSetting.SocketSettings.TrustedXForwardedFor) == 0) {
errors.LogWarning(
context.Background(),
`====== SECURITY WARNING ======`,
"\n",
`inbound "`, c.Tag, `" using `, protocol, ` has not configured "sockopt.trustedXForwardedFor".`,
"\n",
`THIS IS VERY INSECURE!!!`,
"\n",
`For compatibility, Xray still allows this for now and still trusts X-Forwarded-For implicitly.`,
"\n",
`Please configure "sockopt.trustedXForwardedFor" immediately.`,
"\n",
`In future versions, this option must be explicitly set.`,
"\n",
`====== SECURITY WARNING ======`,
)
}
if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil || if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil ||
len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) { len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) {
errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`) errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`)
+10 -5
View File
@@ -29,6 +29,13 @@ type Client struct {
} }
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
v := core.MustFromContext(ctx)
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
if _, ok := streamSettings.ProtocolSettings.(*hysteria.Config); !ok {
return nil, errors.New("not hysteria transport")
}
if config.Server == nil { if config.Server == nil {
return nil, errors.New(`no target server found`) return nil, errors.New(`no target server found`)
} }
@@ -37,12 +44,10 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
return nil, errors.New("failed to get server spec").Base(err) return nil, errors.New("failed to get server spec").Base(err)
} }
v := core.MustFromContext(ctx) return &Client{
client := &Client{
server: server, server: server,
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), policyManager: p,
} }, nil
return client, nil
} }
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
+12 -6
View File
@@ -16,6 +16,7 @@ import (
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/proxy/hysteria/account" "github.com/xtls/xray-core/proxy/hysteria/account"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria" "github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
) )
@@ -27,6 +28,14 @@ type Server struct {
} }
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
v := core.MustFromContext(ctx)
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
if _, ok := streamSettings.ProtocolSettings.(*hysteria.Config); !ok {
return nil, errors.New("not hysteria transport")
}
validator := account.NewValidator() validator := account.NewValidator()
for _, user := range config.Users { for _, user := range config.Users {
u, err := user.ToMemoryUser() u, err := user.ToMemoryUser()
@@ -39,14 +48,11 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
} }
} }
v := core.MustFromContext(ctx) return &Server{
s := &Server{
config: config, config: config,
validator: validator, validator: validator,
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), policyManager: p,
} }, nil
return s, nil
} }
func (s *Server) HysteriaInboundValidator() *account.Validator { func (s *Server) HysteriaInboundValidator() *account.Validator {
+22 -10
View File
@@ -7,6 +7,7 @@
package loopback package loopback
import ( import (
proxyman "github.com/xtls/xray-core/app/proxyman"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@@ -22,8 +23,9 @@ const (
) )
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"` InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
Sniffing *proxyman.SniffingConfig `protobuf:"bytes,2,opt,name=sniffing,proto3" json:"sniffing,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -65,14 +67,22 @@ func (x *Config) GetInboundTag() string {
return "" return ""
} }
func (x *Config) GetSniffing() *proxyman.SniffingConfig {
if x != nil {
return x.Sniffing
}
return nil
}
var File_proxy_loopback_config_proto protoreflect.FileDescriptor var File_proxy_loopback_config_proto protoreflect.FileDescriptor
const file_proxy_loopback_config_proto_rawDesc = "" + const file_proxy_loopback_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\")\n" + "\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\x1a\x19app/proxyman/config.proto\"h\n" +
"\x06Config\x12\x1f\n" + "\x06Config\x12\x1f\n" +
"\vinbound_tag\x18\x01 \x01(\tR\n" + "\vinbound_tag\x18\x01 \x01(\tR\n" +
"inboundTagB[\n" + "inboundTag\x12=\n" +
"\bsniffing\x18\x02 \x01(\v2!.xray.app.proxyman.SniffingConfigR\bsniffingB[\n" +
"\x17com.xray.proxy.loopbackP\x01Z(github.com/xtls/xray-core/proxy/loopback\xaa\x02\x13Xray.Proxy.Loopbackb\x06proto3" "\x17com.xray.proxy.loopbackP\x01Z(github.com/xtls/xray-core/proxy/loopback\xaa\x02\x13Xray.Proxy.Loopbackb\x06proto3"
var ( var (
@@ -89,14 +99,16 @@ func file_proxy_loopback_config_proto_rawDescGZIP() []byte {
var file_proxy_loopback_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proxy_loopback_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_loopback_config_proto_goTypes = []any{ var file_proxy_loopback_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.proxy.loopback.Config (*Config)(nil), // 0: xray.proxy.loopback.Config
(*proxyman.SniffingConfig)(nil), // 1: xray.app.proxyman.SniffingConfig
} }
var file_proxy_loopback_config_proto_depIdxs = []int32{ var file_proxy_loopback_config_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type 1, // 0: xray.proxy.loopback.Config.sniffing:type_name -> xray.app.proxyman.SniffingConfig
0, // [0:0] is the sub-list for method input_type 1, // [1:1] is the sub-list for method output_type
0, // [0:0] is the sub-list for extension type_name 1, // [1:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension extendee 1, // [1:1] is the sub-list for extension type_name
0, // [0:0] is the sub-list for field type_name 1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
} }
func init() { file_proxy_loopback_config_proto_init() } func init() { file_proxy_loopback_config_proto_init() }
+3
View File
@@ -6,6 +6,9 @@ option go_package = "github.com/xtls/xray-core/proxy/loopback";
option java_package = "com.xray.proxy.loopback"; option java_package = "com.xray.proxy.loopback";
option java_multiple_files = true; option java_multiple_files = true;
import "app/proxyman/config.proto";
message Config { message Config {
string inbound_tag = 1; string inbound_tag = 1;
xray.app.proxyman.SniffingConfig sniffing = 2;
} }
+14 -5
View File
@@ -3,6 +3,7 @@ package loopback
import ( import (
"context" "context"
proxyman "github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
@@ -13,7 +14,8 @@ import (
) )
type Loopback struct { type Loopback struct {
config *Config inboundTag string
sniffingRequest session.SniffingRequest
dispatcherInstance routing.Dispatcher dispatcherInstance routing.Dispatcher
} }
@@ -29,6 +31,7 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
errors.LogInfo(ctx, "opening connection to ", destination) errors.LogInfo(ctx, "opening connection to ", destination)
content := new(session.Content) content := new(session.Content)
content.SkipDNSResolve = true content.SkipDNSResolve = true
content.SniffingRequest = l.sniffingRequest
ctx = session.ContextWithContent(ctx, content) ctx = session.ContextWithContent(ctx, content)
inbound := &session.Inbound{} inbound := &session.Inbound{}
@@ -37,20 +40,26 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
// get a shallow copy to avoid modifying the inbound tag in upstream context // get a shallow copy to avoid modifying the inbound tag in upstream context
*inbound = *originInbound *inbound = *originInbound
} }
inbound.Tag = l.config.InboundTag inbound.Tag = l.inboundTag
ctx = session.ContextWithInbound(ctx, inbound) ctx = session.ContextWithInbound(ctx, inbound)
err := l.dispatcherInstance.DispatchLink(ctx, destination, link) err := l.dispatcherInstance.DispatchLink(ctx, destination, link)
if err != nil { if err != nil {
errors.New(ctx, "failed to process loopback connection").Base(err) return errors.New(ctx, "failed to process loopback connection").Base(err)
return err
} }
return nil return nil
} }
func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error { func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error {
l.dispatcherInstance = dispatcherInstance l.dispatcherInstance = dispatcherInstance
l.config = config l.inboundTag = config.InboundTag
if config.Sniffing.GetEnabled() {
request, err := proxyman.BuildSniffingRequest(config.Sniffing)
if err != nil {
return errors.New("failed to build loopback sniffing request").Base(err).AtError()
}
l.sniffingRequest = request
}
return nil return nil
} }
+9 -28
View File
@@ -356,15 +356,13 @@ func (x *Account) GetKey() string {
} }
type ClientConfig struct { type ClientConfig struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"`
UdpOverTcp bool `protobuf:"varint,5,opt,name=udp_over_tcp,json=udpOverTcp,proto3" json:"udp_over_tcp,omitempty"` unknownFields protoimpl.UnknownFields
UdpOverTcpVersion uint32 `protobuf:"varint,6,opt,name=udp_over_tcp_version,json=udpOverTcpVersion,proto3" json:"udp_over_tcp_version,omitempty"` sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *ClientConfig) Reset() { func (x *ClientConfig) Reset() {
@@ -425,20 +423,6 @@ func (x *ClientConfig) GetKey() string {
return "" return ""
} }
func (x *ClientConfig) GetUdpOverTcp() bool {
if x != nil {
return x.UdpOverTcp
}
return false
}
func (x *ClientConfig) GetUdpOverTcpVersion() uint32 {
if x != nil {
return x.UdpOverTcpVersion
}
return 0
}
var File_proxy_shadowsocks_2022_config_proto protoreflect.FileDescriptor var File_proxy_shadowsocks_2022_config_proto protoreflect.FileDescriptor
const file_proxy_shadowsocks_2022_config_proto_rawDesc = "" + const file_proxy_shadowsocks_2022_config_proto_rawDesc = "" +
@@ -467,15 +451,12 @@ const file_proxy_shadowsocks_2022_config_proto_rawDesc = "" +
"\fdestinations\x18\x03 \x03(\v2-.xray.proxy.shadowsocks_2022.RelayDestinationR\fdestinations\x122\n" + "\fdestinations\x18\x03 \x03(\v2-.xray.proxy.shadowsocks_2022.RelayDestinationR\fdestinations\x122\n" +
"\anetwork\x18\x04 \x03(\x0e2\x18.xray.common.net.NetworkR\anetwork\"\x1b\n" + "\anetwork\x18\x04 \x03(\x0e2\x18.xray.common.net.NetworkR\anetwork\"\x1b\n" +
"\aAccount\x12\x10\n" + "\aAccount\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\"\xd6\x01\n" + "\x03key\x18\x01 \x01(\tR\x03key\"\x83\x01\n" +
"\fClientConfig\x125\n" + "\fClientConfig\x125\n" +
"\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" + "\aaddress\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\aaddress\x12\x12\n" +
"\x04port\x18\x02 \x01(\rR\x04port\x12\x16\n" + "\x04port\x18\x02 \x01(\rR\x04port\x12\x16\n" +
"\x06method\x18\x03 \x01(\tR\x06method\x12\x10\n" + "\x06method\x18\x03 \x01(\tR\x06method\x12\x10\n" +
"\x03key\x18\x04 \x01(\tR\x03key\x12 \n" + "\x03key\x18\x04 \x01(\tR\x03keyBr\n" +
"\fudp_over_tcp\x18\x05 \x01(\bR\n" +
"udpOverTcp\x12/\n" +
"\x14udp_over_tcp_version\x18\x06 \x01(\rR\x11udpOverTcpVersionBr\n" +
"\x1fcom.xray.proxy.shadowsocks_2022P\x01Z0github.com/xtls/xray-core/proxy/shadowsocks_2022\xaa\x02\x1aXray.Proxy.Shadowsocks2022b\x06proto3" "\x1fcom.xray.proxy.shadowsocks_2022P\x01Z0github.com/xtls/xray-core/proxy/shadowsocks_2022\xaa\x02\x1aXray.Proxy.Shadowsocks2022b\x06proto3"
var ( var (
-2
View File
@@ -49,6 +49,4 @@ message ClientConfig {
uint32 port = 2; uint32 port = 2;
string method = 3; string method = 3;
string key = 4; string key = 4;
bool udp_over_tcp = 5;
uint32 udp_over_tcp_version = 6;
} }
+6 -23
View File
@@ -10,7 +10,6 @@ import (
B "github.com/sagernet/sing/common/buf" B "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -29,10 +28,9 @@ func init() {
} }
type Outbound struct { type Outbound struct {
ctx context.Context ctx context.Context
server net.Destination server net.Destination
method shadowsocks.Method method shadowsocks.Method
uotClient *uot.Client
} }
func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) { func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) {
@@ -56,9 +54,6 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Outbound, error) {
} else { } else {
return nil, errors.New("unknown method ", config.Method) return nil, errors.New("unknown method ", config.Method)
} }
if config.UdpOverTcp {
o.uotClient = &uot.Client{Version: uint8(config.UdpOverTcpVersion)}
}
return o, nil return o, nil
} }
@@ -82,11 +77,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
errors.LogInfo(ctx, "tunneling request to ", destination, " via ", o.server.NetAddr()) errors.LogInfo(ctx, "tunneling request to ", destination, " via ", o.server.NetAddr())
serverDestination := o.server serverDestination := o.server
if o.uotClient != nil { serverDestination.Network = network
serverDestination.Network = net.Network_TCP
} else {
serverDestination.Network = network
}
connection, err := dialer.Dial(ctx, serverDestination) connection, err := dialer.Dial(ctx, serverDestination)
if err != nil { if err != nil {
return errors.New("failed to connect to server").Base(err) return errors.New("failed to connect to server").Base(err)
@@ -149,15 +140,7 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
} }
} }
if o.uotClient != nil { serverConn := o.method.DialPacketConn(connection)
uConn, err := o.uotClient.DialEarlyConn(o.method.DialEarlyConn(connection, uot.RequestDestination(o.uotClient.Version)), false, singbridge.ToSocksaddr(destination)) return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, serverConn))
if err != nil {
return err
}
return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, uConn))
} else {
serverConn := o.method.DialPacketConn(connection)
return singbridge.ReturnError(bufio.CopyPacketConn(ctx, packetConn, serverConn))
}
} }
} }
+4 -3
View File
@@ -209,11 +209,12 @@ func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer net.Co
} }
responsePort = net.Port(udpHub.LocalAddr().(*net.UDPAddr).Port) responsePort = net.Port(udpHub.LocalAddr().(*net.UDPAddr).Port)
expectedRemote := &gonet.UDPAddr{} expectedRemote := &gonet.UDPAddr{}
if request.Address.IP().IsUnspecified() { // UDP Associate should not specify a domain as source IP
if request.Address.Family().IsDomain() || request.Address.IP().IsUnspecified() {
expectedRemote.IP = writer.RemoteAddr().(*net.TCPAddr).IP // unix? expectedRemote.IP = writer.RemoteAddr().(*net.TCPAddr).IP // unix?
} else { } else {
expectedRemote.IP = request.Address.IP() // panic? expectedRemote.IP = request.Address.IP()
expectedRemote.Port = int(request.Port) // 0 is allowed expectedRemote.Port = int(request.Port) // 0 is allowed
} }
tempUDPConn = NewTempUDPConn(udpHub, writer, expectedRemote) tempUDPConn = NewTempUDPConn(udpHub, writer, expectedRemote)
} }
+13 -3
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
goerrors "errors" goerrors "errors"
"io" "io"
"sync"
"time" "time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -216,18 +217,27 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
defer udpServer.RemoveRay() defer udpServer.RemoveRay()
inbound := session.InboundFromContext(ctx) inbound := session.InboundFromContext(ctx)
if inbound != nil && inbound.Source.IsValid() {
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
}
var dest *net.Destination var dest *net.Destination
reader := buf.NewPacketReader(conn) reader := buf.NewPacketReader(conn)
var changeRemote sync.Once
for { for {
mpayload, err := reader.ReadMultiBuffer() mpayload, err := reader.ReadMultiBuffer()
if err != nil { if err != nil {
return err return err
} }
changeRemote.Do(func() {
if inbound != nil {
newInbound := *inbound
// change source to real remote UDP address
newInbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
newInbound.Local = net.DestinationFromAddr(conn.LocalAddr())
inbound = &newInbound
ctx = session.ContextWithInbound(ctx, inbound)
errors.LogInfo(ctx, "client UDP connection from ", inbound.Source)
}
})
for _, payload := range mpayload { for _, payload := range mpayload {
request, err := DecodeUDPPacket(payload) request, err := DecodeUDPPacket(payload)
+24 -5
View File
@@ -2,6 +2,8 @@ package tun
import ( import (
"context" "context"
"net/netip"
"strings"
"syscall" "syscall"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
@@ -40,10 +42,11 @@ type ConnectionHandler interface {
// Handler implements ConnectionHandler // Handler implements ConnectionHandler
var _ ConnectionHandler = (*Handler)(nil) var _ ConnectionHandler = (*Handler)(nil)
// Handler implements common.Runnable
var _ common.Runnable = (*Handler)(nil)
// Init the Handler instance with necessary parameters // Init the Handler instance with necessary parameters
func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error { func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {
var err error
// Retrieve tag and sniffing config from context (set by AlwaysOnInboundHandler) // Retrieve tag and sniffing config from context (set by AlwaysOnInboundHandler)
if inbound := session.InboundFromContext(ctx); inbound != nil { if inbound := session.InboundFromContext(ctx); inbound != nil {
t.tag = inbound.Tag t.tag = inbound.Tag
@@ -56,6 +59,10 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
t.policyManager = pm t.policyManager = pm
t.dispatcher = dispatcher t.dispatcher = dispatcher
return nil
}
func (t *Handler) Start() error {
tunName := t.config.Name tunName := t.config.Name
tunInterface, err := NewTun(t.config) tunInterface, err := NewTun(t.config)
if err != nil { if err != nil {
@@ -80,6 +87,11 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
return nil return nil
} }
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
addrPort, _ := netip.ParseAddrPort(address)
// skip loopback
if addrPort.Addr().IsLoopback() || strings.HasPrefix(strings.ToLower(address), "localhost:") {
return
}
err := setinterface(network, address, fd, iface) err := setinterface(network, address, fd, iface)
if err != nil { if err != nil {
errors.LogInfoInner(context.Background(), err, "[tun] falied to set interface") errors.LogInfoInner(context.Background(), err, "[tun] falied to set interface")
@@ -92,7 +104,7 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
tunStackOptions := StackOptions{ tunStackOptions := StackOptions{
Tun: tunInterface, Tun: tunInterface,
IdleTimeout: pm.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle, IdleTimeout: t.policyManager.ForLevel(t.config.UserLevel).Timeouts.ConnectionIdle,
} }
tunStack, err := NewStack(t.ctx, tunStackOptions, t) tunStack, err := NewStack(t.ctx, tunStackOptions, t)
if err != nil { if err != nil {
@@ -131,7 +143,14 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
defer cancel() defer cancel()
ctx = c.ContextWithID(ctx, session.NewID()) ctx = c.ContextWithID(ctx, session.NewID())
source := net.DestinationFromAddr(conn.RemoteAddr()) // if the connection is already closed, conn.RemoteAddr() will be nil
// due to gvisor weird behavior
remote := conn.RemoteAddr()
if remote == nil {
errors.LogInfo(t.ctx, "dropped quickly closed connection")
return
}
source := net.DestinationFromAddr(remote)
inbound := session.Inbound{ inbound := session.Inbound{
Name: "tun", Name: "tun",
Tag: t.tag, Tag: t.tag,
@@ -167,7 +186,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
// Close implements common.Closable. // Close implements common.Closable.
func (t *Handler) Close() error { func (t *Handler) Close() error {
return errors.Combine(t.stack.Close(), t.tun.Close()) return errors.Combine(common.CloseIfExists(t.stack), common.CloseIfExists(t.tun))
} }
// Network implements proxy.Inbound // Network implements proxy.Inbound
+73 -3
View File
@@ -4,8 +4,11 @@ package tun
import ( import (
"net" "net"
"strconv"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased" "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -18,6 +21,7 @@ type LinuxTun struct {
tunFd int tunFd int
tunLink netlink.Link tunLink netlink.Link
options *Config options *Config
ownsTun bool
} }
// LinuxTun implements Tun // LinuxTun implements Tun
@@ -25,12 +29,24 @@ var _ Tun = (*LinuxTun)(nil)
// NewTun builds new tun interface handler (linux specific) // NewTun builds new tun interface handler (linux specific)
func NewTun(options *Config) (Tun, error) { func NewTun(options *Config) (Tun, error) {
tunFd, err := open(options.Name) tunFd, tunLink, fdProvided, err := openFromEnv(options.Name)
if err != nil {
return nil, err
}
if fdProvided {
return &LinuxTun{
tunFd: tunFd,
tunLink: tunLink,
options: options,
}, nil
}
tunFd, err = open(options.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tunLink, err := setup(options.Name, int(options.MTU)) tunLink, err = setup(options.Name, int(options.MTU))
if err != nil { if err != nil {
_ = unix.Close(tunFd) _ = unix.Close(tunFd)
return nil, err return nil, err
@@ -40,11 +56,59 @@ func NewTun(options *Config) (Tun, error) {
tunFd: tunFd, tunFd: tunFd,
tunLink: tunLink, tunLink: tunLink,
options: options, options: options,
ownsTun: true,
} }
return linuxTun, nil return linuxTun, nil
} }
func openFromEnv(expectedName string) (int, netlink.Link, bool, error) {
fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" })
if fdStr == "" {
return -1, nil, false, nil
}
fd, err := strconv.Atoi(fdStr)
if err != nil {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey).Base(err)
}
if fd < 3 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor must be >= 3")
}
ifr, err := unix.NewIfreq("")
if err != nil {
return -1, nil, true, err
}
if err = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {
return -1, nil, true, err
}
flags := ifr.Uint16()
if flags&unix.IFF_TUN == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor is not a TUN device")
}
if flags&unix.IFF_NO_PI == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device must use IFF_NO_PI")
}
actualName := ifr.Name()
if expectedName != "" && actualName != expectedName {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device name ", actualName, " does not match configured name ", expectedName)
}
tunLink, err := netlink.LinkByName(actualName)
if err != nil {
return -1, nil, true, err
}
if err = unix.SetNonblock(fd, true); err != nil {
return -1, nil, true, err
}
return fd, tunLink, true, nil
}
// open the file that implements tun interface in the OS // open the file that implements tun interface in the OS
func open(name string) (int, error) { func open(name string) (int, error) {
fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0) fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
@@ -93,6 +157,10 @@ func setup(name string, MTU int) (netlink.Link, error) {
// Start is called by handler to bring tun interface to life // Start is called by handler to bring tun interface to life
func (t *LinuxTun) Start() error { func (t *LinuxTun) Start() error {
if !t.ownsTun {
return nil
}
err := netlink.LinkSetUp(t.tunLink) err := netlink.LinkSetUp(t.tunLink)
if err != nil { if err != nil {
return err return err
@@ -103,7 +171,9 @@ func (t *LinuxTun) Start() error {
// Close is called to shut down the tun interface // Close is called to shut down the tun interface
func (t *LinuxTun) Close() error { func (t *LinuxTun) Close() error {
_ = netlink.LinkSetDown(t.tunLink) if t.ownsTun {
_ = netlink.LinkSetDown(t.tunLink)
}
_ = unix.Close(t.tunFd) _ = unix.Close(t.tunFd)
return nil return nil
+19 -11
View File
@@ -98,18 +98,22 @@ type udpConn struct {
} }
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
e, ok := <-c.egress for {
if !ok { e, ok := <-c.egress
return nil, io.EOF if !ok {
} return nil, io.EOF
}
b := buf.New() b := buf.New()
if _, err := b.Write(e.data); err != nil { if _, err := b.Write(e.data); err != nil {
return nil, err errors.LogErrorInner(context.Background(), err, "drop packet to ", e.dest, " with size ", len(e.data))
} b.Release()
b.UDP = e.dest continue
}
b.UDP = e.dest
return buf.MultiBuffer{b}, nil return buf.MultiBuffer{b}, nil
}
} }
// Read packets from the connection // Read packets from the connection
@@ -129,7 +133,11 @@ func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb { for i, b := range mb {
dst := c.dst dst := c.dst
if b.UDP != nil { if b.UDP != nil {
dst = *b.UDP if b.UDP.Address.Family().IsDomain() {
errors.LogError(context.Background(), "impossible domain packet ", b.UDP, " reply via original target ", dst)
} else {
dst = *b.UDP
}
} }
err := c.handler.writePacket(b.Bytes(), dst, c.src) err := c.handler.writePacket(b.Bytes(), dst, c.src)
if err != nil { if err != nil {
+122 -237
View File
@@ -2,265 +2,150 @@ package wireguard
import ( import (
"context" "context"
gonet "net" goerrors "errors"
"io"
"net"
"net/netip" "net/netip"
"runtime"
"strconv" "strconv"
"sync"
"syscall"
"golang.zx2c4.com/wireguard/conn" "github.com/xtls/xray-core/common"
"golang.zx2c4.com/wireguard/device"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "golang.zx2c4.com/wireguard/conn"
"github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/transport/internet"
) )
type netReadInfo struct { type bind struct {
buff *buf.Buffer resolveFunc func(host string) (net.IP, error)
endpoint conn.Endpoint listenFunc func() (net.PacketConn, error)
downFunc func() error
reserved []byte
net.PacketConn
closeCh chan struct{}
mu sync.Mutex
} }
// reduce duplicated code func (b *bind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
type netBind struct { b.mu.Lock()
dns dns.Client defer b.mu.Unlock()
dnsOption dns.IPOption
workers int if b.PacketConn != nil {
readQueue chan *netReadInfo return nil, 0, conn.ErrBindAlreadyOpen
closedCh chan struct{} }
c, err := b.listenFunc()
if err != nil {
return nil, 0, err
}
b.PacketConn = c
ch := make(chan struct{})
b.closeCh = ch
return []conn.ReceiveFunc{
func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
for {
n, addr, err := c.ReadFrom(bufs[0])
if err != nil {
if goerrors.Is(err, io.EOF) || goerrors.Is(err, io.ErrClosedPipe) || goerrors.Is(err, net.ErrClosed) {
select {
case <-ch:
default:
errors.LogErrorInner(context.Background(), err, "unexpected closed")
if b.downFunc != nil {
go func() {
common.Must(b.downFunc())
}()
}
}
return 0, net.ErrClosed
}
errors.LogErrorInner(context.Background(), err, "bind recv err")
continue
}
if n > 3 {
bufs[0][1] = 0
bufs[0][2] = 0
bufs[0][3] = 0
}
sizes[0] = n
eps[0] = &conn.StdNetEndpoint{AddrPort: addr.(*net.UDPAddr).AddrPort()}
return 1, nil
}
},
}, uint16(c.LocalAddr().(*net.UDPAddr).Port), nil
} }
// SetMark implements conn.Bind func (b *bind) Close() error {
func (bind *netBind) SetMark(mark uint32) error { b.mu.Lock()
defer b.mu.Unlock()
if b.PacketConn != nil {
close(b.closeCh)
_ = b.PacketConn.Close()
b.PacketConn = nil
}
return nil return nil
} }
// ParseEndpoint implements conn.Bind func (b *bind) SetMark(mark uint32) error {
func (n *netBind) ParseEndpoint(s string) (conn.Endpoint, error) { return nil
ipStr, port, err := net.SplitHostPort(s) }
if err != nil {
return nil, err func (b *bind) Send(bufs [][]byte, ep conn.Endpoint) (err error) {
} b.mu.Lock()
portNum, err := strconv.Atoi(port) c := b.PacketConn
if err != nil { b.mu.Unlock()
return nil, err
if c == nil {
return syscall.EAFNOSUPPORT
} }
addr := net.ParseAddress(ipStr) for i := range bufs {
if addr.Family() == net.AddressFamilyDomain { if len(bufs[i]) > 3 && len(b.reserved) == 3 {
ips, _, err := n.dns.LookupIP(addr.Domain(), n.dnsOption) bufs[i][1] = b.reserved[0]
bufs[i][2] = b.reserved[1]
bufs[i][3] = b.reserved[2]
}
_, err = c.WriteTo(bufs[i], net.UDPAddrFromAddrPort(ep.(*conn.StdNetEndpoint).AddrPort))
if err != nil { if err != nil {
return nil, err errors.LogErrorInner(context.Background(), err, "bind send err")
} else if len(ips) == 0 { break
return nil, dns.ErrEmptyResponse
}
addr = net.IPAddress(ips[0])
}
dst := net.Destination{
Address: addr,
Port: net.Port(portNum),
Network: net.Network_UDP,
}
return &netEndpoint{
dst: dst,
}, nil
}
// BatchSize implements conn.Bind
func (bind *netBind) BatchSize() int {
return 1
}
// Open implements conn.Bind
func (bind *netBind) Open(uport uint16) ([]conn.ReceiveFunc, uint16, error) {
bind.closedCh = make(chan struct{})
errors.LogDebug(context.Background(), "bind opened")
fun := func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
select {
case r := <-bind.readQueue:
sizes[0], eps[0] = copy(bufs[0], r.buff.Bytes()), r.endpoint
r.buff.Release()
return 1, nil
case <-bind.closedCh:
errors.LogDebug(context.Background(), "recv func closed")
return 0, gonet.ErrClosed
} }
} }
workers := bind.workers
if workers <= 0 {
workers = runtime.NumCPU()
}
if workers <= 0 {
workers = 1
}
arr := make([]conn.ReceiveFunc, workers)
for i := 0; i < workers; i++ {
arr[i] = fun
}
return arr, uint16(uport), nil
}
// Close implements conn.Bind
func (bind *netBind) Close() error {
errors.LogDebug(context.Background(), "bind closed")
if bind.closedCh != nil {
close(bind.closedCh)
}
return nil
}
type netBindClient struct {
netBind
ctx context.Context
dialer internet.Dialer
reserved []byte
}
func (bind *netBindClient) connectTo(endpoint *netEndpoint) error {
c, err := bind.dialer.Dial(bind.ctx, endpoint.dst)
if err != nil {
return err
}
endpoint.conn = c
go func() {
for {
buff := buf.NewWithSize(device.MaxMessageSize)
n, err := buff.ReadFrom(c)
if err != nil {
buff.Release()
endpoint.conn = nil
c.Close()
return
}
rawBytes := buff.Bytes()
if n > 3 {
rawBytes[1] = 0
rawBytes[2] = 0
rawBytes[3] = 0
}
select {
case bind.readQueue <- &netReadInfo{
buff: buff,
endpoint: endpoint,
}:
case <-bind.closedCh:
buff.Release()
endpoint.conn = nil
c.Close()
return
}
}
}()
return nil
}
func (bind *netBindClient) Send(buff [][]byte, endpoint conn.Endpoint) error {
var err error
nend, ok := endpoint.(*netEndpoint)
if !ok {
return conn.ErrWrongEndpointType
}
if nend.conn == nil {
err = bind.connectTo(nend)
if err != nil {
return err
}
}
for _, buff := range buff {
if len(buff) > 3 && len(bind.reserved) == 3 {
copy(buff[1:], bind.reserved)
}
if _, err = nend.conn.Write(buff); err != nil {
return err
}
}
return nil
}
type netBindServer struct {
netBind
}
func (bind *netBindServer) Send(buff [][]byte, endpoint conn.Endpoint) error {
var err error
nend, ok := endpoint.(*netEndpoint)
if !ok {
return conn.ErrWrongEndpointType
}
if nend.conn == nil {
errors.LogDebug(context.Background(), nend.dst.NetAddr(), " send on closed peer")
return errors.New("peer closed")
}
for _, buff := range buff {
if _, err = nend.conn.Write(buff); err != nil {
return err
}
}
return err return err
} }
type netEndpoint struct { func (b *bind) ParseEndpoint(s string) (conn.Endpoint, error) {
dst net.Destination if b.resolveFunc == nil {
conn net.Conn e, err := netip.ParseAddrPort(s)
} if err != nil {
return nil, err
func (netEndpoint) ClearSrc() {}
func (e netEndpoint) DstIP() netip.Addr {
return netip.Addr{}
}
func (e netEndpoint) SrcIP() netip.Addr {
return netip.Addr{}
}
func (e netEndpoint) DstToBytes() []byte {
var dat []byte
if e.dst.Address.Family().IsIPv4() {
dat = e.dst.Address.IP().To4()[:]
} else {
dat = e.dst.Address.IP().To16()[:]
}
dat = append(dat, byte(e.dst.Port), byte(e.dst.Port>>8))
return dat
}
func (e netEndpoint) DstToString() string {
return e.dst.NetAddr()
}
func (e netEndpoint) SrcToString() string {
return ""
}
func toNetIpAddr(addr net.Address) netip.Addr {
if addr.Family().IsIPv4() {
ip := addr.IP()
return netip.AddrFrom4([4]byte{ip[0], ip[1], ip[2], ip[3]})
} else {
ip := addr.IP()
arr := [16]byte{}
for i := 0; i < 16; i++ {
arr[i] = ip[i]
} }
return netip.AddrFrom16(arr) return &conn.StdNetEndpoint{
AddrPort: e,
}, nil
} }
host, sport, err := net.SplitHostPort(s)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(sport)
if err != nil {
return nil, err
}
if port < 0 || port > 65535 {
return nil, errors.New("invalid port " + sport)
}
ip, err := b.resolveFunc(host)
if err != nil {
return nil, err
}
addr, _ := netip.AddrFromSlice(ip)
return &conn.StdNetEndpoint{
AddrPort: netip.AddrPortFrom(addr, uint16(port)),
}, nil
}
func (b *bind) BatchSize() int {
return 1
} }
+351 -250
View File
@@ -1,148 +1,135 @@
/*
Some of codes are copied from https://github.com/octeep/wireproxy, license below.
Copyright (c) 2022 Wind T.F. Wong <octeep@pm.me>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package wireguard package wireguard
import ( import (
"context" "context"
"fmt" "fmt"
gonet "net"
"net/netip" "net/netip"
reflect "reflect"
"strings" "strings"
"sync" "sync"
"golang.zx2c4.com/wireguard/tun"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/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/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/task" "github.com/xtls/xray-core/common/task"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"golang.zx2c4.com/wireguard/device"
) )
// Handler is an outbound connection that silently swallow the entire payload.
type Handler struct { type Handler struct {
conf *DeviceConfig conf *DeviceConfig
net Tunnel
bind *netBindClient
policyManager policy.Manager policyManager policy.Manager
dns dns.Client dns dns.Client
// cached configuration
endpoints []netip.Addr streamSettings *internet.MemoryStreamConfig
hasIPv4, hasIPv6 bool uplinkCounter stats.Counter
wgLock sync.Mutex downlinkCounter stats.Counter
tun tun.Device
tnet *Net
dev *device.Device
mu sync.Mutex
} }
// New creates a new wireguard handler. func NewClient(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
func New(ctx context.Context, conf *DeviceConfig) (*Handler, error) {
v := core.MustFromContext(ctx) v := core.MustFromContext(ctx)
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
d := v.GetFeature(dns.ClientType()).(dns.Client)
endpoints, hasIPv4, hasIPv6, err := parseEndpoints(conf) streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
tag := session.FullHandlerFromContext(ctx).Tag()
var uplinkCounter stats.Counter
var downlinkCounter stats.Counter
if len(tag) > 0 && p.ForSystem().Stats.OutboundUplink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
if c != nil {
uplinkCounter = c
}
}
if len(tag) > 0 && p.ForSystem().Stats.OutboundDownlink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
if c != nil {
downlinkCounter = c
}
}
if len(conf.Peers) == 0 {
return nil, errors.New("empty peers")
}
for _, peer := range conf.Peers {
if peer.PublicKey == "" {
return nil, errors.New("peer without publickey")
}
if peer.Endpoint == "" {
return nil, errors.New("peer without endpoint")
}
}
localAddresses := make([]netip.Addr, 0, len(conf.Endpoint))
for _, localaddress := range conf.Endpoint {
addr, err := netip.ParseAddr(localaddress)
if err == nil {
localAddresses = append(localAddresses, addr)
continue
}
prefix, err := netip.ParsePrefix(localaddress)
if err == nil {
localAddresses = append(localAddresses, prefix.Addr())
continue
}
return nil, err
}
kernelTunSupported, err := KernelTunSupported()
if err != nil {
errors.LogWarningInner(context.Background(), err, "Failed to check kernel TUN support")
}
var tun tun.Device
var tnet *Net
if !conf.NoKernelTun && kernelTunSupported {
errors.LogWarning(context.Background(), "Using kernel TUN")
tun, tnet, err = createKernelTun(localAddresses, []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1.0.0.1"), netip.MustParseAddr("2606:4700:4700::1111"), netip.MustParseAddr("2606:4700:4700::1001")}, int(conf.Mtu))
} else {
errors.LogWarning(context.Background(), "Using gVisor TUN")
tun, tnet, _, err = CreateNetTUN(localAddresses, []netip.Addr{netip.MustParseAddr("1.1.1.1"), netip.MustParseAddr("1.0.0.1"), netip.MustParseAddr("2606:4700:4700::1111"), netip.MustParseAddr("2606:4700:4700::1001")}, int(conf.Mtu), true)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
d := v.GetFeature(dns.ClientType()).(dns.Client)
return &Handler{ return &Handler{
conf: conf, conf: conf,
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager), policyManager: p,
dns: d, dns: d,
endpoints: endpoints,
hasIPv4: hasIPv4, streamSettings: streamSettings,
hasIPv6: hasIPv6, uplinkCounter: uplinkCounter,
downlinkCounter: downlinkCounter,
tun: tun,
tnet: tnet,
}, nil }, nil
} }
func (h *Handler) Close() (err error) { // Process implements proxy.Outbound.Process.
go func() {
h.wgLock.Lock()
defer h.wgLock.Unlock()
if h.net != nil {
_ = h.net.Close()
h.net = nil
}
}()
return nil
}
func (h *Handler) processWireGuard(ctx context.Context, dialer internet.Dialer) (err error) {
h.wgLock.Lock()
defer h.wgLock.Unlock()
if h.bind != nil && h.bind.dialer == dialer && h.net != nil {
return nil
}
log.Record(&log.GeneralMessage{
Severity: log.Severity_Info,
Content: "switching dialer",
})
if h.net != nil {
_ = h.net.Close()
h.net = nil
}
if h.bind != nil {
_ = h.bind.Close()
h.bind = nil
}
// bind := conn.NewStdNetBind() // TODO: conn.Bind wrapper for dialer
h.bind = &netBindClient{
netBind: netBind{
dns: h.dns,
dnsOption: dns.IPOption{
IPv4Enable: h.hasIPv4,
IPv6Enable: h.hasIPv6,
},
workers: int(h.conf.NumWorkers),
readQueue: make(chan *netReadInfo),
},
ctx: ctx,
dialer: dialer,
reserved: h.conf.Reserved,
}
defer func() {
if err != nil {
h.bind.Close()
h.bind = nil
}
}()
h.net, err = h.makeVirtualTun()
if err != nil {
return errors.New("failed to create virtual tun interface").Base(err)
}
return nil
}
// Process implements OutboundHandler.Dispatch().
func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error { func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
@@ -152,40 +139,31 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
ob.Name = "wireguard" ob.Name = "wireguard"
ob.CanSpliceCopy = 3 ob.CanSpliceCopy = 3
if err := h.processWireGuard(ctx, dialer); err != nil { if h.dev == nil {
if err := h.init(ctx); err != nil {
return err
}
}
if err := h.dev.Up(); err != nil {
return err return err
} }
// Destination of the inner request. var addr netip.Addr
destination := ob.Target if ob.Target.Address.Family().IsDomain() {
command := protocol.RequestCommandTCP ip, err := h.resolveRemote(ob.Target.Address.String())
if destination.Network == net.Network_UDP { if err != nil {
command = protocol.RequestCommandUDP return errors.New("failed to resolve domain").Base(err)
}
addr, _ = netip.AddrFromSlice(ip)
} else {
addr, _ = netip.AddrFromSlice(ob.Target.Address.IP())
} }
// resolve dns addrPort := netip.AddrPortFrom(addr, ob.Target.Port.Value())
addr := destination.Address if !addrPort.IsValid() {
if addr.Family().IsDomain() { return errors.New("invalid target ", ob.Target)
ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.hasIPv4 && h.conf.preferIP4(),
IPv6Enable: h.hasIPv6 && h.conf.preferIP6(),
})
{ // Resolve fallback
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.hasIPv4 && h.conf.fallbackIP4(),
IPv6Enable: h.hasIPv6 && h.conf.fallbackIP6(),
})
}
}
if err != nil {
return errors.New("failed to lookup DNS").Base(err)
} else if len(ips) == 0 {
return dns.ErrEmptyResponse
}
addr = net.IPAddress(ips[dice.Roll(len(ips))])
} }
destination.Address = addr
var newCtx context.Context var newCtx context.Context
var newCancel context.CancelFunc var newCancel context.CancelFunc
@@ -193,59 +171,64 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
newCtx, newCancel = context.WithCancel(context.Background()) newCtx, newCancel = context.WithCancel(context.Background())
} }
p := h.policyManager.ForLevel(0) sessionPolicy := h.policyManager.ForLevel(0)
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, func() { timer := signal.CancelAfterInactivity(ctx, func() {
cancel() cancel()
if newCancel != nil { if newCancel != nil {
newCancel() newCancel()
} }
}, p.Timeouts.ConnectionIdle) }, sessionPolicy.Timeouts.ConnectionIdle)
addrPort := netip.AddrPortFrom(toNetIpAddr(addr), destination.Port.Value())
var requestFunc func() error if newCtx != nil {
var responseFunc func() error ctx = newCtx
}
if command == protocol.RequestCommandTCP { var reader buf.Reader
conn, err := h.net.DialContextTCPAddrPort(ctx, addrPort) var writer buf.Writer
switch ob.Target.Network {
case net.Network_TCP:
var conn net.Conn
var err error
if sessionPolicy.Timeouts.Handshake != 0 {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, sessionPolicy.Timeouts.Handshake)
conn, err = h.tnet.DialContextTCPAddrPort(timeoutCtx, addrPort)
timeoutCancel()
} else {
conn, err = h.tnet.DialContextTCPAddrPort(ctx, addrPort)
}
if err != nil { if err != nil {
return errors.New("failed to create TCP connection").Base(err) return errors.New("failed to create TCP connection").Base(err)
} }
defer conn.Close() defer conn.Close()
reader = buf.NewReader(conn)
requestFunc = func() error { writer = buf.NewWriter(conn)
defer timer.SetTimeout(p.Timeouts.DownlinkOnly) case net.Network_UDP:
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)) conn, err := h.tnet.DialUDPAddrPort(netip.AddrPort{}, addrPort)
}
responseFunc = func() error {
defer timer.SetTimeout(p.Timeouts.UplinkOnly)
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
}
} else if command == protocol.RequestCommandUDP {
conn, err := h.net.DialUDPAddrPort(netip.AddrPort{}, addrPort)
if err != nil { if err != nil {
return errors.New("failed to create UDP connection").Base(err) return errors.New("failed to create UDP connection").Base(err)
} }
defer conn.Close() defer conn.Close()
c := &udpConnClient{
conn = &udpConnClient{ PacketConn: conn.(*internet.PacketConnWrapper).PacketConn,
Conn: conn, resolveFunc: h.resolveRemote,
dest: destination, dest: gonet.UDPAddrFromAddrPort(addrPort),
}
requestFunc = func() error {
defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
}
responseFunc = func() error {
defer timer.SetTimeout(p.Timeouts.UplinkOnly)
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
} }
reader = c
writer = c
default:
panic(ob.Target.Network)
} }
if newCtx != nil { requestFunc := func() error {
ctx = newCtx defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, writer, buf.UpdateActivity(timer))
}
responseFunc := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
} }
responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer)) responseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))
@@ -258,108 +241,191 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
return nil return nil
} }
// creates a tun interface on netstack given a configuration func (h *Handler) Close() (err error) {
func (h *Handler) makeVirtualTun() (Tunnel, error) { h.mu.Lock()
t, err := h.conf.createTun()(h.endpoints, int(h.conf.Mtu), nil) defer h.mu.Unlock()
if h.dev != nil {
h.dev.Close()
h.dev = nil
h.tun = nil
} else if h.tun != nil {
h.tun.Close()
h.tun = nil
}
return nil
}
func (h *Handler) init(ctx context.Context) error {
h.mu.Lock()
defer h.mu.Unlock()
if h.dev != nil {
return nil
}
resolveFunc := h.resolveLocal
listenFunc := func() (net.PacketConn, error) {
dest, err := net.ParseDestination("udp:" + h.conf.Peers[0].Endpoint)
if err != nil {
return nil, err
}
conn, err := internet.DialSystem(ctx, dest, h.streamSettings.SocketSettings)
if err != nil {
return nil, err
}
var pktConn net.PacketConn
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
if h.streamSettings.UdpmaskManager != nil {
newConn, err := h.streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
if h.uplinkCounter != nil || h.downlinkCounter != nil {
pktConn = &PacketCounterConnection{
PacketConn: pktConn,
ReadCounter: h.downlinkCounter,
WriteCounter: h.uplinkCounter,
}
}
return pktConn, nil
}
bind := &bind{}
logger := &device.Logger{
Verbosef: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Debug,
Content: fmt.Sprintf(format, args...),
})
},
Errorf: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Error,
Content: fmt.Sprintf(format, args...),
})
},
}
dev := device.NewDevice(h.tun, bind, logger)
bind.resolveFunc = resolveFunc
bind.listenFunc = listenFunc
bind.downFunc = dev.Down
bind.reserved = h.conf.Reserved
var cfg strings.Builder
cfg.WriteString("private_key=" + h.conf.SecretKey + "\n")
for _, peer := range h.conf.Peers {
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
if peer.PreSharedKey != "" {
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
}
cfg.WriteString("endpoint=" + peer.Endpoint + "\n")
for _, ip := range peer.AllowedIps {
cfg.WriteString("allowed_ip=" + ip + "\n")
}
if peer.KeepAlive != "" {
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
}
}
err := dev.IpcSet(cfg.String())
if err != nil {
return err
}
err = dev.Up()
if err != nil {
return err
}
h.dev = dev
return nil
}
func (h *Handler) resolveLocal(host string) (net.IP, error) {
return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) {
ips, _, err := h.dns.LookupIP(host, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
return ips, err
})
}
func (h *Handler) resolveRemote(host string) (net.IP, error) {
return resolveDomain(host, h.conf.DomainStrategy, func(host string) ([]net.IP, error) {
addrs, err := h.tnet.LookupHost(host)
if err != nil {
return nil, err
}
ips := make([]net.IP, 0, len(addrs))
for _, addr := range addrs {
ips = append(ips, net.ParseIP(addr))
}
return ips, nil
})
}
func resolveDomain(host string, strategy DeviceConfig_DomainStrategy, lookupIP func(host string) ([]net.IP, error)) (net.IP, error) {
if ip := net.ParseIP(host); ip != nil {
return ip, nil
}
ips, err := lookupIP(host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(ips) == 0 {
h.bind.dnsOption.IPv4Enable = h.hasIPv4 return nil, dns.ErrEmptyResponse
h.bind.dnsOption.IPv6Enable = h.hasIPv6
if err = t.BuildDevice(h.createIPCRequest(), h.bind); err != nil {
_ = t.Close()
return nil, err
} }
return t, nil var got4, got6 []net.IP
} for _, ip := range ips {
if ip.To4() != nil {
// serialize the config into an IPC request got4 = append(got4, ip)
func (h *Handler) createIPCRequest() string { } else {
var request strings.Builder got6 = append(got6, ip)
request.WriteString(fmt.Sprintf("private_key=%s\n", h.conf.SecretKey))
if !h.conf.IsClient {
// placeholder, we'll handle actual port listening on Xray
request.WriteString("listen_port=1337\n")
}
for _, peer := range h.conf.Peers {
if peer.PublicKey != "" {
request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
}
if peer.PreSharedKey != "" {
request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey))
}
address, port, err := net.SplitHostPort(peer.Endpoint)
if err != nil {
errors.LogError(h.bind.ctx, "failed to split endpoint ", peer.Endpoint, " into address and port")
}
addr := net.ParseAddress(address)
if addr.Family().IsDomain() {
dialerIp := h.bind.dialer.DestIpAddress()
if dialerIp != nil {
addr = net.ParseAddress(dialerIp.String())
errors.LogInfo(h.bind.ctx, "createIPCRequest use dialer dest ip: ", addr)
} else {
ips, _, err := h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.conf.preferIP4(),
IPv6Enable: h.conf.preferIP6(),
})
{ // Resolve fallback
if (len(ips) == 0 || err != nil) && h.conf.hasFallback() {
ips, _, err = h.dns.LookupIP(addr.Domain(), dns.IPOption{
IPv4Enable: h.conf.fallbackIP4(),
IPv6Enable: h.conf.fallbackIP6(),
})
}
}
if err != nil {
errors.LogInfoInner(h.bind.ctx, err, "createIPCRequest failed to lookup DNS")
} else if len(ips) == 0 {
errors.LogInfo(h.bind.ctx, "createIPCRequest empty lookup DNS")
} else {
addr = net.IPAddress(ips[dice.Roll(len(ips))])
}
}
}
if peer.Endpoint != "" {
request.WriteString(fmt.Sprintf("endpoint=%s:%s\n", addr, port))
}
for _, ip := range peer.AllowedIps {
request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip))
}
if peer.KeepAlive != 0 {
request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive))
} }
} }
var got []net.IP
return request.String()[:request.Len()] switch strategy {
case DeviceConfig_FORCE_IP:
got = ips
return ips[dice.Roll(len(ips))], nil
case DeviceConfig_FORCE_IP4:
got = got4
case DeviceConfig_FORCE_IP6:
got = got6
case DeviceConfig_FORCE_IP46:
got = got4
if len(got) == 0 {
got = got6
}
case DeviceConfig_FORCE_IP64:
got = got6
if len(got) == 0 {
got = got4
}
default:
panic(strategy)
}
if len(got) == 0 {
return nil, dns.ErrEmptyResponse
}
return got[dice.Roll(len(got))], nil
} }
type udpConnClient struct { type udpConnClient struct {
net.Conn net.PacketConn
dest net.Destination resolveFunc func(host string) (net.IP, error)
dest *net.UDPAddr
} }
func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) { func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New() b := buf.New()
b.Resize(0, buf.Size) b.Resize(0, buf.Size)
n, addr, err := c.Conn.(net.PacketConn).ReadFrom(b.Bytes()) n, addr, err := c.PacketConn.ReadFrom(b.Bytes())
if err != nil { if err != nil {
b.Release() b.Release()
return nil, err return nil, err
} }
if addr == nil { // should never hit
addr = c.dest.RawNetAddr()
}
b.Resize(0, int32(n)) b.Resize(0, int32(n))
b.UDP = &net.Destination{ b.UDP = &net.Destination{
@@ -375,9 +441,22 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb { for i, b := range mb {
dst := c.dest dst := c.dest
if b.UDP != nil { if b.UDP != nil {
dst = *b.UDP if b.UDP.Address.Family().IsDomain() {
ip, err := c.resolveFunc(b.UDP.Address.String())
if err != nil {
errors.LogErrorInner(context.Background(), err, "drop packet to ", b.UDP, " with size ", len(b.Bytes()))
b.Release()
continue
}
dst = &net.UDPAddr{
IP: ip,
Port: int(b.UDP.Port),
}
} else {
dst = b.UDP.RawNetAddr().(*net.UDPAddr)
}
} }
_, err := c.Conn.(net.PacketConn).WriteTo(b.Bytes(), dst.RawNetAddr()) _, err := c.PacketConn.WriteTo(b.Bytes(), dst)
if err != nil { if err != nil {
buf.ReleaseMulti(mb[i:]) buf.ReleaseMulti(mb[i:])
return err return err
@@ -386,3 +465,25 @@ func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
} }
return nil return nil
} }
type PacketCounterConnection struct {
net.PacketConn
ReadCounter stats.Counter
WriteCounter stats.Counter
}
func (c *PacketCounterConnection) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p)
if err == nil && c.ReadCounter != nil {
c.ReadCounter.Add(int64(n))
}
return
}
func (c *PacketCounterConnection) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr)
if err == nil && c.WriteCounter != nil {
c.WriteCounter.Add(int64(n))
}
return
}
-53
View File
@@ -1,54 +1 @@
package wireguard package wireguard
import (
"context"
"github.com/xtls/xray-core/common/errors"
)
func (c *DeviceConfig) preferIP4() bool {
return c.DomainStrategy == DeviceConfig_FORCE_IP ||
c.DomainStrategy == DeviceConfig_FORCE_IP4 ||
c.DomainStrategy == DeviceConfig_FORCE_IP46
}
func (c *DeviceConfig) preferIP6() bool {
return c.DomainStrategy == DeviceConfig_FORCE_IP ||
c.DomainStrategy == DeviceConfig_FORCE_IP6 ||
c.DomainStrategy == DeviceConfig_FORCE_IP64
}
func (c *DeviceConfig) hasFallback() bool {
return c.DomainStrategy == DeviceConfig_FORCE_IP46 || c.DomainStrategy == DeviceConfig_FORCE_IP64
}
func (c *DeviceConfig) fallbackIP4() bool {
return c.DomainStrategy == DeviceConfig_FORCE_IP64
}
func (c *DeviceConfig) fallbackIP6() bool {
return c.DomainStrategy == DeviceConfig_FORCE_IP46
}
func (c *DeviceConfig) createTun() tunCreator {
if !c.IsClient {
// See tun_linux.go createKernelTun()
errors.LogWarning(context.Background(), "Using gVisor TUN. WG inbound doesn't support kernel TUN yet.")
return createGVisorTun
}
if c.NoKernelTun {
errors.LogWarning(context.Background(), "Using gVisor TUN. NoKernelTun is set to true.")
return createGVisorTun
}
kernelTunSupported, err := KernelTunSupported()
if err != nil {
errors.LogWarning(context.Background(), "Using gVisor TUN. Failed to check kernel TUN support:", err)
return createGVisorTun
}
if !kernelTunSupported {
errors.LogWarning(context.Background(), "Using gVisor TUN. Kernel TUN is not supported on your OS, or your permission is insufficient.")
return createGVisorTun
}
errors.LogWarning(context.Background(), "Using kernel TUN.")
return createKernelTun
}
+6 -16
View File
@@ -81,7 +81,7 @@ type PeerConfig struct {
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
PreSharedKey string `protobuf:"bytes,2,opt,name=pre_shared_key,json=preSharedKey,proto3" json:"pre_shared_key,omitempty"` PreSharedKey string `protobuf:"bytes,2,opt,name=pre_shared_key,json=preSharedKey,proto3" json:"pre_shared_key,omitempty"`
Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"` Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
KeepAlive uint32 `protobuf:"varint,4,opt,name=keep_alive,json=keepAlive,proto3" json:"keep_alive,omitempty"` KeepAlive string `protobuf:"bytes,4,opt,name=keep_alive,json=keepAlive,proto3" json:"keep_alive,omitempty"`
AllowedIps []string `protobuf:"bytes,5,rep,name=allowed_ips,json=allowedIps,proto3" json:"allowed_ips,omitempty"` AllowedIps []string `protobuf:"bytes,5,rep,name=allowed_ips,json=allowedIps,proto3" json:"allowed_ips,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -138,11 +138,11 @@ func (x *PeerConfig) GetEndpoint() string {
return "" return ""
} }
func (x *PeerConfig) GetKeepAlive() uint32 { func (x *PeerConfig) GetKeepAlive() string {
if x != nil { if x != nil {
return x.KeepAlive return x.KeepAlive
} }
return 0 return ""
} }
func (x *PeerConfig) GetAllowedIps() []string { func (x *PeerConfig) GetAllowedIps() []string {
@@ -158,7 +158,6 @@ type DeviceConfig struct {
Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"` Endpoint []string `protobuf:"bytes,2,rep,name=endpoint,proto3" json:"endpoint,omitempty"`
Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"` Peers []*PeerConfig `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"` Mtu int32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
NumWorkers int32 `protobuf:"varint,5,opt,name=num_workers,json=numWorkers,proto3" json:"num_workers,omitempty"`
Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"` Reserved []byte `protobuf:"bytes,6,opt,name=reserved,proto3" json:"reserved,omitempty"`
DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"` DomainStrategy DeviceConfig_DomainStrategy `protobuf:"varint,7,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.proxy.wireguard.DeviceConfig_DomainStrategy" json:"domain_strategy,omitempty"`
IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"` IsClient bool `protobuf:"varint,8,opt,name=is_client,json=isClient,proto3" json:"is_client,omitempty"`
@@ -225,13 +224,6 @@ func (x *DeviceConfig) GetMtu() int32 {
return 0 return 0
} }
func (x *DeviceConfig) GetNumWorkers() int32 {
if x != nil {
return x.NumWorkers
}
return 0
}
func (x *DeviceConfig) GetReserved() []byte { func (x *DeviceConfig) GetReserved() []byte {
if x != nil { if x != nil {
return x.Reserved return x.Reserved
@@ -272,17 +264,15 @@ const file_proxy_wireguard_config_proto_rawDesc = "" +
"\x0epre_shared_key\x18\x02 \x01(\tR\fpreSharedKey\x12\x1a\n" + "\x0epre_shared_key\x18\x02 \x01(\tR\fpreSharedKey\x12\x1a\n" +
"\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\x1d\n" + "\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\x1d\n" +
"\n" + "\n" +
"keep_alive\x18\x04 \x01(\rR\tkeepAlive\x12\x1f\n" + "keep_alive\x18\x04 \x01(\tR\tkeepAlive\x12\x1f\n" +
"\vallowed_ips\x18\x05 \x03(\tR\n" + "\vallowed_ips\x18\x05 \x03(\tR\n" +
"allowedIps\"\xcb\x03\n" + "allowedIps\"\xaa\x03\n" +
"\fDeviceConfig\x12\x1d\n" + "\fDeviceConfig\x12\x1d\n" +
"\n" + "\n" +
"secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" + "secret_key\x18\x01 \x01(\tR\tsecretKey\x12\x1a\n" +
"\bendpoint\x18\x02 \x03(\tR\bendpoint\x126\n" + "\bendpoint\x18\x02 \x03(\tR\bendpoint\x126\n" +
"\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x12\x10\n" + "\x05peers\x18\x03 \x03(\v2 .xray.proxy.wireguard.PeerConfigR\x05peers\x12\x10\n" +
"\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1f\n" + "\x03mtu\x18\x04 \x01(\x05R\x03mtu\x12\x1a\n" +
"\vnum_workers\x18\x05 \x01(\x05R\n" +
"numWorkers\x12\x1a\n" +
"\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" + "\breserved\x18\x06 \x01(\fR\breserved\x12Z\n" +
"\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" + "\x0fdomain_strategy\x18\a \x01(\x0e21.xray.proxy.wireguard.DeviceConfig.DomainStrategyR\x0edomainStrategy\x12\x1b\n" +
"\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" + "\tis_client\x18\b \x01(\bR\bisClient\x12\"\n" +
+2 -2
View File
@@ -10,7 +10,7 @@ message PeerConfig {
string public_key = 1; string public_key = 1;
string pre_shared_key = 2; string pre_shared_key = 2;
string endpoint = 3; string endpoint = 3;
uint32 keep_alive = 4; string keep_alive = 4;
repeated string allowed_ips = 5; repeated string allowed_ips = 5;
} }
@@ -26,7 +26,7 @@ message DeviceConfig {
repeated string endpoint = 2; repeated string endpoint = 2;
repeated PeerConfig peers = 3; repeated PeerConfig peers = 3;
int32 mtu = 4; int32 mtu = 4;
int32 num_workers = 5;
bytes reserved = 6; bytes reserved = 6;
DomainStrategy domain_strategy = 7; DomainStrategy domain_strategy = 7;
bool is_client = 8; bool is_client = 8;
-226
View File
@@ -1,226 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2022 WireGuard LLC. All Rights Reserved.
*/
package gvisortun
import (
"context"
"fmt"
"net/netip"
"os"
"sync"
"syscall"
"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
type netTun struct {
ep *channel.Endpoint
stack *stack.Stack
events chan tun.Event
notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View
mtu int
hasV4, hasV6 bool
closeOnce sync.Once
}
type Net netTun
func CreateNetTUN(localAddresses []netip.Addr, mtu int, promiscuousMode bool) (tun.Device, *Net, *stack.Stack, error) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
HandleLocal: !promiscuousMode,
}
dev := &netTun{
ep: channel.New(1024, uint32(mtu), ""),
stack: stack.New(opts),
events: make(chan tun.Event, 10),
incomingPacket: make(chan *buffer.View),
mtu: mtu,
}
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
if tcpipErr != nil {
return nil, nil, dev.stack, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
}
dev.notifyHandle = dev.ep.AddNotify(dev)
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
if tcpipErr != nil {
return nil, nil, dev.stack, fmt.Errorf("CreateNIC: %v", tcpipErr)
}
for _, ip := range localAddresses {
var protoNumber tcpip.NetworkProtocolNumber
if ip.Is4() {
protoNumber = ipv4.ProtocolNumber
} else if ip.Is6() {
protoNumber = ipv6.ProtocolNumber
}
protoAddr := tcpip.ProtocolAddress{
Protocol: protoNumber,
AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(),
}
tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{})
if tcpipErr != nil {
return nil, nil, dev.stack, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr)
}
if ip.Is4() {
dev.hasV4 = true
} else if ip.Is6() {
dev.hasV6 = true
}
}
if dev.hasV4 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
}
if dev.hasV6 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
}
if promiscuousMode {
// enable promiscuous mode to handle all packets processed by netstack
dev.stack.SetPromiscuousMode(1, true)
dev.stack.SetSpoofing(1, true)
}
dev.events <- tun.EventUp
return dev, (*Net)(dev), dev.stack, nil
}
// Name implements tun.Device
func (tun *netTun) Name() (string, error) {
return "go", nil
}
// File implements tun.Device
func (tun *netTun) File() *os.File {
return nil
}
// Events implements tun.Device
func (tun *netTun) Events() <-chan tun.Event {
return tun.events
}
// Read implements tun.Device
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
view, ok := <-tun.incomingPacket
if !ok {
return 0, os.ErrClosed
}
n, err := view.Read(buf[0][offset:])
if err != nil {
return 0, err
}
sizes[0] = n
return 1, nil
}
// Write implements tun.Device
func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
for _, buf := range buf {
packet := buf[offset:]
if len(packet) == 0 {
continue
}
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
switch packet[0] >> 4 {
case 4:
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
case 6:
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
default:
return 0, syscall.EAFNOSUPPORT
}
}
return len(buf), nil
}
// WriteNotify implements channel.Notification
func (tun *netTun) WriteNotify() {
pkt := tun.ep.Read()
if pkt == nil {
return
}
view := pkt.ToView()
pkt.DecRef()
tun.incomingPacket <- view
}
// Close implements tun.Device
func (tun *netTun) Close() error {
tun.closeOnce.Do(func() {
tun.stack.RemoveNIC(1)
tun.stack.Close()
tun.ep.RemoveNotify(tun.notifyHandle)
tun.ep.Close()
close(tun.events)
close(tun.incomingPacket)
})
return nil
}
// MTU implements tun.Device
func (tun *netTun) MTU() (int, error) {
return tun.mtu, nil
}
// BatchSize implements tun.Device
func (tun *netTun) BatchSize() int {
return 1
}
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
var protoNumber tcpip.NetworkProtocolNumber
if endpoint.Addr().Is4() {
protoNumber = ipv4.ProtocolNumber
} else {
protoNumber = ipv6.ProtocolNumber
}
return tcpip.FullAddress{
NIC: 1,
Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()),
Port: endpoint.Port(),
}, protoNumber
}
func (net *Net) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (*gonet.TCPConn, error) {
fa, pn := convertToFullAddr(addr)
return gonet.DialContextTCP(ctx, net.stack, fa, pn)
}
func (net *Net) DialUDPAddrPort(laddr, raddr netip.AddrPort) (*gonet.UDPConn, error) {
var lfa, rfa *tcpip.FullAddress
var pn tcpip.NetworkProtocolNumber
if laddr.IsValid() || laddr.Port() > 0 {
var addr tcpip.FullAddress
addr, pn = convertToFullAddr(laddr)
lfa = &addr
}
if raddr.IsValid() || raddr.Port() > 0 {
var addr tcpip.FullAddress
addr, pn = convertToFullAddr(raddr)
rfa = &addr
rfa = nil // do not ep connect
}
return gonet.DialUDP(net.stack, lfa, rfa, pn)
}
+690
View File
@@ -0,0 +1,690 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
*/
package wireguard
import (
"context"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/netip"
"os"
"strings"
"syscall"
"time"
"github.com/xtls/xray-core/transport/internet"
"golang.zx2c4.com/wireguard/tun"
"golang.org/x/net/dns/dnsmessage"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
type netTun struct {
ep *channel.Endpoint
stack *stack.Stack
events chan tun.Event
notifyHandle *channel.NotificationHandle
incomingPacket chan *buffer.View
mtu int
dnsServers []netip.Addr
hasV4, hasV6 bool
}
func CreateNetTUN(localAddresses, dnsServers []netip.Addr, mtu int, handleLocal bool) (tun.Device, *Net, *stack.Stack, error) {
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
HandleLocal: handleLocal,
}
dev := &netTun{
ep: channel.New(1024, uint32(mtu), ""),
stack: stack.New(opts),
events: make(chan tun.Event, 10),
incomingPacket: make(chan *buffer.View),
dnsServers: dnsServers,
mtu: mtu,
}
sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
if tcpipErr != nil {
return nil, nil, nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
}
dev.notifyHandle = dev.ep.AddNotify(dev)
tcpipErr = dev.stack.CreateNIC(1, dev.ep)
if tcpipErr != nil {
return nil, nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
}
for _, ip := range localAddresses {
var protoNumber tcpip.NetworkProtocolNumber
if ip.Is4() {
protoNumber = ipv4.ProtocolNumber
} else if ip.Is6() {
protoNumber = ipv6.ProtocolNumber
}
protoAddr := tcpip.ProtocolAddress{
Protocol: protoNumber,
AddressWithPrefix: tcpip.AddrFromSlice(ip.AsSlice()).WithPrefix(),
}
tcpipErr := dev.stack.AddProtocolAddress(1, protoAddr, stack.AddressProperties{})
if tcpipErr != nil {
return nil, nil, nil, fmt.Errorf("AddProtocolAddress(%v): %v", ip, tcpipErr)
}
if ip.Is4() {
dev.hasV4 = true
} else if ip.Is6() {
dev.hasV6 = true
}
}
if dev.hasV4 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
}
if dev.hasV6 {
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
}
tnet := &Net{
DialContextTCPAddrPort: dev.DialContextTCPAddrPort,
DialUDPAddrPort: dev.DialUDPAddrPort,
dnsServers: dev.dnsServers,
hasV4: dev.hasV4,
hasV6: dev.hasV6,
}
dev.events <- tun.EventUp
return dev, tnet, dev.stack, nil
}
func (tun *netTun) Name() (string, error) {
return "go", nil
}
func (tun *netTun) File() *os.File {
return nil
}
func (tun *netTun) Events() <-chan tun.Event {
return tun.events
}
func (tun *netTun) Read(buf [][]byte, sizes []int, offset int) (int, error) {
view, ok := <-tun.incomingPacket
if !ok {
return 0, os.ErrClosed
}
n, err := view.Read(buf[0][offset:])
if err != nil {
return 0, err
}
sizes[0] = n
return 1, nil
}
func (tun *netTun) Write(buf [][]byte, offset int) (int, error) {
for _, buf := range buf {
packet := buf[offset:]
if len(packet) == 0 {
continue
}
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buffer.MakeWithData(packet)})
switch packet[0] >> 4 {
case 4:
tun.ep.InjectInbound(header.IPv4ProtocolNumber, pkb)
case 6:
tun.ep.InjectInbound(header.IPv6ProtocolNumber, pkb)
default:
return 0, syscall.EAFNOSUPPORT
}
}
return len(buf), nil
}
func (tun *netTun) WriteNotify() {
pkt := tun.ep.Read()
if pkt == nil {
return
}
view := pkt.ToView()
pkt.DecRef()
tun.incomingPacket <- view
}
func (tun *netTun) Close() error {
tun.stack.RemoveNIC(1)
tun.stack.Close()
tun.ep.RemoveNotify(tun.notifyHandle)
tun.ep.Close()
if tun.events != nil {
close(tun.events)
}
if tun.incomingPacket != nil {
close(tun.incomingPacket)
}
return nil
}
func (tun *netTun) MTU() (int, error) {
return tun.mtu, nil
}
func (tun *netTun) BatchSize() int {
return 1
}
func (tun *netTun) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
fa, pn := convertToFullAddr(addr)
return gonet.DialContextTCP(ctx, tun.stack, fa, pn)
}
func (tun *netTun) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
var pn tcpip.NetworkProtocolNumber = ipv6.ProtocolNumber
if raddr.IsValid() || raddr.Port() > 0 {
_, pn = convertToFullAddr(raddr)
}
conn, err := gonet.DialUDP(tun.stack, nil, nil, pn)
if err != nil {
return nil, err
}
return &internet.PacketConnWrapper{
PacketConn: conn,
Dest: net.UDPAddrFromAddrPort(raddr),
}, nil
}
type Net struct {
DialContextTCPAddrPort func(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
DialUDPAddrPort func(laddr, raddr netip.AddrPort) (net.Conn, error)
dnsServers []netip.Addr
hasV4, hasV6 bool
}
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
var protoNumber tcpip.NetworkProtocolNumber
if endpoint.Addr().Is4() {
protoNumber = ipv4.ProtocolNumber
} else {
protoNumber = ipv6.ProtocolNumber
}
return tcpip.FullAddress{
NIC: 1,
Addr: tcpip.AddrFromSlice(endpoint.Addr().AsSlice()),
Port: endpoint.Port(),
}, protoNumber
}
var (
errNoSuchHost = errors.New("no such host")
errLameReferral = errors.New("lame referral")
errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message")
errCannotMarshalDNSMessage = errors.New("cannot marshal DNS message")
errServerMisbehaving = errors.New("server misbehaving")
errInvalidDNSResponse = errors.New("invalid DNS response")
errNoAnswerFromDNSServer = errors.New("no answer from DNS server")
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
errCanceled = errors.New("operation was canceled")
errTimeout = errors.New("i/o timeout")
)
func (net *Net) LookupHost(host string) (addrs []string, err error) {
return net.LookupContextHost(context.Background(), host)
}
func isDomainName(s string) bool {
l := len(s)
if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
return false
}
last := byte('.')
nonNumeric := false
partlen := 0
for i := 0; i < len(s); i++ {
c := s[i]
switch {
default:
return false
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
nonNumeric = true
partlen++
case '0' <= c && c <= '9':
partlen++
case c == '-':
if last == '.' {
return false
}
partlen++
nonNumeric = true
case c == '.':
if last == '.' || last == '-' {
return false
}
if partlen > 63 || partlen == 0 {
return false
}
partlen = 0
}
last = c
}
if last == '-' || partlen > 63 {
return false
}
return nonNumeric
}
func randU16() uint16 {
var b [2]byte
_, err := rand.Read(b[:])
if err != nil {
panic(err)
}
return binary.LittleEndian.Uint16(b[:])
}
func newRequest(q dnsmessage.Question) (id uint16, udpReq, tcpReq []byte, err error) {
id = randU16()
b := dnsmessage.NewBuilder(make([]byte, 2, 514), dnsmessage.Header{ID: id, RecursionDesired: true})
b.EnableCompression()
if err := b.StartQuestions(); err != nil {
return 0, nil, nil, err
}
if err := b.Question(q); err != nil {
return 0, nil, nil, err
}
tcpReq, err = b.Finish()
udpReq = tcpReq[2:]
l := len(tcpReq) - 2
tcpReq[0] = byte(l >> 8)
tcpReq[1] = byte(l)
return id, udpReq, tcpReq, err
}
func equalASCIIName(x, y dnsmessage.Name) bool {
if x.Length != y.Length {
return false
}
for i := 0; i < int(x.Length); i++ {
a := x.Data[i]
b := y.Data[i]
if 'A' <= a && a <= 'Z' {
a += 0x20
}
if 'A' <= b && b <= 'Z' {
b += 0x20
}
if a != b {
return false
}
}
return true
}
func checkResponse(reqID uint16, reqQues dnsmessage.Question, respHdr dnsmessage.Header, respQues dnsmessage.Question) bool {
if !respHdr.Response {
return false
}
if reqID != respHdr.ID {
return false
}
if reqQues.Type != respQues.Type || reqQues.Class != respQues.Class || !equalASCIIName(reqQues.Name, respQues.Name) {
return false
}
return true
}
func dnsPacketRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
if _, err := c.Write(b); err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
b = make([]byte, 512)
for {
n, err := c.Read(b)
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
var p dnsmessage.Parser
h, err := p.Start(b[:n])
if err != nil {
continue
}
q, err := p.Question()
if err != nil || !checkResponse(id, query, h, q) {
continue
}
return p, h, nil
}
}
func dnsStreamRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
if _, err := c.Write(b); err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
b = make([]byte, 1280)
if _, err := io.ReadFull(c, b[:2]); err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
l := int(b[0])<<8 | int(b[1])
if l > len(b) {
b = make([]byte, l)
}
n, err := io.ReadFull(c, b[:l])
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
var p dnsmessage.Parser
h, err := p.Start(b[:n])
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
}
q, err := p.Question()
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
}
if !checkResponse(id, query, h, q) {
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
}
return p, h, nil
}
func (tnet *Net) exchange(ctx context.Context, server netip.Addr, q dnsmessage.Question, timeout time.Duration) (dnsmessage.Parser, dnsmessage.Header, error) {
q.Class = dnsmessage.ClassINET
id, udpReq, tcpReq, err := newRequest(q)
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage
}
for _, useUDP := range []bool{true, false} {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
var c net.Conn
var err error
if useUDP {
c, err = tnet.DialUDPAddrPort(netip.AddrPort{}, netip.AddrPortFrom(server, 53))
} else {
c, err = tnet.DialContextTCPAddrPort(ctx, netip.AddrPortFrom(server, 53))
}
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
if d, ok := ctx.Deadline(); ok && !d.IsZero() {
err := c.SetDeadline(d)
if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
}
var p dnsmessage.Parser
var h dnsmessage.Header
if useUDP {
p, h, err = dnsPacketRoundTrip(c, id, q, udpReq)
} else {
p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq)
}
c.Close()
if err != nil {
if err == context.Canceled {
err = errCanceled
} else if err == context.DeadlineExceeded {
err = errTimeout
}
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone {
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
}
if h.Truncated {
continue
}
return p, h, nil
}
return dnsmessage.Parser{}, dnsmessage.Header{}, errNoAnswerFromDNSServer
}
func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error {
if h.RCode == dnsmessage.RCodeNameError {
return errNoSuchHost
}
_, err := p.AnswerHeader()
if err != nil && err != dnsmessage.ErrSectionDone {
return errCannotUnmarshalDNSMessage
}
if h.RCode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone {
return errLameReferral
}
if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError {
if h.RCode == dnsmessage.RCodeServerFailure {
return errServerTemporarilyMisbehaving
}
return errServerMisbehaving
}
return nil
}
func skipToAnswer(p *dnsmessage.Parser, qtype dnsmessage.Type) error {
for {
h, err := p.AnswerHeader()
if err == dnsmessage.ErrSectionDone {
return errNoSuchHost
}
if err != nil {
return errCannotUnmarshalDNSMessage
}
if h.Type == qtype {
return nil
}
if err := p.SkipAnswer(); err != nil {
return errCannotUnmarshalDNSMessage
}
}
}
func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, error) {
var lastErr error
n, err := dnsmessage.NewName(name)
if err != nil {
return dnsmessage.Parser{}, "", errCannotMarshalDNSMessage
}
q := dnsmessage.Question{
Name: n,
Type: qtype,
Class: dnsmessage.ClassINET,
}
for i := 0; i < 2; i++ {
for _, server := range tnet.dnsServers {
p, h, err := tnet.exchange(ctx, server, q, time.Second*5)
if err != nil {
dnsErr := &net.DNSError{
Err: err.Error(),
Name: name,
Server: server.String(),
}
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
dnsErr.IsTimeout = true
}
if _, ok := err.(*net.OpError); ok {
dnsErr.IsTemporary = true
}
lastErr = dnsErr
continue
}
if err := checkHeader(&p, h); err != nil {
dnsErr := &net.DNSError{
Err: err.Error(),
Name: name,
Server: server.String(),
}
if err == errServerTemporarilyMisbehaving {
dnsErr.IsTemporary = true
}
if err == errNoSuchHost {
dnsErr.IsNotFound = true
return p, server.String(), dnsErr
}
lastErr = dnsErr
continue
}
err = skipToAnswer(&p, qtype)
if err == nil {
return p, server.String(), nil
}
lastErr = &net.DNSError{
Err: err.Error(),
Name: name,
Server: server.String(),
}
if err == errNoSuchHost {
lastErr.(*net.DNSError).IsNotFound = true
return p, server.String(), lastErr
}
}
}
return dnsmessage.Parser{}, "", lastErr
}
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
zlen := len(host)
if strings.IndexByte(host, ':') != -1 {
if zidx := strings.LastIndexByte(host, '%'); zidx != -1 {
zlen = zidx
}
}
if ip, err := netip.ParseAddr(host[:zlen]); err == nil {
return []string{ip.String()}, nil
}
if !isDomainName(host) {
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
}
type result struct {
p dnsmessage.Parser
server string
error
}
var addrsV4, addrsV6 []netip.Addr
lanes := 0
if tnet.hasV4 {
lanes++
}
if tnet.hasV6 {
lanes++
}
lane := make(chan result, lanes)
var lastErr error
if tnet.hasV4 {
go func() {
p, server, err := tnet.tryOneName(ctx, host+".", dnsmessage.TypeA)
lane <- result{p, server, err}
}()
}
if tnet.hasV6 {
go func() {
p, server, err := tnet.tryOneName(ctx, host+".", dnsmessage.TypeAAAA)
lane <- result{p, server, err}
}()
}
for l := 0; l < lanes; l++ {
result := <-lane
if result.error != nil {
if lastErr == nil {
lastErr = result.error
}
continue
}
loop:
for {
h, err := result.p.AnswerHeader()
if err != nil && err != dnsmessage.ErrSectionDone {
lastErr = &net.DNSError{
Err: errCannotMarshalDNSMessage.Error(),
Name: host,
Server: result.server,
}
}
if err != nil {
break
}
switch h.Type {
case dnsmessage.TypeA:
a, err := result.p.AResource()
if err != nil {
lastErr = &net.DNSError{
Err: errCannotMarshalDNSMessage.Error(),
Name: host,
Server: result.server,
}
break loop
}
addrsV4 = append(addrsV4, netip.AddrFrom4(a.A))
case dnsmessage.TypeAAAA:
aaaa, err := result.p.AAAAResource()
if err != nil {
lastErr = &net.DNSError{
Err: errCannotMarshalDNSMessage.Error(),
Name: host,
Server: result.server,
}
break loop
}
addrsV6 = append(addrsV6, netip.AddrFrom16(aaaa.AAAA))
default:
if err := result.p.SkipAnswer(); err != nil {
lastErr = &net.DNSError{
Err: errCannotMarshalDNSMessage.Error(),
Name: host,
Server: result.server,
}
break loop
}
continue
}
}
}
// We don't do RFC6724. Instead just put V6 addresses first if an IPv6 address is enabled
var addrs []netip.Addr
if tnet.hasV6 {
addrs = append(addrsV6, addrsV4...)
} else {
addrs = append(addrsV4, addrsV6...)
}
if len(addrs) == 0 && lastErr != nil {
return nil, lastErr
}
saddrs := make([]string, 0, len(addrs))
for _, ip := range addrs {
saddrs = append(saddrs, ip.String())
}
return saddrs, nil
}
+200 -112
View File
@@ -2,6 +2,10 @@ package wireguard
import ( import (
"context" "context"
"fmt"
"net/netip"
"strings"
"sync"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
c "github.com/xtls/xray-core/common/ctx" c "github.com/xtls/xray-core/common/ctx"
@@ -10,162 +14,246 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/tcpip/stack"
) )
var nullDestination = net.TCPDestination(net.AnyIP, 0)
type Server struct { type Server struct {
bindServer *netBindServer conf *DeviceConfig
ctx context.Context
info routingInfo
policyManager policy.Manager policyManager policy.Manager
} dispatcher routing.Dispatcher
type routingInfo struct { tag string
ctx context.Context src net.Destination
dispatcher routing.Dispatcher sniffingRequest session.SniffingRequest
inboundTag *session.Inbound streamSettings *internet.MemoryStreamConfig
contentTag *session.Content uplinkCounter stats.Counter
downlinkCounter stats.Counter
tun tun.Device
stack *stack.Stack
dev *device.Device
mu sync.Mutex
} }
func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) { func NewServer(ctx context.Context, conf *DeviceConfig) (*Server, error) {
v := core.MustFromContext(ctx) v := core.MustFromContext(ctx)
p := v.GetFeature(policy.ManagerType()).(policy.Manager)
d := v.GetFeature(routing.DispatcherType()).(routing.Dispatcher)
endpoints, hasIPv4, hasIPv6, err := parseEndpoints(conf) inbound := session.InboundFromContext(ctx)
content := session.ContentFromContext(ctx)
streamSettings := session.StreamSettingsFromContext(ctx).(*internet.MemoryStreamConfig)
tag := inbound.Tag
var uplinkCounter stats.Counter
var downlinkCounter stats.Counter
if len(tag) > 0 && p.ForSystem().Stats.InboundUplink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
if c != nil {
uplinkCounter = c
}
}
if len(tag) > 0 && p.ForSystem().Stats.InboundDownlink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
if c != nil {
downlinkCounter = c
}
}
if len(conf.Peers) == 0 {
return nil, errors.New("empty peers")
}
for _, peer := range conf.Peers {
if peer.PublicKey == "" {
return nil, errors.New("peer without publickey")
}
}
localAddresses := make([]netip.Addr, 0, len(conf.Endpoint))
for _, localaddress := range conf.Endpoint {
addr, err := netip.ParseAddr(localaddress)
if err == nil {
localAddresses = append(localAddresses, addr)
continue
}
prefix, err := netip.ParsePrefix(localaddress)
if err == nil {
localAddresses = append(localAddresses, prefix.Addr())
continue
}
return nil, err
}
tun, _, stack, err := CreateNetTUN(localAddresses, nil, int(conf.Mtu), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
server := &Server{ return &Server{
bindServer: &netBindServer{ conf: conf,
netBind: netBind{ ctx: core.ToBackgroundDetachedContext(ctx),
dns: v.GetFeature(dns.ClientType()).(dns.Client), policyManager: p,
dnsOption: dns.IPOption{ dispatcher: d,
IPv4Enable: hasIPv4,
IPv6Enable: hasIPv6,
},
workers: int(conf.NumWorkers),
readQueue: make(chan *netReadInfo),
},
},
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}
tun, err := conf.createTun()(endpoints, int(conf.Mtu), server.forwardConnection) tag: inbound.Tag,
if err != nil { src: inbound.Source,
return nil, err sniffingRequest: content.SniffingRequest,
} streamSettings: streamSettings,
uplinkCounter: uplinkCounter,
downlinkCounter: downlinkCounter,
if err = tun.BuildDevice(createIPCRequest(conf), server.bindServer); err != nil { tun: tun,
_ = tun.Close() stack: stack,
return nil, err }, nil
}
return server, nil
} }
// Network implements proxy.Inbound. // Network implements proxy.Inbound.Network.
func (*Server) Network() []net.Network { func (*Server) Network() []net.Network {
return []net.Network{net.Network_UDP} return []net.Network{}
} }
// Process implements proxy.Inbound. // Process implements proxy.Inbound.Process.
func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error { func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
s.info = routingInfo{ return nil
ctx: ctx, }
dispatcher: dispatcher,
inboundTag: session.InboundFromContext(ctx),
contentTag: session.ContentFromContext(ctx),
}
ep, err := s.bindServer.ParseEndpoint(conn.RemoteAddr().String()) // Close implements common.Closable.Close.
func (s *Server) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.dev != nil {
s.dev.Close()
s.dev = nil
s.tun = nil
} else if s.tun != nil {
s.tun.Close()
s.tun = nil
}
return nil
}
// Start implements common.Runnable.Start.
func (s *Server) Start() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.dev != nil {
return nil
}
if s.src.Address.Family().IsDomain() {
return errors.New("address is domain")
}
listenFunc := func() (net.PacketConn, error) {
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: s.src.Address.IP(), Port: int(s.src.Port)}, s.streamSettings.SocketSettings)
if err != nil {
return nil, err
}
if s.streamSettings.UdpmaskManager != nil {
newConn, err := s.streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
if s.uplinkCounter != nil || s.downlinkCounter != nil {
pktConn = &PacketCounterConnection{
PacketConn: pktConn,
ReadCounter: s.uplinkCounter,
WriteCounter: s.downlinkCounter,
}
}
return pktConn, nil
}
bind := &bind{
listenFunc: listenFunc,
}
logger := &device.Logger{
Verbosef: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Debug,
Content: fmt.Sprintf(format, args...),
})
},
Errorf: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Error,
Content: fmt.Sprintf(format, args...),
})
},
}
dev := device.NewDevice(s.tun, bind, logger)
var cfg strings.Builder
cfg.WriteString("private_key=" + s.conf.SecretKey + "\n")
for _, peer := range s.conf.Peers {
cfg.WriteString("public_key=" + peer.PublicKey + "\n")
if peer.PreSharedKey != "" {
cfg.WriteString("preshared_key=" + peer.PreSharedKey + "\n")
}
for _, ip := range peer.AllowedIps {
cfg.WriteString("allowed_ip=" + ip + "\n")
}
if peer.KeepAlive != "" {
cfg.WriteString("persistent_keepalive_interval=" + peer.KeepAlive + "\n")
}
}
err := dev.IpcSet(cfg.String())
if err != nil { if err != nil {
return err return err
} }
err = dev.Up()
nep := ep.(*netEndpoint) if err != nil {
nep.conn = conn return err
reader := buf.NewPacketReader(conn)
for {
mb, err := reader.ReadMultiBuffer()
if err != nil {
nep.conn = nil
buf.ReleaseMulti(mb)
return err
}
for i, b := range mb {
rawBytes := b.Bytes()
if b.Len() > 3 {
rawBytes[1] = 0
rawBytes[2] = 0
rawBytes[3] = 0
}
select {
case s.bindServer.readQueue <- &netReadInfo{
buff: b,
endpoint: nep,
}:
case <-s.bindServer.closedCh:
nep.conn = nil
buf.ReleaseMulti(mb[i:])
return errors.New("bind closed")
}
}
} }
s.dev = dev
createForwarder(s.stack, s.HandleConnection)
return nil
} }
func (s *Server) forwardConnection(dest net.Destination, conn net.Conn) { func (s *Server) HandleConnection(conn net.Conn, dest net.Destination) {
if s.info.dispatcher == nil { defer conn.Close()
errors.LogError(s.info.ctx, "unexpected: dispatcher == nil") ctx, cancel := context.WithCancel(s.ctx)
return defer cancel()
ctx = c.ContextWithID(ctx, session.NewID())
source := net.DestinationFromAddr(conn.RemoteAddr())
inbound := session.Inbound{
Name: "wireguard",
Tag: s.tag,
CanSpliceCopy: 3,
Source: source,
} }
ctx, cancel := context.WithCancel(core.ToBackgroundDetachedContext(s.info.ctx))
sid := session.NewID()
ctx = c.ContextWithID(ctx, sid)
inbound := session.Inbound{} // since promiscuousModeHandler mixed-up context, we shallow copy inbound (tag) and content (configs)
if s.info.inboundTag != nil {
inbound = *s.info.inboundTag
}
inbound.Name = "wireguard"
inbound.CanSpliceCopy = 3
// overwrite the source to use the tun address for each sub context.
// Since gvisor.ForwarderRequest doesn't provide any info to associate the sub-context with the Parent context
// Currently we have no way to link to the original source address
inbound.Source = net.DestinationFromAddr(conn.RemoteAddr())
ctx = session.ContextWithInbound(ctx, &inbound) ctx = session.ContextWithInbound(ctx, &inbound)
content := new(session.Content) ctx = session.ContextWithContent(ctx, &session.Content{
if s.info.contentTag != nil { SniffingRequest: s.sniffingRequest,
content.SniffingRequest = s.info.contentTag.SniffingRequest })
}
ctx = session.ContextWithContent(ctx, content)
ctx = session.SubContextFromMuxInbound(ctx) ctx = session.SubContextFromMuxInbound(ctx)
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{ ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: nullDestination, From: inbound.Source,
To: dest, To: dest,
Status: log.AccessAccepted, Status: log.AccessAccepted,
Reason: "", Reason: "",
}) })
errors.LogInfo(ctx, "processing from ", source, " to ", dest)
err := s.info.dispatcher.DispatchLink(ctx, dest, &transport.Link{ link := &transport.Link{
Reader: buf.NewReader(conn), Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
Writer: buf.NewWriter(conn), Writer: buf.NewWriter(conn),
})
if err != nil {
errors.LogInfoInner(ctx, err, "connection ends")
} }
if err := s.dispatcher.DispatchLink(ctx, dest, link); err != nil {
cancel() errors.LogError(ctx, errors.New("connection closed").Base(err))
conn.Close() }
} }
-53
View File
@@ -1,53 +0,0 @@
package wireguard_test
import (
"context"
"runtime/debug"
"testing"
"github.com/stretchr/testify/assert"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/wireguard"
)
// TestWireGuardServerInitializationError verifies that an error during TUN initialization
// (triggered by an empty SecretKey) in the WireGuard server does not cause a panic and returns an error instead.
func TestWireGuardServerInitializationError(t *testing.T) {
// Create a minimal core instance with default features
config := &core.Config{}
instance, err := core.New(config)
if err != nil {
t.Fatalf("Failed to create core instance: %v", err)
}
// Set the Xray instance in the context
ctx := context.WithValue(context.Background(), core.XrayKey(1), instance)
// Define the server configuration with an empty SecretKey to trigger error
conf := &wireguard.DeviceConfig{
IsClient: false,
Endpoint: []string{"10.0.0.1/32"},
Mtu: 1420,
SecretKey: "", // Empty SecretKey to trigger error
Peers: []*wireguard.PeerConfig{
{
PublicKey: "some_public_key",
AllowedIps: []string{"10.0.0.2/32"},
},
},
}
// Use defer to catch any panic and fail the test explicitly
defer func() {
if r := recover(); r != nil {
t.Errorf("TUN initialization panicked: %v", r)
debug.PrintStack()
}
}()
// Attempt to initialize the WireGuard server
_, err = wireguard.NewServer(ctx, conf)
// Check that an error is returned
assert.ErrorContains(t, err, "failed to set private_key: hex string does not fit the slice")
}
+68 -164
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"net/netip"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@@ -13,9 +12,7 @@ import (
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/proxy/wireguard/gvisortun"
"gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
@@ -25,77 +22,8 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp" "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter" "gvisor.dev/gvisor/pkg/waiter"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
) )
type tunCreator func(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error)
type promiscuousModeHandler func(dest net.Destination, conn net.Conn)
type Tunnel interface {
BuildDevice(ipc string, bind conn.Bind) error
DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error)
Close() error
}
type tunnel struct {
tun tun.Device
device *device.Device
rw sync.Mutex
}
func (t *tunnel) BuildDevice(ipc string, bind conn.Bind) (err error) {
t.rw.Lock()
defer t.rw.Unlock()
if t.device != nil {
return errors.New("device is already initialized")
}
logger := &device.Logger{
Verbosef: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Debug,
Content: fmt.Sprintf(format, args...),
})
},
Errorf: func(format string, args ...any) {
log.Record(&log.GeneralMessage{
Severity: log.Severity_Error,
Content: fmt.Sprintf(format, args...),
})
},
}
t.device = device.NewDevice(t.tun, bind, logger)
if err = t.device.IpcSet(ipc); err != nil {
return err
}
if err = t.device.Up(); err != nil {
return err
}
return nil
}
func (t *tunnel) Close() (err error) {
t.rw.Lock()
defer t.rw.Unlock()
if t.device == nil {
return nil
}
t.device.Close()
t.device = nil
err = t.tun.Close()
t.tun = nil
return nil
}
func CalculateInterfaceName(name string) (tunName string) { func CalculateInterfaceName(name string) (tunName string) {
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
tunName = "utun" tunName = "utun"
@@ -121,93 +49,61 @@ func CalculateInterfaceName(name string) (tunName string) {
return return
} }
var _ Tunnel = (*gvisorNet)(nil) func createForwarder(gstack *stack.Stack, handler func(conn net.Conn, dest net.Destination)) {
gstack.SetPromiscuousMode(1, true)
gstack.SetSpoofing(1, true)
type gvisorNet struct { tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) {
tunnel go func(r *tcp.ForwarderRequest) {
net *gvisortun.Net var wq waiter.Queue
} id := r.ID()
func (g *gvisorNet) Close() error { ep, err := r.CreateEndpoint(&wq)
return g.tunnel.Close() if err != nil {
} errors.LogError(context.Background(), err.String())
r.Complete(true)
func (g *gvisorNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) ( return
net.Conn, error,
) {
return g.net.DialContextTCPAddrPort(ctx, addr)
}
func (g *gvisorNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
return g.net.DialUDPAddrPort(laddr, raddr)
}
func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) {
out := &gvisorNet{}
tun, n, gstack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil)
if err != nil {
return nil, err
}
if handler != nil {
// handler is only used for promiscuous mode
// capture all packets and send to handler
tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) {
go func(r *tcp.ForwarderRequest) {
var wq waiter.Queue
id := r.ID()
ep, err := r.CreateEndpoint(&wq)
if err != nil {
errors.LogError(context.Background(), err.String())
r.Complete(true)
return
}
options := ep.SocketOptions()
options.SetKeepAlive(false)
options.SetReuseAddress(true)
options.SetReusePort(true)
handler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep))
ep.Close()
r.Complete(false)
}(r)
})
gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
manager := &udpManager{
stack: gstack,
handler: handler,
m: make(map[string]*udpConn),
}
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
data := pkt.Clone().Data().AsRange().ToSlice()
// if len(data) == 0 {
// return false
// }
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
if srcIP == nil || dstIP == nil {
panic(id)
} }
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort)) options := ep.SocketOptions()
manager.feed(src, dst, data) options.SetKeepAlive(false)
return true options.SetReuseAddress(true)
}) options.SetReusePort(true)
handler(gonet.NewTCPConn(&wq, ep), net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)))
ep.Close()
r.Complete(false)
}(r)
})
gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
manager := &udpManager{
stack: gstack,
handler: handler,
m: make(map[string]*udpConn),
} }
out.tun, out.net = tun, n gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
return out, nil data := pkt.Clone().Data().AsRange().ToSlice()
// if len(data) == 0 {
// return false
// }
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
if srcIP == nil || dstIP == nil {
panic(id)
}
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
manager.feed(src, dst, data)
return true
})
} }
type udpManager struct { type udpManager struct {
stack *stack.Stack stack *stack.Stack
handler func(dest net.Destination, conn net.Conn) handler func(conn net.Conn, dest net.Destination)
m map[string]*udpConn m map[string]*udpConn
mutex sync.RWMutex mutex sync.RWMutex
} }
@@ -246,7 +142,7 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
m.mutex.Unlock() m.mutex.Unlock()
} }
m.m[src.NetAddr()] = uc m.m[src.NetAddr()] = uc
go m.handler(dst, uc) go m.handler(uc, dst)
} }
select { select {
@@ -345,19 +241,23 @@ type udpConn struct {
} }
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) { func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
q, ok := <-c.queue for {
if !ok { q, ok := <-c.queue
return nil, io.EOF if !ok {
return nil, io.EOF
}
b := buf.New()
if _, err := b.Write(q.p); err != nil {
errors.LogErrorInner(context.Background(), err, "drop packet to ", q.dest, " with size ", len(q.p))
b.Release()
continue
}
b.UDP = q.dest
return buf.MultiBuffer{b}, nil
} }
b := buf.New()
if _, err := b.Write(q.p); err != nil {
return nil, err
}
b.UDP = q.dest
return buf.MultiBuffer{b}, nil
} }
func (c *udpConn) Read(p []byte) (int, error) { func (c *udpConn) Read(p []byte) (int, error) {
@@ -376,7 +276,11 @@ func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb { for i, b := range mb {
dst := c.dst dst := c.dst
if b.UDP != nil { if b.UDP != nil {
dst = *b.UDP if b.UDP.Address.Family().IsDomain() {
errors.LogError(context.Background(), "impossible domain packet ", b.UDP, " reply via original target ", dst)
} else {
dst = *b.UDP
}
} }
err := c.writeFunc(b.Bytes(), dst, c.src) err := c.writeFunc(b.Bytes(), dst, c.src)
if err != nil { if err != nil {
+5 -3
View File
@@ -1,14 +1,16 @@
//go:build !linux || android //go:build !linux
package wireguard package wireguard
import ( import (
"errors" "errors"
"net/netip" "net/netip"
"golang.zx2c4.com/wireguard/tun"
) )
func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (t Tunnel, err error) { func createKernelTun([]netip.Addr, []netip.Addr, int) (tdev tun.Device, tnet *Net, err error) {
return nil, errors.New("not implemented") return nil, nil, errors.New("not implemented")
} }
func KernelTunSupported() (bool, error) { func KernelTunSupported() (bool, error) {
+108 -118
View File
@@ -1,4 +1,4 @@
//go:build linux && !android //go:build linux
package wireguard package wireguard
@@ -20,17 +20,6 @@ import (
"golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun"
) )
type deviceNet struct {
tunnel
dialer *net.Dialer
lc *net.ListenConfig
handle *netlink.Handle
linkAddrs []netlink.Addr
routes []*netlink.Route
rules []*netlink.Rule
}
var ( var (
tableIndex int = 10230 tableIndex int = 10230
mu sync.Mutex mu sync.Mutex
@@ -48,82 +37,18 @@ func allocateIPv6TableIndex() int {
return currentIndex return currentIndex
} }
func newDeviceNet(interfaceName string) *deviceNet { type kernelTun struct {
dialer := &net.Dialer{} tun.Device
dialer.Control = func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) { dialer *net.Dialer
if err := syscall.BindToDevice(int(fd), interfaceName); err != nil { lc *net.ListenConfig
errors.LogInfoInner(context.Background(), err, "failed to bind to device") handle *netlink.Handle
} linkAddrs []netlink.Addr
}) routes []*netlink.Route
} rules []*netlink.Rule
lc := &net.ListenConfig{}
lc.Control = func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if err := syscall.BindToDevice(int(fd), interfaceName); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
}
})
}
return &deviceNet{dialer: dialer, lc: lc}
} }
func (d *deviceNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) ( func createKernelTun(localAddresses, dnsServers []netip.Addr, mtu int) (tdev tun.Device, tnet *Net, err error) {
net.Conn, error,
) {
return d.dialer.DialContext(ctx, "tcp", addr.String())
}
func (d *deviceNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
var conn net.PacketConn
var err error
if raddr.Addr().Is4() {
conn, err = d.lc.ListenPacket(context.Background(), "udp", "0.0.0.0:0")
} else {
conn, err = d.lc.ListenPacket(context.Background(), "udp", "[::]:0")
}
if err != nil {
return nil, err
}
return &internet.PacketConnWrapper{
PacketConn: conn,
Dest: &net.UDPAddr{
IP: raddr.Addr().AsSlice(),
Port: int(raddr.Port()),
},
}, nil
}
func (d *deviceNet) Close() (err error) {
var errs []error
for _, rule := range d.rules {
if err = d.handle.RuleDel(rule); err != nil {
errs = append(errs, fmt.Errorf("failed to delete rule: %w", err))
}
}
for _, route := range d.routes {
if err = d.handle.RouteDel(route); err != nil {
errs = append(errs, fmt.Errorf("failed to delete route: %w", err))
}
}
if err = d.tunnel.Close(); err != nil {
errs = append(errs, fmt.Errorf("failed to close tunnel: %w", err))
}
if d.handle != nil {
d.handle.Close()
d.handle = nil
}
if len(errs) == 0 {
return nil
}
return goerrors.Join(errs...)
}
func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (t Tunnel, err error) {
if handler != nil {
return nil, errors.New("TODO: support promiscuous mode")
}
var v4, v6 *netip.Addr var v4, v6 *netip.Addr
for _, prefixes := range localAddresses { for _, prefixes := range localAddresses {
if v4 == nil && prefixes.Is4() { if v4 == nil && prefixes.Is4() {
@@ -150,22 +75,22 @@ func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
// system configs. // system configs.
if v4 != nil { if v4 != nil {
if err = writeSysctlZero("/proc/sys/net/ipv4/conf/all/rp_filter"); err != nil { if err = writeSysctlZero("/proc/sys/net/ipv4/conf/all/rp_filter"); err != nil {
return nil, fmt.Errorf("failed to disable ipv4 rp_filter for all: %w", err) return nil, nil, fmt.Errorf("failed to disable ipv4 rp_filter for all: %w", err)
} }
} }
if v6 != nil { if v6 != nil {
if err = writeSysctlZero("/proc/sys/net/ipv6/conf/all/disable_ipv6"); err != nil { if err = writeSysctlZero("/proc/sys/net/ipv6/conf/all/disable_ipv6"); err != nil {
return nil, fmt.Errorf("failed to enable ipv6: %w", err) return nil, nil, fmt.Errorf("failed to enable ipv6: %w", err)
} }
if err = writeSysctlZero("/proc/sys/net/ipv6/conf/all/rp_filter"); err != nil { if err = writeSysctlZero("/proc/sys/net/ipv6/conf/all/rp_filter"); err != nil {
return nil, fmt.Errorf("failed to disable ipv6 rp_filter for all: %w", err) return nil, nil, fmt.Errorf("failed to disable ipv6 rp_filter for all: %w", err)
} }
} }
n := CalculateInterfaceName("wg") n := CalculateInterfaceName("wg")
wgt, err := tun.CreateTUN(n, mtu) wgt, err := tun.CreateTUN(n, mtu)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
defer func() { defer func() {
if err != nil { if err != nil {
@@ -177,12 +102,12 @@ func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
// the operation require root privilege on container require '--privileged' flag. // the operation require root privilege on container require '--privileged' flag.
if v4 != nil { if v4 != nil {
if err = writeSysctlZero("/proc/sys/net/ipv4/conf/" + n + "/rp_filter"); err != nil { if err = writeSysctlZero("/proc/sys/net/ipv4/conf/" + n + "/rp_filter"); err != nil {
return nil, fmt.Errorf("failed to disable ipv4 rp_filter for tunnel: %w", err) return nil, nil, fmt.Errorf("failed to disable ipv4 rp_filter for tunnel: %w", err)
} }
} }
if v6 != nil { if v6 != nil {
if err = writeSysctlZero("/proc/sys/net/ipv6/conf/" + n + "/rp_filter"); err != nil { if err = writeSysctlZero("/proc/sys/net/ipv6/conf/" + n + "/rp_filter"); err != nil {
return nil, fmt.Errorf("failed to disable ipv6 rp_filter for tunnel: %w", err) return nil, nil, fmt.Errorf("failed to disable ipv6 rp_filter for tunnel: %w", err)
} }
} }
@@ -196,25 +121,28 @@ func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
} }
ipv6TableIndex-- ipv6TableIndex--
if ipv6TableIndex < 0 { if ipv6TableIndex < 0 {
return nil, fmt.Errorf("failed to find available ipv6 table index") return nil, nil, fmt.Errorf("failed to find available ipv6 table index")
} }
} }
} }
out := newDeviceNet(n) t := &kernelTun{
out.handle, err = netlink.NewHandle() Device: wgt,
}
t.handle, err = netlink.NewHandle()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
defer func() { defer func() {
if err != nil { if err != nil {
_ = out.Close() t.Close()
} }
}() }()
l, err := netlink.LinkByName(n) l, err := netlink.LinkByName(n)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if v4 != nil { if v4 != nil {
@@ -224,7 +152,7 @@ func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
Mask: net.CIDRMask(v4.BitLen(), v4.BitLen()), Mask: net.CIDRMask(v4.BitLen(), v4.BitLen()),
}, },
} }
out.linkAddrs = append(out.linkAddrs, addr) t.linkAddrs = append(t.linkAddrs, addr)
} }
if v6 != nil { if v6 != nil {
addr := netlink.Addr{ addr := netlink.Addr{
@@ -233,7 +161,7 @@ func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
Mask: net.CIDRMask(v6.BitLen(), v6.BitLen()), Mask: net.CIDRMask(v6.BitLen(), v6.BitLen()),
}, },
} }
out.linkAddrs = append(out.linkAddrs, addr) t.linkAddrs = append(t.linkAddrs, addr)
rt := &netlink.Route{ rt := &netlink.Route{
LinkIndex: l.Attrs().Index, LinkIndex: l.Attrs().Index,
@@ -243,40 +171,102 @@ func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
}, },
Table: ipv6TableIndex, Table: ipv6TableIndex,
} }
out.routes = append(out.routes, rt) t.routes = append(t.routes, rt)
r := netlink.NewRule() r := netlink.NewRule()
r.Table, r.Family, r.Src = ipv6TableIndex, unix.AF_INET6, addr.IPNet r.Table, r.Family, r.Src = ipv6TableIndex, unix.AF_INET6, addr.IPNet
out.rules = append(out.rules, r) t.rules = append(t.rules, r)
r = netlink.NewRule() r = netlink.NewRule()
r.Table, r.Family, r.OifName = ipv6TableIndex, unix.AF_INET6, n r.Table, r.Family, r.OifName = ipv6TableIndex, unix.AF_INET6, n
out.rules = append(out.rules, r) t.rules = append(t.rules, r)
} }
for _, addr := range out.linkAddrs { for _, addr := range t.linkAddrs {
if err = out.handle.AddrAdd(l, &addr); err != nil { if err = t.handle.AddrAdd(l, &addr); err != nil {
return nil, fmt.Errorf("failed to add address %s to %s: %w", addr, n, err) return nil, nil, fmt.Errorf("failed to add address %s to %s: %w", addr, n, err)
} }
} }
if err = out.handle.LinkSetMTU(l, mtu); err != nil { if err = t.handle.LinkSetMTU(l, mtu); err != nil {
return nil, err return nil, nil, err
} }
if err = out.handle.LinkSetUp(l); err != nil { if err = t.handle.LinkSetUp(l); err != nil {
return nil, err return nil, nil, err
} }
for _, route := range out.routes { for _, route := range t.routes {
if err = out.handle.RouteAdd(route); err != nil { if err = t.handle.RouteAdd(route); err != nil {
return nil, fmt.Errorf("failed to add route %s: %w", route, err) return nil, nil, fmt.Errorf("failed to add route %s: %w", route, err)
} }
} }
for _, rule := range out.rules { for _, rule := range t.rules {
if err = out.handle.RuleAdd(rule); err != nil { if err = t.handle.RuleAdd(rule); err != nil {
return nil, fmt.Errorf("failed to add rule %s: %w", rule, err) return nil, nil, fmt.Errorf("failed to add rule %s: %w", rule, err)
} }
} }
out.tun = wgt
return out, nil dialer := &net.Dialer{}
dialer.Control = func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if err := syscall.BindToDevice(int(fd), n); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
}
})
}
lc := &net.ListenConfig{}
lc.Control = func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if err := syscall.BindToDevice(int(fd), n); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
}
})
}
t.dialer = dialer
t.lc = lc
tnet = &Net{
DialContextTCPAddrPort: t.DialContextTCPAddrPort,
DialUDPAddrPort: t.DialUDPAddrPort,
dnsServers: dnsServers,
hasV4: v4 != nil,
hasV6: v6 != nil,
}
return t, tnet, nil
}
func (tun *kernelTun) Close() (err error) {
var errs []error
for _, rule := range tun.rules {
if err = tun.handle.RuleDel(rule); err != nil {
errs = append(errs, fmt.Errorf("failed to delete rule: %w", err))
}
}
for _, route := range tun.routes {
if err = tun.handle.RouteDel(route); err != nil {
errs = append(errs, fmt.Errorf("failed to delete route: %w", err))
}
}
if err = tun.Device.Close(); err != nil {
errs = append(errs, fmt.Errorf("failed to close device: %w", err))
}
tun.handle.Close()
errs = append(errs, tun.Device.Close())
return goerrors.Join(errs...)
}
func (tun *kernelTun) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {
return tun.dialer.DialContext(ctx, "tcp", addr.String())
}
func (tun *kernelTun) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
conn, err := tun.lc.ListenPacket(context.Background(), "udp", ":0")
if err != nil {
return nil, err
}
return &internet.PacketConnWrapper{
PacketConn: conn,
Dest: net.UDPAddrFromAddrPort(raddr),
}, nil
} }
func KernelTunSupported() (bool, error) { func KernelTunSupported() (bool, error) {
+1 -76
View File
@@ -2,10 +2,6 @@ package wireguard
import ( import (
"context" "context"
"errors"
"fmt"
"net/netip"
"strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
) )
@@ -14,80 +10,9 @@ func init() {
common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*DeviceConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
deviceConfig := config.(*DeviceConfig) deviceConfig := config.(*DeviceConfig)
if deviceConfig.IsClient { if deviceConfig.IsClient {
return New(ctx, deviceConfig) return NewClient(ctx, deviceConfig)
} else { } else {
return NewServer(ctx, deviceConfig) return NewServer(ctx, deviceConfig)
} }
})) }))
} }
// convert endpoint string to netip.Addr
func parseEndpoints(conf *DeviceConfig) ([]netip.Addr, bool, bool, error) {
var hasIPv4, hasIPv6 bool
endpoints := make([]netip.Addr, len(conf.Endpoint))
for i, str := range conf.Endpoint {
var addr netip.Addr
if strings.Contains(str, "/") {
prefix, err := netip.ParsePrefix(str)
if err != nil {
return nil, false, false, err
}
addr = prefix.Addr()
if prefix.Bits() != addr.BitLen() {
return nil, false, false, errors.New("interface address subnet should be /32 for IPv4 and /128 for IPv6")
}
} else {
var err error
addr, err = netip.ParseAddr(str)
if err != nil {
return nil, false, false, err
}
}
endpoints[i] = addr
if addr.Is4() {
hasIPv4 = true
} else if addr.Is6() {
hasIPv6 = true
}
}
return endpoints, hasIPv4, hasIPv6, nil
}
// serialize the config into an IPC request
func createIPCRequest(conf *DeviceConfig) string {
var request strings.Builder
request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey))
if !conf.IsClient {
// placeholder, we'll handle actual port listening on Xray
request.WriteString("listen_port=1337\n")
}
for _, peer := range conf.Peers {
if peer.PublicKey != "" {
request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
}
if peer.PreSharedKey != "" {
request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey))
}
if peer.Endpoint != "" {
request.WriteString(fmt.Sprintf("endpoint=%s\n", peer.Endpoint))
}
for _, ip := range peer.AllowedIps {
request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip))
}
if peer.KeepAlive != 0 {
request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive))
}
}
return request.String()[:request.Len()]
}
+11 -10
View File
@@ -3,6 +3,7 @@ package finalmask
import ( import (
"context" "context"
"net" "net"
"slices"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -28,7 +29,7 @@ func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) { func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int var sizes []int
var conns []net.PacketConn var conns []net.PacketConn
for i, mask := range m.udpmasks { for i, mask := range slices.Backward(m.udpmasks) {
if _, ok := mask.(headerConn); ok { if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1) conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
if err != nil { if err != nil {
@@ -61,7 +62,7 @@ func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketCon
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) { func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int var sizes []int
var conns []net.PacketConn var conns []net.PacketConn
for i, mask := range m.udpmasks { for i, mask := range slices.Backward(m.udpmasks) {
if _, ok := mask.(headerConn); ok { if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1) conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
if err != nil { if err != nil {
@@ -124,7 +125,7 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
if err != nil { if err != nil {
return n, addr, err return n, addr, err
} }
b = b[:n] buf := b[:n]
sum := 0 sum := 0
for _, size := range c.sizes { for _, size := range c.sizes {
@@ -132,24 +133,24 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
} }
if n < sum { if n < sum {
errors.LogError(context.Background(), "[mask] drop packet from ", addr, " with size ", len(b)) errors.LogError(context.Background(), "[mask] drop packet from ", addr, " with size ", n)
continue continue
} }
for i := range c.conns { for i := range c.conns {
n, _, err = c.conns[i].ReadFrom(b) n, _, err = c.conns[i].ReadFrom(buf)
if err != nil { if err != nil {
errors.LogErrorInner(context.Background(), err, "[mask] drop packet from ", addr, " with size ", len(b)) errors.LogErrorInner(context.Background(), err, "[mask] drop packet from ", addr, " with size ", n)
break break
} }
b = b[c.sizes[i] : n+c.sizes[i]] buf = buf[c.sizes[i] : n+c.sizes[i]]
} }
if err != nil { if err != nil {
continue continue
} }
return copy(p, b), addr, nil return copy(p, buf), addr, nil
} }
} }
@@ -212,7 +213,7 @@ func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {
func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) { func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
var err error var err error
for _, mask := range m.tcpmasks { for _, mask := range slices.Backward(m.tcpmasks) {
raw, err = mask.WrapConnClient(raw) raw, err = mask.WrapConnClient(raw)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -223,7 +224,7 @@ func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) { func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
var err error var err error
for _, mask := range m.tcpmasks { for _, mask := range slices.Backward(m.tcpmasks) {
raw, err = mask.WrapConnServer(raw) raw, err = mask.WrapConnServer(raw)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -25,12 +25,12 @@ type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
PacketsFrom int64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"` PacketsFrom int64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"`
PacketsTo int64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"` PacketsTo int64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"`
LengthMin int64 `protobuf:"varint,3,opt,name=length_min,json=lengthMin,proto3" json:"length_min,omitempty"`
LengthMax int64 `protobuf:"varint,4,opt,name=length_max,json=lengthMax,proto3" json:"length_max,omitempty"`
DelayMin int64 `protobuf:"varint,5,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,6,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
MaxSplitMin int64 `protobuf:"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3" json:"max_split_min,omitempty"` MaxSplitMin int64 `protobuf:"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3" json:"max_split_min,omitempty"`
MaxSplitMax int64 `protobuf:"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3" json:"max_split_max,omitempty"` MaxSplitMax int64 `protobuf:"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3" json:"max_split_max,omitempty"`
LengthsMin []int64 `protobuf:"varint,9,rep,packed,name=lengths_min,json=lengthsMin,proto3" json:"lengths_min,omitempty"`
LengthsMax []int64 `protobuf:"varint,10,rep,packed,name=lengths_max,json=lengthsMax,proto3" json:"lengths_max,omitempty"`
DelaysMin []int64 `protobuf:"varint,11,rep,packed,name=delays_min,json=delaysMin,proto3" json:"delays_min,omitempty"`
DelaysMax []int64 `protobuf:"varint,12,rep,packed,name=delays_max,json=delaysMax,proto3" json:"delays_max,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -79,34 +79,6 @@ func (x *Config) GetPacketsTo() int64 {
return 0 return 0
} }
func (x *Config) GetLengthMin() int64 {
if x != nil {
return x.LengthMin
}
return 0
}
func (x *Config) GetLengthMax() int64 {
if x != nil {
return x.LengthMax
}
return 0
}
func (x *Config) GetDelayMin() int64 {
if x != nil {
return x.DelayMin
}
return 0
}
func (x *Config) GetDelayMax() int64 {
if x != nil {
return x.DelayMax
}
return 0
}
func (x *Config) GetMaxSplitMin() int64 { func (x *Config) GetMaxSplitMin() int64 {
if x != nil { if x != nil {
return x.MaxSplitMin return x.MaxSplitMin
@@ -121,23 +93,54 @@ func (x *Config) GetMaxSplitMax() int64 {
return 0 return 0
} }
func (x *Config) GetLengthsMin() []int64 {
if x != nil {
return x.LengthsMin
}
return nil
}
func (x *Config) GetLengthsMax() []int64 {
if x != nil {
return x.LengthsMax
}
return nil
}
func (x *Config) GetDelaysMin() []int64 {
if x != nil {
return x.DelaysMin
}
return nil
}
func (x *Config) GetDelaysMax() []int64 {
if x != nil {
return x.DelaysMax
}
return nil
}
var File_transport_internet_finalmask_fragment_config_proto protoreflect.FileDescriptor var File_transport_internet_finalmask_fragment_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_fragment_config_proto_rawDesc = "" + const file_transport_internet_finalmask_fragment_config_proto_rawDesc = "" +
"\n" + "\n" +
"2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x8a\x02\n" + "2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x92\x02\n" +
"\x06Config\x12!\n" + "\x06Config\x12!\n" +
"\fpackets_from\x18\x01 \x01(\x03R\vpacketsFrom\x12\x1d\n" + "\fpackets_from\x18\x01 \x01(\x03R\vpacketsFrom\x12\x1d\n" +
"\n" + "\n" +
"packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\x1d\n" + "packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\"\n" +
"\n" +
"length_min\x18\x03 \x01(\x03R\tlengthMin\x12\x1d\n" +
"\n" +
"length_max\x18\x04 \x01(\x03R\tlengthMax\x12\x1b\n" +
"\tdelay_min\x18\x05 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x06 \x01(\x03R\bdelayMax\x12\"\n" +
"\rmax_split_min\x18\a \x01(\x03R\vmaxSplitMin\x12\"\n" + "\rmax_split_min\x18\a \x01(\x03R\vmaxSplitMin\x12\"\n" +
"\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMaxB\xa0\x01\n" + "\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMax\x12\x1f\n" +
"\vlengths_min\x18\t \x03(\x03R\n" +
"lengthsMin\x12\x1f\n" +
"\vlengths_max\x18\n" +
" \x03(\x03R\n" +
"lengthsMax\x12\x1d\n" +
"\n" +
"delays_min\x18\v \x03(\x03R\tdelaysMin\x12\x1d\n" +
"\n" +
"delays_max\x18\f \x03(\x03R\tdelaysMaxB\xa0\x01\n" +
".com.xray.transport.internet.finalmask.fragmentP\x01Z?github.com/xtls/xray-core/transport/internet/finalmask/fragment\xaa\x02*Xray.Transport.Internet.Finalmask.Fragmentb\x06proto3" ".com.xray.transport.internet.finalmask.fragmentP\x01Z?github.com/xtls/xray-core/transport/internet/finalmask/fragment\xaa\x02*Xray.Transport.Internet.Finalmask.Fragmentb\x06proto3"
var ( var (
@@ -9,10 +9,10 @@ option java_multiple_files = true;
message Config { message Config {
int64 packets_from = 1; int64 packets_from = 1;
int64 packets_to = 2; int64 packets_to = 2;
int64 length_min = 3;
int64 length_max = 4;
int64 delay_min = 5;
int64 delay_max = 6;
int64 max_split_min = 7; int64 max_split_min = 7;
int64 max_split_max = 8; int64 max_split_max = 8;
repeated int64 lengths_min = 9;
repeated int64 lengths_max = 10;
repeated int64 delays_min = 11;
repeated int64 delays_max = 12;
} }
+41 -9
View File
@@ -43,6 +43,29 @@ func (c *fragmentConn) Splice() bool {
return true return true
} }
// lengthForSegment returns the length range (min, max) for the given segment index (0-based).
// Clamps to the last entry when the index exceeds the list length.
func (c *fragmentConn) lengthForSegment(segIdx int) (int64, int64) {
if segIdx >= len(c.config.LengthsMin) {
segIdx = len(c.config.LengthsMin) - 1
}
return c.config.LengthsMin[segIdx], c.config.LengthsMax[segIdx]
}
// delayForSegment returns the delay range (min, max) for the given segment index (0-based).
// Clamps to the last entry when the index exceeds the list length.
func (c *fragmentConn) delayForSegment(segIdx int) (int64, int64) {
if segIdx >= len(c.config.DelaysMin) {
segIdx = len(c.config.DelaysMin) - 1
}
return c.config.DelaysMin[segIdx], c.config.DelaysMax[segIdx]
}
// mergeTlsHelloSegments returns true only when delays has exactly one zero entry.
func (c *fragmentConn) mergeTlsHelloSegments() bool {
return len(c.config.DelaysMax) == 1 && c.config.DelaysMax[0] == 0
}
func (c *fragmentConn) Write(p []byte) (n int, err error) { func (c *fragmentConn) Write(p []byte) (n int, err error) {
c.count++ c.count++
@@ -57,12 +80,13 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
data := p[5:recordLen] data := p[5:recordLen]
buff := make([]byte, 2048) buff := make([]byte, 2048)
var hello []byte var hello []byte
mergeHello := c.mergeTlsHelloSegments()
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax) maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64 var splitNum int64
for from := 0; ; { for from := 0; ; {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax)) lengthMin, lengthMax := c.lengthForSegment(int(splitNum))
splitNum++ to := from + int(crypto.RandBetween(lengthMin, lengthMax))
if to > len(data) || (maxSplit > 0 && splitNum >= maxSplit) { if to > len(data) || (maxSplit > 0 && splitNum+1 >= maxSplit) {
to = len(data) to = len(data)
} }
l := to - from l := to - from
@@ -74,15 +98,19 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
from = to from = to
buff[3] = byte(l >> 8) buff[3] = byte(l >> 8)
buff[4] = byte(l) buff[4] = byte(l)
if c.config.DelayMax == 0 { if mergeHello {
hello = append(hello, buff[:5+l]...) hello = append(hello, buff[:5+l]...)
} else { } else {
delayMin, delayMax := c.delayForSegment(int(splitNum))
_, err := c.Conn.Write(buff[:5+l]) _, err := c.Conn.Write(buff[:5+l])
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond) if delayMax > 0 {
time.Sleep(time.Duration(crypto.RandBetween(delayMin, delayMax)) * time.Millisecond)
}
if err != nil { if err != nil {
return 0, err return 0, err
} }
} }
splitNum++
if from == len(data) { if from == len(data) {
if len(hello) > 0 { if len(hello) > 0 {
_, err := c.Conn.Write(hello) _, err := c.Conn.Write(hello)
@@ -107,9 +135,9 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax) maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64 var splitNum int64
for from := 0; ; { for from := 0; ; {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax)) lengthMin, lengthMax := c.lengthForSegment(int(splitNum))
splitNum++ to := from + int(crypto.RandBetween(lengthMin, lengthMax))
if to > len(p) || (maxSplit > 0 && splitNum >= maxSplit) { if to > len(p) || (maxSplit > 0 && splitNum+1 >= maxSplit) {
to = len(p) to = len(p)
} }
n, err := c.Conn.Write(p[from:to]) n, err := c.Conn.Write(p[from:to])
@@ -117,7 +145,11 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
if err != nil { if err != nil {
return from, err return from, err
} }
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond) delayMin, delayMax := c.delayForSegment(int(splitNum))
if delayMax > 0 {
time.Sleep(time.Duration(crypto.RandBetween(delayMin, delayMax)) * time.Millisecond)
}
splitNum++
if from >= len(p) { if from >= len(p) {
return from, nil return from, nil
} }
@@ -1,11 +1,11 @@
package salamander package salamander
import ( import (
"crypto/rand"
"fmt" "fmt"
"math/rand"
"sync" "sync"
"time"
"github.com/xtls/xray-core/common"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
) )
@@ -21,8 +21,7 @@ var ErrPSKTooShort = fmt.Errorf("PSK must be at least %d bytes", smPSKMinLen)
// the BLAKE2b-256 hash of a pre-shared key combined with a random salt. // the BLAKE2b-256 hash of a pre-shared key combined with a random salt.
// Packet format: [8-byte salt][payload] // Packet format: [8-byte salt][payload]
type SalamanderObfuscator struct { type SalamanderObfuscator struct {
PSK []byte PSK []byte
RandSrc *rand.Rand
lk sync.Mutex lk sync.Mutex
keyInput []byte keyInput []byte
@@ -37,7 +36,6 @@ func NewSalamanderObfuscator(psk []byte) (*SalamanderObfuscator, error) {
copy(keyInput, pskCopy) copy(keyInput, pskCopy)
return &SalamanderObfuscator{ return &SalamanderObfuscator{
PSK: pskCopy, PSK: pskCopy,
RandSrc: rand.New(rand.NewSource(time.Now().UnixNano())),
keyInput: keyInput, keyInput: keyInput,
}, nil }, nil
} }
@@ -47,8 +45,8 @@ func (o *SalamanderObfuscator) Obfuscate(in, out []byte) int {
if len(out) < outLen { if len(out) < outLen {
return 0 return 0
} }
common.Must2(rand.Read(out[:smSaltLen]))
o.lk.Lock() o.lk.Lock()
_, _ = o.RandSrc.Read(out[:smSaltLen])
key := o.keyLocked(out[:smSaltLen]) key := o.keyLocked(out[:smSaltLen])
o.lk.Unlock() o.lk.Unlock()
for i, c := range in { for i, c := range in {
+6 -6
View File
@@ -2,7 +2,7 @@ package sudoku
import ( import (
"fmt" "fmt"
"math/rand" "math/rand/v2"
) )
var perm4 = [24][4]byte{ var perm4 = [24][4]byte{
@@ -67,7 +67,7 @@ func pickPaddingChance(rng *rand.Rand, pMin, pMax int) int {
if pMax == pMin { if pMax == pMin {
return pMin return pMin
} }
return pMin + rng.Intn(pMax-pMin+1) return pMin + rng.IntN(pMax-pMin+1)
} }
func (c *codec) shouldPad() bool { func (c *codec) shouldPad() bool {
@@ -77,7 +77,7 @@ func (c *codec) shouldPad() bool {
if c.paddingChance >= 100 { if c.paddingChance >= 100 {
return true return true
} }
return c.rng.Intn(100) < c.paddingChance return c.rng.IntN(100) < c.paddingChance
} }
func (c *codec) currentTable() *table { func (c *codec) currentTable() *table {
@@ -89,7 +89,7 @@ func (c *codec) currentTable() *table {
func (c *codec) randomPadding(t *table) byte { func (c *codec) randomPadding(t *table) byte {
pool := t.layout.paddingPool pool := t.layout.paddingPool
return pool[c.rng.Intn(len(pool))] return pool[c.rng.IntN(len(pool))]
} }
func (c *codec) encode(in []byte) ([]byte, error) { func (c *codec) encode(in []byte) ([]byte, error) {
@@ -112,8 +112,8 @@ func (c *codec) encode(in []byte) ([]byte, error) {
return nil, fmt.Errorf("sudoku encode table missing for byte %d", b) return nil, fmt.Errorf("sudoku encode table missing for byte %d", b)
} }
hints := enc[c.rng.Intn(len(enc))] hints := enc[c.rng.IntN(len(enc))]
perm := perm4[c.rng.Intn(len(perm4))] perm := perm4[c.rng.IntN(len(perm4))]
for _, idx := range perm { for _, idx := range perm {
if c.shouldPad() { if c.shouldPad() {
out = append(out, c.randomPadding(t)) out = append(out, c.randomPadding(t))
@@ -72,7 +72,7 @@ func (e *packedEncoder) maybePad(out []byte, layout *byteLayout) []byte {
return append(out, layout.paddingPool[0]) return append(out, layout.paddingPool[0])
} }
for { for {
b := layout.paddingPool[e.codec.rng.Intn(len(layout.paddingPool))] b := layout.paddingPool[e.codec.rng.IntN(len(layout.paddingPool))]
if b != layout.padMarker { if b != layout.padMarker {
return append(out, b) return append(out, b)
} }
+7 -8
View File
@@ -7,10 +7,12 @@ import (
"fmt" "fmt"
"math/bits" "math/bits"
"math/rand" "math/rand"
rand_v2 "math/rand/v2"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time"
"github.com/xtls/xray-core/common"
) )
type table struct { type table struct {
@@ -570,11 +572,8 @@ func sort4(in [4]byte) [4]byte {
return in return in
} }
func newSeededRand() *rand.Rand { func newSeededRand() *rand_v2.Rand {
seed := time.Now().UnixNano() var seedBytes [32]byte
var seedBytes [8]byte common.Must2(crypto_rand.Read(seedBytes[:]))
if _, err := crypto_rand.Read(seedBytes[:]); err == nil { return rand_v2.New(rand_v2.NewChaCha8(seedBytes))
seed = int64(binary.BigEndian.Uint64(seedBytes[:]))
}
return rand.New(rand.NewSource(seed))
} }
-21
View File
@@ -2,7 +2,6 @@ package finalmask_test
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"io" "io"
"net" "net"
@@ -10,8 +9,6 @@ import (
"testing" "testing"
"time" "time"
singM "github.com/sagernet/sing/common/metadata"
singN "github.com/sagernet/sing/common/network"
"github.com/xtls/xray-core/proxy" "github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom" "github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
@@ -134,24 +131,6 @@ func (c *scriptedPacketConn) SetWriteDeadline(t time.Time) error {
return nil return nil
} }
type captureUDPHandler struct {
gotMetadata chan singM.Metadata
}
func (h *captureUDPHandler) NewConnection(_ context.Context, _ net.Conn, _ singM.Metadata) error {
return nil
}
func (h *captureUDPHandler) NewPacketConnection(_ context.Context, _ singN.PacketConn, metadata singM.Metadata) error {
select {
case h.gotMetadata <- metadata:
default:
}
return nil
}
func (h *captureUDPHandler) NewError(_ context.Context, _ error) {}
func newStandaloneEchoUDPConfig() *custom.UDPStandaloneConfig { func newStandaloneEchoUDPConfig() *custom.UDPStandaloneConfig {
return &custom.UDPStandaloneConfig{ return &custom.UDPStandaloneConfig{
Client: []*custom.UDPItem{ Client: []*custom.UDPItem{
@@ -1,3 +1,5 @@
//go:build !linux
package xicmp package xicmp
import ( import (
@@ -0,0 +1,364 @@
//go:build linux
package xicmp
import (
"context"
goerrors "errors"
"io"
"net"
"net/netip"
"sync"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/transport/internet/finalmask"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
func clientIDToAddr(clientID [8]byte) *net.UDPAddr {
ip := make(net.IP, 16)
ip[0] = 0xfd
ip[1] = 0x00
copy(ip[8:], clientID[:])
return &net.UDPAddr{IP: ip}
}
type record struct {
id int
seq int
addr net.Addr
dst net.IP
last time.Time
}
type xicmpConnServer struct {
conn net.PacketConn
icmp4 *icmp.PacketConn
icmp6 *icmp.PacketConn
ipv4PC *ipv4.PacketConn
ipv6PC *ipv6.PacketConn
ips map[netip.Addr]struct{}
rec map[string]record
readCh chan packet
closedCh chan struct{}
mu sync.Mutex
}
func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
}
icmp6, err := icmp.ListenPacket("ip6:ipv6-icmp", "::")
if err != nil {
return nil, err
}
ips := make(map[netip.Addr]struct{})
for _, ip := range c.IPs {
ips[netip.MustParseAddr(ip)] = struct{}{}
}
conn := &xicmpConnServer{
conn: raw,
icmp4: icmp4,
icmp6: icmp6,
ipv4PC: icmp4.IPv4PacketConn(),
ipv6PC: icmp6.IPv6PacketConn(),
ips: ips,
rec: make(map[string]record),
readCh: make(chan packet),
closedCh: make(chan struct{}),
}
common.Must(conn.ipv4PC.SetControlMessage(ipv4.FlagDst, true))
common.Must(conn.ipv6PC.SetControlMessage(ipv6.FlagDst, true))
go conn.clean()
go conn.recv4()
go conn.recv6()
return conn, nil
}
func (c *xicmpConnServer) closed() bool {
select {
case <-c.closedCh:
return true
default:
return false
}
}
func (c *xicmpConnServer) clean() {
ticker := time.NewTicker(time.Minute / 2)
defer ticker.Stop()
for {
select {
case <-ticker.C:
now := time.Now()
c.mu.Lock()
for key, r := range c.rec {
if now.Sub(r.last) > time.Minute {
delete(c.rec, key)
}
}
c.mu.Unlock()
case <-c.closedCh:
return
}
}
}
func (c *xicmpConnServer) recv4() {
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, cm, addr, err := c.ipv4PC.ReadFrom(b[:])
if err != nil {
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closedCh:
return
}
}
continue
}
msg, err := icmp.ParseMessage(1, b[:n])
if err != nil {
continue
}
if msg.Type != ipv4.ICMPTypeEcho {
continue
}
echo, ok := msg.Body.(*icmp.Echo)
if !ok {
continue
}
if len(echo.Data) <= 8 {
continue
}
if len(c.ips) > 0 {
netipAddr, ok := netip.AddrFromSlice(addr.(*net.IPAddr).IP)
if !ok {
continue
}
if _, ok := c.ips[netipAddr]; !ok {
continue
}
}
cAddr := clientIDToAddr([8]byte(echo.Data[:8]))
c.mu.Lock()
c.rec[cAddr.String()] = record{
id: echo.ID,
seq: echo.Seq,
addr: addr,
dst: cm.Dst,
last: time.Now(),
}
c.mu.Unlock()
p := pool.Get().([]byte)[:len(echo.Data[8:])]
copy(p, echo.Data[8:])
select {
case c.readCh <- packet{
p: p,
addr: cAddr,
}:
case <-c.closedCh:
pool.Put(p)
return
}
}
}
func (c *xicmpConnServer) recv6() {
var b [finalmask.UDPSize]byte
for {
if c.closed() {
return
}
n, cm, addr, err := c.ipv6PC.ReadFrom(b[:])
if err != nil {
var netErr net.Error
if goerrors.As(err, &netErr) && netErr.Timeout() {
select {
case c.readCh <- packet{
err: err,
}:
case <-c.closedCh:
return
}
}
continue
}
msg, err := icmp.ParseMessage(58, b[:n])
if err != nil {
continue
}
if msg.Type != ipv6.ICMPTypeEchoRequest {
continue
}
echo, ok := msg.Body.(*icmp.Echo)
if !ok {
continue
}
if len(echo.Data) <= 8 {
continue
}
if len(c.ips) > 0 {
netipAddr, ok := netip.AddrFromSlice(addr.(*net.IPAddr).IP)
if !ok {
continue
}
if _, ok := c.ips[netipAddr]; !ok {
continue
}
}
cAddr := clientIDToAddr([8]byte(echo.Data[:8]))
c.mu.Lock()
c.rec[cAddr.String()] = record{
id: echo.ID,
seq: echo.Seq,
addr: addr,
dst: cm.Dst,
last: time.Now(),
}
c.mu.Unlock()
p := pool.Get().([]byte)[:len(echo.Data[8:])]
copy(p, echo.Data[8:])
select {
case c.readCh <- packet{
p: p,
addr: cAddr,
}:
case <-c.closedCh:
pool.Put(p)
return
}
}
}
func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
select {
case packet := <-c.readCh:
if packet.p != nil {
n = copy(p, packet.p)
pool.Put(packet.p)
}
return n, packet.addr, packet.err
case <-c.closedCh:
return 0, nil, io.EOF
}
}
func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if len(p)+8 > finalmask.UDPSize {
errors.LogError(context.Background(), "drop packet to ", addr, " with size ", len(p))
return 0, nil
}
c.mu.Lock()
r, ok := c.rec[addr.String()]
if !ok {
errors.LogError(context.Background(), "drop packet to ", addr, " with size ", len(p))
c.mu.Unlock()
return 0, nil
}
r.last = time.Now()
c.rec[addr.String()] = r
c.mu.Unlock()
// errors.LogDebug(context.Background(), "id ", r.id, " seq ", r.seq, " addr ", r.addr)
b := pool.Get().([]byte)[:finalmask.UDPSize]
defer pool.Put(b)
copy(b[8:], p)
if r.addr.(*net.IPAddr).IP.To4() != nil {
b = marshal(b, ipv4.ICMPTypeEchoReply, r.id, r.seq, len(p))
_, err = c.ipv4PC.WriteTo(b, &ipv4.ControlMessage{Src: r.dst}, r.addr)
} else {
b = marshal(b, ipv6.ICMPTypeEchoReply, r.id, r.seq, len(p))
_, err = c.ipv6PC.WriteTo(b, &ipv6.ControlMessage{Src: r.dst}, r.addr)
}
if err != nil {
errors.LogErrorInner(context.Background(), err, "xicmp write")
return 0, err
}
return len(p), nil
}
func (c *xicmpConnServer) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed() {
return nil
}
close(c.closedCh)
_ = c.icmp4.Close()
_ = c.icmp6.Close()
_ = c.conn.Close()
return nil
}
func (c *xicmpConnServer) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *xicmpConnServer) SetDeadline(t time.Time) error {
_ = c.icmp4.SetDeadline(t)
_ = c.icmp6.SetDeadline(t)
return nil
}
func (c *xicmpConnServer) SetReadDeadline(t time.Time) error {
_ = c.icmp4.SetReadDeadline(t)
_ = c.icmp6.SetReadDeadline(t)
return nil
}
func (c *xicmpConnServer) SetWriteDeadline(t time.Time) error {
_ = c.icmp4.SetWriteDeadline(t)
_ = c.icmp6.SetWriteDeadline(t)
return nil
}
+2 -2
View File
@@ -62,7 +62,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
if err != nil { if err != nil {
return nil, errors.New("Cannot dial gRPC").Base(err) return nil, errors.New("Cannot dial gRPC").Base(err)
} }
return encoding.NewMultiHunkConn(grpcService, nil), nil return encoding.NewMultiHunkConn(grpcService, nil, nil), nil
} }
errors.LogDebug(ctx, "using gRPC tun mode service name: `"+grpcSettings.getServiceName()+"` stream name: `"+grpcSettings.getTunStreamName()+"`") errors.LogDebug(ctx, "using gRPC tun mode service name: `"+grpcSettings.getServiceName()+"` stream name: `"+grpcSettings.getTunStreamName()+"`")
@@ -71,7 +71,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
return nil, errors.New("Cannot dial gRPC").Base(err) return nil, errors.New("Cannot dial gRPC").Base(err)
} }
return encoding.NewHunkConn(grpcService, nil), nil return encoding.NewHunkConn(grpcService, nil, nil), nil
} }
func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, error) { func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, error) {
+2 -27
View File
@@ -9,8 +9,6 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
) )
type HunkConn interface { type HunkConn interface {
@@ -38,31 +36,8 @@ func NewHunkReadWriter(hc HunkConn, cancel context.CancelFunc) *HunkReaderWriter
return &HunkReaderWriter{hc, cancel, done.New(), nil, 0} return &HunkReaderWriter{hc, cancel, done.New(), nil, 0}
} }
func NewHunkConn(hc HunkConn, cancel context.CancelFunc) net.Conn { func NewHunkConn(hc HunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn {
var rAddr net.Addr rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor)
pr, ok := peer.FromContext(hc.Context())
if ok {
rAddr = pr.Addr
} else {
rAddr = &net.TCPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}
}
md, ok := metadata.FromIncomingContext(hc.Context())
if ok {
header := md.Get("x-real-ip")
if len(header) > 0 {
realip := net.ParseAddress(header[0])
if realip.Family().IsIP() {
rAddr = &net.TCPAddr{
IP: realip.IP(),
Port: 0,
}
}
}
}
wrc := NewHunkReadWriter(hc, cancel) wrc := NewHunkReadWriter(hc, cancel)
return cnc.NewConnection( return cnc.NewConnection(
cnc.ConnectionInput(wrc), cnc.ConnectionInput(wrc),
+3 -29
View File
@@ -3,15 +3,12 @@ package encoding
import ( import (
"context" "context"
"io" "io"
"net"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
xnet "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
) )
type MultiHunkConn interface { type MultiHunkConn interface {
@@ -34,31 +31,8 @@ func NewMultiHunkReadWriter(hc MultiHunkConn, cancel context.CancelFunc) *MultiH
return &MultiHunkReaderWriter{hc, cancel, done.New(), nil} return &MultiHunkReaderWriter{hc, cancel, done.New(), nil}
} }
func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc) net.Conn { func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn {
var rAddr net.Addr rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor)
pr, ok := peer.FromContext(hc.Context())
if ok {
rAddr = pr.Addr
} else {
rAddr = &net.TCPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}
}
md, ok := metadata.FromIncomingContext(hc.Context())
if ok {
header := md.Get("x-real-ip")
if len(header) > 0 {
realip := xnet.ParseAddress(header[0])
if realip.Family().IsIP() {
rAddr = &net.TCPAddr{
IP: realip.IP(),
Port: 0,
}
}
}
}
wrc := NewMultiHunkReadWriter(hc, cancel) wrc := NewMultiHunkReadWriter(hc, cancel)
return cnc.NewConnection( return cnc.NewConnection(
cnc.ConnectionInputMulti(wrc), cnc.ConnectionInputMulti(wrc),
@@ -0,0 +1,58 @@
package encoding
import (
"context"
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
func remoteAddrFromContext(ctx context.Context, trusted []string) net.Addr {
var remoteAddr net.Addr
if pr, ok := peer.FromContext(ctx); ok {
remoteAddr = pr.Addr
} else {
remoteAddr = &net.TCPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return remoteAddr
}
if forwardedAddr := parseTrustedXForwardedFor(md, trusted, remoteAddr); forwardedAddr != nil && forwardedAddr.Family().IsIP() {
remoteAddr = &net.TCPAddr{
IP: forwardedAddr.IP(),
Port: 0,
}
}
return remoteAddr
}
func parseTrustedXForwardedFor(md metadata.MD, trusted []string, remoteAddr net.Addr) net.Address {
values := md.Get("X-Forwarded-For")
if len(values) == 0 || values[0] == "" {
return nil
}
value := values[0]
for _, t := range trusted {
if len(md.Get(t)) > 0 {
if idx := strings.IndexByte(value, ','); idx >= 0 {
value = value[:idx]
}
return net.ParseAddress(value)
}
}
if len(trusted) == 0 {
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
} else {
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
}
return nil
}
@@ -0,0 +1,53 @@
package encoding
import (
"context"
"net"
"testing"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
func TestRemoteAddrFromContext(t *testing.T) {
tests := []struct {
name string
metadata metadata.MD
trustedXForwardedFor []string
expectedRemoteAddress string
}{
{
name: "trust X-Forwarded-For when configured",
metadata: metadata.Pairs("X-Forwarded-For", "2.2.2.2, 3.3.3.3"),
trustedXForwardedFor: []string{"X-Forwarded-For"},
expectedRemoteAddress: "2.2.2.2:0",
},
{
name: "trust X-Forwarded-For with trusted marker",
metadata: metadata.Pairs("X-Forwarded-For", "4.4.4.4", "X-Trusted-CDN", "1"),
trustedXForwardedFor: []string{"X-Trusted-CDN"},
expectedRemoteAddress: "4.4.4.4:0",
},
{
name: "ignore X-Forwarded-For without trusted marker",
metadata: metadata.Pairs("X-Forwarded-For", "5.5.5.5"),
trustedXForwardedFor: []string{"X-Trusted-CDN"},
expectedRemoteAddress: "127.0.0.1:12345",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := peer.NewContext(metadata.NewIncomingContext(context.Background(), test.metadata), &peer.Peer{
Addr: &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 12345,
},
})
remoteAddr := remoteAddrFromContext(ctx, test.trustedXForwardedFor)
if remoteAddr.String() != test.expectedRemoteAddress {
t.Fatalf("unexpected remote address: %s", remoteAddr.String())
}
})
}
}
+10 -6
View File
@@ -19,24 +19,25 @@ import (
type Listener struct { type Listener struct {
encoding.UnimplementedGRPCServiceServer encoding.UnimplementedGRPCServiceServer
ctx context.Context ctx context.Context
handler internet.ConnHandler handler internet.ConnHandler
local net.Addr local net.Addr
config *Config config *Config
trustedXForwardedFor []string
s *grpc.Server s *grpc.Server
} }
func (l Listener) Tun(server encoding.GRPCService_TunServer) error { func (l Listener) Tun(server encoding.GRPCService_TunServer) error {
tunCtx, cancel := context.WithCancel(l.ctx) tunCtx, cancel := context.WithCancel(l.ctx)
l.handler(encoding.NewHunkConn(server, cancel)) l.handler(encoding.NewHunkConn(server, cancel, l.trustedXForwardedFor))
<-tunCtx.Done() <-tunCtx.Done()
return nil return nil
} }
func (l Listener) TunMulti(server encoding.GRPCService_TunMultiServer) error { func (l Listener) TunMulti(server encoding.GRPCService_TunMultiServer) error {
tunCtx, cancel := context.WithCancel(l.ctx) tunCtx, cancel := context.WithCancel(l.ctx)
l.handler(encoding.NewMultiHunkConn(server, cancel)) l.handler(encoding.NewMultiHunkConn(server, cancel, l.trustedXForwardedFor))
<-tunCtx.Done() <-tunCtx.Done()
return nil return nil
} }
@@ -74,6 +75,9 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
} }
listener.ctx = ctx listener.ctx = ctx
if settings.SocketSettings != nil {
listener.trustedXForwardedFor = settings.SocketSettings.TrustedXForwardedFor
}
config := tls.ConfigFromStreamSettings(settings) config := tls.ConfigFromStreamSettings(settings)
@@ -138,6 +138,9 @@ func TestDialWithRemoteAddr(t *testing.T) {
ProtocolSettings: &Config{ ProtocolSettings: &Config{
Path: "httpupgrade", Path: "httpupgrade",
}, },
SocketSettings: &internet.SocketConfig{
TrustedXForwardedFor: []string{"X-Forwarded-For"},
},
}, func(conn stat.Connection) { }, func(conn stat.Connection) {
go func(c stat.Connection) { go func(c stat.Connection) {
defer c.Close() defer c.Close()
+4 -16
View File
@@ -80,24 +80,12 @@ func (s *server) upgrade(conn net.Conn) (stat.Connection, error) {
return nil, err return nil, err
} }
var forwardedAddrs []net.Address
if s.socketSettings != nil && len(s.socketSettings.TrustedXForwardedFor) > 0 {
for _, key := range s.socketSettings.TrustedXForwardedFor {
if len(req.Header.Values(key)) > 0 {
forwardedAddrs = http_proto.ParseXForwardedFor(req.Header)
break
}
}
} else {
forwardedAddrs = http_proto.ParseXForwardedFor(req.Header)
}
remoteAddr := conn.RemoteAddr() remoteAddr := conn.RemoteAddr()
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { var trustedXFF []string
remoteAddr = &net.TCPAddr{ if s.socketSettings != nil {
IP: forwardedAddrs[0].IP(), trustedXFF = s.socketSettings.TrustedXForwardedFor
Port: int(0),
}
} }
remoteAddr = http_proto.ApplyTrustedXForwardedFor(req.Header, trustedXFF, remoteAddr)
return stat.Connection(newConnection(conn, remoteAddr)), nil return stat.Connection(newConnection(conn, remoteAddr)), nil
} }
+23 -27
View File
@@ -126,6 +126,8 @@ func (c *client) dial(ctx context.Context) error {
switch c := conn.(type) { switch c := conn.(type) {
case *internet.PacketConnWrapper: case *internet.PacketConnWrapper:
pktConn = c.PacketConn pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default: default:
panic(reflect.TypeOf(c)) panic(reflect.TypeOf(c))
} }
@@ -135,36 +137,30 @@ func (c *client) dial(ctx context.Context) error {
var pktConn net.PacketConn var pktConn net.PacketConn
var udpAddr *net.UDPAddr var udpAddr *net.UDPAddr
var index int
if len(quicParams.UdpHop.Ports) > 0 { if len(quicParams.UdpHop.Ports) > 0 {
index := rand.Intn(len(quicParams.UdpHop.Ports)) index = rand.Intn(len(quicParams.UdpHop.Ports))
c.dest.Port = net.Port(quicParams.UdpHop.Ports[index]) c.dest.Port = net.Port(quicParams.UdpHop.Ports[index])
conn, err := internet.DialSystem(ctx, c.dest, c.socketConfig) }
if err != nil {
return errors.New("failed to dial to dest").Base(err) raw, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
} if err != nil {
switch c := conn.(type) { return errors.New("failed to dial to dest").Base(err)
case *internet.PacketConnWrapper: }
pktConn = c.PacketConn switch c := raw.(type) {
udpAddr = conn.RemoteAddr().(*net.UDPAddr) case *internet.PacketConnWrapper:
default: pktConn = c.PacketConn
panic(reflect.TypeOf(c)) udpAddr = raw.RemoteAddr().(*net.UDPAddr)
} case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
if len(quicParams.UdpHop.Ports) > 0 {
pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index) pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index)
} else {
conn, err := internet.DialSystem(ctx, c.dest, c.socketConfig)
if err != nil {
return errors.New("failed to dial to dest").Base(err)
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = c.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
} }
if c.udpmaskManager != nil { if c.udpmaskManager != nil {
+3 -13
View File
@@ -5,7 +5,6 @@ import (
"math/rand" "math/rand"
"net" "net"
"sync" "sync"
"syscall"
"time" "time"
"github.com/xtls/xray-core/transport/internet/finalmask" "github.com/xtls/xray-core/transport/internet/finalmask"
@@ -138,8 +137,8 @@ func (u *UdpHopPacketConn) hop() {
if u.closed { if u.closed {
return return
} }
u.addrIndex = rand.Intn(len(u.Addrs)) addrIndex := rand.Intn(len(u.Addrs))
newConn, err := u.ListenUDPFunc(u.Addrs[u.addrIndex].(*net.UDPAddr)) newConn, err := u.ListenUDPFunc(u.Addrs[addrIndex].(*net.UDPAddr))
if err != nil { if err != nil {
return return
} }
@@ -147,6 +146,7 @@ func (u *UdpHopPacketConn) hop() {
_ = u.prevConn.Close() _ = u.prevConn.Close()
} }
u.prevConn = u.currentConn u.prevConn = u.currentConn
u.addrIndex = addrIndex
u.currentConn = newConn u.currentConn = newConn
if !u.deadline.IsZero() { if !u.deadline.IsZero() {
_ = u.currentConn.SetDeadline(u.deadline) _ = u.currentConn.SetDeadline(u.deadline)
@@ -241,16 +241,6 @@ func (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error {
return u.currentConn.SetWriteDeadline(t) return u.currentConn.SetWriteDeadline(t)
} }
func (u *UdpHopPacketConn) SyscallConn() (syscall.RawConn, error) {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
sc, ok := u.currentConn.(syscall.Conn)
if !ok {
return nil, errors.New("not supported")
}
return sc.SyscallConn()
}
func ToAddrs(ip net.IP, ports []uint32) []net.Addr { func ToAddrs(ip net.IP, ports []uint32) []net.Addr {
var addrs []net.Addr var addrs []net.Addr
for _, port := range ports { for _, port := range ports {
+15 -1
View File
@@ -9,6 +9,7 @@ import (
"net/http/httptrace" "net/http/httptrace"
"sync" "sync"
"github.com/apernet/quic-go/http3"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -59,7 +60,11 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
if body != nil { if body != nil {
method = c.transportConfig.GetNormalizedUplinkHTTPMethod() // stream-up/one method = c.transportConfig.GetNormalizedUplinkHTTPMethod() // stream-up/one
} }
req, _ := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body) req, err := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body)
if err != nil {
errors.LogInfoInner(ctx, err, "failed to create HTTP request for "+url)
return nil, nil, nil, err
}
c.transportConfig.FillStreamRequest(req, sessionId, "") c.transportConfig.FillStreamRequest(req, sessionId, "")
wrc = &WaitReadCloser{Wait: make(chan struct{})} wrc = &WaitReadCloser{Wait: make(chan struct{})}
@@ -172,6 +177,15 @@ func (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, sessio
return nil return nil
} }
// HTTP/1.1 and HTTP/2 will close itself, we only handle HTTP/3 here
func (c *DefaultDialerClient) Close() error {
transport := c.client.Transport
if h3Transport, ok := transport.(*http3.Transport); ok {
h3Transport.Close()
}
return nil
}
type WaitReadCloser struct { type WaitReadCloser struct {
Wait chan struct{} Wait chan struct{}
io.ReadCloser io.ReadCloser
+70 -34
View File
@@ -4,6 +4,7 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
"math/rand/v2"
"net/http" "net/http"
"strings" "strings"
@@ -11,6 +12,7 @@ import (
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/crypto" "github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/utils" "github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
) )
@@ -131,26 +133,26 @@ func (c *Config) GetNormalizedUplinkHTTPMethod() string {
return c.UplinkHTTPMethod return c.UplinkHTTPMethod
} }
func (c *Config) GetNormalizedScMaxEachPostBytes() RangeConfig { func (c *Config) GetNormalizedScMaxEachPostBytes() *RangeConfig {
if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 { if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {
return RangeConfig{ return &RangeConfig{
From: 1000000, From: 1000000,
To: 1000000, To: 1000000,
} }
} }
return *c.ScMaxEachPostBytes return c.ScMaxEachPostBytes
} }
func (c *Config) GetNormalizedScMinPostsIntervalMs() RangeConfig { func (c *Config) GetNormalizedScMinPostsIntervalMs() *RangeConfig {
if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 { if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {
return RangeConfig{ return &RangeConfig{
From: 30, From: 30,
To: 30, To: 30,
} }
} }
return *c.ScMinPostsIntervalMs return c.ScMinPostsIntervalMs
} }
func (c *Config) GetNormalizedScMaxBufferedPosts() int { func (c *Config) GetNormalizedScMaxBufferedPosts() int {
@@ -161,27 +163,27 @@ func (c *Config) GetNormalizedScMaxBufferedPosts() int {
return int(c.ScMaxBufferedPosts) return int(c.ScMaxBufferedPosts)
} }
func (c *Config) GetNormalizedScStreamUpServerSecs() RangeConfig { func (c *Config) GetNormalizedScStreamUpServerSecs() *RangeConfig {
if c.ScStreamUpServerSecs == nil || c.ScStreamUpServerSecs.To == 0 { if c.ScStreamUpServerSecs == nil || c.ScStreamUpServerSecs.To == 0 {
return RangeConfig{ return &RangeConfig{
From: 20, From: 20,
To: 80, To: 80,
} }
} }
return *c.ScStreamUpServerSecs return c.ScStreamUpServerSecs
} }
func (c *Config) GetNormalizedUplinkChunkSize() RangeConfig { func (c *Config) GetNormalizedUplinkChunkSize() *RangeConfig {
if c.UplinkChunkSize == nil || c.UplinkChunkSize.To == 0 { if c.UplinkChunkSize == nil || c.UplinkChunkSize.To == 0 {
switch c.UplinkDataPlacement { switch c.UplinkDataPlacement {
case PlacementCookie: case PlacementCookie:
return RangeConfig{ return &RangeConfig{
From: 2 * 1024, // 2 KiB From: 2 * 1024, // 2 KiB
To: 3 * 1024, // 3 KiB To: 3 * 1024, // 3 KiB
} }
case PlacementHeader: case PlacementHeader:
return RangeConfig{ return &RangeConfig{
From: 3 * 1000, // 3 KB From: 3 * 1000, // 3 KB
To: 4 * 1000, // 4 KB To: 4 * 1000, // 4 KB
} }
@@ -189,13 +191,13 @@ func (c *Config) GetNormalizedUplinkChunkSize() RangeConfig {
return c.GetNormalizedScMaxEachPostBytes() return c.GetNormalizedScMaxEachPostBytes()
} }
} else if c.UplinkChunkSize.From < 64 { } else if c.UplinkChunkSize.From < 64 {
return RangeConfig{ return &RangeConfig{
From: 64, From: 64,
To: max(64, c.UplinkChunkSize.To), To: max(64, c.UplinkChunkSize.To),
} }
} }
return *c.UplinkChunkSize return c.UplinkChunkSize
} }
func (c *Config) GetNormalizedServerMaxHeaderBytes() int { func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
@@ -207,10 +209,10 @@ func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
} }
func (c *Config) GetNormalizedSessionPlacement() string { func (c *Config) GetNormalizedSessionPlacement() string {
if c.SessionPlacement == "" { if c.SessionIDPlacement == "" {
return PlacementPath return PlacementPath
} }
return c.SessionPlacement return c.SessionIDPlacement
} }
func (c *Config) GetNormalizedSeqPlacement() string { func (c *Config) GetNormalizedSeqPlacement() string {
@@ -228,8 +230,8 @@ func (c *Config) GetNormalizedUplinkDataPlacement() string {
} }
func (c *Config) GetNormalizedSessionKey() string { func (c *Config) GetNormalizedSessionKey() string {
if c.SessionKey != "" { if c.SessionIDKey != "" {
return c.SessionKey return c.SessionIDKey
} }
switch c.GetNormalizedSessionPlacement() { switch c.GetNormalizedSessionPlacement() {
case PlacementHeader: case PlacementHeader:
@@ -417,59 +419,59 @@ func (c *Config) ExtractMetaFromRequest(req *http.Request, path string) (session
return sessionId, seqStr return sessionId, seqStr
} }
func (m *XmuxConfig) GetNormalizedMaxConcurrency() RangeConfig { func (m *XmuxConfig) GetNormalizedMaxConcurrency() *RangeConfig {
if m.MaxConcurrency == nil { if m.MaxConcurrency == nil {
return RangeConfig{ return &RangeConfig{
From: 0, From: 0,
To: 0, To: 0,
} }
} }
return *m.MaxConcurrency return m.MaxConcurrency
} }
func (m *XmuxConfig) GetNormalizedMaxConnections() RangeConfig { func (m *XmuxConfig) GetNormalizedMaxConnections() *RangeConfig {
if m.MaxConnections == nil { if m.MaxConnections == nil {
return RangeConfig{ return &RangeConfig{
From: 0, From: 0,
To: 0, To: 0,
} }
} }
return *m.MaxConnections return m.MaxConnections
} }
func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() RangeConfig { func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() *RangeConfig {
if m.CMaxReuseTimes == nil { if m.CMaxReuseTimes == nil {
return RangeConfig{ return &RangeConfig{
From: 0, From: 0,
To: 0, To: 0,
} }
} }
return *m.CMaxReuseTimes return m.CMaxReuseTimes
} }
func (m *XmuxConfig) GetNormalizedHMaxRequestTimes() RangeConfig { func (m *XmuxConfig) GetNormalizedHMaxRequestTimes() *RangeConfig {
if m.HMaxRequestTimes == nil { if m.HMaxRequestTimes == nil {
return RangeConfig{ return &RangeConfig{
From: 0, From: 0,
To: 0, To: 0,
} }
} }
return *m.HMaxRequestTimes return m.HMaxRequestTimes
} }
func (m *XmuxConfig) GetNormalizedHMaxReusableSecs() RangeConfig { func (m *XmuxConfig) GetNormalizedHMaxReusableSecs() *RangeConfig {
if m.HMaxReusableSecs == nil { if m.HMaxReusableSecs == nil {
return RangeConfig{ return &RangeConfig{
From: 0, From: 0,
To: 0, To: 0,
} }
} }
return *m.HMaxReusableSecs return m.HMaxReusableSecs
} }
func init() { func init() {
@@ -478,10 +480,44 @@ func init() {
})) }))
} }
func (c RangeConfig) rand() int32 { func (c *RangeConfig) rand() int32 {
if c == nil {
return 0
}
return int32(crypto.RandBetween(int64(c.From), int64(c.To))) return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
} }
// predefined
var PredefinedTable = map[string]string{
"ALPHABET": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"Alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
"BASE36": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"Base62": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
"HEX": "0123456789ABCDEF",
"alphabet": "abcdefghijklmnopqrstuvwxyz",
"base36": "0123456789abcdefghijklmnopqrstuvwxyz",
"hex": "0123456789abcdef",
"number": "0123456789",
}
func (c *Config) GenerateSessionID() string {
length := c.SessionIDLength.rand()
table := c.SessionIDTable
if predefined, ok := PredefinedTable[table]; ok {
table = predefined
}
if table != "" && length > 0 {
id := make([]byte, length)
for i := range id {
id[i] = table[rand.N(len(table))]
}
return string(id)
} else {
uuid := uuid.New()
return uuid.String()
}
}
func appendToPath(path, value string) string { func appendToPath(path, value string) string {
if strings.HasSuffix(path, "/") { if strings.HasSuffix(path, "/") {
return path + value return path + value
+35 -18
View File
@@ -179,14 +179,16 @@ type Config struct {
XPaddingPlacement string `protobuf:"bytes,17,opt,name=xPaddingPlacement,proto3" json:"xPaddingPlacement,omitempty"` XPaddingPlacement string `protobuf:"bytes,17,opt,name=xPaddingPlacement,proto3" json:"xPaddingPlacement,omitempty"`
XPaddingMethod string `protobuf:"bytes,18,opt,name=xPaddingMethod,proto3" json:"xPaddingMethod,omitempty"` XPaddingMethod string `protobuf:"bytes,18,opt,name=xPaddingMethod,proto3" json:"xPaddingMethod,omitempty"`
UplinkHTTPMethod string `protobuf:"bytes,19,opt,name=uplinkHTTPMethod,proto3" json:"uplinkHTTPMethod,omitempty"` UplinkHTTPMethod string `protobuf:"bytes,19,opt,name=uplinkHTTPMethod,proto3" json:"uplinkHTTPMethod,omitempty"`
SessionPlacement string `protobuf:"bytes,20,opt,name=sessionPlacement,proto3" json:"sessionPlacement,omitempty"` SessionIDPlacement string `protobuf:"bytes,20,opt,name=sessionIDPlacement,proto3" json:"sessionIDPlacement,omitempty"`
SessionKey string `protobuf:"bytes,21,opt,name=sessionKey,proto3" json:"sessionKey,omitempty"` SessionIDKey string `protobuf:"bytes,21,opt,name=sessionIDKey,proto3" json:"sessionIDKey,omitempty"`
SeqPlacement string `protobuf:"bytes,22,opt,name=seqPlacement,proto3" json:"seqPlacement,omitempty"` SeqPlacement string `protobuf:"bytes,22,opt,name=seqPlacement,proto3" json:"seqPlacement,omitempty"`
SeqKey string `protobuf:"bytes,23,opt,name=seqKey,proto3" json:"seqKey,omitempty"` SeqKey string `protobuf:"bytes,23,opt,name=seqKey,proto3" json:"seqKey,omitempty"`
UplinkDataPlacement string `protobuf:"bytes,24,opt,name=uplinkDataPlacement,proto3" json:"uplinkDataPlacement,omitempty"` UplinkDataPlacement string `protobuf:"bytes,24,opt,name=uplinkDataPlacement,proto3" json:"uplinkDataPlacement,omitempty"`
UplinkDataKey string `protobuf:"bytes,25,opt,name=uplinkDataKey,proto3" json:"uplinkDataKey,omitempty"` UplinkDataKey string `protobuf:"bytes,25,opt,name=uplinkDataKey,proto3" json:"uplinkDataKey,omitempty"`
UplinkChunkSize *RangeConfig `protobuf:"bytes,26,opt,name=uplinkChunkSize,proto3" json:"uplinkChunkSize,omitempty"` UplinkChunkSize *RangeConfig `protobuf:"bytes,26,opt,name=uplinkChunkSize,proto3" json:"uplinkChunkSize,omitempty"`
ServerMaxHeaderBytes int32 `protobuf:"varint,27,opt,name=serverMaxHeaderBytes,proto3" json:"serverMaxHeaderBytes,omitempty"` ServerMaxHeaderBytes int32 `protobuf:"varint,27,opt,name=serverMaxHeaderBytes,proto3" json:"serverMaxHeaderBytes,omitempty"`
SessionIDTable string `protobuf:"bytes,28,opt,name=sessionIDTable,proto3" json:"sessionIDTable,omitempty"`
SessionIDLength *RangeConfig `protobuf:"bytes,29,opt,name=sessionIDLength,proto3" json:"sessionIDLength,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -354,16 +356,16 @@ func (x *Config) GetUplinkHTTPMethod() string {
return "" return ""
} }
func (x *Config) GetSessionPlacement() string { func (x *Config) GetSessionIDPlacement() string {
if x != nil { if x != nil {
return x.SessionPlacement return x.SessionIDPlacement
} }
return "" return ""
} }
func (x *Config) GetSessionKey() string { func (x *Config) GetSessionIDKey() string {
if x != nil { if x != nil {
return x.SessionKey return x.SessionIDKey
} }
return "" return ""
} }
@@ -410,6 +412,20 @@ func (x *Config) GetServerMaxHeaderBytes() int32 {
return 0 return 0
} }
func (x *Config) GetSessionIDTable() string {
if x != nil {
return x.SessionIDTable
}
return ""
}
func (x *Config) GetSessionIDLength() *RangeConfig {
if x != nil {
return x.SessionIDLength
}
return nil
}
var File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor var File_transport_internet_splithttp_config_proto protoreflect.FileDescriptor
const file_transport_internet_splithttp_config_proto_rawDesc = "" + const file_transport_internet_splithttp_config_proto_rawDesc = "" +
@@ -425,7 +441,7 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
"\x0ecMaxReuseTimes\x18\x03 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0ecMaxReuseTimes\x12Z\n" + "\x0ecMaxReuseTimes\x18\x03 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0ecMaxReuseTimes\x12Z\n" +
"\x10hMaxRequestTimes\x18\x04 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxRequestTimes\x12Z\n" + "\x10hMaxRequestTimes\x18\x04 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxRequestTimes\x12Z\n" +
"\x10hMaxReusableSecs\x18\x05 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxReusableSecs\x12*\n" + "\x10hMaxReusableSecs\x18\x05 \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x10hMaxReusableSecs\x12*\n" +
"\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xc2\v\n" + "\x10hKeepAlivePeriod\x18\x06 \x01(\x03R\x10hKeepAlivePeriod\"\xcc\f\n" +
"\x06Config\x12\x12\n" + "\x06Config\x12\x12\n" +
"\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" + "\x04host\x18\x01 \x01(\tR\x04host\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12\x12\n" + "\x04path\x18\x02 \x01(\tR\x04path\x12\x12\n" +
@@ -446,17 +462,17 @@ const file_transport_internet_splithttp_config_proto_rawDesc = "" +
"\x0exPaddingHeader\x18\x10 \x01(\tR\x0exPaddingHeader\x12,\n" + "\x0exPaddingHeader\x18\x10 \x01(\tR\x0exPaddingHeader\x12,\n" +
"\x11xPaddingPlacement\x18\x11 \x01(\tR\x11xPaddingPlacement\x12&\n" + "\x11xPaddingPlacement\x18\x11 \x01(\tR\x11xPaddingPlacement\x12&\n" +
"\x0exPaddingMethod\x18\x12 \x01(\tR\x0exPaddingMethod\x12*\n" + "\x0exPaddingMethod\x18\x12 \x01(\tR\x0exPaddingMethod\x12*\n" +
"\x10uplinkHTTPMethod\x18\x13 \x01(\tR\x10uplinkHTTPMethod\x12*\n" + "\x10uplinkHTTPMethod\x18\x13 \x01(\tR\x10uplinkHTTPMethod\x12.\n" +
"\x10sessionPlacement\x18\x14 \x01(\tR\x10sessionPlacement\x12\x1e\n" + "\x12sessionIDPlacement\x18\x14 \x01(\tR\x12sessionIDPlacement\x12\"\n" +
"\n" + "\fsessionIDKey\x18\x15 \x01(\tR\fsessionIDKey\x12\"\n" +
"sessionKey\x18\x15 \x01(\tR\n" +
"sessionKey\x12\"\n" +
"\fseqPlacement\x18\x16 \x01(\tR\fseqPlacement\x12\x16\n" + "\fseqPlacement\x18\x16 \x01(\tR\fseqPlacement\x12\x16\n" +
"\x06seqKey\x18\x17 \x01(\tR\x06seqKey\x120\n" + "\x06seqKey\x18\x17 \x01(\tR\x06seqKey\x120\n" +
"\x13uplinkDataPlacement\x18\x18 \x01(\tR\x13uplinkDataPlacement\x12$\n" + "\x13uplinkDataPlacement\x18\x18 \x01(\tR\x13uplinkDataPlacement\x12$\n" +
"\ruplinkDataKey\x18\x19 \x01(\tR\ruplinkDataKey\x12X\n" + "\ruplinkDataKey\x18\x19 \x01(\tR\ruplinkDataKey\x12X\n" +
"\x0fuplinkChunkSize\x18\x1a \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fuplinkChunkSize\x122\n" + "\x0fuplinkChunkSize\x18\x1a \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fuplinkChunkSize\x122\n" +
"\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x1a:\n" + "\x14serverMaxHeaderBytes\x18\x1b \x01(\x05R\x14serverMaxHeaderBytes\x12&\n" +
"\x0esessionIDTable\x18\x1c \x01(\tR\x0esessionIDTable\x12X\n" +
"\x0fsessionIDLength\x18\x1d \x01(\v2..xray.transport.internet.splithttp.RangeConfigR\x0fsessionIDLength\x1a:\n" +
"\fHeadersEntry\x12\x10\n" + "\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x85\x01\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x85\x01\n" +
@@ -496,11 +512,12 @@ var file_transport_internet_splithttp_config_proto_depIdxs = []int32{
1, // 10: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.XmuxConfig 1, // 10: xray.transport.internet.splithttp.Config.xmux:type_name -> xray.transport.internet.splithttp.XmuxConfig
4, // 11: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig 4, // 11: xray.transport.internet.splithttp.Config.downloadSettings:type_name -> xray.transport.internet.StreamConfig
0, // 12: xray.transport.internet.splithttp.Config.uplinkChunkSize:type_name -> xray.transport.internet.splithttp.RangeConfig 0, // 12: xray.transport.internet.splithttp.Config.uplinkChunkSize:type_name -> xray.transport.internet.splithttp.RangeConfig
13, // [13:13] is the sub-list for method output_type 0, // 13: xray.transport.internet.splithttp.Config.sessionIDLength:type_name -> xray.transport.internet.splithttp.RangeConfig
13, // [13:13] is the sub-list for method input_type 14, // [14:14] is the sub-list for method output_type
13, // [13:13] is the sub-list for extension type_name 14, // [14:14] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension extendee 14, // [14:14] is the sub-list for extension type_name
0, // [0:13] is the sub-list for field type_name 14, // [14:14] is the sub-list for extension extendee
0, // [0:14] is the sub-list for field type_name
} }
func init() { file_transport_internet_splithttp_config_proto_init() } func init() { file_transport_internet_splithttp_config_proto_init() }
+4 -2
View File
@@ -42,12 +42,14 @@ message Config {
string xPaddingPlacement = 17; string xPaddingPlacement = 17;
string xPaddingMethod = 18; string xPaddingMethod = 18;
string uplinkHTTPMethod = 19; string uplinkHTTPMethod = 19;
string sessionPlacement = 20; string sessionIDPlacement = 20;
string sessionKey = 21; string sessionIDKey = 21;
string seqPlacement = 22; string seqPlacement = 22;
string seqKey = 23; string seqKey = 23;
string uplinkDataPlacement = 24; string uplinkDataPlacement = 24;
string uplinkDataKey = 25; string uplinkDataKey = 25;
RangeConfig uplinkChunkSize = 26; RangeConfig uplinkChunkSize = 26;
int32 serverMaxHeaderBytes = 27; int32 serverMaxHeaderBytes = 27;
string sessionIDTable = 28;
RangeConfig sessionIDLength = 29;
} }
+29 -34
View File
@@ -24,7 +24,6 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/browser_dialer" "github.com/xtls/xray-core/transport/internet/browser_dialer"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion" "github.com/xtls/xray-core/transport/internet/hysteria/congestion"
@@ -210,6 +209,8 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
switch c := conn.(type) { switch c := conn.(type) {
case *internet.PacketConnWrapper: case *internet.PacketConnWrapper:
pktConn = c.PacketConn pktConn = c.PacketConn
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default: default:
panic(reflect.TypeOf(c)) panic(reflect.TypeOf(c))
} }
@@ -219,36 +220,30 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
var pktConn net.PacketConn var pktConn net.PacketConn
var udpAddr *net.UDPAddr var udpAddr *net.UDPAddr
var index int
if len(quicParams.UdpHop.Ports) > 0 { if len(quicParams.UdpHop.Ports) > 0 {
index := rand.Intn(len(quicParams.UdpHop.Ports)) index = rand.Intn(len(quicParams.UdpHop.Ports))
dest.Port = net.Port(quicParams.UdpHop.Ports[index]) dest.Port = net.Port(quicParams.UdpHop.Ports[index])
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings) }
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err) raw, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
} if err != nil {
switch c := conn.(type) { return nil, errors.New("failed to dial to dest").Base(err)
case *internet.PacketConnWrapper: }
pktConn = c.PacketConn switch c := raw.(type) {
udpAddr = conn.RemoteAddr().(*net.UDPAddr) case *internet.PacketConnWrapper:
default: pktConn = c.PacketConn
panic(reflect.TypeOf(c)) udpAddr = raw.RemoteAddr().(*net.UDPAddr)
} case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
if len(quicParams.UdpHop.Ports) > 0 {
pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index) pktConn = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer, pktConn, index)
} else {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("failed to dial to dest").Base(err)
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
udpAddr = c.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
udpAddr = &net.UDPAddr{IP: c.RemoteAddr().(*net.TCPAddr).IP, Port: c.RemoteAddr().(*net.TCPAddr).Port}
default:
panic(reflect.TypeOf(c))
}
} }
if streamSettings.UdpmaskManager != nil { if streamSettings.UdpmaskManager != nil {
@@ -264,6 +259,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
if err != nil { if err != nil {
return nil, err return nil, err
} }
context.AfterFunc(conn.Context(), func() { pktConn.Close() })
switch quicParams.Congestion { switch quicParams.Congestion {
case "reno": case "reno":
@@ -376,8 +372,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
sessionId := "" sessionId := ""
if mode != "stream-one" { if mode != "stream-one" {
sessionIdUuid := uuid.New() sessionId = transportConfiguration.GenerateSessionID()
sessionId = sessionIdUuid.String()
} }
errors.LogInfo(ctx, fmt.Sprintf("XHTTP is dialing to %s, mode %s, HTTP version %s, host %s", dest, mode, httpVersion, requestURL.Host)) errors.LogInfo(ctx, fmt.Sprintf("XHTTP is dialing to %s, mode %s, HTTP version %s, host %s", dest, mode, httpVersion, requestURL.Host))
@@ -431,10 +426,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
} }
if xmuxClient != nil { if xmuxClient != nil {
xmuxClient.OpenUsage.Add(1) xmuxClient.AddRunning()
} }
if xmuxClient2 != nil && xmuxClient2 != xmuxClient { if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
xmuxClient2.OpenUsage.Add(1) xmuxClient2.AddRunning()
} }
var closed atomic.Int32 var closed atomic.Int32
@@ -446,10 +441,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
return return
} }
if xmuxClient != nil { if xmuxClient != nil {
xmuxClient.OpenUsage.Add(-1) xmuxClient.DoneRunning()
} }
if xmuxClient2 != nil && xmuxClient2 != xmuxClient { if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
xmuxClient2.OpenUsage.Add(-1) xmuxClient2.DoneRunning()
} }
}, },
} }
+7 -18
View File
@@ -146,6 +146,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
writer.WriteHeader(http.StatusBadRequest) writer.WriteHeader(http.StatusBadRequest)
return return
} }
obfsPaddingAccepted := h.config.XPaddingObfsMode && paddingValue != ""
sessionId, seqStr := h.config.ExtractMetaFromRequest(request, h.path) sessionId, seqStr := h.config.ExtractMetaFromRequest(request, h.path)
@@ -155,17 +156,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
return return
} }
var forwardedAddrs []net.Address
if h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {
for _, key := range h.socketSettings.TrustedXForwardedFor {
if len(request.Header.Values(key)) > 0 {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
break
}
}
} else {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
}
var remoteAddr net.Addr var remoteAddr net.Addr
var err error var err error
remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr) remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr)
@@ -181,12 +171,11 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
Port: remoteAddr.(*net.TCPAddr).Port, Port: remoteAddr.(*net.TCPAddr).Port,
} }
} }
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { var trustedXFF []string
remoteAddr = &net.TCPAddr{ if h.socketSettings != nil {
IP: forwardedAddrs[0].IP(), trustedXFF = h.socketSettings.TrustedXForwardedFor
Port: 0,
}
} }
remoteAddr = http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr)
var currentSession *httpSession var currentSession *httpSession
if sessionId != "" { if sessionId != "" {
@@ -227,8 +216,8 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
writer.Header().Set("Cache-Control", "no-store") writer.Header().Set("Cache-Control", "no-store")
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs() scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs()
referrer := request.Header.Get("Referer") hasLegacyRefererCompatMarker := request.Header.Get("Referer") != ""
if referrer != "" && scStreamUpServerSecs.To > 0 { if (hasLegacyRefererCompatMarker || obfsPaddingAccepted) && scStreamUpServerSecs.To > 0 {
go func() { go func() {
for { for {
_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand()))) _, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand())))
+23 -3
View File
@@ -8,6 +8,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
) )
@@ -17,10 +18,27 @@ type XmuxConn interface {
type XmuxClient struct { type XmuxClient struct {
XmuxConn XmuxConn XmuxConn XmuxConn
OpenUsage atomic.Int32 Running atomic.Int32
leftUsage int32 leftUsage int32
LeftRequests atomic.Int32 LeftRequests atomic.Int32
UnreusableAt time.Time UnreusableAt time.Time
NotUsed atomic.Bool
}
func (c *XmuxClient) AddRunning() {
c.Running.Add(1)
}
func (c *XmuxClient) DoneRunning() {
c.Running.Add(-1)
c.maybeClose()
}
// close the XmuxConn if it is not used and has no running requests
func (c *XmuxClient) maybeClose() {
if c.NotUsed.Load() && c.Running.Load() <= 0 {
common.Close(c.XmuxConn)
}
} }
type XmuxManager struct { type XmuxManager struct {
@@ -68,10 +86,12 @@ func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when l
xmuxClient.LeftRequests.Load() <= 0 || xmuxClient.LeftRequests.Load() <= 0 ||
(xmuxClient.UnreusableAt != time.Time{} && time.Now().After(xmuxClient.UnreusableAt)) { (xmuxClient.UnreusableAt != time.Time{} && time.Now().After(xmuxClient.UnreusableAt)) {
errors.LogDebug(ctx, "XMUX: removing xmuxClient, IsClosed() = ", xmuxClient.XmuxConn.IsClosed(), errors.LogDebug(ctx, "XMUX: removing xmuxClient, IsClosed() = ", xmuxClient.XmuxConn.IsClosed(),
", OpenUsage = ", xmuxClient.OpenUsage.Load(), ", Running = ", xmuxClient.Running.Load(),
", leftUsage = ", xmuxClient.leftUsage, ", leftUsage = ", xmuxClient.leftUsage,
", LeftRequests = ", xmuxClient.LeftRequests.Load(), ", LeftRequests = ", xmuxClient.LeftRequests.Load(),
", UnreusableAt = ", xmuxClient.UnreusableAt) ", UnreusableAt = ", xmuxClient.UnreusableAt)
xmuxClient.NotUsed.Store(true)
xmuxClient.maybeClose()
m.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...) m.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...)
} else { } else {
i++ i++
@@ -91,7 +111,7 @@ func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when l
xmuxClients := make([]*XmuxClient, 0) xmuxClients := make([]*XmuxClient, 0)
if m.concurrency > 0 { if m.concurrency > 0 {
for _, xmuxClient := range m.xmuxClients { for _, xmuxClient := range m.xmuxClients {
if xmuxClient.OpenUsage.Load() < m.concurrency { if xmuxClient.Running.Load() < m.concurrency {
xmuxClients = append(xmuxClients, xmuxClient) xmuxClients = append(xmuxClients, xmuxClient)
} }
} }
+2 -2
View File
@@ -63,7 +63,7 @@ func TestMaxConcurrency(t *testing.T) {
xmuxClients := make(map[interface{}]struct{}) xmuxClients := make(map[interface{}]struct{})
for i := 0; i < 64; i++ { for i := 0; i < 64; i++ {
xmuxClient := xmuxManager.GetXmuxClient(context.Background()) xmuxClient := xmuxManager.GetXmuxClient(context.Background())
xmuxClient.OpenUsage.Add(1) xmuxClient.AddRunning()
xmuxClients[xmuxClient] = struct{}{} xmuxClients[xmuxClient] = struct{}{}
} }
@@ -82,7 +82,7 @@ func TestDefault(t *testing.T) {
xmuxClients := make(map[interface{}]struct{}) xmuxClients := make(map[interface{}]struct{})
for i := 0; i < 64; i++ { for i := 0; i < 64; i++ {
xmuxClient := xmuxManager.GetXmuxClient(context.Background()) xmuxClient := xmuxManager.GetXmuxClient(context.Background())
xmuxClient.OpenUsage.Add(1) xmuxClient.AddRunning()
xmuxClients[xmuxClient] = struct{}{} xmuxClients[xmuxClient] = struct{}{}
} }
@@ -88,6 +88,9 @@ func TestDialWithRemoteAddr(t *testing.T) {
ProtocolSettings: &Config{ ProtocolSettings: &Config{
Path: "sh", Path: "sh",
}, },
SocketSettings: &internet.SocketConfig{
TrustedXForwardedFor: []string{"X-Forwarded-For"},
},
}, func(conn stat.Connection) { }, func(conn stat.Connection) {
go func(c stat.Connection) { go func(c stat.Connection) {
defer c.Close() defer c.Close()
+3 -3
View File
@@ -176,15 +176,15 @@ func ApplyPaddingToQuery(u *url.URL, key, value string) {
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
} }
func (c *Config) GetNormalizedXPaddingBytes() RangeConfig { func (c *Config) GetNormalizedXPaddingBytes() *RangeConfig {
if c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 { if c.XPaddingBytes == nil || c.XPaddingBytes.To == 0 {
return RangeConfig{ return &RangeConfig{
From: 100, From: 100,
To: 1000, To: 1000,
} }
} }
return *c.XPaddingBytes return c.XPaddingBytes
} }
func (c *Config) ApplyXPaddingToHeader(h http.Header, config XPaddingConfig) { func (c *Config) ApplyXPaddingToHeader(h http.Header, config XPaddingConfig) {
+3
View File
@@ -234,6 +234,9 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
// h2c: in config is just for claim
// change scheme to https and expect outbound to handle TLS (freedom + tlsSetting)
req.URL.Scheme = "https"
req.Header.Set("Accept", "application/dns-message") req.Header.Set("Accept", "application/dns-message")
req.Header.Set("Content-Type", "application/dns-message") req.Header.Set("Content-Type", "application/dns-message")
utils.TryDefaultHeadersWith(req.Header, "fetch") utils.TryDefaultHeadersWith(req.Header, "fetch")
+4 -16
View File
@@ -65,24 +65,12 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
return return
} }
var forwardedAddrs []net.Address
if h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {
for _, key := range h.socketSettings.TrustedXForwardedFor {
if len(request.Header.Values(key)) > 0 {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
break
}
}
} else {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
}
remoteAddr := conn.RemoteAddr() remoteAddr := conn.RemoteAddr()
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { var trustedXFF []string
remoteAddr = &net.TCPAddr{ if h.socketSettings != nil {
IP: forwardedAddrs[0].IP(), trustedXFF = h.socketSettings.TrustedXForwardedFor
Port: int(0),
}
} }
remoteAddr = http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr)
h.ln.addConn(NewConnection(conn, remoteAddr, extraReader, h.ln.config.HeartbeatPeriod)) h.ln.addConn(NewConnection(conn, remoteAddr, extraReader, h.ln.config.HeartbeatPeriod))
} }
+3
View File
@@ -79,6 +79,9 @@ func TestDialWithRemoteAddr(t *testing.T) {
ProtocolSettings: &Config{ ProtocolSettings: &Config{
Path: "ws", Path: "ws",
}, },
SocketSettings: &internet.SocketConfig{
TrustedXForwardedFor: []string{"X-Forwarded-For"},
},
}, func(conn stat.Connection) { }, func(conn stat.Connection) {
go func(c stat.Connection) { go func(c stat.Connection) {
defer c.Close() defer c.Close()