mirror of
https://github.com/XrayR-project/XrayR.git
synced 2025-08-01 15:06:54 +00:00
Compare commits
240 Commits
v0.8.8
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e00514674c | ||
![]() |
b1f5ccfee8 | ||
![]() |
2f88334373 | ||
![]() |
35e726e766 | ||
![]() |
4bbf3c9899 | ||
![]() |
9391c759c9 | ||
![]() |
eb16b709e2 | ||
![]() |
1f09502613 | ||
![]() |
0ae73a0eca | ||
![]() |
63c1a18a7d | ||
![]() |
34726d1659 | ||
![]() |
5edc68d475 | ||
![]() |
944e8cd6a8 | ||
![]() |
c4581ad34b | ||
![]() |
127318ccbd | ||
![]() |
db784f18e6 | ||
![]() |
8a5a5e4356 | ||
![]() |
cfdb5166f3 | ||
![]() |
540be1b7c8 | ||
![]() |
98014fa60f | ||
![]() |
1de718a5fc | ||
![]() |
81c068bad4 | ||
![]() |
dbde0c24fc | ||
![]() |
1bbd061d42 | ||
![]() |
793f26e763 | ||
![]() |
175e46d0b7 | ||
![]() |
9261a6063d | ||
![]() |
5ee2679924 | ||
![]() |
06fe198243 | ||
![]() |
b3f31bf06b | ||
![]() |
0010a876f3 | ||
![]() |
dbed635aae | ||
![]() |
561f317e24 | ||
![]() |
c24a2a28f2 | ||
![]() |
fa80ed8014 | ||
![]() |
5409cb2dc3 | ||
![]() |
b8b3a16d3b | ||
![]() |
d1e5762937 | ||
![]() |
4f1dafa8ed | ||
![]() |
aae0f9b24d | ||
![]() |
aee8ca2980 | ||
![]() |
933e745a70 | ||
![]() |
515fc708dc | ||
![]() |
5e134967fd | ||
![]() |
4a234d50e2 | ||
![]() |
115d7bad6f | ||
![]() |
5ba0624bbc | ||
![]() |
4439afa29b | ||
![]() |
03c9fe4218 | ||
![]() |
f648aedc91 | ||
![]() |
1720fbdc4c | ||
![]() |
cd7907c4c9 | ||
![]() |
296ba86bdd | ||
![]() |
43e6ec85cd | ||
![]() |
7ba2d999f0 | ||
![]() |
53141f4416 | ||
![]() |
e378f0a367 | ||
![]() |
d01a4265cd | ||
![]() |
94d2c7c23a | ||
![]() |
3de7600a4c | ||
![]() |
a0378b6cff | ||
![]() |
4688833bc7 | ||
![]() |
294a2dfacb | ||
![]() |
5780731cd3 | ||
![]() |
911b0c2ff5 | ||
![]() |
fa7fb7087f | ||
![]() |
0a6bca9755 | ||
![]() |
8562c798a9 | ||
![]() |
56199afa90 | ||
![]() |
c688cf02b4 | ||
![]() |
c170272f40 | ||
![]() |
6ffbe599b4 | ||
![]() |
78c2e31bdf | ||
![]() |
c04d52330d | ||
![]() |
6f4bf62113 | ||
![]() |
56e993ce43 | ||
![]() |
6d3d6f9e53 | ||
![]() |
76076b2d6a | ||
![]() |
4e48fdbe61 | ||
![]() |
0f3a4a0008 | ||
![]() |
d9971b2181 | ||
![]() |
4d1ed21dc7 | ||
![]() |
9a03f85930 | ||
![]() |
11b46ea485 | ||
![]() |
dc3d256d85 | ||
![]() |
287c30e7d0 | ||
![]() |
97d89549dd | ||
![]() |
cac4288e07 | ||
![]() |
d1d5193cec | ||
![]() |
85607527a5 | ||
![]() |
fd0a23bf6c | ||
![]() |
b1bfd04895 | ||
![]() |
edf02307ad | ||
![]() |
551e2d4299 | ||
![]() |
0254c6c557 | ||
![]() |
4c52e33adb | ||
![]() |
b8d40c201b | ||
![]() |
dbd4a85a6c | ||
![]() |
8f28716b21 | ||
![]() |
73bc37cb51 | ||
![]() |
78f2f88296 | ||
![]() |
42a2226769 | ||
![]() |
71aba0601e | ||
![]() |
5fe18e020d | ||
![]() |
a0f2730bb2 | ||
![]() |
0c10c59877 | ||
![]() |
119e4810f2 | ||
![]() |
5dad910488 | ||
![]() |
8841e55f70 | ||
![]() |
5d20732881 | ||
![]() |
d579933451 | ||
![]() |
46e836f93b | ||
![]() |
e27b0c6cd8 | ||
![]() |
e34a3b4a94 | ||
![]() |
b366171401 | ||
![]() |
b034a2d48f | ||
![]() |
65b25ed3f6 | ||
![]() |
b65dfd7f92 | ||
![]() |
6569a0bf36 | ||
![]() |
e5e5e4ef92 | ||
![]() |
f35a056cc6 | ||
![]() |
73ffd3f505 | ||
![]() |
73c5e28f41 | ||
![]() |
f1b45c02f4 | ||
![]() |
941f256ba5 | ||
![]() |
2e547afdb7 | ||
![]() |
422ed1a311 | ||
![]() |
214e412993 | ||
![]() |
f85f6b47ee | ||
![]() |
7e09aef1cc | ||
![]() |
0b4caba8f6 | ||
![]() |
b0866011e5 | ||
![]() |
4d1d89b837 | ||
![]() |
0febf96021 | ||
![]() |
0f10e837e4 | ||
![]() |
5ab352f9c9 | ||
![]() |
5d5470a919 | ||
![]() |
db27722bbc | ||
![]() |
914510c687 | ||
![]() |
a8226b01e2 | ||
![]() |
54a958f39c | ||
![]() |
a1e407e18f | ||
![]() |
42da6c155d | ||
![]() |
89f2342a42 | ||
![]() |
ee1a606888 | ||
![]() |
c183c6492e | ||
![]() |
a70a0d9a31 | ||
![]() |
4c651e15fa | ||
![]() |
dda00c5dd6 | ||
![]() |
28e1b82320 | ||
![]() |
f1ab2eac13 | ||
![]() |
4f13aac094 | ||
![]() |
4ab196ad29 | ||
![]() |
9590697c29 | ||
![]() |
0bec6c4fdf | ||
![]() |
223589ba14 | ||
![]() |
6ba7fe2776 | ||
![]() |
b49798ab16 | ||
![]() |
af9224f5bb | ||
![]() |
c54650f195 | ||
![]() |
c73af2309b | ||
![]() |
169f742b76 | ||
![]() |
0fa8a45e51 | ||
![]() |
17c538a5b7 | ||
![]() |
5625d570fd | ||
![]() |
9b0e55f037 | ||
![]() |
4597d6ac56 | ||
![]() |
9aaad5e8ad | ||
![]() |
94d82f33ea | ||
![]() |
49aa520d67 | ||
![]() |
73c3047651 | ||
![]() |
613da96543 | ||
![]() |
76127f4757 | ||
![]() |
1d5a34cf98 | ||
![]() |
bf13971502 | ||
![]() |
572be38ae4 | ||
![]() |
34d96c9338 | ||
![]() |
4e69c0656e | ||
![]() |
88f7709fad | ||
![]() |
f95825395d | ||
![]() |
e279d39d5a | ||
![]() |
74038add93 | ||
![]() |
0a2b8612dc | ||
![]() |
5923018a9d | ||
![]() |
239a0eea78 | ||
![]() |
456951155d | ||
![]() |
a7a3d0220d | ||
![]() |
b857bfac3e | ||
![]() |
f4ea59493f | ||
![]() |
957b439ced | ||
![]() |
19583503cd | ||
![]() |
d8a3242e22 | ||
![]() |
007d9a0717 | ||
![]() |
016051892b | ||
![]() |
5764c3e610 | ||
![]() |
5bacee35aa | ||
![]() |
e6b6abee24 | ||
![]() |
edddd2f965 | ||
![]() |
0cd17f2b55 | ||
![]() |
3838b2d333 | ||
![]() |
7a90f157b8 | ||
![]() |
a10efcaea0 | ||
![]() |
1f85ce3762 | ||
![]() |
eed0b8ae32 | ||
![]() |
ea2f7a64e1 | ||
![]() |
ad8243fcf5 | ||
![]() |
66fccfd422 | ||
![]() |
448d44ff46 | ||
![]() |
891068a091 | ||
![]() |
127ccef619 | ||
![]() |
84bc47ba18 | ||
![]() |
a139d48959 | ||
![]() |
180a4af7ca | ||
![]() |
8512c354f5 | ||
![]() |
4746bd57d0 | ||
![]() |
6477ef6c44 | ||
![]() |
21e0ebc428 | ||
![]() |
40c65a86fc | ||
![]() |
b5a8f4464c | ||
![]() |
451b5a1fd4 | ||
![]() |
8ddf257aa4 | ||
![]() |
a8742426b3 | ||
![]() |
d44fa11eee | ||
![]() |
0c2d03f3e0 | ||
![]() |
9c6ae5b7ab | ||
![]() |
4c699d86f2 | ||
![]() |
be940fdcea | ||
![]() |
619a455432 | ||
![]() |
03b7bf6ed1 | ||
![]() |
224224084e | ||
![]() |
ac460c2f71 | ||
![]() |
e1b512ef5b | ||
![]() |
d5ab69d703 | ||
![]() |
d9bbb836b8 | ||
![]() |
1de5143fde | ||
![]() |
77814acd1a | ||
![]() |
3926463c59 | ||
![]() |
05ff6dff1b | ||
![]() |
1d5d4564e5 | ||
![]() |
0777cc8892 |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -46,7 +46,11 @@ jobs:
|
|||||||
# By default, queries listed here will override any specified in a config file.
|
# By default, queries listed here will override any specified in a config file.
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
|
116
.github/workflows/docker.yml
vendored
116
.github/workflows/docker.yml
vendored
@@ -1,44 +1,94 @@
|
|||||||
name: Publish Docker image
|
name: Publish Docker image
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
release:
|
||||||
push:
|
types:
|
||||||
branches:
|
- published
|
||||||
- master
|
env:
|
||||||
paths:
|
REGISTRY: ghcr.io
|
||||||
- "**/*.go"
|
IMAGE_NAME: xrayr-project/xrayr
|
||||||
- "go.mod"
|
|
||||||
- "go.sum"
|
|
||||||
- ".github/workflows/*.yml"
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- 'master'
|
|
||||||
jobs:
|
jobs:
|
||||||
push_to_registry:
|
build:
|
||||||
name: Push Docker image to Docker Hub
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm/v6
|
||||||
|
- linux/arm/v7
|
||||||
|
- linux/arm64
|
||||||
|
- linux/s390x
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
- name: Build and push
|
- name: Set up QEMU
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/arm/v7,linux/arm64,linux/amd64,linux/s390x
|
platforms: ${{ matrix.platform }}
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: digests
|
||||||
|
path: /tmp/digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
merge:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4.1.7
|
||||||
|
with:
|
||||||
|
name: digests
|
||||||
|
path: /tmp/digests
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: /tmp/digests
|
||||||
|
run: |
|
||||||
|
ls -al
|
||||||
|
echo docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||||
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||||
|
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||||
|
59
.github/workflows/release.yml
vendored
59
.github/workflows/release.yml
vendored
@@ -26,12 +26,10 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# Include amd64 on all platforms.
|
# Include amd64 on all platforms.
|
||||||
goos: [ windows, freebsd, openbsd, linux, dragonfly, darwin ]
|
goos: [ windows, freebsd, openbsd, linux, darwin ]
|
||||||
goarch: [ amd64, 386 ]
|
goarch: [ amd64, 386 ]
|
||||||
exclude:
|
exclude:
|
||||||
# Exclude i386 on darwin and dragonfly.
|
# Exclude i386 on darwin.
|
||||||
- goarch: 386
|
|
||||||
goos: dragonfly
|
|
||||||
- goarch: 386
|
- goarch: 386
|
||||||
goos: darwin
|
goos: darwin
|
||||||
- goarch: 386
|
- goarch: 386
|
||||||
@@ -113,7 +111,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ^1.19
|
go-version: ^1.20
|
||||||
|
|
||||||
- name: Get project dependencies
|
- name: Get project dependencies
|
||||||
run: go mod download
|
run: go mod download
|
||||||
@@ -122,12 +120,12 @@ jobs:
|
|||||||
- name: Build XrayR
|
- name: Build XrayR
|
||||||
run: |
|
run: |
|
||||||
mkdir -p build_assets
|
mkdir -p build_assets
|
||||||
go build -v -o build_assets/XrayR -trimpath -ldflags "-s -w -buildid=" ./main
|
go build -v -o build_assets/XrayR -trimpath -ldflags "-s -w -buildid="
|
||||||
|
|
||||||
- name: Build Mips softfloat XrayR
|
- name: Build Mips softfloat XrayR
|
||||||
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
|
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
|
||||||
run: |
|
run: |
|
||||||
GOMIPS=softfloat go build -v -o build_assets/XrayR_softfloat -trimpath -ldflags "-s -w -buildid=" ./main
|
GOMIPS=softfloat go build -v -o build_assets/XrayR_softfloat -trimpath -ldflags "-s -w -buildid="
|
||||||
- name: Rename Windows XrayR
|
- name: Rename Windows XrayR
|
||||||
if: matrix.goos == 'windows'
|
if: matrix.goos == 'windows'
|
||||||
run: |
|
run: |
|
||||||
@@ -135,27 +133,32 @@ jobs:
|
|||||||
mv XrayR XrayR.exe
|
mv XrayR XrayR.exe
|
||||||
|
|
||||||
- name: Prepare to release
|
- name: Prepare to release
|
||||||
run: |
|
uses: nick-fields/retry@v2
|
||||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
with:
|
||||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
timeout_minutes: 60
|
||||||
cp ${GITHUB_WORKSPACE}/main/dns.json ./build_assets/dns.json
|
retry_wait_seconds: 60
|
||||||
cp ${GITHUB_WORKSPACE}/main/route.json ./build_assets/route.json
|
max_attempts: 5
|
||||||
cp ${GITHUB_WORKSPACE}/main/custom_outbound.json ./build_assets/custom_outbound.json
|
command: |
|
||||||
cp ${GITHUB_WORKSPACE}/main/custom_inbound.json ./build_assets/custom_inbound.json
|
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||||
cp ${GITHUB_WORKSPACE}/main/rulelist ./build_assets/rulelist
|
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||||
cp ${GITHUB_WORKSPACE}/main/config.yml.example ./build_assets/config.yml
|
cp ${GITHUB_WORKSPACE}/release/config/dns.json ./build_assets/dns.json
|
||||||
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
cp ${GITHUB_WORKSPACE}/release/config/route.json ./build_assets/route.json
|
||||||
for i in "${LIST[@]}"
|
cp ${GITHUB_WORKSPACE}/release/config/custom_outbound.json ./build_assets/custom_outbound.json
|
||||||
do
|
cp ${GITHUB_WORKSPACE}/release/config/custom_inbound.json ./build_assets/custom_inbound.json
|
||||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
|
cp ${GITHUB_WORKSPACE}/release/config/rulelist ./build_assets/rulelist
|
||||||
LASTEST_TAG="$(curl -sL "https://api.github.com/repos/v2fly/${INFO[0]}/releases" | jq -r ".[0].tag_name" || echo "latest")"
|
cp ${GITHUB_WORKSPACE}/release/config/config.yml.example ./build_assets/config.yml
|
||||||
FILE_NAME="${INFO[2]}.dat"
|
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
||||||
echo -e "Downloading ${FILE_NAME}..."
|
for i in "${LIST[@]}"
|
||||||
curl -L "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat" -o ./build_assets/${FILE_NAME}
|
do
|
||||||
echo -e "Verifying HASH key..."
|
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
|
||||||
HASH="$(curl -sL "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
|
DOWNLOAD_URL="https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat"
|
||||||
[ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
FILE_NAME="${INFO[2]}.dat"
|
||||||
done
|
echo -e "Downloading ${DOWNLOAD_URL}..."
|
||||||
|
curl -L "${DOWNLOAD_URL}" -o ./build_assets/${FILE_NAME}
|
||||||
|
echo -e "Verifying HASH key..."
|
||||||
|
HASH="$(curl -sL "${DOWNLOAD_URL}.sha256sum" | awk -F ' ' '{print $1}')"
|
||||||
|
[ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
||||||
|
done
|
||||||
- name: Create ZIP archive
|
- name: Create ZIP archive
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
38
.gitignore
vendored
38
.gitignore
vendored
@@ -1,18 +1,20 @@
|
|||||||
main/main
|
.idea
|
||||||
main/XrayR
|
*.iml
|
||||||
main/XrayR*
|
out
|
||||||
main/mytest
|
gen
|
||||||
main/access.logo
|
*.exe
|
||||||
main/error.log
|
*.exe~
|
||||||
api/chooseparser.go.bak
|
*.dll
|
||||||
common/Inboundbuilder/.lego/
|
*.so
|
||||||
common/legocmd/.lego/
|
*.dylib
|
||||||
.vscode/launch.json
|
*.test
|
||||||
main/.lego
|
*.out
|
||||||
main/cert
|
go.work
|
||||||
main/config.yml
|
main
|
||||||
./vscode
|
XrayR
|
||||||
.idea/*
|
XrayR*
|
||||||
.DS_Store
|
access.log
|
||||||
*.bak
|
error.log
|
||||||
go.work*
|
.lego
|
||||||
|
cert
|
||||||
|
config.yml
|
@@ -1,10 +1,10 @@
|
|||||||
# Build go
|
# Build go
|
||||||
FROM golang:1.19-alpine AS builder
|
FROM golang:1.22.0-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
RUN go build -v -o XrayR -trimpath -ldflags "-s -w -buildid=" ./main
|
RUN go build -v -o XrayR -trimpath -ldflags "-s -w -buildid="
|
||||||
|
|
||||||
# Release
|
# Release
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
110
README-en.md
Normal file
110
README-en.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# XrayR
|
||||||
|
|
||||||
|
[](https://t.me/XrayR_project)
|
||||||
|
[](https://t.me/XrayR_channel)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
[]()
|
||||||
|
|
||||||
|
[Iranian(farsi) README](https://github.com/XrayR-project/XrayR/blob/master/README_Fa.md), [Vietnamese(vi) README](https://github.com/XrayR-project/XrayR/blob/master/README-vi.md), [English(en) README](https://github.com/XrayR-project/XrayR/blob/master/README-en.md)
|
||||||
|
|
||||||
|
A Xray backend framework that can easily support many panels.
|
||||||
|
|
||||||
|
A back -end framework based on XRAY supports V2ay, Trojan, Shadowsocks protocols, which are easy to expand and support multi -panel docker.
|
||||||
|
|
||||||
|
|
||||||
|
If you like this project, you can click STAR+WATCH in the upper right corner to continue to pay attention to the progress of this project.
|
||||||
|
|
||||||
|
## Guide for use
|
||||||
|
|
||||||
|
Tutorial:[Detailed tutorial](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This project is just my personal learning and development and maintenance. I do not guarantee any availability and is not responsible for any consequences caused by the use of this software.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Permanent open source and free.
|
||||||
|
* Support V2Ray, Trojan, Shadowsocks multiple protocols.
|
||||||
|
* Support new features such as Vless and XTLS.
|
||||||
|
* Support single instance docking multi -panel and multi -node, no need to start repeatedly.
|
||||||
|
* Support restriction online IP
|
||||||
|
* Support node port level and user level speed limit.
|
||||||
|
* The configuration is simple and clear.
|
||||||
|
* Modify the automatic restart instance.
|
||||||
|
* Easy to compile and upgrade, you can quickly update the core version and support the new features of XRAY-CORE.
|
||||||
|
|
||||||
|
## Function
|
||||||
|
|
||||||
|
| Function | v2ray | trojan | shadowsocks |
|
||||||
|
|-----------|-------|--------|-------------|
|
||||||
|
| Get node information | √ | √ | √ |
|
||||||
|
| Get user information | √ | √ | √ |
|
||||||
|
| User traffic statistics | √ | √ | √ |
|
||||||
|
| Server information report | √ | √ | √ |
|
||||||
|
| Automatically apply for a TLS certificate | √ | √ | √ |
|
||||||
|
| Automatic renewal TLS certificate | √ | √ | √ |
|
||||||
|
| Number of online people | √ | √ | √ |
|
||||||
|
| Online user restrictions | √ | √ | √ |
|
||||||
|
| Audit rules | √ | √ | √ |
|
||||||
|
| Node port speed limit | √ | √ | √ |
|
||||||
|
| According to user speed limit | √ | √ | √ |
|
||||||
|
| Custom DNS | √ | √ | √ |
|
||||||
|
|
||||||
|
## Support for panels
|
||||||
|
|
||||||
|
| Panel | v2ray | trojan | shadowsocks |
|
||||||
|
|--------------------------------------------------------|-------|--------|-------------------------|
|
||||||
|
| sspanel-uim | √ | √ | √ (Single-ended multi-user and V2Ray-Plugin) |
|
||||||
|
| v2board | √ | √ | √ |
|
||||||
|
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||||
|
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||||
|
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
||||||
|
| [BunPanel](https://github.com/pennyMorant/bunpanel-release) | √ | √ | √ |
|
||||||
|
|
||||||
|
## Software Installation
|
||||||
|
|
||||||
|
### 1-Click installation
|
||||||
|
|
||||||
|
```
|
||||||
|
wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh && bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
[Docker deployment tutorial](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/docker)
|
||||||
|
|
||||||
|
### Manual installation
|
||||||
|
|
||||||
|
[Manual installation tutorial](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
|
||||||
|
|
||||||
|
## Configuration file and detailed use tutorial
|
||||||
|
|
||||||
|
[Detailed tutorial](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
* [Project X](https://github.com/XTLS/)
|
||||||
|
* [V2Fly](https://github.com/v2fly)
|
||||||
|
* [VNet-V2ray](https://github.com/ProxyPanel/VNet-V2ray)
|
||||||
|
* [Air-Universe](https://github.com/crossfw/Air-Universe)
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
[Mozilla Public License Version 2.0](https://github.com/XrayR-project/XrayR/blob/master/LICENSE)
|
||||||
|
|
||||||
|
## Telgram
|
||||||
|
|
||||||
|
[Xrayr back-end discussion](https://t.me/XrayR_project)
|
||||||
|
|
||||||
|
[Xrayr notification](https://t.me/XrayR_channel)
|
||||||
|
|
||||||
|
## Stargazers over time
|
||||||
|
|
||||||
|
[](https://starchart.cc/XrayR-project/XrayR)
|
||||||
|
|
||||||
|
|
105
README-vi.md
Normal file
105
README-vi.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# XrayR
|
||||||
|
|
||||||
|
[](https://t.me/XrayR_project)
|
||||||
|
[](https://t.me/XrayR_channel)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
[]()
|
||||||
|
|
||||||
|
[Iranian(farsi) README](https://github.com/XrayR-project/XrayR/blob/master/README_Fa.md), [Vietnamese(vi) README](https://github.com/XrayR-project/XrayR/blob/master/README-vi.md), [English(en) README](https://github.com/XrayR-project/XrayR/blob/master/README-en.md)
|
||||||
|
|
||||||
|
A Xray backend framework that can easily support many panels.
|
||||||
|
|
||||||
|
Khung trở lại dựa trên XRay hỗ trợ các giao thức V2ay, Trojan, Shadowsocks, dễ dàng mở rộng và hỗ trợ kết nối nhiều người.
|
||||||
|
|
||||||
|
Nếu bạn thích dự án này, bạn có thể nhấp vào Star+Watch ở góc trên bên phải để tiếp tục chú ý đến tiến trình của dự án này.
|
||||||
|
|
||||||
|
## Tài liệu
|
||||||
|
Sử dụng hướng dẫn: [Hướng dẫn chi tiết](https://xrayr-project.github.io/XrayR-doc/) ( Tiếng Trung )
|
||||||
|
|
||||||
|
## Tuyên bố miễn trừ
|
||||||
|
|
||||||
|
Dự án này chỉ là học tập và phát triển và bảo trì cá nhân của tôi. Tôi không đảm bảo bất kỳ sự sẵn có nào và không chịu trách nhiệm cho bất kỳ hậu quả nào do việc sử dụng phần mềm này.
|
||||||
|
|
||||||
|
## Đặt điểm nổi bật
|
||||||
|
|
||||||
|
* Nguồn mở vĩnh viễn và miễn phí.
|
||||||
|
* Hỗ trợ V2Ray, Trojan, Shadowsocks nhiều giao thức.
|
||||||
|
* Hỗ trợ các tính năng mới như Vless và XTL.
|
||||||
|
* Hỗ trợ trường hợp đơn lẻ kết nối Multi -Panel và Multi -Node, không cần phải bắt đầu nhiều lần.
|
||||||
|
* Hỗ trợ hạn chế IP trực tuyến
|
||||||
|
* Hỗ trợ cấp cổng nút và giới hạn tốc độ cấp người dùng.
|
||||||
|
* Cấu hình đơn giản và rõ ràng.
|
||||||
|
* Sửa đổi phiên bản khởi động lại tự động.
|
||||||
|
* Dễ dàng biên dịch và nâng cấp, bạn có thể nhanh chóng cập nhật phiên bản cốt lõi và hỗ trợ các tính năng mới của Xray-Core.
|
||||||
|
|
||||||
|
## Chức năng
|
||||||
|
|
||||||
|
| Chức năng | v2ray | trojan | shadowsocks |
|
||||||
|
|-----------|-------|--------|-------------|
|
||||||
|
| Nhận thông tin Node | √ | √ | √ |
|
||||||
|
| Nhận thông tin người dùng | √ | √ | √ |
|
||||||
|
| Thống kê lưu lượng người dùng | √ | √ | √ |
|
||||||
|
| Báo cáo thông tin máy chủ | √ | √ | √ |
|
||||||
|
| Tự động đăng ký chứng chỉ TLS | √ | √ | √ |
|
||||||
|
| Chứng chỉ TLS gia hạn tự động | √ | √ | √ |
|
||||||
|
| Số người trực tuyến | √ | √ | √ |
|
||||||
|
| Hạn chế người dùng trực tuyến | √ | √ | √ |
|
||||||
|
| Quy tắc kiểm toán | √ | √ | √ |
|
||||||
|
| Giới hạn tốc độ cổng nút | √ | √ | √ |
|
||||||
|
| Theo giới hạn tốc độ người dùng | √ | √ | √ |
|
||||||
|
| DNS tùy chỉnh | √ | √ | √ |
|
||||||
|
|
||||||
|
## Hỗ trợ Panel
|
||||||
|
|
||||||
|
| Panel | v2ray | trojan | shadowsocks |
|
||||||
|
|--------------------------------------------------------|-------|--------|-------------------------|
|
||||||
|
| sspanel-uim | √ | √ | √ (Nhiều người dùng cuối và v2ray-plugin) |
|
||||||
|
| v2board | √ | √ | √ |
|
||||||
|
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||||
|
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||||
|
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
||||||
|
| [BunPanel](https://github.com/pennyMorant/bunpanel-release) | √ | √ | √ |
|
||||||
|
|
||||||
|
## Cài đặt phần mềm
|
||||||
|
|
||||||
|
### Một cài đặt chính
|
||||||
|
|
||||||
|
```
|
||||||
|
wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh && bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sử dụng phần mềm triển khai Docker
|
||||||
|
|
||||||
|
[Hướng dẫn cài đặt thông qua Docker](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/docker)
|
||||||
|
|
||||||
|
### Hướng dẫn cài đặt
|
||||||
|
|
||||||
|
[Hướng dẫn cài đặt thủ công](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
|
||||||
|
|
||||||
|
## Tệp cấu hình và hướng dẫn sử dụng chi tiết
|
||||||
|
|
||||||
|
[Hướng dẫn chi tiết](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
* [Project X](https://github.com/XTLS/)
|
||||||
|
* [V2Fly](https://github.com/v2fly)
|
||||||
|
* [VNet-V2ray](https://github.com/ProxyPanel/VNet-V2ray)
|
||||||
|
* [Air-Universe](https://github.com/crossfw/Air-Universe)
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
[Mozilla Public License Version 2.0](https://github.com/XrayR-project/XrayR/blob/master/LICENSE)
|
||||||
|
|
||||||
|
## Telgram
|
||||||
|
|
||||||
|
[Xrayr Back-end Thảo luận](https://t.me/XrayR_project)
|
||||||
|
|
||||||
|
[Thông báo Xrayr](https://t.me/XrayR_channel)
|
||||||
|
|
||||||
|
## Stargazers over time
|
||||||
|
|
||||||
|
[](https://starchart.cc/XrayR-project/XrayR)
|
@@ -8,6 +8,9 @@
|
|||||||

|

|
||||||
[]()
|
[]()
|
||||||
|
|
||||||
|
|
||||||
|
[English](https://github.com/XrayR-project/XrayR/blob/master/README-en.md)|[Iranian](https://github.com/XrayR-project/XrayR/blob/master/README_Fa.md)|[Vietnamese](https://github.com/XrayR-project/XrayR/blob/master/README-vi.md)
|
||||||
|
|
||||||
A Xray backend framework that can easily support many panels.
|
A Xray backend framework that can easily support many panels.
|
||||||
|
|
||||||
一个基于Xray的后端框架,支持V2ay,Trojan,Shadowsocks协议,极易扩展,支持多面板对接。
|
一个基于Xray的后端框架,支持V2ay,Trojan,Shadowsocks协议,极易扩展,支持多面板对接。
|
||||||
@@ -16,6 +19,7 @@ A Xray backend framework that can easily support many panels.
|
|||||||
|
|
||||||
使用教程:[详细使用教程](https://xrayr-project.github.io/XrayR-doc/)
|
使用教程:[详细使用教程](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
|
|
||||||
## 免责声明
|
## 免责声明
|
||||||
|
|
||||||
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
||||||
@@ -58,6 +62,8 @@ A Xray backend framework that can easily support many panels.
|
|||||||
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||||
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||||
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
||||||
|
| [GoV2Panel](https://github.com/pingProMax/gov2panel) | √ | √ | √ |
|
||||||
|
| [BunPanel](https://github.com/pennyMorant/bunpanel-release) | √ | √ | √ |
|
||||||
|
|
||||||
## 软件安装
|
## 软件安装
|
||||||
|
|
||||||
|
105
README_Fa.md
Normal file
105
README_Fa.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# XrayR
|
||||||
|
|
||||||
|
[](https://t.me/XrayR_project)
|
||||||
|
[](https://t.me/XrayR_channel)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
[]()
|
||||||
|
|
||||||
|
[Iranian(farsi) README](https://github.com/XrayR-project/XrayR/blob/master/README_Fa.md), [Vietnamese(vi) README](https://github.com/XrayR-project/XrayR/blob/master/README-vi.md), [English(en) README](https://github.com/XrayR-project/XrayR/blob/master/README-en.md)
|
||||||
|
|
||||||
|
یک فریمورک بک اند مبتنی بر xray که از چند از پنل پشتیبانی می کند
|
||||||
|
|
||||||
|
یک چارچوب بکاند مبتنی بر Xray که از پروتکلهای V2ay، Trojan و Shadowsocks پشتیبانی میکند، به راحتی قابل گسترش است و از اتصال چند پنل پشتیبانی میکند.
|
||||||
|
|
||||||
|
اگر این پروژه را دوست دارید، می توانید با کلیک بر روی ستاره+ساعت در گوشه بالا سمت راست به ادامه روند پیشرفت این پروژه توجه کنید.
|
||||||
|
|
||||||
|
آموزش:[اموزش با جزئیات](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
|
## سلب مسئولیت
|
||||||
|
|
||||||
|
این پروژه فقط مطالعه، توسعه و نگهداری شخصی من است. من هیچ گونه قابلیت استفاده را تضمین نمی کنم و مسئولیتی در قبال عواقب ناشی از استفاده از این نرم افزار ندارم.
|
||||||
|
## امکانات
|
||||||
|
|
||||||
|
* منبع باز دائمی و رایگان
|
||||||
|
* پشتیبانی از چندین پروتکل V2ray، Trojan، Shadowsocks.
|
||||||
|
* پشتیبانی از ویژگی های جدید مانند Vless و XTLS.
|
||||||
|
* پشتیبانی از اتصال یک نمونه چند پانل، چند گره، بدون نیاز به شروع مکرر.
|
||||||
|
* پشتیبانی محدود IP آنلاین
|
||||||
|
* پشتیبانی از سطح پورت گره، محدودیت سرعت سطح کاربر.
|
||||||
|
* پیکربندی ساده و سرراست است.
|
||||||
|
* پیکربندی را تغییر دهید تا نمونه به طور خودکار راه اندازی مجدد شود.
|
||||||
|
* کامپایل و ارتقاء آن آسان است و می تواند به سرعت نسخه اصلی را به روز کند و از ویژگی های جدید Xray-core پشتیبانی می کند.
|
||||||
|
|
||||||
|
## امکانات
|
||||||
|
|
||||||
|
| امکانات | v2ray | trojan | shadowsocks |
|
||||||
|
|-----------|-------|--------|-------------|
|
||||||
|
| اطلاعات گره را دریافت کنید | √ | √ | √ |
|
||||||
|
| دریافت اطلاعات کاربر | √ | √ | √ |
|
||||||
|
| آمار ترافیک کاربران | √ | √ | √ |
|
||||||
|
| گزارش اطلاعات سرور | √ | √ | √ |
|
||||||
|
| به طور خودکار برای گواهی tls درخواست دهید | √ | √ | √ |
|
||||||
|
| تمدید خودکار گواهی tls | √ | √ | √ |
|
||||||
|
| آمار آنلاین | √ | √ | √ |
|
||||||
|
| محدودیت کاربر آنلاین | √ | √ | √ |
|
||||||
|
| قوانین حسابرسی | √ | √ | √ |
|
||||||
|
| محدودیت سرعت پورت گره | √ | √ | √ |
|
||||||
|
| محدودیت سرعت بر اساس کاربر | √ | √ | √ |
|
||||||
|
| DNS سفارشی | √ | √ | √ |
|
||||||
|
|
||||||
|
## پشتیبانی از قسمت فرانت
|
||||||
|
|
||||||
|
| قسمت فرانت | v2ray | trojan | shadowsocks |
|
||||||
|
|--------------------------------------------------------|-------|--------|-------------------------|
|
||||||
|
| sspanel-uim | √ | √ | √ (تک پورت چند کاربره و V2ray-Plugin) |
|
||||||
|
| v2board | √ | √ | √ |
|
||||||
|
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||||
|
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||||
|
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
||||||
|
| [BunPanel](https://github.com/pennyMorant/bunpanel-release) | √ | √ | √ |
|
||||||
|
|
||||||
|
## نصب نرم افزار
|
||||||
|
|
||||||
|
### نصب بصورت یکپارچه
|
||||||
|
|
||||||
|
```
|
||||||
|
wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh && bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### استقرار نرم افزار با استفاده از Docker
|
||||||
|
|
||||||
|
[آموزش استقرار داکر](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/docker)
|
||||||
|
|
||||||
|
### نصب دستی
|
||||||
|
|
||||||
|
[آموزش نصب دستی](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
|
||||||
|
|
||||||
|
## فایل های پیکربندی و آموزش های با جرئیات
|
||||||
|
|
||||||
|
[آموزش مفصل](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
* [Project X](https://github.com/XTLS/)
|
||||||
|
* [V2Fly](https://github.com/v2fly)
|
||||||
|
* [VNet-V2ray](https://github.com/ProxyPanel/VNet-V2ray)
|
||||||
|
* [Air-Universe](https://github.com/crossfw/Air-Universe)
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
[Mozilla Public License Version 2.0](https://github.com/XrayR-project/XrayR/blob/master/LICENSE)
|
||||||
|
|
||||||
|
## Telgram
|
||||||
|
|
||||||
|
[بحث در مورد XrayR Backend](https://t.me/XrayR_project)
|
||||||
|
|
||||||
|
[کانال اعلان در مورد XrayR](https://t.me/XrayR_channel)
|
||||||
|
|
||||||
|
## Stargazers over time
|
||||||
|
|
||||||
|
[](https://starchart.cc/XrayR-project/XrayR)
|
||||||
|
|
||||||
|
|
100
api/apimodel.go
100
api/apimodel.go
@@ -3,6 +3,14 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserNotModified = "users not modified"
|
||||||
|
NodeNotModified = "node not modified"
|
||||||
|
RuleNotModified = "rules not modified"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config API config
|
// Config API config
|
||||||
@@ -12,7 +20,7 @@ type Config struct {
|
|||||||
Key string `mapstructure:"ApiKey"`
|
Key string `mapstructure:"ApiKey"`
|
||||||
NodeType string `mapstructure:"NodeType"`
|
NodeType string `mapstructure:"NodeType"`
|
||||||
EnableVless bool `mapstructure:"EnableVless"`
|
EnableVless bool `mapstructure:"EnableVless"`
|
||||||
EnableXTLS bool `mapstructure:"EnableXTLS"`
|
VlessFlow string `mapstructure:"VlessFlow"`
|
||||||
Timeout int `mapstructure:"Timeout"`
|
Timeout int `mapstructure:"Timeout"`
|
||||||
SpeedLimit float64 `mapstructure:"SpeedLimit"`
|
SpeedLimit float64 `mapstructure:"SpeedLimit"`
|
||||||
DeviceLimit int `mapstructure:"DeviceLimit"`
|
DeviceLimit int `mapstructure:"DeviceLimit"`
|
||||||
@@ -29,38 +37,59 @@ type NodeStatus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NodeInfo struct {
|
type NodeInfo struct {
|
||||||
NodeType string // Must be V2ray, Trojan, and Shadowsocks
|
AcceptProxyProtocol bool
|
||||||
NodeID int
|
Authority string
|
||||||
Port uint32
|
NodeType string // Must be V2ray, Trojan, and Shadowsocks
|
||||||
SpeedLimit uint64 // Bps
|
NodeID int
|
||||||
AlterID uint16
|
Port uint32
|
||||||
TransportProtocol string
|
SpeedLimit uint64 // Bps
|
||||||
FakeType string
|
AlterID uint16
|
||||||
Host string
|
TransportProtocol string
|
||||||
Path string
|
FakeType string
|
||||||
EnableTLS bool
|
Host string
|
||||||
TLSType string
|
Path string
|
||||||
EnableVless bool
|
EnableTLS bool
|
||||||
CypherMethod string
|
EnableSniffing bool
|
||||||
ServerKey string
|
RouteOnly bool
|
||||||
ServiceName string
|
EnableVless bool
|
||||||
Header json.RawMessage
|
VlessFlow string
|
||||||
|
CypherMethod string
|
||||||
|
ServerKey string
|
||||||
|
ServiceName string
|
||||||
|
Method string
|
||||||
|
Header json.RawMessage
|
||||||
|
HttpHeaders map[string]*conf.StringList
|
||||||
|
Headers map[string]string
|
||||||
|
NameServerConfig []*conf.NameServerConfig
|
||||||
|
EnableREALITY bool
|
||||||
|
REALITYConfig *REALITYConfig
|
||||||
|
Show bool
|
||||||
|
EnableTFO bool
|
||||||
|
Dest string
|
||||||
|
ProxyProtocolVer uint64
|
||||||
|
ServerNames []string
|
||||||
|
PrivateKey string
|
||||||
|
MinClientVer string
|
||||||
|
MaxClientVer string
|
||||||
|
MaxTimeDiff uint64
|
||||||
|
ShortIds []string
|
||||||
|
Xver uint64
|
||||||
|
Flow string
|
||||||
|
Security string
|
||||||
|
Key string
|
||||||
|
RejectUnknownSni bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
UID int
|
UID int
|
||||||
Email string
|
Email string
|
||||||
Passwd string
|
UUID string
|
||||||
Port uint32
|
Passwd string
|
||||||
Method string
|
Port uint32
|
||||||
SpeedLimit uint64 // Bps
|
AlterID uint16
|
||||||
DeviceLimit int
|
Method string
|
||||||
Protocol string
|
SpeedLimit uint64 // Bps
|
||||||
ProtocolParam string
|
DeviceLimit int
|
||||||
Obfs string
|
|
||||||
ObfsParam string
|
|
||||||
UUID string
|
|
||||||
AlterID uint16
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnlineUser struct {
|
type OnlineUser struct {
|
||||||
@@ -91,3 +120,14 @@ type DetectResult struct {
|
|||||||
UID int
|
UID int
|
||||||
RuleID int
|
RuleID int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type REALITYConfig struct {
|
||||||
|
Dest string
|
||||||
|
ProxyProtocolVer uint64
|
||||||
|
ServerNames []string
|
||||||
|
PrivateKey string
|
||||||
|
MinClientVer string
|
||||||
|
MaxClientVer string
|
||||||
|
MaxTimeDiff uint64
|
||||||
|
ShortIds []string
|
||||||
|
}
|
||||||
|
427
api/bunpanel/bunpanel.go
Normal file
427
api/bunpanel/bunpanel.go
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
package bunpanel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIClient struct {
|
||||||
|
client *resty.Client
|
||||||
|
APIHost string
|
||||||
|
NodeID int
|
||||||
|
Key string
|
||||||
|
NodeType string
|
||||||
|
EnableVless bool
|
||||||
|
VlessFlow string
|
||||||
|
SpeedLimit float64
|
||||||
|
DeviceLimit int
|
||||||
|
LocalRuleList []api.DetectRule
|
||||||
|
LastReportOnline map[int]int
|
||||||
|
access sync.Mutex
|
||||||
|
eTags map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportIllegal implements api.API.
|
||||||
|
func (*APIClient) ReportIllegal(detectResultList *[]api.DetectResult) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportNodeStatus implements api.API.
|
||||||
|
func (*APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeRule implements api.API.
|
||||||
|
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||||
|
ruleList := c.LocalRuleList
|
||||||
|
return &ruleList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(apiConfig *api.Config) *APIClient {
|
||||||
|
client := resty.New()
|
||||||
|
client.SetRetryCount(3)
|
||||||
|
if apiConfig.Timeout > 0 {
|
||||||
|
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
|
||||||
|
} else {
|
||||||
|
client.SetTimeout(5 * time.Second)
|
||||||
|
}
|
||||||
|
client.OnError(func(req *resty.Request, err error) {
|
||||||
|
if v, ok := err.(*resty.ResponseError); ok {
|
||||||
|
// v.Response contains the last response from the server
|
||||||
|
// v.Err contains the original error
|
||||||
|
log.Print(v.Err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
client.SetBaseURL(apiConfig.APIHost)
|
||||||
|
// Create Key for each requests
|
||||||
|
client.SetQueryParams(map[string]string{
|
||||||
|
"serverId": strconv.Itoa(apiConfig.NodeID),
|
||||||
|
"nodeType": strings.ToLower(apiConfig.NodeType),
|
||||||
|
"token": apiConfig.Key,
|
||||||
|
})
|
||||||
|
// Read local rule list
|
||||||
|
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
|
||||||
|
apiClient := &APIClient{
|
||||||
|
client: client,
|
||||||
|
NodeID: apiConfig.NodeID,
|
||||||
|
Key: apiConfig.Key,
|
||||||
|
APIHost: apiConfig.APIHost,
|
||||||
|
NodeType: apiConfig.NodeType,
|
||||||
|
EnableVless: apiConfig.EnableVless,
|
||||||
|
VlessFlow: apiConfig.VlessFlow,
|
||||||
|
SpeedLimit: apiConfig.SpeedLimit,
|
||||||
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
|
LocalRuleList: localRuleList,
|
||||||
|
eTags: make(map[string]string),
|
||||||
|
}
|
||||||
|
return apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// readLocalRuleList reads the local rule list file
|
||||||
|
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||||
|
LocalRuleList = make([]api.DetectRule, 0)
|
||||||
|
|
||||||
|
if path != "" {
|
||||||
|
// open the file
|
||||||
|
file, err := os.Open(path)
|
||||||
|
|
||||||
|
// handle errors while opening
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error when opening file: %s", err)
|
||||||
|
return LocalRuleList
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
fileScanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
// read line by line
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
LocalRuleList = append(LocalRuleList, api.DetectRule{
|
||||||
|
ID: -1,
|
||||||
|
Pattern: regexp.MustCompile(fileScanner.Text()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// handle first encountered error while reading
|
||||||
|
if err := fileScanner.Err(); err != nil {
|
||||||
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalRuleList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe return a description of the client
|
||||||
|
func (c *APIClient) Describe() api.ClientInfo {
|
||||||
|
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug set the client debug for client
|
||||||
|
func (c *APIClient) Debug() {
|
||||||
|
c.client.SetDebug(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) assembleURL(path string) string {
|
||||||
|
return c.APIHost + path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*Response, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode() > 400 {
|
||||||
|
body := res.Body()
|
||||||
|
return nil, fmt.Errorf("request %s failed: %s, %v", c.assembleURL(path), string(body), err)
|
||||||
|
}
|
||||||
|
response := res.Result().(*Response)
|
||||||
|
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
res, _ := json.Marshal(&response)
|
||||||
|
return nil, fmt.Errorf("statusCode %s invalid", string(res))
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
|
path := fmt.Sprintf("/v2/server/%d/get", c.NodeID)
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetResult(&Response{}).
|
||||||
|
SetHeader("If-None-Match", c.eTags["node"]).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Get(path)
|
||||||
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.NodeNotModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["node"] {
|
||||||
|
c.eTags["node"] = res.Header().Get("ETag")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInfoResponse := new(Server)
|
||||||
|
|
||||||
|
if err := json.Unmarshal(response.Datas, nodeInfoResponse); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInfo, err = c.ParseNodeInfo(nodeInfoResponse)
|
||||||
|
if err != nil {
|
||||||
|
res, _ := json.Marshal(nodeInfoResponse)
|
||||||
|
return nil, fmt.Errorf("parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://xrayr-project.github.io/XrayR-doc/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
res, _ := json.Marshal(nodeInfoResponse)
|
||||||
|
return nil, fmt.Errorf("parse node info failed: %s, \nError: %s", string(res), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
|
path := "/v2/user/get"
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParam("serverId", strconv.Itoa(c.NodeID)).
|
||||||
|
SetHeader("If-None-Match", c.eTags["users"]).
|
||||||
|
SetResult(&Response{}).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Get(path)
|
||||||
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.UserNotModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["users"] {
|
||||||
|
c.eTags["users"] = res.Header().Get("ETag")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userListResponse := new([]User)
|
||||||
|
|
||||||
|
if err := json.Unmarshal(response.Datas, userListResponse); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||||
|
}
|
||||||
|
userList, err := c.ParseUserListResponse(userListResponse)
|
||||||
|
if err != nil {
|
||||||
|
res, _ := json.Marshal(userListResponse)
|
||||||
|
return nil, fmt.Errorf("parse user list failed: %s", string(res))
|
||||||
|
}
|
||||||
|
return userList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
reportOnline := make(map[int]int)
|
||||||
|
data := make([]OnlineUser, len(*onlineUserList))
|
||||||
|
for i, user := range *onlineUserList {
|
||||||
|
data[i] = OnlineUser{UID: user.UID, IP: user.IP}
|
||||||
|
reportOnline[user.UID]++
|
||||||
|
}
|
||||||
|
c.LastReportOnline = reportOnline // Update LastReportOnline
|
||||||
|
|
||||||
|
postData := &PostData{Data: data}
|
||||||
|
path := "/v2/user/online/create"
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParam("serverId", strconv.Itoa(c.NodeID)).
|
||||||
|
SetBody(postData).
|
||||||
|
SetResult(&Response{}).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(path)
|
||||||
|
|
||||||
|
_, err = c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||||
|
|
||||||
|
data := make([]UserTraffic, len(*userTraffic))
|
||||||
|
for i, traffic := range *userTraffic {
|
||||||
|
data[i] = UserTraffic{
|
||||||
|
UID: traffic.UID,
|
||||||
|
Upload: traffic.Upload,
|
||||||
|
Download: traffic.Download}
|
||||||
|
}
|
||||||
|
postData := &PostData{Data: data}
|
||||||
|
path := "/v2/user/data-usage/create"
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParam("serverId", strconv.Itoa(c.NodeID)).
|
||||||
|
SetBody(postData).
|
||||||
|
SetResult(&Response{}).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(path)
|
||||||
|
_, err = c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]User) (*[]api.UserInfo, error) {
|
||||||
|
c.access.Lock()
|
||||||
|
// Clear Last report log
|
||||||
|
defer func() {
|
||||||
|
c.LastReportOnline = make(map[int]int)
|
||||||
|
c.access.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var deviceLimit, localDeviceLimit = 0, 0
|
||||||
|
var speedLimit uint64 = 0
|
||||||
|
var userList []api.UserInfo
|
||||||
|
for _, user := range *userInfoResponse {
|
||||||
|
if c.DeviceLimit > 0 {
|
||||||
|
deviceLimit = c.DeviceLimit
|
||||||
|
} else {
|
||||||
|
deviceLimit = user.DeviceLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is still device available, add the user
|
||||||
|
if deviceLimit > 0 && user.AliveIP > 0 {
|
||||||
|
lastOnline := 0
|
||||||
|
if v, ok := c.LastReportOnline[user.ID]; ok {
|
||||||
|
lastOnline = v
|
||||||
|
}
|
||||||
|
// If there are any available device.
|
||||||
|
if localDeviceLimit = deviceLimit - user.AliveIP + lastOnline; localDeviceLimit > 0 {
|
||||||
|
deviceLimit = localDeviceLimit
|
||||||
|
// If this backend server has reported any user in the last reporting period.
|
||||||
|
} else if lastOnline > 0 {
|
||||||
|
deviceLimit = lastOnline
|
||||||
|
// Remove this user.
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SpeedLimit > 0 {
|
||||||
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
|
} else {
|
||||||
|
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||||
|
}
|
||||||
|
userList = append(userList, api.UserInfo{
|
||||||
|
UID: user.ID,
|
||||||
|
UUID: user.UUID,
|
||||||
|
SpeedLimit: speedLimit,
|
||||||
|
DeviceLimit: deviceLimit,
|
||||||
|
Passwd: user.UUID,
|
||||||
|
Email: user.UUID + "@bunpanel.user",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) ParseNodeInfo(nodeInfoResponse *Server) (*api.NodeInfo, error) {
|
||||||
|
var (
|
||||||
|
speedLimit uint64 = 0
|
||||||
|
enableTLS, enableVless, enableREALITY bool
|
||||||
|
alterID uint16 = 0
|
||||||
|
tlsType, transportProtocol string
|
||||||
|
)
|
||||||
|
|
||||||
|
nodeConfig := nodeInfoResponse
|
||||||
|
port := uint32(nodeConfig.Port)
|
||||||
|
|
||||||
|
switch c.NodeType {
|
||||||
|
case "Shadowsocks":
|
||||||
|
transportProtocol = "tcp"
|
||||||
|
case "V2ray":
|
||||||
|
transportProtocol = nodeConfig.Network
|
||||||
|
tlsType = nodeConfig.Security
|
||||||
|
|
||||||
|
if tlsType == "tls" || tlsType == "xtls" {
|
||||||
|
enableTLS = true
|
||||||
|
}
|
||||||
|
if tlsType == "reality" {
|
||||||
|
enableREALITY = true
|
||||||
|
enableVless = true
|
||||||
|
}
|
||||||
|
case "Trojan":
|
||||||
|
enableTLS = true
|
||||||
|
tlsType = "tls"
|
||||||
|
transportProtocol = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reality config
|
||||||
|
realityConfig := new(api.REALITYConfig)
|
||||||
|
if nodeConfig.RealitySettings != nil {
|
||||||
|
r := new(RealitySettings)
|
||||||
|
json.Unmarshal(nodeConfig.RealitySettings, r)
|
||||||
|
realityConfig = &api.REALITYConfig{
|
||||||
|
Dest: r.Dest,
|
||||||
|
ProxyProtocolVer: r.ProxyProtocolVer,
|
||||||
|
ServerNames: r.ServerNames,
|
||||||
|
PrivateKey: r.PrivateKey,
|
||||||
|
MinClientVer: r.MinClientVer,
|
||||||
|
MaxClientVer: r.MaxClientVer,
|
||||||
|
MaxTimeDiff: r.MaxTimeDiff,
|
||||||
|
ShortIds: r.ShortIds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wsConfig := new(WsSettings)
|
||||||
|
if nodeConfig.WsSettings != nil {
|
||||||
|
json.Unmarshal(nodeConfig.WsSettings, wsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcConfig := new(GrpcSettigns)
|
||||||
|
if nodeConfig.GrpcSettings != nil {
|
||||||
|
json.Unmarshal(nodeConfig.GrpcSettings, grpcConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConfig := new(TcpSettings)
|
||||||
|
if nodeConfig.TcpSettings != nil {
|
||||||
|
json.Unmarshal(nodeConfig.TcpSettings, tcpConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GeneralNodeInfo
|
||||||
|
nodeInfo := &api.NodeInfo{
|
||||||
|
NodeType: c.NodeType,
|
||||||
|
NodeID: c.NodeID,
|
||||||
|
Port: port,
|
||||||
|
SpeedLimit: speedLimit,
|
||||||
|
AlterID: alterID,
|
||||||
|
TransportProtocol: transportProtocol,
|
||||||
|
Host: wsConfig.Headers.Host,
|
||||||
|
Path: wsConfig.Path,
|
||||||
|
EnableTLS: enableTLS,
|
||||||
|
EnableVless: enableVless,
|
||||||
|
VlessFlow: nodeConfig.Flow,
|
||||||
|
CypherMethod: nodeConfig.Method,
|
||||||
|
ServiceName: grpcConfig.ServiceName,
|
||||||
|
Header: tcpConfig.Header,
|
||||||
|
EnableREALITY: enableREALITY,
|
||||||
|
REALITYConfig: realityConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeInfo, nil
|
||||||
|
}
|
@@ -1,24 +1,24 @@
|
|||||||
package v2board_test
|
package bunpanel_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
"github.com/XrayR-project/XrayR/api/v2board"
|
"github.com/XrayR-project/XrayR/api/bunpanel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateClient() api.API {
|
func CreateClient() api.API {
|
||||||
apiConfig := &api.Config{
|
apiConfig := &api.Config{
|
||||||
APIHost: "http://localhost:9897",
|
APIHost: "http://localhost:8080",
|
||||||
Key: "qwertyuiopasdfghjkl",
|
Key: "123456",
|
||||||
NodeID: 1,
|
NodeID: 1,
|
||||||
NodeType: "V2ray",
|
NodeType: "V2ray",
|
||||||
}
|
}
|
||||||
client := v2board.New(apiConfig)
|
client := bunpanel.New(apiConfig)
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetV2rayNodeinfo(t *testing.T) {
|
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
nodeInfo, err := client.GetNodeInfo()
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -27,14 +27,14 @@ func TestGetV2rayNodeinfo(t *testing.T) {
|
|||||||
t.Log(nodeInfo)
|
t.Log(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSSNodeinfo(t *testing.T) {
|
func TestGetSSNodeInfo(t *testing.T) {
|
||||||
apiConfig := &api.Config{
|
apiConfig := &api.Config{
|
||||||
APIHost: "http://127.0.0.1:668",
|
APIHost: "http://127.0.0.1:668",
|
||||||
Key: "qwertyuiopasdfghjkl",
|
Key: "qwertyuiopasdfghjkl",
|
||||||
NodeID: 1,
|
NodeID: 1,
|
||||||
NodeType: "Shadowsocks",
|
NodeType: "Shadowsocks",
|
||||||
}
|
}
|
||||||
client := v2board.New(apiConfig)
|
client := bunpanel.New(apiConfig)
|
||||||
nodeInfo, err := client.GetNodeInfo()
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -42,14 +42,14 @@ func TestGetSSNodeinfo(t *testing.T) {
|
|||||||
t.Log(nodeInfo)
|
t.Log(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTrojanNodeinfo(t *testing.T) {
|
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||||
apiConfig := &api.Config{
|
apiConfig := &api.Config{
|
||||||
APIHost: "http://127.0.0.1:668",
|
APIHost: "http://127.0.0.1:668",
|
||||||
Key: "qwertyuiopasdfghjkl",
|
Key: "qwertyuiopasdfghjkl",
|
||||||
NodeID: 1,
|
NodeID: 1,
|
||||||
NodeType: "Trojan",
|
NodeType: "Trojan",
|
||||||
}
|
}
|
||||||
client := v2board.New(apiConfig)
|
client := bunpanel.New(apiConfig)
|
||||||
nodeInfo, err := client.GetNodeInfo()
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -78,8 +78,8 @@ func TestReportReportUserTraffic(t *testing.T) {
|
|||||||
for i, userInfo := range *userList {
|
for i, userInfo := range *userList {
|
||||||
generalUserTraffic[i] = api.UserTraffic{
|
generalUserTraffic[i] = api.UserTraffic{
|
||||||
UID: userInfo.UID,
|
UID: userInfo.UID,
|
||||||
Upload: 114514,
|
Upload: 1111,
|
||||||
Download: 114514,
|
Download: 2222,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// client.Debug()
|
// client.Debug()
|
72
api/bunpanel/model.go
Normal file
72
api/bunpanel/model.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package bunpanel
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Port int `json:"serverPort"`
|
||||||
|
Network string `json:"network"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Security string `json:"security"`
|
||||||
|
Flow string `json:"flow"`
|
||||||
|
WsSettings json.RawMessage `json:"wsSettings"`
|
||||||
|
RealitySettings json.RawMessage `json:"realitySettings"`
|
||||||
|
GrpcSettings json.RawMessage `json:"grpcSettings"`
|
||||||
|
TcpSettings json.RawMessage `json:"tcpSettings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WsSettings struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Headers struct {
|
||||||
|
Host string `json:"Host"`
|
||||||
|
} `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrpcSettigns struct {
|
||||||
|
ServiceName string `json:"serviceName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TcpSettings struct {
|
||||||
|
Header json.RawMessage `json:"header"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RealitySettings struct {
|
||||||
|
Show bool `json:"show"`
|
||||||
|
Dest string `json:"dest"`
|
||||||
|
Xver uint64 `json:"xver"`
|
||||||
|
ServerNames []string `json:"serverNames"`
|
||||||
|
PrivateKey string `json:"privateKey"`
|
||||||
|
MinClientVer string `json:"minClientVer"`
|
||||||
|
MaxClientVer string `json:"maxClientVer"`
|
||||||
|
MaxTimeDiff uint64 `json:"maxTimeDiff"`
|
||||||
|
ProxyProtocolVer uint64 `json:"proxyProtocolVer"`
|
||||||
|
ShortIds []string `json:"shortIds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
SpeedLimit float64 `json:"speedLimit"`
|
||||||
|
DeviceLimit int `json:"ipLimit"`
|
||||||
|
AliveIP int `json:"onlineIp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnlineUser struct {
|
||||||
|
UID int `json:"userId"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserTraffic is the data structure of traffic
|
||||||
|
type UserTraffic struct {
|
||||||
|
UID int `json:"userId"`
|
||||||
|
Upload int64 `json:"u"`
|
||||||
|
Download int64 `json:"d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
StatusCode int `json:"statusCode"`
|
||||||
|
Datas json.RawMessage `json:"datas"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostData struct {
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
329
api/gov2panel/gov2panel.go
Normal file
329
api/gov2panel/gov2panel.go
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
package gov2panel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/net/gclient"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIClient API config
|
||||||
|
type APIClient struct {
|
||||||
|
ctx context.Context
|
||||||
|
APIHost string
|
||||||
|
NodeID int
|
||||||
|
Key string
|
||||||
|
NodeType string
|
||||||
|
EnableVless bool
|
||||||
|
VlessFlow string
|
||||||
|
Timeout int
|
||||||
|
SpeedLimit float64
|
||||||
|
DeviceLimit int
|
||||||
|
RuleListPath string
|
||||||
|
DisableCustomConfig bool
|
||||||
|
|
||||||
|
LocalRuleList []api.DetectRule
|
||||||
|
}
|
||||||
|
|
||||||
|
// New create an api instance
|
||||||
|
func New(apiConfig *api.Config) *APIClient {
|
||||||
|
|
||||||
|
//https://goframe.org/pages/viewpage.action?pageId=1114381
|
||||||
|
|
||||||
|
apiClient := &APIClient{
|
||||||
|
ctx: context.Background(),
|
||||||
|
APIHost: apiConfig.APIHost,
|
||||||
|
NodeID: apiConfig.NodeID,
|
||||||
|
Key: apiConfig.Key,
|
||||||
|
NodeType: apiConfig.NodeType,
|
||||||
|
EnableVless: apiConfig.EnableVless,
|
||||||
|
VlessFlow: apiConfig.VlessFlow,
|
||||||
|
Timeout: apiConfig.Timeout,
|
||||||
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
|
RuleListPath: apiConfig.RuleListPath,
|
||||||
|
DisableCustomConfig: apiConfig.DisableCustomConfig,
|
||||||
|
|
||||||
|
LocalRuleList: readLocalRuleList(apiConfig.RuleListPath), //加载本地路由规则
|
||||||
|
}
|
||||||
|
return apiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// readLocalRuleList reads the local rule list file
|
||||||
|
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||||
|
|
||||||
|
LocalRuleList = make([]api.DetectRule, 0)
|
||||||
|
if path != "" {
|
||||||
|
// open the file
|
||||||
|
file, err := os.Open(path)
|
||||||
|
|
||||||
|
// handle errors while opening
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error when opening file: %s", err)
|
||||||
|
return LocalRuleList
|
||||||
|
}
|
||||||
|
|
||||||
|
fileScanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
// read line by line
|
||||||
|
for fileScanner.Scan() {
|
||||||
|
LocalRuleList = append(LocalRuleList, api.DetectRule{
|
||||||
|
ID: -1,
|
||||||
|
Pattern: regexp.MustCompile(fileScanner.Text()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// handle first encountered error while reading
|
||||||
|
if err := fileScanner.Err(); err != nil {
|
||||||
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalRuleList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
|
|
||||||
|
apiPath := "/api/server/config"
|
||||||
|
reslutJson, err := c.sendRequest(
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
apiPath,
|
||||||
|
g.Map{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reslutJson.Get("data").String() == "" {
|
||||||
|
return nil, errors.New("gov2panel node config data is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reslutJson.Get("data.port").Int() == 0 {
|
||||||
|
return nil, errors.New("server port must > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInfo = new(api.NodeInfo)
|
||||||
|
err = reslutJson.Get("data").Scan(nodeInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse node info failed: \nError: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
routes := make([]route, 0)
|
||||||
|
err = reslutJson.Get("data.routes").Scan(&routes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse node routes failed: \nError: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInfo.NodeType = c.NodeType
|
||||||
|
nodeInfo.NodeID = c.NodeID
|
||||||
|
nodeInfo.EnableVless = c.EnableVless
|
||||||
|
nodeInfo.VlessFlow = c.VlessFlow
|
||||||
|
|
||||||
|
nodeInfo.AlterID = 0
|
||||||
|
|
||||||
|
nodeInfo.NameServerConfig = parseDNSConfig(routes)
|
||||||
|
|
||||||
|
return nodeInfo, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDNSConfig(routes []route) (nameServerList []*conf.NameServerConfig) {
|
||||||
|
|
||||||
|
nameServerList = make([]*conf.NameServerConfig, 0)
|
||||||
|
for i := range routes {
|
||||||
|
if routes[i].Action == "dns" {
|
||||||
|
nameServerList = append(nameServerList, &conf.NameServerConfig{
|
||||||
|
Address: &conf.Address{Address: net.ParseAddress(routes[i].ActionValue)},
|
||||||
|
Domains: routes[i].Match,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserList will pull user form panel
|
||||||
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
|
|
||||||
|
apiPath := "/api/server/user"
|
||||||
|
|
||||||
|
switch c.NodeType {
|
||||||
|
case "V2ray", "Trojan", "Shadowsocks", "Vmess", "Vless":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
reslutJson, err := c.sendRequest(
|
||||||
|
nil,
|
||||||
|
"GET",
|
||||||
|
apiPath,
|
||||||
|
g.Map{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []*user
|
||||||
|
reslutJson.Get("data.users").Scan(&users)
|
||||||
|
|
||||||
|
userList := make([]api.UserInfo, len(users))
|
||||||
|
for i := 0; i < len(users); i++ {
|
||||||
|
u := api.UserInfo{
|
||||||
|
UID: users[i].Id,
|
||||||
|
UUID: users[i].Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support 1.7.1 speed limit
|
||||||
|
if c.SpeedLimit > 0 {
|
||||||
|
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
|
||||||
|
} else {
|
||||||
|
u.SpeedLimit = uint64(users[i].SpeedLimit * 1000000 / 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
|
||||||
|
u.Email = u.UUID + "@gov2panel.user"
|
||||||
|
if c.NodeType == "Shadowsocks" {
|
||||||
|
u.Passwd = u.UUID
|
||||||
|
}
|
||||||
|
userList[i] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUser *[]api.OnlineUser) (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportUserTraffic reports the user traffic
|
||||||
|
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) (err error) {
|
||||||
|
apiPath := "/api/server/push"
|
||||||
|
reslutJson, err := c.sendRequest(
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
apiPath,
|
||||||
|
g.Map{
|
||||||
|
"data": userTraffic,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reslutJson.Get("code").Int() != 0 {
|
||||||
|
return errors.New(reslutJson.Get("message").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) Describe() api.ClientInfo {
|
||||||
|
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeRule implements the API interface
|
||||||
|
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||||
|
ruleList := c.LocalRuleList
|
||||||
|
|
||||||
|
apiPath := "/api/server/config"
|
||||||
|
reslutJson, err := c.sendRequest(
|
||||||
|
nil,
|
||||||
|
"POST",
|
||||||
|
apiPath,
|
||||||
|
g.Map{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
routes := make([]route, 0)
|
||||||
|
err = reslutJson.Get("data.routes").Scan(&routes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse node routes failed: \nError: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range routes {
|
||||||
|
if routes[i].Action == "block" {
|
||||||
|
for _, v := range routes[i].Match {
|
||||||
|
ruleList = append(ruleList, api.DetectRule{
|
||||||
|
ID: i,
|
||||||
|
Pattern: regexp.MustCompile(v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ruleList, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *APIClient) Debug() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// request 统一请求接口
|
||||||
|
func (c *APIClient) sendRequest(headerM map[string]string, method string, url string, data g.Map) (reslutJson *gjson.Json, err error) {
|
||||||
|
url = c.APIHost + url
|
||||||
|
|
||||||
|
client := gclient.New()
|
||||||
|
|
||||||
|
var gResponse *gclient.Response
|
||||||
|
|
||||||
|
if c.Timeout > 0 {
|
||||||
|
client.SetTimeout(time.Duration(c.Timeout) * time.Second) //方法用于设置当前请求超时时间
|
||||||
|
} else {
|
||||||
|
client.SetTimeout(5 * time.Second)
|
||||||
|
}
|
||||||
|
client.Retry(3, 10*time.Second) //方法用于设置请求失败时重连次数和重连间隔。
|
||||||
|
|
||||||
|
client.SetHeaderMap(headerM)
|
||||||
|
client.SetHeader("Content-Type", "application/json")
|
||||||
|
|
||||||
|
data["token"] = c.Key
|
||||||
|
data["node_id"] = c.NodeID
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case "GET":
|
||||||
|
gResponse, err = client.Get(c.ctx, url, data)
|
||||||
|
case "POST":
|
||||||
|
gResponse, err = client.Post(c.ctx, url, data)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported method: %s", method)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gResponse.Close()
|
||||||
|
|
||||||
|
reslutJson = gjson.New(gResponse.ReadAllString())
|
||||||
|
if reslutJson == nil {
|
||||||
|
err = fmt.Errorf("http reslut to json, err : %s", gResponse.ReadAllString())
|
||||||
|
}
|
||||||
|
if reslutJson.Get("code").Int() != 0 {
|
||||||
|
err = errors.New(reslutJson.Get("message").String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
83
api/gov2panel/gov2panel_test.go
Normal file
83
api/gov2panel/gov2panel_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package gov2panel_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/api/gov2panel"
|
||||||
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateClient() api.API {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "http://localhost:8080",
|
||||||
|
Key: "123456",
|
||||||
|
NodeID: 90,
|
||||||
|
NodeType: "V2ray",
|
||||||
|
}
|
||||||
|
client := gov2panel.New(apiConfig)
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNodeInfo(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInfoJson := gjson.New(nodeInfo)
|
||||||
|
t.Log(nodeInfoJson.String())
|
||||||
|
t.Log(nodeInfoJson.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserList(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(len(*userList))
|
||||||
|
t.Log(userList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReportReportUserTraffic(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(userList)
|
||||||
|
generalUserTraffic := make([]api.UserTraffic, len(*userList))
|
||||||
|
for i, userInfo := range *userList {
|
||||||
|
generalUserTraffic[i] = api.UserTraffic{
|
||||||
|
UID: userInfo.UID,
|
||||||
|
Upload: 1073741824,
|
||||||
|
Download: 1073741824,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(gconv.String(generalUserTraffic))
|
||||||
|
client = CreateClient()
|
||||||
|
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNodeRule(t *testing.T) {
|
||||||
|
|
||||||
|
client := CreateClient()
|
||||||
|
client.Debug()
|
||||||
|
|
||||||
|
ruleList, err := client.GetNodeRule()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(ruleList)
|
||||||
|
}
|
14
api/gov2panel/model.go
Normal file
14
api/gov2panel/model.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package gov2panel
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
SpeedLimit int `json:"speed_limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type route struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Match []string `json:"match"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
ActionValue string `json:"action_value"`
|
||||||
|
}
|
@@ -1 +1,74 @@
|
|||||||
package newV2board
|
package newV2board
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverConfig struct {
|
||||||
|
shadowsocks
|
||||||
|
v2ray
|
||||||
|
trojan
|
||||||
|
|
||||||
|
ServerPort int `json:"server_port"`
|
||||||
|
BaseConfig struct {
|
||||||
|
PushInterval int `json:"push_interval"`
|
||||||
|
PullInterval int `json:"pull_interval"`
|
||||||
|
} `json:"base_config"`
|
||||||
|
Routes []route `json:"routes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type shadowsocks struct {
|
||||||
|
Cipher string `json:"cipher"`
|
||||||
|
Obfs string `json:"obfs"`
|
||||||
|
ObfsSettings struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
} `json:"obfs_settings"`
|
||||||
|
ServerKey string `json:"server_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type v2ray struct {
|
||||||
|
Network string `json:"network"`
|
||||||
|
NetworkSettings struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Headers *json.RawMessage `json:"headers"`
|
||||||
|
ServiceName string `json:"serviceName"`
|
||||||
|
Header *json.RawMessage `json:"header"`
|
||||||
|
} `json:"networkSettings"`
|
||||||
|
VlessNetworkSettings struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Headers *json.RawMessage `json:"headers"`
|
||||||
|
ServiceName string `json:"serviceName"`
|
||||||
|
Header *json.RawMessage `json:"header"`
|
||||||
|
} `json:"network_settings"`
|
||||||
|
VlessFlow string `json:"flow"`
|
||||||
|
VlessTlsSettings struct {
|
||||||
|
ServerPort string `json:"server_port"`
|
||||||
|
Dest string `json:"dest"`
|
||||||
|
xVer uint64 `json:"xver"`
|
||||||
|
Sni string `json:"server_name"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
ShortId string `json:"short_id"`
|
||||||
|
} `json:"tls_settings"`
|
||||||
|
Tls int `json:"tls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type trojan struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type route struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Match []string `json:"match"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
ActionValue string `json:"action_value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
SpeedLimit int `json:"speed_limit"`
|
||||||
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -13,8 +12,12 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/bitly/go-simplejson"
|
"github.com/bitly/go-simplejson"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
@@ -27,12 +30,12 @@ type APIClient struct {
|
|||||||
Key string
|
Key string
|
||||||
NodeType string
|
NodeType string
|
||||||
EnableVless bool
|
EnableVless bool
|
||||||
EnableXTLS bool
|
VlessFlow string
|
||||||
SpeedLimit float64
|
SpeedLimit float64
|
||||||
DeviceLimit int
|
DeviceLimit int
|
||||||
LocalRuleList []api.DetectRule
|
LocalRuleList []api.DetectRule
|
||||||
resp atomic.Value
|
resp atomic.Value
|
||||||
eTag string
|
eTags map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create an api instance
|
// New create an api instance
|
||||||
@@ -52,10 +55,18 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
client.SetBaseURL(apiConfig.APIHost)
|
client.SetBaseURL(apiConfig.APIHost)
|
||||||
|
|
||||||
|
var nodeType string
|
||||||
|
|
||||||
|
if apiConfig.NodeType == "V2ray" && apiConfig.EnableVless {
|
||||||
|
nodeType = "vless"
|
||||||
|
} else {
|
||||||
|
nodeType = strings.ToLower(apiConfig.NodeType)
|
||||||
|
}
|
||||||
// Create Key for each requests
|
// Create Key for each requests
|
||||||
client.SetQueryParams(map[string]string{
|
client.SetQueryParams(map[string]string{
|
||||||
"node_id": strconv.Itoa(apiConfig.NodeID),
|
"node_id": strconv.Itoa(apiConfig.NodeID),
|
||||||
"node_type": strings.ToLower(apiConfig.NodeType),
|
"node_type": nodeType,
|
||||||
"token": apiConfig.Key,
|
"token": apiConfig.Key,
|
||||||
})
|
})
|
||||||
// Read local rule list
|
// Read local rule list
|
||||||
@@ -67,10 +78,11 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
APIHost: apiConfig.APIHost,
|
APIHost: apiConfig.APIHost,
|
||||||
NodeType: apiConfig.NodeType,
|
NodeType: apiConfig.NodeType,
|
||||||
EnableVless: apiConfig.EnableVless,
|
EnableVless: apiConfig.EnableVless,
|
||||||
EnableXTLS: apiConfig.EnableXTLS,
|
VlessFlow: apiConfig.VlessFlow,
|
||||||
SpeedLimit: apiConfig.SpeedLimit,
|
SpeedLimit: apiConfig.SpeedLimit,
|
||||||
DeviceLimit: apiConfig.DeviceLimit,
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
LocalRuleList: localRuleList,
|
LocalRuleList: localRuleList,
|
||||||
|
eTags: make(map[string]string),
|
||||||
}
|
}
|
||||||
return apiClient
|
return apiClient
|
||||||
}
|
}
|
||||||
@@ -82,7 +94,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
if path != "" {
|
if path != "" {
|
||||||
// open the file
|
// open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
defer file.Close()
|
||||||
// handle errors while opening
|
// handle errors while opening
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error when opening file: %s", err)
|
log.Printf("Error when opening file: %s", err)
|
||||||
@@ -103,8 +115,6 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
log.Fatalf("Error while reading file: %s", err)
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalRuleList
|
return LocalRuleList
|
||||||
@@ -126,49 +136,66 @@ func (c *APIClient) assembleURL(path string) string {
|
|||||||
|
|
||||||
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*simplejson.Json, error) {
|
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*simplejson.Json, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
|
return nil, fmt.Errorf("request %s failed: %v", c.assembleURL(path), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode() > 399 {
|
if res.StatusCode() > 399 {
|
||||||
body := res.Body()
|
return nil, fmt.Errorf("request %s failed: %s, %v", c.assembleURL(path), res.String(), err)
|
||||||
return nil, fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rtn, err := simplejson.NewJson(res.Body())
|
rtn, err := simplejson.NewJson(res.Body())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ret %s invalid", res.String())
|
return nil, fmt.Errorf("ret %s invalid", res.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtn, nil
|
return rtn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNodeInfo will pull NodeInfo Config from panel
|
// GetNodeInfo will pull NodeInfo Config from panel
|
||||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
|
server := new(serverConfig)
|
||||||
path := "/api/v1/server/UniProxy/config"
|
path := "/api/v1/server/UniProxy/config"
|
||||||
|
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
|
SetHeader("If-None-Match", c.eTags["node"]).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
response, err := c.parseResponse(res, path, err)
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.NodeNotModified)
|
||||||
|
}
|
||||||
|
// update etag
|
||||||
|
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["node"] {
|
||||||
|
c.eTags["node"] = res.Header().Get("Etag")
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInfoResp, err := c.parseResponse(res, path, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
b, _ := nodeInfoResp.Encode()
|
||||||
|
json.Unmarshal(b, server)
|
||||||
|
|
||||||
c.resp.Store(response)
|
if server.ServerPort == 0 {
|
||||||
|
return nil, errors.New("server port must > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.resp.Store(server)
|
||||||
|
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
nodeInfo, err = c.parseV2rayNodeResponse(response)
|
nodeInfo, err = c.parseV2rayNodeResponse(server)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
nodeInfo, err = c.parseTrojanNodeResponse(response)
|
nodeInfo, err = c.parseTrojanNodeResponse(server)
|
||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
nodeInfo, err = c.parseSSNodeResponse(response)
|
nodeInfo, err = c.parseSSNodeResponse(server)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, _ := response.MarshalJSON()
|
return nil, fmt.Errorf("parse node info failed: %s, \nError: %v", res.String(), err)
|
||||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeInfo, nil
|
return nodeInfo, nil
|
||||||
@@ -176,48 +203,52 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
|
|
||||||
// GetUserList will pull user form panel
|
// GetUserList will pull user form panel
|
||||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
|
var users []*user
|
||||||
path := "/api/v1/server/UniProxy/user"
|
path := "/api/v1/server/UniProxy/user"
|
||||||
|
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray", "Trojan", "Shadowsocks":
|
case "V2ray", "Trojan", "Shadowsocks", "Vmess", "Vless":
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
SetHeader("If-None-Match", c.eTag).
|
SetHeader("If-None-Match", c.eTags["users"]).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
if res.StatusCode() == 304 {
|
if res.StatusCode() == 304 {
|
||||||
return nil, errors.New("users no change")
|
return nil, errors.New(api.UserNotModified)
|
||||||
}
|
}
|
||||||
// update etag
|
// update etag
|
||||||
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTag {
|
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["users"] {
|
||||||
c.eTag = res.Header().Get("Etag")
|
c.eTags["users"] = res.Header().Get("Etag")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := c.parseResponse(res, path, err)
|
usersResp, err := c.parseResponse(res, path, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
b, _ := usersResp.Get("users").Encode()
|
||||||
|
json.Unmarshal(b, &users)
|
||||||
|
if len(users) == 0 {
|
||||||
|
return nil, errors.New("users is null")
|
||||||
|
}
|
||||||
|
|
||||||
numOfUsers := len(response.Get("users").MustArray())
|
userList := make([]api.UserInfo, len(users))
|
||||||
userList := make([]api.UserInfo, numOfUsers)
|
for i := 0; i < len(users); i++ {
|
||||||
for i := 0; i < numOfUsers; i++ {
|
|
||||||
user := response.Get("users").GetIndex(i)
|
|
||||||
u := api.UserInfo{
|
u := api.UserInfo{
|
||||||
UID: user.Get("id").MustInt(),
|
UID: users[i].Id,
|
||||||
UUID: user.Get("uuid").MustString(),
|
UUID: users[i].Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support 1.7.1 speed limit
|
// Support 1.7.1 speed limit
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
|
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
|
||||||
} else {
|
} else {
|
||||||
u.SpeedLimit = uint64(user.Get("speed_limit").MustUint64() * 1000000 / 8)
|
u.SpeedLimit = uint64(users[i].SpeedLimit * 1000000 / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
|
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
|
||||||
@@ -241,10 +272,7 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|||||||
data[traffic.UID] = []int64{traffic.Upload, traffic.Download}
|
data[traffic.UID] = []int64{traffic.Upload, traffic.Download}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().SetBody(data).ForceContentType("application/json").Post(path)
|
||||||
SetBody(data).
|
|
||||||
ForceContentType("application/json").
|
|
||||||
Post(path)
|
|
||||||
_, err = c.parseResponse(res, path, err)
|
_, err = c.parseResponse(res, path, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -255,17 +283,16 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|||||||
|
|
||||||
// GetNodeRule implements the API interface
|
// GetNodeRule implements the API interface
|
||||||
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||||
|
routes := c.resp.Load().(*serverConfig).Routes
|
||||||
|
|
||||||
ruleList := c.LocalRuleList
|
ruleList := c.LocalRuleList
|
||||||
|
|
||||||
nodeInfoResponse := c.resp.Load().(*simplejson.Json)
|
for i := range routes {
|
||||||
for i, rule := range nodeInfoResponse.Get("routes").MustArray() {
|
if routes[i].Action == "block" {
|
||||||
r := rule.(map[string]any)
|
ruleList = append(ruleList, api.DetectRule{
|
||||||
if r["action"] == "block" {
|
|
||||||
ruleListItem := api.DetectRule{
|
|
||||||
ID: i,
|
ID: i,
|
||||||
Pattern: regexp.MustCompile(strings.TrimPrefix(r["match"].(string), "regexp:")),
|
Pattern: regexp.MustCompile(strings.Join(routes[i].Match, "|")),
|
||||||
}
|
})
|
||||||
ruleList = append(ruleList, ruleListItem)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,90 +315,159 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseTrojanNodeResponse parse the response for the given nodeInfo format
|
// parseTrojanNodeResponse parse the response for the given nodeInfo format
|
||||||
func (c *APIClient) parseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
func (c *APIClient) parseTrojanNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||||
var TLSType = "tls"
|
|
||||||
if c.EnableXTLS {
|
|
||||||
TLSType = "xtls"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeInfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
Port: uint32(s.ServerPort),
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
EnableTLS: true,
|
EnableTLS: true,
|
||||||
TLSType: TLSType,
|
Host: s.Host,
|
||||||
Host: nodeInfoResponse.Get("host").MustString(),
|
ServiceName: s.ServerName,
|
||||||
ServiceName: nodeInfoResponse.Get("server_name").MustString(),
|
NameServerConfig: s.parseDNSConfig(),
|
||||||
}
|
}
|
||||||
return nodeInfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSSNodeResponse parse the response for the given nodeInfo format
|
// parseSSNodeResponse parse the response for the given nodeInfo format
|
||||||
func (c *APIClient) parseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
func (c *APIClient) parseSSNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||||
|
var header json.RawMessage
|
||||||
|
|
||||||
|
if s.Obfs == "http" {
|
||||||
|
path := "/"
|
||||||
|
if p := s.ObfsSettings.Path; p != "" {
|
||||||
|
if strings.HasPrefix(p, "/") {
|
||||||
|
path = p
|
||||||
|
} else {
|
||||||
|
path += p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h := simplejson.New()
|
||||||
|
h.Set("type", "http")
|
||||||
|
h.SetPath([]string{"request", "path"}, path)
|
||||||
|
header, _ = h.Encode()
|
||||||
|
}
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
return &api.NodeInfo{
|
return &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
Port: uint32(s.ServerPort),
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
CypherMethod: nodeInfoResponse.Get("cipher").MustString(),
|
CypherMethod: s.Cipher,
|
||||||
ServerKey: nodeInfoResponse.Get("server_key").MustString(), // shadowsocks2022 share key
|
ServerKey: s.ServerKey, // shadowsocks2022 share key
|
||||||
|
NameServerConfig: s.parseDNSConfig(),
|
||||||
|
Header: header,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseV2rayNodeResponse parse the response for the given nodeInfo format
|
// parseV2rayNodeResponse parse the response for the given nodeInfo format
|
||||||
func (c *APIClient) parseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
func (c *APIClient) parseV2rayNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||||
var (
|
var (
|
||||||
TLSType = "tls"
|
host string
|
||||||
path, host, serviceName string
|
header json.RawMessage
|
||||||
header json.RawMessage
|
enableTLS bool
|
||||||
enableTLS bool
|
enableREALITY bool
|
||||||
alterID uint16 = 0
|
dest string
|
||||||
|
xVer uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
if c.EnableXTLS {
|
if s.VlessTlsSettings.Dest != "" {
|
||||||
TLSType = "xtls"
|
dest = s.VlessTlsSettings.Dest
|
||||||
|
} else {
|
||||||
|
dest = s.VlessTlsSettings.Sni
|
||||||
|
}
|
||||||
|
if s.VlessTlsSettings.xVer != 0 {
|
||||||
|
xVer = s.VlessTlsSettings.xVer
|
||||||
|
} else {
|
||||||
|
xVer = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
transportProtocol := nodeInfoResponse.Get("network").MustString()
|
realityConfig := api.REALITYConfig{
|
||||||
|
Dest: dest + ":" + s.VlessTlsSettings.ServerPort,
|
||||||
|
ProxyProtocolVer: xVer,
|
||||||
|
ServerNames: []string{s.VlessTlsSettings.Sni},
|
||||||
|
PrivateKey: s.VlessTlsSettings.PrivateKey,
|
||||||
|
ShortIds: []string{s.VlessTlsSettings.ShortId},
|
||||||
|
}
|
||||||
|
|
||||||
switch transportProtocol {
|
if c.EnableVless {
|
||||||
|
s.NetworkSettings = s.VlessNetworkSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
path = nodeInfoResponse.Get("networkSettings").Get("path").MustString()
|
if s.NetworkSettings.Headers != nil {
|
||||||
host = nodeInfoResponse.Get("networkSettings").Get("headers").Get("Host").MustString()
|
if httpHeader, err := s.NetworkSettings.Headers.MarshalJSON(); err != nil {
|
||||||
case "grpc":
|
return nil, err
|
||||||
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("serviceName"); ok {
|
} else {
|
||||||
serviceName = data.MustString()
|
b, _ := simplejson.NewJson(httpHeader)
|
||||||
|
host = b.Get("Host").MustString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "tcp":
|
case "tcp":
|
||||||
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("headers"); ok {
|
if s.NetworkSettings.Header != nil {
|
||||||
if httpHeader, err := data.MarshalJSON(); err != nil {
|
if httpHeader, err := s.NetworkSettings.Header.MarshalJSON(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
header = httpHeader
|
header = httpHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "httpupgrade", "splithttp":
|
||||||
|
if s.NetworkSettings.Headers != nil {
|
||||||
|
if httpHeaders, err := s.NetworkSettings.Headers.MarshalJSON(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
b, _ := simplejson.NewJson(httpHeaders)
|
||||||
|
host = b.Get("Host").MustString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.NetworkSettings.Host != "" {
|
||||||
|
host = s.NetworkSettings.Host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if nodeInfoResponse.Get("tls").MustInt() == 1 {
|
switch s.Tls {
|
||||||
|
case 0:
|
||||||
|
enableTLS = false
|
||||||
|
enableREALITY = false
|
||||||
|
case 1:
|
||||||
enableTLS = true
|
enableTLS = true
|
||||||
|
enableREALITY = false
|
||||||
|
case 2:
|
||||||
|
enableTLS = true
|
||||||
|
enableREALITY = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
return &api.NodeInfo{
|
return &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
Port: uint32(s.ServerPort),
|
||||||
AlterID: alterID,
|
AlterID: 0,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: s.Network,
|
||||||
EnableTLS: enableTLS,
|
EnableTLS: enableTLS,
|
||||||
TLSType: TLSType,
|
Path: s.NetworkSettings.Path,
|
||||||
Path: path,
|
|
||||||
Host: host,
|
Host: host,
|
||||||
EnableVless: c.EnableVless,
|
EnableVless: c.EnableVless,
|
||||||
ServiceName: serviceName,
|
VlessFlow: s.VlessFlow,
|
||||||
|
ServiceName: s.NetworkSettings.ServiceName,
|
||||||
Header: header,
|
Header: header,
|
||||||
|
EnableREALITY: enableREALITY,
|
||||||
|
REALITYConfig: &realityConfig,
|
||||||
|
NameServerConfig: s.parseDNSConfig(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serverConfig) parseDNSConfig() (nameServerList []*conf.NameServerConfig) {
|
||||||
|
for i := range s.Routes {
|
||||||
|
if s.Routes[i].Action == "dns" {
|
||||||
|
nameServerList = append(nameServerList, &conf.NameServerConfig{
|
||||||
|
Address: &conf.Address{Address: net.ParseAddress(s.Routes[i].ActionValue)},
|
||||||
|
Domains: s.Routes[i].Match,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@@ -4,13 +4,14 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
@@ -24,7 +25,7 @@ type APIClient struct {
|
|||||||
Key string
|
Key string
|
||||||
NodeType string
|
NodeType string
|
||||||
EnableVless bool
|
EnableVless bool
|
||||||
EnableXTLS bool
|
VlessFlow string
|
||||||
SpeedLimit float64
|
SpeedLimit float64
|
||||||
DeviceLimit int
|
DeviceLimit int
|
||||||
LocalRuleList []api.DetectRule
|
LocalRuleList []api.DetectRule
|
||||||
@@ -61,7 +62,7 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
APIHost: apiConfig.APIHost,
|
APIHost: apiConfig.APIHost,
|
||||||
NodeType: apiConfig.NodeType,
|
NodeType: apiConfig.NodeType,
|
||||||
EnableVless: apiConfig.EnableVless,
|
EnableVless: apiConfig.EnableVless,
|
||||||
EnableXTLS: apiConfig.EnableXTLS,
|
VlessFlow: apiConfig.VlessFlow,
|
||||||
SpeedLimit: apiConfig.SpeedLimit,
|
SpeedLimit: apiConfig.SpeedLimit,
|
||||||
DeviceLimit: apiConfig.DeviceLimit,
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
LocalRuleList: localRuleList,
|
LocalRuleList: localRuleList,
|
||||||
@@ -359,8 +360,8 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
|||||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
var enableTLS bool
|
var enableTLS bool
|
||||||
var path, host, TLStype, transportProtocol, serviceName string
|
var path, host, transportProtocol, serviceName string
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
|
|
||||||
port := nodeInfoResponse.Port
|
port := nodeInfoResponse.Port
|
||||||
alterID := nodeInfoResponse.AlterId
|
alterID := nodeInfoResponse.AlterId
|
||||||
@@ -376,34 +377,29 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
}
|
}
|
||||||
// Compatible with more node types config
|
// Compatible with more node types config
|
||||||
switch nodeInfoResponse.Security {
|
switch nodeInfoResponse.Security {
|
||||||
case "tls", "xtls":
|
case "tls":
|
||||||
if c.EnableXTLS {
|
|
||||||
TLStype = "xtls"
|
|
||||||
} else {
|
|
||||||
TLStype = "tls"
|
|
||||||
}
|
|
||||||
enableTLS = true
|
enableTLS = true
|
||||||
default:
|
default:
|
||||||
enableTLS = false
|
enableTLS = false
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeinfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
AlterID: alterID,
|
AlterID: alterID,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
EnableTLS: enableTLS,
|
EnableTLS: enableTLS,
|
||||||
TLSType: TLStype,
|
|
||||||
Path: path,
|
Path: path,
|
||||||
Host: host,
|
Host: host,
|
||||||
EnableVless: c.EnableVless,
|
EnableVless: c.EnableVless,
|
||||||
|
VlessFlow: c.VlessFlow,
|
||||||
ServiceName: serviceName,
|
ServiceName: serviceName,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,38 +408,33 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: nodeInfoResponse.Port,
|
Port: nodeInfoResponse.Port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
CypherMethod: nodeInfoResponse.Method,
|
CypherMethod: nodeInfoResponse.Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
// 域名或IP;port=连接端口#偏移端口|host=xx
|
// 域名或IP;port=连接端口#偏移端口|host=xx
|
||||||
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
||||||
var TLSType, host string
|
var host string
|
||||||
var transportProtocol = "tcp"
|
var transportProtocol = "tcp"
|
||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
if c.EnableXTLS {
|
|
||||||
TLSType = "xtls"
|
|
||||||
} else {
|
|
||||||
TLSType = "tls"
|
|
||||||
}
|
|
||||||
host = nodeInfoResponse.Host
|
host = nodeInfoResponse.Host
|
||||||
port := nodeInfoResponse.Port
|
port := nodeInfoResponse.Port
|
||||||
|
|
||||||
@@ -456,25 +447,24 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
|||||||
transportProtocol = "grpc"
|
transportProtocol = "grpc"
|
||||||
}
|
}
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedlimit,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
EnableTLS: true,
|
EnableTLS: true,
|
||||||
TLSType: TLSType,
|
|
||||||
Host: host,
|
Host: host,
|
||||||
ServiceName: nodeInfoResponse.Sni,
|
ServiceName: nodeInfoResponse.Sni,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseUserListResponse parse the response for the given nodeinfo format
|
// ParseUserListResponse parse the response for the given nodeinfo format
|
||||||
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
|
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
|
||||||
var deviceLimit int = 0
|
var deviceLimit = 0
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
userList := make([]api.UserInfo, len(*userInfoResponse))
|
userList := make([]api.UserInfo, len(*userInfoResponse))
|
||||||
for i, user := range *userInfoResponse {
|
for i, user := range *userInfoResponse {
|
||||||
if c.DeviceLimit > 0 {
|
if c.DeviceLimit > 0 {
|
||||||
@@ -483,15 +473,15 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
|||||||
deviceLimit = user.DeviceLimit
|
deviceLimit = user.DeviceLimit
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
userList[i] = api.UserInfo{
|
userList[i] = api.UserInfo{
|
||||||
UID: user.ID,
|
UID: user.ID,
|
||||||
Passwd: user.Passwd,
|
Passwd: user.Passwd,
|
||||||
UUID: user.Passwd,
|
UUID: user.Passwd,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
DeviceLimit: deviceLimit,
|
DeviceLimit: deviceLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,14 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
@@ -24,7 +25,7 @@ type APIClient struct {
|
|||||||
Key string
|
Key string
|
||||||
NodeType string
|
NodeType string
|
||||||
EnableVless bool
|
EnableVless bool
|
||||||
EnableXTLS bool
|
VlessFlow string
|
||||||
SpeedLimit float64
|
SpeedLimit float64
|
||||||
DeviceLimit int
|
DeviceLimit int
|
||||||
LocalRuleList []api.DetectRule
|
LocalRuleList []api.DetectRule
|
||||||
@@ -57,7 +58,7 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
APIHost: apiConfig.APIHost,
|
APIHost: apiConfig.APIHost,
|
||||||
NodeType: apiConfig.NodeType,
|
NodeType: apiConfig.NodeType,
|
||||||
EnableVless: apiConfig.EnableVless,
|
EnableVless: apiConfig.EnableVless,
|
||||||
EnableXTLS: apiConfig.EnableXTLS,
|
VlessFlow: apiConfig.VlessFlow,
|
||||||
SpeedLimit: apiConfig.SpeedLimit,
|
SpeedLimit: apiConfig.SpeedLimit,
|
||||||
DeviceLimit: apiConfig.DeviceLimit,
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
LocalRuleList: localRuleList,
|
LocalRuleList: localRuleList,
|
||||||
@@ -144,7 +145,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
var path string
|
var path string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
path = fmt.Sprintf("/api/v2ray/v1/node/%d", c.NodeID)
|
path = fmt.Sprintf("/api/v2ray/v1/node/%d", c.NodeID)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
path = fmt.Sprintf("/api/trojan/v1/node/%d", c.NodeID)
|
path = fmt.Sprintf("/api/trojan/v1/node/%d", c.NodeID)
|
||||||
@@ -165,7 +166,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
nodeInfo, err = c.ParseV2rayNodeResponse(&response.Data)
|
nodeInfo, err = c.ParseV2rayNodeResponse(&response.Data)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
nodeInfo, err = c.ParseTrojanNodeResponse(&response.Data)
|
nodeInfo, err = c.ParseTrojanNodeResponse(&response.Data)
|
||||||
@@ -177,7 +178,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, _ := json.Marshal(response.Data)
|
res, _ := json.Marshal(response.Data)
|
||||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
|
return nil, fmt.Errorf("parse node info failed: %s, \nError: %s", string(res), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeInfo, nil
|
return nodeInfo, nil
|
||||||
@@ -187,7 +188,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
var path string
|
var path string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
path = fmt.Sprintf("/api/v2ray/v1/userList/%d", c.NodeID)
|
path = fmt.Sprintf("/api/v2ray/v1/userList/%d", c.NodeID)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
path = fmt.Sprintf("/api/trojan/v1/userList/%d", c.NodeID)
|
path = fmt.Sprintf("/api/trojan/v1/userList/%d", c.NodeID)
|
||||||
@@ -208,7 +209,7 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
}
|
}
|
||||||
userList := new([]api.UserInfo)
|
userList := new([]api.UserInfo)
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
userList, err = c.ParseV2rayUserListResponse(&response.Data)
|
userList, err = c.ParseV2rayUserListResponse(&response.Data)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
userList, err = c.ParseTrojanUserListResponse(&response.Data)
|
userList, err = c.ParseTrojanUserListResponse(&response.Data)
|
||||||
@@ -228,7 +229,7 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||||
var path string
|
var path string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
path = fmt.Sprintf("/api/v2ray/v1/nodeStatus/%d", c.NodeID)
|
path = fmt.Sprintf("/api/v2ray/v1/nodeStatus/%d", c.NodeID)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
path = fmt.Sprintf("/api/trojan/v1/nodeStatus/%d", c.NodeID)
|
path = fmt.Sprintf("/api/trojan/v1/nodeStatus/%d", c.NodeID)
|
||||||
@@ -264,7 +265,7 @@ func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) erro
|
|||||||
|
|
||||||
var path string
|
var path string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
path = fmt.Sprintf("/api/v2ray/v1/nodeOnline/%d", c.NodeID)
|
path = fmt.Sprintf("/api/v2ray/v1/nodeOnline/%d", c.NodeID)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
path = fmt.Sprintf("/api/trojan/v1/nodeOnline/%d", c.NodeID)
|
path = fmt.Sprintf("/api/trojan/v1/nodeOnline/%d", c.NodeID)
|
||||||
@@ -297,7 +298,7 @@ func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) erro
|
|||||||
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||||
var path string
|
var path string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
path = fmt.Sprintf("/api/v2ray/v1/userTraffic/%d", c.NodeID)
|
path = fmt.Sprintf("/api/v2ray/v1/userTraffic/%d", c.NodeID)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
path = fmt.Sprintf("/api/trojan/v1/userTraffic/%d", c.NodeID)
|
path = fmt.Sprintf("/api/trojan/v1/userTraffic/%d", c.NodeID)
|
||||||
@@ -332,7 +333,7 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|||||||
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||||
var path string
|
var path string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
path = fmt.Sprintf("/api/v2ray/v1/nodeRule/%d", c.NodeID)
|
path = fmt.Sprintf("/api/v2ray/v1/nodeRule/%d", c.NodeID)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
path = fmt.Sprintf("/api/trojan/v1/nodeRule/%d", c.NodeID)
|
path = fmt.Sprintf("/api/trojan/v1/nodeRule/%d", c.NodeID)
|
||||||
@@ -380,7 +381,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|||||||
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||||
var path string
|
var path string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
path = fmt.Sprintf("/api/v2ray/v1/trigger/%d", c.NodeID)
|
path = fmt.Sprintf("/api/v2ray/v1/trigger/%d", c.NodeID)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
path = fmt.Sprintf("/api/trojan/v1/trigger/%d", c.NodeID)
|
path = fmt.Sprintf("/api/trojan/v1/trigger/%d", c.NodeID)
|
||||||
@@ -412,13 +413,7 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
|||||||
|
|
||||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||||
var TLStype string
|
var speedLimit uint64 = 0
|
||||||
var speedlimit uint64 = 0
|
|
||||||
if c.EnableXTLS {
|
|
||||||
TLStype = "xtls"
|
|
||||||
} else {
|
|
||||||
TLStype = "tls"
|
|
||||||
}
|
|
||||||
|
|
||||||
v2rayNodeInfo := new(V2rayNodeInfo)
|
v2rayNodeInfo := new(V2rayNodeInfo)
|
||||||
if err := json.Unmarshal(*nodeInfoResponse, v2rayNodeInfo); err != nil {
|
if err := json.Unmarshal(*nodeInfoResponse, v2rayNodeInfo); err != nil {
|
||||||
@@ -426,9 +421,9 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((v2rayNodeInfo.SpeedLimit * 1000000) / 8)
|
speedLimit = (v2rayNodeInfo.SpeedLimit * 1000000) / 8
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.DeviceLimit == 0 && v2rayNodeInfo.ClientLimit > 0 {
|
if c.DeviceLimit == 0 && v2rayNodeInfo.ClientLimit > 0 {
|
||||||
@@ -436,72 +431,65 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: v2rayNodeInfo.V2Port,
|
Port: v2rayNodeInfo.V2Port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
AlterID: v2rayNodeInfo.V2AlterID,
|
AlterID: v2rayNodeInfo.V2AlterID,
|
||||||
TransportProtocol: v2rayNodeInfo.V2Net,
|
TransportProtocol: v2rayNodeInfo.V2Net,
|
||||||
FakeType: v2rayNodeInfo.V2Type,
|
FakeType: v2rayNodeInfo.V2Type,
|
||||||
EnableTLS: v2rayNodeInfo.V2TLS,
|
EnableTLS: v2rayNodeInfo.V2TLS,
|
||||||
TLSType: TLStype,
|
|
||||||
Path: v2rayNodeInfo.V2Path,
|
Path: v2rayNodeInfo.V2Path,
|
||||||
Host: v2rayNodeInfo.V2Host,
|
Host: v2rayNodeInfo.V2Host,
|
||||||
EnableVless: c.EnableVless,
|
EnableVless: c.EnableVless,
|
||||||
|
VlessFlow: c.VlessFlow,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
shadowsocksNodeInfo := new(ShadowsocksNodeInfo)
|
shadowsocksNodeInfo := new(ShadowsocksNodeInfo)
|
||||||
if err := json.Unmarshal(*nodeInfoResponse, shadowsocksNodeInfo); err != nil {
|
if err := json.Unmarshal(*nodeInfoResponse, shadowsocksNodeInfo); err != nil {
|
||||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((shadowsocksNodeInfo.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((shadowsocksNodeInfo.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.DeviceLimit == 0 && shadowsocksNodeInfo.ClientLimit > 0 {
|
if c.DeviceLimit == 0 && shadowsocksNodeInfo.ClientLimit > 0 {
|
||||||
c.DeviceLimit = shadowsocksNodeInfo.ClientLimit
|
c.DeviceLimit = shadowsocksNodeInfo.ClientLimit
|
||||||
}
|
}
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: shadowsocksNodeInfo.Port,
|
Port: shadowsocksNodeInfo.Port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
CypherMethod: shadowsocksNodeInfo.Method,
|
CypherMethod: shadowsocksNodeInfo.Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||||
|
var speedLimit uint64 = 0
|
||||||
var TLSType string
|
|
||||||
var speedlimit uint64 = 0
|
|
||||||
if c.EnableXTLS {
|
|
||||||
TLSType = "xtls"
|
|
||||||
} else {
|
|
||||||
TLSType = "tls"
|
|
||||||
}
|
|
||||||
|
|
||||||
trojanNodeInfo := new(TrojanNodeInfo)
|
trojanNodeInfo := new(TrojanNodeInfo)
|
||||||
if err := json.Unmarshal(*nodeInfoResponse, trojanNodeInfo); err != nil {
|
if err := json.Unmarshal(*nodeInfoResponse, trojanNodeInfo); err != nil {
|
||||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((trojanNodeInfo.SpeedLimit * 1000000) / 8)
|
speedLimit = (trojanNodeInfo.SpeedLimit * 1000000) / 8
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.DeviceLimit == 0 && trojanNodeInfo.ClientLimit > 0 {
|
if c.DeviceLimit == 0 && trojanNodeInfo.ClientLimit > 0 {
|
||||||
@@ -509,22 +497,21 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: trojanNodeInfo.TrojanPort,
|
Port: trojanNodeInfo.TrojanPort,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
EnableTLS: true,
|
EnableTLS: true,
|
||||||
TLSType: TLSType,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseV2rayUserListResponse parse the response for the given userinfo format
|
// ParseV2rayUserListResponse parse the response for the given userinfo format
|
||||||
func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
|
|
||||||
vmessUserList := new([]*VMessUser)
|
vmessUserList := new([]*VMessUser)
|
||||||
if err := json.Unmarshal(*userInfoResponse, vmessUserList); err != nil {
|
if err := json.Unmarshal(*userInfoResponse, vmessUserList); err != nil {
|
||||||
@@ -534,16 +521,16 @@ func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage
|
|||||||
userList := make([]api.UserInfo, len(*vmessUserList))
|
userList := make([]api.UserInfo, len(*vmessUserList))
|
||||||
for i, user := range *vmessUserList {
|
for i, user := range *vmessUserList {
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
speedLimit = (user.SpeedLimit * 1000000) / 8
|
||||||
}
|
}
|
||||||
userList[i] = api.UserInfo{
|
userList[i] = api.UserInfo{
|
||||||
UID: user.UID,
|
UID: user.UID,
|
||||||
Email: "",
|
Email: "",
|
||||||
UUID: user.VmessUID,
|
UUID: user.VmessUID,
|
||||||
DeviceLimit: c.DeviceLimit,
|
DeviceLimit: c.DeviceLimit,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +539,7 @@ func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage
|
|||||||
|
|
||||||
// ParseTrojanUserListResponse parse the response for the given userinfo format
|
// ParseTrojanUserListResponse parse the response for the given userinfo format
|
||||||
func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
|
|
||||||
trojanUserList := new([]*TrojanUser)
|
trojanUserList := new([]*TrojanUser)
|
||||||
if err := json.Unmarshal(*userInfoResponse, trojanUserList); err != nil {
|
if err := json.Unmarshal(*userInfoResponse, trojanUserList); err != nil {
|
||||||
@@ -562,16 +549,16 @@ func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessag
|
|||||||
userList := make([]api.UserInfo, len(*trojanUserList))
|
userList := make([]api.UserInfo, len(*trojanUserList))
|
||||||
for i, user := range *trojanUserList {
|
for i, user := range *trojanUserList {
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = (user.SpeedLimit * 1000000) / 8
|
speedLimit = (user.SpeedLimit * 1000000) / 8
|
||||||
}
|
}
|
||||||
userList[i] = api.UserInfo{
|
userList[i] = api.UserInfo{
|
||||||
UID: user.UID,
|
UID: user.UID,
|
||||||
Email: "",
|
Email: "",
|
||||||
UUID: user.Password,
|
UUID: user.Password,
|
||||||
DeviceLimit: c.DeviceLimit,
|
DeviceLimit: c.DeviceLimit,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,7 +567,7 @@ func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessag
|
|||||||
|
|
||||||
// ParseSSUserListResponse parse the response for the given userinfo format
|
// ParseSSUserListResponse parse the response for the given userinfo format
|
||||||
func (c *APIClient) ParseSSUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
func (c *APIClient) ParseSSUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
|
|
||||||
ssUserList := new([]*SSUser)
|
ssUserList := new([]*SSUser)
|
||||||
if err := json.Unmarshal(*userInfoResponse, ssUserList); err != nil {
|
if err := json.Unmarshal(*userInfoResponse, ssUserList); err != nil {
|
||||||
@@ -590,16 +577,16 @@ func (c *APIClient) ParseSSUserListResponse(userInfoResponse *json.RawMessage) (
|
|||||||
userList := make([]api.UserInfo, len(*ssUserList))
|
userList := make([]api.UserInfo, len(*ssUserList))
|
||||||
for i, user := range *ssUserList {
|
for i, user := range *ssUserList {
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64(user.SpeedLimit * 1000000 / 8)
|
speedLimit = uint64(user.SpeedLimit * 1000000 / 8)
|
||||||
}
|
}
|
||||||
userList[i] = api.UserInfo{
|
userList[i] = api.UserInfo{
|
||||||
UID: user.UID,
|
UID: user.UID,
|
||||||
Email: "",
|
Email: "",
|
||||||
Passwd: user.Password,
|
Passwd: user.Password,
|
||||||
DeviceLimit: c.DeviceLimit,
|
DeviceLimit: c.DeviceLimit,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -154,8 +154,8 @@ func TestReportIllegal(t *testing.T) {
|
|||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
|
|
||||||
detectResult := []api.DetectResult{
|
detectResult := []api.DetectResult{
|
||||||
{1, 1},
|
{UID: 1, RuleID: 1},
|
||||||
{1, 2},
|
{UID: 1, RuleID: 2},
|
||||||
}
|
}
|
||||||
client.Debug()
|
client.Debug()
|
||||||
err := client.ReportIllegal(&detectResult)
|
err := client.ReportIllegal(&detectResult)
|
||||||
|
@@ -8,7 +8,6 @@ type NodeInfoResponse struct {
|
|||||||
Class int `json:"node_class"`
|
Class int `json:"node_class"`
|
||||||
SpeedLimit float64 `json:"node_speedlimit"`
|
SpeedLimit float64 `json:"node_speedlimit"`
|
||||||
TrafficRate float64 `json:"traffic_rate"`
|
TrafficRate float64 `json:"traffic_rate"`
|
||||||
MuOnly int `json:"mu_only"`
|
|
||||||
Sort int `json:"sort"`
|
Sort int `json:"sort"`
|
||||||
RawServerString string `json:"server"`
|
RawServerString string `json:"server"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -17,51 +16,35 @@ type NodeInfoResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CustomConfig struct {
|
type CustomConfig struct {
|
||||||
OffsetPortUser string `json:"offset_port_user"`
|
|
||||||
OffsetPortNode string `json:"offset_port_node"`
|
OffsetPortNode string `json:"offset_port_node"`
|
||||||
ServerSub string `json:"server_sub"`
|
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
MuPort string `json:"mu_port"`
|
Method string `json:"method"`
|
||||||
MuEncryption string `json:"mu_encryption"`
|
|
||||||
MuProtocol string `json:"mu_protocol"`
|
|
||||||
MuObfs string `json:"mu_obfs"`
|
|
||||||
MuSuffix string `json:"mu_suffix"`
|
|
||||||
V2Port string `json:"v2_port"`
|
|
||||||
TLS string `json:"tls"`
|
TLS string `json:"tls"`
|
||||||
EnableVless string `json:"enable_vless"`
|
EnableVless string `json:"enable_vless"`
|
||||||
AlterID string `json:"alter_id"`
|
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Security string `json:"security"`
|
Security string `json:"security"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
VerifyCert bool `json:"verify_cert"`
|
VerifyCert bool `json:"verify_cert"`
|
||||||
Obfs string `json:"obfs"`
|
Obfs string `json:"obfs"`
|
||||||
Header json.RawMessage `json:"header"`
|
Header json.RawMessage `json:"header"`
|
||||||
TrojanPort string `json:"trojan_port"`
|
|
||||||
AllowInsecure string `json:"allow_insecure"`
|
AllowInsecure string `json:"allow_insecure"`
|
||||||
Grpc string `json:"grpc"`
|
|
||||||
Servicename string `json:"servicename"`
|
Servicename string `json:"servicename"`
|
||||||
EnableXtls string `json:"enable_xtls"`
|
EnableXtls string `json:"enable_xtls"`
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
EnableREALITY bool `json:"enable_reality"`
|
||||||
|
RealityOpts *REALITYConfig `json:"reality-opts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserResponse is the response of user
|
// UserResponse is the response of user
|
||||||
type UserResponse struct {
|
type UserResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Email string `json:"email"`
|
Passwd string `json:"passwd"`
|
||||||
Passwd string `json:"passwd"`
|
Port uint32 `json:"port"`
|
||||||
Port uint32 `json:"port"`
|
Method string `json:"method"`
|
||||||
Method string `json:"method"`
|
SpeedLimit float64 `json:"node_speedlimit"`
|
||||||
SpeedLimit float64 `json:"node_speedlimit"`
|
DeviceLimit int `json:"node_iplimit"`
|
||||||
DeviceLimit int `json:"node_connector"`
|
UUID string `json:"uuid"`
|
||||||
Protocol string `json:"protocol"`
|
AliveIP int `json:"alive_ip"`
|
||||||
ProtocolParam string `json:"protocol_param"`
|
|
||||||
Obfs string `json:"obfs"`
|
|
||||||
ObfsParam string `json:"obfs_param"`
|
|
||||||
ForbiddenIP string `json:"forbidden_ip"`
|
|
||||||
ForbiddenPort string `json:"forbidden_port"`
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
MultiUser int `json:"is_multi_user"`
|
|
||||||
AliveIP int `json:"alive_ip"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response is the common response
|
// Response is the common response
|
||||||
@@ -75,7 +58,7 @@ type PostData struct {
|
|||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemLoad is the data structure of systemload
|
// SystemLoad is the data structure of system load
|
||||||
type SystemLoad struct {
|
type SystemLoad struct {
|
||||||
Uptime string `json:"uptime"`
|
Uptime string `json:"uptime"`
|
||||||
Load string `json:"load"`
|
Load string `json:"load"`
|
||||||
@@ -103,3 +86,14 @@ type IllegalItem struct {
|
|||||||
ID int `json:"list_id"`
|
ID int `json:"list_id"`
|
||||||
UID int `json:"user_id"`
|
UID int `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type REALITYConfig struct {
|
||||||
|
Dest string `json:"dest,omitempty"`
|
||||||
|
ProxyProtocolVer uint64 `json:"proxy_protocol_ver,omitempty"`
|
||||||
|
ServerNames []string `json:"server_names,omitempty"`
|
||||||
|
PrivateKey string `json:"private_key,omitempty"`
|
||||||
|
MinClientVer string `json:"min_client_ver,omitempty"`
|
||||||
|
MaxClientVer string `json:"max_client_ver,omitempty"`
|
||||||
|
MaxTimeDiff uint64 `json:"max_time_diff,omitempty"`
|
||||||
|
ShortIds []string `json:"short_ids,omitempty"`
|
||||||
|
}
|
||||||
|
@@ -3,8 +3,8 @@ package sspanel
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
@@ -32,19 +34,21 @@ type APIClient struct {
|
|||||||
Key string
|
Key string
|
||||||
NodeType string
|
NodeType string
|
||||||
EnableVless bool
|
EnableVless bool
|
||||||
EnableXTLS bool
|
VlessFlow string
|
||||||
SpeedLimit float64
|
SpeedLimit float64
|
||||||
DeviceLimit int
|
DeviceLimit int
|
||||||
DisableCustomConfig bool
|
DisableCustomConfig bool
|
||||||
LocalRuleList []api.DetectRule
|
LocalRuleList []api.DetectRule
|
||||||
LastReportOnline map[int]int
|
LastReportOnline map[int]int
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
|
version string
|
||||||
|
eTags map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creat a api instance
|
// New create api instance
|
||||||
func New(apiConfig *api.Config) *APIClient {
|
func New(apiConfig *api.Config) *APIClient {
|
||||||
|
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
|
|
||||||
client.SetRetryCount(3)
|
client.SetRetryCount(3)
|
||||||
if apiConfig.Timeout > 0 {
|
if apiConfig.Timeout > 0 {
|
||||||
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
|
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
|
||||||
@@ -52,12 +56,14 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
client.SetTimeout(5 * time.Second)
|
client.SetTimeout(5 * time.Second)
|
||||||
}
|
}
|
||||||
client.OnError(func(req *resty.Request, err error) {
|
client.OnError(func(req *resty.Request, err error) {
|
||||||
if v, ok := err.(*resty.ResponseError); ok {
|
var v *resty.ResponseError
|
||||||
|
if errors.As(err, &v) {
|
||||||
// v.Response contains the last response from the server
|
// v.Response contains the last response from the server
|
||||||
// v.Err contains the original error
|
// v.Err contains the original error
|
||||||
log.Print(v.Err)
|
log.Print(v.Err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
client.SetBaseURL(apiConfig.APIHost)
|
client.SetBaseURL(apiConfig.APIHost)
|
||||||
// Create Key for each requests
|
// Create Key for each requests
|
||||||
client.SetQueryParam("key", apiConfig.Key)
|
client.SetQueryParam("key", apiConfig.Key)
|
||||||
@@ -73,23 +79,29 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
APIHost: apiConfig.APIHost,
|
APIHost: apiConfig.APIHost,
|
||||||
NodeType: apiConfig.NodeType,
|
NodeType: apiConfig.NodeType,
|
||||||
EnableVless: apiConfig.EnableVless,
|
EnableVless: apiConfig.EnableVless,
|
||||||
EnableXTLS: apiConfig.EnableXTLS,
|
VlessFlow: apiConfig.VlessFlow,
|
||||||
SpeedLimit: apiConfig.SpeedLimit,
|
SpeedLimit: apiConfig.SpeedLimit,
|
||||||
DeviceLimit: apiConfig.DeviceLimit,
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
LocalRuleList: localRuleList,
|
LocalRuleList: localRuleList,
|
||||||
DisableCustomConfig: apiConfig.DisableCustomConfig,
|
DisableCustomConfig: apiConfig.DisableCustomConfig,
|
||||||
LastReportOnline: make(map[int]int),
|
LastReportOnline: make(map[int]int),
|
||||||
|
eTags: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readLocalRuleList reads the local rule list file
|
// readLocalRuleList reads the local rule list file
|
||||||
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||||
|
|
||||||
LocalRuleList = make([]api.DetectRule, 0)
|
LocalRuleList = make([]api.DetectRule, 0)
|
||||||
if path != "" {
|
if path != "" {
|
||||||
// open the file
|
// open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
|
||||||
|
defer func(file *os.File) {
|
||||||
|
err := file.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error when closing file: %s", err)
|
||||||
|
}
|
||||||
|
}(file)
|
||||||
// handle errors while opening
|
// handle errors while opening
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error when opening file: %s", err)
|
log.Printf("Error when opening file: %s", err)
|
||||||
@@ -110,8 +122,6 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
log.Fatalf("Error while reading file: %s", err)
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalRuleList
|
return LocalRuleList
|
||||||
@@ -138,7 +148,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
|
|
||||||
if res.StatusCode() > 400 {
|
if res.StatusCode() > 400 {
|
||||||
body := res.Body()
|
body := res.Body()
|
||||||
return nil, fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
|
return nil, fmt.Errorf("request %s failed: %s, %v", c.assembleURL(path), string(body), err)
|
||||||
}
|
}
|
||||||
response := res.Result().(*Response)
|
response := res.Result().(*Response)
|
||||||
|
|
||||||
@@ -149,13 +159,22 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNodeInfo will pull NodeInfo Config from sspanel
|
// GetNodeInfo will pull NodeInfo Config from ssPanel
|
||||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
SetResult(&Response{}).
|
SetResult(&Response{}).
|
||||||
|
SetHeader("If-None-Match", c.eTags["node"]).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
Get(path)
|
Get(path)
|
||||||
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.NodeNotModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["node"] {
|
||||||
|
c.eTags["node"] = res.Header().Get("ETag")
|
||||||
|
}
|
||||||
|
|
||||||
response, err := c.parseResponse(res, path, err)
|
response, err := c.parseResponse(res, path, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -168,25 +187,18 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New sspanel API
|
// determine ssPanel version, if disable custom config or version < 2021.11, then use old api
|
||||||
disableCustomConfig := c.DisableCustomConfig
|
c.version = nodeInfoResponse.Version
|
||||||
if nodeInfoResponse.Version != "" && !disableCustomConfig {
|
var isExpired bool
|
||||||
// Check if custom_config is empty
|
if compareVersion(c.version, "2021.11") == -1 {
|
||||||
if configString, err := json.Marshal(nodeInfoResponse.CustomConfig); err != nil || string(configString) == "[]" {
|
isExpired = true
|
||||||
log.Printf("custom_config is empty! take config from address now.")
|
|
||||||
disableCustomConfig = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
disableCustomConfig = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !disableCustomConfig {
|
if c.DisableCustomConfig || isExpired {
|
||||||
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
|
if isExpired {
|
||||||
if err != nil {
|
log.Print("The panel version is expired, it is recommended to update immediately")
|
||||||
res, _ := json.Marshal(nodeInfoResponse)
|
|
||||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://xrayr-project.github.io/XrayR-doc/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray":
|
||||||
nodeInfo, err = c.ParseV2rayNodeResponse(nodeInfoResponse)
|
nodeInfo, err = c.ParseV2rayNodeResponse(nodeInfoResponse)
|
||||||
@@ -199,24 +211,39 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
|
||||||
|
if err != nil {
|
||||||
|
res, _ := json.Marshal(nodeInfoResponse)
|
||||||
|
return nil, fmt.Errorf("parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://xrayr-project.github.io/XrayR-doc/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, _ := json.Marshal(nodeInfoResponse)
|
res, _ := json.Marshal(nodeInfoResponse)
|
||||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
|
return nil, fmt.Errorf("parse node info failed: %s, \nError: %s", string(res), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeInfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserList will pull user form sspanel
|
// GetUserList will pull user form ssPanel
|
||||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
path := "/mod_mu/users"
|
path := "/mod_mu/users"
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||||
|
SetHeader("If-None-Match", c.eTags["users"]).
|
||||||
SetResult(&Response{}).
|
SetResult(&Response{}).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
Get(path)
|
Get(path)
|
||||||
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.UserNotModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["users"] {
|
||||||
|
c.eTags["users"] = res.Header().Get("ETag")
|
||||||
|
}
|
||||||
|
|
||||||
response, err := c.parseResponse(res, path, err)
|
response, err := c.parseResponse(res, path, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -236,25 +263,27 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
return userList, nil
|
return userList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportNodeStatus reports the node status to the sspanel
|
// ReportNodeStatus reports the node status to the ssPanel
|
||||||
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
// Determine whether a status report is in need
|
||||||
systemload := SystemLoad{
|
if compareVersion(c.version, "2023.2") == -1 {
|
||||||
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
|
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||||
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
|
systemLoad := SystemLoad{
|
||||||
|
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
|
||||||
|
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetBody(systemLoad).
|
||||||
|
SetResult(&Response{}).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(path)
|
||||||
|
|
||||||
|
_, err = c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.client.R().
|
|
||||||
SetBody(systemload).
|
|
||||||
SetResult(&Response{}).
|
|
||||||
ForceContentType("application/json").
|
|
||||||
Post(path)
|
|
||||||
|
|
||||||
_, err = c.parseResponse(res, path, err)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,16 +296,12 @@ func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) erro
|
|||||||
data := make([]OnlineUser, len(*onlineUserList))
|
data := make([]OnlineUser, len(*onlineUserList))
|
||||||
for i, user := range *onlineUserList {
|
for i, user := range *onlineUserList {
|
||||||
data[i] = OnlineUser{UID: user.UID, IP: user.IP}
|
data[i] = OnlineUser{UID: user.UID, IP: user.IP}
|
||||||
if _, ok := reportOnline[user.UID]; ok {
|
reportOnline[user.UID]++ // will start from 1 if key doesn’t exist
|
||||||
reportOnline[user.UID]++
|
|
||||||
} else {
|
|
||||||
reportOnline[user.UID] = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
c.LastReportOnline = reportOnline // Update LastReportOnline
|
c.LastReportOnline = reportOnline // Update LastReportOnline
|
||||||
|
|
||||||
postData := &PostData{Data: data}
|
postData := &PostData{Data: data}
|
||||||
path := fmt.Sprintf("/mod_mu/users/aliveip")
|
path := "/mod_mu/users/aliveip"
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||||
SetBody(postData).
|
SetBody(postData).
|
||||||
@@ -318,15 +343,25 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNodeRule will pull the audit rule form sspanel
|
// GetNodeRule will pull the audit rule form ssPanel
|
||||||
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||||
ruleList := c.LocalRuleList
|
ruleList := c.LocalRuleList
|
||||||
path := "/mod_mu/func/detect_rules"
|
path := "/mod_mu/func/detect_rules"
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
SetResult(&Response{}).
|
SetResult(&Response{}).
|
||||||
|
SetHeader("If-None-Match", c.eTags["rules"]).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
Get(path)
|
Get(path)
|
||||||
|
|
||||||
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.RuleNotModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["rules"] {
|
||||||
|
c.eTags["rules"] = res.Header().Get("ETag")
|
||||||
|
}
|
||||||
|
|
||||||
response, err := c.parseResponse(res, path, err)
|
response, err := c.parseResponse(res, path, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -372,12 +407,12 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
// ParseV2rayNodeResponse parse the response for the given node info format
|
||||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
var enableTLS bool
|
var enableTLS bool
|
||||||
var path, host, TLStype, transportProtocol, serviceName, HeaderType string
|
var path, host, transportProtocol, serviceName, HeaderType string
|
||||||
var header json.RawMessage
|
var header json.RawMessage
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
if nodeInfoResponse.RawServerString == "" {
|
if nodeInfoResponse.RawServerString == "" {
|
||||||
return nil, fmt.Errorf("no server info in response")
|
return nil, fmt.Errorf("no server info in response")
|
||||||
}
|
}
|
||||||
@@ -399,12 +434,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
// Compatible with more node types config
|
// Compatible with more node types config
|
||||||
for _, value := range serverConf[3:5] {
|
for _, value := range serverConf[3:5] {
|
||||||
switch value {
|
switch value {
|
||||||
case "tls", "xtls":
|
case "tls":
|
||||||
if c.EnableXTLS {
|
|
||||||
TLStype = "xtls"
|
|
||||||
} else {
|
|
||||||
TLStype = "tls"
|
|
||||||
}
|
|
||||||
enableTLS = true
|
enableTLS = true
|
||||||
default:
|
default:
|
||||||
if value != "" {
|
if value != "" {
|
||||||
@@ -429,14 +459,14 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
host = value
|
host = value
|
||||||
case "servicename":
|
case "servicename":
|
||||||
serviceName = value
|
serviceName = value
|
||||||
case "headertype":
|
case "headerType":
|
||||||
HeaderType = value
|
HeaderType = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
if HeaderType != "" {
|
if HeaderType != "" {
|
||||||
@@ -445,33 +475,33 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal Header Type %s into config fialed: %s", header, err)
|
return nil, fmt.Errorf("marshal Header Type %s into config failed: %s", header, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
AlterID: alterID,
|
AlterID: alterID,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
EnableTLS: enableTLS,
|
EnableTLS: enableTLS,
|
||||||
TLSType: TLStype,
|
|
||||||
Path: path,
|
Path: path,
|
||||||
Host: host,
|
Host: host,
|
||||||
EnableVless: c.EnableVless,
|
EnableVless: c.EnableVless,
|
||||||
|
VlessFlow: c.VlessFlow,
|
||||||
ServiceName: serviceName,
|
ServiceName: serviceName,
|
||||||
Header: header,
|
Header: header,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
// ParseSSNodeResponse parse the response for the given node info format
|
||||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
var port uint32 = 0
|
var port uint32 = 0
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
var method string
|
var method string
|
||||||
path := "/mod_mu/users"
|
path := "/mod_mu/users"
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
@@ -490,41 +520,35 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
|
|||||||
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
|
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
|
||||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||||
}
|
}
|
||||||
// Find the multi-user
|
|
||||||
for _, u := range *userListResponse {
|
// init server port
|
||||||
if u.MultiUser > 0 {
|
if len(*userListResponse) != 0 {
|
||||||
port = u.Port
|
port = (*userListResponse)[0].Port
|
||||||
method = u.Method
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if port == 0 || method == "" {
|
|
||||||
return nil, fmt.Errorf("cant find the single port multi user")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
CypherMethod: method,
|
CypherMethod: method,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSSPluginNodeResponse parse the response for the given nodeinfor format
|
// ParseSSPluginNodeResponse parse the response for the given node info format
|
||||||
func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
var enableTLS bool
|
var enableTLS bool
|
||||||
var path, host, TLStype, transportProtocol string
|
var path, host, transportProtocol string
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
|
|
||||||
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
||||||
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
|
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
|
||||||
@@ -539,12 +563,7 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
|
|||||||
// Compatible with more node types config
|
// Compatible with more node types config
|
||||||
for _, value := range serverConf[3:5] {
|
for _, value := range serverConf[3:5] {
|
||||||
switch value {
|
switch value {
|
||||||
case "tls", "xtls":
|
case "tls":
|
||||||
if c.EnableXTLS {
|
|
||||||
TLStype = "xtls"
|
|
||||||
} else {
|
|
||||||
TLStype = "tls"
|
|
||||||
}
|
|
||||||
enableTLS = true
|
enableTLS = true
|
||||||
case "ws":
|
case "ws":
|
||||||
transportProtocol = "ws"
|
transportProtocol = "ws"
|
||||||
@@ -570,38 +589,32 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
EnableTLS: enableTLS,
|
EnableTLS: enableTLS,
|
||||||
TLSType: TLStype,
|
|
||||||
Path: path,
|
Path: path,
|
||||||
Host: host,
|
Host: host,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
// ParseTrojanNodeResponse parse the response for the given node info format
|
||||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
// 域名或IP;port=连接端口#偏移端口|host=xx
|
// 域名或IP;port=连接端口#偏移端口|host=xx
|
||||||
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
||||||
var p, TLSType, host, outsidePort, insidePort, transportProtocol, serviceName string
|
var p, host, outsidePort, insidePort, transportProtocol, serviceName string
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
if c.EnableXTLS {
|
|
||||||
TLSType = "xtls"
|
|
||||||
} else {
|
|
||||||
TLSType = "tls"
|
|
||||||
}
|
|
||||||
|
|
||||||
if nodeInfoResponse.RawServerString == "" {
|
if nodeInfoResponse.RawServerString == "" {
|
||||||
return nil, fmt.Errorf("no server info in response")
|
return nil, fmt.Errorf("no server info in response")
|
||||||
@@ -648,27 +661,26 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
EnableTLS: true,
|
EnableTLS: true,
|
||||||
TLSType: TLSType,
|
|
||||||
Host: host,
|
Host: host,
|
||||||
ServiceName: serviceName,
|
ServiceName: serviceName,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseUserListResponse parse the response for the given nodeinfo format
|
// ParseUserListResponse parse the response for the given node info format
|
||||||
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
|
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
// Clear Last report log
|
// Clear Last report log
|
||||||
@@ -677,8 +689,8 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
|||||||
c.access.Unlock()
|
c.access.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var deviceLimit, localDeviceLimit int = 0, 0
|
var deviceLimit, localDeviceLimit = 0, 0
|
||||||
var speedlimit uint64 = 0
|
var speedLimit uint64 = 0
|
||||||
var userList []api.UserInfo
|
var userList []api.UserInfo
|
||||||
for _, user := range *userInfoResponse {
|
for _, user := range *userInfoResponse {
|
||||||
if c.DeviceLimit > 0 {
|
if c.DeviceLimit > 0 {
|
||||||
@@ -706,111 +718,142 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
userList = append(userList, api.UserInfo{
|
userList = append(userList, api.UserInfo{
|
||||||
UID: user.ID,
|
UID: user.ID,
|
||||||
Email: user.Email,
|
UUID: user.UUID,
|
||||||
UUID: user.UUID,
|
Passwd: user.Passwd,
|
||||||
Passwd: user.Passwd,
|
SpeedLimit: speedLimit,
|
||||||
SpeedLimit: speedlimit,
|
DeviceLimit: deviceLimit,
|
||||||
DeviceLimit: deviceLimit,
|
Port: user.Port,
|
||||||
Port: user.Port,
|
Method: user.Method,
|
||||||
Method: user.Method,
|
|
||||||
Protocol: user.Protocol,
|
|
||||||
ProtocolParam: user.ProtocolParam,
|
|
||||||
Obfs: user.Obfs,
|
|
||||||
ObfsParam: user.ObfsParam,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &userList, nil
|
return &userList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSSPanelNodeInfo parse the response for the given nodeinfor format
|
// ParseSSPanelNodeInfo parse the response for the given node info format
|
||||||
// Only used for SSPanel version >= 2021.11
|
// Only available for SSPanel version >= 2021.11
|
||||||
func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
|
var (
|
||||||
|
speedLimit uint64 = 0
|
||||||
|
enableTLS, enableVless bool
|
||||||
|
alterID uint16 = 0
|
||||||
|
transportProtocol string
|
||||||
|
)
|
||||||
|
|
||||||
var speedlimit uint64 = 0
|
// Check if custom_config is null
|
||||||
var EnableTLS, EnableVless bool
|
if len(nodeInfoResponse.CustomConfig) == 0 {
|
||||||
var AlterID uint16 = 0
|
return nil, errors.New("custom_config is empty, disable custom config")
|
||||||
var TLSType, transportProtocol string
|
}
|
||||||
|
|
||||||
nodeConfig := new(CustomConfig)
|
nodeConfig := new(CustomConfig)
|
||||||
json.Unmarshal(nodeInfoResponse.CustomConfig, nodeConfig)
|
err := json.Unmarshal(nodeInfoResponse.CustomConfig, nodeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("custom_config format error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedPort, err := strconv.ParseInt(nodeConfig.OffsetPortNode, 10, 32)
|
parsedPort, err := strconv.ParseInt(nodeConfig.OffsetPortNode, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
port := uint32(parsedPort)
|
port := uint32(parsedPort)
|
||||||
|
|
||||||
if c.NodeType == "Shadowsocks" {
|
switch c.NodeType {
|
||||||
|
case "Shadowsocks":
|
||||||
transportProtocol = "tcp"
|
transportProtocol = "tcp"
|
||||||
}
|
case "V2ray":
|
||||||
|
|
||||||
if c.NodeType == "V2ray" {
|
|
||||||
transportProtocol = nodeConfig.Network
|
transportProtocol = nodeConfig.Network
|
||||||
TLSType = nodeConfig.Security
|
|
||||||
if parsedAlterID, err := strconv.ParseInt(nodeConfig.AlterID, 10, 16); err != nil {
|
tlsType := nodeConfig.Security
|
||||||
return nil, err
|
if tlsType == "tls" || tlsType == "xtls" {
|
||||||
} else {
|
enableTLS = true
|
||||||
AlterID = uint16(parsedAlterID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if TLSType == "tls" || TLSType == "xtls" {
|
|
||||||
EnableTLS = true
|
|
||||||
}
|
|
||||||
if nodeConfig.EnableVless == "1" {
|
if nodeConfig.EnableVless == "1" {
|
||||||
EnableVless = true
|
enableVless = true
|
||||||
}
|
}
|
||||||
}
|
case "Trojan":
|
||||||
|
enableTLS = true
|
||||||
if c.NodeType == "Trojan" {
|
|
||||||
EnableTLS = true
|
|
||||||
TLSType = "tls"
|
|
||||||
transportProtocol = "tcp"
|
transportProtocol = "tcp"
|
||||||
|
|
||||||
// Select security type
|
|
||||||
if nodeConfig.EnableXtls == "1" {
|
|
||||||
TLSType = "xtls"
|
|
||||||
} else if nodeConfig.Security != "" {
|
|
||||||
TLSType = nodeConfig.Security // try to read security from config
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select transport protocol
|
// Select transport protocol
|
||||||
if nodeConfig.Grpc == "1" {
|
if nodeConfig.Network != "" {
|
||||||
transportProtocol = "grpc"
|
|
||||||
} else if nodeConfig.Network != "" {
|
|
||||||
transportProtocol = nodeConfig.Network // try to read transport protocol from config
|
transportProtocol = nodeConfig.Network // try to read transport protocol from config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse reality config
|
||||||
|
realityConfig := new(api.REALITYConfig)
|
||||||
|
if nodeConfig.RealityOpts != nil {
|
||||||
|
r := nodeConfig.RealityOpts
|
||||||
|
realityConfig = &api.REALITYConfig{
|
||||||
|
Dest: r.Dest,
|
||||||
|
ProxyProtocolVer: r.ProxyProtocolVer,
|
||||||
|
ServerNames: r.ServerNames,
|
||||||
|
PrivateKey: r.PrivateKey,
|
||||||
|
MinClientVer: r.MinClientVer,
|
||||||
|
MaxClientVer: r.MaxClientVer,
|
||||||
|
MaxTimeDiff: r.MaxTimeDiff,
|
||||||
|
ShortIds: r.ShortIds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedLimit,
|
||||||
AlterID: AlterID,
|
AlterID: alterID,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
Host: nodeConfig.Host,
|
Host: nodeConfig.Host,
|
||||||
Path: nodeConfig.Path,
|
Path: nodeConfig.Path,
|
||||||
EnableTLS: EnableTLS,
|
EnableTLS: enableTLS,
|
||||||
TLSType: TLSType,
|
EnableVless: enableVless,
|
||||||
EnableVless: EnableVless,
|
VlessFlow: nodeConfig.Flow,
|
||||||
CypherMethod: nodeConfig.MuEncryption,
|
CypherMethod: nodeConfig.Method,
|
||||||
ServiceName: nodeConfig.Servicename,
|
ServiceName: nodeConfig.Servicename,
|
||||||
Header: nodeConfig.Header,
|
Header: nodeConfig.Header,
|
||||||
|
EnableREALITY: nodeConfig.EnableREALITY,
|
||||||
|
REALITYConfig: realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareVersion, version1 > version2 return 1, version1 < version2 return -1, 0 means equal
|
||||||
|
func compareVersion(version1, version2 string) int {
|
||||||
|
n, m := len(version1), len(version2)
|
||||||
|
i, j := 0, 0
|
||||||
|
for i < n || j < m {
|
||||||
|
x := 0
|
||||||
|
for ; i < n && version1[i] != '.'; i++ {
|
||||||
|
x = x*10 + int(version1[i]-'0')
|
||||||
|
}
|
||||||
|
i++ // jump dot
|
||||||
|
y := 0
|
||||||
|
for ; j < m && version2[j] != '.'; j++ {
|
||||||
|
y = y*10 + int(version2[j]-'0')
|
||||||
|
}
|
||||||
|
j++ // jump dot
|
||||||
|
if x > y {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if x < y {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ func CreateClient() api.API {
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetV2rayNodeinfo(t *testing.T) {
|
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
|
|
||||||
nodeInfo, err := client.GetNodeInfo()
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
@@ -29,7 +29,7 @@ func TestGetV2rayNodeinfo(t *testing.T) {
|
|||||||
t.Log(nodeInfo)
|
t.Log(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSSNodeinfo(t *testing.T) {
|
func TestGetSSNodeInfo(t *testing.T) {
|
||||||
apiConfig := &api.Config{
|
apiConfig := &api.Config{
|
||||||
APIHost: "http://127.0.0.1:667",
|
APIHost: "http://127.0.0.1:667",
|
||||||
Key: "123",
|
Key: "123",
|
||||||
@@ -44,7 +44,7 @@ func TestGetSSNodeinfo(t *testing.T) {
|
|||||||
t.Log(nodeInfo)
|
t.Log(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTrojanNodeinfo(t *testing.T) {
|
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||||
apiConfig := &api.Config{
|
apiConfig := &api.Config{
|
||||||
APIHost: "http://127.0.0.1:667",
|
APIHost: "http://127.0.0.1:667",
|
||||||
Key: "123",
|
Key: "123",
|
||||||
@@ -59,7 +59,7 @@ func TestGetTrojanNodeinfo(t *testing.T) {
|
|||||||
t.Log(nodeInfo)
|
t.Log(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSSinfo(t *testing.T) {
|
func TestGetSSInfo(t *testing.T) {
|
||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
|
|
||||||
nodeInfo, err := client.GetNodeInfo()
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
@@ -148,8 +148,8 @@ func TestReportIllegal(t *testing.T) {
|
|||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
|
|
||||||
detectResult := []api.DetectResult{
|
detectResult := []api.DetectResult{
|
||||||
{1, 2},
|
{UID: 1, RuleID: 2},
|
||||||
{1, 3},
|
{UID: 1, RuleID: 3},
|
||||||
}
|
}
|
||||||
client.Debug()
|
client.Debug()
|
||||||
err := client.ReportIllegal(&detectResult)
|
err := client.ReportIllegal(&detectResult)
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
// Deprecated: after 2023.6.1
|
|
||||||
package v2board
|
|
||||||
|
|
||||||
type UserTraffic struct {
|
|
||||||
UID int `json:"user_id"`
|
|
||||||
Upload int64 `json:"u"`
|
|
||||||
Download int64 `json:"d"`
|
|
||||||
}
|
|
@@ -1,422 +0,0 @@
|
|||||||
package v2board
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bitly/go-simplejson"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// APIClient create an api client to the panel.
|
|
||||||
type APIClient struct {
|
|
||||||
client *resty.Client
|
|
||||||
APIHost string
|
|
||||||
NodeID int
|
|
||||||
Key string
|
|
||||||
NodeType string
|
|
||||||
EnableVless bool
|
|
||||||
EnableXTLS bool
|
|
||||||
SpeedLimit float64
|
|
||||||
DeviceLimit int
|
|
||||||
LocalRuleList []api.DetectRule
|
|
||||||
ConfigResp *simplejson.Json
|
|
||||||
access sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// New create an api instance
|
|
||||||
func New(apiConfig *api.Config) *APIClient {
|
|
||||||
|
|
||||||
client := resty.New()
|
|
||||||
client.SetRetryCount(3)
|
|
||||||
if apiConfig.Timeout > 0 {
|
|
||||||
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
|
|
||||||
} else {
|
|
||||||
client.SetTimeout(5 * time.Second)
|
|
||||||
}
|
|
||||||
client.OnError(func(req *resty.Request, err error) {
|
|
||||||
if v, ok := err.(*resty.ResponseError); ok {
|
|
||||||
// v.Response contains the last response from the server
|
|
||||||
// v.Err contains the original error
|
|
||||||
log.Print(v.Err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
client.SetBaseURL(apiConfig.APIHost)
|
|
||||||
// Create Key for each requests
|
|
||||||
client.SetQueryParams(map[string]string{
|
|
||||||
"node_id": strconv.Itoa(apiConfig.NodeID),
|
|
||||||
"token": apiConfig.Key,
|
|
||||||
})
|
|
||||||
// Read local rule list
|
|
||||||
localRuleList := readLocalRuleList(apiConfig.RuleListPath)
|
|
||||||
apiClient := &APIClient{
|
|
||||||
client: client,
|
|
||||||
NodeID: apiConfig.NodeID,
|
|
||||||
Key: apiConfig.Key,
|
|
||||||
APIHost: apiConfig.APIHost,
|
|
||||||
NodeType: apiConfig.NodeType,
|
|
||||||
EnableVless: apiConfig.EnableVless,
|
|
||||||
EnableXTLS: apiConfig.EnableXTLS,
|
|
||||||
SpeedLimit: apiConfig.SpeedLimit,
|
|
||||||
DeviceLimit: apiConfig.DeviceLimit,
|
|
||||||
LocalRuleList: localRuleList,
|
|
||||||
}
|
|
||||||
return apiClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// readLocalRuleList reads the local rule list file
|
|
||||||
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|
||||||
|
|
||||||
LocalRuleList = make([]api.DetectRule, 0)
|
|
||||||
if path != "" {
|
|
||||||
// open the file
|
|
||||||
file, err := os.Open(path)
|
|
||||||
|
|
||||||
// handle errors while opening
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error when opening file: %s", err)
|
|
||||||
return LocalRuleList
|
|
||||||
}
|
|
||||||
|
|
||||||
fileScanner := bufio.NewScanner(file)
|
|
||||||
|
|
||||||
// read line by line
|
|
||||||
for fileScanner.Scan() {
|
|
||||||
LocalRuleList = append(LocalRuleList, api.DetectRule{
|
|
||||||
ID: -1,
|
|
||||||
Pattern: regexp.MustCompile(fileScanner.Text()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// handle first encountered error while reading
|
|
||||||
if err := fileScanner.Err(); err != nil {
|
|
||||||
log.Fatalf("Error while reading file: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return LocalRuleList
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe return a description of the client
|
|
||||||
func (c *APIClient) Describe() api.ClientInfo {
|
|
||||||
return api.ClientInfo{APIHost: c.APIHost, NodeID: c.NodeID, Key: c.Key, NodeType: c.NodeType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug set the client debug for client
|
|
||||||
func (c *APIClient) Debug() {
|
|
||||||
c.client.SetDebug(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *APIClient) assembleURL(path string) string {
|
|
||||||
return c.APIHost + path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (*simplejson.Json, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.StatusCode() > 400 {
|
|
||||||
body := res.Body()
|
|
||||||
return nil, fmt.Errorf("request %s failed: %s, %s", c.assembleURL(path), string(body), err)
|
|
||||||
}
|
|
||||||
rtn, err := simplejson.NewJson(res.Body())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ret %s invalid", res.String())
|
|
||||||
}
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodeInfo will pull NodeInfo Config from sspanel
|
|
||||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|
||||||
var path string
|
|
||||||
switch c.NodeType {
|
|
||||||
case "V2ray":
|
|
||||||
path = "/api/v1/server/Deepbwork/config"
|
|
||||||
case "Trojan":
|
|
||||||
path = "/api/v1/server/TrojanTidalab/config"
|
|
||||||
case "Shadowsocks":
|
|
||||||
if nodeInfo, err = c.ParseSSNodeResponse(); err == nil {
|
|
||||||
return nodeInfo, nil
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
|
||||||
}
|
|
||||||
res, err := c.client.R().
|
|
||||||
SetQueryParam("local_port", "1").
|
|
||||||
ForceContentType("application/json").
|
|
||||||
Get(path)
|
|
||||||
|
|
||||||
response, err := c.parseResponse(res, path, err)
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
c.ConfigResp = response
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.NodeType {
|
|
||||||
case "V2ray":
|
|
||||||
nodeInfo, err = c.ParseV2rayNodeResponse(response)
|
|
||||||
case "Trojan":
|
|
||||||
nodeInfo, err = c.ParseTrojanNodeResponse(response)
|
|
||||||
case "Shadowsocks":
|
|
||||||
nodeInfo, err = c.ParseSSNodeResponse()
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
res, _ := response.MarshalJSON()
|
|
||||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s", string(res), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserList will pull user form sspanel
|
|
||||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|
||||||
var path string
|
|
||||||
switch c.NodeType {
|
|
||||||
case "V2ray":
|
|
||||||
path = "/api/v1/server/Deepbwork/user"
|
|
||||||
case "Trojan":
|
|
||||||
path = "/api/v1/server/TrojanTidalab/user"
|
|
||||||
case "Shadowsocks":
|
|
||||||
path = "/api/v1/server/ShadowsocksTidalab/user"
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
|
||||||
}
|
|
||||||
res, err := c.client.R().
|
|
||||||
ForceContentType("application/json").
|
|
||||||
Get(path)
|
|
||||||
|
|
||||||
response, err := c.parseResponse(res, path, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
numOfUsers := len(response.Get("data").MustArray())
|
|
||||||
userList := make([]api.UserInfo, numOfUsers)
|
|
||||||
for i := 0; i < numOfUsers; i++ {
|
|
||||||
user := api.UserInfo{}
|
|
||||||
user.UID = response.Get("data").GetIndex(i).Get("id").MustInt()
|
|
||||||
user.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
|
|
||||||
user.DeviceLimit = c.DeviceLimit
|
|
||||||
switch c.NodeType {
|
|
||||||
case "Shadowsocks":
|
|
||||||
user.Email = response.Get("data").GetIndex(i).Get("secret").MustString()
|
|
||||||
user.Passwd = response.Get("data").GetIndex(i).Get("secret").MustString()
|
|
||||||
user.Method = response.Get("data").GetIndex(i).Get("cipher").MustString()
|
|
||||||
user.Port = uint32(response.Get("data").GetIndex(i).Get("port").MustUint64())
|
|
||||||
case "Trojan":
|
|
||||||
user.UUID = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
|
|
||||||
user.Email = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
|
|
||||||
case "V2ray":
|
|
||||||
user.UUID = response.Get("data").GetIndex(i).Get("v2ray_user").Get("uuid").MustString()
|
|
||||||
user.Email = response.Get("data").GetIndex(i).Get("v2ray_user").Get("email").MustString()
|
|
||||||
user.AlterID = uint16(response.Get("data").GetIndex(i).Get("v2ray_user").Get("alter_id").MustUint64())
|
|
||||||
}
|
|
||||||
userList[i] = user
|
|
||||||
}
|
|
||||||
return &userList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReportUserTraffic reports the user traffic
|
|
||||||
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|
||||||
var path string
|
|
||||||
switch c.NodeType {
|
|
||||||
case "V2ray":
|
|
||||||
path = "/api/v1/server/Deepbwork/submit"
|
|
||||||
case "Trojan":
|
|
||||||
path = "/api/v1/server/TrojanTidalab/submit"
|
|
||||||
case "Shadowsocks":
|
|
||||||
path = "/api/v1/server/ShadowsocksTidalab/submit"
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make([]UserTraffic, len(*userTraffic))
|
|
||||||
for i, traffic := range *userTraffic {
|
|
||||||
data[i] = UserTraffic{
|
|
||||||
UID: traffic.UID,
|
|
||||||
Upload: traffic.Upload,
|
|
||||||
Download: traffic.Download}
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := c.client.R().
|
|
||||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
|
||||||
SetBody(data).
|
|
||||||
ForceContentType("application/json").
|
|
||||||
Post(path)
|
|
||||||
_, err = c.parseResponse(res, path, err)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodeRule implements the API interface
|
|
||||||
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|
||||||
ruleList := c.LocalRuleList
|
|
||||||
if c.NodeType != "V2ray" {
|
|
||||||
return &ruleList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// V2board only support the rule for v2ray
|
|
||||||
// fix: reuse config response
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
ruleListResponse := c.ConfigResp.Get("routing").Get("rules").GetIndex(1).Get("domain").MustStringArray()
|
|
||||||
for i, rule := range ruleListResponse {
|
|
||||||
rule = strings.TrimPrefix(rule, "regexp:")
|
|
||||||
ruleListItem := api.DetectRule{
|
|
||||||
ID: i,
|
|
||||||
Pattern: regexp.MustCompile(rule),
|
|
||||||
}
|
|
||||||
ruleList = append(ruleList, ruleListItem)
|
|
||||||
}
|
|
||||||
return &ruleList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReportNodeStatus implements the API interface
|
|
||||||
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReportNodeOnlineUsers implements the API interface
|
|
||||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReportIllegal implements the API interface
|
|
||||||
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
|
||||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
|
||||||
var TLSType = "tls"
|
|
||||||
if c.EnableXTLS {
|
|
||||||
TLSType = "xtls"
|
|
||||||
}
|
|
||||||
port := uint32(nodeInfoResponse.Get("local_port").MustUint64())
|
|
||||||
host := nodeInfoResponse.Get("ssl").Get("sni").MustString()
|
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
|
||||||
nodeinfo := &api.NodeInfo{
|
|
||||||
NodeType: c.NodeType,
|
|
||||||
NodeID: c.NodeID,
|
|
||||||
Port: port,
|
|
||||||
TransportProtocol: "tcp",
|
|
||||||
EnableTLS: true,
|
|
||||||
TLSType: TLSType,
|
|
||||||
Host: host,
|
|
||||||
}
|
|
||||||
return nodeinfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
|
||||||
func (c *APIClient) ParseSSNodeResponse() (*api.NodeInfo, error) {
|
|
||||||
var port uint32
|
|
||||||
var method string
|
|
||||||
userInfo, err := c.GetUserList()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(*userInfo) > 0 {
|
|
||||||
port = (*userInfo)[0].Port
|
|
||||||
method = (*userInfo)[0].Method
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
|
||||||
nodeinfo := &api.NodeInfo{
|
|
||||||
NodeType: c.NodeType,
|
|
||||||
NodeID: c.NodeID,
|
|
||||||
Port: port,
|
|
||||||
TransportProtocol: "tcp",
|
|
||||||
CypherMethod: method,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeinfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
|
||||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
|
||||||
var TLSType string = "tls"
|
|
||||||
var path, host, serviceName string
|
|
||||||
var header json.RawMessage
|
|
||||||
var enableTLS bool
|
|
||||||
var alterID uint16 = 0
|
|
||||||
if c.EnableXTLS {
|
|
||||||
TLSType = "xtls"
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundInfo := simplejson.New()
|
|
||||||
if tmpInboundInfo, ok := nodeInfoResponse.CheckGet("inbound"); ok {
|
|
||||||
inboundInfo = tmpInboundInfo
|
|
||||||
// Compatible with v2board 1.5.5-dev
|
|
||||||
} else if tmpInboundInfo, ok := nodeInfoResponse.CheckGet("inbounds"); ok {
|
|
||||||
tmpInboundInfo := tmpInboundInfo.MustArray()
|
|
||||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
|
||||||
inboundInfo, _ = simplejson.NewJson(marshalByte)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("unable to find inbound(s) in the nodeInfo")
|
|
||||||
}
|
|
||||||
|
|
||||||
port := uint32(inboundInfo.Get("port").MustUint64())
|
|
||||||
transportProtocol := inboundInfo.Get("streamSettings").Get("network").MustString()
|
|
||||||
|
|
||||||
switch transportProtocol {
|
|
||||||
case "ws":
|
|
||||||
path = inboundInfo.Get("streamSettings").Get("wsSettings").Get("path").MustString()
|
|
||||||
host = inboundInfo.Get("streamSettings").Get("wsSettings").Get("headers").Get("Host").MustString()
|
|
||||||
case "grpc":
|
|
||||||
if data, ok := inboundInfo.Get("streamSettings").Get("grpcSettings").CheckGet("serviceName"); ok {
|
|
||||||
serviceName = data.MustString()
|
|
||||||
}
|
|
||||||
case "tcp":
|
|
||||||
if data, ok := inboundInfo.Get("streamSettings").Get("tcpSettings").CheckGet("header"); ok {
|
|
||||||
if httpHeader, err := data.MarshalJSON(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
header = httpHeader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if inboundInfo.Get("streamSettings").Get("security").MustString() == "tls" {
|
|
||||||
enableTLS = true
|
|
||||||
} else {
|
|
||||||
enableTLS = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
|
||||||
// AlterID will be updated after next sync
|
|
||||||
nodeInfo := &api.NodeInfo{
|
|
||||||
NodeType: c.NodeType,
|
|
||||||
NodeID: c.NodeID,
|
|
||||||
Port: port,
|
|
||||||
AlterID: alterID,
|
|
||||||
TransportProtocol: transportProtocol,
|
|
||||||
EnableTLS: enableTLS,
|
|
||||||
TLSType: TLSType,
|
|
||||||
Path: path,
|
|
||||||
Host: host,
|
|
||||||
EnableVless: c.EnableVless,
|
|
||||||
ServiceName: serviceName,
|
|
||||||
Header: header,
|
|
||||||
}
|
|
||||||
return nodeInfo, nil
|
|
||||||
}
|
|
@@ -1,7 +1,35 @@
|
|||||||
package v2raysocks
|
package v2raysocks
|
||||||
|
|
||||||
type UserTraffic struct {
|
type UserTraffic struct {
|
||||||
UID int `json:"user_id"`
|
UID int `json:"uid"`
|
||||||
Upload int64 `json:"u"`
|
Upload int64 `json:"u"`
|
||||||
Download int64 `json:"d"`
|
Download int64 `json:"d"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NodeStatus struct {
|
||||||
|
CPU string `json:"cpu"`
|
||||||
|
Mem string `json:"mem"`
|
||||||
|
Net string `json:"net"`
|
||||||
|
Disk string `json:"disk"`
|
||||||
|
Uptime int `json:"uptime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeOnline struct {
|
||||||
|
UID int `json:"uid"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IllegalItem struct {
|
||||||
|
UID int `json:"uid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type REALITYConfig struct {
|
||||||
|
Dest string `json:"dest,omitempty"`
|
||||||
|
ProxyProtocolVer uint64 `json:"proxy_protocol_ver,omitempty"`
|
||||||
|
ServerNames []string `json:"server_names,omitempty"`
|
||||||
|
PrivateKey string `json:"private_key,omitempty"`
|
||||||
|
MinClientVer string `json:"min_client_ver,omitempty"`
|
||||||
|
MaxClientVer string `json:"max_client_ver,omitempty"`
|
||||||
|
MaxTimeDiff uint64 `json:"max_time_diff,omitempty"`
|
||||||
|
ShortIds []string `json:"short_ids,omitempty"`
|
||||||
|
}
|
@@ -3,8 +3,8 @@ package v2raysocks
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -12,8 +12,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/bitly/go-simplejson"
|
"github.com/bitly/go-simplejson"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
|
C "github.com/sagernet/sing/common"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
@@ -26,12 +30,13 @@ type APIClient struct {
|
|||||||
Key string
|
Key string
|
||||||
NodeType string
|
NodeType string
|
||||||
EnableVless bool
|
EnableVless bool
|
||||||
EnableXTLS bool
|
VlessFlow string
|
||||||
SpeedLimit float64
|
SpeedLimit float64
|
||||||
DeviceLimit int
|
DeviceLimit int
|
||||||
LocalRuleList []api.DetectRule
|
LocalRuleList []api.DetectRule
|
||||||
ConfigResp *simplejson.Json
|
ConfigResp *simplejson.Json
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
|
eTags map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create an api instance
|
// New create an api instance
|
||||||
@@ -44,13 +49,16 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
} else {
|
} else {
|
||||||
client.SetTimeout(5 * time.Second)
|
client.SetTimeout(5 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
client.OnError(func(req *resty.Request, err error) {
|
client.OnError(func(req *resty.Request, err error) {
|
||||||
if v, ok := err.(*resty.ResponseError); ok {
|
var v *resty.ResponseError
|
||||||
|
if errors.As(err, &v) {
|
||||||
// v.Response contains the last response from the server
|
// v.Response contains the last response from the server
|
||||||
// v.Err contains the original error
|
// v.Err contains the original error
|
||||||
log.Print(v.Err)
|
log.Print(v.Err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create Key for each requests
|
// Create Key for each requests
|
||||||
client.SetQueryParams(map[string]string{
|
client.SetQueryParams(map[string]string{
|
||||||
"node_id": strconv.Itoa(apiConfig.NodeID),
|
"node_id": strconv.Itoa(apiConfig.NodeID),
|
||||||
@@ -65,10 +73,11 @@ func New(apiConfig *api.Config) *APIClient {
|
|||||||
APIHost: apiConfig.APIHost,
|
APIHost: apiConfig.APIHost,
|
||||||
NodeType: apiConfig.NodeType,
|
NodeType: apiConfig.NodeType,
|
||||||
EnableVless: apiConfig.EnableVless,
|
EnableVless: apiConfig.EnableVless,
|
||||||
EnableXTLS: apiConfig.EnableXTLS,
|
VlessFlow: apiConfig.VlessFlow,
|
||||||
SpeedLimit: apiConfig.SpeedLimit,
|
SpeedLimit: apiConfig.SpeedLimit,
|
||||||
DeviceLimit: apiConfig.DeviceLimit,
|
DeviceLimit: apiConfig.DeviceLimit,
|
||||||
LocalRuleList: localRuleList,
|
LocalRuleList: localRuleList,
|
||||||
|
eTags: make(map[string]string),
|
||||||
}
|
}
|
||||||
return apiClient
|
return apiClient
|
||||||
}
|
}
|
||||||
@@ -138,23 +147,33 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
return rtn, nil
|
return rtn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNodeInfo will pull NodeInfo Config from sspanel
|
// GetNodeInfo will pull NodeInfo Config from panel
|
||||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
var nodeType string
|
var nodeType string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray", "Trojan", "Shadowsocks":
|
case "V2ray", "Vmess", "Vless", "Trojan", "Shadowsocks":
|
||||||
nodeType = strings.ToLower(c.NodeType)
|
nodeType = strings.ToLower(c.NodeType)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
|
SetHeader("If-None-Match", c.eTags["config"]).
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"act": "config",
|
"act": "config",
|
||||||
"nodetype": nodeType,
|
"node_type": nodeType,
|
||||||
}).
|
}).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
Get(c.APIHost)
|
Get(c.APIHost)
|
||||||
|
|
||||||
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.NodeNotModified)
|
||||||
|
}
|
||||||
|
// update etag
|
||||||
|
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["config"] {
|
||||||
|
c.eTags["config"] = res.Header().Get("Etag")
|
||||||
|
}
|
||||||
|
|
||||||
response, err := c.parseResponse(res, "", err)
|
response, err := c.parseResponse(res, "", err)
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@@ -164,7 +183,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
nodeInfo, err = c.ParseV2rayNodeResponse(response)
|
nodeInfo, err = c.ParseV2rayNodeResponse(response)
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
nodeInfo, err = c.ParseTrojanNodeResponse(response)
|
nodeInfo, err = c.ParseTrojanNodeResponse(response)
|
||||||
@@ -182,23 +201,33 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
return nodeInfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserList will pull user form sspanel
|
// GetUserList will pull user form panel
|
||||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
var nodeType string
|
var nodeType string
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray", "Trojan", "Shadowsocks":
|
case "V2ray", "Vmess", "Vless", "Trojan", "Shadowsocks":
|
||||||
nodeType = strings.ToLower(c.NodeType)
|
nodeType = strings.ToLower(c.NodeType)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
|
SetHeader("If-None-Match", c.eTags["user"]).
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"act": "user",
|
"act": "user",
|
||||||
"nodetype": nodeType,
|
"node_type": nodeType,
|
||||||
}).
|
}).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
Get(c.APIHost)
|
Get(c.APIHost)
|
||||||
|
|
||||||
|
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||||
|
if res.StatusCode() == 304 {
|
||||||
|
return nil, errors.New(api.UserNotModified)
|
||||||
|
}
|
||||||
|
// update etag
|
||||||
|
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["user"] {
|
||||||
|
c.eTags["user"] = res.Header().Get("Etag")
|
||||||
|
}
|
||||||
|
|
||||||
response, err := c.parseResponse(res, "", err)
|
response, err := c.parseResponse(res, "", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -210,24 +239,30 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
user.UID = response.Get("data").GetIndex(i).Get("id").MustInt()
|
user.UID = response.Get("data").GetIndex(i).Get("id").MustInt()
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
user.Email = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("secret").MustString()
|
user.Email = response.Get("data").GetIndex(i).Get("secret").MustString()
|
||||||
user.Passwd = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("secret").MustString()
|
user.Passwd = response.Get("data").GetIndex(i).Get("secret").MustString()
|
||||||
user.Method = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("cipher").MustString()
|
user.Method = response.Get("data").GetIndex(i).Get("cipher").MustString()
|
||||||
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("speed_limit").MustUint64() * 1000000 / 8)
|
user.SpeedLimit = response.Get("data").GetIndex(i).Get("st").MustUint64() * 1000000 / 8
|
||||||
|
user.DeviceLimit = response.Get("data").GetIndex(i).Get("dt").MustInt()
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
user.UUID = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
|
user.UUID = response.Get("data").GetIndex(i).Get("password").MustString()
|
||||||
user.Email = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
|
user.Email = response.Get("data").GetIndex(i).Get("password").MustString()
|
||||||
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("trojan_user").Get("speed_limit").MustUint64() * 1000000 / 8)
|
user.SpeedLimit = response.Get("data").GetIndex(i).Get("st").MustUint64() * 1000000 / 8
|
||||||
case "V2ray":
|
user.DeviceLimit = response.Get("data").GetIndex(i).Get("dt").MustInt()
|
||||||
user.UUID = response.Get("data").GetIndex(i).Get("v2ray_user").Get("uuid").MustString()
|
case "V2ray", "Vmess", "Vless":
|
||||||
user.Email = response.Get("data").GetIndex(i).Get("v2ray_user").Get("email").MustString()
|
user.UUID = response.Get("data").GetIndex(i).Get("uuid").MustString()
|
||||||
user.AlterID = uint16(response.Get("data").GetIndex(i).Get("v2ray_user").Get("alter_id").MustUint64())
|
user.Email = user.UUID + "@x.com"
|
||||||
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("v2ray_user").Get("speed_limit").MustUint64() * 1000000 / 8)
|
user.SpeedLimit = response.Get("data").GetIndex(i).Get("st").MustUint64() * 1000000 / 8
|
||||||
|
user.DeviceLimit = response.Get("data").GetIndex(i).Get("dt").MustInt()
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
user.SpeedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
user.SpeedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
user.DeviceLimit = c.DeviceLimit
|
|
||||||
|
if c.DeviceLimit > 0 {
|
||||||
|
user.DeviceLimit = c.DeviceLimit
|
||||||
|
}
|
||||||
|
|
||||||
userList[i] = user
|
userList[i] = user
|
||||||
}
|
}
|
||||||
return &userList, nil
|
return &userList, nil
|
||||||
@@ -247,8 +282,8 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"act": "submit",
|
"act": "submit",
|
||||||
"nodetype": strings.ToLower(c.NodeType),
|
"node_type": strings.ToLower(c.NodeType),
|
||||||
}).
|
}).
|
||||||
SetBody(data).
|
SetBody(data).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
@@ -263,11 +298,7 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|||||||
// GetNodeRule implements the API interface
|
// GetNodeRule implements the API interface
|
||||||
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||||
ruleList := c.LocalRuleList
|
ruleList := c.LocalRuleList
|
||||||
if c.NodeType != "V2ray" {
|
|
||||||
return &ruleList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// V2board only support the rule for v2ray
|
|
||||||
// fix: reuse config response
|
// fix: reuse config response
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@@ -285,26 +316,79 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|||||||
|
|
||||||
// ReportNodeStatus implements the API interface
|
// ReportNodeStatus implements the API interface
|
||||||
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||||
|
systemload := NodeStatus{
|
||||||
|
Uptime: int(nodeStatus.Uptime),
|
||||||
|
CPU: fmt.Sprintf("%d%%", int(nodeStatus.CPU)),
|
||||||
|
Mem: fmt.Sprintf("%d%%", int(nodeStatus.Mem)),
|
||||||
|
Disk: fmt.Sprintf("%d%%", int(nodeStatus.Disk)),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"act": "nodestatus",
|
||||||
|
"node_type": strings.ToLower(c.NodeType),
|
||||||
|
}).
|
||||||
|
SetBody(systemload).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(c.APIHost)
|
||||||
|
_, err = c.parseResponse(res, "", err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportNodeOnlineUsers implements the API interface
|
// ReportNodeOnlineUsers implements the API interface
|
||||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||||
|
data := make([]NodeOnline, len(*onlineUserList))
|
||||||
|
for i, user := range *onlineUserList {
|
||||||
|
data[i] = NodeOnline{UID: user.UID, IP: user.IP}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"act": "onlineusers",
|
||||||
|
"node_type": strings.ToLower(c.NodeType),
|
||||||
|
}).
|
||||||
|
SetBody(data).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(c.APIHost)
|
||||||
|
_, err = c.parseResponse(res, "", err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportIllegal implements the API interface
|
// ReportIllegal implements the API interface
|
||||||
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||||
|
data := make([]IllegalItem, len(*detectResultList))
|
||||||
|
for i, r := range *detectResultList {
|
||||||
|
data[i] = IllegalItem{
|
||||||
|
UID: r.UID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"act": "illegal",
|
||||||
|
"node_type": strings.ToLower(c.NodeType),
|
||||||
|
}).
|
||||||
|
SetBody(data).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(c.APIHost)
|
||||||
|
_, err = c.parseResponse(res, "", err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
// ParseTrojanNodeResponse parse the response for the given nodeInfo format
|
||||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
var TLSType = "tls"
|
|
||||||
if c.EnableXTLS {
|
|
||||||
TLSType = "xtls"
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||||
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||||
@@ -313,55 +397,52 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (
|
|||||||
host := inboundInfo.Get("streamSettings").Get("tlsSettings").Get("serverName").MustString()
|
host := inboundInfo.Get("streamSettings").Get("tlsSettings").Get("serverName").MustString()
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
EnableTLS: true,
|
EnableTLS: true,
|
||||||
TLSType: TLSType,
|
|
||||||
Host: host,
|
Host: host,
|
||||||
}
|
}
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
// ParseSSNodeResponse parse the response for the given nodeInfo format
|
||||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
var method string
|
var method, serverPsk string
|
||||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||||
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||||
|
|
||||||
port := uint32(inboundInfo.Get("port").MustUint64())
|
port := uint32(inboundInfo.Get("port").MustUint64())
|
||||||
userInfo, err := c.GetUserList()
|
method = inboundInfo.Get("settings").Get("method").MustString()
|
||||||
if err != nil {
|
// Shadowsocks 2022
|
||||||
return nil, err
|
if C.Contains(shadowaead_2022.List, method) {
|
||||||
}
|
serverPsk = inboundInfo.Get("settings").Get("password").MustString()
|
||||||
if len(*userInfo) > 0 {
|
|
||||||
method = (*userInfo)[0].Method
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
nodeinfo := &api.NodeInfo{
|
nodeInfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
CypherMethod: method,
|
CypherMethod: method,
|
||||||
|
ServerKey: serverPsk,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeinfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
// ParseV2rayNodeResponse parse the response for the given nodeInfo format
|
||||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
var TLSType string = "tls"
|
|
||||||
var path, host, serviceName string
|
var path, host, serviceName string
|
||||||
var header json.RawMessage
|
var header json.RawMessage
|
||||||
var enableTLS bool
|
var enableTLS bool
|
||||||
var alterID uint16 = 0
|
var enableVless bool
|
||||||
if c.EnableXTLS {
|
var enableReality bool
|
||||||
TLSType = "xtls"
|
var vlessFlow string
|
||||||
}
|
|
||||||
|
|
||||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||||
@@ -374,6 +455,12 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
|||||||
case "ws":
|
case "ws":
|
||||||
path = inboundInfo.Get("streamSettings").Get("wsSettings").Get("path").MustString()
|
path = inboundInfo.Get("streamSettings").Get("wsSettings").Get("path").MustString()
|
||||||
host = inboundInfo.Get("streamSettings").Get("wsSettings").Get("headers").Get("Host").MustString()
|
host = inboundInfo.Get("streamSettings").Get("wsSettings").Get("headers").Get("Host").MustString()
|
||||||
|
case "httpupgrade":
|
||||||
|
host = inboundInfo.Get("streamSettings").Get("httpupgradeSettings").Get("Host").MustString()
|
||||||
|
path = inboundInfo.Get("streamSettings").Get("httpupgradeSettings").Get("path").MustString()
|
||||||
|
case "splithttp":
|
||||||
|
host = inboundInfo.Get("streamSettings").Get("splithttpSettings").Get("Host").MustString()
|
||||||
|
path = inboundInfo.Get("streamSettings").Get("splithttpSettings").Get("path").MustString()
|
||||||
case "grpc":
|
case "grpc":
|
||||||
if data, ok := inboundInfo.Get("streamSettings").Get("grpcSettings").CheckGet("serviceName"); ok {
|
if data, ok := inboundInfo.Get("streamSettings").Get("grpcSettings").CheckGet("serviceName"); ok {
|
||||||
serviceName = data.MustString()
|
serviceName = data.MustString()
|
||||||
@@ -386,12 +473,32 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
|||||||
header = httpHeader
|
header = httpHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if inboundInfo.Get("streamSettings").Get("security").MustString() == "tls" {
|
|
||||||
enableTLS = true
|
enableTLS = inboundInfo.Get("streamSettings").Get("security").MustString() == "tls"
|
||||||
|
enableVless = inboundInfo.Get("protocol").MustString() == "vless"
|
||||||
|
enableReality = inboundInfo.Get("streamSettings").Get("security").MustString() == "reality"
|
||||||
|
|
||||||
|
realityConfig := new(api.REALITYConfig)
|
||||||
|
if enableVless {
|
||||||
|
// parse reality config
|
||||||
|
realityConfig = &api.REALITYConfig{
|
||||||
|
Dest: inboundInfo.Get("streamSettings").Get("realitySettings").Get("dest").MustString(),
|
||||||
|
ProxyProtocolVer: inboundInfo.Get("streamSettings").Get("realitySettings").Get("xver").MustUint64(),
|
||||||
|
ServerNames: inboundInfo.Get("streamSettings").Get("realitySettings").Get("serverNames").MustStringArray(),
|
||||||
|
PrivateKey: inboundInfo.Get("streamSettings").Get("realitySettings").Get("privateKey").MustString(),
|
||||||
|
MinClientVer: inboundInfo.Get("streamSettings").Get("realitySettings").Get("minClientVer").MustString(),
|
||||||
|
MaxClientVer: inboundInfo.Get("streamSettings").Get("realitySettings").Get("maxClientVer").MustString(),
|
||||||
|
MaxTimeDiff: inboundInfo.Get("streamSettings").Get("realitySettings").Get("maxTimeDiff").MustUint64(),
|
||||||
|
ShortIds: inboundInfo.Get("streamSettings").Get("realitySettings").Get("shortIds").MustStringArray(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTLS only supports TLS and REALITY directly for now
|
||||||
|
if transportProtocol == "tcp" && enableReality {
|
||||||
|
vlessFlow = "xtls-rprx-vision"
|
||||||
} else {
|
} else {
|
||||||
enableTLS = false
|
vlessFlow = c.VlessFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
@@ -400,15 +507,17 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
|||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: port,
|
||||||
AlterID: alterID,
|
AlterID: 0,
|
||||||
TransportProtocol: transportProtocol,
|
TransportProtocol: transportProtocol,
|
||||||
EnableTLS: enableTLS,
|
EnableTLS: enableTLS,
|
||||||
TLSType: TLSType,
|
|
||||||
Path: path,
|
Path: path,
|
||||||
Host: host,
|
Host: host,
|
||||||
EnableVless: c.EnableVless,
|
EnableVless: enableVless,
|
||||||
|
VlessFlow: vlessFlow,
|
||||||
ServiceName: serviceName,
|
ServiceName: serviceName,
|
||||||
Header: header,
|
Header: header,
|
||||||
|
EnableREALITY: enableReality,
|
||||||
|
REALITYConfig: realityConfig,
|
||||||
}
|
}
|
||||||
return nodeInfo, nil
|
return nodeInfo, nil
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package xrayr.app.mydispatcher;
|
package xrayr.app.mydispatcher;
|
||||||
option csharp_namespace = "XrayR.App.Myispatcher";
|
option csharp_namespace = "XrayR.App.Mydispatcher";
|
||||||
option go_package = "github.com/XrayR-project/XrayR/app/mydispatcher";
|
option go_package = "github.com/XrayR-project/XrayR/app/mydispatcher";
|
||||||
option java_package = "com.xrayr.app.mydispatcher";
|
option java_package = "com.xrayr.app.mydispatcher";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
@@ -145,76 +146,9 @@ func (*DefaultDispatcher) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, error) {
|
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, error) {
|
||||||
downOpt := pipe.OptionsFromContext(ctx)
|
opt := pipe.OptionsFromContext(ctx)
|
||||||
upOpt := downOpt
|
uplinkReader, uplinkWriter := pipe.New(opt...)
|
||||||
|
downlinkReader, downlinkWriter := pipe.New(opt...)
|
||||||
if network == net.Network_UDP {
|
|
||||||
var ip2domain *sync.Map // net.IP.String() => domain, this map is used by server side when client turn on fakedns
|
|
||||||
// Client will send domain address in the buffer.UDP.Address, server record all possible target IP addrs.
|
|
||||||
// When target replies, server will restore the domain and send back to client.
|
|
||||||
// Note: this map is not global but per connection context
|
|
||||||
upOpt = append(upOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
|
||||||
for i, buffer := range mb {
|
|
||||||
if buffer.UDP == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addr := buffer.UDP.Address
|
|
||||||
if addr.Family().IsIP() {
|
|
||||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(addr) && sniffing.Enabled {
|
|
||||||
domain := fkr0.GetDomainFromFakeDNS(addr)
|
|
||||||
if len(domain) > 0 {
|
|
||||||
buffer.UDP.Address = net.DomainAddress(domain)
|
|
||||||
newError("[fakedns client] override with domain: ", domain, " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
} else {
|
|
||||||
newError("[fakedns client] failed to find domain! :", addr.String(), " for xUDP buffer at ", i).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ip2domain == nil {
|
|
||||||
ip2domain = new(sync.Map)
|
|
||||||
newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
}
|
|
||||||
domain := addr.Domain()
|
|
||||||
ips, err := d.dns.LookupIP(domain, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
|
||||||
if err == nil {
|
|
||||||
for _, ip := range ips {
|
|
||||||
ip2domain.Store(ip.String(), domain)
|
|
||||||
}
|
|
||||||
newError("[fakedns client] candidate ip: "+fmt.Sprintf("%v", ips), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
} else {
|
|
||||||
newError("[fakedns client] failed to look up IP for ", domain, " for xUDP buffer at ", i).Base(err).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mb
|
|
||||||
}))
|
|
||||||
downOpt = append(downOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
|
||||||
for i, buffer := range mb {
|
|
||||||
if buffer.UDP == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addr := buffer.UDP.Address
|
|
||||||
if addr.Family().IsIP() {
|
|
||||||
if ip2domain == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if domain, found := ip2domain.Load(addr.IP().String()); found {
|
|
||||||
buffer.UDP.Address = net.DomainAddress(domain.(string))
|
|
||||||
newError("[fakedns client] restore domain: ", domain.(string), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok {
|
|
||||||
fakeIp := fkr0.GetFakeIPForDomain(addr.Domain())
|
|
||||||
buffer.UDP.Address = fakeIp[0]
|
|
||||||
newError("[fakedns client] restore FakeIP: ", buffer.UDP, fmt.Sprintf("%v", fakeIp), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mb
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
uplinkReader, uplinkWriter := pipe.New(upOpt...)
|
|
||||||
downlinkReader, downlinkWriter := pipe.New(downOpt...)
|
|
||||||
|
|
||||||
inboundLink := &transport.Link{
|
inboundLink := &transport.Link{
|
||||||
Reader: downlinkReader,
|
Reader: downlinkReader,
|
||||||
@@ -236,7 +170,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
|
|||||||
// Speed Limit and Device Limit
|
// Speed Limit and Device Limit
|
||||||
bucket, ok, reject := d.Limiter.GetUserBucket(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
|
bucket, ok, reject := d.Limiter.GetUserBucket(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
|
||||||
if reject {
|
if reject {
|
||||||
newError("Devices reach the limit: ", user.Email).AtWarning().WriteToLog()
|
errors.LogWarning(ctx, "Devices reach the limit: ", user.Email)
|
||||||
common.Close(outboundLink.Writer)
|
common.Close(outboundLink.Writer)
|
||||||
common.Close(inboundLink.Writer)
|
common.Close(inboundLink.Writer)
|
||||||
common.Interrupt(outboundLink.Reader)
|
common.Interrupt(outboundLink.Reader)
|
||||||
@@ -247,6 +181,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
|
|||||||
inboundLink.Writer = d.Limiter.RateWriter(inboundLink.Writer, bucket)
|
inboundLink.Writer = d.Limiter.RateWriter(inboundLink.Writer, bucket)
|
||||||
outboundLink.Writer = d.Limiter.RateWriter(outboundLink.Writer, bucket)
|
outboundLink.Writer = d.Limiter.RateWriter(outboundLink.Writer, bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := d.policy.ForLevel(user.Level)
|
p := d.policy.ForLevel(user.Level)
|
||||||
if p.Stats.UserUplink {
|
if p.Stats.UserUplink {
|
||||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||||
@@ -283,12 +218,12 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
|
|||||||
protocolString = resComp.ProtocolForDomainResult()
|
protocolString = resComp.ProtocolForDomainResult()
|
||||||
}
|
}
|
||||||
for _, p := range request.OverrideDestinationForProtocol {
|
for _, p := range request.OverrideDestinationForProtocol {
|
||||||
if strings.HasPrefix(protocolString, p) {
|
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||||
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
|
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
|
||||||
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
errors.LogInfo(ctx, "Using sniffer ", protocolString, " since the fake DNS missed")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
||||||
@@ -306,10 +241,14 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
|||||||
if !destination.IsValid() {
|
if !destination.IsValid() {
|
||||||
panic("Dispatcher: Invalid destination.")
|
panic("Dispatcher: Invalid destination.")
|
||||||
}
|
}
|
||||||
ob := &session.Outbound{
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
Target: destination,
|
if len(outbounds) == 0 {
|
||||||
|
outbounds = []*session.Outbound{{}}
|
||||||
|
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||||
}
|
}
|
||||||
ctx = session.ContextWithOutbound(ctx, ob)
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
ob.OriginalTarget = destination
|
||||||
|
ob.Target = destination
|
||||||
content := session.ContentFromContext(ctx)
|
content := session.ContentFromContext(ctx)
|
||||||
if content == nil {
|
if content == nil {
|
||||||
content = new(session.Content)
|
content = new(session.Content)
|
||||||
@@ -317,104 +256,25 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
|||||||
}
|
}
|
||||||
|
|
||||||
sniffingRequest := content.SniffingRequest
|
sniffingRequest := content.SniffingRequest
|
||||||
in, out, err := d.getLink(ctx, destination.Network, sniffingRequest)
|
inbound, outbound, err := d.getLink(ctx, destination.Network, sniffingRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch {
|
if !sniffingRequest.Enabled {
|
||||||
case !sniffingRequest.Enabled:
|
|
||||||
go d.routedDispatch(ctx, out, destination)
|
|
||||||
case destination.Network != net.Network_TCP:
|
|
||||||
// Only metadata sniff will be used for non tcp connection
|
|
||||||
result, err := sniffer(ctx, nil, true)
|
|
||||||
if err == nil {
|
|
||||||
content.Protocol = result.Protocol()
|
|
||||||
if d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
|
||||||
domain := result.Domain()
|
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
destination.Address = net.ParseAddress(domain)
|
|
||||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
|
||||||
ob.RouteTarget = destination
|
|
||||||
} else {
|
|
||||||
ob.Target = destination
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go d.routedDispatch(ctx, out, destination)
|
|
||||||
default:
|
|
||||||
go func() {
|
|
||||||
cReader := &cachedReader{
|
|
||||||
reader: out.Reader.(*pipe.Reader),
|
|
||||||
}
|
|
||||||
out.Reader = cReader
|
|
||||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
|
|
||||||
if err == nil {
|
|
||||||
content.Protocol = result.Protocol()
|
|
||||||
}
|
|
||||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
|
||||||
domain := result.Domain()
|
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
destination.Address = net.ParseAddress(domain)
|
|
||||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
|
||||||
ob.RouteTarget = destination
|
|
||||||
} else {
|
|
||||||
ob.Target = destination
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.routedDispatch(ctx, out, destination)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return in, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DispatchLink implements routing.Dispatcher.
|
|
||||||
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
|
|
||||||
if !destination.IsValid() {
|
|
||||||
return newError("Dispatcher: Invalid destination.")
|
|
||||||
}
|
|
||||||
ob := &session.Outbound{
|
|
||||||
Target: destination,
|
|
||||||
}
|
|
||||||
ctx = session.ContextWithOutbound(ctx, ob)
|
|
||||||
content := session.ContentFromContext(ctx)
|
|
||||||
if content == nil {
|
|
||||||
content = new(session.Content)
|
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
|
||||||
}
|
|
||||||
sniffingRequest := content.SniffingRequest
|
|
||||||
switch {
|
|
||||||
case !sniffingRequest.Enabled:
|
|
||||||
go d.routedDispatch(ctx, outbound, destination)
|
go d.routedDispatch(ctx, outbound, destination)
|
||||||
case destination.Network != net.Network_TCP:
|
} else {
|
||||||
// Only metadata sniff will be used for non tcp connection
|
|
||||||
result, err := sniffer(ctx, nil, true)
|
|
||||||
if err == nil {
|
|
||||||
content.Protocol = result.Protocol()
|
|
||||||
if d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
|
||||||
domain := result.Domain()
|
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
|
||||||
destination.Address = net.ParseAddress(domain)
|
|
||||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
|
||||||
ob.RouteTarget = destination
|
|
||||||
} else {
|
|
||||||
ob.Target = destination
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go d.routedDispatch(ctx, outbound, destination)
|
|
||||||
default:
|
|
||||||
go func() {
|
go func() {
|
||||||
cReader := &cachedReader{
|
cReader := &cachedReader{
|
||||||
reader: outbound.Reader.(*pipe.Reader),
|
reader: outbound.Reader.(*pipe.Reader),
|
||||||
}
|
}
|
||||||
outbound.Reader = cReader
|
outbound.Reader = cReader
|
||||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
|
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
content.Protocol = result.Protocol()
|
content.Protocol = result.Protocol()
|
||||||
}
|
}
|
||||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
domain := result.Domain()
|
domain := result.Domain()
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||||
destination.Address = net.ParseAddress(domain)
|
destination.Address = net.ParseAddress(domain)
|
||||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
||||||
ob.RouteTarget = destination
|
ob.RouteTarget = destination
|
||||||
@@ -425,10 +285,58 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
|||||||
d.routedDispatch(ctx, outbound, destination)
|
d.routedDispatch(ctx, outbound, destination)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DispatchLink implements routing.Dispatcher.
|
||||||
|
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
|
||||||
|
if !destination.IsValid() {
|
||||||
|
return newError("Dispatcher: Invalid destination.")
|
||||||
|
}
|
||||||
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
if len(outbounds) == 0 {
|
||||||
|
outbounds = []*session.Outbound{{}}
|
||||||
|
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||||
|
}
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
ob.OriginalTarget = destination
|
||||||
|
ob.Target = destination
|
||||||
|
content := session.ContentFromContext(ctx)
|
||||||
|
if content == nil {
|
||||||
|
content = new(session.Content)
|
||||||
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
|
}
|
||||||
|
sniffingRequest := content.SniffingRequest
|
||||||
|
if !sniffingRequest.Enabled {
|
||||||
|
go d.routedDispatch(ctx, outbound, destination)
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
cReader := &cachedReader{
|
||||||
|
reader: outbound.Reader.(*pipe.Reader),
|
||||||
|
}
|
||||||
|
outbound.Reader = cReader
|
||||||
|
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||||
|
if err == nil {
|
||||||
|
content.Protocol = result.Protocol()
|
||||||
|
}
|
||||||
|
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
|
domain := result.Domain()
|
||||||
|
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||||
|
destination.Address = net.ParseAddress(domain)
|
||||||
|
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
||||||
|
ob.RouteTarget = destination
|
||||||
|
} else {
|
||||||
|
ob.Target = destination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.routedDispatch(ctx, outbound, destination)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (SniffResult, error) {
|
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
|
||||||
payload := buf.New()
|
payload := buf.New()
|
||||||
defer payload.Release()
|
defer payload.Release()
|
||||||
|
|
||||||
@@ -454,7 +362,7 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
|
|||||||
|
|
||||||
cReader.Cache(payload)
|
cReader.Cache(payload)
|
||||||
if !payload.IsEmpty() {
|
if !payload.IsEmpty() {
|
||||||
result, err := sniffer.Sniff(ctx, payload.Bytes())
|
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
||||||
if err != common.ErrNoClue {
|
if err != common.ErrNoClue {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -475,7 +383,8 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
||||||
ob := session.OutboundFromContext(ctx)
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
|
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
|
||||||
proxied := hosts.LookupHosts(ob.Target.String())
|
proxied := hosts.LookupHosts(ob.Target.String())
|
||||||
if proxied != nil {
|
if proxied != nil {
|
||||||
@@ -496,7 +405,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
// Whether the inbound connection contains a user
|
// Whether the inbound connection contains a user
|
||||||
if sessionInbound.User != nil {
|
if sessionInbound.User != nil {
|
||||||
if d.RuleManager.Detect(sessionInbound.Tag, destination.String(), sessionInbound.User.Email) {
|
if d.RuleManager.Detect(sessionInbound.Tag, destination.String(), sessionInbound.User.Email) {
|
||||||
newError(fmt.Sprintf("User %s access %s reject by rule", sessionInbound.User.Email, destination.String())).AtError().WriteToLog()
|
errors.LogError(ctx, fmt.Sprintf("User %s access %s reject by rule", sessionInbound.User.Email, destination.String()))
|
||||||
newError("destination is reject by rule")
|
newError("destination is reject by rule")
|
||||||
common.Close(link.Writer)
|
common.Close(link.Writer)
|
||||||
common.Interrupt(link.Reader)
|
common.Interrupt(link.Reader)
|
||||||
@@ -511,10 +420,10 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
||||||
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
||||||
isPickRoute = 1
|
isPickRoute = 1
|
||||||
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
errors.LogInfo(ctx, "taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]")
|
||||||
handler = h
|
handler = h
|
||||||
} else {
|
} else {
|
||||||
newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx))
|
errors.LogError(ctx, "non existing tag for platform initialized detour: ", forcedOutboundTag)
|
||||||
common.Close(link.Writer)
|
common.Close(link.Writer)
|
||||||
common.Interrupt(link.Reader)
|
common.Interrupt(link.Reader)
|
||||||
return
|
return
|
||||||
@@ -524,13 +433,13 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
outTag := route.GetOutboundTag()
|
outTag := route.GetOutboundTag()
|
||||||
if h := d.ohm.GetHandler(outTag); h != nil {
|
if h := d.ohm.GetHandler(outTag); h != nil {
|
||||||
isPickRoute = 2
|
isPickRoute = 2
|
||||||
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
errors.LogInfo(ctx, "taking detour [", outTag, "] for [", destination, "]")
|
||||||
handler = h
|
handler = h
|
||||||
} else {
|
} else {
|
||||||
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
errors.LogWarning(ctx, "non existing outTag: ", outTag)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
errors.LogInfo(ctx, "default route for ", destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +453,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
|
errors.LogInfo(ctx, "default outbound handler not exist")
|
||||||
common.Close(link.Writer)
|
common.Close(link.Writer)
|
||||||
common.Interrupt(link.Reader)
|
common.Interrupt(link.Reader)
|
||||||
return
|
return
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -26,11 +27,13 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
|
|||||||
return protocolSnifferWithMetadata{}, errNotInit
|
return protocolSnifferWithMetadata{}, errNotInit
|
||||||
}
|
}
|
||||||
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||||
Target := session.OutboundFromContext(ctx).Target
|
outbounds := session.OutboundsFromContext(ctx)
|
||||||
|
ob := outbounds[len(outbounds)-1]
|
||||||
|
Target := ob.Target
|
||||||
if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP {
|
if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP {
|
||||||
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
|
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
|
||||||
if domainFromFakeDNS != "" {
|
if domainFromFakeDNS != "" {
|
||||||
newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx))
|
errors.LogInfo(ctx, "fake dns got domain: ", domainFromFakeDNS, " for ip: ", ob.Target.Address.String())
|
||||||
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,10 +110,10 @@ func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWit
|
|||||||
}
|
}
|
||||||
return nil, common.ErrNoClue
|
return nil, common.ErrNoClue
|
||||||
}
|
}
|
||||||
newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
|
errors.LogDebug(ctx, "ip address not in fake dns range, return as is")
|
||||||
return nil, common.ErrNoClue
|
return nil, common.ErrNoClue
|
||||||
}
|
}
|
||||||
newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
|
errors.LogWarning(ctx, "fake dns sniffer did not set address in range option, assume false.")
|
||||||
return nil, common.ErrNoClue
|
return nil, common.ErrNoClue
|
||||||
},
|
},
|
||||||
metadataSniffer: false,
|
metadataSniffer: false,
|
||||||
|
@@ -4,8 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
||||||
"github.com/xtls/xray-core/common/protocol/http"
|
"github.com/xtls/xray-core/common/protocol/http"
|
||||||
|
"github.com/xtls/xray-core/common/protocol/quic"
|
||||||
"github.com/xtls/xray-core/common/protocol/tls"
|
"github.com/xtls/xray-core/common/protocol/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ type protocolSnifferWithMetadata struct {
|
|||||||
// for both TCP and UDP connections
|
// for both TCP and UDP connections
|
||||||
// It will not be shown as a traffic type for routing unless there is no other successful sniffing.
|
// It will not be shown as a traffic type for routing unless there is no other successful sniffing.
|
||||||
metadataSniffer bool
|
metadataSniffer bool
|
||||||
|
network net.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sniffer struct {
|
type Sniffer struct {
|
||||||
@@ -31,9 +34,11 @@ type Sniffer struct {
|
|||||||
func NewSniffer(ctx context.Context) *Sniffer {
|
func NewSniffer(ctx context.Context) *Sniffer {
|
||||||
ret := &Sniffer{
|
ret := &Sniffer{
|
||||||
sniffer: []protocolSnifferWithMetadata{
|
sniffer: []protocolSnifferWithMetadata{
|
||||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false},
|
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP},
|
||||||
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false},
|
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
|
||||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false},
|
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
|
||||||
|
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
|
||||||
|
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
|
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
|
||||||
@@ -49,11 +54,11 @@ func NewSniffer(ctx context.Context) *Sniffer {
|
|||||||
|
|
||||||
var errUnknownContent = newError("unknown content")
|
var errUnknownContent = newError("unknown content")
|
||||||
|
|
||||||
func (s *Sniffer) Sniff(c context.Context, payload []byte) (SniffResult, error) {
|
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
||||||
var pendingSniffer []protocolSnifferWithMetadata
|
var pendingSniffer []protocolSnifferWithMetadata
|
||||||
for _, si := range s.sniffer {
|
for _, si := range s.sniffer {
|
||||||
s := si.protocolSniffer
|
s := si.protocolSniffer
|
||||||
if si.metadataSniffer {
|
if si.metadataSniffer || si.network != network {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result, err := s(c, payload)
|
result, err := s(c, payload)
|
||||||
|
@@ -31,7 +31,6 @@ import (
|
|||||||
_ "github.com/xtls/xray-core/proxy/dokodemo"
|
_ "github.com/xtls/xray-core/proxy/dokodemo"
|
||||||
_ "github.com/xtls/xray-core/proxy/freedom"
|
_ "github.com/xtls/xray-core/proxy/freedom"
|
||||||
_ "github.com/xtls/xray-core/proxy/http"
|
_ "github.com/xtls/xray-core/proxy/http"
|
||||||
_ "github.com/xtls/xray-core/proxy/mtproto"
|
|
||||||
_ "github.com/xtls/xray-core/proxy/shadowsocks"
|
_ "github.com/xtls/xray-core/proxy/shadowsocks"
|
||||||
_ "github.com/xtls/xray-core/proxy/socks"
|
_ "github.com/xtls/xray-core/proxy/socks"
|
||||||
_ "github.com/xtls/xray-core/proxy/trojan"
|
_ "github.com/xtls/xray-core/proxy/trojan"
|
||||||
@@ -45,11 +44,11 @@ import (
|
|||||||
_ "github.com/xtls/xray-core/transport/internet/http"
|
_ "github.com/xtls/xray-core/transport/internet/http"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/kcp"
|
_ "github.com/xtls/xray-core/transport/internet/kcp"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/quic"
|
_ "github.com/xtls/xray-core/transport/internet/quic"
|
||||||
|
_ "github.com/xtls/xray-core/transport/internet/reality"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/tcp"
|
_ "github.com/xtls/xray-core/transport/internet/tcp"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/tls"
|
_ "github.com/xtls/xray-core/transport/internet/tls"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/udp"
|
_ "github.com/xtls/xray-core/transport/internet/udp"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/websocket"
|
_ "github.com/xtls/xray-core/transport/internet/websocket"
|
||||||
_ "github.com/xtls/xray-core/transport/internet/xtls"
|
|
||||||
|
|
||||||
// Transport headers
|
// Transport headers
|
||||||
_ "github.com/xtls/xray-core/transport/internet/headers/http"
|
_ "github.com/xtls/xray-core/transport/internet/headers/http"
|
@@ -1,9 +1,7 @@
|
|||||||
package main
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
@@ -12,36 +10,40 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/panel"
|
"github.com/XrayR-project/XrayR/panel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configFile = flag.String("config", "", "Config file for XrayR.")
|
cfgFile string
|
||||||
printVersion = flag.Bool("version", false, "show version")
|
rootCmd = &cobra.Command{
|
||||||
|
Use: "XrayR",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func init() {
|
||||||
version = "0.8.8"
|
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "Config file for XrayR.")
|
||||||
codename = "XrayR"
|
|
||||||
intro = "A Xray backend that supports many panels"
|
|
||||||
)
|
|
||||||
|
|
||||||
func showVersion() {
|
|
||||||
fmt.Printf("%s %s (%s) \n", codename, version, intro)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfig() *viper.Viper {
|
func getConfig() *viper.Viper {
|
||||||
config := viper.New()
|
config := viper.New()
|
||||||
|
|
||||||
// Set custom path and name
|
// Set custom path and name
|
||||||
if *configFile != "" {
|
if cfgFile != "" {
|
||||||
configName := path.Base(*configFile)
|
configName := path.Base(cfgFile)
|
||||||
configFileExt := path.Ext(*configFile)
|
configFileExt := path.Ext(cfgFile)
|
||||||
configNameOnly := strings.TrimSuffix(configName, configFileExt)
|
configNameOnly := strings.TrimSuffix(configName, configFileExt)
|
||||||
configPath := path.Dir(*configFile)
|
configPath := path.Dir(cfgFile)
|
||||||
config.SetConfigName(configNameOnly)
|
config.SetConfigName(configNameOnly)
|
||||||
config.SetConfigType(strings.TrimPrefix(configFileExt, "."))
|
config.SetConfigType(strings.TrimPrefix(configFileExt, "."))
|
||||||
config.AddConfigPath(configPath)
|
config.AddConfigPath(configPath)
|
||||||
@@ -65,18 +67,19 @@ func getConfig() *viper.Viper {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func run() error {
|
||||||
flag.Parse()
|
|
||||||
showVersion()
|
showVersion()
|
||||||
if *printVersion {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config := getConfig()
|
config := getConfig()
|
||||||
panelConfig := &panel.Config{}
|
panelConfig := &panel.Config{}
|
||||||
if err := config.Unmarshal(panelConfig); err != nil {
|
if err := config.Unmarshal(panelConfig); err != nil {
|
||||||
log.Panicf("Parse config file %v failed: %s \n", configFile, err)
|
return fmt.Errorf("Parse config file %v failed: %s \n", cfgFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if panelConfig.LogConfig.Level == "debug" {
|
||||||
|
log.SetReportCaller(true)
|
||||||
|
}
|
||||||
|
|
||||||
p := panel.New(panelConfig)
|
p := panel.New(panelConfig)
|
||||||
lastTime := time.Now()
|
lastTime := time.Now()
|
||||||
config.OnConfigChange(func(e fsnotify.Event) {
|
config.OnConfigChange(func(e fsnotify.Event) {
|
||||||
@@ -88,21 +91,31 @@ func main() {
|
|||||||
// Delete old instance and trigger GC
|
// Delete old instance and trigger GC
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
if err := config.Unmarshal(panelConfig); err != nil {
|
if err := config.Unmarshal(panelConfig); err != nil {
|
||||||
log.Panicf("Parse config file %v failed: %s \n", configFile, err)
|
log.Panicf("Parse config file %v failed: %s \n", cfgFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if panelConfig.LogConfig.Level == "debug" {
|
||||||
|
log.SetReportCaller(true)
|
||||||
|
}
|
||||||
|
|
||||||
p.Start()
|
p.Start()
|
||||||
lastTime = time.Now()
|
lastTime = time.Now()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
p.Start()
|
p.Start()
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
// Explicitly triggering GC to remove garbage from config loading.
|
// Explicitly triggering GC to remove garbage from config loading.
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
// Running backend
|
// Running backend
|
||||||
{
|
osSignals := make(chan os.Signal, 1)
|
||||||
osSignals := make(chan os.Signal, 1)
|
signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||||
signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
|
<-osSignals
|
||||||
<-osSignals
|
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() error {
|
||||||
|
return rootCmd.Execute()
|
||||||
}
|
}
|
27
cmd/version.go
Normal file
27
cmd/version.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
version = "0.9.5"
|
||||||
|
codename = "XrayR"
|
||||||
|
intro = "A Xray backend that supports many panels"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(&cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Print current version of XrayR",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
showVersion()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func showVersion() {
|
||||||
|
fmt.Printf("%s %s (%s) \n", codename, version, intro)
|
||||||
|
}
|
66
cmd/x25519.go
Normal file
66
cmd/x25519.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
priKey string
|
||||||
|
x25519Cmd = &cobra.Command{
|
||||||
|
Use: "x25519",
|
||||||
|
Short: "Generate key pair for x25519 key exchange",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := x25519(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
x25519Cmd.PersistentFlags().StringVarP(&priKey, "input", "i", "", "Input private key (base64.RawURLEncoding)")
|
||||||
|
rootCmd.AddCommand(x25519Cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func x25519() error {
|
||||||
|
privateKey := make([]byte, curve25519.ScalarSize)
|
||||||
|
|
||||||
|
if priKey == "" {
|
||||||
|
if _, err := rand.Read(privateKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p, err := base64.RawURLEncoding.DecodeString(priKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(p) != curve25519.ScalarSize {
|
||||||
|
return errors.New("invalid private key")
|
||||||
|
}
|
||||||
|
privateKey = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify random bytes using algorithm described at:
|
||||||
|
// https://cr.yp.to/ecdh.html.
|
||||||
|
privateKey[0] &= 248
|
||||||
|
privateKey[31] &= 127
|
||||||
|
privateKey[31] |= 64
|
||||||
|
|
||||||
|
publicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
output := fmt.Sprintf("Private key: %v\nPublic key: %v",
|
||||||
|
base64.RawURLEncoding.EncodeToString(privateKey),
|
||||||
|
base64.RawURLEncoding.EncodeToString(publicKey))
|
||||||
|
fmt.Println(output)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,7 +0,0 @@
|
|||||||
package limiter
|
|
||||||
|
|
||||||
import "github.com/xtls/xray-core/common/errors"
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
|
||||||
return errors.New(values...)
|
|
||||||
}
|
|
@@ -14,8 +14,9 @@ import (
|
|||||||
"github.com/eko/gocache/lib/v4/store"
|
"github.com/eko/gocache/lib/v4/store"
|
||||||
goCacheStore "github.com/eko/gocache/store/go_cache/v4"
|
goCacheStore "github.com/eko/gocache/store/go_cache/v4"
|
||||||
redisStore "github.com/eko/gocache/store/redis/v4"
|
redisStore "github.com/eko/gocache/store/redis/v4"
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
goCache "github.com/patrickmn/go-cache"
|
goCache "github.com/patrickmn/go-cache"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
@@ -66,7 +67,9 @@ func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList
|
|||||||
// init redis store
|
// init redis store
|
||||||
rs := redisStore.NewRedis(redis.NewClient(
|
rs := redisStore.NewRedis(redis.NewClient(
|
||||||
&redis.Options{
|
&redis.Options{
|
||||||
|
Network: globalLimit.RedisNetwork,
|
||||||
Addr: globalLimit.RedisAddr,
|
Addr: globalLimit.RedisAddr,
|
||||||
|
Username: globalLimit.RedisUsername,
|
||||||
Password: globalLimit.RedisPassword,
|
Password: globalLimit.RedisPassword,
|
||||||
DB: globalLimit.RedisDB,
|
DB: globalLimit.RedisDB,
|
||||||
}),
|
}),
|
||||||
@@ -216,7 +219,7 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r
|
|||||||
return nil, false, false
|
return nil, false, false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newError("Get Inbound Limiter information failed").AtDebug().WriteToLog()
|
errors.LogDebug(context.Background(), "Get Inbound Limiter information failed")
|
||||||
return nil, false, false
|
return nil, false, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +239,7 @@ func globalLimit(inboundInfo *InboundInfo, email string, uid int, ip string, dev
|
|||||||
// If the email is a new device
|
// If the email is a new device
|
||||||
go pushIP(inboundInfo, uniqueKey, &map[string]int{ip: uid})
|
go pushIP(inboundInfo, uniqueKey, &map[string]int{ip: uid})
|
||||||
} else {
|
} else {
|
||||||
newError("cache service").Base(err).AtError().WriteToLog()
|
errors.LogErrorInner(context.Background(), err, "cache service")
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -262,7 +265,7 @@ func pushIP(inboundInfo *InboundInfo, uniqueKey string, ipMap *map[string]int) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := inboundInfo.GlobalLimit.globalOnlineIP.Set(ctx, uniqueKey, ipMap); err != nil {
|
if err := inboundInfo.GlobalLimit.globalOnlineIP.Set(ctx, uniqueKey, ipMap); err != nil {
|
||||||
newError("cache service").Base(err).AtError().WriteToLog()
|
errors.LogErrorInner(context.Background(), err, "cache service")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,9 @@ package limiter
|
|||||||
|
|
||||||
type GlobalDeviceLimitConfig struct {
|
type GlobalDeviceLimitConfig struct {
|
||||||
Enable bool `mapstructure:"Enable"`
|
Enable bool `mapstructure:"Enable"`
|
||||||
RedisAddr string `mapstructure:"RedisAddr"` // host:port
|
RedisNetwork string `mapstructure:"RedisNetwork"` // tcp or unix
|
||||||
|
RedisAddr string `mapstructure:"RedisAddr"` // host:port, or /path/to/unix.sock
|
||||||
|
RedisUsername string `mapstructure:"RedisUsername"`
|
||||||
RedisPassword string `mapstructure:"RedisPassword"`
|
RedisPassword string `mapstructure:"RedisPassword"`
|
||||||
RedisDB int `mapstructure:"RedisDB"`
|
RedisDB int `mapstructure:"RedisDB"`
|
||||||
Timeout int `mapstructure:"Timeout"`
|
Timeout int `mapstructure:"Timeout"`
|
||||||
|
@@ -6,12 +6,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
@@ -4,11 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
|
@@ -149,8 +149,8 @@ func (l *LegoCMD) RenewCert() (CertPath string, KeyPath string, ok bool, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkCertFile(domain string) (string, string, error) {
|
func checkCertFile(domain string) (string, string, error) {
|
||||||
keyPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.key", domain))
|
keyPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.key", sanitizedDomain(domain)))
|
||||||
certPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.crt", domain))
|
certPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.crt", sanitizedDomain(domain)))
|
||||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||||
return "", "", fmt.Errorf("cert key failed: %s", domain)
|
return "", "", fmt.Errorf("cert key failed: %s", domain)
|
||||||
}
|
}
|
||||||
|
@@ -3,9 +3,10 @@ package mylego
|
|||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
@@ -2,11 +2,11 @@ package mylego
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const rootPathWarningMessage = `!!!! HEADS UP !!!!
|
const rootPathWarningMessage = `!!!! HEADS UP !!!!
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
package mylego
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
package rule
|
|
||||||
|
|
||||||
import "github.com/xtls/xray-core/common/errors"
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
|
||||||
return errors.New(values...)
|
|
||||||
}
|
|
@@ -2,6 +2,7 @@
|
|||||||
package rule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
mapset "github.com/deckarep/golang-set"
|
mapset "github.com/deckarep/golang-set"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
@@ -65,7 +67,7 @@ func (r *Manager) Detect(tag string, destination string, email string) (reject b
|
|||||||
l := strings.Split(email, "|")
|
l := strings.Split(email, "|")
|
||||||
uid, err := strconv.Atoi(l[len(l)-1])
|
uid, err := strconv.Atoi(l[len(l)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
newError(fmt.Sprintf("Record illegal behavior failed! Cannot find user's uid: %s", email)).AtDebug().WriteToLog()
|
errors.LogDebug(context.Background(), fmt.Sprintf("Record illegal behavior failed! Cannot find user's uid: %s", email))
|
||||||
return reject
|
return reject
|
||||||
}
|
}
|
||||||
newSet := mapset.NewSetWith(api.DetectResult{UID: uid, RuleID: hitRuleID})
|
newSet := mapset.NewSetWith(api.DetectResult{UID: uid, RuleID: hitRuleID})
|
||||||
|
378
go.mod
378
go.mod
@@ -1,191 +1,309 @@
|
|||||||
module github.com/XrayR-project/XrayR
|
module github.com/XrayR-project/XrayR
|
||||||
|
|
||||||
go 1.19
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bitly/go-simplejson v0.5.0
|
dario.cat/mergo v1.0.0
|
||||||
|
github.com/bitly/go-simplejson v0.5.1
|
||||||
github.com/deckarep/golang-set v1.8.0
|
github.com/deckarep/golang-set v1.8.0
|
||||||
github.com/eko/gocache/lib/v4 v4.1.2
|
github.com/eko/gocache/lib/v4 v4.1.6
|
||||||
github.com/eko/gocache/store/go_cache/v4 v4.1.2
|
github.com/eko/gocache/store/go_cache/v4 v4.2.2
|
||||||
github.com/eko/gocache/store/redis/v4 v4.1.2
|
github.com/eko/gocache/store/redis/v4 v4.2.1
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/go-acme/lego/v4 v4.9.1
|
github.com/go-acme/lego/v4 v4.16.1
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-resty/resty/v2 v2.13.1
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/gogf/gf/v2 v2.7.0
|
||||||
github.com/imdario/mergo v0.3.13
|
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/r3labs/diff/v2 v2.15.1
|
github.com/r3labs/diff/v2 v2.15.1
|
||||||
github.com/sagernet/sing v0.1.0
|
github.com/redis/go-redis/v9 v9.7.0
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.0
|
github.com/sagernet/sing v0.4.1
|
||||||
github.com/shirou/gopsutil/v3 v3.22.11
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/spf13/viper v1.14.0
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/xtls/xray-core v1.6.7-0.20221215133507-a55cf1d0bff2
|
github.com/spf13/cobra v1.8.1
|
||||||
golang.org/x/crypto v0.4.0
|
github.com/spf13/viper v1.18.2
|
||||||
golang.org/x/net v0.4.0
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/time v0.3.0
|
github.com/xtls/xray-core v1.8.20
|
||||||
google.golang.org/protobuf v1.28.1
|
golang.org/x/crypto v0.28.0
|
||||||
|
golang.org/x/net v0.27.0
|
||||||
|
golang.org/x/time v0.7.0
|
||||||
|
google.golang.org/protobuf v1.36.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.13.0 // indirect
|
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible // indirect
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
|
||||||
|
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
|
||||||
|
github.com/Joker/jade v1.1.3 // indirect
|
||||||
|
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 // indirect
|
||||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1 // indirect
|
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.695 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.39.0 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.25.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.27.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/lightsail v1.36.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 // indirect
|
||||||
|
github.com/aws/smithy-go v1.20.1 // indirect
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
github.com/boombuler/barcode v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/bytedance/sonic v1.11.3 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/civo/civogo v0.3.11 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/cloudflare/cloudflare-go v0.49.0 // indirect
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
|
github.com/civo/civogo v0.3.63 // indirect
|
||||||
|
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||||
|
github.com/cloudflare/circl v1.3.9 // indirect
|
||||||
|
github.com/cloudflare/cloudflare-go v0.90.0 // indirect
|
||||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
github.com/deepmap/oapi-codegen v1.16.2 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
github.com/dnsimple/dnsimple-go v0.71.1 // indirect
|
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||||
github.com/exoscale/egoscale v0.90.0 // indirect
|
github.com/exoscale/egoscale v1.19.0 // indirect
|
||||||
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
github.com/fatih/structs v1.1.0 // indirect
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/flosch/pongo2/v4 v4.0.2 // indirect
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/go-errors/errors v1.0.1 // indirect
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
github.com/go-errors/errors v1.5.1 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.7.0-rc.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gophercloud/gophercloud v1.11.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
|
||||||
|
github.com/gorilla/css v1.0.1 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
|
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
|
github.com/iris-contrib/schema v0.0.6 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||||
github.com/klauspost/compress v1.15.13 // indirect
|
github.com/kataras/blocks v0.0.8 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.2 // indirect
|
github.com/kataras/golog v0.1.11 // indirect
|
||||||
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
|
github.com/kataras/iris/v12 v12.2.10 // indirect
|
||||||
|
github.com/kataras/pio v0.0.13 // indirect
|
||||||
|
github.com/kataras/sitemap v0.0.6 // indirect
|
||||||
|
github.com/kataras/tunnel v0.0.4 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||||
github.com/linode/linodego v1.9.1 // indirect
|
github.com/labstack/echo/v4 v4.11.4 // indirect
|
||||||
github.com/liquidweb/go-lwApi v0.0.5 // indirect
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/liquidweb/liquidweb-go v1.6.3 // indirect
|
github.com/linode/linodego v1.30.0 // indirect
|
||||||
github.com/lucas-clemente/quic-go v0.31.1 // indirect
|
github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||||
github.com/magiconair/properties v1.8.6 // indirect
|
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
|
github.com/mailgun/raymond/v2 v2.0.48 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/miekg/dns v1.1.50 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mimuret/golang-iij-dpf v0.7.1 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
|
||||||
|
github.com/miekg/dns v1.1.61 // indirect
|
||||||
|
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||||
github.com/nrdcg/desec v0.6.0 // indirect
|
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
|
||||||
|
github.com/nrdcg/desec v0.7.0 // indirect
|
||||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||||
github.com/nrdcg/goinwx v0.8.1 // indirect
|
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||||
|
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
github.com/nrdcg/porkbun v0.1.1 // indirect
|
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.6.0 // indirect
|
github.com/nrdcg/porkbun v0.3.0 // indirect
|
||||||
|
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||||
github.com/ovh/go-ovh v1.1.0 // indirect
|
github.com/ovh/go-ovh v1.4.3 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/pquerna/otp v1.3.0 // indirect
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.6.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.50.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.13.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.2.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.45.1 // indirect
|
||||||
|
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/sacloud/api-client-go v0.2.1 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/sacloud/go-http v0.1.2 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sacloud/iaas-api-go v1.3.2 // indirect
|
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||||
github.com/sacloud/packages-go v0.0.5 // indirect
|
github.com/sacloud/go-http v0.1.8 // indirect
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
github.com/sacloud/iaas-api-go v1.11.2 // indirect
|
||||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
|
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.25 // indirect
|
||||||
|
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
|
||||||
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
github.com/shopspring/decimal v1.3.1 // indirect
|
||||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
github.com/softlayer/softlayer-go v1.0.6 // indirect
|
github.com/softlayer/softlayer-go v1.1.3 // indirect
|
||||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
github.com/spf13/afero v1.9.2 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/subosito/gotenv v1.4.1 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
github.com/tdewolff/minify/v2 v2.20.19 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
github.com/tdewolff/parse/v2 v2.7.12 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.878 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.878 // indirect
|
||||||
github.com/transip/gotransip/v6 v6.17.0 // indirect
|
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||||
|
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 // indirect
|
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d // indirect
|
||||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
github.com/yandex-cloud/go-genproto v0.0.0-20240311082839-58e1a7554a75 // indirect
|
||||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
github.com/yandex-cloud/go-sdk v0.0.0-20240311083148-81c0846b96cd // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yosssi/ace v0.0.5 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
go.uber.org/ratelimit v0.2.0 // indirect
|
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 // indirect
|
go.opentelemetry.io/otel/sdk v1.22.0 // indirect
|
||||||
golang.org/x/mod v0.7.0 // indirect
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/sys v0.3.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
golang.org/x/text v0.5.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/tools v0.4.0 // indirect
|
go.uber.org/ratelimit v0.3.1 // indirect
|
||||||
google.golang.org/api v0.103.0 // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect
|
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
|
||||||
google.golang.org/grpc v1.51.0 // indirect
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.20.0 // indirect
|
||||||
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
|
golang.org/x/text v0.19.0 // indirect
|
||||||
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
|
google.golang.org/api v0.170.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
|
||||||
|
google.golang.org/grpc v1.65.0 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.6.5 // indirect
|
gopkg.in/ns1/ns1-go.v2 v2.9.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
|
||||||
lukechampine.com/blake3 v1.1.7 // indirect
|
k8s.io/api v0.29.2 // indirect
|
||||||
|
k8s.io/apimachinery v0.29.2 // indirect
|
||||||
|
k8s.io/klog/v2 v2.120.1 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect
|
||||||
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/exoscale/egoscale => github.com/exoscale/egoscale v0.102.3
|
||||||
|
13
main.go
Normal file
13
main.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,91 +0,0 @@
|
|||||||
Log:
|
|
||||||
Level: warning # Log level: none, error, warning, info, debug
|
|
||||||
AccessPath: # /etc/XrayR/access.Log
|
|
||||||
ErrorPath: # /etc/XrayR/error.log
|
|
||||||
DnsConfigPath: # /etc/XrayR/dns.json # Path to dns config, check https://xtls.github.io/config/dns.html for help
|
|
||||||
RouteConfigPath: # /etc/XrayR/route.json # Path to route config, check https://xtls.github.io/config/routing.html for help
|
|
||||||
InboundConfigPath: # /etc/XrayR/custom_inbound.json # Path to custom inbound config, check https://xtls.github.io/config/inbound.html for help
|
|
||||||
OutboundConfigPath: # /etc/XrayR/custom_outbound.json # Path to custom outbound config, check https://xtls.github.io/config/outbound.html for help
|
|
||||||
ConnectionConfig:
|
|
||||||
Handshake: 4 # Handshake time limit, Second
|
|
||||||
ConnIdle: 30 # Connection idle time limit, Second
|
|
||||||
UplinkOnly: 2 # Time limit when the connection downstream is closed, Second
|
|
||||||
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
|
|
||||||
BufferSize: 64 # The internal cache size of each connection, kB
|
|
||||||
Nodes:
|
|
||||||
-
|
|
||||||
PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
|
||||||
ApiConfig:
|
|
||||||
ApiHost: "http://127.0.0.1:667"
|
|
||||||
ApiKey: "123"
|
|
||||||
NodeID: 41
|
|
||||||
NodeType: V2ray # Node type: V2ray, Shadowsocks, Trojan, Shadowsocks-Plugin
|
|
||||||
Timeout: 30 # Timeout for the api request
|
|
||||||
EnableVless: false # Enable Vless for V2ray Type
|
|
||||||
EnableXTLS: false # Enable XTLS for V2ray and Trojan
|
|
||||||
SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable
|
|
||||||
DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable
|
|
||||||
RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file
|
|
||||||
ControllerConfig:
|
|
||||||
ListenIP: 0.0.0.0 # IP address you want to listen
|
|
||||||
SendIP: 0.0.0.0 # IP address you want to send pacakage
|
|
||||||
UpdatePeriodic: 60 # Time to update the nodeinfo, how many sec.
|
|
||||||
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
|
||||||
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
|
||||||
EnableProxyProtocol: false # Only works for WebSocket and TCP
|
|
||||||
AutoSpeedLimitConfig:
|
|
||||||
Limit: 0 # Warned speed. Set to 0 to disable AutoSpeedLimit (mbps)
|
|
||||||
WarnTimes: 0 # After (WarnTimes) consecutive warnings, the user will be limited. Set to 0 to punish overspeed user immediately.
|
|
||||||
LimitSpeed: 0 # The speedlimit of a limited user (unit: mbps)
|
|
||||||
LimitDuration: 0 # How many minutes will the limiting last (unit: minute)
|
|
||||||
GlobalDeviceLimitConfig:
|
|
||||||
Enable: false # Enable the global device limit of a user
|
|
||||||
RedisAddr: 127.0.0.1:6379 # The redis server address
|
|
||||||
RedisPassword: YOUR PASSWORD # Redis password
|
|
||||||
RedisDB: 0 # Redis DB
|
|
||||||
Timeout: 5 # Timeout for redis request
|
|
||||||
Expiry: 60 # Expiry time (second)
|
|
||||||
EnableFallback: false # Only support for Trojan and Vless
|
|
||||||
FallBackConfigs: # Support multiple fallbacks
|
|
||||||
-
|
|
||||||
SNI: # TLS SNI(Server Name Indication), Empty for any
|
|
||||||
Alpn: # Alpn, Empty for any
|
|
||||||
Path: # HTTP PATH, Empty for any
|
|
||||||
Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
|
|
||||||
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable
|
|
||||||
CertConfig:
|
|
||||||
CertMode: dns # Option about how to get certificate: none, file, http, tls, dns. Choose "none" will forcedly disable the tls config.
|
|
||||||
CertDomain: "node1.test.com" # Domain to cert
|
|
||||||
CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
|
||||||
KeyFile: /etc/XrayR/cert/node1.test.com.key
|
|
||||||
Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
|
|
||||||
Email: test@me.com
|
|
||||||
DNSEnv: # DNS ENV option used by DNS provider
|
|
||||||
ALICLOUD_ACCESS_KEY: aaa
|
|
||||||
ALICLOUD_SECRET_KEY: bbb
|
|
||||||
# -
|
|
||||||
# PanelType: "NewV2board" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
|
||||||
# ApiConfig:
|
|
||||||
# ApiHost: "http://127.0.0.1:668"
|
|
||||||
# ApiKey: "123"
|
|
||||||
# NodeID: 4
|
|
||||||
# NodeType: Shadowsocks # Node type: V2ray, Shadowsocks, Trojan
|
|
||||||
# Timeout: 30 # Timeout for the api request
|
|
||||||
# EnableVless: false # Enable Vless for V2ray Type
|
|
||||||
# EnableXTLS: false # Enable XTLS for V2ray and Trojan
|
|
||||||
# SpeedLimit: 0 # Mbps, Local settings will replace remote settings
|
|
||||||
# DeviceLimit: 0 # Local settings will replace remote settings
|
|
||||||
# ControllerConfig:
|
|
||||||
# ListenIP: 0.0.0.0 # IP address you want to listen
|
|
||||||
# UpdatePeriodic: 10 # Time to update the nodeinfo, how many sec.
|
|
||||||
# EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
|
||||||
# CertConfig:
|
|
||||||
# CertMode: dns # Option about how to get certificate: none, file, http, dns
|
|
||||||
# CertDomain: "node1.test.com" # Domain to cert
|
|
||||||
# CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
|
||||||
# KeyFile: /etc/XrayR/cert/node1.test.com.pem
|
|
||||||
# Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
|
|
||||||
# Email: test@me.com
|
|
||||||
# DNSEnv: # DNS ENV option used by DNS provider
|
|
||||||
# ALICLOUD_ACCESS_KEY: aaa
|
|
||||||
# ALICLOUD_SECRET_KEY: bbb
|
|
@@ -2,15 +2,12 @@ package panel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api/newV2board"
|
"dario.cat/mergo"
|
||||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
"github.com/r3labs/diff/v2"
|
"github.com/r3labs/diff/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
"github.com/xtls/xray-core/app/stats"
|
"github.com/xtls/xray-core/app/stats"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
@@ -18,12 +15,15 @@ import (
|
|||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/api/bunpanel"
|
||||||
|
"github.com/XrayR-project/XrayR/api/gov2panel"
|
||||||
|
"github.com/XrayR-project/XrayR/api/newV2board"
|
||||||
"github.com/XrayR-project/XrayR/api/pmpanel"
|
"github.com/XrayR-project/XrayR/api/pmpanel"
|
||||||
"github.com/XrayR-project/XrayR/api/proxypanel"
|
"github.com/XrayR-project/XrayR/api/proxypanel"
|
||||||
"github.com/XrayR-project/XrayR/api/sspanel"
|
"github.com/XrayR-project/XrayR/api/sspanel"
|
||||||
"github.com/XrayR-project/XrayR/api/v2board"
|
|
||||||
"github.com/XrayR-project/XrayR/api/v2raysocks"
|
"github.com/XrayR-project/XrayR/api/v2raysocks"
|
||||||
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||||
|
_ "github.com/XrayR-project/XrayR/cmd/distro/all"
|
||||||
"github.com/XrayR-project/XrayR/service"
|
"github.com/XrayR-project/XrayR/service"
|
||||||
"github.com/XrayR-project/XrayR/service/controller"
|
"github.com/XrayR-project/XrayR/service/controller"
|
||||||
)
|
)
|
||||||
@@ -66,10 +66,17 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init controller's DNS config
|
||||||
|
// for _, config := range p.panelConfig.NodesConfig {
|
||||||
|
// config.ControllerConfig.DNSConfig = coreDnsConfig
|
||||||
|
// }
|
||||||
|
|
||||||
dnsConfig, err := coreDnsConfig.Build()
|
dnsConfig, err := coreDnsConfig.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help: %s", err)
|
log.Panicf("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routing config
|
// Routing config
|
||||||
coreRouterConfig := &conf.RouterConfig{}
|
coreRouterConfig := &conf.RouterConfig{}
|
||||||
if panelConfig.RouteConfigPath != "" {
|
if panelConfig.RouteConfigPath != "" {
|
||||||
@@ -147,7 +154,6 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("failed to create instance: %s", err)
|
log.Panicf("failed to create instance: %s", err)
|
||||||
}
|
}
|
||||||
log.Printf("Xray Core Version: %s", core.Version())
|
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
@@ -170,10 +176,7 @@ func (p *Panel) Start() {
|
|||||||
switch nodeConfig.PanelType {
|
switch nodeConfig.PanelType {
|
||||||
case "SSpanel":
|
case "SSpanel":
|
||||||
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
||||||
// todo Deprecated after 2023.6.1
|
case "NewV2board", "V2board":
|
||||||
case "V2board":
|
|
||||||
apiClient = v2board.New(nodeConfig.ApiConfig)
|
|
||||||
case "NewV2board":
|
|
||||||
apiClient = newV2board.New(nodeConfig.ApiConfig)
|
apiClient = newV2board.New(nodeConfig.ApiConfig)
|
||||||
case "PMpanel":
|
case "PMpanel":
|
||||||
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
||||||
@@ -181,6 +184,10 @@ func (p *Panel) Start() {
|
|||||||
apiClient = proxypanel.New(nodeConfig.ApiConfig)
|
apiClient = proxypanel.New(nodeConfig.ApiConfig)
|
||||||
case "V2RaySocks":
|
case "V2RaySocks":
|
||||||
apiClient = v2raysocks.New(nodeConfig.ApiConfig)
|
apiClient = v2raysocks.New(nodeConfig.ApiConfig)
|
||||||
|
case "GoV2Panel":
|
||||||
|
apiClient = gov2panel.New(nodeConfig.ApiConfig)
|
||||||
|
case "BunPanel":
|
||||||
|
apiClient = bunpanel.New(nodeConfig.ApiConfig)
|
||||||
default:
|
default:
|
||||||
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
|
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
|
||||||
}
|
}
|
||||||
@@ -201,7 +208,7 @@ func (p *Panel) Start() {
|
|||||||
for _, s := range p.Service {
|
for _, s := range p.Service {
|
||||||
err := s.Start()
|
err := s.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Panel Start fialed: %s", err)
|
log.Panicf("Panel Start failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Running = true
|
p.Running = true
|
||||||
@@ -215,7 +222,7 @@ func (p *Panel) Close() {
|
|||||||
for _, s := range p.Service {
|
for _, s := range p.Service {
|
||||||
err := s.Close()
|
err := s.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Panel Close fialed: %s", err)
|
log.Panicf("Panel Close failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Service = nil
|
p.Service = nil
|
||||||
|
144
release/config/config.yml.example
Normal file
144
release/config/config.yml.example
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
Log:
|
||||||
|
Level: warning # Log level: none, error, warning, info, debug
|
||||||
|
AccessPath: # /etc/XrayR/access.Log
|
||||||
|
ErrorPath: # /etc/XrayR/error.log
|
||||||
|
DnsConfigPath: # /etc/XrayR/dns.json # Path to dns config, check https://xtls.github.io/config/dns.html for help
|
||||||
|
RouteConfigPath: # /etc/XrayR/route.json # Path to route config, check https://xtls.github.io/config/routing.html for help
|
||||||
|
InboundConfigPath: # /etc/XrayR/custom_inbound.json # Path to custom inbound config, check https://xtls.github.io/config/inbound.html for help
|
||||||
|
OutboundConfigPath: # /etc/XrayR/custom_outbound.json # Path to custom outbound config, check https://xtls.github.io/config/outbound.html for help
|
||||||
|
ConnectionConfig:
|
||||||
|
Handshake: 4 # Handshake time limit, Second
|
||||||
|
ConnIdle: 30 # Connection idle time limit, Second
|
||||||
|
UplinkOnly: 2 # Time limit when the connection downstream is closed, Second
|
||||||
|
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
|
||||||
|
BufferSize: 64 # The internal cache size of each connection, kB
|
||||||
|
Nodes:
|
||||||
|
- PanelType: "SSpanel" # Panel type: SSpanel, NewV2board, PMpanel, Proxypanel, V2RaySocks, GoV2Panel, BunPanel
|
||||||
|
ApiConfig:
|
||||||
|
ApiHost: "http://127.0.0.1:667"
|
||||||
|
ApiKey: "123"
|
||||||
|
NodeID: 41
|
||||||
|
NodeType: V2ray # Node type: V2ray, Vmess, Vless, Shadowsocks, Trojan, Shadowsocks-Plugin
|
||||||
|
Timeout: 30 # Timeout for the api request
|
||||||
|
EnableVless: false # Enable Vless for V2ray Type
|
||||||
|
VlessFlow: "xtls-rprx-vision" # Only support vless
|
||||||
|
SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable
|
||||||
|
DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable
|
||||||
|
RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file
|
||||||
|
DisableCustomConfig: false # disable custom config for sspanel
|
||||||
|
ControllerConfig:
|
||||||
|
ListenIP: 0.0.0.0 # IP address you want to listen
|
||||||
|
SendIP: 0.0.0.0 # IP address you want to send pacakage
|
||||||
|
UpdatePeriodic: 60 # Time to update the nodeinfo, how many sec.
|
||||||
|
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
||||||
|
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
||||||
|
EnableProxyProtocol: false # Only works for WebSocket and TCP
|
||||||
|
AutoSpeedLimitConfig:
|
||||||
|
Limit: 0 # Warned speed. Set to 0 to disable AutoSpeedLimit (mbps)
|
||||||
|
WarnTimes: 0 # After (WarnTimes) consecutive warnings, the user will be limited. Set to 0 to punish overspeed user immediately.
|
||||||
|
LimitSpeed: 0 # The speedlimit of a limited user (unit: mbps)
|
||||||
|
LimitDuration: 0 # How many minutes will the limiting last (unit: minute)
|
||||||
|
GlobalDeviceLimitConfig:
|
||||||
|
Enable: false # Enable the global device limit of a user
|
||||||
|
RedisNetwork: tcp # Redis protocol, tcp or unix
|
||||||
|
RedisAddr: 127.0.0.1:6379 # Redis server address, or unix socket path
|
||||||
|
RedisUsername: # Redis username
|
||||||
|
RedisPassword: YOUR PASSWORD # Redis password
|
||||||
|
RedisDB: 0 # Redis DB
|
||||||
|
Timeout: 5 # Timeout for redis request
|
||||||
|
Expiry: 60 # Expiry time (second)
|
||||||
|
EnableFallback: false # Only support for Trojan and Vless
|
||||||
|
FallBackConfigs: # Support multiple fallbacks
|
||||||
|
- SNI: # TLS SNI(Server Name Indication), Empty for any
|
||||||
|
Alpn: # Alpn, Empty for any
|
||||||
|
Path: # HTTP PATH, Empty for any
|
||||||
|
Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
|
||||||
|
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||||
|
DisableLocalREALITYConfig: false # disable local reality config
|
||||||
|
EnableREALITY: false # Enable REALITY
|
||||||
|
REALITYConfigs:
|
||||||
|
Show: true # Show REALITY debug
|
||||||
|
Dest: www.amazon.com:443 # Required, Same as fallback
|
||||||
|
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||||
|
ServerNames: # Required, list of available serverNames for the client, * wildcard is not supported at the moment.
|
||||||
|
- www.amazon.com
|
||||||
|
PrivateKey: YOUR_PRIVATE_KEY # Required, execute './XrayR x25519' to generate.
|
||||||
|
MinClientVer: # Optional, minimum version of Xray client, format is x.y.z.
|
||||||
|
MaxClientVer: # Optional, maximum version of Xray client, format is x.y.z.
|
||||||
|
MaxTimeDiff: 0 # Optional, maximum allowed time difference, unit is in milliseconds.
|
||||||
|
ShortIds: # Required, list of available shortIds for the client, can be used to differentiate between different clients.
|
||||||
|
- ""
|
||||||
|
- 0123456789abcdef
|
||||||
|
CertConfig:
|
||||||
|
CertMode: dns # Option about how to get certificate: none, file, http, tls, dns. Choose "none" will forcedly disable the tls config.
|
||||||
|
CertDomain: "node1.test.com" # Domain to cert
|
||||||
|
CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
||||||
|
KeyFile: /etc/XrayR/cert/node1.test.com.key
|
||||||
|
Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
|
||||||
|
Email: test@me.com
|
||||||
|
DNSEnv: # DNS ENV option used by DNS provider
|
||||||
|
ALICLOUD_ACCESS_KEY: aaa
|
||||||
|
ALICLOUD_SECRET_KEY: bbb
|
||||||
|
|
||||||
|
# - PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks, GoV2Panel
|
||||||
|
# ApiConfig:
|
||||||
|
# ApiHost: "http://127.0.0.1:668"
|
||||||
|
# ApiKey: "123"
|
||||||
|
# NodeID: 41
|
||||||
|
# NodeType: V2ray # Node type: V2ray, Shadowsocks, Trojan, Shadowsocks-Plugin
|
||||||
|
# Timeout: 30 # Timeout for the api request
|
||||||
|
# EnableVless: false # Enable Vless for V2ray Type
|
||||||
|
# VlessFlow: "xtls-rprx-vision" # Only support vless
|
||||||
|
# SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable
|
||||||
|
# DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable
|
||||||
|
# RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file
|
||||||
|
# ControllerConfig:
|
||||||
|
# ListenIP: 0.0.0.0 # IP address you want to listen
|
||||||
|
# SendIP: 0.0.0.0 # IP address you want to send pacakage
|
||||||
|
# UpdatePeriodic: 60 # Time to update the nodeinfo, how many sec.
|
||||||
|
# EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
||||||
|
# DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
||||||
|
# EnableProxyProtocol: false # Only works for WebSocket and TCP
|
||||||
|
# AutoSpeedLimitConfig:
|
||||||
|
# Limit: 0 # Warned speed. Set to 0 to disable AutoSpeedLimit (mbps)
|
||||||
|
# WarnTimes: 0 # After (WarnTimes) consecutive warnings, the user will be limited. Set to 0 to punish overspeed user immediately.
|
||||||
|
# LimitSpeed: 0 # The speedlimit of a limited user (unit: mbps)
|
||||||
|
# LimitDuration: 0 # How many minutes will the limiting last (unit: minute)
|
||||||
|
# GlobalDeviceLimitConfig:
|
||||||
|
# Enable: false # Enable the global device limit of a user
|
||||||
|
# RedisAddr: 127.0.0.1:6379 # The redis server address
|
||||||
|
# RedisPassword: YOUR PASSWORD # Redis password
|
||||||
|
# RedisDB: 0 # Redis DB
|
||||||
|
# Timeout: 5 # Timeout for redis request
|
||||||
|
# Expiry: 60 # Expiry time (second)
|
||||||
|
# EnableFallback: false # Only support for Trojan and Vless
|
||||||
|
# FallBackConfigs: # Support multiple fallbacks
|
||||||
|
# - SNI: # TLS SNI(Server Name Indication), Empty for any
|
||||||
|
# Alpn: # Alpn, Empty for any
|
||||||
|
# Path: # HTTP PATH, Empty for any
|
||||||
|
# Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
|
||||||
|
# ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||||
|
# EnableREALITY: true # Enable REALITY
|
||||||
|
# REALITYConfigs:
|
||||||
|
# Show: true # Show REALITY debug
|
||||||
|
# Dest: www.amazon.com:443 # Required, Same as fallback
|
||||||
|
# ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||||
|
# ServerNames: # Required, list of available serverNames for the client, * wildcard is not supported at the moment.
|
||||||
|
# - www.amazon.com
|
||||||
|
# PrivateKey: YOUR_PRIVATE_KEY # Required, execute './XrayR x25519' to generate.
|
||||||
|
# MinClientVer: # Optional, minimum version of Xray client, format is x.y.z.
|
||||||
|
# MaxClientVer: # Optional, maximum version of Xray client, format is x.y.z.
|
||||||
|
# MaxTimeDiff: 0 # Optional, maximum allowed time difference, unit is in milliseconds.
|
||||||
|
# ShortIds: # Required, list of available shortIds for the client, can be used to differentiate between different clients.
|
||||||
|
# - ""
|
||||||
|
# - 0123456789abcdef
|
||||||
|
# CertConfig:
|
||||||
|
# CertMode: dns # Option about how to get certificate: none, file, http, tls, dns. Choose "none" will forcedly disable the tls config.
|
||||||
|
# CertDomain: "node1.test.com" # Domain to cert
|
||||||
|
# CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
||||||
|
# KeyFile: /etc/XrayR/cert/node1.test.com.key
|
||||||
|
# Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
|
||||||
|
# Email: test@me.com
|
||||||
|
# DNSEnv: # DNS ENV option used by DNS provider
|
||||||
|
# ALICLOUD_ACCESS_KEY: aaa
|
||||||
|
# ALICLOUD_SECRET_KEY: bbb
|
@@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"listen": "0.0.0.0",
|
"listen": "127.0.0.1",
|
||||||
"port": 1234,
|
"port": 1234,
|
||||||
"protocol": "socks",
|
"protocol": "socks",
|
||||||
"settings": {
|
"settings": {
|
@@ -6,21 +6,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ListenIP string `mapstructure:"ListenIP"`
|
ListenIP string `mapstructure:"ListenIP"`
|
||||||
SendIP string `mapstructure:"SendIP"`
|
SendIP string `mapstructure:"SendIP"`
|
||||||
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
||||||
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
|
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
|
||||||
EnableDNS bool `mapstructure:"EnableDNS"`
|
EnableDNS bool `mapstructure:"EnableDNS"`
|
||||||
DNSType string `mapstructure:"DNSType"`
|
DNSType string `mapstructure:"DNSType"`
|
||||||
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
||||||
DisableGetRule bool `mapstructure:"DisableGetRule"`
|
DisableGetRule bool `mapstructure:"DisableGetRule"`
|
||||||
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
|
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
|
||||||
EnableFallback bool `mapstructure:"EnableFallback"`
|
EnableFallback bool `mapstructure:"EnableFallback"`
|
||||||
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
||||||
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
||||||
AutoSpeedLimitConfig *AutoSpeedLimitConfig `mapstructure:"AutoSpeedLimitConfig"`
|
AutoSpeedLimitConfig *AutoSpeedLimitConfig `mapstructure:"AutoSpeedLimitConfig"`
|
||||||
GlobalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig `mapstructure:"GlobalDeviceLimitConfig"`
|
GlobalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig `mapstructure:"GlobalDeviceLimitConfig"`
|
||||||
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
||||||
|
DisableLocalREALITYConfig bool `mapstructure:"DisableLocalREALITYConfig"`
|
||||||
|
EnableREALITY bool `mapstructure:"EnableREALITY"`
|
||||||
|
REALITYConfigs *REALITYConfig `mapstructure:"REALITYConfigs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutoSpeedLimitConfig struct {
|
type AutoSpeedLimitConfig struct {
|
||||||
@@ -37,3 +40,15 @@ type FallBackConfig struct {
|
|||||||
Dest string `mapstructure:"Dest"`
|
Dest string `mapstructure:"Dest"`
|
||||||
ProxyProtocolVer uint64 `mapstructure:"ProxyProtocolVer"`
|
ProxyProtocolVer uint64 `mapstructure:"ProxyProtocolVer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type REALITYConfig struct {
|
||||||
|
Show bool `mapstructure:"Show"`
|
||||||
|
Dest string `mapstructure:"Dest"`
|
||||||
|
ProxyProtocolVer uint64 `mapstructure:"ProxyProtocolVer"`
|
||||||
|
ServerNames []string `mapstructure:"ServerNames"`
|
||||||
|
PrivateKey string `mapstructure:"PrivateKey"`
|
||||||
|
MinClientVer string `mapstructure:"MinClientVer"`
|
||||||
|
MaxClientVer string `mapstructure:"MaxClientVer"`
|
||||||
|
MaxTimeDiff uint64 `mapstructure:"MaxTimeDiff"`
|
||||||
|
ShortIds []string `mapstructure:"ShortIds"`
|
||||||
|
}
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/task"
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
@@ -43,6 +44,7 @@ type Controller struct {
|
|||||||
stm stats.Manager
|
stm stats.Manager
|
||||||
dispatcher *mydispatcher.DefaultDispatcher
|
dispatcher *mydispatcher.DefaultDispatcher
|
||||||
startAt time.Time
|
startAt time.Time
|
||||||
|
logger *log.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
type periodicTask struct {
|
type periodicTask struct {
|
||||||
@@ -52,6 +54,11 @@ type periodicTask struct {
|
|||||||
|
|
||||||
// New return a Controller service with default parameters.
|
// New return a Controller service with default parameters.
|
||||||
func New(server *core.Instance, api api.API, config *Config, panelType string) *Controller {
|
func New(server *core.Instance, api api.API, config *Config, panelType string) *Controller {
|
||||||
|
logger := log.NewEntry(log.StandardLogger()).WithFields(log.Fields{
|
||||||
|
"Host": api.Describe().APIHost,
|
||||||
|
"Type": api.Describe().NodeType,
|
||||||
|
"ID": api.Describe().NodeID,
|
||||||
|
})
|
||||||
controller := &Controller{
|
controller := &Controller{
|
||||||
server: server,
|
server: server,
|
||||||
config: config,
|
config: config,
|
||||||
@@ -62,6 +69,7 @@ func New(server *core.Instance, api api.API, config *Config, panelType string) *
|
|||||||
stm: server.GetFeature(stats.ManagerType()).(stats.Manager),
|
stm: server.GetFeature(stats.ManagerType()).(stats.Manager),
|
||||||
dispatcher: server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher),
|
dispatcher: server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher),
|
||||||
startAt: time.Now(),
|
startAt: time.Now(),
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
@@ -75,12 +83,16 @@ func (c *Controller) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if newNodeInfo.Port == 0 {
|
||||||
|
return errors.New("server port must > 0")
|
||||||
|
}
|
||||||
c.nodeInfo = newNodeInfo
|
c.nodeInfo = newNodeInfo
|
||||||
c.Tag = c.buildNodeTag()
|
c.Tag = c.buildNodeTag()
|
||||||
|
|
||||||
// Add new tag
|
// Add new tag
|
||||||
err = c.addNewTag(newNodeInfo)
|
err = c.addNewTag(newNodeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
c.logger.Panic(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Update user
|
// Update user
|
||||||
@@ -99,16 +111,16 @@ func (c *Controller) Start() error {
|
|||||||
|
|
||||||
// Add Limiter
|
// Add Limiter
|
||||||
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, userInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, userInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Rule Manager
|
// Add Rule Manager
|
||||||
if !c.config.DisableGetRule {
|
if !c.config.DisableGetRule {
|
||||||
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
||||||
log.Printf("Get rule list filed: %s", err)
|
c.logger.Printf("Get rule list filed: %s", err)
|
||||||
} else if len(*ruleList) > 0 {
|
} else if len(*ruleList) > 0 {
|
||||||
if err := c.UpdateRule(c.Tag, *ruleList); err != nil {
|
if err := c.UpdateRule(c.Tag, *ruleList); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +151,7 @@ func (c *Controller) Start() error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Check cert service in need
|
// Check cert service in need
|
||||||
if c.nodeInfo.EnableTLS {
|
if c.nodeInfo.EnableTLS && c.config.EnableREALITY == false {
|
||||||
c.tasks = append(c.tasks, periodicTask{
|
c.tasks = append(c.tasks, periodicTask{
|
||||||
tag: "cert monitor",
|
tag: "cert monitor",
|
||||||
Periodic: &task.Periodic{
|
Periodic: &task.Periodic{
|
||||||
@@ -150,7 +162,7 @@ func (c *Controller) Start() error {
|
|||||||
|
|
||||||
// Start periodic tasks
|
// Start periodic tasks
|
||||||
for i := range c.tasks {
|
for i := range c.tasks {
|
||||||
log.Printf("%s Start %s periodic task", c.logPrefix(), c.tasks[i].tag)
|
c.logger.Printf("Start %s periodic task", c.tasks[i].tag)
|
||||||
go c.tasks[i].Start()
|
go c.tasks[i].Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +174,7 @@ func (c *Controller) Close() error {
|
|||||||
for i := range c.tasks {
|
for i := range c.tasks {
|
||||||
if c.tasks[i].Periodic != nil {
|
if c.tasks[i].Periodic != nil {
|
||||||
if err := c.tasks[i].Periodic.Close(); err != nil {
|
if err := c.tasks[i].Periodic.Close(); err != nil {
|
||||||
log.Panicf("%s %s periodic task close failed: %s", c.logPrefix(), c.tasks[i].tag, err)
|
c.logger.Panicf("%s periodic task close failed: %s", c.tasks[i].tag, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,65 +189,79 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// First fetch Node Info
|
// First fetch Node Info
|
||||||
|
var nodeInfoChanged = true
|
||||||
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
if err.Error() == api.NodeNotModified {
|
||||||
return nil
|
nodeInfoChanged = false
|
||||||
|
newNodeInfo = c.nodeInfo
|
||||||
|
} else {
|
||||||
|
c.logger.Print(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newNodeInfo.Port == 0 {
|
||||||
|
return errors.New("server port must > 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update User
|
// Update User
|
||||||
var usersChanged = true
|
var usersChanged = true
|
||||||
newUserInfo, err := c.apiClient.GetUserList()
|
newUserInfo, err := c.apiClient.GetUserList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "users no change" {
|
if err.Error() == api.UserNotModified {
|
||||||
usersChanged = false
|
usersChanged = false
|
||||||
newUserInfo = c.userList
|
newUserInfo = c.userList
|
||||||
} else {
|
} else {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeInfoChanged = false
|
|
||||||
// If nodeInfo changed
|
// If nodeInfo changed
|
||||||
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
if nodeInfoChanged {
|
||||||
// Remove old tag
|
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
||||||
oldTag := c.Tag
|
// Remove old tag
|
||||||
err := c.removeOldTag(oldTag)
|
oldTag := c.Tag
|
||||||
if err != nil {
|
err := c.removeOldTag(oldTag)
|
||||||
log.Print(err)
|
if err != nil {
|
||||||
return nil
|
c.logger.Print(err)
|
||||||
}
|
return nil
|
||||||
if c.nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
}
|
||||||
err = c.removeOldTag(fmt.Sprintf("dokodemo-door_%s+1", c.Tag))
|
if c.nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||||
}
|
err = c.removeOldTag(fmt.Sprintf("dokodemo-door_%s+1", c.Tag))
|
||||||
if err != nil {
|
}
|
||||||
log.Print(err)
|
if err != nil {
|
||||||
return nil
|
c.logger.Print(err)
|
||||||
}
|
return nil
|
||||||
// Add new tag
|
}
|
||||||
c.nodeInfo = newNodeInfo
|
// Add new tag
|
||||||
c.Tag = c.buildNodeTag()
|
c.nodeInfo = newNodeInfo
|
||||||
err = c.addNewTag(newNodeInfo)
|
c.Tag = c.buildNodeTag()
|
||||||
if err != nil {
|
err = c.addNewTag(newNodeInfo)
|
||||||
log.Print(err)
|
if err != nil {
|
||||||
return nil
|
c.logger.Print(err)
|
||||||
}
|
return nil
|
||||||
nodeInfoChanged = true
|
}
|
||||||
// Remove Old limiter
|
nodeInfoChanged = true
|
||||||
if err = c.DeleteInboundLimiter(oldTag); err != nil {
|
// Remove Old limiter
|
||||||
log.Print(err)
|
if err = c.DeleteInboundLimiter(oldTag); err != nil {
|
||||||
return nil
|
c.logger.Print(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodeInfoChanged = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Rule
|
// Check Rule
|
||||||
if !c.config.DisableGetRule {
|
if !c.config.DisableGetRule {
|
||||||
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
||||||
log.Printf("Get rule list filed: %s", err)
|
if err.Error() != api.RuleNotModified {
|
||||||
|
c.logger.Printf("Get rule list filed: %s", err)
|
||||||
|
}
|
||||||
} else if len(*ruleList) > 0 {
|
} else if len(*ruleList) > 0 {
|
||||||
if err := c.UpdateRule(c.Tag, *ruleList); err != nil {
|
if err := c.UpdateRule(c.Tag, *ruleList); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,15 +269,16 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
if nodeInfoChanged {
|
if nodeInfoChanged {
|
||||||
err = c.addNewUser(newUserInfo, newNodeInfo)
|
err = c.addNewUser(newUserInfo, newNodeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Limiter
|
// Add Limiter
|
||||||
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, newUserInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, newUserInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
var deleted, added []api.UserInfo
|
var deleted, added []api.UserInfo
|
||||||
if usersChanged {
|
if usersChanged {
|
||||||
@@ -263,21 +290,21 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
err := c.removeUsers(deletedEmail, c.Tag)
|
err := c.removeUsers(deletedEmail, c.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(added) > 0 {
|
if len(added) > 0 {
|
||||||
err = c.addNewUser(&added, c.nodeInfo)
|
err = c.addNewUser(&added, c.nodeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
// Update Limiter
|
// Update Limiter
|
||||||
if err := c.UpdateInboundLimiter(c.Tag, &added); err != nil {
|
if err := c.UpdateInboundLimiter(c.Tag, &added); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("%s %d user deleted, %d user added", c.logPrefix(), len(deleted), len(added))
|
c.logger.Printf("%d user deleted, %d user added", len(deleted), len(added))
|
||||||
}
|
}
|
||||||
c.userList = newUserInfo
|
c.userList = newUserInfo
|
||||||
return nil
|
return nil
|
||||||
@@ -378,18 +405,11 @@ func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error)
|
|||||||
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
|
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
|
||||||
users := make([]*protocol.User, 0)
|
users := make([]*protocol.User, 0)
|
||||||
switch nodeInfo.NodeType {
|
switch nodeInfo.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
if nodeInfo.EnableVless {
|
if nodeInfo.EnableVless || (nodeInfo.NodeType == "Vless" && nodeInfo.NodeType != "Vmess") {
|
||||||
users = c.buildVlessUser(userInfo)
|
users = c.buildVlessUser(userInfo)
|
||||||
} else {
|
} else {
|
||||||
var alterID uint16 = 0
|
users = c.buildVmessUser(userInfo)
|
||||||
if (c.panelType == "V2board" || c.panelType == "V2RaySocks") && len(*userInfo) > 0 {
|
|
||||||
// use latest userInfo
|
|
||||||
alterID = (*userInfo)[0].AlterID
|
|
||||||
} else {
|
|
||||||
alterID = nodeInfo.AlterID
|
|
||||||
}
|
|
||||||
users = c.buildVmessUser(userInfo, alterID)
|
|
||||||
}
|
}
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
users = c.buildTrojanUser(userInfo)
|
users = c.buildTrojanUser(userInfo)
|
||||||
@@ -405,7 +425,7 @@ func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("%s Added %d new users", c.logPrefix(), len(*userInfo))
|
c.logger.Printf("Added %d new users", len(*userInfo))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +473,7 @@ func limitUser(c *Controller, user api.UserInfo, silentUsers *[]api.UserInfo) {
|
|||||||
currentSpeedLimit: c.config.AutoSpeedLimitConfig.LimitSpeed,
|
currentSpeedLimit: c.config.AutoSpeedLimitConfig.LimitSpeed,
|
||||||
originSpeedLimit: user.SpeedLimit,
|
originSpeedLimit: user.SpeedLimit,
|
||||||
}
|
}
|
||||||
log.Printf("Limit User: %s Speed: %d End: %s", c.buildUserTag(&user), c.config.AutoSpeedLimitConfig.LimitSpeed, time.Unix(c.limitedUsers[user].end, 0).Format("01-02 15:04:05"))
|
c.logger.Printf("Limit User: %s Speed: %d End: %s", c.buildUserTag(&user), c.config.AutoSpeedLimitConfig.LimitSpeed, time.Unix(c.limitedUsers[user].end, 0).Format("01-02 15:04:05"))
|
||||||
user.SpeedLimit = uint64((c.config.AutoSpeedLimitConfig.LimitSpeed * 1000000) / 8)
|
user.SpeedLimit = uint64((c.config.AutoSpeedLimitConfig.LimitSpeed * 1000000) / 8)
|
||||||
*silentUsers = append(*silentUsers, user)
|
*silentUsers = append(*silentUsers, user)
|
||||||
}
|
}
|
||||||
@@ -467,7 +487,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
// Get server status
|
// Get server status
|
||||||
CPU, Mem, Disk, Uptime, err := serverstatus.GetSystemInfo()
|
CPU, Mem, Disk, Uptime, err := serverstatus.GetSystemInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
err = c.apiClient.ReportNodeStatus(
|
err = c.apiClient.ReportNodeStatus(
|
||||||
&api.NodeStatus{
|
&api.NodeStatus{
|
||||||
@@ -477,25 +497,25 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
Uptime: Uptime,
|
Uptime: Uptime,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
// Unlock users
|
// Unlock users
|
||||||
if c.config.AutoSpeedLimitConfig.Limit > 0 && len(c.limitedUsers) > 0 {
|
if c.config.AutoSpeedLimitConfig.Limit > 0 && len(c.limitedUsers) > 0 {
|
||||||
log.Printf("%s Limited users:", c.logPrefix())
|
c.logger.Printf("Limited users:")
|
||||||
toReleaseUsers := make([]api.UserInfo, 0)
|
toReleaseUsers := make([]api.UserInfo, 0)
|
||||||
for user, limitInfo := range c.limitedUsers {
|
for user, limitInfo := range c.limitedUsers {
|
||||||
if time.Now().Unix() > limitInfo.end {
|
if time.Now().Unix() > limitInfo.end {
|
||||||
user.SpeedLimit = limitInfo.originSpeedLimit
|
user.SpeedLimit = limitInfo.originSpeedLimit
|
||||||
toReleaseUsers = append(toReleaseUsers, user)
|
toReleaseUsers = append(toReleaseUsers, user)
|
||||||
log.Printf("User: %s Speed: %d End: nil (Unlimit)", c.buildUserTag(&user), user.SpeedLimit)
|
c.logger.Printf("User: %s Speed: %d End: nil (Unlimit)", c.buildUserTag(&user), user.SpeedLimit)
|
||||||
delete(c.limitedUsers, user)
|
delete(c.limitedUsers, user)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("User: %s Speed: %d End: %s", c.buildUserTag(&user), limitInfo.currentSpeedLimit, time.Unix(c.limitedUsers[user].end, 0).Format("01-02 15:04:05"))
|
c.logger.Printf("User: %s Speed: %d End: %s", c.buildUserTag(&user), limitInfo.currentSpeedLimit, time.Unix(c.limitedUsers[user].end, 0).Format("01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(toReleaseUsers) > 0 {
|
if len(toReleaseUsers) > 0 {
|
||||||
if err := c.UpdateInboundLimiter(c.Tag, &toReleaseUsers); err != nil {
|
if err := c.UpdateInboundLimiter(c.Tag, &toReleaseUsers); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,7 +566,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
if len(limitedUsers) > 0 {
|
if len(limitedUsers) > 0 {
|
||||||
if err := c.UpdateInboundLimiter(c.Tag, &limitedUsers); err != nil {
|
if err := c.UpdateInboundLimiter(c.Tag, &limitedUsers); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(userTraffic) > 0 {
|
if len(userTraffic) > 0 {
|
||||||
@@ -556,7 +576,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
// If report traffic error, not clear the traffic
|
// If report traffic error, not clear the traffic
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
} else {
|
} else {
|
||||||
c.resetTraffic(&upCounterList, &downCounterList)
|
c.resetTraffic(&upCounterList, &downCounterList)
|
||||||
}
|
}
|
||||||
@@ -564,23 +584,23 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
|
|
||||||
// Report Online info
|
// Report Online info
|
||||||
if onlineDevice, err := c.GetOnlineDevice(c.Tag); err != nil {
|
if onlineDevice, err := c.GetOnlineDevice(c.Tag); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
} else if len(*onlineDevice) > 0 {
|
} else if len(*onlineDevice) > 0 {
|
||||||
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("%s Report %d online users", c.logPrefix(), len(*onlineDevice))
|
c.logger.Printf("Report %d online users", len(*onlineDevice))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report Illegal user
|
// Report Illegal user
|
||||||
if detectResult, err := c.GetDetectResult(c.Tag); err != nil {
|
if detectResult, err := c.GetDetectResult(c.Tag); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
} else if len(*detectResult) > 0 {
|
} else if len(*detectResult) > 0 {
|
||||||
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("%s Report %d illegal behaviors", c.logPrefix(), len(*detectResult))
|
c.logger.Printf("Report %d illegal behaviors", len(*detectResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -591,23 +611,23 @@ func (c *Controller) buildNodeTag() string {
|
|||||||
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
|
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) logPrefix() string {
|
// func (c *Controller) logPrefix() string {
|
||||||
return fmt.Sprintf("[%s] %s(ID=%d)", c.clientInfo.APIHost, c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
// return fmt.Sprintf("[%s] %s(ID=%d)", c.clientInfo.APIHost, c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check Cert
|
// Check Cert
|
||||||
func (c *Controller) certMonitor() error {
|
func (c *Controller) certMonitor() error {
|
||||||
if c.nodeInfo.EnableTLS {
|
if c.nodeInfo.EnableTLS && c.config.EnableREALITY == false {
|
||||||
switch c.config.CertConfig.CertMode {
|
switch c.config.CertConfig.CertMode {
|
||||||
case "dns", "http", "tls":
|
case "dns", "http", "tls":
|
||||||
lego, err := mylego.New(c.config.CertConfig)
|
lego, err := mylego.New(c.config.CertConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
// Xray-core supports the OcspStapling certification hot renew
|
// Xray-core supports the OcspStapling certification hot renew
|
||||||
_, _, _, err = lego.RenewCert()
|
_, _, _, err = lego.RenewCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
c.logger.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,8 +13,8 @@ import (
|
|||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
"github.com/XrayR-project/XrayR/api/sspanel"
|
"github.com/XrayR-project/XrayR/api/sspanel"
|
||||||
|
_ "github.com/XrayR-project/XrayR/cmd/distro/all"
|
||||||
"github.com/XrayR-project/XrayR/common/mylego"
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
|
||||||
. "github.com/XrayR-project/XrayR/service/controller"
|
. "github.com/XrayR-project/XrayR/service/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -41,7 +41,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
// SniffingConfig
|
// SniffingConfig
|
||||||
sniffingConfig := &conf.SniffingConfig{
|
sniffingConfig := &conf.SniffingConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
DestOverride: &conf.StringList{"http", "tls"},
|
DestOverride: &conf.StringList{"http", "tls", "quic", "fakedns"},
|
||||||
}
|
}
|
||||||
if config.DisableSniffing {
|
if config.DisableSniffing {
|
||||||
sniffingConfig.Enabled = false
|
sniffingConfig.Enabled = false
|
||||||
@@ -57,8 +57,8 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
var proxySetting any
|
var proxySetting any
|
||||||
// Build Protocol and Protocol setting
|
// Build Protocol and Protocol setting
|
||||||
switch nodeInfo.NodeType {
|
switch nodeInfo.NodeType {
|
||||||
case "V2ray":
|
case "V2ray", "Vmess", "Vless":
|
||||||
if nodeInfo.EnableVless {
|
if nodeInfo.EnableVless || (nodeInfo.NodeType == "Vless" && nodeInfo.NodeType != "Vmess") {
|
||||||
protocol = "vless"
|
protocol = "vless"
|
||||||
// Enable fallback
|
// Enable fallback
|
||||||
if config.EnableFallback {
|
if config.EnableFallback {
|
||||||
@@ -139,7 +139,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
|
|
||||||
setting, err := json.Marshal(proxySetting)
|
setting, err := json.Marshal(proxySetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
return nil, fmt.Errorf("marshal proxy %s config failed: %s", nodeInfo.NodeType, err)
|
||||||
}
|
}
|
||||||
inboundDetourConfig.Protocol = protocol
|
inboundDetourConfig.Protocol = protocol
|
||||||
inboundDetourConfig.Settings = &setting
|
inboundDetourConfig.Settings = &setting
|
||||||
@@ -164,6 +164,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
headers["Host"] = nodeInfo.Host
|
headers["Host"] = nodeInfo.Host
|
||||||
wsSettings := &conf.WebSocketConfig{
|
wsSettings := &conf.WebSocketConfig{
|
||||||
AcceptProxyProtocol: config.EnableProxyProtocol,
|
AcceptProxyProtocol: config.EnableProxyProtocol,
|
||||||
|
Host: nodeInfo.Host,
|
||||||
Path: nodeInfo.Path,
|
Path: nodeInfo.Path,
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
}
|
}
|
||||||
@@ -171,40 +172,90 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
case "http":
|
case "http":
|
||||||
hosts := conf.StringList{nodeInfo.Host}
|
hosts := conf.StringList{nodeInfo.Host}
|
||||||
httpSettings := &conf.HTTPConfig{
|
httpSettings := &conf.HTTPConfig{
|
||||||
Host: &hosts,
|
Host: &hosts,
|
||||||
Path: nodeInfo.Path,
|
Path: nodeInfo.Path,
|
||||||
|
Method: nodeInfo.Method,
|
||||||
|
Headers: nodeInfo.HttpHeaders,
|
||||||
}
|
}
|
||||||
streamSetting.HTTPSettings = httpSettings
|
streamSetting.HTTPSettings = httpSettings
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpcSettings := &conf.GRPCConfig{
|
grpcSettings := &conf.GRPCConfig{
|
||||||
ServiceName: nodeInfo.ServiceName,
|
ServiceName: nodeInfo.ServiceName,
|
||||||
|
Authority: nodeInfo.Authority,
|
||||||
}
|
}
|
||||||
streamSetting.GRPCConfig = grpcSettings
|
streamSetting.GRPCConfig = grpcSettings
|
||||||
|
case "quic":
|
||||||
|
quicSettings := &conf.QUICConfig{
|
||||||
|
Header: nodeInfo.Header,
|
||||||
|
Security: nodeInfo.Security,
|
||||||
|
Key: nodeInfo.Key,
|
||||||
|
}
|
||||||
|
streamSetting.QUICSettings = quicSettings
|
||||||
|
case "httpupgrade":
|
||||||
|
httpupgradeSettings := &conf.HttpUpgradeConfig{
|
||||||
|
Headers: nodeInfo.Headers,
|
||||||
|
Path: nodeInfo.Path,
|
||||||
|
Host: nodeInfo.Host,
|
||||||
|
AcceptProxyProtocol: nodeInfo.AcceptProxyProtocol,
|
||||||
|
}
|
||||||
|
streamSetting.HTTPUPGRADESettings = httpupgradeSettings
|
||||||
|
case "splithttp":
|
||||||
|
splithttpSetting := &conf.SplitHTTPConfig{
|
||||||
|
Path: nodeInfo.Path,
|
||||||
|
Host: nodeInfo.Host,
|
||||||
|
}
|
||||||
|
streamSetting.SplitHTTPSettings = splithttpSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
streamSetting.Network = &transportProtocol
|
streamSetting.Network = &transportProtocol
|
||||||
|
|
||||||
// Build TLS and XTLS settings
|
// Build TLS and REALITY settings
|
||||||
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
var isREALITY bool
|
||||||
streamSetting.Security = nodeInfo.TLSType
|
if config.DisableLocalREALITYConfig {
|
||||||
|
if nodeInfo.REALITYConfig != nil && nodeInfo.EnableREALITY {
|
||||||
|
isREALITY = true
|
||||||
|
streamSetting.Security = "reality"
|
||||||
|
|
||||||
|
r := nodeInfo.REALITYConfig
|
||||||
|
streamSetting.REALITYSettings = &conf.REALITYConfig{
|
||||||
|
Show: config.REALITYConfigs.Show,
|
||||||
|
Dest: []byte(`"` + r.Dest + `"`),
|
||||||
|
Xver: r.ProxyProtocolVer,
|
||||||
|
ServerNames: r.ServerNames,
|
||||||
|
PrivateKey: r.PrivateKey,
|
||||||
|
MinClientVer: r.MinClientVer,
|
||||||
|
MaxClientVer: r.MaxClientVer,
|
||||||
|
MaxTimeDiff: r.MaxTimeDiff,
|
||||||
|
ShortIds: r.ShortIds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if config.EnableREALITY && config.REALITYConfigs != nil {
|
||||||
|
isREALITY = true
|
||||||
|
streamSetting.Security = "reality"
|
||||||
|
|
||||||
|
streamSetting.REALITYSettings = &conf.REALITYConfig{
|
||||||
|
Show: config.REALITYConfigs.Show,
|
||||||
|
Dest: []byte(`"` + config.REALITYConfigs.Dest + `"`),
|
||||||
|
Xver: config.REALITYConfigs.ProxyProtocolVer,
|
||||||
|
ServerNames: config.REALITYConfigs.ServerNames,
|
||||||
|
PrivateKey: config.REALITYConfigs.PrivateKey,
|
||||||
|
MinClientVer: config.REALITYConfigs.MinClientVer,
|
||||||
|
MaxClientVer: config.REALITYConfigs.MaxClientVer,
|
||||||
|
MaxTimeDiff: config.REALITYConfigs.MaxTimeDiff,
|
||||||
|
ShortIds: config.REALITYConfigs.ShortIds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isREALITY && nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
||||||
|
streamSetting.Security = "tls"
|
||||||
certFile, keyFile, err := getCertFile(config.CertConfig)
|
certFile, keyFile, err := getCertFile(config.CertConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if nodeInfo.TLSType == "tls" {
|
tlsSettings := &conf.TLSConfig{
|
||||||
tlsSettings := &conf.TLSConfig{
|
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
||||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
|
||||||
}
|
|
||||||
tlsSettings.Certs = append(tlsSettings.Certs, &conf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
|
|
||||||
|
|
||||||
streamSetting.TLSSettings = tlsSettings
|
|
||||||
} else if nodeInfo.TLSType == "xtls" {
|
|
||||||
xtlsSettings := &conf.XTLSConfig{
|
|
||||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
|
||||||
}
|
|
||||||
xtlsSettings.Certs = append(xtlsSettings.Certs, &conf.XTLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
|
|
||||||
streamSetting.XTLSSettings = xtlsSettings
|
|
||||||
}
|
}
|
||||||
|
tlsSettings.Certs = append(tlsSettings.Certs, &conf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
|
||||||
|
streamSetting.TLSSettings = tlsSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support ProxyProtocol for any transport protocol
|
// Support ProxyProtocol for any transport protocol
|
||||||
@@ -260,13 +311,13 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun
|
|||||||
for i, c := range fallbackConfigs {
|
for i, c := range fallbackConfigs {
|
||||||
|
|
||||||
if c.Dest == "" {
|
if c.Dest == "" {
|
||||||
return nil, fmt.Errorf("dest is required for fallback fialed")
|
return nil, fmt.Errorf("dest is required for fallback failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest json.RawMessage
|
var dest json.RawMessage
|
||||||
dest, err := json.Marshal(c.Dest)
|
dest, err := json.Marshal(c.Dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
return nil, fmt.Errorf("marshal dest %s config failed: %s", dest, err)
|
||||||
}
|
}
|
||||||
vlessFallBacks[i] = &conf.VLessInboundFallback{
|
vlessFallBacks[i] = &conf.VLessInboundFallback{
|
||||||
Name: c.SNI,
|
Name: c.SNI,
|
||||||
@@ -288,13 +339,13 @@ func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInbo
|
|||||||
for i, c := range fallbackConfigs {
|
for i, c := range fallbackConfigs {
|
||||||
|
|
||||||
if c.Dest == "" {
|
if c.Dest == "" {
|
||||||
return nil, fmt.Errorf("dest is required for fallback fialed")
|
return nil, fmt.Errorf("dest is required for fallback failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest json.RawMessage
|
var dest json.RawMessage
|
||||||
dest, err := json.Marshal(c.Dest)
|
dest, err := json.Marshal(c.Dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
return nil, fmt.Errorf("marshal dest %s config failed: %s", dest, err)
|
||||||
}
|
}
|
||||||
trojanFallBacks[i] = &conf.TrojanInboundFallback{
|
trojanFallBacks[i] = &conf.TrojanInboundFallback{
|
||||||
Name: c.SNI,
|
Name: c.SNI,
|
||||||
|
@@ -19,7 +19,6 @@ func TestBuildV2ray(t *testing.T) {
|
|||||||
Host: "test.test.tk",
|
Host: "test.test.tk",
|
||||||
Path: "v2ray",
|
Path: "v2ray",
|
||||||
EnableTLS: false,
|
EnableTLS: false,
|
||||||
TLSType: "tls",
|
|
||||||
}
|
}
|
||||||
certConfig := &mylego.CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "http",
|
CertMode: "http",
|
||||||
@@ -47,7 +46,6 @@ func TestBuildTrojan(t *testing.T) {
|
|||||||
Host: "trojan.test.tk",
|
Host: "trojan.test.tk",
|
||||||
Path: "v2ray",
|
Path: "v2ray",
|
||||||
EnableTLS: false,
|
EnableTLS: false,
|
||||||
TLSType: "tls",
|
|
||||||
}
|
}
|
||||||
DNSEnv := make(map[string]string)
|
DNSEnv := make(map[string]string)
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||||
@@ -79,7 +77,6 @@ func TestBuildSS(t *testing.T) {
|
|||||||
Host: "test.test.tk",
|
Host: "test.test.tk",
|
||||||
Path: "v2ray",
|
Path: "v2ray",
|
||||||
EnableTLS: false,
|
EnableTLS: false,
|
||||||
TLSType: "tls",
|
|
||||||
}
|
}
|
||||||
DNSEnv := make(map[string]string)
|
DNSEnv := make(map[string]string)
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
@@ -17,11 +16,8 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
|
|||||||
outboundDetourConfig.Protocol = "freedom"
|
outboundDetourConfig.Protocol = "freedom"
|
||||||
outboundDetourConfig.Tag = tag
|
outboundDetourConfig.Tag = tag
|
||||||
|
|
||||||
// Build Send IP address
|
// SendThrough setting
|
||||||
if config.SendIP != "" {
|
outboundDetourConfig.SendThrough = &config.SendIP
|
||||||
ipAddress := net.ParseAddress(config.SendIP)
|
|
||||||
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Freedom Protocol setting
|
// Freedom Protocol setting
|
||||||
var domainStrategy = "Asis"
|
var domainStrategy = "Asis"
|
||||||
@@ -42,7 +38,7 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
|
|||||||
var setting json.RawMessage
|
var setting json.RawMessage
|
||||||
setting, err := json.Marshal(proxySetting)
|
setting, err := json.Marshal(proxySetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
return nil, fmt.Errorf("marshal proxy %s config failed: %s", nodeInfo.NodeType, err)
|
||||||
}
|
}
|
||||||
outboundDetourConfig.Settings = &setting
|
outboundDetourConfig.Settings = &setting
|
||||||
return outboundDetourConfig.Build()
|
return outboundDetourConfig.Build()
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
C "github.com/sagernet/sing/common"
|
C "github.com/sagernet/sing/common"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
@@ -25,12 +27,11 @@ var AEADMethod = map[shadowsocks.CipherType]uint8{
|
|||||||
shadowsocks.CipherType_XCHACHA20_POLY1305: 0,
|
shadowsocks.CipherType_XCHACHA20_POLY1305: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) buildVmessUser(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) {
|
func (c *Controller) buildVmessUser(userInfo *[]api.UserInfo) (users []*protocol.User) {
|
||||||
users = make([]*protocol.User, len(*userInfo))
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
for i, user := range *userInfo {
|
for i, user := range *userInfo {
|
||||||
vmessAccount := &conf.VMessAccount{
|
vmessAccount := &conf.VMessAccount{
|
||||||
ID: user.UUID,
|
ID: user.UUID,
|
||||||
AlterIds: serverAlterID,
|
|
||||||
Security: "auto",
|
Security: "auto",
|
||||||
}
|
}
|
||||||
users[i] = &protocol.User{
|
users[i] = &protocol.User{
|
||||||
@@ -47,7 +48,7 @@ func (c *Controller) buildVlessUser(userInfo *[]api.UserInfo) (users []*protocol
|
|||||||
for i, user := range *userInfo {
|
for i, user := range *userInfo {
|
||||||
vlessAccount := &vless.Account{
|
vlessAccount := &vless.Account{
|
||||||
Id: user.UUID,
|
Id: user.UUID,
|
||||||
Flow: "xtls-rprx-direct",
|
Flow: c.nodeInfo.VlessFlow,
|
||||||
}
|
}
|
||||||
users[i] = &protocol.User{
|
users[i] = &protocol.User{
|
||||||
Level: 0,
|
Level: 0,
|
||||||
@@ -63,7 +64,6 @@ func (c *Controller) buildTrojanUser(userInfo *[]api.UserInfo) (users []*protoco
|
|||||||
for i, user := range *userInfo {
|
for i, user := range *userInfo {
|
||||||
trojanAccount := &trojan.Account{
|
trojanAccount := &trojan.Account{
|
||||||
Password: user.UUID,
|
Password: user.UUID,
|
||||||
Flow: "xtls-rprx-direct",
|
|
||||||
}
|
}
|
||||||
users[i] = &protocol.User{
|
users[i] = &protocol.User{
|
||||||
Level: 0,
|
Level: 0,
|
||||||
@@ -78,26 +78,19 @@ func (c *Controller) buildSSUser(userInfo *[]api.UserInfo, method string) (users
|
|||||||
users = make([]*protocol.User, len(*userInfo))
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
|
|
||||||
for i, user := range *userInfo {
|
for i, user := range *userInfo {
|
||||||
// // shadowsocks2022 Key = "openssl rand -base64 32" and multi users needn't cipher method
|
// shadowsocks2022 Key = "openssl rand -base64 32" and multi users needn't cipher method
|
||||||
if C.Contains(shadowaead_2022.List, strings.ToLower(method)) {
|
if C.Contains(shadowaead_2022.List, strings.ToLower(method)) {
|
||||||
e := c.buildUserTag(&user)
|
e := c.buildUserTag(&user)
|
||||||
if len(user.Passwd) < 16 {
|
userKey, err := c.checkShadowsocksPassword(user.Passwd, method)
|
||||||
newError("shadowsocks2022 key's length must be greater than 16").AtError().WriteToLog()
|
if err != nil {
|
||||||
return
|
errors.LogError(context.Background(), "[UID: %d] %s", user.UID, err)
|
||||||
}
|
continue
|
||||||
userKey := user.Passwd[:16]
|
|
||||||
if strings.Contains(method, "256") {
|
|
||||||
if len(user.Passwd) < 32 {
|
|
||||||
newError("shadowsocks2022 key's length must be greater than 32").AtError().WriteToLog()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userKey = user.Passwd[:32]
|
|
||||||
}
|
}
|
||||||
users[i] = &protocol.User{
|
users[i] = &protocol.User{
|
||||||
Level: 0,
|
Level: 0,
|
||||||
Email: e,
|
Email: e,
|
||||||
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||||
Key: base64.StdEncoding.EncodeToString([]byte(userKey)),
|
Key: userKey,
|
||||||
Email: e,
|
Email: e,
|
||||||
Level: 0,
|
Level: 0,
|
||||||
}),
|
}),
|
||||||
@@ -123,11 +116,16 @@ func (c *Controller) buildSSPluginUser(userInfo *[]api.UserInfo) (users []*proto
|
|||||||
// shadowsocks2022 Key = openssl rand -base64 32 and multi users needn't cipher method
|
// shadowsocks2022 Key = openssl rand -base64 32 and multi users needn't cipher method
|
||||||
if C.Contains(shadowaead_2022.List, strings.ToLower(user.Method)) {
|
if C.Contains(shadowaead_2022.List, strings.ToLower(user.Method)) {
|
||||||
e := c.buildUserTag(&user)
|
e := c.buildUserTag(&user)
|
||||||
|
userKey, err := c.checkShadowsocksPassword(user.Passwd, user.Method)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(context.Background(), "[UID: %d] %s", user.UID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
users[i] = &protocol.User{
|
users[i] = &protocol.User{
|
||||||
Level: 0,
|
Level: 0,
|
||||||
Email: e,
|
Email: e,
|
||||||
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||||
Key: base64.StdEncoding.EncodeToString([]byte(user.Passwd)),
|
Key: userKey,
|
||||||
Email: e,
|
Email: e,
|
||||||
Level: 0,
|
Level: 0,
|
||||||
}),
|
}),
|
||||||
@@ -168,3 +166,23 @@ func cipherFromString(c string) shadowsocks.CipherType {
|
|||||||
func (c *Controller) buildUserTag(user *api.UserInfo) string {
|
func (c *Controller) buildUserTag(user *api.UserInfo) string {
|
||||||
return fmt.Sprintf("%s|%s|%d", c.Tag, user.Email, user.UID)
|
return fmt.Sprintf("%s|%s|%d", c.Tag, user.Email, user.UID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) checkShadowsocksPassword(password string, method string) (string, error) {
|
||||||
|
if strings.Contains(c.panelType, "V2board") {
|
||||||
|
var userKey string
|
||||||
|
if len(password) < 16 {
|
||||||
|
return "", newError("shadowsocks2022 key's length must be greater than 16").AtWarning()
|
||||||
|
}
|
||||||
|
if method == "2022-blake3-aes-128-gcm" {
|
||||||
|
userKey = password[:16]
|
||||||
|
} else {
|
||||||
|
if len(password) < 32 {
|
||||||
|
return "", newError("shadowsocks2022 key's length must be greater than 32").AtWarning()
|
||||||
|
}
|
||||||
|
userKey = password[:32]
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(userKey)), nil
|
||||||
|
} else {
|
||||||
|
return password, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user