mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-25 08:08:00 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aabbbeb0fa |
@@ -1 +0,0 @@
|
|||||||
powershell.exe -ExecutionPolicy Bypass -File ".\xray_no_window.ps1"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0
|
|
||||||
@@ -37,7 +37,6 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
|
|||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
# Create log files
|
# Create log files
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
|
|||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
|
|
||||||
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
|
||||||
|
|
||||||
# Create log files
|
# Create log files
|
||||||
|
|||||||
@@ -64,16 +64,8 @@ jobs:
|
|||||||
echo "Latest: '$LATEST'."
|
echo "Latest: '$LATEST'."
|
||||||
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
||||||
|
|
||||||
NEWEST=false
|
|
||||||
if [[ "${{ github.event_name }}" == "release" ]]; then
|
|
||||||
NEWEST=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Newest: '$NEWEST'."
|
|
||||||
echo "NEWEST=$NEWEST" >>${GITHUB_ENV}
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v4
|
uses: docker/setup-qemu-action@v4
|
||||||
@@ -82,7 +74,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v4.6.0
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -132,13 +124,6 @@ jobs:
|
|||||||
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${{ env.NEWEST }}" == "true" ]]; then
|
|
||||||
echo "Adding 'pre-release' tag to manifest: '${{ env.FULL_IMAGE_NAME }}:pre-release'."
|
|
||||||
docker buildx imagetools create \
|
|
||||||
--tag ${{ env.FULL_IMAGE_NAME }}:pre-release \
|
|
||||||
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||||
@@ -146,7 +131,3 @@ jobs:
|
|||||||
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
||||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
|
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${{ env.NEWEST }}" == "true" ]]; then
|
|
||||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:pre-release
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -11,16 +11,15 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
check-assets:
|
check-assets:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -76,14 +75,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos}}
|
GOOS: ${{ matrix.goos}}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Show workflow information
|
- name: Show workflow information
|
||||||
run: |
|
run: |
|
||||||
@@ -92,7 +90,7 @@ jobs:
|
|||||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -119,13 +117,13 @@ jobs:
|
|||||||
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||||
|
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -134,17 +132,15 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv -f resources/geo* build_assets/
|
mv -f resources/geo* build_assets/
|
||||||
if [[ ${GOOS} == 'windows' ]]; then
|
if [[ ${GOOS} == 'windows' ]]; then
|
||||||
cp .github/build/windows/* build_assets/
|
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||||
fi
|
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||||
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
|
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Copy README.md & LICENSE
|
- name: Copy README.md & LICENSE
|
||||||
|
|||||||
@@ -11,16 +11,15 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
check-assets:
|
check-assets:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -162,7 +161,6 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
@@ -170,7 +168,7 @@ jobs:
|
|||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up NDK
|
- name: Set up NDK
|
||||||
if: matrix.goos == 'android'
|
if: matrix.goos == 'android'
|
||||||
@@ -193,7 +191,7 @@ jobs:
|
|||||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -210,9 +208,6 @@ jobs:
|
|||||||
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||||
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
|
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
|
||||||
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||||
elif [[ ${GOOS} == 'android' ]]; then
|
|
||||||
echo 'Building Xray for Android...'
|
|
||||||
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid= -checklinkname=0" -v ./main
|
|
||||||
else
|
else
|
||||||
echo 'Building Xray...'
|
echo 'Building Xray...'
|
||||||
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
|
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
|
||||||
@@ -225,14 +220,14 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|
||||||
- name: Restore Wintun Cache
|
- name: Restore Wintun Cache
|
||||||
if: matrix.goos == 'windows'
|
if: matrix.goos == 'windows'
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -241,10 +236,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv -f resources/geo* build_assets/
|
mv -f resources/geo* build_assets/
|
||||||
if [[ ${GOOS} == 'windows' ]]; then
|
if [[ ${GOOS} == 'windows' ]]; then
|
||||||
cp .github/build/windows/* build_assets/
|
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||||
fi
|
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||||
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
|
||||||
@@ -254,7 +247,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
|
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Copy README.md & LICENSE
|
- name: Copy README.md & LICENSE
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Save Geodat Cache
|
- name: Save Geodat Cache
|
||||||
uses: actions/cache/save@v6
|
uses: actions/cache/save@v5
|
||||||
if: ${{ steps.update.outputs.unhit }}
|
if: ${{ steps.update.outputs.unhit }}
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
@@ -68,12 +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@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-wintun-
|
key: xray-wintun-
|
||||||
@@ -99,6 +96,7 @@ 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
|
||||||
@@ -115,21 +113,16 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
if [[ "$missing" == true ]]; then
|
if [[ "$missing" == true ]]; then
|
||||||
FILENAME=wintun.zip
|
FILENAME=wintun.zip
|
||||||
DOWNLOAD_FILE=wintun-${ASSETVER}.zip
|
DOWNLOAD_FILE=wintun-0.14.1.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}"
|
||||||
if [[ "$(sha256sum "./${FILENAME}" | awk -F ' ' '{print $1}')" == "${ASSETHASH}" ]]; then
|
echo -e "Unpacking wintun..."
|
||||||
echo -e "Unpacking wintun..."
|
unzip -u ${FILENAME} -d resources/
|
||||||
unzip -u ${FILENAME} -d resources/
|
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||||
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
|
||||||
uses: actions/cache/save@v6
|
uses: actions/cache/save@v5
|
||||||
if: ${{ steps.update.outputs.unhit }}
|
if: ${{ steps.update.outputs.unhit }}
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Tests and Checkings
|
name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -8,10 +8,9 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
check-assets:
|
check-assets:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
steps:
|
steps:
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
@@ -37,10 +36,9 @@ jobs:
|
|||||||
|
|
||||||
check-proto:
|
check-proto:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
- 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
|
||||||
@@ -52,26 +50,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
check-format:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Checkout codebase
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v7
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- name: Check Format
|
|
||||||
run: go run ./infra/vformat/main.go -mode check -pwd ./
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
needs: check-assets
|
needs: check-assets
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -81,14 +61,14 @@ jobs:
|
|||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore Geodat Cache
|
- name: Restore Geodat Cache
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: resources
|
path: resources
|
||||||
key: xray-geodat-
|
key: xray-geodat-
|
||||||
|
|||||||
@@ -73,7 +73,6 @@
|
|||||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||||
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||||
- Magisk
|
- Magisk
|
||||||
- [Magic_V2Ray](https://github.com/vincentng295/Magic_V2Ray)
|
|
||||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||||
- Homebrew
|
- Homebrew
|
||||||
- `brew install xray`
|
- `brew install xray`
|
||||||
@@ -146,8 +145,6 @@
|
|||||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||||
- [OneXray](https://github.com/OneXray/OneXray)
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
- HarmonyOS
|
|
||||||
- [Hey](https://github.com/popsiclelmlm/Hey)
|
|
||||||
|
|
||||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||||
|
|
||||||
@@ -165,7 +162,6 @@
|
|||||||
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
|
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
|
||||||
- [xtlsapi](https://github.com/hiddify/xtlsapi)
|
- [xtlsapi](https://github.com/hiddify/xtlsapi)
|
||||||
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
||||||
- [flutter_vless](https://github.com/XIIIFOX/flutter_vless)
|
|
||||||
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
|
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
|
||||||
- [xray-api](https://github.com/XVGuardian/xray-api)
|
- [xray-api](https://github.com/XVGuardian/xray-api)
|
||||||
- [XrayR](https://github.com/XrayR-project/XrayR)
|
- [XrayR](https://github.com/XrayR-project/XrayR)
|
||||||
@@ -187,27 +183,6 @@
|
|||||||
- [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)
|
||||||
@@ -231,13 +206,6 @@ Make sure that you are using the same Go version, and remember to set the git co
|
|||||||
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
|
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
|
||||||
```
|
```
|
||||||
|
|
||||||
For Android:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GOOS=android GOARCH=arm64 CGO_ENABLED=1 CC=/path/to/aarch64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
|
|
||||||
GOOS=android GOARCH=amd64 CGO_ENABLED=1 CC=/path/to/x86_64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
|
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ package commander
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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/signal/done"
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
core "github.com/xtls/xray-core/core"
|
core "github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,15 +69,16 @@ func (c *Commander) Start() error {
|
|||||||
}
|
}
|
||||||
c.Unlock()
|
c.Unlock()
|
||||||
|
|
||||||
listen := func(listener net.Listener) {
|
var listen = func(listener net.Listener) {
|
||||||
if err := c.server.Serve(listener); err != nil {
|
if err := c.server.Serve(listener); err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
|
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if len(c.listen) > 0 {
|
if len(c.listen) > 0 {
|
||||||
var addr net.Addr
|
var addr net.Addr
|
||||||
|
|
||||||
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
|
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
|
||||||
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
|
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
|
||||||
} else {
|
} else {
|
||||||
@@ -88,7 +89,7 @@ func (c *Commander) Start() error {
|
|||||||
}
|
}
|
||||||
addr = tcpAddr
|
addr = tcpAddr
|
||||||
}
|
}
|
||||||
l, err := internet.ListenSystem(context.Background(), addr, nil)
|
l, err := internet.ListenSystem(context.Background(), addr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
|||||||
p := d.policy.ForLevel(user.Level)
|
p := d.policy.ForLevel(user.Level)
|
||||||
if p.Stats.UserUplink {
|
if p.Stats.UserUplink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||||
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
|
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||||
inboundLink.Writer = &SizeStatWriter{
|
inboundLink.Writer = &SizeStatWriter{
|
||||||
Counter: c,
|
Counter: c,
|
||||||
Writer: inboundLink.Writer,
|
Writer: inboundLink.Writer,
|
||||||
@@ -171,7 +171,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
|||||||
}
|
}
|
||||||
if p.Stats.UserDownlink {
|
if p.Stats.UserDownlink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||||
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
|
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||||
outboundLink.Writer = &SizeStatWriter{
|
outboundLink.Writer = &SizeStatWriter{
|
||||||
Counter: c,
|
Counter: c,
|
||||||
Writer: outboundLink.Writer,
|
Writer: outboundLink.Writer,
|
||||||
@@ -200,13 +200,13 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
|
|||||||
p := policyManager.ForLevel(user.Level)
|
p := policyManager.ForLevel(user.Level)
|
||||||
if p.Stats.UserUplink {
|
if p.Stats.UserUplink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||||
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
|
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||||
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
|
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.Stats.UserDownlink {
|
if p.Stats.UserDownlink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||||
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
|
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||||
link.Writer = &SizeStatWriter{
|
link.Writer = &SizeStatWriter{
|
||||||
Counter: c,
|
Counter: c,
|
||||||
Writer: link.Writer,
|
Writer: link.Writer,
|
||||||
@@ -223,7 +223,7 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
|
|||||||
|
|
||||||
func trackOnlineIP(ctx context.Context, sm stats.Manager, email, ip string) {
|
func trackOnlineIP(ctx context.Context, sm stats.Manager, email, ip string) {
|
||||||
name := "user>>>" + email + ">>>online"
|
name := "user>>>" + email + ">>>online"
|
||||||
if om, _ := sm.GetOrRegisterOnlineMap(name); om != nil {
|
if om, _ := stats.GetOrRegisterOnlineMap(sm, name); om != nil {
|
||||||
om.AddIP(ip)
|
om.AddIP(ip)
|
||||||
context.AfterFunc(ctx, func() { om.RemoveIP(ip) })
|
context.AfterFunc(ctx, func() { om.RemoveIP(ip) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,8 +139,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
|||||||
|
|
||||||
if lenAfter == 0 {
|
if lenAfter == 0 {
|
||||||
if c.highWatermark >= minSizeForEmptyRebuild {
|
if c.highWatermark >= minSizeForEmptyRebuild {
|
||||||
errors.LogDebug(
|
errors.LogDebug(context.Background(), c.name,
|
||||||
context.Background(), c.name,
|
|
||||||
" rebuilding empty cache map to reclaim memory.",
|
" rebuilding empty cache map to reclaim memory.",
|
||||||
" size_before_cleanup=", lenBefore,
|
" size_before_cleanup=", lenBefore,
|
||||||
" peak_size_before_rebuild=", c.highWatermark,
|
" peak_size_before_rebuild=", c.highWatermark,
|
||||||
@@ -154,8 +153,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
|||||||
|
|
||||||
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
|
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
|
||||||
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
|
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
|
||||||
errors.LogDebug(
|
errors.LogDebug(context.Background(), c.name,
|
||||||
context.Background(), c.name,
|
|
||||||
" shrinking cache map to reclaim memory.",
|
" shrinking cache map to reclaim memory.",
|
||||||
" new_size=", lenAfter,
|
" new_size=", lenAfter,
|
||||||
" peak_size_before_shrink=", c.highWatermark,
|
" peak_size_before_shrink=", c.highWatermark,
|
||||||
@@ -167,6 +165,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
|||||||
c.highWatermark = lenAfter
|
c.highWatermark = lenAfter
|
||||||
go c.migrate()
|
go c.migrate()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type migrationEntry struct {
|
type migrationEntry struct {
|
||||||
|
|||||||
+2
-2
@@ -86,7 +86,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
return nil, errors.New("failed to create hosts").Base(err)
|
return nil, errors.New("failed to create hosts").Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultTag := config.Tag
|
var defaultTag = config.Tag
|
||||||
if len(config.Tag) == 0 {
|
if len(config.Tag) == 0 {
|
||||||
defaultTag = generateRandomTag()
|
defaultTag = generateRandomTag()
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
|||||||
serveExpiredTTL = *ns.ServeExpiredTTL
|
serveExpiredTTL = *ns.ServeExpiredTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
tag := defaultTag
|
var tag = defaultTag
|
||||||
if len(ns.Tag) > 0 {
|
if len(ns.Tag) > 0 {
|
||||||
tag = ns.Tag
|
tag = ns.Tag
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-16
@@ -127,20 +127,15 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
|
|||||||
return opt
|
return opt
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) ([]*dnsRequest, error) {
|
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
|
||||||
name, err := dnsmessage.NewName(domain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
qA := dnsmessage.Question{
|
qA := dnsmessage.Question{
|
||||||
Name: name,
|
Name: dnsmessage.MustNewName(domain),
|
||||||
Type: dnsmessage.TypeA,
|
Type: dnsmessage.TypeA,
|
||||||
Class: dnsmessage.ClassINET,
|
Class: dnsmessage.ClassINET,
|
||||||
}
|
}
|
||||||
|
|
||||||
qAAAA := dnsmessage.Question{
|
qAAAA := dnsmessage.Question{
|
||||||
Name: name,
|
Name: dnsmessage.MustNewName(domain),
|
||||||
Type: dnsmessage.TypeAAAA,
|
Type: dnsmessage.TypeAAAA,
|
||||||
Class: dnsmessage.ClassINET,
|
Class: dnsmessage.ClassINET,
|
||||||
}
|
}
|
||||||
@@ -180,7 +175,7 @@ func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() ui
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return reqs, nil
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseResponse parses DNS answers from the returned payload
|
// parseResponse parses DNS answers from the returned payload
|
||||||
@@ -198,14 +193,9 @@ func parseResponse(payload []byte) (*IPRecord, error) {
|
|||||||
ipRecord := &IPRecord{
|
ipRecord := &IPRecord{
|
||||||
ReqID: h.ID,
|
ReqID: h.ID,
|
||||||
RCode: h.RCode,
|
RCode: h.RCode,
|
||||||
|
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
|
||||||
RawHeader: &h,
|
RawHeader: &h,
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
// set to default TTL if no valid TTL is found
|
|
||||||
if ipRecord.Expire.IsZero() {
|
|
||||||
ipRecord.Expire = now.Add(time.Second * dns_feature.DefaultTTL)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
L:
|
L:
|
||||||
for {
|
for {
|
||||||
@@ -222,7 +212,7 @@ L:
|
|||||||
ttl = 1
|
ttl = 1
|
||||||
}
|
}
|
||||||
expire := now.Add(time.Duration(ttl) * time.Second)
|
expire := now.Add(time.Duration(ttl) * time.Second)
|
||||||
if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
|
if ipRecord.Expire.After(expire) {
|
||||||
ipRecord.Expire = expire
|
ipRecord.Expire = expire
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -25,8 +24,7 @@ func Test_parseResponse(t *testing.T) {
|
|||||||
|
|
||||||
ans = new(dns.Msg)
|
ans = new(dns.Msg)
|
||||||
ans.Id = 1
|
ans.Id = 1
|
||||||
ans.Answer = append(
|
ans.Answer = append(ans.Answer,
|
||||||
ans.Answer,
|
|
||||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
||||||
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
|
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
|
||||||
@@ -36,8 +34,7 @@ func Test_parseResponse(t *testing.T) {
|
|||||||
|
|
||||||
ans = new(dns.Msg)
|
ans = new(dns.Msg)
|
||||||
ans.Id = 2
|
ans.Id = 2
|
||||||
ans.Answer = append(
|
ans.Answer = append(ans.Answer,
|
||||||
ans.Answer,
|
|
||||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
||||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||||
@@ -134,15 +131,10 @@ func Test_buildReqMsgs(t *testing.T) {
|
|||||||
IPv6Enable: false,
|
IPv6Enable: false,
|
||||||
FakeEnable: false,
|
FakeEnable: false,
|
||||||
}, nil}, 0},
|
}, nil}, 0},
|
||||||
{"name too long", args{strings.Repeat("a", 256), dns_feature.IPOption{
|
|
||||||
IPv4Enable: true,
|
|
||||||
IPv6Enable: true,
|
|
||||||
FakeEnable: false,
|
|
||||||
}, nil}, 0},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got, _ := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
|
if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
|
||||||
t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
|
t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -129,16 +129,15 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFakeDNSMulti(t *testing.T) {
|
func TestFakeDNSMulti(t *testing.T) {
|
||||||
fakeMulti, err := NewFakeDNSHolderMulti(
|
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
|
||||||
&FakeDnsPoolMulti{
|
Pools: []*FakeDnsPool{{
|
||||||
Pools: []*FakeDnsPool{{
|
IpPool: "240.0.0.0/12",
|
||||||
IpPool: "240.0.0.0/12",
|
LruSize: 256,
|
||||||
LruSize: 256,
|
}, {
|
||||||
}, {
|
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
||||||
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
LruSize: 256,
|
||||||
LruSize: 256,
|
}},
|
||||||
}},
|
},
|
||||||
},
|
|
||||||
)
|
)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ func NewClient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutMs := 4000 * time.Millisecond
|
var timeoutMs = 4000 * time.Millisecond
|
||||||
if ns.TimeoutMs > 0 {
|
if ns.TimeoutMs > 0 {
|
||||||
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,32 +137,14 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
|
|||||||
if s.Name()+"." == "DOH//"+fqdn {
|
if s.Name()+"." == "DOH//"+fqdn {
|
||||||
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
|
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
|
||||||
if noResponseErrCh != nil {
|
if noResponseErrCh != nil {
|
||||||
err := errors.New("tries to resolve itself!", s.Name())
|
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
|
||||||
if option.IPv4Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
if option.IPv6Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
|
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
|
||||||
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
|
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
|
||||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
|
||||||
if noResponseErrCh != nil {
|
|
||||||
if option.IPv4Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
if option.IPv6Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type LocalNameServer struct {
|
|||||||
|
|
||||||
// QueryIP implements Server.
|
// QueryIP implements Server.
|
||||||
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
|
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
ips, ttl, err = s.client.LookupIP(domain, option)
|
ips, ttl, err = s.client.LookupIP(domain, option)
|
||||||
|
|
||||||
|
|||||||
@@ -78,19 +78,7 @@ func (s *QUICNameServer) getCacheController() *CacheController { return s.cacheC
|
|||||||
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
||||||
|
|
||||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
|
||||||
if noResponseErrCh != nil {
|
|
||||||
if option.IPv4Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
if option.IPv6Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
|||||||
@@ -113,19 +113,7 @@ func (s *TCPNameServer) getCacheController() *CacheController {
|
|||||||
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||||
|
|
||||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
|
||||||
if noResponseErrCh != nil {
|
|
||||||
if option.IPv4Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
if option.IPv6Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
if d, ok := ctx.Deadline(); ok {
|
if d, ok := ctx.Deadline(); ok {
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
|||||||
newReq.msg = &newMsg
|
newReq.msg = &newMsg
|
||||||
s.addPendingRequest(&newReq)
|
s.addPendingRequest(&newReq)
|
||||||
b, _ := dns.PackMessage(newReq.msg)
|
b, _ := dns.PackMessage(newReq.msg)
|
||||||
|
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||||
|
b.UDP = ©Dest
|
||||||
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
|
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -161,19 +163,7 @@ func (s *ClassicNameServer) getCacheController() *CacheController {
|
|||||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||||
|
|
||||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||||
if err != nil {
|
|
||||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
|
||||||
if noResponseErrCh != nil {
|
|
||||||
if option.IPv4Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
if option.IPv6Enable {
|
|
||||||
noResponseErrCh <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
udpReq := &udpDnsRequest{
|
udpReq := &udpDnsRequest{
|
||||||
@@ -189,6 +179,8 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||||
|
b.UDP = ©Dest
|
||||||
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-83
@@ -2,7 +2,6 @@ package geodata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
go_errors "errors"
|
go_errors "errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -10,7 +9,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
@@ -18,7 +16,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/utils"
|
"github.com/xtls/xray-core/common/utils"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
"github.com/xtls/xray-core/transport/internet/tagged"
|
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const idleTimeout = 30 * time.Second
|
const idleTimeout = 30 * time.Second
|
||||||
@@ -29,9 +26,8 @@ type stage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type downloader struct {
|
type downloader struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
httpClient *http.Client
|
client *http.Client
|
||||||
httpsClient *http.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type idleConn struct {
|
type idleConn struct {
|
||||||
@@ -57,84 +53,52 @@ func (c *idleConn) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
||||||
return &downloader{
|
return &downloader{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
httpClient: newClient(ctx, dispatcher, outbound, false),
|
client: newClient(ctx, dispatcher, outbound),
|
||||||
httpsClient: newClient(ctx, dispatcher, outbound, true),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
|
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client {
|
||||||
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
return &http.Client{
|
||||||
var conn net.Conn
|
Transport: &http.Transport{
|
||||||
err := task.Run(ctx, func() error {
|
Proxy: nil,
|
||||||
if tagged.Dialer == nil {
|
DisableKeepAlives: true,
|
||||||
return errors.New("tagged dialer is not initialized")
|
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
}
|
var conn net.Conn
|
||||||
dest, err := net.ParseDestination(network + ":" + address)
|
err := task.Run(ctx, func() error {
|
||||||
if err != nil {
|
if tagged.Dialer == nil {
|
||||||
return errors.New("cannot understand address").Base(err)
|
return errors.New("tagged dialer is not initialized")
|
||||||
}
|
}
|
||||||
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
dest, err := net.ParseDestination(network + ":" + address)
|
||||||
if err != nil {
|
|
||||||
return errors.New("cannot dial remote address ", dest).Base(err)
|
|
||||||
}
|
|
||||||
conn = c
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("cannot finish connection").Base(err)
|
|
||||||
}
|
|
||||||
return &idleConn{
|
|
||||||
Conn: conn,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
if isHTTPS {
|
|
||||||
return &http.Client{
|
|
||||||
Transport: &http2.Transport{
|
|
||||||
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
|
|
||||||
conn, err := dial(ctx, network, address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return errors.New("cannot understand address").Base(err)
|
||||||
}
|
}
|
||||||
host, _, _ := net.SplitHostPort(address)
|
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
||||||
tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
|
if err != nil {
|
||||||
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
|
return errors.New("cannot dial remote address ", dest).Base(err)
|
||||||
defer cancel()
|
|
||||||
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return tlsConn, nil
|
conn = c
|
||||||
},
|
return nil
|
||||||
},
|
})
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
if err != nil {
|
||||||
if req.URL.Scheme != "https" {
|
return nil, errors.New("cannot finish connection").Base(err)
|
||||||
return errors.New("redirected to non-https URL: ", req.URL.String())
|
|
||||||
}
|
}
|
||||||
if len(via) >= 10 {
|
return &idleConn{
|
||||||
return errors.New("stopped after 10 redirects")
|
Conn: conn,
|
||||||
}
|
}, nil
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
TLSHandshakeTimeout: idleTimeout,
|
||||||
} else {
|
ResponseHeaderTimeout: idleTimeout,
|
||||||
return &http.Client{
|
},
|
||||||
Transport: &http.Transport{
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
Proxy: nil,
|
if req.URL.Scheme != "https" {
|
||||||
DisableKeepAlives: true,
|
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||||
DialContext: dial,
|
}
|
||||||
ResponseHeaderTimeout: idleTimeout,
|
if len(via) >= 10 {
|
||||||
},
|
return errors.New("stopped after 10 redirects")
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
}
|
||||||
if req.URL.Scheme != "https" {
|
return nil
|
||||||
return errors.New("redirected to non-https URL: ", req.URL.String())
|
},
|
||||||
}
|
|
||||||
if len(via) >= 10 {
|
|
||||||
return errors.New("stopped after 10 redirects")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,13 +160,7 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
|
|||||||
}
|
}
|
||||||
utils.TryDefaultHeadersWith(req.Header, "nav")
|
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||||
|
|
||||||
var client *http.Client
|
resp, err := d.client.Do(req)
|
||||||
if req.URL.Scheme == "https" {
|
|
||||||
client = d.httpsClient
|
|
||||||
} else {
|
|
||||||
client = d.httpClient
|
|
||||||
}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-150
@@ -2,18 +2,15 @@ package metrics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
stderrors "errors"
|
|
||||||
"expvar"
|
"expvar"
|
||||||
stdnet "net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/observatory"
|
"github.com/xtls/xray-core/app/observatory"
|
||||||
"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"
|
||||||
xnet "github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/signal/done"
|
"github.com/xtls/xray-core/common/signal/done"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/extension"
|
"github.com/xtls/xray-core/features/extension"
|
||||||
@@ -24,17 +21,15 @@ import (
|
|||||||
type MetricsHandler struct {
|
type MetricsHandler struct {
|
||||||
ohm outbound.Manager
|
ohm outbound.Manager
|
||||||
statsManager feature_stats.Manager
|
statsManager feature_stats.Manager
|
||||||
ctx context.Context
|
observatory extension.Observatory
|
||||||
tag string
|
tag string
|
||||||
listen string
|
listen string
|
||||||
tcpListener xnet.Listener
|
tcpListener net.Listener
|
||||||
listener *OutboundListener
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMetricsHandler creates a new MetricsHandler based on the given config.
|
// NewMetricsHandler creates a new MetricsHandler based on the given config.
|
||||||
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
|
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
|
||||||
c := &MetricsHandler{
|
c := &MetricsHandler{
|
||||||
ctx: ctx,
|
|
||||||
tag: config.Tag,
|
tag: config.Tag,
|
||||||
listen: config.Listen,
|
listen: config.Listen,
|
||||||
}
|
}
|
||||||
@@ -42,6 +37,46 @@ func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, er
|
|||||||
c.statsManager = sm
|
c.statsManager = sm
|
||||||
c.ohm = om
|
c.ohm = om
|
||||||
}))
|
}))
|
||||||
|
expvar.Publish("stats", expvar.Func(func() interface{} {
|
||||||
|
resp := map[string]map[string]map[string]int64{
|
||||||
|
"inbound": {},
|
||||||
|
"outbound": {},
|
||||||
|
"user": {},
|
||||||
|
}
|
||||||
|
c.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
|
||||||
|
nameSplit := strings.Split(name, ">>>")
|
||||||
|
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
|
||||||
|
if item, found := resp[typeName][tagOrUser]; found {
|
||||||
|
item[direction] = counter.Value()
|
||||||
|
} else {
|
||||||
|
resp[typeName][tagOrUser] = map[string]int64{
|
||||||
|
direction: counter.Value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return resp
|
||||||
|
}))
|
||||||
|
expvar.Publish("observatory", expvar.Func(func() interface{} {
|
||||||
|
if c.observatory == nil {
|
||||||
|
common.Must(core.RequireFeatures(ctx, func(observatory extension.Observatory) error {
|
||||||
|
c.observatory = observatory
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
if c.observatory == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp := map[string]*observatory.OutboundStatus{}
|
||||||
|
if o, err := c.observatory.GetObservation(context.Background()); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
|
||||||
|
resp[x.OutboundTag] = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}))
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,172 +85,46 @@ func (p *MetricsHandler) Type() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MetricsHandler) Start() error {
|
func (p *MetricsHandler) Start() error {
|
||||||
handler := p.httpHandler()
|
|
||||||
|
|
||||||
// direct listen a port if listen is set
|
// direct listen a port if listen is set
|
||||||
if p.listen != "" {
|
if p.listen != "" {
|
||||||
TCPlistener, err := xnet.Listen("tcp", p.listen)
|
TCPlistener, err := net.Listen("tcp", p.listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.tcpListener = TCPlistener
|
p.tcpListener = TCPlistener
|
||||||
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
||||||
|
|
||||||
go p.serve(TCPlistener, handler)
|
go func() {
|
||||||
}
|
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||||
if p.tag == "" {
|
}
|
||||||
if p.tcpListener == nil {
|
}()
|
||||||
return errors.New("metrics must have a tag or listen address")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listener := &OutboundListener{
|
listener := &OutboundListener{
|
||||||
buffer: make(chan xnet.Conn, 4),
|
buffer: make(chan net.Conn, 4),
|
||||||
done: done.New(),
|
done: done.New(),
|
||||||
}
|
}
|
||||||
p.listener = listener
|
|
||||||
|
|
||||||
go p.serve(listener, handler)
|
go func() {
|
||||||
|
if err := http.Serve(listener, http.DefaultServeMux); err != nil {
|
||||||
|
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
||||||
errors.LogInfo(context.Background(), "failed to remove existing handler")
|
errors.LogInfo(context.Background(), "failed to remove existing handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.ohm.AddHandler(context.Background(), &Outbound{
|
return p.ohm.AddHandler(context.Background(), &Outbound{
|
||||||
tag: p.tag,
|
tag: p.tag,
|
||||||
listener: listener,
|
listener: listener,
|
||||||
}); err != nil {
|
})
|
||||||
if closeErr := p.Close(); closeErr != nil {
|
|
||||||
errors.LogErrorInner(context.Background(), closeErr, "failed to close metrics server after start failure")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MetricsHandler) Close() error {
|
func (p *MetricsHandler) Close() error {
|
||||||
var errs []error
|
return nil
|
||||||
if p.tcpListener != nil {
|
|
||||||
errs = append(errs, p.tcpListener.Close())
|
|
||||||
p.tcpListener = nil
|
|
||||||
}
|
|
||||||
if p.listener != nil {
|
|
||||||
errs = append(errs, p.listener.Close())
|
|
||||||
p.listener = nil
|
|
||||||
}
|
|
||||||
if p.ohm != nil && p.tag != "" {
|
|
||||||
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
|
||||||
errors.LogInfo(context.Background(), "failed to remove metrics handler")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Combine(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MetricsHandler) serve(listener xnet.Listener, handler http.Handler) {
|
|
||||||
if err := http.Serve(listener, handler); err != nil && !isClosedListenerError(err) {
|
|
||||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isClosedListenerError(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if stderrors.Is(err, stdnet.ErrClosed) || stderrors.Is(err, http.ErrServerClosed) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
errText := err.Error()
|
|
||||||
return strings.Contains(errText, "listen closed") ||
|
|
||||||
strings.Contains(errText, "use of closed network connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MetricsHandler) httpHandler() http.Handler {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/debug/vars", p.handleDebugVars)
|
|
||||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
||||||
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
||||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
||||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
||||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
||||||
return mux
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MetricsHandler) handleDebugVars(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
vars := map[string]json.RawMessage{}
|
|
||||||
expvar.Do(func(kv expvar.KeyValue) {
|
|
||||||
value := json.RawMessage(kv.Value.String())
|
|
||||||
if !json.Valid(value) {
|
|
||||||
value = json.RawMessage("null")
|
|
||||||
}
|
|
||||||
vars[kv.Key] = value
|
|
||||||
})
|
|
||||||
vars["stats"] = marshalJSON(p.stats())
|
|
||||||
vars["observatory"] = marshalJSON(p.observatoryStatus())
|
|
||||||
|
|
||||||
payload, err := json.Marshal(vars)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalJSON(value interface{}) json.RawMessage {
|
|
||||||
data, err := json.Marshal(value)
|
|
||||||
if err != nil {
|
|
||||||
return json.RawMessage("null")
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MetricsHandler) stats() map[string]map[string]map[string]int64 {
|
|
||||||
resp := map[string]map[string]map[string]int64{
|
|
||||||
"inbound": {},
|
|
||||||
"outbound": {},
|
|
||||||
"user": {},
|
|
||||||
}
|
|
||||||
p.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
|
|
||||||
nameSplit := strings.Split(name, ">>>")
|
|
||||||
if len(nameSplit) < 4 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
|
|
||||||
items, found := resp[typeName]
|
|
||||||
if !found {
|
|
||||||
items = map[string]map[string]int64{}
|
|
||||||
resp[typeName] = items
|
|
||||||
}
|
|
||||||
if item, found := items[tagOrUser]; found {
|
|
||||||
item[direction] = counter.Value()
|
|
||||||
} else {
|
|
||||||
items[tagOrUser] = map[string]int64{
|
|
||||||
direction: counter.Value(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MetricsHandler) observatoryStatus() interface{} {
|
|
||||||
feature := core.MustFromContext(p.ctx).GetFeature(extension.ObservatoryType())
|
|
||||||
if feature == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
observatoryFeature := feature.(extension.Observatory)
|
|
||||||
resp := map[string]*observatory.OutboundStatus{}
|
|
||||||
if o, err := observatoryFeature.GetObservation(context.Background()); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
|
|
||||||
resp[x.OutboundTag] = x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
package metrics
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
stdnet "net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/dispatcher"
|
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
|
||||||
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
|
||||||
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
|
||||||
appstats "github.com/xtls/xray-core/app/stats"
|
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
"github.com/xtls/xray-core/core"
|
|
||||||
feature_outbound "github.com/xtls/xray-core/features/outbound"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMetricsCanRestartInSameProcess(t *testing.T) {
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
server := startMetricsTestServer(t)
|
|
||||||
readMetricsVars(t, server)
|
|
||||||
readMetricsPprof(t, server)
|
|
||||||
if err := server.Close(); err != nil {
|
|
||||||
t.Fatalf("failed to close metrics server: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMetricsCanRunMultipleInstancesInSameProcess(t *testing.T) {
|
|
||||||
server1 := startMetricsTestServer(t)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = server1.Close()
|
|
||||||
})
|
|
||||||
server2 := startMetricsTestServer(t)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = server2.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
readMetricsVars(t, server1)
|
|
||||||
readMetricsVars(t, server2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMetricsListenOnlyWithoutTagDoesNotRegisterOutbound(t *testing.T) {
|
|
||||||
listen := pickMetricsListenAddress(t)
|
|
||||||
server := startMetricsTestServerWithMetricsConfig(t, &Config{
|
|
||||||
Listen: listen,
|
|
||||||
})
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = server.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
response, err := http.Get("http://" + listen + "/debug/vars")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read listen-only metrics: %v", err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
t.Fatalf("unexpected listen-only metrics status: %d", response.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
outboundManager := server.GetFeature(feature_outbound.ManagerType()).(feature_outbound.Manager)
|
|
||||||
if handlers := outboundManager.ListHandlers(context.Background()); len(handlers) != 0 {
|
|
||||||
t.Fatalf("listen-only metrics registered outbound handlers: got %d, want 0", len(handlers))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startMetricsTestServer(t *testing.T) *core.Instance {
|
|
||||||
return startMetricsTestServerWithMetricsConfig(t, &Config{
|
|
||||||
Tag: "metrics_out",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startMetricsTestServerWithMetricsConfig(t *testing.T, metricsConfig *Config) *core.Instance {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
server, err := core.New(metricsTestConfig(metricsConfig))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create metrics server: %v", err)
|
|
||||||
}
|
|
||||||
if err := server.Start(); err != nil {
|
|
||||||
_ = server.Close()
|
|
||||||
t.Fatalf("failed to start metrics server: %v", err)
|
|
||||||
}
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
||||||
func metricsTestConfig(metricsConfig *Config) *core.Config {
|
|
||||||
return &core.Config{
|
|
||||||
App: []*serial.TypedMessage{
|
|
||||||
serial.ToTypedMessage(&dispatcher.Config{}),
|
|
||||||
serial.ToTypedMessage(&proxyman.InboundConfig{}),
|
|
||||||
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
|
|
||||||
serial.ToTypedMessage(&appstats.Config{}),
|
|
||||||
serial.ToTypedMessage(metricsConfig),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickMetricsListenAddress(t *testing.T) string {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to pick metrics listen address: %v", err)
|
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
return listener.Addr().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMetricsVars(t *testing.T, server *core.Instance) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
metricsHandler(t, server).httpHandler().ServeHTTP(
|
|
||||||
recorder,
|
|
||||||
httptest.NewRequest(http.MethodGet, "/debug/vars", nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
if recorder.Code != http.StatusOK {
|
|
||||||
t.Fatalf("unexpected metrics vars status: %d", recorder.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
var payload map[string]interface{}
|
|
||||||
if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil {
|
|
||||||
t.Fatalf("failed to decode metrics vars: %v", err)
|
|
||||||
}
|
|
||||||
if _, found := payload["stats"]; !found {
|
|
||||||
t.Fatal("metrics vars missing stats")
|
|
||||||
}
|
|
||||||
if _, found := payload["observatory"]; !found {
|
|
||||||
t.Fatal("metrics vars missing observatory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMetricsPprof(t *testing.T, server *core.Instance) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
metricsHandler(t, server).httpHandler().ServeHTTP(
|
|
||||||
recorder,
|
|
||||||
httptest.NewRequest(http.MethodGet, "/debug/pprof/goroutine?debug=1", nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
if recorder.Code != http.StatusOK {
|
|
||||||
t.Fatalf("unexpected metrics pprof status: %d", recorder.Code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func metricsHandler(t *testing.T, server *core.Instance) *MetricsHandler {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
feature := server.GetFeature((*MetricsHandler)(nil))
|
|
||||||
handler, ok := feature.(*MetricsHandler)
|
|
||||||
if !ok || handler == nil {
|
|
||||||
t.Fatal("metrics handler not registered")
|
|
||||||
}
|
|
||||||
return handler
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ package burst
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/observatory"
|
"github.com/xtls/xray-core/app/observatory"
|
||||||
@@ -71,6 +72,7 @@ func (o *Observer) Start() error {
|
|||||||
o.hp.StartScheduler(func() ([]string, error) {
|
o.hp.StartScheduler(func() ([]string, error) {
|
||||||
hs, ok := o.ohm.(outbound.HandlerSelector)
|
hs, ok := o.ohm.(outbound.HandlerSelector)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
return nil, errors.New("outbound.Manager is not a HandlerSelector")
|
return nil, errors.New("outbound.Manager is not a HandlerSelector")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/dice"
|
"github.com/xtls/xray-core/common/dice"
|
||||||
@@ -25,12 +24,11 @@ type HealthPingSettings struct {
|
|||||||
|
|
||||||
// HealthPing is the health checker for balancers
|
// HealthPing is the health checker for balancers
|
||||||
type HealthPing struct {
|
type HealthPing struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelCtx context.CancelFunc
|
dispatcher routing.Dispatcher
|
||||||
cancelPending atomic.Pointer[context.CancelFunc]
|
access sync.Mutex
|
||||||
dispatcher routing.Dispatcher
|
ticker *time.Ticker
|
||||||
access sync.Mutex
|
tickerClose chan struct{}
|
||||||
ticker *time.Ticker
|
|
||||||
|
|
||||||
Settings *HealthPingSettings
|
Settings *HealthPingSettings
|
||||||
Results map[string]*HealthPingRTTS
|
Results map[string]*HealthPingRTTS
|
||||||
@@ -64,10 +62,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
|
|||||||
settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
|
settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
|
||||||
}
|
}
|
||||||
if settings.Interval == 0 {
|
if settings.Interval == 0 {
|
||||||
settings.Interval = 1 * time.Minute
|
settings.Interval = time.Duration(1) * time.Minute
|
||||||
} else if settings.Interval < 10*time.Second {
|
} else if settings.Interval < 10 {
|
||||||
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
|
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
|
||||||
settings.Interval = 10 * time.Second
|
settings.Interval = time.Duration(10) * time.Second
|
||||||
}
|
}
|
||||||
if settings.SamplingCount <= 0 {
|
if settings.SamplingCount <= 0 {
|
||||||
settings.SamplingCount = 10
|
settings.SamplingCount = 10
|
||||||
@@ -75,12 +73,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
|
|||||||
if settings.Timeout <= 0 {
|
if settings.Timeout <= 0 {
|
||||||
// results are saved after all health pings finish,
|
// results are saved after all health pings finish,
|
||||||
// a larger timeout could possibly makes checks run longer
|
// a larger timeout could possibly makes checks run longer
|
||||||
settings.Timeout = 5 * time.Second
|
settings.Timeout = time.Duration(5) * time.Second
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
return &HealthPing{
|
return &HealthPing{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancelCtx: cancel,
|
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher,
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
Results: nil,
|
Results: nil,
|
||||||
@@ -94,7 +90,17 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
|||||||
}
|
}
|
||||||
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
|
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
|
tickerClose := make(chan struct{})
|
||||||
h.ticker = ticker
|
h.ticker = ticker
|
||||||
|
h.tickerClose = tickerClose
|
||||||
|
go func() {
|
||||||
|
tags, err := selector()
|
||||||
|
if err != nil {
|
||||||
|
errors.LogWarning(h.ctx, "error select outbounds for initial health check: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Check(tags)
|
||||||
|
}()
|
||||||
|
|
||||||
// init run to get a fast check result
|
// init run to get a fast check result
|
||||||
go func() {
|
go func() {
|
||||||
@@ -114,20 +120,13 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
|||||||
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
|
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
subCtx, cancel := context.WithCancel(h.ctx)
|
h.doCheck(tags, interval, h.Settings.SamplingCount)
|
||||||
old := h.cancelPending.Swap(&cancel)
|
|
||||||
if old != nil {
|
|
||||||
errors.LogDebug(h.ctx, "scheduled health check not finished before next round, canceling previous one")
|
|
||||||
(*old)()
|
|
||||||
}
|
|
||||||
h.doCheck(subCtx, tags, interval, h.Settings.SamplingCount)
|
|
||||||
h.cancelPending.CompareAndSwap(&cancel, nil)
|
|
||||||
h.Cleanup(tags)
|
h.Cleanup(tags)
|
||||||
}()
|
}()
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
continue
|
continue
|
||||||
case <-h.ctx.Done():
|
case <-tickerClose:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +140,8 @@ func (h *HealthPing) StopScheduler() {
|
|||||||
}
|
}
|
||||||
h.ticker.Stop()
|
h.ticker.Stop()
|
||||||
h.ticker = nil
|
h.ticker = nil
|
||||||
h.cancelCtx()
|
close(h.tickerClose)
|
||||||
|
h.tickerClose = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check implements the HealthChecker
|
// Check implements the HealthChecker
|
||||||
@@ -150,7 +150,7 @@ func (h *HealthPing) Check(tags []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
|
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
|
||||||
h.doCheck(h.ctx, tags, 0, 1)
|
h.doCheck(tags, 0, 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,14 +161,13 @@ type rtt struct {
|
|||||||
|
|
||||||
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
|
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
|
||||||
// sure all tags are valid for current balancer
|
// sure all tags are valid for current balancer
|
||||||
// cancel ctx will stop all pending checks
|
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
|
||||||
func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.Duration, rounds int) {
|
|
||||||
count := len(tags) * rounds
|
count := len(tags) * rounds
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ch := make(chan *rtt, count)
|
ch := make(chan *rtt, count)
|
||||||
timers := make([]*time.Timer, 0, count)
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
handler := tag
|
handler := tag
|
||||||
client := newPingClient(
|
client := newPingClient(
|
||||||
@@ -183,7 +182,7 @@ func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.D
|
|||||||
if duration > 0 {
|
if duration > 0 {
|
||||||
delay = time.Duration(dice.RollInt63n(int64(duration)))
|
delay = time.Duration(dice.RollInt63n(int64(duration)))
|
||||||
}
|
}
|
||||||
timers = append(timers, time.AfterFunc(delay, func() {
|
time.AfterFunc(delay, func() {
|
||||||
errors.LogDebug(h.ctx, "checking ", handler)
|
errors.LogDebug(h.ctx, "checking ", handler)
|
||||||
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
|
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -211,21 +210,14 @@ func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.D
|
|||||||
handler: handler,
|
handler: handler,
|
||||||
value: rttFailed,
|
value: rttFailed,
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
select {
|
rtt := <-ch
|
||||||
case rtt := <-ch:
|
if rtt.value > 0 {
|
||||||
if rtt.value > 0 {
|
// should not put results when network is down
|
||||||
// should not put results when network is down
|
h.PutResult(rtt.handler, rtt.value)
|
||||||
h.PutResult(rtt.handler, rtt.value)
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
for _, timer := range timers {
|
|
||||||
timer.Stop()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (h *HealthPingRTTS) Put(d time.Duration) {
|
|||||||
if h.rtts == nil {
|
if h.rtts == nil {
|
||||||
h.rtts = make([]*pingRTT, h.cap)
|
h.rtts = make([]*pingRTT, h.cap)
|
||||||
for i := 0; i < h.cap; i++ {
|
for i := 0; i < h.cap; i++ {
|
||||||
h.rtts[i] = &pingRTT{value: rttUntested}
|
h.rtts[i] = &pingRTT{}
|
||||||
}
|
}
|
||||||
h.idx = -1
|
h.idx = -1
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
|
|||||||
validRTTs := make([]time.Duration, 0)
|
validRTTs := make([]time.Duration, 0)
|
||||||
for _, rtt := range h.rtts {
|
for _, rtt := range h.rtts {
|
||||||
switch {
|
switch {
|
||||||
case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
|
case rtt.value == 0 || time.Since(rtt.time) > h.validity:
|
||||||
continue
|
continue
|
||||||
case rtt.value == rttFailed:
|
case rtt.value == rttFailed:
|
||||||
stats.Fail++
|
stats.Fail++
|
||||||
|
|||||||
@@ -78,12 +78,6 @@ func (o *Observer) background() {
|
|||||||
sleepTime = time.Duration(o.config.ProbeInterval)
|
sleepTime = time.Duration(o.config.ProbeInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(outbounds) == 0 {
|
|
||||||
errors.LogWarning(o.ctx, "no outbound matches subjectSelector ", o.config.SubjectSelector)
|
|
||||||
time.Sleep(sleepTime)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !o.config.EnableConcurrency {
|
if !o.config.EnableConcurrency {
|
||||||
sort.Strings(outbounds)
|
sort.Strings(outbounds)
|
||||||
for _, v := range outbounds {
|
for _, v := range outbounds {
|
||||||
@@ -192,7 +186,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorMessage := "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
|
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
|
||||||
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
|
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
|
||||||
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
|
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInbound
|
|||||||
if len(request.Email) > 0 {
|
if len(request.Email) > 0 {
|
||||||
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
|
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
|
||||||
}
|
}
|
||||||
result := make([]*protocol.User, 0, 100)
|
var result = make([]*protocol.User, 0, 100)
|
||||||
users := um.GetUsers(ctx)
|
users := um.GetUsers(ctx)
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
result = append(result, protocol.ToProtoUser(u))
|
result = append(result, protocol.ToProtoUser(u))
|
||||||
|
|||||||
+22
-11
@@ -330,6 +330,7 @@ type SenderConfig struct {
|
|||||||
// Send traffic through the given IP. Only IP is allowed.
|
// Send traffic through the given IP. Only IP is allowed.
|
||||||
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
|
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
|
||||||
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
|
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
|
||||||
|
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
|
||||||
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
|
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
|
||||||
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
|
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
|
||||||
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
|
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
|
||||||
@@ -381,6 +382,13 @@ func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProxySettings
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
|
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.MultiplexSettings
|
return x.MultiplexSettings
|
||||||
@@ -498,13 +506,14 @@ const file_app_proxyman_config_proto_rawDesc = "" +
|
|||||||
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
|
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
|
||||||
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
|
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
|
||||||
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
|
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
|
||||||
"\x0eOutboundConfig\"\xd6\x02\n" +
|
"\x0eOutboundConfig\"\x9d\x03\n" +
|
||||||
"\fSenderConfig\x12-\n" +
|
"\fSenderConfig\x12-\n" +
|
||||||
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
|
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
|
||||||
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12T\n" +
|
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12K\n" +
|
||||||
|
"\x0eproxy_settings\x18\x03 \x01(\v2$.xray.transport.internet.ProxyConfigR\rproxySettings\x12T\n" +
|
||||||
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
|
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
|
||||||
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
|
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
|
||||||
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
|
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategy\"\xa4\x01\n" +
|
||||||
"\x12MultiplexingConfig\x12\x18\n" +
|
"\x12MultiplexingConfig\x12\x18\n" +
|
||||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
||||||
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
||||||
@@ -539,7 +548,8 @@ var file_app_proxyman_config_proto_goTypes = []any{
|
|||||||
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||||
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
||||||
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
||||||
(internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
|
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig
|
||||||
|
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
|
||||||
}
|
}
|
||||||
var file_app_proxyman_config_proto_depIdxs = []int32{
|
var file_app_proxyman_config_proto_depIdxs = []int32{
|
||||||
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
|
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
|
||||||
@@ -552,13 +562,14 @@ var file_app_proxyman_config_proto_depIdxs = []int32{
|
|||||||
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
12, // 7: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
||||||
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
10, // 8: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
||||||
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||||
6, // 10: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
|
||||||
13, // 11: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
||||||
12, // [12:12] is the sub-list for method output_type
|
14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||||
12, // [12:12] is the sub-list for method input_type
|
13, // [13:13] is the sub-list for method output_type
|
||||||
12, // [12:12] is the sub-list for extension type_name
|
13, // [13:13] is the sub-list for method input_type
|
||||||
12, // [12:12] is the sub-list for extension extendee
|
13, // [13:13] is the sub-list for extension type_name
|
||||||
0, // [0:12] is the sub-list for field type_name
|
13, // [13:13] is the sub-list for extension extendee
|
||||||
|
0, // [0:13] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_app_proxyman_config_proto_init() }
|
func init() { file_app_proxyman_config_proto_init() }
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ message SenderConfig {
|
|||||||
// Send traffic through the given IP. Only IP is allowed.
|
// Send traffic through the given IP. Only IP is allowed.
|
||||||
xray.common.net.IPOrDomain via = 1;
|
xray.common.net.IPOrDomain via = 1;
|
||||||
xray.transport.internet.StreamConfig stream_settings = 2;
|
xray.transport.internet.StreamConfig stream_settings = 2;
|
||||||
reserved 3;
|
xray.transport.internet.ProxyConfig proxy_settings = 3;
|
||||||
MultiplexingConfig multiplex_settings = 4;
|
MultiplexingConfig multiplex_settings = 4;
|
||||||
string via_cidr = 5;
|
string via_cidr = 5;
|
||||||
xray.transport.internet.DomainStrategy target_strategy = 6;
|
xray.transport.internet.DomainStrategy target_strategy = 6;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
|
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
||||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
uplinkCounter = c
|
uplinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {
|
if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
|
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
|
||||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
downlinkCounter = c
|
downlinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -57,23 +57,16 @@ 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)
|
|
||||||
if receiverConfig.Listen != nil {
|
|
||||||
src.Address = receiverConfig.Listen.AsAddress()
|
|
||||||
}
|
|
||||||
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})
|
// Set tag and sniffing config in context before creating proxy
|
||||||
newCtx = session.ContextWithContent(newCtx, &session.Content{SniffingRequest: sniffingRequest})
|
// This allows proxies like TUN to access these settings
|
||||||
newCtx = session.ContextWithStreamSettings(newCtx, mss)
|
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})
|
||||||
|
if receiverConfig.SniffingSettings != nil {
|
||||||
rawProxy, err := common.CreateObject(newCtx, proxyConfig)
|
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||||
|
SniffingRequest: sniffingRequest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -99,6 +92,11 @@ 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{}
|
||||||
@@ -172,12 +170,6 @@ 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
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import (
|
|||||||
|
|
||||||
// Manager manages all inbound handlers.
|
// Manager manages all inbound handlers.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
untaggedHandlers []inbound.Handler
|
untaggedHandlers []inbound.Handler
|
||||||
taggedHandlers map[string]inbound.Handler
|
taggedHandlers map[string]inbound.Handler
|
||||||
running bool
|
running bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Manager for inbound handlers.
|
// New returns a new Manager for inbound handlers.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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"
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/mux"
|
"github.com/xtls/xray-core/common/mux"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/net/cnc"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -25,6 +27,8 @@ import (
|
|||||||
"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"
|
||||||
"github.com/xtls/xray-core/transport/internet/stat"
|
"github.com/xtls/xray-core/transport/internet/stat"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"github.com/xtls/xray-core/transport/pipe"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +40,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
|
if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
|
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
|
||||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
uplinkCounter = c
|
uplinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -44,7 +48,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
|||||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
|
if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
|
||||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||||
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
|
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
|
||||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
downlinkCounter = c
|
downlinkCounter = c
|
||||||
}
|
}
|
||||||
@@ -60,6 +64,7 @@ type Handler struct {
|
|||||||
streamSettings *internet.MemoryStreamConfig
|
streamSettings *internet.MemoryStreamConfig
|
||||||
proxyConfig proto.Message
|
proxyConfig proto.Message
|
||||||
proxy proxy.Outbound
|
proxy proxy.Outbound
|
||||||
|
outboundManager outbound.Manager
|
||||||
mux *mux.ClientManager
|
mux *mux.ClientManager
|
||||||
xudp *mux.ClientManager
|
xudp *mux.ClientManager
|
||||||
udp443 string
|
udp443 string
|
||||||
@@ -73,6 +78,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
|||||||
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
tag: config.Tag,
|
tag: config.Tag,
|
||||||
|
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
|
||||||
uplinkCounter: uplinkCounter,
|
uplinkCounter: uplinkCounter,
|
||||||
downlinkCounter: downlinkCounter,
|
downlinkCounter: downlinkCounter,
|
||||||
}
|
}
|
||||||
@@ -103,10 +109,6 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
|||||||
|
|
||||||
ctx = session.ContextWithFullHandler(ctx, h)
|
ctx = session.ContextWithFullHandler(ctx, h)
|
||||||
|
|
||||||
if h.streamSettings != nil {
|
|
||||||
ctx = session.ContextWithStreamSettings(ctx, h.streamSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -194,6 +196,7 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
|
|||||||
common.Interrupt(link.Reader)
|
common.Interrupt(link.Reader)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
unchangedDomain := ob.Target.Address.Domain()
|
unchangedDomain := ob.Target.Address.Domain()
|
||||||
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||||
@@ -266,26 +269,71 @@ func (h *Handler) DestIpAddress() net.IP {
|
|||||||
|
|
||||||
// Dial implements internet.Dialer.
|
// Dial implements internet.Dialer.
|
||||||
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||||
if h.senderSettings != nil && h.senderSettings.Via != nil {
|
if h.senderSettings != nil {
|
||||||
outbounds := session.OutboundsFromContext(ctx)
|
|
||||||
ob := outbounds[len(outbounds)-1]
|
if h.senderSettings.ProxySettings.HasTag() {
|
||||||
h.SetOutboundGateway(ctx, ob)
|
|
||||||
|
tag := h.senderSettings.ProxySettings.Tag
|
||||||
|
handler := h.outboundManager.GetHandler(tag)
|
||||||
|
if handler != nil {
|
||||||
|
errors.LogDebug(ctx, "proxying to ", tag, " for dest ", dest)
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
|
||||||
|
Target: dest,
|
||||||
|
Tag: tag,
|
||||||
|
})) // add another outbound in session ctx
|
||||||
|
opts := pipe.OptionsFromContext(ctx)
|
||||||
|
uplinkReader, uplinkWriter := pipe.New(opts...)
|
||||||
|
downlinkReader, downlinkWriter := pipe.New(opts...)
|
||||||
|
|
||||||
|
go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
|
||||||
|
conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
|
||||||
|
|
||||||
|
if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
|
||||||
|
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
|
||||||
|
conn = tls.Client(conn, tlsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.getStatCouterConnection(conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.LogError(ctx, "failed to get outbound handler with tag: ", tag)
|
||||||
|
return nil, errors.New("failed to get outbound handler with tag: " + tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.senderSettings.Via != nil {
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
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)
|
||||||
conn = h.getStatCouterConnection(conn)
|
conn = h.getStatCouterConnection(conn)
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
if outbounds != nil {
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
ob.Conn = conn
|
||||||
|
} else {
|
||||||
|
// for Vision's pre-connect
|
||||||
|
}
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
|
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
|
||||||
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
|
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil && !h.senderSettings.ProxySettings.HasTag() && (h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
||||||
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
|
||||||
var domain string
|
var domain string
|
||||||
addr := h.senderSettings.Via.AsAddress()
|
addr := h.senderSettings.Via.AsAddress()
|
||||||
domain = h.senderSettings.Via.GetDomain()
|
domain = h.senderSettings.Via.GetDomain()
|
||||||
switch {
|
switch {
|
||||||
case h.senderSettings.ViaCidr != "":
|
case h.senderSettings.ViaCidr != "":
|
||||||
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
||||||
|
|
||||||
case domain == "origin":
|
case domain == "origin":
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||||
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
|
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
|
||||||
@@ -300,9 +348,12 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
|
|||||||
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: // case addr.Family().IsDomain():
|
//case addr.Family().IsDomain():
|
||||||
|
default:
|
||||||
ob.Gateway = addr
|
ob.Gateway = addr
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,6 +396,7 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ParseRandomIP(addr net.Address, prefix string) net.Address {
|
func ParseRandomIP(addr net.Address, prefix string) net.Address {
|
||||||
|
|
||||||
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
|
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
|
||||||
|
|
||||||
ones, bits := ipnet.Mask.Size()
|
ones, bits := ipnet.Mask.Size()
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestInterfaces(t *testing.T) {
|
func TestInterfaces(t *testing.T) {
|
||||||
_ = outbound.Handler(new(Handler))
|
_ = (outbound.Handler)(new(Handler))
|
||||||
_ = outbound.Manager(new(Manager))
|
_ = (outbound.Manager)(new(Manager))
|
||||||
}
|
}
|
||||||
|
|
||||||
const xrayKey core.XrayKey = 1
|
const xrayKey core.XrayKey = 1
|
||||||
@@ -43,7 +43,7 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v, _ := core.New(config)
|
v, _ := core.New(config)
|
||||||
v.AddFeature(outbound.Manager(new(Manager)))
|
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||||
@@ -73,7 +73,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v, _ := core.New(config)
|
v, _ := core.New(config)
|
||||||
v.AddFeature(outbound.Manager(new(Manager)))
|
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||||
@@ -88,6 +88,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTagsCache(t *testing.T) {
|
func TestTagsCache(t *testing.T) {
|
||||||
|
|
||||||
test_duration := 10 * time.Second
|
test_duration := 10 * time.Second
|
||||||
threads_num := 50
|
threads_num := 50
|
||||||
delay := 10 * time.Millisecond
|
delay := 10 * time.Millisecond
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
|
|||||||
|
|
||||||
// Select implements outbound.HandlerSelector.
|
// Select implements outbound.HandlerSelector.
|
||||||
func (m *Manager) Select(selectors []string) []string {
|
func (m *Manager) Select(selectors []string) []string {
|
||||||
|
|
||||||
key := strings.Join(selectors, ",")
|
key := strings.Join(selectors, ",")
|
||||||
if cache, ok := m.tagsCache.Load(key); ok {
|
if cache, ok := m.tagsCache.Load(key); ok {
|
||||||
return cache.([]string)
|
return cache.([]string)
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -136,7 +136,7 @@ func (b *Balancer) SelectOutbounds() ([]string, error) {
|
|||||||
|
|
||||||
// GetPrincipleTarget implements routing.BalancerPrincipleTarget
|
// GetPrincipleTarget implements routing.BalancerPrincipleTarget
|
||||||
func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
||||||
if b, ok := (*r.balancers.Load())[tag]; ok {
|
if b, ok := r.balancers[tag]; ok {
|
||||||
if s, ok := b.strategy.(BalancingPrincipleTarget); ok {
|
if s, ok := b.strategy.(BalancingPrincipleTarget); ok {
|
||||||
candidates, err := b.SelectOutbounds()
|
candidates, err := b.SelectOutbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -151,7 +151,7 @@ func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
|||||||
|
|
||||||
// SetOverrideTarget implements routing.BalancerOverrider
|
// SetOverrideTarget implements routing.BalancerOverrider
|
||||||
func (r *Router) SetOverrideTarget(tag, target string) error {
|
func (r *Router) SetOverrideTarget(tag, target string) error {
|
||||||
if b, ok := (*r.balancers.Load())[tag]; ok {
|
if b, ok := r.balancers[tag]; ok {
|
||||||
b.override.Put(target)
|
b.override.Put(target)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ func (r *Router) SetOverrideTarget(tag, target string) error {
|
|||||||
|
|
||||||
// GetOverrideTarget implements routing.BalancerOverrider
|
// GetOverrideTarget implements routing.BalancerOverrider
|
||||||
func (r *Router) GetOverrideTarget(tag string) (string, error) {
|
func (r *Router) GetOverrideTarget(tag string) (string, error) {
|
||||||
if b, ok := (*r.balancers.Load())[tag]; ok {
|
if b, ok := r.balancers[tag]; ok {
|
||||||
return b.override.Get(), nil
|
return b.override.Get(), nil
|
||||||
}
|
}
|
||||||
return "", errors.New("cannot find tag")
|
return "", errors.New("cannot find tag")
|
||||||
|
|||||||
@@ -2,8 +2,25 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *Router) OverrideBalancer(balancer string, target string) error {
|
||||||
|
var b *Balancer
|
||||||
|
for tag, bl := range r.balancers {
|
||||||
|
if tag == balancer {
|
||||||
|
b = bl
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
return errors.New("balancer '", balancer, "' not found")
|
||||||
|
}
|
||||||
|
b.override.Put(target)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type overrideSettings struct {
|
type overrideSettings struct {
|
||||||
target string
|
target string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
|
|||||||
return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)
|
return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)
|
||||||
}
|
}
|
||||||
return nil, errors.New("unsupported router implementation")
|
return nil, errors.New("unsupported router implementation")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -394,22 +393,3 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalOSMatcher matches the operating system Xray itself is running on. That never
|
|
||||||
// changes while Xray is running, so the result is resolved when the rule is built.
|
|
||||||
type LocalOSMatcher struct {
|
|
||||||
matched bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLocalOSMatcher(names []string) *LocalOSMatcher {
|
|
||||||
return &LocalOSMatcher{
|
|
||||||
matched: slices.ContainsFunc(names, func(name string) bool {
|
|
||||||
return strings.EqualFold(name, runtime.GOOS)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply implements Condition.
|
|
||||||
func (m *LocalOSMatcher) Apply(_ routing.Context) bool {
|
|
||||||
return m.matched
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package router_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/app/router"
|
. "github.com/xtls/xray-core/app/router"
|
||||||
@@ -345,31 +343,6 @@ func TestChinaSites(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLocalOSRule(t *testing.T) {
|
|
||||||
otherOS := "plan9"
|
|
||||||
if runtime.GOOS == otherOS {
|
|
||||||
otherOS = "linux"
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
localOS []string
|
|
||||||
output bool
|
|
||||||
}{
|
|
||||||
{localOS: []string{runtime.GOOS}, output: true},
|
|
||||||
{localOS: []string{otherOS}, output: false},
|
|
||||||
{localOS: []string{otherOS, runtime.GOOS}, output: true},
|
|
||||||
{localOS: []string{strings.ToUpper(runtime.GOOS)}, output: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range cases {
|
|
||||||
cond, err := (&RoutingRule{LocalOs: test.localOS}).BuildCondition()
|
|
||||||
common.Must(err)
|
|
||||||
if got := cond.Apply(withBackground()); got != test.output {
|
|
||||||
t.Errorf("for localOS %v on %s: expected %v, got %v", test.localOS, runtime.GOOS, test.output, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMphDomainMatcher(b *testing.B) {
|
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ func (r *Rule) Apply(ctx routing.Context) bool {
|
|||||||
func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||||
conds := NewConditionChan()
|
conds := NewConditionChan()
|
||||||
|
|
||||||
if len(rr.LocalOs) > 0 {
|
|
||||||
conds.Add(NewLocalOSMatcher(rr.LocalOs))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rr.InboundTag) > 0 {
|
if len(rr.InboundTag) > 0 {
|
||||||
conds.Add(NewInboundTagMatcher(rr.InboundTag))
|
conds.Add(NewInboundTagMatcher(rr.InboundTag))
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-14
@@ -107,10 +107,8 @@ type RoutingRule struct {
|
|||||||
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
|
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
|
||||||
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
|
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
|
||||||
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
||||||
// List of operating systems for matching the one Xray itself is running on.
|
unknownFields protoimpl.UnknownFields
|
||||||
LocalOs []string `protobuf:"bytes,23,rep,name=local_os,json=localOs,proto3" json:"local_os,omitempty"`
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoutingRule) Reset() {
|
func (x *RoutingRule) Reset() {
|
||||||
@@ -280,13 +278,6 @@ func (x *RoutingRule) GetWebhook() *WebhookConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoutingRule) GetLocalOs() []string {
|
|
||||||
if x != nil {
|
|
||||||
return x.LocalOs
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type isRoutingRule_TargetTag interface {
|
type isRoutingRule_TargetTag interface {
|
||||||
isRoutingRule_TargetTag()
|
isRoutingRule_TargetTag()
|
||||||
}
|
}
|
||||||
@@ -646,7 +637,7 @@ var File_app_router_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_app_router_config_proto_rawDesc = "" +
|
const file_app_router_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"\xdc\a\n" +
|
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"\xc1\a\n" +
|
||||||
"\vRoutingRule\x12\x12\n" +
|
"\vRoutingRule\x12\x12\n" +
|
||||||
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
|
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
|
||||||
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
|
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
|
||||||
@@ -670,8 +661,7 @@ const file_app_router_config_proto_rawDesc = "" +
|
|||||||
"\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" +
|
"\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" +
|
||||||
"\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" +
|
"\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" +
|
||||||
"\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
|
"\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
|
||||||
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x12\x19\n" +
|
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x1a=\n" +
|
||||||
"\blocal_os\x18\x17 \x03(\tR\alocalOs\x1a=\n" +
|
|
||||||
"\x0fAttributesEntry\x12\x10\n" +
|
"\x0fAttributesEntry\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\f\n" +
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\f\n" +
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ message RoutingRule {
|
|||||||
|
|
||||||
repeated string process = 21;
|
repeated string process = 21;
|
||||||
WebhookConfig webhook = 22;
|
WebhookConfig webhook = 22;
|
||||||
|
|
||||||
// List of operating systems for matching the one Xray itself is running on.
|
|
||||||
repeated string local_os = 23;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message WebhookConfig {
|
message WebhookConfig {
|
||||||
|
|||||||
+116
-59
@@ -2,9 +2,7 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"maps"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"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"
|
||||||
@@ -19,8 +17,8 @@ import (
|
|||||||
// Router is an implementation of routing.Router.
|
// Router is an implementation of routing.Router.
|
||||||
type Router struct {
|
type Router struct {
|
||||||
domainStrategy Config_DomainStrategy
|
domainStrategy Config_DomainStrategy
|
||||||
rules atomic.Pointer[[]*Rule]
|
rules []*Rule
|
||||||
balancers atomic.Pointer[map[string]*Balancer]
|
balancers map[string]*Balancer
|
||||||
dns dns.Client
|
dns dns.Client
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -45,9 +43,52 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
|
|||||||
r.ohm = ohm
|
r.ohm = ohm
|
||||||
r.dispatcher = dispatcher
|
r.dispatcher = dispatcher
|
||||||
|
|
||||||
r.rules.Store(new([]*Rule))
|
r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
|
||||||
r.balancers.Store(&map[string]*Balancer{})
|
for _, rule := range config.BalancingRule {
|
||||||
return r.ReloadRules(config, false)
|
balancer, err := rule.Build(ohm, dispatcher)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
balancer.InjectContext(ctx)
|
||||||
|
r.balancers[rule.Tag] = balancer
|
||||||
|
}
|
||||||
|
|
||||||
|
r.rules = make([]*Rule, 0, len(config.Rule))
|
||||||
|
for _, rule := range config.Rule {
|
||||||
|
cond, err := rule.BuildCondition()
|
||||||
|
if err != nil {
|
||||||
|
r.closeWebhooks()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rr := &Rule{
|
||||||
|
Condition: cond,
|
||||||
|
Tag: rule.GetTag(),
|
||||||
|
RuleTag: rule.GetRuleTag(),
|
||||||
|
}
|
||||||
|
if wh := rule.GetWebhook(); wh != nil {
|
||||||
|
notifier, err := NewWebhookNotifier(wh)
|
||||||
|
if err != nil {
|
||||||
|
r.closeWebhooks()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rr.Webhook = notifier
|
||||||
|
}
|
||||||
|
btag := rule.GetBalancingTag()
|
||||||
|
if len(btag) > 0 {
|
||||||
|
brule, found := r.balancers[btag]
|
||||||
|
if !found {
|
||||||
|
if rr.Webhook != nil {
|
||||||
|
rr.Webhook.Close()
|
||||||
|
}
|
||||||
|
r.closeWebhooks()
|
||||||
|
return errors.New("balancer ", btag, " not found")
|
||||||
|
}
|
||||||
|
rr.Balancer = brule
|
||||||
|
}
|
||||||
|
r.rules = append(r.rules, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PickRoute implements routing.Router.
|
// PickRoute implements routing.Router.
|
||||||
@@ -69,6 +110,7 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
|
|||||||
|
|
||||||
// AddRule implements routing.Router.
|
// AddRule implements routing.Router.
|
||||||
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
|
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
|
||||||
|
|
||||||
inst, err := config.GetInstance()
|
inst, err := config.GetInstance()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -83,22 +125,18 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
|||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
oldRules := *r.rules.Load()
|
if !shouldAppend {
|
||||||
oldBalancers := *r.balancers.Load()
|
for _, rule := range r.rules {
|
||||||
|
if rule.Webhook != nil {
|
||||||
var newRules []*Rule
|
rule.Webhook.Close()
|
||||||
newBalancers := make(map[string]*Balancer)
|
}
|
||||||
existTags := make(map[string]bool, len(oldRules)+len(config.Rule))
|
|
||||||
if shouldAppend {
|
|
||||||
newRules = append(newRules, oldRules...)
|
|
||||||
maps.Copy(newBalancers, oldBalancers)
|
|
||||||
for _, rule := range oldRules {
|
|
||||||
existTags[rule.RuleTag] = true
|
|
||||||
}
|
}
|
||||||
|
r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
|
||||||
|
r.rules = make([]*Rule, 0, len(config.Rule))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range config.BalancingRule {
|
for _, rule := range config.BalancingRule {
|
||||||
if _, found := newBalancers[rule.Tag]; found {
|
_, found := r.balancers[rule.Tag]
|
||||||
|
if found {
|
||||||
return errors.New("duplicate balancer tag")
|
return errors.New("duplicate balancer tag")
|
||||||
}
|
}
|
||||||
balancer, err := rule.Build(r.ohm, r.dispatcher)
|
balancer, err := rule.Build(r.ohm, r.dispatcher)
|
||||||
@@ -106,12 +144,27 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
balancer.InjectContext(r.ctx)
|
balancer.InjectContext(r.ctx)
|
||||||
newBalancers[rule.Tag] = balancer
|
r.balancers[rule.Tag] = balancer
|
||||||
|
}
|
||||||
|
|
||||||
|
startIdx := len(r.rules)
|
||||||
|
closeNewWebhooks := func() {
|
||||||
|
for i := startIdx; i < len(r.rules); i++ {
|
||||||
|
if r.rules[i].Webhook != nil {
|
||||||
|
r.rules[i].Webhook.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.rules = r.rules[:startIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range config.Rule {
|
for _, rule := range config.Rule {
|
||||||
|
if r.RuleExists(rule.GetRuleTag()) {
|
||||||
|
closeNewWebhooks()
|
||||||
|
return errors.New("duplicate ruleTag ", rule.GetRuleTag())
|
||||||
|
}
|
||||||
cond, err := rule.BuildCondition()
|
cond, err := rule.BuildCondition()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
closeNewWebhooks()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rr := &Rule{
|
rr := &Rule{
|
||||||
@@ -119,64 +172,70 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
|||||||
Tag: rule.GetTag(),
|
Tag: rule.GetTag(),
|
||||||
RuleTag: rule.GetRuleTag(),
|
RuleTag: rule.GetRuleTag(),
|
||||||
}
|
}
|
||||||
if rr.RuleTag != "" && existTags[rr.RuleTag] {
|
|
||||||
return errors.New("duplicate ruleTag ", rr.RuleTag)
|
|
||||||
}
|
|
||||||
existTags[rr.RuleTag] = true
|
|
||||||
if wh := rule.GetWebhook(); wh != nil {
|
if wh := rule.GetWebhook(); wh != nil {
|
||||||
notifier, err := NewWebhookNotifier(wh)
|
notifier, err := NewWebhookNotifier(wh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
closeNewWebhooks()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rr.Webhook = notifier
|
rr.Webhook = notifier
|
||||||
}
|
}
|
||||||
if btag := rule.GetBalancingTag(); len(btag) > 0 {
|
btag := rule.GetBalancingTag()
|
||||||
brule, found := newBalancers[btag]
|
if len(btag) > 0 {
|
||||||
|
brule, found := r.balancers[btag]
|
||||||
if !found {
|
if !found {
|
||||||
|
if rr.Webhook != nil {
|
||||||
|
rr.Webhook.Close()
|
||||||
|
}
|
||||||
|
closeNewWebhooks()
|
||||||
return errors.New("balancer ", btag, " not found")
|
return errors.New("balancer ", btag, " not found")
|
||||||
}
|
}
|
||||||
rr.Balancer = brule
|
rr.Balancer = brule
|
||||||
}
|
}
|
||||||
newRules = append(newRules, rr)
|
r.rules = append(r.rules, rr)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.balancers.Store(&newBalancers)
|
|
||||||
r.rules.Store(&newRules)
|
|
||||||
if !shouldAppend {
|
|
||||||
closeWebhooks(oldRules)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) RuleExists(tag string) bool {
|
||||||
|
if tag != "" {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
if rule.RuleTag == tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveRule implements routing.Router.
|
// RemoveRule implements routing.Router.
|
||||||
func (r *Router) RemoveRule(tag string) error {
|
func (r *Router) RemoveRule(tag string) error {
|
||||||
if tag == "" {
|
|
||||||
return errors.New("empty tag name!")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
oldRules := *r.rules.Load()
|
newRules := []*Rule{}
|
||||||
newRules := make([]*Rule, 0, len(oldRules))
|
if tag != "" {
|
||||||
var removed []*Rule
|
for _, rule := range r.rules {
|
||||||
for _, rule := range oldRules {
|
if rule.RuleTag != tag {
|
||||||
if rule.RuleTag != tag {
|
newRules = append(newRules, rule)
|
||||||
newRules = append(newRules, rule)
|
} else if rule.Webhook != nil {
|
||||||
} else {
|
rule.Webhook.Close()
|
||||||
removed = append(removed, rule)
|
}
|
||||||
}
|
}
|
||||||
|
r.rules = newRules
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
r.rules.Store(&newRules)
|
return errors.New("empty tag name!")
|
||||||
closeWebhooks(removed)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRule implements routing.Router
|
// ListRule implements routing.Router
|
||||||
func (r *Router) ListRule() []routing.Route {
|
func (r *Router) ListRule() []routing.Route {
|
||||||
rules := *r.rules.Load()
|
r.mu.Lock()
|
||||||
ruleList := make([]routing.Route, 0, len(rules))
|
defer r.mu.Unlock()
|
||||||
for _, rule := range rules {
|
ruleList := make([]routing.Route, 0)
|
||||||
|
for _, rule := range r.rules {
|
||||||
ruleList = append(ruleList, &Route{
|
ruleList = append(ruleList, &Route{
|
||||||
outboundTag: rule.Tag,
|
outboundTag: rule.Tag,
|
||||||
ruleTag: rule.RuleTag,
|
ruleTag: rule.RuleTag,
|
||||||
@@ -195,9 +254,7 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
|||||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||||
}
|
}
|
||||||
|
|
||||||
rules := *r.rules.Load()
|
for _, rule := range r.rules {
|
||||||
|
|
||||||
for _, rule := range rules {
|
|
||||||
if rule.Apply(ctx) {
|
if rule.Apply(ctx) {
|
||||||
return rule, ctx, nil
|
return rule, ctx, nil
|
||||||
}
|
}
|
||||||
@@ -210,7 +267,7 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
|||||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||||
|
|
||||||
// Try applying rules again if we have IPs.
|
// Try applying rules again if we have IPs.
|
||||||
for _, rule := range rules {
|
for _, rule := range r.rules {
|
||||||
if rule.Apply(ctx) {
|
if rule.Apply(ctx) {
|
||||||
return rule, ctx, nil
|
return rule, ctx, nil
|
||||||
}
|
}
|
||||||
@@ -224,9 +281,9 @@ func (r *Router) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeWebhooks closes all webhook notifiers in the given rule set.
|
// closeWebhooks closes all webhook notifiers in the current rule set.
|
||||||
func closeWebhooks(rules []*Rule) {
|
func (r *Router) closeWebhooks() {
|
||||||
for _, rule := range rules {
|
for _, rule := range r.rules {
|
||||||
if rule.Webhook != nil {
|
if rule.Webhook != nil {
|
||||||
rule.Webhook.Close()
|
rule.Webhook.Close()
|
||||||
}
|
}
|
||||||
@@ -237,7 +294,7 @@ func closeWebhooks(rules []*Rule) {
|
|||||||
func (r *Router) Close() error {
|
func (r *Router) Close() error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
closeWebhooks(*r.rules.Load())
|
r.closeWebhooks()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ func TestSelectLeastExpected(t *testing.T) {
|
|||||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectLeastExpected2(t *testing.T) {
|
func TestSelectLeastExpected2(t *testing.T) {
|
||||||
strategy := &LeastLoadStrategy{
|
strategy := &LeastLoadStrategy{
|
||||||
settings: &StrategyLeastLoadConfig{
|
settings: &StrategyLeastLoadConfig{
|
||||||
@@ -103,7 +102,6 @@ func TestSelectLeastExpected2(t *testing.T) {
|
|||||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
||||||
strategy := &LeastLoadStrategy{
|
strategy := &LeastLoadStrategy{
|
||||||
settings: &StrategyLeastLoadConfig{
|
settings: &StrategyLeastLoadConfig{
|
||||||
@@ -124,7 +122,6 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
|||||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
||||||
strategy := &LeastLoadStrategy{
|
strategy := &LeastLoadStrategy{
|
||||||
settings: &StrategyLeastLoadConfig{
|
settings: &StrategyLeastLoadConfig{
|
||||||
@@ -145,7 +142,6 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
|||||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectLeastLoadBaselines(t *testing.T) {
|
func TestSelectLeastLoadBaselines(t *testing.T) {
|
||||||
strategy := &LeastLoadStrategy{
|
strategy := &LeastLoadStrategy{
|
||||||
settings: &StrategyLeastLoadConfig{
|
settings: &StrategyLeastLoadConfig{
|
||||||
@@ -164,7 +160,6 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
|
|||||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
|
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
|
||||||
strategy := &LeastLoadStrategy{
|
strategy := &LeastLoadStrategy{
|
||||||
settings: &StrategyLeastLoadConfig{
|
settings: &StrategyLeastLoadConfig{
|
||||||
|
|||||||
+25
-17
@@ -8,7 +8,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -17,6 +16,8 @@ import (
|
|||||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func ptr[T any](v T) *T { return &v }
|
func ptr[T any](v T) *T { return &v }
|
||||||
|
|
||||||
type event struct {
|
type event struct {
|
||||||
@@ -41,7 +42,6 @@ type WebhookNotifier struct {
|
|||||||
deduplication uint32
|
deduplication uint32
|
||||||
client *http.Client
|
client *http.Client
|
||||||
seen sync.Map
|
seen sync.Map
|
||||||
lastSweep atomic.Int64
|
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
@@ -79,6 +79,11 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.deduplication > 0 {
|
||||||
|
h.wg.Add(1)
|
||||||
|
go h.cleanupLoop()
|
||||||
|
}
|
||||||
|
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +203,6 @@ func (h *WebhookNotifier) isDuplicate(email string) bool {
|
|||||||
}
|
}
|
||||||
ttl := time.Duration(h.deduplication) * time.Second
|
ttl := time.Duration(h.deduplication) * time.Second
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
h.maybeSweep(now, ttl)
|
|
||||||
if v, loaded := h.seen.LoadOrStore(email, now); loaded {
|
if v, loaded := h.seen.LoadOrStore(email, now); loaded {
|
||||||
if now.Sub(v.(time.Time)) < ttl {
|
if now.Sub(v.(time.Time)) < ttl {
|
||||||
return true
|
return true
|
||||||
@@ -208,23 +212,27 @@ func (h *WebhookNotifier) isDuplicate(email string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *WebhookNotifier) maybeSweep(now time.Time, ttl time.Duration) {
|
func (h *WebhookNotifier) cleanupLoop() {
|
||||||
last := h.lastSweep.Load()
|
defer h.wg.Done()
|
||||||
if now.UnixNano()-last < int64(ttl) {
|
ttl := time.Duration(h.deduplication) * time.Second
|
||||||
return
|
ticker := time.NewTicker(ttl)
|
||||||
}
|
defer ticker.Stop()
|
||||||
if !h.lastSweep.CompareAndSwap(last, now.UnixNano()) {
|
for {
|
||||||
return // another goroutine did the sweep
|
select {
|
||||||
}
|
case <-h.done:
|
||||||
h.seen.Range(func(key, value any) bool {
|
return
|
||||||
if now.Sub(value.(time.Time)) >= ttl {
|
case <-ticker.C:
|
||||||
h.seen.Delete(key)
|
now := time.Now()
|
||||||
|
h.seen.Range(func(key, value any) bool {
|
||||||
|
if now.Sub(value.(time.Time)) >= ttl {
|
||||||
|
h.seen.Delete(key)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return true
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only need to call if the Notifier is really used, otherwise GC can clean it
|
|
||||||
func (h *WebhookNotifier) Close() error {
|
func (h *WebhookNotifier) Close() error {
|
||||||
h.closeOnce.Do(func() {
|
h.closeOnce.Do(func() {
|
||||||
close(h.done)
|
close(h.done)
|
||||||
|
|||||||
@@ -48,20 +48,6 @@ func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrRegisterCounter implements stats.Manager.
|
|
||||||
func (m *Manager) GetOrRegisterCounter(name string) (stats.Counter, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
|
|
||||||
if c, found := m.counters[name]; found {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
errors.LogDebug(context.Background(), "create new counter ", name)
|
|
||||||
c := new(Counter)
|
|
||||||
m.counters[name] = c
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnregisterCounter implements stats.Manager.
|
// UnregisterCounter implements stats.Manager.
|
||||||
func (m *Manager) UnregisterCounter(name string) error {
|
func (m *Manager) UnregisterCounter(name string) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
@@ -111,20 +97,6 @@ func (m *Manager) RegisterOnlineMap(name string) (stats.OnlineMap, error) {
|
|||||||
return om, nil
|
return om, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrRegisterOnlineMap implements stats.Manager.
|
|
||||||
func (m *Manager) GetOrRegisterOnlineMap(name string) (stats.OnlineMap, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
|
|
||||||
if om, found := m.onlineMaps[name]; found {
|
|
||||||
return om, nil
|
|
||||||
}
|
|
||||||
errors.LogDebug(context.Background(), "create new OnlineMap ", name)
|
|
||||||
om := NewOnlineMap()
|
|
||||||
m.onlineMaps[name] = om
|
|
||||||
return om, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnregisterOnlineMap implements stats.Manager.
|
// UnregisterOnlineMap implements stats.Manager.
|
||||||
func (m *Manager) UnregisterOnlineMap(name string) error {
|
func (m *Manager) UnregisterOnlineMap(name string) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
@@ -177,26 +149,6 @@ func (m *Manager) RegisterChannel(name string) (stats.Channel, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrRegisterChannel implements stats.Manager.
|
|
||||||
func (m *Manager) GetOrRegisterChannel(name string) (stats.Channel, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
|
|
||||||
if c, found := m.channels[name]; found {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
errors.LogDebug(context.Background(), "create new channel ", name)
|
|
||||||
c := NewChannel(&ChannelConfig{BufferSize: 64, Blocking: false})
|
|
||||||
if m.running {
|
|
||||||
// Start before publishing so no goroutine can observe an unstarted channel.
|
|
||||||
if err := c.Start(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.channels[name] = c
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnregisterChannel implements stats.Manager.
|
// UnregisterChannel implements stats.Manager.
|
||||||
func (m *Manager) UnregisterChannel(name string) error {
|
func (m *Manager) UnregisterChannel(name string) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestInterface(t *testing.T) {
|
func TestInterface(t *testing.T) {
|
||||||
_ = stats.Manager(new(Manager))
|
_ = (stats.Manager)(new(Manager))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatsChannelRunnable(t *testing.T) {
|
func TestStatsChannelRunnable(t *testing.T) {
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ package version
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
|
|||||||
+1
-1
@@ -122,7 +122,7 @@ func NewReader(reader io.Reader) Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, isFile := reader.(*os.File)
|
_, isFile := reader.(*os.File)
|
||||||
if !isFile && useReadV() {
|
if !isFile && useReadv {
|
||||||
if sc, ok := reader.(syscall.Conn); ok {
|
if sc, ok := reader.(syscall.Conn); ok {
|
||||||
rawConn, err := sc.SyscallConn()
|
rawConn, err := sc.SyscallConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
|
|||||||
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
|
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
|
||||||
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
|
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
|
||||||
n := len(dest)
|
n := len(dest)
|
||||||
if n > 0 && !dest[n-1].IsFull() {
|
if n > 0 && !(dest)[n-1].IsFull() {
|
||||||
nBytes, _ := dest[n-1].Write(src)
|
nBytes, _ := (dest)[n-1].Write(src)
|
||||||
src = src[nBytes:]
|
src = src[nBytes:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,11 +121,11 @@ func TestPacketReader_ReadMultiBuffer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReaderInterface(t *testing.T) {
|
func TestReaderInterface(t *testing.T) {
|
||||||
_ = io.Reader(new(ReadVReader))
|
_ = (io.Reader)(new(ReadVReader))
|
||||||
_ = Reader(new(ReadVReader))
|
_ = (Reader)(new(ReadVReader))
|
||||||
|
|
||||||
_ = Reader(new(BufferedReader))
|
_ = (Reader)(new(BufferedReader))
|
||||||
_ = io.Reader(new(BufferedReader))
|
_ = (io.Reader)(new(BufferedReader))
|
||||||
_ = io.ByteReader(new(BufferedReader))
|
_ = (io.ByteReader)(new(BufferedReader))
|
||||||
_ = io.WriterTo(new(BufferedReader))
|
_ = (io.WriterTo)(new(BufferedReader))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (r *posixReader) Init(bs []*Buffer) {
|
|||||||
}
|
}
|
||||||
for idx, b := range bs {
|
for idx, b := range bs {
|
||||||
iovecs = append(iovecs, syscall.Iovec{
|
iovecs = append(iovecs, syscall.Iovec{
|
||||||
Base: &b.v[0],
|
Base: &(b.v[0]),
|
||||||
})
|
})
|
||||||
iovecs[idx].SetLen(int(Size))
|
iovecs[idx].SetLen(int(Size))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package buf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/platform"
|
"github.com/xtls/xray-core/common/platform"
|
||||||
@@ -144,24 +143,13 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
|
|||||||
return mb, nil
|
return mb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var useReadv atomic.Bool
|
var useReadv bool
|
||||||
|
|
||||||
func useReadV() bool {
|
|
||||||
return useReadv.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadEnvSettings() error {
|
|
||||||
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
|
||||||
value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })
|
|
||||||
enabled := false
|
|
||||||
switch value {
|
|
||||||
case defaultFlagValue, "auto", "enable":
|
|
||||||
enabled = true
|
|
||||||
}
|
|
||||||
useReadv.Store(enabled)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
platform.RegisterEnvReload(reloadEnvSettings)
|
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
||||||
|
value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })
|
||||||
|
switch value {
|
||||||
|
case defaultFlagValue, "auto", "enable":
|
||||||
|
useReadv = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
func useReadV() bool {
|
const useReadv = false
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
|
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type windowsReader struct {
|
type windowsReader struct {
|
||||||
bufs []syscall.WSABuf
|
bufs []syscall.WSABuf
|
||||||
ready bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Init(bs []*Buffer) {
|
func (r *windowsReader) Init(bs []*Buffer) {
|
||||||
@@ -16,7 +15,6 @@ func (r *windowsReader) Init(bs []*Buffer) {
|
|||||||
for _, b := range bs {
|
for _, b := range bs {
|
||||||
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
|
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
|
||||||
}
|
}
|
||||||
r.ready = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Clear() {
|
func (r *windowsReader) Clear() {
|
||||||
@@ -27,14 +25,6 @@ func (r *windowsReader) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *windowsReader) Read(fd uintptr) int32 {
|
func (r *windowsReader) Read(fd uintptr) int32 {
|
||||||
// On the first invocation, we return -1 to indicate "not ready"
|
|
||||||
// to make rawConn.Read wait for readability using the runtime's own mechanism
|
|
||||||
// because syscall.WSARecv() is a blocking call when used with nil OVERLAPPED
|
|
||||||
if !r.ready {
|
|
||||||
r.ready = true
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
var nBytes uint32
|
var nBytes uint32
|
||||||
var flags uint32
|
var flags uint32
|
||||||
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
|
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
|
||||||
|
|||||||
@@ -118,9 +118,7 @@ func (w *BufferedWriter) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
nBytes, err := w.buffer.Write(b)
|
nBytes, err := w.buffer.Write(b)
|
||||||
totalBytes += nBytes
|
totalBytes += nBytes
|
||||||
|
if err != nil {
|
||||||
// ErrBufferFull means a partial write, so flush below and continue
|
|
||||||
if err != nil && err != ErrBufferFull {
|
|
||||||
return totalBytes, err
|
return totalBytes, err
|
||||||
}
|
}
|
||||||
if !w.buffered || w.buffer.IsFull() {
|
if !w.buffered || w.buffer.IsFull() {
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
|
|
||||||
// [,)
|
// [,)
|
||||||
func RandBetween(from int64, to int64) int64 {
|
func RandBetween(from int64, to int64) int64 {
|
||||||
|
if from == to {
|
||||||
|
return from
|
||||||
|
}
|
||||||
if from > to {
|
if from > to {
|
||||||
from, to = to, from
|
from, to = to, from
|
||||||
}
|
}
|
||||||
if d := to - from; d == 0 || d == 1 {
|
|
||||||
return from
|
|
||||||
}
|
|
||||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||||
return from + bigInt.Int64()
|
return from + bigInt.Int64()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package geodata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
|
|
||||||
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
|
|
||||||
"0.0.0.0/8",
|
|
||||||
"10.0.0.0/8",
|
|
||||||
"100.64.0.0/10",
|
|
||||||
"127.0.0.0/8",
|
|
||||||
"169.254.0.0/16",
|
|
||||||
"172.16.0.0/12",
|
|
||||||
"192.0.0.0/24",
|
|
||||||
"192.0.2.0/24",
|
|
||||||
"192.88.99.0/24",
|
|
||||||
"192.168.0.0/16",
|
|
||||||
"198.18.0.0/15",
|
|
||||||
"198.51.100.0/24",
|
|
||||||
"203.0.113.0/24",
|
|
||||||
"224.0.0.0/3",
|
|
||||||
"::/127",
|
|
||||||
"fc00::/7",
|
|
||||||
"fe80::/10",
|
|
||||||
"ff00::/8",
|
|
||||||
}))))
|
|
||||||
})
|
|
||||||
|
|
||||||
func GetPrivateIPMatcher() IPMatcher { return privateIPMatcher() }
|
|
||||||
|
|
||||||
var privateDomainMatcher = sync.OnceValue(func() DomainMatcher {
|
|
||||||
return common.Must2(DomainReg.BuildDomainMatcher(common.Must2(ParseDomainRules([]string{
|
|
||||||
"lan",
|
|
||||||
"localdomain",
|
|
||||||
"example",
|
|
||||||
"invalid",
|
|
||||||
"localhost",
|
|
||||||
"test",
|
|
||||||
"local",
|
|
||||||
"home.arpa",
|
|
||||||
"internal",
|
|
||||||
"regexp:^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$", // Dotless domains
|
|
||||||
}, Domain_Domain))))
|
|
||||||
})
|
|
||||||
|
|
||||||
func GetPrivateDomainMatcher() DomainMatcher { return privateDomainMatcher() }
|
|
||||||
@@ -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(strings.ToLower(d.Value))
|
return strmatcher.Domain.New(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:
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ 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 *utils.WeakCacheMap[uuid.UUID, DynamicDomainMatcher]
|
matchers []*DynamicDomainMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||||
@@ -26,7 +24,7 @@ func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher,
|
|||||||
}
|
}
|
||||||
|
|
||||||
d := NewDynamicDomainMatcher(rules, m)
|
d := NewDynamicDomainMatcher(rules, m)
|
||||||
r.matchers.Store(uuid.New(), d)
|
r.matchers = append(r.matchers, d)
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,20 +32,15 @@ func (r *DomainRegistry) Reload() error {
|
|||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
var matchers []*DynamicDomainMatcher
|
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(r.matchers), " domain matcher(s)")
|
||||||
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(matchers))
|
reloaded := make([]reloadEntry, len(r.matchers))
|
||||||
for i, d := range matchers {
|
for i, d := range r.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)
|
||||||
@@ -59,14 +52,13 @@ 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(matchers), " domain matcher(s)")
|
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(r.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](),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matcher.AnyMatch([]net.IP{
|
if !matcher.AnyMatch([]net.IP{
|
||||||
{},
|
net.IP{},
|
||||||
ip("1.1.1.1"),
|
ip("1.1.1.1"),
|
||||||
ip("8.8.8.8"),
|
ip("8.8.8.8"),
|
||||||
}) {
|
}) {
|
||||||
@@ -345,7 +345,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
|
|||||||
|
|
||||||
if matcher.Matches([]net.IP{
|
if matcher.Matches([]net.IP{
|
||||||
ip("8.8.8.8"),
|
ip("8.8.8.8"),
|
||||||
{},
|
net.IP{},
|
||||||
}) {
|
}) {
|
||||||
t.Fatal("expect Matches to be false when any IP is invalid")
|
t.Fatal("expect Matches to be false when any IP is invalid")
|
||||||
}
|
}
|
||||||
@@ -362,7 +362,7 @@ func TestIPMatcherFilterIPs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
matched, unmatched := matcher.FilterIPs([]net.IP{
|
matched, unmatched := matcher.FilterIPs([]net.IP{
|
||||||
{},
|
net.IP{},
|
||||||
ip("8.8.8.8"),
|
ip("8.8.8.8"),
|
||||||
ip("91.108.255.254"),
|
ip("91.108.255.254"),
|
||||||
ip("1.1.1.1"),
|
ip("1.1.1.1"),
|
||||||
|
|||||||
@@ -7,27 +7,25 @@ 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
|
||||||
factory *IPSetFactory
|
ipsetFactory *IPSetFactory
|
||||||
matchers *utils.WeakCacheMap[uuid.UUID, DynamicIPMatcher]
|
matchers []*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.factory, rules)
|
m, err := buildOptimizedIPMatcher(r.ipsetFactory, rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d := NewDynamicIPMatcher(rules, m)
|
d := NewDynamicIPMatcher(rules, m)
|
||||||
r.matchers.Store(uuid.New(), d)
|
r.matchers = append(r.matchers, d)
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,20 +33,15 @@ func (r *IPRegistry) Reload() error {
|
|||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
var matchers []*DynamicIPMatcher
|
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||||
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(matchers))
|
reloaded := make([]reloadEntry, len(r.matchers))
|
||||||
for i, d := range matchers {
|
for i, d := range r.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)
|
||||||
@@ -59,15 +52,14 @@ func (r *IPRegistry) Reload() error {
|
|||||||
for _, entry := range reloaded {
|
for _, entry := range reloaded {
|
||||||
entry.dynamic.Reload(entry.matcher)
|
entry.dynamic.Reload(entry.matcher)
|
||||||
}
|
}
|
||||||
r.factory = factory
|
r.ipsetFactory = factory
|
||||||
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(matchers), " IP matcher(s)")
|
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIPRegistry() *IPRegistry {
|
func newIPRegistry() *IPRegistry {
|
||||||
return &IPRegistry{
|
return &IPRegistry{
|
||||||
factory: newIPSetFactory(),
|
ipsetFactory: newIPSetFactory(),
|
||||||
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicIPMatcher](),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func ParseDomainRule(r string, defaultType Domain_Type) (*DomainRule, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prefix := 0
|
prefix := 0
|
||||||
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
|
for _, ext := range [...]string{"ext:", "ext-domain:"} {
|
||||||
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:", "ext-site:"} {
|
for _, ext := range [...]string{"ext:", "ext-domain:"} {
|
||||||
if strings.HasPrefix(r, ext) {
|
if strings.HasPrefix(r, ext) {
|
||||||
prefix = len(ext)
|
prefix = len(ext)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ func (l *generalLogger) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *generalLogger) Handle(msg Message) {
|
func (l *generalLogger) Handle(msg Message) {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case l.buffer <- msg:
|
case l.buffer <- msg:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,350 +0,0 @@
|
|||||||
//go:build darwin && !ios
|
|
||||||
|
|
||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
darwinProcPIDListFDs = 1
|
|
||||||
darwinProcPIDFDSocketInfo = 3
|
|
||||||
darwinProcFDTypeSocket = 2
|
|
||||||
darwinProcFDInfoSize = 8
|
|
||||||
darwinSocketFDInfoSize = 792
|
|
||||||
darwinSocketFDInfoPSIOff = 24
|
|
||||||
darwinSocketInfoProtoOff = darwinSocketFDInfoPSIOff + 156
|
|
||||||
darwinSocketInfoFamilyOff = darwinSocketFDInfoPSIOff + 160
|
|
||||||
darwinSocketInfoKindOff = darwinSocketFDInfoPSIOff + 232
|
|
||||||
darwinSocketInfoInSockOff = darwinSocketFDInfoPSIOff + 240
|
|
||||||
darwinInSockInfoFPortOff = darwinSocketInfoInSockOff
|
|
||||||
darwinInSockInfoLPortOff = darwinSocketInfoInSockOff + 4
|
|
||||||
darwinInSockInfoVFlagOff = darwinSocketInfoInSockOff + 24
|
|
||||||
darwinInSockInfoFAddrOff = darwinSocketInfoInSockOff + 32
|
|
||||||
darwinInSockInfoLAddrOff = darwinSocketInfoInSockOff + 48
|
|
||||||
darwinInSockInfoSize = 80
|
|
||||||
darwinInSockInfoIPv4 = 0x1
|
|
||||||
darwinInSockInfoIPv6 = 0x2
|
|
||||||
darwinSockInfoIN = 1
|
|
||||||
darwinSockInfoTCP = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type darwinSocketMatchLevel int
|
|
||||||
|
|
||||||
const (
|
|
||||||
darwinSocketNoMatch darwinSocketMatchLevel = iota
|
|
||||||
darwinSocketPortMatch
|
|
||||||
darwinSocketRemoteMatch
|
|
||||||
darwinSocketLocalMatch
|
|
||||||
darwinSocketExactMatch
|
|
||||||
)
|
|
||||||
|
|
||||||
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (PID int, Name string, AbsolutePath string, err error) {
|
|
||||||
isLocal, err := IsLocal(net.ParseIP(srcIP))
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", "", errors.New("failed to determine if address is local: ", err)
|
|
||||||
}
|
|
||||||
if !isLocal {
|
|
||||||
return 0, "", "", ErrNotLocal
|
|
||||||
}
|
|
||||||
if network != "tcp" && network != "udp" {
|
|
||||||
panic("Unsupported network type for process lookup.")
|
|
||||||
}
|
|
||||||
|
|
||||||
srcAddr, err := netip.ParseAddr(srcIP)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", "", errors.New("invalid source IP address: ", srcIP)
|
|
||||||
}
|
|
||||||
srcAddr = srcAddr.Unmap()
|
|
||||||
|
|
||||||
var dstAddr netip.Addr
|
|
||||||
hasDstAddr := false
|
|
||||||
if destIP != "" && destPort != 0 {
|
|
||||||
dstAddr, err = netip.ParseAddr(destIP)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", "", errors.New("invalid destination IP address: ", destIP)
|
|
||||||
}
|
|
||||||
dstAddr = dstAddr.Unmap()
|
|
||||||
hasDstAddr = true
|
|
||||||
}
|
|
||||||
|
|
||||||
processes, err := unix.SysctlKinfoProcSlice("kern.proc.all")
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", "", errors.New("failed to list processes").Base(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bestPID int32
|
|
||||||
bestLevel := darwinSocketNoMatch
|
|
||||||
ambiguousBest := false
|
|
||||||
|
|
||||||
for _, process := range processes {
|
|
||||||
pid := process.Proc.P_pid
|
|
||||||
if pid <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
matchLevel, err := darwinProcessSocketMatchLevel(pid, network, srcAddr, srcPort, dstAddr, destPort, hasDstAddr)
|
|
||||||
if err != nil || matchLevel == darwinSocketNoMatch {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if matchLevel == darwinSocketExactMatch {
|
|
||||||
bestPID = pid
|
|
||||||
bestLevel = matchLevel
|
|
||||||
ambiguousBest = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if matchLevel > bestLevel {
|
|
||||||
bestPID = pid
|
|
||||||
bestLevel = matchLevel
|
|
||||||
ambiguousBest = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if matchLevel == bestLevel {
|
|
||||||
ambiguousBest = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bestLevel == darwinSocketNoMatch {
|
|
||||||
return 0, "", "", errors.New("process not found for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
|
|
||||||
}
|
|
||||||
if ambiguousBest {
|
|
||||||
return 0, "", "", errors.New("ambiguous process match for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
absPath, err := darwinProcessPath(bestPID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", "", errors.New("could not get process path for PID ", bestPID, ": ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
absPath = filepath.ToSlash(absPath)
|
|
||||||
return int(bestPID), filepath.Base(absPath), absPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinProcessSocketMatchLevel(pid int32, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) (darwinSocketMatchLevel, error) {
|
|
||||||
fds, err := darwinProcessFDs(pid)
|
|
||||||
if err != nil {
|
|
||||||
return darwinSocketNoMatch, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bestLevel := darwinSocketNoMatch
|
|
||||||
info := make([]byte, darwinSocketFDInfoSize)
|
|
||||||
for fd := 0; fd+darwinProcFDInfoSize <= len(fds); fd += darwinProcFDInfoSize {
|
|
||||||
fdNumber := int32(darwinReadNativeUint32(fds[fd : fd+4]))
|
|
||||||
fdType := darwinReadNativeUint32(fds[fd+4 : fd+8])
|
|
||||||
if fdType != darwinProcFDTypeSocket {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := darwinProcPIDFDInfo(pid, fdNumber, darwinProcPIDFDSocketInfo, info)
|
|
||||||
if err != nil || n < darwinSocketInfoInSockOff+darwinInSockInfoSize {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
level := darwinSocketInfoMatchLevel(info[:n], network, srcAddr, srcPort, dstAddr, dstPort, hasDstAddr)
|
|
||||||
if level == darwinSocketExactMatch {
|
|
||||||
return level, nil
|
|
||||||
}
|
|
||||||
if level > bestLevel {
|
|
||||||
bestLevel = level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestLevel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinProcessFDs(pid int32) ([]byte, error) {
|
|
||||||
n, err := darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n <= 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, n)
|
|
||||||
n, err = darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf[:n], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinSocketInfoMatchLevel(info []byte, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) darwinSocketMatchLevel {
|
|
||||||
protocol := int(darwinReadNativeUint32(info[darwinSocketInfoProtoOff : darwinSocketInfoProtoOff+4]))
|
|
||||||
family := int(darwinReadNativeUint32(info[darwinSocketInfoFamilyOff : darwinSocketInfoFamilyOff+4]))
|
|
||||||
kind := int(darwinReadNativeUint32(info[darwinSocketInfoKindOff : darwinSocketInfoKindOff+4]))
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp":
|
|
||||||
if protocol != unix.IPPROTO_TCP || kind != darwinSockInfoTCP {
|
|
||||||
return darwinSocketNoMatch
|
|
||||||
}
|
|
||||||
case "udp":
|
|
||||||
if protocol != unix.IPPROTO_UDP || kind != darwinSockInfoIN {
|
|
||||||
return darwinSocketNoMatch
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return darwinSocketNoMatch
|
|
||||||
}
|
|
||||||
|
|
||||||
vflag := info[darwinInSockInfoVFlagOff]
|
|
||||||
if srcAddr.Is4() {
|
|
||||||
// Dual-stack sockets expose IPv4-mapped connections as AF_INET6
|
|
||||||
// while marking the endpoint as IPv4 in ini_vflag.
|
|
||||||
if (family != unix.AF_INET && family != unix.AF_INET6) || vflag&darwinInSockInfoIPv4 == 0 {
|
|
||||||
return darwinSocketNoMatch
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if family != unix.AF_INET6 || vflag&darwinInSockInfoIPv6 == 0 {
|
|
||||||
return darwinSocketNoMatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localPort := int32(darwinReadNativeUint32(info[darwinInSockInfoLPortOff : darwinInSockInfoLPortOff+4]))
|
|
||||||
if !darwinPortMatches(localPort, srcPort) {
|
|
||||||
return darwinSocketNoMatch
|
|
||||||
}
|
|
||||||
localAddrMatches := darwinAddrMatchesOrUnspecified(info[darwinInSockInfoLAddrOff:darwinInSockInfoLAddrOff+16], srcAddr)
|
|
||||||
|
|
||||||
foreignAddrRaw := info[darwinInSockInfoFAddrOff : darwinInSockInfoFAddrOff+16]
|
|
||||||
foreignPort := int32(darwinReadNativeUint32(info[darwinInSockInfoFPortOff : darwinInSockInfoFPortOff+4]))
|
|
||||||
|
|
||||||
if !hasDstAddr {
|
|
||||||
if localAddrMatches {
|
|
||||||
return darwinSocketExactMatch
|
|
||||||
}
|
|
||||||
return darwinSocketNoMatch
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteMatches := darwinPortMatches(foreignPort, dstPort) && darwinAddrMatches(foreignAddrRaw, dstAddr)
|
|
||||||
if network == "udp" && darwinEndpointIsZero(foreignAddrRaw, foreignPort) && localAddrMatches {
|
|
||||||
return darwinSocketExactMatch
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case localAddrMatches && remoteMatches:
|
|
||||||
return darwinSocketExactMatch
|
|
||||||
case localAddrMatches:
|
|
||||||
return darwinSocketLocalMatch
|
|
||||||
case remoteMatches:
|
|
||||||
return darwinSocketRemoteMatch
|
|
||||||
default:
|
|
||||||
return darwinSocketPortMatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinPortMatches(value int32, port uint16) bool {
|
|
||||||
raw := uint16(value)
|
|
||||||
return raw == port || darwinNtohs(raw) == port
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinNtohs(value uint16) uint16 {
|
|
||||||
return value<<8 | value>>8
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinAddrMatches(raw []byte, addr netip.Addr) bool {
|
|
||||||
if addr.Is4() {
|
|
||||||
ip := addr.As4()
|
|
||||||
return bytes.Equal(raw[12:16], ip[:])
|
|
||||||
}
|
|
||||||
ip := addr.As16()
|
|
||||||
return bytes.Equal(raw, ip[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinAddrMatchesOrUnspecified(raw []byte, addr netip.Addr) bool {
|
|
||||||
if darwinAddrMatches(raw, addr) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if addr.Is4() {
|
|
||||||
return darwinBytesAreZero(raw[12:16])
|
|
||||||
}
|
|
||||||
return darwinBytesAreZero(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinEndpointIsZero(rawAddr []byte, port int32) bool {
|
|
||||||
return uint32(port) == 0 && darwinBytesAreZero(rawAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinBytesAreZero(raw []byte) bool {
|
|
||||||
for _, value := range raw {
|
|
||||||
if value != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinReadNativeUint32(b []byte) uint32 {
|
|
||||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinProcessPath(pid int32) (string, error) {
|
|
||||||
buf := make([]byte, unix.PathMax)
|
|
||||||
n, err := darwinProcPIDPath(pid, buf)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if n <= 0 {
|
|
||||||
return "", errors.New("empty process path")
|
|
||||||
}
|
|
||||||
return strings.TrimRight(string(buf[:n]), "\x00"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinProcPIDInfo(pid int32, flavor int, arg uint64, buf []byte) (int, error) {
|
|
||||||
var ptr unsafe.Pointer
|
|
||||||
if len(buf) > 0 {
|
|
||||||
ptr = unsafe.Pointer(&buf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
r0, _, errno := syscall_syscall6(libc_proc_pidinfo_trampoline_addr, uintptr(pid), uintptr(flavor), uintptr(arg), uintptr(ptr), uintptr(len(buf)), 0)
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, errno
|
|
||||||
}
|
|
||||||
return int(r0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinProcPIDFDInfo(pid int32, fd int32, flavor int, buf []byte) (int, error) {
|
|
||||||
var ptr unsafe.Pointer
|
|
||||||
if len(buf) > 0 {
|
|
||||||
ptr = unsafe.Pointer(&buf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
r0, _, errno := syscall_syscall6(libc_proc_pidfdinfo_trampoline_addr, uintptr(pid), uintptr(fd), uintptr(flavor), uintptr(ptr), uintptr(len(buf)), 0)
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, errno
|
|
||||||
}
|
|
||||||
return int(r0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinProcPIDPath(pid int32, buf []byte) (int, error) {
|
|
||||||
r0, _, errno := syscall_syscall6(libc_proc_pidpath_trampoline_addr, uintptr(pid), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0, 0, 0)
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, errno
|
|
||||||
}
|
|
||||||
return int(r0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var libc_proc_pidinfo_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_proc_pidinfo proc_pidinfo "/usr/lib/libproc.dylib"
|
|
||||||
|
|
||||||
var libc_proc_pidfdinfo_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_proc_pidfdinfo proc_pidfdinfo "/usr/lib/libproc.dylib"
|
|
||||||
|
|
||||||
var libc_proc_pidpath_trampoline_addr uintptr
|
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_proc_pidpath proc_pidpath "/usr/lib/libproc.dylib"
|
|
||||||
|
|
||||||
// Implemented in the runtime package (runtime/sys_darwin.go).
|
|
||||||
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
|
||||||
|
|
||||||
//go:linkname syscall_syscall6 syscall.syscall6
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//go:build darwin && !ios
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
TEXT libc_proc_pidinfo_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_proc_pidinfo(SB)
|
|
||||||
GLOBL ·libc_proc_pidinfo_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_proc_pidinfo_trampoline_addr(SB)/8, $libc_proc_pidinfo_trampoline<>(SB)
|
|
||||||
|
|
||||||
TEXT libc_proc_pidfdinfo_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_proc_pidfdinfo(SB)
|
|
||||||
GLOBL ·libc_proc_pidfdinfo_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_proc_pidfdinfo_trampoline_addr(SB)/8, $libc_proc_pidfdinfo_trampoline<>(SB)
|
|
||||||
|
|
||||||
TEXT libc_proc_pidpath_trampoline<>(SB),NOSPLIT,$0-0
|
|
||||||
JMP libc_proc_pidpath(SB)
|
|
||||||
GLOBL ·libc_proc_pidpath_trampoline_addr(SB), RODATA, $8
|
|
||||||
DATA ·libc_proc_pidpath_trampoline_addr(SB)/8, $libc_proc_pidpath_trampoline<>(SB)
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
//go:build darwin && !ios
|
|
||||||
|
|
||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
stdnet "net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFindProcessDarwinTCP(t *testing.T) {
|
|
||||||
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
accepted := make(chan stdnet.Conn, 1)
|
|
||||||
go func() {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err == nil {
|
|
||||||
accepted <- conn
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(accepted)
|
|
||||||
}()
|
|
||||||
|
|
||||||
conn, err := stdnet.Dial("tcp", listener.Addr().String())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
serverConn := <-accepted
|
|
||||||
if serverConn == nil {
|
|
||||||
t.Fatal("server did not accept tcp connection")
|
|
||||||
}
|
|
||||||
defer serverConn.Close()
|
|
||||||
|
|
||||||
local := conn.LocalAddr().(*stdnet.TCPAddr)
|
|
||||||
remote := conn.RemoteAddr().(*stdnet.TCPAddr)
|
|
||||||
|
|
||||||
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), remote.IP.String(), uint16(remote.Port))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCurrentProcess(t, pid, name, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindProcessDarwinTCPIPv4Mapped(t *testing.T) {
|
|
||||||
listener, err := stdnet.Listen("tcp4", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
accepted := make(chan stdnet.Conn, 1)
|
|
||||||
go func() {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err == nil {
|
|
||||||
accepted <- conn
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(accepted)
|
|
||||||
}()
|
|
||||||
|
|
||||||
listenerAddr := listener.Addr().(*stdnet.TCPAddr)
|
|
||||||
fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer unix.Close(fd)
|
|
||||||
|
|
||||||
mappedAddr := [16]byte{10: 0xff, 11: 0xff, 12: 127, 15: 1}
|
|
||||||
if err := unix.Connect(fd, &unix.SockaddrInet6{
|
|
||||||
Port: listenerAddr.Port,
|
|
||||||
Addr: mappedAddr,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
serverConn := <-accepted
|
|
||||||
if serverConn == nil {
|
|
||||||
t.Fatal("server did not accept tcp connection")
|
|
||||||
}
|
|
||||||
defer serverConn.Close()
|
|
||||||
|
|
||||||
local, err := unix.Getsockname(fd)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
localPort := local.(*unix.SockaddrInet6).Port
|
|
||||||
|
|
||||||
pid, name, path, err := FindProcess("tcp", "127.0.0.1", uint16(localPort), "127.0.0.1", uint16(listenerAddr.Port))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCurrentProcess(t, pid, name, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindProcessDarwinUDP(t *testing.T) {
|
|
||||||
conn, err := stdnet.ListenUDP("udp", &stdnet.UDPAddr{IP: stdnet.ParseIP("127.0.0.1")})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
local := conn.LocalAddr().(*stdnet.UDPAddr)
|
|
||||||
|
|
||||||
pid, name, path, err := FindProcess("udp", local.IP.String(), uint16(local.Port), "", 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCurrentProcess(t, pid, name, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindProcessDarwinNonLocal(t *testing.T) {
|
|
||||||
_, _, _, err := FindProcess("tcp", "203.0.113.1", 80, "", 0)
|
|
||||||
if err != ErrNotLocal {
|
|
||||||
t.Fatalf("expected ErrNotLocal, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindProcessDarwinUnsupportedNetwork(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if recover() == nil {
|
|
||||||
t.Fatal("expected panic")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, _, _, _ = FindProcess("icmp", "127.0.0.1", 0, "", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertCurrentProcess(t *testing.T, pid int, name string, path string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if pid != os.Getpid() {
|
|
||||||
t.Fatalf("expected pid %d, got %d (%s, %s)", os.Getpid(), pid, name, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
executable, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if path == "" || name == "" {
|
|
||||||
t.Fatalf("expected process path and name, got name=%q path=%q", name, path)
|
|
||||||
}
|
|
||||||
if sameFile(executable, path) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Fatalf("expected executable %q, got %q", executable, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameFile(left string, right string) bool {
|
|
||||||
leftInfo, leftErr := os.Stat(left)
|
|
||||||
rightInfo, rightErr := os.Stat(right)
|
|
||||||
if leftErr != nil || rightErr != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return os.SameFile(leftInfo, rightInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindProcessDarwinTCPWithoutDestination(t *testing.T) {
|
|
||||||
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
accepted := make(chan stdnet.Conn, 1)
|
|
||||||
go func() {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err == nil {
|
|
||||||
accepted <- conn
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(accepted)
|
|
||||||
}()
|
|
||||||
|
|
||||||
conn, err := stdnet.DialTimeout("tcp", listener.Addr().String(), time.Second)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
serverConn := <-accepted
|
|
||||||
if serverConn == nil {
|
|
||||||
t.Fatal("server did not accept tcp connection")
|
|
||||||
}
|
|
||||||
defer serverConn.Close()
|
|
||||||
|
|
||||||
local := conn.LocalAddr().(*stdnet.TCPAddr)
|
|
||||||
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), "", 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCurrentProcess(t, pid, name, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindProcessDarwinTCPWithDifferentDestination(t *testing.T) {
|
|
||||||
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
accepted := make(chan stdnet.Conn, 1)
|
|
||||||
go func() {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err == nil {
|
|
||||||
accepted <- conn
|
|
||||||
return
|
|
||||||
}
|
|
||||||
close(accepted)
|
|
||||||
}()
|
|
||||||
|
|
||||||
conn, err := stdnet.DialTimeout("tcp", listener.Addr().String(), time.Second)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
serverConn := <-accepted
|
|
||||||
if serverConn == nil {
|
|
||||||
t.Fatal("server did not accept tcp connection")
|
|
||||||
}
|
|
||||||
defer serverConn.Close()
|
|
||||||
|
|
||||||
local := conn.LocalAddr().(*stdnet.TCPAddr)
|
|
||||||
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), "203.0.113.10", 443)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assertCurrentProcess(t, pid, name, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDarwinSocketInfoMatchLevelFallbacks(t *testing.T) {
|
|
||||||
src := netip.MustParseAddr("198.18.0.2")
|
|
||||||
dst := netip.MustParseAddr("203.0.113.10")
|
|
||||||
otherLocal := netip.MustParseAddr("192.168.1.10")
|
|
||||||
otherRemote := netip.MustParseAddr("198.51.100.10")
|
|
||||||
unspecifiedLocal := netip.MustParseAddr("0.0.0.0")
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
local netip.Addr
|
|
||||||
remote netip.Addr
|
|
||||||
hasDst bool
|
|
||||||
wantLevel darwinSocketMatchLevel
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "exact",
|
|
||||||
local: src,
|
|
||||||
remote: dst,
|
|
||||||
hasDst: true,
|
|
||||||
wantLevel: darwinSocketExactMatch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unspecified local with matching remote",
|
|
||||||
local: unspecifiedLocal,
|
|
||||||
remote: dst,
|
|
||||||
hasDst: true,
|
|
||||||
wantLevel: darwinSocketExactMatch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unspecified local without destination",
|
|
||||||
local: unspecifiedLocal,
|
|
||||||
remote: otherRemote,
|
|
||||||
hasDst: false,
|
|
||||||
wantLevel: darwinSocketExactMatch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "local match with different remote",
|
|
||||||
local: src,
|
|
||||||
remote: otherRemote,
|
|
||||||
hasDst: true,
|
|
||||||
wantLevel: darwinSocketLocalMatch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "remote match with different local",
|
|
||||||
local: otherLocal,
|
|
||||||
remote: dst,
|
|
||||||
hasDst: true,
|
|
||||||
wantLevel: darwinSocketRemoteMatch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "port only with destination",
|
|
||||||
local: otherLocal,
|
|
||||||
remote: otherRemote,
|
|
||||||
hasDst: true,
|
|
||||||
wantLevel: darwinSocketPortMatch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "different local without destination",
|
|
||||||
local: otherLocal,
|
|
||||||
remote: otherRemote,
|
|
||||||
hasDst: false,
|
|
||||||
wantLevel: darwinSocketNoMatch,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
info := newDarwinSocketInfo("tcp", test.local, 12345, test.remote, 443)
|
|
||||||
level := darwinSocketInfoMatchLevel(info, "tcp", src, 12345, dst, 443, test.hasDst)
|
|
||||||
if level != test.wantLevel {
|
|
||||||
t.Fatalf("unexpected match level: got %d, want %d", level, test.wantLevel)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDarwinSocketInfoMatchLevelIPv4Mapped(t *testing.T) {
|
|
||||||
src := netip.MustParseAddr("127.0.0.1")
|
|
||||||
dst := netip.MustParseAddr("203.0.113.10")
|
|
||||||
info := newDarwinSocketInfo("tcp", src, 12345, dst, 443)
|
|
||||||
writeDarwinNativeUint32(info, darwinSocketInfoFamilyOff, uint32(unix.AF_INET6))
|
|
||||||
|
|
||||||
level := darwinSocketInfoMatchLevel(info, "tcp", src, 12345, dst, 443, true)
|
|
||||||
if level != darwinSocketExactMatch {
|
|
||||||
t.Fatalf("unexpected match level: got %d, want %d", level, darwinSocketExactMatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDarwinSocketInfo(network string, local netip.Addr, localPort uint16, remote netip.Addr, remotePort uint16) []byte {
|
|
||||||
info := make([]byte, darwinSocketFDInfoSize)
|
|
||||||
switch network {
|
|
||||||
case "tcp":
|
|
||||||
writeDarwinNativeUint32(info, darwinSocketInfoProtoOff, uint32(unix.IPPROTO_TCP))
|
|
||||||
writeDarwinNativeUint32(info, darwinSocketInfoKindOff, uint32(darwinSockInfoTCP))
|
|
||||||
case "udp":
|
|
||||||
writeDarwinNativeUint32(info, darwinSocketInfoProtoOff, uint32(unix.IPPROTO_UDP))
|
|
||||||
writeDarwinNativeUint32(info, darwinSocketInfoKindOff, uint32(darwinSockInfoIN))
|
|
||||||
}
|
|
||||||
writeDarwinNativeUint32(info, darwinSocketInfoFamilyOff, uint32(unix.AF_INET))
|
|
||||||
info[darwinInSockInfoVFlagOff] = darwinInSockInfoIPv4
|
|
||||||
writeDarwinNativeUint32(info, darwinInSockInfoLPortOff, uint32(localPort))
|
|
||||||
writeDarwinNativeUint32(info, darwinInSockInfoFPortOff, uint32(remotePort))
|
|
||||||
copyDarwinIPv4(info[darwinInSockInfoLAddrOff:darwinInSockInfoLAddrOff+16], local)
|
|
||||||
copyDarwinIPv4(info[darwinInSockInfoFAddrOff:darwinInSockInfoFAddrOff+16], remote)
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeDarwinNativeUint32(b []byte, offset int, value uint32) {
|
|
||||||
*(*uint32)(unsafe.Pointer(&b[offset])) = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDarwinIPv4(dst []byte, addr netip.Addr) {
|
|
||||||
ip := addr.As4()
|
|
||||||
copy(dst[12:16], ip[:])
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package net
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (int, string, string, error) {
|
|
||||||
return 0, "", "", errors.New("process lookup is not supported on this platform")
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !windows && !linux && !android && !darwin
|
//go:build !windows && !linux && !android
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package platform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var envReloadRegistry = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
handlers []func() error
|
|
||||||
}{}
|
|
||||||
|
|
||||||
// RegisterEnvReload registers an environment reload handler and runs it once
|
|
||||||
// immediately so package defaults keep the same behavior as init-time reads.
|
|
||||||
func RegisterEnvReload(handler func() error) {
|
|
||||||
if handler == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
envReloadRegistry.Lock()
|
|
||||||
envReloadRegistry.handlers = append(envReloadRegistry.handlers, handler)
|
|
||||||
envReloadRegistry.Unlock()
|
|
||||||
if err := handler(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReloadEnvSettings refreshes all registered environment-backed package state.
|
|
||||||
func ReloadEnvSettings() error {
|
|
||||||
envReloadRegistry.RLock()
|
|
||||||
handlers := append([]func() error{}, envReloadRegistry.handlers...)
|
|
||||||
envReloadRegistry.RUnlock()
|
|
||||||
|
|
||||||
var errs []error
|
|
||||||
for _, handler := range handlers {
|
|
||||||
if err := handler(); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(errs...)
|
|
||||||
}
|
|
||||||
@@ -36,10 +36,9 @@ func TestNormalizeEnvName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvFlag(t *testing.T) {
|
func TestEnvFlag(t *testing.T) {
|
||||||
v := EnvFlag{
|
if v := (EnvFlag{
|
||||||
Name: "xxxxx.y",
|
Name: "xxxxx.y",
|
||||||
}.GetValueAsInt(10)
|
}.GetValueAsInt(10)); v != 10 {
|
||||||
if v != 10 {
|
|
||||||
t.Error("env value: ", v)
|
t.Error("env value: ", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package bittorrent
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/buf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SniffHeader struct{}
|
type SniffHeader struct{}
|
||||||
@@ -36,44 +39,50 @@ func SniffUTP(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, common.ErrNoClue
|
return nil, common.ErrNoClue
|
||||||
}
|
}
|
||||||
|
|
||||||
// type 4 (ST_SYN), version 1
|
buffer := buf.FromBytes(b)
|
||||||
if b[0] != 0x41 {
|
|
||||||
|
var typeAndVersion uint8
|
||||||
|
|
||||||
|
if binary.Read(buffer, binary.BigEndian, &typeAndVersion) != nil {
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
} else if b[0]>>4&0xF > 4 || b[0]&0xF != 1 {
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
// timestamp_difference is always 0 in new connections
|
var extension uint8
|
||||||
if binary.BigEndian.Uint32(b[8:12]) != 0 {
|
|
||||||
|
if binary.Read(buffer, binary.BigEndian, &extension) != nil {
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
} else if extension != 0 && extension != 1 {
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the extension chain. Selective ack (1) and extension bits (2)
|
|
||||||
extension, offset := b[1], 20
|
|
||||||
for extension != 0 {
|
for extension != 0 {
|
||||||
if len(b) < offset+2 {
|
if extension != 1 {
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
length := int(b[offset+1])
|
if binary.Read(buffer, binary.BigEndian, &extension) != nil {
|
||||||
switch extension {
|
return nil, common.ErrNoClue
|
||||||
case 1: // selective ack
|
|
||||||
if length < 4 || length%4 != 0 {
|
|
||||||
return nil, errNotBittorrent
|
|
||||||
}
|
|
||||||
case 2: // extension bits: fixed 8 bytes, sent in ST_SYN by µTorrent
|
|
||||||
if length != 8 {
|
|
||||||
return nil, errNotBittorrent
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errNotBittorrent
|
|
||||||
}
|
}
|
||||||
if len(b) < offset+2+length {
|
|
||||||
return nil, errNotBittorrent
|
var length uint8
|
||||||
|
if err := binary.Read(buffer, binary.BigEndian, &length); err != nil {
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
if common.Error2(buffer.ReadBytes(int32(length))) != nil {
|
||||||
|
return nil, common.ErrNoClue
|
||||||
}
|
}
|
||||||
extension = b[offset]
|
|
||||||
offset += 2 + length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extensions should consume all ST_SYN payload
|
if common.Error2(buffer.ReadBytes(2)) != nil {
|
||||||
if len(b) != offset {
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp uint32
|
||||||
|
if err := binary.Read(buffer, binary.BigEndian, ×tamp); err != nil {
|
||||||
|
return nil, common.ErrNoClue
|
||||||
|
}
|
||||||
|
if math.Abs(float64(time.Now().UnixMicro()-int64(timestamp))) > float64(24*time.Hour) {
|
||||||
return nil, errNotBittorrent
|
return nil, errNotBittorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package bittorrent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// utpPacket builds the fixed 20-byte header defined by BEP 29.
|
|
||||||
func utpPacket(packetType, extension byte, tsDiff uint32, payload ...byte) []byte {
|
|
||||||
b := make([]byte, 20)
|
|
||||||
b[0] = packetType<<4 | 1
|
|
||||||
b[1] = extension
|
|
||||||
binary.BigEndian.PutUint16(b[2:4], 0x4a3f) // connection_id, random
|
|
||||||
binary.BigEndian.PutUint32(b[4:8], 0x8c3a91d2) // timestamp_microseconds, sender's clock
|
|
||||||
binary.BigEndian.PutUint32(b[8:12], tsDiff)
|
|
||||||
binary.BigEndian.PutUint32(b[12:16], 0x00100000) // wnd_size
|
|
||||||
binary.BigEndian.PutUint16(b[16:18], 0x71ee) // seq_nr, random in libutp/libtorrent
|
|
||||||
binary.BigEndian.PutUint16(b[18:20], 0x0000) // ack_nr
|
|
||||||
return append(b, payload...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffUTP(t *testing.T) {
|
|
||||||
selectiveAck := []byte{0, 4, 0xff, 0x00, 0xff, 0x00}
|
|
||||||
wrongVersion := utpPacket(4, 0, 0)
|
|
||||||
wrongVersion[0] = 4<<4 | 2
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
payload []byte
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{"syn", utpPacket(4, 0, 0), nil},
|
|
||||||
{"syn with selective ack", append(utpPacket(4, 1, 0), selectiveAck...), nil},
|
|
||||||
{"syn with extension bits", append(utpPacket(4, 2, 0), 0, 8, 1, 2, 3, 4, 5, 6, 7, 8), nil},
|
|
||||||
{"extension bits with wrong length", append(utpPacket(4, 2, 0), 0, 4, 1, 2, 3, 4), errNotBittorrent},
|
|
||||||
{"syn with nonzero timestamp_difference", utpPacket(4, 0, 0x1234), errNotBittorrent},
|
|
||||||
{"syn with trailing payload", utpPacket(4, 0, 0, 'x'), errNotBittorrent},
|
|
||||||
// txid 0x4100, no EDNS0: the worst case colliding with the uTP header
|
|
||||||
{"dns query", []byte{
|
|
||||||
0x41, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x01, 'a', 0x02, 'c', 'o', 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
||||||
}, errNotBittorrent},
|
|
||||||
{"established connection packets", utpPacket(0, 0, 0x5678, 'x', 'y', 'z'), errNotBittorrent},
|
|
||||||
{"state", utpPacket(2, 0, 0x5678), errNotBittorrent},
|
|
||||||
{"fin", utpPacket(1, 0, 0x5678), errNotBittorrent},
|
|
||||||
{"wrong version", wrongVersion, errNotBittorrent},
|
|
||||||
{"unknown packet type", utpPacket(5, 0, 0), errNotBittorrent},
|
|
||||||
{"unknown extension", utpPacket(4, 2, 0), errNotBittorrent},
|
|
||||||
{"extension chain past the datagram", utpPacket(4, 1, 0, 0, 8, 0xff), errNotBittorrent},
|
|
||||||
{"selective ack not in multiples of 4", append(utpPacket(4, 1, 0), 0, 3, 0xff, 0x00, 0xff), errNotBittorrent},
|
|
||||||
{"shorter than the header", utpPacket(4, 0, 0)[:19], common.ErrNoClue},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.name, func(t *testing.T) {
|
|
||||||
h, err := SniffUTP(c.payload)
|
|
||||||
if err != c.err {
|
|
||||||
t.Fatalf("expected error %v, got %v", c.err, err)
|
|
||||||
}
|
|
||||||
if err == nil && h == nil {
|
|
||||||
t.Fatal("expected a sniff header, got nil")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,6 +28,8 @@ const (
|
|||||||
SecurityType_AUTO SecurityType = 2
|
SecurityType_AUTO SecurityType = 2
|
||||||
SecurityType_AES128_GCM SecurityType = 3
|
SecurityType_AES128_GCM SecurityType = 3
|
||||||
SecurityType_CHACHA20_POLY1305 SecurityType = 4
|
SecurityType_CHACHA20_POLY1305 SecurityType = 4
|
||||||
|
SecurityType_NONE SecurityType = 5 // [DEPRECATED 2023-06]
|
||||||
|
SecurityType_ZERO SecurityType = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for SecurityType.
|
// Enum value maps for SecurityType.
|
||||||
@@ -37,12 +39,16 @@ var (
|
|||||||
2: "AUTO",
|
2: "AUTO",
|
||||||
3: "AES128_GCM",
|
3: "AES128_GCM",
|
||||||
4: "CHACHA20_POLY1305",
|
4: "CHACHA20_POLY1305",
|
||||||
|
5: "NONE",
|
||||||
|
6: "ZERO",
|
||||||
}
|
}
|
||||||
SecurityType_value = map[string]int32{
|
SecurityType_value = map[string]int32{
|
||||||
"UNKNOWN": 0,
|
"UNKNOWN": 0,
|
||||||
"AUTO": 2,
|
"AUTO": 2,
|
||||||
"AES128_GCM": 3,
|
"AES128_GCM": 3,
|
||||||
"CHACHA20_POLY1305": 4,
|
"CHACHA20_POLY1305": 4,
|
||||||
|
"NONE": 5,
|
||||||
|
"ZERO": 6,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,13 +129,15 @@ const file_common_protocol_headers_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"\x1dcommon/protocol/headers.proto\x12\x14xray.common.protocol\"H\n" +
|
"\x1dcommon/protocol/headers.proto\x12\x14xray.common.protocol\"H\n" +
|
||||||
"\x0eSecurityConfig\x126\n" +
|
"\x0eSecurityConfig\x126\n" +
|
||||||
"\x04type\x18\x01 \x01(\x0e2\".xray.common.protocol.SecurityTypeR\x04type*L\n" +
|
"\x04type\x18\x01 \x01(\x0e2\".xray.common.protocol.SecurityTypeR\x04type*`\n" +
|
||||||
"\fSecurityType\x12\v\n" +
|
"\fSecurityType\x12\v\n" +
|
||||||
"\aUNKNOWN\x10\x00\x12\b\n" +
|
"\aUNKNOWN\x10\x00\x12\b\n" +
|
||||||
"\x04AUTO\x10\x02\x12\x0e\n" +
|
"\x04AUTO\x10\x02\x12\x0e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"AES128_GCM\x10\x03\x12\x15\n" +
|
"AES128_GCM\x10\x03\x12\x15\n" +
|
||||||
"\x11CHACHA20_POLY1305\x10\x04B^\n" +
|
"\x11CHACHA20_POLY1305\x10\x04\x12\b\n" +
|
||||||
|
"\x04NONE\x10\x05\x12\b\n" +
|
||||||
|
"\x04ZERO\x10\x06B^\n" +
|
||||||
"\x18com.xray.common.protocolP\x01Z)github.com/xtls/xray-core/common/protocol\xaa\x02\x14Xray.Common.Protocolb\x06proto3"
|
"\x18com.xray.common.protocolP\x01Z)github.com/xtls/xray-core/common/protocol\xaa\x02\x14Xray.Common.Protocolb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ enum SecurityType {
|
|||||||
AUTO = 2;
|
AUTO = 2;
|
||||||
AES128_GCM = 3;
|
AES128_GCM = 3;
|
||||||
CHACHA20_POLY1305 = 4;
|
CHACHA20_POLY1305 = 4;
|
||||||
|
NONE = 5; // [DEPRECATED 2023-06]
|
||||||
|
ZERO = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SecurityConfig {
|
message SecurityConfig {
|
||||||
|
|||||||
@@ -1,41 +1,25 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ApplyTrustedXForwardedFor returns remoteAddr overridden by X-Forwarded-For only when a configured trusted header is present.
|
// ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it.
|
||||||
func ApplyTrustedXForwardedFor(header http.Header, trusted []string, remoteAddr net.Addr) net.Addr {
|
func ParseXForwardedFor(header http.Header) []net.Address {
|
||||||
value := header.Get("X-Forwarded-For")
|
xff := header.Get("X-Forwarded-For")
|
||||||
if value == "" {
|
if xff == "" {
|
||||||
return remoteAddr
|
return nil
|
||||||
}
|
}
|
||||||
for _, t := range trusted {
|
list := strings.Split(xff, ",")
|
||||||
if len(header.Values(t)) > 0 {
|
addrs := make([]net.Address, 0, len(list))
|
||||||
if idx := strings.IndexByte(value, ','); idx >= 0 {
|
for _, proxy := range list {
|
||||||
value = value[:idx]
|
addrs = append(addrs, net.ParseAddress(proxy))
|
||||||
}
|
|
||||||
if addr := net.ParseAddress(value); addr.Family().IsIP() {
|
|
||||||
return &net.TCPAddr{
|
|
||||||
IP: addr.IP(),
|
|
||||||
Port: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return remoteAddr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(trusted) == 0 {
|
return addrs
|
||||||
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.
|
||||||
|
|||||||
@@ -2,48 +2,23 @@ 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 TestApplyTrustedXForwardedFor(t *testing.T) {
|
func TestParseXForwardedFor(t *testing.T) {
|
||||||
remoteAddr := &gonet.TCPAddr{IP: gonet.ParseIP("127.0.0.1"), Port: 12345}
|
header := http.Header{}
|
||||||
|
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
||||||
t.Run("ignore X-Forwarded-For without trusted header", func(t *testing.T) {
|
addrs := ParseXForwardedFor(header)
|
||||||
header := http.Header{}
|
if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" {
|
||||||
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
t.Error(r)
|
||||||
|
}
|
||||||
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) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type ID struct {
|
|||||||
|
|
||||||
// Equals returns true if this ID equals to the other one.
|
// Equals returns true if this ID equals to the other one.
|
||||||
func (id *ID) Equals(another *ID) bool {
|
func (id *ID) Equals(another *ID) bool {
|
||||||
return id.uuid.Equals(&another.uuid)
|
return id.uuid.Equals(&(another.uuid))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (id *ID) Bytes() []byte {
|
func (id *ID) Bytes() []byte {
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
package quic
|
package quic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
_ "crypto/tls"
|
_ "crypto/tls"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CipherSuiteTLS13 struct {
|
||||||
|
ID uint16
|
||||||
|
KeyLen int
|
||||||
|
AEAD func(key, fixedNonce []byte) cipher.AEAD
|
||||||
|
Hash crypto.Hash
|
||||||
|
}
|
||||||
|
|
||||||
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
||||||
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package quic
|
|||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@@ -27,43 +28,22 @@ func (s SniffHeader) Domain() string {
|
|||||||
return s.domain
|
return s.domain
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
const (
|
||||||
errNotQUIC = errors.New("not quic")
|
versionDraft29 uint32 = 0xff00001d
|
||||||
errNotQUICInitial = errors.New("not initial packet")
|
version1 uint32 = 0x1
|
||||||
)
|
)
|
||||||
|
|
||||||
type quicVersionSpec struct {
|
|
||||||
ver uint32
|
|
||||||
typeInitial byte
|
|
||||||
initialSalt []byte
|
|
||||||
labelPrefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
quicDraft29 = quicVersionSpec{
|
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||||
ver: 0xff00001d,
|
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||||
typeInitial: 0b00,
|
initialSuite = &CipherSuiteTLS13{
|
||||||
initialSalt: []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99},
|
ID: tls.TLS_AES_128_GCM_SHA256,
|
||||||
labelPrefix: "quic",
|
KeyLen: 16,
|
||||||
}
|
AEAD: AEADAESGCMTLS13,
|
||||||
quicV1 = quicVersionSpec{
|
Hash: crypto.SHA256,
|
||||||
ver: 0x1,
|
|
||||||
typeInitial: 0b00,
|
|
||||||
initialSalt: []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a},
|
|
||||||
labelPrefix: "quic",
|
|
||||||
}
|
|
||||||
quicV2 = quicVersionSpec{
|
|
||||||
ver: 0x6b3343cf,
|
|
||||||
typeInitial: 0b01,
|
|
||||||
initialSalt: []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9},
|
|
||||||
labelPrefix: "quicv2",
|
|
||||||
}
|
|
||||||
|
|
||||||
quicVersionSpecMap = map[uint32]*quicVersionSpec{
|
|
||||||
quicDraft29.ver: &quicDraft29,
|
|
||||||
quicV1.ver: &quicV1,
|
|
||||||
quicV2.ver: &quicV2,
|
|
||||||
}
|
}
|
||||||
|
errNotQuic = errors.New("not quic")
|
||||||
|
errNotQuicInitial = errors.New("not initial packet")
|
||||||
)
|
)
|
||||||
|
|
||||||
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||||
@@ -83,61 +63,60 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
buffer := buf.FromBytes(b)
|
buffer := buf.FromBytes(b)
|
||||||
typeByte, err := buffer.ReadByte()
|
typeByte, err := buffer.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
isLongHeader := typeByte&0x80 > 0
|
isLongHeader := typeByte&0x80 > 0
|
||||||
if !isLongHeader || typeByte&0x40 == 0 {
|
if !isLongHeader || typeByte&0x40 == 0 {
|
||||||
return nil, errNotQUICInitial
|
return nil, errNotQuicInitial
|
||||||
}
|
}
|
||||||
|
|
||||||
vb, err := buffer.ReadBytes(4)
|
vb, err := buffer.ReadBytes(4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
versionNumber := binary.BigEndian.Uint32(vb)
|
versionNumber := binary.BigEndian.Uint32(vb)
|
||||||
var s *quicVersionSpec
|
if versionNumber != 0 && typeByte&0x40 == 0 {
|
||||||
if v, ok := quicVersionSpecMap[versionNumber]; ok {
|
return nil, errNotQuic
|
||||||
s = v
|
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
||||||
} else {
|
return nil, errNotQuic
|
||||||
return nil, errNotQUIC
|
|
||||||
}
|
|
||||||
|
|
||||||
var destConnID []byte
|
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
}
|
|
||||||
|
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
}
|
}
|
||||||
|
|
||||||
packetType := (typeByte & 0x30) >> 4
|
packetType := (typeByte & 0x30) >> 4
|
||||||
isQUICInitial := packetType == s.typeInitial
|
isQuicInitial := packetType == 0x0
|
||||||
|
|
||||||
if isQUICInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
var destConnID []byte
|
||||||
tokenLen, err := readShortQUICVarint(buffer)
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
}
|
||||||
|
|
||||||
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
||||||
|
return nil, errNotQuic
|
||||||
|
}
|
||||||
|
|
||||||
|
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||||
|
tokenLen, err := readShortQuicVarint(buffer)
|
||||||
if err != nil || tokenLen > int32(len(b)) {
|
if err != nil || tokenLen > int32(len(b)) {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packetLen, err := readShortQUICVarint(buffer)
|
packetLen, err := readShortQuicVarint(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
// packetLen is impossible to be shorter than this
|
// packetLen is impossible to be shorter than this
|
||||||
if packetLen < 4 {
|
if packetLen < 4 {
|
||||||
return nil, errNotQUIC
|
return nil, errNotQuic
|
||||||
}
|
}
|
||||||
|
|
||||||
hdrLen := len(b) - int(buffer.Len())
|
hdrLen := len(b) - int(buffer.Len())
|
||||||
@@ -146,23 +125,25 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restPayload := b[hdrLen+int(packetLen):]
|
restPayload := b[hdrLen+int(packetLen):]
|
||||||
if !isQUICInitial { // Skip this packet if it's not initial packet
|
if !isQuicInitial { // Skip this packet if it's not initial packet
|
||||||
b = restPayload
|
b = restPayload
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
salt := s.initialSalt
|
var salt []byte
|
||||||
label := s.labelPrefix
|
if versionNumber == version1 {
|
||||||
|
salt = quicSalt
|
||||||
|
} else {
|
||||||
|
salt = quicSaltOld
|
||||||
|
}
|
||||||
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||||
secret := hkdfExpandLabel(initialSecret, "client in", crypto.SHA256.Size())
|
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
||||||
hpKey := hkdfExpandLabel(secret, label+" hp", 16)
|
hpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, "quic hp", initialSuite.KeyLen)
|
||||||
block, err := aes.NewCipher(hpKey)
|
block, err := aes.NewCipher(hpKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(b) < hdrLen+4+block.BlockSize() {
|
|
||||||
return nil, errNotQUIC
|
|
||||||
}
|
|
||||||
cache.Clear()
|
cache.Clear()
|
||||||
mask := cache.Extend(int32(block.BlockSize()))
|
mask := cache.Extend(int32(block.BlockSize()))
|
||||||
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
|
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
|
||||||
@@ -172,8 +153,8 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
b[hdrLen+i] ^= mask[i+1]
|
b[hdrLen+i] ^= mask[i+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
key := hkdfExpandLabel(secret, label+" key", 16)
|
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
||||||
iv := hkdfExpandLabel(secret, label+" iv", 12)
|
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
||||||
cipher := AEADAESGCMTLS13(key, iv)
|
cipher := AEADAESGCMTLS13(key, iv)
|
||||||
|
|
||||||
nonce := cache.Extend(int32(cipher.NonceSize()))
|
nonce := cache.Extend(int32(cipher.NonceSize()))
|
||||||
@@ -198,44 +179,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
case 0x00: // PADDING frame
|
case 0x00: // PADDING frame
|
||||||
case 0x01: // PING frame
|
case 0x01: // PING frame
|
||||||
case 0x02, 0x03: // ACK frame
|
case 0x02, 0x03: // ACK frame
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Largest Acknowledged
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Delay
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Delay
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
ackRangeCount, err := readShortQUICVarint(buffer) // Field: ACK Range Count
|
ackRangeCount, err := readShortQuicVarint(buffer) // Field: ACK Range Count
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: First ACK Range
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: First ACK Range
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if frameType == 0x03 {
|
if frameType == 0x03 {
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
if _, err = readShortQuicVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 0x06: // CRYPTO frame, we will use this frame
|
case 0x06: // CRYPTO frame, we will use this frame
|
||||||
offset, err := readShortQUICVarint(buffer) // Field: Offset
|
offset, err := readShortQuicVarint(buffer) // Field: Offset
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQUICVarint(buffer) // Field: Length
|
length, err := readShortQuicVarint(buffer) // Field: Length
|
||||||
if err != nil || length > buffer.Len() {
|
if err != nil || length > buffer.Len() {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -251,13 +232,13 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Error Code
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Error Code
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Frame Type
|
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Frame Type
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQUICVarint(buffer) // Field: Reason Phrase Length
|
length, err := readShortQuicVarint(buffer) // Field: Reason Phrase Length
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -267,7 +248,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
default:
|
default:
|
||||||
// Only above frame types are permitted in initial packet.
|
// Only above frame types are permitted in initial packet.
|
||||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||||
return nil, errNotQUICInitial
|
return nil, errNotQuicInitial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,33 +266,35 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, protocol.ErrProtoNeedMoreData
|
return nil, protocol.ErrProtoNeedMoreData
|
||||||
}
|
}
|
||||||
|
|
||||||
func hkdfExpandLabel(secret []byte, label string, length int) []byte {
|
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||||
b := make([]byte, 0, 2+1+6+len(label)+1)
|
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(length))
|
binary.BigEndian.PutUint16(b, uint16(length))
|
||||||
b = append(b, byte(6+len(label)))
|
b[2] = uint8(6 + len(label))
|
||||||
b = append(b, "tls13 "...)
|
b = append(b, []byte("tls13 ")...)
|
||||||
b = append(b, label...)
|
b = append(b, []byte(label)...)
|
||||||
b = append(b, 0) // context
|
b = b[:3+6+len(label)+1]
|
||||||
|
b[3+6+len(label)] = uint8(len(context))
|
||||||
|
b = append(b, context...)
|
||||||
|
|
||||||
out := make([]byte, length)
|
out := make([]byte, length)
|
||||||
n, err := hkdf.Expand(crypto.SHA256.New, secret, b).Read(out)
|
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
||||||
if err != nil || n != length {
|
if err != nil || n != length {
|
||||||
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// readShortQUICVarint wraps quicvarint.Read with a max limit for length related fields.
|
// readShortQuicVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||||
// we only handle QUIC Initial so these numbers should not exceed 65535
|
// we only handle QUIC Initial so these numbers should not exceed 65535
|
||||||
// returns int32 to reduce type conversion
|
// returns int32 to reduce type conversion
|
||||||
func readShortQUICVarint(reader io.ByteReader) (int32, error) {
|
func readShortQuicVarint(reader io.ByteReader) (int32, error) {
|
||||||
v, err := quicvarint.Read(reader)
|
v, err := quicvarint.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if v > 65535 {
|
if v > 65535 {
|
||||||
// not used(
|
// not used(
|
||||||
return 0, errNotQUICInitial
|
return 0, errNotQuicInitial
|
||||||
}
|
}
|
||||||
return int32(v), nil
|
return int32(v), nil
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ServerSpec struct {
|
type ServerSpec struct {
|
||||||
Destination net.Destination
|
Destination net.Destination
|
||||||
User *MemoryUser
|
User *MemoryUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
|
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ func isValueKind(kind reflect.Kind) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
||||||
|
|
||||||
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
|
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ func TestMashalAccount(t *testing.T) {
|
|||||||
|
|
||||||
j, ok := MarshalToJson(user, false)
|
j, ok := MarshalToJson(user, false)
|
||||||
if !ok || strings.Contains(j, "_TypedMessage_") {
|
if !ok || strings.Contains(j, "_TypedMessage_") {
|
||||||
|
|
||||||
t.Error("marshal account failed")
|
t.Error("marshal account failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,12 +79,13 @@ func TestMashalStruct(t *testing.T) {
|
|||||||
|
|
||||||
v := (*f2.Arr)[0]["foo"]["hello"]
|
v := (*f2.Arr)[0]["foo"]["hello"]
|
||||||
|
|
||||||
if f1.N != f2.N || *f1.Np != *f2.Np || f1.S != f2.S || v != "world" {
|
if f1.N != f2.N || *(f1.Np) != *(f2.Np) || f1.S != f2.S || v != "world" {
|
||||||
t.Error("f1 not equal to f2")
|
t.Error("f1 not equal to f2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshalConfigJson(t *testing.T) {
|
func TestMarshalConfigJson(t *testing.T) {
|
||||||
|
|
||||||
buf := bytes.NewBufferString(getConfig())
|
buf := bytes.NewBufferString(getConfig())
|
||||||
config, err := iserial.DecodeJSONConfig(buf)
|
config, err := iserial.DecodeJSONConfig(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -207,7 +209,6 @@ func getConfig() string {
|
|||||||
"tag": "XHTTP_IN",
|
"tag": "XHTTP_IN",
|
||||||
"streamSettings": {
|
"streamSettings": {
|
||||||
"network": "xhttp",
|
"network": "xhttp",
|
||||||
"security": "tls",
|
|
||||||
"xhttpSettings": {
|
"xhttpSettings": {
|
||||||
"host": "bing.com",
|
"host": "bing.com",
|
||||||
"path": "/xhttp_client_upload",
|
"path": "/xhttp_client_upload",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/xtls/xray-core/common/serial"
|
. "github.com/xtls/xray-core/common/serial"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetInstance(t *testing.T) {
|
func TestGetInstance(t *testing.T) {
|
||||||
@@ -22,3 +23,31 @@ func TestConvertingNilMessage(t *testing.T) {
|
|||||||
t.Error("expect nil, but actually not")
|
t.Error("expect nil, but actually not")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTypedMessageRoundTripPreservesFinalmaskCustomUDPMode(t *testing.T) {
|
||||||
|
msg := &custom.UDPConfig{
|
||||||
|
Mode: "standalone",
|
||||||
|
Client: []*custom.UDPItem{
|
||||||
|
{Rand: 12, Save: "txid"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tm := ToTypedMessage(msg)
|
||||||
|
if tm == nil {
|
||||||
|
t.Fatal("expected typed message")
|
||||||
|
}
|
||||||
|
|
||||||
|
roundTrip, err := tm.GetInstance()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetInstance() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
udp, ok := roundTrip.(*custom.UDPConfig)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected round-trip type: %T", roundTrip)
|
||||||
|
}
|
||||||
|
|
||||||
|
if udp.GetMode() != "standalone" {
|
||||||
|
t.Fatalf("mode lost during typed message round-trip: got %q", udp.GetMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ 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 {
|
||||||
@@ -194,11 +192,3 @@ 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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ type Outbound struct {
|
|||||||
Tag string
|
Tag string
|
||||||
// Name of the outbound proxy that handles the connection.
|
// Name of the outbound proxy that handles the connection.
|
||||||
Name string
|
Name string
|
||||||
|
// Unused. Conn is actually internet.Connection. May be nil. It is currently nil for outbound with proxySettings
|
||||||
|
Conn net.Conn
|
||||||
// CanSpliceCopy is a property for this connection
|
// CanSpliceCopy is a property for this connection
|
||||||
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
|
// 1 = can, 2 = after processing protocol info should be able to, 3 = cannot
|
||||||
CanSpliceCopy int
|
CanSpliceCopy int
|
||||||
|
|||||||
@@ -10,44 +10,37 @@ func TestByteSizes(t *testing.T) {
|
|||||||
size := units.ByteSize(0)
|
size := units.ByteSize(0)
|
||||||
assertSizeString(t, size, "0")
|
assertSizeString(t, size, "0")
|
||||||
size++
|
size++
|
||||||
assertSizeValue(
|
assertSizeValue(t,
|
||||||
t,
|
|
||||||
assertSizeString(t, size, "1.00B"),
|
assertSizeString(t, size, "1.00B"),
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
size <<= 10
|
size <<= 10
|
||||||
assertSizeValue(
|
assertSizeValue(t,
|
||||||
t,
|
|
||||||
assertSizeString(t, size, "1.00KB"),
|
assertSizeString(t, size, "1.00KB"),
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
size <<= 10
|
size <<= 10
|
||||||
assertSizeValue(
|
assertSizeValue(t,
|
||||||
t,
|
|
||||||
assertSizeString(t, size, "1.00MB"),
|
assertSizeString(t, size, "1.00MB"),
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
size <<= 10
|
size <<= 10
|
||||||
assertSizeValue(
|
assertSizeValue(t,
|
||||||
t,
|
|
||||||
assertSizeString(t, size, "1.00GB"),
|
assertSizeString(t, size, "1.00GB"),
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
size <<= 10
|
size <<= 10
|
||||||
assertSizeValue(
|
assertSizeValue(t,
|
||||||
t,
|
|
||||||
assertSizeString(t, size, "1.00TB"),
|
assertSizeString(t, size, "1.00TB"),
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
size <<= 10
|
size <<= 10
|
||||||
assertSizeValue(
|
assertSizeValue(t,
|
||||||
t,
|
|
||||||
assertSizeString(t, size, "1.00PB"),
|
assertSizeString(t, size, "1.00PB"),
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
size <<= 10
|
size <<= 10
|
||||||
assertSizeValue(
|
assertSizeValue(t,
|
||||||
t,
|
|
||||||
assertSizeString(t, size, "1.00EB"),
|
assertSizeString(t, size, "1.00EB"),
|
||||||
size,
|
size,
|
||||||
)
|
)
|
||||||
|
|||||||
+43
-77
@@ -4,10 +4,10 @@ import (
|
|||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/klauspost/cpuid/v2"
|
"github.com/klauspost/cpuid/v2"
|
||||||
)
|
)
|
||||||
@@ -18,7 +18,6 @@ func GetRandomizer() *rand.Rand {
|
|||||||
fnvHash.Write([]byte(strconv.Itoa(cpuid.CPU.Family) + strconv.Itoa(cpuid.CPU.Model) + strconv.Itoa(cpuid.CPU.PhysicalCores) + strconv.Itoa(cpuid.CPU.LogicalCores) + strconv.Itoa(cpuid.CPU.CacheLine) + strconv.Itoa(cpuid.CPU.ThreadsPerCore)))
|
fnvHash.Write([]byte(strconv.Itoa(cpuid.CPU.Family) + strconv.Itoa(cpuid.CPU.Model) + strconv.Itoa(cpuid.CPU.PhysicalCores) + strconv.Itoa(cpuid.CPU.LogicalCores) + strconv.Itoa(cpuid.CPU.CacheLine) + strconv.Itoa(cpuid.CPU.ThreadsPerCore)))
|
||||||
return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
|
return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalRng *rand.Rand = GetRandomizer()
|
var globalRng *rand.Rand = GetRandomizer()
|
||||||
|
|
||||||
// The Chrome version generator will suffer from deviation of a normal distribution.
|
// The Chrome version generator will suffer from deviation of a normal distribution.
|
||||||
@@ -27,114 +26,83 @@ func ChromeVersion() int {
|
|||||||
var startVersion int = 144
|
var startVersion int = 144
|
||||||
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
||||||
var timeCurrent int64 = time.Now().Unix() / 86400
|
var timeCurrent int64 = time.Now().Unix() / 86400
|
||||||
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*105))
|
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 105))
|
||||||
return startVersion + (timeDiff / 35) // It's 31.15 currently.
|
return startVersion + (timeDiff / 35) // It's 31.15 currently.
|
||||||
}
|
}
|
||||||
|
|
||||||
var safariMinorMap [25]int = [25]int{
|
var safariMinorMap [25]int = [25]int{0, 0, 0, 1, 1,
|
||||||
0, 0, 0, 1, 1,
|
|
||||||
1, 2, 2, 2, 2, 3, 3, 3, 4, 4,
|
1, 2, 2, 2, 2, 3, 3, 3, 4, 4,
|
||||||
4, 5, 5, 5, 5, 5, 6, 6, 6, 6,
|
4, 5, 5, 5, 5, 5, 6, 6, 6, 6}
|
||||||
}
|
|
||||||
|
|
||||||
// The following version generators use deterministic generators, but with the distribution scaled by a curve.
|
// The following version generators use deterministic generators, but with the distribution scaled by a curve.
|
||||||
func CurlVersion() string {
|
func CurlVersion() string {
|
||||||
// curl 8.0.0 was released on 20/03/2023.
|
// curl 8.0.0 was released on 20/03/2023.
|
||||||
var timeCurrent int64 = time.Now().Unix() / 86400
|
var timeCurrent int64 = time.Now().Unix() / 86400
|
||||||
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
||||||
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*165))
|
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 165))
|
||||||
var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
|
var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
|
||||||
return "8." + strconv.Itoa(minorValue) + ".0"
|
return "8." + strconv.Itoa(minorValue) + ".0"
|
||||||
}
|
}
|
||||||
|
|
||||||
func FirefoxVersion() int {
|
func FirefoxVersion() int {
|
||||||
// Firefox 128 ESR was released on 09/07/2023.
|
// Firefox 128 ESR was released on 09/07/2023.
|
||||||
var timeCurrent int64 = time.Now().Unix() / 86400
|
var timeCurrent int64 = time.Now().Unix() / 86400
|
||||||
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
||||||
timeDiff := timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2)*50))
|
var timeDiff = timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2) * 50))
|
||||||
return int(timeDiff/30) + 128
|
return int(timeDiff / 30) + 128
|
||||||
}
|
}
|
||||||
|
|
||||||
func SafariVersion() string {
|
func SafariVersion() string {
|
||||||
var anchoredTime time.Time = time.Now()
|
var anchoredTime time.Time = time.Now()
|
||||||
var releaseYear int = anchoredTime.Year()
|
var releaseYear int = anchoredTime.Year()
|
||||||
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
|
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
|
||||||
delayedDays := int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
|
var delayedDays = int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
|
||||||
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
|
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
|
||||||
if anchoredTime.Compare(splitPoint) < 0 {
|
if (anchoredTime.Compare(splitPoint) < 0) {
|
||||||
releaseYear--
|
releaseYear --
|
||||||
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
|
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
|
||||||
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
|
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
|
||||||
}
|
}
|
||||||
minorVersion := safariMinorMap[(anchoredTime.Unix()-splitPoint.Unix())/1296000]
|
var minorVersion = safariMinorMap[(anchoredTime.Unix() - splitPoint.Unix()) / 1296000]
|
||||||
return strconv.Itoa(releaseYear-1999) + "." + strconv.Itoa(minorVersion)
|
return strconv.Itoa(releaseYear - 1999) + "." + strconv.Itoa(minorVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The full Chromium brand GREASE implementation
|
// The full Chromium brand GREASE implementation
|
||||||
var (
|
var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
|
||||||
clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
|
var clientHintVersionNA = []string{"8", "99", "24"}
|
||||||
clientHintVersionNA = []string{"8", "99", "24"}
|
var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
|
||||||
clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
|
var clientHintShuffle4 = [][4]int{
|
||||||
clientHintShuffle4 = [][4]int{
|
{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
|
||||||
{0, 1, 2, 3},
|
{1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
|
||||||
{0, 1, 3, 2},
|
{2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
|
||||||
{0, 2, 1, 3},
|
{3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}
|
||||||
{0, 2, 3, 1},
|
|
||||||
{0, 3, 1, 2},
|
|
||||||
{0, 3, 2, 1},
|
|
||||||
{1, 0, 2, 3},
|
|
||||||
{1, 0, 3, 2},
|
|
||||||
{1, 2, 0, 3},
|
|
||||||
{1, 2, 3, 0},
|
|
||||||
{1, 3, 0, 2},
|
|
||||||
{1, 3, 2, 0},
|
|
||||||
{2, 0, 1, 3},
|
|
||||||
{2, 0, 3, 1},
|
|
||||||
{2, 1, 0, 3},
|
|
||||||
{2, 1, 3, 0},
|
|
||||||
{2, 3, 0, 1},
|
|
||||||
{2, 3, 1, 0},
|
|
||||||
{3, 0, 1, 2},
|
|
||||||
{3, 0, 2, 1},
|
|
||||||
{3, 1, 0, 2},
|
|
||||||
{3, 1, 2, 0},
|
|
||||||
{3, 2, 0, 1},
|
|
||||||
{3, 2, 1, 0},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func getGreasedChInvalidBrand(seed int) string {
|
func getGreasedChInvalidBrand(seed int) string {
|
||||||
return "\"Not" + clientHintGreaseNA[seed%len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed+1)%len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed%len(clientHintVersionNA)] + "\""
|
return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGreasedChOrder(brandLength int, seed int) []int {
|
func getGreasedChOrder(brandLength int, seed int) []int {
|
||||||
switch brandLength {
|
switch brandLength {
|
||||||
case 1:
|
case 1:
|
||||||
return []int{0}
|
return []int{0}
|
||||||
case 2:
|
case 2:
|
||||||
return []int{seed % brandLength, (seed + 1) % brandLength}
|
return []int{seed % brandLength, (seed + 1) % brandLength}
|
||||||
case 3:
|
case 3:
|
||||||
return clientHintShuffle3[seed%len(clientHintShuffle3)][:]
|
return clientHintShuffle3[seed % len(clientHintShuffle3)][:]
|
||||||
default:
|
default:
|
||||||
return clientHintShuffle4[seed%len(clientHintShuffle4)][:]
|
return clientHintShuffle4[seed % len(clientHintShuffle4)][:]
|
||||||
}
|
}
|
||||||
//return []int{}
|
//return []int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUngreasedChUa(majorVersion int, forkName string) []string {
|
func getUngreasedChUa(majorVersion int, forkName string) []string {
|
||||||
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
|
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
|
||||||
baseChUa := make([]string, 0, 4)
|
baseChUa := make([]string, 0, 4)
|
||||||
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
|
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
|
||||||
"\"Chromium\";v=\""+strconv.Itoa(majorVersion)+"\"")
|
"\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"")
|
||||||
switch forkName {
|
switch forkName {
|
||||||
case "chrome":
|
case "chrome":
|
||||||
baseChUa = append(baseChUa, "\"Google Chrome\";v=\""+strconv.Itoa(majorVersion)+"\"")
|
baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"")
|
||||||
case "edge":
|
case "edge":
|
||||||
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\""+strconv.Itoa(majorVersion)+"\"")
|
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"")
|
||||||
}
|
}
|
||||||
return baseChUa
|
return baseChUa
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGreasedChUa(majorVersion int, forkName string) string {
|
func getGreasedChUa(majorVersion int, forkName string) string {
|
||||||
ungreasedCh := getUngreasedChUa(majorVersion, forkName)
|
ungreasedCh := getUngreasedChUa(majorVersion, forkName)
|
||||||
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
|
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
|
||||||
@@ -146,18 +114,16 @@ func getGreasedChUa(majorVersion int, forkName string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
|
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
|
||||||
var (
|
var CurlUA = "curl/" + CurlVersion()
|
||||||
CurlUA = "curl/" + CurlVersion()
|
var AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
|
||||||
AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
|
var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
|
||||||
FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
|
var SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
|
||||||
SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
|
// Chromium browsers.
|
||||||
// Chromium browsers.
|
var AnchoredChromeVersion = ChromeVersion()
|
||||||
AnchoredChromeVersion = ChromeVersion()
|
var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
|
||||||
ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
|
var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
|
||||||
ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
|
var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
|
||||||
MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
|
var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
|
||||||
MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
|
|
||||||
)
|
|
||||||
|
|
||||||
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
|
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
|
||||||
// Browser-specific.
|
// Browser-specific.
|
||||||
|
|||||||
@@ -109,4 +109,4 @@ func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
|
|||||||
previous = anyPrevious.(V)
|
previous = anyPrevious.(V)
|
||||||
}
|
}
|
||||||
return previous, loaded
|
return previous, loaded
|
||||||
}
|
}
|
||||||
+20
-19
@@ -19,18 +19,18 @@ import (
|
|||||||
// Filesystem paths and abstract sockets on other platforms are returned
|
// Filesystem paths and abstract sockets on other platforms are returned
|
||||||
// unchanged.
|
// unchanged.
|
||||||
func ResolveSocketPath(path string) string {
|
func ResolveSocketPath(path string) string {
|
||||||
if len(path) == 0 || path[0] != '@' {
|
if len(path) == 0 || path[0] != '@' {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
|
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
if len(path) > 1 && path[1] == '@' {
|
if len(path) > 1 && path[1] == '@' {
|
||||||
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
|
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
|
||||||
copy(fullAddr, path[1:])
|
copy(fullAddr, path[1:])
|
||||||
return string(fullAddr)
|
return string(fullAddr)
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitHTTPUnixURL splits a target into an HTTP URL and an optional Unix
|
// SplitHTTPUnixURL splits a target into an HTTP URL and an optional Unix
|
||||||
@@ -44,11 +44,12 @@ func ResolveSocketPath(path string) string {
|
|||||||
// The :/ separator delimits the socket path from the HTTP request path.
|
// The :/ separator delimits the socket path from the HTTP request path.
|
||||||
// If omitted, "/" is used.
|
// If omitted, "/" is used.
|
||||||
func SplitHTTPUnixURL(raw string) (httpURL, socketPath string) {
|
func SplitHTTPUnixURL(raw string) (httpURL, socketPath string) {
|
||||||
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
|
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
|
||||||
return raw, ""
|
return raw, ""
|
||||||
}
|
}
|
||||||
if idx := strings.Index(raw, ":/"); idx >= 0 {
|
if idx := strings.Index(raw, ":/"); idx >= 0 {
|
||||||
return "http://localhost" + raw[idx+1:], raw[:idx]
|
return "http://localhost" + raw[idx+1:], raw[:idx]
|
||||||
}
|
}
|
||||||
return "http://localhost/", raw
|
return "http://localhost/", raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"maps"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"weak"
|
"weak"
|
||||||
@@ -44,16 +43,3 @@ 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+19
-33
@@ -8,7 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
@@ -27,39 +27,25 @@ var AddrParser = protocol.NewAddressParser(
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Show atomic.Bool
|
Show bool
|
||||||
baseKey atomic.Value
|
BaseKey []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func reloadEnvSettings() error {
|
|
||||||
Show.Store(strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true")
|
|
||||||
raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" })
|
|
||||||
if raw == "" {
|
|
||||||
ensureBaseKey()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
key, _ := base64.RawURLEncoding.DecodeString(raw)
|
|
||||||
if len(key) != 32 {
|
|
||||||
return errors.New(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(key)))
|
|
||||||
}
|
|
||||||
baseKey.Store(append([]byte(nil), key...))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureBaseKey() []byte {
|
|
||||||
if key := baseKey.Load(); key != nil {
|
|
||||||
return key.([]byte)
|
|
||||||
}
|
|
||||||
key := make([]byte, 32)
|
|
||||||
if _, err := rand.Read(key); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
baseKey.Store(key)
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
platform.RegisterEnvReload(reloadEnvSettings)
|
if strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true" {
|
||||||
|
Show = true
|
||||||
|
}
|
||||||
|
BaseKey = make([]byte, 32)
|
||||||
|
rand.Read(BaseKey)
|
||||||
|
go func() {
|
||||||
|
time.Sleep(100 * time.Millisecond) // this is not nice, but need to give some time for Android to setup ENV
|
||||||
|
if raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" }); raw != "" {
|
||||||
|
if BaseKey, _ = base64.RawURLEncoding.DecodeString(raw); len(BaseKey) == 32 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(BaseKey)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
||||||
@@ -68,10 +54,10 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
|
|||||||
}
|
}
|
||||||
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
|
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
|
||||||
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
|
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
|
||||||
h := blake3.New(8, ensureBaseKey())
|
h := blake3.New(8, BaseKey)
|
||||||
h.Write([]byte(inbound.Source.String()))
|
h.Write([]byte(inbound.Source.String()))
|
||||||
copy(globalID[:], h.Sum(nil))
|
copy(globalID[:], h.Sum(nil))
|
||||||
if Show.Load() {
|
if Show {
|
||||||
errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID))
|
errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 26
|
Version_x byte = 26
|
||||||
Version_y byte = 9
|
Version_y byte = 5
|
||||||
Version_z byte = 9
|
Version_z byte = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user