mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-16 06:20:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c412e77a9b | ||
|
|
ccb69ea5e2 | ||
|
|
52a412d9e2 | ||
|
|
18a1b5042a | ||
|
|
c26d2eda24 | ||
|
|
a1bf968be9 | ||
|
|
c037ccd98d | ||
|
|
37ceb8b4b6 | ||
|
|
fd2ca74822 | ||
|
|
47cfe9994a | ||
|
|
3e2f040cd8 | ||
|
|
c7245c0336 | ||
|
|
eef6e63bc1 | ||
|
|
6ce8dc53e7 | ||
|
|
01a034be53 | ||
|
|
de2caf3cef | ||
|
|
cecc88f43c | ||
|
|
cd4ce973e9 | ||
|
|
fc7b980636 | ||
|
|
8ee131cbbb | ||
|
|
2776ea6d74 | ||
|
|
5e245b082e | ||
|
|
d9c54026c5 | ||
|
|
c1958dba04 | ||
|
|
540b9070f5 | ||
|
|
ada99a4eb0 | ||
|
|
65458e919f | ||
|
|
aa3d6589da | ||
|
|
25c11e2d2b | ||
|
|
dffc7ada5e | ||
|
|
77f98eba09 | ||
|
|
f124daf5a3 | ||
|
|
598bde7412 | ||
|
|
9b373e39ca | ||
|
|
c7e569b037 | ||
|
|
f02a357861 | ||
|
|
5fe6d6217a | ||
|
|
0604ffa957 | ||
|
|
d3f1a24285 | ||
|
|
2323273e37 | ||
|
|
09107b71dc | ||
|
|
7021606ad3 | ||
|
|
7d214f8b09 | ||
|
|
8b419d833d | ||
|
|
a12801c13b | ||
|
|
a000371b2a | ||
|
|
bc6e966af8 | ||
|
|
fc5620de98 | ||
|
|
b02bdcf4cc | ||
|
|
2b329b3675 | ||
|
|
5ca6f4b7d4 | ||
|
|
18e283909c | ||
|
|
6ab123bf8f | ||
|
|
4aba687dd3 | ||
|
|
5b1b41058e | ||
|
|
6e3322d219 | ||
|
|
1d8eb81d70 | ||
|
|
e78d8ef184 | ||
|
|
6ce924ad56 | ||
|
|
035d438979 | ||
|
|
50231eaff9 | ||
|
|
1f74c480d6 | ||
|
|
af7eb68028 | ||
|
|
35387572e0 | ||
|
|
8f15190c23 | ||
|
|
64fada32b5 | ||
|
|
0bafca9486 | ||
|
|
d5bc58dc6b | ||
|
|
c18b39ed80 | ||
|
|
e2ad0acf60 | ||
|
|
c320e89108 | ||
|
|
412898fed7 | ||
|
|
5c62d50d43 | ||
|
|
1aabe7ea78 | ||
|
|
e4e7614c62 | ||
|
|
987290ba48 | ||
|
|
d7fa2076c3 | ||
|
|
fb548f54d2 | ||
|
|
0495b17650 | ||
|
|
65f6f0a43b | ||
|
|
3263ae9255 | ||
|
|
3dc8bf3d8b | ||
|
|
695e68ef9e | ||
|
|
dfdbcf86cc | ||
|
|
2b828b7bc2 | ||
|
|
45cf2898ab | ||
|
|
18b85adb4e | ||
|
|
452b719504 | ||
|
|
345c76f9a8 | ||
|
|
f496437b84 | ||
|
|
b12bc504c8 | ||
|
|
dda2b10c9d | ||
|
|
241aa38ac0 | ||
|
|
7e7e820763 | ||
|
|
f9eb1597ad | ||
|
|
ac04c445bd | ||
|
|
e7e9254630 | ||
|
|
fab4bcc1ed | ||
|
|
b99c3e5657 | ||
|
|
583bb4a63f | ||
|
|
9cd9382e3d | ||
|
|
567500c4af | ||
|
|
5aefcb41fb | ||
|
|
be8009c625 | ||
|
|
8734774e4a | ||
|
|
1e036ce1c5 | ||
|
|
c815c2f2df | ||
|
|
986c512e0f | ||
|
|
711aea4e34 | ||
|
|
6412738486 | ||
|
|
ad2e4cb0e1 | ||
|
|
829d54d7be | ||
|
|
862631172d | ||
|
|
d27b3e46e2 | ||
|
|
da21a8f77f | ||
|
|
e10347bf01 | ||
|
|
26a022c905 | ||
|
|
95e9816223 | ||
|
|
3239d21168 | ||
|
|
06b4931743 | ||
|
|
6189d2bfd5 | ||
|
|
a0e9347f1b | ||
|
|
83cf229909 | ||
|
|
2249f8b5c6 | ||
|
|
fdb9b616fc | ||
|
|
d792fba59c | ||
|
|
55956f8d70 | ||
|
|
94ffd50060 | ||
|
|
c4dfcd4c1a | ||
|
|
cb8cd048c1 | ||
|
|
455f6bc2d5 | ||
|
|
ba538619eb | ||
|
|
d43a808ea5 | ||
|
|
ca4b156b57 | ||
|
|
aba22722a6 | ||
|
|
569459c54c | ||
|
|
2b42699623 | ||
|
|
66a8100737 | ||
|
|
36303694d1 | ||
|
|
4dcf802ae3 | ||
|
|
a2cec2e580 | ||
|
|
1cd7d25fec | ||
|
|
09002ab763 | ||
|
|
cb206ddc74 | ||
|
|
fa466f8174 | ||
|
|
e26f5e9548 | ||
|
|
787aa7677b | ||
|
|
d878fc83f8 | ||
|
|
ee2b2c5ab6 | ||
|
|
81d993f49d | ||
|
|
4c3842711d | ||
|
|
ab69985fcc | ||
|
|
56bb63668c | ||
|
|
359a28f876 | ||
|
|
da9ba693cb | ||
|
|
5488b86c67 | ||
|
|
a3c054a54c | ||
|
|
1bdb488c9e | ||
|
|
d38ba9d507 | ||
|
|
9d9eaf399f | ||
|
|
eb32d166af | ||
|
|
1dbafe629a | ||
|
|
c42deab55c | ||
|
|
906d49a271 | ||
|
|
4192ca0827 | ||
|
|
228f1e13aa | ||
|
|
15968585f3 | ||
|
|
4951994ebe | ||
|
|
756a2d1327 | ||
|
|
b279076ba1 | ||
|
|
e61eeae258 | ||
|
|
958eb9ea8f | ||
|
|
8381a5a8a6 | ||
|
|
1ead940a71 | ||
|
|
bdff2fa72e | ||
|
|
1d62941bd2 | ||
|
|
52cf9ef5d6 | ||
|
|
16568314d8 | ||
|
|
1fc6850dc4 | ||
|
|
4e87f59628 | ||
|
|
7ab0a3ccb7 | ||
|
|
2fff03720d | ||
|
|
7f7fc5a829 | ||
|
|
ff6c060168 | ||
|
|
5b552db781 | ||
|
|
108bf7ff82 | ||
|
|
1836b1c6e4 | ||
|
|
b4f08981be | ||
|
|
cd4d0baacd | ||
|
|
3bc24a3d5d | ||
|
|
fa07b34956 | ||
|
|
85a8bf5f39 | ||
|
|
d0f533f94a | ||
|
|
d1db1d6a27 | ||
|
|
1a14ffcec6 | ||
|
|
454c930d13 | ||
|
|
bc590bcb56 | ||
|
|
7cf25970de | ||
|
|
d837687368 | ||
|
|
b4650360d6 | ||
|
|
d52f15060b | ||
|
|
31ab22c33d | ||
|
|
d42c981f9c | ||
|
|
cb1106c2fb | ||
|
|
df4b97097c | ||
|
|
a9cec25b8d | ||
|
|
c5edc122b7 | ||
|
|
9dd17d55fb | ||
|
|
310b764811 | ||
|
|
3691741440 | ||
|
|
05e259c8e4 | ||
|
|
175502d807 | ||
|
|
6780045550 | ||
|
|
ff6126463b | ||
|
|
5c3d639c09 | ||
|
|
7c56b7beea | ||
|
|
5b91b152bb | ||
|
|
ef77a42063 | ||
|
|
dab99614dc | ||
|
|
14e8ecfacf | ||
|
|
7094f8dc07 | ||
|
|
d342361c89 | ||
|
|
f17fabfff5 | ||
|
|
05a11910d4 | ||
|
|
82624bcaf0 | ||
|
|
e9f7d61c2e | ||
|
|
806b8dc27d | ||
|
|
f27edc3172 | ||
|
|
c93478b891 | ||
|
|
1642fdfbdd | ||
|
|
a91a88c7b2 | ||
|
|
32937846c5 | ||
|
|
93225a1132 | ||
|
|
4d958cbfd3 | ||
|
|
e5a9fb752e | ||
|
|
6a1a13b797 | ||
|
|
6c4008edad | ||
|
|
3f608b3a58 | ||
|
|
4c3020ca6f | ||
|
|
ba88aa173c | ||
|
|
08301e272c | ||
|
|
6eccc59728 | ||
|
|
1e89a8fd98 | ||
|
|
d2758a023c | ||
|
|
8aacdbd71b | ||
|
|
14524cc3b7 | ||
|
|
cb7bfeb54c | ||
|
|
d62f5cfb62 | ||
|
|
755f0a1d12 | ||
|
|
d8a8629a14 | ||
|
|
982c95d89a | ||
|
|
ae3ddd1c06 | ||
|
|
f926ee4aa0 | ||
|
|
67a71adad1 | ||
|
|
ce66db7032 | ||
|
|
7d93062f3d | ||
|
|
2320416ca3 |
@@ -0,0 +1 @@
|
||||
powershell.exe -ExecutionPolicy Bypass -File ".\xray_no_window.ps1"
|
||||
@@ -0,0 +1 @@
|
||||
Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden
|
||||
@@ -0,0 +1 @@
|
||||
CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0
|
||||
@@ -32,9 +32,13 @@ 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_transport.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.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
|
||||
|
||||
# Create log files
|
||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
@@ -45,8 +49,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=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=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/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,9 +32,13 @@ 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_transport.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/08_stats.json
|
||||
RUN echo '{}' >/tmp/usr/local/etc/xray/09_reverse.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
|
||||
|
||||
# Create log files
|
||||
RUN mkdir -p /tmp/var/log/xray && touch \
|
||||
@@ -54,8 +58,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=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=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/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,8 +64,16 @@ 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@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
@@ -74,7 +82,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -124,6 +132,13 @@ 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 }}
|
||||
@@ -131,3 +146,7 @@ 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,15 +11,16 @@ 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@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -75,13 +76,14 @@ 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@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
@@ -90,7 +92,7 @@ jobs:
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -117,13 +119,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@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -132,15 +134,17 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
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
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
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.txt
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -11,15 +11,16 @@ 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@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -55,7 +56,7 @@ jobs:
|
||||
|
||||
- name: Trigger Asset Update Workflow if Assets Missing
|
||||
if: steps.check-assets.outputs.missing == 'true'
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -161,6 +162,7 @@ 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 }}
|
||||
@@ -168,7 +170,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up NDK
|
||||
if: matrix.goos == 'android'
|
||||
@@ -191,7 +193,7 @@ jobs:
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -208,6 +210,9 @@ 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
|
||||
@@ -220,14 +225,14 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Restore Wintun Cache
|
||||
if: matrix.goos == 'windows'
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -236,8 +241,10 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
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
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
@@ -247,7 +254,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.txt
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -26,14 +26,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
- name: Update Geodat
|
||||
id: update
|
||||
uses: nick-fields/retry@v3
|
||||
uses: nick-fields/retry@v4
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
retry_wait_seconds: 60
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Save Geodat Cache
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v6
|
||||
if: ${{ steps.update.outputs.unhit }}
|
||||
with:
|
||||
path: resources
|
||||
@@ -68,9 +68,12 @@ 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@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-wintun-
|
||||
@@ -82,7 +85,7 @@ jobs:
|
||||
|
||||
- name: Update Wintun
|
||||
id: update
|
||||
uses: nick-fields/retry@v3
|
||||
uses: nick-fields/retry@v4
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
retry_wait_seconds: 60
|
||||
@@ -96,7 +99,6 @@ 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
|
||||
@@ -113,16 +115,21 @@ jobs:
|
||||
fi
|
||||
if [[ "$missing" == true ]]; then
|
||||
FILENAME=wintun.zip
|
||||
DOWNLOAD_FILE=wintun-0.14.1.zip
|
||||
DOWNLOAD_FILE=wintun-${ASSETVER}.zip
|
||||
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
|
||||
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
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
|
||||
fi
|
||||
|
||||
- name: Save Wintun Cache
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v6
|
||||
if: ${{ steps.update.outputs.unhit }}
|
||||
with:
|
||||
path: resources
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Test
|
||||
name: Tests and Checkings
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -8,9 +8,10 @@ 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@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
@@ -36,9 +37,10 @@ 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@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Check Proto Version Header
|
||||
run: |
|
||||
head -n 4 core/config.pb.go > ref.txt
|
||||
@@ -50,8 +52,26 @@ 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 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 }}
|
||||
@@ -61,14 +81,14 @@ jobs:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: resources
|
||||
key: xray-geodat-
|
||||
|
||||
@@ -67,13 +67,13 @@
|
||||
- [Marzban](https://github.com/Gozargah/Marzban)
|
||||
- [Hiddify](https://github.com/hiddify/Hiddify-Manager)
|
||||
- [TX-UI](https://github.com/AghayeCoder/tx-ui)
|
||||
- [CELERITY](https://github.com/ClickDevTech/CELERITY-panel)
|
||||
- One Click
|
||||
- [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)
|
||||
- [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
|
||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||
- [Magic_V2Ray](https://github.com/vincentng295/Magic_V2Ray)
|
||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||
- Homebrew
|
||||
- `brew install xray`
|
||||
@@ -110,6 +110,8 @@
|
||||
- [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)
|
||||
@@ -117,11 +119,13 @@
|
||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [NetProxy-Magisk](https://github.com/Fanju6/NetProxy-Magisk)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- [AsteriskNG](https://github.com/Asterisk4Magisk/AsteriskNG)
|
||||
- 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)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
|
||||
- macOS arm64 & x64
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973)
|
||||
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||
@@ -132,6 +136,7 @@
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
- [INCY](https://apps.apple.com/en/app/incy/id6756943388)
|
||||
- Linux
|
||||
- [v2rayA](https://github.com/v2rayA/v2rayA)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
@@ -140,10 +145,14 @@
|
||||
- [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)
|
||||
@@ -156,6 +165,7 @@
|
||||
- [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)
|
||||
@@ -177,6 +187,27 @@
|
||||
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
|
||||
- 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)
|
||||
@@ -200,6 +231,13 @@ 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,6 +3,7 @@ package commander
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
@@ -10,6 +11,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -67,20 +69,32 @@ func (c *Commander) Start() error {
|
||||
}
|
||||
c.Unlock()
|
||||
|
||||
var listen = func(listener net.Listener) {
|
||||
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 {
|
||||
if l, err := net.Listen("tcp", c.listen); err != nil {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+19
-33
@@ -2,7 +2,6 @@ package dispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -163,7 +162,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, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
|
||||
inboundLink.Writer = &SizeStatWriter{
|
||||
Counter: c,
|
||||
Writer: inboundLink.Writer,
|
||||
@@ -172,7 +171,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
||||
}
|
||||
if p.Stats.UserDownlink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
|
||||
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
|
||||
outboundLink.Writer = &SizeStatWriter{
|
||||
Counter: c,
|
||||
Writer: outboundLink.Writer,
|
||||
@@ -181,12 +180,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
||||
}
|
||||
|
||||
if p.Stats.UserOnline {
|
||||
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) })
|
||||
}
|
||||
trackOnlineIP(ctx, d.stats, user.Email, sessionInbound.Source.Address.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,13 +200,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, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
|
||||
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
|
||||
}
|
||||
}
|
||||
if p.Stats.UserDownlink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
|
||||
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
|
||||
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
|
||||
link.Writer = &SizeStatWriter{
|
||||
Counter: c,
|
||||
Writer: link.Writer,
|
||||
@@ -220,39 +214,31 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
|
||||
}
|
||||
}
|
||||
if p.Stats.UserOnline {
|
||||
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) })
|
||||
}
|
||||
trackOnlineIP(ctx, statsManager, user.Email, sessionInbound.Source.Address.String())
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
protocolString := result.Protocol()
|
||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||
|
||||
@@ -139,7 +139,8 @@ 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,
|
||||
@@ -153,7 +154,8 @@ 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,
|
||||
@@ -165,7 +167,6 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
c.highWatermark = lenAfter
|
||||
go c.migrate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type migrationEntry struct {
|
||||
|
||||
+11
-35
@@ -2,48 +2,24 @@ 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 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
|
||||
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"}}},
|
||||
}
|
||||
|
||||
func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
||||
|
||||
+72
-260
@@ -7,7 +7,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
router "github.com/xtls/xray-core/app/router"
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
@@ -23,58 +23,6 @@ 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 (
|
||||
@@ -111,11 +59,11 @@ func (x QueryStrategy) String() string {
|
||||
}
|
||||
|
||||
func (QueryStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_app_dns_config_proto_enumTypes[1].Descriptor()
|
||||
return file_app_dns_config_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (QueryStrategy) Type() protoreflect.EnumType {
|
||||
return &file_app_dns_config_proto_enumTypes[1]
|
||||
return &file_app_dns_config_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x QueryStrategy) Number() protoreflect.EnumNumber {
|
||||
@@ -124,30 +72,29 @@ func (x QueryStrategy) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use QueryStrategy.Descriptor instead.
|
||||
func (QueryStrategy) EnumDescriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1}
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
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"`
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func (x *NameServer) Reset() {
|
||||
@@ -201,23 +148,16 @@ func (x *NameServer) GetSkipFallback() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
||||
func (x *NameServer) GetDomain() []*geodata.DomainRule {
|
||||
if x != nil {
|
||||
return x.PrioritizedDomain
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetExpectedGeoip() []*router.GeoIP {
|
||||
func (x *NameServer) GetExpectedIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.ExpectedGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetOriginalRules() []*NameServer_OriginalRule {
|
||||
if x != nil {
|
||||
return x.OriginalRules
|
||||
return x.ExpectedIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -278,9 +218,9 @@ func (x *NameServer) GetFinalQuery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetUnexpectedGeoip() []*router.GeoIP {
|
||||
func (x *NameServer) GetUnexpectedIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.UnexpectedGeoip
|
||||
return x.UnexpectedIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -429,114 +369,9 @@ 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"`
|
||||
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"`
|
||||
Domain *geodata.DomainRule `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.
|
||||
@@ -547,7 +382,7 @@ type Config_HostMapping struct {
|
||||
|
||||
func (x *Config_HostMapping) Reset() {
|
||||
*x = Config_HostMapping{}
|
||||
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -559,7 +394,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[4]
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -575,18 +410,11 @@ func (*Config_HostMapping) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 0}
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetType() DomainMatchingType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DomainMatchingType_Full
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetDomain() string {
|
||||
func (x *Config_HostMapping) GetDomain() *geodata.DomainRule {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetIp() [][]byte {
|
||||
@@ -607,15 +435,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\x17app/router/config.proto\"\xdf\a\n" +
|
||||
"\x14app/dns/config.proto\x12\fxray.app.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\xde\x05\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\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" +
|
||||
"\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" +
|
||||
"\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" +
|
||||
@@ -628,21 +456,15 @@ 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\x12A\n" +
|
||||
"\x10unexpected_geoip\x18\r \x03(\v2\x16.xray.app.router.GeoIPR\x0funexpectedGeoip\x12\x1e\n" +
|
||||
"finalQuery\x12@\n" +
|
||||
"\runexpected_ip\x18\r \x03(\v2\x1b.xray.common.geodata.IPRuleR\funexpectedIp\x12\x1e\n" +
|
||||
"\n" +
|
||||
"actUnprior\x18\x0e \x01(\bR\n" +
|
||||
"actUnprior\x12\x1a\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" +
|
||||
"\bpolicyID\x18\x11 \x01(\rR\bpolicyIDB\x0f\n" +
|
||||
"\r_disableCacheB\r\n" +
|
||||
"\v_serveStaleB\x12\n" +
|
||||
"\x10_serveExpiredTTL\"\x98\x05\n" +
|
||||
"\x10_serveExpiredTTLJ\x04\b\x04\x10\x05\"\x82\x05\n" +
|
||||
"\x06Config\x129\n" +
|
||||
"\vname_server\x18\x05 \x03(\v2\x18.xray.app.dns.NameServerR\n" +
|
||||
"nameServer\x12\x1b\n" +
|
||||
@@ -658,17 +480,11 @@ 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\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" +
|
||||
"\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" +
|
||||
"\x02ip\x18\x03 \x03(\fR\x02ip\x12%\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" +
|
||||
"\x0eproxied_domain\x18\x04 \x01(\tR\rproxiedDomainJ\x04\b\a\x10\b*B\n" +
|
||||
"\rQueryStrategy\x12\n" +
|
||||
"\n" +
|
||||
"\x06USE_IP\x10\x00\x12\v\n" +
|
||||
@@ -689,36 +505,32 @@ 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, 2)
|
||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
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_goTypes = []any{
|
||||
(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
|
||||
(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
|
||||
}
|
||||
var file_app_dns_config_proto_depIdxs = []int32{
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func init() { file_app_dns_config_proto_init() }
|
||||
@@ -732,8 +544,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: 2,
|
||||
NumMessages: 5,
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
+6
-25
@@ -7,26 +7,15 @@ option java_package = "com.xray.app.dns";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/net/destination.proto";
|
||||
import "app/router/config.proto";
|
||||
import "common/geodata/geodat.proto";
|
||||
|
||||
message NameServer {
|
||||
xray.common.net.Endpoint address = 1;
|
||||
bytes client_ip = 5;
|
||||
bool skipFallback = 6;
|
||||
|
||||
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;
|
||||
repeated xray.common.geodata.DomainRule domain = 2;
|
||||
repeated xray.common.geodata.IPRule expected_ip = 3;
|
||||
reserved 4;
|
||||
QueryStrategy query_strategy = 7;
|
||||
bool actPrior = 8;
|
||||
string tag = 9;
|
||||
@@ -35,18 +24,11 @@ message NameServer {
|
||||
optional bool serveStale = 15;
|
||||
optional uint32 serveExpiredTTL = 16;
|
||||
bool finalQuery = 12;
|
||||
repeated xray.app.router.GeoIP unexpected_geoip = 13;
|
||||
repeated xray.common.geodata.IPRule unexpected_ip = 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;
|
||||
@@ -64,8 +46,7 @@ message Config {
|
||||
bytes client_ip = 3;
|
||||
|
||||
message HostMapping {
|
||||
DomainMatchingType type = 1;
|
||||
string domain = 2;
|
||||
xray.common.geodata.DomainRule domain = 2;
|
||||
|
||||
repeated bytes ip = 3;
|
||||
|
||||
|
||||
+75
-142
@@ -5,20 +5,16 @@ 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/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
@@ -32,15 +28,15 @@ type DNS struct {
|
||||
hosts *StaticHosts
|
||||
clients []*Client
|
||||
ctx context.Context
|
||||
domainMatcher strmatcher.IndexMatcher
|
||||
domainMatcher geodata.DomainMatcher
|
||||
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
|
||||
domainRuleIdx uint16
|
||||
clientIdx uint16
|
||||
domainRule string
|
||||
}
|
||||
|
||||
// New creates a new DNS server with given configuration.
|
||||
@@ -85,56 +81,40 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
return nil, errors.New("unexpected query strategy ", config.QueryStrategy)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hosts, err := NewStaticHosts(config.StaticHosts)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create hosts").Base(err)
|
||||
}
|
||||
|
||||
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
|
||||
defaultTag := config.Tag
|
||||
if len(config.Tag) == 0 {
|
||||
defaultTag = generateRandomTag()
|
||||
}
|
||||
|
||||
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{}
|
||||
clients := make([]*Client, 0, len(config.NameServer))
|
||||
matcherInfos := make([]*DomainMatcherInfo, 0)
|
||||
effectiveRules := make([]*geodata.DomainRule, 0)
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
clientIdx := len(clients)
|
||||
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) {
|
||||
midx := domainMatcher.Add(domainRule)
|
||||
matcherInfos[midx] = &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRuleIdx: uint16(originalRuleIdx),
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,22 +139,31 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
serveExpiredTTL = *ns.ServeExpiredTTL
|
||||
}
|
||||
|
||||
var tag = defaultTag
|
||||
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, &matcherInfos, updateDomain)
|
||||
client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, updateRules)
|
||||
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))
|
||||
@@ -232,7 +221,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
|
||||
}
|
||||
|
||||
if s.checkSystem {
|
||||
supportIPv4, supportIPv6 := checkRoutes()
|
||||
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||
} else {
|
||||
@@ -283,24 +272,27 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
|
||||
// Priority domain matching
|
||||
hasMatch := false
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,17 +306,13 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
logDecision(s.ctx, domain, domainRules, clientNames)
|
||||
|
||||
if len(clients) == 0 {
|
||||
if len(s.clients) > 0 {
|
||||
@@ -339,6 +327,15 @@ 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
|
||||
@@ -540,67 +537,3 @@ 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
|
||||
}
|
||||
|
||||
+68
-100
@@ -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,7 +147,9 @@ func TestUDPServerSubnet(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -207,7 +209,9 @@ func TestUDPServer(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -331,10 +335,9 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "google.com"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -346,7 +349,9 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -415,7 +420,9 @@ func TestUDPServerIPv6(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -471,8 +478,7 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
},
|
||||
StaticHosts: []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "example.com"}}},
|
||||
ProxiedDomain: "google.com",
|
||||
},
|
||||
},
|
||||
@@ -483,7 +489,9 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -539,17 +547,9 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{
|
||||
CountryCode: "local",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
// inner ip, will not match
|
||||
Ip: []byte{192, 168, 11, 1},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
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}}}},
|
||||
},
|
||||
},
|
||||
// second dns, match ip
|
||||
@@ -563,25 +563,9 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
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}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -592,7 +576,9 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -663,19 +649,15 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
// Equivalent of dotless:localhost
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^[^.]*localhost[^.]*$"}}},
|
||||
},
|
||||
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},
|
||||
},
|
||||
},
|
||||
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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -688,23 +670,21 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
// Equivalent of dotless: and domain:local
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
||||
{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"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
StaticHosts: []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "hostnamestatic",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "hostnamestatic"}}},
|
||||
Ip: [][]byte{{127, 0, 0, 53}},
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "hostnamealias",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "hostnamealias"}}},
|
||||
ProxiedDomain: "hostname.localdomain",
|
||||
},
|
||||
},
|
||||
@@ -715,7 +695,9 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -891,19 +873,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
},
|
||||
},
|
||||
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},
|
||||
},
|
||||
},
|
||||
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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -916,18 +894,14 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
},
|
||||
},
|
||||
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},
|
||||
},
|
||||
},
|
||||
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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -940,18 +914,14 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "api.google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "api.google.com"}},
|
||||
},
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.7.7 (api.google.com)
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
|
||||
},
|
||||
},
|
||||
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}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -964,18 +934,14 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "v2.api.google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "v2.api.google.com"}},
|
||||
},
|
||||
},
|
||||
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},
|
||||
},
|
||||
},
|
||||
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}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -986,7 +952,9 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Outbound: []*core.OutboundHandlerConfig{
|
||||
{
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
||||
FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+16
-6
@@ -127,15 +127,20 @@ 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 {
|
||||
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
|
||||
}
|
||||
|
||||
qA := dnsmessage.Question{
|
||||
Name: dnsmessage.MustNewName(domain),
|
||||
Name: name,
|
||||
Type: dnsmessage.TypeA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
}
|
||||
|
||||
qAAAA := dnsmessage.Question{
|
||||
Name: dnsmessage.MustNewName(domain),
|
||||
Name: name,
|
||||
Type: dnsmessage.TypeAAAA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
}
|
||||
@@ -175,7 +180,7 @@ func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() ui
|
||||
})
|
||||
}
|
||||
|
||||
return reqs
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
// parseResponse parses DNS answers from the returned payload
|
||||
@@ -193,9 +198,14 @@ 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 {
|
||||
@@ -212,7 +222,7 @@ L:
|
||||
ttl = 1
|
||||
}
|
||||
expire := now.Add(time.Duration(ttl) * time.Second)
|
||||
if ipRecord.Expire.After(expire) {
|
||||
if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
|
||||
ipRecord.Expire = expire
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -24,7 +25,8 @@ 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")),
|
||||
@@ -34,7 +36,8 @@ 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")),
|
||||
@@ -131,10 +134,15 @@ 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)
|
||||
}
|
||||
})
|
||||
|
||||
+8
-12
@@ -17,7 +17,7 @@ import (
|
||||
type Holder struct {
|
||||
domainToIP cache.Lru
|
||||
ipRange *net.IPNet
|
||||
mu *sync.Mutex
|
||||
mu sync.Mutex
|
||||
|
||||
config *FakeDnsPool
|
||||
}
|
||||
@@ -49,9 +49,7 @@ func (fkdns *Holder) Start() error {
|
||||
}
|
||||
|
||||
func (fkdns *Holder) Close() error {
|
||||
fkdns.domainToIP = nil
|
||||
fkdns.ipRange = nil
|
||||
fkdns.mu = nil
|
||||
// nothing to do for now, just wait GC
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,7 +68,7 @@ func NewFakeDNSHolder() (*Holder, error) {
|
||||
}
|
||||
|
||||
func NewFakeDNSHolderConfigOnly(conf *FakeDnsPool) (*Holder, error) {
|
||||
return &Holder{nil, nil, nil, conf}, nil
|
||||
return &Holder{config: conf}, nil
|
||||
}
|
||||
|
||||
func (fkdns *Holder) initializeFromConfig() error {
|
||||
@@ -92,7 +90,6 @@ func (fkdns *Holder) initialize(ipPoolCidr string, lruSize int) error {
|
||||
}
|
||||
fkdns.domainToIP = cache.NewLru(lruSize)
|
||||
fkdns.ipRange = ipRange
|
||||
fkdns.mu = new(sync.Mutex)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -103,7 +100,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().UnixNano() / 1e6)
|
||||
currentTimeMillis := uint64(time.Now().UnixMilli())
|
||||
ones, bits := fkdns.ipRange.Mask.Size()
|
||||
rooms := bits - ones
|
||||
if rooms < 64 {
|
||||
@@ -202,12 +199,11 @@ func (h *HolderMulti) Start() error {
|
||||
}
|
||||
|
||||
func (h *HolderMulti) Close() error {
|
||||
var errs []error
|
||||
for _, v := range h.holders {
|
||||
if err := v.Close(); err != nil {
|
||||
return errors.New("Cannot close all fake dns pools").Base(err)
|
||||
}
|
||||
errs = append(errs, v.Close())
|
||||
}
|
||||
return nil
|
||||
return errors.Combine(errs...)
|
||||
}
|
||||
|
||||
func (h *HolderMulti) createHolderGroups() error {
|
||||
@@ -222,7 +218,7 @@ func (h *HolderMulti) createHolderGroups() error {
|
||||
}
|
||||
|
||||
func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
|
||||
holderMulti := &HolderMulti{nil, conf}
|
||||
holderMulti := &HolderMulti{config: conf}
|
||||
if err := holderMulti.createHolderGroups(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -129,15 +129,16 @@ 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)
|
||||
|
||||
|
||||
+37
-81
@@ -2,39 +2,28 @@ 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 {
|
||||
ips [][]net.Address
|
||||
matchers strmatcher.IndexMatcher
|
||||
responses [][]net.Address
|
||||
matcher geodata.DomainMatcher
|
||||
}
|
||||
|
||||
// NewStaticHosts creates a new StaticHosts instance.
|
||||
func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
g := new(strmatcher.MatcherGroup)
|
||||
sh := &StaticHosts{
|
||||
ips: make([][]net.Address, len(hosts)+16),
|
||||
matchers: g,
|
||||
}
|
||||
reps := make([][]net.Address, 0, len(hosts))
|
||||
rules := make([]*geodata.DomainRule, 0, len(hosts))
|
||||
|
||||
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)
|
||||
for _, mapping := range hosts {
|
||||
rep := make([]net.Address, 0, len(mapping.Ip))
|
||||
switch {
|
||||
case len(mapping.ProxiedDomain) > 0:
|
||||
if mapping.ProxiedDomain[0] == '#' {
|
||||
@@ -42,28 +31,36 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = append(ips, dns.RCodeError(rcode))
|
||||
rep = append(rep, dns.RCodeError(rcode))
|
||||
} else {
|
||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||
rep = append(rep, 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 [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule: ", mapping.Domain)
|
||||
continue
|
||||
}
|
||||
ips = append(ips, addr)
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
continue
|
||||
rep = append(rep, addr)
|
||||
}
|
||||
}
|
||||
|
||||
sh.ips[id] = ips
|
||||
reps = append(reps, rep)
|
||||
rules = append(rules, mapping.Domain)
|
||||
}
|
||||
|
||||
return sh, nil
|
||||
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
|
||||
}
|
||||
|
||||
func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||
@@ -79,16 +76,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 _, id := range h.matchers.Match(domain) {
|
||||
for _, v := range h.ips[id] {
|
||||
if err, ok := v.(dns.RCodeError); ok {
|
||||
for _, idx := range h.matcher.Match(domain) {
|
||||
for _, rep := range h.responses[idx] {
|
||||
if err, ok := rep.(dns.RCodeError); ok {
|
||||
if uint16(err) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ips = append(ips, h.ips[id]...)
|
||||
ips = append(ips, h.responses[idx]...)
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
@@ -98,10 +95,13 @@ 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 len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||
case addrs == nil: // Not recorded in static hosts, return nil
|
||||
return nil, nil
|
||||
case len(addrs) == 0: // Domain recorded, but no valid IP returned
|
||||
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,52 +122,8 @@ 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
|
||||
}
|
||||
|
||||
+7
-68
@@ -1,13 +1,12 @@
|
||||
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"
|
||||
)
|
||||
@@ -15,20 +14,17 @@ import (
|
||||
func TestStaticHosts(t *testing.T) {
|
||||
pb := []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "lan",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "lan"}}},
|
||||
ProxiedDomain: "#3",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "example.com"}}},
|
||||
Ip: [][]byte{
|
||||
{1, 1, 1, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "proxy.xray.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "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},
|
||||
@@ -36,20 +32,17 @@ func TestStaticHosts(t *testing.T) {
|
||||
ProxiedDomain: "another-proxy.xray.com",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "proxy2.xray.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "proxy2.xray.com"}}},
|
||||
ProxiedDomain: "proxy.xray.com",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "example.cn",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "example.cn"}}},
|
||||
Ip: [][]byte{
|
||||
{2, 2, 2, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "baidu.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "baidu.com"}}},
|
||||
Ip: [][]byte{
|
||||
{127, 0, 0, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
@@ -132,57 +125,3 @@ 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-106
@@ -3,34 +3,19 @@ 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/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"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.
|
||||
@@ -46,9 +31,8 @@ type Server interface {
|
||||
type Client struct {
|
||||
server Server
|
||||
skipFallback bool
|
||||
domains []string
|
||||
expectedIPs router.GeoIPMatcher
|
||||
unexpectedIPs router.GeoIPMatcher
|
||||
expectedIPs geodata.IPMatcher
|
||||
unexpectedIPs geodata.IPMatcher
|
||||
actPrior bool
|
||||
actUnprior bool
|
||||
tag string
|
||||
@@ -111,11 +95,9 @@ func NewClient(
|
||||
disableCache bool, serveStale bool, serveExpiredTTL uint32,
|
||||
tag string,
|
||||
ipOption dns.IPOption,
|
||||
matcherInfos *[]*DomainMatcherInfo,
|
||||
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo),
|
||||
updateRules func(bool),
|
||||
) (*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)
|
||||
@@ -123,97 +105,25 @@ func NewClient(
|
||||
return errors.New("failed to create nameserver").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()
|
||||
_, isLocalDNS := server.(*LocalNameServer)
|
||||
updateRules(isLocalDNS)
|
||||
|
||||
// Establish expected IPs
|
||||
var expectedMatcher router.GeoIPMatcher
|
||||
if len(ns.ExpectedGeoip) > 0 {
|
||||
expectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)
|
||||
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()
|
||||
}
|
||||
ns.ExpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// Establish unexpected IPs
|
||||
var unexpectedMatcher router.GeoIPMatcher
|
||||
if len(ns.UnexpectedGeoip) > 0 {
|
||||
unexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)
|
||||
var unexpectedMatcher geodata.IPMatcher
|
||||
if len(ns.UnexpectedIp) > 0 {
|
||||
unexpectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.UnexpectedIp)
|
||||
if err != nil {
|
||||
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
ns.UnexpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(clientIP) > 0 {
|
||||
@@ -225,7 +135,7 @@ func NewClient(
|
||||
}
|
||||
}
|
||||
|
||||
var timeoutMs = 4000 * time.Millisecond
|
||||
timeoutMs := 4000 * time.Millisecond
|
||||
if ns.TimeoutMs > 0 {
|
||||
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
||||
}
|
||||
@@ -234,7 +144,6 @@ func NewClient(
|
||||
|
||||
client.server = server
|
||||
client.skipFallback = ns.SkipFallback
|
||||
client.domains = rules
|
||||
client.expectedIPs = expectedMatcher
|
||||
client.unexpectedIPs = unexpectedMatcher
|
||||
client.actPrior = ns.ActPrior
|
||||
@@ -258,7 +167,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 := checkRoutes()
|
||||
supportIPv4, supportIPv6 := utils.CheckRoutes()
|
||||
option.IPv4Enable = option.IPv4Enable && supportIPv4
|
||||
option.IPv6Enable = option.IPv6Enable && supportIPv6
|
||||
} else {
|
||||
|
||||
@@ -137,14 +137,32 @@ 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 {
|
||||
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
|
||||
err := errors.New("tries to resolve itself!", s.Name())
|
||||
if option.IPv4Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
}
|
||||
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 := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
||||
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
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -18,7 +18,6 @@ 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,7 +78,19 @@ 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 := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
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
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -113,7 +113,19 @@ 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 := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
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
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -131,8 +131,6 @@ 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
|
||||
}
|
||||
@@ -163,7 +161,19 @@ 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 := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
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
|
||||
}
|
||||
|
||||
for _, req := range reqs {
|
||||
udpReq := &udpDnsRequest{
|
||||
@@ -179,8 +189,6 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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))
|
||||
}))
|
||||
}
|
||||
+150
-64
@@ -2,16 +2,18 @@ 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"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
xnet "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"
|
||||
@@ -22,15 +24,17 @@ import (
|
||||
type MetricsHandler struct {
|
||||
ohm outbound.Manager
|
||||
statsManager feature_stats.Manager
|
||||
observatory extension.Observatory
|
||||
ctx context.Context
|
||||
tag string
|
||||
listen string
|
||||
tcpListener net.Listener
|
||||
tcpListener xnet.Listener
|
||||
listener *OutboundListener
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
@@ -38,50 +42,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -90,46 +50,172 @@ 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 := net.Listen("tcp", p.listen)
|
||||
TCPlistener, err := xnet.Listen("tcp", p.listen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.tcpListener = TCPlistener
|
||||
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
|
||||
|
||||
go func() {
|
||||
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||
}
|
||||
}()
|
||||
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
|
||||
}
|
||||
|
||||
listener := &OutboundListener{
|
||||
buffer: make(chan net.Conn, 4),
|
||||
buffer: make(chan xnet.Conn, 4),
|
||||
done: done.New(),
|
||||
}
|
||||
p.listener = listener
|
||||
|
||||
go func() {
|
||||
if err := http.Serve(listener, http.DefaultServeMux); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
|
||||
}
|
||||
}()
|
||||
go p.serve(listener, handler)
|
||||
|
||||
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
|
||||
errors.LogInfo(context.Background(), "failed to remove existing handler")
|
||||
}
|
||||
|
||||
return p.ohm.AddHandler(context.Background(), &Outbound{
|
||||
if err := 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 {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
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,7 +2,6 @@ package burst
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/app/observatory"
|
||||
@@ -72,7 +71,6 @@ 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,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
@@ -24,11 +25,12 @@ type HealthPingSettings struct {
|
||||
|
||||
// HealthPing is the health checker for balancers
|
||||
type HealthPing struct {
|
||||
ctx context.Context
|
||||
dispatcher routing.Dispatcher
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
tickerClose chan struct{}
|
||||
ctx context.Context
|
||||
cancelCtx context.CancelFunc
|
||||
cancelPending atomic.Pointer[context.CancelFunc]
|
||||
dispatcher routing.Dispatcher
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
|
||||
Settings *HealthPingSettings
|
||||
Results map[string]*HealthPingRTTS
|
||||
@@ -62,10 +64,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 = time.Duration(1) * time.Minute
|
||||
} else if settings.Interval < 10 {
|
||||
settings.Interval = 1 * time.Minute
|
||||
} else if settings.Interval < 10*time.Second {
|
||||
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
|
||||
settings.Interval = time.Duration(10) * time.Second
|
||||
settings.Interval = 10 * time.Second
|
||||
}
|
||||
if settings.SamplingCount <= 0 {
|
||||
settings.SamplingCount = 10
|
||||
@@ -73,10 +75,12 @@ 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 = time.Duration(5) * time.Second
|
||||
settings.Timeout = 5 * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &HealthPing{
|
||||
ctx: ctx,
|
||||
cancelCtx: cancel,
|
||||
dispatcher: dispatcher,
|
||||
Settings: settings,
|
||||
Results: nil,
|
||||
@@ -90,9 +94,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
|
||||
h.tickerClose = tickerClose
|
||||
|
||||
// init run to get a fast check result
|
||||
go func() {
|
||||
tags, err := selector()
|
||||
if err != nil {
|
||||
@@ -110,13 +114,20 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
|
||||
return
|
||||
}
|
||||
h.doCheck(tags, interval, h.Settings.SamplingCount)
|
||||
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.Cleanup(tags)
|
||||
}()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
continue
|
||||
case <-tickerClose:
|
||||
case <-h.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -130,8 +141,7 @@ func (h *HealthPing) StopScheduler() {
|
||||
}
|
||||
h.ticker.Stop()
|
||||
h.ticker = nil
|
||||
close(h.tickerClose)
|
||||
h.tickerClose = nil
|
||||
h.cancelCtx()
|
||||
}
|
||||
|
||||
// Check implements the HealthChecker
|
||||
@@ -140,7 +150,7 @@ func (h *HealthPing) Check(tags []string) error {
|
||||
return nil
|
||||
}
|
||||
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
|
||||
h.doCheck(tags, 0, 1)
|
||||
h.doCheck(h.ctx, tags, 0, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -151,13 +161,14 @@ type rtt struct {
|
||||
|
||||
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
|
||||
// sure all tags are valid for current balancer
|
||||
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
|
||||
// cancel ctx will stop all pending checks
|
||||
func (h *HealthPing) doCheck(ctx context.Context, 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(
|
||||
@@ -172,7 +183,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
|
||||
if duration > 0 {
|
||||
delay = time.Duration(dice.RollInt63n(int64(duration)))
|
||||
}
|
||||
time.AfterFunc(delay, func() {
|
||||
timers = append(timers, time.AfterFunc(delay, func() {
|
||||
errors.LogDebug(h.ctx, "checking ", handler)
|
||||
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
|
||||
if err == nil {
|
||||
@@ -200,14 +211,21 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
|
||||
handler: handler,
|
||||
value: rttFailed,
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
rtt := <-ch
|
||||
if rtt.value > 0 {
|
||||
// should not put results when network is down
|
||||
h.PutResult(rtt.handler, rtt.value)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
h.rtts[i] = &pingRTT{value: rttUntested}
|
||||
}
|
||||
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 == 0 || time.Since(rtt.time) > h.validity:
|
||||
case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
|
||||
continue
|
||||
case rtt.value == rttFailed:
|
||||
stats.Fail++
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -70,13 +71,19 @@ func (o *Observer) background() {
|
||||
|
||||
outbounds := hs.Select(o.config.SubjectSelector)
|
||||
|
||||
o.updateStatus(outbounds)
|
||||
o.clearRemovedOutbounds(outbounds)
|
||||
|
||||
sleepTime := time.Second * 10
|
||||
if o.config.ProbeInterval != 0 {
|
||||
sleepTime = time.Duration(o.config.ProbeInterval)
|
||||
}
|
||||
|
||||
if len(outbounds) == 0 {
|
||||
errors.LogWarning(o.ctx, "no outbound matches subjectSelector ", o.config.SubjectSelector)
|
||||
time.Sleep(sleepTime)
|
||||
continue
|
||||
}
|
||||
|
||||
if !o.config.EnableConcurrency {
|
||||
sort.Strings(outbounds)
|
||||
for _, v := range outbounds {
|
||||
@@ -111,11 +118,19 @@ func (o *Observer) background() {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Observer) updateStatus(outbounds []string) {
|
||||
func (o *Observer) clearRemovedOutbounds(outbounds []string) {
|
||||
o.statusLock.Lock()
|
||||
defer o.statusLock.Unlock()
|
||||
// TODO should remove old inbound that is removed
|
||||
_ = outbounds
|
||||
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
|
||||
}
|
||||
|
||||
func (o *Observer) probe(outbound string) ProbeResult {
|
||||
@@ -177,7 +192,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
|
||||
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}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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
|
||||
}
|
||||
var result = make([]*protocol.User, 0, 100)
|
||||
result := make([]*protocol.User, 0, 100)
|
||||
users := um.GetUsers(ctx)
|
||||
for _, u := range users {
|
||||
result = append(result, protocol.ToProtoUser(u))
|
||||
|
||||
@@ -1 +1,34 @@
|
||||
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
|
||||
}
|
||||
|
||||
+44
-41
@@ -7,6 +7,7 @@
|
||||
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"
|
||||
@@ -66,8 +67,9 @@ 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 []string `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"`
|
||||
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"`
|
||||
// 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.
|
||||
@@ -121,13 +123,20 @@ func (x *SniffingConfig) GetDestinationOverride() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SniffingConfig) GetDomainsExcluded() []string {
|
||||
func (x *SniffingConfig) GetDomainsExcluded() []*geodata.DomainRule {
|
||||
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
|
||||
@@ -321,7 +330,6 @@ type SenderConfig struct {
|
||||
// Send traffic through the given IP. Only IP is allowed.
|
||||
Via *net.IPOrDomain `protobuf:"bytes,1,opt,name=via,proto3" json:"via,omitempty"`
|
||||
StreamSettings *internet.StreamConfig `protobuf:"bytes,2,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
|
||||
ProxySettings *internet.ProxyConfig `protobuf:"bytes,3,opt,name=proxy_settings,json=proxySettings,proto3" json:"proxy_settings,omitempty"`
|
||||
MultiplexSettings *MultiplexingConfig `protobuf:"bytes,4,opt,name=multiplex_settings,json=multiplexSettings,proto3" json:"multiplex_settings,omitempty"`
|
||||
ViaCidr string `protobuf:"bytes,5,opt,name=via_cidr,json=viaCidr,proto3" json:"via_cidr,omitempty"`
|
||||
TargetStrategy internet.DomainStrategy `protobuf:"varint,6,opt,name=target_strategy,json=targetStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"target_strategy,omitempty"`
|
||||
@@ -373,13 +381,6 @@ func (x *SenderConfig) GetStreamSettings() *internet.StreamConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SenderConfig) GetProxySettings() *internet.ProxyConfig {
|
||||
if x != nil {
|
||||
return x.ProxySettings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SenderConfig) GetMultiplexSettings() *MultiplexingConfig {
|
||||
if x != nil {
|
||||
return x.MultiplexSettings
|
||||
@@ -477,12 +478,13 @@ 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\"\x0f\n" +
|
||||
"\rInboundConfig\"\xcc\x01\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" +
|
||||
"\x0eSniffingConfig\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x121\n" +
|
||||
"\x14destination_override\x18\x02 \x03(\tR\x13destinationOverride\x12)\n" +
|
||||
"\x10domains_excluded\x18\x03 \x03(\tR\x0fdomainsExcluded\x12#\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" +
|
||||
"\rmetadata_only\x18\x04 \x01(\bR\fmetadataOnly\x12\x1d\n" +
|
||||
"\n" +
|
||||
"route_only\x18\x05 \x01(\bR\trouteOnly\"\xe5\x02\n" +
|
||||
@@ -496,14 +498,13 @@ const file_app_proxyman_config_proto_rawDesc = "" +
|
||||
"\x03tag\x18\x01 \x01(\tR\x03tag\x12M\n" +
|
||||
"\x11receiver_settings\x18\x02 \x01(\v2 .xray.common.serial.TypedMessageR\x10receiverSettings\x12G\n" +
|
||||
"\x0eproxy_settings\x18\x03 \x01(\v2 .xray.common.serial.TypedMessageR\rproxySettings\"\x10\n" +
|
||||
"\x0eOutboundConfig\"\x9d\x03\n" +
|
||||
"\x0eOutboundConfig\"\xd6\x02\n" +
|
||||
"\fSenderConfig\x12-\n" +
|
||||
"\x03via\x18\x01 \x01(\v2\x1b.xray.common.net.IPOrDomainR\x03via\x12N\n" +
|
||||
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12K\n" +
|
||||
"\x0eproxy_settings\x18\x03 \x01(\v2$.xray.transport.internet.ProxyConfigR\rproxySettings\x12T\n" +
|
||||
"\x0fstream_settings\x18\x02 \x01(\v2%.xray.transport.internet.StreamConfigR\x0estreamSettings\x12T\n" +
|
||||
"\x12multiplex_settings\x18\x04 \x01(\v2%.xray.app.proxyman.MultiplexingConfigR\x11multiplexSettings\x12\x19\n" +
|
||||
"\bvia_cidr\x18\x05 \x01(\tR\aviaCidr\x12P\n" +
|
||||
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategy\"\xa4\x01\n" +
|
||||
"\x0ftarget_strategy\x18\x06 \x01(\x0e2'.xray.transport.internet.DomainStrategyR\x0etargetStrategyJ\x04\b\x03\x10\x04\"\xa4\x01\n" +
|
||||
"\x12MultiplexingConfig\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\x12 \n" +
|
||||
"\vconcurrency\x18\x02 \x01(\x05R\vconcurrency\x12(\n" +
|
||||
@@ -532,30 +533,32 @@ 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
|
||||
(*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
|
||||
(*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.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
|
||||
}
|
||||
var file_app_proxyman_config_proto_depIdxs = []int32{
|
||||
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
|
||||
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
|
||||
6, // 10: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
|
||||
13, // 11: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_proxyman_config_proto_init() }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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 {}
|
||||
|
||||
@@ -20,7 +21,10 @@ 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 string domains_excluded = 3;
|
||||
|
||||
repeated xray.common.geodata.DomainRule domains_excluded = 3;
|
||||
|
||||
repeated xray.common.geodata.IPRule ips_excluded = 6;
|
||||
|
||||
// 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
|
||||
@@ -53,7 +57,7 @@ message SenderConfig {
|
||||
// Send traffic through the given IP. Only IP is allowed.
|
||||
xray.common.net.IPOrDomain via = 1;
|
||||
xray.transport.internet.StreamConfig stream_settings = 2;
|
||||
xray.transport.internet.ProxyConfig proxy_settings = 3;
|
||||
reserved 3;
|
||||
MultiplexingConfig multiplex_settings = 4;
|
||||
string via_cidr = 5;
|
||||
xray.transport.internet.DomainStrategy target_strategy = 6;
|
||||
|
||||
@@ -26,7 +26,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
|
||||
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
|
||||
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
|
||||
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
|
||||
c, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
c, _ := statsManager.GetOrRegisterCounter(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, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
@@ -53,21 +53,27 @@ type AlwaysOnInboundHandler struct {
|
||||
}
|
||||
|
||||
func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*AlwaysOnInboundHandler, error) {
|
||||
// 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,
|
||||
},
|
||||
})
|
||||
sniffingRequest, err := proxyman.BuildSniffingRequest(receiverConfig.SniffingSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,11 +99,6 @@ 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{}
|
||||
@@ -117,7 +118,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
stream: mss,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
sniffingRequest: sniffingRequest,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
@@ -139,7 +140,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
recvOrigDest: receiverConfig.ReceiveOriginalDestination,
|
||||
tag: tag,
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
sniffingRequest: sniffingRequest,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
ctx: ctx,
|
||||
@@ -154,7 +155,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
address: address,
|
||||
port: net.Port(port),
|
||||
dispatcher: h.mux,
|
||||
sniffingConfig: receiverConfig.SniffingSettings,
|
||||
sniffingRequest: sniffingRequest,
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
stream: mss,
|
||||
@@ -171,6 +172,12 @@ 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
|
||||
@@ -186,6 +193,7 @@ 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,7 +6,6 @@ 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"
|
||||
@@ -19,9 +18,9 @@ import (
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
"github.com/xtls/xray-core/proxy"
|
||||
"github.com/xtls/xray-core/proxy/hysteria/account"
|
||||
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
|
||||
hysteria_proxy "github.com/xtls/xray-core/proxy/hysteria"
|
||||
"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"
|
||||
@@ -43,7 +42,7 @@ type tcpWorker struct {
|
||||
recvOrigDest bool
|
||||
tag string
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
sniffingRequest session.SniffingRequest
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
@@ -118,13 +117,7 @@ func (w *tcpWorker) callback(conn stat.Connection) {
|
||||
})
|
||||
|
||||
content := new(session.Content)
|
||||
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
|
||||
}
|
||||
content.SniffingRequest = w.sniffingRequest
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
|
||||
if err := w.proxy.Process(ctx, net.Network_TCP, conn, w.dispatcher); err != nil {
|
||||
@@ -141,10 +134,8 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
|
||||
func (w *tcpWorker) Start() error {
|
||||
ctx := context.Background()
|
||||
|
||||
type HysteriaInboundValidator interface{ HysteriaInboundValidator() *account.Validator }
|
||||
if v, ok := w.proxy.(HysteriaInboundValidator); ok {
|
||||
ctx = hyCtx.ContextWithRequireDatagram(ctx, true)
|
||||
ctx = hyCtx.ContextWithValidator(ctx, v.HysteriaInboundValidator())
|
||||
if v, ok := w.proxy.(*hysteria_proxy.Server); ok {
|
||||
ctx = hysteria.ContextWithValidator(ctx, v.HysteriaInboundValidator())
|
||||
}
|
||||
|
||||
hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn stat.Connection) {
|
||||
@@ -275,7 +266,7 @@ type udpWorker struct {
|
||||
tag string
|
||||
stream *internet.MemoryStreamConfig
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
sniffingRequest session.SniffingRequest
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
@@ -365,13 +356,7 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
||||
Tag: w.tag,
|
||||
})
|
||||
content := new(session.Content)
|
||||
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
|
||||
}
|
||||
content.SniffingRequest = w.sniffingRequest
|
||||
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")
|
||||
@@ -487,7 +472,7 @@ type dsWorker struct {
|
||||
stream *internet.MemoryStreamConfig
|
||||
tag string
|
||||
dispatcher routing.Dispatcher
|
||||
sniffingConfig *proxyman.SniffingConfig
|
||||
sniffingRequest session.SniffingRequest
|
||||
uplinkCounter stats.Counter
|
||||
downlinkCounter stats.Counter
|
||||
|
||||
@@ -517,13 +502,7 @@ func (w *dsWorker) callback(conn stat.Connection) {
|
||||
})
|
||||
|
||||
content := new(session.Content)
|
||||
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
|
||||
}
|
||||
content.SniffingRequest = w.sniffingRequest
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
|
||||
if err := w.proxy.Process(ctx, net.Network_UNIX, conn, w.dispatcher); err != nil {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
|
||||
@@ -16,7 +15,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/mux"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/net/cnc"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -27,8 +25,6 @@ import (
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
"github.com/xtls/xray-core/transport/pipe"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -40,7 +36,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, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||
if c != nil {
|
||||
uplinkCounter = c
|
||||
}
|
||||
@@ -48,7 +44,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, _ := stats.GetOrRegisterCounter(statsManager, name)
|
||||
c, _ := statsManager.GetOrRegisterCounter(name)
|
||||
if c != nil {
|
||||
downlinkCounter = c
|
||||
}
|
||||
@@ -64,7 +60,6 @@ type Handler struct {
|
||||
streamSettings *internet.MemoryStreamConfig
|
||||
proxyConfig proto.Message
|
||||
proxy proxy.Outbound
|
||||
outboundManager outbound.Manager
|
||||
mux *mux.ClientManager
|
||||
xudp *mux.ClientManager
|
||||
udp443 string
|
||||
@@ -78,7 +73,6 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
||||
uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
|
||||
h := &Handler{
|
||||
tag: config.Tag,
|
||||
outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
|
||||
uplinkCounter: uplinkCounter,
|
||||
downlinkCounter: downlinkCounter,
|
||||
}
|
||||
@@ -109,6 +103,10 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
||||
|
||||
ctx = session.ContextWithFullHandler(ctx, h)
|
||||
|
||||
if h.streamSettings != nil {
|
||||
ctx = session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||
}
|
||||
|
||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -196,7 +194,6 @@ func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
unchangedDomain := ob.Target.Address.Domain()
|
||||
ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
|
||||
@@ -269,71 +266,26 @@ func (h *Handler) DestIpAddress() net.IP {
|
||||
|
||||
// Dial implements internet.Dialer.
|
||||
func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||
if h.senderSettings != nil {
|
||||
|
||||
if h.senderSettings.ProxySettings.HasTag() {
|
||||
|
||||
tag := h.senderSettings.ProxySettings.Tag
|
||||
handler := h.outboundManager.GetHandler(tag)
|
||||
if handler != nil {
|
||||
errors.LogDebug(ctx, "proxying to ", tag, " for dest ", dest)
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ctx = session.ContextWithOutbounds(ctx, append(outbounds, &session.Outbound{
|
||||
Target: dest,
|
||||
Tag: tag,
|
||||
})) // add another outbound in session ctx
|
||||
opts := pipe.OptionsFromContext(ctx)
|
||||
uplinkReader, uplinkWriter := pipe.New(opts...)
|
||||
downlinkReader, downlinkWriter := pipe.New(opts...)
|
||||
|
||||
go handler.Dispatch(ctx, &transport.Link{Reader: uplinkReader, Writer: downlinkWriter})
|
||||
conn := cnc.NewConnection(cnc.ConnectionInputMulti(uplinkWriter), cnc.ConnectionOutputMulti(downlinkReader))
|
||||
|
||||
if config := tls.ConfigFromStreamSettings(h.streamSettings); config != nil {
|
||||
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
|
||||
conn = tls.Client(conn, tlsConfig)
|
||||
}
|
||||
|
||||
return h.getStatCouterConnection(conn), nil
|
||||
}
|
||||
|
||||
errors.LogError(ctx, "failed to get outbound handler with tag: ", tag)
|
||||
return nil, errors.New("failed to get outbound handler with tag: " + tag)
|
||||
}
|
||||
|
||||
if h.senderSettings.Via != nil {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
h.SetOutboundGateway(ctx, ob)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {
|
||||
return conn, err
|
||||
if h.senderSettings != nil && h.senderSettings.Via != nil {
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
h.SetOutboundGateway(ctx, ob)
|
||||
}
|
||||
|
||||
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
||||
conn = h.getStatCouterConnection(conn)
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
if outbounds != nil {
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
ob.Conn = conn
|
||||
} else {
|
||||
// for Vision's pre-connect
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound) {
|
||||
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil && !h.senderSettings.ProxySettings.HasTag() && (h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
||||
if ob.Gateway == nil && h.senderSettings != nil && h.senderSettings.Via != nil &&
|
||||
(h.streamSettings.SocketSettings == nil || len(h.streamSettings.SocketSettings.DialerProxy) == 0) {
|
||||
var domain string
|
||||
addr := h.senderSettings.Via.AsAddress()
|
||||
domain = h.senderSettings.Via.GetDomain()
|
||||
switch {
|
||||
case h.senderSettings.ViaCidr != "":
|
||||
ob.Gateway = ParseRandomIP(addr, h.senderSettings.ViaCidr)
|
||||
|
||||
case domain == "origin":
|
||||
if inbound := session.InboundFromContext(ctx); inbound != nil {
|
||||
if inbound.Local.IsValid() && inbound.Local.Address.Family().IsIP() {
|
||||
@@ -348,12 +300,9 @@ 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():
|
||||
default:
|
||||
default: // case addr.Family().IsDomain():
|
||||
ob.Gateway = addr
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +345,6 @@ 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{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||
})
|
||||
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{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||
})
|
||||
conn, _ := h.(*Handler).Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), 13146))
|
||||
_, ok := conn.(*stat.CounterConnection)
|
||||
@@ -88,7 +88,6 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTagsCache(t *testing.T) {
|
||||
|
||||
test_duration := 10 * time.Second
|
||||
threads_num := 50
|
||||
delay := 10 * time.Millisecond
|
||||
@@ -118,7 +117,7 @@ func TestTagsCache(t *testing.T) {
|
||||
tag := fmt.Sprintf("%s%d", tags_prefix, idx)
|
||||
cfg := &core.OutboundHandlerConfig{
|
||||
Tag: tag,
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
|
||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{FinalRules: []*freedom.FinalRuleConfig{{Action: freedom.RuleAction_Allow}}}),
|
||||
}
|
||||
if h, err := NewHandler(ctx, cfg); err == nil {
|
||||
if err := ohm.AddHandler(ctx, h); err == nil {
|
||||
|
||||
@@ -162,7 +162,6 @@ 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)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
)
|
||||
|
||||
func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||
if dest.Address == nil {
|
||||
return nil, errors.New("nil destination address")
|
||||
}
|
||||
if !dest.Address.Family().IsDomain() {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
var uotVersion int
|
||||
if dest.Address.Domain() == uot.MagicAddress {
|
||||
uotVersion = uot.Version
|
||||
} else if dest.Address.Domain() == uot.LegacyMagicAddress {
|
||||
uotVersion = uot.LegacyVersion
|
||||
} else {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
packetConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to listen socket").Base(err)
|
||||
}
|
||||
conn := uot.NewServerConn(packetConn, uotVersion)
|
||||
return h.getStatCouterConnection(conn), nil
|
||||
}
|
||||
@@ -136,7 +136,7 @@ func (b *Balancer) SelectOutbounds() ([]string, error) {
|
||||
|
||||
// GetPrincipleTarget implements routing.BalancerPrincipleTarget
|
||||
func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
||||
if b, ok := r.balancers[tag]; ok {
|
||||
if b, ok := (*r.balancers.Load())[tag]; ok {
|
||||
if s, ok := b.strategy.(BalancingPrincipleTarget); ok {
|
||||
candidates, err := b.SelectOutbounds()
|
||||
if err != nil {
|
||||
@@ -151,7 +151,7 @@ func (r *Router) GetPrincipleTarget(tag string) ([]string, error) {
|
||||
|
||||
// SetOverrideTarget implements routing.BalancerOverrider
|
||||
func (r *Router) SetOverrideTarget(tag, target string) error {
|
||||
if b, ok := r.balancers[tag]; ok {
|
||||
if b, ok := (*r.balancers.Load())[tag]; ok {
|
||||
b.override.Put(target)
|
||||
return nil
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func (r *Router) SetOverrideTarget(tag, target string) error {
|
||||
|
||||
// GetOverrideTarget implements routing.BalancerOverrider
|
||||
func (r *Router) GetOverrideTarget(tag string) (string, error) {
|
||||
if b, ok := r.balancers[tag]; ok {
|
||||
if b, ok := (*r.balancers.Load())[tag]; ok {
|
||||
return b.override.Get(), nil
|
||||
}
|
||||
return "", errors.New("cannot find tag")
|
||||
|
||||
@@ -2,25 +2,8 @@ package router
|
||||
|
||||
import (
|
||||
sync "sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
func (r *Router) OverrideBalancer(balancer string, target string) error {
|
||||
var b *Balancer
|
||||
for tag, bl := range r.balancers {
|
||||
if tag == balancer {
|
||||
b = bl
|
||||
break
|
||||
}
|
||||
}
|
||||
if b == nil {
|
||||
return errors.New("balancer '", balancer, "' not found")
|
||||
}
|
||||
b.override.Put(target)
|
||||
return nil
|
||||
}
|
||||
|
||||
type overrideSettings struct {
|
||||
target string
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ 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,6 +12,7 @@ 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"
|
||||
@@ -303,12 +304,12 @@ func TestServiceTestRoute(t *testing.T) {
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
Domain: []*router.Domain{{Type: router.Domain_Domain, Value: "com"}},
|
||||
Domain: []*geodata.DomainRule{{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "com"}}}},
|
||||
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"},
|
||||
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"},
|
||||
},
|
||||
{
|
||||
UserEmail: []string{"example@example.com"},
|
||||
|
||||
+49
-66
@@ -2,17 +2,18 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"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,67 +46,18 @@ func (v *ConditionChan) Len() int {
|
||||
return len(*v)
|
||||
}
|
||||
|
||||
var matcherTypeMap = map[Domain_Type]strmatcher.Type{
|
||||
Domain_Plain: strmatcher.Substr,
|
||||
Domain_Regex: strmatcher.Regex,
|
||||
Domain_Domain: strmatcher.Domain,
|
||||
Domain_Full: strmatcher.Full,
|
||||
}
|
||||
type DomainMatcher struct{ geodata.DomainMatcher }
|
||||
|
||||
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)
|
||||
func NewDomainMatcher(rules []*geodata.DomainRule) (*DomainMatcher, error) {
|
||||
m, err := geodata.DomainReg.BuildDomainMatcher(rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
return &DomainMatcher{DomainMatcher: m}, nil
|
||||
}
|
||||
|
||||
func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
||||
return len(m.Matchers.Match(strings.ToLower(domain))) > 0
|
||||
return m.DomainMatcher.MatchAny(strings.ToLower(domain))
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
@@ -114,7 +66,7 @@ func (m *DomainMatcher) Apply(ctx routing.Context) bool {
|
||||
if len(domain) == 0 {
|
||||
return false
|
||||
}
|
||||
return m.ApplyDomain(domain)
|
||||
return m.DomainMatcher.MatchAny(strings.ToLower(domain))
|
||||
}
|
||||
|
||||
type MatcherAsType byte
|
||||
@@ -127,16 +79,16 @@ const (
|
||||
)
|
||||
|
||||
type IPMatcher struct {
|
||||
matcher GeoIPMatcher
|
||||
matcher geodata.IPMatcher
|
||||
asType MatcherAsType
|
||||
}
|
||||
|
||||
func NewIPMatcher(geoips []*GeoIP, asType MatcherAsType) (*IPMatcher, error) {
|
||||
matcher, err := BuildOptimizedGeoIPMatcher(geoips...)
|
||||
func NewIPMatcher(rules []*geodata.IPRule, asType MatcherAsType) (*IPMatcher, error) {
|
||||
m, err := geodata.IPReg.BuildIPMatcher(rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IPMatcher{matcher: matcher, asType: asType}, nil
|
||||
return &IPMatcher{matcher: m, asType: asType}, nil
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
@@ -390,8 +342,10 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||
if len(ctx.GetSourceIPs()) == 0 {
|
||||
return false
|
||||
}
|
||||
srcPort := ctx.GetSourcePort().String()
|
||||
|
||||
srcPort := uint16(ctx.GetSourcePort())
|
||||
srcIP := ctx.GetSourceIPs()[0].String()
|
||||
|
||||
var network string
|
||||
switch ctx.GetNetwork() {
|
||||
case net.Network_TCP:
|
||||
@@ -401,11 +355,21 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
src, err := net.ParseDestination(strings.Join([]string{network, srcIP, srcPort}, ":"))
|
||||
if err != nil {
|
||||
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())
|
||||
}
|
||||
pid, name, absPath, err := net.FindProcess(src)
|
||||
|
||||
pid, name, absPath, err := net.FindProcess(network, srcIP, uint16(srcPort), dstIP, uint16(dstPort))
|
||||
if err != nil {
|
||||
if err != net.ErrNotLocal {
|
||||
errors.LogError(context.Background(), "Unables to find local process name: ", err)
|
||||
@@ -430,3 +394,22 @@ func (m *ProcessNameMatcher) Apply(ctx routing.Context) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LocalOSMatcher matches the operating system Xray itself is running on. That never
|
||||
// changes while Xray is running, so the result is resolved when the rule is built.
|
||||
type LocalOSMatcher struct {
|
||||
matched bool
|
||||
}
|
||||
|
||||
func NewLocalOSMatcher(names []string) *LocalOSMatcher {
|
||||
return &LocalOSMatcher{
|
||||
matched: slices.ContainsFunc(names, func(name string) bool {
|
||||
return strings.EqualFold(name, runtime.GOOS)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
func (m *LocalOSMatcher) Apply(_ routing.Context) bool {
|
||||
return m.matched
|
||||
}
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
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,20 +1,21 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "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/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 {
|
||||
@@ -45,18 +46,15 @@ func TestRoutingRule(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
Domain: []*Domain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Value: "example.com",
|
||||
Type: Domain_Plain,
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Substr, Value: "example.com"}},
|
||||
},
|
||||
{
|
||||
Value: "google.com",
|
||||
Type: Domain_Domain,
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
},
|
||||
{
|
||||
Value: "^facebook\\.com$",
|
||||
Type: Domain_Regex,
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^facebook\\.com$"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -93,20 +91,25 @@ func TestRoutingRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
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: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
},
|
||||
{
|
||||
Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
|
||||
Prefix: 128,
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(), Prefix: 128},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -133,12 +136,11 @@ func TestRoutingRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
SourceGeoip: []*GeoIP{
|
||||
SourceIp: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -300,35 +302,12 @@ 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) {
|
||||
domains, err := loadGeoSite("CN")
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||
common.Must(err)
|
||||
|
||||
acMatcher, err := NewMphMatcherGroup(domains)
|
||||
matcher, err := NewDomainMatcher(rules)
|
||||
common.Must(err)
|
||||
|
||||
type TestCase struct {
|
||||
@@ -359,18 +338,44 @@ func TestChinaSites(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
r := acMatcher.ApplyDomain(testCase.Domain)
|
||||
r := matcher.ApplyDomain(testCase.Domain)
|
||||
if r != testCase.Output {
|
||||
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
||||
t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalOSRule(t *testing.T) {
|
||||
otherOS := "plan9"
|
||||
if runtime.GOOS == otherOS {
|
||||
otherOS = "linux"
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
localOS []string
|
||||
output bool
|
||||
}{
|
||||
{localOS: []string{runtime.GOOS}, output: true},
|
||||
{localOS: []string{otherOS}, output: false},
|
||||
{localOS: []string{otherOS, runtime.GOOS}, output: true},
|
||||
{localOS: []string{strings.ToUpper(runtime.GOOS)}, output: true},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
cond, err := (&RoutingRule{LocalOs: test.localOS}).BuildCondition()
|
||||
common.Must(err)
|
||||
if got := cond.Apply(withBackground()); got != test.output {
|
||||
t.Errorf("for localOS %v on %s: expected %v, got %v", test.localOS, runtime.GOOS, test.output, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||
domains, err := loadGeoSite("CN")
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := NewMphMatcherGroup(domains)
|
||||
matcher, err := NewDomainMatcher(rules)
|
||||
common.Must(err)
|
||||
|
||||
type TestCase struct {
|
||||
@@ -409,45 +414,11 @@ func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||
var geoips []*GeoIP
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseIPRules([]string{"geoip:cn", "geoip:jp", "geoip:ca", "geoip:us"})
|
||||
common.Must(err)
|
||||
|
||||
{
|
||||
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)
|
||||
matcher, err := NewIPMatcher(rules, MatcherAsType_Target)
|
||||
common.Must(err)
|
||||
|
||||
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
|
||||
|
||||
+14
-53
@@ -3,12 +3,9 @@ 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"
|
||||
)
|
||||
@@ -36,6 +33,10 @@ func (r *Rule) Apply(ctx routing.Context) bool {
|
||||
func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||
conds := NewConditionChan()
|
||||
|
||||
if len(rr.LocalOs) > 0 {
|
||||
conds.Add(NewLocalOSMatcher(rr.LocalOs))
|
||||
}
|
||||
|
||||
if len(rr.InboundTag) > 0 {
|
||||
conds.Add(NewInboundTagMatcher(rr.InboundTag))
|
||||
}
|
||||
@@ -76,60 +77,37 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||
conds.Add(&AttributeMatcher{configuredKeys})
|
||||
}
|
||||
|
||||
if len(rr.Geoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.Geoip, MatcherAsType_Target)
|
||||
if len(rr.Ip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.Ip, MatcherAsType_Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.Geoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.SourceGeoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.SourceGeoip, MatcherAsType_Source)
|
||||
if len(rr.SourceIp) > 0 {
|
||||
cond, err := NewIPMatcher(rr.SourceIp, MatcherAsType_Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.SourceGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.LocalGeoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.LocalGeoip, MatcherAsType_Local)
|
||||
if len(rr.LocalIp) > 0 {
|
||||
cond, err := NewIPMatcher(rr.LocalIp, 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 {
|
||||
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)")
|
||||
cond, err := NewDomainMatcher(rr.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(matcher)
|
||||
rr.Domain = nil
|
||||
runtime.GC()
|
||||
conds.Add(cond)
|
||||
}
|
||||
|
||||
if len(rr.Process) > 0 {
|
||||
@@ -189,20 +167,3 @@ 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
|
||||
|
||||
}
|
||||
|
||||
+108
-612
@@ -7,6 +7,7 @@
|
||||
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"
|
||||
@@ -23,63 +24,6 @@ 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 (
|
||||
@@ -116,11 +60,11 @@ func (x Config_DomainStrategy) String() string {
|
||||
}
|
||||
|
||||
func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_app_router_config_proto_enumTypes[1].Descriptor()
|
||||
return file_app_router_config_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Config_DomainStrategy) Type() protoreflect.EnumType {
|
||||
return &file_app_router_config_proto_enumTypes[1]
|
||||
return &file_app_router_config_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
|
||||
@@ -129,326 +73,7 @@ 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{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
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{5, 0}
|
||||
}
|
||||
|
||||
type RoutingRule struct {
|
||||
@@ -460,37 +85,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 []*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.
|
||||
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.
|
||||
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 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 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 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"`
|
||||
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
|
||||
// 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"`
|
||||
// List of operating systems for matching the one Xray itself is running on.
|
||||
LocalOs []string `protobuf:"bytes,23,rep,name=local_os,json=localOs,proto3" json:"local_os,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RoutingRule) Reset() {
|
||||
*x = RoutingRule{}
|
||||
mi := &file_app_router_config_proto_msgTypes[6]
|
||||
mi := &file_app_router_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -502,7 +127,7 @@ func (x *RoutingRule) String() string {
|
||||
func (*RoutingRule) ProtoMessage() {}
|
||||
|
||||
func (x *RoutingRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[6]
|
||||
mi := &file_app_router_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -515,7 +140,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{6}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetTargetTag() isRoutingRule_TargetTag {
|
||||
@@ -550,16 +175,16 @@ func (x *RoutingRule) GetRuleTag() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetDomain() []*Domain {
|
||||
func (x *RoutingRule) GetDomain() []*geodata.DomainRule {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetGeoip() []*GeoIP {
|
||||
func (x *RoutingRule) GetIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.Geoip
|
||||
return x.Ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -578,9 +203,9 @@ func (x *RoutingRule) GetNetworks() []net.Network {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetSourceGeoip() []*GeoIP {
|
||||
func (x *RoutingRule) GetSourceIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.SourceGeoip
|
||||
return x.SourceIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -620,9 +245,9 @@ func (x *RoutingRule) GetAttributes() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetLocalGeoip() []*GeoIP {
|
||||
func (x *RoutingRule) GetLocalIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.LocalGeoip
|
||||
return x.LocalIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -655,6 +280,13 @@ func (x *RoutingRule) GetWebhook() *WebhookConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetLocalOs() []string {
|
||||
if x != nil {
|
||||
return x.LocalOs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isRoutingRule_TargetTag interface {
|
||||
isRoutingRule_TargetTag()
|
||||
}
|
||||
@@ -684,7 +316,7 @@ type WebhookConfig struct {
|
||||
|
||||
func (x *WebhookConfig) Reset() {
|
||||
*x = WebhookConfig{}
|
||||
mi := &file_app_router_config_proto_msgTypes[7]
|
||||
mi := &file_app_router_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -696,7 +328,7 @@ func (x *WebhookConfig) String() string {
|
||||
func (*WebhookConfig) ProtoMessage() {}
|
||||
|
||||
func (x *WebhookConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[7]
|
||||
mi := &file_app_router_config_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -709,7 +341,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{7}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *WebhookConfig) GetUrl() string {
|
||||
@@ -746,7 +378,7 @@ type BalancingRule struct {
|
||||
|
||||
func (x *BalancingRule) Reset() {
|
||||
*x = BalancingRule{}
|
||||
mi := &file_app_router_config_proto_msgTypes[8]
|
||||
mi := &file_app_router_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -758,7 +390,7 @@ func (x *BalancingRule) String() string {
|
||||
func (*BalancingRule) ProtoMessage() {}
|
||||
|
||||
func (x *BalancingRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[8]
|
||||
mi := &file_app_router_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -771,7 +403,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{8}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *BalancingRule) GetTag() string {
|
||||
@@ -820,7 +452,7 @@ type StrategyWeight struct {
|
||||
|
||||
func (x *StrategyWeight) Reset() {
|
||||
*x = StrategyWeight{}
|
||||
mi := &file_app_router_config_proto_msgTypes[9]
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -832,7 +464,7 @@ func (x *StrategyWeight) String() string {
|
||||
func (*StrategyWeight) ProtoMessage() {}
|
||||
|
||||
func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[9]
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -845,7 +477,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{9}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *StrategyWeight) GetRegexp() bool {
|
||||
@@ -887,7 +519,7 @@ type StrategyLeastLoadConfig struct {
|
||||
|
||||
func (x *StrategyLeastLoadConfig) Reset() {
|
||||
*x = StrategyLeastLoadConfig{}
|
||||
mi := &file_app_router_config_proto_msgTypes[10]
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -899,7 +531,7 @@ func (x *StrategyLeastLoadConfig) String() string {
|
||||
func (*StrategyLeastLoadConfig) ProtoMessage() {}
|
||||
|
||||
func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[10]
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -912,7 +544,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{10}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight {
|
||||
@@ -961,7 +593,7 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_app_router_config_proto_msgTypes[11]
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -973,7 +605,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[11]
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -986,7 +618,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{11}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *Config) GetDomainStrategy() Config_DomainStrategy {
|
||||
@@ -1010,141 +642,21 @@ 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\"\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" +
|
||||
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"\xdc\a\n" +
|
||||
"\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\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" +
|
||||
"\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" +
|
||||
"\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\x129\n" +
|
||||
"\fsource_geoip\x18\v \x03(\v2\x16.xray.app.router.GeoIPR\vsourceGeoip\x12C\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" +
|
||||
"\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" +
|
||||
@@ -1153,13 +665,13 @@ 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\x127\n" +
|
||||
"\vlocal_geoip\x18\x11 \x03(\v2\x16.xray.app.router.GeoIPR\n" +
|
||||
"localGeoip\x12A\n" +
|
||||
"attributes\x126\n" +
|
||||
"\blocal_ip\x18\x11 \x03(\v2\x1b.xray.common.geodata.IPRuleR\alocalIp\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" +
|
||||
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x1a=\n" +
|
||||
"\awebhook\x18\x16 \x01(\v2\x1e.xray.app.router.WebhookConfigR\awebhook\x12\x19\n" +
|
||||
"\blocal_os\x18\x17 \x03(\tR\alocalOs\x1a=\n" +
|
||||
"\x0fAttributesEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\f\n" +
|
||||
@@ -1187,16 +699,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\"\x90\x02\n" +
|
||||
"\ttolerance\x18\x06 \x01(\x02R\ttolerance\"\x96\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\"<\n" +
|
||||
"\x0ebalancing_rule\x18\x03 \x03(\v2\x1e.xray.app.router.BalancingRuleR\rbalancingRule\"B\n" +
|
||||
"\x0eDomainStrategy\x12\b\n" +
|
||||
"\x04AsIs\x10\x00\x12\x10\n" +
|
||||
"\fIpIfNonMatch\x10\x02\x12\x0e\n" +
|
||||
"\n" +
|
||||
"IpOnDemand\x10\x03BO\n" +
|
||||
"IpOnDemand\x10\x03\"\x04\b\x01\x10\x01BO\n" +
|
||||
"\x13com.xray.app.routerP\x01Z$github.com/xtls/xray-core/app/router\xaa\x02\x0fXray.App.Routerb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -1211,59 +723,47 @@ 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, 2)
|
||||
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
|
||||
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_goTypes = []any{
|
||||
(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
|
||||
(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
|
||||
}
|
||||
var file_app_router_config_proto_depIdxs = []int32{
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func init() { file_app_router_config_proto_init() }
|
||||
@@ -1271,21 +771,17 @@ func file_app_router_config_proto_init() {
|
||||
if File_app_router_config_proto != nil {
|
||||
return
|
||||
}
|
||||
file_app_router_config_proto_msgTypes[6].OneofWrappers = []any{
|
||||
file_app_router_config_proto_msgTypes[0].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: 2,
|
||||
NumMessages: 15,
|
||||
NumEnums: 1,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
+18
-75
@@ -9,67 +9,7 @@ option java_multiple_files = true;
|
||||
import "common/serial/typed_message.proto";
|
||||
import "common/net/port.proto";
|
||||
import "common/net/network.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;
|
||||
}
|
||||
import "common/geodata/geodat.proto";
|
||||
|
||||
message RoutingRule {
|
||||
oneof target_tag {
|
||||
@@ -79,26 +19,23 @@ 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 Domain domain = 2;
|
||||
repeated xray.common.geodata.DomainRule domain = 2;
|
||||
|
||||
// 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 IPs for target IP address matching.
|
||||
repeated xray.common.geodata.IPRule ip = 10;
|
||||
|
||||
// List of ports.
|
||||
// List of ports for target port matching.
|
||||
xray.common.net.PortList port_list = 14;
|
||||
|
||||
// List of networks for matching.
|
||||
repeated xray.common.net.Network networks = 13;
|
||||
|
||||
// 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 IPs for source IP address matching.
|
||||
repeated xray.common.geodata.IPRule source_ip = 11;
|
||||
|
||||
// List of ports for source port matching.
|
||||
xray.common.net.PortList source_port_list = 16;
|
||||
@@ -109,12 +46,19 @@ message RoutingRule {
|
||||
|
||||
map<string, string> attributes = 15;
|
||||
|
||||
repeated GeoIP local_geoip = 17;
|
||||
// List of IPs for local IP address matching.
|
||||
repeated xray.common.geodata.IPRule local_ip = 17;
|
||||
|
||||
// List of ports for local port matching.
|
||||
xray.common.net.PortList local_port_list = 18;
|
||||
|
||||
xray.common.net.PortList vless_route_list = 20;
|
||||
|
||||
repeated string process = 21;
|
||||
WebhookConfig webhook = 22;
|
||||
|
||||
// List of operating systems for matching the one Xray itself is running on.
|
||||
repeated string local_os = 23;
|
||||
}
|
||||
|
||||
message WebhookConfig {
|
||||
@@ -155,8 +99,7 @@ message Config {
|
||||
// Use domain as is.
|
||||
AsIs = 0;
|
||||
|
||||
// [Deprecated] Always resolve IP for domains.
|
||||
// UseIp = 1;
|
||||
reserved 1;
|
||||
|
||||
// Resolve to IP if the domain doesn't match any rules.
|
||||
IpIfNonMatch = 2;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
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
|
||||
}
|
||||
+59
-116
@@ -2,7 +2,9 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -17,8 +19,8 @@ import (
|
||||
// Router is an implementation of routing.Router.
|
||||
type Router struct {
|
||||
domainStrategy Config_DomainStrategy
|
||||
rules []*Rule
|
||||
balancers map[string]*Balancer
|
||||
rules atomic.Pointer[[]*Rule]
|
||||
balancers atomic.Pointer[map[string]*Balancer]
|
||||
dns dns.Client
|
||||
|
||||
ctx context.Context
|
||||
@@ -43,52 +45,9 @@ func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm out
|
||||
r.ohm = ohm
|
||||
r.dispatcher = dispatcher
|
||||
|
||||
r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
|
||||
for _, rule := range config.BalancingRule {
|
||||
balancer, err := rule.Build(ohm, dispatcher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
balancer.InjectContext(ctx)
|
||||
r.balancers[rule.Tag] = balancer
|
||||
}
|
||||
|
||||
r.rules = make([]*Rule, 0, len(config.Rule))
|
||||
for _, rule := range config.Rule {
|
||||
cond, err := rule.BuildCondition()
|
||||
if err != nil {
|
||||
r.closeWebhooks()
|
||||
return err
|
||||
}
|
||||
rr := &Rule{
|
||||
Condition: cond,
|
||||
Tag: rule.GetTag(),
|
||||
RuleTag: rule.GetRuleTag(),
|
||||
}
|
||||
if wh := rule.GetWebhook(); wh != nil {
|
||||
notifier, err := NewWebhookNotifier(wh)
|
||||
if err != nil {
|
||||
r.closeWebhooks()
|
||||
return err
|
||||
}
|
||||
rr.Webhook = notifier
|
||||
}
|
||||
btag := rule.GetBalancingTag()
|
||||
if len(btag) > 0 {
|
||||
brule, found := r.balancers[btag]
|
||||
if !found {
|
||||
if rr.Webhook != nil {
|
||||
rr.Webhook.Close()
|
||||
}
|
||||
r.closeWebhooks()
|
||||
return errors.New("balancer ", btag, " not found")
|
||||
}
|
||||
rr.Balancer = brule
|
||||
}
|
||||
r.rules = append(r.rules, rr)
|
||||
}
|
||||
|
||||
return nil
|
||||
r.rules.Store(new([]*Rule))
|
||||
r.balancers.Store(&map[string]*Balancer{})
|
||||
return r.ReloadRules(config, false)
|
||||
}
|
||||
|
||||
// PickRoute implements routing.Router.
|
||||
@@ -110,7 +69,6 @@ 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
|
||||
@@ -125,18 +83,22 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if !shouldAppend {
|
||||
for _, rule := range r.rules {
|
||||
if rule.Webhook != nil {
|
||||
rule.Webhook.Close()
|
||||
}
|
||||
oldRules := *r.rules.Load()
|
||||
oldBalancers := *r.balancers.Load()
|
||||
|
||||
var newRules []*Rule
|
||||
newBalancers := make(map[string]*Balancer)
|
||||
existTags := make(map[string]bool, len(oldRules)+len(config.Rule))
|
||||
if shouldAppend {
|
||||
newRules = append(newRules, oldRules...)
|
||||
maps.Copy(newBalancers, oldBalancers)
|
||||
for _, rule := range oldRules {
|
||||
existTags[rule.RuleTag] = true
|
||||
}
|
||||
r.balancers = make(map[string]*Balancer, len(config.BalancingRule))
|
||||
r.rules = make([]*Rule, 0, len(config.Rule))
|
||||
}
|
||||
|
||||
for _, rule := range config.BalancingRule {
|
||||
_, found := r.balancers[rule.Tag]
|
||||
if found {
|
||||
if _, found := newBalancers[rule.Tag]; found {
|
||||
return errors.New("duplicate balancer tag")
|
||||
}
|
||||
balancer, err := rule.Build(r.ohm, r.dispatcher)
|
||||
@@ -144,27 +106,12 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
||||
return err
|
||||
}
|
||||
balancer.InjectContext(r.ctx)
|
||||
r.balancers[rule.Tag] = balancer
|
||||
}
|
||||
|
||||
startIdx := len(r.rules)
|
||||
closeNewWebhooks := func() {
|
||||
for i := startIdx; i < len(r.rules); i++ {
|
||||
if r.rules[i].Webhook != nil {
|
||||
r.rules[i].Webhook.Close()
|
||||
}
|
||||
}
|
||||
r.rules = r.rules[:startIdx]
|
||||
newBalancers[rule.Tag] = balancer
|
||||
}
|
||||
|
||||
for _, rule := range config.Rule {
|
||||
if r.RuleExists(rule.GetRuleTag()) {
|
||||
closeNewWebhooks()
|
||||
return errors.New("duplicate ruleTag ", rule.GetRuleTag())
|
||||
}
|
||||
cond, err := rule.BuildCondition()
|
||||
if err != nil {
|
||||
closeNewWebhooks()
|
||||
return err
|
||||
}
|
||||
rr := &Rule{
|
||||
@@ -172,70 +119,64 @@ func (r *Router) ReloadRules(config *Config, shouldAppend bool) error {
|
||||
Tag: rule.GetTag(),
|
||||
RuleTag: rule.GetRuleTag(),
|
||||
}
|
||||
if rr.RuleTag != "" && existTags[rr.RuleTag] {
|
||||
return errors.New("duplicate ruleTag ", rr.RuleTag)
|
||||
}
|
||||
existTags[rr.RuleTag] = true
|
||||
if wh := rule.GetWebhook(); wh != nil {
|
||||
notifier, err := NewWebhookNotifier(wh)
|
||||
if err != nil {
|
||||
closeNewWebhooks()
|
||||
return err
|
||||
}
|
||||
rr.Webhook = notifier
|
||||
}
|
||||
btag := rule.GetBalancingTag()
|
||||
if len(btag) > 0 {
|
||||
brule, found := r.balancers[btag]
|
||||
if btag := rule.GetBalancingTag(); len(btag) > 0 {
|
||||
brule, found := newBalancers[btag]
|
||||
if !found {
|
||||
if rr.Webhook != nil {
|
||||
rr.Webhook.Close()
|
||||
}
|
||||
closeNewWebhooks()
|
||||
return errors.New("balancer ", btag, " not found")
|
||||
}
|
||||
rr.Balancer = brule
|
||||
}
|
||||
r.rules = append(r.rules, rr)
|
||||
newRules = append(newRules, rr)
|
||||
}
|
||||
|
||||
r.balancers.Store(&newBalancers)
|
||||
r.rules.Store(&newRules)
|
||||
if !shouldAppend {
|
||||
closeWebhooks(oldRules)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) RuleExists(tag string) bool {
|
||||
if tag != "" {
|
||||
for _, rule := range r.rules {
|
||||
if rule.RuleTag == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveRule implements routing.Router.
|
||||
func (r *Router) RemoveRule(tag string) error {
|
||||
if tag == "" {
|
||||
return errors.New("empty tag name!")
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
newRules := []*Rule{}
|
||||
if tag != "" {
|
||||
for _, rule := range r.rules {
|
||||
if rule.RuleTag != tag {
|
||||
newRules = append(newRules, rule)
|
||||
} else if rule.Webhook != nil {
|
||||
rule.Webhook.Close()
|
||||
}
|
||||
oldRules := *r.rules.Load()
|
||||
newRules := make([]*Rule, 0, len(oldRules))
|
||||
var removed []*Rule
|
||||
for _, rule := range oldRules {
|
||||
if rule.RuleTag != tag {
|
||||
newRules = append(newRules, rule)
|
||||
} else {
|
||||
removed = append(removed, rule)
|
||||
}
|
||||
r.rules = newRules
|
||||
return nil
|
||||
}
|
||||
return errors.New("empty tag name!")
|
||||
|
||||
r.rules.Store(&newRules)
|
||||
closeWebhooks(removed)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListRule implements routing.Router
|
||||
func (r *Router) ListRule() []routing.Route {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
ruleList := make([]routing.Route, 0)
|
||||
for _, rule := range r.rules {
|
||||
rules := *r.rules.Load()
|
||||
ruleList := make([]routing.Route, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
ruleList = append(ruleList, &Route{
|
||||
outboundTag: rule.Tag,
|
||||
ruleTag: rule.RuleTag,
|
||||
@@ -254,7 +195,9 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||
}
|
||||
|
||||
for _, rule := range r.rules {
|
||||
rules := *r.rules.Load()
|
||||
|
||||
for _, rule := range rules {
|
||||
if rule.Apply(ctx) {
|
||||
return rule, ctx, nil
|
||||
}
|
||||
@@ -267,7 +210,7 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
|
||||
ctx = routing_dns.ContextWithDNSClient(ctx, r.dns)
|
||||
|
||||
// Try applying rules again if we have IPs.
|
||||
for _, rule := range r.rules {
|
||||
for _, rule := range rules {
|
||||
if rule.Apply(ctx) {
|
||||
return rule, ctx, nil
|
||||
}
|
||||
@@ -281,9 +224,9 @@ func (r *Router) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeWebhooks closes all webhook notifiers in the current rule set.
|
||||
func (r *Router) closeWebhooks() {
|
||||
for _, rule := range r.rules {
|
||||
// closeWebhooks closes all webhook notifiers in the given rule set.
|
||||
func closeWebhooks(rules []*Rule) {
|
||||
for _, rule := range rules {
|
||||
if rule.Webhook != nil {
|
||||
rule.Webhook.Close()
|
||||
}
|
||||
@@ -294,7 +237,7 @@ func (r *Router) closeWebhooks() {
|
||||
func (r *Router) Close() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.closeWebhooks()
|
||||
closeWebhooks(*r.rules.Load())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+13
-15
@@ -7,6 +7,7 @@ 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"
|
||||
@@ -155,12 +156,11 @@ func TestIPOnDemand(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -200,12 +200,11 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -245,12 +244,11 @@ func TestIPIfNonMatchIP(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Ip: []byte{127, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDRRule{
|
||||
Cidr: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -77,7 +78,7 @@ func (s *LeastLoadStrategy) PickOutbound(candidates []string) string {
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {
|
||||
qualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT))
|
||||
qualified := s.getNodes(candidates)
|
||||
selects := s.selectLeastLoad(qualified)
|
||||
return selects
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node {
|
||||
return nodes[:count]
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node {
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
if s.observer == nil {
|
||||
errors.LogError(s.ctx, "observer is nil")
|
||||
return make([]*node, 0)
|
||||
@@ -151,12 +152,10 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
|
||||
|
||||
results := observeResult.(*observatory.ObservationResult)
|
||||
|
||||
outboundlist := outboundList(candidates)
|
||||
|
||||
var ret []*node
|
||||
|
||||
for _, v := range results.Status {
|
||||
if v.Alive && (v.Delay < maxRTT.Milliseconds() || maxRTT == 0) && outboundlist.contains(v.OutboundTag) {
|
||||
if s.shouldSelectNode(v, candidates) {
|
||||
record := &node{
|
||||
Tag: v.OutboundTag,
|
||||
CountAll: 1,
|
||||
@@ -172,8 +171,8 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -182,6 +181,23 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
|
||||
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,6 +85,7 @@ func TestSelectLeastExpected(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpected2(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -102,6 +103,7 @@ func TestSelectLeastExpected2(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -122,6 +124,7 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -142,6 +145,7 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastLoadBaselines(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -160,6 +164,7 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
|
||||
+20
-72
@@ -7,61 +7,16 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"sync/atomic"
|
||||
"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 {
|
||||
@@ -86,6 +41,7 @@ type WebhookNotifier struct {
|
||||
deduplication uint32
|
||||
client *http.Client
|
||||
seen sync.Map
|
||||
lastSweep atomic.Int64
|
||||
done chan struct{}
|
||||
wg sync.WaitGroup
|
||||
closeOnce sync.Once
|
||||
@@ -96,7 +52,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
httpURL, socketPath := parseURL(cfg.Url)
|
||||
httpURL, socketPath := utils.SplitHTTPUnixURL(cfg.Url)
|
||||
h := &WebhookNotifier{
|
||||
url: httpURL,
|
||||
deduplication: cfg.Deduplication,
|
||||
@@ -107,7 +63,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
}
|
||||
|
||||
if socketPath != "" {
|
||||
dialAddr := resolveSocketPath(socketPath)
|
||||
dialAddr := utils.ResolveSocketPath(socketPath)
|
||||
h.client.Transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
@@ -123,11 +79,6 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if h.deduplication > 0 {
|
||||
h.wg.Add(1)
|
||||
go h.cleanupLoop()
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
@@ -247,6 +198,7 @@ func (h *WebhookNotifier) isDuplicate(email string) bool {
|
||||
}
|
||||
ttl := time.Duration(h.deduplication) * time.Second
|
||||
now := time.Now()
|
||||
h.maybeSweep(now, ttl)
|
||||
if v, loaded := h.seen.LoadOrStore(email, now); loaded {
|
||||
if now.Sub(v.(time.Time)) < ttl {
|
||||
return true
|
||||
@@ -256,27 +208,23 @@ func (h *WebhookNotifier) isDuplicate(email string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *WebhookNotifier) cleanupLoop() {
|
||||
defer h.wg.Done()
|
||||
ttl := time.Duration(h.deduplication) * time.Second
|
||||
ticker := time.NewTicker(ttl)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-h.done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
now := time.Now()
|
||||
h.seen.Range(func(key, value any) bool {
|
||||
if now.Sub(value.(time.Time)) >= ttl {
|
||||
h.seen.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
func (h *WebhookNotifier) maybeSweep(now time.Time, ttl time.Duration) {
|
||||
last := h.lastSweep.Load()
|
||||
if now.UnixNano()-last < int64(ttl) {
|
||||
return
|
||||
}
|
||||
if !h.lastSweep.CompareAndSwap(last, now.UnixNano()) {
|
||||
return // another goroutine did the sweep
|
||||
}
|
||||
h.seen.Range(func(key, value any) bool {
|
||||
if now.Sub(value.(time.Time)) >= ttl {
|
||||
h.seen.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Only need to call if the Notifier is really used, otherwise GC can clean it
|
||||
func (h *WebhookNotifier) Close() error {
|
||||
h.closeOnce.Do(func() {
|
||||
close(h.done)
|
||||
|
||||
@@ -3,12 +3,10 @@ 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"
|
||||
@@ -70,9 +68,10 @@ func (s *statsServer) GetStatsOnlineIpList(ctx context.Context, request *GetStat
|
||||
}
|
||||
|
||||
ips := make(map[string]int64)
|
||||
for ip, t := range c.IPTimeMap() {
|
||||
ips[ip] = t.Unix()
|
||||
}
|
||||
c.ForEach(func(ip string, lastSeen int64) bool {
|
||||
ips[ip] = lastSeen
|
||||
return true
|
||||
})
|
||||
|
||||
return &GetStatsOnlineIpListResponse{
|
||||
Name: request.Name,
|
||||
@@ -86,21 +85,87 @@ func (s *statsServer) GetAllOnlineUsers(ctx context.Context, request *GetAllOnli
|
||||
}, 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
|
||||
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) {
|
||||
response := &QueryStatsResponse{}
|
||||
|
||||
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) {
|
||||
s.stats.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
||||
if strings.Contains(name, request.Pattern) {
|
||||
var value int64
|
||||
if request.Reset_ {
|
||||
value = c.Set(0)
|
||||
|
||||
+314
-28
@@ -551,6 +551,266 @@ 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
|
||||
@@ -559,7 +819,7 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[10]
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -571,7 +831,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[10]
|
||||
mi := &file_app_stats_command_command_proto_msgTypes[15]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -584,7 +844,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{10}
|
||||
return file_app_stats_command_command_proto_rawDescGZIP(), []int{15}
|
||||
}
|
||||
|
||||
var File_app_stats_command_command_proto protoreflect.FileDescriptor
|
||||
@@ -628,8 +888,23 @@ 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\"\b\n" +
|
||||
"\x06Config2\x96\x05\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" +
|
||||
"\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" +
|
||||
@@ -637,7 +912,8 @@ 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\"\x00Bd\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" +
|
||||
"\x1acom.xray.app.stats.commandP\x01Z+github.com/xtls/xray-core/app/stats/command\xaa\x02\x16Xray.App.Stats.Commandb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -652,7 +928,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, 12)
|
||||
var file_app_stats_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
||||
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
|
||||
@@ -664,30 +940,40 @@ 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
|
||||
(*Config)(nil), // 10: xray.app.stats.command.Config
|
||||
nil, // 11: xray.app.stats.command.GetStatsOnlineIpListResponse.IpsEntry
|
||||
(*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
|
||||
}
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func init() { file_app_stats_command_command_proto_init() }
|
||||
@@ -701,7 +987,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: 12,
|
||||
NumMessages: 17,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -57,6 +57,31 @@ 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) {}
|
||||
@@ -64,6 +89,7 @@ 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,6 +25,7 @@ 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.
|
||||
@@ -37,6 +38,7 @@ 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 {
|
||||
@@ -107,6 +109,16 @@ 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.
|
||||
@@ -117,6 +129,7 @@ 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()
|
||||
}
|
||||
|
||||
@@ -145,6 +158,9 @@ 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() {}
|
||||
|
||||
@@ -274,6 +290,24 @@ 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)
|
||||
@@ -305,6 +339,10 @@ 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",
|
||||
|
||||
+15
-27
@@ -13,14 +13,14 @@ const (
|
||||
|
||||
type ipEntry struct {
|
||||
refCount int
|
||||
lastSeen time.Time
|
||||
lastSeen int64
|
||||
}
|
||||
|
||||
// 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 = time.Now()
|
||||
e.lastSeen = now
|
||||
om.entries[ip] = e
|
||||
} else {
|
||||
om.entries[ip] = &ipEntry{
|
||||
om.entries[ip] = ipEntry{
|
||||
refCount: 1,
|
||||
lastSeen: time.Now(),
|
||||
lastSeen: now,
|
||||
}
|
||||
om.count.Add(1)
|
||||
}
|
||||
@@ -57,7 +57,6 @@ 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
|
||||
@@ -66,6 +65,8 @@ func (om *OnlineMap) RemoveIP(ip string) {
|
||||
if e.refCount <= 0 {
|
||||
delete(om.entries, ip)
|
||||
om.count.Add(-1)
|
||||
} else {
|
||||
om.entries[ip] = e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,26 +75,13 @@ func (om *OnlineMap) Count() int {
|
||||
return int(om.count.Load())
|
||||
}
|
||||
|
||||
// List implements stats.OnlineMap.
|
||||
func (om *OnlineMap) List() []string {
|
||||
// ForEach calls fn for each online IP. If fn returns false, iteration stops.
|
||||
func (om *OnlineMap) ForEach(fn func(string, int64) bool) {
|
||||
om.access.Lock()
|
||||
defer om.access.Unlock()
|
||||
|
||||
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
|
||||
if !fn(ip, e.lastSeen) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+83
-19
@@ -11,19 +11,19 @@ import (
|
||||
|
||||
// Manager is an implementation of stats.Manager.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
counters map[string]*Counter
|
||||
onlineMap map[string]*OnlineMap
|
||||
channels map[string]*Channel
|
||||
running bool
|
||||
access sync.RWMutex
|
||||
counters map[string]*Counter
|
||||
onlineMaps 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),
|
||||
onlineMap: make(map[string]*OnlineMap),
|
||||
channels: make(map[string]*Channel),
|
||||
counters: make(map[string]*Counter),
|
||||
onlineMaps: make(map[string]*OnlineMap),
|
||||
channels: make(map[string]*Channel),
|
||||
}
|
||||
|
||||
return m, nil
|
||||
@@ -48,6 +48,20 @@ 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()
|
||||
@@ -88,12 +102,26 @@ func (m *Manager) RegisterOnlineMap(name string) (stats.OnlineMap, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
if _, found := m.onlineMap[name]; found {
|
||||
return nil, errors.New("onlineMap ", name, " already registered.")
|
||||
if _, found := m.onlineMaps[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.onlineMap[name] = om
|
||||
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
|
||||
return om, nil
|
||||
}
|
||||
|
||||
@@ -102,9 +130,9 @@ func (m *Manager) UnregisterOnlineMap(name string) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
|
||||
if _, found := m.onlineMap[name]; found {
|
||||
errors.LogDebug(context.Background(), "remove onlineMap ", name)
|
||||
delete(m.onlineMap, name)
|
||||
if _, found := m.onlineMaps[name]; found {
|
||||
errors.LogDebug(context.Background(), "remove OnlineMap ", name)
|
||||
delete(m.onlineMaps, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -114,12 +142,24 @@ func (m *Manager) GetOnlineMap(name string) stats.OnlineMap {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
if om, found := m.onlineMap[name]; found {
|
||||
if om, found := m.onlineMaps[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()
|
||||
@@ -137,6 +177,26 @@ 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()
|
||||
@@ -166,9 +226,9 @@ func (m *Manager) GetAllOnlineUsers() []string {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
|
||||
usersOnline := make([]string, 0, len(m.onlineMap))
|
||||
for user, onlineMap := range m.onlineMap {
|
||||
if onlineMap.Count() > 0 {
|
||||
usersOnline := make([]string, 0, len(m.onlineMaps))
|
||||
for user, om := range m.onlineMaps {
|
||||
if om.Count() > 0 {
|
||||
usersOnline = append(usersOnline, user)
|
||||
}
|
||||
}
|
||||
@@ -198,6 +258,10 @@ 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,10 +2,11 @@ package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build !windows && !wasm && !illumos
|
||||
// +build !windows,!wasm,!illumos
|
||||
//go:build !windows && !wasm && !illumos && !openbsd
|
||||
// +build !windows,!wasm,!illumos,!openbsd
|
||||
|
||||
package buf
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
//go:build !wasm
|
||||
// +build !wasm
|
||||
//go:build !wasm && !openbsd
|
||||
// +build !wasm,!openbsd
|
||||
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
@@ -143,13 +144,24 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
var useReadv bool
|
||||
var useReadv atomic.Bool
|
||||
|
||||
func init() {
|
||||
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":
|
||||
useReadv = true
|
||||
enabled = true
|
||||
}
|
||||
useReadv.Store(enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
platform.RegisterEnvReload(reloadEnvSettings)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
//go:build wasm || openbsd
|
||||
// +build wasm openbsd
|
||||
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"github.com/xtls/xray-core/features/stats"
|
||||
)
|
||||
|
||||
func useReadV() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
|
||||
panic("not implemented")
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
//go:build wasm
|
||||
// +build wasm
|
||||
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const useReadv = false
|
||||
|
||||
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn) Reader {
|
||||
panic("not implemented")
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build !wasm
|
||||
// +build !wasm
|
||||
//go:build !wasm && !openbsd
|
||||
// +build !wasm,!openbsd
|
||||
|
||||
package buf_test
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
)
|
||||
|
||||
type windowsReader struct {
|
||||
bufs []syscall.WSABuf
|
||||
bufs []syscall.WSABuf
|
||||
ready bool
|
||||
}
|
||||
|
||||
func (r *windowsReader) Init(bs []*Buffer) {
|
||||
@@ -15,6 +16,7 @@ func (r *windowsReader) Init(bs []*Buffer) {
|
||||
for _, b := range bs {
|
||||
r.bufs = append(r.bufs, syscall.WSABuf{Len: uint32(Size), Buf: &b.v[0]})
|
||||
}
|
||||
r.ready = false
|
||||
}
|
||||
|
||||
func (r *windowsReader) Clear() {
|
||||
@@ -25,6 +27,14 @@ func (r *windowsReader) Clear() {
|
||||
}
|
||||
|
||||
func (r *windowsReader) Read(fd uintptr) int32 {
|
||||
// On the first invocation, we return -1 to indicate "not ready"
|
||||
// to make rawConn.Read wait for readability using the runtime's own mechanism
|
||||
// because syscall.WSARecv() is a blocking call when used with nil OVERLAPPED
|
||||
if !r.ready {
|
||||
r.ready = true
|
||||
return -1
|
||||
}
|
||||
|
||||
var nBytes uint32
|
||||
var flags uint32
|
||||
err := syscall.WSARecv(syscall.Handle(fd), &r.bufs[0], uint32(len(r.bufs)), &nBytes, &flags, nil, nil)
|
||||
|
||||
@@ -118,7 +118,9 @@ func (w *BufferedWriter) Write(b []byte) (int, error) {
|
||||
|
||||
nBytes, err := w.buffer.Write(b)
|
||||
totalBytes += nBytes
|
||||
if err != nil {
|
||||
|
||||
// ErrBufferFull means a partial write, so flush below and continue
|
||||
if err != nil && err != ErrBufferFull {
|
||||
return totalBytes, err
|
||||
}
|
||||
if !w.buffered || w.buffer.IsFull() {
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
|
||||
// [,)
|
||||
func RandBetween(from int64, to int64) int64 {
|
||||
if from == to {
|
||||
return from
|
||||
}
|
||||
if from > to {
|
||||
from, to = to, from
|
||||
}
|
||||
if d := to - from; d == 0 || d == 1 {
|
||||
return from
|
||||
}
|
||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||
return from + bigInt.Int64()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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() }
|
||||
@@ -0,0 +1,238 @@
|
||||
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]()}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,965 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
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,19 +1,22 @@
|
||||
package router
|
||||
package geodata
|
||||
|
||||
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 GeoIPMatcher interface {
|
||||
type IPMatcher interface {
|
||||
// TODO: (PERF) all net.IP -> netipx.Addr
|
||||
|
||||
// Invalid IP always return false.
|
||||
@@ -33,13 +36,13 @@ type GeoIPMatcher interface {
|
||||
SetReverse(reverse bool)
|
||||
}
|
||||
|
||||
type GeoIPSet struct {
|
||||
type IPSet struct {
|
||||
ipv4, ipv6 *netipx.IPSet
|
||||
max4, max6 uint8
|
||||
}
|
||||
|
||||
type HeuristicGeoIPMatcher struct {
|
||||
ipset *GeoIPSet
|
||||
type HeuristicIPMatcher struct {
|
||||
ipset *IPSet
|
||||
reverse bool
|
||||
}
|
||||
|
||||
@@ -48,8 +51,8 @@ type ipBucket struct {
|
||||
ips []net.IP
|
||||
}
|
||||
|
||||
// Match implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) Match(ip net.IP) bool {
|
||||
// Match implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) Match(ip net.IP) bool {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -57,18 +60,24 @@ func (m *HeuristicGeoIPMatcher) Match(ip net.IP) bool {
|
||||
return m.matchAddr(ipx)
|
||||
}
|
||||
|
||||
func (m *HeuristicGeoIPMatcher) matchAddr(ipx netip.Addr) bool {
|
||||
func (m *HeuristicIPMatcher) 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 GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
// AnyMatch implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -117,8 +126,8 @@ func (m *HeuristicGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
// Matches implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) Matches(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -205,8 +214,8 @@ func prefixKeyFromIP(ip net.IP) (key [9]byte, ok bool) {
|
||||
return key, false // illegal
|
||||
}
|
||||
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
// FilterIPs implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return []net.IP{}, []net.IP{}
|
||||
@@ -295,22 +304,22 @@ func (m *HeuristicGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmat
|
||||
return
|
||||
}
|
||||
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) ToggleReverse() {
|
||||
// ToggleReverse implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) ToggleReverse() {
|
||||
m.reverse = !m.reverse
|
||||
}
|
||||
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) SetReverse(reverse bool) {
|
||||
// SetReverse implements IPMatcher.
|
||||
func (m *HeuristicIPMatcher) SetReverse(reverse bool) {
|
||||
m.reverse = reverse
|
||||
}
|
||||
|
||||
type GeneralMultiGeoIPMatcher struct {
|
||||
matchers []GeoIPMatcher
|
||||
type GeneralMultiIPMatcher struct {
|
||||
matchers []IPMatcher
|
||||
}
|
||||
|
||||
// Match implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
// Match implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) Match(ip net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.Match(ip) {
|
||||
return true
|
||||
@@ -319,8 +328,8 @@ func (mm *GeneralMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
// AnyMatch implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.AnyMatch(ips) {
|
||||
return true
|
||||
@@ -329,8 +338,8 @@ func (mm *GeneralMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
// Matches implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) Matches(ips []net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.Matches(ips) {
|
||||
return true
|
||||
@@ -339,8 +348,8 @@ func (mm *GeneralMultiGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
// FilterIPs implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
matched = make([]net.IP, 0, len(ips))
|
||||
unmatched = ips
|
||||
for _, m := range mm.matchers {
|
||||
@@ -356,26 +365,26 @@ func (mm *GeneralMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, u
|
||||
return
|
||||
}
|
||||
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) ToggleReverse() {
|
||||
// ToggleReverse implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) ToggleReverse() {
|
||||
for _, m := range mm.matchers {
|
||||
m.ToggleReverse()
|
||||
}
|
||||
}
|
||||
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) SetReverse(reverse bool) {
|
||||
// SetReverse implements IPMatcher.
|
||||
func (mm *GeneralMultiIPMatcher) SetReverse(reverse bool) {
|
||||
for _, m := range mm.matchers {
|
||||
m.SetReverse(reverse)
|
||||
}
|
||||
}
|
||||
|
||||
type HeuristicMultiGeoIPMatcher struct {
|
||||
matchers []*HeuristicGeoIPMatcher
|
||||
type HeuristicMultiIPMatcher struct {
|
||||
matchers []*HeuristicIPMatcher
|
||||
}
|
||||
|
||||
// Match implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
// Match implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) Match(ip net.IP) bool {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
@@ -389,8 +398,8 @@ func (mm *HeuristicMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
// AnyMatch implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -439,8 +448,8 @@ func (mm *HeuristicMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
// Matches implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) Matches(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
@@ -503,7 +512,7 @@ type ipViews struct {
|
||||
precise4, precise6 []netip.Addr
|
||||
}
|
||||
|
||||
func (v *ipViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) bool {
|
||||
func (v *ipViews) ensureForMatcher(m *HeuristicIPMatcher, 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
|
||||
@@ -581,8 +590,8 @@ func (v *ipViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) bool
|
||||
return true
|
||||
}
|
||||
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
// FilterIPs implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return []net.IP{}, []net.IP{}
|
||||
@@ -694,7 +703,7 @@ type ipBucketViews struct {
|
||||
precise4, precise6 map[netip.Addr]net.IP
|
||||
}
|
||||
|
||||
func (v *ipBucketViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) {
|
||||
func (v *ipBucketViews) ensureForMatcher(m *HeuristicIPMatcher, 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
|
||||
@@ -782,70 +791,123 @@ func (v *ipBucketViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP)
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) ToggleReverse() {
|
||||
// ToggleReverse implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) ToggleReverse() {
|
||||
for _, m := range mm.matchers {
|
||||
m.ToggleReverse()
|
||||
}
|
||||
}
|
||||
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) SetReverse(reverse bool) {
|
||||
// SetReverse implements IPMatcher.
|
||||
func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
|
||||
for _, m := range mm.matchers {
|
||||
m.SetReverse(reverse)
|
||||
}
|
||||
}
|
||||
|
||||
type GeoIPSetFactory struct {
|
||||
type IPSetFactory struct {
|
||||
sync.Mutex
|
||||
shared map[string]*GeoIPSet // TODO: cleanup
|
||||
shared *utils.WeakCacheMap[string, IPSet]
|
||||
}
|
||||
|
||||
var ipsetFactory = GeoIPSetFactory{shared: make(map[string]*GeoIPSet)}
|
||||
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
|
||||
key := buildGeoIPRulesKey(rules)
|
||||
|
||||
func (f *GeoIPSetFactory) GetOrCreate(key string, cidrGroups [][]*CIDR) (*GeoIPSet, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if ipset := f.shared[key]; ipset != nil {
|
||||
if ipset, ok := f.shared.Load(key); ok {
|
||||
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
|
||||
return ipset, nil
|
||||
}
|
||||
errors.LogDebug(context.Background(), "geodata geoip matcher cache MISS ", key)
|
||||
|
||||
ipset, err := f.Create(cidrGroups...)
|
||||
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
|
||||
})
|
||||
if err == nil {
|
||||
f.shared[key] = ipset
|
||||
f.shared.Store(key, ipset)
|
||||
}
|
||||
return ipset, err
|
||||
}
|
||||
|
||||
func (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, error) {
|
||||
var ipv4Builder, ipv6Builder netipx.IPSetBuilder
|
||||
func buildGeoIPRulesKey(rules []*GeoIPRule) string {
|
||||
rules = slices.Clone(rules)
|
||||
|
||||
for _, cidrGroup := range cidrGroups {
|
||||
for i, cidrEntry := range cidrGroup {
|
||||
cidrGroup[i] = nil
|
||||
ipBytes := cidrEntry.GetIp()
|
||||
prefixLen := int(cidrEntry.GetPrefix())
|
||||
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
|
||||
})
|
||||
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
if addr.Is4() {
|
||||
ipv4Builder.AddPrefix(prefix)
|
||||
} else if addr.Is6() {
|
||||
ipv6Builder.AddPrefix(prefix)
|
||||
}
|
||||
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) {
|
||||
var ipv4Builder, ipv6Builder netipx.IPSetBuilder
|
||||
|
||||
err := yield(func(c *CIDR) {
|
||||
ipBytes := c.GetIp()
|
||||
prefixLen := int(c.GetPrefix())
|
||||
|
||||
addr, ok := netip.AddrFromSlice(ipBytes)
|
||||
if !ok {
|
||||
errors.LogError(context.Background(), "ignore invalid IP byte slice: ", ipBytes)
|
||||
return
|
||||
}
|
||||
|
||||
prefix := netip.PrefixFrom(addr, prefixLen)
|
||||
if !prefix.IsValid() {
|
||||
errors.LogError(context.Background(), "ignore created invalid prefix from addr ", addr, " and length ", prefixLen)
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -856,107 +918,106 @@ func (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, 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 max4 == 0 {
|
||||
if !has4 {
|
||||
max4 = 0xff
|
||||
} else if max4 == 0 {
|
||||
max4 = 0xfe
|
||||
}
|
||||
if max6 == 0 {
|
||||
if !has6 {
|
||||
max6 = 0xff
|
||||
} else if max6 == 0 {
|
||||
max6 = 0xfe
|
||||
}
|
||||
|
||||
return &GeoIPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
|
||||
return &IPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
|
||||
}
|
||||
|
||||
func BuildOptimizedGeoIPMatcher(geoips ...*GeoIP) (GeoIPMatcher, error) {
|
||||
n := len(geoips)
|
||||
if n == 0 {
|
||||
return nil, errors.New("no geoip configs provided")
|
||||
}
|
||||
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)
|
||||
|
||||
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
|
||||
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)
|
||||
}
|
||||
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)
|
||||
case *IPRule_Geoip:
|
||||
if !v.Geoip.ReverseMatch {
|
||||
posGeoip = append(posGeoip, v.Geoip)
|
||||
} else {
|
||||
negGeoip = append(negGeoip, v.Geoip)
|
||||
}
|
||||
default:
|
||||
panic("unknown ip rule type")
|
||||
}
|
||||
|
||||
return ipsetFactory.GetOrCreate(sb.String(), cidrGroups)
|
||||
}
|
||||
|
||||
ipset, err := buildIPSet(pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ipset != nil {
|
||||
subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: false})
|
||||
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(neg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(negCustom) > 0 {
|
||||
ipset, err := f.CreateFromCIDRs(negCustom)
|
||||
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})
|
||||
|
||||
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})
|
||||
}
|
||||
|
||||
switch len(subs) {
|
||||
case 0:
|
||||
return nil, errors.New("no valid geoip matcher")
|
||||
return nil, errors.New("no valid ip matcher")
|
||||
case 1:
|
||||
return subs[0], nil
|
||||
default:
|
||||
return &HeuristicMultiGeoIPMatcher{matchers: subs}, nil
|
||||
return &HeuristicMultiIPMatcher{matchers: subs}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func newIPSetFactory() *IPSetFactory {
|
||||
return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user