mirror of
https://github.com/XrayR-project/XrayR.git
synced 2025-08-10 19:31:47 +00:00
Compare commits
154 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0bea22e145 | ||
![]() |
f25d5993f3 | ||
![]() |
676365b13b | ||
![]() |
e774d5c822 | ||
![]() |
be4f9cdac1 | ||
![]() |
9a06f78653 | ||
![]() |
85d73408c3 | ||
![]() |
c93fdb4cf3 | ||
![]() |
0d565b034b | ||
![]() |
212f0ff135 | ||
![]() |
608764a8a0 | ||
![]() |
fc16cb0972 | ||
![]() |
5397720430 | ||
![]() |
839b15c22c | ||
![]() |
5b45b8ffe8 | ||
![]() |
a56cd91b34 | ||
![]() |
b6600729b2 | ||
![]() |
8d0225bcbb | ||
![]() |
ce5fe799f4 | ||
![]() |
8763d5960f | ||
![]() |
7a452a4a53 | ||
![]() |
fac8b62286 | ||
![]() |
d320aadb54 | ||
![]() |
40ae48f507 | ||
![]() |
d1bc36782b | ||
![]() |
c04330d0bf | ||
![]() |
872eb12d35 | ||
![]() |
e1d4428d98 | ||
![]() |
0fef3cf278 | ||
![]() |
a4ca37b1de | ||
![]() |
74f3a75682 | ||
![]() |
183b1be519 | ||
![]() |
2f10c3f6b8 | ||
![]() |
86324ff1ae | ||
![]() |
1897404c9d | ||
![]() |
ee53e746c5 | ||
![]() |
656df61c17 | ||
![]() |
e0237f5c54 | ||
![]() |
e16d94fb4a | ||
![]() |
e357fc438f | ||
![]() |
cb1638ac21 | ||
![]() |
de0da25c21 | ||
![]() |
faec840c23 | ||
![]() |
a6a1baf70c | ||
![]() |
4013f71e4c | ||
![]() |
c8f0981b0e | ||
![]() |
5274edf657 | ||
![]() |
e6232c1852 | ||
![]() |
e0688fc609 | ||
![]() |
ffa444f2ab | ||
![]() |
81ba4ebb43 | ||
![]() |
f50c61c782 | ||
![]() |
9c356cd28c | ||
![]() |
310353f344 | ||
![]() |
808b5ecc3c | ||
![]() |
c4ef5bb843 | ||
![]() |
ddce3fa86d | ||
![]() |
209f5a17d6 | ||
![]() |
af3fae9cdb | ||
![]() |
3b96b352cb | ||
![]() |
398c3133d3 | ||
![]() |
a397af5d73 | ||
![]() |
b47954ea64 | ||
![]() |
9a2188cb0c | ||
![]() |
c7af43fc49 | ||
![]() |
87aa855154 | ||
![]() |
37eff6755c | ||
![]() |
5e346ddfee | ||
![]() |
838c667a87 | ||
![]() |
057f4156bf | ||
![]() |
1f59a7cd7a | ||
![]() |
2b5fa4feee | ||
![]() |
571191a190 | ||
![]() |
3d5891fef3 | ||
![]() |
70a0099f2c | ||
![]() |
af8f24b5b1 | ||
![]() |
79528d3e17 | ||
![]() |
2f0461ddda | ||
![]() |
023680fec7 | ||
![]() |
708adf1e43 | ||
![]() |
0bbec7ebeb | ||
![]() |
a9dfd5404f | ||
![]() |
39c1036c4a | ||
![]() |
7604e33b03 | ||
![]() |
a906006015 | ||
![]() |
cb5cc17a82 | ||
![]() |
1cf8bca79e | ||
![]() |
cbffafbb4c | ||
![]() |
b37705b374 | ||
![]() |
e00d228c3d | ||
![]() |
21847fee7d | ||
![]() |
f42e30cc61 | ||
![]() |
e3c7cf2a5f | ||
![]() |
82731d9d9c | ||
![]() |
45f4d80a49 | ||
![]() |
10691e4cf3 | ||
![]() |
9dbb231a12 | ||
![]() |
8724ece88a | ||
![]() |
f0610d09fd | ||
![]() |
8991284e19 | ||
![]() |
7a12a806a8 | ||
![]() |
fda14882a4 | ||
![]() |
710f081025 | ||
![]() |
c280cca7b3 | ||
![]() |
fa51cb5309 | ||
![]() |
529eb46ea0 | ||
![]() |
2a528070bf | ||
![]() |
d9089520db | ||
![]() |
bdbf916a14 | ||
![]() |
1ec1765517 | ||
![]() |
f29d2db235 | ||
![]() |
337f441c32 | ||
![]() |
c9895fa02f | ||
![]() |
5d6eeb4f29 | ||
![]() |
fc9cb4ac10 | ||
![]() |
01ef6fb699 | ||
![]() |
e46dc1d8d7 | ||
![]() |
84db0453cb | ||
![]() |
b934a52875 | ||
![]() |
246f9374a4 | ||
![]() |
4dd827d94d | ||
![]() |
c349d3d9a1 | ||
![]() |
48ca6d9648 | ||
![]() |
f7d1114fb2 | ||
![]() |
dbf7727bcd | ||
![]() |
09f27ec1f1 | ||
![]() |
61b501de3d | ||
![]() |
9947d7e4f3 | ||
![]() |
a0e1491bb5 | ||
![]() |
b31bef02da | ||
![]() |
25594bf9e2 | ||
![]() |
1566ffd937 | ||
![]() |
5682ab37db | ||
![]() |
454afe5285 | ||
![]() |
807a7182bb | ||
![]() |
911d06bece | ||
![]() |
7f823007cb | ||
![]() |
943660ee56 | ||
![]() |
8195f72eaa | ||
![]() |
35a6585db1 | ||
![]() |
f0911f6239 | ||
![]() |
afc11dbb41 | ||
![]() |
e1b0cd3c79 | ||
![]() |
6ce4d4e7a2 | ||
![]() |
febeee6b2c | ||
![]() |
fbe57822ce | ||
![]() |
63697586a2 | ||
![]() |
6b6e2a03d8 | ||
![]() |
674445afae | ||
![]() |
bf29bd11c4 | ||
![]() |
40ba9b202b | ||
![]() |
39a09bea92 | ||
![]() |
5c0eea8d44 | ||
![]() |
69b0afcb35 |
124
.github/build/friendly-filenames.json
vendored
124
.github/build/friendly-filenames.json
vendored
@@ -1,33 +1,95 @@
|
|||||||
{
|
{
|
||||||
"android-arm64": { "friendlyName": "android-arm64-v8a" },
|
"android-arm64": {
|
||||||
"darwin-amd64": { "friendlyName": "macos-64" },
|
"friendlyName": "android-arm64-v8a"
|
||||||
"darwin-arm64": { "friendlyName": "macos-arm64-v8a" },
|
},
|
||||||
"dragonfly-amd64": { "friendlyName": "dragonfly-64" },
|
"darwin-amd64": {
|
||||||
"freebsd-386": { "friendlyName": "freebsd-32" },
|
"friendlyName": "macos-64"
|
||||||
"freebsd-amd64": { "friendlyName": "freebsd-64" },
|
},
|
||||||
"freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" },
|
"darwin-arm64": {
|
||||||
"freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" },
|
"friendlyName": "macos-arm64-v8a"
|
||||||
"linux-386": { "friendlyName": "linux-32" },
|
},
|
||||||
"linux-amd64": { "friendlyName": "linux-64" },
|
"dragonfly-amd64": {
|
||||||
"linux-arm5": { "friendlyName": "linux-arm32-v5" },
|
"friendlyName": "dragonfly-64"
|
||||||
"linux-arm64": { "friendlyName": "linux-arm64-v8a" },
|
},
|
||||||
"linux-arm6": { "friendlyName": "linux-arm32-v6" },
|
"freebsd-386": {
|
||||||
"linux-arm7": { "friendlyName": "linux-arm32-v7a" },
|
"friendlyName": "freebsd-32"
|
||||||
"linux-mips64le": { "friendlyName": "linux-mips64le" },
|
},
|
||||||
"linux-mips64": { "friendlyName": "linux-mips64" },
|
"freebsd-amd64": {
|
||||||
"linux-mipslesoftfloat": { "friendlyName": "linux-mips32le-softfloat" },
|
"friendlyName": "freebsd-64"
|
||||||
"linux-mipsle": { "friendlyName": "linux-mips32le" },
|
},
|
||||||
"linux-mipssoftfloat": { "friendlyName": "linux-mips32-softfloat" },
|
"freebsd-arm64": {
|
||||||
"linux-mips": { "friendlyName": "linux-mips32" },
|
"friendlyName": "freebsd-arm64-v8a"
|
||||||
"linux-ppc64le": { "friendlyName": "linux-ppc64le" },
|
},
|
||||||
"linux-ppc64": { "friendlyName": "linux-ppc64" },
|
"freebsd-arm7": {
|
||||||
"linux-riscv64": { "friendlyName": "linux-riscv64" },
|
"friendlyName": "freebsd-arm32-v7a"
|
||||||
"linux-s390x": { "friendlyName": "linux-s390x" },
|
},
|
||||||
"openbsd-386": { "friendlyName": "openbsd-32" },
|
"linux-386": {
|
||||||
"openbsd-amd64": { "friendlyName": "openbsd-64" },
|
"friendlyName": "linux-32"
|
||||||
"openbsd-arm64": { "friendlyName": "openbsd-arm64-v8a" },
|
},
|
||||||
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
|
"linux-amd64": {
|
||||||
"windows-386": { "friendlyName": "windows-32" },
|
"friendlyName": "linux-64"
|
||||||
"windows-amd64": { "friendlyName": "windows-64" },
|
},
|
||||||
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
|
"linux-arm5": {
|
||||||
|
"friendlyName": "linux-arm32-v5"
|
||||||
|
},
|
||||||
|
"linux-arm64": {
|
||||||
|
"friendlyName": "linux-arm64-v8a"
|
||||||
|
},
|
||||||
|
"linux-arm6": {
|
||||||
|
"friendlyName": "linux-arm32-v6"
|
||||||
|
},
|
||||||
|
"linux-arm7": {
|
||||||
|
"friendlyName": "linux-arm32-v7a"
|
||||||
|
},
|
||||||
|
"linux-mips64le": {
|
||||||
|
"friendlyName": "linux-mips64le"
|
||||||
|
},
|
||||||
|
"linux-mips64": {
|
||||||
|
"friendlyName": "linux-mips64"
|
||||||
|
},
|
||||||
|
"linux-mipslesoftfloat": {
|
||||||
|
"friendlyName": "linux-mips32le-softfloat"
|
||||||
|
},
|
||||||
|
"linux-mipsle": {
|
||||||
|
"friendlyName": "linux-mips32le"
|
||||||
|
},
|
||||||
|
"linux-mipssoftfloat": {
|
||||||
|
"friendlyName": "linux-mips32-softfloat"
|
||||||
|
},
|
||||||
|
"linux-mips": {
|
||||||
|
"friendlyName": "linux-mips32"
|
||||||
|
},
|
||||||
|
"linux-ppc64le": {
|
||||||
|
"friendlyName": "linux-ppc64le"
|
||||||
|
},
|
||||||
|
"linux-ppc64": {
|
||||||
|
"friendlyName": "linux-ppc64"
|
||||||
|
},
|
||||||
|
"linux-riscv64": {
|
||||||
|
"friendlyName": "linux-riscv64"
|
||||||
|
},
|
||||||
|
"linux-s390x": {
|
||||||
|
"friendlyName": "linux-s390x"
|
||||||
|
},
|
||||||
|
"openbsd-386": {
|
||||||
|
"friendlyName": "openbsd-32"
|
||||||
|
},
|
||||||
|
"openbsd-amd64": {
|
||||||
|
"friendlyName": "openbsd-64"
|
||||||
|
},
|
||||||
|
"openbsd-arm64": {
|
||||||
|
"friendlyName": "openbsd-arm64-v8a"
|
||||||
|
},
|
||||||
|
"openbsd-arm7": {
|
||||||
|
"friendlyName": "openbsd-arm32-v7a"
|
||||||
|
},
|
||||||
|
"windows-386": {
|
||||||
|
"friendlyName": "windows-32"
|
||||||
|
},
|
||||||
|
"windows-amd64": {
|
||||||
|
"friendlyName": "windows-64"
|
||||||
|
},
|
||||||
|
"windows-arm7": {
|
||||||
|
"friendlyName": "windows-arm32-v7a"
|
||||||
}
|
}
|
||||||
|
}
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -35,11 +35,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -64,4 +64,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
50
.github/workflows/docker.yml
vendored
50
.github/workflows/docker.yml
vendored
@@ -2,39 +2,43 @@ name: Publish Docker image
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- "**/*.go"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
- ".github/workflows/*.yml"
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
jobs:
|
jobs:
|
||||||
push_to_registry:
|
push_to_registry:
|
||||||
name: Push Docker image to Docker Hub
|
name: Push Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Docker meta
|
uses: docker/setup-buildx-action@v2
|
||||||
id: docker_meta
|
- name: Log in to the Container registry
|
||||||
uses: crazy-max/ghaction-docker-meta@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
images: crackair/xrayr
|
registry: ghcr.io
|
||||||
tag-semver: |
|
username: ${{ github.actor }}
|
||||||
{{version}}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
{{major}}.{{minor}}
|
- name: Docker meta
|
||||||
-
|
id: meta
|
||||||
name: Set up Docker Buildx
|
uses: docker/metadata-action@v4
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
-
|
|
||||||
name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
- name: Build and push
|
||||||
-
|
uses: docker/build-push-action@v3
|
||||||
name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/arm/v7,linux/arm64,linux/amd64
|
platforms: linux/arm/v7,linux/arm64,linux/amd64,linux/s390x
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@@ -11,14 +11,14 @@ on:
|
|||||||
- "go.sum"
|
- "go.sum"
|
||||||
- ".github/workflows/*.yml"
|
- ".github/workflows/*.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [ opened, synchronize, reopened ]
|
||||||
paths:
|
paths:
|
||||||
- "**/*.go"
|
- "**/*.go"
|
||||||
- "go.mod"
|
- "go.mod"
|
||||||
- "go.sum"
|
- "go.sum"
|
||||||
- ".github/workflows/*.yml"
|
- ".github/workflows/*.yml"
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [ published ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -26,8 +26,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# Include amd64 on all platforms.
|
# Include amd64 on all platforms.
|
||||||
goos: [windows, freebsd, openbsd, linux, dragonfly, darwin]
|
goos: [ windows, freebsd, openbsd, linux, dragonfly, darwin ]
|
||||||
goarch: [amd64, 386]
|
goarch: [ amd64, 386 ]
|
||||||
exclude:
|
exclude:
|
||||||
# Exclude i386 on darwin and dragonfly.
|
# Exclude i386 on darwin and dragonfly.
|
||||||
- goarch: 386
|
- goarch: 386
|
||||||
@@ -74,8 +74,8 @@ jobs:
|
|||||||
goarch: mips
|
goarch: mips
|
||||||
# END MIPS
|
# END MIPS
|
||||||
# BEGIN PPC
|
# BEGIN PPC
|
||||||
- goos: linux
|
# - goos: linux # Removed due to the unsupport of shirou/gopsutil
|
||||||
goarch: ppc64
|
# goarch: ppc64
|
||||||
- goos: linux
|
- goos: linux
|
||||||
goarch: ppc64le
|
goarch: ppc64le
|
||||||
# END PPC
|
# END PPC
|
||||||
@@ -101,19 +101,19 @@ jobs:
|
|||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Show workflow information
|
- name: Show workflow information
|
||||||
id: get_filename
|
id: get_filename
|
||||||
run: |
|
run: |
|
||||||
export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
|
||||||
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
|
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
|
||||||
echo "::set-output name=ASSET_NAME::$_NAME"
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT
|
||||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ^1.17.2
|
go-version: ^1.19
|
||||||
|
|
||||||
- name: Get project dependencies
|
- name: Get project dependencies
|
||||||
run: go mod download
|
run: go mod download
|
||||||
@@ -141,6 +141,7 @@ jobs:
|
|||||||
cp ${GITHUB_WORKSPACE}/main/dns.json ./build_assets/dns.json
|
cp ${GITHUB_WORKSPACE}/main/dns.json ./build_assets/dns.json
|
||||||
cp ${GITHUB_WORKSPACE}/main/route.json ./build_assets/route.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_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/rulelist ./build_assets/rulelist
|
||||||
cp ${GITHUB_WORKSPACE}/main/config.yml.example ./build_assets/config.yml
|
cp ${GITHUB_WORKSPACE}/main/config.yml.example ./build_assets/config.yml
|
||||||
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
||||||
@@ -172,7 +173,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv build_assets XrayR-$ASSET_NAME
|
mv build_assets XrayR-$ASSET_NAME
|
||||||
- name: Upload files to Artifacts
|
- name: Upload files to Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: XrayR-${{ steps.get_filename.outputs.ASSET_NAME }}
|
name: XrayR-${{ steps.get_filename.outputs.ASSET_NAME }}
|
||||||
path: |
|
path: |
|
||||||
|
22
.github/workflows/stale.yml
vendored
Normal file
22
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Close inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v5
|
||||||
|
with:
|
||||||
|
days-before-issue-stale: 30
|
||||||
|
days-before-issue-close: 14
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-pr-close: -1
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,3 +13,6 @@ main/cert
|
|||||||
main/config.yml
|
main/config.yml
|
||||||
./vscode
|
./vscode
|
||||||
.idea/*
|
.idea/*
|
||||||
|
.DS_Store
|
||||||
|
*.bak
|
||||||
|
go.work*
|
@@ -1,5 +1,5 @@
|
|||||||
# Build go
|
# Build go
|
||||||
FROM golang:1.17-alpine AS builder
|
FROM golang:1.19-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
|
31
README.md
31
README.md
@@ -1,6 +1,12 @@
|
|||||||
# XrayR
|
# XrayR
|
||||||
|
|
||||||
[](https://t.me/XrayR_project)
|
[](https://t.me/XrayR_project)
|
||||||
[](https://t.me/XrayR_channel)
|
[](https://t.me/XrayR_channel)
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
[]()
|
||||||
|
|
||||||
A Xray backend framework that can easily support many panels.
|
A Xray backend framework that can easily support many panels.
|
||||||
|
|
||||||
@@ -8,12 +14,14 @@ A Xray backend framework that can easily support many panels.
|
|||||||
|
|
||||||
如果您喜欢本项目,可以右上角点个star+watch,持续关注本项目的进展。
|
如果您喜欢本项目,可以右上角点个star+watch,持续关注本项目的进展。
|
||||||
|
|
||||||
使用教程:[详细使用教程](https://crackair.gitbook.io/xrayr-project/)
|
使用教程:[详细使用教程](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
## 免责声明
|
## 免责声明
|
||||||
|
|
||||||
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
||||||
|
|
||||||
## 特点
|
## 特点
|
||||||
|
|
||||||
* 永久开源且免费。
|
* 永久开源且免费。
|
||||||
* 支持V2ray,Trojan, Shadowsocks多种协议。
|
* 支持V2ray,Trojan, Shadowsocks多种协议。
|
||||||
* 支持Vless和XTLS等新特性。
|
* 支持Vless和XTLS等新特性。
|
||||||
@@ -27,7 +35,7 @@ A Xray backend framework that can easily support many panels.
|
|||||||
## 功能介绍
|
## 功能介绍
|
||||||
|
|
||||||
| 功能 | v2ray | trojan | shadowsocks |
|
| 功能 | v2ray | trojan | shadowsocks |
|
||||||
| --------------- | ----- | ------ | ----------- |
|
|-----------|-------|--------|-------------|
|
||||||
| 获取节点信息 | √ | √ | √ |
|
| 获取节点信息 | √ | √ | √ |
|
||||||
| 获取用户信息 | √ | √ | √ |
|
| 获取用户信息 | √ | √ | √ |
|
||||||
| 用户流量统计 | √ | √ | √ |
|
| 用户流量统计 | √ | √ | √ |
|
||||||
@@ -40,29 +48,36 @@ A Xray backend framework that can easily support many panels.
|
|||||||
| 节点端口限速 | √ | √ | √ |
|
| 节点端口限速 | √ | √ | √ |
|
||||||
| 按照用户限速 | √ | √ | √ |
|
| 按照用户限速 | √ | √ | √ |
|
||||||
| 自定义DNS | √ | √ | √ |
|
| 自定义DNS | √ | √ | √ |
|
||||||
|
|
||||||
## 支持前端
|
## 支持前端
|
||||||
|
|
||||||
| 前端 | v2ray | trojan | shadowsocks |
|
| 前端 | v2ray | trojan | shadowsocks |
|
||||||
| ------------------------------------------------------ | ----- | ------ | ------------------------------ |
|
|--------------------------------------------------------|-------|--------|-------------------------|
|
||||||
| sspanel-uim | √ | √ | √ (单端口多用户和V2ray-Plugin) |
|
| sspanel-uim | √ | √ | √ (单端口多用户和V2ray-Plugin) |
|
||||||
| v2board | √ | √ | √ |
|
| v2board | √ | √ | √ |
|
||||||
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||||
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||||
|
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
||||||
|
|
||||||
## 软件安装
|
## 软件安装
|
||||||
|
|
||||||
### 一键安装
|
### 一键安装
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh)
|
wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh && bash install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用Docker部署软件
|
### 使用Docker部署软件
|
||||||
[Docker部署教程](https://crackair.gitbook.io/xrayr-project/xrayr-xia-zai-he-an-zhuang/install/docker)
|
|
||||||
|
[Docker部署教程](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/docker)
|
||||||
|
|
||||||
### 手动安装
|
### 手动安装
|
||||||
[手动安装教程](https://crackair.gitbook.io/xrayr-project/xrayr-xia-zai-he-an-zhuang/install/manual)
|
|
||||||
|
[手动安装教程](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
|
||||||
|
|
||||||
## 配置文件及详细使用教程
|
## 配置文件及详细使用教程
|
||||||
|
|
||||||
[详细使用教程](https://crackair.gitbook.io/xrayr-project/)
|
[详细使用教程](https://xrayr-project.github.io/XrayR-doc/)
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
@@ -80,7 +95,9 @@ bash <(curl -Ls https://raw.githubusercontent.com/XrayR-project/XrayR-release/ma
|
|||||||
[XrayR后端讨论](https://t.me/XrayR_project)
|
[XrayR后端讨论](https://t.me/XrayR_project)
|
||||||
|
|
||||||
[XrayR通知](https://t.me/XrayR_channel)
|
[XrayR通知](https://t.me/XrayR_channel)
|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
|
|
||||||
[](https://starchart.cc/XrayR-project/XrayR)
|
[](https://starchart.cc/XrayR-project/XrayR)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// API config
|
// Config API config
|
||||||
type Config struct {
|
type Config struct {
|
||||||
APIHost string `mapstructure:"ApiHost"`
|
APIHost string `mapstructure:"ApiHost"`
|
||||||
NodeID int `mapstructure:"NodeID"`
|
NodeID int `mapstructure:"NodeID"`
|
||||||
@@ -20,20 +20,20 @@ type Config struct {
|
|||||||
DisableCustomConfig bool `mapstructure:"DisableCustomConfig"`
|
DisableCustomConfig bool `mapstructure:"DisableCustomConfig"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node status
|
// NodeStatus Node status
|
||||||
type NodeStatus struct {
|
type NodeStatus struct {
|
||||||
CPU float64
|
CPU float64
|
||||||
Mem float64
|
Mem float64
|
||||||
Disk float64
|
Disk float64
|
||||||
Uptime int
|
Uptime uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeInfo struct {
|
type NodeInfo struct {
|
||||||
NodeType string // Must be V2ray, Trojan, and Shadowsocks
|
NodeType string // Must be V2ray, Trojan, and Shadowsocks
|
||||||
NodeID int
|
NodeID int
|
||||||
Port int
|
Port uint32
|
||||||
SpeedLimit uint64 // Bps
|
SpeedLimit uint64 // Bps
|
||||||
AlterID int
|
AlterID uint16
|
||||||
TransportProtocol string
|
TransportProtocol string
|
||||||
FakeType string
|
FakeType string
|
||||||
Host string
|
Host string
|
||||||
@@ -42,6 +42,7 @@ type NodeInfo struct {
|
|||||||
TLSType string
|
TLSType string
|
||||||
EnableVless bool
|
EnableVless bool
|
||||||
CypherMethod string
|
CypherMethod string
|
||||||
|
ServerKey string
|
||||||
ServiceName string
|
ServiceName string
|
||||||
Header json.RawMessage
|
Header json.RawMessage
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ type UserInfo struct {
|
|||||||
UID int
|
UID int
|
||||||
Email string
|
Email string
|
||||||
Passwd string
|
Passwd string
|
||||||
Port int
|
Port uint32
|
||||||
Method string
|
Method string
|
||||||
SpeedLimit uint64 // Bps
|
SpeedLimit uint64 // Bps
|
||||||
DeviceLimit int
|
DeviceLimit int
|
||||||
@@ -59,7 +60,7 @@ type UserInfo struct {
|
|||||||
Obfs string
|
Obfs string
|
||||||
ObfsParam string
|
ObfsParam string
|
||||||
UUID string
|
UUID string
|
||||||
AlterID int
|
AlterID uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnlineUser struct {
|
type OnlineUser struct {
|
||||||
|
1
api/newV2board/model.go
Normal file
1
api/newV2board/model.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package newV2board
|
369
api/newV2board/v2board.go
Normal file
369
api/newV2board/v2board.go
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
package newV2board
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"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
|
||||||
|
resp atomic.Value
|
||||||
|
eTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
"node_type": 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,
|
||||||
|
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() > 399 {
|
||||||
|
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 panel
|
||||||
|
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||||
|
path := "/api/v1/server/UniProxy/config"
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Get(path)
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.resp.Store(response)
|
||||||
|
|
||||||
|
switch c.NodeType {
|
||||||
|
case "V2ray":
|
||||||
|
nodeInfo, err = c.parseV2rayNodeResponse(response)
|
||||||
|
case "Trojan":
|
||||||
|
nodeInfo, err = c.parseTrojanNodeResponse(response)
|
||||||
|
case "Shadowsocks":
|
||||||
|
nodeInfo, err = c.parseSSNodeResponse(response)
|
||||||
|
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 panel
|
||||||
|
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||||
|
path := "/api/v1/server/UniProxy/user"
|
||||||
|
|
||||||
|
switch c.NodeType {
|
||||||
|
case "V2ray", "Trojan", "Shadowsocks":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetHeader("If-None-Match", c.eTag).
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
// update etag
|
||||||
|
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTag {
|
||||||
|
c.eTag = res.Header().Get("Etag")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, path, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
numOfUsers := len(response.Get("users").MustArray())
|
||||||
|
userList := make([]api.UserInfo, numOfUsers)
|
||||||
|
for i := 0; i < numOfUsers; i++ {
|
||||||
|
user := response.Get("users").GetIndex(i)
|
||||||
|
u := api.UserInfo{}
|
||||||
|
u.UID = user.Get("id").MustInt()
|
||||||
|
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8) // todo waiting v2board send configuration
|
||||||
|
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
|
||||||
|
u.UUID = user.Get("uuid").MustString()
|
||||||
|
u.Email = u.UUID + "@v2board.user"
|
||||||
|
if c.NodeType == "Shadowsocks" {
|
||||||
|
u.Passwd = u.UUID
|
||||||
|
}
|
||||||
|
userList[i] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportUserTraffic reports the user traffic
|
||||||
|
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||||
|
path := "/api/v1/server/UniProxy/push"
|
||||||
|
|
||||||
|
// json structure: {uid1: [u, d], uid2: [u, d], uid1: [u, d], uid3: [u, d]}
|
||||||
|
data := make(map[int][]int64, len(*userTraffic))
|
||||||
|
for _, traffic := range *userTraffic {
|
||||||
|
data[traffic.UID] = []int64{traffic.Upload, traffic.Download}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
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
|
||||||
|
|
||||||
|
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{
|
||||||
|
ID: i,
|
||||||
|
Pattern: regexp.MustCompile(r["match"].(string)),
|
||||||
|
}
|
||||||
|
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 nodeInfo format
|
||||||
|
func (c *APIClient) parseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
|
var TLSType = "tls"
|
||||||
|
if c.EnableXTLS {
|
||||||
|
TLSType = "xtls"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GeneralNodeInfo
|
||||||
|
nodeInfo := &api.NodeInfo{
|
||||||
|
NodeType: c.NodeType,
|
||||||
|
NodeID: c.NodeID,
|
||||||
|
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||||
|
TransportProtocol: "tcp",
|
||||||
|
EnableTLS: true,
|
||||||
|
TLSType: TLSType,
|
||||||
|
Host: nodeInfoResponse.Get("host").MustString(),
|
||||||
|
ServiceName: nodeInfoResponse.Get("server_name").MustString(),
|
||||||
|
}
|
||||||
|
return nodeInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSSNodeResponse parse the response for the given nodeInfo format
|
||||||
|
func (c *APIClient) parseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
|
// Create GeneralNodeInfo
|
||||||
|
return &api.NodeInfo{
|
||||||
|
NodeType: c.NodeType,
|
||||||
|
NodeID: c.NodeID,
|
||||||
|
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||||
|
TransportProtocol: "tcp",
|
||||||
|
CypherMethod: nodeInfoResponse.Get("cipher").MustString(),
|
||||||
|
ServerKey: nodeInfoResponse.Get("server_key").MustString(), // shadowsocks2022 share key
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseV2rayNodeResponse parse the response for the given nodeInfo format
|
||||||
|
func (c *APIClient) parseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
|
var (
|
||||||
|
TLSType = "tls"
|
||||||
|
path, host, serviceName string
|
||||||
|
header json.RawMessage
|
||||||
|
enableTLS bool
|
||||||
|
alterID uint16 = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.EnableXTLS {
|
||||||
|
TLSType = "xtls"
|
||||||
|
}
|
||||||
|
|
||||||
|
transportProtocol := nodeInfoResponse.Get("network").MustString()
|
||||||
|
|
||||||
|
switch transportProtocol {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
case "tcp":
|
||||||
|
if data, ok := nodeInfoResponse.Get("networkSettings").CheckGet("headers"); ok {
|
||||||
|
if httpHeader, err := data.MarshalJSON(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
header = httpHeader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeInfoResponse.Get("tls").MustInt() == 1 {
|
||||||
|
enableTLS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GeneralNodeInfo
|
||||||
|
return &api.NodeInfo{
|
||||||
|
NodeType: c.NodeType,
|
||||||
|
NodeID: c.NodeID,
|
||||||
|
Port: uint32(nodeInfoResponse.Get("server_port").MustUint64()),
|
||||||
|
AlterID: alterID,
|
||||||
|
TransportProtocol: transportProtocol,
|
||||||
|
EnableTLS: enableTLS,
|
||||||
|
TLSType: TLSType,
|
||||||
|
Path: path,
|
||||||
|
Host: host,
|
||||||
|
EnableVless: c.EnableVless,
|
||||||
|
ServiceName: serviceName,
|
||||||
|
Header: header,
|
||||||
|
}, nil
|
||||||
|
}
|
101
api/newV2board/v2board_test.go
Normal file
101
api/newV2board/v2board_test.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package newV2board_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/api/newV2board"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateClient() api.API {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "http://localhost:9897",
|
||||||
|
Key: "qwertyuiopasdfghjkl",
|
||||||
|
NodeID: 1,
|
||||||
|
NodeType: "V2ray",
|
||||||
|
}
|
||||||
|
client := newV2board.New(apiConfig)
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSSNodeInfo(t *testing.T) {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "http://127.0.0.1:668",
|
||||||
|
Key: "qwertyuiopasdfghjkl",
|
||||||
|
NodeID: 1,
|
||||||
|
NodeType: "Shadowsocks",
|
||||||
|
}
|
||||||
|
client := newV2board.New(apiConfig)
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "http://127.0.0.1:668",
|
||||||
|
Key: "qwertyuiopasdfghjkl",
|
||||||
|
NodeID: 1,
|
||||||
|
NodeType: "Trojan",
|
||||||
|
}
|
||||||
|
client := newV2board.New(apiConfig)
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserList(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(userList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReportReportUserTraffic(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
generalUserTraffic := make([]api.UserTraffic, len(*userList))
|
||||||
|
for i, userInfo := range *userList {
|
||||||
|
generalUserTraffic[i] = api.UserTraffic{
|
||||||
|
UID: userInfo.UID,
|
||||||
|
Upload: 114514,
|
||||||
|
Download: 114514,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// client.Debug()
|
||||||
|
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
}
|
@@ -9,14 +9,14 @@ type NodeInfoResponse struct {
|
|||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
TrafficRate float64 `json:"trafficRate"`
|
TrafficRate float64 `json:"trafficRate"`
|
||||||
RawServerString string `json:"outServer"`
|
RawServerString string `json:"outServer"`
|
||||||
Port int `json:"outPort"`
|
Port uint32 `json:"outPort"`
|
||||||
AlterId int `json:"alterId"`
|
AlterId uint16 `json:"alterId"`
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Security string `json:"security"`
|
Security string `json:"security"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Grpc bool `json:"grpc"`
|
Grpc bool `json:"grpc"`
|
||||||
Sni string `json:sni`
|
Sni string `json:"sni"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserResponse is the response of user
|
// UserResponse is the response of user
|
||||||
|
@@ -11,8 +11,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIClient create a api client to the panel.
|
// APIClient create a api client to the panel.
|
||||||
@@ -76,7 +77,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// open the file
|
// open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
|
||||||
//handle errors while opening
|
// handle errors while opening
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error when opening file: %s", err)
|
log.Printf("Error when opening file: %s", err)
|
||||||
return LocalRuleList
|
return LocalRuleList
|
||||||
@@ -94,7 +95,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// handle first encountered error while reading
|
// handle first encountered error while reading
|
||||||
if err := fileScanner.Err(); err != nil {
|
if err := fileScanner.Err(); err != nil {
|
||||||
log.Fatalf("Error while reading file: %s", err)
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
return make([]api.DetectRule, 0)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
file.Close()
|
||||||
@@ -130,7 +131,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
|
|
||||||
if response.Ret != 200 {
|
if response.Ret != 200 {
|
||||||
res, _ := json.Marshal(&response)
|
res, _ := json.Marshal(&response)
|
||||||
return nil, fmt.Errorf("Ret %s invalid", string(res))
|
return nil, fmt.Errorf("ret %s invalid", string(res))
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
@@ -167,7 +168,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
nodeInfoResponse := new(NodeInfoResponse)
|
nodeInfoResponse := new(NodeInfoResponse)
|
||||||
|
|
||||||
if err := json.Unmarshal(response.Data, nodeInfoResponse); err != nil {
|
if err := json.Unmarshal(response.Data, nodeInfoResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
case "V2ray":
|
case "V2ray":
|
||||||
@@ -177,7 +178,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
nodeInfo, err = c.ParseSSNodeResponse(nodeInfoResponse)
|
nodeInfo, err = c.ParseSSNodeResponse(nodeInfoResponse)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -219,12 +220,12 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
|
|
||||||
var userListResponse *[]UserResponse
|
var userListResponse *[]UserResponse
|
||||||
if err := json.Unmarshal(response.Data, &userListResponse); err != nil {
|
if err := json.Unmarshal(response.Data, &userListResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||||
}
|
}
|
||||||
userList, err := c.ParseUserListResponse(userListResponse)
|
userList, err := c.ParseUserListResponse(userListResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, _ := json.Marshal(userListResponse)
|
res, _ := json.Marshal(userListResponse)
|
||||||
return nil, fmt.Errorf("Parse user list failed: %s", string(res))
|
return nil, fmt.Errorf("parse user list failed: %s", string(res))
|
||||||
}
|
}
|
||||||
return userList, nil
|
return userList, nil
|
||||||
}
|
}
|
||||||
@@ -234,7 +235,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//ReportNodeOnlineUsers reports online user ip
|
// ReportNodeOnlineUsers reports online user ip
|
||||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||||
var nodeType = ""
|
var nodeType = ""
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
@@ -338,7 +339,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|||||||
ruleListResponse := new([]RuleItem)
|
ruleListResponse := new([]RuleItem)
|
||||||
|
|
||||||
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
|
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range *ruleListResponse {
|
for _, r := range *ruleListResponse {
|
||||||
@@ -411,11 +412,8 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
var port int = 0
|
|
||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
|
|
||||||
port = nodeInfoResponse.Port
|
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
@@ -425,7 +423,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
|
|||||||
nodeinfo := &api.NodeInfo{
|
nodeinfo := &api.NodeInfo{
|
||||||
NodeType: c.NodeType,
|
NodeType: c.NodeType,
|
||||||
NodeID: c.NodeID,
|
NodeID: c.NodeID,
|
||||||
Port: port,
|
Port: nodeInfoResponse.Port,
|
||||||
SpeedLimit: speedlimit,
|
SpeedLimit: speedlimit,
|
||||||
TransportProtocol: "tcp",
|
TransportProtocol: "tcp",
|
||||||
CypherMethod: nodeInfoResponse.Method,
|
CypherMethod: nodeInfoResponse.Method,
|
||||||
|
@@ -85,7 +85,7 @@ func TestGetUserList(t *testing.T) {
|
|||||||
func TestReportNodeStatus(t *testing.T) {
|
func TestReportNodeStatus(t *testing.T) {
|
||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
nodeStatus := &api.NodeStatus{
|
nodeStatus := &api.NodeStatus{
|
||||||
1, 1, 1, 256,
|
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
|
||||||
}
|
}
|
||||||
err := client.ReportNodeStatus(nodeStatus)
|
err := client.ReportNodeStatus(nodeStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,7 +107,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
|
|||||||
IP: fmt.Sprintf("1.1.1.%d", i),
|
IP: fmt.Sprintf("1.1.1.%d", i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//client.Debug()
|
// client.Debug()
|
||||||
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -128,7 +128,7 @@ func TestReportReportUserTraffic(t *testing.T) {
|
|||||||
Download: 114514,
|
Download: 114514,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//client.Debug()
|
// client.Debug()
|
||||||
err = client.ReportUserTraffic(&generalUserTraffic)
|
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -150,8 +150,8 @@ func TestReportIllegal(t *testing.T) {
|
|||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
|
|
||||||
detectResult := []api.DetectResult{
|
detectResult := []api.DetectResult{
|
||||||
api.DetectResult{1, 2},
|
{1, 2},
|
||||||
api.DetectResult{1, 3},
|
{1, 3},
|
||||||
}
|
}
|
||||||
client.Debug()
|
client.Debug()
|
||||||
err := client.ReportIllegal(&detectResult)
|
err := client.ReportIllegal(&detectResult)
|
||||||
|
@@ -19,8 +19,8 @@ type V2rayNodeInfo struct {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Cert string `json:"pem"`
|
Cert string `json:"pem"`
|
||||||
V2License string `json:"v2_license"`
|
V2License string `json:"v2_license"`
|
||||||
V2AlterID int `json:"v2_alter_id"`
|
V2AlterID uint16 `json:"v2_alter_id"`
|
||||||
V2Port int `json:"v2_port"`
|
V2Port uint32 `json:"v2_port"`
|
||||||
V2Method string `json:"v2_method"`
|
V2Method string `json:"v2_method"`
|
||||||
V2Net string `json:"v2_net"`
|
V2Net string `json:"v2_net"`
|
||||||
V2Type string `json:"v2_type"`
|
V2Type string `json:"v2_type"`
|
||||||
@@ -37,7 +37,7 @@ type ShadowsocksNodeInfo struct {
|
|||||||
SpeedLimit uint64 `json:"speed_limit"`
|
SpeedLimit uint64 `json:"speed_limit"`
|
||||||
ClientLimit int `json:"client_limit"`
|
ClientLimit int `json:"client_limit"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Port int `json:"port"`
|
Port uint32 `json:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrojanNodeInfo struct {
|
type TrojanNodeInfo struct {
|
||||||
@@ -46,10 +46,10 @@ type TrojanNodeInfo struct {
|
|||||||
SpeedLimit uint64 `json:"speed_limit"`
|
SpeedLimit uint64 `json:"speed_limit"`
|
||||||
ClientLimit int `json:"client_limit"`
|
ClientLimit int `json:"client_limit"`
|
||||||
PushPort int `json:"push_port"`
|
PushPort int `json:"push_port"`
|
||||||
TrojanPort int `json:"trojan_port"`
|
TrojanPort uint32 `json:"trojan_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node status report
|
// NodeStatus Node status report
|
||||||
type NodeStatus struct {
|
type NodeStatus struct {
|
||||||
CPU string `json:"cpu"`
|
CPU string `json:"cpu"`
|
||||||
Mem string `json:"mem"`
|
Mem string `json:"mem"`
|
||||||
@@ -98,7 +98,6 @@ type NodeRuleItem struct {
|
|||||||
Pattern string `json:"pattern"`
|
Pattern string `json:"pattern"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IllegalReport
|
|
||||||
type IllegalReport struct {
|
type IllegalReport struct {
|
||||||
UID int `json:"uid"`
|
UID int `json:"uid"`
|
||||||
RuleID int `json:"rule_id"`
|
RuleID int `json:"rule_id"`
|
||||||
|
@@ -11,8 +11,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIClient create a api client to the panel.
|
// APIClient create a api client to the panel.
|
||||||
@@ -72,7 +73,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// open the file
|
// open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
|
||||||
//handle errors while opening
|
// handle errors while opening
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error when opening file: %s", err)
|
log.Printf("Error when opening file: %s", err)
|
||||||
return LocalRuleList
|
return LocalRuleList
|
||||||
@@ -90,7 +91,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// handle first encountered error while reading
|
// handle first encountered error while reading
|
||||||
if err := fileScanner.Err(); err != nil {
|
if err := fileScanner.Err(); err != nil {
|
||||||
log.Fatalf("Error while reading file: %s", err)
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
return make([]api.DetectRule, 0)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
file.Close()
|
||||||
@@ -134,7 +135,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
|
|
||||||
if response.Status != "success" {
|
if response.Status != "success" {
|
||||||
res, _ := json.Marshal(&response)
|
res, _ := json.Marshal(&response)
|
||||||
return nil, fmt.Errorf("Ret %s invalid", string(res))
|
return nil, fmt.Errorf("ret %s invalid", string(res))
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
path = fmt.Sprintf("/api/ss/v1/node/%d", c.NodeID)
|
path = fmt.Sprintf("/api/ss/v1/node/%d", c.NodeID)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.createCommonRequest().
|
res, err := c.createCommonRequest().
|
||||||
@@ -171,7 +172,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
nodeInfo, err = c.ParseSSNodeResponse(&response.Data)
|
nodeInfo, err = c.ParseSSNodeResponse(&response.Data)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -193,7 +194,7 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
path = fmt.Sprintf("/api/ss/v1/userList/%d", c.NodeID)
|
path = fmt.Sprintf("/api/ss/v1/userList/%d", c.NodeID)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.createCommonRequest().
|
res, err := c.createCommonRequest().
|
||||||
@@ -214,11 +215,11 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
userList, err = c.ParseSSUserListResponse(&response.Data)
|
userList, err = c.ParseSSUserListResponse(&response.Data)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, _ := json.Marshal(response.Data)
|
res, _ := json.Marshal(response.Data)
|
||||||
return nil, fmt.Errorf("Parse user list failed: %s", string(res))
|
return nil, fmt.Errorf("parse user list failed: %s", string(res))
|
||||||
}
|
}
|
||||||
return userList, nil
|
return userList, nil
|
||||||
}
|
}
|
||||||
@@ -234,11 +235,11 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
path = fmt.Sprintf("/api/ss/v1/nodeStatus/%d", c.NodeID)
|
path = fmt.Sprintf("/api/ss/v1/nodeStatus/%d", c.NodeID)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
systemload := NodeStatus{
|
systemload := NodeStatus{
|
||||||
Uptime: nodeStatus.Uptime,
|
Uptime: int(nodeStatus.Uptime),
|
||||||
CPU: fmt.Sprintf("%d%%", int(nodeStatus.CPU)),
|
CPU: fmt.Sprintf("%d%%", int(nodeStatus.CPU)),
|
||||||
Mem: fmt.Sprintf("%d%%", int(nodeStatus.Mem)),
|
Mem: fmt.Sprintf("%d%%", int(nodeStatus.Mem)),
|
||||||
Disk: fmt.Sprintf("%d%%", int(nodeStatus.Disk)),
|
Disk: fmt.Sprintf("%d%%", int(nodeStatus.Disk)),
|
||||||
@@ -258,7 +259,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//ReportNodeOnlineUsers reports online user ip
|
// ReportNodeOnlineUsers reports online user ip
|
||||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||||
|
|
||||||
var path string
|
var path string
|
||||||
@@ -270,7 +271,7 @@ func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) erro
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
path = fmt.Sprintf("/api/ss/v1/nodeOnline/%d", c.NodeID)
|
path = fmt.Sprintf("/api/ss/v1/nodeOnline/%d", c.NodeID)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]NodeOnline, len(*onlineUserList))
|
data := make([]NodeOnline, len(*onlineUserList))
|
||||||
@@ -303,7 +304,7 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
path = fmt.Sprintf("/api/ss/v1/userTraffic/%d", c.NodeID)
|
path = fmt.Sprintf("/api/ss/v1/userTraffic/%d", c.NodeID)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]UserTraffic, len(*userTraffic))
|
data := make([]UserTraffic, len(*userTraffic))
|
||||||
@@ -338,7 +339,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
path = fmt.Sprintf("/api/ss/v1/nodeRule/%d", c.NodeID)
|
path = fmt.Sprintf("/api/ss/v1/nodeRule/%d", c.NodeID)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.createCommonRequest().
|
res, err := c.createCommonRequest().
|
||||||
@@ -354,7 +355,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|||||||
ruleListResponse := new(NodeRule)
|
ruleListResponse := new(NodeRule)
|
||||||
|
|
||||||
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
|
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
|
||||||
}
|
}
|
||||||
ruleList := c.LocalRuleList
|
ruleList := c.LocalRuleList
|
||||||
// Only support reject rule type
|
// Only support reject rule type
|
||||||
@@ -386,7 +387,7 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
|||||||
case "Shadowsocks":
|
case "Shadowsocks":
|
||||||
path = fmt.Sprintf("/api/ss/v1/trigger/%d", c.NodeID)
|
path = fmt.Sprintf("/api/ss/v1/trigger/%d", c.NodeID)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range *detectResultList {
|
for _, r := range *detectResultList {
|
||||||
@@ -421,7 +422,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*
|
|||||||
|
|
||||||
v2rayNodeInfo := new(V2rayNodeInfo)
|
v2rayNodeInfo := new(V2rayNodeInfo)
|
||||||
if err := json.Unmarshal(*nodeInfoResponse, v2rayNodeInfo); err != nil {
|
if err := json.Unmarshal(*nodeInfoResponse, v2rayNodeInfo); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
@@ -458,7 +459,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *json.RawMessage) (*api
|
|||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
shadowsocksNodeInfo := new(ShadowsocksNodeInfo)
|
shadowsocksNodeInfo := new(ShadowsocksNodeInfo)
|
||||||
if err := json.Unmarshal(*nodeInfoResponse, shadowsocksNodeInfo); err != nil {
|
if err := json.Unmarshal(*nodeInfoResponse, shadowsocksNodeInfo); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
@@ -495,7 +496,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (
|
|||||||
|
|
||||||
trojanNodeInfo := new(TrojanNodeInfo)
|
trojanNodeInfo := new(TrojanNodeInfo)
|
||||||
if err := json.Unmarshal(*nodeInfoResponse, trojanNodeInfo); err != nil {
|
if err := json.Unmarshal(*nodeInfoResponse, trojanNodeInfo); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
@@ -527,7 +528,7 @@ func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage
|
|||||||
|
|
||||||
vmessUserList := new([]*VMessUser)
|
vmessUserList := new([]*VMessUser)
|
||||||
if err := json.Unmarshal(*userInfoResponse, vmessUserList); err != nil {
|
if err := json.Unmarshal(*userInfoResponse, vmessUserList); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userList := make([]api.UserInfo, len(*vmessUserList))
|
userList := make([]api.UserInfo, len(*vmessUserList))
|
||||||
@@ -555,7 +556,7 @@ func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessag
|
|||||||
|
|
||||||
trojanUserList := new([]*TrojanUser)
|
trojanUserList := new([]*TrojanUser)
|
||||||
if err := json.Unmarshal(*userInfoResponse, trojanUserList); err != nil {
|
if err := json.Unmarshal(*userInfoResponse, trojanUserList); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userList := make([]api.UserInfo, len(*trojanUserList))
|
userList := make([]api.UserInfo, len(*trojanUserList))
|
||||||
@@ -563,7 +564,7 @@ func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessag
|
|||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
} else {
|
} else {
|
||||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
speedlimit = (user.SpeedLimit * 1000000) / 8
|
||||||
}
|
}
|
||||||
userList[i] = api.UserInfo{
|
userList[i] = api.UserInfo{
|
||||||
UID: user.UID,
|
UID: user.UID,
|
||||||
@@ -583,7 +584,7 @@ func (c *APIClient) ParseSSUserListResponse(userInfoResponse *json.RawMessage) (
|
|||||||
|
|
||||||
ssUserList := new([]*SSUser)
|
ssUserList := new([]*SSUser)
|
||||||
if err := json.Unmarshal(*userInfoResponse, ssUserList); err != nil {
|
if err := json.Unmarshal(*userInfoResponse, ssUserList); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*userInfoResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userList := make([]api.UserInfo, len(*ssUserList))
|
userList := make([]api.UserInfo, len(*ssUserList))
|
||||||
|
@@ -89,7 +89,7 @@ func TestGetUserList(t *testing.T) {
|
|||||||
func TestReportNodeStatus(t *testing.T) {
|
func TestReportNodeStatus(t *testing.T) {
|
||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
nodeStatus := &api.NodeStatus{
|
nodeStatus := &api.NodeStatus{
|
||||||
1, 1, 1, 256,
|
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
|
||||||
}
|
}
|
||||||
err := client.ReportNodeStatus(nodeStatus)
|
err := client.ReportNodeStatus(nodeStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,7 +111,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
|
|||||||
IP: fmt.Sprintf("1.1.1.%d", i),
|
IP: fmt.Sprintf("1.1.1.%d", i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//client.Debug()
|
// client.Debug()
|
||||||
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -154,8 +154,8 @@ func TestReportIllegal(t *testing.T) {
|
|||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
|
|
||||||
detectResult := []api.DetectResult{
|
detectResult := []api.DetectResult{
|
||||||
api.DetectResult{1, 1},
|
{1, 1},
|
||||||
api.DetectResult{1, 2},
|
{1, 2},
|
||||||
}
|
}
|
||||||
client.Debug()
|
client.Debug()
|
||||||
err := client.ReportIllegal(&detectResult)
|
err := client.ReportIllegal(&detectResult)
|
||||||
|
@@ -49,7 +49,7 @@ type UserResponse struct {
|
|||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Passwd string `json:"passwd"`
|
Passwd string `json:"passwd"`
|
||||||
Port int `json:"port"`
|
Port uint32 `json:"port"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
SpeedLimit float64 `json:"node_speedlimit"`
|
SpeedLimit float64 `json:"node_speedlimit"`
|
||||||
DeviceLimit int `json:"node_connector"`
|
DeviceLimit int `json:"node_connector"`
|
||||||
|
@@ -13,14 +13,15 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
firstPortRe = regexp.MustCompile(`(?m)port=(?P<outport>\d+)#?`) // First Port
|
firstPortRe = regexp.MustCompile(`(?m)port=(?P<outport>\d+)#?`) // First Port
|
||||||
secondPortRe = regexp.MustCompile(`(?m)port=\d+#(\d+)`) // Second Port
|
secondPortRe = regexp.MustCompile(`(?m)port=\d+#(\d+)`) // Second Port
|
||||||
hostRe = regexp.MustCompile(`(?m)host=([\w\.]+)\|?`) // Host
|
hostRe = regexp.MustCompile(`(?m)host=([\w.]+)\|?`) // Host
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIClient create a api client to the panel.
|
// APIClient create a api client to the panel.
|
||||||
@@ -89,7 +90,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// open the file
|
// open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
|
||||||
//handle errors while opening
|
// handle errors while opening
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error when opening file: %s", err)
|
log.Printf("Error when opening file: %s", err)
|
||||||
return LocalRuleList
|
return LocalRuleList
|
||||||
@@ -107,7 +108,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// handle first encountered error while reading
|
// handle first encountered error while reading
|
||||||
if err := fileScanner.Err(); err != nil {
|
if err := fileScanner.Err(); err != nil {
|
||||||
log.Fatalf("Error while reading file: %s", err)
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
return make([]api.DetectRule, 0)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
file.Close()
|
||||||
@@ -143,7 +144,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
|
|
||||||
if response.Ret != 1 {
|
if response.Ret != 1 {
|
||||||
res, _ := json.Marshal(&response)
|
res, _ := json.Marshal(&response)
|
||||||
return nil, fmt.Errorf("Ret %s invalid", string(res))
|
return nil, fmt.Errorf("ret %s invalid", string(res))
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
@@ -164,12 +165,12 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
nodeInfoResponse := new(NodeInfoResponse)
|
nodeInfoResponse := new(NodeInfoResponse)
|
||||||
|
|
||||||
if err := json.Unmarshal(response.Data, nodeInfoResponse); err != nil {
|
if err := json.Unmarshal(response.Data, nodeInfoResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(nodeInfoResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New sspanel API
|
// New sspanel API
|
||||||
disableCustomConfig := c.DisableCustomConfig
|
disableCustomConfig := c.DisableCustomConfig
|
||||||
if nodeInfoResponse.Version == "2021.11" && !disableCustomConfig {
|
if nodeInfoResponse.Version != "" && !disableCustomConfig {
|
||||||
// Check if custom_config is empty
|
// Check if custom_config is empty
|
||||||
if configString, err := json.Marshal(nodeInfoResponse.CustomConfig); err != nil || string(configString) == "[]" {
|
if configString, err := json.Marshal(nodeInfoResponse.CustomConfig); err != nil || string(configString) == "[]" {
|
||||||
log.Printf("custom_config is empty! take config from address now.")
|
log.Printf("custom_config is empty! take config from address now.")
|
||||||
@@ -183,7 +184,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
|
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, _ := json.Marshal(nodeInfoResponse)
|
res, _ := json.Marshal(nodeInfoResponse)
|
||||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://crackair.gitbook.io/xrayr-project/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
|
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://xrayr-project.github.io/XrayR-doc/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch c.NodeType {
|
switch c.NodeType {
|
||||||
@@ -196,7 +197,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
|||||||
case "Shadowsocks-Plugin":
|
case "Shadowsocks-Plugin":
|
||||||
nodeInfo, err = c.ParseSSPluginNodeResponse(nodeInfoResponse)
|
nodeInfo, err = c.ParseSSPluginNodeResponse(nodeInfoResponse)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,12 +226,12 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
userListResponse := new([]UserResponse)
|
userListResponse := new([]UserResponse)
|
||||||
|
|
||||||
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
|
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||||
}
|
}
|
||||||
userList, err := c.ParseUserListResponse(userListResponse)
|
userList, err := c.ParseUserListResponse(userListResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res, _ := json.Marshal(userListResponse)
|
res, _ := json.Marshal(userListResponse)
|
||||||
return nil, fmt.Errorf("Parse user list failed: %s", string(res))
|
return nil, fmt.Errorf("parse user list failed: %s", string(res))
|
||||||
}
|
}
|
||||||
return userList, nil
|
return userList, nil
|
||||||
}
|
}
|
||||||
@@ -239,8 +240,8 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||||
systemload := SystemLoad{
|
systemload := SystemLoad{
|
||||||
Uptime: strconv.Itoa(nodeStatus.Uptime),
|
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
|
||||||
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.CPU/100, nodeStatus.CPU/100),
|
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.client.R().
|
res, err := c.client.R().
|
||||||
@@ -257,7 +258,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//ReportNodeOnlineUsers reports online user ip
|
// ReportNodeOnlineUsers reports online user ip
|
||||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@@ -334,7 +335,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|||||||
ruleListResponse := new([]RuleItem)
|
ruleListResponse := new([]RuleItem)
|
||||||
|
|
||||||
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
|
if err := json.Unmarshal(response.Data, ruleListResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(ruleListResponse), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range *ruleListResponse {
|
for _, r := range *ruleListResponse {
|
||||||
@@ -378,18 +379,23 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
var header json.RawMessage
|
var header json.RawMessage
|
||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
if nodeInfoResponse.RawServerString == "" {
|
if nodeInfoResponse.RawServerString == "" {
|
||||||
return nil, fmt.Errorf("No server info in response")
|
return nil, fmt.Errorf("no server info in response")
|
||||||
}
|
}
|
||||||
//nodeInfo.RawServerString = strings.ToLower(nodeInfo.RawServerString)
|
// nodeInfo.RawServerString = strings.ToLower(nodeInfo.RawServerString)
|
||||||
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
||||||
port, err := strconv.Atoi(serverConf[1])
|
|
||||||
|
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
alterID, err := strconv.Atoi(serverConf[2])
|
port := uint32(parsedPort)
|
||||||
|
|
||||||
|
parsedAlterID, err := strconv.ParseInt(serverConf[2], 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
alterID := uint16(parsedAlterID)
|
||||||
|
|
||||||
// Compatible with more node types config
|
// Compatible with more node types config
|
||||||
for _, value := range serverConf[3:5] {
|
for _, value := range serverConf[3:5] {
|
||||||
switch value {
|
switch value {
|
||||||
@@ -439,7 +445,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal Header Type %s into config fialed: %s", header, err)
|
return nil, fmt.Errorf("marshal Header Type %s into config fialed: %s", header, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
@@ -464,7 +470,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
|||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||||
var port int = 0
|
var port uint32 = 0
|
||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
var method string
|
var method string
|
||||||
path := "/mod_mu/users"
|
path := "/mod_mu/users"
|
||||||
@@ -482,7 +488,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
|
|||||||
userListResponse := new([]UserResponse)
|
userListResponse := new([]UserResponse)
|
||||||
|
|
||||||
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
|
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
|
||||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||||
}
|
}
|
||||||
// Find the multi-user
|
// Find the multi-user
|
||||||
for _, u := range *userListResponse {
|
for _, u := range *userListResponse {
|
||||||
@@ -493,7 +499,7 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if port == 0 || method == "" {
|
if port == 0 || method == "" {
|
||||||
return nil, fmt.Errorf("Cant find the single port multi user")
|
return nil, fmt.Errorf("cant find the single port multi user")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.SpeedLimit > 0 {
|
if c.SpeedLimit > 0 {
|
||||||
@@ -521,10 +527,11 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
|
|||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
|
|
||||||
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
||||||
port, err := strconv.Atoi(serverConf[1])
|
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
port := uint32(parsedPort)
|
||||||
port = port - 1 // Shadowsocks-Plugin requires two ports, one for ss the other for other stream protocol
|
port = port - 1 // Shadowsocks-Plugin requires two ports, one for ss the other for other stream protocol
|
||||||
if port <= 0 {
|
if port <= 0 {
|
||||||
return nil, fmt.Errorf("Shadowsocks-Plugin listen port must bigger than 1")
|
return nil, fmt.Errorf("Shadowsocks-Plugin listen port must bigger than 1")
|
||||||
@@ -597,7 +604,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if nodeInfoResponse.RawServerString == "" {
|
if nodeInfoResponse.RawServerString == "" {
|
||||||
return nil, fmt.Errorf("No server info in response")
|
return nil, fmt.Errorf("no server info in response")
|
||||||
}
|
}
|
||||||
if result := firstPortRe.FindStringSubmatch(nodeInfoResponse.RawServerString); len(result) > 1 {
|
if result := firstPortRe.FindStringSubmatch(nodeInfoResponse.RawServerString); len(result) > 1 {
|
||||||
outsidePort = result[1]
|
outsidePort = result[1]
|
||||||
@@ -615,10 +622,11 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
|||||||
p = outsidePort
|
p = outsidePort
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := strconv.Atoi(p)
|
parsedPort, err := strconv.ParseInt(p, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
port := uint32(parsedPort)
|
||||||
|
|
||||||
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
||||||
extraServerConf := strings.Split(serverConf[1], "|")
|
extraServerConf := strings.Split(serverConf[1], "|")
|
||||||
@@ -671,7 +679,7 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
|||||||
|
|
||||||
var deviceLimit, localDeviceLimit int = 0, 0
|
var deviceLimit, localDeviceLimit int = 0, 0
|
||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
userList := []api.UserInfo{}
|
var userList []api.UserInfo
|
||||||
for _, user := range *userInfoResponse {
|
for _, user := range *userInfoResponse {
|
||||||
if c.DeviceLimit > 0 {
|
if c.DeviceLimit > 0 {
|
||||||
deviceLimit = c.DeviceLimit
|
deviceLimit = c.DeviceLimit
|
||||||
@@ -727,7 +735,7 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
|
|||||||
|
|
||||||
var speedlimit uint64 = 0
|
var speedlimit uint64 = 0
|
||||||
var EnableTLS, EnableVless bool
|
var EnableTLS, EnableVless bool
|
||||||
var AlterID int = 0
|
var AlterID uint16 = 0
|
||||||
var TLSType, transportProtocol string
|
var TLSType, transportProtocol string
|
||||||
|
|
||||||
nodeConfig := new(CustomConfig)
|
nodeConfig := new(CustomConfig)
|
||||||
@@ -739,10 +747,11 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
|
|||||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := strconv.Atoi(nodeConfig.OffsetPortNode)
|
parsedPort, err := strconv.ParseInt(nodeConfig.OffsetPortNode, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
port := uint32(parsedPort)
|
||||||
|
|
||||||
if c.NodeType == "Shadowsocks" {
|
if c.NodeType == "Shadowsocks" {
|
||||||
transportProtocol = "tcp"
|
transportProtocol = "tcp"
|
||||||
@@ -751,9 +760,12 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
|
|||||||
if c.NodeType == "V2ray" {
|
if c.NodeType == "V2ray" {
|
||||||
transportProtocol = nodeConfig.Network
|
transportProtocol = nodeConfig.Network
|
||||||
TLSType = nodeConfig.Security
|
TLSType = nodeConfig.Security
|
||||||
if AlterID, err = strconv.Atoi(nodeConfig.AlterID); err != nil {
|
if parsedAlterID, err := strconv.ParseInt(nodeConfig.AlterID, 10, 16); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
AlterID = uint16(parsedAlterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if TLSType == "tls" || TLSType == "xtls" {
|
if TLSType == "tls" || TLSType == "xtls" {
|
||||||
EnableTLS = true
|
EnableTLS = true
|
||||||
}
|
}
|
||||||
@@ -765,14 +777,20 @@ func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*a
|
|||||||
if c.NodeType == "Trojan" {
|
if c.NodeType == "Trojan" {
|
||||||
EnableTLS = true
|
EnableTLS = true
|
||||||
TLSType = "tls"
|
TLSType = "tls"
|
||||||
if nodeConfig.Grpc == "1" {
|
|
||||||
transportProtocol = "grpc"
|
|
||||||
} else {
|
|
||||||
transportProtocol = "tcp"
|
transportProtocol = "tcp"
|
||||||
}
|
|
||||||
|
|
||||||
|
// Select security type
|
||||||
if nodeConfig.EnableXtls == "1" {
|
if nodeConfig.EnableXtls == "1" {
|
||||||
TLSType = "xtls"
|
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 != "" {
|
||||||
|
transportProtocol = nodeConfig.Network // try to read transport protocol from config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -83,7 +83,7 @@ func TestGetUserList(t *testing.T) {
|
|||||||
func TestReportNodeStatus(t *testing.T) {
|
func TestReportNodeStatus(t *testing.T) {
|
||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
nodeStatus := &api.NodeStatus{
|
nodeStatus := &api.NodeStatus{
|
||||||
1, 1, 1, 256,
|
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
|
||||||
}
|
}
|
||||||
err := client.ReportNodeStatus(nodeStatus)
|
err := client.ReportNodeStatus(nodeStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -105,7 +105,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
|
|||||||
IP: fmt.Sprintf("1.1.1.%d", i),
|
IP: fmt.Sprintf("1.1.1.%d", i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//client.Debug()
|
// client.Debug()
|
||||||
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -126,7 +126,7 @@ func TestReportReportUserTraffic(t *testing.T) {
|
|||||||
Download: 114514,
|
Download: 114514,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//client.Debug()
|
// client.Debug()
|
||||||
err = client.ReportUserTraffic(&generalUserTraffic)
|
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -148,8 +148,8 @@ func TestReportIllegal(t *testing.T) {
|
|||||||
client := CreateClient()
|
client := CreateClient()
|
||||||
|
|
||||||
detectResult := []api.DetectResult{
|
detectResult := []api.DetectResult{
|
||||||
api.DetectResult{1, 2},
|
{1, 2},
|
||||||
api.DetectResult{1, 3},
|
{1, 3},
|
||||||
}
|
}
|
||||||
client.Debug()
|
client.Debug()
|
||||||
err := client.ReportIllegal(&detectResult)
|
err := client.ReportIllegal(&detectResult)
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
// Deprecated: after 2023.6.1
|
||||||
package v2board
|
package v2board
|
||||||
|
|
||||||
type UserTraffic struct {
|
type UserTraffic struct {
|
||||||
|
@@ -8,12 +8,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/bitly/go-simplejson"
|
"github.com/bitly/go-simplejson"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIClient create an api client to the panel.
|
// APIClient create an api client to the panel.
|
||||||
@@ -80,7 +82,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// open the file
|
// open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
|
||||||
//handle errors while opening
|
// handle errors while opening
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error when opening file: %s", err)
|
log.Printf("Error when opening file: %s", err)
|
||||||
return LocalRuleList
|
return LocalRuleList
|
||||||
@@ -98,7 +100,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
|||||||
// handle first encountered error while reading
|
// handle first encountered error while reading
|
||||||
if err := fileScanner.Err(); err != nil {
|
if err := fileScanner.Err(); err != nil {
|
||||||
log.Fatalf("Error while reading file: %s", err)
|
log.Fatalf("Error while reading file: %s", err)
|
||||||
return make([]api.DetectRule, 0)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
file.Close()
|
||||||
@@ -132,7 +134,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
|||||||
}
|
}
|
||||||
rtn, err := simplejson.NewJson(res.Body())
|
rtn, err := simplejson.NewJson(res.Body())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Ret %s invalid", res.String())
|
return nil, fmt.Errorf("ret %s invalid", res.String())
|
||||||
}
|
}
|
||||||
return rtn, nil
|
return rtn, nil
|
||||||
}
|
}
|
||||||
@@ -219,14 +221,14 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
|||||||
user.Email = response.Get("data").GetIndex(i).Get("secret").MustString()
|
user.Email = response.Get("data").GetIndex(i).Get("secret").MustString()
|
||||||
user.Passwd = 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.Method = response.Get("data").GetIndex(i).Get("cipher").MustString()
|
||||||
user.Port = response.Get("data").GetIndex(i).Get("port").MustInt()
|
user.Port = uint32(response.Get("data").GetIndex(i).Get("port").MustUint64())
|
||||||
case "Trojan":
|
case "Trojan":
|
||||||
user.UUID = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
|
user.UUID = response.Get("data").GetIndex(i).Get("trojan_user").Get("password").MustString()
|
||||||
user.Email = 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":
|
case "V2ray":
|
||||||
user.UUID = response.Get("data").GetIndex(i).Get("v2ray_user").Get("uuid").MustString()
|
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.Email = response.Get("data").GetIndex(i).Get("v2ray_user").Get("email").MustString()
|
||||||
user.AlterID = response.Get("data").GetIndex(i).Get("v2ray_user").Get("alter_id").MustInt()
|
user.AlterID = uint16(response.Get("data").GetIndex(i).Get("v2ray_user").Get("alter_id").MustUint64())
|
||||||
}
|
}
|
||||||
userList[i] = user
|
userList[i] = user
|
||||||
}
|
}
|
||||||
@@ -278,6 +280,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
|||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
ruleListResponse := c.ConfigResp.Get("routing").Get("rules").GetIndex(1).Get("domain").MustStringArray()
|
ruleListResponse := c.ConfigResp.Get("routing").Get("rules").GetIndex(1).Get("domain").MustStringArray()
|
||||||
for i, rule := range ruleListResponse {
|
for i, rule := range ruleListResponse {
|
||||||
|
rule = strings.TrimPrefix(rule, "regexp:")
|
||||||
ruleListItem := api.DetectRule{
|
ruleListItem := api.DetectRule{
|
||||||
ID: i,
|
ID: i,
|
||||||
Pattern: regexp.MustCompile(rule),
|
Pattern: regexp.MustCompile(rule),
|
||||||
@@ -292,7 +295,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//ReportNodeOnlineUsers implements the API interface
|
// ReportNodeOnlineUsers implements the API interface
|
||||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -308,7 +311,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (
|
|||||||
if c.EnableXTLS {
|
if c.EnableXTLS {
|
||||||
TLSType = "xtls"
|
TLSType = "xtls"
|
||||||
}
|
}
|
||||||
port := nodeInfoResponse.Get("local_port").MustInt()
|
port := uint32(nodeInfoResponse.Get("local_port").MustUint64())
|
||||||
host := nodeInfoResponse.Get("ssl").Get("sni").MustString()
|
host := nodeInfoResponse.Get("ssl").Get("sni").MustString()
|
||||||
|
|
||||||
// Create GeneralNodeInfo
|
// Create GeneralNodeInfo
|
||||||
@@ -326,7 +329,7 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (
|
|||||||
|
|
||||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||||
func (c *APIClient) ParseSSNodeResponse() (*api.NodeInfo, error) {
|
func (c *APIClient) ParseSSNodeResponse() (*api.NodeInfo, error) {
|
||||||
var port int
|
var port uint32
|
||||||
var method string
|
var method string
|
||||||
userInfo, err := c.GetUserList()
|
userInfo, err := c.GetUserList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -355,7 +358,7 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
|||||||
var path, host, serviceName string
|
var path, host, serviceName string
|
||||||
var header json.RawMessage
|
var header json.RawMessage
|
||||||
var enableTLS bool
|
var enableTLS bool
|
||||||
var alterID int = 0
|
var alterID uint16 = 0
|
||||||
if c.EnableXTLS {
|
if c.EnableXTLS {
|
||||||
TLSType = "xtls"
|
TLSType = "xtls"
|
||||||
}
|
}
|
||||||
@@ -369,10 +372,10 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
|||||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||||
inboundInfo, _ = simplejson.NewJson(marshalByte)
|
inboundInfo, _ = simplejson.NewJson(marshalByte)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Unable to find inbound(s) in the nodeInfo.")
|
return nil, fmt.Errorf("unable to find inbound(s) in the nodeInfo")
|
||||||
}
|
}
|
||||||
|
|
||||||
port := inboundInfo.Get("port").MustInt()
|
port := uint32(inboundInfo.Get("port").MustUint64())
|
||||||
transportProtocol := inboundInfo.Get("streamSettings").Get("network").MustString()
|
transportProtocol := inboundInfo.Get("streamSettings").Get("network").MustString()
|
||||||
|
|
||||||
switch transportProtocol {
|
switch transportProtocol {
|
||||||
|
@@ -82,7 +82,7 @@ func TestReportReportUserTraffic(t *testing.T) {
|
|||||||
Download: 114514,
|
Download: 114514,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//client.Debug()
|
// client.Debug()
|
||||||
err = client.ReportUserTraffic(&generalUserTraffic)
|
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
7
api/v2raysocks/model.go
Normal file
7
api/v2raysocks/model.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package v2raysocks
|
||||||
|
|
||||||
|
type UserTraffic struct {
|
||||||
|
UID int `json:"user_id"`
|
||||||
|
Upload int64 `json:"u"`
|
||||||
|
Download int64 `json:"d"`
|
||||||
|
}
|
414
api/v2raysocks/v2raysocks.go
Normal file
414
api/v2raysocks/v2raysocks.go
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
package v2raysocks
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 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 nodeType string
|
||||||
|
switch c.NodeType {
|
||||||
|
case "V2ray", "Trojan", "Shadowsocks":
|
||||||
|
nodeType = strings.ToLower(c.NodeType)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
|
}
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"act": "config",
|
||||||
|
"nodetype": nodeType,
|
||||||
|
}).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Get(c.APIHost)
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, "", 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(response)
|
||||||
|
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 nodeType string
|
||||||
|
switch c.NodeType {
|
||||||
|
case "V2ray", "Trojan", "Shadowsocks":
|
||||||
|
nodeType = strings.ToLower(c.NodeType)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||||
|
}
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"act": "user",
|
||||||
|
"nodetype": nodeType,
|
||||||
|
}).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Get(c.APIHost)
|
||||||
|
|
||||||
|
response, err := c.parseResponse(res, "", 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()
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if c.SpeedLimit > 0 {
|
||||||
|
user.SpeedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||||
|
}
|
||||||
|
user.DeviceLimit = c.DeviceLimit
|
||||||
|
userList[i] = user
|
||||||
|
}
|
||||||
|
return &userList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportUserTraffic reports the user traffic
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.R().
|
||||||
|
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"act": "submit",
|
||||||
|
"nodetype": strings.ToLower(c.NodeType),
|
||||||
|
}).
|
||||||
|
SetBody(data).
|
||||||
|
ForceContentType("application/json").
|
||||||
|
Post(c.APIHost)
|
||||||
|
_, err = c.parseResponse(res, "", 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||||
|
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||||
|
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||||
|
|
||||||
|
port := uint32(inboundInfo.Get("port").MustUint64())
|
||||||
|
host := inboundInfo.Get("streamSettings").Get("tlsSettings").Get("serverName").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(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||||
|
var method 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
|
||||||
|
}
|
||||||
|
// 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||||
|
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||||
|
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
102
api/v2raysocks/v2raysocks_test.go
Normal file
102
api/v2raysocks/v2raysocks_test.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package v2raysocks_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/api/v2raysocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateClient() api.API {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "https://127.0.0.1/",
|
||||||
|
Key: "123456789",
|
||||||
|
NodeID: 280002,
|
||||||
|
NodeType: "V2ray",
|
||||||
|
}
|
||||||
|
client := v2raysocks.New(apiConfig)
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetV2rayNodeinfo(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
client.Debug()
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSSNodeinfo(t *testing.T) {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "https://127.0.0.1/",
|
||||||
|
Key: "123456789",
|
||||||
|
NodeID: 280009,
|
||||||
|
NodeType: "Shadowsocks",
|
||||||
|
}
|
||||||
|
client := v2raysocks.New(apiConfig)
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTrojanNodeinfo(t *testing.T) {
|
||||||
|
apiConfig := &api.Config{
|
||||||
|
APIHost: "https://127.0.0.1/",
|
||||||
|
Key: "123456789",
|
||||||
|
NodeID: 280008,
|
||||||
|
NodeType: "Trojan",
|
||||||
|
}
|
||||||
|
client := v2raysocks.New(apiConfig)
|
||||||
|
nodeInfo, err := client.GetNodeInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserList(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(userList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReportReportUserTraffic(t *testing.T) {
|
||||||
|
client := CreateClient()
|
||||||
|
userList, err := client.GetUserList()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
generalUserTraffic := make([]api.UserTraffic, len(*userList))
|
||||||
|
for i, userInfo := range *userList {
|
||||||
|
generalUserTraffic[i] = api.UserTraffic{
|
||||||
|
UID: userInfo.UID,
|
||||||
|
Upload: 114514,
|
||||||
|
Download: 114514,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// client.Debug()
|
||||||
|
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.28.0
|
||||||
// protoc v3.19.4
|
// protoc v3.19.4
|
||||||
// source: config.proto
|
// source: app/mydispatcher/config.proto
|
||||||
|
|
||||||
package mydispatcher
|
package mydispatcher
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ type SessionConfig struct {
|
|||||||
func (x *SessionConfig) Reset() {
|
func (x *SessionConfig) Reset() {
|
||||||
*x = SessionConfig{}
|
*x = SessionConfig{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_config_proto_msgTypes[0]
|
mi := &file_app_mydispatcher_config_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ func (x *SessionConfig) String() string {
|
|||||||
func (*SessionConfig) ProtoMessage() {}
|
func (*SessionConfig) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
|
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_config_proto_msgTypes[0]
|
mi := &file_app_mydispatcher_config_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -55,7 +55,7 @@ func (x *SessionConfig) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead.
|
// Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead.
|
||||||
func (*SessionConfig) Descriptor() ([]byte, []int) {
|
func (*SessionConfig) Descriptor() ([]byte, []int) {
|
||||||
return file_config_proto_rawDescGZIP(), []int{0}
|
return file_app_mydispatcher_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -69,7 +69,7 @@ type Config struct {
|
|||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
*x = Config{}
|
*x = Config{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_config_proto_msgTypes[1]
|
mi := &file_app_mydispatcher_config_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ func (x *Config) String() string {
|
|||||||
func (*Config) ProtoMessage() {}
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_config_proto_msgTypes[1]
|
mi := &file_app_mydispatcher_config_proto_msgTypes[1]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -95,7 +95,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
return file_config_proto_rawDescGZIP(), []int{1}
|
return file_app_mydispatcher_config_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetSettings() *SessionConfig {
|
func (x *Config) GetSettings() *SessionConfig {
|
||||||
@@ -105,45 +105,46 @@ func (x *Config) GetSettings() *SessionConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var File_config_proto protoreflect.FileDescriptor
|
var File_app_mydispatcher_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_config_proto_rawDesc = []byte{
|
var file_app_mydispatcher_config_proto_rawDesc = []byte{
|
||||||
0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16,
|
0x0a, 0x1d, 0x61, 0x70, 0x70, 0x2f, 0x6d, 0x79, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68,
|
||||||
0x78, 0x72, 0x61, 0x79, 0x72, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x79, 0x64, 0x69, 0x73, 0x70,
|
0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||||
0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,
|
0x16, 0x78, 0x72, 0x61, 0x79, 0x72, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x79, 0x64, 0x69, 0x73,
|
||||||
0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x4b, 0x0a,
|
0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73, 0x69,
|
||||||
0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69,
|
0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x4b,
|
||||||
0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79,
|
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74,
|
||||||
0x72, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x79, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68,
|
0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61,
|
||||||
0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x79, 0x72, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x79, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63,
|
||||||
0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x67, 0x0a, 0x1a, 0x63, 0x6f,
|
0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x72, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x79, 0x64, 0x69,
|
0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x67, 0x0a, 0x1a, 0x63,
|
||||||
0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68,
|
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x72, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6d, 0x79, 0x64,
|
||||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x58, 0x72, 0x61, 0x79, 0x52, 0x2d, 0x70, 0x72, 0x6f,
|
0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74,
|
||||||
0x6a, 0x65, 0x63, 0x74, 0x2f, 0x58, 0x72, 0x61, 0x79, 0x52, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x6d,
|
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x58, 0x72, 0x61, 0x79, 0x52, 0x2d, 0x70, 0x72,
|
||||||
0x79, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa, 0x02, 0x15, 0x58, 0x72,
|
0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x58, 0x72, 0x61, 0x79, 0x52, 0x2f, 0x61, 0x70, 0x70, 0x2f,
|
||||||
0x61, 0x79, 0x52, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x79, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63,
|
0x6d, 0x79, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa, 0x02, 0x15, 0x58,
|
||||||
0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x72, 0x61, 0x79, 0x52, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4d, 0x79, 0x69, 0x73, 0x70, 0x61, 0x74,
|
||||||
|
0x63, 0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_config_proto_rawDescOnce sync.Once
|
file_app_mydispatcher_config_proto_rawDescOnce sync.Once
|
||||||
file_config_proto_rawDescData = file_config_proto_rawDesc
|
file_app_mydispatcher_config_proto_rawDescData = file_app_mydispatcher_config_proto_rawDesc
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_config_proto_rawDescGZIP() []byte {
|
func file_app_mydispatcher_config_proto_rawDescGZIP() []byte {
|
||||||
file_config_proto_rawDescOnce.Do(func() {
|
file_app_mydispatcher_config_proto_rawDescOnce.Do(func() {
|
||||||
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
|
file_app_mydispatcher_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_mydispatcher_config_proto_rawDescData)
|
||||||
})
|
})
|
||||||
return file_config_proto_rawDescData
|
return file_app_mydispatcher_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
var file_app_mydispatcher_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
var file_config_proto_goTypes = []interface{}{
|
var file_app_mydispatcher_config_proto_goTypes = []interface{}{
|
||||||
(*SessionConfig)(nil), // 0: xrayr.app.mydispatcher.SessionConfig
|
(*SessionConfig)(nil), // 0: xrayr.app.mydispatcher.SessionConfig
|
||||||
(*Config)(nil), // 1: xrayr.app.mydispatcher.Config
|
(*Config)(nil), // 1: xrayr.app.mydispatcher.Config
|
||||||
}
|
}
|
||||||
var file_config_proto_depIdxs = []int32{
|
var file_app_mydispatcher_config_proto_depIdxs = []int32{
|
||||||
0, // 0: xrayr.app.mydispatcher.Config.settings:type_name -> xrayr.app.mydispatcher.SessionConfig
|
0, // 0: xrayr.app.mydispatcher.Config.settings:type_name -> xrayr.app.mydispatcher.SessionConfig
|
||||||
1, // [1:1] is the sub-list for method output_type
|
1, // [1:1] is the sub-list for method output_type
|
||||||
1, // [1:1] is the sub-list for method input_type
|
1, // [1:1] is the sub-list for method input_type
|
||||||
@@ -152,13 +153,13 @@ var file_config_proto_depIdxs = []int32{
|
|||||||
0, // [0:1] is the sub-list for field type_name
|
0, // [0:1] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_config_proto_init() }
|
func init() { file_app_mydispatcher_config_proto_init() }
|
||||||
func file_config_proto_init() {
|
func file_app_mydispatcher_config_proto_init() {
|
||||||
if File_config_proto != nil {
|
if File_app_mydispatcher_config_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
if !protoimpl.UnsafeEnabled {
|
||||||
file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
file_app_mydispatcher_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*SessionConfig); i {
|
switch v := v.(*SessionConfig); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -170,7 +171,7 @@ func file_config_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
file_app_mydispatcher_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*Config); i {
|
switch v := v.(*Config); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -187,18 +188,18 @@ func file_config_proto_init() {
|
|||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_config_proto_rawDesc,
|
RawDescriptor: file_app_mydispatcher_config_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 2,
|
NumMessages: 2,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_config_proto_goTypes,
|
GoTypes: file_app_mydispatcher_config_proto_goTypes,
|
||||||
DependencyIndexes: file_config_proto_depIdxs,
|
DependencyIndexes: file_app_mydispatcher_config_proto_depIdxs,
|
||||||
MessageInfos: file_config_proto_msgTypes,
|
MessageInfos: file_app_mydispatcher_config_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_config_proto = out.File
|
File_app_mydispatcher_config_proto = out.File
|
||||||
file_config_proto_rawDesc = nil
|
file_app_mydispatcher_config_proto_rawDesc = nil
|
||||||
file_config_proto_goTypes = nil
|
file_app_mydispatcher_config_proto_goTypes = nil
|
||||||
file_config_proto_depIdxs = nil
|
file_app_mydispatcher_config_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
@@ -9,8 +9,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/limiter"
|
|
||||||
"github.com/XrayR-project/XrayR/common/rule"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/log"
|
"github.com/xtls/xray-core/common/log"
|
||||||
@@ -22,10 +20,13 @@ import (
|
|||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
"github.com/xtls/xray-core/features/policy"
|
"github.com/xtls/xray-core/features/policy"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
"github.com/xtls/xray-core/features/routing"
|
||||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
routingSession "github.com/xtls/xray-core/features/routing/session"
|
||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
"github.com/xtls/xray-core/transport/pipe"
|
"github.com/xtls/xray-core/transport/pipe"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/common/limiter"
|
||||||
|
"github.com/XrayR-project/XrayR/common/rule"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errSniffingTimeout = newError("timeout on sniffing")
|
var errSniffingTimeout = newError("timeout on sniffing")
|
||||||
@@ -95,15 +96,19 @@ type DefaultDispatcher struct {
|
|||||||
router routing.Router
|
router routing.Router
|
||||||
policy policy.Manager
|
policy policy.Manager
|
||||||
stats stats.Manager
|
stats stats.Manager
|
||||||
hosts dns.HostsLookup
|
dns dns.Client
|
||||||
|
fdns dns.FakeDNSEngine
|
||||||
Limiter *limiter.Limiter
|
Limiter *limiter.Limiter
|
||||||
RuleManager *rule.RuleManager
|
RuleManager *rule.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||||
d := new(DefaultDispatcher)
|
d := new(DefaultDispatcher)
|
||||||
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
|
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
|
||||||
|
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||||
|
d.fdns = fdns
|
||||||
|
})
|
||||||
return d.Init(config.(*Config), om, router, pm, sm, dc)
|
return d.Init(config.(*Config), om, router, pm, sm, dc)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -113,16 +118,14 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes DefaultDispatcher.
|
// Init initializes DefaultDispatcher.
|
||||||
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
|
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error {
|
||||||
d.ohm = om
|
d.ohm = om
|
||||||
d.router = router
|
d.router = router
|
||||||
d.policy = pm
|
d.policy = pm
|
||||||
d.stats = sm
|
d.stats = sm
|
||||||
d.Limiter = limiter.New()
|
d.Limiter = limiter.New()
|
||||||
d.RuleManager = rule.New()
|
d.RuleManager = rule.New()
|
||||||
if hosts, ok := dc.(dns.HostsLookup); ok {
|
d.dns = dns
|
||||||
d.hosts = hosts
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,12 +140,81 @@ func (*DefaultDispatcher) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close implements common.Closable.
|
// Close implements common.Closable.
|
||||||
func (*DefaultDispatcher) Close() error { return nil }
|
func (*DefaultDispatcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *transport.Link, error) {
|
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, error) {
|
||||||
opt := pipe.OptionsFromContext(ctx)
|
downOpt := pipe.OptionsFromContext(ctx)
|
||||||
uplinkReader, uplinkWriter := pipe.New(opt...)
|
upOpt := downOpt
|
||||||
downlinkReader, downlinkWriter := pipe.New(opt...)
|
|
||||||
|
if network == net.Network_UDP {
|
||||||
|
var ip2domain *sync.Map // net.IP.String() => domain, this map is used by server side when client turn on fakedns
|
||||||
|
// Client will send domain address in the buffer.UDP.Address, server record all possible target IP addrs.
|
||||||
|
// When target replies, server will restore the domain and send back to client.
|
||||||
|
// Note: this map is not global but per connection context
|
||||||
|
upOpt = append(upOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
||||||
|
for i, buffer := range mb {
|
||||||
|
if buffer.UDP == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr := buffer.UDP.Address
|
||||||
|
if addr.Family().IsIP() {
|
||||||
|
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(addr) && sniffing.Enabled {
|
||||||
|
domain := fkr0.GetDomainFromFakeDNS(addr)
|
||||||
|
if len(domain) > 0 {
|
||||||
|
buffer.UDP.Address = net.DomainAddress(domain)
|
||||||
|
newError("[fakedns client] override with domain: ", domain, " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
} else {
|
||||||
|
newError("[fakedns client] failed to find domain! :", addr.String(), " for xUDP buffer at ", i).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ip2domain == nil {
|
||||||
|
ip2domain = new(sync.Map)
|
||||||
|
newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
}
|
||||||
|
domain := addr.Domain()
|
||||||
|
ips, err := d.dns.LookupIP(domain, dns.IPOption{IPv4Enable: true, IPv6Enable: true})
|
||||||
|
if err == nil {
|
||||||
|
for _, ip := range ips {
|
||||||
|
ip2domain.Store(ip.String(), domain)
|
||||||
|
}
|
||||||
|
newError("[fakedns client] candidate ip: "+fmt.Sprintf("%v", ips), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
} else {
|
||||||
|
newError("[fakedns client] failed to look up IP for ", domain, " for xUDP buffer at ", i).Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mb
|
||||||
|
}))
|
||||||
|
downOpt = append(downOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
||||||
|
for i, buffer := range mb {
|
||||||
|
if buffer.UDP == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr := buffer.UDP.Address
|
||||||
|
if addr.Family().IsIP() {
|
||||||
|
if ip2domain == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if domain, found := ip2domain.Load(addr.IP().String()); found {
|
||||||
|
buffer.UDP.Address = net.DomainAddress(domain.(string))
|
||||||
|
newError("[fakedns client] restore domain: ", domain.(string), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok {
|
||||||
|
fakeIp := fkr0.GetFakeIPForDomain(addr.Domain())
|
||||||
|
buffer.UDP.Address = fakeIp[0]
|
||||||
|
newError("[fakedns client] restore FakeIP: ", buffer.UDP, fmt.Sprintf("%v", fakeIp), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mb
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
uplinkReader, uplinkWriter := pipe.New(upOpt...)
|
||||||
|
downlinkReader, downlinkWriter := pipe.New(downOpt...)
|
||||||
|
|
||||||
inboundLink := &transport.Link{
|
inboundLink := &transport.Link{
|
||||||
Reader: downlinkReader,
|
Reader: downlinkReader,
|
||||||
@@ -164,7 +236,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
|||||||
// Speed Limit and Device Limit
|
// Speed Limit and Device Limit
|
||||||
bucket, ok, reject := d.Limiter.GetUserBucket(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
|
bucket, ok, reject := d.Limiter.GetUserBucket(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
|
||||||
if reject {
|
if reject {
|
||||||
newError("Devices reach the limit: ", user.Email).AtError().WriteToLog()
|
newError("Devices reach the limit: ", user.Email).AtWarning().WriteToLog()
|
||||||
common.Close(outboundLink.Writer)
|
common.Close(outboundLink.Writer)
|
||||||
common.Close(inboundLink.Writer)
|
common.Close(inboundLink.Writer)
|
||||||
common.Interrupt(outboundLink.Reader)
|
common.Interrupt(outboundLink.Reader)
|
||||||
@@ -199,17 +271,13 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
|
|||||||
return inboundLink, outboundLink, nil
|
return inboundLink, outboundLink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
|
||||||
domain := result.Domain()
|
domain := result.Domain()
|
||||||
for _, d := range request.ExcludeForDomain {
|
for _, d := range request.ExcludeForDomain {
|
||||||
if strings.ToLower(domain) == d {
|
if strings.ToLower(domain) == d {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var fakeDNSEngine dns.FakeDNSEngine
|
|
||||||
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
|
||||||
fakeDNSEngine = fdns
|
|
||||||
})
|
|
||||||
protocolString := result.Protocol()
|
protocolString := result.Protocol()
|
||||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||||
protocolString = resComp.ProtocolForDomainResult()
|
protocolString = resComp.ProtocolForDomainResult()
|
||||||
@@ -218,7 +286,7 @@ func shouldOverride(ctx context.Context, result SniffResult, request session.Sni
|
|||||||
if strings.HasPrefix(protocolString, p) {
|
if strings.HasPrefix(protocolString, p) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||||
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
|
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
|
||||||
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
||||||
return true
|
return true
|
||||||
@@ -242,26 +310,26 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
|||||||
Target: destination,
|
Target: destination,
|
||||||
}
|
}
|
||||||
ctx = session.ContextWithOutbound(ctx, ob)
|
ctx = session.ContextWithOutbound(ctx, ob)
|
||||||
|
|
||||||
inbound, outbound, err := d.getLink(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
content := session.ContentFromContext(ctx)
|
content := session.ContentFromContext(ctx)
|
||||||
if content == nil {
|
if content == nil {
|
||||||
content = new(session.Content)
|
content = new(session.Content)
|
||||||
ctx = session.ContextWithContent(ctx, content)
|
ctx = session.ContextWithContent(ctx, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
sniffingRequest := content.SniffingRequest
|
sniffingRequest := content.SniffingRequest
|
||||||
|
in, out, err := d.getLink(ctx, destination.Network, sniffingRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case !sniffingRequest.Enabled:
|
case !sniffingRequest.Enabled:
|
||||||
go d.routedDispatch(ctx, outbound, destination)
|
go d.routedDispatch(ctx, out, destination)
|
||||||
case destination.Network != net.Network_TCP:
|
case destination.Network != net.Network_TCP:
|
||||||
// Only metadata sniff will be used for non tcp connection
|
// Only metadata sniff will be used for non tcp connection
|
||||||
result, err := sniffer(ctx, nil, true)
|
result, err := sniffer(ctx, nil, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
content.Protocol = result.Protocol()
|
content.Protocol = result.Protocol()
|
||||||
if shouldOverride(ctx, result, sniffingRequest, destination) {
|
if d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
domain := result.Domain()
|
domain := result.Domain()
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||||
destination.Address = net.ParseAddress(domain)
|
destination.Address = net.ParseAddress(domain)
|
||||||
@@ -272,18 +340,18 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
go d.routedDispatch(ctx, outbound, destination)
|
go d.routedDispatch(ctx, out, destination)
|
||||||
default:
|
default:
|
||||||
go func() {
|
go func() {
|
||||||
cReader := &cachedReader{
|
cReader := &cachedReader{
|
||||||
reader: outbound.Reader.(*pipe.Reader),
|
reader: out.Reader.(*pipe.Reader),
|
||||||
}
|
}
|
||||||
outbound.Reader = cReader
|
out.Reader = cReader
|
||||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
|
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
content.Protocol = result.Protocol()
|
content.Protocol = result.Protocol()
|
||||||
}
|
}
|
||||||
if err == nil && shouldOverride(ctx, result, sniffingRequest, destination) {
|
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
domain := result.Domain()
|
domain := result.Domain()
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||||
destination.Address = net.ParseAddress(domain)
|
destination.Address = net.ParseAddress(domain)
|
||||||
@@ -293,10 +361,10 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
|||||||
ob.Target = destination
|
ob.Target = destination
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.routedDispatch(ctx, outbound, destination)
|
d.routedDispatch(ctx, out, destination)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return inbound, nil
|
return in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchLink implements routing.Dispatcher.
|
// DispatchLink implements routing.Dispatcher.
|
||||||
@@ -322,7 +390,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
|||||||
result, err := sniffer(ctx, nil, true)
|
result, err := sniffer(ctx, nil, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
content.Protocol = result.Protocol()
|
content.Protocol = result.Protocol()
|
||||||
if shouldOverride(ctx, result, sniffingRequest, destination) {
|
if d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
domain := result.Domain()
|
domain := result.Domain()
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||||
destination.Address = net.ParseAddress(domain)
|
destination.Address = net.ParseAddress(domain)
|
||||||
@@ -344,7 +412,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
content.Protocol = result.Protocol()
|
content.Protocol = result.Protocol()
|
||||||
}
|
}
|
||||||
if err == nil && shouldOverride(ctx, result, sniffingRequest, destination) {
|
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||||
domain := result.Domain()
|
domain := result.Domain()
|
||||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||||
destination.Address = net.ParseAddress(domain)
|
destination.Address = net.ParseAddress(domain)
|
||||||
@@ -408,8 +476,8 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
|
|||||||
|
|
||||||
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
|
||||||
ob := session.OutboundFromContext(ctx)
|
ob := session.OutboundFromContext(ctx)
|
||||||
if d.hosts != nil && destination.Address.Family().IsDomain() {
|
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
|
||||||
proxied := d.hosts.LookupHosts(ob.Target.String())
|
proxied := hosts.LookupHosts(ob.Target.String())
|
||||||
if proxied != nil {
|
if proxied != nil {
|
||||||
ro := ob.RouteTarget == destination
|
ro := ob.RouteTarget == destination
|
||||||
destination.Address = *proxied
|
destination.Address = *proxied
|
||||||
@@ -436,12 +504,13 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routingLink := routing_session.AsRoutingContext(ctx)
|
routingLink := routingSession.AsRoutingContext(ctx)
|
||||||
inTag := routingLink.GetInboundTag()
|
inTag := routingLink.GetInboundTag()
|
||||||
|
isPickRoute := 0
|
||||||
if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" {
|
if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" {
|
||||||
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
||||||
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
||||||
|
isPickRoute = 1
|
||||||
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||||
handler = h
|
handler = h
|
||||||
} else {
|
} else {
|
||||||
@@ -451,13 +520,14 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if d.router != nil {
|
} else if d.router != nil {
|
||||||
if route, err := d.router.PickRoute(routing_session.AsRoutingContext(ctx)); err == nil {
|
if route, err := d.router.PickRoute(routingLink); err == nil {
|
||||||
tag := route.GetOutboundTag()
|
outTag := route.GetOutboundTag()
|
||||||
if h := d.ohm.GetHandler(tag); h != nil {
|
if h := d.ohm.GetHandler(outTag); h != nil {
|
||||||
newError("taking detour [", tag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
isPickRoute = 2
|
||||||
|
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||||
handler = h
|
handler = h
|
||||||
} else {
|
} else {
|
||||||
newError("non existing outTag: ", tag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
||||||
@@ -482,7 +552,15 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
|||||||
|
|
||||||
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
|
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
|
||||||
if tag := handler.Tag(); tag != "" {
|
if tag := handler.Tag(); tag != "" {
|
||||||
|
if inTag == "" {
|
||||||
accessMessage.Detour = tag
|
accessMessage.Detour = tag
|
||||||
|
} else if isPickRoute == 1 {
|
||||||
|
accessMessage.Detour = inTag + " ==> " + tag
|
||||||
|
} else if isPickRoute == 2 {
|
||||||
|
accessMessage.Detour = inTag + " -> " + tag
|
||||||
|
} else {
|
||||||
|
accessMessage.Detour = inTag + " >> " + tag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.Record(accessMessage)
|
log.Record(accessMessage)
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// Package dispather implement the rate limiter and the onlie device counter
|
// Package mydispatcher Package dispatcher implement the rate limiter and the online device counter
|
||||||
package mydispatcher
|
package mydispatcher
|
||||||
|
|
||||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||||
|
@@ -2,8 +2,6 @@ package mydispatcher
|
|||||||
|
|
||||||
import "github.com/xtls/xray-core/common/errors"
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
type errPathObjHolder struct{}
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
func newError(values ...interface{}) *errors.Error {
|
||||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
return errors.New(values...)
|
||||||
}
|
}
|
||||||
|
@@ -14,12 +14,13 @@ import (
|
|||||||
// newFakeDNSSniffer Create a Fake DNS metadata sniffer
|
// newFakeDNSSniffer Create a Fake DNS metadata sniffer
|
||||||
func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) {
|
func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) {
|
||||||
var fakeDNSEngine dns.FakeDNSEngine
|
var fakeDNSEngine dns.FakeDNSEngine
|
||||||
err := core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
{
|
||||||
fakeDNSEngine = fdns
|
fakeDNSEngineFeat := core.MustFromContext(ctx).GetFeature((*dns.FakeDNSEngine)(nil))
|
||||||
})
|
if fakeDNSEngineFeat != nil {
|
||||||
if err != nil {
|
fakeDNSEngine = fakeDNSEngineFeat.(dns.FakeDNSEngine)
|
||||||
return protocolSnifferWithMetadata{}, err
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if fakeDNSEngine == nil {
|
if fakeDNSEngine == nil {
|
||||||
errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
||||||
return protocolSnifferWithMetadata{}, errNotInit
|
return protocolSnifferWithMetadata{}, errNotInit
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import "github.com/urfave/cli"
|
|
||||||
|
|
||||||
// CreateCommands Creates all CLI commands.
|
|
||||||
func CreateCommands() []cli.Command {
|
|
||||||
return []cli.Command{
|
|
||||||
createRun(),
|
|
||||||
createRevoke(),
|
|
||||||
createRenew(),
|
|
||||||
createDNSHelp(),
|
|
||||||
createList(),
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Before(ctx *cli.Context) error {
|
|
||||||
if ctx.GlobalString("path") == "" {
|
|
||||||
log.Panic("Could not determine current working directory. Please pass --path.")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := createNonExistingFolder(ctx.GlobalString("path"))
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Could not check/create path: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.GlobalString("server") == "" {
|
|
||||||
log.Panic("Could not determine current working server. Please pass --server.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,73 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createDNSHelp() cli.Command {
|
|
||||||
return cli.Command{
|
|
||||||
Name: "dnshelp",
|
|
||||||
Usage: "Shows additional help for the '--dns' global option",
|
|
||||||
Action: dnsHelp,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "code, c",
|
|
||||||
Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsHelp(ctx *cli.Context) error {
|
|
||||||
code := ctx.String("code")
|
|
||||||
if code == "" {
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
||||||
ew := &errWriter{w: w}
|
|
||||||
|
|
||||||
ew.writeln(`Credentials for DNS providers must be passed through environment variables.`)
|
|
||||||
ew.writeln()
|
|
||||||
ew.writeln(`To display the documentation for a DNS providers:`)
|
|
||||||
ew.writeln()
|
|
||||||
ew.writeln("\t$ lego dnshelp -c code")
|
|
||||||
ew.writeln()
|
|
||||||
ew.writeln("All DNS codes:")
|
|
||||||
ew.writef("\t%s\n", allDNSCodes())
|
|
||||||
ew.writeln()
|
|
||||||
ew.writeln("More information: https://go-acme.github.io/lego/dns")
|
|
||||||
|
|
||||||
if ew.err != nil {
|
|
||||||
return ew.err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
return displayDNSHelp(strings.ToLower(code))
|
|
||||||
}
|
|
||||||
|
|
||||||
type errWriter struct {
|
|
||||||
w io.Writer
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ew *errWriter) writeln(a ...interface{}) {
|
|
||||||
if ew.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ew.err = fmt.Fprintln(ew.w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ew *errWriter) writef(format string, a ...interface{}) {
|
|
||||||
if ew.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ew.err = fmt.Fprintf(ew.w, format, a...)
|
|
||||||
}
|
|
@@ -1,136 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createList() cli.Command {
|
|
||||||
return cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Usage: "Display certificates and accounts information.",
|
|
||||||
Action: list,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "accounts, a",
|
|
||||||
Usage: "Display accounts.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "names, n",
|
|
||||||
Usage: "Display certificate common names only.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func list(ctx *cli.Context) error {
|
|
||||||
if ctx.Bool("accounts") && !ctx.Bool("names") {
|
|
||||||
if err := listAccount(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return listCertificates(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listCertificates(ctx *cli.Context) error {
|
|
||||||
certsStorage := NewCertificatesStorage(ctx)
|
|
||||||
|
|
||||||
matches, err := filepath.Glob(filepath.Join(certsStorage.GetRootPath(), "*.crt"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
names := ctx.Bool("names")
|
|
||||||
|
|
||||||
if len(matches) == 0 {
|
|
||||||
if !names {
|
|
||||||
fmt.Println("No certificates found.")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !names {
|
|
||||||
fmt.Println("Found the following certs:")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, filename := range matches {
|
|
||||||
if strings.HasSuffix(filename, ".issuer.crt") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pCert, err := certcrypto.ParsePEMCertificate(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if names {
|
|
||||||
fmt.Println(pCert.Subject.CommonName)
|
|
||||||
} else {
|
|
||||||
fmt.Println(" Certificate Name:", pCert.Subject.CommonName)
|
|
||||||
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
|
|
||||||
fmt.Println(" Expiry Date:", pCert.NotAfter)
|
|
||||||
fmt.Println(" Certificate Path:", filename)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listAccount(ctx *cli.Context) error {
|
|
||||||
// fake email, needed by NewAccountsStorage
|
|
||||||
if err := ctx.GlobalSet("email", "unknown"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
accountsStorage := NewAccountsStorage(ctx)
|
|
||||||
|
|
||||||
matches, err := filepath.Glob(filepath.Join(accountsStorage.GetRootPath(), "*", "*", "*.json"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(matches) == 0 {
|
|
||||||
fmt.Println("No accounts found.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Found the following accounts:")
|
|
||||||
for _, filename := range matches {
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var account Account
|
|
||||||
err = json.Unmarshal(data, &account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uri, err := url.Parse(account.Registration.URI)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(" Email:", account.Email)
|
|
||||||
fmt.Println(" Server:", uri.Host)
|
|
||||||
fmt.Println(" Path:", filepath.Dir(filename))
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,225 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
|
||||||
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
|
||||||
renewEnvCertPath = "LEGO_CERT_PATH"
|
|
||||||
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createRenew() cli.Command {
|
|
||||||
return cli.Command{
|
|
||||||
Name: "renew",
|
|
||||||
Usage: "Renew a certificate",
|
|
||||||
Action: renew,
|
|
||||||
Before: func(ctx *cli.Context) error {
|
|
||||||
// we require either domains or csr, but not both
|
|
||||||
hasDomains := len(ctx.GlobalStringSlice("domains")) > 0
|
|
||||||
hasCsr := len(ctx.GlobalString("csr")) > 0
|
|
||||||
if hasDomains && hasCsr {
|
|
||||||
log.Panic("Please specify either --domains/-d or --csr/-c, but not both")
|
|
||||||
}
|
|
||||||
if !hasDomains && !hasCsr {
|
|
||||||
log.Panic("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "days",
|
|
||||||
Value: 30,
|
|
||||||
Usage: "The number of days left on a certificate to renew it.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "reuse-key",
|
|
||||||
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "no-bundle",
|
|
||||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "must-staple",
|
|
||||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "renew-hook",
|
|
||||||
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "preferred-chain",
|
|
||||||
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func renew(ctx *cli.Context) error {
|
|
||||||
account, client := setup(ctx, NewAccountsStorage(ctx))
|
|
||||||
setupChallenges(ctx, client)
|
|
||||||
|
|
||||||
if account.Registration == nil {
|
|
||||||
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
certsStorage := NewCertificatesStorage(ctx)
|
|
||||||
|
|
||||||
bundle := !ctx.Bool("no-bundle")
|
|
||||||
|
|
||||||
meta := map[string]string{renewEnvAccountEmail: account.Email}
|
|
||||||
|
|
||||||
// CSR
|
|
||||||
if ctx.GlobalIsSet("csr") {
|
|
||||||
return renewForCSR(ctx, client, certsStorage, bundle, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domains
|
|
||||||
return renewForDomains(ctx, client, certsStorage, bundle, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
|
|
||||||
domains := ctx.GlobalStringSlice("domains")
|
|
||||||
domain := domains[0]
|
|
||||||
|
|
||||||
// load the cert resource from files.
|
|
||||||
// We store the certificate, private key and metadata in different files
|
|
||||||
// as web servers would not be able to work with a combined file.
|
|
||||||
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert := certificates[0]
|
|
||||||
|
|
||||||
if !needRenewal(cert, domain, ctx.Int("days")) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is just meant to be informal for the user.
|
|
||||||
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
|
||||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
|
||||||
|
|
||||||
certDomains := certcrypto.ExtractDomains(cert)
|
|
||||||
|
|
||||||
var privateKey crypto.PrivateKey
|
|
||||||
if ctx.Bool("reuse-key") {
|
|
||||||
keyBytes, errR := certsStorage.ReadFile(domain, ".key")
|
|
||||||
if errR != nil {
|
|
||||||
log.Panicf("Error while loading the private key for domain %s\n\t%v", domain, errR)
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey, errR = certcrypto.ParsePEMPrivateKey(keyBytes)
|
|
||||||
if errR != nil {
|
|
||||||
return errR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
|
||||||
Domains: merge(certDomains, domains),
|
|
||||||
Bundle: bundle,
|
|
||||||
PrivateKey: privateKey,
|
|
||||||
MustStaple: ctx.Bool("must-staple"),
|
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
|
||||||
}
|
|
||||||
certRes, err := client.Certificate.Obtain(request)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certsStorage.SaveResource(certRes)
|
|
||||||
|
|
||||||
meta[renewEnvCertDomain] = domain
|
|
||||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
|
|
||||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
|
|
||||||
|
|
||||||
return launchHook(ctx.String("renew-hook"), meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
|
|
||||||
csr, err := readCSRFile(ctx.GlobalString("csr"))
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := csr.Subject.CommonName
|
|
||||||
|
|
||||||
// load the cert resource from files.
|
|
||||||
// We store the certificate, private key and metadata in different files
|
|
||||||
// as web servers would not be able to work with a combined file.
|
|
||||||
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert := certificates[0]
|
|
||||||
|
|
||||||
if !needRenewal(cert, domain, ctx.Int("days")) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is just meant to be informal for the user.
|
|
||||||
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
|
||||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
|
||||||
|
|
||||||
certRes, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
|
|
||||||
CSR: csr,
|
|
||||||
Bundle: bundle,
|
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certsStorage.SaveResource(certRes)
|
|
||||||
|
|
||||||
meta[renewEnvCertDomain] = domain
|
|
||||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
|
|
||||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
|
|
||||||
|
|
||||||
return launchHook(ctx.String("renew-hook"), meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
|
|
||||||
if x509Cert.IsCA {
|
|
||||||
log.Panicf("[%s] Certificate bundle starts with a CA certificate", domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
if days >= 0 {
|
|
||||||
notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0)
|
|
||||||
if notAfter > days {
|
|
||||||
log.Printf("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.",
|
|
||||||
domain, notAfter, days)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func merge(prevDomains, nextDomains []string) []string {
|
|
||||||
for _, next := range nextDomains {
|
|
||||||
var found bool
|
|
||||||
for _, prev := range prevDomains {
|
|
||||||
if prev == next {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
prevDomains = append(prevDomains, next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prevDomains
|
|
||||||
}
|
|
@@ -1,62 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createRevoke() cli.Command {
|
|
||||||
return cli.Command{
|
|
||||||
Name: "revoke",
|
|
||||||
Usage: "Revoke a certificate",
|
|
||||||
Action: revoke,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "keep, k",
|
|
||||||
Usage: "Keep the certificates after the revocation instead of archiving them.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func revoke(ctx *cli.Context) error {
|
|
||||||
acc, client := setup(ctx, NewAccountsStorage(ctx))
|
|
||||||
|
|
||||||
if acc.Registration == nil {
|
|
||||||
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
certsStorage := NewCertificatesStorage(ctx)
|
|
||||||
certsStorage.CreateRootFolder()
|
|
||||||
|
|
||||||
for _, domain := range ctx.GlobalStringSlice("domains") {
|
|
||||||
log.Printf("Trying to revoke certificate for domain %s", domain)
|
|
||||||
|
|
||||||
certBytes, err := certsStorage.ReadFile(domain, ".crt")
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.Certificate.Revoke(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Certificate was revoked.")
|
|
||||||
|
|
||||||
if ctx.Bool("keep") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
certsStorage.CreateArchiveFolder()
|
|
||||||
|
|
||||||
err = certsStorage.MoveToArchive(domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Certificate was archived for domain:", domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,186 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createRun() cli.Command {
|
|
||||||
return cli.Command{
|
|
||||||
Name: "run",
|
|
||||||
Usage: "Register an account, then create and install a certificate",
|
|
||||||
Before: func(ctx *cli.Context) error {
|
|
||||||
// we require either domains or csr, but not both
|
|
||||||
hasDomains := len(ctx.GlobalStringSlice("domains")) > 0
|
|
||||||
hasCsr := len(ctx.GlobalString("csr")) > 0
|
|
||||||
if hasDomains && hasCsr {
|
|
||||||
log.Panic("Please specify either --domains/-d or --csr/-c, but not both")
|
|
||||||
}
|
|
||||||
if !hasDomains && !hasCsr {
|
|
||||||
log.Panic("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Action: run,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "no-bundle",
|
|
||||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "must-staple",
|
|
||||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "run-hook",
|
|
||||||
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "preferred-chain",
|
|
||||||
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootPathWarningMessage = `!!!! HEADS UP !!!!
|
|
||||||
|
|
||||||
Your account credentials have been saved in your Let's Encrypt
|
|
||||||
configuration directory at "%s".
|
|
||||||
|
|
||||||
You should make a secure backup of this folder now. This
|
|
||||||
configuration directory will also contain certificates and
|
|
||||||
private keys obtained from Let's Encrypt so making regular
|
|
||||||
backups of this folder is ideal.
|
|
||||||
`
|
|
||||||
|
|
||||||
func run(ctx *cli.Context) error {
|
|
||||||
accountsStorage := NewAccountsStorage(ctx)
|
|
||||||
|
|
||||||
account, client := setup(ctx, accountsStorage)
|
|
||||||
setupChallenges(ctx, client)
|
|
||||||
|
|
||||||
if account.Registration == nil {
|
|
||||||
reg, err := register(ctx, client)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Could not complete registration\n\t%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
account.Registration = reg
|
|
||||||
if err = accountsStorage.Save(account); err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
|
|
||||||
}
|
|
||||||
|
|
||||||
certsStorage := NewCertificatesStorage(ctx)
|
|
||||||
certsStorage.CreateRootFolder()
|
|
||||||
|
|
||||||
cert, err := obtainCertificate(ctx, client)
|
|
||||||
if err != nil {
|
|
||||||
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
|
|
||||||
// Due to us not returning partial certificate we can just exit here instead of at the end.
|
|
||||||
log.Panicf("Could not obtain certificates:\n\t%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certsStorage.SaveResource(cert)
|
|
||||||
|
|
||||||
meta := map[string]string{
|
|
||||||
renewEnvAccountEmail: account.Email,
|
|
||||||
renewEnvCertDomain: cert.Domain,
|
|
||||||
renewEnvCertPath: certsStorage.GetFileName(cert.Domain, ".crt"),
|
|
||||||
renewEnvCertKeyPath: certsStorage.GetFileName(cert.Domain, ".key"),
|
|
||||||
}
|
|
||||||
|
|
||||||
return launchHook(ctx.String("run-hook"), meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
|
|
||||||
// Check for a global accept override
|
|
||||||
if ctx.GlobalBool("accept-tos") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
log.Printf("Please review the TOS at %s", client.GetToSURL())
|
|
||||||
|
|
||||||
for {
|
|
||||||
fmt.Println("Do you accept the TOS? Y/n")
|
|
||||||
text, err := reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Could not read from console: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
text = strings.Trim(text, "\r\n")
|
|
||||||
switch text {
|
|
||||||
case "", "y", "Y":
|
|
||||||
return true
|
|
||||||
case "n", "N":
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
fmt.Println("Your input was invalid. Please answer with one of Y/y, n/N or by pressing enter.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, error) {
|
|
||||||
accepted := handleTOS(ctx, client)
|
|
||||||
if !accepted {
|
|
||||||
log.Panic("You did not accept the TOS. Unable to proceed.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.GlobalBool("eab") {
|
|
||||||
kid := ctx.GlobalString("kid")
|
|
||||||
hmacEncoded := ctx.GlobalString("hmac")
|
|
||||||
|
|
||||||
if kid == "" || hmacEncoded == "" {
|
|
||||||
log.Panicf("Requires arguments --kid and --hmac.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
|
||||||
TermsOfServiceAgreed: accepted,
|
|
||||||
Kid: kid,
|
|
||||||
HmacEncoded: hmacEncoded,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Resource, error) {
|
|
||||||
bundle := !ctx.Bool("no-bundle")
|
|
||||||
|
|
||||||
domains := ctx.GlobalStringSlice("domains")
|
|
||||||
if len(domains) > 0 {
|
|
||||||
// obtain a certificate, generating a new private key
|
|
||||||
request := certificate.ObtainRequest{
|
|
||||||
Domains: domains,
|
|
||||||
Bundle: bundle,
|
|
||||||
MustStaple: ctx.Bool("must-staple"),
|
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
|
||||||
}
|
|
||||||
return client.Certificate.Obtain(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// read the CSR
|
|
||||||
csr, err := readCSRFile(ctx.GlobalString("csr"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// obtain a certificate for this CSR
|
|
||||||
return client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
|
|
||||||
CSR: csr,
|
|
||||||
Bundle: bundle,
|
|
||||||
PreferredChain: ctx.String("preferred-chain"),
|
|
||||||
})
|
|
||||||
}
|
|
@@ -1,120 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateFlags(defaultPath string) []cli.Flag {
|
|
||||||
return []cli.Flag{
|
|
||||||
cli.StringSliceFlag{
|
|
||||||
Name: "domains, d",
|
|
||||||
Usage: "Add a domain to the process. Can be specified multiple times.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "server, s",
|
|
||||||
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
|
|
||||||
Value: lego.LEDirectoryProduction,
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "accept-tos, a",
|
|
||||||
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "email, m",
|
|
||||||
Usage: "Email used for registration and recovery contact.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "csr, c",
|
|
||||||
Usage: "Certificate signing request filename, if an external CSR is to be used.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "eab",
|
|
||||||
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "kid",
|
|
||||||
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "hmac",
|
|
||||||
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "key-type, k",
|
|
||||||
Value: "ec256",
|
|
||||||
Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "filename",
|
|
||||||
Usage: "(deprecated) Filename of the generated certificate.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "path",
|
|
||||||
EnvVar: "LEGO_PATH",
|
|
||||||
Usage: "Directory to use for storing the data.",
|
|
||||||
Value: defaultPath,
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "http",
|
|
||||||
Usage: "Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "http.port",
|
|
||||||
Usage: "Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port.",
|
|
||||||
Value: ":80",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "http.proxy-header",
|
|
||||||
Usage: "Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy.",
|
|
||||||
Value: "Host",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "http.webroot",
|
|
||||||
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge",
|
|
||||||
},
|
|
||||||
cli.StringSliceFlag{
|
|
||||||
Name: "http.memcached-host",
|
|
||||||
Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "tls",
|
|
||||||
Usage: "Use the TLS challenge to solve challenges. Can be mixed with other types of challenges.",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "tls.port",
|
|
||||||
Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port.",
|
|
||||||
Value: ":443",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "dns",
|
|
||||||
Usage: "Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "dns.disable-cp",
|
|
||||||
Usage: "By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.",
|
|
||||||
},
|
|
||||||
cli.StringSliceFlag{
|
|
||||||
Name: "dns.resolvers",
|
|
||||||
Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "http-timeout",
|
|
||||||
Usage: "Set the HTTP timeout value to a specific value in seconds.",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "dns-timeout",
|
|
||||||
Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries.",
|
|
||||||
Value: 10,
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "pem",
|
|
||||||
Usage: "Generate a .pem file by concatenating the .key and .crt files together.",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "cert.timeout",
|
|
||||||
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
|
|
||||||
Value: 30,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,47 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func launchHook(hook string, meta map[string]string) error {
|
|
||||||
if hook == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxCmd, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
parts := strings.Fields(hook)
|
|
||||||
|
|
||||||
cmdCtx := exec.CommandContext(ctxCmd, parts[0], parts[1:]...)
|
|
||||||
cmdCtx.Env = append(os.Environ(), metaToEnv(meta)...)
|
|
||||||
|
|
||||||
output, err := cmdCtx.CombinedOutput()
|
|
||||||
|
|
||||||
if len(output) > 0 {
|
|
||||||
fmt.Println(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(ctxCmd.Err(), context.DeadlineExceeded) {
|
|
||||||
return errors.New("hook timed out")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func metaToEnv(meta map[string]string) []string {
|
|
||||||
var envs []string
|
|
||||||
|
|
||||||
for k, v := range meta {
|
|
||||||
envs = append(envs, k+"="+v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return envs
|
|
||||||
}
|
|
@@ -1,129 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
const filePerm os.FileMode = 0o600
|
|
||||||
|
|
||||||
func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego.Client) {
|
|
||||||
keyType := getKeyType(ctx)
|
|
||||||
privateKey := accountsStorage.GetPrivateKey(keyType)
|
|
||||||
|
|
||||||
var account *Account
|
|
||||||
if accountsStorage.ExistsAccountFilePath() {
|
|
||||||
account = accountsStorage.LoadAccount(privateKey)
|
|
||||||
} else {
|
|
||||||
account = &Account{Email: accountsStorage.GetUserID(), key: privateKey}
|
|
||||||
}
|
|
||||||
|
|
||||||
client := newClient(ctx, account, keyType)
|
|
||||||
|
|
||||||
return account, client
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
|
|
||||||
config := lego.NewConfig(acc)
|
|
||||||
config.CADirURL = ctx.GlobalString("server")
|
|
||||||
|
|
||||||
config.Certificate = lego.CertificateConfig{
|
|
||||||
KeyType: keyType,
|
|
||||||
Timeout: time.Duration(ctx.GlobalInt("cert.timeout")) * time.Second,
|
|
||||||
}
|
|
||||||
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
|
|
||||||
|
|
||||||
if ctx.GlobalIsSet("http-timeout") {
|
|
||||||
config.HTTPClient.Timeout = time.Duration(ctx.GlobalInt("http-timeout")) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := lego.NewClient(config)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Could not create client: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.GetExternalAccountRequired() && !ctx.GlobalIsSet("eab") {
|
|
||||||
log.Panic("Server requires External Account Binding. Use --eab with --kid and --hmac.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// getKeyType the type from which private keys should be generated.
|
|
||||||
func getKeyType(ctx *cli.Context) certcrypto.KeyType {
|
|
||||||
keyType := ctx.GlobalString("key-type")
|
|
||||||
switch strings.ToUpper(keyType) {
|
|
||||||
case "RSA2048":
|
|
||||||
return certcrypto.RSA2048
|
|
||||||
case "RSA4096":
|
|
||||||
return certcrypto.RSA4096
|
|
||||||
case "RSA8192":
|
|
||||||
return certcrypto.RSA8192
|
|
||||||
case "EC256":
|
|
||||||
return certcrypto.EC256
|
|
||||||
case "EC384":
|
|
||||||
return certcrypto.EC384
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Panicf("Unsupported KeyType: %s", keyType)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEmail(ctx *cli.Context) string {
|
|
||||||
email := ctx.GlobalString("email")
|
|
||||||
if email == "" {
|
|
||||||
log.Panic("You have to pass an account (email address) to the program using --email or -m")
|
|
||||||
}
|
|
||||||
return email
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNonExistingFolder(path string) error {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
return os.MkdirAll(path, 0o700)
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readCSRFile(filename string) (*x509.CertificateRequest, error) {
|
|
||||||
bytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
raw := bytes
|
|
||||||
|
|
||||||
// see if we can find a PEM-encoded CSR
|
|
||||||
var p *pem.Block
|
|
||||||
rest := bytes
|
|
||||||
for {
|
|
||||||
// decode a PEM block
|
|
||||||
p, rest = pem.Decode(rest)
|
|
||||||
|
|
||||||
// did we fail?
|
|
||||||
if p == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// did we get a CSR?
|
|
||||||
if p.Type == "CERTIFICATE REQUEST" {
|
|
||||||
raw = p.Bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no PEM-encoded CSR
|
|
||||||
// assume we were given a DER-encoded ASN.1 CSR
|
|
||||||
// (if this assumption is wrong, parsing these bytes will fail)
|
|
||||||
return x509.ParseCertificateRequest(raw)
|
|
||||||
}
|
|
@@ -1,126 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns"
|
|
||||||
"github.com/go-acme/lego/v4/providers/http/memcached"
|
|
||||||
"github.com/go-acme/lego/v4/providers/http/webroot"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupChallenges(ctx *cli.Context, client *lego.Client) {
|
|
||||||
if !ctx.GlobalBool("http") && !ctx.GlobalBool("tls") && !ctx.GlobalIsSet("dns") {
|
|
||||||
log.Panic("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.GlobalBool("http") {
|
|
||||||
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx))
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.GlobalBool("tls") {
|
|
||||||
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.GlobalIsSet("dns") {
|
|
||||||
setupDNS(ctx, client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
|
||||||
switch {
|
|
||||||
case ctx.GlobalIsSet("http.webroot"):
|
|
||||||
ps, err := webroot.NewHTTPProvider(ctx.GlobalString("http.webroot"))
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
return ps
|
|
||||||
case ctx.GlobalIsSet("http.memcached-host"):
|
|
||||||
ps, err := memcached.NewMemcachedProvider(ctx.GlobalStringSlice("http.memcached-host"))
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
return ps
|
|
||||||
case ctx.GlobalIsSet("http.port"):
|
|
||||||
iface := ctx.GlobalString("http.port")
|
|
||||||
if !strings.Contains(iface, ":") {
|
|
||||||
log.Panicf("The --http switch only accepts interface:port or :port for its argument.")
|
|
||||||
}
|
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(iface)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := http01.NewProviderServer(host, port)
|
|
||||||
if header := ctx.GlobalString("http.proxy-header"); header != "" {
|
|
||||||
srv.SetProxyHeader(header)
|
|
||||||
}
|
|
||||||
return srv
|
|
||||||
case ctx.GlobalBool("http"):
|
|
||||||
srv := http01.NewProviderServer("", "")
|
|
||||||
if header := ctx.GlobalString("http.proxy-header"); header != "" {
|
|
||||||
srv.SetProxyHeader(header)
|
|
||||||
}
|
|
||||||
return srv
|
|
||||||
default:
|
|
||||||
log.Panic("Invalid HTTP challenge options.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
|
||||||
switch {
|
|
||||||
case ctx.GlobalIsSet("tls.port"):
|
|
||||||
iface := ctx.GlobalString("tls.port")
|
|
||||||
if !strings.Contains(iface, ":") {
|
|
||||||
log.Panicf("The --tls switch only accepts interface:port or :port for its argument.")
|
|
||||||
}
|
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(iface)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsalpn01.NewProviderServer(host, port)
|
|
||||||
case ctx.GlobalBool("tls"):
|
|
||||||
return tlsalpn01.NewProviderServer("", "")
|
|
||||||
default:
|
|
||||||
log.Panic("Invalid HTTP challenge options.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupDNS(ctx *cli.Context, client *lego.Client) {
|
|
||||||
provider, err := dns.NewDNSChallengeProviderByName(ctx.GlobalString("dns"))
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
servers := ctx.GlobalStringSlice("dns.resolvers")
|
|
||||||
err = client.Challenge.SetDNS01Provider(provider,
|
|
||||||
dns01.CondOption(len(servers) > 0,
|
|
||||||
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.GlobalStringSlice("dns.resolvers")))),
|
|
||||||
dns01.CondOption(ctx.GlobalBool("dns.disable-cp"),
|
|
||||||
dns01.DisableCompletePropagationRequirement()),
|
|
||||||
dns01.CondOption(ctx.GlobalIsSet("dns-timeout"),
|
|
||||||
dns01.AddDNSTimeout(time.Duration(ctx.GlobalInt("dns-timeout"))*time.Second)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@@ -1,189 +0,0 @@
|
|||||||
// Let's Encrypt client to go!
|
|
||||||
// CLI application for generating Let's Encrypt certificates using the ACME package.
|
|
||||||
package legocmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/cmd"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var version = "dev"
|
|
||||||
var defaultPath string
|
|
||||||
|
|
||||||
type LegoCMD struct {
|
|
||||||
cmdClient *cli.App
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() (*LegoCMD, error) {
|
|
||||||
app := cli.NewApp()
|
|
||||||
app.Name = "lego"
|
|
||||||
app.HelpName = "lego"
|
|
||||||
app.Usage = "Let's Encrypt client written in Go"
|
|
||||||
app.EnableBashCompletion = true
|
|
||||||
|
|
||||||
app.Version = version
|
|
||||||
cli.VersionPrinter = func(c *cli.Context) {
|
|
||||||
fmt.Printf("lego version %s %s/%s\n", c.App.Version, runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set default path to configPath/cert
|
|
||||||
var path string = ""
|
|
||||||
configPath := os.Getenv("XRAY_LOCATION_CONFIG")
|
|
||||||
if configPath != "" {
|
|
||||||
path = configPath
|
|
||||||
} else if cwd, err := os.Getwd(); err==nil{
|
|
||||||
path = cwd
|
|
||||||
} else {
|
|
||||||
path = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultPath = filepath.Join(path, "cert")
|
|
||||||
|
|
||||||
app.Flags = cmd.CreateFlags(defaultPath)
|
|
||||||
|
|
||||||
app.Before = cmd.Before
|
|
||||||
|
|
||||||
app.Commands = cmd.CreateCommands()
|
|
||||||
|
|
||||||
lego := &LegoCMD{
|
|
||||||
cmdClient: app,
|
|
||||||
}
|
|
||||||
|
|
||||||
return lego, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSCert cert a domain using DNS API
|
|
||||||
func (l *LegoCMD) DNSCert(domain, email, provider string, DNSEnv map[string]string) (CertPath string, KeyPath string, err error) {
|
|
||||||
defer func() (string, string, error) {
|
|
||||||
// Handle any error
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
switch x := r.(type) {
|
|
||||||
case string:
|
|
||||||
err = errors.New(x)
|
|
||||||
case error:
|
|
||||||
err = x
|
|
||||||
default:
|
|
||||||
err = errors.New("unknow panic")
|
|
||||||
}
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return CertPath, KeyPath, nil
|
|
||||||
}()
|
|
||||||
// Set Env for DNS configuration
|
|
||||||
for key, value := range DNSEnv {
|
|
||||||
os.Setenv(key, value)
|
|
||||||
}
|
|
||||||
// First check if the certificate exists
|
|
||||||
CertPath, KeyPath, err = checkCertfile(domain)
|
|
||||||
if err == nil {
|
|
||||||
return CertPath, KeyPath, err
|
|
||||||
}
|
|
||||||
|
|
||||||
argstring := fmt.Sprintf("lego -a -d %s -m %s --dns %s run", domain, email, provider)
|
|
||||||
err = l.cmdClient.Run(strings.Split(argstring, " "))
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
CertPath, KeyPath, err = checkCertfile(domain)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return CertPath, KeyPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPCert cert a domain using http methods
|
|
||||||
func (l *LegoCMD) HTTPCert(domain, email string) (CertPath string, KeyPath string, err error) {
|
|
||||||
defer func() (string, string, error) {
|
|
||||||
// Handle any error
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
switch x := r.(type) {
|
|
||||||
case string:
|
|
||||||
err = errors.New(x)
|
|
||||||
case error:
|
|
||||||
err = x
|
|
||||||
default:
|
|
||||||
err = errors.New("unknow panic")
|
|
||||||
}
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return CertPath, KeyPath, nil
|
|
||||||
}()
|
|
||||||
// First check if the certificate exists
|
|
||||||
CertPath, KeyPath, err = checkCertfile(domain)
|
|
||||||
if err == nil {
|
|
||||||
return CertPath, KeyPath, err
|
|
||||||
}
|
|
||||||
argstring := fmt.Sprintf("lego -a -d %s -m %s --http run", domain, email)
|
|
||||||
err = l.cmdClient.Run(strings.Split(argstring, " "))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
CertPath, KeyPath, err = checkCertfile(domain)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return CertPath, KeyPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//RenewCert renew a domain cert
|
|
||||||
func (l *LegoCMD) RenewCert(domain, email, certMode, provider string, DNSEnv map[string]string) (CertPath string, KeyPath string, err error) {
|
|
||||||
var argstring string
|
|
||||||
defer func() (string, string, error) {
|
|
||||||
// Handle any error
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
switch x := r.(type) {
|
|
||||||
case string:
|
|
||||||
err = errors.New(x)
|
|
||||||
case error:
|
|
||||||
err = x
|
|
||||||
default:
|
|
||||||
err = errors.New("unknow panic")
|
|
||||||
}
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return CertPath, KeyPath, nil
|
|
||||||
}()
|
|
||||||
if certMode == "http" {
|
|
||||||
argstring = fmt.Sprintf("lego -a -d %s -m %s --http renew --days 30", domain, email)
|
|
||||||
} else if certMode == "dns" {
|
|
||||||
// Set Env for DNS configuration
|
|
||||||
for key, value := range DNSEnv {
|
|
||||||
os.Setenv(key, value)
|
|
||||||
}
|
|
||||||
argstring = fmt.Sprintf("lego -a -d %s -m %s --dns %s renew --days 30", domain, email, provider)
|
|
||||||
} else {
|
|
||||||
return "", "", fmt.Errorf("Unsupport cert mode: %s", certMode)
|
|
||||||
}
|
|
||||||
err = l.cmdClient.Run(strings.Split(argstring, " "))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
CertPath, KeyPath, err = checkCertfile(domain)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return CertPath, KeyPath, nil
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
|
||||||
return "", "", fmt.Errorf("Cert key failed: %s", domain)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(certPath); os.IsNotExist(err) {
|
|
||||||
return "", "", fmt.Errorf("Cert cert failed: %s", domain)
|
|
||||||
}
|
|
||||||
absKeyPath, _ := filepath.Abs(keyPath)
|
|
||||||
absCertPath, _ := filepath.Abs(certPath)
|
|
||||||
return absCertPath, absKeyPath, nil
|
|
||||||
}
|
|
@@ -1,82 +0,0 @@
|
|||||||
package legocmd_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLegoClient(t *testing.T) {
|
|
||||||
_, err := legocmd.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLegoDNSCert(t *testing.T) {
|
|
||||||
lego, err := legocmd.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
domain string = "node1.test.com"
|
|
||||||
email string = "test@gmail.com"
|
|
||||||
provider string = "alidns"
|
|
||||||
DNSEnv map[string]string
|
|
||||||
)
|
|
||||||
DNSEnv = make(map[string]string)
|
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
|
||||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
|
||||||
certPath, keyPath, err := lego.DNSCert(domain, email, provider, DNSEnv)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
t.Log(certPath)
|
|
||||||
t.Log(keyPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLegoHTTPCert(t *testing.T) {
|
|
||||||
lego, err := legocmd.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
domain string = "node1.test.com"
|
|
||||||
email string = "test@gmail.com"
|
|
||||||
)
|
|
||||||
certPath, keyPath, err := lego.HTTPCert(domain, email)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
t.Log(certPath)
|
|
||||||
t.Log(keyPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLegoRenewCert(t *testing.T) {
|
|
||||||
lego, err := legocmd.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
domain string = "node1.test.com"
|
|
||||||
email string = "test@gmail.com"
|
|
||||||
provider string = "alidns"
|
|
||||||
DNSEnv map[string]string
|
|
||||||
)
|
|
||||||
DNSEnv = make(map[string]string)
|
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
|
||||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
|
||||||
certPath, keyPath, err := lego.RenewCert(domain, email, "dns", provider, DNSEnv)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
t.Log(certPath)
|
|
||||||
t.Log(keyPath)
|
|
||||||
|
|
||||||
certPath, keyPath, err = lego.RenewCert(domain, email, "http", provider, DNSEnv)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
t.Log(certPath)
|
|
||||||
t.Log(keyPath)
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logger is an optional custom logger.
|
|
||||||
var Logger StdLogger = log.New(os.Stdout, "", log.LstdFlags)
|
|
||||||
|
|
||||||
// StdLogger interface for Standard Logger.
|
|
||||||
type StdLogger interface {
|
|
||||||
Panic(args ...interface{})
|
|
||||||
Fatalln(args ...interface{})
|
|
||||||
Panicf(format string, args ...interface{})
|
|
||||||
Print(args ...interface{})
|
|
||||||
Println(args ...interface{})
|
|
||||||
Printf(format string, args ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic writes a log entry.
|
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
|
||||||
func Panic(args ...interface{}) {
|
|
||||||
Logger.Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicf writes a log entry.
|
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
|
||||||
func Panicf(format string, args ...interface{}) {
|
|
||||||
Logger.Panicf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print writes a log entry.
|
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
|
||||||
func Print(args ...interface{}) {
|
|
||||||
Logger.Print(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println writes a log entry.
|
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
|
||||||
func Println(args ...interface{}) {
|
|
||||||
Logger.Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf writes a log entry.
|
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
|
||||||
func Printf(format string, args ...interface{}) {
|
|
||||||
Logger.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnf writes a log entry.
|
|
||||||
func Warnf(format string, args ...interface{}) {
|
|
||||||
Printf("[WARN] "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof writes a log entry.
|
|
||||||
func Infof(format string, args ...interface{}) {
|
|
||||||
Printf("[INFO] "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
@@ -2,8 +2,6 @@ package limiter
|
|||||||
|
|
||||||
import "github.com/xtls/xray-core/common/errors"
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
type errPathObjHolder struct{}
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
func newError(values ...interface{}) *errors.Error {
|
||||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
return errors.New(values...)
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,24 @@
|
|||||||
// Package limiter is to control the links that go into the dispather
|
// Package limiter is to control the links that go into the dispatcher
|
||||||
package limiter
|
package limiter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/eko/gocache/lib/v4/cache"
|
||||||
|
"github.com/eko/gocache/lib/v4/marshaler"
|
||||||
|
"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"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
"github.com/juju/ratelimit"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
@@ -20,8 +31,12 @@ type InboundInfo struct {
|
|||||||
Tag string
|
Tag string
|
||||||
NodeSpeedLimit uint64
|
NodeSpeedLimit uint64
|
||||||
UserInfo *sync.Map // Key: Email value: UserInfo
|
UserInfo *sync.Map // Key: Email value: UserInfo
|
||||||
BucketHub *sync.Map // key: Email, value: *ratelimit.Bucket
|
BucketHub *sync.Map // key: Email, value: *rate.Limiter
|
||||||
UserOnlineIP *sync.Map // Key: Email Value: *sync.Map: Key: IP, Value: UID
|
UserOnlineIP *sync.Map // Key: Email, value: {Key: IP, value: UID}
|
||||||
|
GlobalLimit struct {
|
||||||
|
config *GlobalDeviceLimitConfig
|
||||||
|
globalOnlineIP *marshaler.Marshaler
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Limiter struct {
|
type Limiter struct {
|
||||||
@@ -34,13 +49,37 @@ func New() *Limiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo) error {
|
func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo, globalLimit *GlobalDeviceLimitConfig) error {
|
||||||
inboundInfo := &InboundInfo{
|
inboundInfo := &InboundInfo{
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
NodeSpeedLimit: nodeSpeedLimit,
|
NodeSpeedLimit: nodeSpeedLimit,
|
||||||
BucketHub: new(sync.Map),
|
BucketHub: new(sync.Map),
|
||||||
UserOnlineIP: new(sync.Map),
|
UserOnlineIP: new(sync.Map),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if globalLimit != nil && globalLimit.Enable {
|
||||||
|
inboundInfo.GlobalLimit.config = globalLimit
|
||||||
|
|
||||||
|
// init local store
|
||||||
|
gs := goCacheStore.NewGoCache(goCache.New(time.Duration(globalLimit.Expiry)*time.Second, 1*time.Minute))
|
||||||
|
|
||||||
|
// init redis store
|
||||||
|
rs := redisStore.NewRedis(redis.NewClient(
|
||||||
|
&redis.Options{
|
||||||
|
Addr: globalLimit.RedisAddr,
|
||||||
|
Password: globalLimit.RedisPassword,
|
||||||
|
DB: globalLimit.RedisDB,
|
||||||
|
}),
|
||||||
|
store.WithExpiration(time.Duration(globalLimit.Expiry)*time.Second))
|
||||||
|
|
||||||
|
// init chained cache. First use local go-cache, if go-cache is nil, then use redis cache
|
||||||
|
cacheManager := cache.NewChain[any](
|
||||||
|
cache.New[any](gs), // go-cache is priority
|
||||||
|
cache.New[any](rs),
|
||||||
|
)
|
||||||
|
inboundInfo.GlobalLimit.globalOnlineIP = marshaler.New(cacheManager)
|
||||||
|
}
|
||||||
|
|
||||||
userMap := new(sync.Map)
|
userMap := new(sync.Map)
|
||||||
for _, u := range *userList {
|
for _, u := range *userList {
|
||||||
userMap.Store(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID), UserInfo{
|
userMap.Store(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID), UserInfo{
|
||||||
@@ -55,7 +94,6 @@ func (l *Limiter) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limiter) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserInfo) error {
|
func (l *Limiter) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserInfo) error {
|
||||||
|
|
||||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||||
inboundInfo := value.(*InboundInfo)
|
inboundInfo := value.(*InboundInfo)
|
||||||
// Update User info
|
// Update User info
|
||||||
@@ -65,7 +103,17 @@ func (l *Limiter) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserIn
|
|||||||
SpeedLimit: u.SpeedLimit,
|
SpeedLimit: u.SpeedLimit,
|
||||||
DeviceLimit: u.DeviceLimit,
|
DeviceLimit: u.DeviceLimit,
|
||||||
})
|
})
|
||||||
inboundInfo.BucketHub.Delete(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID)) // Delete old limiter bucket
|
// Update old limiter bucket
|
||||||
|
limit := determineRate(inboundInfo.NodeSpeedLimit, u.SpeedLimit)
|
||||||
|
if limit > 0 {
|
||||||
|
if bucket, ok := inboundInfo.BucketHub.Load(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID)); ok {
|
||||||
|
limiter := bucket.(*rate.Limiter)
|
||||||
|
limiter.SetLimit(rate.Limit(limit))
|
||||||
|
limiter.SetBurst(int(limit))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inboundInfo.BucketHub.Delete(fmt.Sprintf("%s|%s|%d", tag, u.Email, u.UID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("no such inbound in limiter: %s", tag)
|
return fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||||
@@ -79,7 +127,8 @@ func (l *Limiter) DeleteInboundLimiter(tag string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limiter) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
|
func (l *Limiter) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
|
||||||
onlineUser := make([]api.OnlineUser, 0)
|
var onlineUser []api.OnlineUser
|
||||||
|
|
||||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||||
inboundInfo := value.(*InboundInfo)
|
inboundInfo := value.(*InboundInfo)
|
||||||
// Clear Speed Limiter bucket for users who are not online
|
// Clear Speed Limiter bucket for users who are not online
|
||||||
@@ -91,43 +140,48 @@ func (l *Limiter) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
|
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
|
||||||
|
email := key.(string)
|
||||||
ipMap := value.(*sync.Map)
|
ipMap := value.(*sync.Map)
|
||||||
ipMap.Range(func(key, value interface{}) bool {
|
ipMap.Range(func(key, value interface{}) bool {
|
||||||
ip := key.(string)
|
|
||||||
uid := value.(int)
|
uid := value.(int)
|
||||||
|
ip := key.(string)
|
||||||
onlineUser = append(onlineUser, api.OnlineUser{UID: uid, IP: ip})
|
onlineUser = append(onlineUser, api.OnlineUser{UID: uid, IP: ip})
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
email := key.(string)
|
|
||||||
inboundInfo.UserOnlineIP.Delete(email) // Reset online device
|
inboundInfo.UserOnlineIP.Delete(email) // Reset online device
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("no such inbound in limiter: %s", tag)
|
return nil, fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &onlineUser, nil
|
return &onlineUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *ratelimit.Bucket, SpeedLimit bool, Reject bool) {
|
func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *rate.Limiter, SpeedLimit bool, Reject bool) {
|
||||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||||
|
var (
|
||||||
|
userLimit uint64 = 0
|
||||||
|
deviceLimit, uid int
|
||||||
|
)
|
||||||
|
|
||||||
inboundInfo := value.(*InboundInfo)
|
inboundInfo := value.(*InboundInfo)
|
||||||
nodeLimit := inboundInfo.NodeSpeedLimit
|
nodeLimit := inboundInfo.NodeSpeedLimit
|
||||||
var userLimit uint64 = 0
|
|
||||||
var deviceLimit int = 0
|
|
||||||
var uid int = 0
|
|
||||||
if v, ok := inboundInfo.UserInfo.Load(email); ok {
|
if v, ok := inboundInfo.UserInfo.Load(email); ok {
|
||||||
u := v.(UserInfo)
|
u := v.(UserInfo)
|
||||||
uid = u.UID
|
uid = u.UID
|
||||||
userLimit = u.SpeedLimit
|
userLimit = u.SpeedLimit
|
||||||
deviceLimit = u.DeviceLimit
|
deviceLimit = u.DeviceLimit
|
||||||
}
|
}
|
||||||
// Report online device
|
|
||||||
|
// Local device limit
|
||||||
ipMap := new(sync.Map)
|
ipMap := new(sync.Map)
|
||||||
ipMap.Store(ip, uid)
|
ipMap.Store(ip, uid)
|
||||||
// If any device is online
|
// If any device is online
|
||||||
if v, ok := inboundInfo.UserOnlineIP.LoadOrStore(email, ipMap); ok {
|
if v, ok := inboundInfo.UserOnlineIP.LoadOrStore(email, ipMap); ok {
|
||||||
ipMap := v.(*sync.Map)
|
ipMap := v.(*sync.Map)
|
||||||
// If this ip is a new device
|
// If this is a new ip
|
||||||
if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
|
if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
|
||||||
counter := 0
|
counter := 0
|
||||||
ipMap.Range(func(key, value interface{}) bool {
|
ipMap.Range(func(key, value interface{}) bool {
|
||||||
@@ -140,11 +194,20 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
limit := determineRate(nodeLimit, userLimit) // If need the Speed limit
|
|
||||||
|
// GlobalLimit
|
||||||
|
if inboundInfo.GlobalLimit.config != nil && inboundInfo.GlobalLimit.config.Enable {
|
||||||
|
if reject := globalLimit(inboundInfo, email, uid, ip, deviceLimit); reject {
|
||||||
|
return nil, false, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speed limit
|
||||||
|
limit := determineRate(nodeLimit, userLimit) // Determine the speed limit rate
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
limiter := ratelimit.NewBucketWithQuantum(time.Duration(int64(time.Second)), int64(limit), int64(limit)) // Byte/s
|
limiter := rate.NewLimiter(rate.Limit(limit), int(limit)) // Byte/s
|
||||||
if v, ok := inboundInfo.BucketHub.LoadOrStore(email, limiter); ok {
|
if v, ok := inboundInfo.BucketHub.LoadOrStore(email, limiter); ok {
|
||||||
bucket := v.(*ratelimit.Bucket)
|
bucket := v.(*rate.Limiter)
|
||||||
return bucket, true, false
|
return bucket, true, false
|
||||||
} else {
|
} else {
|
||||||
return limiter, true, false
|
return limiter, true, false
|
||||||
@@ -158,6 +221,51 @@ func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global device limit
|
||||||
|
func globalLimit(inboundInfo *InboundInfo, email string, uid int, ip string, deviceLimit int) bool {
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(inboundInfo.GlobalLimit.config.Timeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// reformat email for unique key
|
||||||
|
uniqueKey := strings.Replace(email, inboundInfo.Tag, strconv.Itoa(deviceLimit), 1)
|
||||||
|
|
||||||
|
v, err := inboundInfo.GlobalLimit.globalOnlineIP.Get(ctx, uniqueKey, new(map[string]int))
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*store.NotFound); ok {
|
||||||
|
// If the email is a new device
|
||||||
|
go pushIP(inboundInfo, uniqueKey, &map[string]int{ip: uid})
|
||||||
|
} else {
|
||||||
|
newError("cache service").Base(err).AtError().WriteToLog()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ipMap := v.(*map[string]int)
|
||||||
|
// Reject device reach limit directly
|
||||||
|
if deviceLimit > 0 && len(*ipMap) > deviceLimit {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the ip is not in cache
|
||||||
|
if _, ok := (*ipMap)[ip]; !ok {
|
||||||
|
(*ipMap)[ip] = uid
|
||||||
|
go pushIP(inboundInfo, email, ipMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// push the ip to cache
|
||||||
|
func pushIP(inboundInfo *InboundInfo, email string, ipMap *map[string]int) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(inboundInfo.GlobalLimit.config.Timeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := inboundInfo.GlobalLimit.globalOnlineIP.Set(ctx, email, ipMap); err != nil {
|
||||||
|
newError("cache service").Base(err).AtError().WriteToLog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// determineRate returns the minimum non-zero rate
|
// determineRate returns the minimum non-zero rate
|
||||||
func determineRate(nodeLimit, userLimit uint64) (limit uint64) {
|
func determineRate(nodeLimit, userLimit uint64) (limit uint64) {
|
||||||
if nodeLimit == 0 || userLimit == 0 {
|
if nodeLimit == 0 || userLimit == 0 {
|
||||||
|
10
common/limiter/model.go
Normal file
10
common/limiter/model.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
type GlobalDeviceLimitConfig struct {
|
||||||
|
Enable bool `mapstructure:"Enable"`
|
||||||
|
RedisAddr string `mapstructure:"RedisAddr"` // host:port
|
||||||
|
RedisPassword string `mapstructure:"RedisPassword"`
|
||||||
|
RedisDB int `mapstructure:"RedisDB"`
|
||||||
|
Timeout int `mapstructure:"Timeout"`
|
||||||
|
Expiry int `mapstructure:"Expiry"` // second
|
||||||
|
}
|
@@ -1,20 +1,21 @@
|
|||||||
package limiter
|
package limiter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/juju/ratelimit"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Writer struct {
|
type Writer struct {
|
||||||
writer buf.Writer
|
writer buf.Writer
|
||||||
limiter *ratelimit.Bucket
|
limiter *rate.Limiter
|
||||||
w io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Limiter) RateWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer {
|
func (l *Limiter) RateWriter(writer buf.Writer, limiter *rate.Limiter) buf.Writer {
|
||||||
return &Writer{
|
return &Writer{
|
||||||
writer: writer,
|
writer: writer,
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
@@ -26,6 +27,7 @@ func (w *Writer) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||||
w.limiter.Wait(int64(mb.Len()))
|
ctx := context.Background()
|
||||||
|
w.limiter.WaitN(ctx, int(mb.Len()))
|
||||||
return w.writer.WriteMultiBuffer(mb)
|
return w.writer.WriteMultiBuffer(mb)
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
@@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
@@ -6,18 +6,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"log"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
"github.com/urfave/cli"
|
"golang.org/x/crypto/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -59,27 +57,24 @@ const (
|
|||||||
// │ │ └── CA server ("server" option)
|
// │ │ └── CA server ("server" option)
|
||||||
// │ └── root accounts directory
|
// │ └── root accounts directory
|
||||||
// └── "path" option
|
// └── "path" option
|
||||||
//
|
|
||||||
type AccountsStorage struct {
|
type AccountsStorage struct {
|
||||||
userID string
|
userID string
|
||||||
rootPath string
|
rootPath string
|
||||||
rootUserPath string
|
rootUserPath string
|
||||||
keysPath string
|
keysPath string
|
||||||
accountFilePath string
|
accountFilePath string
|
||||||
ctx *cli.Context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccountsStorage Creates a new AccountsStorage.
|
// NewAccountsStorage Creates a new AccountsStorage.
|
||||||
func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
func NewAccountsStorage(l *LegoCMD) *AccountsStorage {
|
||||||
// TODO: move to account struct? Currently MUST pass email.
|
email := l.C.Email
|
||||||
email := getEmail(ctx)
|
|
||||||
|
|
||||||
serverURL, err := url.Parse(ctx.GlobalString("server"))
|
serverURL, err := url.Parse(acme.LetsEncryptURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootPath := filepath.Join(ctx.GlobalString("path"), baseAccountsRootFolderName)
|
rootPath := filepath.Join(l.path, baseAccountsRootFolderName)
|
||||||
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
|
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
|
||||||
accountsPath := filepath.Join(rootPath, serverPath)
|
accountsPath := filepath.Join(rootPath, serverPath)
|
||||||
rootUserPath := filepath.Join(accountsPath, email)
|
rootUserPath := filepath.Join(accountsPath, email)
|
||||||
@@ -90,7 +85,6 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
|||||||
rootUserPath: rootUserPath,
|
rootUserPath: rootUserPath,
|
||||||
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
|
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
|
||||||
accountFilePath: filepath.Join(rootUserPath, accountFileName),
|
accountFilePath: filepath.Join(rootUserPath, accountFileName),
|
||||||
ctx: ctx,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,11 +116,11 @@ func (s *AccountsStorage) Save(account *Account) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(s.accountFilePath, jsonBytes, filePerm)
|
return os.WriteFile(s.accountFilePath, jsonBytes, filePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
||||||
fileBytes, err := ioutil.ReadFile(s.accountFilePath)
|
fileBytes, err := os.ReadFile(s.accountFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Could not load file for account %s: %v", s.userID, err)
|
log.Panicf("Could not load file for account %s: %v", s.userID, err)
|
||||||
}
|
}
|
||||||
@@ -140,7 +134,7 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
|||||||
account.key = privateKey
|
account.key = privateKey
|
||||||
|
|
||||||
if account.Registration == nil || account.Registration.Body.Status == "" {
|
if account.Registration == nil || account.Registration.Body.Status == "" {
|
||||||
reg, err := tryRecoverRegistration(s.ctx, privateKey)
|
reg, err := tryRecoverRegistration(privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Could not load account for %s. Registration is nil: %#v", s.userID, err)
|
log.Panicf("Could not load account for %s. Registration is nil: %#v", s.userID, err)
|
||||||
}
|
}
|
||||||
@@ -207,7 +201,7 @@ func generatePrivateKey(file string, keyType certcrypto.KeyType) (crypto.Private
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
||||||
keyBytes, err := ioutil.ReadFile(file)
|
keyBytes, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -224,11 +218,11 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
|||||||
return nil, errors.New("unknown private key type")
|
return nil, errors.New("unknown private key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) {
|
func tryRecoverRegistration(privateKey crypto.PrivateKey) (*registration.Resource, error) {
|
||||||
// couldn't load account but got a key. Try to look the account up.
|
// couldn't load account but got a key. Try to look the account up.
|
||||||
config := lego.NewConfig(&Account{key: privateKey})
|
config := lego.NewConfig(&Account{key: privateKey})
|
||||||
config.CADirURL = ctx.GlobalString("server")
|
config.CADirURL = acme.LetsEncryptURL
|
||||||
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
|
config.UserAgent = "lego-cli/dev"
|
||||||
|
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
@@ -1,29 +1,24 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/urfave/cli"
|
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
baseCertificatesFolderName = "certificates"
|
baseCertificatesFolderName = "certificates"
|
||||||
baseArchivesFolderName = "archives"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertificatesStorage a certificates storage.
|
// CertificatesStorage a certificates' storage.
|
||||||
//
|
//
|
||||||
// rootPath:
|
// rootPath:
|
||||||
//
|
//
|
||||||
@@ -36,21 +31,15 @@ const (
|
|||||||
// ./.lego/archives/
|
// ./.lego/archives/
|
||||||
// │ └── archived certificates directory
|
// │ └── archived certificates directory
|
||||||
// └── "path" option
|
// └── "path" option
|
||||||
//
|
|
||||||
type CertificatesStorage struct {
|
type CertificatesStorage struct {
|
||||||
rootPath string
|
rootPath string
|
||||||
archivePath string
|
|
||||||
pem bool
|
pem bool
|
||||||
filename string // Deprecated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificatesStorage create a new certificates storage.
|
// NewCertificatesStorage create a new certificates storage.
|
||||||
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
|
func NewCertificatesStorage(path string) *CertificatesStorage {
|
||||||
return &CertificatesStorage{
|
return &CertificatesStorage{
|
||||||
rootPath: filepath.Join(ctx.GlobalString("path"), baseCertificatesFolderName),
|
rootPath: filepath.Join(path, baseCertificatesFolderName),
|
||||||
archivePath: filepath.Join(ctx.GlobalString("path"), baseArchivesFolderName),
|
|
||||||
pem: ctx.GlobalBool("pem"),
|
|
||||||
filename: ctx.GlobalString("filename"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,13 +50,6 @@ func (s *CertificatesStorage) CreateRootFolder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) CreateArchiveFolder() {
|
|
||||||
err := createNonExistingFolder(s.archivePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Could not check/create path: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CertificatesStorage) GetRootPath() string {
|
func (s *CertificatesStorage) GetRootPath() string {
|
||||||
return s.rootPath
|
return s.rootPath
|
||||||
}
|
}
|
||||||
@@ -144,7 +126,7 @@ func (s *CertificatesStorage) ExistsFile(domain, extension string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) ReadFile(domain, extension string) ([]byte, error) {
|
func (s *CertificatesStorage) ReadFile(domain, extension string) ([]byte, error) {
|
||||||
return ioutil.ReadFile(s.GetFileName(domain, extension))
|
return os.ReadFile(s.GetFileName(domain, extension))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) GetFileName(domain, extension string) string {
|
func (s *CertificatesStorage) GetFileName(domain, extension string) string {
|
||||||
@@ -163,36 +145,11 @@ func (s *CertificatesStorage) ReadCertificate(domain, extension string) ([]*x509
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
|
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
|
||||||
var baseFileName string
|
var baseFileName = sanitizedDomain(domain)
|
||||||
if s.filename != "" {
|
|
||||||
baseFileName = s.filename
|
|
||||||
} else {
|
|
||||||
baseFileName = sanitizedDomain(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := filepath.Join(s.rootPath, baseFileName+extension)
|
filePath := filepath.Join(s.rootPath, baseFileName+extension)
|
||||||
|
|
||||||
return ioutil.WriteFile(filePath, data, filePerm)
|
return os.WriteFile(filePath, data, filePerm)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CertificatesStorage) MoveToArchive(domain string) error {
|
|
||||||
matches, err := filepath.Glob(filepath.Join(s.rootPath, sanitizedDomain(domain)+".*"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, oldFile := range matches {
|
|
||||||
date := strconv.FormatInt(time.Now().Unix(), 10)
|
|
||||||
filename := date + "." + filepath.Base(oldFile)
|
|
||||||
newFile := filepath.Join(s.archivePath, filename)
|
|
||||||
|
|
||||||
err = os.Rename(oldFile, newFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
|
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
|
87
common/mylego/lego_test.go
Normal file
87
common/mylego/lego_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package mylego_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLegoClient(t *testing.T) {
|
||||||
|
_, err := mylego.New(&mylego.CertConfig{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegoDNSCert(t *testing.T) {
|
||||||
|
lego, err := mylego.New(&mylego.CertConfig{
|
||||||
|
CertDomain: "node1.test.com",
|
||||||
|
Provider: "alidns",
|
||||||
|
Email: "test@gmail.com",
|
||||||
|
DNSEnv: map[string]string{
|
||||||
|
"ALICLOUD_ACCESS_KEY": "aaa",
|
||||||
|
"ALICLOUD_SECRET_KEY": "bbb",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPath, keyPath, err := lego.DNSCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegoHTTPCert(t *testing.T) {
|
||||||
|
lego, err := mylego.New(&mylego.CertConfig{
|
||||||
|
CertMode: "http",
|
||||||
|
CertDomain: "node1.test.com",
|
||||||
|
Email: "test@gmail.com",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPath, keyPath, err := lego.HTTPCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegoRenewCert(t *testing.T) {
|
||||||
|
lego, err := mylego.New(&mylego.CertConfig{
|
||||||
|
CertDomain: "node1.test.com",
|
||||||
|
Email: "test@gmail.com",
|
||||||
|
Provider: "alidns",
|
||||||
|
DNSEnv: map[string]string{
|
||||||
|
"ALICLOUD_ACCESS_KEY": "aaa",
|
||||||
|
"ALICLOUD_SECRET_KEY": "bbb",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
lego.C.CertMode = "http"
|
||||||
|
certPath, keyPath, ok, err := lego.RenewCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
t.Log(ok)
|
||||||
|
|
||||||
|
lego.C.CertMode = "dns"
|
||||||
|
certPath, keyPath, ok, err = lego.RenewCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
t.Log(ok)
|
||||||
|
}
|
17
common/mylego/model.go
Normal file
17
common/mylego/model.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
type CertConfig struct {
|
||||||
|
CertMode string `mapstructure:"CertMode"` // none, file, http, dns
|
||||||
|
CertDomain string `mapstructure:"CertDomain"`
|
||||||
|
CertFile string `mapstructure:"CertFile"`
|
||||||
|
KeyFile string `mapstructure:"KeyFile"`
|
||||||
|
Provider string `mapstructure:"Provider"` // alidns, cloudflare, gandi, godaddy....
|
||||||
|
Email string `mapstructure:"Email"`
|
||||||
|
DNSEnv map[string]string `mapstructure:"DNSEnv"`
|
||||||
|
RejectUnknownSni bool `mapstructure:"RejectUnknownSni"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegoCMD struct {
|
||||||
|
C *CertConfig
|
||||||
|
path string
|
||||||
|
}
|
163
common/mylego/mylego.go
Normal file
163
common/mylego/mylego.go
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultPath string
|
||||||
|
|
||||||
|
func New(certConf *CertConfig) (*LegoCMD, error) {
|
||||||
|
// Set default path to configPath/cert
|
||||||
|
var p = ""
|
||||||
|
configPath := os.Getenv("XRAY_LOCATION_CONFIG")
|
||||||
|
if configPath != "" {
|
||||||
|
p = configPath
|
||||||
|
} else if cwd, err := os.Getwd(); err == nil {
|
||||||
|
p = cwd
|
||||||
|
} else {
|
||||||
|
p = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPath = filepath.Join(p, "cert")
|
||||||
|
lego := &LegoCMD{
|
||||||
|
C: certConf,
|
||||||
|
path: defaultPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
return lego, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LegoCMD) getPath() string {
|
||||||
|
return l.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LegoCMD) getCertConfig() *CertConfig {
|
||||||
|
return l.C
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSCert cert a domain using DNS API
|
||||||
|
func (l *LegoCMD) DNSCert() (CertPath string, KeyPath string, err error) {
|
||||||
|
defer func() (string, string, error) {
|
||||||
|
// Handle any error
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
switch x := r.(type) {
|
||||||
|
case string:
|
||||||
|
err = errors.New(x)
|
||||||
|
case error:
|
||||||
|
err = x
|
||||||
|
default:
|
||||||
|
err = errors.New("unknown panic")
|
||||||
|
}
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set Env for DNS configuration
|
||||||
|
for key, value := range l.C.DNSEnv {
|
||||||
|
os.Setenv(strings.ToUpper(key), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check if the certificate exists
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err == nil {
|
||||||
|
return CertPath, KeyPath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Run()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPCert cert a domain using http methods
|
||||||
|
func (l *LegoCMD) HTTPCert() (CertPath string, KeyPath string, err error) {
|
||||||
|
defer func() (string, string, error) {
|
||||||
|
// Handle any error
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
switch x := r.(type) {
|
||||||
|
case string:
|
||||||
|
err = errors.New(x)
|
||||||
|
case error:
|
||||||
|
err = x
|
||||||
|
default:
|
||||||
|
err = errors.New("unknown panic")
|
||||||
|
}
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// First check if the certificate exists
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err == nil {
|
||||||
|
return CertPath, KeyPath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Run()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewCert renew a domain cert
|
||||||
|
func (l *LegoCMD) RenewCert() (CertPath string, KeyPath string, ok bool, err error) {
|
||||||
|
defer func() (string, string, bool, error) {
|
||||||
|
// Handle any error
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
switch x := r.(type) {
|
||||||
|
case string:
|
||||||
|
err = errors.New(x)
|
||||||
|
case error:
|
||||||
|
err = x
|
||||||
|
default:
|
||||||
|
err = errors.New("unknown panic")
|
||||||
|
}
|
||||||
|
return "", "", false, err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, ok, nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
ok, err = l.Renew()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||||
|
return "", "", fmt.Errorf("cert key failed: %s", domain)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(certPath); os.IsNotExist(err) {
|
||||||
|
return "", "", fmt.Errorf("cert cert failed: %s", domain)
|
||||||
|
}
|
||||||
|
absKeyPath, _ := filepath.Abs(keyPath)
|
||||||
|
absCertPath, _ := filepath.Abs(certPath)
|
||||||
|
return absCertPath, absKeyPath, nil
|
||||||
|
}
|
77
common/mylego/renew.go
Normal file
77
common/mylego/renew.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *LegoCMD) Renew() (bool, error) {
|
||||||
|
account, client := setup(NewAccountsStorage(l))
|
||||||
|
setupChallenges(l, client)
|
||||||
|
|
||||||
|
if account.Registration == nil {
|
||||||
|
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
return renewForDomains(l.C.CertDomain, client, NewCertificatesStorage(l.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func renewForDomains(domain string, client *lego.Client, certsStorage *CertificatesStorage) (bool, error) {
|
||||||
|
// load the cert resource from files.
|
||||||
|
// We store the certificate, private key and metadata in different files
|
||||||
|
// as web servers would not be able to work with a combined file.
|
||||||
|
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := certificates[0]
|
||||||
|
|
||||||
|
if !needRenewal(cert, domain, 30) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just meant to be informal for the user.
|
||||||
|
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||||
|
log.Printf("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
||||||
|
|
||||||
|
certDomains := certcrypto.ExtractDomains(cert)
|
||||||
|
|
||||||
|
var privateKey crypto.PrivateKey
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: certDomains,
|
||||||
|
Bundle: true,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
}
|
||||||
|
certRes, err := client.Certificate.Obtain(request)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certsStorage.SaveResource(certRes)
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
|
||||||
|
if x509Cert.IsCA {
|
||||||
|
log.Panicf("[%s] Certificate bundle starts with a CA certificate", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if days >= 0 {
|
||||||
|
notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0)
|
||||||
|
if notAfter > days {
|
||||||
|
log.Printf("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.",
|
||||||
|
domain, notAfter, days)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@@ -116,3 +116,19 @@ func Test_needRenewal(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func merge(prevDomains, nextDomains []string) []string {
|
||||||
|
for _, next := range nextDomains {
|
||||||
|
var found bool
|
||||||
|
for _, prev := range prevDomains {
|
||||||
|
if prev == next {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
prevDomains = append(prevDomains, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prevDomains
|
||||||
|
}
|
68
common/mylego/run.go
Normal file
68
common/mylego/run.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootPathWarningMessage = `!!!! HEADS UP !!!!
|
||||||
|
|
||||||
|
Your account credentials have been saved in your Let's Encrypt
|
||||||
|
configuration directory at "%s".
|
||||||
|
|
||||||
|
You should make a secure backup of this folder now. This
|
||||||
|
configuration directory will also contain certificates and
|
||||||
|
private keys obtained from Let's Encrypt so making regular
|
||||||
|
backups of this folder is ideal.
|
||||||
|
`
|
||||||
|
|
||||||
|
func (l *LegoCMD) Run() error {
|
||||||
|
accountsStorage := NewAccountsStorage(l)
|
||||||
|
|
||||||
|
account, client := setup(accountsStorage)
|
||||||
|
setupChallenges(l, client)
|
||||||
|
|
||||||
|
if account.Registration == nil {
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Could not complete registration\n\t%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Registration = reg
|
||||||
|
if err = accountsStorage.Save(account); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
certsStorage := NewCertificatesStorage(l.path)
|
||||||
|
certsStorage.CreateRootFolder()
|
||||||
|
|
||||||
|
cert, err := obtainCertificate([]string{l.C.CertDomain}, client)
|
||||||
|
if err != nil {
|
||||||
|
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
|
||||||
|
// Due to us not returning partial certificate we can just exit here instead of at the end.
|
||||||
|
log.Panicf("Could not obtain certificates:\n\t%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certsStorage.SaveResource(cert)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func obtainCertificate(domains []string, client *lego.Client) (*certificate.Resource, error) {
|
||||||
|
if len(domains) > 0 {
|
||||||
|
// obtain a certificate, generating a new private key
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: domains,
|
||||||
|
Bundle: true,
|
||||||
|
}
|
||||||
|
return client.Certificate.Obtain(request)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not a valid domain")
|
||||||
|
}
|
95
common/mylego/setup.go
Normal file
95
common/mylego/setup.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
const filePerm os.FileMode = 0o600
|
||||||
|
|
||||||
|
func setup(accountsStorage *AccountsStorage) (*Account, *lego.Client) {
|
||||||
|
keyType := certcrypto.EC256
|
||||||
|
privateKey := accountsStorage.GetPrivateKey(keyType)
|
||||||
|
|
||||||
|
var account *Account
|
||||||
|
if accountsStorage.ExistsAccountFilePath() {
|
||||||
|
account = accountsStorage.LoadAccount(privateKey)
|
||||||
|
} else {
|
||||||
|
account = &Account{Email: accountsStorage.GetUserID(), key: privateKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := newClient(account, keyType)
|
||||||
|
|
||||||
|
return account, client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(acc registration.User, keyType certcrypto.KeyType) *lego.Client {
|
||||||
|
config := lego.NewConfig(acc)
|
||||||
|
config.CADirURL = acme.LetsEncryptURL
|
||||||
|
|
||||||
|
config.Certificate = lego.CertificateConfig{
|
||||||
|
KeyType: keyType,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
config.UserAgent = "lego-cli/dev"
|
||||||
|
|
||||||
|
client, err := lego.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Could not create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNonExistingFolder(path string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return os.MkdirAll(path, 0o700)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupChallenges(l *LegoCMD, client *lego.Client) {
|
||||||
|
switch l.C.CertMode {
|
||||||
|
case "http":
|
||||||
|
err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
case "tls":
|
||||||
|
err := client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
case "dns":
|
||||||
|
setupDNS(l.C.Provider, client)
|
||||||
|
default:
|
||||||
|
log.Panic("No challenge selected. You must specify at least one challenge: `http`, `tls`, `dns`.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDNS(p string, client *lego.Client) {
|
||||||
|
provider, err := dns.NewDNSChallengeProviderByName(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Challenge.SetDNS01Provider(
|
||||||
|
provider,
|
||||||
|
dns01.CondOption(true, dns01.AddDNSTimeout(10*time.Second)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
@@ -2,8 +2,6 @@ package rule
|
|||||||
|
|
||||||
import "github.com/xtls/xray-core/common/errors"
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
type errPathObjHolder struct{}
|
|
||||||
|
|
||||||
func newError(values ...interface{}) *errors.Error {
|
func newError(values ...interface{}) *errors.Error {
|
||||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
return errors.New(values...)
|
||||||
}
|
}
|
||||||
|
@@ -8,23 +8,24 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
mapset "github.com/deckarep/golang-set"
|
mapset "github.com/deckarep/golang-set"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuleManager struct {
|
type Manager struct {
|
||||||
InboundRule *sync.Map // Key: Tag, Value: []api.DetectRule
|
InboundRule *sync.Map // Key: Tag, Value: []api.DetectRule
|
||||||
InboundDetectResult *sync.Map // key: Tag, Value: mapset.NewSet []api.DetectResult
|
InboundDetectResult *sync.Map // key: Tag, Value: mapset.NewSet []api.DetectResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *RuleManager {
|
func New() *Manager {
|
||||||
return &RuleManager{
|
return &Manager{
|
||||||
InboundRule: new(sync.Map),
|
InboundRule: new(sync.Map),
|
||||||
InboundDetectResult: new(sync.Map),
|
InboundDetectResult: new(sync.Map),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleManager) UpdateRule(tag string, newRuleList []api.DetectRule) error {
|
func (r *Manager) UpdateRule(tag string, newRuleList []api.DetectRule) error {
|
||||||
if value, ok := r.InboundRule.LoadOrStore(tag, newRuleList); ok {
|
if value, ok := r.InboundRule.LoadOrStore(tag, newRuleList); ok {
|
||||||
oldRuleList := value.([]api.DetectRule)
|
oldRuleList := value.([]api.DetectRule)
|
||||||
if !reflect.DeepEqual(oldRuleList, newRuleList) {
|
if !reflect.DeepEqual(oldRuleList, newRuleList) {
|
||||||
@@ -34,7 +35,7 @@ func (r *RuleManager) UpdateRule(tag string, newRuleList []api.DetectRule) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleManager) GetDetectResult(tag string) (*[]api.DetectResult, error) {
|
func (r *Manager) GetDetectResult(tag string) (*[]api.DetectResult, error) {
|
||||||
detectResult := make([]api.DetectResult, 0)
|
detectResult := make([]api.DetectResult, 0)
|
||||||
if value, ok := r.InboundDetectResult.LoadAndDelete(tag); ok {
|
if value, ok := r.InboundDetectResult.LoadAndDelete(tag); ok {
|
||||||
resultSet := value.(mapset.Set)
|
resultSet := value.(mapset.Set)
|
||||||
@@ -46,9 +47,9 @@ func (r *RuleManager) GetDetectResult(tag string) (*[]api.DetectResult, error) {
|
|||||||
return &detectResult, nil
|
return &detectResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuleManager) Detect(tag string, destination string, email string) (reject bool) {
|
func (r *Manager) Detect(tag string, destination string, email string) (reject bool) {
|
||||||
reject = false
|
reject = false
|
||||||
var hitRuleID int = -1
|
var hitRuleID = -1
|
||||||
// If we have some rule for this inbound
|
// If we have some rule for this inbound
|
||||||
if value, ok := r.InboundRule.Load(tag); ok {
|
if value, ok := r.InboundRule.Load(tag); ok {
|
||||||
ruleList := value.([]api.DetectRule)
|
ruleList := value.([]api.DetectRule)
|
||||||
|
@@ -3,39 +3,51 @@ package serverstatus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"github.com/shirou/gopsutil/disk"
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/v3/host"
|
||||||
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetSystemInfo get the system info of a given periodic
|
// GetSystemInfo get the system info of a given periodic
|
||||||
func GetSystemInfo() (Cpu float64, Mem float64, Disk float64, Uptime int, err error) {
|
func GetSystemInfo() (Cpu float64, Mem float64, Disk float64, Uptime uint64, err error) {
|
||||||
|
|
||||||
|
errorString := ""
|
||||||
|
|
||||||
upTime := time.Now()
|
|
||||||
cpuPercent, err := cpu.Percent(0, false)
|
cpuPercent, err := cpu.Percent(0, false)
|
||||||
// Check if cpuPercent is empty
|
// Check if cpuPercent is empty
|
||||||
if len(cpuPercent) > 0 {
|
if len(cpuPercent) > 0 && err == nil {
|
||||||
Cpu = cpuPercent[0]
|
Cpu = cpuPercent[0]
|
||||||
} else {
|
} else {
|
||||||
Cpu = 0
|
Cpu = 0
|
||||||
}
|
errorString += fmt.Sprintf("get cpu usage failed: %s ", err)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, 0, 0, fmt.Errorf("get cpu usage failed: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memUsage, err := mem.VirtualMemory()
|
memUsage, err := mem.VirtualMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, fmt.Errorf("get mem usage failed: %s", err)
|
errorString += fmt.Sprintf("get mem usage failed: %s ", err)
|
||||||
|
} else {
|
||||||
|
Mem = memUsage.UsedPercent
|
||||||
}
|
}
|
||||||
|
|
||||||
diskUsage, err := disk.Usage("/")
|
diskUsage, err := disk.Usage("/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, fmt.Errorf("et disk usage failed: %s", err)
|
errorString += fmt.Sprintf("get disk usage failed: %s ", err)
|
||||||
|
} else {
|
||||||
|
Disk = diskUsage.UsedPercent
|
||||||
}
|
}
|
||||||
|
|
||||||
Uptime = int(time.Since(upTime).Seconds())
|
uptime, err := host.Uptime()
|
||||||
return Cpu, memUsage.UsedPercent, diskUsage.UsedPercent, Uptime, nil
|
if err != nil {
|
||||||
|
errorString += fmt.Sprintf("get uptime failed: %s ", err)
|
||||||
|
} else {
|
||||||
|
Uptime = uptime
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorString != "" {
|
||||||
|
err = fmt.Errorf(errorString)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cpu, Mem, Disk, Uptime, err
|
||||||
}
|
}
|
||||||
|
195
go.mod
195
go.mod
@@ -1,33 +1,36 @@
|
|||||||
module github.com/XrayR-project/XrayR
|
module github.com/XrayR-project/XrayR
|
||||||
|
|
||||||
go 1.17
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bitly/go-simplejson v0.5.0
|
github.com/bitly/go-simplejson v0.5.0
|
||||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
|
||||||
github.com/deckarep/golang-set v1.8.0
|
github.com/deckarep/golang-set v1.8.0
|
||||||
github.com/fsnotify/fsnotify v1.5.4
|
github.com/eko/gocache/lib/v4 v4.1.2
|
||||||
github.com/go-acme/lego/v4 v4.6.0
|
github.com/eko/gocache/store/go_cache/v4 v4.1.2
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
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/go-resty/resty/v2 v2.7.0
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/imdario/mergo v0.3.13
|
||||||
github.com/imdario/mergo v0.3.12
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/juju/ratelimit v1.0.1
|
|
||||||
github.com/r3labs/diff/v2 v2.15.1
|
github.com/r3labs/diff/v2 v2.15.1
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/sagernet/sing v0.1.0
|
||||||
github.com/spf13/viper v1.11.0
|
github.com/sagernet/sing-shadowsocks v0.1.0
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/shirou/gopsutil/v3 v3.22.11
|
||||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
github.com/spf13/viper v1.14.0
|
||||||
github.com/urfave/cli v1.22.7
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/xtls/xray-core v1.5.5
|
github.com/xtls/xray-core v1.6.5
|
||||||
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861
|
golang.org/x/crypto v0.4.0
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
golang.org/x/net v0.4.0
|
||||||
google.golang.org/protobuf v1.28.0
|
golang.org/x/time v0.3.0
|
||||||
|
google.golang.org/protobuf v1.28.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.5.0 // indirect
|
cloud.google.com/go/compute v1.12.1 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go v62.0.0+incompatible // indirect
|
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
||||||
@@ -39,132 +42,150 @@ require (
|
|||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1 // indirect
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1505 // 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/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.43.12 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1 // indirect
|
github.com/aws/aws-sdk-go v1.39.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cheekybits/genny v1.0.0 // indirect
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||||
github.com/cloudflare/cloudflare-go v0.34.0 // 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/cpu/goacmedns v0.1.1 // indirect
|
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
github.com/dnsimple/dnsimple-go v0.71.1 // indirect
|
github.com/dnsimple/dnsimple-go v0.71.1 // indirect
|
||||||
github.com/exoscale/egoscale v1.19.0 // indirect
|
github.com/exoscale/egoscale v0.90.0 // indirect
|
||||||
github.com/fatih/structs v1.1.0 // indirect
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
||||||
github.com/go-errors/errors v1.4.2 // 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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.3.0 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.3.0 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||||
github.com/gophercloud/gophercloud v0.24.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
|
||||||
github.com/gophercloud/utils v0.0.0-20220209210848-d0ab9f2a8909 // 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/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
github.com/jarcoal/httpmock v1.1.0 // indirect
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||||
github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b // indirect
|
github.com/klauspost/compress v1.15.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.1 // indirect
|
||||||
|
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b // indirect
|
||||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||||
github.com/linode/linodego v1.3.0 // indirect
|
github.com/linode/linodego v1.9.1 // indirect
|
||||||
github.com/liquidweb/go-lwApi v0.0.5 // indirect
|
github.com/liquidweb/go-lwApi v0.0.5 // indirect
|
||||||
github.com/liquidweb/liquidweb-cli v0.6.10 // indirect
|
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||||
github.com/liquidweb/liquidweb-go v1.6.3 // indirect
|
github.com/liquidweb/liquidweb-go v1.6.3 // indirect
|
||||||
github.com/lucas-clemente/quic-go v0.27.0 // indirect
|
github.com/lucas-clemente/quic-go v0.31.0 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/magiconair/properties v1.8.6 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
|
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/miekg/dns v1.1.48 // indirect
|
github.com/miekg/dns v1.1.50 // indirect
|
||||||
|
github.com/mimuret/golang-iij-dpf v0.7.1 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||||
github.com/nrdcg/auroradns v1.0.1 // indirect
|
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||||
github.com/nrdcg/desec v0.6.0 // indirect
|
github.com/nrdcg/desec v0.6.0 // indirect
|
||||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||||
github.com/nrdcg/goinwx v0.8.1 // indirect
|
github.com/nrdcg/goinwx v0.8.1 // indirect
|
||||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
github.com/nrdcg/porkbun v0.1.1 // indirect
|
github.com/nrdcg/porkbun v0.1.1 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/onsi/ginkgo/v2 v2.5.1 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
|
||||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||||
github.com/ovh/go-ovh v1.1.0 // indirect
|
github.com/ovh/go-ovh v1.1.0 // indirect
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/pquerna/otp v1.3.0 // indirect
|
github.com/pquerna/otp v1.3.0 // indirect
|
||||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 // indirect
|
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.0.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/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
github.com/sacloud/api-client-go v0.2.1 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/sacloud/go-http v0.1.2 // indirect
|
||||||
github.com/sacloud/libsacloud v1.36.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/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
github.com/softlayer/softlayer-go v1.0.4 // indirect
|
github.com/softlayer/softlayer-go v1.0.6 // indirect
|
||||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
github.com/spf13/afero v1.8.2 // indirect
|
github.com/spf13/afero v1.9.2 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/objx v0.3.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.4.1 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.360 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.360 // indirect
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||||
github.com/tklauser/numcpus v0.4.0 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/transip/gotransip/v6 v6.14.0 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
|
github.com/transip/gotransip/v6 v6.17.0 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||||
github.com/vultr/govultr/v2 v2.14.1 // indirect
|
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // 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/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.opencensus.io v0.23.0 // indirect
|
||||||
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
|
go.starlark.net v0.0.0-20221028183056-acb66ad56dd2 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/ratelimit v0.2.0 // indirect
|
go.uber.org/ratelimit v0.2.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.7.0 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
golang.org/x/sys v0.3.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.5.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
golang.org/x/tools v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.1.10 // indirect
|
google.golang.org/api v0.102.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/api v0.74.0 // indirect
|
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 // indirect
|
google.golang.org/grpc v1.51.0 // indirect
|
||||||
google.golang.org/grpc v1.46.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
gopkg.in/ns1/ns1-go.v2 v2.6.5 // indirect
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.6.3 // indirect
|
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // 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
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/linode/linodego => github.com/linode/linodego v0.31.1
|
|
||||||
|
|
||||||
replace github.com/exoscale/egoscale => github.com/exoscale/egoscale v0.67.0
|
|
||||||
|
@@ -6,7 +6,7 @@ DnsConfigPath: # /etc/XrayR/dns.json # Path to dns config, check https://xtls.gi
|
|||||||
RouteConfigPath: # /etc/XrayR/route.json # Path to route config, check https://xtls.github.io/config/routing.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
|
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
|
OutboundConfigPath: # /etc/XrayR/custom_outbound.json # Path to custom outbound config, check https://xtls.github.io/config/outbound.html for help
|
||||||
ConnetionConfig:
|
ConnectionConfig:
|
||||||
Handshake: 4 # Handshake time limit, Second
|
Handshake: 4 # Handshake time limit, Second
|
||||||
ConnIdle: 30 # Connection idle time limit, Second
|
ConnIdle: 30 # Connection idle time limit, Second
|
||||||
UplinkOnly: 2 # Time limit when the connection downstream is closed, Second
|
UplinkOnly: 2 # Time limit when the connection downstream is closed, Second
|
||||||
@@ -14,7 +14,7 @@ ConnetionConfig:
|
|||||||
BufferSize: 64 # The internal cache size of each connection, kB
|
BufferSize: 64 # The internal cache size of each connection, kB
|
||||||
Nodes:
|
Nodes:
|
||||||
-
|
-
|
||||||
PanelType: "SSpanel" # Panel type: SSpanel, V2board, PMpanel, , Proxypanel
|
PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
||||||
ApiConfig:
|
ApiConfig:
|
||||||
ApiHost: "http://127.0.0.1:667"
|
ApiHost: "http://127.0.0.1:667"
|
||||||
ApiKey: "123"
|
ApiKey: "123"
|
||||||
@@ -33,6 +33,18 @@ Nodes:
|
|||||||
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
||||||
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
||||||
EnableProxyProtocol: false # Only works for WebSocket and TCP
|
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
|
EnableFallback: false # Only support for Trojan and Vless
|
||||||
FallBackConfigs: # Support multiple fallbacks
|
FallBackConfigs: # Support multiple fallbacks
|
||||||
-
|
-
|
||||||
@@ -42,7 +54,7 @@ Nodes:
|
|||||||
Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
|
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
|
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable
|
||||||
CertConfig:
|
CertConfig:
|
||||||
CertMode: dns # Option about how to get certificate: none, file, http, dns. Choose "none" will forcedly disable the tls config.
|
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
|
CertDomain: "node1.test.com" # Domain to cert
|
||||||
CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
||||||
KeyFile: /etc/XrayR/cert/node1.test.com.key
|
KeyFile: /etc/XrayR/cert/node1.test.com.key
|
||||||
@@ -52,7 +64,7 @@ Nodes:
|
|||||||
ALICLOUD_ACCESS_KEY: aaa
|
ALICLOUD_ACCESS_KEY: aaa
|
||||||
ALICLOUD_SECRET_KEY: bbb
|
ALICLOUD_SECRET_KEY: bbb
|
||||||
# -
|
# -
|
||||||
# PanelType: "V2board" # Panel type: SSpanel, V2board
|
# PanelType: "NewV2board" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks
|
||||||
# ApiConfig:
|
# ApiConfig:
|
||||||
# ApiHost: "http://127.0.0.1:668"
|
# ApiHost: "http://127.0.0.1:668"
|
||||||
# ApiKey: "123"
|
# ApiKey: "123"
|
||||||
@@ -77,4 +89,3 @@ Nodes:
|
|||||||
# DNSEnv: # DNS ENV option used by DNS provider
|
# DNSEnv: # DNS ENV option used by DNS provider
|
||||||
# ALICLOUD_ACCESS_KEY: aaa
|
# ALICLOUD_ACCESS_KEY: aaa
|
||||||
# ALICLOUD_SECRET_KEY: bbb
|
# ALICLOUD_SECRET_KEY: bbb
|
||||||
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"tag": "IPv4_out",
|
"tag": "IPv4_out",
|
||||||
"protocol": "freedom"
|
"protocol": "freedom",
|
||||||
|
"settings": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "IPv6_out",
|
"tag": "IPv6_out",
|
||||||
@@ -10,6 +11,18 @@
|
|||||||
"domainStrategy": "UseIPv6"
|
"domainStrategy": "UseIPv6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"tag": "socks5-warp",
|
||||||
|
"protocol": "socks",
|
||||||
|
"settings": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 1080
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"protocol": "blackhole",
|
"protocol": "blackhole",
|
||||||
"tag": "block"
|
"tag": "block"
|
||||||
|
@@ -3,11 +3,13 @@ package all
|
|||||||
import (
|
import (
|
||||||
// The following are necessary as they register handlers in their init functions.
|
// The following are necessary as they register handlers in their init functions.
|
||||||
|
|
||||||
// Required features. Can't remove unless there is replacements.
|
|
||||||
// _ "github.com/xtls/xray-core/app/dispatcher"
|
|
||||||
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
||||||
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||||
|
|
||||||
|
// Required features. Can't remove unless there is replacements.
|
||||||
|
// _ "github.com/xtls/xray-core/app/dispatcher"
|
||||||
|
_ "github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||||
|
|
||||||
// Default commander and all its services. This is an optional feature.
|
// Default commander and all its services. This is an optional feature.
|
||||||
_ "github.com/xtls/xray-core/app/commander"
|
_ "github.com/xtls/xray-core/app/commander"
|
||||||
_ "github.com/xtls/xray-core/app/log/command"
|
_ "github.com/xtls/xray-core/app/log/command"
|
||||||
@@ -17,6 +19,7 @@ import (
|
|||||||
// Other optional features.
|
// Other optional features.
|
||||||
_ "github.com/xtls/xray-core/app/dns"
|
_ "github.com/xtls/xray-core/app/dns"
|
||||||
_ "github.com/xtls/xray-core/app/log"
|
_ "github.com/xtls/xray-core/app/log"
|
||||||
|
_ "github.com/xtls/xray-core/app/metrics"
|
||||||
_ "github.com/xtls/xray-core/app/policy"
|
_ "github.com/xtls/xray-core/app/policy"
|
||||||
_ "github.com/xtls/xray-core/app/reverse"
|
_ "github.com/xtls/xray-core/app/reverse"
|
||||||
_ "github.com/xtls/xray-core/app/router"
|
_ "github.com/xtls/xray-core/app/router"
|
||||||
|
BIN
main/geoip.dat
BIN
main/geoip.dat
Binary file not shown.
46817
main/geosite.dat
46817
main/geosite.dat
File diff suppressed because one or more lines are too long
17
main/main.go
17
main/main.go
@@ -12,9 +12,10 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/panel"
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/panel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -23,7 +24,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "0.8.0"
|
version = "0.8.7"
|
||||||
codename = "XrayR"
|
codename = "XrayR"
|
||||||
intro = "A Xray backend that supports many panels"
|
intro = "A Xray backend that supports many panels"
|
||||||
)
|
)
|
||||||
@@ -56,7 +57,7 @@ func getConfig() *viper.Viper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := config.ReadInConfig(); err != nil {
|
if err := config.ReadInConfig(); err != nil {
|
||||||
log.Panicf("Fatal error config file: %s \n", err)
|
log.Panicf("Config file error: %s \n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.WatchConfig() // Watch the config
|
config.WatchConfig() // Watch the config
|
||||||
@@ -73,7 +74,9 @@ func main() {
|
|||||||
|
|
||||||
config := getConfig()
|
config := getConfig()
|
||||||
panelConfig := &panel.Config{}
|
panelConfig := &panel.Config{}
|
||||||
config.Unmarshal(panelConfig)
|
if err := config.Unmarshal(panelConfig); err != nil {
|
||||||
|
log.Panicf("Parse config file %v failed: %s \n", configFile, err)
|
||||||
|
}
|
||||||
p := panel.New(panelConfig)
|
p := panel.New(panelConfig)
|
||||||
lastTime := time.Now()
|
lastTime := time.Now()
|
||||||
config.OnConfigChange(func(e fsnotify.Event) {
|
config.OnConfigChange(func(e fsnotify.Event) {
|
||||||
@@ -84,7 +87,9 @@ func main() {
|
|||||||
p.Close()
|
p.Close()
|
||||||
// Delete old instance and trigger GC
|
// Delete old instance and trigger GC
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
config.Unmarshal(panelConfig)
|
if err := config.Unmarshal(panelConfig); err != nil {
|
||||||
|
log.Panicf("Parse config file %v failed: %s \n", configFile, err)
|
||||||
|
}
|
||||||
p.Start()
|
p.Start()
|
||||||
lastTime = time.Now()
|
lastTime = time.Now()
|
||||||
}
|
}
|
||||||
@@ -92,7 +97,7 @@ func main() {
|
|||||||
p.Start()
|
p.Start()
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
//Explicitly triggering GC to remove garbage from config loading.
|
// Explicitly triggering GC to remove garbage from config loading.
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
// Running backend
|
// Running backend
|
||||||
{
|
{
|
||||||
|
@@ -15,12 +15,22 @@
|
|||||||
"bittorrent"
|
"bittorrent"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "socks5-warp",
|
||||||
|
"domain": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "IPv6_out",
|
"outboundTag": "IPv6_out",
|
||||||
"domain": [
|
"domain": [
|
||||||
"geosite:netflix"
|
"geosite:netflix"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"outboundTag": "IPv4_out",
|
||||||
|
"network": "udp,tcp"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@@ -11,7 +11,7 @@ type Config struct {
|
|||||||
InboundConfigPath string `mapstructure:"InboundConfigPath"`
|
InboundConfigPath string `mapstructure:"InboundConfigPath"`
|
||||||
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
|
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
|
||||||
RouteConfigPath string `mapstructure:"RouteConfigPath"`
|
RouteConfigPath string `mapstructure:"RouteConfigPath"`
|
||||||
ConnetionConfig *ConnetionConfig `mapstructure:"ConnetionConfig"`
|
ConnectionConfig *ConnectionConfig `mapstructure:"ConnectionConfig"`
|
||||||
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
|
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ type LogConfig struct {
|
|||||||
ErrorPath string `mapstructure:"ErrorPath"`
|
ErrorPath string `mapstructure:"ErrorPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnetionConfig struct {
|
type ConnectionConfig struct {
|
||||||
Handshake uint32 `mapstructure:"handshake"`
|
Handshake uint32 `mapstructure:"handshake"`
|
||||||
ConnIdle uint32 `mapstructure:"connIdle"`
|
ConnIdle uint32 `mapstructure:"connIdle"`
|
||||||
UplinkOnly uint32 `mapstructure:"uplinkOnly"`
|
UplinkOnly uint32 `mapstructure:"uplinkOnly"`
|
||||||
|
@@ -10,8 +10,8 @@ func getDefaultLogConfig() *LogConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultConnetionConfig() *ConnetionConfig {
|
func getDefaultConnectionConfig() *ConnectionConfig {
|
||||||
return &ConnetionConfig{
|
return &ConnectionConfig{
|
||||||
Handshake: 4,
|
Handshake: 4,
|
||||||
ConnIdle: 30,
|
ConnIdle: 30,
|
||||||
UplinkOnly: 2,
|
UplinkOnly: 2,
|
||||||
|
@@ -2,19 +2,13 @@ package panel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
io "io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"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/app/mydispatcher"
|
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||||
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
|
||||||
"github.com/XrayR-project/XrayR/service"
|
|
||||||
"github.com/XrayR-project/XrayR/service/controller"
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/r3labs/diff/v2"
|
"github.com/r3labs/diff/v2"
|
||||||
"github.com/xtls/xray-core/app/proxyman"
|
"github.com/xtls/xray-core/app/proxyman"
|
||||||
@@ -22,6 +16,16 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"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/service"
|
||||||
|
"github.com/XrayR-project/XrayR/service/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Panel Structure
|
// Panel Structure
|
||||||
@@ -54,7 +58,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
// DNS config
|
// DNS config
|
||||||
coreDnsConfig := &conf.DNSConfig{}
|
coreDnsConfig := &conf.DNSConfig{}
|
||||||
if panelConfig.DnsConfigPath != "" {
|
if panelConfig.DnsConfigPath != "" {
|
||||||
if data, err := io.ReadFile(panelConfig.DnsConfigPath); err != nil {
|
if data, err := os.ReadFile(panelConfig.DnsConfigPath); err != nil {
|
||||||
log.Panicf("Failed to read DNS config file at: %s", panelConfig.DnsConfigPath)
|
log.Panicf("Failed to read DNS config file at: %s", panelConfig.DnsConfigPath)
|
||||||
} else {
|
} else {
|
||||||
if err = json.Unmarshal(data, coreDnsConfig); err != nil {
|
if err = json.Unmarshal(data, coreDnsConfig); err != nil {
|
||||||
@@ -69,7 +73,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
// Routing config
|
// Routing config
|
||||||
coreRouterConfig := &conf.RouterConfig{}
|
coreRouterConfig := &conf.RouterConfig{}
|
||||||
if panelConfig.RouteConfigPath != "" {
|
if panelConfig.RouteConfigPath != "" {
|
||||||
if data, err := io.ReadFile(panelConfig.RouteConfigPath); err != nil {
|
if data, err := os.ReadFile(panelConfig.RouteConfigPath); err != nil {
|
||||||
log.Panicf("Failed to read Routing config file at: %s", panelConfig.RouteConfigPath)
|
log.Panicf("Failed to read Routing config file at: %s", panelConfig.RouteConfigPath)
|
||||||
} else {
|
} else {
|
||||||
if err = json.Unmarshal(data, coreRouterConfig); err != nil {
|
if err = json.Unmarshal(data, coreRouterConfig); err != nil {
|
||||||
@@ -82,9 +86,9 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
log.Panicf("Failed to understand Routing config Please check: https://xtls.github.io/config/routing.html for help: %s", err)
|
log.Panicf("Failed to understand Routing config Please check: https://xtls.github.io/config/routing.html for help: %s", err)
|
||||||
}
|
}
|
||||||
// Custom Inbound config
|
// Custom Inbound config
|
||||||
coreCustomInboundConfig := []conf.InboundDetourConfig{}
|
var coreCustomInboundConfig []conf.InboundDetourConfig
|
||||||
if panelConfig.InboundConfigPath != "" {
|
if panelConfig.InboundConfigPath != "" {
|
||||||
if data, err := io.ReadFile(panelConfig.InboundConfigPath); err != nil {
|
if data, err := os.ReadFile(panelConfig.InboundConfigPath); err != nil {
|
||||||
log.Panicf("Failed to read Custom Inbound config file at: %s", panelConfig.OutboundConfigPath)
|
log.Panicf("Failed to read Custom Inbound config file at: %s", panelConfig.OutboundConfigPath)
|
||||||
} else {
|
} else {
|
||||||
if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil {
|
if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil {
|
||||||
@@ -92,7 +96,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inBoundConfig := []*core.InboundHandlerConfig{}
|
var inBoundConfig []*core.InboundHandlerConfig
|
||||||
for _, config := range coreCustomInboundConfig {
|
for _, config := range coreCustomInboundConfig {
|
||||||
oc, err := config.Build()
|
oc, err := config.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,9 +105,9 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
inBoundConfig = append(inBoundConfig, oc)
|
inBoundConfig = append(inBoundConfig, oc)
|
||||||
}
|
}
|
||||||
// Custom Outbound config
|
// Custom Outbound config
|
||||||
coreCustomOutboundConfig := []conf.OutboundDetourConfig{}
|
var coreCustomOutboundConfig []conf.OutboundDetourConfig
|
||||||
if panelConfig.OutboundConfigPath != "" {
|
if panelConfig.OutboundConfigPath != "" {
|
||||||
if data, err := io.ReadFile(panelConfig.OutboundConfigPath); err != nil {
|
if data, err := os.ReadFile(panelConfig.OutboundConfigPath); err != nil {
|
||||||
log.Panicf("Failed to read Custom Outbound config file at: %s", panelConfig.OutboundConfigPath)
|
log.Panicf("Failed to read Custom Outbound config file at: %s", panelConfig.OutboundConfigPath)
|
||||||
} else {
|
} else {
|
||||||
if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil {
|
if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil {
|
||||||
@@ -111,7 +115,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outBoundConfig := []*core.OutboundHandlerConfig{}
|
var outBoundConfig []*core.OutboundHandlerConfig
|
||||||
for _, config := range coreCustomOutboundConfig {
|
for _, config := range coreCustomOutboundConfig {
|
||||||
oc, err := config.Build()
|
oc, err := config.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,7 +124,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
outBoundConfig = append(outBoundConfig, oc)
|
outBoundConfig = append(outBoundConfig, oc)
|
||||||
}
|
}
|
||||||
// Policy config
|
// Policy config
|
||||||
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnetionConfig)
|
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnectionConfig)
|
||||||
corePolicyConfig := &conf.PolicyConfig{}
|
corePolicyConfig := &conf.PolicyConfig{}
|
||||||
corePolicyConfig.Levels = map[uint32]*conf.Policy{0: levelPolicyConfig}
|
corePolicyConfig.Levels = map[uint32]*conf.Policy{0: levelPolicyConfig}
|
||||||
policyConfig, _ := corePolicyConfig.Build()
|
policyConfig, _ := corePolicyConfig.Build()
|
||||||
@@ -148,7 +152,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start Start the panel
|
// Start the panel
|
||||||
func (p *Panel) Start() {
|
func (p *Panel) Start() {
|
||||||
p.access.Lock()
|
p.access.Lock()
|
||||||
defer p.access.Unlock()
|
defer p.access.Unlock()
|
||||||
@@ -159,18 +163,24 @@ func (p *Panel) Start() {
|
|||||||
log.Panicf("Failed to start instance: %s", err)
|
log.Panicf("Failed to start instance: %s", err)
|
||||||
}
|
}
|
||||||
p.Server = server
|
p.Server = server
|
||||||
|
|
||||||
// Load Nodes config
|
// Load Nodes config
|
||||||
for _, nodeConfig := range p.panelConfig.NodesConfig {
|
for _, nodeConfig := range p.panelConfig.NodesConfig {
|
||||||
var apiClient api.API
|
var apiClient api.API
|
||||||
switch nodeConfig.PanelType {
|
switch nodeConfig.PanelType {
|
||||||
case "SSpanel":
|
case "SSpanel":
|
||||||
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
||||||
|
// todo Deprecated after 2023.6.1
|
||||||
case "V2board":
|
case "V2board":
|
||||||
apiClient = v2board.New(nodeConfig.ApiConfig)
|
apiClient = v2board.New(nodeConfig.ApiConfig)
|
||||||
|
case "NewV2board":
|
||||||
|
apiClient = newV2board.New(nodeConfig.ApiConfig)
|
||||||
case "PMpanel":
|
case "PMpanel":
|
||||||
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
||||||
case "Proxypanel":
|
case "Proxypanel":
|
||||||
apiClient = proxypanel.New(nodeConfig.ApiConfig)
|
apiClient = proxypanel.New(nodeConfig.ApiConfig)
|
||||||
|
case "V2RaySocks":
|
||||||
|
apiClient = v2raysocks.New(nodeConfig.ApiConfig)
|
||||||
default:
|
default:
|
||||||
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
|
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
|
||||||
}
|
}
|
||||||
@@ -198,7 +208,7 @@ func (p *Panel) Start() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close Close the panel
|
// Close the panel
|
||||||
func (p *Panel) Close() {
|
func (p *Panel) Close() {
|
||||||
p.access.Lock()
|
p.access.Lock()
|
||||||
defer p.access.Unlock()
|
defer p.access.Unlock()
|
||||||
@@ -214,21 +224,21 @@ func (p *Panel) Close() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConnectionConfig(c *ConnetionConfig) (policy *conf.Policy) {
|
func parseConnectionConfig(c *ConnectionConfig) (policy *conf.Policy) {
|
||||||
connetionConfig := getDefaultConnetionConfig()
|
connectionConfig := getDefaultConnectionConfig()
|
||||||
if c != nil {
|
if c != nil {
|
||||||
if _, err := diff.Merge(connetionConfig, c, connetionConfig); err != nil {
|
if _, err := diff.Merge(connectionConfig, c, connectionConfig); err != nil {
|
||||||
log.Panicf("Read ConnetionConfig failed: %s", err)
|
log.Panicf("Read ConnectionConfig failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
policy = &conf.Policy{
|
policy = &conf.Policy{
|
||||||
StatsUserUplink: true,
|
StatsUserUplink: true,
|
||||||
StatsUserDownlink: true,
|
StatsUserDownlink: true,
|
||||||
Handshake: &connetionConfig.Handshake,
|
Handshake: &connectionConfig.Handshake,
|
||||||
ConnectionIdle: &connetionConfig.ConnIdle,
|
ConnectionIdle: &connectionConfig.ConnIdle,
|
||||||
UplinkOnly: &connetionConfig.UplinkOnly,
|
UplinkOnly: &connectionConfig.UplinkOnly,
|
||||||
DownlinkOnly: &connetionConfig.DownlinkOnly,
|
DownlinkOnly: &connectionConfig.DownlinkOnly,
|
||||||
BufferSize: &connetionConfig.BufferSize,
|
BufferSize: &connectionConfig.BufferSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/XrayR-project/XrayR/common/limiter"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ListenIP string `mapstructure:"ListenIP"`
|
ListenIP string `mapstructure:"ListenIP"`
|
||||||
SendIP string `mapstructure:"SendIP"`
|
SendIP string `mapstructure:"SendIP"`
|
||||||
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
||||||
CertConfig *CertConfig `mapstructure:"CertConfig"`
|
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
|
||||||
EnableDNS bool `mapstructure:"EnableDNS"`
|
EnableDNS bool `mapstructure:"EnableDNS"`
|
||||||
DNSType string `mapstructure:"DNSType"`
|
DNSType string `mapstructure:"DNSType"`
|
||||||
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
||||||
@@ -13,18 +18,16 @@ type Config struct {
|
|||||||
EnableFallback bool `mapstructure:"EnableFallback"`
|
EnableFallback bool `mapstructure:"EnableFallback"`
|
||||||
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
||||||
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
||||||
|
AutoSpeedLimitConfig *AutoSpeedLimitConfig `mapstructure:"AutoSpeedLimitConfig"`
|
||||||
|
GlobalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig `mapstructure:"GlobalDeviceLimitConfig"`
|
||||||
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertConfig struct {
|
type AutoSpeedLimitConfig struct {
|
||||||
CertMode string `mapstructure:"CertMode"` // none, file, http, dns
|
Limit int `mapstructure:"Limit"` // mbps
|
||||||
RejectUnknownSni bool `mapstructure:"RejectUnknownSni"`
|
WarnTimes int `mapstructure:"WarnTimes"`
|
||||||
CertDomain string `mapstructure:"CertDomain"`
|
LimitSpeed int `mapstructure:"LimitSpeed"` // mbps
|
||||||
CertFile string `mapstructure:"CertFile"`
|
LimitDuration int `mapstructure:"LimitDuration"` // minute
|
||||||
KeyFile string `mapstructure:"KeyFile"`
|
|
||||||
Provider string `mapstructure:"Provider"` // alidns, cloudflare, gandi, godaddy....
|
|
||||||
Email string `mapstructure:"Email"`
|
|
||||||
DNSEnv map[string]string `mapstructure:"DNSEnv"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FallBackConfig struct {
|
type FallBackConfig struct {
|
||||||
|
@@ -4,31 +4,28 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/inbound"
|
"github.com/xtls/xray-core/features/inbound"
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
"github.com/xtls/xray-core/features/routing"
|
|
||||||
"github.com/xtls/xray-core/features/stats"
|
"github.com/xtls/xray-core/features/stats"
|
||||||
"github.com/xtls/xray-core/proxy"
|
"github.com/xtls/xray-core/proxy"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/common/limiter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Controller) removeInbound(tag string) error {
|
func (c *Controller) removeInbound(tag string) error {
|
||||||
inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager)
|
err := c.ibm.RemoveHandler(context.Background(), tag)
|
||||||
err := inboundManager.RemoveHandler(context.Background(), tag)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) removeOutbound(tag string) error {
|
func (c *Controller) removeOutbound(tag string) error {
|
||||||
outboundManager := c.server.GetFeature(outbound.ManagerType()).(outbound.Manager)
|
err := c.obm.RemoveHandler(context.Background(), tag)
|
||||||
err := outboundManager.RemoveHandler(context.Background(), tag)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) addInbound(config *core.InboundHandlerConfig) error {
|
func (c *Controller) addInbound(config *core.InboundHandlerConfig) error {
|
||||||
inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager)
|
|
||||||
rawHandler, err := core.CreateObject(c.server, config)
|
rawHandler, err := core.CreateObject(c.server, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -37,14 +34,13 @@ func (c *Controller) addInbound(config *core.InboundHandlerConfig) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("not an InboundHandler: %s", err)
|
return fmt.Errorf("not an InboundHandler: %s", err)
|
||||||
}
|
}
|
||||||
if err := inboundManager.AddHandler(context.Background(), handler); err != nil {
|
if err := c.ibm.AddHandler(context.Background(), handler); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) addOutbound(config *core.OutboundHandlerConfig) error {
|
func (c *Controller) addOutbound(config *core.OutboundHandlerConfig) error {
|
||||||
outboundManager := c.server.GetFeature(outbound.ManagerType()).(outbound.Manager)
|
|
||||||
rawHandler, err := core.CreateObject(c.server, config)
|
rawHandler, err := core.CreateObject(c.server, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -53,26 +49,25 @@ func (c *Controller) addOutbound(config *core.OutboundHandlerConfig) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("not an InboundHandler: %s", err)
|
return fmt.Errorf("not an InboundHandler: %s", err)
|
||||||
}
|
}
|
||||||
if err := outboundManager.AddHandler(context.Background(), handler); err != nil {
|
if err := c.obm.AddHandler(context.Background(), handler); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) addUsers(users []*protocol.User, tag string) error {
|
func (c *Controller) addUsers(users []*protocol.User, tag string) error {
|
||||||
inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager)
|
handler, err := c.ibm.GetHandler(context.Background(), tag)
|
||||||
handler, err := inboundManager.GetHandler(context.Background(), tag)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("No such inbound tag: %s", err)
|
return fmt.Errorf("no such inbound tag: %s", err)
|
||||||
}
|
}
|
||||||
inboundInstance, ok := handler.(proxy.GetInbound)
|
inboundInstance, ok := handler.(proxy.GetInbound)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("handler %s is not implement proxy.GetInbound", tag)
|
return fmt.Errorf("handler %s has not implemented proxy.GetInbound", tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
userManager, ok := inboundInstance.GetInbound().(proxy.UserManager)
|
userManager, ok := inboundInstance.GetInbound().(proxy.UserManager)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("handler %s is not implement proxy.UserManager", err)
|
return fmt.Errorf("handler %s has not implemented proxy.UserManager", tag)
|
||||||
}
|
}
|
||||||
for _, item := range users {
|
for _, item := range users {
|
||||||
mUser, err := item.ToMemoryUser()
|
mUser, err := item.ToMemoryUser()
|
||||||
@@ -88,10 +83,9 @@ func (c *Controller) addUsers(users []*protocol.User, tag string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) removeUsers(users []string, tag string) error {
|
func (c *Controller) removeUsers(users []string, tag string) error {
|
||||||
inboundManager := c.server.GetFeature(inbound.ManagerType()).(inbound.Manager)
|
handler, err := c.ibm.GetHandler(context.Background(), tag)
|
||||||
handler, err := inboundManager.GetHandler(context.Background(), tag)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("No such inbound tag: %s", err)
|
return fmt.Errorf("no such inbound tag: %s", err)
|
||||||
}
|
}
|
||||||
inboundInstance, ok := handler.(proxy.GetInbound)
|
inboundInstance, ok := handler.(proxy.GetInbound)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -111,54 +105,57 @@ func (c *Controller) removeUsers(users []string, tag string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) getTraffic(email string) (up int64, down int64) {
|
func (c *Controller) getTraffic(email string) (up int64, down int64, upCounter stats.Counter, downCounter stats.Counter) {
|
||||||
upName := "user>>>" + email + ">>>traffic>>>uplink"
|
upName := "user>>>" + email + ">>>traffic>>>uplink"
|
||||||
downName := "user>>>" + email + ">>>traffic>>>downlink"
|
downName := "user>>>" + email + ">>>traffic>>>downlink"
|
||||||
statsManager := c.server.GetFeature(stats.ManagerType()).(stats.Manager)
|
upCounter = c.stm.GetCounter(upName)
|
||||||
upCounter := statsManager.GetCounter(upName)
|
downCounter = c.stm.GetCounter(downName)
|
||||||
downCounter := statsManager.GetCounter(downName)
|
if upCounter != nil && upCounter.Value() != 0 {
|
||||||
if upCounter != nil {
|
|
||||||
up = upCounter.Value()
|
up = upCounter.Value()
|
||||||
upCounter.Set(0)
|
} else {
|
||||||
|
upCounter = nil
|
||||||
}
|
}
|
||||||
if downCounter != nil {
|
if downCounter != nil && downCounter.Value() != 0 {
|
||||||
down = downCounter.Value()
|
down = downCounter.Value()
|
||||||
downCounter.Set(0)
|
} else {
|
||||||
|
downCounter = nil
|
||||||
}
|
}
|
||||||
return up, down
|
return up, down, upCounter, downCounter
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo) error {
|
func (c *Controller) resetTraffic(upCounterList *[]stats.Counter, downCounterList *[]stats.Counter) {
|
||||||
dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher)
|
for _, upCounter := range *upCounterList {
|
||||||
err := dispather.Limiter.AddInboundLimiter(tag, nodeSpeedLimit, userList)
|
upCounter.Set(0)
|
||||||
|
}
|
||||||
|
for _, downCounter := range *downCounterList {
|
||||||
|
downCounter.Set(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) AddInboundLimiter(tag string, nodeSpeedLimit uint64, userList *[]api.UserInfo, globalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig) error {
|
||||||
|
err := c.dispatcher.Limiter.AddInboundLimiter(tag, nodeSpeedLimit, userList, globalDeviceLimitConfig)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserInfo) error {
|
func (c *Controller) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserInfo) error {
|
||||||
dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher)
|
err := c.dispatcher.Limiter.UpdateInboundLimiter(tag, updatedUserList)
|
||||||
err := dispather.Limiter.UpdateInboundLimiter(tag, updatedUserList)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) DeleteInboundLimiter(tag string) error {
|
func (c *Controller) DeleteInboundLimiter(tag string) error {
|
||||||
dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher)
|
err := c.dispatcher.Limiter.DeleteInboundLimiter(tag)
|
||||||
err := dispather.Limiter.DeleteInboundLimiter(tag)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
|
func (c *Controller) GetOnlineDevice(tag string) (*[]api.OnlineUser, error) {
|
||||||
dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher)
|
return c.dispatcher.Limiter.GetOnlineDevice(tag)
|
||||||
return dispather.Limiter.GetOnlineDevice(tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) UpdateRule(tag string, newRuleList []api.DetectRule) error {
|
func (c *Controller) UpdateRule(tag string, newRuleList []api.DetectRule) error {
|
||||||
dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher)
|
err := c.dispatcher.RuleManager.UpdateRule(tag, newRuleList)
|
||||||
err := dispather.RuleManager.UpdateRule(tag, newRuleList)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GetDetectResult(tag string) (*[]api.DetectResult, error) {
|
func (c *Controller) GetDetectResult(tag string) (*[]api.DetectResult, error) {
|
||||||
dispather := c.server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher)
|
return c.dispatcher.RuleManager.GetDetectResult(tag)
|
||||||
return dispather.RuleManager.GetDetectResult(tag)
|
|
||||||
}
|
}
|
||||||
|
@@ -3,18 +3,29 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd"
|
|
||||||
"github.com/XrayR-project/XrayR/common/serverstatus"
|
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/task"
|
"github.com/xtls/xray-core/common/task"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/features/inbound"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
"github.com/xtls/xray-core/features/routing"
|
||||||
|
"github.com/xtls/xray-core/features/stats"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
|
"github.com/XrayR-project/XrayR/common/serverstatus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LimitInfo struct {
|
||||||
|
end int64
|
||||||
|
currentSpeedLimit int
|
||||||
|
originSpeedLimit uint64
|
||||||
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
server *core.Instance
|
server *core.Instance
|
||||||
config *Config
|
config *Config
|
||||||
@@ -23,9 +34,20 @@ type Controller struct {
|
|||||||
nodeInfo *api.NodeInfo
|
nodeInfo *api.NodeInfo
|
||||||
Tag string
|
Tag string
|
||||||
userList *[]api.UserInfo
|
userList *[]api.UserInfo
|
||||||
nodeInfoMonitorPeriodic *task.Periodic
|
tasks []periodicTask
|
||||||
userReportPeriodic *task.Periodic
|
limitedUsers map[api.UserInfo]LimitInfo
|
||||||
|
warnedUsers map[api.UserInfo]int
|
||||||
panelType string
|
panelType string
|
||||||
|
ibm inbound.Manager
|
||||||
|
obm outbound.Manager
|
||||||
|
stm stats.Manager
|
||||||
|
dispatcher *mydispatcher.DefaultDispatcher
|
||||||
|
startAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type periodicTask struct {
|
||||||
|
tag string
|
||||||
|
*task.Periodic
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return a Controller service with default parameters.
|
// New return a Controller service with default parameters.
|
||||||
@@ -35,7 +57,13 @@ func New(server *core.Instance, api api.API, config *Config, panelType string) *
|
|||||||
config: config,
|
config: config,
|
||||||
apiClient: api,
|
apiClient: api,
|
||||||
panelType: panelType,
|
panelType: panelType,
|
||||||
|
ibm: server.GetFeature(inbound.ManagerType()).(inbound.Manager),
|
||||||
|
obm: server.GetFeature(outbound.ManagerType()).(outbound.Manager),
|
||||||
|
stm: server.GetFeature(stats.ManagerType()).(stats.Manager),
|
||||||
|
dispatcher: server.GetFeature(routing.DispatcherType()).(*mydispatcher.DefaultDispatcher),
|
||||||
|
startAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,17 +89,19 @@ func (c *Controller) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sync controller userList
|
||||||
|
c.userList = userInfo
|
||||||
|
|
||||||
err = c.addNewUser(userInfo, newNodeInfo)
|
err = c.addNewUser(userInfo, newNodeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//sync controller userList
|
|
||||||
c.userList = userInfo
|
|
||||||
|
|
||||||
// Add Limiter
|
// Add Limiter
|
||||||
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, userInfo); err != nil {
|
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, userInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Rule Manager
|
// Add Rule Manager
|
||||||
if !c.config.DisableGetRule {
|
if !c.config.DisableGetRule {
|
||||||
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
||||||
@@ -82,49 +112,70 @@ func (c *Controller) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.nodeInfoMonitorPeriodic = &task.Periodic{
|
|
||||||
|
// Init AutoSpeedLimitConfig
|
||||||
|
if c.config.AutoSpeedLimitConfig == nil {
|
||||||
|
c.config.AutoSpeedLimitConfig = &AutoSpeedLimitConfig{0, 0, 0, 0}
|
||||||
|
}
|
||||||
|
if c.config.AutoSpeedLimitConfig.Limit > 0 {
|
||||||
|
c.limitedUsers = make(map[api.UserInfo]LimitInfo)
|
||||||
|
c.warnedUsers = make(map[api.UserInfo]int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add periodic tasks
|
||||||
|
c.tasks = append(c.tasks,
|
||||||
|
periodicTask{
|
||||||
|
tag: "node monitor",
|
||||||
|
Periodic: &task.Periodic{
|
||||||
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
||||||
Execute: c.nodeInfoMonitor,
|
Execute: c.nodeInfoMonitor,
|
||||||
}
|
}},
|
||||||
c.userReportPeriodic = &task.Periodic{
|
periodicTask{
|
||||||
|
tag: "user monitor",
|
||||||
|
Periodic: &task.Periodic{
|
||||||
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
||||||
Execute: c.userInfoMonitor,
|
Execute: c.userInfoMonitor,
|
||||||
}
|
}},
|
||||||
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
)
|
||||||
// delay to start nodeInfoMonitor
|
|
||||||
go func() {
|
// Check cert service in need
|
||||||
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second)
|
if c.nodeInfo.EnableTLS {
|
||||||
_ = c.nodeInfoMonitorPeriodic.Start()
|
c.tasks = append(c.tasks, periodicTask{
|
||||||
}()
|
tag: "cert monitor",
|
||||||
|
Periodic: &task.Periodic{
|
||||||
|
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second * 60,
|
||||||
|
Execute: c.certMonitor,
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start periodic tasks
|
||||||
|
for i := range c.tasks {
|
||||||
|
log.Printf("%s Start %s periodic task", c.logPrefix(), c.tasks[i].tag)
|
||||||
|
go c.tasks[i].Start()
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
|
||||||
// delay to start userReport
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second)
|
|
||||||
_ = c.userReportPeriodic.Start()
|
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implement the Close() function of the service interface
|
// Close implement the Close() function of the service interface
|
||||||
func (c *Controller) Close() error {
|
func (c *Controller) Close() error {
|
||||||
if c.nodeInfoMonitorPeriodic != nil {
|
for i := range c.tasks {
|
||||||
err := c.nodeInfoMonitorPeriodic.Close()
|
if c.tasks[i].Periodic != nil {
|
||||||
if err != nil {
|
if err := c.tasks[i].Periodic.Close(); err != nil {
|
||||||
log.Panicf("node info periodic close failed: %s", err)
|
log.Panicf("%s %s periodic task close failed: %s", c.logPrefix(), c.tasks[i].tag, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.nodeInfoMonitorPeriodic != nil {
|
|
||||||
err := c.userReportPeriodic.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("user report periodic close failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) nodeInfoMonitor() (err error) {
|
func (c *Controller) nodeInfoMonitor() (err error) {
|
||||||
|
// delay to start
|
||||||
|
if time.Since(c.startAt) < time.Duration(c.config.UpdatePeriodic)*time.Second {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// First fetch Node Info
|
// First fetch Node Info
|
||||||
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -133,18 +184,24 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update User
|
// Update User
|
||||||
|
var usersChanged = true
|
||||||
newUserInfo, err := c.apiClient.GetUserList()
|
newUserInfo, err := c.apiClient.GetUserList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err.Error() == "users no change" {
|
||||||
|
usersChanged = false
|
||||||
|
newUserInfo = c.userList
|
||||||
|
} else {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var nodeInfoChanged = false
|
var nodeInfoChanged = false
|
||||||
// If nodeInfo changed
|
// If nodeInfo changed
|
||||||
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
||||||
// Remove old tag
|
// Remove old tag
|
||||||
oldtag := c.Tag
|
oldTag := c.Tag
|
||||||
err := c.removeOldTag(oldtag)
|
err := c.removeOldTag(oldTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return nil
|
return nil
|
||||||
@@ -166,7 +223,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
nodeInfoChanged = true
|
nodeInfoChanged = true
|
||||||
// Remove Old limiter
|
// Remove Old limiter
|
||||||
if err = c.DeleteInboundLimiter(oldtag); err != nil {
|
if err = c.DeleteInboundLimiter(oldTag); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -183,32 +240,22 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Cert
|
|
||||||
if c.nodeInfo.EnableTLS && (c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") {
|
|
||||||
lego, err := legocmd.New()
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
// Xray-core supports the OcspStapling certification hot renew
|
|
||||||
_, _, err = lego.RenewCert(c.config.CertConfig.CertDomain, c.config.CertConfig.Email, c.config.CertConfig.CertMode, c.config.CertConfig.Provider, c.config.CertConfig.DNSEnv)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nodeInfoChanged {
|
if nodeInfoChanged {
|
||||||
err = c.addNewUser(newUserInfo, newNodeInfo)
|
err = c.addNewUser(newUserInfo, newNodeInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Limiter
|
// Add Limiter
|
||||||
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, newUserInfo); err != nil {
|
if err := c.AddInboundLimiter(c.Tag, newNodeInfo.SpeedLimit, newUserInfo, c.config.GlobalDeviceLimitConfig); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deleted, added := compareUserList(c.userList, newUserInfo)
|
var deleted, added []api.UserInfo
|
||||||
|
if usersChanged {
|
||||||
|
deleted, added = compareUserList(c.userList, newUserInfo)
|
||||||
if len(deleted) > 0 {
|
if len(deleted) > 0 {
|
||||||
deletedEmail := make([]string, len(deleted))
|
deletedEmail := make([]string, len(deleted))
|
||||||
for i, u := range deleted {
|
for i, u := range deleted {
|
||||||
@@ -229,18 +276,19 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("[%s: %d] %d user deleted, %d user added", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(deleted), len(added))
|
}
|
||||||
|
log.Printf("%s %d user deleted, %d user added", c.logPrefix(), len(deleted), len(added))
|
||||||
}
|
}
|
||||||
c.userList = newUserInfo
|
c.userList = newUserInfo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) removeOldTag(oldtag string) (err error) {
|
func (c *Controller) removeOldTag(oldTag string) (err error) {
|
||||||
err = c.removeInbound(oldtag)
|
err = c.removeInbound(oldTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = c.removeOutbound(oldtag)
|
err = c.removeOutbound(oldTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -276,7 +324,7 @@ func (c *Controller) addNewTag(newNodeInfo *api.NodeInfo) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error) {
|
func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error) {
|
||||||
// Shadowsocks-Plugin require a seaperate inbound for other TransportProtocol likes: ws, grpc
|
// Shadowsocks-Plugin require a separate inbound for other TransportProtocol likes: ws, grpc
|
||||||
fakeNodeInfo := newNodeInfo
|
fakeNodeInfo := newNodeInfo
|
||||||
fakeNodeInfo.TransportProtocol = "tcp"
|
fakeNodeInfo.TransportProtocol = "tcp"
|
||||||
fakeNodeInfo.EnableTLS = false
|
fakeNodeInfo.EnableTLS = false
|
||||||
@@ -329,69 +377,66 @@ func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error)
|
|||||||
|
|
||||||
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
|
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
|
||||||
users := make([]*protocol.User, 0)
|
users := make([]*protocol.User, 0)
|
||||||
if nodeInfo.NodeType == "V2ray" {
|
switch nodeInfo.NodeType {
|
||||||
|
case "V2ray":
|
||||||
if nodeInfo.EnableVless {
|
if nodeInfo.EnableVless {
|
||||||
users = c.buildVlessUser(userInfo)
|
users = c.buildVlessUser(userInfo)
|
||||||
} else {
|
} else {
|
||||||
alterID := 0
|
var alterID uint16 = 0
|
||||||
if c.panelType == "V2board" {
|
if (c.panelType == "V2board" || c.panelType == "V2RaySocks") && len(*userInfo) > 0 {
|
||||||
// use latest userInfo
|
// use latest userInfo
|
||||||
alterID = (*userInfo)[0].AlterID
|
alterID = (*userInfo)[0].AlterID
|
||||||
} else {
|
} else {
|
||||||
alterID = nodeInfo.AlterID
|
alterID = nodeInfo.AlterID
|
||||||
}
|
}
|
||||||
if alterID >= 0 && alterID < math.MaxUint16 {
|
users = c.buildVmessUser(userInfo, alterID)
|
||||||
users = c.buildVmessUser(userInfo, uint16(alterID))
|
|
||||||
} else {
|
|
||||||
users = c.buildVmessUser(userInfo, 0)
|
|
||||||
return fmt.Errorf("AlterID should between 0 to 1<<16 - 1, set it to 0 for now")
|
|
||||||
}
|
}
|
||||||
}
|
case "Trojan":
|
||||||
} else if nodeInfo.NodeType == "Trojan" {
|
|
||||||
users = c.buildTrojanUser(userInfo)
|
users = c.buildTrojanUser(userInfo)
|
||||||
} else if nodeInfo.NodeType == "Shadowsocks" {
|
case "Shadowsocks":
|
||||||
users = c.buildSSUser(userInfo, nodeInfo.CypherMethod)
|
users = c.buildSSUser(userInfo, nodeInfo.CypherMethod)
|
||||||
} else if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
case "Shadowsocks-Plugin":
|
||||||
users = c.buildSSPluginUser(userInfo)
|
users = c.buildSSPluginUser(userInfo)
|
||||||
} else {
|
default:
|
||||||
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
|
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.addUsers(users, c.Tag)
|
err = c.addUsers(users, c.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("[%s: %d] Added %d new users", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*userInfo))
|
log.Printf("%s Added %d new users", c.logPrefix(), len(*userInfo))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareUserList(old, new *[]api.UserInfo) (deleted, added []api.UserInfo) {
|
func compareUserList(old, new *[]api.UserInfo) (deleted, added []api.UserInfo) {
|
||||||
msrc := make(map[api.UserInfo]byte) //按源数组建索引
|
mSrc := make(map[api.UserInfo]byte) // 按源数组建索引
|
||||||
mall := make(map[api.UserInfo]byte) //源+目所有元素建索引
|
mAll := make(map[api.UserInfo]byte) // 源+目所有元素建索引
|
||||||
|
|
||||||
var set []api.UserInfo //交集
|
var set []api.UserInfo // 交集
|
||||||
|
|
||||||
//1.源数组建立map
|
// 1.源数组建立map
|
||||||
for _, v := range *old {
|
for _, v := range *old {
|
||||||
msrc[v] = 0
|
mSrc[v] = 0
|
||||||
mall[v] = 0
|
mAll[v] = 0
|
||||||
}
|
}
|
||||||
//2.目数组中,存不进去,即重复元素,所有存不进去的集合就是并集
|
// 2.目数组中,存不进去,即重复元素,所有存不进去的集合就是并集
|
||||||
for _, v := range *new {
|
for _, v := range *new {
|
||||||
l := len(mall)
|
l := len(mAll)
|
||||||
mall[v] = 1
|
mAll[v] = 1
|
||||||
if l != len(mall) { //长度变化,即可以存
|
if l != len(mAll) { // 长度变化,即可以存
|
||||||
l = len(mall)
|
l = len(mAll)
|
||||||
} else { //存不了,进并集
|
} else { // 存不了,进并集
|
||||||
set = append(set, v)
|
set = append(set, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//3.遍历交集,在并集中找,找到就从并集中删,删完后就是补集(即并-交=所有变化的元素)
|
// 3.遍历交集,在并集中找,找到就从并集中删,删完后就是补集(即并-交=所有变化的元素)
|
||||||
for _, v := range set {
|
for _, v := range set {
|
||||||
delete(mall, v)
|
delete(mAll, v)
|
||||||
}
|
}
|
||||||
//4.此时,mall是补集,所有元素去源中找,找到就是删除的,找不到的必定能在目数组中找到,即新加的
|
// 4.此时,mall是补集,所有元素去源中找,找到就是删除的,找不到的必定能在目数组中找到,即新加的
|
||||||
for v := range mall {
|
for v := range mAll {
|
||||||
_, exist := msrc[v]
|
_, exist := mSrc[v]
|
||||||
if exist {
|
if exist {
|
||||||
deleted = append(deleted, v)
|
deleted = append(deleted, v)
|
||||||
} else {
|
} else {
|
||||||
@@ -402,7 +447,23 @@ func compareUserList(old, new *[]api.UserInfo) (deleted, added []api.UserInfo) {
|
|||||||
return deleted, added
|
return deleted, added
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func limitUser(c *Controller, user api.UserInfo, silentUsers *[]api.UserInfo) {
|
||||||
|
c.limitedUsers[user] = LimitInfo{
|
||||||
|
end: time.Now().Unix() + int64(c.config.AutoSpeedLimitConfig.LimitDuration*60),
|
||||||
|
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"))
|
||||||
|
user.SpeedLimit = uint64((c.config.AutoSpeedLimitConfig.LimitSpeed * 1000000) / 8)
|
||||||
|
*silentUsers = append(*silentUsers, user)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) userInfoMonitor() (err error) {
|
func (c *Controller) userInfoMonitor() (err error) {
|
||||||
|
// delay to start
|
||||||
|
if time.Since(c.startAt) < time.Duration(c.config.UpdatePeriodic)*time.Second {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Get server status
|
// Get server status
|
||||||
CPU, Mem, Disk, Uptime, err := serverstatus.GetSystemInfo()
|
CPU, Mem, Disk, Uptime, err := serverstatus.GetSystemInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -418,23 +479,86 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
// Unlock users
|
||||||
|
if c.config.AutoSpeedLimitConfig.Limit > 0 && len(c.limitedUsers) > 0 {
|
||||||
|
log.Printf("%s Limited users:", c.logPrefix())
|
||||||
|
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)
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(toReleaseUsers) > 0 {
|
||||||
|
if err := c.UpdateInboundLimiter(c.Tag, &toReleaseUsers); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get User traffic
|
// Get User traffic
|
||||||
userTraffic := make([]api.UserTraffic, 0)
|
var userTraffic []api.UserTraffic
|
||||||
|
var upCounterList []stats.Counter
|
||||||
|
var downCounterList []stats.Counter
|
||||||
|
AutoSpeedLimit := int64(c.config.AutoSpeedLimitConfig.Limit)
|
||||||
|
UpdatePeriodic := int64(c.config.UpdatePeriodic)
|
||||||
|
limitedUsers := make([]api.UserInfo, 0)
|
||||||
for _, user := range *c.userList {
|
for _, user := range *c.userList {
|
||||||
up, down := c.getTraffic(c.buildUserTag(&user))
|
up, down, upCounter, downCounter := c.getTraffic(c.buildUserTag(&user))
|
||||||
if up > 0 || down > 0 {
|
if up > 0 || down > 0 {
|
||||||
|
// Over speed users
|
||||||
|
if AutoSpeedLimit > 0 {
|
||||||
|
if down > AutoSpeedLimit*1000000*UpdatePeriodic/8 || up > AutoSpeedLimit*1000000*UpdatePeriodic/8 {
|
||||||
|
if _, ok := c.limitedUsers[user]; !ok {
|
||||||
|
if c.config.AutoSpeedLimitConfig.WarnTimes == 0 {
|
||||||
|
limitUser(c, user, &limitedUsers)
|
||||||
|
} else {
|
||||||
|
c.warnedUsers[user] += 1
|
||||||
|
if c.warnedUsers[user] > c.config.AutoSpeedLimitConfig.WarnTimes {
|
||||||
|
limitUser(c, user, &limitedUsers)
|
||||||
|
delete(c.warnedUsers, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete(c.warnedUsers, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
userTraffic = append(userTraffic, api.UserTraffic{
|
userTraffic = append(userTraffic, api.UserTraffic{
|
||||||
UID: user.UID,
|
UID: user.UID,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Upload: up,
|
Upload: up,
|
||||||
Download: down})
|
Download: down})
|
||||||
|
|
||||||
|
if upCounter != nil {
|
||||||
|
upCounterList = append(upCounterList, upCounter)
|
||||||
|
}
|
||||||
|
if downCounter != nil {
|
||||||
|
downCounterList = append(downCounterList, downCounter)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete(c.warnedUsers, user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(userTraffic) > 0 && !c.config.DisableUploadTraffic {
|
if len(limitedUsers) > 0 {
|
||||||
|
if err := c.UpdateInboundLimiter(c.Tag, &limitedUsers); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(userTraffic) > 0 {
|
||||||
|
var err error // Define an empty error
|
||||||
|
if !c.config.DisableUploadTraffic {
|
||||||
err = c.apiClient.ReportUserTraffic(&userTraffic)
|
err = c.apiClient.ReportUserTraffic(&userTraffic)
|
||||||
|
}
|
||||||
|
// If report traffic error, not clear the traffic
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
} else {
|
||||||
|
c.resetTraffic(&upCounterList, &downCounterList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,9 +569,10 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[%s: %d] Report %d online users", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*onlineDevice))
|
log.Printf("%s Report %d online users", c.logPrefix(), len(*onlineDevice))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report Illegal user
|
// Report Illegal user
|
||||||
if detectResult, err := c.GetDetectResult(c.Tag); err != nil {
|
if detectResult, err := c.GetDetectResult(c.Tag); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
@@ -455,7 +580,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[%s: %d] Report %d illegal behaviors", c.nodeInfo.NodeType, c.nodeInfo.NodeID, len(*detectResult))
|
log.Printf("%s Report %d illegal behaviors", c.logPrefix(), len(*detectResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -465,3 +590,26 @@ func (c *Controller) userInfoMonitor() (err error) {
|
|||||||
func (c *Controller) buildNodeTag() string {
|
func (c *Controller) buildNodeTag() string {
|
||||||
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
|
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) logPrefix() string {
|
||||||
|
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 {
|
||||||
|
switch c.config.CertConfig.CertMode {
|
||||||
|
case "dns", "http", "tls":
|
||||||
|
lego, err := mylego.New(c.config.CertConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
// Xray-core supports the OcspStapling certification hot renew
|
||||||
|
_, _, _, err = lego.RenewCert()
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -8,12 +8,14 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/XrayR-project/XrayR/api/sspanel"
|
|
||||||
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
|
||||||
. "github.com/XrayR-project/XrayR/service/controller"
|
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/api/sspanel"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
|
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
||||||
|
. "github.com/XrayR-project/XrayR/service/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestController(t *testing.T) {
|
func TestController(t *testing.T) {
|
||||||
@@ -22,7 +24,7 @@ func TestController(t *testing.T) {
|
|||||||
LogConfig: &conf.LogConfig{LogLevel: "debug"},
|
LogConfig: &conf.LogConfig{LogLevel: "debug"},
|
||||||
}
|
}
|
||||||
policyConfig := &conf.PolicyConfig{}
|
policyConfig := &conf.PolicyConfig{}
|
||||||
policyConfig.Levels = map[uint32]*conf.Policy{0: &conf.Policy{
|
policyConfig.Levels = map[uint32]*conf.Policy{0: {
|
||||||
StatsUserUplink: true,
|
StatsUserUplink: true,
|
||||||
StatsUserDownlink: true,
|
StatsUserDownlink: true,
|
||||||
}}
|
}}
|
||||||
@@ -45,13 +47,13 @@ func TestController(t *testing.T) {
|
|||||||
if err = server.Start(); err != nil {
|
if err = server.Start(); err != nil {
|
||||||
t.Errorf("Failed to start instance: %s", err)
|
t.Errorf("Failed to start instance: %s", err)
|
||||||
}
|
}
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "http",
|
CertMode: "http",
|
||||||
CertDomain: "test.ss.tk",
|
CertDomain: "test.ss.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
Email: "ss@ss.com",
|
Email: "ss@ss.com",
|
||||||
}
|
}
|
||||||
controlerconfig := &Config{
|
controlerConfig := &Config{
|
||||||
UpdatePeriodic: 5,
|
UpdatePeriodic: 5,
|
||||||
CertConfig: certConfig,
|
CertConfig: certConfig,
|
||||||
}
|
}
|
||||||
@@ -61,14 +63,14 @@ func TestController(t *testing.T) {
|
|||||||
NodeID: 41,
|
NodeID: 41,
|
||||||
NodeType: "V2ray",
|
NodeType: "V2ray",
|
||||||
}
|
}
|
||||||
apiclient := sspanel.New(apiConfig)
|
apiClient := sspanel.New(apiConfig)
|
||||||
c := New(server, apiclient, controlerconfig)
|
c := New(server, apiClient, controlerConfig, "SSpanel")
|
||||||
fmt.Println("Sleep 1s")
|
fmt.Println("Sleep 1s")
|
||||||
err = c.Start()
|
err = c.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
//Explicitly triggering GC to remove garbage from config loading.
|
// Explicitly triggering GC to remove garbage from config loading.
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
||||||
{
|
{
|
||||||
|
7
service/controller/errors.generated.go
Normal file
7
service/controller/errors.generated.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import "github.com/xtls/xray-core/common/errors"
|
||||||
|
|
||||||
|
func newError(values ...interface{}) *errors.Error {
|
||||||
|
return errors.New(values...)
|
||||||
|
}
|
@@ -1,33 +1,39 @@
|
|||||||
//Package generate the InbounderConfig used by add inbound
|
// Package controller Package generate the InboundConfig used by add inbound
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd"
|
C "github.com/sagernet/sing/common"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/uuid"
|
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
)
|
)
|
||||||
|
|
||||||
//InboundBuilder build Inbound config for different protocol
|
// InboundBuilder build Inbound config for different protocol
|
||||||
func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
|
func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
|
||||||
inboundDetourConfig := &conf.InboundDetourConfig{}
|
inboundDetourConfig := &conf.InboundDetourConfig{}
|
||||||
// Build Listen IP address
|
// Build Listen IP address
|
||||||
if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||||
// Shdowsocks listen in 127.0.0.1 for safety
|
// Shdowsocks listen in 127.0.0.1 for safety
|
||||||
inboundDetourConfig.ListenOn = &conf.Address{net.ParseAddress("127.0.0.1")}
|
inboundDetourConfig.ListenOn = &conf.Address{Address: net.ParseAddress("127.0.0.1")}
|
||||||
} else if config.ListenIP != "" {
|
} else if config.ListenIP != "" {
|
||||||
ipAddress := net.ParseAddress(config.ListenIP)
|
ipAddress := net.ParseAddress(config.ListenIP)
|
||||||
inboundDetourConfig.ListenOn = &conf.Address{ipAddress}
|
inboundDetourConfig.ListenOn = &conf.Address{Address: ipAddress}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Port
|
// Build Port
|
||||||
portList := &conf.PortList{
|
portList := &conf.PortList{
|
||||||
Range: []conf.PortRange{{From: uint32(nodeInfo.Port), To: uint32(nodeInfo.Port)}},
|
Range: []conf.PortRange{{From: nodeInfo.Port, To: nodeInfo.Port}},
|
||||||
}
|
}
|
||||||
inboundDetourConfig.PortList = portList
|
inboundDetourConfig.PortList = portList
|
||||||
// Build Tag
|
// Build Tag
|
||||||
@@ -48,9 +54,10 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
setting json.RawMessage
|
setting json.RawMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
var proxySetting interface{}
|
var proxySetting any
|
||||||
// Build Protocol and Protocol setting
|
// Build Protocol and Protocol setting
|
||||||
if nodeInfo.NodeType == "V2ray" {
|
switch nodeInfo.NodeType {
|
||||||
|
case "V2ray":
|
||||||
if nodeInfo.EnableVless {
|
if nodeInfo.EnableVless {
|
||||||
protocol = "vless"
|
protocol = "vless"
|
||||||
// Enable fallback
|
// Enable fallback
|
||||||
@@ -73,7 +80,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
protocol = "vmess"
|
protocol = "vmess"
|
||||||
proxySetting = &conf.VMessInboundConfig{}
|
proxySetting = &conf.VMessInboundConfig{}
|
||||||
}
|
}
|
||||||
} else if nodeInfo.NodeType == "Trojan" {
|
case "Trojan":
|
||||||
protocol = "trojan"
|
protocol = "trojan"
|
||||||
// Enable fallback
|
// Enable fallback
|
||||||
if config.EnableFallback {
|
if config.EnableFallback {
|
||||||
@@ -88,23 +95,36 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
} else {
|
} else {
|
||||||
proxySetting = &conf.TrojanServerConfig{}
|
proxySetting = &conf.TrojanServerConfig{}
|
||||||
}
|
}
|
||||||
} else if nodeInfo.NodeType == "Shadowsocks" || nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
case "Shadowsocks", "Shadowsocks-Plugin":
|
||||||
protocol = "shadowsocks"
|
protocol = "shadowsocks"
|
||||||
proxySetting = &conf.ShadowsocksServerConfig{}
|
cipher := strings.ToLower(nodeInfo.CypherMethod)
|
||||||
randomPasswd := uuid.New()
|
|
||||||
defaultSSuser := &conf.ShadowsocksUserConfig{
|
proxySetting = &conf.ShadowsocksServerConfig{
|
||||||
Cipher: "aes-128-gcm",
|
Cipher: cipher,
|
||||||
Password: randomPasswd.String(),
|
Password: nodeInfo.ServerKey, // shadowsocks2022 shareKey
|
||||||
}
|
}
|
||||||
|
|
||||||
proxySetting, _ := proxySetting.(*conf.ShadowsocksServerConfig)
|
proxySetting, _ := proxySetting.(*conf.ShadowsocksServerConfig)
|
||||||
proxySetting.Users = append(proxySetting.Users, defaultSSuser)
|
// shadowsocks must have a random password
|
||||||
|
// shadowsocks2022's password == user PSK, thus should a length of string >= 32 and base64 encoder
|
||||||
|
b := make([]byte, 32)
|
||||||
|
rand.Read(b)
|
||||||
|
randPasswd := hex.EncodeToString(b)
|
||||||
|
if C.Contains(shadowaead_2022.List, cipher) {
|
||||||
|
proxySetting.Users = append(proxySetting.Users, &conf.ShadowsocksUserConfig{
|
||||||
|
Password: base64.StdEncoding.EncodeToString(b),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
proxySetting.Password = randPasswd
|
||||||
|
}
|
||||||
|
|
||||||
proxySetting.NetworkList = &conf.NetworkList{"tcp", "udp"}
|
proxySetting.NetworkList = &conf.NetworkList{"tcp", "udp"}
|
||||||
proxySetting.IVCheck = true
|
proxySetting.IVCheck = true
|
||||||
if config.DisableIVCheck {
|
if config.DisableIVCheck {
|
||||||
proxySetting.IVCheck = false
|
proxySetting.IVCheck = false
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if nodeInfo.NodeType == "dokodemo-door" {
|
case "dokodemo-door":
|
||||||
protocol = "dokodemo-door"
|
protocol = "dokodemo-door"
|
||||||
proxySetting = struct {
|
proxySetting = struct {
|
||||||
Host string `json:"address"`
|
Host string `json:"address"`
|
||||||
@@ -113,14 +133,16 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
Host: "v1.mux.cool",
|
Host: "v1.mux.cool",
|
||||||
NetworkList: []string{"tcp", "udp"},
|
NetworkList: []string{"tcp", "udp"},
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
return nil, fmt.Errorf("Unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
setting, err := json.Marshal(proxySetting)
|
setting, err := json.Marshal(proxySetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||||
}
|
}
|
||||||
|
inboundDetourConfig.Protocol = protocol
|
||||||
|
inboundDetourConfig.Settings = &setting
|
||||||
|
|
||||||
// Build streamSettings
|
// Build streamSettings
|
||||||
streamSetting = new(conf.StreamConfig)
|
streamSetting = new(conf.StreamConfig)
|
||||||
@@ -129,13 +151,15 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("convert TransportProtocol failed: %s", err)
|
return nil, fmt.Errorf("convert TransportProtocol failed: %s", err)
|
||||||
}
|
}
|
||||||
if networkType == "tcp" {
|
|
||||||
|
switch networkType {
|
||||||
|
case "tcp":
|
||||||
tcpSetting := &conf.TCPConfig{
|
tcpSetting := &conf.TCPConfig{
|
||||||
AcceptProxyProtocol: config.EnableProxyProtocol,
|
AcceptProxyProtocol: config.EnableProxyProtocol,
|
||||||
HeaderConfig: nodeInfo.Header,
|
HeaderConfig: nodeInfo.Header,
|
||||||
}
|
}
|
||||||
streamSetting.TCPSettings = tcpSetting
|
streamSetting.TCPSettings = tcpSetting
|
||||||
} else if networkType == "websocket" {
|
case "websocket":
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
headers["Host"] = nodeInfo.Host
|
headers["Host"] = nodeInfo.Host
|
||||||
wsSettings := &conf.WebSocketConfig{
|
wsSettings := &conf.WebSocketConfig{
|
||||||
@@ -144,14 +168,14 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
Headers: headers,
|
Headers: headers,
|
||||||
}
|
}
|
||||||
streamSetting.WSSettings = wsSettings
|
streamSetting.WSSettings = wsSettings
|
||||||
} else if networkType == "http" {
|
case "http":
|
||||||
hosts := conf.StringList{nodeInfo.Host}
|
hosts := conf.StringList{nodeInfo.Host}
|
||||||
httpSettings := &conf.HTTPConfig{
|
httpSettings := &conf.HTTPConfig{
|
||||||
Host: &hosts,
|
Host: &hosts,
|
||||||
Path: nodeInfo.Path,
|
Path: nodeInfo.Path,
|
||||||
}
|
}
|
||||||
streamSetting.HTTPSettings = httpSettings
|
streamSetting.HTTPSettings = httpSettings
|
||||||
} else if networkType == "grpc" {
|
case "grpc":
|
||||||
grpcSettings := &conf.GRPCConfig{
|
grpcSettings := &conf.GRPCConfig{
|
||||||
ServiceName: nodeInfo.ServiceName,
|
ServiceName: nodeInfo.ServiceName,
|
||||||
}
|
}
|
||||||
@@ -159,6 +183,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
streamSetting.Network = &transportProtocol
|
streamSetting.Network = &transportProtocol
|
||||||
|
|
||||||
// Build TLS and XTLS settings
|
// Build TLS and XTLS settings
|
||||||
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
||||||
streamSetting.Security = nodeInfo.TLSType
|
streamSetting.Security = nodeInfo.TLSType
|
||||||
@@ -181,6 +206,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
streamSetting.XTLSSettings = xtlsSettings
|
streamSetting.XTLSSettings = xtlsSettings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support ProxyProtocol for any transport protocol
|
// Support ProxyProtocol for any transport protocol
|
||||||
if networkType != "tcp" && networkType != "ws" && config.EnableProxyProtocol {
|
if networkType != "tcp" && networkType != "ws" && config.EnableProxyProtocol {
|
||||||
sockoptConfig := &conf.SocketConfig{
|
sockoptConfig := &conf.SocketConfig{
|
||||||
@@ -188,60 +214,59 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
}
|
}
|
||||||
streamSetting.SocketSettings = sockoptConfig
|
streamSetting.SocketSettings = sockoptConfig
|
||||||
}
|
}
|
||||||
inboundDetourConfig.Protocol = protocol
|
|
||||||
inboundDetourConfig.StreamSetting = streamSetting
|
inboundDetourConfig.StreamSetting = streamSetting
|
||||||
inboundDetourConfig.Settings = &setting
|
|
||||||
|
|
||||||
return inboundDetourConfig.Build()
|
return inboundDetourConfig.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCertFile(certConfig *CertConfig) (certFile string, keyFile string, err error) {
|
func getCertFile(certConfig *mylego.CertConfig) (certFile string, keyFile string, err error) {
|
||||||
if certConfig.CertMode == "file" {
|
switch certConfig.CertMode {
|
||||||
|
case "file":
|
||||||
if certConfig.CertFile == "" || certConfig.KeyFile == "" {
|
if certConfig.CertFile == "" || certConfig.KeyFile == "" {
|
||||||
return "", "", fmt.Errorf("Cert file path or key file path not exist")
|
return "", "", fmt.Errorf("cert file path or key file path not exist")
|
||||||
}
|
}
|
||||||
return certConfig.CertFile, certConfig.KeyFile, nil
|
return certConfig.CertFile, certConfig.KeyFile, nil
|
||||||
} else if certConfig.CertMode == "dns" {
|
case "dns":
|
||||||
lego, err := legocmd.New()
|
lego, err := mylego.New(certConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
certPath, keyPath, err := lego.DNSCert(certConfig.CertDomain, certConfig.Email, certConfig.Provider, certConfig.DNSEnv)
|
certPath, keyPath, err := lego.DNSCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return certPath, keyPath, err
|
return certPath, keyPath, err
|
||||||
} else if certConfig.CertMode == "http" {
|
case "http", "tls":
|
||||||
lego, err := legocmd.New()
|
lego, err := mylego.New(certConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
certPath, keyPath, err := lego.HTTPCert(certConfig.CertDomain, certConfig.Email)
|
certPath, keyPath, err := lego.HTTPCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return certPath, keyPath, err
|
return certPath, keyPath, err
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", fmt.Errorf("Unsupported certmode: %s", certConfig.CertMode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboundFallback, error) {
|
func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboundFallback, error) {
|
||||||
if fallbackConfigs == nil {
|
if fallbackConfigs == nil {
|
||||||
return nil, fmt.Errorf("You must provide FallBackConfigs")
|
return nil, fmt.Errorf("you must provide FallBackConfigs")
|
||||||
}
|
}
|
||||||
|
|
||||||
vlessFallBacks := make([]*conf.VLessInboundFallback, len(fallbackConfigs))
|
vlessFallBacks := make([]*conf.VLessInboundFallback, len(fallbackConfigs))
|
||||||
for i, c := range fallbackConfigs {
|
for i, c := range fallbackConfigs {
|
||||||
|
|
||||||
if c.Dest == "" {
|
if c.Dest == "" {
|
||||||
return nil, fmt.Errorf("Dest is required for fallback fialed")
|
return nil, fmt.Errorf("dest is required for fallback fialed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest json.RawMessage
|
var dest json.RawMessage
|
||||||
dest, err := json.Marshal(c.Dest)
|
dest, err := json.Marshal(c.Dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
|
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
||||||
}
|
}
|
||||||
vlessFallBacks[i] = &conf.VLessInboundFallback{
|
vlessFallBacks[i] = &conf.VLessInboundFallback{
|
||||||
Name: c.SNI,
|
Name: c.SNI,
|
||||||
@@ -256,20 +281,20 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun
|
|||||||
|
|
||||||
func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInboundFallback, error) {
|
func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInboundFallback, error) {
|
||||||
if fallbackConfigs == nil {
|
if fallbackConfigs == nil {
|
||||||
return nil, fmt.Errorf("You must provide FallBackConfigs")
|
return nil, fmt.Errorf("you must provide FallBackConfigs")
|
||||||
}
|
}
|
||||||
|
|
||||||
trojanFallBacks := make([]*conf.TrojanInboundFallback, len(fallbackConfigs))
|
trojanFallBacks := make([]*conf.TrojanInboundFallback, len(fallbackConfigs))
|
||||||
for i, c := range fallbackConfigs {
|
for i, c := range fallbackConfigs {
|
||||||
|
|
||||||
if c.Dest == "" {
|
if c.Dest == "" {
|
||||||
return nil, fmt.Errorf("Dest is required for fallback fialed")
|
return nil, fmt.Errorf("dest is required for fallback fialed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest json.RawMessage
|
var dest json.RawMessage
|
||||||
dest, err := json.Marshal(c.Dest)
|
dest, err := json.Marshal(c.Dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
|
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
||||||
}
|
}
|
||||||
trojanFallBacks[i] = &conf.TrojanInboundFallback{
|
trojanFallBacks[i] = &conf.TrojanInboundFallback{
|
||||||
Name: c.SNI,
|
Name: c.SNI,
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
. "github.com/XrayR-project/XrayR/service/controller"
|
. "github.com/XrayR-project/XrayR/service/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ func TestBuildV2ray(t *testing.T) {
|
|||||||
EnableTLS: false,
|
EnableTLS: false,
|
||||||
TLSType: "tls",
|
TLSType: "tls",
|
||||||
}
|
}
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "http",
|
CertMode: "http",
|
||||||
CertDomain: "test.test.tk",
|
CertDomain: "test.test.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
@@ -29,7 +30,7 @@ func TestBuildV2ray(t *testing.T) {
|
|||||||
config := &Config{
|
config := &Config{
|
||||||
CertConfig: certConfig,
|
CertConfig: certConfig,
|
||||||
}
|
}
|
||||||
_, err := InboundBuilder(config, nodeInfo)
|
_, err := InboundBuilder(config, nodeInfo, "test_tag")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ func TestBuildTrojan(t *testing.T) {
|
|||||||
DNSEnv := make(map[string]string)
|
DNSEnv := make(map[string]string)
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "dns",
|
CertMode: "dns",
|
||||||
CertDomain: "trojan.test.tk",
|
CertDomain: "trojan.test.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
@@ -61,7 +62,7 @@ func TestBuildTrojan(t *testing.T) {
|
|||||||
config := &Config{
|
config := &Config{
|
||||||
CertConfig: certConfig,
|
CertConfig: certConfig,
|
||||||
}
|
}
|
||||||
_, err := InboundBuilder(config, nodeInfo)
|
_, err := InboundBuilder(config, nodeInfo, "test_tag")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,7 @@ func TestBuildSS(t *testing.T) {
|
|||||||
DNSEnv := make(map[string]string)
|
DNSEnv := make(map[string]string)
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "dns",
|
CertMode: "dns",
|
||||||
CertDomain: "trojan.test.tk",
|
CertDomain: "trojan.test.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
@@ -93,7 +94,7 @@ func TestBuildSS(t *testing.T) {
|
|||||||
config := &Config{
|
config := &Config{
|
||||||
CertConfig: certConfig,
|
CertConfig: certConfig,
|
||||||
}
|
}
|
||||||
_, err := InboundBuilder(config, nodeInfo)
|
_, err := InboundBuilder(config, nodeInfo, "test_tag")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,14 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
//OutboundBuilder build freedom outbund config for addoutbound
|
// OutboundBuilder build freedom outbound config for addOutbound
|
||||||
func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.OutboundHandlerConfig, error) {
|
func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.OutboundHandlerConfig, error) {
|
||||||
outboundDetourConfig := &conf.OutboundDetourConfig{}
|
outboundDetourConfig := &conf.OutboundDetourConfig{}
|
||||||
outboundDetourConfig.Protocol = "freedom"
|
outboundDetourConfig.Protocol = "freedom"
|
||||||
@@ -19,11 +20,11 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
|
|||||||
// Build Send IP address
|
// Build Send IP address
|
||||||
if config.SendIP != "" {
|
if config.SendIP != "" {
|
||||||
ipAddress := net.ParseAddress(config.SendIP)
|
ipAddress := net.ParseAddress(config.SendIP)
|
||||||
outboundDetourConfig.SendThrough = &conf.Address{ipAddress}
|
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freedom Protocol setting
|
// Freedom Protocol setting
|
||||||
var domainStrategy string = "Asis"
|
var domainStrategy = "Asis"
|
||||||
if config.EnableDNS {
|
if config.EnableDNS {
|
||||||
if config.DNSType != "" {
|
if config.DNSType != "" {
|
||||||
domainStrategy = config.DNSType
|
domainStrategy = config.DNSType
|
||||||
@@ -41,7 +42,7 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
|
|||||||
var setting json.RawMessage
|
var setting json.RawMessage
|
||||||
setting, err := json.Marshal(proxySetting)
|
setting, err := json.Marshal(proxySetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||||
}
|
}
|
||||||
outboundDetourConfig.Settings = &setting
|
outboundDetourConfig.Settings = &setting
|
||||||
return outboundDetourConfig.Build()
|
return outboundDetourConfig.Build()
|
||||||
|
@@ -1,19 +1,29 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
|
C "github.com/sagernet/sing/common"
|
||||||
"github.com/xtls/xray-core/common/protocol"
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
"github.com/xtls/xray-core/common/serial"
|
||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/proxy/shadowsocks"
|
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||||
|
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||||
"github.com/xtls/xray-core/proxy/trojan"
|
"github.com/xtls/xray-core/proxy/trojan"
|
||||||
"github.com/xtls/xray-core/proxy/vless"
|
"github.com/xtls/xray-core/proxy/vless"
|
||||||
|
|
||||||
|
"github.com/XrayR-project/XrayR/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AEADMethod = []shadowsocks.CipherType{shadowsocks.CipherType_AES_128_GCM, shadowsocks.CipherType_AES_256_GCM, shadowsocks.CipherType_CHACHA20_POLY1305, shadowsocks.CipherType_XCHACHA20_POLY1305}
|
var AEADMethod = map[shadowsocks.CipherType]uint8{
|
||||||
|
shadowsocks.CipherType_AES_128_GCM: 0,
|
||||||
|
shadowsocks.CipherType_AES_256_GCM: 0,
|
||||||
|
shadowsocks.CipherType_CHACHA20_POLY1305: 0,
|
||||||
|
shadowsocks.CipherType_XCHACHA20_POLY1305: 0,
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) buildVmessUser(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) {
|
func (c *Controller) buildVmessUser(userInfo *[]api.UserInfo, serverAlterID uint16) (users []*protocol.User) {
|
||||||
users = make([]*protocol.User, len(*userInfo))
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
@@ -65,43 +75,65 @@ func (c *Controller) buildTrojanUser(userInfo *[]api.UserInfo) (users []*protoco
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) buildSSUser(userInfo *[]api.UserInfo, method string) (users []*protocol.User) {
|
func (c *Controller) buildSSUser(userInfo *[]api.UserInfo, method string) (users []*protocol.User) {
|
||||||
users = make([]*protocol.User, 0)
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
|
|
||||||
cypherMethod := cipherFromString(method)
|
for i, user := range *userInfo {
|
||||||
for _, user := range *userInfo {
|
// // shadowsocks2022 Key = openssl rand -base64 32 and multi users needn't cipher method
|
||||||
ssAccount := &shadowsocks.Account{
|
if C.Contains(shadowaead_2022.List, strings.ToLower(method)) {
|
||||||
Password: user.Passwd,
|
e := c.buildUserTag(&user)
|
||||||
CipherType: cypherMethod,
|
users[i] = &protocol.User{
|
||||||
|
Level: 0,
|
||||||
|
Email: e,
|
||||||
|
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||||
|
Key: base64.StdEncoding.EncodeToString([]byte(user.Passwd)),
|
||||||
|
Email: e,
|
||||||
|
Level: 0,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
users = append(users, &protocol.User{
|
} else {
|
||||||
|
users[i] = &protocol.User{
|
||||||
Level: 0,
|
Level: 0,
|
||||||
Email: c.buildUserTag(&user),
|
Email: c.buildUserTag(&user),
|
||||||
Account: serial.ToTypedMessage(ssAccount),
|
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||||
})
|
Password: user.Passwd,
|
||||||
|
CipherType: cipherFromString(method),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) buildSSPluginUser(userInfo *[]api.UserInfo) (users []*protocol.User) {
|
func (c *Controller) buildSSPluginUser(userInfo *[]api.UserInfo) (users []*protocol.User) {
|
||||||
users = make([]*protocol.User, 0)
|
users = make([]*protocol.User, len(*userInfo))
|
||||||
|
|
||||||
for _, user := range *userInfo {
|
for i, user := range *userInfo {
|
||||||
|
// 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)
|
||||||
|
users[i] = &protocol.User{
|
||||||
|
Level: 0,
|
||||||
|
Email: e,
|
||||||
|
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||||
|
Key: base64.StdEncoding.EncodeToString([]byte(user.Passwd)),
|
||||||
|
Email: e,
|
||||||
|
Level: 0,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// Check if the cypher method is AEAD
|
// Check if the cypher method is AEAD
|
||||||
cypherMethod := cipherFromString(user.Method)
|
cypherMethod := cipherFromString(user.Method)
|
||||||
for _, aeadMethod := range AEADMethod {
|
if _, ok := AEADMethod[cypherMethod]; ok {
|
||||||
if aeadMethod == cypherMethod {
|
users[i] = &protocol.User{
|
||||||
ssAccount := &shadowsocks.Account{
|
|
||||||
Password: user.Passwd,
|
|
||||||
CipherType: cypherMethod,
|
|
||||||
}
|
|
||||||
users = append(users, &protocol.User{
|
|
||||||
Level: 0,
|
Level: 0,
|
||||||
Email: c.buildUserTag(&user),
|
Email: c.buildUserTag(&user),
|
||||||
Account: serial.ToTypedMessage(ssAccount),
|
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||||
})
|
Password: user.Passwd,
|
||||||
|
CipherType: cypherMethod,
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
// Package service contains all the services used by XrayR
|
// Package service contains all the services used by XrayR
|
||||||
// To implement an service, one needs to implement the interface below.
|
// To implement a service, one needs to implement the interface below.
|
||||||
package service
|
package service
|
||||||
|
|
||||||
// Service is the interface of all the services running in the panel
|
// Service is the interface of all the services running in the panel
|
||||||
|
Reference in New Issue
Block a user