mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-16 22:40:27 +00:00
Compare commits
1
Commits
obs-timeout
..
h2c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b53ae0a183 |
@@ -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
|
||||
@@ -32,13 +32,9 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/07_stats.json
|
||||
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/10_observatory.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/07_transport.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
||||
|
||||
# Create log files
|
||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
@@ -49,8 +45,8 @@ RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
|
||||
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||
COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||
COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||
|
||||
@@ -32,13 +32,9 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/03_routing.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/04_policy.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/05_inbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/06_outbounds.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/07_stats.json
|
||||
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/10_observatory.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/07_transport.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.json
|
||||
|
||||
# Create log files
|
||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
@@ -58,8 +54,8 @@ RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
FROM --platform=linux/amd64 gcr.io/distroless/static:nonroot
|
||||
|
||||
COPY --from=build --chown=0:0 --chmod=755 /src/xray /usr/local/bin/xray
|
||||
COPY --from=build --chown=65532:65532 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||
COPY --from=build --chown=65532:65532 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/share/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/geodat/*.dat /usr/local/share/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /usr/local/etc/xray
|
||||
COPY --from=build --chown=0:0 --chmod=644 /tmp/usr/local/etc/xray/*.json /usr/local/etc/xray/
|
||||
COPY --from=build --chown=0:0 --chmod=755 /tmp/empty /var/log/xray
|
||||
|
||||
@@ -64,16 +64,8 @@ jobs:
|
||||
echo "Latest: '$LATEST'."
|
||||
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
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
@@ -132,13 +124,6 @@ jobs:
|
||||
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||
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
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||
@@ -146,7 +131,3 @@ jobs:
|
||||
if [[ "${{ env.LATEST }}" == "true" ]]; then
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
|
||||
fi
|
||||
|
||||
if [[ "${{ env.NEWEST }}" == "true" ]]; then
|
||||
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:pre-release
|
||||
fi
|
||||
|
||||
@@ -11,16 +11,15 @@ on:
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -76,14 +75,13 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
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:
|
||||
GOOS: ${{ matrix.goos}}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
@@ -92,7 +90,7 @@ jobs:
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
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
|
||||
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -134,17 +132,15 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == '386' ]]; then
|
||||
mv resources/wintun/bin/x86/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -11,16 +11,15 @@ on:
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -56,7 +55,7 @@ jobs:
|
||||
|
||||
- name: Trigger Asset Update Workflow if Assets Missing
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -162,7 +161,6 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
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:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
@@ -170,7 +168,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up NDK
|
||||
if: matrix.goos == 'android'
|
||||
@@ -193,7 +191,7 @@ jobs:
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
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
|
||||
# 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
|
||||
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
|
||||
echo 'Building Xray...'
|
||||
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
|
||||
@@ -225,14 +220,14 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
if: matrix.goos == 'windows'
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -241,10 +236,8 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
@@ -254,7 +247,7 @@ jobs:
|
||||
if [[ ${GOARCH} == 'arm64' ]]; then
|
||||
mv resources/wintun/bin/arm64/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Save Geodat Cache
|
||||
uses: actions/cache/save@v6
|
||||
uses: actions/cache/save@v5
|
||||
if: ${{ steps.update.outputs.unhit }}
|
||||
with:
|
||||
path: resources
|
||||
@@ -68,12 +68,9 @@ jobs:
|
||||
wintun:
|
||||
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ASSETVER: 0.14.1
|
||||
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
|
||||
steps:
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -99,6 +96,7 @@ jobs:
|
||||
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
|
||||
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} exists"
|
||||
continue
|
||||
else
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} is missing"
|
||||
missing=true
|
||||
@@ -115,21 +113,16 @@ jobs:
|
||||
fi
|
||||
if [[ "$missing" == true ]]; then
|
||||
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}..."
|
||||
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
|
||||
if [[ "$(sha256sum "./${FILENAME}" | awk -F ' ' '{print $1}')" == "${ASSETHASH}" ]]; then
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo -e "Digest of ${FILENAME} mismatch."
|
||||
exit 1
|
||||
fi
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Save Wintun Cache
|
||||
uses: actions/cache/save@v6
|
||||
uses: actions/cache/save@v5
|
||||
if: ${{ steps.update.outputs.unhit }}
|
||||
with:
|
||||
path: resources
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Tests and Checkings
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -8,10 +8,9 @@ on:
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
@@ -37,10 +36,9 @@ jobs:
|
||||
|
||||
check-proto:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
- name: Check Proto Version Header
|
||||
run: |
|
||||
head -n 4 core/config.pb.go > ref.txt
|
||||
@@ -52,28 +50,8 @@ jobs:
|
||||
fi
|
||||
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 install -v mvdan.cc/gofumpt@latest
|
||||
go run ./infra/vformat/main.go -mode check -pwd ./
|
||||
|
||||
test:
|
||||
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:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -83,14 +61,14 @@ jobs:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v6
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
@@ -73,7 +73,8 @@
|
||||
- [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)
|
||||
- Magisk
|
||||
- [Magic_V2Ray](https://github.com/vincentng295/Magic_V2Ray)
|
||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||
- Homebrew
|
||||
- `brew install xray`
|
||||
@@ -110,8 +111,6 @@
|
||||
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- [XrayUI-dev](https://github.com/PhoenixNil/XrayUI-dev)
|
||||
- Android
|
||||
- [v2rayNG](https://github.com/2dust/v2rayNG)
|
||||
- [X-flutter](https://github.com/XTLS/X-flutter)
|
||||
@@ -119,8 +118,7 @@
|
||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- [AsteriskNG](https://github.com/Asterisk4Magisk/AsteriskNG)
|
||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
@@ -145,14 +143,10 @@
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- HarmonyOS
|
||||
- [Hey](https://github.com/popsiclelmlm/Hey)
|
||||
|
||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Anywhere](https://github.com/NodePassProject/Anywhere)
|
||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
||||
- [Egern](https://apps.apple.com/us/app/egern/id1616105820)
|
||||
@@ -165,7 +159,6 @@
|
||||
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
|
||||
- [xtlsapi](https://github.com/hiddify/xtlsapi)
|
||||
- [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-api](https://github.com/XVGuardian/xray-api)
|
||||
- [XrayR](https://github.com/XrayR-project/XrayR)
|
||||
@@ -187,27 +180,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).
|
||||
- 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
|
||||
|
||||
### Windows (PowerShell)
|
||||
@@ -231,13 +203,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
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,7 +3,6 @@ package commander
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
core "github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
@@ -69,32 +67,20 @@ func (c *Commander) Start() error {
|
||||
}
|
||||
c.Unlock()
|
||||
|
||||
listen := func(listener net.Listener) {
|
||||
var listen = func(listener net.Listener) {
|
||||
if err := c.server.Serve(listener); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.listen) > 0 {
|
||||
var addr net.Addr
|
||||
|
||||
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
|
||||
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
|
||||
} else {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", c.listen)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "API server failed to parse listen address ", c.listen)
|
||||
return err
|
||||
}
|
||||
addr = tcpAddr
|
||||
}
|
||||
l, err := internet.ListenSystem(context.Background(), addr, nil)
|
||||
if err != nil {
|
||||
if l, err := net.Listen("tcp", c.listen); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
||||
return err
|
||||
} else {
|
||||
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
|
||||
go listen(l)
|
||||
}
|
||||
errors.LogInfo(context.Background(), "API server listening on ", l.Addr())
|
||||
go listen(l)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+33
-19
@@ -2,6 +2,7 @@ package dispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -162,7 +163,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
||||
p := d.policy.ForLevel(user.Level)
|
||||
if p.Stats.UserUplink {
|
||||
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{
|
||||
Counter: c,
|
||||
Writer: inboundLink.Writer,
|
||||
@@ -171,7 +172,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
||||
}
|
||||
if p.Stats.UserDownlink {
|
||||
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{
|
||||
Counter: c,
|
||||
Writer: outboundLink.Writer,
|
||||
@@ -180,7 +181,12 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
||||
}
|
||||
|
||||
if p.Stats.UserOnline {
|
||||
trackOnlineIP(ctx, d.stats, user.Email, sessionInbound.Source.Address.String())
|
||||
name := "user>>>" + user.Email + ">>>online"
|
||||
if om, _ := stats.GetOrRegisterOnlineMap(d.stats, name); om != nil {
|
||||
userIP := sessionInbound.Source.Address.String()
|
||||
om.AddIP(userIP)
|
||||
context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,13 +206,13 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
|
||||
p := policyManager.ForLevel(user.Level)
|
||||
if p.Stats.UserUplink {
|
||||
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
|
||||
}
|
||||
}
|
||||
if p.Stats.UserDownlink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
|
||||
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||
link.Writer = &SizeStatWriter{
|
||||
Counter: c,
|
||||
Writer: link.Writer,
|
||||
@@ -214,31 +220,39 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
|
||||
}
|
||||
}
|
||||
if p.Stats.UserOnline {
|
||||
trackOnlineIP(ctx, statsManager, user.Email, sessionInbound.Source.Address.String())
|
||||
name := "user>>>" + user.Email + ">>>online"
|
||||
if om, _ := stats.GetOrRegisterOnlineMap(statsManager, name); om != nil {
|
||||
userIP := sessionInbound.Source.Address.String()
|
||||
om.AddIP(userIP)
|
||||
context.AfterFunc(ctx, func() { om.RemoveIP(userIP) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
func trackOnlineIP(ctx context.Context, sm stats.Manager, email, ip string) {
|
||||
name := "user>>>" + email + ">>>online"
|
||||
if om, _ := sm.GetOrRegisterOnlineMap(name); om != nil {
|
||||
om.AddIP(ip)
|
||||
context.AfterFunc(ctx, func() { om.RemoveIP(ip) })
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
||||
domain := result.Domain()
|
||||
if domain == "" {
|
||||
return false
|
||||
}
|
||||
if request.ExcludeForDomain != nil && request.ExcludeForDomain.MatchAny(strings.ToLower(domain)) {
|
||||
return false
|
||||
}
|
||||
if request.ExcludeForIP != nil && destination.Address.Family().IsIP() && request.ExcludeForIP.Match(destination.Address.IP()) {
|
||||
return false
|
||||
for _, d := range request.ExcludeForDomain {
|
||||
if strings.HasPrefix(d, "regexp:") {
|
||||
pattern := d[7:]
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
errors.LogInfo(ctx, "Unable to compile regex")
|
||||
continue
|
||||
}
|
||||
if re.MatchString(domain) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if strings.ToLower(domain) == d {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
protocolString := result.Protocol()
|
||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||
|
||||
@@ -139,8 +139,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
|
||||
if lenAfter == 0 {
|
||||
if c.highWatermark >= minSizeForEmptyRebuild {
|
||||
errors.LogDebug(
|
||||
context.Background(), c.name,
|
||||
errors.LogDebug(context.Background(), c.name,
|
||||
" rebuilding empty cache map to reclaim memory.",
|
||||
" size_before_cleanup=", lenBefore,
|
||||
" peak_size_before_rebuild=", c.highWatermark,
|
||||
@@ -154,8 +153,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
|
||||
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
|
||||
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
|
||||
errors.LogDebug(
|
||||
context.Background(), c.name,
|
||||
errors.LogDebug(context.Background(), c.name,
|
||||
" shrinking cache map to reclaim memory.",
|
||||
" new_size=", lenAfter,
|
||||
" peak_size_before_shrink=", c.highWatermark,
|
||||
@@ -167,6 +165,7 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
c.highWatermark = lenAfter
|
||||
go c.migrate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type migrationEntry struct {
|
||||
|
||||
+35
-11
@@ -2,24 +2,48 @@ package dns
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
var typeMap = map[DomainMatchingType]strmatcher.Type{
|
||||
DomainMatchingType_Full: strmatcher.Full,
|
||||
DomainMatchingType_Subdomain: strmatcher.Domain,
|
||||
DomainMatchingType_Keyword: strmatcher.Substr,
|
||||
DomainMatchingType_Regex: strmatcher.Regex,
|
||||
}
|
||||
|
||||
// References:
|
||||
// https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml
|
||||
// https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan
|
||||
var localTLDsAndDotlessDomainsRules = []*geodata.DomainRule{
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^[^.]+$"}}}, // This will only match domains without any dot
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "local"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "localdomain"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "localhost"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "lan"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "home.arpa"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "example"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "invalid"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "test"}}},
|
||||
var localTLDsAndDotlessDomains = []*NameServer_PriorityDomain{
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]+$"}, // This will only match domains without any dot
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "localhost"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "lan"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "home.arpa"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "example"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "invalid"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "test"},
|
||||
}
|
||||
|
||||
var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
|
||||
Rule: "geosite:private",
|
||||
Size: uint32(len(localTLDsAndDotlessDomains)),
|
||||
}
|
||||
|
||||
func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) {
|
||||
strMType, f := typeMap[t]
|
||||
if !f {
|
||||
return nil, errors.New("unknown mapping type", t).AtWarning()
|
||||
}
|
||||
matcher, err := strMType.New(domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create str matcher").Base(err)
|
||||
}
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
||||
|
||||
+260
-72
@@ -7,7 +7,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
router "github.com/xtls/xray-core/app/router"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
@@ -23,6 +23,58 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type DomainMatchingType int32
|
||||
|
||||
const (
|
||||
DomainMatchingType_Full DomainMatchingType = 0
|
||||
DomainMatchingType_Subdomain DomainMatchingType = 1
|
||||
DomainMatchingType_Keyword DomainMatchingType = 2
|
||||
DomainMatchingType_Regex DomainMatchingType = 3
|
||||
)
|
||||
|
||||
// Enum value maps for DomainMatchingType.
|
||||
var (
|
||||
DomainMatchingType_name = map[int32]string{
|
||||
0: "Full",
|
||||
1: "Subdomain",
|
||||
2: "Keyword",
|
||||
3: "Regex",
|
||||
}
|
||||
DomainMatchingType_value = map[string]int32{
|
||||
"Full": 0,
|
||||
"Subdomain": 1,
|
||||
"Keyword": 2,
|
||||
"Regex": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x DomainMatchingType) Enum() *DomainMatchingType {
|
||||
p := new(DomainMatchingType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x DomainMatchingType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (DomainMatchingType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_app_dns_config_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (DomainMatchingType) Type() protoreflect.EnumType {
|
||||
return &file_app_dns_config_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x DomainMatchingType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DomainMatchingType.Descriptor instead.
|
||||
func (DomainMatchingType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type QueryStrategy int32
|
||||
|
||||
const (
|
||||
@@ -59,11 +111,11 @@ func (x QueryStrategy) String() string {
|
||||
}
|
||||
|
||||
func (QueryStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_app_dns_config_proto_enumTypes[0].Descriptor()
|
||||
return file_app_dns_config_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (QueryStrategy) Type() protoreflect.EnumType {
|
||||
return &file_app_dns_config_proto_enumTypes[0]
|
||||
return &file_app_dns_config_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x QueryStrategy) Number() protoreflect.EnumNumber {
|
||||
@@ -72,29 +124,30 @@ func (x QueryStrategy) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use QueryStrategy.Descriptor instead.
|
||||
func (QueryStrategy) EnumDescriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
type NameServer struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||
Domain []*geodata.DomainRule `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
ExpectedIp []*geodata.IPRule `protobuf:"bytes,3,rep,name=expected_ip,json=expectedIp,proto3" json:"expected_ip,omitempty"`
|
||||
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||
ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"`
|
||||
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
|
||||
DisableCache *bool `protobuf:"varint,11,opt,name=disableCache,proto3,oneof" json:"disableCache,omitempty"`
|
||||
ServeStale *bool `protobuf:"varint,15,opt,name=serveStale,proto3,oneof" json:"serveStale,omitempty"`
|
||||
ServeExpiredTTL *uint32 `protobuf:"varint,16,opt,name=serveExpiredTTL,proto3,oneof" json:"serveExpiredTTL,omitempty"`
|
||||
FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"`
|
||||
UnexpectedIp []*geodata.IPRule `protobuf:"bytes,13,rep,name=unexpected_ip,json=unexpectedIp,proto3" json:"unexpected_ip,omitempty"`
|
||||
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
|
||||
PolicyID uint32 `protobuf:"varint,17,opt,name=policyID,proto3" json:"policyID,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Address *net.Endpoint `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
|
||||
ClientIp []byte `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
|
||||
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||
PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
|
||||
ExpectedGeoip []*router.GeoIP `protobuf:"bytes,3,rep,name=expected_geoip,json=expectedGeoip,proto3" json:"expected_geoip,omitempty"`
|
||||
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
||||
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||
ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"`
|
||||
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
|
||||
DisableCache *bool `protobuf:"varint,11,opt,name=disableCache,proto3,oneof" json:"disableCache,omitempty"`
|
||||
ServeStale *bool `protobuf:"varint,15,opt,name=serveStale,proto3,oneof" json:"serveStale,omitempty"`
|
||||
ServeExpiredTTL *uint32 `protobuf:"varint,16,opt,name=serveExpiredTTL,proto3,oneof" json:"serveExpiredTTL,omitempty"`
|
||||
FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"`
|
||||
UnexpectedGeoip []*router.GeoIP `protobuf:"bytes,13,rep,name=unexpected_geoip,json=unexpectedGeoip,proto3" json:"unexpected_geoip,omitempty"`
|
||||
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
|
||||
PolicyID uint32 `protobuf:"varint,17,opt,name=policyID,proto3" json:"policyID,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NameServer) Reset() {
|
||||
@@ -148,16 +201,23 @@ func (x *NameServer) GetSkipFallback() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetDomain() []*geodata.DomainRule {
|
||||
func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
return x.PrioritizedDomain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetExpectedIp() []*geodata.IPRule {
|
||||
func (x *NameServer) GetExpectedGeoip() []*router.GeoIP {
|
||||
if x != nil {
|
||||
return x.ExpectedIp
|
||||
return x.ExpectedGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetOriginalRules() []*NameServer_OriginalRule {
|
||||
if x != nil {
|
||||
return x.OriginalRules
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -218,9 +278,9 @@ func (x *NameServer) GetFinalQuery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetUnexpectedIp() []*geodata.IPRule {
|
||||
func (x *NameServer) GetUnexpectedGeoip() []*router.GeoIP {
|
||||
if x != nil {
|
||||
return x.UnexpectedIp
|
||||
return x.UnexpectedGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -369,9 +429,114 @@ func (x *Config) GetEnableParallelQuery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type NameServer_PriorityDomain struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
|
||||
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NameServer_PriorityDomain) Reset() {
|
||||
*x = NameServer_PriorityDomain{}
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NameServer_PriorityDomain) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NameServer_PriorityDomain) ProtoMessage() {}
|
||||
|
||||
func (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NameServer_PriorityDomain.ProtoReflect.Descriptor instead.
|
||||
func (*NameServer_PriorityDomain) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *NameServer_PriorityDomain) GetType() DomainMatchingType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DomainMatchingType_Full
|
||||
}
|
||||
|
||||
func (x *NameServer_PriorityDomain) GetDomain() string {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type NameServer_OriginalRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Rule string `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"`
|
||||
Size uint32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) Reset() {
|
||||
*x = NameServer_OriginalRule{}
|
||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NameServer_OriginalRule) ProtoMessage() {}
|
||||
|
||||
func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead.
|
||||
func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 1}
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) GetRule() string {
|
||||
if x != nil {
|
||||
return x.Rule
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) GetSize() uint32 {
|
||||
if x != nil {
|
||||
return x.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Config_HostMapping struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Domain *geodata.DomainRule `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
Type DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
|
||||
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
Ip [][]byte `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||
// ProxiedDomain indicates the mapped domain has the same IP address on this
|
||||
// domain. Xray will use this domain for IP queries.
|
||||
@@ -382,7 +547,7 @@ type Config_HostMapping struct {
|
||||
|
||||
func (x *Config_HostMapping) Reset() {
|
||||
*x = Config_HostMapping{}
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -394,7 +559,7 @@ func (x *Config_HostMapping) String() string {
|
||||
func (*Config_HostMapping) ProtoMessage() {}
|
||||
|
||||
func (x *Config_HostMapping) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -410,11 +575,18 @@ func (*Config_HostMapping) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 0}
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetDomain() *geodata.DomainRule {
|
||||
func (x *Config_HostMapping) GetType() DomainMatchingType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DomainMatchingType_Full
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetDomain() string {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetIp() [][]byte {
|
||||
@@ -435,15 +607,15 @@ var File_app_dns_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_app_dns_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x14app/dns/config.proto\x12\fxray.app.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\xde\x05\n" +
|
||||
"\x14app/dns/config.proto\x12\fxray.app.dns\x1a\x1ccommon/net/destination.proto\x1a\x17app/router/config.proto\"\xdf\a\n" +
|
||||
"\n" +
|
||||
"NameServer\x123\n" +
|
||||
"\aaddress\x18\x01 \x01(\v2\x19.xray.common.net.EndpointR\aaddress\x12\x1b\n" +
|
||||
"\tclient_ip\x18\x05 \x01(\fR\bclientIp\x12\"\n" +
|
||||
"\fskipFallback\x18\x06 \x01(\bR\fskipFallback\x127\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12<\n" +
|
||||
"\vexpected_ip\x18\x03 \x03(\v2\x1b.xray.common.geodata.IPRuleR\n" +
|
||||
"expectedIp\x12B\n" +
|
||||
"\fskipFallback\x18\x06 \x01(\bR\fskipFallback\x12V\n" +
|
||||
"\x12prioritized_domain\x18\x02 \x03(\v2'.xray.app.dns.NameServer.PriorityDomainR\x11prioritizedDomain\x12=\n" +
|
||||
"\x0eexpected_geoip\x18\x03 \x03(\v2\x16.xray.app.router.GeoIPR\rexpectedGeoip\x12L\n" +
|
||||
"\x0eoriginal_rules\x18\x04 \x03(\v2%.xray.app.dns.NameServer.OriginalRuleR\roriginalRules\x12B\n" +
|
||||
"\x0equery_strategy\x18\a \x01(\x0e2\x1b.xray.app.dns.QueryStrategyR\rqueryStrategy\x12\x1a\n" +
|
||||
"\bactPrior\x18\b \x01(\bR\bactPrior\x12\x10\n" +
|
||||
"\x03tag\x18\t \x01(\tR\x03tag\x12\x1c\n" +
|
||||
@@ -456,15 +628,21 @@ const file_app_dns_config_proto_rawDesc = "" +
|
||||
"\x0fserveExpiredTTL\x18\x10 \x01(\rH\x02R\x0fserveExpiredTTL\x88\x01\x01\x12\x1e\n" +
|
||||
"\n" +
|
||||
"finalQuery\x18\f \x01(\bR\n" +
|
||||
"finalQuery\x12@\n" +
|
||||
"\runexpected_ip\x18\r \x03(\v2\x1b.xray.common.geodata.IPRuleR\funexpectedIp\x12\x1e\n" +
|
||||
"finalQuery\x12A\n" +
|
||||
"\x10unexpected_geoip\x18\r \x03(\v2\x16.xray.app.router.GeoIPR\x0funexpectedGeoip\x12\x1e\n" +
|
||||
"\n" +
|
||||
"actUnprior\x18\x0e \x01(\bR\n" +
|
||||
"actUnprior\x12\x1a\n" +
|
||||
"\bpolicyID\x18\x11 \x01(\rR\bpolicyIDB\x0f\n" +
|
||||
"\bpolicyID\x18\x11 \x01(\rR\bpolicyID\x1a^\n" +
|
||||
"\x0ePriorityDomain\x124\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2 .xray.app.dns.DomainMatchingTypeR\x04type\x12\x16\n" +
|
||||
"\x06domain\x18\x02 \x01(\tR\x06domain\x1a6\n" +
|
||||
"\fOriginalRule\x12\x12\n" +
|
||||
"\x04rule\x18\x01 \x01(\tR\x04rule\x12\x12\n" +
|
||||
"\x04size\x18\x02 \x01(\rR\x04sizeB\x0f\n" +
|
||||
"\r_disableCacheB\r\n" +
|
||||
"\v_serveStaleB\x12\n" +
|
||||
"\x10_serveExpiredTTLJ\x04\b\x04\x10\x05\"\x82\x05\n" +
|
||||
"\x10_serveExpiredTTL\"\x98\x05\n" +
|
||||
"\x06Config\x129\n" +
|
||||
"\vname_server\x18\x05 \x03(\v2\x18.xray.app.dns.NameServerR\n" +
|
||||
"nameServer\x12\x1b\n" +
|
||||
@@ -480,11 +658,17 @@ const file_app_dns_config_proto_rawDesc = "" +
|
||||
"\x0fdisableFallback\x18\n" +
|
||||
" \x01(\bR\x0fdisableFallback\x126\n" +
|
||||
"\x16disableFallbackIfMatch\x18\v \x01(\bR\x16disableFallbackIfMatch\x120\n" +
|
||||
"\x13enableParallelQuery\x18\x0e \x01(\bR\x13enableParallelQuery\x1a}\n" +
|
||||
"\vHostMapping\x127\n" +
|
||||
"\x06domain\x18\x02 \x01(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12\x0e\n" +
|
||||
"\x13enableParallelQuery\x18\x0e \x01(\bR\x13enableParallelQuery\x1a\x92\x01\n" +
|
||||
"\vHostMapping\x124\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2 .xray.app.dns.DomainMatchingTypeR\x04type\x12\x16\n" +
|
||||
"\x06domain\x18\x02 \x01(\tR\x06domain\x12\x0e\n" +
|
||||
"\x02ip\x18\x03 \x03(\fR\x02ip\x12%\n" +
|
||||
"\x0eproxied_domain\x18\x04 \x01(\tR\rproxiedDomainJ\x04\b\a\x10\b*B\n" +
|
||||
"\x0eproxied_domain\x18\x04 \x01(\tR\rproxiedDomainJ\x04\b\a\x10\b*E\n" +
|
||||
"\x12DomainMatchingType\x12\b\n" +
|
||||
"\x04Full\x10\x00\x12\r\n" +
|
||||
"\tSubdomain\x10\x01\x12\v\n" +
|
||||
"\aKeyword\x10\x02\x12\t\n" +
|
||||
"\x05Regex\x10\x03*B\n" +
|
||||
"\rQueryStrategy\x12\n" +
|
||||
"\n" +
|
||||
"\x06USE_IP\x10\x00\x12\v\n" +
|
||||
@@ -505,32 +689,36 @@ func file_app_dns_config_proto_rawDescGZIP() []byte {
|
||||
return file_app_dns_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_app_dns_config_proto_goTypes = []any{
|
||||
(QueryStrategy)(0), // 0: xray.app.dns.QueryStrategy
|
||||
(*NameServer)(nil), // 1: xray.app.dns.NameServer
|
||||
(*Config)(nil), // 2: xray.app.dns.Config
|
||||
(*Config_HostMapping)(nil), // 3: xray.app.dns.Config.HostMapping
|
||||
(*net.Endpoint)(nil), // 4: xray.common.net.Endpoint
|
||||
(*geodata.DomainRule)(nil), // 5: xray.common.geodata.DomainRule
|
||||
(*geodata.IPRule)(nil), // 6: xray.common.geodata.IPRule
|
||||
(DomainMatchingType)(0), // 0: xray.app.dns.DomainMatchingType
|
||||
(QueryStrategy)(0), // 1: xray.app.dns.QueryStrategy
|
||||
(*NameServer)(nil), // 2: xray.app.dns.NameServer
|
||||
(*Config)(nil), // 3: xray.app.dns.Config
|
||||
(*NameServer_PriorityDomain)(nil), // 4: xray.app.dns.NameServer.PriorityDomain
|
||||
(*NameServer_OriginalRule)(nil), // 5: xray.app.dns.NameServer.OriginalRule
|
||||
(*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping
|
||||
(*net.Endpoint)(nil), // 7: xray.common.net.Endpoint
|
||||
(*router.GeoIP)(nil), // 8: xray.app.router.GeoIP
|
||||
}
|
||||
var file_app_dns_config_proto_depIdxs = []int32{
|
||||
4, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
||||
5, // 1: xray.app.dns.NameServer.domain:type_name -> xray.common.geodata.DomainRule
|
||||
6, // 2: xray.app.dns.NameServer.expected_ip:type_name -> xray.common.geodata.IPRule
|
||||
0, // 3: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
6, // 4: xray.app.dns.NameServer.unexpected_ip:type_name -> xray.common.geodata.IPRule
|
||||
1, // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
||||
3, // 6: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
||||
0, // 7: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
5, // 8: xray.app.dns.Config.HostMapping.domain:type_name -> xray.common.geodata.DomainRule
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
7, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
||||
4, // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain
|
||||
8, // 2: xray.app.dns.NameServer.expected_geoip:type_name -> xray.app.router.GeoIP
|
||||
5, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
|
||||
1, // 4: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
8, // 5: xray.app.dns.NameServer.unexpected_geoip:type_name -> xray.app.router.GeoIP
|
||||
2, // 6: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
||||
6, // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
||||
1, // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
0, // 9: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
|
||||
0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_dns_config_proto_init() }
|
||||
@@ -544,8 +732,8 @@ func file_app_dns_config_proto_init() {
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dns_config_proto_rawDesc), len(file_app_dns_config_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumEnums: 2,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
+25
-6
@@ -7,15 +7,26 @@ option java_package = "com.xray.app.dns";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/net/destination.proto";
|
||||
import "common/geodata/geodat.proto";
|
||||
import "app/router/config.proto";
|
||||
|
||||
message NameServer {
|
||||
xray.common.net.Endpoint address = 1;
|
||||
bytes client_ip = 5;
|
||||
bool skipFallback = 6;
|
||||
repeated xray.common.geodata.DomainRule domain = 2;
|
||||
repeated xray.common.geodata.IPRule expected_ip = 3;
|
||||
reserved 4;
|
||||
|
||||
message PriorityDomain {
|
||||
DomainMatchingType type = 1;
|
||||
string domain = 2;
|
||||
}
|
||||
|
||||
message OriginalRule {
|
||||
string rule = 1;
|
||||
uint32 size = 2;
|
||||
}
|
||||
|
||||
repeated PriorityDomain prioritized_domain = 2;
|
||||
repeated xray.app.router.GeoIP expected_geoip = 3;
|
||||
repeated OriginalRule original_rules = 4;
|
||||
QueryStrategy query_strategy = 7;
|
||||
bool actPrior = 8;
|
||||
string tag = 9;
|
||||
@@ -24,11 +35,18 @@ message NameServer {
|
||||
optional bool serveStale = 15;
|
||||
optional uint32 serveExpiredTTL = 16;
|
||||
bool finalQuery = 12;
|
||||
repeated xray.common.geodata.IPRule unexpected_ip = 13;
|
||||
repeated xray.app.router.GeoIP unexpected_geoip = 13;
|
||||
bool actUnprior = 14;
|
||||
uint32 policyID = 17;
|
||||
}
|
||||
|
||||
enum DomainMatchingType {
|
||||
Full = 0;
|
||||
Subdomain = 1;
|
||||
Keyword = 2;
|
||||
Regex = 3;
|
||||
}
|
||||
|
||||
enum QueryStrategy {
|
||||
USE_IP = 0;
|
||||
USE_IP4 = 1;
|
||||
@@ -46,7 +64,8 @@ message Config {
|
||||
bytes client_ip = 3;
|
||||
|
||||
message HostMapping {
|
||||
xray.common.geodata.DomainRule domain = 2;
|
||||
DomainMatchingType type = 1;
|
||||
string domain = 2;
|
||||
|
||||
repeated bytes ip = 3;
|
||||
|
||||
|
||||
+142
-75
@@ -5,16 +5,20 @@ import (
|
||||
"context"
|
||||
go_errors "errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
@@ -28,15 +32,15 @@ type DNS struct {
|
||||
hosts *StaticHosts
|
||||
clients []*Client
|
||||
ctx context.Context
|
||||
domainMatcher geodata.DomainMatcher
|
||||
domainMatcher strmatcher.IndexMatcher
|
||||
matcherInfos []*DomainMatcherInfo
|
||||
checkSystem bool
|
||||
}
|
||||
|
||||
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher.
|
||||
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
|
||||
type DomainMatcherInfo struct {
|
||||
clientIdx uint16
|
||||
domainRule string
|
||||
clientIdx uint16
|
||||
domainRuleIdx uint16
|
||||
}
|
||||
|
||||
// New creates a new DNS server with given configuration.
|
||||
@@ -81,40 +85,56 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
return nil, errors.New("unexpected query strategy ", config.QueryStrategy)
|
||||
}
|
||||
|
||||
hosts, err := NewStaticHosts(config.StaticHosts)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create hosts").Base(err)
|
||||
var hosts *StaticHosts
|
||||
mphLoaded := false
|
||||
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||
if domainMatcherPath != "" {
|
||||
if f, err := os.Open(domainMatcherPath); err == nil {
|
||||
defer f.Close()
|
||||
if m, err := router.LoadGeoSiteMatcher(f, "HOSTS"); err == nil {
|
||||
f.Seek(0, 0)
|
||||
if hostIPs, err := router.LoadGeoSiteHosts(f); err == nil {
|
||||
if sh, err := NewStaticHostsFromCache(m, hostIPs); err == nil {
|
||||
hosts = sh
|
||||
mphLoaded = true
|
||||
errors.LogDebug(ctx, "MphDomainMatcher loaded from cache for DNS hosts, size: ", sh.matchers.Size())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultTag := config.Tag
|
||||
if !mphLoaded {
|
||||
sh, err := NewStaticHosts(config.StaticHosts)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create hosts").Base(err)
|
||||
}
|
||||
hosts = sh
|
||||
}
|
||||
|
||||
var clients []*Client
|
||||
domainRuleCount := 0
|
||||
|
||||
var defaultTag = config.Tag
|
||||
if len(config.Tag) == 0 {
|
||||
defaultTag = generateRandomTag()
|
||||
}
|
||||
|
||||
clients := make([]*Client, 0, len(config.NameServer))
|
||||
matcherInfos := make([]*DomainMatcherInfo, 0)
|
||||
effectiveRules := make([]*geodata.DomainRule, 0)
|
||||
for _, ns := range config.NameServer {
|
||||
domainRuleCount += len(ns.PrioritizedDomain)
|
||||
}
|
||||
|
||||
// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
|
||||
matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
|
||||
domainMatcher := &strmatcher.MatcherGroup{}
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
clientIdx := len(clients)
|
||||
updateRules := func(isLocalNameServer bool) {
|
||||
// Prioritize local domains with specific TLDs or those without any dot for the local DNS
|
||||
if isLocalNameServer {
|
||||
effectiveRules = append(effectiveRules, localTLDsAndDotlessDomainsRules...)
|
||||
for _, rule := range localTLDsAndDotlessDomainsRules {
|
||||
matcherInfos = append(matcherInfos, &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRule: rule.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
effectiveRules = append(effectiveRules, ns.Domain...)
|
||||
for _, rule := range ns.Domain {
|
||||
matcherInfos = append(matcherInfos, &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRule: rule.String(),
|
||||
})
|
||||
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) {
|
||||
midx := domainMatcher.Add(domainRule)
|
||||
matcherInfos[midx] = &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRuleIdx: uint16(originalRuleIdx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,31 +159,22 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
serveExpiredTTL = *ns.ServeExpiredTTL
|
||||
}
|
||||
|
||||
tag := defaultTag
|
||||
var tag = defaultTag
|
||||
if len(ns.Tag) > 0 {
|
||||
tag = ns.Tag
|
||||
}
|
||||
|
||||
clientIPOption := ResolveIpOptionOverride(ns.QueryStrategy, ipOption)
|
||||
if !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {
|
||||
return nil, errors.New("no QueryStrategy available for ", ns.Address)
|
||||
}
|
||||
|
||||
client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, updateRules)
|
||||
client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, &matcherInfos, updateDomain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create client").Base(err)
|
||||
}
|
||||
clients = append(clients, client)
|
||||
}
|
||||
|
||||
var domainMatcher geodata.DomainMatcher
|
||||
if len(effectiveRules) > 0 {
|
||||
domainMatcher, err = geodata.DomainReg.BuildDomainMatcher(effectiveRules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no DNS client in config, add a `localhost` DNS client
|
||||
if len(clients) == 0 {
|
||||
clients = append(clients, NewLocalDNSClient(ipOption))
|
||||
@@ -221,7 +232,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
|
||||
}
|
||||
|
||||
if s.checkSystem {
|
||||
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||
supportIPv4, supportIPv6 := checkRoutes()
|
||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||
} else {
|
||||
@@ -272,27 +283,24 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
|
||||
// Priority domain matching
|
||||
hasMatch := false
|
||||
if s.domainMatcher != nil {
|
||||
matchSlice := s.domainMatcher.Match(strings.ToLower(domain))
|
||||
sort.Slice(matchSlice, func(i, j int) bool {
|
||||
return matchSlice[i] < matchSlice[j]
|
||||
})
|
||||
for _, match := range matchSlice {
|
||||
info := s.matcherInfos[match]
|
||||
client := s.clients[info.clientIdx]
|
||||
domainRule := info.domainRule
|
||||
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
||||
if clientUsed[info.clientIdx] {
|
||||
continue
|
||||
}
|
||||
clientUsed[info.clientIdx] = true
|
||||
clients = append(clients, client)
|
||||
clientNames = append(clientNames, client.Name())
|
||||
hasMatch = true
|
||||
if client.finalQuery {
|
||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||
return clients
|
||||
}
|
||||
MatchSlice := s.domainMatcher.Match(domain)
|
||||
sort.Slice(MatchSlice, func(i, j int) bool {
|
||||
return MatchSlice[i] < MatchSlice[j]
|
||||
})
|
||||
for _, match := range MatchSlice {
|
||||
info := s.matcherInfos[match]
|
||||
client := s.clients[info.clientIdx]
|
||||
domainRule := client.domains[info.domainRuleIdx]
|
||||
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
||||
if clientUsed[info.clientIdx] {
|
||||
continue
|
||||
}
|
||||
clientUsed[info.clientIdx] = true
|
||||
clients = append(clients, client)
|
||||
clientNames = append(clientNames, client.Name())
|
||||
hasMatch = true
|
||||
if client.finalQuery {
|
||||
return clients
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,13 +314,17 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
clients = append(clients, client)
|
||||
clientNames = append(clientNames, client.Name())
|
||||
if client.finalQuery {
|
||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||
return clients
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||
if len(domainRules) > 0 {
|
||||
errors.LogDebug(s.ctx, "domain ", domain, " matches following rules: ", domainRules)
|
||||
}
|
||||
if len(clientNames) > 0 {
|
||||
errors.LogDebug(s.ctx, "domain ", domain, " will use DNS in order: ", clientNames)
|
||||
}
|
||||
|
||||
if len(clients) == 0 {
|
||||
if len(s.clients) > 0 {
|
||||
@@ -327,15 +339,6 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
return clients
|
||||
}
|
||||
|
||||
func logDecision(ctx context.Context, domain string, domainRules []string, clientNames []string) {
|
||||
if len(domainRules) > 0 {
|
||||
errors.LogDebug(ctx, "domain ", domain, " matches following rules: ", domainRules)
|
||||
}
|
||||
if len(clientNames) > 0 {
|
||||
errors.LogDebug(ctx, "domain ", domain, " will use DNS in order: ", clientNames)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeQueryErrors(domain string, errs []error) error {
|
||||
if len(errs) == 0 {
|
||||
return dns.ErrEmptyResponse
|
||||
@@ -537,3 +540,67 @@ func init() {
|
||||
return New(ctx, config.(*Config))
|
||||
}))
|
||||
}
|
||||
|
||||
func probeRoutes() (ipv4 bool, ipv6 bool) {
|
||||
if conn, err := net.Dial("udp4", "192.33.4.12:53"); err == nil {
|
||||
ipv4 = true
|
||||
conn.Close()
|
||||
}
|
||||
if conn, err := net.Dial("udp6", "[2001:500:2::c]:53"); err == nil {
|
||||
ipv6 = true
|
||||
conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var routeCache struct {
|
||||
sync.Once
|
||||
sync.RWMutex
|
||||
expire time.Time
|
||||
ipv4, ipv6 bool
|
||||
}
|
||||
|
||||
func checkRoutes() (bool, bool) {
|
||||
if !isGUIPlatform {
|
||||
routeCache.Once.Do(func() {
|
||||
routeCache.ipv4, routeCache.ipv6 = probeRoutes()
|
||||
})
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
|
||||
routeCache.RWMutex.RLock()
|
||||
now := time.Now()
|
||||
if routeCache.expire.After(now) {
|
||||
routeCache.RWMutex.RUnlock()
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
routeCache.RWMutex.RUnlock()
|
||||
|
||||
routeCache.RWMutex.Lock()
|
||||
defer routeCache.RWMutex.Unlock()
|
||||
|
||||
now = time.Now()
|
||||
if routeCache.expire.After(now) { // double-check
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
routeCache.ipv4, routeCache.ipv6 = probeRoutes() // ~2ms
|
||||
routeCache.expire = now.Add(100 * time.Millisecond) // ttl
|
||||
return routeCache.ipv4, routeCache.ipv6
|
||||
}
|
||||
|
||||
var isGUIPlatform = detectGUIPlatform()
|
||||
|
||||
func detectGUIPlatform() bool {
|
||||
switch runtime.GOOS {
|
||||
case "android", "ios", "windows", "darwin":
|
||||
return true
|
||||
case "linux", "freebsd", "openbsd":
|
||||
if t := os.Getenv("XDG_SESSION_TYPE"); t == "wayland" || t == "x11" {
|
||||
return true
|
||||
}
|
||||
if os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+100
-68
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/xtls/xray-core/app/policy"
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -147,9 +147,7 @@ func TestUDPServerSubnet(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -209,9 +207,7 @@ func TestUDPServer(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -335,9 +331,10 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Domain: []*geodata.DomainRule{
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "google.com"}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "google.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -349,9 +346,7 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -420,9 +415,7 @@ func TestUDPServerIPv6(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -478,7 +471,8 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
},
|
||||
StaticHosts: []*Config_HostMapping{
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "example.com"}}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
ProxiedDomain: "google.com",
|
||||
},
|
||||
},
|
||||
@@ -489,9 +483,7 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -547,9 +539,17 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// inner ip, will not match
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{192, 168, 11, 1}, Prefix: 32}}}},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{
|
||||
CountryCode: "local",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
// inner ip, will not match
|
||||
Ip: []byte{192, 168, 11, 1},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// second dns, match ip
|
||||
@@ -563,9 +563,25 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 4}, Prefix: 32}}}},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{
|
||||
CountryCode: "test",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "test",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 4},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -576,9 +592,7 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -649,15 +663,19 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Domain: []*geodata.DomainRule{
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
// Equivalent of dotless:localhost
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^[^.]*localhost[^.]*$"}}},
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will match localhost, localhost-a and localhost-b,
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 2}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 3}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 4}, Prefix: 32}}}},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will match localhost, localhost-a and localhost-b,
|
||||
CountryCode: "local",
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
|
||||
{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
|
||||
{Ip: []byte{127, 0, 0, 4}, Prefix: 32},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -670,21 +688,23 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Domain: []*geodata.DomainRule{
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
// Equivalent of dotless: and domain:local
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^[^.]*$"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "local"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "localdomain"}}},
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
||||
},
|
||||
},
|
||||
},
|
||||
StaticHosts: []*Config_HostMapping{
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "hostnamestatic"}}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "hostnamestatic",
|
||||
Ip: [][]byte{{127, 0, 0, 53}},
|
||||
},
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "hostnamealias"}}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "hostnamealias",
|
||||
ProxiedDomain: "hostname.localdomain",
|
||||
},
|
||||
},
|
||||
@@ -695,9 +715,7 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -873,15 +891,19 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Domain: []*geodata.DomainRule{
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "google.com",
|
||||
},
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.8.8 and 8.8.4.4
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32}}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 4, 4}, Prefix: 32}}}},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.8.8 and 8.8.4.4
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{Ip: []byte{8, 8, 4, 4}, Prefix: 32},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -894,14 +916,18 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Domain: []*geodata.DomainRule{
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "google.com",
|
||||
},
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will match 8.8.8.8 and 8.8.8.7, etc
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 7}, Prefix: 24}}}},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will match 8.8.8.8 and 8.8.8.7, etc
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 7}, Prefix: 24},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -914,14 +940,18 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Domain: []*geodata.DomainRule{
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "api.google.com"}},
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "api.google.com",
|
||||
},
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.7.7 (api.google.com)
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 7, 7}, Prefix: 32}}}},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.7.7 (api.google.com)
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -934,14 +964,18 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
Domain: []*geodata.DomainRule{
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "v2.api.google.com"}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "v2.api.google.com",
|
||||
},
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.7.8 (v2.api.google.com)
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{8, 8, 7, 8}, Prefix: 32}}}},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.7.8 (v2.api.google.com)
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 7, 8}, Prefix: 32},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -952,9 +986,7 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+6
-16
@@ -127,20 +127,15 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
|
||||
return opt
|
||||
}
|
||||
|
||||
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) ([]*dnsRequest, error) {
|
||||
name, err := dnsmessage.NewName(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
|
||||
qA := dnsmessage.Question{
|
||||
Name: name,
|
||||
Name: dnsmessage.MustNewName(domain),
|
||||
Type: dnsmessage.TypeA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
}
|
||||
|
||||
qAAAA := dnsmessage.Question{
|
||||
Name: name,
|
||||
Name: dnsmessage.MustNewName(domain),
|
||||
Type: dnsmessage.TypeAAAA,
|
||||
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
|
||||
@@ -198,14 +193,9 @@ func parseResponse(payload []byte) (*IPRecord, error) {
|
||||
ipRecord := &IPRecord{
|
||||
ReqID: h.ID,
|
||||
RCode: h.RCode,
|
||||
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
|
||||
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:
|
||||
for {
|
||||
@@ -222,7 +212,7 @@ L:
|
||||
ttl = 1
|
||||
}
|
||||
expire := now.Add(time.Duration(ttl) * time.Second)
|
||||
if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
|
||||
if ipRecord.Expire.After(expire) {
|
||||
ipRecord.Expire = expire
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package dns
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -25,8 +24,7 @@ func Test_parseResponse(t *testing.T) {
|
||||
|
||||
ans = new(dns.Msg)
|
||||
ans.Id = 1
|
||||
ans.Answer = append(
|
||||
ans.Answer,
|
||||
ans.Answer = append(ans.Answer,
|
||||
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 A 8.8.8.8")),
|
||||
@@ -36,8 +34,7 @@ func Test_parseResponse(t *testing.T) {
|
||||
|
||||
ans = new(dns.Msg)
|
||||
ans.Id = 2
|
||||
ans.Answer = append(
|
||||
ans.Answer,
|
||||
ans.Answer = append(ans.Answer,
|
||||
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 m.test.google.com")),
|
||||
@@ -134,15 +131,10 @@ func Test_buildReqMsgs(t *testing.T) {
|
||||
IPv6Enable: false,
|
||||
FakeEnable: false,
|
||||
}, 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 {
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
+12
-8
@@ -17,7 +17,7 @@ import (
|
||||
type Holder struct {
|
||||
domainToIP cache.Lru
|
||||
ipRange *net.IPNet
|
||||
mu sync.Mutex
|
||||
mu *sync.Mutex
|
||||
|
||||
config *FakeDnsPool
|
||||
}
|
||||
@@ -49,7 +49,9 @@ func (fkdns *Holder) Start() error {
|
||||
}
|
||||
|
||||
func (fkdns *Holder) Close() error {
|
||||
// nothing to do for now, just wait GC
|
||||
fkdns.domainToIP = nil
|
||||
fkdns.ipRange = nil
|
||||
fkdns.mu = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -68,7 +70,7 @@ func NewFakeDNSHolder() (*Holder, error) {
|
||||
}
|
||||
|
||||
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
|
||||
return &Holder{config: conf}, nil
|
||||
return &Holder{nil, nil, nil, conf}, nil
|
||||
}
|
||||
|
||||
func (fkdns *Holder) initializeFromConfig() error {
|
||||
@@ -90,6 +92,7 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
|
||||
}
|
||||
fkdns.domainToIP = cache.NewLru(lruSize)
|
||||
fkdns.ipRange = ipRange
|
||||
fkdns.mu = new(sync.Mutex)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,7 +103,7 @@ func (fkdns *Holder) GetFakeIPForDomain(domain string) []net.Address {
|
||||
if v, ok := fkdns.domainToIP.Get(domain); ok {
|
||||
return []net.Address{v.(net.Address)}
|
||||
}
|
||||
currentTimeMillis := uint64(time.Now().UnixMilli())
|
||||
currentTimeMillis := uint64(time.Now().UnixNano() / 1e6)
|
||||
ones, bits := fkdns.ipRange.Mask.Size()
|
||||
rooms := bits - ones
|
||||
if rooms < 64 {
|
||||
@@ -199,11 +202,12 @@ func (h *HolderMulti) Start() error {
|
||||
}
|
||||
|
||||
func (h *HolderMulti) Close() error {
|
||||
var errs []error
|
||||
for _, v := range h.holders {
|
||||
errs = append(errs, v.Close())
|
||||
if err := v.Close(); err != nil {
|
||||
return errors.New("Cannot close all fake dns pools").Base(err)
|
||||
}
|
||||
}
|
||||
return errors.Combine(errs...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HolderMulti) createHolderGroups() error {
|
||||
@@ -218,7 +222,7 @@ func (h *HolderMulti) createHolderGroups() error {
|
||||
}
|
||||
|
||||
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
|
||||
holderMulti := &HolderMulti{config: conf}
|
||||
holderMulti := &HolderMulti{nil, conf}
|
||||
if err := holderMulti.createHolderGroups(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -129,16 +129,15 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFakeDNSMulti(t *testing.T) {
|
||||
fakeMulti, err := NewFakeDNSHolderMulti(
|
||||
&FakeDnsPoolMulti{
|
||||
Pools: []*FakeDnsPool{{
|
||||
IpPool: "240.0.0.0/12",
|
||||
LruSize: 256,
|
||||
}, {
|
||||
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
||||
LruSize: 256,
|
||||
}},
|
||||
},
|
||||
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
|
||||
Pools: []*FakeDnsPool{{
|
||||
IpPool: "240.0.0.0/12",
|
||||
LruSize: 256,
|
||||
}, {
|
||||
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
||||
LruSize: 256,
|
||||
}},
|
||||
},
|
||||
)
|
||||
common.Must(err)
|
||||
|
||||
|
||||
+81
-37
@@ -2,28 +2,39 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
// StaticHosts represents static domain-ip mapping in DNS server.
|
||||
type StaticHosts struct {
|
||||
responses [][]net.Address
|
||||
matcher geodata.DomainMatcher
|
||||
ips [][]net.Address
|
||||
matchers strmatcher.IndexMatcher
|
||||
}
|
||||
|
||||
// NewStaticHosts creates a new StaticHosts instance.
|
||||
func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
reps := make([][]net.Address, 0, len(hosts))
|
||||
rules := make([]*geodata.DomainRule, 0, len(hosts))
|
||||
g := new(strmatcher.MatcherGroup)
|
||||
sh := &StaticHosts{
|
||||
ips: make([][]net.Address, len(hosts)+16),
|
||||
matchers: g,
|
||||
}
|
||||
|
||||
for _, mapping := range hosts {
|
||||
rep := make([]net.Address, 0, len(mapping.Ip))
|
||||
defer runtime.GC()
|
||||
for i, mapping := range hosts {
|
||||
hosts[i] = nil
|
||||
matcher, err := toStrMatcher(mapping.Type, mapping.Domain)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to create domain matcher, ignore domain rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
continue
|
||||
}
|
||||
id := g.Add(matcher)
|
||||
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||
switch {
|
||||
case len(mapping.ProxiedDomain) > 0:
|
||||
if mapping.ProxiedDomain[0] == '#' {
|
||||
@@ -31,36 +42,28 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rep = append(rep, dns.RCodeError(rcode))
|
||||
ips = append(ips, dns.RCodeError(rcode))
|
||||
} else {
|
||||
rep = append(rep, net.DomainAddress(mapping.ProxiedDomain))
|
||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||
}
|
||||
case len(mapping.Ip) > 0:
|
||||
for _, ip := range mapping.Ip {
|
||||
addr := net.IPAddress(ip)
|
||||
if addr == nil {
|
||||
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule: ", mapping.Domain)
|
||||
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
continue
|
||||
}
|
||||
rep = append(rep, addr)
|
||||
ips = append(ips, addr)
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
reps = append(reps, rep)
|
||||
rules = append(rules, mapping.Domain)
|
||||
|
||||
sh.ips[id] = ips
|
||||
}
|
||||
|
||||
if len(rules) == 0 {
|
||||
return &StaticHosts{}, nil
|
||||
}
|
||||
|
||||
matcher, err := geodata.DomainReg.BuildDomainMatcher(rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StaticHosts{
|
||||
responses: reps,
|
||||
matcher: matcher,
|
||||
}, nil
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||
@@ -76,16 +79,16 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||
ips := make([]net.Address, 0)
|
||||
found := false
|
||||
for _, idx := range h.matcher.Match(domain) {
|
||||
for _, rep := range h.responses[idx] {
|
||||
if err, ok := rep.(dns.RCodeError); ok {
|
||||
for _, id := range h.matchers.Match(domain) {
|
||||
for _, v := range h.ips[id] {
|
||||
if err, ok := v.(dns.RCodeError); ok {
|
||||
if uint16(err) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ips = append(ips, h.responses[idx]...)
|
||||
ips = append(ips, h.ips[id]...)
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
@@ -95,13 +98,10 @@ func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||
}
|
||||
|
||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
|
||||
domain = strings.ToLower(domain)
|
||||
switch addrs, err := h.lookupInternal(domain); {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case addrs == nil: // Not recorded in static hosts, return nil
|
||||
return nil, nil
|
||||
case len(addrs) == 0: // Domain recorded, but no valid IP returned
|
||||
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||
return addrs, nil
|
||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
||||
@@ -122,8 +122,52 @@ func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) (
|
||||
|
||||
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
||||
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
|
||||
if h.matcher == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return h.lookup(domain, option, 5)
|
||||
}
|
||||
func NewStaticHostsFromCache(matcher strmatcher.IndexMatcher, hostIPs map[string][]string) (*StaticHosts, error) {
|
||||
sh := &StaticHosts{
|
||||
ips: make([][]net.Address, matcher.Size()+1),
|
||||
matchers: matcher,
|
||||
}
|
||||
|
||||
order := hostIPs["_ORDER"]
|
||||
var offset uint32
|
||||
|
||||
img, ok := matcher.(*strmatcher.IndexMatcherGroup)
|
||||
if !ok {
|
||||
// Single matcher (e.g. only manual or only one geosite)
|
||||
if len(order) > 0 {
|
||||
pattern := order[0]
|
||||
ips := parseIPs(hostIPs[pattern])
|
||||
for i := uint32(1); i <= matcher.Size(); i++ {
|
||||
sh.ips[i] = ips
|
||||
}
|
||||
}
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
for i, m := range img.Matchers {
|
||||
if i < len(order) {
|
||||
pattern := order[i]
|
||||
ips := parseIPs(hostIPs[pattern])
|
||||
for j := uint32(1); j <= m.Size(); j++ {
|
||||
sh.ips[offset+j] = ips
|
||||
}
|
||||
offset += m.Size()
|
||||
}
|
||||
}
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
func parseIPs(raw []string) []net.Address {
|
||||
addrs := make([]net.Address, 0, len(raw))
|
||||
for _, s := range raw {
|
||||
if len(s) > 1 && s[0] == '#' {
|
||||
rcode, _ := strconv.Atoi(s[1:])
|
||||
addrs = append(addrs, dns.RCodeError(rcode))
|
||||
} else {
|
||||
addrs = append(addrs, net.ParseAddress(s))
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
+68
-7
@@ -1,12 +1,13 @@
|
||||
package dns_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/xtls/xray-core/app/dns"
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
@@ -14,17 +15,20 @@ import (
|
||||
func TestStaticHosts(t *testing.T) {
|
||||
pb := []*Config_HostMapping{
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "lan"}}},
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "lan",
|
||||
ProxiedDomain: "#3",
|
||||
},
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "example.com"}}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
Ip: [][]byte{
|
||||
{1, 1, 1, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "proxy.xray.com"}}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "proxy.xray.com",
|
||||
Ip: [][]byte{
|
||||
{1, 2, 3, 4},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
@@ -32,17 +36,20 @@ func TestStaticHosts(t *testing.T) {
|
||||
ProxiedDomain: "another-proxy.xray.com",
|
||||
},
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "proxy2.xray.com"}}},
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "proxy2.xray.com",
|
||||
ProxiedDomain: "proxy.xray.com",
|
||||
},
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "example.cn"}}},
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "example.cn",
|
||||
Ip: [][]byte{
|
||||
{2, 2, 2, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "baidu.com"}}},
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "baidu.com",
|
||||
Ip: [][]byte{
|
||||
{127, 0, 0, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
@@ -125,3 +132,57 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestStaticHostsFromCache(t *testing.T) {
|
||||
sites := []*router.GeoSite{
|
||||
{
|
||||
CountryCode: "cloudflare-dns.com",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Full, Value: "example.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "geosite:cn",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "baidu.cn"},
|
||||
},
|
||||
},
|
||||
}
|
||||
deps := map[string][]string{
|
||||
"HOSTS": {"cloudflare-dns.com", "geosite:cn"},
|
||||
}
|
||||
hostIPs := map[string][]string{
|
||||
"cloudflare-dns.com": {"1.1.1.1"},
|
||||
"geosite:cn": {"2.2.2.2"},
|
||||
"_ORDER": {"cloudflare-dns.com", "geosite:cn"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := router.SerializeGeoSiteList(sites, deps, hostIPs, &buf)
|
||||
common.Must(err)
|
||||
|
||||
// Load matcher
|
||||
m, err := router.LoadGeoSiteMatcher(bytes.NewReader(buf.Bytes()), "HOSTS")
|
||||
common.Must(err)
|
||||
|
||||
// Load hostIPs
|
||||
f := bytes.NewReader(buf.Bytes())
|
||||
hips, err := router.LoadGeoSiteHosts(f)
|
||||
common.Must(err)
|
||||
|
||||
hosts, err := NewStaticHostsFromCache(m, hips)
|
||||
common.Must(err)
|
||||
|
||||
{
|
||||
ips, _ := hosts.Lookup("example.com", dns.IPOption{IPv4Enable: true})
|
||||
if len(ips) != 1 || ips[0].String() != "1.1.1.1" {
|
||||
t.Error("failed to lookup example.com from cache")
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ips, _ := hosts.Lookup("baidu.cn", dns.IPOption{IPv4Enable: true})
|
||||
if len(ips) != 1 || ips[0].String() != "2.2.2.2" {
|
||||
t.Error("failed to lookup baidu.cn from cache deps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+110
-19
@@ -3,19 +3,34 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
|
||||
type mphMatcherWrapper struct {
|
||||
m strmatcher.IndexMatcher
|
||||
}
|
||||
|
||||
func (w *mphMatcherWrapper) Match(s string) bool {
|
||||
return w.m.Match(s) != nil
|
||||
}
|
||||
|
||||
func (w *mphMatcherWrapper) String() string {
|
||||
return "mph-matcher"
|
||||
}
|
||||
|
||||
// Server is the interface for Name Server.
|
||||
type Server interface {
|
||||
// Name of the Client.
|
||||
@@ -31,8 +46,9 @@ type Server interface {
|
||||
type Client struct {
|
||||
server Server
|
||||
skipFallback bool
|
||||
expectedIPs geodata.IPMatcher
|
||||
unexpectedIPs geodata.IPMatcher
|
||||
domains []string
|
||||
expectedIPs router.GeoIPMatcher
|
||||
unexpectedIPs router.GeoIPMatcher
|
||||
actPrior bool
|
||||
actUnprior bool
|
||||
tag string
|
||||
@@ -95,9 +111,11 @@ func NewClient(
|
||||
disableCache bool, serveStale bool, serveExpiredTTL uint32,
|
||||
tag string,
|
||||
ipOption dns.IPOption,
|
||||
updateRules func(bool),
|
||||
matcherInfos *[]*DomainMatcherInfo,
|
||||
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo),
|
||||
) (*Client, error) {
|
||||
client := &Client{}
|
||||
|
||||
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||
// Create a new server for each client for now
|
||||
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
@@ -105,25 +123,97 @@ func NewClient(
|
||||
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
_, isLocalDNS := server.(*LocalNameServer)
|
||||
updateRules(isLocalDNS)
|
||||
|
||||
// Establish expected IPs
|
||||
var expectedMatcher geodata.IPMatcher
|
||||
if len(ns.ExpectedIp) > 0 {
|
||||
expectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.ExpectedIp)
|
||||
if err != nil {
|
||||
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
|
||||
// Prioritize local domains with specific TLDs or those without any dot for the local DNS
|
||||
if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
|
||||
ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
|
||||
ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
|
||||
// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
|
||||
// Because the `localhost` DNS client will append len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
|
||||
// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
|
||||
// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
|
||||
// Related issues:
|
||||
// https://github.com/v2fly/v2ray-core/issues/529
|
||||
// https://github.com/v2fly/v2ray-core/issues/719
|
||||
for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
|
||||
*matcherInfos = append(*matcherInfos, &DomainMatcherInfo{
|
||||
clientIdx: uint16(0),
|
||||
domainRuleIdx: uint16(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Establish domain rules
|
||||
var rules []string
|
||||
ruleCurr := 0
|
||||
ruleIter := 0
|
||||
|
||||
// Check if domain matcher cache is provided via environment
|
||||
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||
var mphLoaded bool
|
||||
|
||||
if domainMatcherPath != "" && ns.Tag != "" {
|
||||
f, err := filesystem.NewFileReader(domainMatcherPath)
|
||||
if err == nil {
|
||||
defer f.Close()
|
||||
g, err := router.LoadGeoSiteMatcher(f, ns.Tag)
|
||||
if err == nil {
|
||||
errors.LogDebug(ctx, "MphDomainMatcher loaded from cache for ", ns.Tag, " dns tag)")
|
||||
updateDomainRule(&mphMatcherWrapper{m: g}, 0, *matcherInfos)
|
||||
rules = append(rules, "[MPH Cache]")
|
||||
mphLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mphLoaded {
|
||||
for i, domain := range ns.PrioritizedDomain {
|
||||
ns.PrioritizedDomain[i] = nil
|
||||
domainRule, err := toStrMatcher(domain.Type, domain.Domain)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to create domain matcher, ignore domain rule [type: ", domain.Type, ", domain: ", domain.Domain, "]")
|
||||
domainRule, _ = toStrMatcher(DomainMatchingType_Full, "hack.fix.index.for.illegal.domain.rule")
|
||||
}
|
||||
originalRuleIdx := ruleCurr
|
||||
if ruleCurr < len(ns.OriginalRules) {
|
||||
rule := ns.OriginalRules[ruleCurr]
|
||||
if ruleCurr >= len(rules) {
|
||||
rules = append(rules, rule.Rule)
|
||||
}
|
||||
ruleIter++
|
||||
if ruleIter >= int(rule.Size) {
|
||||
ruleIter = 0
|
||||
ruleCurr++
|
||||
}
|
||||
} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
|
||||
rules = append(rules, domainRule.String())
|
||||
ruleCurr++
|
||||
}
|
||||
updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||
}
|
||||
}
|
||||
ns.PrioritizedDomain = nil
|
||||
runtime.GC()
|
||||
|
||||
// Establish expected IPs
|
||||
var expectedMatcher router.GeoIPMatcher
|
||||
if len(ns.ExpectedGeoip) > 0 {
|
||||
expectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)
|
||||
if err != nil {
|
||||
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
ns.ExpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// Establish unexpected IPs
|
||||
var unexpectedMatcher geodata.IPMatcher
|
||||
if len(ns.UnexpectedIp) > 0 {
|
||||
unexpectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.UnexpectedIp)
|
||||
var unexpectedMatcher router.GeoIPMatcher
|
||||
if len(ns.UnexpectedGeoip) > 0 {
|
||||
unexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)
|
||||
if err != nil {
|
||||
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
ns.UnexpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(clientIP) > 0 {
|
||||
@@ -135,7 +225,7 @@ func NewClient(
|
||||
}
|
||||
}
|
||||
|
||||
timeoutMs := 4000 * time.Millisecond
|
||||
var timeoutMs = 4000 * time.Millisecond
|
||||
if ns.TimeoutMs > 0 {
|
||||
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
||||
}
|
||||
@@ -144,6 +234,7 @@ func NewClient(
|
||||
|
||||
client.server = server
|
||||
client.skipFallback = ns.SkipFallback
|
||||
client.domains = rules
|
||||
client.expectedIPs = expectedMatcher
|
||||
client.unexpectedIPs = unexpectedMatcher
|
||||
client.actPrior = ns.ActPrior
|
||||
@@ -167,7 +258,7 @@ func (c *Client) Name() string {
|
||||
// QueryIP sends DNS query to the name server with the client's IP.
|
||||
func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]net.IP, uint32, error) {
|
||||
if c.checkSystem {
|
||||
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||
supportIPv4, supportIPv6 := checkRoutes()
|
||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||
} else {
|
||||
|
||||
@@ -137,32 +137,14 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
|
||||
if s.Name()+"." == "DOH//"+fqdn {
|
||||
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
|
||||
if noResponseErrCh != nil {
|
||||
err := errors.New("tries to resolve itself!", s.Name())
|
||||
if option.IPv4Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
|
||||
}
|
||||
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
|
||||
// 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))))
|
||||
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
|
||||
}
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -18,6 +18,7 @@ type LocalNameServer struct {
|
||||
|
||||
// QueryIP implements Server.
|
||||
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
|
||||
|
||||
start := time.Now()
|
||||
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) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
||||
|
||||
reqs, err := 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
|
||||
}
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
|
||||
var deadline time.Time
|
||||
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) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||
|
||||
reqs, err := 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
|
||||
}
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -131,6 +131,8 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
||||
newReq.msg = &newMsg
|
||||
s.addPendingRequest(&newReq)
|
||||
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)
|
||||
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) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||
|
||||
reqs, err := 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
|
||||
}
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
|
||||
for _, req := range reqs {
|
||||
udpReq := &udpDnsRequest{
|
||||
@@ -189,6 +179,8 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<
|
||||
}
|
||||
return
|
||||
}
|
||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||
b.UDP = ©Dest
|
||||
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// source: app/geodata/config.proto
|
||||
|
||||
package geodata
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Asset struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||
File string `protobuf:"bytes,2,opt,name=file,proto3" json:"file,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Asset) Reset() {
|
||||
*x = Asset{}
|
||||
mi := &file_app_geodata_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Asset) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Asset) ProtoMessage() {}
|
||||
|
||||
func (x *Asset) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_geodata_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Asset.ProtoReflect.Descriptor instead.
|
||||
func (*Asset) Descriptor() ([]byte, []int) {
|
||||
return file_app_geodata_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Asset) GetUrl() string {
|
||||
if x != nil {
|
||||
return x.Url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Asset) GetFile() string {
|
||||
if x != nil {
|
||||
return x.File
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Cron string `protobuf:"bytes,1,opt,name=cron,proto3" json:"cron,omitempty"`
|
||||
Outbound string `protobuf:"bytes,2,opt,name=outbound,proto3" json:"outbound,omitempty"`
|
||||
Assets []*Asset `protobuf:"bytes,3,rep,name=assets,proto3" json:"assets,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_app_geodata_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_geodata_config_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_app_geodata_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Config) GetCron() string {
|
||||
if x != nil {
|
||||
return x.Cron
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetOutbound() string {
|
||||
if x != nil {
|
||||
return x.Outbound
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetAssets() []*Asset {
|
||||
if x != nil {
|
||||
return x.Assets
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_app_geodata_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_app_geodata_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x18app/geodata/config.proto\x12\x10xray.app.geodata\"-\n" +
|
||||
"\x05Asset\x12\x10\n" +
|
||||
"\x03url\x18\x01 \x01(\tR\x03url\x12\x12\n" +
|
||||
"\x04file\x18\x02 \x01(\tR\x04file\"i\n" +
|
||||
"\x06Config\x12\x12\n" +
|
||||
"\x04cron\x18\x01 \x01(\tR\x04cron\x12\x1a\n" +
|
||||
"\boutbound\x18\x02 \x01(\tR\boutbound\x12/\n" +
|
||||
"\x06assets\x18\x03 \x03(\v2\x17.xray.app.geodata.AssetR\x06assetsBR\n" +
|
||||
"\x14com.xray.app.geodataP\x01Z%github.com/xtls/xray-core/app/geodata\xaa\x02\x10Xray.App.Geodatab\x06proto3"
|
||||
|
||||
var (
|
||||
file_app_geodata_config_proto_rawDescOnce sync.Once
|
||||
file_app_geodata_config_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_app_geodata_config_proto_rawDescGZIP() []byte {
|
||||
file_app_geodata_config_proto_rawDescOnce.Do(func() {
|
||||
file_app_geodata_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_app_geodata_config_proto_rawDesc), len(file_app_geodata_config_proto_rawDesc)))
|
||||
})
|
||||
return file_app_geodata_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_app_geodata_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_app_geodata_config_proto_goTypes = []any{
|
||||
(*Asset)(nil), // 0: xray.app.geodata.Asset
|
||||
(*Config)(nil), // 1: xray.app.geodata.Config
|
||||
}
|
||||
var file_app_geodata_config_proto_depIdxs = []int32{
|
||||
0, // 0: xray.app.geodata.Config.assets:type_name -> xray.app.geodata.Asset
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_geodata_config_proto_init() }
|
||||
func file_app_geodata_config_proto_init() {
|
||||
if File_app_geodata_config_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_geodata_config_proto_rawDesc), len(file_app_geodata_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_app_geodata_config_proto_goTypes,
|
||||
DependencyIndexes: file_app_geodata_config_proto_depIdxs,
|
||||
MessageInfos: file_app_geodata_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_app_geodata_config_proto = out.File
|
||||
file_app_geodata_config_proto_goTypes = nil
|
||||
file_app_geodata_config_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.app.geodata;
|
||||
option csharp_namespace = "Xray.App.Geodata";
|
||||
option go_package = "github.com/xtls/xray-core/app/geodata";
|
||||
option java_package = "com.xray.app.geodata";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Asset {
|
||||
string url = 1;
|
||||
|
||||
string file = 2;
|
||||
}
|
||||
|
||||
message Config {
|
||||
string cron = 1;
|
||||
|
||||
string outbound = 2;
|
||||
|
||||
repeated Asset assets = 3;
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
go_errors "errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport/internet/tagged"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const idleTimeout = 30 * time.Second
|
||||
|
||||
type stage struct {
|
||||
target string
|
||||
temp string
|
||||
}
|
||||
|
||||
type downloader struct {
|
||||
ctx context.Context
|
||||
httpClient *http.Client
|
||||
httpsClient *http.Client
|
||||
}
|
||||
|
||||
type idleConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *idleConn) Read(b []byte) (int, error) {
|
||||
t := time.AfterFunc(idleTimeout, func() {
|
||||
_ = c.Close()
|
||||
})
|
||||
|
||||
n, err := c.Conn.Read(b)
|
||||
if !t.Stop() {
|
||||
_ = c.Close()
|
||||
return n, errors.New("connection idle timeout")
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *idleConn) Write(b []byte) (int, error) {
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
|
||||
return &downloader{
|
||||
ctx: ctx,
|
||||
httpClient: newClient(ctx, dispatcher, outbound, false),
|
||||
httpsClient: newClient(ctx, dispatcher, outbound, true),
|
||||
}
|
||||
}
|
||||
|
||||
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
|
||||
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
var conn net.Conn
|
||||
err := task.Run(ctx, func() error {
|
||||
if tagged.Dialer == nil {
|
||||
return errors.New("tagged dialer is not initialized")
|
||||
}
|
||||
dest, err := net.ParseDestination(network + ":" + address)
|
||||
if err != nil {
|
||||
return errors.New("cannot understand address").Base(err)
|
||||
}
|
||||
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
host, _, _ := net.SplitHostPort(address)
|
||||
tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
|
||||
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
|
||||
defer cancel()
|
||||
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
},
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if req.URL.Scheme != "https" {
|
||||
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||
}
|
||||
if len(via) >= 10 {
|
||||
return errors.New("stopped after 10 redirects")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: nil,
|
||||
DisableKeepAlives: true,
|
||||
DialContext: dial,
|
||||
ResponseHeaderTimeout: idleTimeout,
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if req.URL.Scheme != "https" {
|
||||
return errors.New("redirected to non-https URL: ", req.URL.String())
|
||||
}
|
||||
if len(via) >= 10 {
|
||||
return errors.New("stopped after 10 redirects")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *downloader) download(assets []*Asset) ([]stage, error) {
|
||||
staged := make([]stage, 0, len(assets))
|
||||
for _, asset := range assets {
|
||||
stage, err := d.downloadOne(asset)
|
||||
if err != nil {
|
||||
clean(staged)
|
||||
return nil, err
|
||||
}
|
||||
staged = append(staged, stage)
|
||||
}
|
||||
return staged, nil
|
||||
}
|
||||
|
||||
func (d *downloader) downloadOne(asset *Asset) (stage, error) {
|
||||
target, err := filesystem.ResolveAsset(asset.File)
|
||||
if err != nil {
|
||||
return stage{}, err
|
||||
}
|
||||
errors.LogInfo(d.ctx, "downloading geodata asset from ", asset.Url, " to ", target)
|
||||
|
||||
temp, err := tempFile(target, ".tmp")
|
||||
if err != nil {
|
||||
return stage{}, err
|
||||
}
|
||||
tempName := temp.Name()
|
||||
keepTemp := false
|
||||
defer func() {
|
||||
if !keepTemp {
|
||||
os.Remove(tempName)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := d.fetch(asset.Url, temp); err != nil {
|
||||
temp.Close()
|
||||
return stage{}, err
|
||||
}
|
||||
if err := temp.Chmod(0o644); err != nil {
|
||||
temp.Close()
|
||||
return stage{}, err
|
||||
}
|
||||
if err := temp.Close(); err != nil {
|
||||
return stage{}, err
|
||||
}
|
||||
|
||||
keepTemp = true
|
||||
return stage{
|
||||
target: target,
|
||||
temp: tempName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *downloader) fetch(rawURL string, writer io.Writer) error {
|
||||
req, err := http.NewRequestWithContext(d.ctx, http.MethodGet, rawURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.TryDefaultHeadersWith(req.Header, "nav")
|
||||
|
||||
var client *http.Client
|
||||
if req.URL.Scheme == "https" {
|
||||
client = d.httpsClient
|
||||
} else {
|
||||
client = d.httpClient
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
return errors.New("unexpected status code: ", resp.StatusCode)
|
||||
}
|
||||
|
||||
n, err := io.Copy(writer, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return errors.New("empty response body")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clean(assets []stage) {
|
||||
for _, asset := range assets {
|
||||
if asset.temp != "" {
|
||||
os.Remove(asset.temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type tx struct {
|
||||
swaps []swap
|
||||
}
|
||||
|
||||
type swap struct {
|
||||
target string
|
||||
backup string
|
||||
hadOriginal bool
|
||||
}
|
||||
|
||||
func swapAll(assets []stage) (*tx, error) {
|
||||
t := &tx{}
|
||||
for _, asset := range assets {
|
||||
s, err := swapOne(asset)
|
||||
if err != nil {
|
||||
return nil, errors.Combine(err, t.rollback())
|
||||
}
|
||||
t.swaps = append(t.swaps, s)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func swapOne(asset stage) (swap, error) {
|
||||
backup, err := backupFile(asset.target)
|
||||
if err != nil {
|
||||
return swap{}, err
|
||||
}
|
||||
|
||||
s := swap{
|
||||
target: asset.target,
|
||||
backup: backup,
|
||||
}
|
||||
if err := os.Rename(asset.target, backup); err != nil {
|
||||
if !go_errors.Is(err, os.ErrNotExist) {
|
||||
return swap{}, err
|
||||
}
|
||||
if err := os.Remove(backup); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||
return swap{}, err
|
||||
}
|
||||
} else {
|
||||
s.hadOriginal = true
|
||||
}
|
||||
|
||||
if err := os.Rename(asset.temp, asset.target); err != nil {
|
||||
if s.hadOriginal {
|
||||
if restoreErr := os.Rename(backup, asset.target); restoreErr != nil {
|
||||
return swap{}, errors.Combine(err, restoreErr)
|
||||
}
|
||||
}
|
||||
return swap{}, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (t *tx) rollback() error {
|
||||
var errs []error
|
||||
for i := len(t.swaps) - 1; i >= 0; i-- {
|
||||
if err := t.swaps[i].rollback(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Combine(errs...)
|
||||
}
|
||||
|
||||
func (s swap) rollback() error {
|
||||
var errs []error
|
||||
if err := os.Remove(s.target); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if s.hadOriginal {
|
||||
if err := os.Rename(s.backup, s.target); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
} else if err := os.Remove(s.backup); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errors.Combine(errs...)
|
||||
}
|
||||
|
||||
func (t *tx) commit() error {
|
||||
var errs []error
|
||||
for _, swap := range t.swaps {
|
||||
if err := os.Remove(swap.backup); err != nil && !go_errors.Is(err, os.ErrNotExist) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Combine(errs...)
|
||||
}
|
||||
|
||||
func tempFile(target string, suffix string) (*os.File, error) {
|
||||
dir := filepath.Dir(target)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.CreateTemp(dir, "."+filepath.Base(target)+".*"+suffix)
|
||||
}
|
||||
|
||||
func backupFile(target string) (string, error) {
|
||||
file, err := tempFile(target, ".bak")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := file.Name()
|
||||
if err := file.Close(); err != nil {
|
||||
os.Remove(name)
|
||||
return "", err
|
||||
}
|
||||
if err := os.Remove(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
commongeodata "github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
assets []*Asset
|
||||
downloader *downloader
|
||||
tasker *cron.Cron
|
||||
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
}
|
||||
|
||||
func New(ctx context.Context, config *Config) (*Instance, error) {
|
||||
if config.Cron == "" {
|
||||
return &Instance{}, nil
|
||||
}
|
||||
|
||||
g := &Instance{
|
||||
assets: config.Assets,
|
||||
}
|
||||
|
||||
if len(g.assets) > 0 {
|
||||
var dispatcher routing.Dispatcher
|
||||
if err := core.RequireFeatures(ctx, func(d routing.Dispatcher) {
|
||||
dispatcher = d
|
||||
}); err != nil {
|
||||
return nil, errors.New("failed to get dispatcher for geodata downloader").Base(err)
|
||||
}
|
||||
g.downloader = newDownloader(ctx, dispatcher, config.Outbound)
|
||||
}
|
||||
|
||||
g.tasker = cron.New(
|
||||
cron.WithChain(cron.SkipIfStillRunning(cron.DiscardLogger)),
|
||||
cron.WithLogger(cron.DiscardLogger),
|
||||
)
|
||||
if _, err := g.tasker.AddFunc(config.Cron, g.execute); err != nil {
|
||||
return nil, errors.New("invalid geodata cron").Base(err)
|
||||
}
|
||||
errors.LogInfo(ctx, "scheduled geodata reload with cron: ", config.Cron)
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *Instance) execute() {
|
||||
var err error
|
||||
if g.downloader != nil {
|
||||
err = g.reloadWithUpdate()
|
||||
} else {
|
||||
err = reload()
|
||||
}
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "scheduled geodata reload failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Instance) reloadWithUpdate() error {
|
||||
staged, err := g.downloader.download(g.assets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer clean(staged)
|
||||
|
||||
tx, err := swapAll(staged)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := reload(); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to reload geodata after downloading assets, rolling back")
|
||||
rollbackErr := tx.rollback()
|
||||
return errors.Combine(err, rollbackErr)
|
||||
}
|
||||
|
||||
return tx.commit()
|
||||
}
|
||||
|
||||
func reload() error {
|
||||
return errors.Combine(commongeodata.IPReg.Reload(), commongeodata.DomainReg.Reload())
|
||||
}
|
||||
|
||||
func (g *Instance) Type() interface{} {
|
||||
return (*Instance)(nil)
|
||||
}
|
||||
|
||||
func (g *Instance) Start() error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if g.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
if g.tasker != nil {
|
||||
g.tasker.Start()
|
||||
}
|
||||
|
||||
g.running = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Instance) Close() error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if !g.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
if g.tasker != nil {
|
||||
<-g.tasker.Stop().Done()
|
||||
}
|
||||
|
||||
g.running = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||
return New(ctx, cfg.(*Config))
|
||||
}))
|
||||
}
|
||||
+64
-150
@@ -2,18 +2,16 @@ package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"expvar"
|
||||
stdnet "net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
_ "net/http/pprof"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/app/observatory"
|
||||
"github.com/xtls/xray-core/app/stats"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"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/core"
|
||||
"github.com/xtls/xray-core/features/extension"
|
||||
@@ -24,17 +22,15 @@ import (
|
||||
type MetricsHandler struct {
|
||||
ohm outbound.Manager
|
||||
statsManager feature_stats.Manager
|
||||
ctx context.Context
|
||||
observatory extension.Observatory
|
||||
tag string
|
||||
listen string
|
||||
tcpListener xnet.Listener
|
||||
listener *OutboundListener
|
||||
tcpListener net.Listener
|
||||
}
|
||||
|
||||
// NewMetricsHandler creates a new MetricsHandler based on the given config.
|
||||
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
|
||||
c := &MetricsHandler{
|
||||
ctx: ctx,
|
||||
tag: config.Tag,
|
||||
listen: config.Listen,
|
||||
}
|
||||
@@ -42,6 +38,50 @@ func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, er
|
||||
c.statsManager = sm
|
||||
c.ohm = om
|
||||
}))
|
||||
expvar.Publish("stats", expvar.Func(func() interface{} {
|
||||
manager, ok := c.statsManager.(*stats.Manager)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
resp := map[string]map[string]map[string]int64{
|
||||
"inbound": {},
|
||||
"outbound": {},
|
||||
"user": {},
|
||||
}
|
||||
manager.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
|
||||
}
|
||||
|
||||
@@ -50,172 +90,46 @@ func (p *MetricsHandler) Type() interface{} {
|
||||
}
|
||||
|
||||
func (p *MetricsHandler) Start() error {
|
||||
handler := p.httpHandler()
|
||||
|
||||
// direct listen a port if listen is set
|
||||
if p.listen != "" {
|
||||
TCPlistener, err := xnet.Listen("tcp", p.listen)
|
||||
TCPlistener, err := net.Listen("tcp", p.listen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.tcpListener = TCPlistener
|
||||
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
||||
|
||||
go p.serve(TCPlistener, handler)
|
||||
}
|
||||
|
||||
if p.tag == "" {
|
||||
if p.tcpListener == nil {
|
||||
return errors.New("metrics must have a tag or listen address")
|
||||
}
|
||||
return nil
|
||||
go func() {
|
||||
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
listener := &OutboundListener{
|
||||
buffer: make(chan xnet.Conn, 4),
|
||||
buffer: make(chan net.Conn, 4),
|
||||
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 {
|
||||
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,
|
||||
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 {
|
||||
var errs []error
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
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 (
|
||||
"context"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/app/observatory"
|
||||
@@ -71,6 +72,7 @@ func (o *Observer) Start() error {
|
||||
o.hp.StartScheduler(func() ([]string, error) {
|
||||
hs, ok := o.ohm.(outbound.HandlerSelector)
|
||||
if !ok {
|
||||
|
||||
return nil, errors.New("outbound.Manager is not a HandlerSelector")
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
@@ -25,12 +24,11 @@ type HealthPingSettings struct {
|
||||
|
||||
// HealthPing is the health checker for balancers
|
||||
type HealthPing struct {
|
||||
ctx context.Context
|
||||
cancelCtx context.CancelFunc
|
||||
cancelPending atomic.Pointer[context.CancelFunc]
|
||||
dispatcher routing.Dispatcher
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
ctx context.Context
|
||||
dispatcher routing.Dispatcher
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
tickerClose chan struct{}
|
||||
|
||||
Settings *HealthPingSettings
|
||||
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"
|
||||
}
|
||||
if settings.Interval == 0 {
|
||||
settings.Interval = 1 * time.Minute
|
||||
} else if settings.Interval < 10*time.Second {
|
||||
settings.Interval = time.Duration(1) * time.Minute
|
||||
} else if settings.Interval < 10 {
|
||||
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 {
|
||||
settings.SamplingCount = 10
|
||||
@@ -75,12 +73,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
|
||||
if settings.Timeout <= 0 {
|
||||
// results are saved after all health pings finish,
|
||||
// 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{
|
||||
ctx: ctx,
|
||||
cancelCtx: cancel,
|
||||
dispatcher: dispatcher,
|
||||
Settings: settings,
|
||||
Results: nil,
|
||||
@@ -94,9 +90,9 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
}
|
||||
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
|
||||
ticker := time.NewTicker(interval)
|
||||
tickerClose := make(chan struct{})
|
||||
h.ticker = ticker
|
||||
|
||||
// init run to get a fast check result
|
||||
h.tickerClose = tickerClose
|
||||
go func() {
|
||||
tags, err := selector()
|
||||
if err != nil {
|
||||
@@ -114,20 +110,13 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
|
||||
return
|
||||
}
|
||||
subCtx, cancel := context.WithCancel(h.ctx)
|
||||
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.doCheck(tags, interval, h.Settings.SamplingCount)
|
||||
h.Cleanup(tags)
|
||||
}()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
continue
|
||||
case <-h.ctx.Done():
|
||||
case <-tickerClose:
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -141,7 +130,8 @@ func (h *HealthPing) StopScheduler() {
|
||||
}
|
||||
h.ticker.Stop()
|
||||
h.ticker = nil
|
||||
h.cancelCtx()
|
||||
close(h.tickerClose)
|
||||
h.tickerClose = nil
|
||||
}
|
||||
|
||||
// Check implements the HealthChecker
|
||||
@@ -150,7 +140,7 @@ func (h *HealthPing) Check(tags []string) error {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -161,29 +151,28 @@ type rtt struct {
|
||||
|
||||
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
|
||||
// sure all tags are valid for current balancer
|
||||
// cancel ctx will stop all pending checks
|
||||
func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.Duration, rounds int) {
|
||||
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
|
||||
count := len(tags) * rounds
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
ch := make(chan *rtt, count)
|
||||
timers := make([]*time.Timer, 0, count)
|
||||
|
||||
for _, tag := range tags {
|
||||
handler := tag
|
||||
client := newPingClient(
|
||||
ctx,
|
||||
h.ctx,
|
||||
h.dispatcher,
|
||||
h.Settings.Destination,
|
||||
h.Settings.Timeout,
|
||||
handler,
|
||||
)
|
||||
for range rounds {
|
||||
for i := 0; i < rounds; i++ {
|
||||
delay := time.Duration(0)
|
||||
if duration > 0 {
|
||||
delay = time.Duration(dice.RollInt63n(int64(duration)))
|
||||
}
|
||||
timers = append(timers, time.AfterFunc(delay, func() {
|
||||
time.AfterFunc(delay, func() {
|
||||
errors.LogDebug(h.ctx, "checking ", handler)
|
||||
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
|
||||
if err == nil {
|
||||
@@ -211,21 +200,14 @@ func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.D
|
||||
handler: handler,
|
||||
value: rttFailed,
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
select {
|
||||
case rtt := <-ch:
|
||||
if rtt.value > 0 {
|
||||
// should not put results when network is down
|
||||
h.PutResult(rtt.handler, rtt.value)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
for _, timer := range timers {
|
||||
timer.Stop()
|
||||
}
|
||||
return
|
||||
rtt := <-ch
|
||||
if rtt.value > 0 {
|
||||
// should not put results when network is down
|
||||
h.PutResult(rtt.handler, rtt.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (h *HealthPingRTTS) Put(d time.Duration) {
|
||||
if h.rtts == nil {
|
||||
h.rtts = make([]*pingRTT, h.cap)
|
||||
for i := 0; i < h.cap; i++ {
|
||||
h.rtts[i] = &pingRTT{value: rttUntested}
|
||||
h.rtts[i] = &pingRTT{}
|
||||
}
|
||||
h.idx = -1
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
|
||||
validRTTs := make([]time.Duration, 0)
|
||||
for _, rtt := range h.rtts {
|
||||
switch {
|
||||
case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
|
||||
case rtt.value == 0 || time.Since(rtt.time) > h.validity:
|
||||
continue
|
||||
case rtt.value == rttFailed:
|
||||
stats.Fail++
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -71,7 +70,7 @@ func (o *Observer) background() {
|
||||
|
||||
outbounds := hs.Select(o.config.SubjectSelector)
|
||||
|
||||
o.clearRemovedOutbounds(outbounds)
|
||||
o.updateStatus(outbounds)
|
||||
|
||||
sleepTime := time.Second * 10
|
||||
if o.config.ProbeInterval != 0 {
|
||||
@@ -112,19 +111,11 @@ func (o *Observer) background() {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Observer) clearRemovedOutbounds(outbounds []string) {
|
||||
func (o *Observer) updateStatus(outbounds []string) {
|
||||
o.statusLock.Lock()
|
||||
defer o.statusLock.Unlock()
|
||||
if len(o.status) == 0 {
|
||||
return
|
||||
}
|
||||
var pruned []*OutboundStatus
|
||||
for _, status := range o.status {
|
||||
if slices.Contains(outbounds, status.OutboundTag) {
|
||||
pruned = append(pruned, status)
|
||||
}
|
||||
}
|
||||
o.status = pruned
|
||||
// TODO should remove old inbound that is removed
|
||||
_ = outbounds
|
||||
}
|
||||
|
||||
func (o *Observer) probe(outbound string) ProbeResult {
|
||||
@@ -186,7 +177,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
||||
return 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)
|
||||
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package observatory
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestObserverUpdateStatusPrunesStaleOutbounds(t *testing.T) {
|
||||
observer := &Observer{
|
||||
status: []*OutboundStatus{
|
||||
{
|
||||
OutboundTag: "keep",
|
||||
Alive: true,
|
||||
Delay: 42,
|
||||
LastErrorReason: "",
|
||||
LastSeenTime: 111,
|
||||
LastTryTime: 222,
|
||||
},
|
||||
{
|
||||
OutboundTag: "drop",
|
||||
Alive: false,
|
||||
Delay: 99999999,
|
||||
LastErrorReason: "probe failed",
|
||||
LastSeenTime: 333,
|
||||
LastTryTime: 444,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
observer.clearRemovedOutbounds([]string{"keep"})
|
||||
|
||||
if len(observer.status) != 1 {
|
||||
t.Fatalf("expected 1 status after pruning, got %d", len(observer.status))
|
||||
}
|
||||
|
||||
got := observer.status[0]
|
||||
if got.OutboundTag != "keep" {
|
||||
t.Fatalf("expected remaining status for keep, got %q", got.OutboundTag)
|
||||
}
|
||||
if !got.Alive {
|
||||
t.Fatal("expected remaining status to preserve Alive field")
|
||||
}
|
||||
if got.Delay != 42 {
|
||||
t.Fatalf("expected remaining status to preserve Delay, got %d", got.Delay)
|
||||
}
|
||||
if got.LastSeenTime != 111 {
|
||||
t.Fatalf("expected remaining status to preserve LastSeenTime, got %d", got.LastSeenTime)
|
||||
}
|
||||
if got.LastTryTime != 222 {
|
||||
t.Fatalf("expected remaining status to preserve LastTryTime, got %d", got.LastTryTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObserverUpdateStatusClearsWhenNoOutboundsRemain(t *testing.T) {
|
||||
observer := &Observer{
|
||||
status: []*OutboundStatus{
|
||||
{OutboundTag: "drop-1"},
|
||||
{OutboundTag: "drop-2"},
|
||||
},
|
||||
}
|
||||
|
||||
observer.clearRemovedOutbounds(nil)
|
||||
|
||||
if len(observer.status) != 0 {
|
||||
t.Fatalf("expected all statuses to be removed, got %d", len(observer.status))
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInbound
|
||||
if len(request.Email) > 0 {
|
||||
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)
|
||||
for _, u := range users {
|
||||
result = append(result, protocol.ToProtoUser(u))
|
||||
|
||||
@@ -1,34 +1 @@
|
||||
package proxyman
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
)
|
||||
|
||||
func BuildSniffingRequest(config *SniffingConfig) (session.SniffingRequest, error) {
|
||||
if config == nil {
|
||||
return session.SniffingRequest{}, nil
|
||||
}
|
||||
|
||||
request := session.SniffingRequest{
|
||||
Enabled: config.Enabled,
|
||||
OverrideDestinationForProtocol: config.DestinationOverride,
|
||||
MetadataOnly: config.MetadataOnly,
|
||||
RouteOnly: config.RouteOnly,
|
||||
}
|
||||
if len(config.DomainsExcluded) > 0 {
|
||||
excludeForDomain, err := geodata.DomainReg.BuildDomainMatcher(config.DomainsExcluded)
|
||||
if err != nil {
|
||||
return session.SniffingRequest{}, err
|
||||
}
|
||||
request.ExcludeForDomain = excludeForDomain
|
||||
}
|
||||
if len(config.IpsExcluded) > 0 {
|
||||
excludeForIP, err := geodata.IPReg.BuildIPMatcher(config.IpsExcluded)
|
||||
if err != nil {
|
||||
return session.SniffingRequest{}, err
|
||||
}
|
||||
request.ExcludeForIP = excludeForIP
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
+29
-43
@@ -7,7 +7,6 @@
|
||||
package proxyman
|
||||
|
||||
import (
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
serial "github.com/xtls/xray-core/common/serial"
|
||||
internet "github.com/xtls/xray-core/transport/internet"
|
||||
@@ -67,9 +66,8 @@ type SniffingConfig struct {
|
||||
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
||||
// Override target destination if sniff'ed protocol is in the given list.
|
||||
// Supported values are "http", "tls", "fakedns".
|
||||
DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
|
||||
DomainsExcluded []*geodata.DomainRule `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"`
|
||||
IpsExcluded []*geodata.IPRule `protobuf:"bytes,6,rep,name=ips_excluded,json=ipsExcluded,proto3" json:"ips_excluded,omitempty"`
|
||||
DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
|
||||
DomainsExcluded []string `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"`
|
||||
// Whether should only try to sniff metadata without waiting for client input.
|
||||
// Can be used to support SMTP like protocol where server send the first
|
||||
// message.
|
||||
@@ -123,20 +121,13 @@ func (x *SniffingConfig) GetDestinationOverride() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SniffingConfig) GetDomainsExcluded() []*geodata.DomainRule {
|
||||
func (x *SniffingConfig) GetDomainsExcluded() []string {
|
||||
if x != nil {
|
||||
return x.DomainsExcluded
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SniffingConfig) GetIpsExcluded() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.IpsExcluded
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SniffingConfig) GetMetadataOnly() bool {
|
||||
if x != nil {
|
||||
return x.MetadataOnly
|
||||
@@ -486,13 +477,12 @@ var File_app_proxyman_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_app_proxyman_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x19app/proxyman/config.proto\x12\x11xray.app.proxyman\x1a\x18common/net/address.proto\x1a\x15common/net/port.proto\x1a\x1ftransport/internet/config.proto\x1a!common/serial/typed_message.proto\x1a\x1bcommon/geodata/geodat.proto\"\x0f\n" +
|
||||
"\rInboundConfig\"\xad\x02\n" +
|
||||
"\x19app/proxyman/config.proto\x12\x11xray.app.proxyman\x1a\x18common/net/address.proto\x1a\x15common/net/port.proto\x1a\x1ftransport/internet/config.proto\x1a!common/serial/typed_message.proto\"\x0f\n" +
|
||||
"\rInboundConfig\"\xcc\x01\n" +
|
||||
"\x0eSniffingConfig\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x121\n" +
|
||||
"\x14destination_override\x18\x02 \x03(\tR\x13destinationOverride\x12J\n" +
|
||||
"\x10domains_excluded\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x0fdomainsExcluded\x12>\n" +
|
||||
"\fips_excluded\x18\x06 \x03(\v2\x1b.xray.common.geodata.IPRuleR\vipsExcluded\x12#\n" +
|
||||
"\x14destination_override\x18\x02 \x03(\tR\x13destinationOverride\x12)\n" +
|
||||
"\x10domains_excluded\x18\x03 \x03(\tR\x0fdomainsExcluded\x12#\n" +
|
||||
"\rmetadata_only\x18\x04 \x01(\bR\fmetadataOnly\x12\x1d\n" +
|
||||
"\n" +
|
||||
"route_only\x18\x05 \x01(\bR\trouteOnly\"\xe5\x02\n" +
|
||||
@@ -542,34 +532,30 @@ var file_app_proxyman_config_proto_goTypes = []any{
|
||||
(*OutboundConfig)(nil), // 4: xray.app.proxyman.OutboundConfig
|
||||
(*SenderConfig)(nil), // 5: xray.app.proxyman.SenderConfig
|
||||
(*MultiplexingConfig)(nil), // 6: xray.app.proxyman.MultiplexingConfig
|
||||
(*geodata.DomainRule)(nil), // 7: xray.common.geodata.DomainRule
|
||||
(*geodata.IPRule)(nil), // 8: xray.common.geodata.IPRule
|
||||
(*net.PortList)(nil), // 9: xray.common.net.PortList
|
||||
(*net.IPOrDomain)(nil), // 10: xray.common.net.IPOrDomain
|
||||
(*internet.StreamConfig)(nil), // 11: xray.transport.internet.StreamConfig
|
||||
(*serial.TypedMessage)(nil), // 12: xray.common.serial.TypedMessage
|
||||
(*internet.ProxyConfig)(nil), // 13: xray.transport.internet.ProxyConfig
|
||||
(internet.DomainStrategy)(0), // 14: xray.transport.internet.DomainStrategy
|
||||
(*net.PortList)(nil), // 7: xray.common.net.PortList
|
||||
(*net.IPOrDomain)(nil), // 8: xray.common.net.IPOrDomain
|
||||
(*internet.StreamConfig)(nil), // 9: xray.transport.internet.StreamConfig
|
||||
(*serial.TypedMessage)(nil), // 10: xray.common.serial.TypedMessage
|
||||
(*internet.ProxyConfig)(nil), // 11: xray.transport.internet.ProxyConfig
|
||||
(internet.DomainStrategy)(0), // 12: xray.transport.internet.DomainStrategy
|
||||
}
|
||||
var file_app_proxyman_config_proto_depIdxs = []int32{
|
||||
7, // 0: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.geodata.DomainRule
|
||||
8, // 1: xray.app.proxyman.SniffingConfig.ips_excluded:type_name -> xray.common.geodata.IPRule
|
||||
9, // 2: xray.app.proxyman.ReceiverConfig.port_list:type_name -> xray.common.net.PortList
|
||||
10, // 3: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
|
||||
11, // 4: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||
1, // 5: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
|
||||
12, // 6: xray.app.proxyman.InboundHandlerConfig.receiver_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
|
||||
11, // 9: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||
13, // 10: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
|
||||
6, // 11: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
||||
14, // 12: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
13, // [13:13] is the sub-list for method output_type
|
||||
13, // [13:13] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
7, // 0: xray.app.proxyman.ReceiverConfig.port_list:type_name -> xray.common.net.PortList
|
||||
8, // 1: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
|
||||
9, // 2: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||
1, // 3: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
|
||||
10, // 4: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage
|
||||
10, // 5: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
|
||||
8, // 6: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
|
||||
9, // 7: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
|
||||
11, // 8: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
|
||||
6, // 9: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
||||
12, // 10: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_proxyman_config_proto_init() }
|
||||
|
||||
@@ -10,7 +10,6 @@ import "common/net/address.proto";
|
||||
import "common/net/port.proto";
|
||||
import "transport/internet/config.proto";
|
||||
import "common/serial/typed_message.proto";
|
||||
import "common/geodata/geodat.proto";
|
||||
|
||||
message InboundConfig {}
|
||||
|
||||
@@ -21,10 +20,7 @@ message SniffingConfig {
|
||||
// Override target destination if sniff'ed protocol is in the given list.
|
||||
// Supported values are "http", "tls", "fakedns".
|
||||
repeated string destination_override = 2;
|
||||
|
||||
repeated xray.common.geodata.DomainRule domains_excluded = 3;
|
||||
|
||||
repeated xray.common.geodata.IPRule ips_excluded = 6;
|
||||
repeated string domains_excluded = 3;
|
||||
|
||||
// Whether should only try to sniff metadata without waiting for client input.
|
||||
// Can be used to support SMTP like protocol where server send the first
|
||||
|
||||
@@ -26,7 +26,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
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 {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
|
||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
@@ -53,27 +53,21 @@ type AlwaysOnInboundHandler struct {
|
||||
}
|
||||
|
||||
func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
|
||||
sniffingRequest, err := proxyman.BuildSniffingRequest(receiverConfig.SniffingSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Set tag and sniffing config in context before creating proxy
|
||||
// This allows proxies like TUN to access these settings
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})
|
||||
if receiverConfig.SniffingSettings != nil {
|
||||
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||
SniffingRequest: session.SniffingRequest{
|
||||
Enabled: receiverConfig.SniffingSettings.Enabled,
|
||||
OverrideDestinationForProtocol: receiverConfig.SniffingSettings.DestinationOverride,
|
||||
ExcludeForDomain: receiverConfig.SniffingSettings.DomainsExcluded,
|
||||
MetadataOnly: receiverConfig.SniffingSettings.MetadataOnly,
|
||||
RouteOnly: receiverConfig.SniffingSettings.RouteOnly,
|
||||
},
|
||||
})
|
||||
}
|
||||
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})
|
||||
newCtx = session.ContextWithContent(newCtx, &session.Content{SniffingRequest: sniffingRequest})
|
||||
newCtx = session.ContextWithStreamSettings(newCtx, mss)
|
||||
|
||||
rawProxy, err := common.CreateObject(newCtx, proxyConfig)
|
||||
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,6 +93,11 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
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 mss.SocketSettings == nil {
|
||||
mss.SocketSettings = &internet.SocketConfig{}
|
||||
@@ -118,7 +117,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
stream: mss,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingRequest: sniffingRequest,
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
@@ -140,7 +139,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
recvOrigDest: receiverConfig.ReceiveOriginalDestination,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingRequest: sniffingRequest,
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
@@ -155,7 +154,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
address: address,
|
||||
port: net.Port(port),
|
||||
dispatcher: h.mux,
|
||||
sniffingRequest: sniffingRequest,
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
stream: mss,
|
||||
@@ -172,12 +171,6 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
|
||||
// Start implements common.Runnable.
|
||||
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 {
|
||||
if err := worker.Start(); err != nil {
|
||||
return err
|
||||
@@ -193,7 +186,6 @@ func (h *AlwaysOnInboundHandler) Close() error {
|
||||
errs = append(errs, worker.Close())
|
||||
}
|
||||
errs = append(errs, h.mux.Close())
|
||||
errs = append(errs, common.Close(h.proxy))
|
||||
if err := errors.Combine(errs...); err != nil {
|
||||
return errors.New("failed to close all resources").Base(err)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
|
||||
// Manager manages all inbound handlers.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
access sync.RWMutex
|
||||
untaggedHandlers []inbound.Handler
|
||||
taggedHandlers map[string]inbound.Handler
|
||||
running bool
|
||||
taggedHandlers map[string]inbound.Handler
|
||||
running bool
|
||||
}
|
||||
|
||||
// New returns a new Manager for inbound handlers.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
c "github.com/xtls/xray-core/common/ctx"
|
||||
@@ -18,9 +19,9 @@ import (
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
hysteria_proxy "github.com/xtls/xray-core/proxy/hysteria"
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tcp"
|
||||
"github.com/xtls/xray-core/transport/internet/udp"
|
||||
@@ -42,7 +43,7 @@ type tcpWorker struct {
|
||||
recvOrigDest bool
|
||||
tag string
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingRequest session.SniffingRequest
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
@@ -117,7 +118,13 @@ func (w *tcpWorker) callback(conn stat.Connection) {
|
||||
})
|
||||
|
||||
content := new(session.Content)
|
||||
content.SniffingRequest = w.sniffingRequest
|
||||
if w.sniffingConfig != nil {
|
||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
|
||||
if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil {
|
||||
@@ -134,8 +141,10 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
|
||||
func (w *tcpWorker) Start() error {
|
||||
ctx := context.Background()
|
||||
|
||||
if v, ok := w.proxy.(*hysteria_proxy.Server); ok {
|
||||
ctx = hysteria.ContextWithValidator(ctx, v.HysteriaInboundValidator())
|
||||
type HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }
|
||||
if v, ok := w.proxy.(HysteriaInboundValidator); ok {
|
||||
ctx = hyCtx.ContextWithRequireDatagram(ctx, true)
|
||||
ctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())
|
||||
}
|
||||
|
||||
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
||||
@@ -266,7 +275,7 @@ type udpWorker struct {
|
||||
tag string
|
||||
stream *internet.MemoryStreamConfig
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingRequest session.SniffingRequest
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
@@ -356,7 +365,13 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
||||
Tag: w.tag,
|
||||
})
|
||||
content := new(session.Content)
|
||||
content.SniffingRequest = w.sniffingRequest
|
||||
if w.sniffingConfig != nil {
|
||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
if err := w.proxy.Process(ctx, net.Network_UDP, conn, w.dispatcher); err != nil {
|
||||
errors.LogInfoInner(ctx, err, "connection ends")
|
||||
@@ -472,7 +487,7 @@ type dsWorker struct {
|
||||
stream *internet.MemoryStreamConfig
|
||||
tag string
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingRequest session.SniffingRequest
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
@@ -502,7 +517,13 @@ func (w *dsWorker) callback(conn stat.Connection) {
|
||||
})
|
||||
|
||||
content := new(session.Content)
|
||||
content.SniffingRequest = w.sniffingRequest
|
||||
if w.sniffingConfig != nil {
|
||||
content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
|
||||
content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
|
||||
content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
|
||||
content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
|
||||
content.SniffingRequest.RouteOnly = w.sniffingConfig.RouteOnly
|
||||
}
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
|
||||
if err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher); err != nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
|
||||
@@ -39,7 +40,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
|
||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
uplinkCounter = c
|
||||
}
|
||||
@@ -47,7 +48,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
|
||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
@@ -108,9 +109,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
||||
|
||||
ctx = session.ContextWithFullHandler(ctx, h)
|
||||
|
||||
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||
|
||||
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
|
||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -307,6 +306,11 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
|
||||
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)
|
||||
@@ -344,7 +348,7 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
|
||||
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
||||
}
|
||||
}
|
||||
// case addr.Family().IsDomain():
|
||||
//case addr.Family().IsDomain():
|
||||
default:
|
||||
ob.Gateway = addr
|
||||
|
||||
@@ -392,6 +396,7 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
|
||||
}
|
||||
|
||||
func ParseRandomIP(addr net.Address, prefix string) net.Address {
|
||||
|
||||
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
|
||||
|
||||
ones, bits := ipnet.Mask.Size()
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
)
|
||||
|
||||
func TestInterfaces(t *testing.T) {
|
||||
_ = outbound.Handler(new(Handler))
|
||||
_ = outbound.Manager(new(Manager))
|
||||
_ = (outbound.Handler)(new(Handler))
|
||||
_ = (outbound.Manager)(new(Manager))
|
||||
}
|
||||
|
||||
const xrayKey core.XrayKey = 1
|
||||
@@ -43,12 +43,12 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
|
||||
}
|
||||
|
||||
v, _ := core.New(config)
|
||||
v.AddFeature(outbound.Manager(new(Manager)))
|
||||
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||
Tag: "tag",
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
})
|
||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||
_, ok := conn.(*stat.CounterConnection)
|
||||
@@ -73,12 +73,12 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
||||
}
|
||||
|
||||
v, _ := core.New(config)
|
||||
v.AddFeature(outbound.Manager(new(Manager)))
|
||||
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||
Tag: "tag",
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
})
|
||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||
_, ok := conn.(*stat.CounterConnection)
|
||||
@@ -88,6 +88,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTagsCache(t *testing.T) {
|
||||
|
||||
test_duration := 10 * time.Second
|
||||
threads_num := 50
|
||||
delay := 10 * time.Millisecond
|
||||
@@ -117,7 +118,7 @@ func TestTagsCache(t *testing.T) {
|
||||
tag := fmt.Sprintf("%s%d", tags_prefix, idx)
|
||||
cfg := &core.OutboundHandlerConfig{
|
||||
Tag: tag,
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
}
|
||||
if h, err := NewHandler(ctx, cfg); err == nil {
|
||||
if err := ohm.AddHandler(ctx, h); err == nil {
|
||||
|
||||
@@ -162,6 +162,7 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
|
||||
|
||||
// Select implements outbound.HandlerSelector.
|
||||
func (m *Manager) Select(selectors []string) []string {
|
||||
|
||||
key := strings.Join(selectors, ",")
|
||||
if cache, ok := m.tagsCache.Load(key); ok {
|
||||
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
|
||||
}
|
||||
@@ -58,6 +58,7 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
|
||||
return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)
|
||||
}
|
||||
return nil, errors.New("unsupported router implementation")
|
||||
|
||||
}
|
||||
|
||||
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
. "github.com/xtls/xray-core/app/router/command"
|
||||
"github.com/xtls/xray-core/app/stats"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/testing/mocks"
|
||||
@@ -304,12 +303,12 @@ func TestServiceTestRoute(t *testing.T) {
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
Domain: []*geodata.DomainRule{{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "com"}}}},
|
||||
Domain: []*router.Domain{{Type: router.Domain_Domain, Value: "com"}},
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
SourceIp: []*geodata.IPRule{{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDRRule{Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}}},
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
SourceGeoip: []*router.GeoIP{{CountryCode: "private", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
UserEmail: []string{"example@example.com"},
|
||||
|
||||
+66
-29
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -9,10 +10,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/features/routing/dns"
|
||||
)
|
||||
|
||||
type Condition interface {
|
||||
@@ -45,18 +45,67 @@ func (v *ConditionChan) Len() int {
|
||||
return len(*v)
|
||||
}
|
||||
|
||||
type DomainMatcher struct{ geodata.DomainMatcher }
|
||||
var matcherTypeMap = map[Domain_Type]strmatcher.Type{
|
||||
Domain_Plain: strmatcher.Substr,
|
||||
Domain_Regex: strmatcher.Regex,
|
||||
Domain_Domain: strmatcher.Domain,
|
||||
Domain_Full: strmatcher.Full,
|
||||
}
|
||||
|
||||
func NewDomainMatcher(rules []*geodata.DomainRule) (*DomainMatcher, error) {
|
||||
m, err := geodata.DomainReg.BuildDomainMatcher(rules)
|
||||
type DomainMatcher struct {
|
||||
Matchers strmatcher.IndexMatcher
|
||||
}
|
||||
|
||||
func SerializeDomainMatcher(domains []*Domain, w io.Writer) error {
|
||||
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for _, d := range domains {
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
if !f {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := g.AddPattern(d.Value, matcherType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
g.Build()
|
||||
// serialize
|
||||
return g.Serialize(w)
|
||||
}
|
||||
|
||||
func NewDomainMatcherFromBuffer(data []byte) (*strmatcher.MphMatcherGroup, error) {
|
||||
matcher, err := strmatcher.NewMphMatcherGroupFromBuffer(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DomainMatcher{DomainMatcher: m}, nil
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for i, d := range domains {
|
||||
domains[i] = nil
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
if !f {
|
||||
errors.LogError(context.Background(), "ignore unsupported domain type ", d.Type, " of rule ", d.Value)
|
||||
continue
|
||||
}
|
||||
_, err := g.AddPattern(d.Value, matcherType)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "ignore domain rule ", d.Type, " ", d.Value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
g.Build()
|
||||
return &DomainMatcher{
|
||||
Matchers: g,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
||||
return m.DomainMatcher.MatchAny(strings.ToLower(domain))
|
||||
return len(m.Matchers.Match(strings.ToLower(domain))) > 0
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
@@ -65,7 +114,7 @@ func (m *DomainMatcher) Apply(ctx routing.Context) bool {
|
||||
if len(domain) == 0 {
|
||||
return false
|
||||
}
|
||||
return m.DomainMatcher.MatchAny(strings.ToLower(domain))
|
||||
return m.ApplyDomain(domain)
|
||||
}
|
||||
|
||||
type MatcherAsType byte
|
||||
@@ -78,16 +127,16 @@ const (
|
||||
)
|
||||
|
||||
type IPMatcher struct {
|
||||
matcher geodata.IPMatcher
|
||||
matcher GeoIPMatcher
|
||||
asType MatcherAsType
|
||||
}
|
||||
|
||||
func NewIPMatcher(rules []*geodata.IPRule, asType MatcherAsType) (*IPMatcher, error) {
|
||||
m, err := geodata.IPReg.BuildIPMatcher(rules)
|
||||
func NewIPMatcher(geoips []*GeoIP, asType MatcherAsType) (*IPMatcher, error) {
|
||||
matcher, err := BuildOptimizedGeoIPMatcher(geoips...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IPMatcher{matcher: m, asType: asType}, nil
|
||||
return &IPMatcher{matcher: matcher, asType: asType}, nil
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
@@ -341,10 +390,8 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||
if len(ctx.GetSourceIPs()) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
srcPort := uint16(ctx.GetSourcePort())
|
||||
srcPort := ctx.GetSourcePort().String()
|
||||
srcIP := ctx.GetSourceIPs()[0].String()
|
||||
|
||||
var network string
|
||||
switch ctx.GetNetwork() {
|
||||
case net.Network_TCP:
|
||||
@@ -354,21 +401,11 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
var dstIP string
|
||||
var dstPort uint16 = 0
|
||||
|
||||
// do not use resolved IP because Android process lookup needs original dst ip
|
||||
resolvableContext, ok := ctx.(*dns.ResolvableContext)
|
||||
if ok && len(resolvableContext.Context.GetTargetIPs()) > 0 {
|
||||
dstIP = resolvableContext.Context.GetTargetIPs()[0].String()
|
||||
dstPort = uint16(resolvableContext.Context.GetTargetPort())
|
||||
} else if len(ctx.GetTargetIPs()) > 0 {
|
||||
dstIP = ctx.GetTargetIPs()[0].String()
|
||||
dstPort = uint16(ctx.GetTargetPort())
|
||||
src, err := net.ParseDestination(strings.Join([]string{network, srcIP, srcPort}, ":"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pid, name, absPath, err := net.FindProcess(network, srcIP, uint16(srcPort), dstIP, uint16(dstPort))
|
||||
pid, name, absPath, err := net.FindProcess(src)
|
||||
if err != nil {
|
||||
if err != net.ErrNotLocal {
|
||||
errors.LogError(context.Background(), "Unables to find local process name: ", err)
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
package geodata
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type IPMatcher interface {
|
||||
type GeoIPMatcher interface {
|
||||
// TODO: (PERF) all net.IP -> netipx.Addr
|
||||
|
||||
// Invalid IP always return false.
|
||||
@@ -36,13 +33,13 @@ type IPMatcher interface {
|
||||
SetReverse(reverse bool)
|
||||
}
|
||||
|
||||
type IPSet struct {
|
||||
type GeoIPSet struct {
|
||||
ipv4, ipv6 *netipx.IPSet
|
||||
max4, max6 uint8
|
||||
}
|
||||
|
||||
type HeuristicIPMatcher struct {
|
||||
ipset *IPSet
|
||||
type HeuristicGeoIPMatcher struct {
|
||||
ipset *GeoIPSet
|
||||
reverse bool
|
||||
}
|
||||
|
||||
@@ -51,8 +48,8 @@ type ipBucket struct {
|
||||
ips []net.IP
|
||||
}
|
||||
|
||||
// Match implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) Match(ip net.IP) bool {
|
||||
// Match implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) Match(ip net.IP) bool {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -60,24 +57,18 @@ func (m *HeuristicIPMatcher) Match(ip net.IP) bool {
|
||||
return m.matchAddr(ipx)
|
||||
}
|
||||
|
||||
func (m *HeuristicIPMatcher) matchAddr(ipx netip.Addr) bool {
|
||||
func (m *HeuristicGeoIPMatcher) matchAddr(ipx netip.Addr) bool {
|
||||
if ipx.Is4() {
|
||||
if m.ipset.max4 == 0xff {
|
||||
return false
|
||||
}
|
||||
return m.ipset.ipv4.Contains(ipx) != m.reverse
|
||||
}
|
||||
if ipx.Is6() {
|
||||
if m.ipset.max6 == 0xff {
|
||||
return false
|
||||
}
|
||||
return m.ipset.ipv6.Contains(ipx) != m.reverse
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -126,8 +117,8 @@ func (m *HeuristicIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) Matches(ips []net.IP) bool {
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -214,8 +205,8 @@ func prefixKeyFromIP(ip net.IP) (key [9]byte, ok bool) {
|
||||
return key, false // illegal
|
||||
}
|
||||
|
||||
// FilterIPs implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return []net.IP{}, []net.IP{}
|
||||
@@ -304,22 +295,22 @@ func (m *HeuristicIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatche
|
||||
return
|
||||
}
|
||||
|
||||
// ToggleReverse implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) ToggleReverse() {
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) ToggleReverse() {
|
||||
m.reverse = !m.reverse
|
||||
}
|
||||
|
||||
// SetReverse implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) SetReverse(reverse bool) {
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) SetReverse(reverse bool) {
|
||||
m.reverse = reverse
|
||||
}
|
||||
|
||||
type GeneralMultiIPMatcher struct {
|
||||
matchers []IPMatcher
|
||||
type GeneralMultiGeoIPMatcher struct {
|
||||
matchers []GeoIPMatcher
|
||||
}
|
||||
|
||||
// Match implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) Match(ip net.IP) bool {
|
||||
// Match implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.Match(ip) {
|
||||
return true
|
||||
@@ -328,8 +319,8 @@ func (mm *GeneralMultiIPMatcher) Match(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.AnyMatch(ips) {
|
||||
return true
|
||||
@@ -338,8 +329,8 @@ func (mm *GeneralMultiIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) Matches(ips []net.IP) bool {
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.Matches(ips) {
|
||||
return true
|
||||
@@ -348,8 +339,8 @@ func (mm *GeneralMultiIPMatcher) Matches(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// FilterIPs implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
matched = make([]net.IP, 0, len(ips))
|
||||
unmatched = ips
|
||||
for _, m := range mm.matchers {
|
||||
@@ -365,26 +356,26 @@ func (mm *GeneralMultiIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unma
|
||||
return
|
||||
}
|
||||
|
||||
// ToggleReverse implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) ToggleReverse() {
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) ToggleReverse() {
|
||||
for _, m := range mm.matchers {
|
||||
m.ToggleReverse()
|
||||
}
|
||||
}
|
||||
|
||||
// SetReverse implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) SetReverse(reverse bool) {
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) SetReverse(reverse bool) {
|
||||
for _, m := range mm.matchers {
|
||||
m.SetReverse(reverse)
|
||||
}
|
||||
}
|
||||
|
||||
type HeuristicMultiIPMatcher struct {
|
||||
matchers []*HeuristicIPMatcher
|
||||
type HeuristicMultiGeoIPMatcher struct {
|
||||
matchers []*HeuristicGeoIPMatcher
|
||||
}
|
||||
|
||||
// Match implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) Match(ip net.IP) bool {
|
||||
// Match implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -398,8 +389,8 @@ func (mm *HeuristicMultiIPMatcher) Match(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -448,8 +439,8 @@ func (mm *HeuristicMultiIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) Matches(ips []net.IP) bool {
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -512,7 +503,7 @@ type ipViews struct {
|
||||
precise4, precise6 []netip.Addr
|
||||
}
|
||||
|
||||
func (v *ipViews) ensureForMatcher(m *HeuristicIPMatcher, ips []net.IP) bool {
|
||||
func (v *ipViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) bool {
|
||||
needHeur4 := m.ipset.max4 <= 24 && v.buckets4 == nil
|
||||
needHeur6 := m.ipset.max6 <= 64 && v.buckets6 == nil
|
||||
needPrec4 := m.ipset.max4 > 24 && v.precise4 == nil
|
||||
@@ -590,8 +581,8 @@ func (v *ipViews) ensureForMatcher(m *HeuristicIPMatcher, ips []net.IP) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// FilterIPs implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return []net.IP{}, []net.IP{}
|
||||
@@ -703,7 +694,7 @@ type ipBucketViews struct {
|
||||
precise4, precise6 map[netip.Addr]net.IP
|
||||
}
|
||||
|
||||
func (v *ipBucketViews) ensureForMatcher(m *HeuristicIPMatcher, ips []net.IP) {
|
||||
func (v *ipBucketViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) {
|
||||
needHeur4 := m.ipset.max4 <= 24 && v.buckets4 == nil
|
||||
needHeur6 := m.ipset.max6 <= 64 && v.buckets6 == nil
|
||||
needPrec4 := m.ipset.max4 > 24 && v.precise4 == nil
|
||||
@@ -791,124 +782,71 @@ func (v *ipBucketViews) ensureForMatcher(m *HeuristicIPMatcher, ips []net.IP) {
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleReverse implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) ToggleReverse() {
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) ToggleReverse() {
|
||||
for _, m := range mm.matchers {
|
||||
m.ToggleReverse()
|
||||
}
|
||||
}
|
||||
|
||||
// SetReverse implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) SetReverse(reverse bool) {
|
||||
for _, m := range mm.matchers {
|
||||
m.SetReverse(reverse)
|
||||
}
|
||||
}
|
||||
|
||||
type IPSetFactory struct {
|
||||
type GeoIPSetFactory struct {
|
||||
sync.Mutex
|
||||
shared *utils.WeakCacheMap[string, IPSet]
|
||||
shared map[string]*GeoIPSet // TODO: cleanup
|
||||
}
|
||||
|
||||
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
|
||||
key := buildGeoIPRulesKey(rules)
|
||||
var ipsetFactory = GeoIPSetFactory{shared: make(map[string]*GeoIPSet)}
|
||||
|
||||
func (f *GeoIPSetFactory) GetOrCreate(key string, cidrGroups [][]*CIDR) (*GeoIPSet, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if ipset, ok := f.shared.Load(key); ok {
|
||||
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
|
||||
if ipset := f.shared[key]; ipset != nil {
|
||||
return ipset, nil
|
||||
}
|
||||
errors.LogDebug(context.Background(), "geodata geoip matcher cache MISS ", key)
|
||||
|
||||
ipset, err := f.createFrom(func(add func(*CIDR)) error {
|
||||
for _, r := range rules {
|
||||
cidrs, err := loadIP(r.File, r.Code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, c := range cidrs {
|
||||
add(c)
|
||||
cidrs[i] = nil // peak mem
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
ipset, err := f.Create(cidrGroups...)
|
||||
if err == nil {
|
||||
f.shared.Store(key, ipset)
|
||||
f.shared[key] = ipset
|
||||
}
|
||||
return ipset, err
|
||||
}
|
||||
|
||||
func buildGeoIPRulesKey(rules []*GeoIPRule) string {
|
||||
rules = slices.Clone(rules)
|
||||
|
||||
sort.Slice(rules, func(i, j int) bool {
|
||||
ri, rj := rules[i], rules[j]
|
||||
if ri.File != rj.File {
|
||||
return ri.File < rj.File
|
||||
}
|
||||
return ri.Code < rj.Code
|
||||
})
|
||||
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(rules) * 20) // geoip.dat:xx,
|
||||
var last *GeoIPRule
|
||||
for i, r := range rules {
|
||||
if i == 0 || (r.File != last.File || r.Code != last.Code) {
|
||||
last = r
|
||||
sb.WriteString(r.File)
|
||||
sb.WriteString(":")
|
||||
sb.WriteString(r.Code)
|
||||
sb.WriteString(",")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (f *IPSetFactory) CreateFromCIDRs(cidrs []*CIDR) (*IPSet, error) {
|
||||
return f.createFrom(func(add func(*CIDR)) error {
|
||||
for _, c := range cidrs {
|
||||
add(c)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (f *IPSetFactory) createFrom(yield func(func(*CIDR)) error) (*IPSet, error) {
|
||||
func (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, error) {
|
||||
var ipv4Builder, ipv6Builder netipx.IPSetBuilder
|
||||
|
||||
err := yield(func(c *CIDR) {
|
||||
ipBytes := c.GetIp()
|
||||
prefixLen := int(c.GetPrefix())
|
||||
for _, cidrGroup := range cidrGroups {
|
||||
for i, cidrEntry := range cidrGroup {
|
||||
cidrGroup[i] = nil
|
||||
ipBytes := cidrEntry.GetIp()
|
||||
prefixLen := int(cidrEntry.GetPrefix())
|
||||
|
||||
addr, ok := netip.AddrFromSlice(ipBytes)
|
||||
if !ok {
|
||||
errors.LogError(context.Background(), "ignore invalid IP byte slice: ", ipBytes)
|
||||
return
|
||||
}
|
||||
addr, ok := netip.AddrFromSlice(ipBytes)
|
||||
if !ok {
|
||||
errors.LogError(context.Background(), "ignore invalid IP byte slice: ", ipBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := netip.PrefixFrom(addr, prefixLen)
|
||||
if !prefix.IsValid() {
|
||||
errors.LogError(context.Background(), "ignore created invalid prefix from addr ", addr, " and length ", prefixLen)
|
||||
return
|
||||
}
|
||||
prefix := netip.PrefixFrom(addr, prefixLen)
|
||||
if !prefix.IsValid() {
|
||||
errors.LogError(context.Background(), "ignore created invalid prefix from addr ", addr, " and length ", prefixLen)
|
||||
continue
|
||||
}
|
||||
|
||||
if addr.Is4() {
|
||||
ipv4Builder.AddPrefix(prefix)
|
||||
} else if addr.Is6() {
|
||||
ipv6Builder.AddPrefix(prefix)
|
||||
if addr.Is4() {
|
||||
ipv4Builder.AddPrefix(prefix)
|
||||
} else if addr.Is6() {
|
||||
ipv6Builder.AddPrefix(prefix)
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// peak mem
|
||||
runtime.GC()
|
||||
defer runtime.GC()
|
||||
|
||||
ipv4, err := ipv4Builder.IPSet()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build IPv4 set").Base(err)
|
||||
@@ -918,106 +856,107 @@ func (f *IPSetFactory) createFrom(yield func(func(*CIDR)) error) (*IPSet, error)
|
||||
return nil, errors.New("failed to build IPv6 set").Base(err)
|
||||
}
|
||||
|
||||
var has4, has6 bool
|
||||
var max4, max6 int
|
||||
|
||||
for _, p := range ipv4.Prefixes() {
|
||||
has4 = true
|
||||
if b := p.Bits(); b > max4 {
|
||||
max4 = b
|
||||
}
|
||||
}
|
||||
for _, p := range ipv6.Prefixes() {
|
||||
has6 = true
|
||||
if b := p.Bits(); b > max6 {
|
||||
max6 = b
|
||||
}
|
||||
}
|
||||
|
||||
if !has4 {
|
||||
if max4 == 0 {
|
||||
max4 = 0xff
|
||||
} else if max4 == 0 {
|
||||
max4 = 0xfe
|
||||
}
|
||||
if !has6 {
|
||||
if max6 == 0 {
|
||||
max6 = 0xff
|
||||
} else if max6 == 0 {
|
||||
max6 = 0xfe
|
||||
}
|
||||
|
||||
return &IPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
|
||||
return &GeoIPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
|
||||
}
|
||||
|
||||
func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error) {
|
||||
n := len(rules)
|
||||
posCustom := make([]*CIDR, 0, n)
|
||||
negCustom := make([]*CIDR, 0, n)
|
||||
posGeoip := make([]*GeoIPRule, 0, n)
|
||||
negGeoip := make([]*GeoIPRule, 0, n)
|
||||
func BuildOptimizedGeoIPMatcher(geoips ...*GeoIP) (GeoIPMatcher, error) {
|
||||
n := len(geoips)
|
||||
if n == 0 {
|
||||
return nil, errors.New("no geoip configs provided")
|
||||
}
|
||||
|
||||
for _, r := range rules {
|
||||
switch v := r.Value.(type) {
|
||||
case *IPRule_Custom:
|
||||
if !v.Custom.ReverseMatch {
|
||||
posCustom = append(posCustom, v.Custom.Cidr)
|
||||
} else {
|
||||
negCustom = append(negCustom, v.Custom.Cidr)
|
||||
var subs []*HeuristicGeoIPMatcher
|
||||
pos := make([]*GeoIP, 0, n)
|
||||
neg := make([]*GeoIP, 0, n/2)
|
||||
|
||||
for _, geoip := range geoips {
|
||||
if geoip == nil {
|
||||
return nil, errors.New("geoip entry is nil")
|
||||
}
|
||||
if geoip.CountryCode == "" {
|
||||
ipset, err := ipsetFactory.Create(geoip.Cidr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *IPRule_Geoip:
|
||||
if !v.Geoip.ReverseMatch {
|
||||
posGeoip = append(posGeoip, v.Geoip)
|
||||
} else {
|
||||
negGeoip = append(negGeoip, v.Geoip)
|
||||
subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: geoip.ReverseMatch})
|
||||
continue
|
||||
}
|
||||
if !geoip.ReverseMatch {
|
||||
pos = append(pos, geoip)
|
||||
} else {
|
||||
neg = append(neg, geoip)
|
||||
}
|
||||
}
|
||||
|
||||
buildIPSet := func(mergeables []*GeoIP) (*GeoIPSet, error) {
|
||||
n := len(mergeables)
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sort.Slice(mergeables, func(i, j int) bool {
|
||||
gi, gj := mergeables[i], mergeables[j]
|
||||
return gi.CountryCode < gj.CountryCode
|
||||
})
|
||||
|
||||
var sb strings.Builder
|
||||
sb.Grow(n * 3) // xx,
|
||||
cidrGroups := make([][]*CIDR, 0, n)
|
||||
var last *GeoIP
|
||||
for i, geoip := range mergeables {
|
||||
if i == 0 || (geoip.CountryCode != last.CountryCode) {
|
||||
last = geoip
|
||||
sb.WriteString(geoip.CountryCode)
|
||||
sb.WriteString(",")
|
||||
cidrGroups = append(cidrGroups, geoip.Cidr)
|
||||
}
|
||||
default:
|
||||
panic("unknown ip rule type")
|
||||
}
|
||||
|
||||
return ipsetFactory.GetOrCreate(sb.String(), cidrGroups)
|
||||
}
|
||||
|
||||
subs := make([]*HeuristicIPMatcher, 0, 4)
|
||||
|
||||
if len(posCustom) > 0 {
|
||||
ipset, err := f.CreateFromCIDRs(posCustom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: false})
|
||||
ipset, err := buildIPSet(pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ipset != nil {
|
||||
subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: false})
|
||||
}
|
||||
|
||||
if len(negCustom) > 0 {
|
||||
ipset, err := f.CreateFromCIDRs(negCustom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: true})
|
||||
ipset, err = buildIPSet(neg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(posGeoip) > 0 {
|
||||
ipset, err := f.GetOrCreateFromGeoIPRules(posGeoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: false})
|
||||
}
|
||||
|
||||
if len(negGeoip) > 0 {
|
||||
ipset, err := f.GetOrCreateFromGeoIPRules(negGeoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicIPMatcher{ipset: ipset, reverse: true})
|
||||
if ipset != nil {
|
||||
subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: true})
|
||||
}
|
||||
|
||||
switch len(subs) {
|
||||
case 0:
|
||||
return nil, errors.New("no valid ip matcher")
|
||||
return nil, errors.New("no valid geoip matcher")
|
||||
case 1:
|
||||
return subs[0], nil
|
||||
default:
|
||||
return &HeuristicMultiIPMatcher{matchers: subs}, nil
|
||||
return &HeuristicMultiGeoIPMatcher{matchers: subs}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func newIPSetFactory() *IPSetFactory {
|
||||
return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func getAssetPath(file string) (string, error) {
|
||||
path := platform.GetAssetLocation(file)
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
path := filepath.Join("..", "..", "resources", file)
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("can't find %s in standard asset locations or {project_root}/resources", file)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't stat %s: %v", path, err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't stat %s: %v", path, err)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher(t *testing.T) {
|
||||
cidrList := []*router.CIDR{
|
||||
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{100, 64, 0, 0}, Prefix: 10},
|
||||
{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{169, 254, 0, 0}, Prefix: 16},
|
||||
{Ip: []byte{172, 16, 0, 0}, Prefix: 12},
|
||||
{Ip: []byte{192, 0, 0, 0}, Prefix: 24},
|
||||
{Ip: []byte{192, 0, 2, 0}, Prefix: 24},
|
||||
{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
{Ip: []byte{192, 18, 0, 0}, Prefix: 15},
|
||||
{Ip: []byte{198, 51, 100, 0}, Prefix: 24},
|
||||
{Ip: []byte{203, 0, 113, 0}, Prefix: 24},
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
||||
}
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: cidrList,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "192.168.1.1",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.1.0",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "0.1.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "1.0.0.1",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.7",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ip := net.ParseAddress(testCase.Input).IP()
|
||||
actual := matcher.Match(ip)
|
||||
if actual != testCase.Output {
|
||||
t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcherRegression(t *testing.T) {
|
||||
cidrList := []*router.CIDR{
|
||||
{Ip: []byte{98, 108, 20, 0}, Prefix: 22},
|
||||
{Ip: []byte{98, 108, 20, 0}, Prefix: 23},
|
||||
}
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: cidrList,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "98.108.22.11",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "98.108.25.0",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ip := net.ParseAddress(testCase.Input).IP()
|
||||
actual := matcher.Match(ip)
|
||||
if actual != testCase.Output {
|
||||
t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPReverseMatcher(t *testing.T) {
|
||||
cidrList := []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
||||
}
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: cidrList,
|
||||
})
|
||||
common.Must(err)
|
||||
matcher.SetReverse(true) // Reverse match
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ip := net.ParseAddress(testCase.Input).IP()
|
||||
actual := matcher.Match(ip)
|
||||
if actual != testCase.Output {
|
||||
t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher4CN(t *testing.T) {
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
if matcher.Match([]byte{8, 8, 8, 8}) {
|
||||
t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher6US(t *testing.T) {
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
|
||||
t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not")
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
path, err := getAssetPath("geoip.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
geoipBytes, err := filesystem.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var geoipList router.GeoIPList
|
||||
if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, geoip := range geoipList.Entry {
|
||||
if geoip.CountryCode == country {
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
}
|
||||
|
||||
panic("country not found: " + country)
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match([]byte{8, 8, 8, 8})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
)
|
||||
|
||||
func TestDomainMatcherSerialization(t *testing.T) {
|
||||
|
||||
domains := []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.com"},
|
||||
{Type: router.Domain_Domain, Value: "v2ray.com"},
|
||||
{Type: router.Domain_Full, Value: "full.example.com"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := router.SerializeDomainMatcher(domains, &buf); err != nil {
|
||||
t.Fatalf("Serialize failed: %v", err)
|
||||
}
|
||||
|
||||
matcher, err := router.NewDomainMatcherFromBuffer(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Deserialize failed: %v", err)
|
||||
}
|
||||
|
||||
dMatcher := &router.DomainMatcher{
|
||||
Matchers: matcher,
|
||||
}
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Match bool
|
||||
}{
|
||||
{"google.com", true},
|
||||
{"maps.google.com", true},
|
||||
{"v2ray.com", true},
|
||||
{"full.example.com", true},
|
||||
|
||||
{"example.com", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if res := dMatcher.ApplyDomain(tc.Input); res != tc.Match {
|
||||
t.Errorf("Match(%s) = %v, want %v", tc.Input, res, tc.Match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoSiteSerialization(t *testing.T) {
|
||||
sites := []*router.GeoSite{
|
||||
{
|
||||
CountryCode: "CN",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "baidu.cn"},
|
||||
{Type: router.Domain_Domain, Value: "qq.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "US",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.com"},
|
||||
{Type: router.Domain_Domain, Value: "facebook.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := router.SerializeGeoSiteList(sites, nil, nil, &buf); err != nil {
|
||||
t.Fatalf("SerializeGeoSiteList failed: %v", err)
|
||||
}
|
||||
|
||||
tmp := t.TempDir()
|
||||
path := filepath.Join(tmp, "matcher.cache")
|
||||
|
||||
f, err := os.Create(path)
|
||||
require.NoError(t, err)
|
||||
_, err = f.Write(buf.Bytes())
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
f, err = os.Open(path)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
require.NoError(t, err)
|
||||
data, _ := filesystem.ReadFile(path)
|
||||
|
||||
// cn
|
||||
gp, err := router.LoadGeoSiteMatcher(bytes.NewReader(data), "CN")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadGeoSiteMatcher(CN) failed: %v", err)
|
||||
}
|
||||
|
||||
cnMatcher := &router.DomainMatcher{
|
||||
Matchers: gp,
|
||||
}
|
||||
|
||||
if !cnMatcher.ApplyDomain("baidu.cn") {
|
||||
t.Error("CN matcher should match baidu.cn")
|
||||
}
|
||||
if cnMatcher.ApplyDomain("google.com") {
|
||||
t.Error("CN matcher should NOT match google.com")
|
||||
}
|
||||
|
||||
// us
|
||||
gp, err = router.LoadGeoSiteMatcher(bytes.NewReader(data), "US")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadGeoSiteMatcher(US) failed: %v", err)
|
||||
}
|
||||
|
||||
usMatcher := &router.DomainMatcher{
|
||||
Matchers: gp,
|
||||
}
|
||||
if !usMatcher.ApplyDomain("google.com") {
|
||||
t.Error("US matcher should match google.com")
|
||||
}
|
||||
if usMatcher.ApplyDomain("baidu.cn") {
|
||||
t.Error("US matcher should NOT match baidu.cn")
|
||||
}
|
||||
|
||||
// unknown
|
||||
_, err = router.LoadGeoSiteMatcher(bytes.NewReader(data), "unknown")
|
||||
if err == nil {
|
||||
t.Error("LoadGeoSiteMatcher(unknown) should fail")
|
||||
}
|
||||
}
|
||||
func TestGeoSiteSerializationWithDeps(t *testing.T) {
|
||||
sites := []*router.GeoSite{
|
||||
{
|
||||
CountryCode: "geosite:cn",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "baidu.cn"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "geosite:google@cn",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.cn"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "rule-1",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
deps := map[string][]string{
|
||||
"rule-1": {"geosite:cn", "geosite:google@cn"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := router.SerializeGeoSiteList(sites, deps, nil, &buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
matcher, err := router.LoadGeoSiteMatcher(bytes.NewReader(buf.Bytes()), "rule-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, matcher.Match("google.com") != nil)
|
||||
require.True(t, matcher.Match("baidu.cn") != nil)
|
||||
require.True(t, matcher.Match("google.cn") != nil)
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/protocol/http"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func withBackground() routing.Context {
|
||||
@@ -44,15 +45,18 @@ func TestRoutingRule(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
Domain: []*geodata.DomainRule{
|
||||
Domain: []*Domain{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Substr, Value: "example.com"}},
|
||||
Value: "example.com",
|
||||
Type: Domain_Plain,
|
||||
},
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
Value: "google.com",
|
||||
Type: Domain_Domain,
|
||||
},
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^facebook\\.com$"}},
|
||||
Value: "^facebook\\.com$",
|
||||
Type: Domain_Regex,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -89,25 +93,20 @@ func TestRoutingRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
Ip: []*geodata.IPRule{
|
||||
Geoip: []*GeoIP{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), Prefix: 128},
|
||||
{
|
||||
Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
|
||||
Prefix: 128,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -134,11 +133,12 @@ func TestRoutingRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
SourceIp: []*geodata.IPRule{
|
||||
SourceGeoip: []*GeoIP{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -300,12 +300,35 @@ func TestRoutingRule(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoSite(country string) ([]*Domain, error) {
|
||||
path, err := getAssetPath("geosite.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
geositeBytes, err := filesystem.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var geositeList GeoSiteList
|
||||
if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, site := range geositeList.Entry {
|
||||
if site.CountryCode == country {
|
||||
return site.Domain, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("country not found: " + country)
|
||||
}
|
||||
|
||||
func TestChinaSites(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||
domains, err := loadGeoSite("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := NewDomainMatcher(rules)
|
||||
acMatcher, err := NewMphMatcherGroup(domains)
|
||||
common.Must(err)
|
||||
|
||||
type TestCase struct {
|
||||
@@ -336,19 +359,18 @@ func TestChinaSites(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
r := matcher.ApplyDomain(testCase.Domain)
|
||||
r := acMatcher.ApplyDomain(testCase.Domain)
|
||||
if r != testCase.Output {
|
||||
t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
||||
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||
domains, err := loadGeoSite("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := NewDomainMatcher(rules)
|
||||
matcher, err := NewMphMatcherGroup(domains)
|
||||
common.Must(err)
|
||||
|
||||
type TestCase struct {
|
||||
@@ -387,11 +409,45 @@ func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseIPRules([]string{"geoip:cn", "geoip:jp", "geoip:ca", "geoip:us"})
|
||||
common.Must(err)
|
||||
var geoips []*GeoIP
|
||||
|
||||
matcher, err := NewIPMatcher(rules, MatcherAsType_Target)
|
||||
{
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "CN",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("JP")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "JP",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("CA")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "CA",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "US",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
matcher, err := NewIPMatcher(geoips, MatcherAsType_Target)
|
||||
common.Must(err)
|
||||
|
||||
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
|
||||
|
||||
+53
-10
@@ -3,9 +3,12 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
@@ -73,37 +76,60 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||
conds.Add(&AttributeMatcher{configuredKeys})
|
||||
}
|
||||
|
||||
if len(rr.Ip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.Ip, MatcherAsType_Target)
|
||||
if len(rr.Geoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.Geoip, MatcherAsType_Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.Geoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.SourceIp) > 0 {
|
||||
cond, err := NewIPMatcher(rr.SourceIp, MatcherAsType_Source)
|
||||
if len(rr.SourceGeoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.SourceGeoip, MatcherAsType_Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.SourceGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.LocalIp) > 0 {
|
||||
cond, err := NewIPMatcher(rr.LocalIp, MatcherAsType_Local)
|
||||
if len(rr.LocalGeoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.LocalGeoip, MatcherAsType_Local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
errors.LogWarning(context.Background(), "Due to some limitations, in UDP connections, localIP is always equal to listen interface IP, so \"localIP\" rule condition does not work properly on UDP inbound connections that listen on all interfaces")
|
||||
rr.LocalGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.Domain) > 0 {
|
||||
cond, err := NewDomainMatcher(rr.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var matcher *DomainMatcher
|
||||
var err error
|
||||
// Check if domain matcher cache is provided via environment
|
||||
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||
|
||||
if domainMatcherPath != "" {
|
||||
matcher, err = GetDomainMatcherWithRuleTag(domainMatcherPath, rr.RuleTag)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build domain condition from cached MphDomainMatcher").Base(err)
|
||||
}
|
||||
errors.LogDebug(context.Background(), "MphDomainMatcher loaded from cache for ", rr.RuleTag, " rule tag)")
|
||||
|
||||
} else {
|
||||
matcher, err = NewMphMatcherGroup(rr.Domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
|
||||
}
|
||||
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)")
|
||||
}
|
||||
conds.Add(cond)
|
||||
conds.Add(matcher)
|
||||
rr.Domain = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.Process) > 0 {
|
||||
@@ -163,3 +189,20 @@ func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatch
|
||||
return nil, errors.New("unrecognized balancer type")
|
||||
}
|
||||
}
|
||||
|
||||
func GetDomainMatcherWithRuleTag(domainMatcherPath string, ruleTag string) (*DomainMatcher, error) {
|
||||
f, err := filesystem.NewFileReader(domainMatcherPath)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file: ", domainMatcherPath).Base(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
g, err := LoadGeoSiteMatcher(f, ruleTag)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file:", domainMatcherPath).Base(err)
|
||||
}
|
||||
return &DomainMatcher{
|
||||
Matchers: g,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
+609
-95
@@ -7,7 +7,6 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
serial "github.com/xtls/xray-core/common/serial"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
@@ -24,6 +23,63 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// Type of domain value.
|
||||
type Domain_Type int32
|
||||
|
||||
const (
|
||||
// The value is used as is.
|
||||
Domain_Plain Domain_Type = 0
|
||||
// The value is used as a regular expression.
|
||||
Domain_Regex Domain_Type = 1
|
||||
// The value is a root domain.
|
||||
Domain_Domain Domain_Type = 2
|
||||
// The value is a domain.
|
||||
Domain_Full Domain_Type = 3
|
||||
)
|
||||
|
||||
// Enum value maps for Domain_Type.
|
||||
var (
|
||||
Domain_Type_name = map[int32]string{
|
||||
0: "Plain",
|
||||
1: "Regex",
|
||||
2: "Domain",
|
||||
3: "Full",
|
||||
}
|
||||
Domain_Type_value = map[string]int32{
|
||||
"Plain": 0,
|
||||
"Regex": 1,
|
||||
"Domain": 2,
|
||||
"Full": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Domain_Type) Enum() *Domain_Type {
|
||||
p := new(Domain_Type)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Domain_Type) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Domain_Type) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_app_router_config_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Domain_Type) Type() protoreflect.EnumType {
|
||||
return &file_app_router_config_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Domain_Type) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Domain_Type.Descriptor instead.
|
||||
func (Domain_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type Config_DomainStrategy int32
|
||||
|
||||
const (
|
||||
@@ -60,11 +116,11 @@ func (x Config_DomainStrategy) String() string {
|
||||
}
|
||||
|
||||
func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_app_router_config_proto_enumTypes[0].Descriptor()
|
||||
return file_app_router_config_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (Config_DomainStrategy) Type() protoreflect.EnumType {
|
||||
return &file_app_router_config_proto_enumTypes[0]
|
||||
return &file_app_router_config_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
|
||||
@@ -73,7 +129,326 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use Config_DomainStrategy.Descriptor instead.
|
||||
func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{5, 0}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{11, 0}
|
||||
}
|
||||
|
||||
// Domain for routing decision.
|
||||
type Domain struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Domain matching type.
|
||||
Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.router.Domain_Type" json:"type,omitempty"`
|
||||
// Domain value.
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
// Attributes of this domain. May be used for filtering.
|
||||
Attribute []*Domain_Attribute `protobuf:"bytes,3,rep,name=attribute,proto3" json:"attribute,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Domain) Reset() {
|
||||
*x = Domain{}
|
||||
mi := &file_app_router_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Domain) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Domain) ProtoMessage() {}
|
||||
|
||||
func (x *Domain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
|
||||
func (*Domain) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Domain) GetType() Domain_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return Domain_Plain
|
||||
}
|
||||
|
||||
func (x *Domain) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Domain) GetAttribute() []*Domain_Attribute {
|
||||
if x != nil {
|
||||
return x.Attribute
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IP for routing decision, in CIDR form.
|
||||
type CIDR struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// IP address, should be either 4 or 16 bytes.
|
||||
Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
// Number of leading ones in the network mask.
|
||||
Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CIDR) Reset() {
|
||||
*x = CIDR{}
|
||||
mi := &file_app_router_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *CIDR) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CIDR) ProtoMessage() {}
|
||||
|
||||
func (x *CIDR) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CIDR.ProtoReflect.Descriptor instead.
|
||||
func (*CIDR) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *CIDR) GetIp() []byte {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CIDR) GetPrefix() uint32 {
|
||||
if x != nil {
|
||||
return x.Prefix
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type GeoIP struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
|
||||
Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"`
|
||||
ReverseMatch bool `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoIP) Reset() {
|
||||
*x = GeoIP{}
|
||||
mi := &file_app_router_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoIP) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoIP) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIP) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIP) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetCountryCode() string {
|
||||
if x != nil {
|
||||
return x.CountryCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetCidr() []*CIDR {
|
||||
if x != nil {
|
||||
return x.Cidr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetReverseMatch() bool {
|
||||
if x != nil {
|
||||
return x.ReverseMatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type GeoIPList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoIPList) Reset() {
|
||||
*x = GeoIPList{}
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoIPList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoIPList) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIPList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIPList) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *GeoIPList) GetEntry() []*GeoIP {
|
||||
if x != nil {
|
||||
return x.Entry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GeoSite struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
|
||||
Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoSite) Reset() {
|
||||
*x = GeoSite{}
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoSite) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoSite) ProtoMessage() {}
|
||||
|
||||
func (x *GeoSite) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoSite.ProtoReflect.Descriptor instead.
|
||||
func (*GeoSite) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *GeoSite) GetCountryCode() string {
|
||||
if x != nil {
|
||||
return x.CountryCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoSite) GetDomain() []*Domain {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GeoSiteList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Entry []*GeoSite `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoSiteList) Reset() {
|
||||
*x = GeoSiteList{}
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoSiteList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoSiteList) ProtoMessage() {}
|
||||
|
||||
func (x *GeoSiteList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead.
|
||||
func (*GeoSiteList) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *GeoSiteList) GetEntry() []*GeoSite {
|
||||
if x != nil {
|
||||
return x.Entry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RoutingRule struct {
|
||||
@@ -85,35 +460,37 @@ type RoutingRule struct {
|
||||
TargetTag isRoutingRule_TargetTag `protobuf_oneof:"target_tag"`
|
||||
RuleTag string `protobuf:"bytes,19,opt,name=rule_tag,json=ruleTag,proto3" json:"rule_tag,omitempty"`
|
||||
// List of domains for target domain matching.
|
||||
Domain []*geodata.DomainRule `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
// List of IPs for target IP address matching.
|
||||
Ip []*geodata.IPRule `protobuf:"bytes,10,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||
// List of ports for target port matching.
|
||||
Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
// List of GeoIPs for target IP address matching. If this entry exists, the
|
||||
// cidr above will have no effect. GeoIP fields with the same country code are
|
||||
// supposed to contain exactly same content. They will be merged during
|
||||
// runtime. For customized GeoIPs, please leave country code empty.
|
||||
Geoip []*GeoIP `protobuf:"bytes,10,rep,name=geoip,proto3" json:"geoip,omitempty"`
|
||||
// List of ports.
|
||||
PortList *net.PortList `protobuf:"bytes,14,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
|
||||
// List of networks for matching.
|
||||
Networks []net.Network `protobuf:"varint,13,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,omitempty"`
|
||||
// List of IPs for source IP address matching.
|
||||
SourceIp []*geodata.IPRule `protobuf:"bytes,11,rep,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"`
|
||||
// List of GeoIPs for source IP address matching. If this entry exists, the
|
||||
// source_cidr above will have no effect.
|
||||
SourceGeoip []*GeoIP `protobuf:"bytes,11,rep,name=source_geoip,json=sourceGeoip,proto3" json:"source_geoip,omitempty"`
|
||||
// List of ports for source port matching.
|
||||
SourcePortList *net.PortList `protobuf:"bytes,16,opt,name=source_port_list,json=sourcePortList,proto3" json:"source_port_list,omitempty"`
|
||||
UserEmail []string `protobuf:"bytes,7,rep,name=user_email,json=userEmail,proto3" json:"user_email,omitempty"`
|
||||
InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
|
||||
Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"`
|
||||
Attributes map[string]string `protobuf:"bytes,15,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
// List of IPs for local IP address matching.
|
||||
LocalIp []*geodata.IPRule `protobuf:"bytes,17,rep,name=local_ip,json=localIp,proto3" json:"local_ip,omitempty"`
|
||||
// List of ports for local port matching.
|
||||
LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_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"`
|
||||
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
||||
LocalGeoip []*GeoIP `protobuf:"bytes,17,rep,name=local_geoip,json=localGeoip,proto3" json:"local_geoip,omitempty"`
|
||||
LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_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"`
|
||||
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RoutingRule) Reset() {
|
||||
*x = RoutingRule{}
|
||||
mi := &file_app_router_config_proto_msgTypes[0]
|
||||
mi := &file_app_router_config_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -125,7 +502,7 @@ func (x *RoutingRule) String() string {
|
||||
func (*RoutingRule) ProtoMessage() {}
|
||||
|
||||
func (x *RoutingRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[0]
|
||||
mi := &file_app_router_config_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -138,7 +515,7 @@ func (x *RoutingRule) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RoutingRule.ProtoReflect.Descriptor instead.
|
||||
func (*RoutingRule) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{0}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetTargetTag() isRoutingRule_TargetTag {
|
||||
@@ -173,16 +550,16 @@ func (x *RoutingRule) GetRuleTag() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetDomain() []*geodata.DomainRule {
|
||||
func (x *RoutingRule) GetDomain() []*Domain {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetIp() []*geodata.IPRule {
|
||||
func (x *RoutingRule) GetGeoip() []*GeoIP {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
return x.Geoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -201,9 +578,9 @@ func (x *RoutingRule) GetNetworks() []net.Network {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetSourceIp() []*geodata.IPRule {
|
||||
func (x *RoutingRule) GetSourceGeoip() []*GeoIP {
|
||||
if x != nil {
|
||||
return x.SourceIp
|
||||
return x.SourceGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -243,9 +620,9 @@ func (x *RoutingRule) GetAttributes() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetLocalIp() []*geodata.IPRule {
|
||||
func (x *RoutingRule) GetLocalGeoip() []*GeoIP {
|
||||
if x != nil {
|
||||
return x.LocalIp
|
||||
return x.LocalGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -307,7 +684,7 @@ type WebhookConfig struct {
|
||||
|
||||
func (x *WebhookConfig) Reset() {
|
||||
*x = WebhookConfig{}
|
||||
mi := &file_app_router_config_proto_msgTypes[1]
|
||||
mi := &file_app_router_config_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -319,7 +696,7 @@ func (x *WebhookConfig) String() string {
|
||||
func (*WebhookConfig) ProtoMessage() {}
|
||||
|
||||
func (x *WebhookConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[1]
|
||||
mi := &file_app_router_config_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -332,7 +709,7 @@ func (x *WebhookConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use WebhookConfig.ProtoReflect.Descriptor instead.
|
||||
func (*WebhookConfig) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{1}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *WebhookConfig) GetUrl() string {
|
||||
@@ -369,7 +746,7 @@ type BalancingRule struct {
|
||||
|
||||
func (x *BalancingRule) Reset() {
|
||||
*x = BalancingRule{}
|
||||
mi := &file_app_router_config_proto_msgTypes[2]
|
||||
mi := &file_app_router_config_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -381,7 +758,7 @@ func (x *BalancingRule) String() string {
|
||||
func (*BalancingRule) ProtoMessage() {}
|
||||
|
||||
func (x *BalancingRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[2]
|
||||
mi := &file_app_router_config_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -394,7 +771,7 @@ func (x *BalancingRule) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use BalancingRule.ProtoReflect.Descriptor instead.
|
||||
func (*BalancingRule) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{2}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *BalancingRule) GetTag() string {
|
||||
@@ -443,7 +820,7 @@ type StrategyWeight struct {
|
||||
|
||||
func (x *StrategyWeight) Reset() {
|
||||
*x = StrategyWeight{}
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
mi := &file_app_router_config_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -455,7 +832,7 @@ func (x *StrategyWeight) String() string {
|
||||
func (*StrategyWeight) ProtoMessage() {}
|
||||
|
||||
func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
mi := &file_app_router_config_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -468,7 +845,7 @@ func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StrategyWeight.ProtoReflect.Descriptor instead.
|
||||
func (*StrategyWeight) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{3}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *StrategyWeight) GetRegexp() bool {
|
||||
@@ -510,7 +887,7 @@ type StrategyLeastLoadConfig struct {
|
||||
|
||||
func (x *StrategyLeastLoadConfig) Reset() {
|
||||
*x = StrategyLeastLoadConfig{}
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
mi := &file_app_router_config_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -522,7 +899,7 @@ func (x *StrategyLeastLoadConfig) String() string {
|
||||
func (*StrategyLeastLoadConfig) ProtoMessage() {}
|
||||
|
||||
func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
mi := &file_app_router_config_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -535,7 +912,7 @@ func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StrategyLeastLoadConfig.ProtoReflect.Descriptor instead.
|
||||
func (*StrategyLeastLoadConfig) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{4}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight {
|
||||
@@ -584,7 +961,7 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
mi := &file_app_router_config_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -596,7 +973,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
mi := &file_app_router_config_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -609,7 +986,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{5}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *Config) GetDomainStrategy() Config_DomainStrategy {
|
||||
@@ -633,21 +1010,141 @@ func (x *Config) GetBalancingRule() []*BalancingRule {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Domain_Attribute struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
// Types that are valid to be assigned to TypedValue:
|
||||
//
|
||||
// *Domain_Attribute_BoolValue
|
||||
// *Domain_Attribute_IntValue
|
||||
TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) Reset() {
|
||||
*x = Domain_Attribute{}
|
||||
mi := &file_app_router_config_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Domain_Attribute) ProtoMessage() {}
|
||||
|
||||
func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[12]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead.
|
||||
func (*Domain_Attribute) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue {
|
||||
if x != nil {
|
||||
return x.TypedValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetBoolValue() bool {
|
||||
if x != nil {
|
||||
if x, ok := x.TypedValue.(*Domain_Attribute_BoolValue); ok {
|
||||
return x.BoolValue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetIntValue() int64 {
|
||||
if x != nil {
|
||||
if x, ok := x.TypedValue.(*Domain_Attribute_IntValue); ok {
|
||||
return x.IntValue
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type isDomain_Attribute_TypedValue interface {
|
||||
isDomain_Attribute_TypedValue()
|
||||
}
|
||||
|
||||
type Domain_Attribute_BoolValue struct {
|
||||
BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Domain_Attribute_IntValue struct {
|
||||
IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {}
|
||||
|
||||
func (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {}
|
||||
|
||||
var File_app_router_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_app_router_config_proto_rawDesc = "" +
|
||||
"\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" +
|
||||
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\"\xb3\x02\n" +
|
||||
"\x06Domain\x120\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1c.xray.app.router.Domain.TypeR\x04type\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value\x12?\n" +
|
||||
"\tattribute\x18\x03 \x03(\v2!.xray.app.router.Domain.AttributeR\tattribute\x1al\n" +
|
||||
"\tAttribute\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x1f\n" +
|
||||
"\n" +
|
||||
"bool_value\x18\x02 \x01(\bH\x00R\tboolValue\x12\x1d\n" +
|
||||
"\tint_value\x18\x03 \x01(\x03H\x00R\bintValueB\r\n" +
|
||||
"\vtyped_value\"2\n" +
|
||||
"\x04Type\x12\t\n" +
|
||||
"\x05Plain\x10\x00\x12\t\n" +
|
||||
"\x05Regex\x10\x01\x12\n" +
|
||||
"\n" +
|
||||
"\x06Domain\x10\x02\x12\b\n" +
|
||||
"\x04Full\x10\x03\".\n" +
|
||||
"\x04CIDR\x12\x0e\n" +
|
||||
"\x02ip\x18\x01 \x01(\fR\x02ip\x12\x16\n" +
|
||||
"\x06prefix\x18\x02 \x01(\rR\x06prefix\"z\n" +
|
||||
"\x05GeoIP\x12!\n" +
|
||||
"\fcountry_code\x18\x01 \x01(\tR\vcountryCode\x12)\n" +
|
||||
"\x04cidr\x18\x02 \x03(\v2\x15.xray.app.router.CIDRR\x04cidr\x12#\n" +
|
||||
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"9\n" +
|
||||
"\tGeoIPList\x12,\n" +
|
||||
"\x05entry\x18\x01 \x03(\v2\x16.xray.app.router.GeoIPR\x05entry\"]\n" +
|
||||
"\aGeoSite\x12!\n" +
|
||||
"\fcountry_code\x18\x01 \x01(\tR\vcountryCode\x12/\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x17.xray.app.router.DomainR\x06domain\"=\n" +
|
||||
"\vGeoSiteList\x12.\n" +
|
||||
"\x05entry\x18\x01 \x03(\v2\x18.xray.app.router.GeoSiteR\x05entry\"\xbc\a\n" +
|
||||
"\vRoutingRule\x12\x12\n" +
|
||||
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
|
||||
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
|
||||
"\brule_tag\x18\x13 \x01(\tR\aruleTag\x127\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12+\n" +
|
||||
"\x02ip\x18\n" +
|
||||
" \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x126\n" +
|
||||
"\brule_tag\x18\x13 \x01(\tR\aruleTag\x12/\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x17.xray.app.router.DomainR\x06domain\x12,\n" +
|
||||
"\x05geoip\x18\n" +
|
||||
" \x03(\v2\x16.xray.app.router.GeoIPR\x05geoip\x126\n" +
|
||||
"\tport_list\x18\x0e \x01(\v2\x19.xray.common.net.PortListR\bportList\x124\n" +
|
||||
"\bnetworks\x18\r \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x128\n" +
|
||||
"\tsource_ip\x18\v \x03(\v2\x1b.xray.common.geodata.IPRuleR\bsourceIp\x12C\n" +
|
||||
"\bnetworks\x18\r \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x129\n" +
|
||||
"\fsource_geoip\x18\v \x03(\v2\x16.xray.app.router.GeoIPR\vsourceGeoip\x12C\n" +
|
||||
"\x10source_port_list\x18\x10 \x01(\v2\x19.xray.common.net.PortListR\x0esourcePortList\x12\x1d\n" +
|
||||
"\n" +
|
||||
"user_email\x18\a \x03(\tR\tuserEmail\x12\x1f\n" +
|
||||
@@ -656,8 +1153,9 @@ const file_app_router_config_proto_rawDesc = "" +
|
||||
"\bprotocol\x18\t \x03(\tR\bprotocol\x12L\n" +
|
||||
"\n" +
|
||||
"attributes\x18\x0f \x03(\v2,.xray.app.router.RoutingRule.AttributesEntryR\n" +
|
||||
"attributes\x126\n" +
|
||||
"\blocal_ip\x18\x11 \x03(\v2\x1b.xray.common.geodata.IPRuleR\alocalIp\x12A\n" +
|
||||
"attributes\x127\n" +
|
||||
"\vlocal_geoip\x18\x11 \x03(\v2\x16.xray.app.router.GeoIPR\n" +
|
||||
"localGeoip\x12A\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" +
|
||||
"\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
|
||||
@@ -689,16 +1187,16 @@ const file_app_router_config_proto_rawDesc = "" +
|
||||
"\tbaselines\x18\x03 \x03(\x03R\tbaselines\x12\x1a\n" +
|
||||
"\bexpected\x18\x04 \x01(\x05R\bexpected\x12\x16\n" +
|
||||
"\x06maxRTT\x18\x05 \x01(\x03R\x06maxRTT\x12\x1c\n" +
|
||||
"\ttolerance\x18\x06 \x01(\x02R\ttolerance\"\x96\x02\n" +
|
||||
"\ttolerance\x18\x06 \x01(\x02R\ttolerance\"\x90\x02\n" +
|
||||
"\x06Config\x12O\n" +
|
||||
"\x0fdomain_strategy\x18\x01 \x01(\x0e2&.xray.app.router.Config.DomainStrategyR\x0edomainStrategy\x120\n" +
|
||||
"\x04rule\x18\x02 \x03(\v2\x1c.xray.app.router.RoutingRuleR\x04rule\x12E\n" +
|
||||
"\x0ebalancing_rule\x18\x03 \x03(\v2\x1e.xray.app.router.BalancingRuleR\rbalancingRule\"B\n" +
|
||||
"\x0ebalancing_rule\x18\x03 \x03(\v2\x1e.xray.app.router.BalancingRuleR\rbalancingRule\"<\n" +
|
||||
"\x0eDomainStrategy\x12\b\n" +
|
||||
"\x04AsIs\x10\x00\x12\x10\n" +
|
||||
"\fIpIfNonMatch\x10\x02\x12\x0e\n" +
|
||||
"\n" +
|
||||
"IpOnDemand\x10\x03\"\x04\b\x01\x10\x01BO\n" +
|
||||
"IpOnDemand\x10\x03BO\n" +
|
||||
"\x13com.xray.app.routerP\x01Z$github.com/xtls/xray-core/app/router\xaa\x02\x0fXray.App.Routerb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -713,47 +1211,59 @@ func file_app_router_config_proto_rawDescGZIP() []byte {
|
||||
return file_app_router_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
|
||||
var file_app_router_config_proto_goTypes = []any{
|
||||
(Config_DomainStrategy)(0), // 0: xray.app.router.Config.DomainStrategy
|
||||
(*RoutingRule)(nil), // 1: xray.app.router.RoutingRule
|
||||
(*WebhookConfig)(nil), // 2: xray.app.router.WebhookConfig
|
||||
(*BalancingRule)(nil), // 3: xray.app.router.BalancingRule
|
||||
(*StrategyWeight)(nil), // 4: xray.app.router.StrategyWeight
|
||||
(*StrategyLeastLoadConfig)(nil), // 5: xray.app.router.StrategyLeastLoadConfig
|
||||
(*Config)(nil), // 6: xray.app.router.Config
|
||||
nil, // 7: xray.app.router.RoutingRule.AttributesEntry
|
||||
nil, // 8: xray.app.router.WebhookConfig.HeadersEntry
|
||||
(*geodata.DomainRule)(nil), // 9: xray.common.geodata.DomainRule
|
||||
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
|
||||
(*net.PortList)(nil), // 11: xray.common.net.PortList
|
||||
(net.Network)(0), // 12: xray.common.net.Network
|
||||
(*serial.TypedMessage)(nil), // 13: xray.common.serial.TypedMessage
|
||||
(Domain_Type)(0), // 0: xray.app.router.Domain.Type
|
||||
(Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy
|
||||
(*Domain)(nil), // 2: xray.app.router.Domain
|
||||
(*CIDR)(nil), // 3: xray.app.router.CIDR
|
||||
(*GeoIP)(nil), // 4: xray.app.router.GeoIP
|
||||
(*GeoIPList)(nil), // 5: xray.app.router.GeoIPList
|
||||
(*GeoSite)(nil), // 6: xray.app.router.GeoSite
|
||||
(*GeoSiteList)(nil), // 7: xray.app.router.GeoSiteList
|
||||
(*RoutingRule)(nil), // 8: xray.app.router.RoutingRule
|
||||
(*WebhookConfig)(nil), // 9: xray.app.router.WebhookConfig
|
||||
(*BalancingRule)(nil), // 10: xray.app.router.BalancingRule
|
||||
(*StrategyWeight)(nil), // 11: xray.app.router.StrategyWeight
|
||||
(*StrategyLeastLoadConfig)(nil), // 12: xray.app.router.StrategyLeastLoadConfig
|
||||
(*Config)(nil), // 13: xray.app.router.Config
|
||||
(*Domain_Attribute)(nil), // 14: xray.app.router.Domain.Attribute
|
||||
nil, // 15: xray.app.router.RoutingRule.AttributesEntry
|
||||
nil, // 16: xray.app.router.WebhookConfig.HeadersEntry
|
||||
(*net.PortList)(nil), // 17: xray.common.net.PortList
|
||||
(net.Network)(0), // 18: xray.common.net.Network
|
||||
(*serial.TypedMessage)(nil), // 19: xray.common.serial.TypedMessage
|
||||
}
|
||||
var file_app_router_config_proto_depIdxs = []int32{
|
||||
9, // 0: xray.app.router.RoutingRule.domain:type_name -> xray.common.geodata.DomainRule
|
||||
10, // 1: xray.app.router.RoutingRule.ip:type_name -> xray.common.geodata.IPRule
|
||||
11, // 2: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
|
||||
12, // 3: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
|
||||
10, // 4: xray.app.router.RoutingRule.source_ip:type_name -> xray.common.geodata.IPRule
|
||||
11, // 5: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
|
||||
7, // 6: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
|
||||
10, // 7: xray.app.router.RoutingRule.local_ip:type_name -> xray.common.geodata.IPRule
|
||||
11, // 8: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
|
||||
11, // 9: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
|
||||
2, // 10: xray.app.router.RoutingRule.webhook:type_name -> xray.app.router.WebhookConfig
|
||||
8, // 11: xray.app.router.WebhookConfig.headers:type_name -> xray.app.router.WebhookConfig.HeadersEntry
|
||||
13, // 12: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
|
||||
4, // 13: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
|
||||
0, // 14: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
|
||||
1, // 15: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
|
||||
3, // 16: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
|
||||
17, // [17:17] is the sub-list for method output_type
|
||||
17, // [17:17] is the sub-list for method input_type
|
||||
17, // [17:17] is the sub-list for extension type_name
|
||||
17, // [17:17] is the sub-list for extension extendee
|
||||
0, // [0:17] is the sub-list for field type_name
|
||||
0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type
|
||||
14, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute
|
||||
3, // 2: xray.app.router.GeoIP.cidr:type_name -> xray.app.router.CIDR
|
||||
4, // 3: xray.app.router.GeoIPList.entry:type_name -> xray.app.router.GeoIP
|
||||
2, // 4: xray.app.router.GeoSite.domain:type_name -> xray.app.router.Domain
|
||||
6, // 5: xray.app.router.GeoSiteList.entry:type_name -> xray.app.router.GeoSite
|
||||
2, // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain
|
||||
4, // 7: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP
|
||||
17, // 8: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
|
||||
18, // 9: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
|
||||
4, // 10: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP
|
||||
17, // 11: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
|
||||
15, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
|
||||
4, // 13: xray.app.router.RoutingRule.local_geoip:type_name -> xray.app.router.GeoIP
|
||||
17, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
|
||||
17, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
|
||||
9, // 16: xray.app.router.RoutingRule.webhook:type_name -> xray.app.router.WebhookConfig
|
||||
16, // 17: xray.app.router.WebhookConfig.headers:type_name -> xray.app.router.WebhookConfig.HeadersEntry
|
||||
19, // 18: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
|
||||
11, // 19: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
|
||||
1, // 20: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
|
||||
8, // 21: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
|
||||
10, // 22: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
|
||||
23, // [23:23] is the sub-list for method output_type
|
||||
23, // [23:23] is the sub-list for method input_type
|
||||
23, // [23:23] is the sub-list for extension type_name
|
||||
23, // [23:23] is the sub-list for extension extendee
|
||||
0, // [0:23] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_router_config_proto_init() }
|
||||
@@ -761,17 +1271,21 @@ func file_app_router_config_proto_init() {
|
||||
if File_app_router_config_proto != nil {
|
||||
return
|
||||
}
|
||||
file_app_router_config_proto_msgTypes[0].OneofWrappers = []any{
|
||||
file_app_router_config_proto_msgTypes[6].OneofWrappers = []any{
|
||||
(*RoutingRule_Tag)(nil),
|
||||
(*RoutingRule_BalancingTag)(nil),
|
||||
}
|
||||
file_app_router_config_proto_msgTypes[12].OneofWrappers = []any{
|
||||
(*Domain_Attribute_BoolValue)(nil),
|
||||
(*Domain_Attribute_IntValue)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_config_proto_rawDesc), len(file_app_router_config_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 8,
|
||||
NumEnums: 2,
|
||||
NumMessages: 15,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
+75
-15
@@ -9,7 +9,67 @@ option java_multiple_files = true;
|
||||
import "common/serial/typed_message.proto";
|
||||
import "common/net/port.proto";
|
||||
import "common/net/network.proto";
|
||||
import "common/geodata/geodat.proto";
|
||||
|
||||
// Domain for routing decision.
|
||||
message Domain {
|
||||
// Type of domain value.
|
||||
enum Type {
|
||||
// The value is used as is.
|
||||
Plain = 0;
|
||||
// The value is used as a regular expression.
|
||||
Regex = 1;
|
||||
// The value is a root domain.
|
||||
Domain = 2;
|
||||
// The value is a domain.
|
||||
Full = 3;
|
||||
}
|
||||
|
||||
// Domain matching type.
|
||||
Type type = 1;
|
||||
|
||||
// Domain value.
|
||||
string value = 2;
|
||||
|
||||
message Attribute {
|
||||
string key = 1;
|
||||
|
||||
oneof typed_value {
|
||||
bool bool_value = 2;
|
||||
int64 int_value = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes of this domain. May be used for filtering.
|
||||
repeated Attribute attribute = 3;
|
||||
}
|
||||
|
||||
// IP for routing decision, in CIDR form.
|
||||
message CIDR {
|
||||
// IP address, should be either 4 or 16 bytes.
|
||||
bytes ip = 1;
|
||||
|
||||
// Number of leading ones in the network mask.
|
||||
uint32 prefix = 2;
|
||||
}
|
||||
|
||||
message GeoIP {
|
||||
string country_code = 1;
|
||||
repeated CIDR cidr = 2;
|
||||
bool reverse_match = 3;
|
||||
}
|
||||
|
||||
message GeoIPList {
|
||||
repeated GeoIP entry = 1;
|
||||
}
|
||||
|
||||
message GeoSite {
|
||||
string country_code = 1;
|
||||
repeated Domain domain = 2;
|
||||
}
|
||||
|
||||
message GeoSiteList {
|
||||
repeated GeoSite entry = 1;
|
||||
}
|
||||
|
||||
message RoutingRule {
|
||||
oneof target_tag {
|
||||
@@ -19,23 +79,26 @@ message RoutingRule {
|
||||
// Tag of routing balancer.
|
||||
string balancing_tag = 12;
|
||||
}
|
||||
|
||||
string rule_tag = 19;
|
||||
string rule_tag = 19;
|
||||
|
||||
// List of domains for target domain matching.
|
||||
repeated xray.common.geodata.DomainRule domain = 2;
|
||||
repeated Domain domain = 2;
|
||||
|
||||
// List of IPs for target IP address matching.
|
||||
repeated xray.common.geodata.IPRule ip = 10;
|
||||
// List of GeoIPs for target IP address matching. If this entry exists, the
|
||||
// cidr above will have no effect. GeoIP fields with the same country code are
|
||||
// supposed to contain exactly same content. They will be merged during
|
||||
// runtime. For customized GeoIPs, please leave country code empty.
|
||||
repeated GeoIP geoip = 10;
|
||||
|
||||
// List of ports for target port matching.
|
||||
// List of ports.
|
||||
xray.common.net.PortList port_list = 14;
|
||||
|
||||
// List of networks for matching.
|
||||
repeated xray.common.net.Network networks = 13;
|
||||
|
||||
// List of IPs for source IP address matching.
|
||||
repeated xray.common.geodata.IPRule source_ip = 11;
|
||||
// List of GeoIPs for source IP address matching. If this entry exists, the
|
||||
// source_cidr above will have no effect.
|
||||
repeated GeoIP source_geoip = 11;
|
||||
|
||||
// List of ports for source port matching.
|
||||
xray.common.net.PortList source_port_list = 16;
|
||||
@@ -46,14 +109,10 @@ message RoutingRule {
|
||||
|
||||
map<string, string> attributes = 15;
|
||||
|
||||
// List of IPs for local IP address matching.
|
||||
repeated xray.common.geodata.IPRule local_ip = 17;
|
||||
|
||||
// List of ports for local port matching.
|
||||
repeated GeoIP local_geoip = 17;
|
||||
xray.common.net.PortList local_port_list = 18;
|
||||
|
||||
xray.common.net.PortList vless_route_list = 20;
|
||||
|
||||
repeated string process = 21;
|
||||
WebhookConfig webhook = 22;
|
||||
}
|
||||
@@ -96,7 +155,8 @@ message Config {
|
||||
// Use domain as is.
|
||||
AsIs = 0;
|
||||
|
||||
reserved 1;
|
||||
// [Deprecated] Always resolve IP for domains.
|
||||
// UseIp = 1;
|
||||
|
||||
// Resolve to IP if the domain doesn't match any rules.
|
||||
IpIfNonMatch = 2;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
)
|
||||
|
||||
type geoSiteListGob struct {
|
||||
Sites map[string][]byte
|
||||
Deps map[string][]string
|
||||
Hosts map[string][]string
|
||||
}
|
||||
|
||||
func SerializeGeoSiteList(sites []*GeoSite, deps map[string][]string, hosts map[string][]string, w io.Writer) error {
|
||||
data := geoSiteListGob{
|
||||
Sites: make(map[string][]byte),
|
||||
Deps: deps,
|
||||
Hosts: hosts,
|
||||
}
|
||||
|
||||
for _, site := range sites {
|
||||
if site == nil {
|
||||
continue
|
||||
}
|
||||
var buf bytesWriter
|
||||
if err := SerializeDomainMatcher(site.Domain, &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
data.Sites[site.CountryCode] = buf.Bytes()
|
||||
}
|
||||
|
||||
return gob.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
type bytesWriter struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (w *bytesWriter) Write(p []byte) (n int, err error) {
|
||||
w.data = append(w.data, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *bytesWriter) Bytes() []byte {
|
||||
return w.data
|
||||
}
|
||||
|
||||
func LoadGeoSiteMatcher(r io.Reader, countryCode string) (strmatcher.IndexMatcher, error) {
|
||||
var data geoSiteListGob
|
||||
if err := gob.NewDecoder(r).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loadWithDeps(&data, countryCode, make(map[string]bool))
|
||||
}
|
||||
|
||||
func loadWithDeps(data *geoSiteListGob, code string, visited map[string]bool) (strmatcher.IndexMatcher, error) {
|
||||
if visited[code] {
|
||||
return nil, errors.New("cyclic dependency")
|
||||
}
|
||||
visited[code] = true
|
||||
|
||||
var matchers []strmatcher.IndexMatcher
|
||||
|
||||
if siteData, ok := data.Sites[code]; ok {
|
||||
m, err := NewDomainMatcherFromBuffer(siteData)
|
||||
if err == nil {
|
||||
matchers = append(matchers, m)
|
||||
}
|
||||
}
|
||||
|
||||
if deps, ok := data.Deps[code]; ok {
|
||||
for _, dep := range deps {
|
||||
m, err := loadWithDeps(data, dep, visited)
|
||||
if err == nil {
|
||||
matchers = append(matchers, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(matchers) == 0 {
|
||||
return nil, errors.New("matcher not found for: " + code)
|
||||
}
|
||||
if len(matchers) == 1 {
|
||||
return matchers[0], nil
|
||||
}
|
||||
runtime.GC()
|
||||
return &strmatcher.IndexMatcherGroup{Matchers: matchers}, nil
|
||||
}
|
||||
func LoadGeoSiteHosts(r io.Reader) (map[string][]string, error) {
|
||||
var data geoSiteListGob
|
||||
if err := gob.NewDecoder(r).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data.Hosts, nil
|
||||
}
|
||||
@@ -110,6 +110,7 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
|
||||
|
||||
// AddRule implements routing.Router.
|
||||
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
|
||||
|
||||
inst, err := config.GetInstance()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -226,6 +227,7 @@ func (r *Router) RemoveRule(tag string) error {
|
||||
return nil
|
||||
}
|
||||
return errors.New("empty tag name!")
|
||||
|
||||
}
|
||||
|
||||
// ListRule implements routing.Router
|
||||
|
||||
+15
-13
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
. "github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
@@ -156,11 +155,12 @@ func TestIPOnDemand(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Ip: []*geodata.IPRule{
|
||||
Geoip: []*GeoIP{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -200,11 +200,12 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Ip: []*geodata.IPRule{
|
||||
Geoip: []*GeoIP{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -244,11 +245,12 @@ func TestIPIfNonMatchIP(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Ip: []*geodata.IPRule{
|
||||
Geoip: []*GeoIP{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{127, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,7 +3,6 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -78,7 +77,7 @@ func (s *LeastLoadStrategy) PickOutbound(candidates []string) string {
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {
|
||||
qualified := s.getNodes(candidates)
|
||||
qualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT))
|
||||
selects := s.selectLeastLoad(qualified)
|
||||
return selects
|
||||
}
|
||||
@@ -139,7 +138,7 @@ func (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node {
|
||||
return nodes[:count]
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node {
|
||||
if s.observer == nil {
|
||||
errors.LogError(s.ctx, "observer is nil")
|
||||
return make([]*node, 0)
|
||||
@@ -152,10 +151,12 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
|
||||
results := observeResult.(*observatory.ObservationResult)
|
||||
|
||||
outboundlist := outboundList(candidates)
|
||||
|
||||
var ret []*node
|
||||
|
||||
for _, v := range results.Status {
|
||||
if s.shouldSelectNode(v, candidates) {
|
||||
if v.Alive && (v.Delay < maxRTT.Milliseconds() || maxRTT == 0) && outboundlist.contains(v.OutboundTag) {
|
||||
record := &node{
|
||||
Tag: v.OutboundTag,
|
||||
CountAll: 1,
|
||||
@@ -171,8 +172,8 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
record.RTTDeviationCost = time.Duration(s.costs.Apply(v.OutboundTag, float64(v.HealthPing.Deviation)))
|
||||
record.CountAll = int(v.HealthPing.All)
|
||||
record.CountFail = int(v.HealthPing.Fail)
|
||||
}
|
||||
|
||||
}
|
||||
ret = append(ret, record)
|
||||
}
|
||||
}
|
||||
@@ -181,23 +182,6 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) shouldSelectNode(v *observatory.OutboundStatus, candidates []string) bool {
|
||||
maxRTT := time.Duration(s.settings.MaxRTT)
|
||||
if !v.Alive {
|
||||
return false
|
||||
}
|
||||
if maxRTT != 0 && v.Delay >= maxRTT.Milliseconds() {
|
||||
return false
|
||||
}
|
||||
if !slices.Contains(candidates, v.OutboundTag) {
|
||||
return false
|
||||
}
|
||||
if v.HealthPing != nil && v.HealthPing.All > 0 && s.settings.Tolerance > 0 && float64(v.HealthPing.Fail)/float64(v.HealthPing.All) > float64(s.settings.Tolerance) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func leastloadSort(nodes []*node) {
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
left := nodes[i]
|
||||
|
||||
@@ -85,7 +85,6 @@ func TestSelectLeastExpected(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpected2(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -103,7 +102,6 @@ func TestSelectLeastExpected2(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -124,7 +122,6 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -145,7 +142,6 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastLoadBaselines(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -164,7 +160,6 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
|
||||
+49
-3
@@ -7,15 +7,61 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||
)
|
||||
|
||||
// parseURL splits a webhook URL into an HTTP URL and an optional Unix socket
|
||||
// path. For regular http/https URLs the input is returned unchanged with an
|
||||
// empty socketPath. For Unix sockets the format is:
|
||||
//
|
||||
// /path/to/socket.sock:/http/path
|
||||
// @abstract:/http/path
|
||||
// @@padded:/http/path
|
||||
//
|
||||
// The :/ separator after the socket path delimits the HTTP request path.
|
||||
// If omitted, "/" is used.
|
||||
func parseURL(raw string) (httpURL, socketPath string) {
|
||||
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
|
||||
return raw, ""
|
||||
}
|
||||
if idx := strings.Index(raw, ":/"); idx >= 0 {
|
||||
return "http://localhost" + raw[idx+1:], raw[:idx]
|
||||
}
|
||||
return "http://localhost/", raw
|
||||
}
|
||||
|
||||
// resolveSocketPath applies platform-specific transformations to a Unix
|
||||
// socket path, matching the behaviour of the listen side in
|
||||
// transport/internet/system_listener.go.
|
||||
//
|
||||
// For abstract sockets (prefix @) on Linux/Android:
|
||||
// - single @ — used as-is (lock-free abstract socket)
|
||||
// - double @@ — stripped to single @ and padded to
|
||||
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
|
||||
func resolveSocketPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '@' {
|
||||
return path
|
||||
}
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
|
||||
return path
|
||||
}
|
||||
if len(path) > 1 && path[1] == '@' {
|
||||
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
|
||||
copy(fullAddr, path[1:])
|
||||
return string(fullAddr)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
type event struct {
|
||||
@@ -50,7 +96,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
httpURL, socketPath := utils.SplitHTTPUnixURL(cfg.Url)
|
||||
httpURL, socketPath := parseURL(cfg.Url)
|
||||
h := &WebhookNotifier{
|
||||
url: httpURL,
|
||||
deduplication: cfg.Deduplication,
|
||||
@@ -61,7 +107,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
}
|
||||
|
||||
if socketPath != "" {
|
||||
dialAddr := utils.ResolveSocketPath(socketPath)
|
||||
dialAddr := resolveSocketPath(socketPath)
|
||||
h.client.Transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
|
||||
@@ -3,10 +3,12 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/app/stats"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/core"
|
||||
feature_stats "github.com/xtls/xray-core/features/stats"
|
||||
grpc "google.golang.org/grpc"
|
||||
@@ -68,10 +70,9 @@ func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStat
|
||||
}
|
||||
|
||||
ips := make(map[string]int64)
|
||||
c.ForEach(func(ip string, lastSeen int64) bool {
|
||||
ips[ip] = lastSeen
|
||||
return true
|
||||
})
|
||||
for ip, t := range c.IPTimeMap() {
|
||||
ips[ip] = t.Unix()
|
||||
}
|
||||
|
||||
return &GetStatsOnlineIpListResponse{
|
||||
Name: request.Name,
|
||||
@@ -85,87 +86,21 @@ func (s *statsServer) GetAllOnlineUsers(ctx context.Context, request *GetAllOnli
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *statsServer) GetUsersStats(ctx context.Context, request *GetUsersStatsRequest) (*GetUsersStatsResponse, error) {
|
||||
userMap := make(map[string]*UserStat)
|
||||
|
||||
s.stats.VisitOnlineMaps(func(name string, om feature_stats.OnlineMap) bool {
|
||||
if om.Count() == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
_, rest, _ := strings.Cut(name, ">>>")
|
||||
email, _, _ := strings.Cut(rest, ">>>")
|
||||
|
||||
user := &UserStat{Email: email}
|
||||
om.ForEach(func(ip string, lastSeen int64) bool {
|
||||
user.Ips = append(user.Ips, &OnlineIPEntry{
|
||||
Ip: ip,
|
||||
LastSeen: lastSeen,
|
||||
})
|
||||
return true
|
||||
})
|
||||
if len(user.Ips) > 0 {
|
||||
userMap[email] = user
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if request.IncludeTraffic {
|
||||
for _, u := range userMap {
|
||||
u.Traffic = &TrafficUserStat{}
|
||||
}
|
||||
const (
|
||||
prefixUser = "user>>>"
|
||||
suffixUplink = ">>>traffic>>>uplink"
|
||||
suffixDownlink = ">>>traffic>>>downlink"
|
||||
)
|
||||
s.stats.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
||||
var email string
|
||||
var isUplink bool
|
||||
|
||||
if strings.HasSuffix(name, suffixUplink) {
|
||||
email = name[len(prefixUser) : len(name)-len(suffixUplink)]
|
||||
isUplink = true
|
||||
} else if strings.HasSuffix(name, suffixDownlink) {
|
||||
email = name[len(prefixUser) : len(name)-len(suffixDownlink)]
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
u, ok := userMap[email]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var value int64
|
||||
if request.Reset_ {
|
||||
value = c.Set(0)
|
||||
} else {
|
||||
value = c.Value()
|
||||
}
|
||||
|
||||
if isUplink {
|
||||
u.Traffic.Uplink = value
|
||||
} else {
|
||||
u.Traffic.Downlink = value
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
resp := &GetUsersStatsResponse{}
|
||||
for _, u := range userMap {
|
||||
resp.Users = append(resp.Users, u)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
|
||||
matcher, err := strmatcher.Substr.New(request.Pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &QueryStatsResponse{}
|
||||
|
||||
s.stats.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
||||
if strings.Contains(name, request.Pattern) {
|
||||
manager, ok := s.stats.(*stats.Manager)
|
||||
if !ok {
|
||||
return nil, errors.New("QueryStats only works its own stats.Manager.")
|
||||
}
|
||||
|
||||
manager.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
||||
if matcher.Match(name) {
|
||||
var value int64
|
||||
if request.Reset_ {
|
||||
value = c.Set(0)
|
||||
|
||||
+28
-314
@@ -551,266 +551,6 @@ func (x *GetAllOnlineUsersResponse) GetUsers() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
type OnlineIPEntry struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
LastSeen int64 `protobuf:"varint,2,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *OnlineIPEntry) Reset() {
|
||||
*x = OnlineIPEntry{}
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *OnlineIPEntry) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*OnlineIPEntry) ProtoMessage() {}
|
||||
|
||||
func (x *OnlineIPEntry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use OnlineIPEntry.ProtoReflect.Descriptor instead.
|
||||
func (*OnlineIPEntry) Descriptor() ([]byte, []int) {
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *OnlineIPEntry) GetIp() string {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *OnlineIPEntry) GetLastSeen() int64 {
|
||||
if x != nil {
|
||||
return x.LastSeen
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type TrafficUserStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Uplink int64 `protobuf:"varint,1,opt,name=uplink,proto3" json:"uplink,omitempty"`
|
||||
Downlink int64 `protobuf:"varint,2,opt,name=downlink,proto3" json:"downlink,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TrafficUserStat) Reset() {
|
||||
*x = TrafficUserStat{}
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *TrafficUserStat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TrafficUserStat) ProtoMessage() {}
|
||||
|
||||
func (x *TrafficUserStat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TrafficUserStat.ProtoReflect.Descriptor instead.
|
||||
func (*TrafficUserStat) Descriptor() ([]byte, []int) {
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *TrafficUserStat) GetUplink() int64 {
|
||||
if x != nil {
|
||||
return x.Uplink
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *TrafficUserStat) GetDownlink() int64 {
|
||||
if x != nil {
|
||||
return x.Downlink
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type UserStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"`
|
||||
Ips []*OnlineIPEntry `protobuf:"bytes,2,rep,name=ips,proto3" json:"ips,omitempty"`
|
||||
Traffic *TrafficUserStat `protobuf:"bytes,3,opt,name=traffic,proto3" json:"traffic,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UserStat) Reset() {
|
||||
*x = UserStat{}
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *UserStat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UserStat) ProtoMessage() {}
|
||||
|
||||
func (x *UserStat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[12]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UserStat.ProtoReflect.Descriptor instead.
|
||||
func (*UserStat) Descriptor() ([]byte, []int) {
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *UserStat) GetEmail() string {
|
||||
if x != nil {
|
||||
return x.Email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UserStat) GetIps() []*OnlineIPEntry {
|
||||
if x != nil {
|
||||
return x.Ips
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *UserStat) GetTraffic() *TrafficUserStat {
|
||||
if x != nil {
|
||||
return x.Traffic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetUsersStatsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
IncludeTraffic bool `protobuf:"varint,1,opt,name=include_traffic,json=includeTraffic,proto3" json:"include_traffic,omitempty"`
|
||||
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetUsersStatsRequest) Reset() {
|
||||
*x = GetUsersStatsRequest{}
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetUsersStatsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetUsersStatsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetUsersStatsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[13]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetUsersStatsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetUsersStatsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *GetUsersStatsRequest) GetIncludeTraffic() bool {
|
||||
if x != nil {
|
||||
return x.IncludeTraffic
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *GetUsersStatsRequest) GetReset_() bool {
|
||||
if x != nil {
|
||||
return x.Reset_
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type GetUsersStatsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Users []*UserStat `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetUsersStatsResponse) Reset() {
|
||||
*x = GetUsersStatsResponse{}
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetUsersStatsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetUsersStatsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetUsersStatsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[14]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetUsersStatsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetUsersStatsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
func (x *GetUsersStatsResponse) GetUsers() []*UserStat {
|
||||
if x != nil {
|
||||
return x.Users
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
@@ -819,7 +559,7 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[15]
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -831,7 +571,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[15]
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -844,7 +584,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{15}
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
var File_app_stats_command_command_proto protoreflect.FileDescriptor
|
||||
@@ -888,23 +628,8 @@ const file_app_stats_command_command_proto_rawDesc = "" +
|
||||
"\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"\x1a\n" +
|
||||
"\x18GetAllOnlineUsersRequest\"1\n" +
|
||||
"\x19GetAllOnlineUsersResponse\x12\x14\n" +
|
||||
"\x05users\x18\x01 \x03(\tR\x05users\"<\n" +
|
||||
"\rOnlineIPEntry\x12\x0e\n" +
|
||||
"\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1b\n" +
|
||||
"\tlast_seen\x18\x02 \x01(\x03R\blastSeen\"E\n" +
|
||||
"\x0fTrafficUserStat\x12\x16\n" +
|
||||
"\x06uplink\x18\x01 \x01(\x03R\x06uplink\x12\x1a\n" +
|
||||
"\bdownlink\x18\x02 \x01(\x03R\bdownlink\"\x9c\x01\n" +
|
||||
"\bUserStat\x12\x14\n" +
|
||||
"\x05email\x18\x01 \x01(\tR\x05email\x127\n" +
|
||||
"\x03ips\x18\x02 \x03(\v2%.xray.app.stats.command.OnlineIPEntryR\x03ips\x12A\n" +
|
||||
"\atraffic\x18\x03 \x01(\v2'.xray.app.stats.command.TrafficUserStatR\atraffic\"U\n" +
|
||||
"\x14GetUsersStatsRequest\x12'\n" +
|
||||
"\x0finclude_traffic\x18\x01 \x01(\bR\x0eincludeTraffic\x12\x14\n" +
|
||||
"\x05reset\x18\x02 \x01(\bR\x05reset\"O\n" +
|
||||
"\x15GetUsersStatsResponse\x126\n" +
|
||||
"\x05users\x18\x01 \x03(\v2 .xray.app.stats.command.UserStatR\x05users\"\b\n" +
|
||||
"\x06Config2\x86\x06\n" +
|
||||
"\x05users\x18\x01 \x03(\tR\x05users\"\b\n" +
|
||||
"\x06Config2\x96\x05\n" +
|
||||
"\fStatsService\x12_\n" +
|
||||
"\bGetStats\x12'.xray.app.stats.command.GetStatsRequest\x1a(.xray.app.stats.command.GetStatsResponse\"\x00\x12e\n" +
|
||||
"\x0eGetStatsOnline\x12'.xray.app.stats.command.GetStatsRequest\x1a(.xray.app.stats.command.GetStatsResponse\"\x00\x12e\n" +
|
||||
@@ -912,8 +637,7 @@ const file_app_stats_command_command_proto_rawDesc = "" +
|
||||
"QueryStats\x12).xray.app.stats.command.QueryStatsRequest\x1a*.xray.app.stats.command.QueryStatsResponse\"\x00\x12b\n" +
|
||||
"\vGetSysStats\x12'.xray.app.stats.command.SysStatsRequest\x1a(.xray.app.stats.command.SysStatsResponse\"\x00\x12w\n" +
|
||||
"\x14GetStatsOnlineIpList\x12'.xray.app.stats.command.GetStatsRequest\x1a4.xray.app.stats.command.GetStatsOnlineIpListResponse\"\x00\x12z\n" +
|
||||
"\x11GetAllOnlineUsers\x120.xray.app.stats.command.GetAllOnlineUsersRequest\x1a1.xray.app.stats.command.GetAllOnlineUsersResponse\"\x00\x12n\n" +
|
||||
"\rGetUsersStats\x12,.xray.app.stats.command.GetUsersStatsRequest\x1a-.xray.app.stats.command.GetUsersStatsResponse\"\x00Bd\n" +
|
||||
"\x11GetAllOnlineUsers\x120.xray.app.stats.command.GetAllOnlineUsersRequest\x1a1.xray.app.stats.command.GetAllOnlineUsersResponse\"\x00Bd\n" +
|
||||
"\x1acom.xray.app.stats.commandP\x01Z+github.com/xtls/xray-core/app/stats/command\xaa\x02\x16Xray.App.Stats.Commandb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -928,7 +652,7 @@ func file_app_stats_command_command_proto_rawDescGZIP() []byte {
|
||||
return file_app_stats_command_command_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
||||
var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
||||
var file_app_stats_command_command_proto_goTypes = []any{
|
||||
(*GetStatsRequest)(nil), // 0: xray.app.stats.command.GetStatsRequest
|
||||
(*Stat)(nil), // 1: xray.app.stats.command.Stat
|
||||
@@ -940,40 +664,30 @@ var file_app_stats_command_command_proto_goTypes = []any{
|
||||
(*GetStatsOnlineIpListResponse)(nil), // 7: xray.app.stats.command.GetStatsOnlineIpListResponse
|
||||
(*GetAllOnlineUsersRequest)(nil), // 8: xray.app.stats.command.GetAllOnlineUsersRequest
|
||||
(*GetAllOnlineUsersResponse)(nil), // 9: xray.app.stats.command.GetAllOnlineUsersResponse
|
||||
(*OnlineIPEntry)(nil), // 10: xray.app.stats.command.OnlineIPEntry
|
||||
(*TrafficUserStat)(nil), // 11: xray.app.stats.command.TrafficUserStat
|
||||
(*UserStat)(nil), // 12: xray.app.stats.command.UserStat
|
||||
(*GetUsersStatsRequest)(nil), // 13: xray.app.stats.command.GetUsersStatsRequest
|
||||
(*GetUsersStatsResponse)(nil), // 14: xray.app.stats.command.GetUsersStatsResponse
|
||||
(*Config)(nil), // 15: xray.app.stats.command.Config
|
||||
nil, // 16: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
|
||||
(*Config)(nil), // 10: xray.app.stats.command.Config
|
||||
nil, // 11: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
|
||||
}
|
||||
var file_app_stats_command_command_proto_depIdxs = []int32{
|
||||
1, // 0: xray.app.stats.command.GetStatsResponse.stat:type_name -> xray.app.stats.command.Stat
|
||||
1, // 1: xray.app.stats.command.QueryStatsResponse.stat:type_name -> xray.app.stats.command.Stat
|
||||
16, // 2: xray.app.stats.command.GetStatsOnlineIpListResponse.ips:type_name -> xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
|
||||
10, // 3: xray.app.stats.command.UserStat.ips:type_name -> xray.app.stats.command.OnlineIPEntry
|
||||
11, // 4: xray.app.stats.command.UserStat.traffic:type_name -> xray.app.stats.command.TrafficUserStat
|
||||
12, // 5: xray.app.stats.command.GetUsersStatsResponse.users:type_name -> xray.app.stats.command.UserStat
|
||||
0, // 6: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest
|
||||
0, // 7: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest
|
||||
3, // 8: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest
|
||||
5, // 9: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest
|
||||
0, // 10: xray.app.stats.command.StatsService.GetStatsOnlineIpList:input_type -> xray.app.stats.command.GetStatsRequest
|
||||
8, // 11: xray.app.stats.command.StatsService.GetAllOnlineUsers:input_type -> xray.app.stats.command.GetAllOnlineUsersRequest
|
||||
13, // 12: xray.app.stats.command.StatsService.GetUsersStats:input_type -> xray.app.stats.command.GetUsersStatsRequest
|
||||
2, // 13: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse
|
||||
2, // 14: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse
|
||||
4, // 15: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse
|
||||
6, // 16: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse
|
||||
7, // 17: xray.app.stats.command.StatsService.GetStatsOnlineIpList:output_type -> xray.app.stats.command.GetStatsOnlineIpListResponse
|
||||
9, // 18: xray.app.stats.command.StatsService.GetAllOnlineUsers:output_type -> xray.app.stats.command.GetAllOnlineUsersResponse
|
||||
14, // 19: xray.app.stats.command.StatsService.GetUsersStats:output_type -> xray.app.stats.command.GetUsersStatsResponse
|
||||
13, // [13:20] is the sub-list for method output_type
|
||||
6, // [6:13] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
11, // 2: xray.app.stats.command.GetStatsOnlineIpListResponse.ips:type_name -> xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
|
||||
0, // 3: xray.app.stats.command.StatsService.GetStats:input_type -> xray.app.stats.command.GetStatsRequest
|
||||
0, // 4: xray.app.stats.command.StatsService.GetStatsOnline:input_type -> xray.app.stats.command.GetStatsRequest
|
||||
3, // 5: xray.app.stats.command.StatsService.QueryStats:input_type -> xray.app.stats.command.QueryStatsRequest
|
||||
5, // 6: xray.app.stats.command.StatsService.GetSysStats:input_type -> xray.app.stats.command.SysStatsRequest
|
||||
0, // 7: xray.app.stats.command.StatsService.GetStatsOnlineIpList:input_type -> xray.app.stats.command.GetStatsRequest
|
||||
8, // 8: xray.app.stats.command.StatsService.GetAllOnlineUsers:input_type -> xray.app.stats.command.GetAllOnlineUsersRequest
|
||||
2, // 9: xray.app.stats.command.StatsService.GetStats:output_type -> xray.app.stats.command.GetStatsResponse
|
||||
2, // 10: xray.app.stats.command.StatsService.GetStatsOnline:output_type -> xray.app.stats.command.GetStatsResponse
|
||||
4, // 11: xray.app.stats.command.StatsService.QueryStats:output_type -> xray.app.stats.command.QueryStatsResponse
|
||||
6, // 12: xray.app.stats.command.StatsService.GetSysStats:output_type -> xray.app.stats.command.SysStatsResponse
|
||||
7, // 13: xray.app.stats.command.StatsService.GetStatsOnlineIpList:output_type -> xray.app.stats.command.GetStatsOnlineIpListResponse
|
||||
9, // 14: xray.app.stats.command.StatsService.GetAllOnlineUsers:output_type -> xray.app.stats.command.GetAllOnlineUsersResponse
|
||||
9, // [9:15] is the sub-list for method output_type
|
||||
3, // [3:9] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_stats_command_command_proto_init() }
|
||||
@@ -987,7 +701,7 @@ func file_app_stats_command_command_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_stats_command_command_proto_rawDesc), len(file_app_stats_command_command_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 17,
|
||||
NumMessages: 12,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -57,31 +57,6 @@ message GetAllOnlineUsersResponse {
|
||||
repeated string users = 1;
|
||||
}
|
||||
|
||||
message OnlineIPEntry {
|
||||
string ip = 1;
|
||||
int64 last_seen = 2;
|
||||
}
|
||||
|
||||
message TrafficUserStat {
|
||||
int64 uplink = 1;
|
||||
int64 downlink = 2;
|
||||
}
|
||||
|
||||
message UserStat {
|
||||
string email = 1;
|
||||
repeated OnlineIPEntry ips = 2;
|
||||
TrafficUserStat traffic = 3;
|
||||
}
|
||||
|
||||
message GetUsersStatsRequest {
|
||||
bool include_traffic = 1;
|
||||
bool reset = 2;
|
||||
}
|
||||
|
||||
message GetUsersStatsResponse {
|
||||
repeated UserStat users = 1;
|
||||
}
|
||||
|
||||
service StatsService {
|
||||
rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}
|
||||
rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {}
|
||||
@@ -89,7 +64,6 @@ service StatsService {
|
||||
rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}
|
||||
rpc GetStatsOnlineIpList(GetStatsRequest) returns (GetStatsOnlineIpListResponse) {}
|
||||
rpc GetAllOnlineUsers(GetAllOnlineUsersRequest) returns (GetAllOnlineUsersResponse) {}
|
||||
rpc GetUsersStats(GetUsersStatsRequest) returns (GetUsersStatsResponse) {}
|
||||
}
|
||||
|
||||
message Config {}
|
||||
|
||||
@@ -25,7 +25,6 @@ const (
|
||||
StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats"
|
||||
StatsService_GetStatsOnlineIpList_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnlineIpList"
|
||||
StatsService_GetAllOnlineUsers_FullMethodName = "/xray.app.stats.command.StatsService/GetAllOnlineUsers"
|
||||
StatsService_GetUsersStats_FullMethodName = "/xray.app.stats.command.StatsService/GetUsersStats"
|
||||
)
|
||||
|
||||
// StatsServiceClient is the client API for StatsService service.
|
||||
@@ -38,7 +37,6 @@ type StatsServiceClient interface {
|
||||
GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error)
|
||||
GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error)
|
||||
GetAllOnlineUsers(ctx context.Context, in *GetAllOnlineUsersRequest, opts ...grpc.CallOption) (*GetAllOnlineUsersResponse, error)
|
||||
GetUsersStats(ctx context.Context, in *GetUsersStatsRequest, opts ...grpc.CallOption) (*GetUsersStatsResponse, error)
|
||||
}
|
||||
|
||||
type statsServiceClient struct {
|
||||
@@ -109,16 +107,6 @@ func (c *statsServiceClient) GetAllOnlineUsers(ctx context.Context, in *GetAllOn
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) GetUsersStats(ctx context.Context, in *GetUsersStatsRequest, opts ...grpc.CallOption) (*GetUsersStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetUsersStatsResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_GetUsersStats_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// StatsServiceServer is the server API for StatsService service.
|
||||
// All implementations must embed UnimplementedStatsServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -129,7 +117,6 @@ type StatsServiceServer interface {
|
||||
GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error)
|
||||
GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error)
|
||||
GetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error)
|
||||
GetUsersStats(context.Context, *GetUsersStatsRequest) (*GetUsersStatsResponse, error)
|
||||
mustEmbedUnimplementedStatsServiceServer()
|
||||
}
|
||||
|
||||
@@ -158,9 +145,6 @@ func (UnimplementedStatsServiceServer) GetStatsOnlineIpList(context.Context, *Ge
|
||||
func (UnimplementedStatsServiceServer) GetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetAllOnlineUsers not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) GetUsersStats(context.Context, *GetUsersStatsRequest) (*GetUsersStatsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetUsersStats not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {}
|
||||
func (UnimplementedStatsServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
@@ -290,24 +274,6 @@ func _StatsService_GetAllOnlineUsers_Handler(srv interface{}, ctx context.Contex
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StatsService_GetUsersStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetUsersStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).GetUsersStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_GetUsersStats_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).GetUsersStats(ctx, req.(*GetUsersStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -339,10 +305,6 @@ var StatsService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetAllOnlineUsers",
|
||||
Handler: _StatsService_GetAllOnlineUsers_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetUsersStats",
|
||||
Handler: _StatsService_GetUsersStats_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "app/stats/command/command.proto",
|
||||
|
||||
+28
-16
@@ -13,14 +13,14 @@ const (
|
||||
|
||||
type ipEntry struct {
|
||||
refCount int
|
||||
lastSeen int64
|
||||
lastSeen time.Time
|
||||
}
|
||||
|
||||
// OnlineMap is a refcount-based implementation of stats.OnlineMap.
|
||||
// IPs are tracked by reference counting: AddIP increments, RemoveIP decrements.
|
||||
// An IP is removed from the map when its reference count reaches zero.
|
||||
type OnlineMap struct {
|
||||
entries map[string]ipEntry
|
||||
entries map[string]*ipEntry
|
||||
access sync.Mutex
|
||||
count atomic.Int64
|
||||
}
|
||||
@@ -28,7 +28,7 @@ type OnlineMap struct {
|
||||
// NewOnlineMap creates a new OnlineMap instance.
|
||||
func NewOnlineMap() *OnlineMap {
|
||||
return &OnlineMap{
|
||||
entries: make(map[string]ipEntry),
|
||||
entries: make(map[string]*ipEntry),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,17 +37,17 @@ func (om *OnlineMap) AddIP(ip string) {
|
||||
if ip == localhostIPv4 || ip == localhostIPv6 {
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
|
||||
om.access.Lock()
|
||||
defer om.access.Unlock()
|
||||
|
||||
if e, ok := om.entries[ip]; ok {
|
||||
e.refCount++
|
||||
e.lastSeen = now
|
||||
om.entries[ip] = e
|
||||
e.lastSeen = time.Now()
|
||||
} else {
|
||||
om.entries[ip] = ipEntry{
|
||||
om.entries[ip] = &ipEntry{
|
||||
refCount: 1,
|
||||
lastSeen: now,
|
||||
lastSeen: time.Now(),
|
||||
}
|
||||
om.count.Add(1)
|
||||
}
|
||||
@@ -57,6 +57,7 @@ func (om *OnlineMap) AddIP(ip string) {
|
||||
func (om *OnlineMap) RemoveIP(ip string) {
|
||||
om.access.Lock()
|
||||
defer om.access.Unlock()
|
||||
|
||||
e, ok := om.entries[ip]
|
||||
if !ok {
|
||||
return
|
||||
@@ -65,8 +66,6 @@ func (om *OnlineMap) RemoveIP(ip string) {
|
||||
if e.refCount <= 0 {
|
||||
delete(om.entries, ip)
|
||||
om.count.Add(-1)
|
||||
} else {
|
||||
om.entries[ip] = e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,13 +74,26 @@ func (om *OnlineMap) Count() int {
|
||||
return int(om.count.Load())
|
||||
}
|
||||
|
||||
// ForEach calls fn for each online IP. If fn returns false, iteration stops.
|
||||
func (om *OnlineMap) ForEach(fn func(string, int64) bool) {
|
||||
// List implements stats.OnlineMap.
|
||||
func (om *OnlineMap) List() []string {
|
||||
om.access.Lock()
|
||||
defer om.access.Unlock()
|
||||
for ip, e := range om.entries {
|
||||
if !fn(ip, e.lastSeen) {
|
||||
break
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(om.entries))
|
||||
for ip := range om.entries {
|
||||
keys = append(keys, ip)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// IPTimeMap implements stats.OnlineMap.
|
||||
func (om *OnlineMap) IPTimeMap() map[string]time.Time {
|
||||
om.access.Lock()
|
||||
defer om.access.Unlock()
|
||||
|
||||
result := make(map[string]time.Time, len(om.entries))
|
||||
for ip, e := range om.entries {
|
||||
result[ip] = e.lastSeen
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+19
-83
@@ -11,19 +11,19 @@ import (
|
||||
|
||||
// Manager is an implementation of stats.Manager.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
counters map[string]*Counter
|
||||
onlineMaps map[string]*OnlineMap
|
||||
channels map[string]*Channel
|
||||
running bool
|
||||
access sync.RWMutex
|
||||
counters map[string]*Counter
|
||||
onlineMap map[string]*OnlineMap
|
||||
channels map[string]*Channel
|
||||
running bool
|
||||
}
|
||||
|
||||
// NewManager creates an instance of Statistics Manager.
|
||||
func NewManager(ctx context.Context, config *Config) (*Manager, error) {
|
||||
m := &Manager{
|
||||
counters: make(map[string]*Counter),
|
||||
onlineMaps: make(map[string]*OnlineMap),
|
||||
channels: make(map[string]*Channel),
|
||||
counters: make(map[string]*Counter),
|
||||
onlineMap: make(map[string]*OnlineMap),
|
||||
channels: make(map[string]*Channel),
|
||||
}
|
||||
|
||||
return m, nil
|
||||
@@ -48,20 +48,6 @@ func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
|
||||
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.
|
||||
func (m *Manager) UnregisterCounter(name string) error {
|
||||
m.access.Lock()
|
||||
@@ -102,26 +88,12 @@ func (m *Manager) RegisterOnlineMap(name string) (stats.OnlineMap, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
if _, found := m.onlineMaps[name]; found {
|
||||
return nil, errors.New("OnlineMap ", name, " already registered.")
|
||||
if _, found := m.onlineMap[name]; found {
|
||||
return nil, errors.New("onlineMap ", name, " already registered.")
|
||||
}
|
||||
errors.LogDebug(context.Background(), "create new OnlineMap ", name)
|
||||
errors.LogDebug(context.Background(), "create new onlineMap ", name)
|
||||
om := NewOnlineMap()
|
||||
m.onlineMaps[name] = om
|
||||
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
|
||||
m.onlineMap[name] = om
|
||||
return om, nil
|
||||
}
|
||||
|
||||
@@ -130,9 +102,9 @@ func (m *Manager) UnregisterOnlineMap(name string) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
if _, found := m.onlineMaps[name]; found {
|
||||
errors.LogDebug(context.Background(), "remove OnlineMap ", name)
|
||||
delete(m.onlineMaps, name)
|
||||
if _, found := m.onlineMap[name]; found {
|
||||
errors.LogDebug(context.Background(), "remove onlineMap ", name)
|
||||
delete(m.onlineMap, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -142,24 +114,12 @@ func (m *Manager) GetOnlineMap(name string) stats.OnlineMap {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
if om, found := m.onlineMaps[name]; found {
|
||||
if om, found := m.onlineMap[name]; found {
|
||||
return om
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitOnlineMaps calls visitor function on all managed online maps.
|
||||
// The visitor runs under a read lock; it must not call RegisterOnlineMap or UnregisterOnlineMap (would deadlock).
|
||||
func (m *Manager) VisitOnlineMaps(visitor func(string, stats.OnlineMap) bool) {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
for name, om := range m.onlineMaps {
|
||||
if !visitor(name, om) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterChannel implements stats.Manager.
|
||||
func (m *Manager) RegisterChannel(name string) (stats.Channel, error) {
|
||||
m.access.Lock()
|
||||
@@ -177,26 +137,6 @@ func (m *Manager) RegisterChannel(name string) (stats.Channel, error) {
|
||||
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.
|
||||
func (m *Manager) UnregisterChannel(name string) error {
|
||||
m.access.Lock()
|
||||
@@ -226,9 +166,9 @@ func (m *Manager) GetAllOnlineUsers() []string {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
usersOnline := make([]string, 0, len(m.onlineMaps))
|
||||
for user, om := range m.onlineMaps {
|
||||
if om.Count() > 0 {
|
||||
usersOnline := make([]string, 0, len(m.onlineMap))
|
||||
for user, onlineMap := range m.onlineMap {
|
||||
if onlineMap.Count() > 0 {
|
||||
usersOnline = append(usersOnline, user)
|
||||
}
|
||||
}
|
||||
@@ -258,10 +198,6 @@ func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.running = false
|
||||
for name := range m.onlineMaps {
|
||||
errors.LogDebug(context.Background(), "remove OnlineMap ", name)
|
||||
delete(m.onlineMaps, name)
|
||||
}
|
||||
errs := []error{}
|
||||
for name, channel := range m.channels {
|
||||
errors.LogDebug(context.Background(), "remove channel ", name)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestInterface(t *testing.T) {
|
||||
_ = stats.Manager(new(Manager))
|
||||
_ = (stats.Manager)(new(Manager))
|
||||
}
|
||||
|
||||
func TestStatsChannelRunnable(t *testing.T) {
|
||||
|
||||
@@ -2,11 +2,10 @@ package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ func NewReader(reader io.Reader) Reader {
|
||||
}
|
||||
|
||||
_, isFile := reader.(*os.File)
|
||||
if !isFile && useReadV() {
|
||||
if !isFile && useReadv {
|
||||
if sc, ok := reader.(syscall.Conn); ok {
|
||||
rawConn, err := sc.SyscallConn()
|
||||
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.
|
||||
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
|
||||
n := len(dest)
|
||||
if n > 0 && !dest[n-1].IsFull() {
|
||||
nBytes, _ := dest[n-1].Write(src)
|
||||
if n > 0 && !(dest)[n-1].IsFull() {
|
||||
nBytes, _ := (dest)[n-1].Write(src)
|
||||
src = src[nBytes:]
|
||||
}
|
||||
|
||||
|
||||
@@ -121,11 +121,11 @@ func TestPacketReader_ReadMultiBuffer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReaderInterface(t *testing.T) {
|
||||
_ = io.Reader(new(ReadVReader))
|
||||
_ = Reader(new(ReadVReader))
|
||||
_ = (io.Reader)(new(ReadVReader))
|
||||
_ = (Reader)(new(ReadVReader))
|
||||
|
||||
_ = Reader(new(BufferedReader))
|
||||
_ = io.Reader(new(BufferedReader))
|
||||
_ = io.ByteReader(new(BufferedReader))
|
||||
_ = io.WriterTo(new(BufferedReader))
|
||||
_ = (Reader)(new(BufferedReader))
|
||||
_ = (io.Reader)(new(BufferedReader))
|
||||
_ = (io.ByteReader)(new(BufferedReader))
|
||||
_ = (io.WriterTo)(new(BufferedReader))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func (r *posixReader) Init(bs []*Buffer) {
|
||||
}
|
||||
for idx, b := range bs {
|
||||
iovecs = append(iovecs, syscall.Iovec{
|
||||
Base: &b.v[0],
|
||||
Base: &(b.v[0]),
|
||||
})
|
||||
iovecs[idx].SetLen(int(Size))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
@@ -144,24 +143,13 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
var useReadv atomic.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
|
||||
}
|
||||
var useReadv bool
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func useReadV() bool {
|
||||
return false
|
||||
}
|
||||
const useReadv = false
|
||||
|
||||
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
|
||||
panic("not implemented")
|
||||
|
||||
@@ -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() }
|
||||
@@ -1,238 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
type DomainMatcher interface {
|
||||
// Match returns the indices of all rules that match the input domain.
|
||||
// The returned slice is owned by the caller and may be safely modified.
|
||||
// Note: the slice may contain duplicates and the order is unspecified.
|
||||
Match(input string) []uint32
|
||||
|
||||
MatchAny(input string) bool
|
||||
}
|
||||
|
||||
type DomainMatcherFactory interface {
|
||||
BuildMatcher(rules []*DomainRule) (DomainMatcher, error)
|
||||
}
|
||||
|
||||
type MphDomainMatcherFactory struct {
|
||||
sync.Mutex
|
||||
shared *utils.WeakCacheMap[string, strmatcher.MphValueMatcher]
|
||||
}
|
||||
|
||||
func buildDomainRulesKey(rules []*DomainRule) string {
|
||||
var sb strings.Builder
|
||||
cache := false
|
||||
for _, r := range rules {
|
||||
switch v := r.Value.(type) {
|
||||
case *DomainRule_Custom:
|
||||
sb.WriteString(v.Custom.Type.String())
|
||||
sb.WriteString(":")
|
||||
sb.WriteString(v.Custom.Value)
|
||||
sb.WriteString(",")
|
||||
case *DomainRule_Geosite:
|
||||
cache = true
|
||||
sb.WriteString(v.Geosite.File)
|
||||
sb.WriteString(":")
|
||||
sb.WriteString(v.Geosite.Code)
|
||||
sb.WriteString("@")
|
||||
sb.WriteString(v.Geosite.Attrs)
|
||||
sb.WriteString(",")
|
||||
default:
|
||||
panic("unknown domain rule type")
|
||||
}
|
||||
}
|
||||
if !cache {
|
||||
return ""
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// BuildMatcher implements DomainMatcherFactory.
|
||||
func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||
if len(rules) == 0 {
|
||||
return nil, errors.New("empty domain rule list")
|
||||
}
|
||||
key := buildDomainRulesKey(rules)
|
||||
if key != "" {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
if g, ok := f.shared.Load(key); ok {
|
||||
errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
|
||||
return g, nil
|
||||
}
|
||||
errors.LogDebug(context.Background(), "geodata mph domain matcher cache MISS for ", len(rules), " rules")
|
||||
}
|
||||
g := strmatcher.NewMphValueMatcher()
|
||||
for i, r := range rules {
|
||||
switch v := r.Value.(type) {
|
||||
case *DomainRule_Custom:
|
||||
m, err := parseDomain(v.Custom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Add(m, uint32(i))
|
||||
case *DomainRule_Geosite:
|
||||
domains, err := loadSiteWithAttrs(v.Geosite.File, v.Geosite.Code, v.Geosite.Attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for j, d := range domains {
|
||||
domains[j] = nil // peak mem
|
||||
m, err := parseDomain(d)
|
||||
if err != nil {
|
||||
errors.LogError(context.Background(), "ignore invalid geosite entry in ", v.Geosite.File, ":", v.Geosite.Code, " at index ", j, ", ", err)
|
||||
continue
|
||||
}
|
||||
g.Add(m, uint32(i))
|
||||
}
|
||||
default:
|
||||
panic("unknown domain rule type")
|
||||
}
|
||||
}
|
||||
if err := g.Build(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key != "" {
|
||||
f.shared.Store(key, g)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
type CompactDomainMatcherFactory struct {
|
||||
sync.Mutex
|
||||
shared *utils.WeakCacheMap[string, strmatcher.LinearAnyMatcher]
|
||||
}
|
||||
|
||||
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
|
||||
key := rule.File + ":" + rule.Code + "@" + rule.Attrs
|
||||
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if s, ok := f.shared.Load(key); ok {
|
||||
errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
|
||||
return s, nil
|
||||
}
|
||||
errors.LogDebug(context.Background(), "geodata geosite matcher cache MISS ", key)
|
||||
|
||||
s := strmatcher.NewLinearAnyMatcher()
|
||||
domains, err := loadSiteWithAttrs(rule.File, rule.Code, rule.Attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, d := range domains {
|
||||
domains[i] = nil // peak mem
|
||||
m, err := parseDomain(d)
|
||||
if err != nil {
|
||||
errors.LogError(context.Background(), "ignore invalid geosite entry in ", rule.File, ":", rule.Code, " at index ", i, ", ", err)
|
||||
continue
|
||||
}
|
||||
s.Add(m)
|
||||
}
|
||||
f.shared.Store(key, s)
|
||||
return s, err
|
||||
}
|
||||
|
||||
// BuildMatcher implements DomainMatcherFactory.
|
||||
func (f *CompactDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||
if len(rules) == 0 {
|
||||
return nil, errors.New("empty domain rule list")
|
||||
}
|
||||
compact := &CompactDomainMatcher{
|
||||
matchers: make([]strmatcher.MatcherSet, 0, len(rules)),
|
||||
values: make([]uint32, 0, len(rules)),
|
||||
}
|
||||
for i, r := range rules {
|
||||
switch v := r.Value.(type) {
|
||||
case *DomainRule_Custom:
|
||||
m, err := parseDomain(v.Custom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if compact.custom == nil {
|
||||
compact.custom = strmatcher.NewLinearValueMatcher()
|
||||
}
|
||||
compact.custom.Add(m, uint32(i))
|
||||
case *DomainRule_Geosite:
|
||||
m, err := f.getOrCreateFrom(v.Geosite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
compact.matchers = append(compact.matchers, m)
|
||||
compact.values = append(compact.values, uint32(i))
|
||||
default:
|
||||
panic("unknown domain rule type")
|
||||
}
|
||||
}
|
||||
return compact, nil
|
||||
}
|
||||
|
||||
type CompactDomainMatcher struct {
|
||||
custom strmatcher.ValueMatcher
|
||||
matchers []strmatcher.MatcherSet
|
||||
values []uint32
|
||||
}
|
||||
|
||||
// Match implements DomainMatcher.
|
||||
func (c *CompactDomainMatcher) Match(input string) []uint32 {
|
||||
var result []uint32
|
||||
if c.custom != nil {
|
||||
result = append(result, c.custom.Match(input)...)
|
||||
}
|
||||
for i, m := range c.matchers {
|
||||
if m.MatchAny(input) {
|
||||
result = append(result, c.values[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MatchAny implements DomainMatcher.
|
||||
func (c *CompactDomainMatcher) MatchAny(input string) bool {
|
||||
if c.custom != nil && c.custom.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
for _, m := range c.matchers {
|
||||
if m.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseDomain(d *Domain) (strmatcher.Matcher, error) {
|
||||
if d == nil {
|
||||
return nil, errors.New("domain must not be nil")
|
||||
}
|
||||
switch d.Type {
|
||||
case Domain_Substr:
|
||||
return strmatcher.Substr.New(strings.ToLower(d.Value))
|
||||
case Domain_Regex:
|
||||
return strmatcher.Regex.New(d.Value)
|
||||
case Domain_Domain:
|
||||
return strmatcher.Domain.New(strings.ToLower(d.Value))
|
||||
case Domain_Full:
|
||||
return strmatcher.Full.New(strings.ToLower(d.Value))
|
||||
default:
|
||||
return nil, errors.New("unknown domain type: ", d.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func newDomainMatcherFactory() DomainMatcherFactory {
|
||||
switch runtime.GOOS {
|
||||
case "ios", "android":
|
||||
return &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
default:
|
||||
return &MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
||||
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
got := matcher.Match("example.com")
|
||||
slices.Sort(got)
|
||||
|
||||
want := []uint32{0, 1}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("Match() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
got := matcher.Match("163.com")
|
||||
slices.Sort(got)
|
||||
|
||||
want := []uint32{0, 1}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("Match() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
|
||||
matcher, err := (&MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}).
|
||||
BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
got := matcher.Match("example.com")
|
||||
if !reflect.DeepEqual(got, []uint32{0, 1}) {
|
||||
t.Fatalf("Match() = %v, want %v", got, []uint32{0, 1})
|
||||
}
|
||||
|
||||
got[0] = 1
|
||||
|
||||
gotAgain := matcher.Match("example.com")
|
||||
if !reflect.DeepEqual(gotAgain, []uint32{0, 1}) {
|
||||
t.Fatalf("Match() after caller mutation = %v, want %v", gotAgain, []uint32{0, 1})
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
type DomainRegistry struct {
|
||||
mu sync.Mutex
|
||||
factory DomainMatcherFactory
|
||||
matchers *utils.WeakCacheMap[uuid.UUID, DynamicDomainMatcher]
|
||||
}
|
||||
|
||||
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
m, err := r.factory.BuildMatcher(rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := NewDynamicDomainMatcher(rules, m)
|
||||
r.matchers.Store(uuid.New(), d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (r *DomainRegistry) Reload() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
var matchers []*DynamicDomainMatcher
|
||||
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicDomainMatcher) bool {
|
||||
matchers = append(matchers, matcher)
|
||||
return true
|
||||
})
|
||||
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(matchers), " domain matcher(s)")
|
||||
|
||||
factory := newDomainMatcherFactory()
|
||||
type reloadEntry struct {
|
||||
dynamic *DynamicDomainMatcher
|
||||
matcher DomainMatcher
|
||||
}
|
||||
reloaded := make([]reloadEntry, len(matchers))
|
||||
for i, d := range matchers {
|
||||
m, err := factory.BuildMatcher(d.rules)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i)
|
||||
return err
|
||||
}
|
||||
reloaded[i] = reloadEntry{dynamic: d, matcher: m}
|
||||
}
|
||||
for _, entry := range reloaded {
|
||||
entry.dynamic.Reload(entry.matcher)
|
||||
}
|
||||
r.factory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(matchers), " domain matcher(s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDomainRegistry() *DomainRegistry {
|
||||
return &DomainRegistry{
|
||||
factory: newDomainMatcherFactory(),
|
||||
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicDomainMatcher](),
|
||||
}
|
||||
}
|
||||
|
||||
var DomainReg = newDomainRegistry()
|
||||
|
||||
type domainMatcherState struct {
|
||||
matcher DomainMatcher
|
||||
}
|
||||
|
||||
type DynamicDomainMatcher struct {
|
||||
rules []*DomainRule
|
||||
state atomic.Pointer[domainMatcherState]
|
||||
}
|
||||
|
||||
// Match implements DomainMatcher.
|
||||
func (d *DynamicDomainMatcher) Match(input string) []uint32 {
|
||||
return d.state.Load().matcher.Match(input)
|
||||
}
|
||||
|
||||
// MatchAny implements DomainMatcher.
|
||||
func (d *DynamicDomainMatcher) MatchAny(input string) bool {
|
||||
return d.state.Load().matcher.MatchAny(input)
|
||||
}
|
||||
|
||||
func (d *DynamicDomainMatcher) Reload(newMatcher DomainMatcher) {
|
||||
d.state.Store(&domainMatcherState{matcher: newMatcher})
|
||||
}
|
||||
|
||||
func NewDynamicDomainMatcher(rules []*DomainRule, matcher DomainMatcher) *DynamicDomainMatcher {
|
||||
d := &DynamicDomainMatcher{rules: rules}
|
||||
d.Reload(matcher)
|
||||
return d
|
||||
}
|
||||
@@ -1,965 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// source: common/geodata/geodat.proto
|
||||
|
||||
package geodata
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// Type of domain value.
|
||||
type Domain_Type int32
|
||||
|
||||
const (
|
||||
// The value is used as a sub string.
|
||||
Domain_Substr Domain_Type = 0
|
||||
// The value is used as a regular expression.
|
||||
Domain_Regex Domain_Type = 1
|
||||
// The value is a domain.
|
||||
Domain_Domain Domain_Type = 2
|
||||
// The value is a full domain.
|
||||
Domain_Full Domain_Type = 3
|
||||
)
|
||||
|
||||
// Enum value maps for Domain_Type.
|
||||
var (
|
||||
Domain_Type_name = map[int32]string{
|
||||
0: "Substr",
|
||||
1: "Regex",
|
||||
2: "Domain",
|
||||
3: "Full",
|
||||
}
|
||||
Domain_Type_value = map[string]int32{
|
||||
"Substr": 0,
|
||||
"Regex": 1,
|
||||
"Domain": 2,
|
||||
"Full": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Domain_Type) Enum() *Domain_Type {
|
||||
p := new(Domain_Type)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Domain_Type) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Domain_Type) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_common_geodata_geodat_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Domain_Type) Type() protoreflect.EnumType {
|
||||
return &file_common_geodata_geodat_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Domain_Type) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Domain_Type.Descriptor instead.
|
||||
func (Domain_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Domain matching type.
|
||||
Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.geodata.Domain_Type" json:"type,omitempty"`
|
||||
// Domain value.
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
// Attributes of this domain. May be used for filtering.
|
||||
Attribute []*Domain_Attribute `protobuf:"bytes,3,rep,name=attribute,proto3" json:"attribute,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Domain) Reset() {
|
||||
*x = Domain{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Domain) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Domain) ProtoMessage() {}
|
||||
|
||||
func (x *Domain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
|
||||
func (*Domain) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Domain) GetType() Domain_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return Domain_Substr
|
||||
}
|
||||
|
||||
func (x *Domain) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Domain) GetAttribute() []*Domain_Attribute {
|
||||
if x != nil {
|
||||
return x.Attribute
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GeoSite struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
|
||||
Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoSite) Reset() {
|
||||
*x = GeoSite{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoSite) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoSite) ProtoMessage() {}
|
||||
|
||||
func (x *GeoSite) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoSite.ProtoReflect.Descriptor instead.
|
||||
func (*GeoSite) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *GeoSite) GetCode() string {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoSite) GetDomain() []*Domain {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GeoSiteList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Entry []*GeoSite `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoSiteList) Reset() {
|
||||
*x = GeoSiteList{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoSiteList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoSiteList) ProtoMessage() {}
|
||||
|
||||
func (x *GeoSiteList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead.
|
||||
func (*GeoSiteList) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GeoSiteList) GetEntry() []*GeoSite {
|
||||
if x != nil {
|
||||
return x.Entry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GeoSiteRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
File string `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`
|
||||
Attrs string `protobuf:"bytes,3,opt,name=attrs,proto3" json:"attrs,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoSiteRule) Reset() {
|
||||
*x = GeoSiteRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoSiteRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoSiteRule) ProtoMessage() {}
|
||||
|
||||
func (x *GeoSiteRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoSiteRule.ProtoReflect.Descriptor instead.
|
||||
func (*GeoSiteRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *GeoSiteRule) GetFile() string {
|
||||
if x != nil {
|
||||
return x.File
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoSiteRule) GetCode() string {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoSiteRule) GetAttrs() string {
|
||||
if x != nil {
|
||||
return x.Attrs
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DomainRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Value:
|
||||
//
|
||||
// *DomainRule_Geosite
|
||||
// *DomainRule_Custom
|
||||
Value isDomainRule_Value `protobuf_oneof:"value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DomainRule) Reset() {
|
||||
*x = DomainRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DomainRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DomainRule) ProtoMessage() {}
|
||||
|
||||
func (x *DomainRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DomainRule.ProtoReflect.Descriptor instead.
|
||||
func (*DomainRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *DomainRule) GetValue() isDomainRule_Value {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DomainRule) GetGeosite() *GeoSiteRule {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*DomainRule_Geosite); ok {
|
||||
return x.Geosite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DomainRule) GetCustom() *Domain {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*DomainRule_Custom); ok {
|
||||
return x.Custom
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isDomainRule_Value interface {
|
||||
isDomainRule_Value()
|
||||
}
|
||||
|
||||
type DomainRule_Geosite struct {
|
||||
Geosite *GeoSiteRule `protobuf:"bytes,1,opt,name=geosite,proto3,oneof"`
|
||||
}
|
||||
|
||||
type DomainRule_Custom struct {
|
||||
Custom *Domain `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*DomainRule_Geosite) isDomainRule_Value() {}
|
||||
|
||||
func (*DomainRule_Custom) isDomainRule_Value() {}
|
||||
|
||||
type CIDR struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// IP address, should be either 4 or 16 bytes.
|
||||
Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
// Number of leading ones in the network mask.
|
||||
Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CIDR) Reset() {
|
||||
*x = CIDR{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *CIDR) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CIDR) ProtoMessage() {}
|
||||
|
||||
func (x *CIDR) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CIDR.ProtoReflect.Descriptor instead.
|
||||
func (*CIDR) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *CIDR) GetIp() []byte {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CIDR) GetPrefix() uint32 {
|
||||
if x != nil {
|
||||
return x.Prefix
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type CIDRRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Cidr *CIDR `protobuf:"bytes,1,opt,name=cidr,proto3" json:"cidr,omitempty"`
|
||||
ReverseMatch bool `protobuf:"varint,2,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CIDRRule) Reset() {
|
||||
*x = CIDRRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *CIDRRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CIDRRule) ProtoMessage() {}
|
||||
|
||||
func (x *CIDRRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CIDRRule.ProtoReflect.Descriptor instead.
|
||||
func (*CIDRRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *CIDRRule) GetCidr() *CIDR {
|
||||
if x != nil {
|
||||
return x.Cidr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CIDRRule) GetReverseMatch() bool {
|
||||
if x != nil {
|
||||
return x.ReverseMatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type GeoIP struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
|
||||
Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"`
|
||||
ReverseMatch bool `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoIP) Reset() {
|
||||
*x = GeoIP{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoIP) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoIP) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIP) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIP) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetCode() string {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetCidr() []*CIDR {
|
||||
if x != nil {
|
||||
return x.Cidr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetReverseMatch() bool {
|
||||
if x != nil {
|
||||
return x.ReverseMatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type GeoIPList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoIPList) Reset() {
|
||||
*x = GeoIPList{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoIPList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoIPList) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIPList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIPList) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *GeoIPList) GetEntry() []*GeoIP {
|
||||
if x != nil {
|
||||
return x.Entry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GeoIPRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
File string `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`
|
||||
ReverseMatch bool `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoIPRule) Reset() {
|
||||
*x = GeoIPRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GeoIPRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GeoIPRule) ProtoMessage() {}
|
||||
|
||||
func (x *GeoIPRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GeoIPRule.ProtoReflect.Descriptor instead.
|
||||
func (*GeoIPRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *GeoIPRule) GetFile() string {
|
||||
if x != nil {
|
||||
return x.File
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoIPRule) GetCode() string {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GeoIPRule) GetReverseMatch() bool {
|
||||
if x != nil {
|
||||
return x.ReverseMatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type IPRule struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Value:
|
||||
//
|
||||
// *IPRule_Geoip
|
||||
// *IPRule_Custom
|
||||
Value isIPRule_Value `protobuf_oneof:"value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *IPRule) Reset() {
|
||||
*x = IPRule{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *IPRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IPRule) ProtoMessage() {}
|
||||
|
||||
func (x *IPRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IPRule.ProtoReflect.Descriptor instead.
|
||||
func (*IPRule) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *IPRule) GetValue() isIPRule_Value {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IPRule) GetGeoip() *GeoIPRule {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*IPRule_Geoip); ok {
|
||||
return x.Geoip
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IPRule) GetCustom() *CIDRRule {
|
||||
if x != nil {
|
||||
if x, ok := x.Value.(*IPRule_Custom); ok {
|
||||
return x.Custom
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isIPRule_Value interface {
|
||||
isIPRule_Value()
|
||||
}
|
||||
|
||||
type IPRule_Geoip struct {
|
||||
Geoip *GeoIPRule `protobuf:"bytes,1,opt,name=geoip,proto3,oneof"`
|
||||
}
|
||||
|
||||
type IPRule_Custom struct {
|
||||
Custom *CIDRRule `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*IPRule_Geoip) isIPRule_Value() {}
|
||||
|
||||
func (*IPRule_Custom) isIPRule_Value() {}
|
||||
|
||||
type Domain_Attribute struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
// Types that are valid to be assigned to TypedValue:
|
||||
//
|
||||
// *Domain_Attribute_BoolValue
|
||||
// *Domain_Attribute_IntValue
|
||||
TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) Reset() {
|
||||
*x = Domain_Attribute{}
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Domain_Attribute) ProtoMessage() {}
|
||||
|
||||
func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_geodata_geodat_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead.
|
||||
func (*Domain_Attribute) Descriptor() ([]byte, []int) {
|
||||
return file_common_geodata_geodat_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue {
|
||||
if x != nil {
|
||||
return x.TypedValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetBoolValue() bool {
|
||||
if x != nil {
|
||||
if x, ok := x.TypedValue.(*Domain_Attribute_BoolValue); ok {
|
||||
return x.BoolValue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetIntValue() int64 {
|
||||
if x != nil {
|
||||
if x, ok := x.TypedValue.(*Domain_Attribute_IntValue); ok {
|
||||
return x.IntValue
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type isDomain_Attribute_TypedValue interface {
|
||||
isDomain_Attribute_TypedValue()
|
||||
}
|
||||
|
||||
type Domain_Attribute_BoolValue struct {
|
||||
BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Domain_Attribute_IntValue struct {
|
||||
IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {}
|
||||
|
||||
func (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {}
|
||||
|
||||
var File_common_geodata_geodat_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_common_geodata_geodat_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bcommon/geodata/geodat.proto\x12\x13xray.common.geodata\"\xbc\x02\n" +
|
||||
"\x06Domain\x124\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2 .xray.common.geodata.Domain.TypeR\x04type\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value\x12C\n" +
|
||||
"\tattribute\x18\x03 \x03(\v2%.xray.common.geodata.Domain.AttributeR\tattribute\x1al\n" +
|
||||
"\tAttribute\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x1f\n" +
|
||||
"\n" +
|
||||
"bool_value\x18\x02 \x01(\bH\x00R\tboolValue\x12\x1d\n" +
|
||||
"\tint_value\x18\x03 \x01(\x03H\x00R\bintValueB\r\n" +
|
||||
"\vtyped_value\"3\n" +
|
||||
"\x04Type\x12\n" +
|
||||
"\n" +
|
||||
"\x06Substr\x10\x00\x12\t\n" +
|
||||
"\x05Regex\x10\x01\x12\n" +
|
||||
"\n" +
|
||||
"\x06Domain\x10\x02\x12\b\n" +
|
||||
"\x04Full\x10\x03\"R\n" +
|
||||
"\aGeoSite\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\tR\x04code\x123\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x1b.xray.common.geodata.DomainR\x06domain\"A\n" +
|
||||
"\vGeoSiteList\x122\n" +
|
||||
"\x05entry\x18\x01 \x03(\v2\x1c.xray.common.geodata.GeoSiteR\x05entry\"K\n" +
|
||||
"\vGeoSiteRule\x12\x12\n" +
|
||||
"\x04file\x18\x01 \x01(\tR\x04file\x12\x12\n" +
|
||||
"\x04code\x18\x02 \x01(\tR\x04code\x12\x14\n" +
|
||||
"\x05attrs\x18\x03 \x01(\tR\x05attrs\"\x8a\x01\n" +
|
||||
"\n" +
|
||||
"DomainRule\x12<\n" +
|
||||
"\ageosite\x18\x01 \x01(\v2 .xray.common.geodata.GeoSiteRuleH\x00R\ageosite\x125\n" +
|
||||
"\x06custom\x18\x02 \x01(\v2\x1b.xray.common.geodata.DomainH\x00R\x06customB\a\n" +
|
||||
"\x05value\".\n" +
|
||||
"\x04CIDR\x12\x0e\n" +
|
||||
"\x02ip\x18\x01 \x01(\fR\x02ip\x12\x16\n" +
|
||||
"\x06prefix\x18\x02 \x01(\rR\x06prefix\"^\n" +
|
||||
"\bCIDRRule\x12-\n" +
|
||||
"\x04cidr\x18\x01 \x01(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
|
||||
"\rreverse_match\x18\x02 \x01(\bR\freverseMatch\"o\n" +
|
||||
"\x05GeoIP\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\tR\x04code\x12-\n" +
|
||||
"\x04cidr\x18\x02 \x03(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
|
||||
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"=\n" +
|
||||
"\tGeoIPList\x120\n" +
|
||||
"\x05entry\x18\x01 \x03(\v2\x1a.xray.common.geodata.GeoIPR\x05entry\"X\n" +
|
||||
"\tGeoIPRule\x12\x12\n" +
|
||||
"\x04file\x18\x01 \x01(\tR\x04file\x12\x12\n" +
|
||||
"\x04code\x18\x02 \x01(\tR\x04code\x12#\n" +
|
||||
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"\x82\x01\n" +
|
||||
"\x06IPRule\x126\n" +
|
||||
"\x05geoip\x18\x01 \x01(\v2\x1e.xray.common.geodata.GeoIPRuleH\x00R\x05geoip\x127\n" +
|
||||
"\x06custom\x18\x02 \x01(\v2\x1d.xray.common.geodata.CIDRRuleH\x00R\x06customB\a\n" +
|
||||
"\x05valueB[\n" +
|
||||
"\x17com.xray.common.geodataP\x01Z(github.com/xtls/xray-core/common/geodata\xaa\x02\x13Xray.Common.Geodatab\x06proto3"
|
||||
|
||||
var (
|
||||
file_common_geodata_geodat_proto_rawDescOnce sync.Once
|
||||
file_common_geodata_geodat_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_common_geodata_geodat_proto_rawDescGZIP() []byte {
|
||||
file_common_geodata_geodat_proto_rawDescOnce.Do(func() {
|
||||
file_common_geodata_geodat_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_geodata_geodat_proto_rawDesc), len(file_common_geodata_geodat_proto_rawDesc)))
|
||||
})
|
||||
return file_common_geodata_geodat_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_common_geodata_geodat_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_common_geodata_geodat_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
||||
var file_common_geodata_geodat_proto_goTypes = []any{
|
||||
(Domain_Type)(0), // 0: xray.common.geodata.Domain.Type
|
||||
(*Domain)(nil), // 1: xray.common.geodata.Domain
|
||||
(*GeoSite)(nil), // 2: xray.common.geodata.GeoSite
|
||||
(*GeoSiteList)(nil), // 3: xray.common.geodata.GeoSiteList
|
||||
(*GeoSiteRule)(nil), // 4: xray.common.geodata.GeoSiteRule
|
||||
(*DomainRule)(nil), // 5: xray.common.geodata.DomainRule
|
||||
(*CIDR)(nil), // 6: xray.common.geodata.CIDR
|
||||
(*CIDRRule)(nil), // 7: xray.common.geodata.CIDRRule
|
||||
(*GeoIP)(nil), // 8: xray.common.geodata.GeoIP
|
||||
(*GeoIPList)(nil), // 9: xray.common.geodata.GeoIPList
|
||||
(*GeoIPRule)(nil), // 10: xray.common.geodata.GeoIPRule
|
||||
(*IPRule)(nil), // 11: xray.common.geodata.IPRule
|
||||
(*Domain_Attribute)(nil), // 12: xray.common.geodata.Domain.Attribute
|
||||
}
|
||||
var file_common_geodata_geodat_proto_depIdxs = []int32{
|
||||
0, // 0: xray.common.geodata.Domain.type:type_name -> xray.common.geodata.Domain.Type
|
||||
12, // 1: xray.common.geodata.Domain.attribute:type_name -> xray.common.geodata.Domain.Attribute
|
||||
1, // 2: xray.common.geodata.GeoSite.domain:type_name -> xray.common.geodata.Domain
|
||||
2, // 3: xray.common.geodata.GeoSiteList.entry:type_name -> xray.common.geodata.GeoSite
|
||||
4, // 4: xray.common.geodata.DomainRule.geosite:type_name -> xray.common.geodata.GeoSiteRule
|
||||
1, // 5: xray.common.geodata.DomainRule.custom:type_name -> xray.common.geodata.Domain
|
||||
6, // 6: xray.common.geodata.CIDRRule.cidr:type_name -> xray.common.geodata.CIDR
|
||||
6, // 7: xray.common.geodata.GeoIP.cidr:type_name -> xray.common.geodata.CIDR
|
||||
8, // 8: xray.common.geodata.GeoIPList.entry:type_name -> xray.common.geodata.GeoIP
|
||||
10, // 9: xray.common.geodata.IPRule.geoip:type_name -> xray.common.geodata.GeoIPRule
|
||||
7, // 10: xray.common.geodata.IPRule.custom:type_name -> xray.common.geodata.CIDRRule
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_common_geodata_geodat_proto_init() }
|
||||
func file_common_geodata_geodat_proto_init() {
|
||||
if File_common_geodata_geodat_proto != nil {
|
||||
return
|
||||
}
|
||||
file_common_geodata_geodat_proto_msgTypes[4].OneofWrappers = []any{
|
||||
(*DomainRule_Geosite)(nil),
|
||||
(*DomainRule_Custom)(nil),
|
||||
}
|
||||
file_common_geodata_geodat_proto_msgTypes[10].OneofWrappers = []any{
|
||||
(*IPRule_Geoip)(nil),
|
||||
(*IPRule_Custom)(nil),
|
||||
}
|
||||
file_common_geodata_geodat_proto_msgTypes[11].OneofWrappers = []any{
|
||||
(*Domain_Attribute_BoolValue)(nil),
|
||||
(*Domain_Attribute_IntValue)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_geodata_geodat_proto_rawDesc), len(file_common_geodata_geodat_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 12,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_common_geodata_geodat_proto_goTypes,
|
||||
DependencyIndexes: file_common_geodata_geodat_proto_depIdxs,
|
||||
EnumInfos: file_common_geodata_geodat_proto_enumTypes,
|
||||
MessageInfos: file_common_geodata_geodat_proto_msgTypes,
|
||||
}.Build()
|
||||
File_common_geodata_geodat_proto = out.File
|
||||
file_common_geodata_geodat_proto_goTypes = nil
|
||||
file_common_geodata_geodat_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.common.geodata;
|
||||
option csharp_namespace = "Xray.Common.Geodata";
|
||||
option go_package = "github.com/xtls/xray-core/common/geodata";
|
||||
option java_package = "com.xray.common.geodata";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message Domain {
|
||||
// Type of domain value.
|
||||
enum Type {
|
||||
// The value is used as a sub string.
|
||||
Substr = 0;
|
||||
// The value is used as a regular expression.
|
||||
Regex = 1;
|
||||
// The value is a domain.
|
||||
Domain = 2;
|
||||
// The value is a full domain.
|
||||
Full = 3;
|
||||
}
|
||||
// Domain matching type.
|
||||
Type type = 1;
|
||||
|
||||
// Domain value.
|
||||
string value = 2;
|
||||
|
||||
message Attribute {
|
||||
string key = 1;
|
||||
|
||||
oneof typed_value {
|
||||
bool bool_value = 2;
|
||||
int64 int_value = 3;
|
||||
}
|
||||
}
|
||||
// Attributes of this domain. May be used for filtering.
|
||||
repeated Attribute attribute = 3;
|
||||
}
|
||||
|
||||
message GeoSite {
|
||||
string code = 1;
|
||||
repeated Domain domain = 2;
|
||||
}
|
||||
|
||||
message GeoSiteList {
|
||||
repeated GeoSite entry = 1;
|
||||
}
|
||||
|
||||
message GeoSiteRule {
|
||||
string file = 1;
|
||||
string code = 2;
|
||||
string attrs = 3;
|
||||
}
|
||||
|
||||
message DomainRule {
|
||||
oneof value {
|
||||
GeoSiteRule geosite = 1;
|
||||
Domain custom = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message CIDR {
|
||||
// IP address, should be either 4 or 16 bytes.
|
||||
bytes ip = 1;
|
||||
|
||||
// Number of leading ones in the network mask.
|
||||
uint32 prefix = 2;
|
||||
}
|
||||
|
||||
message CIDRRule {
|
||||
CIDR cidr = 1;
|
||||
bool reverse_match = 2;
|
||||
}
|
||||
|
||||
message GeoIP {
|
||||
string code = 1;
|
||||
repeated CIDR cidr = 2;
|
||||
bool reverse_match = 3;
|
||||
}
|
||||
|
||||
message GeoIPList {
|
||||
repeated GeoIP entry = 1;
|
||||
}
|
||||
|
||||
message GeoIPRule {
|
||||
string file = 1;
|
||||
string code = 2;
|
||||
bool reverse_match = 3;
|
||||
}
|
||||
|
||||
message IPRule {
|
||||
oneof value {
|
||||
GeoIPRule geoip = 1;
|
||||
CIDRRule custom = 2;
|
||||
}
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func checkFile(file, code string) error {
|
||||
r, err := filesystem.OpenAsset(file)
|
||||
if err != nil {
|
||||
return errors.New("failed to open ", file).Base(err)
|
||||
}
|
||||
defer r.Close()
|
||||
if _, err := find(r, []byte(code), false); err != nil {
|
||||
return errors.New("failed to check code ", code, " from ", file).Base(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadFile(file, code string) ([]byte, error) {
|
||||
runtime.GC() // peak mem
|
||||
r, err := filesystem.OpenAsset(file)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to open ", file).Base(err)
|
||||
}
|
||||
defer r.Close()
|
||||
bs, err := find(r, []byte(code), true)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load code ", code, " from ", file).Base(err)
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func loadIP(file, code string) ([]*CIDR, error) {
|
||||
bs, err := loadFile(file, code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer runtime.GC() // peak mem
|
||||
var geoip GeoIP
|
||||
if err := proto.Unmarshal(bs, &geoip); err != nil {
|
||||
return nil, errors.New("error unmarshal IP in ", file, ":", code).Base(err)
|
||||
}
|
||||
return geoip.Cidr, nil
|
||||
}
|
||||
|
||||
func loadSite(file, code string) ([]*Domain, error) {
|
||||
bs, err := loadFile(file, code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer runtime.GC() // peak mem
|
||||
var geosite GeoSite
|
||||
if err := proto.Unmarshal(bs, &geosite); err != nil {
|
||||
return nil, errors.New("error unmarshal Site in ", file, ":", code).Base(err)
|
||||
}
|
||||
return geosite.Domain, nil
|
||||
}
|
||||
|
||||
func decodeVarint(br *bufio.Reader) (uint64, error) {
|
||||
var x uint64
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
b, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
x |= (uint64(b) & 0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return x, nil
|
||||
}
|
||||
}
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
return 0, errors.New("varint overflow")
|
||||
}
|
||||
|
||||
func find(r io.Reader, code []byte, readBody bool) ([]byte, error) {
|
||||
codeL := len(code)
|
||||
if codeL == 0 {
|
||||
return nil, errors.New("empty code")
|
||||
}
|
||||
|
||||
br := bufio.NewReaderSize(r, 64*1024)
|
||||
need := 2 + codeL // TODO: if code too long
|
||||
prefixBuf := make([]byte, need)
|
||||
|
||||
for {
|
||||
if _, err := br.ReadByte(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x, err := decodeVarint(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyL := int(x)
|
||||
if bodyL <= 0 {
|
||||
return nil, errors.New("invalid body length: ", bodyL)
|
||||
}
|
||||
|
||||
prefixL := bodyL
|
||||
if prefixL > need {
|
||||
prefixL = need
|
||||
}
|
||||
prefix := prefixBuf[:prefixL]
|
||||
if _, err := io.ReadFull(br, prefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
match := false
|
||||
if bodyL >= need {
|
||||
if int(prefix[1]) == codeL && bytes.Equal(prefix[2:need], code) {
|
||||
if !readBody {
|
||||
return nil, nil
|
||||
}
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
remain := bodyL - prefixL
|
||||
if match {
|
||||
out := make([]byte, bodyL)
|
||||
copy(out, prefix)
|
||||
if remain > 0 {
|
||||
if _, err := io.ReadFull(br, out[prefixL:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
if remain > 0 {
|
||||
if _, err := br.Discard(remain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type AttributeMatcher interface {
|
||||
Match(*Domain) bool
|
||||
}
|
||||
|
||||
type HasAttrMatcher string
|
||||
|
||||
// Match reports whether this matcher matches any attribute on the domain.
|
||||
func (m HasAttrMatcher) Match(domain *Domain) bool {
|
||||
for _, attr := range domain.Attribute {
|
||||
if attr.Key == string(m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AllAttrsMatcher struct {
|
||||
matchers []AttributeMatcher
|
||||
}
|
||||
|
||||
// Match reports whether the domain matches every matcher in the list.
|
||||
func (m *AllAttrsMatcher) Match(domain *Domain) bool {
|
||||
for _, matcher := range m.matchers {
|
||||
if !matcher.Match(domain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func NewAllAttrsMatcher(attrs string) AttributeMatcher {
|
||||
if attrs == "" {
|
||||
return nil
|
||||
}
|
||||
m := new(AllAttrsMatcher)
|
||||
for _, attr := range strings.Split(attrs, "@") {
|
||||
m.matchers = append(m.matchers, HasAttrMatcher(attr))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func loadSiteWithAttrs(file, code, attrs string) ([]*Domain, error) {
|
||||
domains, err := loadSite(file, code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matcher := NewAllAttrsMatcher(attrs)
|
||||
if matcher == nil {
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
filtered := make([]*Domain, 0, len(domains))
|
||||
for _, d := range domains {
|
||||
if matcher.Match(d) {
|
||||
filtered = append(filtered, d)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
@@ -1,437 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"net"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
xnet "github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
func buildIPMatcher(rawRules ...string) IPMatcher {
|
||||
rules, err := ParseIPRules(rawRules)
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := newIPRegistry().BuildIPMatcher(rules)
|
||||
common.Must(err)
|
||||
|
||||
return matcher
|
||||
}
|
||||
|
||||
func sortIPStrings(ips []net.IP) []string {
|
||||
output := make([]string, 0, len(ips))
|
||||
for _, ip := range ips {
|
||||
output = append(output, ip.String())
|
||||
}
|
||||
slices.Sort(output)
|
||||
return output
|
||||
}
|
||||
|
||||
func TestIPMatcher(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"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.168.0.0/16",
|
||||
"192.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"8.8.8.8/32",
|
||||
"91.108.4.0/16",
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "192.168.1.1",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.1.0",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "0.1.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "1.0.0.1",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.7",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcherFullCIDR4(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"0.0.0.0/0",
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "192.168.1.1",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "0.0.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "255.255.255.255",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "::0",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcherFullCIDR6(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"::0/0",
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "192.168.1.1",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "0.0.0.0",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "255.255.255.255",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "::0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
Output: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcherRegression(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"98.108.20.0/22",
|
||||
"98.108.20.0/23",
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "98.108.22.11",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "98.108.25.0",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPReverseMatcher(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"8.8.8.8/32",
|
||||
"91.108.4.0/16",
|
||||
)
|
||||
matcher.SetReverse(true)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPReverseMatcher2(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"8.8.8.8/32",
|
||||
"91.108.4.0/16",
|
||||
"fe80::", // Keep IPv6 family non-empty so reverse matching can evaluate IPv6 input.
|
||||
)
|
||||
matcher.SetReverse(true)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPCustomReverseMatcher(t *testing.T) {
|
||||
matcher := buildIPMatcher("!8.8.8.8/32")
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "1.1.1.1",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
if v := matcher.Match(xnet.ParseAddress(test.Input).IP()); v != test.Output {
|
||||
t.Error("unexpected output: ", v, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"8.8.8.8/32",
|
||||
"2001:4860:4860::8888/128",
|
||||
)
|
||||
ip := func(raw string) net.IP {
|
||||
return xnet.ParseAddress(raw).IP()
|
||||
}
|
||||
|
||||
if matcher.AnyMatch(nil) {
|
||||
t.Fatal("expect AnyMatch(nil) to be false")
|
||||
}
|
||||
|
||||
if !matcher.AnyMatch([]net.IP{
|
||||
{},
|
||||
ip("1.1.1.1"),
|
||||
ip("8.8.8.8"),
|
||||
}) {
|
||||
t.Fatal("expect AnyMatch to ignore invalid IPs and return true when one valid IP matches")
|
||||
}
|
||||
|
||||
if matcher.AnyMatch([]net.IP{
|
||||
ip("1.1.1.1"),
|
||||
ip("2001:db8::1"),
|
||||
}) {
|
||||
t.Fatal("expect AnyMatch to be false when no valid IP matches")
|
||||
}
|
||||
|
||||
if !matcher.Matches([]net.IP{
|
||||
ip("8.8.8.8"),
|
||||
ip("2001:4860:4860::8888"),
|
||||
}) {
|
||||
t.Fatal("expect Matches to be true when all valid IPs match")
|
||||
}
|
||||
|
||||
if matcher.Matches([]net.IP{
|
||||
ip("8.8.8.8"),
|
||||
ip("1.1.1.1"),
|
||||
}) {
|
||||
t.Fatal("expect Matches to be false when one valid IP does not match")
|
||||
}
|
||||
|
||||
if matcher.Matches([]net.IP{
|
||||
ip("8.8.8.8"),
|
||||
{},
|
||||
}) {
|
||||
t.Fatal("expect Matches to be false when any IP is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcherFilterIPs(t *testing.T) {
|
||||
matcher := buildIPMatcher(
|
||||
"8.8.8.8/32",
|
||||
"91.108.4.0/16",
|
||||
"2001:4860:4860::8888/128",
|
||||
)
|
||||
ip := func(raw string) net.IP {
|
||||
return xnet.ParseAddress(raw).IP()
|
||||
}
|
||||
|
||||
matched, unmatched := matcher.FilterIPs([]net.IP{
|
||||
{},
|
||||
ip("8.8.8.8"),
|
||||
ip("91.108.255.254"),
|
||||
ip("1.1.1.1"),
|
||||
ip("2001:4860:4860::8888"),
|
||||
ip("2001:db8::1"),
|
||||
})
|
||||
|
||||
wantMatched := []string{
|
||||
"2001:4860:4860::8888",
|
||||
"8.8.8.8",
|
||||
"91.108.255.254",
|
||||
}
|
||||
slices.Sort(wantMatched)
|
||||
if v := sortIPStrings(matched); !reflect.DeepEqual(v, wantMatched) {
|
||||
t.Error("unexpected output: ", v, " want ", wantMatched)
|
||||
}
|
||||
|
||||
wantUnmatched := []string{
|
||||
"1.1.1.1",
|
||||
"2001:db8::1",
|
||||
}
|
||||
slices.Sort(wantUnmatched)
|
||||
if v := sortIPStrings(unmatched); !reflect.DeepEqual(v, wantUnmatched) {
|
||||
t.Error("unexpected output: ", v, " want ", wantUnmatched)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher4CN(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
matcher := buildIPMatcher("geoip:cn")
|
||||
|
||||
if matcher.Match([]byte{8, 8, 8, 8}) {
|
||||
t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatcher6US(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
matcher := buildIPMatcher("geoip:us")
|
||||
|
||||
if !matcher.Match(xnet.ParseAddress("2001:4860:4860::8888").IP()) {
|
||||
t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPMatcher4CN(b *testing.B) {
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
matcher := buildIPMatcher("geoip:cn")
|
||||
ip := net.IP{8, 8, 8, 8}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match(ip)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPMatcher6US(b *testing.B) {
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
matcher := buildIPMatcher("geoip:us")
|
||||
ip := xnet.ParseAddress("2001:4860:4860::8888").IP()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match(ip)
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
type IPRegistry struct {
|
||||
mu sync.Mutex
|
||||
factory *IPSetFactory
|
||||
matchers *utils.WeakCacheMap[uuid.UUID, DynamicIPMatcher]
|
||||
}
|
||||
|
||||
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
m, err := buildOptimizedIPMatcher(r.factory, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := NewDynamicIPMatcher(rules, m)
|
||||
r.matchers.Store(uuid.New(), d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (r *IPRegistry) Reload() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
var matchers []*DynamicIPMatcher
|
||||
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicIPMatcher) bool {
|
||||
matchers = append(matchers, matcher)
|
||||
return true
|
||||
})
|
||||
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(matchers), " IP matcher(s)")
|
||||
|
||||
factory := newIPSetFactory()
|
||||
type reloadEntry struct {
|
||||
dynamic *DynamicIPMatcher
|
||||
matcher IPMatcher
|
||||
}
|
||||
reloaded := make([]reloadEntry, len(matchers))
|
||||
for i, d := range matchers {
|
||||
m, err := buildOptimizedIPMatcher(factory, d.rules)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i)
|
||||
return err
|
||||
}
|
||||
reloaded[i] = reloadEntry{dynamic: d, matcher: m}
|
||||
}
|
||||
for _, entry := range reloaded {
|
||||
entry.dynamic.Reload(entry.matcher)
|
||||
}
|
||||
r.factory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(matchers), " IP matcher(s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newIPRegistry() *IPRegistry {
|
||||
return &IPRegistry{
|
||||
factory: newIPSetFactory(),
|
||||
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicIPMatcher](),
|
||||
}
|
||||
}
|
||||
|
||||
var IPReg = newIPRegistry()
|
||||
|
||||
type ipMatcherState struct {
|
||||
matcher IPMatcher
|
||||
}
|
||||
|
||||
type DynamicIPMatcher struct {
|
||||
rules []*IPRule
|
||||
state atomic.Pointer[ipMatcherState]
|
||||
mu sync.Mutex
|
||||
reverse bool
|
||||
reverseSet bool
|
||||
}
|
||||
|
||||
// Match implements IPMatcher.
|
||||
func (d *DynamicIPMatcher) Match(ip net.IP) bool {
|
||||
return d.state.Load().matcher.Match(ip)
|
||||
}
|
||||
|
||||
// AnyMatch implements IPMatcher.
|
||||
func (d *DynamicIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
return d.state.Load().matcher.AnyMatch(ips)
|
||||
}
|
||||
|
||||
// Matches implements IPMatcher.
|
||||
func (d *DynamicIPMatcher) Matches(ips []net.IP) bool {
|
||||
return d.state.Load().matcher.Matches(ips)
|
||||
}
|
||||
|
||||
// FilterIPs implements IPMatcher.
|
||||
func (d *DynamicIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
return d.state.Load().matcher.FilterIPs(ips)
|
||||
}
|
||||
|
||||
// ToggleReverse implements IPMatcher.
|
||||
func (d *DynamicIPMatcher) ToggleReverse() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.reverse = !d.reverse
|
||||
d.state.Load().matcher.ToggleReverse()
|
||||
}
|
||||
|
||||
// SetReverse implements IPMatcher.
|
||||
func (d *DynamicIPMatcher) SetReverse(reverse bool) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.reverse = reverse
|
||||
d.reverseSet = true
|
||||
d.state.Load().matcher.SetReverse(reverse)
|
||||
}
|
||||
|
||||
func (d *DynamicIPMatcher) Reload(newMatcher IPMatcher) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if d.reverseSet {
|
||||
newMatcher.SetReverse(d.reverse)
|
||||
} else if d.reverse {
|
||||
newMatcher.ToggleReverse()
|
||||
}
|
||||
d.state.Store(&ipMatcherState{matcher: newMatcher})
|
||||
}
|
||||
|
||||
func NewDynamicIPMatcher(rules []*IPRule, matcher IPMatcher) *DynamicIPMatcher {
|
||||
d := &DynamicIPMatcher{rules: rules}
|
||||
d.Reload(matcher)
|
||||
return d
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultGeoIPDat = "geoip.dat"
|
||||
DefaultGeoSiteDat = "geosite.dat"
|
||||
)
|
||||
|
||||
func ParseIPRules(rules []string) ([]*IPRule, error) {
|
||||
var ipRules []*IPRule
|
||||
|
||||
for i, r := range rules {
|
||||
r, reverse := cutReversePrefix(r)
|
||||
|
||||
if strings.HasPrefix(r, "geoip:") {
|
||||
r = "ext:" + DefaultGeoIPDat + ":" + r[len("geoip:"):]
|
||||
}
|
||||
|
||||
prefix := 0
|
||||
for _, ext := range [...]string{"ext:", "ext-ip:"} {
|
||||
if strings.HasPrefix(r, ext) {
|
||||
prefix = len(ext)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var rule isIPRule_Value
|
||||
var err error
|
||||
if prefix > 0 {
|
||||
rule, err = parseGeoIPRule(r[prefix:], reverse)
|
||||
} else {
|
||||
rule, err = parseCustomIPRule(r, reverse)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.New("illegal ip rule: ", rules[i]).Base(err)
|
||||
}
|
||||
ipRules = append(ipRules, &IPRule{Value: rule})
|
||||
}
|
||||
|
||||
return ipRules, nil
|
||||
}
|
||||
|
||||
func cutReversePrefix(s string) (string, bool) {
|
||||
reverse := false
|
||||
for strings.HasPrefix(s, "!") {
|
||||
s = s[1:]
|
||||
reverse = !reverse
|
||||
}
|
||||
return s, reverse
|
||||
}
|
||||
|
||||
func parseGeoIPRule(rule string, reverse bool) (*IPRule_Geoip, error) {
|
||||
file, code, ok := strings.Cut(rule, ":")
|
||||
if !ok {
|
||||
return nil, errors.New("syntax error")
|
||||
}
|
||||
|
||||
if file == "" {
|
||||
return nil, errors.New("empty file")
|
||||
}
|
||||
|
||||
code, codeReverse := cutReversePrefix(code)
|
||||
reverse = reverse != codeReverse
|
||||
if code == "" {
|
||||
return nil, errors.New("empty code")
|
||||
}
|
||||
code = strings.ToUpper(code)
|
||||
|
||||
if err := checkFile(file, code); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IPRule_Geoip{
|
||||
Geoip: &GeoIPRule{
|
||||
File: file,
|
||||
Code: code,
|
||||
ReverseMatch: reverse,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCustomIPRule(rule string, reverse bool) (*IPRule_Custom, error) {
|
||||
cidr, err := parseCIDR(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IPRule_Custom{
|
||||
Custom: &CIDRRule{
|
||||
Cidr: cidr,
|
||||
ReverseMatch: reverse,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCIDR(s string) (*CIDR, error) {
|
||||
ipStr, prefixStr, _ := strings.Cut(s, "/")
|
||||
|
||||
ipAddr := net.ParseAddress(ipStr)
|
||||
|
||||
var maxPrefix uint32
|
||||
switch ipAddr.Family() {
|
||||
case net.AddressFamilyIPv4:
|
||||
maxPrefix = 32
|
||||
case net.AddressFamilyIPv6:
|
||||
maxPrefix = 128
|
||||
default:
|
||||
return nil, errors.New("unsupported address family")
|
||||
}
|
||||
|
||||
prefixBits := maxPrefix
|
||||
if prefixStr != "" {
|
||||
parsedPrefix, err := strconv.ParseUint(prefixStr, 10, 32)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid CIDR prefix length: ", prefixStr).Base(err)
|
||||
}
|
||||
prefixBits = uint32(parsedPrefix)
|
||||
}
|
||||
if prefixBits > maxPrefix {
|
||||
return nil, errors.New("CIDR prefix length ", prefixBits, " exceeds max ", maxPrefix)
|
||||
}
|
||||
|
||||
return &CIDR{
|
||||
Ip: []byte(ipAddr.IP()),
|
||||
Prefix: prefixBits,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseDomainRule(r string, defaultType Domain_Type) (*DomainRule, error) {
|
||||
if strings.HasPrefix(r, "geosite:") {
|
||||
r = "ext:" + DefaultGeoSiteDat + ":" + r[len("geosite:"):]
|
||||
}
|
||||
|
||||
prefix := 0
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
|
||||
if strings.HasPrefix(r, ext) {
|
||||
prefix = len(ext)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var rule isDomainRule_Value
|
||||
var err error
|
||||
if prefix > 0 {
|
||||
rule, err = parseGeoSiteRule(r[prefix:])
|
||||
} else {
|
||||
rule, err = parseCustomDomainRule(r, defaultType)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.New("illegal domain rule: ", r).Base(err)
|
||||
}
|
||||
return &DomainRule{Value: rule}, nil
|
||||
}
|
||||
|
||||
func ParseDomainRules(rules []string, defaultType Domain_Type) ([]*DomainRule, error) {
|
||||
var domainRules []*DomainRule
|
||||
|
||||
for i, r := range rules {
|
||||
if strings.HasPrefix(r, "geosite:") {
|
||||
r = "ext:" + DefaultGeoSiteDat + ":" + r[len("geosite:"):]
|
||||
}
|
||||
|
||||
prefix := 0
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
|
||||
if strings.HasPrefix(r, ext) {
|
||||
prefix = len(ext)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var rule isDomainRule_Value
|
||||
var err error
|
||||
if prefix > 0 {
|
||||
rule, err = parseGeoSiteRule(r[prefix:])
|
||||
} else {
|
||||
rule, err = parseCustomDomainRule(r, defaultType)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.New("illegal domain rule: ", rules[i]).Base(err)
|
||||
}
|
||||
domainRules = append(domainRules, &DomainRule{Value: rule})
|
||||
}
|
||||
|
||||
return domainRules, nil
|
||||
}
|
||||
|
||||
func parseGeoSiteRule(rule string) (*DomainRule_Geosite, error) {
|
||||
file, codeWithAttrs, ok := strings.Cut(rule, ":")
|
||||
if !ok {
|
||||
return nil, errors.New("syntax error")
|
||||
}
|
||||
|
||||
if file == "" {
|
||||
return nil, errors.New("empty file")
|
||||
}
|
||||
|
||||
if strings.HasSuffix(codeWithAttrs, "@") || strings.Contains(codeWithAttrs, "@@") {
|
||||
return nil, errors.New("empty attr")
|
||||
}
|
||||
code, attrs, _ := strings.Cut(codeWithAttrs, "@")
|
||||
|
||||
if code == "" {
|
||||
return nil, errors.New("empty code")
|
||||
}
|
||||
code = strings.ToUpper(code)
|
||||
|
||||
if err := checkFile(file, code); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DomainRule_Geosite{
|
||||
Geosite: &GeoSiteRule{
|
||||
File: file,
|
||||
Code: code,
|
||||
Attrs: strings.ToLower(attrs),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCustomDomainRule(rule string, defaultType Domain_Type) (*DomainRule_Custom, error) {
|
||||
domain := new(Domain)
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(rule, "regexp:"):
|
||||
domain.Type = Domain_Regex
|
||||
domain.Value = rule[7:]
|
||||
|
||||
case strings.HasPrefix(rule, "domain:"):
|
||||
domain.Type = Domain_Domain
|
||||
domain.Value = rule[7:]
|
||||
|
||||
case strings.HasPrefix(rule, "full:"):
|
||||
domain.Type = Domain_Full
|
||||
domain.Value = rule[5:]
|
||||
|
||||
case strings.HasPrefix(rule, "keyword:"):
|
||||
domain.Type = Domain_Substr
|
||||
domain.Value = rule[8:]
|
||||
|
||||
case strings.HasPrefix(rule, "dotless:"):
|
||||
domain.Type = Domain_Regex
|
||||
switch substr := rule[8:]; {
|
||||
case substr == "":
|
||||
domain.Value = "^[^.]*$"
|
||||
case !strings.Contains(substr, "."):
|
||||
domain.Value = "^[^.]*" + substr + "[^.]*$"
|
||||
default:
|
||||
return nil, errors.New("substr in dotless rule should not contain a dot")
|
||||
}
|
||||
|
||||
default:
|
||||
domain.Type = defaultType
|
||||
domain.Value = rule
|
||||
}
|
||||
|
||||
return &DomainRule_Custom{
|
||||
Custom: domain,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package geodata_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
)
|
||||
|
||||
func TestParseIPRules(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
rules := []string{
|
||||
"geoip:us",
|
||||
"geoip:cn",
|
||||
"!geoip:cn",
|
||||
"!!geoip:cn",
|
||||
"geoip:!cn",
|
||||
"geoip:!!cn",
|
||||
"!geoip:!cn",
|
||||
"ext:geoip.dat:!cn",
|
||||
"ext:geoip.dat:!!cn",
|
||||
"ext:geoip.dat:ca",
|
||||
"ext-ip:geoip.dat:!cn",
|
||||
"ext-ip:geoip.dat:!ca",
|
||||
"192.168.0.0/24",
|
||||
"!192.168.0.0/24",
|
||||
"!!192.168.0.0/24",
|
||||
"!!!192.168.0.0/24",
|
||||
"192.168.0.1",
|
||||
"fe80::/64",
|
||||
"fe80::",
|
||||
}
|
||||
|
||||
_, err := geodata.ParseIPRules(rules)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse ip rules, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIPRuleReverse(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
for _, tt := range []struct {
|
||||
rule string
|
||||
reverse bool
|
||||
}{
|
||||
{rule: "!192.168.0.0/24", reverse: true},
|
||||
{rule: "!!192.168.0.0/24", reverse: false},
|
||||
{rule: "!!!192.168.0.0/24", reverse: true},
|
||||
{rule: "!!!!192.168.0.0/24", reverse: false},
|
||||
{rule: "geoip:cn", reverse: false},
|
||||
{rule: "!geoip:cn", reverse: true},
|
||||
{rule: "!!geoip:cn", reverse: false},
|
||||
{rule: "geoip:!cn", reverse: true},
|
||||
{rule: "geoip:!!cn", reverse: false},
|
||||
{rule: "!geoip:!cn", reverse: false},
|
||||
{rule: "!!geoip:!cn", reverse: true},
|
||||
{rule: "!geoip:!!cn", reverse: true},
|
||||
{rule: "ext:geoip.dat:!!!cn", reverse: true},
|
||||
} {
|
||||
t.Run(tt.rule, func(t *testing.T) {
|
||||
rules, err := geodata.ParseIPRules([]string{tt.rule})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse ip rules, got %s", err)
|
||||
}
|
||||
|
||||
if len(rules) != 1 {
|
||||
t.Fatalf("Expected 1 rule, got %d", len(rules))
|
||||
}
|
||||
|
||||
switch rule := rules[0]; {
|
||||
case rule.GetGeoip() != nil:
|
||||
if rule.GetGeoip().GetReverseMatch() != tt.reverse {
|
||||
t.Fatalf("Expected geoip reverse match to be %t", tt.reverse)
|
||||
}
|
||||
case rule.GetCustom() != nil:
|
||||
if rule.GetCustom().GetReverseMatch() != tt.reverse {
|
||||
t.Fatalf("Expected custom reverse match to be %t", tt.reverse)
|
||||
}
|
||||
default:
|
||||
t.Fatal("Expected ip rule")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDomainRules(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
rules := []string{
|
||||
"geosite:cn",
|
||||
"geosite:geolocation-!cn",
|
||||
"geosite:cn@!cn",
|
||||
"ext:geosite.dat:geolocation-!cn",
|
||||
"ext:geosite.dat:cn@!cn",
|
||||
"ext-site:geosite.dat:geolocation-!cn",
|
||||
"ext-site:geosite.dat:cn@!cn",
|
||||
"domain:google.com",
|
||||
}
|
||||
|
||||
_, err := geodata.ParseDomainRules(rules, geodata.Domain_Domain)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse domain rules, got %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package strmatcher
|
||||
|
||||
// LinearAnyMatcher is an implementation of AnyMatcher.
|
||||
type LinearAnyMatcher struct {
|
||||
full *FullMatcherSet
|
||||
domain *DomainMatcherSet
|
||||
substr *SubstrMatcherSet
|
||||
regex *SimpleMatcherSet
|
||||
}
|
||||
|
||||
func NewLinearAnyMatcher() *LinearAnyMatcher {
|
||||
return new(LinearAnyMatcher)
|
||||
}
|
||||
|
||||
// Add implements AnyMatcher.Add.
|
||||
func (s *LinearAnyMatcher) Add(matcher Matcher) {
|
||||
switch matcher := matcher.(type) {
|
||||
case FullMatcher:
|
||||
if s.full == nil {
|
||||
s.full = NewFullMatcherSet()
|
||||
}
|
||||
s.full.AddFullMatcher(matcher)
|
||||
case DomainMatcher:
|
||||
if s.domain == nil {
|
||||
s.domain = NewDomainMatcherSet()
|
||||
}
|
||||
s.domain.AddDomainMatcher(matcher)
|
||||
case SubstrMatcher:
|
||||
if s.substr == nil {
|
||||
s.substr = new(SubstrMatcherSet)
|
||||
}
|
||||
s.substr.AddSubstrMatcher(matcher)
|
||||
default:
|
||||
if s.regex == nil {
|
||||
s.regex = new(SimpleMatcherSet)
|
||||
}
|
||||
s.regex.AddMatcher(matcher)
|
||||
}
|
||||
}
|
||||
|
||||
// MatchAny implements AnyMatcher.MatchAny.
|
||||
func (s *LinearAnyMatcher) MatchAny(input string) bool {
|
||||
if s.full != nil && s.full.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
if s.domain != nil && s.domain.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
if s.substr != nil && s.substr.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
return s.regex != nil && s.regex.MatchAny(input)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package strmatcher_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
)
|
||||
|
||||
func BenchmarkLinearIndexMatcher(b *testing.B) {
|
||||
benchmarkIndexMatcher(b, func() IndexMatcher {
|
||||
return NewLinearIndexMatcher()
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMphIndexMatcher(b *testing.B) {
|
||||
benchmarkIndexMatcher(b, func() IndexMatcher {
|
||||
return NewMphIndexMatcher()
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkIndexMatcher(b *testing.B, ctor func() IndexMatcher) {
|
||||
b.Run("Match", func(b *testing.B) {
|
||||
b.Run("Domain------------", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{Domain: true})
|
||||
})
|
||||
b.Run("Domain+Full-------", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{Domain: true, Full: true})
|
||||
})
|
||||
b.Run("Domain+Full+Substr", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{Domain: true, Full: true, Substr: true})
|
||||
})
|
||||
b.Run("All-Fail----------", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{Domain: false, Full: false, Substr: false})
|
||||
})
|
||||
})
|
||||
b.Run("Match/Dotless", func(b *testing.B) { // Dotless domain matcher automatically inserted in DNS app when "localhost" DNS is used.
|
||||
b.Run("All-Succ", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{Domain: true, Full: true, Substr: true, Regex: true})
|
||||
})
|
||||
b.Run("All-Fail", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{Domain: false, Full: false, Substr: false, Regex: false})
|
||||
})
|
||||
})
|
||||
b.Run("MatchAny", func(b *testing.B) {
|
||||
b.Run("First-Full--", func(b *testing.B) {
|
||||
benchmarkMatchAny(b, ctor(), map[Type]bool{Full: true, Domain: true, Substr: true})
|
||||
})
|
||||
b.Run("First-Domain", func(b *testing.B) {
|
||||
benchmarkMatchAny(b, ctor(), map[Type]bool{Full: false, Domain: true, Substr: true})
|
||||
})
|
||||
b.Run("First-Substr", func(b *testing.B) {
|
||||
benchmarkMatchAny(b, ctor(), map[Type]bool{Full: false, Domain: false, Substr: true})
|
||||
})
|
||||
b.Run("All-Fail----", func(b *testing.B) {
|
||||
benchmarkMatchAny(b, ctor(), map[Type]bool{Full: false, Domain: false, Substr: false})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package strmatcher_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
. "github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
)
|
||||
|
||||
func BenchmarkFullMatcher(b *testing.B) {
|
||||
b.Run("SimpleMatcherGroup------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Full, func() MatcherGroup {
|
||||
return new(SimpleMatcherGroup)
|
||||
})
|
||||
})
|
||||
b.Run("FullMatcherGroup--------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Full, func() MatcherGroup {
|
||||
return NewFullMatcherGroup()
|
||||
})
|
||||
})
|
||||
b.Run("ACAutomationMatcherGroup", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Full, func() MatcherGroup {
|
||||
return NewACAutomatonMatcherGroup()
|
||||
})
|
||||
})
|
||||
b.Run("MphMatcherGroup---------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Full, func() MatcherGroup {
|
||||
return NewMphMatcherGroup()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDomainMatcher(b *testing.B) {
|
||||
b.Run("SimpleMatcherGroup------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Domain, func() MatcherGroup {
|
||||
return new(SimpleMatcherGroup)
|
||||
})
|
||||
})
|
||||
b.Run("DomainMatcherGroup------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Domain, func() MatcherGroup {
|
||||
return NewDomainMatcherGroup()
|
||||
})
|
||||
})
|
||||
b.Run("ACAutomationMatcherGroup", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Domain, func() MatcherGroup {
|
||||
return NewACAutomatonMatcherGroup()
|
||||
})
|
||||
})
|
||||
b.Run("MphMatcherGroup---------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Domain, func() MatcherGroup {
|
||||
return NewMphMatcherGroup()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSubstrMatcher(b *testing.B) {
|
||||
b.Run("SimpleMatcherGroup------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Substr, func() MatcherGroup {
|
||||
return new(SimpleMatcherGroup)
|
||||
})
|
||||
})
|
||||
b.Run("SubstrMatcherGroup------", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Substr, func() MatcherGroup {
|
||||
return new(SubstrMatcherGroup)
|
||||
})
|
||||
})
|
||||
b.Run("ACAutomationMatcherGroup", func(b *testing.B) {
|
||||
benchmarkMatcherType(b, Substr, func() MatcherGroup {
|
||||
return NewACAutomatonMatcherGroup()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Utility functions for benchmark
|
||||
|
||||
func benchmarkMatcherType(b *testing.B, t Type, ctor func() MatcherGroup) {
|
||||
b.Run("Match", func(b *testing.B) {
|
||||
b.Run("Succ", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{t: true})
|
||||
})
|
||||
b.Run("Fail", func(b *testing.B) {
|
||||
benchmarkMatch(b, ctor(), map[Type]bool{t: false})
|
||||
})
|
||||
})
|
||||
b.Run("MatchAny", func(b *testing.B) {
|
||||
b.Run("Succ", func(b *testing.B) {
|
||||
benchmarkMatchAny(b, ctor(), map[Type]bool{t: true})
|
||||
})
|
||||
b.Run("Fail", func(b *testing.B) {
|
||||
benchmarkMatchAny(b, ctor(), map[Type]bool{t: false})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkMatch(b *testing.B, g MatcherGroup, enabledTypes map[Type]bool) {
|
||||
prepareMatchers(g, enabledTypes)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = g.Match("0.example.com")
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkMatchAny(b *testing.B, g MatcherGroup, enabledTypes map[Type]bool) {
|
||||
prepareMatchers(g, enabledTypes)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = g.MatchAny("0.example.com")
|
||||
}
|
||||
}
|
||||
|
||||
func prepareMatchers(g MatcherGroup, enabledTypes map[Type]bool) {
|
||||
for matcherType, hasMatch := range enabledTypes {
|
||||
switch matcherType {
|
||||
case Domain:
|
||||
if hasMatch {
|
||||
AddMatcherToGroup(g, DomainMatcher("example.com"), 0)
|
||||
}
|
||||
for i := 1; i < 1024; i++ {
|
||||
AddMatcherToGroup(g, DomainMatcher(strconv.Itoa(i)+".example.com"), uint32(i))
|
||||
}
|
||||
case Full:
|
||||
if hasMatch {
|
||||
AddMatcherToGroup(g, FullMatcher("0.example.com"), 0)
|
||||
}
|
||||
for i := 1; i < 64; i++ {
|
||||
AddMatcherToGroup(g, FullMatcher(strconv.Itoa(i)+".example.com"), uint32(i))
|
||||
}
|
||||
case Substr:
|
||||
if hasMatch {
|
||||
AddMatcherToGroup(g, SubstrMatcher("example.com"), 0)
|
||||
}
|
||||
for i := 1; i < 4; i++ {
|
||||
AddMatcherToGroup(g, SubstrMatcher(strconv.Itoa(i)+".example.com"), uint32(i))
|
||||
}
|
||||
case Regex:
|
||||
matcher, err := Regex.New("^[^.]*$") // Dotless domain matcher automatically inserted in DNS app when "localhost" DNS is used.
|
||||
common.Must(err)
|
||||
AddMatcherToGroup(g, matcher, 0)
|
||||
}
|
||||
}
|
||||
if g, ok := g.(buildable); ok {
|
||||
common.Must(g.Build())
|
||||
}
|
||||
}
|
||||
|
||||
type buildable interface {
|
||||
Build() error
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package strmatcher
|
||||
|
||||
// LinearIndexMatcher is an implementation of IndexMatcher.
|
||||
type LinearIndexMatcher struct {
|
||||
count uint32
|
||||
full *FullMatcherGroup
|
||||
domain *DomainMatcherGroup
|
||||
substr *SubstrMatcherGroup
|
||||
regex *SimpleMatcherGroup
|
||||
}
|
||||
|
||||
func NewLinearIndexMatcher() *LinearIndexMatcher {
|
||||
return new(LinearIndexMatcher)
|
||||
}
|
||||
|
||||
// Add implements IndexMatcher.Add.
|
||||
func (g *LinearIndexMatcher) Add(matcher Matcher) uint32 {
|
||||
g.count++
|
||||
index := g.count
|
||||
|
||||
switch matcher := matcher.(type) {
|
||||
case FullMatcher:
|
||||
if g.full == nil {
|
||||
g.full = NewFullMatcherGroup()
|
||||
}
|
||||
g.full.AddFullMatcher(matcher, index)
|
||||
case DomainMatcher:
|
||||
if g.domain == nil {
|
||||
g.domain = NewDomainMatcherGroup()
|
||||
}
|
||||
g.domain.AddDomainMatcher(matcher, index)
|
||||
case SubstrMatcher:
|
||||
if g.substr == nil {
|
||||
g.substr = new(SubstrMatcherGroup)
|
||||
}
|
||||
g.substr.AddSubstrMatcher(matcher, index)
|
||||
default:
|
||||
if g.regex == nil {
|
||||
g.regex = new(SimpleMatcherGroup)
|
||||
}
|
||||
g.regex.AddMatcher(matcher, index)
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
// Build implements IndexMatcher.Build.
|
||||
func (*LinearIndexMatcher) Build() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match implements IndexMatcher.Match.
|
||||
func (g *LinearIndexMatcher) Match(input string) []uint32 {
|
||||
// Allocate capacity to prevent matches escaping to heap
|
||||
result := make([][]uint32, 0, 5)
|
||||
if g.full != nil {
|
||||
if matches := g.full.Match(input); len(matches) > 0 {
|
||||
result = append(result, matches)
|
||||
}
|
||||
}
|
||||
if g.domain != nil {
|
||||
if matches := g.domain.Match(input); len(matches) > 0 {
|
||||
result = append(result, matches)
|
||||
}
|
||||
}
|
||||
if g.substr != nil {
|
||||
if matches := g.substr.Match(input); len(matches) > 0 {
|
||||
result = append(result, matches)
|
||||
}
|
||||
}
|
||||
if g.regex != nil {
|
||||
if matches := g.regex.Match(input); len(matches) > 0 {
|
||||
result = append(result, matches)
|
||||
}
|
||||
}
|
||||
return CompositeMatches(result)
|
||||
}
|
||||
|
||||
// MatchAny implements IndexMatcher.MatchAny.
|
||||
func (g *LinearIndexMatcher) MatchAny(input string) bool {
|
||||
if g.full != nil && g.full.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
if g.domain != nil && g.domain.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
if g.substr != nil && g.substr.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
return g.regex != nil && g.regex.MatchAny(input)
|
||||
}
|
||||
|
||||
// Size implements IndexMatcher.Size.
|
||||
func (g *LinearIndexMatcher) Size() uint32 {
|
||||
return g.count
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package strmatcher_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
. "github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
)
|
||||
|
||||
// See https://github.com/v2fly/v2ray-core/issues/92#issuecomment-673238489
|
||||
func TestLinearIndexMatcher(t *testing.T) {
|
||||
rules := []struct {
|
||||
Type Type
|
||||
Domain string
|
||||
}{
|
||||
{
|
||||
Type: Regex,
|
||||
Domain: "apis\\.us$",
|
||||
},
|
||||
{
|
||||
Type: Substr,
|
||||
Domain: "apis",
|
||||
},
|
||||
{
|
||||
Type: Domain,
|
||||
Domain: "googleapis.com",
|
||||
},
|
||||
{
|
||||
Type: Domain,
|
||||
Domain: "com",
|
||||
},
|
||||
{
|
||||
Type: Full,
|
||||
Domain: "www.baidu.com",
|
||||
},
|
||||
{
|
||||
Type: Substr,
|
||||
Domain: "apis",
|
||||
},
|
||||
{
|
||||
Type: Domain,
|
||||
Domain: "googleapis.com",
|
||||
},
|
||||
{
|
||||
Type: Full,
|
||||
Domain: "fonts.googleapis.com",
|
||||
},
|
||||
{
|
||||
Type: Full,
|
||||
Domain: "www.baidu.com",
|
||||
},
|
||||
{
|
||||
Type: Domain,
|
||||
Domain: "example.com",
|
||||
},
|
||||
}
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output []uint32
|
||||
}{
|
||||
{
|
||||
Input: "www.baidu.com",
|
||||
Output: []uint32{5, 9, 4},
|
||||
},
|
||||
{
|
||||
Input: "fonts.googleapis.com",
|
||||
Output: []uint32{8, 3, 7, 4, 2, 6},
|
||||
},
|
||||
{
|
||||
Input: "example.googleapis.com",
|
||||
Output: []uint32{3, 7, 4, 2, 6},
|
||||
},
|
||||
{
|
||||
Input: "testapis.us",
|
||||
Output: []uint32{2, 6, 1},
|
||||
},
|
||||
{
|
||||
Input: "example.com",
|
||||
Output: []uint32{10, 4},
|
||||
},
|
||||
}
|
||||
matcherGroup := NewLinearIndexMatcher()
|
||||
for _, rule := range rules {
|
||||
matcher, err := rule.Type.New(rule.Domain)
|
||||
common.Must(err)
|
||||
matcherGroup.Add(matcher)
|
||||
}
|
||||
matcherGroup.Build()
|
||||
for _, test := range cases {
|
||||
if m := matcherGroup.Match(test.Input); !reflect.DeepEqual(m, test.Output) {
|
||||
t.Error("unexpected output: ", m, " for test case ", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package strmatcher
|
||||
|
||||
import "runtime"
|
||||
|
||||
// A MphIndexMatcher is divided into three parts:
|
||||
// 1. `full` and `domain` patterns are matched by Rabin-Karp algorithm and minimal perfect hash table;
|
||||
// 2. `substr` patterns are matched by ac automaton;
|
||||
// 3. `regex` patterns are matched with the regex library.
|
||||
type MphIndexMatcher struct {
|
||||
count uint32
|
||||
mph *MphMatcherGroup
|
||||
ac *ACAutomatonMatcherGroup
|
||||
regex *SimpleMatcherGroup
|
||||
}
|
||||
|
||||
func NewMphIndexMatcher() *MphIndexMatcher {
|
||||
return new(MphIndexMatcher)
|
||||
}
|
||||
|
||||
// Add implements IndexMatcher.Add.
|
||||
func (g *MphIndexMatcher) Add(matcher Matcher) uint32 {
|
||||
g.count++
|
||||
index := g.count
|
||||
|
||||
switch matcher := matcher.(type) {
|
||||
case FullMatcher:
|
||||
if g.mph == nil {
|
||||
g.mph = NewMphMatcherGroup()
|
||||
}
|
||||
g.mph.AddFullMatcher(matcher, index)
|
||||
case DomainMatcher:
|
||||
if g.mph == nil {
|
||||
g.mph = NewMphMatcherGroup()
|
||||
}
|
||||
g.mph.AddDomainMatcher(matcher, index)
|
||||
case SubstrMatcher:
|
||||
if g.ac == nil {
|
||||
g.ac = NewACAutomatonMatcherGroup()
|
||||
}
|
||||
g.ac.AddSubstrMatcher(matcher, index)
|
||||
case *RegexMatcher:
|
||||
if g.regex == nil {
|
||||
g.regex = &SimpleMatcherGroup{}
|
||||
}
|
||||
g.regex.AddMatcher(matcher, index)
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
// Build implements IndexMatcher.Build.
|
||||
func (g *MphIndexMatcher) Build() error {
|
||||
if g.mph != nil {
|
||||
runtime.GC() // peak mem
|
||||
g.mph.Build()
|
||||
}
|
||||
runtime.GC() // peak mem
|
||||
if g.ac != nil {
|
||||
g.ac.Build()
|
||||
runtime.GC() // peak mem
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match implements IndexMatcher.Match.
|
||||
func (g *MphIndexMatcher) Match(input string) []uint32 {
|
||||
result := make([][]uint32, 0, 5)
|
||||
if g.mph != nil {
|
||||
if matches := g.mph.Match(input); len(matches) > 0 {
|
||||
result = append(result, matches)
|
||||
}
|
||||
}
|
||||
if g.ac != nil {
|
||||
if matches := g.ac.Match(input); len(matches) > 0 {
|
||||
result = append(result, matches)
|
||||
}
|
||||
}
|
||||
if g.regex != nil {
|
||||
if matches := g.regex.Match(input); len(matches) > 0 {
|
||||
result = append(result, matches)
|
||||
}
|
||||
}
|
||||
return CompositeMatches(result)
|
||||
}
|
||||
|
||||
// MatchAny implements IndexMatcher.MatchAny.
|
||||
func (g *MphIndexMatcher) MatchAny(input string) bool {
|
||||
if g.mph != nil && g.mph.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
if g.ac != nil && g.ac.MatchAny(input) {
|
||||
return true
|
||||
}
|
||||
return g.regex != nil && g.regex.MatchAny(input)
|
||||
}
|
||||
|
||||
// Size implements IndexMatcher.Size.
|
||||
func (g *MphIndexMatcher) Size() uint32 {
|
||||
return g.count
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user