mirror of
https://github.com/XrayR-project/XrayR.git
synced 2025-07-28 19:38:33 +00:00
Compare commits
240 Commits
v0.8.8
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a644b43e8b | ||
![]() |
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.
|
||||
# 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
|
||||
|
||||
|
||||
- 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).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
|
116
.github/workflows/docker.yml
vendored
116
.github/workflows/docker.yml
vendored
@@ -1,44 +1,94 @@
|
||||
name: Publish Docker image
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: xrayr-project/xrayr
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64
|
||||
- linux/s390x
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
- 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: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Set up QEMU
|
||||
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:
|
||||
context: .
|
||||
platforms: linux/arm/v7,linux/arm64,linux/amd64,linux/s390x
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
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:
|
||||
matrix:
|
||||
# Include amd64 on all platforms.
|
||||
goos: [ windows, freebsd, openbsd, linux, dragonfly, darwin ]
|
||||
goos: [ windows, freebsd, openbsd, linux, darwin ]
|
||||
goarch: [ amd64, 386 ]
|
||||
exclude:
|
||||
# Exclude i386 on darwin and dragonfly.
|
||||
- goarch: 386
|
||||
goos: dragonfly
|
||||
# Exclude i386 on darwin.
|
||||
- goarch: 386
|
||||
goos: darwin
|
||||
- goarch: 386
|
||||
@@ -113,7 +111,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.19
|
||||
go-version: ^1.20
|
||||
|
||||
- name: Get project dependencies
|
||||
run: go mod download
|
||||
@@ -122,12 +120,12 @@ jobs:
|
||||
- name: Build XrayR
|
||||
run: |
|
||||
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
|
||||
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
|
||||
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
|
||||
if: matrix.goos == 'windows'
|
||||
run: |
|
||||
@@ -135,27 +133,32 @@ jobs:
|
||||
mv XrayR XrayR.exe
|
||||
|
||||
- name: Prepare to release
|
||||
run: |
|
||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||
cp ${GITHUB_WORKSPACE}/main/dns.json ./build_assets/dns.json
|
||||
cp ${GITHUB_WORKSPACE}/main/route.json ./build_assets/route.json
|
||||
cp ${GITHUB_WORKSPACE}/main/custom_outbound.json ./build_assets/custom_outbound.json
|
||||
cp ${GITHUB_WORKSPACE}/main/custom_inbound.json ./build_assets/custom_inbound.json
|
||||
cp ${GITHUB_WORKSPACE}/main/rulelist ./build_assets/rulelist
|
||||
cp ${GITHUB_WORKSPACE}/main/config.yml.example ./build_assets/config.yml
|
||||
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
||||
for i in "${LIST[@]}"
|
||||
do
|
||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
|
||||
LASTEST_TAG="$(curl -sL "https://api.github.com/repos/v2fly/${INFO[0]}/releases" | jq -r ".[0].tag_name" || echo "latest")"
|
||||
FILE_NAME="${INFO[2]}.dat"
|
||||
echo -e "Downloading ${FILE_NAME}..."
|
||||
curl -L "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat" -o ./build_assets/${FILE_NAME}
|
||||
echo -e "Verifying HASH key..."
|
||||
HASH="$(curl -sL "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat.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
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
retry_wait_seconds: 60
|
||||
max_attempts: 5
|
||||
command: |
|
||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||
cp ${GITHUB_WORKSPACE}/release/config/dns.json ./build_assets/dns.json
|
||||
cp ${GITHUB_WORKSPACE}/release/config/route.json ./build_assets/route.json
|
||||
cp ${GITHUB_WORKSPACE}/release/config/custom_outbound.json ./build_assets/custom_outbound.json
|
||||
cp ${GITHUB_WORKSPACE}/release/config/custom_inbound.json ./build_assets/custom_inbound.json
|
||||
cp ${GITHUB_WORKSPACE}/release/config/rulelist ./build_assets/rulelist
|
||||
cp ${GITHUB_WORKSPACE}/release/config/config.yml.example ./build_assets/config.yml
|
||||
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
||||
for i in "${LIST[@]}"
|
||||
do
|
||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat"
|
||||
FILE_NAME="${INFO[2]}.dat"
|
||||
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
|
||||
shell: bash
|
||||
run: |
|
||||
|
38
.gitignore
vendored
38
.gitignore
vendored
@@ -1,18 +1,20 @@
|
||||
main/main
|
||||
main/XrayR
|
||||
main/XrayR*
|
||||
main/mytest
|
||||
main/access.logo
|
||||
main/error.log
|
||||
api/chooseparser.go.bak
|
||||
common/Inboundbuilder/.lego/
|
||||
common/legocmd/.lego/
|
||||
.vscode/launch.json
|
||||
main/.lego
|
||||
main/cert
|
||||
main/config.yml
|
||||
./vscode
|
||||
.idea/*
|
||||
.DS_Store
|
||||
*.bak
|
||||
go.work*
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.test
|
||||
*.out
|
||||
go.work
|
||||
main
|
||||
XrayR
|
||||
XrayR*
|
||||
access.log
|
||||
error.log
|
||||
.lego
|
||||
cert
|
||||
config.yml
|
@@ -1,10 +1,10 @@
|
||||
# Build go
|
||||
FROM golang:1.19-alpine AS builder
|
||||
FROM golang:1.22.0-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
ENV CGO_ENABLED=0
|
||||
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
|
||||
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.
|
||||
|
||||
一个基于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/)
|
||||
|
||||
|
||||
## 免责声明
|
||||
|
||||
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
||||
@@ -58,6 +62,8 @@ A Xray backend framework that can easily support many panels.
|
||||
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||
| [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 (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
)
|
||||
|
||||
const (
|
||||
UserNotModified = "users not modified"
|
||||
NodeNotModified = "node not modified"
|
||||
RuleNotModified = "rules not modified"
|
||||
)
|
||||
|
||||
// Config API config
|
||||
@@ -12,7 +20,7 @@ type Config struct {
|
||||
Key string `mapstructure:"ApiKey"`
|
||||
NodeType string `mapstructure:"NodeType"`
|
||||
EnableVless bool `mapstructure:"EnableVless"`
|
||||
EnableXTLS bool `mapstructure:"EnableXTLS"`
|
||||
VlessFlow string `mapstructure:"VlessFlow"`
|
||||
Timeout int `mapstructure:"Timeout"`
|
||||
SpeedLimit float64 `mapstructure:"SpeedLimit"`
|
||||
DeviceLimit int `mapstructure:"DeviceLimit"`
|
||||
@@ -29,38 +37,59 @@ type NodeStatus struct {
|
||||
}
|
||||
|
||||
type NodeInfo struct {
|
||||
NodeType string // Must be V2ray, Trojan, and Shadowsocks
|
||||
NodeID int
|
||||
Port uint32
|
||||
SpeedLimit uint64 // Bps
|
||||
AlterID uint16
|
||||
TransportProtocol string
|
||||
FakeType string
|
||||
Host string
|
||||
Path string
|
||||
EnableTLS bool
|
||||
TLSType string
|
||||
EnableVless bool
|
||||
CypherMethod string
|
||||
ServerKey string
|
||||
ServiceName string
|
||||
Header json.RawMessage
|
||||
AcceptProxyProtocol bool
|
||||
Authority string
|
||||
NodeType string // Must be V2ray, Trojan, and Shadowsocks
|
||||
NodeID int
|
||||
Port uint32
|
||||
SpeedLimit uint64 // Bps
|
||||
AlterID uint16
|
||||
TransportProtocol string
|
||||
FakeType string
|
||||
Host string
|
||||
Path string
|
||||
EnableTLS bool
|
||||
EnableSniffing bool
|
||||
RouteOnly bool
|
||||
EnableVless bool
|
||||
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 {
|
||||
UID int
|
||||
Email string
|
||||
Passwd string
|
||||
Port uint32
|
||||
Method string
|
||||
SpeedLimit uint64 // Bps
|
||||
DeviceLimit int
|
||||
Protocol string
|
||||
ProtocolParam string
|
||||
Obfs string
|
||||
ObfsParam string
|
||||
UUID string
|
||||
AlterID uint16
|
||||
UID int
|
||||
Email string
|
||||
UUID string
|
||||
Passwd string
|
||||
Port uint32
|
||||
AlterID uint16
|
||||
Method string
|
||||
SpeedLimit uint64 // Bps
|
||||
DeviceLimit int
|
||||
}
|
||||
|
||||
type OnlineUser struct {
|
||||
@@ -91,3 +120,14 @@ type DetectResult struct {
|
||||
UID 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/XrayR-project/XrayR/api/v2board"
|
||||
"github.com/XrayR-project/XrayR/api/bunpanel"
|
||||
)
|
||||
|
||||
func CreateClient() api.API {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://localhost:9897",
|
||||
Key: "qwertyuiopasdfghjkl",
|
||||
APIHost: "http://localhost:8080",
|
||||
Key: "123456",
|
||||
NodeID: 1,
|
||||
NodeType: "V2ray",
|
||||
}
|
||||
client := v2board.New(apiConfig)
|
||||
client := bunpanel.New(apiConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||
client := CreateClient()
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
if err != nil {
|
||||
@@ -27,14 +27,14 @@ func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetSSNodeinfo(t *testing.T) {
|
||||
func TestGetSSNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:668",
|
||||
Key: "qwertyuiopasdfghjkl",
|
||||
NodeID: 1,
|
||||
NodeType: "Shadowsocks",
|
||||
}
|
||||
client := v2board.New(apiConfig)
|
||||
client := bunpanel.New(apiConfig)
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -42,14 +42,14 @@ func TestGetSSNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetTrojanNodeinfo(t *testing.T) {
|
||||
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:668",
|
||||
Key: "qwertyuiopasdfghjkl",
|
||||
NodeID: 1,
|
||||
NodeType: "Trojan",
|
||||
}
|
||||
client := v2board.New(apiConfig)
|
||||
client := bunpanel.New(apiConfig)
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -78,8 +78,8 @@ func TestReportReportUserTraffic(t *testing.T) {
|
||||
for i, userInfo := range *userList {
|
||||
generalUserTraffic[i] = api.UserTraffic{
|
||||
UID: userInfo.UID,
|
||||
Upload: 114514,
|
||||
Download: 114514,
|
||||
Upload: 1111,
|
||||
Download: 2222,
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -13,8 +12,12 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/bitly/go-simplejson"
|
||||
"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"
|
||||
)
|
||||
@@ -27,12 +30,12 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
resp atomic.Value
|
||||
eTag string
|
||||
eTags map[string]string
|
||||
}
|
||||
|
||||
// New create an api instance
|
||||
@@ -52,10 +55,18 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
}
|
||||
})
|
||||
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
|
||||
client.SetQueryParams(map[string]string{
|
||||
"node_id": strconv.Itoa(apiConfig.NodeID),
|
||||
"node_type": strings.ToLower(apiConfig.NodeType),
|
||||
"node_type": nodeType,
|
||||
"token": apiConfig.Key,
|
||||
})
|
||||
// Read local rule list
|
||||
@@ -67,10 +78,11 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
eTags: make(map[string]string),
|
||||
}
|
||||
return apiClient
|
||||
}
|
||||
@@ -82,7 +94,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
if path != "" {
|
||||
// open the file
|
||||
file, err := os.Open(path)
|
||||
|
||||
defer file.Close()
|
||||
// handle errors while opening
|
||||
if err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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), res.String(), 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 panel
|
||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
server := new(serverConfig)
|
||||
path := "/api/v1/server/UniProxy/config"
|
||||
|
||||
res, err := c.client.R().
|
||||
SetHeader("If-None-Match", c.eTags["node"]).
|
||||
ForceContentType("application/json").
|
||||
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 {
|
||||
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 {
|
||||
case "V2ray":
|
||||
nodeInfo, err = c.parseV2rayNodeResponse(response)
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
nodeInfo, err = c.parseV2rayNodeResponse(server)
|
||||
case "Trojan":
|
||||
nodeInfo, err = c.parseTrojanNodeResponse(response)
|
||||
nodeInfo, err = c.parseTrojanNodeResponse(server)
|
||||
case "Shadowsocks":
|
||||
nodeInfo, err = c.parseSSNodeResponse(response)
|
||||
nodeInfo, err = c.parseSSNodeResponse(server)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
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 nil, fmt.Errorf("parse node info failed: %s, \nError: %v", res.String(), err)
|
||||
}
|
||||
|
||||
return nodeInfo, nil
|
||||
@@ -176,48 +203,52 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
|
||||
// GetUserList will pull user form panel
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
var users []*user
|
||||
path := "/api/v1/server/UniProxy/user"
|
||||
|
||||
switch c.NodeType {
|
||||
case "V2ray", "Trojan", "Shadowsocks":
|
||||
case "V2ray", "Trojan", "Shadowsocks", "Vmess", "Vless":
|
||||
break
|
||||
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().
|
||||
SetHeader("If-None-Match", c.eTag).
|
||||
SetHeader("If-None-Match", c.eTags["users"]).
|
||||
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("users no change")
|
||||
return nil, errors.New(api.UserNotModified)
|
||||
}
|
||||
// update etag
|
||||
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTag {
|
||||
c.eTag = res.Header().Get("Etag")
|
||||
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)
|
||||
usersResp, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
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, numOfUsers)
|
||||
for i := 0; i < numOfUsers; i++ {
|
||||
user := response.Get("users").GetIndex(i)
|
||||
userList := make([]api.UserInfo, len(users))
|
||||
for i := 0; i < len(users); i++ {
|
||||
u := api.UserInfo{
|
||||
UID: user.Get("id").MustInt(),
|
||||
UUID: user.Get("uuid").MustString(),
|
||||
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(user.Get("speed_limit").MustUint64() * 1000000 / 8)
|
||||
u.SpeedLimit = uint64(users[i].SpeedLimit * 1000000 / 8)
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
res, err := c.client.R().
|
||||
SetBody(data).
|
||||
ForceContentType("application/json").
|
||||
Post(path)
|
||||
res, err := c.client.R().SetBody(data).ForceContentType("application/json").Post(path)
|
||||
_, err = c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -255,17 +283,16 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
|
||||
// GetNodeRule implements the API interface
|
||||
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
routes := c.resp.Load().(*serverConfig).Routes
|
||||
|
||||
ruleList := c.LocalRuleList
|
||||
|
||||
nodeInfoResponse := c.resp.Load().(*simplejson.Json)
|
||||
for i, rule := range nodeInfoResponse.Get("routes").MustArray() {
|
||||
r := rule.(map[string]any)
|
||||
if r["action"] == "block" {
|
||||
ruleListItem := api.DetectRule{
|
||||
for i := range routes {
|
||||
if routes[i].Action == "block" {
|
||||
ruleList = append(ruleList, api.DetectRule{
|
||||
ID: i,
|
||||
Pattern: regexp.MustCompile(strings.TrimPrefix(r["match"].(string), "regexp:")),
|
||||
}
|
||||
ruleList = append(ruleList, ruleListItem)
|
||||
Pattern: regexp.MustCompile(strings.Join(routes[i].Match, "|")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,90 +315,159 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
}
|
||||
|
||||
// parseTrojanNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) parseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||
var TLSType = "tls"
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
}
|
||||
|
||||
func (c *APIClient) parseTrojanNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||
// Create GeneralNodeInfo
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||
Port: uint32(s.ServerPort),
|
||||
TransportProtocol: "tcp",
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
Host: nodeInfoResponse.Get("host").MustString(),
|
||||
ServiceName: nodeInfoResponse.Get("server_name").MustString(),
|
||||
Host: s.Host,
|
||||
ServiceName: s.ServerName,
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
}
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
return &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||
Port: uint32(s.ServerPort),
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: nodeInfoResponse.Get("cipher").MustString(),
|
||||
ServerKey: nodeInfoResponse.Get("server_key").MustString(), // shadowsocks2022 share key
|
||||
CypherMethod: s.Cipher,
|
||||
ServerKey: s.ServerKey, // shadowsocks2022 share key
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
Header: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 (
|
||||
TLSType = "tls"
|
||||
path, host, serviceName string
|
||||
header json.RawMessage
|
||||
enableTLS bool
|
||||
alterID uint16 = 0
|
||||
host string
|
||||
header json.RawMessage
|
||||
enableTLS bool
|
||||
enableREALITY bool
|
||||
dest string
|
||||
xVer uint64
|
||||
)
|
||||
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
if s.VlessTlsSettings.Dest != "" {
|
||||
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":
|
||||
path = nodeInfoResponse.Get("networkSettings").Get("path").MustString()
|
||||
host = nodeInfoResponse.Get("networkSettings").Get("headers").Get("Host").MustString()
|
||||
case "grpc":
|
||||
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("serviceName"); ok {
|
||||
serviceName = data.MustString()
|
||||
if s.NetworkSettings.Headers != nil {
|
||||
if httpHeader, err := s.NetworkSettings.Headers.MarshalJSON(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
b, _ := simplejson.NewJson(httpHeader)
|
||||
host = b.Get("Host").MustString()
|
||||
}
|
||||
}
|
||||
case "tcp":
|
||||
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("headers"); ok {
|
||||
if httpHeader, err := data.MarshalJSON(); err != nil {
|
||||
if s.NetworkSettings.Header != nil {
|
||||
if httpHeader, err := s.NetworkSettings.Header.MarshalJSON(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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
|
||||
enableREALITY = false
|
||||
case 2:
|
||||
enableTLS = true
|
||||
enableREALITY = true
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
return &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
Port: uint32(s.ServerPort),
|
||||
AlterID: 0,
|
||||
TransportProtocol: s.Network,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLSType,
|
||||
Path: path,
|
||||
Path: s.NetworkSettings.Path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
ServiceName: serviceName,
|
||||
VlessFlow: s.VlessFlow,
|
||||
ServiceName: s.NetworkSettings.ServiceName,
|
||||
Header: header,
|
||||
EnableREALITY: enableREALITY,
|
||||
REALITYConfig: &realityConfig,
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
}, 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"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
@@ -24,7 +25,7 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
@@ -61,7 +62,7 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
@@ -359,8 +360,8 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var enableTLS bool
|
||||
var path, host, TLStype, transportProtocol, serviceName string
|
||||
var speedlimit uint64 = 0
|
||||
var path, host, transportProtocol, serviceName string
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
port := nodeInfoResponse.Port
|
||||
alterID := nodeInfoResponse.AlterId
|
||||
@@ -376,34 +377,29 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
}
|
||||
// Compatible with more node types config
|
||||
switch nodeInfoResponse.Security {
|
||||
case "tls", "xtls":
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
case "tls":
|
||||
enableTLS = true
|
||||
default:
|
||||
enableTLS = false
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLStype,
|
||||
Path: path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
|
||||
@@ -412,38 +408,33 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
|
||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: nodeInfoResponse.Port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: nodeInfoResponse.Method,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
// 域名或IP;port=连接端口#偏移端口|host=xx
|
||||
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
||||
var TLSType, host string
|
||||
var host string
|
||||
var transportProtocol = "tcp"
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
} else {
|
||||
TLSType = "tls"
|
||||
}
|
||||
host = nodeInfoResponse.Host
|
||||
port := nodeInfoResponse.Port
|
||||
|
||||
@@ -456,25 +447,24 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
||||
transportProtocol = "grpc"
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
Host: host,
|
||||
ServiceName: nodeInfoResponse.Sni,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseUserListResponse parse the response for the given nodeinfo format
|
||||
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
|
||||
var deviceLimit int = 0
|
||||
var speedlimit uint64 = 0
|
||||
var deviceLimit = 0
|
||||
var speedLimit uint64 = 0
|
||||
userList := make([]api.UserInfo, len(*userInfoResponse))
|
||||
for i, user := range *userInfoResponse {
|
||||
if c.DeviceLimit > 0 {
|
||||
@@ -483,15 +473,15 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
||||
deviceLimit = user.DeviceLimit
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.ID,
|
||||
Passwd: user.Passwd,
|
||||
UUID: user.Passwd,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
DeviceLimit: deviceLimit,
|
||||
}
|
||||
}
|
||||
|
@@ -4,13 +4,14 @@ import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
@@ -24,7 +25,7 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
@@ -57,7 +58,7 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
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) {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
path = fmt.Sprintf("/api/v2ray/v1/node/%d", c.NodeID)
|
||||
case "Trojan":
|
||||
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 {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
nodeInfo, err = c.ParseV2rayNodeResponse(&response.Data)
|
||||
case "Trojan":
|
||||
nodeInfo, err = c.ParseTrojanNodeResponse(&response.Data)
|
||||
@@ -177,7 +178,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
|
||||
if err != nil {
|
||||
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
|
||||
@@ -187,7 +188,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
path = fmt.Sprintf("/api/v2ray/v1/userList/%d", c.NodeID)
|
||||
case "Trojan":
|
||||
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)
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
userList, err = c.ParseV2rayUserListResponse(&response.Data)
|
||||
case "Trojan":
|
||||
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) {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
path = fmt.Sprintf("/api/v2ray/v1/nodeStatus/%d", c.NodeID)
|
||||
case "Trojan":
|
||||
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
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
path = fmt.Sprintf("/api/v2ray/v1/nodeOnline/%d", c.NodeID)
|
||||
case "Trojan":
|
||||
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 {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
path = fmt.Sprintf("/api/v2ray/v1/userTraffic/%d", c.NodeID)
|
||||
case "Trojan":
|
||||
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) {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
path = fmt.Sprintf("/api/v2ray/v1/nodeRule/%d", c.NodeID)
|
||||
case "Trojan":
|
||||
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 {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
path = fmt.Sprintf("/api/v2ray/v1/trigger/%d", c.NodeID)
|
||||
case "Trojan":
|
||||
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
|
||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||
var TLStype string
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
v2rayNodeInfo := new(V2rayNodeInfo)
|
||||
if err := json.Unmarshal(*nodeInfoResponse, v2rayNodeInfo); err != nil {
|
||||
@@ -426,9 +421,9 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((v2rayNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = (v2rayNodeInfo.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
|
||||
if c.DeviceLimit == 0 && v2rayNodeInfo.ClientLimit > 0 {
|
||||
@@ -436,72 +431,65 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: v2rayNodeInfo.V2Port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: v2rayNodeInfo.V2AlterID,
|
||||
TransportProtocol: v2rayNodeInfo.V2Net,
|
||||
FakeType: v2rayNodeInfo.V2Type,
|
||||
EnableTLS: v2rayNodeInfo.V2TLS,
|
||||
TLSType: TLStype,
|
||||
Path: v2rayNodeInfo.V2Path,
|
||||
Host: v2rayNodeInfo.V2Host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
shadowsocksNodeInfo := new(ShadowsocksNodeInfo)
|
||||
if err := json.Unmarshal(*nodeInfoResponse, shadowsocksNodeInfo); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((shadowsocksNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((shadowsocksNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
if c.DeviceLimit == 0 && shadowsocksNodeInfo.ClientLimit > 0 {
|
||||
c.DeviceLimit = shadowsocksNodeInfo.ClientLimit
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: shadowsocksNodeInfo.Port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: shadowsocksNodeInfo.Method,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||
|
||||
var TLSType string
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
} else {
|
||||
TLSType = "tls"
|
||||
}
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
trojanNodeInfo := new(TrojanNodeInfo)
|
||||
if err := json.Unmarshal(*nodeInfoResponse, trojanNodeInfo); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((trojanNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = (trojanNodeInfo.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
|
||||
if c.DeviceLimit == 0 && trojanNodeInfo.ClientLimit > 0 {
|
||||
@@ -509,22 +497,21 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: trojanNodeInfo.TrojanPort,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseV2rayUserListResponse parse the response for the given userinfo format
|
||||
func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
vmessUserList := new([]*VMessUser)
|
||||
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))
|
||||
for i, user := range *vmessUserList {
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = (user.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.UID,
|
||||
Email: "",
|
||||
UUID: user.VmessUID,
|
||||
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
|
||||
func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
trojanUserList := new([]*TrojanUser)
|
||||
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))
|
||||
for i, user := range *trojanUserList {
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = (user.SpeedLimit * 1000000) / 8
|
||||
speedLimit = (user.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.UID,
|
||||
Email: "",
|
||||
UUID: user.Password,
|
||||
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
|
||||
func (c *APIClient) ParseSSUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
ssUserList := new([]*SSUser)
|
||||
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))
|
||||
for i, user := range *ssUserList {
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64(user.SpeedLimit * 1000000 / 8)
|
||||
speedLimit = uint64(user.SpeedLimit * 1000000 / 8)
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.UID,
|
||||
Email: "",
|
||||
Passwd: user.Password,
|
||||
DeviceLimit: c.DeviceLimit,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -154,8 +154,8 @@ func TestReportIllegal(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
detectResult := []api.DetectResult{
|
||||
{1, 1},
|
||||
{1, 2},
|
||||
{UID: 1, RuleID: 1},
|
||||
{UID: 1, RuleID: 2},
|
||||
}
|
||||
client.Debug()
|
||||
err := client.ReportIllegal(&detectResult)
|
||||
|
@@ -8,7 +8,6 @@ type NodeInfoResponse struct {
|
||||
Class int `json:"node_class"`
|
||||
SpeedLimit float64 `json:"node_speedlimit"`
|
||||
TrafficRate float64 `json:"traffic_rate"`
|
||||
MuOnly int `json:"mu_only"`
|
||||
Sort int `json:"sort"`
|
||||
RawServerString string `json:"server"`
|
||||
Type string `json:"type"`
|
||||
@@ -17,51 +16,35 @@ type NodeInfoResponse struct {
|
||||
}
|
||||
|
||||
type CustomConfig struct {
|
||||
OffsetPortUser string `json:"offset_port_user"`
|
||||
OffsetPortNode string `json:"offset_port_node"`
|
||||
ServerSub string `json:"server_sub"`
|
||||
Host string `json:"host"`
|
||||
MuPort string `json:"mu_port"`
|
||||
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"`
|
||||
Method string `json:"method"`
|
||||
TLS string `json:"tls"`
|
||||
EnableVless string `json:"enable_vless"`
|
||||
AlterID string `json:"alter_id"`
|
||||
Network string `json:"network"`
|
||||
Security string `json:"security"`
|
||||
Path string `json:"path"`
|
||||
VerifyCert bool `json:"verify_cert"`
|
||||
Obfs string `json:"obfs"`
|
||||
Header json.RawMessage `json:"header"`
|
||||
TrojanPort string `json:"trojan_port"`
|
||||
AllowInsecure string `json:"allow_insecure"`
|
||||
Grpc string `json:"grpc"`
|
||||
Servicename string `json:"servicename"`
|
||||
EnableXtls string `json:"enable_xtls"`
|
||||
Flow string `json:"flow"`
|
||||
EnableREALITY bool `json:"enable_reality"`
|
||||
RealityOpts *REALITYConfig `json:"reality-opts"`
|
||||
}
|
||||
|
||||
// UserResponse is the response of user
|
||||
type UserResponse struct {
|
||||
ID int `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Passwd string `json:"passwd"`
|
||||
Port uint32 `json:"port"`
|
||||
Method string `json:"method"`
|
||||
SpeedLimit float64 `json:"node_speedlimit"`
|
||||
DeviceLimit int `json:"node_connector"`
|
||||
Protocol string `json:"protocol"`
|
||||
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"`
|
||||
ID int `json:"id"`
|
||||
Passwd string `json:"passwd"`
|
||||
Port uint32 `json:"port"`
|
||||
Method string `json:"method"`
|
||||
SpeedLimit float64 `json:"node_speedlimit"`
|
||||
DeviceLimit int `json:"node_iplimit"`
|
||||
UUID string `json:"uuid"`
|
||||
AliveIP int `json:"alive_ip"`
|
||||
}
|
||||
|
||||
// Response is the common response
|
||||
@@ -75,7 +58,7 @@ type PostData struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// SystemLoad is the data structure of systemload
|
||||
// SystemLoad is the data structure of system load
|
||||
type SystemLoad struct {
|
||||
Uptime string `json:"uptime"`
|
||||
Load string `json:"load"`
|
||||
@@ -103,3 +86,14 @@ type IllegalItem struct {
|
||||
ID int `json:"list_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 (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
@@ -32,19 +34,21 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
DisableCustomConfig bool
|
||||
LocalRuleList []api.DetectRule
|
||||
LastReportOnline map[int]int
|
||||
access sync.Mutex
|
||||
version string
|
||||
eTags map[string]string
|
||||
}
|
||||
|
||||
// New creat a api instance
|
||||
// New create 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)
|
||||
@@ -52,12 +56,14 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
client.SetTimeout(5 * time.Second)
|
||||
}
|
||||
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.Err contains the original error
|
||||
log.Print(v.Err)
|
||||
}
|
||||
})
|
||||
|
||||
client.SetBaseURL(apiConfig.APIHost)
|
||||
// Create Key for each requests
|
||||
client.SetQueryParam("key", apiConfig.Key)
|
||||
@@ -73,23 +79,29 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
DisableCustomConfig: apiConfig.DisableCustomConfig,
|
||||
LastReportOnline: make(map[int]int),
|
||||
eTags: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
log.Printf("Error when closing file: %s", err)
|
||||
}
|
||||
}(file)
|
||||
// handle errors while opening
|
||||
if err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
|
||||
return LocalRuleList
|
||||
@@ -138,7 +148,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
||||
|
||||
if res.StatusCode() > 400 {
|
||||
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)
|
||||
|
||||
@@ -149,13 +159,22 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
||||
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) {
|
||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", 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 {
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// New sspanel API
|
||||
disableCustomConfig := c.DisableCustomConfig
|
||||
if nodeInfoResponse.Version != "" && !disableCustomConfig {
|
||||
// Check if custom_config is empty
|
||||
if configString, err := json.Marshal(nodeInfoResponse.CustomConfig); err != nil || string(configString) == "[]" {
|
||||
log.Printf("custom_config is empty! take config from address now.")
|
||||
disableCustomConfig = true
|
||||
}
|
||||
} else {
|
||||
disableCustomConfig = true
|
||||
// determine ssPanel version, if disable custom config or version < 2021.11, then use old api
|
||||
c.version = nodeInfoResponse.Version
|
||||
var isExpired bool
|
||||
if compareVersion(c.version, "2021.11") == -1 {
|
||||
isExpired = true
|
||||
}
|
||||
|
||||
if !disableCustomConfig {
|
||||
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 c.DisableCustomConfig || isExpired {
|
||||
if isExpired {
|
||||
log.Print("The panel version is expired, it is recommended to update immediately")
|
||||
}
|
||||
} else {
|
||||
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
nodeInfo, err = c.ParseV2rayNodeResponse(nodeInfoResponse)
|
||||
@@ -199,24 +211,39 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
default:
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// GetUserList will pull user form sspanel
|
||||
// GetUserList will pull user form ssPanel
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
path := "/mod_mu/users"
|
||||
res, err := c.client.R().
|
||||
SetQueryParam("node_id", 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 {
|
||||
@@ -236,25 +263,27 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
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) {
|
||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||
systemload := SystemLoad{
|
||||
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
|
||||
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
|
||||
// Determine whether a status report is in need
|
||||
if compareVersion(c.version, "2023.2") == -1 {
|
||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -267,16 +296,12 @@ func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) erro
|
||||
data := make([]OnlineUser, len(*onlineUserList))
|
||||
for i, user := range *onlineUserList {
|
||||
data[i] = OnlineUser{UID: user.UID, IP: user.IP}
|
||||
if _, ok := reportOnline[user.UID]; ok {
|
||||
reportOnline[user.UID]++
|
||||
} else {
|
||||
reportOnline[user.UID] = 1
|
||||
}
|
||||
reportOnline[user.UID]++ // will start from 1 if key doesn’t exist
|
||||
}
|
||||
c.LastReportOnline = reportOnline // Update LastReportOnline
|
||||
|
||||
postData := &PostData{Data: data}
|
||||
path := fmt.Sprintf("/mod_mu/users/aliveip")
|
||||
path := "/mod_mu/users/aliveip"
|
||||
res, err := c.client.R().
|
||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||
SetBody(postData).
|
||||
@@ -318,15 +343,25 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
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) {
|
||||
ruleList := c.LocalRuleList
|
||||
path := "/mod_mu/func/detect_rules"
|
||||
res, err := c.client.R().
|
||||
SetResult(&Response{}).
|
||||
SetHeader("If-None-Match", c.eTags["rules"]).
|
||||
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.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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -372,12 +407,12 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
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) {
|
||||
var enableTLS bool
|
||||
var path, host, TLStype, transportProtocol, serviceName, HeaderType string
|
||||
var path, host, transportProtocol, serviceName, HeaderType string
|
||||
var header json.RawMessage
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
if nodeInfoResponse.RawServerString == "" {
|
||||
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
|
||||
for _, value := range serverConf[3:5] {
|
||||
switch value {
|
||||
case "tls", "xtls":
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
case "tls":
|
||||
enableTLS = true
|
||||
default:
|
||||
if value != "" {
|
||||
@@ -429,14 +459,14 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
host = value
|
||||
case "servicename":
|
||||
serviceName = value
|
||||
case "headertype":
|
||||
case "headerType":
|
||||
HeaderType = value
|
||||
}
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
if HeaderType != "" {
|
||||
@@ -445,33 +475,33 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
}
|
||||
|
||||
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
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLStype,
|
||||
Path: path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
ServiceName: serviceName,
|
||||
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) {
|
||||
var port uint32 = 0
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
var method string
|
||||
path := "/mod_mu/users"
|
||||
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 {
|
||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||
}
|
||||
// Find the multi-user
|
||||
for _, u := range *userListResponse {
|
||||
if u.MultiUser > 0 {
|
||||
port = u.Port
|
||||
method = u.Method
|
||||
break
|
||||
}
|
||||
}
|
||||
if port == 0 || method == "" {
|
||||
return nil, fmt.Errorf("cant find the single port multi user")
|
||||
|
||||
// init server port
|
||||
if len(*userListResponse) != 0 {
|
||||
port = (*userListResponse)[0].Port
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
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) {
|
||||
var enableTLS bool
|
||||
var path, host, TLStype, transportProtocol string
|
||||
var speedlimit uint64 = 0
|
||||
var path, host, transportProtocol string
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
||||
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
|
||||
@@ -539,12 +563,7 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
|
||||
// Compatible with more node types config
|
||||
for _, value := range serverConf[3:5] {
|
||||
switch value {
|
||||
case "tls", "xtls":
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
case "tls":
|
||||
enableTLS = true
|
||||
case "ws":
|
||||
transportProtocol = "ws"
|
||||
@@ -570,38 +589,32 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
|
||||
}
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLStype,
|
||||
Path: path,
|
||||
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) {
|
||||
// 域名或IP;port=连接端口#偏移端口|host=xx
|
||||
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
||||
var p, TLSType, host, outsidePort, insidePort, transportProtocol, serviceName string
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
} else {
|
||||
TLSType = "tls"
|
||||
}
|
||||
var p, host, outsidePort, insidePort, transportProtocol, serviceName string
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
if nodeInfoResponse.RawServerString == "" {
|
||||
return nil, fmt.Errorf("no server info in response")
|
||||
@@ -648,27 +661,26 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
Host: host,
|
||||
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) {
|
||||
c.access.Lock()
|
||||
// Clear Last report log
|
||||
@@ -677,8 +689,8 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
||||
c.access.Unlock()
|
||||
}()
|
||||
|
||||
var deviceLimit, localDeviceLimit int = 0, 0
|
||||
var speedlimit uint64 = 0
|
||||
var deviceLimit, localDeviceLimit = 0, 0
|
||||
var speedLimit uint64 = 0
|
||||
var userList []api.UserInfo
|
||||
for _, user := range *userInfoResponse {
|
||||
if c.DeviceLimit > 0 {
|
||||
@@ -706,111 +718,142 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
userList = append(userList, api.UserInfo{
|
||||
UID: user.ID,
|
||||
Email: user.Email,
|
||||
UUID: user.UUID,
|
||||
Passwd: user.Passwd,
|
||||
SpeedLimit: speedlimit,
|
||||
DeviceLimit: deviceLimit,
|
||||
Port: user.Port,
|
||||
Method: user.Method,
|
||||
Protocol: user.Protocol,
|
||||
ProtocolParam: user.ProtocolParam,
|
||||
Obfs: user.Obfs,
|
||||
ObfsParam: user.ObfsParam,
|
||||
UID: user.ID,
|
||||
UUID: user.UUID,
|
||||
Passwd: user.Passwd,
|
||||
SpeedLimit: speedLimit,
|
||||
DeviceLimit: deviceLimit,
|
||||
Port: user.Port,
|
||||
Method: user.Method,
|
||||
})
|
||||
}
|
||||
|
||||
return &userList, nil
|
||||
}
|
||||
|
||||
// ParseSSPanelNodeInfo parse the response for the given nodeinfor format
|
||||
// Only used for SSPanel version >= 2021.11
|
||||
// ParseSSPanelNodeInfo parse the response for the given node info format
|
||||
// Only available for SSPanel version >= 2021.11
|
||||
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
|
||||
var EnableTLS, EnableVless bool
|
||||
var AlterID uint16 = 0
|
||||
var TLSType, transportProtocol string
|
||||
// Check if custom_config is null
|
||||
if len(nodeInfoResponse.CustomConfig) == 0 {
|
||||
return nil, errors.New("custom_config is empty, disable custom config")
|
||||
}
|
||||
|
||||
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 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
parsedPort, err := strconv.ParseInt(nodeConfig.OffsetPortNode, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
port := uint32(parsedPort)
|
||||
|
||||
if c.NodeType == "Shadowsocks" {
|
||||
switch c.NodeType {
|
||||
case "Shadowsocks":
|
||||
transportProtocol = "tcp"
|
||||
}
|
||||
|
||||
if c.NodeType == "V2ray" {
|
||||
case "V2ray":
|
||||
transportProtocol = nodeConfig.Network
|
||||
TLSType = nodeConfig.Security
|
||||
if parsedAlterID, err := strconv.ParseInt(nodeConfig.AlterID, 10, 16); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
AlterID = uint16(parsedAlterID)
|
||||
|
||||
tlsType := nodeConfig.Security
|
||||
if tlsType == "tls" || tlsType == "xtls" {
|
||||
enableTLS = true
|
||||
}
|
||||
|
||||
if TLSType == "tls" || TLSType == "xtls" {
|
||||
EnableTLS = true
|
||||
}
|
||||
if nodeConfig.EnableVless == "1" {
|
||||
EnableVless = true
|
||||
enableVless = true
|
||||
}
|
||||
}
|
||||
|
||||
if c.NodeType == "Trojan" {
|
||||
EnableTLS = true
|
||||
TLSType = "tls"
|
||||
case "Trojan":
|
||||
enableTLS = true
|
||||
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
|
||||
if nodeConfig.Grpc == "1" {
|
||||
transportProtocol = "grpc"
|
||||
} else if nodeConfig.Network != "" {
|
||||
if nodeConfig.Network != "" {
|
||||
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
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
AlterID: AlterID,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
Host: nodeConfig.Host,
|
||||
Path: nodeConfig.Path,
|
||||
EnableTLS: EnableTLS,
|
||||
TLSType: TLSType,
|
||||
EnableVless: EnableVless,
|
||||
CypherMethod: nodeConfig.MuEncryption,
|
||||
EnableTLS: enableTLS,
|
||||
EnableVless: enableVless,
|
||||
VlessFlow: nodeConfig.Flow,
|
||||
CypherMethod: nodeConfig.Method,
|
||||
ServiceName: nodeConfig.Servicename,
|
||||
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
|
||||
}
|
||||
|
||||
func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
@@ -29,7 +29,7 @@ func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetSSNodeinfo(t *testing.T) {
|
||||
func TestGetSSNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:667",
|
||||
Key: "123",
|
||||
@@ -44,7 +44,7 @@ func TestGetSSNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetTrojanNodeinfo(t *testing.T) {
|
||||
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:667",
|
||||
Key: "123",
|
||||
@@ -59,7 +59,7 @@ func TestGetTrojanNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetSSinfo(t *testing.T) {
|
||||
func TestGetSSInfo(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
@@ -148,8 +148,8 @@ func TestReportIllegal(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
detectResult := []api.DetectResult{
|
||||
{1, 2},
|
||||
{1, 3},
|
||||
{UID: 1, RuleID: 2},
|
||||
{UID: 1, RuleID: 3},
|
||||
}
|
||||
client.Debug()
|
||||
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
|
||||
|
||||
type UserTraffic struct {
|
||||
UID int `json:"user_id"`
|
||||
UID int `json:"uid"`
|
||||
Upload int64 `json:"u"`
|
||||
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 (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -12,8 +12,12 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/bitly/go-simplejson"
|
||||
"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"
|
||||
)
|
||||
@@ -26,12 +30,13 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
ConfigResp *simplejson.Json
|
||||
access sync.Mutex
|
||||
eTags map[string]string
|
||||
}
|
||||
|
||||
// New create an api instance
|
||||
@@ -44,13 +49,16 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
} else {
|
||||
client.SetTimeout(5 * time.Second)
|
||||
}
|
||||
|
||||
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.Err contains the original error
|
||||
log.Print(v.Err)
|
||||
}
|
||||
})
|
||||
|
||||
// Create Key for each requests
|
||||
client.SetQueryParams(map[string]string{
|
||||
"node_id": strconv.Itoa(apiConfig.NodeID),
|
||||
@@ -65,10 +73,11 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
eTags: make(map[string]string),
|
||||
}
|
||||
return apiClient
|
||||
}
|
||||
@@ -138,23 +147,33 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
||||
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) {
|
||||
var nodeType string
|
||||
switch c.NodeType {
|
||||
case "V2ray", "Trojan", "Shadowsocks":
|
||||
case "V2ray", "Vmess", "Vless", "Trojan", "Shadowsocks":
|
||||
nodeType = strings.ToLower(c.NodeType)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
res, err := c.client.R().
|
||||
SetHeader("If-None-Match", c.eTags["config"]).
|
||||
SetQueryParams(map[string]string{
|
||||
"act": "config",
|
||||
"nodetype": nodeType,
|
||||
"act": "config",
|
||||
"node_type": nodeType,
|
||||
}).
|
||||
ForceContentType("application/json").
|
||||
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)
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
@@ -164,7 +183,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
}
|
||||
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
nodeInfo, err = c.ParseV2rayNodeResponse(response)
|
||||
case "Trojan":
|
||||
nodeInfo, err = c.ParseTrojanNodeResponse(response)
|
||||
@@ -182,23 +201,33 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// GetUserList will pull user form sspanel
|
||||
// GetUserList will pull user form panel
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
var nodeType string
|
||||
switch c.NodeType {
|
||||
case "V2ray", "Trojan", "Shadowsocks":
|
||||
case "V2ray", "Vmess", "Vless", "Trojan", "Shadowsocks":
|
||||
nodeType = strings.ToLower(c.NodeType)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
res, err := c.client.R().
|
||||
SetHeader("If-None-Match", c.eTags["user"]).
|
||||
SetQueryParams(map[string]string{
|
||||
"act": "user",
|
||||
"nodetype": nodeType,
|
||||
"act": "user",
|
||||
"node_type": nodeType,
|
||||
}).
|
||||
ForceContentType("application/json").
|
||||
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)
|
||||
if err != nil {
|
||||
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()
|
||||
switch c.NodeType {
|
||||
case "Shadowsocks":
|
||||
user.Email = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("secret").MustString()
|
||||
user.Passwd = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("secret").MustString()
|
||||
user.Method = response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("cipher").MustString()
|
||||
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("shadowsocks_user").Get("speed_limit").MustUint64() * 1000000 / 8)
|
||||
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.SpeedLimit = response.Get("data").GetIndex(i).Get("st").MustUint64() * 1000000 / 8
|
||||
user.DeviceLimit = response.Get("data").GetIndex(i).Get("dt").MustInt()
|
||||
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()
|
||||
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("trojan_user").Get("speed_limit").MustUint64() * 1000000 / 8)
|
||||
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())
|
||||
user.SpeedLimit = uint64(response.Get("data").GetIndex(i).Get("v2ray_user").Get("speed_limit").MustUint64() * 1000000 / 8)
|
||||
user.UUID = response.Get("data").GetIndex(i).Get("password").MustString()
|
||||
user.Email = response.Get("data").GetIndex(i).Get("password").MustString()
|
||||
user.SpeedLimit = response.Get("data").GetIndex(i).Get("st").MustUint64() * 1000000 / 8
|
||||
user.DeviceLimit = response.Get("data").GetIndex(i).Get("dt").MustInt()
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
user.UUID = response.Get("data").GetIndex(i).Get("uuid").MustString()
|
||||
user.Email = user.UUID + "@x.com"
|
||||
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 {
|
||||
user.SpeedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
user.DeviceLimit = c.DeviceLimit
|
||||
|
||||
if c.DeviceLimit > 0 {
|
||||
user.DeviceLimit = c.DeviceLimit
|
||||
}
|
||||
|
||||
userList[i] = user
|
||||
}
|
||||
return &userList, nil
|
||||
@@ -247,8 +282,8 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
res, err := c.client.R().
|
||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||
SetQueryParams(map[string]string{
|
||||
"act": "submit",
|
||||
"nodetype": strings.ToLower(c.NodeType),
|
||||
"act": "submit",
|
||||
"node_type": strings.ToLower(c.NodeType),
|
||||
}).
|
||||
SetBody(data).
|
||||
ForceContentType("application/json").
|
||||
@@ -263,11 +298,7 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
// 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()
|
||||
@@ -285,26 +316,79 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
|
||||
// ReportNodeStatus implements the API interface
|
||||
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
|
||||
}
|
||||
|
||||
// ReportNodeOnlineUsers implements the API interface
|
||||
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
|
||||
}
|
||||
|
||||
// ReportIllegal implements the API interface
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var TLSType = "tls"
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
}
|
||||
|
||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||
@@ -313,55 +397,52 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (
|
||||
host := inboundInfo.Get("streamSettings").Get("tlsSettings").Get("serverName").MustString()
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
TransportProtocol: "tcp",
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
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) {
|
||||
var method string
|
||||
var method, serverPsk string
|
||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||
|
||||
port := uint32(inboundInfo.Get("port").MustUint64())
|
||||
userInfo, err := c.GetUserList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(*userInfo) > 0 {
|
||||
method = (*userInfo)[0].Method
|
||||
method = inboundInfo.Get("settings").Get("method").MustString()
|
||||
// Shadowsocks 2022
|
||||
if C.Contains(shadowaead_2022.List, method) {
|
||||
serverPsk = inboundInfo.Get("settings").Get("password").MustString()
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
TransportProtocol: "tcp",
|
||||
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) {
|
||||
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"
|
||||
}
|
||||
var enableVless bool
|
||||
var enableReality bool
|
||||
var vlessFlow string
|
||||
|
||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||
@@ -374,6 +455,12 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
||||
case "ws":
|
||||
path = inboundInfo.Get("streamSettings").Get("wsSettings").Get("path").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":
|
||||
if data, ok := inboundInfo.Get("streamSettings").Get("grpcSettings").CheckGet("serviceName"); ok {
|
||||
serviceName = data.MustString()
|
||||
@@ -386,12 +473,32 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
||||
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 {
|
||||
enableTLS = false
|
||||
vlessFlow = c.VlessFlow
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
@@ -400,15 +507,17 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
AlterID: alterID,
|
||||
AlterID: 0,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLSType,
|
||||
Path: path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
EnableVless: enableVless,
|
||||
VlessFlow: vlessFlow,
|
||||
ServiceName: serviceName,
|
||||
Header: header,
|
||||
EnableREALITY: enableReality,
|
||||
REALITYConfig: realityConfig,
|
||||
}
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
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 java_package = "com.xrayr.app.mydispatcher";
|
||||
option java_multiple_files = true;
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"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/net"
|
||||
"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) {
|
||||
downOpt := pipe.OptionsFromContext(ctx)
|
||||
upOpt := downOpt
|
||||
|
||||
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...)
|
||||
opt := pipe.OptionsFromContext(ctx)
|
||||
uplinkReader, uplinkWriter := pipe.New(opt...)
|
||||
downlinkReader, downlinkWriter := pipe.New(opt...)
|
||||
|
||||
inboundLink := &transport.Link{
|
||||
Reader: downlinkReader,
|
||||
@@ -236,7 +170,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
|
||||
// Speed Limit and Device Limit
|
||||
bucket, ok, reject := d.Limiter.GetUserBucket(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
|
||||
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(inboundLink.Writer)
|
||||
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)
|
||||
outboundLink.Writer = d.Limiter.RateWriter(outboundLink.Writer, bucket)
|
||||
}
|
||||
|
||||
p := d.policy.ForLevel(user.Level)
|
||||
if p.Stats.UserUplink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||
@@ -283,12 +218,12 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
|
||||
protocolString = resComp.ProtocolForDomainResult()
|
||||
}
|
||||
for _, p := range request.OverrideDestinationForProtocol {
|
||||
if strings.HasPrefix(protocolString, p) {
|
||||
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) {
|
||||
return true
|
||||
}
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||
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
|
||||
}
|
||||
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
||||
@@ -306,10 +241,14 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
if !destination.IsValid() {
|
||||
panic("Dispatcher: Invalid destination.")
|
||||
}
|
||||
ob := &session.Outbound{
|
||||
Target: destination,
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
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)
|
||||
if content == nil {
|
||||
content = new(session.Content)
|
||||
@@ -317,104 +256,25 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
}
|
||||
|
||||
sniffingRequest := content.SniffingRequest
|
||||
in, out, err := d.getLink(ctx, destination.Network, sniffingRequest)
|
||||
inbound, outbound, err := d.getLink(ctx, destination.Network, sniffingRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
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:
|
||||
if !sniffingRequest.Enabled {
|
||||
go d.routedDispatch(ctx, outbound, 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, outbound, destination)
|
||||
default:
|
||||
} else {
|
||||
go func() {
|
||||
cReader := &cachedReader{
|
||||
reader: outbound.Reader.(*pipe.Reader),
|
||||
}
|
||||
outbound.Reader = cReader
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
|
||||
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()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
||||
ob.RouteTarget = destination
|
||||
@@ -425,10 +285,58 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
defer payload.Release()
|
||||
|
||||
@@ -454,7 +362,7 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
|
||||
|
||||
cReader.Cache(payload)
|
||||
if !payload.IsEmpty() {
|
||||
result, err := sniffer.Sniff(ctx, payload.Bytes())
|
||||
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
||||
if err != common.ErrNoClue {
|
||||
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) {
|
||||
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() {
|
||||
proxied := hosts.LookupHosts(ob.Target.String())
|
||||
if proxied != nil {
|
||||
@@ -496,7 +405,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
// Whether the inbound connection contains a user
|
||||
if sessionInbound.User != nil {
|
||||
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")
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
@@ -511,10 +420,10 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
||||
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
||||
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
|
||||
} 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.Interrupt(link.Reader)
|
||||
return
|
||||
@@ -524,13 +433,13 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
outTag := route.GetOutboundTag()
|
||||
if h := d.ohm.GetHandler(outTag); h != nil {
|
||||
isPickRoute = 2
|
||||
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "taking detour [", outTag, "] for [", destination, "]")
|
||||
handler = h
|
||||
} else {
|
||||
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogWarning(ctx, "non existing outTag: ", outTag)
|
||||
}
|
||||
} 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 {
|
||||
newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "default outbound handler not exist")
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -26,11 +27,13 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
|
||||
return protocolSnifferWithMetadata{}, errNotInit
|
||||
}
|
||||
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 {
|
||||
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -107,10 +110,10 @@ func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWit
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
},
|
||||
metadataSniffer: false,
|
||||
|
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
|
||||
"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/http"
|
||||
"github.com/xtls/xray-core/common/protocol/quic"
|
||||
"github.com/xtls/xray-core/common/protocol/tls"
|
||||
)
|
||||
|
||||
@@ -22,6 +24,7 @@ type protocolSnifferWithMetadata struct {
|
||||
// for both TCP and UDP connections
|
||||
// It will not be shown as a traffic type for routing unless there is no other successful sniffing.
|
||||
metadataSniffer bool
|
||||
network net.Network
|
||||
}
|
||||
|
||||
type Sniffer struct {
|
||||
@@ -31,9 +34,11 @@ type Sniffer struct {
|
||||
func NewSniffer(ctx context.Context) *Sniffer {
|
||||
ret := &Sniffer{
|
||||
sniffer: []protocolSnifferWithMetadata{
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(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, net.Network_TCP},
|
||||
{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 {
|
||||
@@ -49,11 +54,11 @@ func NewSniffer(ctx context.Context) *Sniffer {
|
||||
|
||||
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
|
||||
for _, si := range s.sniffer {
|
||||
s := si.protocolSniffer
|
||||
if si.metadataSniffer {
|
||||
if si.metadataSniffer || si.network != network {
|
||||
continue
|
||||
}
|
||||
result, err := s(c, payload)
|
||||
|
@@ -31,7 +31,6 @@ import (
|
||||
_ "github.com/xtls/xray-core/proxy/dokodemo"
|
||||
_ "github.com/xtls/xray-core/proxy/freedom"
|
||||
_ "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/socks"
|
||||
_ "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/kcp"
|
||||
_ "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/tls"
|
||||
_ "github.com/xtls/xray-core/transport/internet/udp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/websocket"
|
||||
_ "github.com/xtls/xray-core/transport/internet/xtls"
|
||||
|
||||
// Transport headers
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/http"
|
@@ -1,9 +1,7 @@
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
@@ -12,36 +10,40 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/XrayR-project/XrayR/panel"
|
||||
)
|
||||
|
||||
var (
|
||||
configFile = flag.String("config", "", "Config file for XrayR.")
|
||||
printVersion = flag.Bool("version", false, "show version")
|
||||
cfgFile string
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "XrayR",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
version = "0.8.8"
|
||||
codename = "XrayR"
|
||||
intro = "A Xray backend that supports many panels"
|
||||
)
|
||||
|
||||
func showVersion() {
|
||||
fmt.Printf("%s %s (%s) \n", codename, version, intro)
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "Config file for XrayR.")
|
||||
}
|
||||
|
||||
func getConfig() *viper.Viper {
|
||||
config := viper.New()
|
||||
|
||||
// Set custom path and name
|
||||
if *configFile != "" {
|
||||
configName := path.Base(*configFile)
|
||||
configFileExt := path.Ext(*configFile)
|
||||
if cfgFile != "" {
|
||||
configName := path.Base(cfgFile)
|
||||
configFileExt := path.Ext(cfgFile)
|
||||
configNameOnly := strings.TrimSuffix(configName, configFileExt)
|
||||
configPath := path.Dir(*configFile)
|
||||
configPath := path.Dir(cfgFile)
|
||||
config.SetConfigName(configNameOnly)
|
||||
config.SetConfigType(strings.TrimPrefix(configFileExt, "."))
|
||||
config.AddConfigPath(configPath)
|
||||
@@ -65,18 +67,19 @@ func getConfig() *viper.Viper {
|
||||
return config
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
func run() error {
|
||||
showVersion()
|
||||
if *printVersion {
|
||||
return
|
||||
}
|
||||
|
||||
config := getConfig()
|
||||
panelConfig := &panel.Config{}
|
||||
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)
|
||||
lastTime := time.Now()
|
||||
config.OnConfigChange(func(e fsnotify.Event) {
|
||||
@@ -88,21 +91,31 @@ func main() {
|
||||
// Delete old instance and trigger GC
|
||||
runtime.GC()
|
||||
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()
|
||||
lastTime = time.Now()
|
||||
}
|
||||
})
|
||||
|
||||
p.Start()
|
||||
defer p.Close()
|
||||
|
||||
// Explicitly triggering GC to remove garbage from config loading.
|
||||
runtime.GC()
|
||||
// Running backend
|
||||
{
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
<-osSignals
|
||||
}
|
||||
osSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignals, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
<-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"
|
||||
goCacheStore "github.com/eko/gocache/store/go_cache/v4"
|
||||
redisStore "github.com/eko/gocache/store/redis/v4"
|
||||
"github.com/go-redis/redis/v8"
|
||||
goCache "github.com/patrickmn/go-cache"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
@@ -66,7 +67,9 @@ func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList
|
||||
// init redis store
|
||||
rs := redisStore.NewRedis(redis.NewClient(
|
||||
&redis.Options{
|
||||
Network: globalLimit.RedisNetwork,
|
||||
Addr: globalLimit.RedisAddr,
|
||||
Username: globalLimit.RedisUsername,
|
||||
Password: globalLimit.RedisPassword,
|
||||
DB: globalLimit.RedisDB,
|
||||
}),
|
||||
@@ -216,7 +219,7 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r
|
||||
return nil, false, false
|
||||
}
|
||||
} else {
|
||||
newError("Get Inbound Limiter information failed").AtDebug().WriteToLog()
|
||||
errors.LogDebug(context.Background(), "Get Inbound Limiter information failed")
|
||||
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
|
||||
go pushIP(inboundInfo, uniqueKey, &map[string]int{ip: uid})
|
||||
} else {
|
||||
newError("cache service").Base(err).AtError().WriteToLog()
|
||||
errors.LogErrorInner(context.Background(), err, "cache service")
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -262,7 +265,7 @@ func pushIP(inboundInfo *InboundInfo, uniqueKey string, ipMap *map[string]int) {
|
||||
defer cancel()
|
||||
|
||||
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 {
|
||||
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"`
|
||||
RedisDB int `mapstructure:"RedisDB"`
|
||||
Timeout int `mapstructure:"Timeout"`
|
||||
|
@@ -6,12 +6,13 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
|
@@ -4,11 +4,12 @@ import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"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) {
|
||||
keyPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.key", domain))
|
||||
certPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.crt", domain))
|
||||
keyPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.key", sanitizedDomain(domain)))
|
||||
certPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.crt", sanitizedDomain(domain)))
|
||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||
return "", "", fmt.Errorf("cert key failed: %s", domain)
|
||||
}
|
||||
|
@@ -3,9 +3,10 @@ package mylego
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
|
@@ -2,11 +2,11 @@ package mylego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const rootPathWarningMessage = `!!!! HEADS UP !!!!
|
||||
|
@@ -1,10 +1,11 @@
|
||||
package mylego
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"sync"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
|
||||
"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, "|")
|
||||
uid, err := strconv.Atoi(l[len(l)-1])
|
||||
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
|
||||
}
|
||||
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
|
||||
|
||||
go 1.19
|
||||
go 1.22.0
|
||||
|
||||
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/eko/gocache/lib/v4 v4.1.2
|
||||
github.com/eko/gocache/store/go_cache/v4 v4.1.2
|
||||
github.com/eko/gocache/store/redis/v4 v4.1.2
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-acme/lego/v4 v4.9.1
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/imdario/mergo v0.3.13
|
||||
github.com/eko/gocache/lib/v4 v4.1.6
|
||||
github.com/eko/gocache/store/go_cache/v4 v4.2.2
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.1
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
github.com/go-resty/resty/v2 v2.13.1
|
||||
github.com/gogf/gf/v2 v2.7.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/r3labs/diff/v2 v2.15.1
|
||||
github.com/sagernet/sing v0.1.0
|
||||
github.com/sagernet/sing-shadowsocks v0.1.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.11
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/xtls/xray-core v1.6.7-0.20221215133507-a55cf1d0bff2
|
||||
golang.org/x/crypto v0.4.0
|
||||
golang.org/x/net v0.4.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/sagernet/sing v0.4.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/xtls/xray-core v1.8.20
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/time v0.7.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.13.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // 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/autorest v0.11.24 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // 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/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/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/akamai/AkamaiOPEN-edgegrid-golang v1.2.1 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/aws/aws-sdk-go v1.39.0 // indirect
|
||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.695 // indirect
|
||||
github.com/andybalholm/brotli v1.1.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/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/civo/civogo v0.3.11 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.49.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.3 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // 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/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 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-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dnsimple/dnsimple-go v0.71.1 // indirect
|
||||
github.com/exoscale/egoscale v0.90.0 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.7.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/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/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // 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/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/mock v1.7.0-rc.1 // 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/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20221203041831-ce31453925ec // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
github.com/gophercloud/gophercloud v1.11.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-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/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/iris-contrib/schema v0.0.6 // 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/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||
github.com/klauspost/compress v1.15.13 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.2 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
|
||||
github.com/kataras/blocks v0.0.8 // indirect
|
||||
github.com/kataras/golog v0.1.11 // 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/goh v1.0.1 // indirect
|
||||
github.com/linode/linodego v1.9.1 // indirect
|
||||
github.com/liquidweb/go-lwApi v0.0.5 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||
github.com/liquidweb/liquidweb-go v1.6.3 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.31.1 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/miekg/dns v1.1.50 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.7.1 // indirect
|
||||
github.com/labstack/echo/v4 v4.11.4 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/linode/linodego v1.30.0 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailgun/raymond/v2 v2.0.48 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // 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/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // 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/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/porkbun v0.1.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.6.0 // indirect
|
||||
github.com/nrdcg/nodion v0.1.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/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/v2 v2.0.5 // indirect
|
||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // 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/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/pquerna/otp v1.3.0 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/refraction-networking/utls v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/pquerna/otp v1.4.0 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.50.0 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.48.2 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.1 // indirect
|
||||
github.com/sacloud/go-http v0.1.2 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.3.2 // indirect
|
||||
github.com/sacloud/packages-go v0.0.5 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||
github.com/sacloud/go-http v0.1.8 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.11.2 // indirect
|
||||
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // 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/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/spf13/afero v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/transip/gotransip/v6 v6.17.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.20.19 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.12 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.878 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.878 // 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/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/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/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/xtls/go v0.0.0-20220914232946-0441cf4cf837 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20240311082839-58e1a7554a75 // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20240311083148-81c0846b96cd // 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.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221211140036-ad323defaf05 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/tools v0.4.0 // indirect
|
||||
google.golang.org/api v0.103.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect
|
||||
google.golang.org/grpc v1.51.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/ratelimit v0.3.1 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // 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/ns1/ns1-go.v2 v2.6.5 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.9.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||
lukechampine.com/blake3 v1.1.7 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // 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 (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api/newV2board"
|
||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"dario.cat/mergo"
|
||||
"github.com/r3labs/diff/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/app/stats"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
@@ -18,12 +15,15 @@ import (
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
|
||||
"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/proxypanel"
|
||||
"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/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/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()
|
||||
if err != nil {
|
||||
log.Panicf("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help: %s", err)
|
||||
}
|
||||
|
||||
// Routing config
|
||||
coreRouterConfig := &conf.RouterConfig{}
|
||||
if panelConfig.RouteConfigPath != "" {
|
||||
@@ -147,7 +154,6 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
if err != nil {
|
||||
log.Panicf("failed to create instance: %s", err)
|
||||
}
|
||||
log.Printf("Xray Core Version: %s", core.Version())
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -170,10 +176,7 @@ func (p *Panel) Start() {
|
||||
switch nodeConfig.PanelType {
|
||||
case "SSpanel":
|
||||
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
||||
// todo Deprecated after 2023.6.1
|
||||
case "V2board":
|
||||
apiClient = v2board.New(nodeConfig.ApiConfig)
|
||||
case "NewV2board":
|
||||
case "NewV2board", "V2board":
|
||||
apiClient = newV2board.New(nodeConfig.ApiConfig)
|
||||
case "PMpanel":
|
||||
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
||||
@@ -181,6 +184,10 @@ func (p *Panel) Start() {
|
||||
apiClient = proxypanel.New(nodeConfig.ApiConfig)
|
||||
case "V2RaySocks":
|
||||
apiClient = v2raysocks.New(nodeConfig.ApiConfig)
|
||||
case "GoV2Panel":
|
||||
apiClient = gov2panel.New(nodeConfig.ApiConfig)
|
||||
case "BunPanel":
|
||||
apiClient = bunpanel.New(nodeConfig.ApiConfig)
|
||||
default:
|
||||
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
|
||||
}
|
||||
@@ -201,7 +208,7 @@ func (p *Panel) Start() {
|
||||
for _, s := range p.Service {
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
log.Panicf("Panel Start fialed: %s", err)
|
||||
log.Panicf("Panel Start failed: %s", err)
|
||||
}
|
||||
}
|
||||
p.Running = true
|
||||
@@ -215,7 +222,7 @@ func (p *Panel) Close() {
|
||||
for _, s := range p.Service {
|
||||
err := s.Close()
|
||||
if err != nil {
|
||||
log.Panicf("Panel Close fialed: %s", err)
|
||||
log.Panicf("Panel Close failed: %s", err)
|
||||
}
|
||||
}
|
||||
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,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
@@ -6,21 +6,24 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ListenIP string `mapstructure:"ListenIP"`
|
||||
SendIP string `mapstructure:"SendIP"`
|
||||
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
||||
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
|
||||
EnableDNS bool `mapstructure:"EnableDNS"`
|
||||
DNSType string `mapstructure:"DNSType"`
|
||||
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
||||
DisableGetRule bool `mapstructure:"DisableGetRule"`
|
||||
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
|
||||
EnableFallback bool `mapstructure:"EnableFallback"`
|
||||
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
||||
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
||||
AutoSpeedLimitConfig *AutoSpeedLimitConfig `mapstructure:"AutoSpeedLimitConfig"`
|
||||
GlobalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig `mapstructure:"GlobalDeviceLimitConfig"`
|
||||
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
||||
ListenIP string `mapstructure:"ListenIP"`
|
||||
SendIP string `mapstructure:"SendIP"`
|
||||
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
||||
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
|
||||
EnableDNS bool `mapstructure:"EnableDNS"`
|
||||
DNSType string `mapstructure:"DNSType"`
|
||||
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
||||
DisableGetRule bool `mapstructure:"DisableGetRule"`
|
||||
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
|
||||
EnableFallback bool `mapstructure:"EnableFallback"`
|
||||
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
||||
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
||||
AutoSpeedLimitConfig *AutoSpeedLimitConfig `mapstructure:"AutoSpeedLimitConfig"`
|
||||
GlobalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig `mapstructure:"GlobalDeviceLimitConfig"`
|
||||
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
||||
DisableLocalREALITYConfig bool `mapstructure:"DisableLocalREALITYConfig"`
|
||||
EnableREALITY bool `mapstructure:"EnableREALITY"`
|
||||
REALITYConfigs *REALITYConfig `mapstructure:"REALITYConfigs"`
|
||||
}
|
||||
|
||||
type AutoSpeedLimitConfig struct {
|
||||
@@ -37,3 +40,15 @@ type FallBackConfig struct {
|
||||
Dest string `mapstructure:"Dest"`
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -43,6 +44,7 @@ type Controller struct {
|
||||
stm stats.Manager
|
||||
dispatcher *mydispatcher.DefaultDispatcher
|
||||
startAt time.Time
|
||||
logger *log.Entry
|
||||
}
|
||||
|
||||
type periodicTask struct {
|
||||
@@ -52,6 +54,11 @@ type periodicTask struct {
|
||||
|
||||
// New return a Controller service with default parameters.
|
||||
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{
|
||||
server: server,
|
||||
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),
|
||||
dispatcher: server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher),
|
||||
startAt: time.Now(),
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return controller
|
||||
@@ -75,12 +83,16 @@ func (c *Controller) Start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newNodeInfo.Port == 0 {
|
||||
return errors.New("server port must > 0")
|
||||
}
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
|
||||
// Add new tag
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
c.logger.Panic(err)
|
||||
return err
|
||||
}
|
||||
// Update user
|
||||
@@ -99,16 +111,16 @@ func (c *Controller) Start() error {
|
||||
|
||||
// Add Limiter
|
||||
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, userInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
}
|
||||
|
||||
// Add Rule Manager
|
||||
if !c.config.DisableGetRule {
|
||||
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 {
|
||||
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
|
||||
if c.nodeInfo.EnableTLS {
|
||||
if c.nodeInfo.EnableTLS && c.config.EnableREALITY == false {
|
||||
c.tasks = append(c.tasks, periodicTask{
|
||||
tag: "cert monitor",
|
||||
Periodic: &task.Periodic{
|
||||
@@ -150,7 +162,7 @@ func (c *Controller) Start() error {
|
||||
|
||||
// Start periodic 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()
|
||||
}
|
||||
|
||||
@@ -162,7 +174,7 @@ func (c *Controller) Close() error {
|
||||
for i := range c.tasks {
|
||||
if c.tasks[i].Periodic != 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
|
||||
var nodeInfoChanged = true
|
||||
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
if err.Error() == api.NodeNotModified {
|
||||
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
|
||||
var usersChanged = true
|
||||
newUserInfo, err := c.apiClient.GetUserList()
|
||||
if err != nil {
|
||||
if err.Error() == "users no change" {
|
||||
if err.Error() == api.UserNotModified {
|
||||
usersChanged = false
|
||||
newUserInfo = c.userList
|
||||
} else {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var nodeInfoChanged = false
|
||||
// If nodeInfo changed
|
||||
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
||||
// Remove old tag
|
||||
oldTag := c.Tag
|
||||
err := c.removeOldTag(oldTag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
if c.nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||
err = c.removeOldTag(fmt.Sprintf("dokodemo-door_%s+1", c.Tag))
|
||||
}
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
// Add new tag
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
nodeInfoChanged = true
|
||||
// Remove Old limiter
|
||||
if err = c.DeleteInboundLimiter(oldTag); err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
if nodeInfoChanged {
|
||||
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
||||
// Remove old tag
|
||||
oldTag := c.Tag
|
||||
err := c.removeOldTag(oldTag)
|
||||
if err != 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 err != nil {
|
||||
c.logger.Print(err)
|
||||
return nil
|
||||
}
|
||||
// Add new tag
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
c.logger.Print(err)
|
||||
return nil
|
||||
}
|
||||
nodeInfoChanged = true
|
||||
// Remove Old limiter
|
||||
if err = c.DeleteInboundLimiter(oldTag); err != nil {
|
||||
c.logger.Print(err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
nodeInfoChanged = false
|
||||
}
|
||||
}
|
||||
|
||||
// Check Rule
|
||||
if !c.config.DisableGetRule {
|
||||
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 {
|
||||
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 {
|
||||
err = c.addNewUser(newUserInfo, newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add Limiter
|
||||
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, newUserInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
} else {
|
||||
var deleted, added []api.UserInfo
|
||||
if usersChanged {
|
||||
@@ -263,21 +290,21 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
||||
}
|
||||
err := c.removeUsers(deletedEmail, c.Tag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
}
|
||||
}
|
||||
if len(added) > 0 {
|
||||
err = c.addNewUser(&added, c.nodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
}
|
||||
// Update Limiter
|
||||
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
|
||||
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) {
|
||||
users := make([]*protocol.User, 0)
|
||||
switch nodeInfo.NodeType {
|
||||
case "V2ray":
|
||||
if nodeInfo.EnableVless {
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
if nodeInfo.EnableVless || (nodeInfo.NodeType == "Vless" && nodeInfo.NodeType != "Vmess") {
|
||||
users = c.buildVlessUser(userInfo)
|
||||
} else {
|
||||
var alterID uint16 = 0
|
||||
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)
|
||||
users = c.buildVmessUser(userInfo)
|
||||
}
|
||||
case "Trojan":
|
||||
users = c.buildTrojanUser(userInfo)
|
||||
@@ -405,7 +425,7 @@ func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("%s Added %d new users", c.logPrefix(), len(*userInfo))
|
||||
c.logger.Printf("Added %d new users", len(*userInfo))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -453,7 +473,7 @@ func limitUser(c *Controller, user api.UserInfo, silentUsers *[]api.UserInfo) {
|
||||
currentSpeedLimit: c.config.AutoSpeedLimitConfig.LimitSpeed,
|
||||
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)
|
||||
*silentUsers = append(*silentUsers, user)
|
||||
}
|
||||
@@ -467,7 +487,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
// Get server status
|
||||
CPU, Mem, Disk, Uptime, err := serverstatus.GetSystemInfo()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
}
|
||||
err = c.apiClient.ReportNodeStatus(
|
||||
&api.NodeStatus{
|
||||
@@ -477,25 +497,25 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
Uptime: Uptime,
|
||||
})
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
}
|
||||
// Unlock users
|
||||
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)
|
||||
for user, limitInfo := range c.limitedUsers {
|
||||
if time.Now().Unix() > limitInfo.end {
|
||||
user.SpeedLimit = limitInfo.originSpeedLimit
|
||||
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)
|
||||
} 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 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 err := c.UpdateInboundLimiter(c.Tag, &limitedUsers); err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
}
|
||||
}
|
||||
if len(userTraffic) > 0 {
|
||||
@@ -556,7 +576,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
}
|
||||
// If report traffic error, not clear the traffic
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
} else {
|
||||
c.resetTraffic(&upCounterList, &downCounterList)
|
||||
}
|
||||
@@ -564,23 +584,23 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
|
||||
// Report Online info
|
||||
if onlineDevice, err := c.GetOnlineDevice(c.Tag); err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
} else if len(*onlineDevice) > 0 {
|
||||
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
} else {
|
||||
log.Printf("%s Report %d online users", c.logPrefix(), len(*onlineDevice))
|
||||
c.logger.Printf("Report %d online users", len(*onlineDevice))
|
||||
}
|
||||
}
|
||||
|
||||
// Report Illegal user
|
||||
if detectResult, err := c.GetDetectResult(c.Tag); err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
} else if len(*detectResult) > 0 {
|
||||
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
} 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)
|
||||
}
|
||||
|
||||
func (c *Controller) logPrefix() string {
|
||||
return fmt.Sprintf("[%s] %s(ID=%d)", c.clientInfo.APIHost, c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
||||
}
|
||||
// func (c *Controller) logPrefix() string {
|
||||
// return fmt.Sprintf("[%s] %s(ID=%d)", c.clientInfo.APIHost, c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
||||
// }
|
||||
|
||||
// Check Cert
|
||||
func (c *Controller) certMonitor() error {
|
||||
if c.nodeInfo.EnableTLS {
|
||||
if c.nodeInfo.EnableTLS && c.config.EnableREALITY == false {
|
||||
switch c.config.CertConfig.CertMode {
|
||||
case "dns", "http", "tls":
|
||||
lego, err := mylego.New(c.config.CertConfig)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
c.logger.Print(err)
|
||||
}
|
||||
// Xray-core supports the OcspStapling certification hot renew
|
||||
_, _, _, err = lego.RenewCert()
|
||||
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/sspanel"
|
||||
_ "github.com/XrayR-project/XrayR/cmd/distro/all"
|
||||
"github.com/XrayR-project/XrayR/common/mylego"
|
||||
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
||||
. "github.com/XrayR-project/XrayR/service/controller"
|
||||
)
|
||||
|
||||
|
@@ -41,7 +41,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
// SniffingConfig
|
||||
sniffingConfig := &conf.SniffingConfig{
|
||||
Enabled: true,
|
||||
DestOverride: &conf.StringList{"http", "tls"},
|
||||
DestOverride: &conf.StringList{"http", "tls", "quic", "fakedns"},
|
||||
}
|
||||
if config.DisableSniffing {
|
||||
sniffingConfig.Enabled = false
|
||||
@@ -57,8 +57,8 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
var proxySetting any
|
||||
// Build Protocol and Protocol setting
|
||||
switch nodeInfo.NodeType {
|
||||
case "V2ray":
|
||||
if nodeInfo.EnableVless {
|
||||
case "V2ray", "Vmess", "Vless":
|
||||
if nodeInfo.EnableVless || (nodeInfo.NodeType == "Vless" && nodeInfo.NodeType != "Vmess") {
|
||||
protocol = "vless"
|
||||
// Enable fallback
|
||||
if config.EnableFallback {
|
||||
@@ -139,7 +139,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
|
||||
setting, err := json.Marshal(proxySetting)
|
||||
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.Settings = &setting
|
||||
@@ -164,6 +164,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
headers["Host"] = nodeInfo.Host
|
||||
wsSettings := &conf.WebSocketConfig{
|
||||
AcceptProxyProtocol: config.EnableProxyProtocol,
|
||||
Host: nodeInfo.Host,
|
||||
Path: nodeInfo.Path,
|
||||
Headers: headers,
|
||||
}
|
||||
@@ -171,40 +172,90 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
case "http":
|
||||
hosts := conf.StringList{nodeInfo.Host}
|
||||
httpSettings := &conf.HTTPConfig{
|
||||
Host: &hosts,
|
||||
Path: nodeInfo.Path,
|
||||
Host: &hosts,
|
||||
Path: nodeInfo.Path,
|
||||
Method: nodeInfo.Method,
|
||||
Headers: nodeInfo.HttpHeaders,
|
||||
}
|
||||
streamSetting.HTTPSettings = httpSettings
|
||||
case "grpc":
|
||||
grpcSettings := &conf.GRPCConfig{
|
||||
ServiceName: nodeInfo.ServiceName,
|
||||
Authority: nodeInfo.Authority,
|
||||
}
|
||||
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
|
||||
|
||||
// Build TLS and XTLS settings
|
||||
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
||||
streamSetting.Security = nodeInfo.TLSType
|
||||
// Build TLS and REALITY settings
|
||||
var isREALITY bool
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nodeInfo.TLSType == "tls" {
|
||||
tlsSettings := &conf.TLSConfig{
|
||||
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 := &conf.TLSConfig{
|
||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
||||
}
|
||||
tlsSettings.Certs = append(tlsSettings.Certs, &conf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
|
||||
streamSetting.TLSSettings = tlsSettings
|
||||
}
|
||||
|
||||
// Support ProxyProtocol for any transport protocol
|
||||
@@ -260,13 +311,13 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun
|
||||
for i, c := range fallbackConfigs {
|
||||
|
||||
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
|
||||
dest, err := json.Marshal(c.Dest)
|
||||
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{
|
||||
Name: c.SNI,
|
||||
@@ -288,13 +339,13 @@ func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInbo
|
||||
for i, c := range fallbackConfigs {
|
||||
|
||||
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
|
||||
dest, err := json.Marshal(c.Dest)
|
||||
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{
|
||||
Name: c.SNI,
|
||||
|
@@ -19,7 +19,6 @@ func TestBuildV2ray(t *testing.T) {
|
||||
Host: "test.test.tk",
|
||||
Path: "v2ray",
|
||||
EnableTLS: false,
|
||||
TLSType: "tls",
|
||||
}
|
||||
certConfig := &mylego.CertConfig{
|
||||
CertMode: "http",
|
||||
@@ -47,7 +46,6 @@ func TestBuildTrojan(t *testing.T) {
|
||||
Host: "trojan.test.tk",
|
||||
Path: "v2ray",
|
||||
EnableTLS: false,
|
||||
TLSType: "tls",
|
||||
}
|
||||
DNSEnv := make(map[string]string)
|
||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||
@@ -79,7 +77,6 @@ func TestBuildSS(t *testing.T) {
|
||||
Host: "test.test.tk",
|
||||
Path: "v2ray",
|
||||
EnableTLS: false,
|
||||
TLSType: "tls",
|
||||
}
|
||||
DNSEnv := make(map[string]string)
|
||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"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.Tag = tag
|
||||
|
||||
// Build Send IP address
|
||||
if config.SendIP != "" {
|
||||
ipAddress := net.ParseAddress(config.SendIP)
|
||||
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
|
||||
}
|
||||
// SendThrough setting
|
||||
outboundDetourConfig.SendThrough = &config.SendIP
|
||||
|
||||
// Freedom Protocol setting
|
||||
var domainStrategy = "Asis"
|
||||
@@ -42,7 +38,7 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
|
||||
var setting json.RawMessage
|
||||
setting, err := json.Marshal(proxySetting)
|
||||
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
|
||||
return outboundDetourConfig.Build()
|
||||
|
@@ -1,12 +1,14 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||
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/serial"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
@@ -25,12 +27,11 @@ var AEADMethod = map[shadowsocks.CipherType]uint8{
|
||||
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))
|
||||
for i, user := range *userInfo {
|
||||
vmessAccount := &conf.VMessAccount{
|
||||
ID: user.UUID,
|
||||
AlterIds: serverAlterID,
|
||||
Security: "auto",
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
@@ -47,7 +48,7 @@ func (c *Controller) buildVlessUser(userInfo *[]api.UserInfo) (users []*protocol
|
||||
for i, user := range *userInfo {
|
||||
vlessAccount := &vless.Account{
|
||||
Id: user.UUID,
|
||||
Flow: "xtls-rprx-direct",
|
||||
Flow: c.nodeInfo.VlessFlow,
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
@@ -63,7 +64,6 @@ func (c *Controller) buildTrojanUser(userInfo *[]api.UserInfo) (users []*protoco
|
||||
for i, user := range *userInfo {
|
||||
trojanAccount := &trojan.Account{
|
||||
Password: user.UUID,
|
||||
Flow: "xtls-rprx-direct",
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
@@ -78,26 +78,19 @@ func (c *Controller) buildSSUser(userInfo *[]api.UserInfo, method string) (users
|
||||
users = make([]*protocol.User, len(*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)) {
|
||||
e := c.buildUserTag(&user)
|
||||
if len(user.Passwd) < 16 {
|
||||
newError("shadowsocks2022 key's length must be greater than 16").AtError().WriteToLog()
|
||||
return
|
||||
}
|
||||
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]
|
||||
userKey, err := c.checkShadowsocksPassword(user.Passwd, method)
|
||||
if err != nil {
|
||||
errors.LogError(context.Background(), "[UID: %d] %s", user.UID, err)
|
||||
continue
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
Email: e,
|
||||
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||
Key: base64.StdEncoding.EncodeToString([]byte(userKey)),
|
||||
Key: userKey,
|
||||
Email: e,
|
||||
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
|
||||
if C.Contains(shadowaead_2022.List, strings.ToLower(user.Method)) {
|
||||
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{
|
||||
Level: 0,
|
||||
Email: e,
|
||||
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||
Key: base64.StdEncoding.EncodeToString([]byte(user.Passwd)),
|
||||
Key: userKey,
|
||||
Email: e,
|
||||
Level: 0,
|
||||
}),
|
||||
@@ -168,3 +166,23 @@ func cipherFromString(c string) shadowsocks.CipherType {
|
||||
func (c *Controller) buildUserTag(user *api.UserInfo) string {
|
||||
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