mirror of
https://github.com/XrayR-project/XrayR.git
synced 2025-07-23 19:37:56 +00:00
Compare commits
281 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0254c6c557 | ||
![]() |
4c52e33adb | ||
![]() |
b8d40c201b | ||
![]() |
dbd4a85a6c | ||
![]() |
8f28716b21 | ||
![]() |
73bc37cb51 | ||
![]() |
78f2f88296 | ||
![]() |
42a2226769 | ||
![]() |
71aba0601e | ||
![]() |
5fe18e020d | ||
![]() |
a0f2730bb2 | ||
![]() |
0c10c59877 | ||
![]() |
119e4810f2 | ||
![]() |
5dad910488 | ||
![]() |
8841e55f70 | ||
![]() |
5d20732881 | ||
![]() |
d579933451 | ||
![]() |
46e836f93b | ||
![]() |
e27b0c6cd8 | ||
![]() |
e34a3b4a94 | ||
![]() |
b366171401 | ||
![]() |
b034a2d48f | ||
![]() |
65b25ed3f6 | ||
![]() |
b65dfd7f92 | ||
![]() |
6569a0bf36 | ||
![]() |
e5e5e4ef92 | ||
![]() |
f35a056cc6 | ||
![]() |
73ffd3f505 | ||
![]() |
73c5e28f41 | ||
![]() |
f1b45c02f4 | ||
![]() |
941f256ba5 | ||
![]() |
2e547afdb7 | ||
![]() |
422ed1a311 | ||
![]() |
214e412993 | ||
![]() |
f85f6b47ee | ||
![]() |
7e09aef1cc | ||
![]() |
0b4caba8f6 | ||
![]() |
b0866011e5 | ||
![]() |
4d1d89b837 | ||
![]() |
0febf96021 | ||
![]() |
0f10e837e4 | ||
![]() |
5ab352f9c9 | ||
![]() |
5d5470a919 | ||
![]() |
db27722bbc | ||
![]() |
914510c687 | ||
![]() |
a8226b01e2 | ||
![]() |
54a958f39c | ||
![]() |
a1e407e18f | ||
![]() |
42da6c155d | ||
![]() |
89f2342a42 | ||
![]() |
ee1a606888 | ||
![]() |
c183c6492e | ||
![]() |
a70a0d9a31 | ||
![]() |
4c651e15fa | ||
![]() |
dda00c5dd6 | ||
![]() |
28e1b82320 | ||
![]() |
f1ab2eac13 | ||
![]() |
4f13aac094 | ||
![]() |
4ab196ad29 | ||
![]() |
9590697c29 | ||
![]() |
0bec6c4fdf | ||
![]() |
223589ba14 | ||
![]() |
6ba7fe2776 | ||
![]() |
b49798ab16 | ||
![]() |
af9224f5bb | ||
![]() |
c54650f195 | ||
![]() |
c73af2309b | ||
![]() |
169f742b76 | ||
![]() |
0fa8a45e51 | ||
![]() |
17c538a5b7 | ||
![]() |
5625d570fd | ||
![]() |
9b0e55f037 | ||
![]() |
4597d6ac56 | ||
![]() |
9aaad5e8ad | ||
![]() |
94d82f33ea | ||
![]() |
49aa520d67 | ||
![]() |
73c3047651 | ||
![]() |
613da96543 | ||
![]() |
76127f4757 | ||
![]() |
1d5a34cf98 | ||
![]() |
bf13971502 | ||
![]() |
572be38ae4 | ||
![]() |
34d96c9338 | ||
![]() |
4e69c0656e | ||
![]() |
88f7709fad | ||
![]() |
f95825395d | ||
![]() |
e279d39d5a | ||
![]() |
74038add93 | ||
![]() |
0a2b8612dc | ||
![]() |
5923018a9d | ||
![]() |
239a0eea78 | ||
![]() |
456951155d | ||
![]() |
a7a3d0220d | ||
![]() |
b857bfac3e | ||
![]() |
f4ea59493f | ||
![]() |
957b439ced | ||
![]() |
19583503cd | ||
![]() |
d8a3242e22 | ||
![]() |
007d9a0717 | ||
![]() |
016051892b | ||
![]() |
5764c3e610 | ||
![]() |
5bacee35aa | ||
![]() |
e6b6abee24 | ||
![]() |
edddd2f965 | ||
![]() |
0cd17f2b55 | ||
![]() |
3838b2d333 | ||
![]() |
7a90f157b8 | ||
![]() |
a10efcaea0 | ||
![]() |
1f85ce3762 | ||
![]() |
eed0b8ae32 | ||
![]() |
ea2f7a64e1 | ||
![]() |
ad8243fcf5 | ||
![]() |
66fccfd422 | ||
![]() |
448d44ff46 | ||
![]() |
891068a091 | ||
![]() |
127ccef619 | ||
![]() |
84bc47ba18 | ||
![]() |
a139d48959 | ||
![]() |
180a4af7ca | ||
![]() |
8512c354f5 | ||
![]() |
4746bd57d0 | ||
![]() |
6477ef6c44 | ||
![]() |
21e0ebc428 | ||
![]() |
40c65a86fc | ||
![]() |
b5a8f4464c | ||
![]() |
451b5a1fd4 | ||
![]() |
8ddf257aa4 | ||
![]() |
a8742426b3 | ||
![]() |
d44fa11eee | ||
![]() |
0c2d03f3e0 | ||
![]() |
9c6ae5b7ab | ||
![]() |
4c699d86f2 | ||
![]() |
be940fdcea | ||
![]() |
619a455432 | ||
![]() |
03b7bf6ed1 | ||
![]() |
224224084e | ||
![]() |
ac460c2f71 | ||
![]() |
e1b512ef5b | ||
![]() |
d5ab69d703 | ||
![]() |
d9bbb836b8 | ||
![]() |
1de5143fde | ||
![]() |
77814acd1a | ||
![]() |
3926463c59 | ||
![]() |
05ff6dff1b | ||
![]() |
1d5d4564e5 | ||
![]() |
0777cc8892 | ||
![]() |
b09f371447 | ||
![]() |
fc71d76f4d | ||
![]() |
443fb959f6 | ||
![]() |
040e19ee87 | ||
![]() |
addde0f8d4 | ||
![]() |
9ef39925fd | ||
![]() |
a6dbf757e9 | ||
![]() |
acd3cce667 | ||
![]() |
eea6c0dea6 | ||
![]() |
889160b908 | ||
![]() |
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 |
126
.github/build/friendly-filenames.json
vendored
126
.github/build/friendly-filenames.json
vendored
@@ -1,33 +1,95 @@
|
||||
{
|
||||
"android-arm64": { "friendlyName": "android-arm64-v8a" },
|
||||
"darwin-amd64": { "friendlyName": "macos-64" },
|
||||
"darwin-arm64": { "friendlyName": "macos-arm64-v8a" },
|
||||
"dragonfly-amd64": { "friendlyName": "dragonfly-64" },
|
||||
"freebsd-386": { "friendlyName": "freebsd-32" },
|
||||
"freebsd-amd64": { "friendlyName": "freebsd-64" },
|
||||
"freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" },
|
||||
"freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" },
|
||||
"linux-386": { "friendlyName": "linux-32" },
|
||||
"linux-amd64": { "friendlyName": "linux-64" },
|
||||
"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" }
|
||||
}
|
||||
"android-arm64": {
|
||||
"friendlyName": "android-arm64-v8a"
|
||||
},
|
||||
"darwin-amd64": {
|
||||
"friendlyName": "macos-64"
|
||||
},
|
||||
"darwin-arm64": {
|
||||
"friendlyName": "macos-arm64-v8a"
|
||||
},
|
||||
"dragonfly-amd64": {
|
||||
"friendlyName": "dragonfly-64"
|
||||
},
|
||||
"freebsd-386": {
|
||||
"friendlyName": "freebsd-32"
|
||||
},
|
||||
"freebsd-amd64": {
|
||||
"friendlyName": "freebsd-64"
|
||||
},
|
||||
"freebsd-arm64": {
|
||||
"friendlyName": "freebsd-arm64-v8a"
|
||||
},
|
||||
"freebsd-arm7": {
|
||||
"friendlyName": "freebsd-arm32-v7a"
|
||||
},
|
||||
"linux-386": {
|
||||
"friendlyName": "linux-32"
|
||||
},
|
||||
"linux-amd64": {
|
||||
"friendlyName": "linux-64"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
56
.github/workflows/codeql-analysis.yml
vendored
56
.github/workflows/codeql-analysis.yml
vendored
@@ -34,34 +34,38 @@ jobs:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
109
.github/workflows/docker.yml
vendored
109
.github/workflows/docker.yml
vendored
@@ -4,39 +4,102 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: xrayr-project/xrayr
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64
|
||||
- linux/s390x
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Docker meta
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: crackair/xrayr
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm/v7,linux/arm64,linux/amd64,linux/s390x
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: digests
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: digests
|
||||
path: /tmp/digests
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
ls -al
|
||||
echo docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
|
77
.github/workflows/release.yml
vendored
77
.github/workflows/release.yml
vendored
@@ -11,14 +11,14 @@ on:
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
types: [ opened, synchronize, reopened ]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
release:
|
||||
types: [published]
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -26,8 +26,8 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# Include amd64 on all platforms.
|
||||
goos: [windows, freebsd, openbsd, linux, dragonfly, darwin]
|
||||
goarch: [amd64, 386]
|
||||
goos: [ windows, freebsd, openbsd, linux, dragonfly, darwin ]
|
||||
goarch: [ amd64, 386 ]
|
||||
exclude:
|
||||
# Exclude i386 on darwin and dragonfly.
|
||||
- goarch: 386
|
||||
@@ -74,8 +74,8 @@ jobs:
|
||||
goarch: mips
|
||||
# END MIPS
|
||||
# BEGIN PPC
|
||||
- goos: linux
|
||||
goarch: ppc64
|
||||
# - goos: linux # Removed due to the unsupport of shirou/gopsutil
|
||||
# goarch: ppc64
|
||||
- goos: linux
|
||||
goarch: ppc64le
|
||||
# END PPC
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
# END S390X
|
||||
# END Other architectures
|
||||
fail-fast: false
|
||||
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
@@ -101,19 +101,19 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v2
|
||||
- name: Show workflow information
|
||||
uses: actions/checkout@v3
|
||||
- name: Show workflow information
|
||||
id: get_filename
|
||||
run: |
|
||||
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 "::set-output name=ASSET_NAME::$_NAME"
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.18
|
||||
go-version: ^1.20
|
||||
|
||||
- name: Get project dependencies
|
||||
run: go mod download
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p build_assets
|
||||
go build -v -o build_assets/XrayR -trimpath -ldflags "-s -w -buildid=" ./main
|
||||
|
||||
|
||||
- name: Build Mips softfloat XrayR
|
||||
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
|
||||
run: |
|
||||
@@ -135,27 +135,32 @@ jobs:
|
||||
mv XrayR XrayR.exe
|
||||
|
||||
- name: Prepare to release
|
||||
run: |
|
||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||
cp ${GITHUB_WORKSPACE}/main/dns.json ./build_assets/dns.json
|
||||
cp ${GITHUB_WORKSPACE}/main/route.json ./build_assets/route.json
|
||||
cp ${GITHUB_WORKSPACE}/main/custom_outbound.json ./build_assets/custom_outbound.json
|
||||
cp ${GITHUB_WORKSPACE}/main/custom_inbound.json ./build_assets/custom_inbound.json
|
||||
cp ${GITHUB_WORKSPACE}/main/rulelist ./build_assets/rulelist
|
||||
cp ${GITHUB_WORKSPACE}/main/config.yml.example ./build_assets/config.yml
|
||||
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
||||
for i in "${LIST[@]}"
|
||||
do
|
||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
|
||||
LASTEST_TAG="$(curl -sL "https://api.github.com/repos/v2fly/${INFO[0]}/releases" | jq -r ".[0].tag_name" || echo "latest")"
|
||||
FILE_NAME="${INFO[2]}.dat"
|
||||
echo -e "Downloading ${FILE_NAME}..."
|
||||
curl -L "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat" -o ./build_assets/${FILE_NAME}
|
||||
echo -e "Verifying HASH key..."
|
||||
HASH="$(curl -sL "https://github.com/v2fly/${INFO[0]}/releases/download/${LASTEST_TAG}/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
|
||||
[ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
||||
done
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 60
|
||||
retry_wait_seconds: 60
|
||||
max_attempts: 5
|
||||
command: |
|
||||
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
|
||||
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
|
||||
cp ${GITHUB_WORKSPACE}/main/dns.json ./build_assets/dns.json
|
||||
cp ${GITHUB_WORKSPACE}/main/route.json ./build_assets/route.json
|
||||
cp ${GITHUB_WORKSPACE}/main/custom_outbound.json ./build_assets/custom_outbound.json
|
||||
cp ${GITHUB_WORKSPACE}/main/custom_inbound.json ./build_assets/custom_inbound.json
|
||||
cp ${GITHUB_WORKSPACE}/main/rulelist ./build_assets/rulelist
|
||||
cp ${GITHUB_WORKSPACE}/main/config.yml.example ./build_assets/config.yml
|
||||
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
|
||||
for i in "${LIST[@]}"
|
||||
do
|
||||
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/v2fly/${INFO[0]}/release/${INFO[1]}.dat"
|
||||
FILE_NAME="${INFO[2]}.dat"
|
||||
echo -e "Downloading ${DOWNLOAD_URL}..."
|
||||
curl -L "${DOWNLOAD_URL}" -o ./build_assets/${FILE_NAME}
|
||||
echo -e "Verifying HASH key..."
|
||||
HASH="$(curl -sL "${DOWNLOAD_URL}.sha256sum" | awk -F ' ' '{print $1}')"
|
||||
[ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
|
||||
done
|
||||
- name: Create ZIP archive
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -173,7 +178,7 @@ jobs:
|
||||
run: |
|
||||
mv build_assets XrayR-$ASSET_NAME
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: XrayR-${{ steps.get_filename.outputs.ASSET_NAME }}
|
||||
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 }}
|
20
.github/workflows/sync.yml
vendored
20
.github/workflows/sync.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Sync to Gitlab
|
||||
|
||||
on:
|
||||
push:
|
||||
delete:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
to_gitlab:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pixta-dev/repository-mirroring-action@v1
|
||||
with:
|
||||
target_repo_url:
|
||||
git@gitlab.com:xrayr-project/XrayR.git
|
||||
ssh_private_key:
|
||||
${{ secrets.SSH_PRIVATEKEY }}
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -12,4 +12,7 @@ main/.lego
|
||||
main/cert
|
||||
main/config.yml
|
||||
./vscode
|
||||
.idea/*
|
||||
.idea/*
|
||||
.DS_Store
|
||||
*.bak
|
||||
go.work*
|
@@ -1,5 +1,5 @@
|
||||
# Build go
|
||||
FROM golang:1.18-alpine AS builder
|
||||
FROM golang:1.21-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
ENV CGO_ENABLED=0
|
||||
|
109
README-en.md
Normal file
109
README-en.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# XrayR
|
||||
|
||||
[](https://t.me/XrayR_project)
|
||||
[](https://t.me/XrayR_channel)
|
||||

|
||||

|
||||

|
||||

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

|
||||

|
||||

|
||||

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

|
||||

|
||||

|
||||

|
||||
[]()
|
||||
|
||||
|
||||
[English](https://github.com/XrayR-project/XrayR/blob/master/README-en.md)|[Iranian](https://github.com/XrayR-project/XrayR/blob/master/README_Fa.md)|[Vietnamese](https://github.com/XrayR-project/XrayR/blob/master/README-vi.md)
|
||||
|
||||
A Xray backend framework that can easily support many panels.
|
||||
|
||||
@@ -8,12 +17,15 @@ A Xray backend framework that can easily support many panels.
|
||||
|
||||
如果您喜欢本项目,可以右上角点个star+watch,持续关注本项目的进展。
|
||||
|
||||
使用教程:[详细使用教程](https://crackair.gitbook.io/xrayr-project/)
|
||||
使用教程:[详细使用教程](https://xrayr-project.github.io/XrayR-doc/)
|
||||
|
||||
|
||||
## 免责声明
|
||||
|
||||
本项目只是本人个人学习开发并维护,本人不保证任何可用性,也不对使用本软件造成的任何后果负责。
|
||||
|
||||
## 特点
|
||||
|
||||
* 永久开源且免费。
|
||||
* 支持V2ray,Trojan, Shadowsocks多种协议。
|
||||
* 支持Vless和XTLS等新特性。
|
||||
@@ -26,43 +38,51 @@ A Xray backend framework that can easily support many panels.
|
||||
|
||||
## 功能介绍
|
||||
|
||||
| 功能 | v2ray | trojan | shadowsocks |
|
||||
| --------------- | ----- | ------ | ----------- |
|
||||
| 功能 | v2ray | trojan | shadowsocks |
|
||||
|-----------|-------|--------|-------------|
|
||||
| 获取节点信息 | √ | √ | √ |
|
||||
| 获取用户信息 | √ | √ | √ |
|
||||
| 用户流量统计 | √ | √ | √ |
|
||||
| 服务器信息上报 | √ | √ | √ |
|
||||
| 服务器信息上报 | √ | √ | √ |
|
||||
| 自动申请tls证书 | √ | √ | √ |
|
||||
| 自动续签tls证书 | √ | √ | √ |
|
||||
| 在线人数统计 | √ | √ | √ |
|
||||
| 在线用户限制 | √ | √ | √ |
|
||||
| 审计规则 | √ | √ | √ |
|
||||
| 审计规则 | √ | √ | √ |
|
||||
| 节点端口限速 | √ | √ | √ |
|
||||
| 按照用户限速 | √ | √ | √ |
|
||||
| 自定义DNS | √ | √ | √ |
|
||||
| 自定义DNS | √ | √ | √ |
|
||||
|
||||
## 支持前端
|
||||
|
||||
| 前端 | v2ray | trojan | shadowsocks |
|
||||
| ------------------------------------------------------ | ----- | ------ | ------------------------------ |
|
||||
| 前端 | v2ray | trojan | shadowsocks |
|
||||
|--------------------------------------------------------|-------|--------|-------------------------|
|
||||
| sspanel-uim | √ | √ | √ (单端口多用户和V2ray-Plugin) |
|
||||
| v2board | √ | √ | √ |
|
||||
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||
| v2board | √ | √ | √ |
|
||||
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
||||
| [GoV2Panel](https://github.com/pingProMax/gov2panel) | √ | √ | √ |
|
||||
|
||||
## 软件安装
|
||||
|
||||
### 一键安装
|
||||
|
||||
```
|
||||
wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh && bash install.sh
|
||||
```
|
||||
|
||||
### 使用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
|
||||
|
||||
@@ -80,7 +100,9 @@ wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/ins
|
||||
[XrayR后端讨论](https://t.me/XrayR_project)
|
||||
|
||||
[XrayR通知](https://t.me/XrayR_channel)
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starchart.cc/XrayR-project/XrayR)
|
||||
|
||||
|
||||
|
104
README_Fa.md
Normal file
104
README_Fa.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# XrayR
|
||||
|
||||
[](https://t.me/XrayR_project)
|
||||
[](https://t.me/XrayR_channel)
|
||||

|
||||

|
||||

|
||||

|
||||
[]()
|
||||
|
||||
[Iranian(farsi) README](https://github.com/XrayR-project/XrayR/blob/master/README_Fa.md), [Vietnamese(vi) README](https://github.com/XrayR-project/XrayR/blob/master/README-vi.md), [English(en) README](https://github.com/XrayR-project/XrayR/blob/master/README-en.md)
|
||||
|
||||
یک فریمورک بک اند مبتنی بر xray که از چند از پنل پشتیبانی می کند
|
||||
|
||||
یک چارچوب بکاند مبتنی بر Xray که از پروتکلهای V2ay، Trojan و Shadowsocks پشتیبانی میکند، به راحتی قابل گسترش است و از اتصال چند پنل پشتیبانی میکند.
|
||||
|
||||
اگر این پروژه را دوست دارید، می توانید با کلیک بر روی ستاره+ساعت در گوشه بالا سمت راست به ادامه روند پیشرفت این پروژه توجه کنید.
|
||||
|
||||
آموزش:[اموزش با جزئیات](https://xrayr-project.github.io/XrayR-doc/)
|
||||
|
||||
## سلب مسئولیت
|
||||
|
||||
این پروژه فقط مطالعه، توسعه و نگهداری شخصی من است. من هیچ گونه قابلیت استفاده را تضمین نمی کنم و مسئولیتی در قبال عواقب ناشی از استفاده از این نرم افزار ندارم.
|
||||
## امکانات
|
||||
|
||||
* منبع باز دائمی و رایگان
|
||||
* پشتیبانی از چندین پروتکل V2ray، Trojan، Shadowsocks.
|
||||
* پشتیبانی از ویژگی های جدید مانند Vless و XTLS.
|
||||
* پشتیبانی از اتصال یک نمونه چند پانل، چند گره، بدون نیاز به شروع مکرر.
|
||||
* پشتیبانی محدود IP آنلاین
|
||||
* پشتیبانی از سطح پورت گره، محدودیت سرعت سطح کاربر.
|
||||
* پیکربندی ساده و سرراست است.
|
||||
* پیکربندی را تغییر دهید تا نمونه به طور خودکار راه اندازی مجدد شود.
|
||||
* کامپایل و ارتقاء آن آسان است و می تواند به سرعت نسخه اصلی را به روز کند و از ویژگی های جدید Xray-core پشتیبانی می کند.
|
||||
|
||||
## امکانات
|
||||
|
||||
| امکانات | v2ray | trojan | shadowsocks |
|
||||
|-----------|-------|--------|-------------|
|
||||
| اطلاعات گره را دریافت کنید | √ | √ | √ |
|
||||
| دریافت اطلاعات کاربر | √ | √ | √ |
|
||||
| آمار ترافیک کاربران | √ | √ | √ |
|
||||
| گزارش اطلاعات سرور | √ | √ | √ |
|
||||
| به طور خودکار برای گواهی tls درخواست دهید | √ | √ | √ |
|
||||
| تمدید خودکار گواهی tls | √ | √ | √ |
|
||||
| آمار آنلاین | √ | √ | √ |
|
||||
| محدودیت کاربر آنلاین | √ | √ | √ |
|
||||
| قوانین حسابرسی | √ | √ | √ |
|
||||
| محدودیت سرعت پورت گره | √ | √ | √ |
|
||||
| محدودیت سرعت بر اساس کاربر | √ | √ | √ |
|
||||
| DNS سفارشی | √ | √ | √ |
|
||||
|
||||
## پشتیبانی از قسمت فرانت
|
||||
|
||||
| قسمت فرانت | v2ray | trojan | shadowsocks |
|
||||
|--------------------------------------------------------|-------|--------|-------------------------|
|
||||
| sspanel-uim | √ | √ | √ (تک پورت چند کاربره و V2ray-Plugin) |
|
||||
| v2board | √ | √ | √ |
|
||||
| [PMPanel](https://github.com/ByteInternetHK/PMPanel) | √ | √ | √ |
|
||||
| [ProxyPanel](https://github.com/ProxyPanel/ProxyPanel) | √ | √ | √ |
|
||||
| [WHMCS (V2RaySocks)](https://v2raysocks.doxtex.com/) | √ | √ | √ |
|
||||
|
||||
## نصب نرم افزار
|
||||
|
||||
### نصب بصورت یکپارچه
|
||||
|
||||
```
|
||||
wget -N https://raw.githubusercontent.com/XrayR-project/XrayR-release/master/install.sh && bash install.sh
|
||||
```
|
||||
|
||||
### استقرار نرم افزار با استفاده از Docker
|
||||
|
||||
[آموزش استقرار داکر](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/docker)
|
||||
|
||||
### نصب دستی
|
||||
|
||||
[آموزش نصب دستی](https://xrayr-project.github.io/XrayR-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
|
||||
|
||||
## فایل های پیکربندی و آموزش های با جرئیات
|
||||
|
||||
[آموزش مفصل](https://xrayr-project.github.io/XrayR-doc/)
|
||||
|
||||
## Thanks
|
||||
|
||||
* [Project X](https://github.com/XTLS/)
|
||||
* [V2Fly](https://github.com/v2fly)
|
||||
* [VNet-V2ray](https://github.com/ProxyPanel/VNet-V2ray)
|
||||
* [Air-Universe](https://github.com/crossfw/Air-Universe)
|
||||
|
||||
## Licence
|
||||
|
||||
[Mozilla Public License Version 2.0](https://github.com/XrayR-project/XrayR/blob/master/LICENSE)
|
||||
|
||||
## Telgram
|
||||
|
||||
[بحث در مورد XrayR Backend](https://t.me/XrayR_project)
|
||||
|
||||
[کانال اعلان در مورد XrayR](https://t.me/XrayR_channel)
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starchart.cc/XrayR-project/XrayR)
|
||||
|
||||
|
@@ -3,16 +3,24 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
)
|
||||
|
||||
// API config
|
||||
const (
|
||||
UserNotModified = "users not modified"
|
||||
NodeNotModified = "node not modified"
|
||||
RuleNotModified = "rules not modified"
|
||||
)
|
||||
|
||||
// Config API config
|
||||
type Config struct {
|
||||
APIHost string `mapstructure:"ApiHost"`
|
||||
NodeID int `mapstructure:"NodeID"`
|
||||
Key string `mapstructure:"ApiKey"`
|
||||
NodeType string `mapstructure:"NodeType"`
|
||||
EnableVless bool `mapstructure:"EnableVless"`
|
||||
EnableXTLS bool `mapstructure:"EnableXTLS"`
|
||||
VlessFlow string `mapstructure:"VlessFlow"`
|
||||
Timeout int `mapstructure:"Timeout"`
|
||||
SpeedLimit float64 `mapstructure:"SpeedLimit"`
|
||||
DeviceLimit int `mapstructure:"DeviceLimit"`
|
||||
@@ -20,46 +28,46 @@ type Config struct {
|
||||
DisableCustomConfig bool `mapstructure:"DisableCustomConfig"`
|
||||
}
|
||||
|
||||
// Node status
|
||||
// NodeStatus Node status
|
||||
type NodeStatus struct {
|
||||
CPU float64
|
||||
Mem float64
|
||||
Disk float64
|
||||
Uptime int
|
||||
Uptime uint64
|
||||
}
|
||||
|
||||
type NodeInfo struct {
|
||||
NodeType string // Must be V2ray, Trojan, and Shadowsocks
|
||||
NodeID int
|
||||
Port int
|
||||
Port uint32
|
||||
SpeedLimit uint64 // Bps
|
||||
AlterID int
|
||||
AlterID uint16
|
||||
TransportProtocol string
|
||||
FakeType string
|
||||
Host string
|
||||
Path string
|
||||
EnableTLS bool
|
||||
TLSType string
|
||||
EnableVless bool
|
||||
VlessFlow string
|
||||
CypherMethod string
|
||||
ServerKey string
|
||||
ServiceName string
|
||||
Header json.RawMessage
|
||||
NameServerConfig []*conf.NameServerConfig
|
||||
EnableREALITY bool
|
||||
REALITYConfig *REALITYConfig
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
UID int
|
||||
Email string
|
||||
Passwd string
|
||||
Port int
|
||||
Method string
|
||||
SpeedLimit uint64 // Bps
|
||||
DeviceLimit int
|
||||
Protocol string
|
||||
ProtocolParam string
|
||||
Obfs string
|
||||
ObfsParam string
|
||||
UUID string
|
||||
AlterID int
|
||||
UID int
|
||||
Email string
|
||||
UUID string
|
||||
Passwd string
|
||||
Port uint32
|
||||
AlterID uint16
|
||||
Method string
|
||||
SpeedLimit uint64 // Bps
|
||||
DeviceLimit int
|
||||
}
|
||||
|
||||
type OnlineUser struct {
|
||||
@@ -90,3 +98,14 @@ type DetectResult struct {
|
||||
UID int
|
||||
RuleID int
|
||||
}
|
||||
|
||||
type REALITYConfig struct {
|
||||
Dest string
|
||||
ProxyProtocolVer uint64
|
||||
ServerNames []string
|
||||
PrivateKey string
|
||||
MinClientVer string
|
||||
MaxClientVer string
|
||||
MaxTimeDiff uint64
|
||||
ShortIds []string
|
||||
}
|
||||
|
402
api/gov2panel/gov2panel.go
Normal file
402
api/gov2panel/gov2panel.go
Normal file
@@ -0,0 +1,402 @@
|
||||
package gov2panel
|
||||
|
||||
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/gogf/gf/v2/util/gconv"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
|
||||
"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
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
resp atomic.Value
|
||||
eTags map[string]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,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
eTags: make(map[string]string),
|
||||
}
|
||||
return apiClient
|
||||
}
|
||||
|
||||
// readLocalRuleList reads the local rule list file
|
||||
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
LocalRuleList = make([]api.DetectRule, 0)
|
||||
|
||||
if path != "" {
|
||||
// open the file
|
||||
file, err := os.Open(path)
|
||||
defer file.Close()
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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: %v", c.assembleURL(path), err)
|
||||
}
|
||||
|
||||
if res.StatusCode() > 399 {
|
||||
return nil, fmt.Errorf("request %s failed: %s, %v", c.assembleURL(path), res.String(), err)
|
||||
}
|
||||
|
||||
rtn, err := simplejson.NewJson(res.Body())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ret %s invalid", res.String())
|
||||
}
|
||||
|
||||
return rtn, nil
|
||||
}
|
||||
|
||||
// GetNodeInfo will pull NodeInfo Config from panel
|
||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
server := new(serverConfig)
|
||||
path := "/api/server/config"
|
||||
|
||||
res, err := c.client.R().
|
||||
SetHeader("If-None-Match", c.eTags["node"]).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
|
||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||
if res.StatusCode() == 304 {
|
||||
return nil, errors.New(api.NodeNotModified)
|
||||
}
|
||||
// update etag
|
||||
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["node"] {
|
||||
c.eTags["node"] = res.Header().Get("Etag")
|
||||
}
|
||||
|
||||
nodeInfoResp, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, _ := nodeInfoResp.Encode()
|
||||
json.Unmarshal(b, server)
|
||||
|
||||
if gconv.Uint32(server.Port) == 0 {
|
||||
return nil, errors.New("server port must > 0")
|
||||
}
|
||||
|
||||
c.resp.Store(server)
|
||||
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
nodeInfo, err = c.parseV2rayNodeResponse(server)
|
||||
case "Trojan":
|
||||
nodeInfo, err = c.parseTrojanNodeResponse(server)
|
||||
case "Shadowsocks":
|
||||
nodeInfo, err = c.parseSSNodeResponse(server)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse node info failed: %s, \nError: %v", res.String(), err)
|
||||
}
|
||||
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// GetUserList will pull user form panel
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
var users []*user
|
||||
path := "/api/server/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.eTags["users"]).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
|
||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||
if res.StatusCode() == 304 {
|
||||
return nil, errors.New(api.UserNotModified)
|
||||
}
|
||||
// update etag
|
||||
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["users"] {
|
||||
c.eTags["users"] = res.Header().Get("Etag")
|
||||
}
|
||||
|
||||
usersResp, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, _ := usersResp.Get("users").Encode()
|
||||
json.Unmarshal(b, &users)
|
||||
if len(users) == 0 {
|
||||
return nil, errors.New("users is null")
|
||||
}
|
||||
|
||||
userList := make([]api.UserInfo, len(users))
|
||||
for i := 0; i < len(users); i++ {
|
||||
u := api.UserInfo{
|
||||
UID: users[i].Id,
|
||||
UUID: users[i].Uuid,
|
||||
}
|
||||
|
||||
// Support 1.7.1 speed limit
|
||||
if c.SpeedLimit > 0 {
|
||||
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
|
||||
} else {
|
||||
u.SpeedLimit = uint64(users[i].SpeedLimit * 1000000 / 8)
|
||||
}
|
||||
|
||||
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
|
||||
u.Email = u.UUID + "@gov2panel.user"
|
||||
if c.NodeType == "Shadowsocks" {
|
||||
u.Passwd = u.UUID
|
||||
}
|
||||
userList[i] = u
|
||||
}
|
||||
|
||||
return &userList, nil
|
||||
}
|
||||
|
||||
// ReportUserTraffic reports the user traffic
|
||||
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
path := "/api/server/push"
|
||||
|
||||
res, err := c.client.R().SetBody(userTraffic).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) {
|
||||
routes := c.resp.Load().(*serverConfig).Routes
|
||||
|
||||
ruleList := c.LocalRuleList
|
||||
|
||||
for i := range routes {
|
||||
if routes[i].Action == "block" {
|
||||
|
||||
ruleList = append(ruleList, api.DetectRule{
|
||||
ID: i,
|
||||
Pattern: regexp.MustCompile(strings.Join(routes[i].Match, "|")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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(s *serverConfig) (*api.NodeInfo, error) {
|
||||
// Create GeneralNodeInfo
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: gconv.Uint32(s.Port),
|
||||
TransportProtocol: "tcp",
|
||||
EnableTLS: true,
|
||||
Host: s.Host,
|
||||
ServiceName: s.Sni,
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
}
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// parseSSNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) parseSSNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||
var header json.RawMessage
|
||||
|
||||
if s.Obfs == "http" {
|
||||
path := "/"
|
||||
if p := s.ObfsSettings.Path; p != "" {
|
||||
if strings.HasPrefix(p, "/") {
|
||||
path = p
|
||||
} else {
|
||||
path += p
|
||||
}
|
||||
}
|
||||
h := simplejson.New()
|
||||
h.Set("type", "http")
|
||||
h.SetPath([]string{"request", "path"}, path)
|
||||
header, _ = h.Encode()
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
return &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: gconv.Uint32(s.Port),
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: s.Encryption,
|
||||
ServerKey: s.ServerKey, // shadowsocks2022 share key
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
Header: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseV2rayNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) parseV2rayNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||
var (
|
||||
header json.RawMessage
|
||||
enableTLS bool
|
||||
)
|
||||
|
||||
switch s.Net {
|
||||
case "tcp":
|
||||
if s.Header != nil {
|
||||
if httpHeader, err := s.Header.MarshalJSON(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
header = httpHeader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.TLS == "tls" {
|
||||
enableTLS = true
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
return &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: gconv.Uint32(s.Port),
|
||||
AlterID: 0,
|
||||
TransportProtocol: s.Net,
|
||||
EnableTLS: enableTLS,
|
||||
Path: s.Path,
|
||||
Host: s.Host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
ServiceName: s.Sni,
|
||||
Header: header,
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *serverConfig) parseDNSConfig() (nameServerList []*conf.NameServerConfig) {
|
||||
for i := range s.Routes {
|
||||
if s.Routes[i].Action == "dns" {
|
||||
nameServerList = append(nameServerList, &conf.NameServerConfig{
|
||||
Address: &conf.Address{Address: net.ParseAddress(s.Routes[i].ActionValue)},
|
||||
Domains: s.Routes[i].Match,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
101
api/gov2panel/gov2panel_test.go
Normal file
101
api/gov2panel/gov2panel_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package gov2panel_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/XrayR-project/XrayR/api/gov2panel"
|
||||
)
|
||||
|
||||
func CreateClient() api.API {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://localhost:8080",
|
||||
Key: "123456",
|
||||
NodeID: 1,
|
||||
NodeType: "V2ray",
|
||||
}
|
||||
client := gov2panel.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 := gov2panel.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 := gov2panel.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: 1111,
|
||||
Download: 2222,
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
46
api/gov2panel/model.go
Normal file
46
api/gov2panel/model.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package gov2panel
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type serverConfig struct {
|
||||
v2ray
|
||||
shadowsocks
|
||||
//---
|
||||
Routes []route `json:"routes"`
|
||||
Header *json.RawMessage `json:"header"`
|
||||
}
|
||||
|
||||
type v2ray struct {
|
||||
Port string `json:"port"`
|
||||
Scy string `json:"scy"`
|
||||
Net string `json:"net"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
TLS string `json:"tls"`
|
||||
Sni string `json:"sni"`
|
||||
Alpn string `json:"alpn"`
|
||||
}
|
||||
|
||||
type shadowsocks struct {
|
||||
Encryption string `json:"encryption"`
|
||||
Obfs string `json:"obfs"`
|
||||
ObfsSettings struct {
|
||||
Path string `json:"path"`
|
||||
Host string `json:"host"`
|
||||
} `json:"obfs_settings"`
|
||||
ServerKey string `json:"server_key"`
|
||||
}
|
||||
|
||||
type route struct {
|
||||
Id int `json:"id"`
|
||||
Match []string `json:"match"`
|
||||
Action string `json:"action"`
|
||||
ActionValue string `json:"action_value"`
|
||||
}
|
||||
|
||||
type user struct {
|
||||
Id int `json:"id"`
|
||||
Uuid string `json:"uuid"`
|
||||
SpeedLimit int `json:"speed_limit"`
|
||||
}
|
57
api/newV2board/model.go
Normal file
57
api/newV2board/model.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package newV2board
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type serverConfig struct {
|
||||
shadowsocks
|
||||
v2ray
|
||||
trojan
|
||||
|
||||
ServerPort int `json:"server_port"`
|
||||
BaseConfig struct {
|
||||
PushInterval int `json:"push_interval"`
|
||||
PullInterval int `json:"pull_interval"`
|
||||
} `json:"base_config"`
|
||||
Routes []route `json:"routes"`
|
||||
}
|
||||
|
||||
type shadowsocks struct {
|
||||
Cipher string `json:"cipher"`
|
||||
Obfs string `json:"obfs"`
|
||||
ObfsSettings struct {
|
||||
Path string `json:"path"`
|
||||
Host string `json:"host"`
|
||||
} `json:"obfs_settings"`
|
||||
ServerKey string `json:"server_key"`
|
||||
}
|
||||
|
||||
type v2ray struct {
|
||||
Network string `json:"network"`
|
||||
NetworkSettings struct {
|
||||
Path string `json:"path"`
|
||||
Headers *json.RawMessage `json:"headers"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
Header *json.RawMessage `json:"header"`
|
||||
} `json:"networkSettings"`
|
||||
Tls int `json:"tls"`
|
||||
}
|
||||
|
||||
type trojan struct {
|
||||
Host string `json:"host"`
|
||||
ServerName string `json:"server_name"`
|
||||
}
|
||||
|
||||
type route struct {
|
||||
Id int `json:"id"`
|
||||
Match []string `json:"match"`
|
||||
Action string `json:"action"`
|
||||
ActionValue string `json:"action_value"`
|
||||
}
|
||||
|
||||
type user struct {
|
||||
Id int `json:"id"`
|
||||
Uuid string `json:"uuid"`
|
||||
SpeedLimit int `json:"speed_limit"`
|
||||
}
|
416
api/newV2board/v2board.go
Normal file
416
api/newV2board/v2board.go
Normal file
@@ -0,0 +1,416 @@
|
||||
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/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
|
||||
"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
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
resp atomic.Value
|
||||
eTags map[string]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,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
eTags: make(map[string]string),
|
||||
}
|
||||
return apiClient
|
||||
}
|
||||
|
||||
// readLocalRuleList reads the local rule list file
|
||||
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
LocalRuleList = make([]api.DetectRule, 0)
|
||||
|
||||
if path != "" {
|
||||
// open the file
|
||||
file, err := os.Open(path)
|
||||
defer file.Close()
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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: %v", c.assembleURL(path), err)
|
||||
}
|
||||
|
||||
if res.StatusCode() > 399 {
|
||||
return nil, fmt.Errorf("request %s failed: %s, %v", c.assembleURL(path), res.String(), err)
|
||||
}
|
||||
|
||||
rtn, err := simplejson.NewJson(res.Body())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ret %s invalid", res.String())
|
||||
}
|
||||
|
||||
return rtn, nil
|
||||
}
|
||||
|
||||
// GetNodeInfo will pull NodeInfo Config from panel
|
||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
server := new(serverConfig)
|
||||
path := "/api/v1/server/UniProxy/config"
|
||||
|
||||
res, err := c.client.R().
|
||||
SetHeader("If-None-Match", c.eTags["node"]).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
|
||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||
if res.StatusCode() == 304 {
|
||||
return nil, errors.New(api.NodeNotModified)
|
||||
}
|
||||
// update etag
|
||||
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["node"] {
|
||||
c.eTags["node"] = res.Header().Get("Etag")
|
||||
}
|
||||
|
||||
nodeInfoResp, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, _ := nodeInfoResp.Encode()
|
||||
json.Unmarshal(b, server)
|
||||
|
||||
if server.ServerPort == 0 {
|
||||
return nil, errors.New("server port must > 0")
|
||||
}
|
||||
|
||||
c.resp.Store(server)
|
||||
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
nodeInfo, err = c.parseV2rayNodeResponse(server)
|
||||
case "Trojan":
|
||||
nodeInfo, err = c.parseTrojanNodeResponse(server)
|
||||
case "Shadowsocks":
|
||||
nodeInfo, err = c.parseSSNodeResponse(server)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse node info failed: %s, \nError: %v", res.String(), err)
|
||||
}
|
||||
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// GetUserList will pull user form panel
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
var users []*user
|
||||
path := "/api/v1/server/UniProxy/user"
|
||||
|
||||
switch c.NodeType {
|
||||
case "V2ray", "Trojan", "Shadowsocks":
|
||||
break
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
res, err := c.client.R().
|
||||
SetHeader("If-None-Match", c.eTags["users"]).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
|
||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||
if res.StatusCode() == 304 {
|
||||
return nil, errors.New(api.UserNotModified)
|
||||
}
|
||||
// update etag
|
||||
if res.Header().Get("Etag") != "" && res.Header().Get("Etag") != c.eTags["users"] {
|
||||
c.eTags["users"] = res.Header().Get("Etag")
|
||||
}
|
||||
|
||||
usersResp, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, _ := usersResp.Get("users").Encode()
|
||||
json.Unmarshal(b, &users)
|
||||
if len(users) == 0 {
|
||||
return nil, errors.New("users is null")
|
||||
}
|
||||
|
||||
userList := make([]api.UserInfo, len(users))
|
||||
for i := 0; i < len(users); i++ {
|
||||
u := api.UserInfo{
|
||||
UID: users[i].Id,
|
||||
UUID: users[i].Uuid,
|
||||
}
|
||||
|
||||
// Support 1.7.1 speed limit
|
||||
if c.SpeedLimit > 0 {
|
||||
u.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
|
||||
} else {
|
||||
u.SpeedLimit = uint64(users[i].SpeedLimit * 1000000 / 8)
|
||||
}
|
||||
|
||||
u.DeviceLimit = c.DeviceLimit // todo waiting v2board send configuration
|
||||
u.Email = u.UUID + "@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) {
|
||||
routes := c.resp.Load().(*serverConfig).Routes
|
||||
|
||||
ruleList := c.LocalRuleList
|
||||
|
||||
for i := range routes {
|
||||
if routes[i].Action == "block" {
|
||||
ruleList = append(ruleList, api.DetectRule{
|
||||
ID: i,
|
||||
Pattern: regexp.MustCompile(strings.Join(routes[i].Match, "|")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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(s *serverConfig) (*api.NodeInfo, error) {
|
||||
// Create GeneralNodeInfo
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: uint32(s.ServerPort),
|
||||
TransportProtocol: "tcp",
|
||||
EnableTLS: true,
|
||||
Host: s.Host,
|
||||
ServiceName: s.ServerName,
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
}
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// parseSSNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) parseSSNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||
var header json.RawMessage
|
||||
|
||||
if s.Obfs == "http" {
|
||||
path := "/"
|
||||
if p := s.ObfsSettings.Path; p != "" {
|
||||
if strings.HasPrefix(p, "/") {
|
||||
path = p
|
||||
} else {
|
||||
path += p
|
||||
}
|
||||
}
|
||||
h := simplejson.New()
|
||||
h.Set("type", "http")
|
||||
h.SetPath([]string{"request", "path"}, path)
|
||||
header, _ = h.Encode()
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
return &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: uint32(s.ServerPort),
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: s.Cipher,
|
||||
ServerKey: s.ServerKey, // shadowsocks2022 share key
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
Header: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseV2rayNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) parseV2rayNodeResponse(s *serverConfig) (*api.NodeInfo, error) {
|
||||
var (
|
||||
host string
|
||||
header json.RawMessage
|
||||
enableTLS bool
|
||||
)
|
||||
|
||||
switch s.Network {
|
||||
case "ws":
|
||||
if s.NetworkSettings.Headers != nil {
|
||||
if httpHeader, err := s.NetworkSettings.Headers.MarshalJSON(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
b, _ := simplejson.NewJson(httpHeader)
|
||||
host = b.Get("Host").MustString()
|
||||
}
|
||||
}
|
||||
case "tcp":
|
||||
if s.NetworkSettings.Header != nil {
|
||||
if httpHeader, err := s.NetworkSettings.Header.MarshalJSON(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
header = httpHeader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.Tls == 1 {
|
||||
enableTLS = true
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
return &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: uint32(s.ServerPort),
|
||||
AlterID: 0,
|
||||
TransportProtocol: s.Network,
|
||||
EnableTLS: enableTLS,
|
||||
Path: s.NetworkSettings.Path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
ServiceName: s.NetworkSettings.ServiceName,
|
||||
Header: header,
|
||||
NameServerConfig: s.parseDNSConfig(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *serverConfig) parseDNSConfig() (nameServerList []*conf.NameServerConfig) {
|
||||
for i := range s.Routes {
|
||||
if s.Routes[i].Action == "dns" {
|
||||
nameServerList = append(nameServerList, &conf.NameServerConfig{
|
||||
Address: &conf.Address{Address: net.ParseAddress(s.Routes[i].ActionValue)},
|
||||
Domains: s.Routes[i].Match,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
package v2board_test
|
||||
package newV2board_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/XrayR-project/XrayR/api/v2board"
|
||||
"github.com/XrayR-project/XrayR/api/newV2board"
|
||||
)
|
||||
|
||||
func CreateClient() api.API {
|
||||
@@ -14,11 +14,11 @@ func CreateClient() api.API {
|
||||
NodeID: 1,
|
||||
NodeType: "V2ray",
|
||||
}
|
||||
client := v2board.New(apiConfig)
|
||||
client := newV2board.New(apiConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||
client := CreateClient()
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
if err != nil {
|
||||
@@ -27,14 +27,14 @@ func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetSSNodeinfo(t *testing.T) {
|
||||
func TestGetSSNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:668",
|
||||
Key: "qwertyuiopasdfghjkl",
|
||||
NodeID: 1,
|
||||
NodeType: "Shadowsocks",
|
||||
}
|
||||
client := v2board.New(apiConfig)
|
||||
client := newV2board.New(apiConfig)
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -42,14 +42,14 @@ func TestGetSSNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetTrojanNodeinfo(t *testing.T) {
|
||||
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:668",
|
||||
Key: "qwertyuiopasdfghjkl",
|
||||
NodeID: 1,
|
||||
NodeType: "Trojan",
|
||||
}
|
||||
client := v2board.New(apiConfig)
|
||||
client := newV2board.New(apiConfig)
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -82,7 +82,7 @@ func TestReportReportUserTraffic(t *testing.T) {
|
||||
Download: 114514,
|
||||
}
|
||||
}
|
||||
//client.Debug()
|
||||
// client.Debug()
|
||||
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||
if err != nil {
|
||||
t.Error(err)
|
@@ -9,14 +9,14 @@ type NodeInfoResponse struct {
|
||||
Method string `json:"method"`
|
||||
TrafficRate float64 `json:"trafficRate"`
|
||||
RawServerString string `json:"outServer"`
|
||||
Port int `json:"outPort"`
|
||||
AlterId int `json:"alterId"`
|
||||
Port uint32 `json:"outPort"`
|
||||
AlterId uint16 `json:"alterId"`
|
||||
Network string `json:"network"`
|
||||
Security string `json:"security"`
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
Grpc bool `json:"grpc"`
|
||||
Sni string `json:sni`
|
||||
Sni string `json:"sni"`
|
||||
}
|
||||
|
||||
// UserResponse is the response of user
|
||||
|
@@ -11,8 +11,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
)
|
||||
|
||||
// APIClient create a api client to the panel.
|
||||
@@ -23,7 +24,7 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
@@ -60,7 +61,7 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
@@ -76,7 +77,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
// open the file
|
||||
file, err := os.Open(path)
|
||||
|
||||
//handle errors while opening
|
||||
// handle errors while opening
|
||||
if err != nil {
|
||||
log.Printf("Error when opening file: %s", err)
|
||||
return LocalRuleList
|
||||
@@ -94,7 +95,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
// handle first encountered error while reading
|
||||
if err := fileScanner.Err(); err != nil {
|
||||
log.Fatalf("Error while reading file: %s", err)
|
||||
return make([]api.DetectRule, 0)
|
||||
return
|
||||
}
|
||||
|
||||
file.Close()
|
||||
@@ -130,7 +131,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
||||
|
||||
if response.Ret != 200 {
|
||||
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
|
||||
}
|
||||
@@ -167,7 +168,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
nodeInfoResponse := new(NodeInfoResponse)
|
||||
|
||||
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 {
|
||||
case "V2ray":
|
||||
@@ -177,7 +178,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
case "Shadowsocks":
|
||||
nodeInfo, err = c.ParseSSNodeResponse(nodeInfoResponse)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -219,12 +220,12 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
|
||||
var userListResponse *[]UserResponse
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
@@ -234,7 +235,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
//ReportNodeOnlineUsers reports online user ip
|
||||
// ReportNodeOnlineUsers reports online user ip
|
||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||
var nodeType = ""
|
||||
switch c.NodeType {
|
||||
@@ -338,7 +339,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
ruleListResponse := new([]RuleItem)
|
||||
|
||||
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 {
|
||||
@@ -358,8 +359,8 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var enableTLS bool
|
||||
var path, host, TLStype, transportProtocol, serviceName string
|
||||
var speedlimit uint64 = 0
|
||||
var path, host, transportProtocol, serviceName string
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
port := nodeInfoResponse.Port
|
||||
alterID := nodeInfoResponse.AlterId
|
||||
@@ -375,34 +376,29 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
}
|
||||
// Compatible with more node types config
|
||||
switch nodeInfoResponse.Security {
|
||||
case "tls", "xtls":
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
case "tls":
|
||||
enableTLS = true
|
||||
default:
|
||||
enableTLS = false
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLStype,
|
||||
Path: path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
|
||||
@@ -411,41 +407,33 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
|
||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var port int = 0
|
||||
var speedlimit uint64 = 0
|
||||
|
||||
port = nodeInfoResponse.Port
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
Port: nodeInfoResponse.Port,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: nodeInfoResponse.Method,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
// 域名或IP;port=连接端口#偏移端口|host=xx
|
||||
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
||||
var TLSType, host string
|
||||
var host string
|
||||
var transportProtocol = "tcp"
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
} else {
|
||||
TLSType = "tls"
|
||||
}
|
||||
host = nodeInfoResponse.Host
|
||||
port := nodeInfoResponse.Port
|
||||
|
||||
@@ -458,25 +446,24 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
||||
transportProtocol = "grpc"
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
Host: host,
|
||||
ServiceName: nodeInfoResponse.Sni,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseUserListResponse parse the response for the given nodeinfo format
|
||||
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
|
||||
var deviceLimit int = 0
|
||||
var speedlimit uint64 = 0
|
||||
var deviceLimit = 0
|
||||
var speedLimit uint64 = 0
|
||||
userList := make([]api.UserInfo, len(*userInfoResponse))
|
||||
for i, user := range *userInfoResponse {
|
||||
if c.DeviceLimit > 0 {
|
||||
@@ -485,15 +472,15 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
||||
deviceLimit = user.DeviceLimit
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.ID,
|
||||
Passwd: user.Passwd,
|
||||
UUID: user.Passwd,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
DeviceLimit: deviceLimit,
|
||||
}
|
||||
}
|
||||
|
@@ -85,7 +85,7 @@ func TestGetUserList(t *testing.T) {
|
||||
func TestReportNodeStatus(t *testing.T) {
|
||||
client := CreateClient()
|
||||
nodeStatus := &api.NodeStatus{
|
||||
1, 1, 1, 256,
|
||||
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
|
||||
}
|
||||
err := client.ReportNodeStatus(nodeStatus)
|
||||
if err != nil {
|
||||
@@ -107,7 +107,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
|
||||
IP: fmt.Sprintf("1.1.1.%d", i),
|
||||
}
|
||||
}
|
||||
//client.Debug()
|
||||
// client.Debug()
|
||||
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -128,7 +128,7 @@ func TestReportReportUserTraffic(t *testing.T) {
|
||||
Download: 114514,
|
||||
}
|
||||
}
|
||||
//client.Debug()
|
||||
// client.Debug()
|
||||
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -150,8 +150,8 @@ func TestReportIllegal(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
detectResult := []api.DetectResult{
|
||||
api.DetectResult{1, 2},
|
||||
api.DetectResult{1, 3},
|
||||
{1, 2},
|
||||
{1, 3},
|
||||
}
|
||||
client.Debug()
|
||||
err := client.ReportIllegal(&detectResult)
|
||||
|
@@ -19,8 +19,8 @@ type V2rayNodeInfo struct {
|
||||
Key string `json:"key"`
|
||||
Cert string `json:"pem"`
|
||||
V2License string `json:"v2_license"`
|
||||
V2AlterID int `json:"v2_alter_id"`
|
||||
V2Port int `json:"v2_port"`
|
||||
V2AlterID uint16 `json:"v2_alter_id"`
|
||||
V2Port uint32 `json:"v2_port"`
|
||||
V2Method string `json:"v2_method"`
|
||||
V2Net string `json:"v2_net"`
|
||||
V2Type string `json:"v2_type"`
|
||||
@@ -37,7 +37,7 @@ type ShadowsocksNodeInfo struct {
|
||||
SpeedLimit uint64 `json:"speed_limit"`
|
||||
ClientLimit int `json:"client_limit"`
|
||||
Method string `json:"method"`
|
||||
Port int `json:"port"`
|
||||
Port uint32 `json:"port"`
|
||||
}
|
||||
|
||||
type TrojanNodeInfo struct {
|
||||
@@ -46,10 +46,10 @@ type TrojanNodeInfo struct {
|
||||
SpeedLimit uint64 `json:"speed_limit"`
|
||||
ClientLimit int `json:"client_limit"`
|
||||
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 {
|
||||
CPU string `json:"cpu"`
|
||||
Mem string `json:"mem"`
|
||||
@@ -98,7 +98,6 @@ type NodeRuleItem struct {
|
||||
Pattern string `json:"pattern"`
|
||||
}
|
||||
|
||||
// IllegalReport
|
||||
type IllegalReport struct {
|
||||
UID int `json:"uid"`
|
||||
RuleID int `json:"rule_id"`
|
||||
|
@@ -11,8 +11,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
)
|
||||
|
||||
// APIClient create a api client to the panel.
|
||||
@@ -23,7 +24,7 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
@@ -56,7 +57,7 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
@@ -72,7 +73,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
// open the file
|
||||
file, err := os.Open(path)
|
||||
|
||||
//handle errors while opening
|
||||
// handle errors while opening
|
||||
if err != nil {
|
||||
log.Printf("Error when opening file: %s", err)
|
||||
return LocalRuleList
|
||||
@@ -90,7 +91,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
// handle first encountered error while reading
|
||||
if err := fileScanner.Err(); err != nil {
|
||||
log.Fatalf("Error while reading file: %s", err)
|
||||
return make([]api.DetectRule, 0)
|
||||
return
|
||||
}
|
||||
|
||||
file.Close()
|
||||
@@ -134,7 +135,7 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
||||
|
||||
if response.Status != "success" {
|
||||
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
|
||||
}
|
||||
@@ -150,7 +151,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
case "Shadowsocks":
|
||||
path = fmt.Sprintf("/api/ss/v1/node/%d", c.NodeID)
|
||||
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().
|
||||
@@ -171,7 +172,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
case "Shadowsocks":
|
||||
nodeInfo, err = c.ParseSSNodeResponse(&response.Data)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -193,7 +194,7 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
case "Shadowsocks":
|
||||
path = fmt.Sprintf("/api/ss/v1/userList/%d", c.NodeID)
|
||||
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().
|
||||
@@ -214,11 +215,11 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
case "Shadowsocks":
|
||||
userList, err = c.ParseSSUserListResponse(&response.Data)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
if err != nil {
|
||||
res, _ := 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
|
||||
}
|
||||
@@ -234,11 +235,11 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||
case "Shadowsocks":
|
||||
path = fmt.Sprintf("/api/ss/v1/nodeStatus/%d", c.NodeID)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
systemload := NodeStatus{
|
||||
Uptime: nodeStatus.Uptime,
|
||||
Uptime: int(nodeStatus.Uptime),
|
||||
CPU: fmt.Sprintf("%d%%", int(nodeStatus.CPU)),
|
||||
Mem: fmt.Sprintf("%d%%", int(nodeStatus.Mem)),
|
||||
Disk: fmt.Sprintf("%d%%", int(nodeStatus.Disk)),
|
||||
@@ -258,7 +259,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
//ReportNodeOnlineUsers reports online user ip
|
||||
// ReportNodeOnlineUsers reports online user ip
|
||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||
|
||||
var path string
|
||||
@@ -270,7 +271,7 @@ func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) erro
|
||||
case "Shadowsocks":
|
||||
path = fmt.Sprintf("/api/ss/v1/nodeOnline/%d", c.NodeID)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
data := make([]NodeOnline, len(*onlineUserList))
|
||||
@@ -303,7 +304,7 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
case "Shadowsocks":
|
||||
path = fmt.Sprintf("/api/ss/v1/userTraffic/%d", c.NodeID)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
data := make([]UserTraffic, len(*userTraffic))
|
||||
@@ -338,7 +339,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
case "Shadowsocks":
|
||||
path = fmt.Sprintf("/api/ss/v1/nodeRule/%d", c.NodeID)
|
||||
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().
|
||||
@@ -354,7 +355,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
ruleListResponse := new(NodeRule)
|
||||
|
||||
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
|
||||
// Only support reject rule type
|
||||
@@ -386,7 +387,7 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
case "Shadowsocks":
|
||||
path = fmt.Sprintf("/api/ss/v1/trigger/%d", c.NodeID)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
|
||||
for _, r := range *detectResultList {
|
||||
@@ -411,23 +412,17 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
|
||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||
var TLStype string
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
v2rayNodeInfo := new(V2rayNodeInfo)
|
||||
if err := json.Unmarshal(*nodeInfoResponse, v2rayNodeInfo); err != nil {
|
||||
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 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((v2rayNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = (v2rayNodeInfo.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
|
||||
if c.DeviceLimit == 0 && v2rayNodeInfo.ClientLimit > 0 {
|
||||
@@ -435,72 +430,65 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *json.RawMessage) (*
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: v2rayNodeInfo.V2Port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: v2rayNodeInfo.V2AlterID,
|
||||
TransportProtocol: v2rayNodeInfo.V2Net,
|
||||
FakeType: v2rayNodeInfo.V2Type,
|
||||
EnableTLS: v2rayNodeInfo.V2TLS,
|
||||
TLSType: TLStype,
|
||||
Path: v2rayNodeInfo.V2Path,
|
||||
Host: v2rayNodeInfo.V2Host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
shadowsocksNodeInfo := new(ShadowsocksNodeInfo)
|
||||
if err := json.Unmarshal(*nodeInfoResponse, shadowsocksNodeInfo); err != nil {
|
||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((shadowsocksNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((shadowsocksNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
if c.DeviceLimit == 0 && shadowsocksNodeInfo.ClientLimit > 0 {
|
||||
c.DeviceLimit = shadowsocksNodeInfo.ClientLimit
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: shadowsocksNodeInfo.Port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: shadowsocksNodeInfo.Method,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (*api.NodeInfo, error) {
|
||||
|
||||
var TLSType string
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
} else {
|
||||
TLSType = "tls"
|
||||
}
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
trojanNodeInfo := new(TrojanNodeInfo)
|
||||
if err := json.Unmarshal(*nodeInfoResponse, trojanNodeInfo); err != nil {
|
||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(*nodeInfoResponse), err)
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((trojanNodeInfo.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = (trojanNodeInfo.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
|
||||
if c.DeviceLimit == 0 && trojanNodeInfo.ClientLimit > 0 {
|
||||
@@ -508,41 +496,40 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *json.RawMessage) (
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: trojanNodeInfo.TrojanPort,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseV2rayUserListResponse parse the response for the given userinfo format
|
||||
func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
vmessUserList := new([]*VMessUser)
|
||||
if err := json.Unmarshal(*userInfoResponse, vmessUserList); err != nil {
|
||||
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))
|
||||
for i, user := range *vmessUserList {
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = (user.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.UID,
|
||||
Email: "",
|
||||
UUID: user.VmessUID,
|
||||
DeviceLimit: c.DeviceLimit,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,26 +538,26 @@ func (c *APIClient) ParseV2rayUserListResponse(userInfoResponse *json.RawMessage
|
||||
|
||||
// ParseTrojanUserListResponse parse the response for the given userinfo format
|
||||
func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
trojanUserList := new([]*TrojanUser)
|
||||
if err := json.Unmarshal(*userInfoResponse, trojanUserList); err != nil {
|
||||
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))
|
||||
for i, user := range *trojanUserList {
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = (user.SpeedLimit * 1000000) / 8
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.UID,
|
||||
Email: "",
|
||||
UUID: user.Password,
|
||||
DeviceLimit: c.DeviceLimit,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,26 +566,26 @@ func (c *APIClient) ParseTrojanUserListResponse(userInfoResponse *json.RawMessag
|
||||
|
||||
// ParseSSUserListResponse parse the response for the given userinfo format
|
||||
func (c *APIClient) ParseSSUserListResponse(userInfoResponse *json.RawMessage) (*[]api.UserInfo, error) {
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
ssUserList := new([]*SSUser)
|
||||
if err := json.Unmarshal(*userInfoResponse, ssUserList); err != nil {
|
||||
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))
|
||||
for i, user := range *ssUserList {
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64(user.SpeedLimit * 1000000 / 8)
|
||||
}
|
||||
userList[i] = api.UserInfo{
|
||||
UID: user.UID,
|
||||
Email: "",
|
||||
Passwd: user.Password,
|
||||
DeviceLimit: c.DeviceLimit,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,7 @@ func TestGetUserList(t *testing.T) {
|
||||
func TestReportNodeStatus(t *testing.T) {
|
||||
client := CreateClient()
|
||||
nodeStatus := &api.NodeStatus{
|
||||
1, 1, 1, 256,
|
||||
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
|
||||
}
|
||||
err := client.ReportNodeStatus(nodeStatus)
|
||||
if err != nil {
|
||||
@@ -111,7 +111,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
|
||||
IP: fmt.Sprintf("1.1.1.%d", i),
|
||||
}
|
||||
}
|
||||
//client.Debug()
|
||||
// client.Debug()
|
||||
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -154,8 +154,8 @@ func TestReportIllegal(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
detectResult := []api.DetectResult{
|
||||
api.DetectResult{1, 1},
|
||||
api.DetectResult{1, 2},
|
||||
{1, 1},
|
||||
{1, 2},
|
||||
}
|
||||
client.Debug()
|
||||
err := client.ReportIllegal(&detectResult)
|
||||
|
@@ -8,7 +8,6 @@ type NodeInfoResponse struct {
|
||||
Class int `json:"node_class"`
|
||||
SpeedLimit float64 `json:"node_speedlimit"`
|
||||
TrafficRate float64 `json:"traffic_rate"`
|
||||
MuOnly int `json:"mu_only"`
|
||||
Sort int `json:"sort"`
|
||||
RawServerString string `json:"server"`
|
||||
Type string `json:"type"`
|
||||
@@ -17,51 +16,35 @@ type NodeInfoResponse struct {
|
||||
}
|
||||
|
||||
type CustomConfig struct {
|
||||
OffsetPortUser string `json:"offset_port_user"`
|
||||
OffsetPortNode string `json:"offset_port_node"`
|
||||
ServerSub string `json:"server_sub"`
|
||||
Host string `json:"host"`
|
||||
MuPort string `json:"mu_port"`
|
||||
MuEncryption string `json:"mu_encryption"`
|
||||
MuProtocol string `json:"mu_protocol"`
|
||||
MuObfs string `json:"mu_obfs"`
|
||||
MuSuffix string `json:"mu_suffix"`
|
||||
V2Port string `json:"v2_port"`
|
||||
Method string `json:"method"`
|
||||
TLS string `json:"tls"`
|
||||
EnableVless string `json:"enable_vless"`
|
||||
AlterID string `json:"alter_id"`
|
||||
Network string `json:"network"`
|
||||
Security string `json:"security"`
|
||||
Path string `json:"path"`
|
||||
VerifyCert bool `json:"verify_cert"`
|
||||
Obfs string `json:"obfs"`
|
||||
Header json.RawMessage `json:"header"`
|
||||
TrojanPort string `json:"trojan_port"`
|
||||
AllowInsecure string `json:"allow_insecure"`
|
||||
Grpc string `json:"grpc"`
|
||||
Servicename string `json:"servicename"`
|
||||
EnableXtls string `json:"enable_xtls"`
|
||||
Flow string `json:"flow"`
|
||||
EnableREALITY bool `json:"enable_reality"`
|
||||
RealityOpts *REALITYConfig `json:"reality-opts"`
|
||||
}
|
||||
|
||||
// UserResponse is the response of user
|
||||
type UserResponse struct {
|
||||
ID int `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Passwd string `json:"passwd"`
|
||||
Port int `json:"port"`
|
||||
Method string `json:"method"`
|
||||
SpeedLimit float64 `json:"node_speedlimit"`
|
||||
DeviceLimit int `json:"node_connector"`
|
||||
Protocol string `json:"protocol"`
|
||||
ProtocolParam string `json:"protocol_param"`
|
||||
Obfs string `json:"obfs"`
|
||||
ObfsParam string `json:"obfs_param"`
|
||||
ForbiddenIP string `json:"forbidden_ip"`
|
||||
ForbiddenPort string `json:"forbidden_port"`
|
||||
UUID string `json:"uuid"`
|
||||
MultiUser int `json:"is_multi_user"`
|
||||
AliveIP int `json:"alive_ip"`
|
||||
ID int `json:"id"`
|
||||
Passwd string `json:"passwd"`
|
||||
Port uint32 `json:"port"`
|
||||
Method string `json:"method"`
|
||||
SpeedLimit float64 `json:"node_speedlimit"`
|
||||
DeviceLimit int `json:"node_iplimit"`
|
||||
UUID string `json:"uuid"`
|
||||
AliveIP int `json:"alive_ip"`
|
||||
}
|
||||
|
||||
// Response is the common response
|
||||
@@ -75,7 +58,7 @@ type PostData struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// SystemLoad is the data structure of systemload
|
||||
// SystemLoad is the data structure of system load
|
||||
type SystemLoad struct {
|
||||
Uptime string `json:"uptime"`
|
||||
Load string `json:"load"`
|
||||
@@ -103,3 +86,14 @@ type IllegalItem struct {
|
||||
ID int `json:"list_id"`
|
||||
UID int `json:"user_id"`
|
||||
}
|
||||
|
||||
type REALITYConfig struct {
|
||||
Dest string `json:"dest,omitempty"`
|
||||
ProxyProtocolVer uint64 `json:"proxy_protocol_ver,omitempty"`
|
||||
ServerNames []string `json:"server_names,omitempty"`
|
||||
PrivateKey string `json:"private_key,omitempty"`
|
||||
MinClientVer string `json:"min_client_ver,omitempty"`
|
||||
MaxClientVer string `json:"max_client_ver,omitempty"`
|
||||
MaxTimeDiff uint64 `json:"max_time_diff,omitempty"`
|
||||
ShortIds []string `json:"short_ids,omitempty"`
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package sspanel
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -13,14 +14,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
)
|
||||
|
||||
var (
|
||||
firstPortRe = regexp.MustCompile(`(?m)port=(?P<outport>\d+)#?`) // First 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.
|
||||
@@ -31,19 +33,21 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
DisableCustomConfig bool
|
||||
LocalRuleList []api.DetectRule
|
||||
LastReportOnline map[int]int
|
||||
access sync.Mutex
|
||||
version string
|
||||
eTags map[string]string
|
||||
}
|
||||
|
||||
// New creat a api instance
|
||||
// New create api instance
|
||||
func New(apiConfig *api.Config) *APIClient {
|
||||
|
||||
client := resty.New()
|
||||
|
||||
client.SetRetryCount(3)
|
||||
if apiConfig.Timeout > 0 {
|
||||
client.SetTimeout(time.Duration(apiConfig.Timeout) * time.Second)
|
||||
@@ -51,12 +55,14 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
client.SetTimeout(5 * time.Second)
|
||||
}
|
||||
client.OnError(func(req *resty.Request, err error) {
|
||||
if v, ok := err.(*resty.ResponseError); ok {
|
||||
var v *resty.ResponseError
|
||||
if errors.As(err, &v) {
|
||||
// v.Response contains the last response from the server
|
||||
// v.Err contains the original error
|
||||
log.Print(v.Err)
|
||||
}
|
||||
})
|
||||
|
||||
client.SetBaseURL(apiConfig.APIHost)
|
||||
// Create Key for each requests
|
||||
client.SetQueryParam("key", apiConfig.Key)
|
||||
@@ -72,24 +78,25 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
DisableCustomConfig: apiConfig.DisableCustomConfig,
|
||||
LastReportOnline: make(map[int]int),
|
||||
eTags: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// readLocalRuleList reads the local rule list file
|
||||
func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
|
||||
LocalRuleList = make([]api.DetectRule, 0)
|
||||
if path != "" {
|
||||
// open the file
|
||||
file, err := os.Open(path)
|
||||
defer file.Close()
|
||||
|
||||
//handle errors while opening
|
||||
// handle errors while opening
|
||||
if err != nil {
|
||||
log.Printf("Error when opening file: %s", err)
|
||||
return LocalRuleList
|
||||
@@ -107,10 +114,8 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
// handle first encountered error while reading
|
||||
if err := fileScanner.Err(); err != nil {
|
||||
log.Fatalf("Error while reading file: %s", err)
|
||||
return make([]api.DetectRule, 0)
|
||||
return
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
|
||||
return LocalRuleList
|
||||
@@ -143,18 +148,27 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
||||
|
||||
if response.Ret != 1 {
|
||||
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
|
||||
}
|
||||
|
||||
// GetNodeInfo will pull NodeInfo Config from sspanel
|
||||
// GetNodeInfo will pull NodeInfo Config from ssPanel
|
||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||
res, err := c.client.R().
|
||||
SetResult(&Response{}).
|
||||
SetHeader("If-None-Match", c.eTags["node"]).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||
if res.StatusCode() == 304 {
|
||||
return nil, errors.New(api.NodeNotModified)
|
||||
}
|
||||
|
||||
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["node"] {
|
||||
c.eTags["node"] = res.Header().Get("ETag")
|
||||
}
|
||||
|
||||
response, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
@@ -164,28 +178,21 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
nodeInfoResponse := new(NodeInfoResponse)
|
||||
|
||||
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
|
||||
disableCustomConfig := c.DisableCustomConfig
|
||||
if nodeInfoResponse.Version == "2021.11" && !disableCustomConfig {
|
||||
// Check if custom_config is empty
|
||||
if configString, err := json.Marshal(nodeInfoResponse.CustomConfig); err != nil || string(configString) == "[]" {
|
||||
log.Printf("custom_config is empty! take config from address now.")
|
||||
disableCustomConfig = true
|
||||
}
|
||||
} else {
|
||||
disableCustomConfig = true
|
||||
// determine ssPanel version, if disable custom config or version < 2021.11, then use old api
|
||||
c.version = nodeInfoResponse.Version
|
||||
var isExpired bool
|
||||
if compareVersion(c.version, "2021.11") == -1 {
|
||||
isExpired = true
|
||||
}
|
||||
|
||||
if !disableCustomConfig {
|
||||
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
|
||||
if err != nil {
|
||||
res, _ := json.Marshal(nodeInfoResponse)
|
||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://crackair.gitbook.io/xrayr-project/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
|
||||
if c.DisableCustomConfig || isExpired {
|
||||
if isExpired {
|
||||
log.Print("The panel version is expired, it is recommended to update immediately")
|
||||
}
|
||||
} else {
|
||||
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
nodeInfo, err = c.ParseV2rayNodeResponse(nodeInfoResponse)
|
||||
@@ -196,7 +203,13 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
case "Shadowsocks-Plugin":
|
||||
nodeInfo, err = c.ParseSSPluginNodeResponse(nodeInfoResponse)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported Node type: %s", c.NodeType)
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
} else {
|
||||
nodeInfo, err = c.ParseSSPanelNodeInfo(nodeInfoResponse)
|
||||
if err != nil {
|
||||
res, _ := json.Marshal(nodeInfoResponse)
|
||||
return nil, fmt.Errorf("Parse node info failed: %s, \nError: %s, \nPlease check the doc of custom_config for help: https://xrayr-project.github.io/XrayR-doc/dui-jie-sspanel/sspanel/sspanel_custom_config", string(res), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,14 +221,23 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// GetUserList will pull user form sspanel
|
||||
// GetUserList will pull user form ssPanel
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
path := "/mod_mu/users"
|
||||
res, err := c.client.R().
|
||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||
SetHeader("If-None-Match", c.eTags["users"]).
|
||||
SetResult(&Response{}).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||
if res.StatusCode() == 304 {
|
||||
return nil, errors.New(api.UserNotModified)
|
||||
}
|
||||
|
||||
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["users"] {
|
||||
c.eTags["users"] = res.Header().Get("ETag")
|
||||
}
|
||||
|
||||
response, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
@@ -225,39 +247,41 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
userListResponse := new([]UserResponse)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// ReportNodeStatus reports the node status to the sspanel
|
||||
// ReportNodeStatus reports the node status to the ssPanel
|
||||
func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||
systemload := SystemLoad{
|
||||
Uptime: strconv.Itoa(nodeStatus.Uptime),
|
||||
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.CPU/100, nodeStatus.CPU/100),
|
||||
// Determine whether a status report is in need
|
||||
if compareVersion(c.version, "2023.2") == -1 {
|
||||
path := fmt.Sprintf("/mod_mu/nodes/%d/info", c.NodeID)
|
||||
systemLoad := SystemLoad{
|
||||
Uptime: strconv.FormatUint(nodeStatus.Uptime, 10),
|
||||
Load: fmt.Sprintf("%.2f %.2f %.2f", nodeStatus.CPU/100, nodeStatus.Mem/100, nodeStatus.Disk/100),
|
||||
}
|
||||
|
||||
res, err := c.client.R().
|
||||
SetBody(systemLoad).
|
||||
SetResult(&Response{}).
|
||||
ForceContentType("application/json").
|
||||
Post(path)
|
||||
|
||||
_, err = c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
res, err := c.client.R().
|
||||
SetBody(systemload).
|
||||
SetResult(&Response{}).
|
||||
ForceContentType("application/json").
|
||||
Post(path)
|
||||
|
||||
_, err = c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//ReportNodeOnlineUsers reports online user ip
|
||||
// ReportNodeOnlineUsers reports online user ip
|
||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
@@ -317,15 +341,25 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNodeRule will pull the audit rule form sspanel
|
||||
// GetNodeRule will pull the audit rule form ssPanel
|
||||
func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
ruleList := c.LocalRuleList
|
||||
path := "/mod_mu/func/detect_rules"
|
||||
res, err := c.client.R().
|
||||
SetResult(&Response{}).
|
||||
SetHeader("If-None-Match", c.eTags["rules"]).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
|
||||
// Etag identifier for a specific version of a resource. StatusCode = 304 means no changed
|
||||
if res.StatusCode() == 304 {
|
||||
return nil, errors.New(api.RuleNotModified)
|
||||
}
|
||||
|
||||
if res.Header().Get("ETag") != "" && res.Header().Get("ETag") != c.eTags["rules"] {
|
||||
c.eTags["rules"] = res.Header().Get("ETag")
|
||||
}
|
||||
|
||||
response, err := c.parseResponse(res, path, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -334,7 +368,7 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
ruleListResponse := new([]RuleItem)
|
||||
|
||||
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 {
|
||||
@@ -371,34 +405,34 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
||||
// ParseV2rayNodeResponse parse the response for the given node info format
|
||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var enableTLS bool
|
||||
var path, host, TLStype, transportProtocol, serviceName, HeaderType string
|
||||
var path, host, transportProtocol, serviceName, HeaderType string
|
||||
var header json.RawMessage
|
||||
var speedlimit uint64 = 0
|
||||
var speedLimit uint64 = 0
|
||||
if nodeInfoResponse.RawServerString == "" {
|
||||
return nil, fmt.Errorf("No server info in response")
|
||||
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, ";")
|
||||
port, err := strconv.Atoi(serverConf[1])
|
||||
|
||||
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alterID, err := strconv.Atoi(serverConf[2])
|
||||
port := uint32(parsedPort)
|
||||
|
||||
parsedAlterID, err := strconv.ParseInt(serverConf[2], 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alterID := uint16(parsedAlterID)
|
||||
|
||||
// Compatible with more node types config
|
||||
for _, value := range serverConf[3:5] {
|
||||
switch value {
|
||||
case "tls", "xtls":
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
case "tls":
|
||||
enableTLS = true
|
||||
default:
|
||||
if value != "" {
|
||||
@@ -423,14 +457,14 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
host = value
|
||||
case "servicename":
|
||||
serviceName = value
|
||||
case "headertype":
|
||||
case "headerType":
|
||||
HeaderType = value
|
||||
}
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
if HeaderType != "" {
|
||||
@@ -439,33 +473,33 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Marshal Header Type %s into config fialed: %s", header, err)
|
||||
return nil, fmt.Errorf("marshal Header Type %s into config fialed: %s", header, err)
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLStype,
|
||||
Path: path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
ServiceName: serviceName,
|
||||
Header: header,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||
// ParseSSNodeResponse parse the response for the given node info format
|
||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var port int = 0
|
||||
var speedlimit uint64 = 0
|
||||
var port uint32 = 0
|
||||
var speedLimit uint64 = 0
|
||||
var method string
|
||||
path := "/mod_mu/users"
|
||||
res, err := c.client.R().
|
||||
@@ -482,49 +516,39 @@ func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*ap
|
||||
userListResponse := new([]UserResponse)
|
||||
|
||||
if err := json.Unmarshal(response.Data, userListResponse); err != nil {
|
||||
return nil, fmt.Errorf("Unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||
}
|
||||
// Find the multi-user
|
||||
for _, u := range *userListResponse {
|
||||
if u.MultiUser > 0 {
|
||||
port = u.Port
|
||||
method = u.Method
|
||||
break
|
||||
}
|
||||
}
|
||||
if port == 0 || method == "" {
|
||||
return nil, fmt.Errorf("Cant find the single port multi user")
|
||||
return nil, fmt.Errorf("unmarshal %s failed: %s", reflect.TypeOf(userListResponse), err)
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: method,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseSSPluginNodeResponse parse the response for the given nodeinfor format
|
||||
// ParseSSPluginNodeResponse parse the response for the given node info format
|
||||
func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var enableTLS bool
|
||||
var path, host, TLStype, transportProtocol string
|
||||
var speedlimit uint64 = 0
|
||||
var path, host, transportProtocol string
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
||||
port, err := strconv.Atoi(serverConf[1])
|
||||
parsedPort, err := strconv.ParseInt(serverConf[1], 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port := uint32(parsedPort)
|
||||
port = port - 1 // Shadowsocks-Plugin requires two ports, one for ss the other for other stream protocol
|
||||
if port <= 0 {
|
||||
return nil, fmt.Errorf("Shadowsocks-Plugin listen port must bigger than 1")
|
||||
@@ -532,12 +556,7 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
|
||||
// Compatible with more node types config
|
||||
for _, value := range serverConf[3:5] {
|
||||
switch value {
|
||||
case "tls", "xtls":
|
||||
if c.EnableXTLS {
|
||||
TLStype = "xtls"
|
||||
} else {
|
||||
TLStype = "tls"
|
||||
}
|
||||
case "tls":
|
||||
enableTLS = true
|
||||
case "ws":
|
||||
transportProtocol = "ws"
|
||||
@@ -563,41 +582,35 @@ func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse
|
||||
}
|
||||
}
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLStype,
|
||||
Path: path,
|
||||
Host: host,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||
// ParseTrojanNodeResponse parse the response for the given node info format
|
||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
// 域名或IP;port=连接端口#偏移端口|host=xx
|
||||
// gz.aaa.com;port=443#12345|host=hk.aaa.com
|
||||
var p, TLSType, host, outsidePort, insidePort, transportProtocol, serviceName string
|
||||
var speedlimit uint64 = 0
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
} else {
|
||||
TLSType = "tls"
|
||||
}
|
||||
var p, host, outsidePort, insidePort, transportProtocol, serviceName string
|
||||
var speedLimit uint64 = 0
|
||||
|
||||
if nodeInfoResponse.RawServerString == "" {
|
||||
return nil, fmt.Errorf("No server info in response")
|
||||
return nil, fmt.Errorf("no server info in response")
|
||||
}
|
||||
if result := firstPortRe.FindStringSubmatch(nodeInfoResponse.RawServerString); len(result) > 1 {
|
||||
outsidePort = result[1]
|
||||
@@ -615,10 +628,11 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
||||
p = outsidePort
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(p)
|
||||
parsedPort, err := strconv.ParseInt(p, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port := uint32(parsedPort)
|
||||
|
||||
serverConf := strings.Split(nodeInfoResponse.RawServerString, ";")
|
||||
extraServerConf := strings.Split(serverConf[1], "|")
|
||||
@@ -640,27 +654,26 @@ func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse)
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
SpeedLimit: speedLimit,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
Host: host,
|
||||
ServiceName: serviceName,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseUserListResponse parse the response for the given nodeinfo format
|
||||
// ParseUserListResponse parse the response for the given node info format
|
||||
func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]api.UserInfo, error) {
|
||||
c.access.Lock()
|
||||
// Clear Last report log
|
||||
@@ -670,8 +683,8 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
||||
}()
|
||||
|
||||
var deviceLimit, localDeviceLimit int = 0, 0
|
||||
var speedlimit uint64 = 0
|
||||
userList := []api.UserInfo{}
|
||||
var speedLimit uint64 = 0
|
||||
var userList []api.UserInfo
|
||||
for _, user := range *userInfoResponse {
|
||||
if c.DeviceLimit > 0 {
|
||||
deviceLimit = c.DeviceLimit
|
||||
@@ -698,101 +711,147 @@ func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) (*[]
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
speedLimit = uint64((user.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
userList = append(userList, api.UserInfo{
|
||||
UID: user.ID,
|
||||
Email: user.Email,
|
||||
UUID: user.UUID,
|
||||
Passwd: user.Passwd,
|
||||
SpeedLimit: speedlimit,
|
||||
DeviceLimit: deviceLimit,
|
||||
Port: user.Port,
|
||||
Method: user.Method,
|
||||
Protocol: user.Protocol,
|
||||
ProtocolParam: user.ProtocolParam,
|
||||
Obfs: user.Obfs,
|
||||
ObfsParam: user.ObfsParam,
|
||||
UID: user.ID,
|
||||
UUID: user.UUID,
|
||||
Passwd: user.Passwd,
|
||||
SpeedLimit: speedLimit,
|
||||
DeviceLimit: deviceLimit,
|
||||
Port: user.Port,
|
||||
Method: user.Method,
|
||||
})
|
||||
}
|
||||
|
||||
return &userList, nil
|
||||
}
|
||||
|
||||
// ParseSSPanelNodeInfo parse the response for the given nodeinfor format
|
||||
// Only used for SSPanel version >= 2021.11
|
||||
// ParseSSPanelNodeInfo parse the response for the given node info format
|
||||
// Only available for SSPanel version >= 2021.11
|
||||
func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error) {
|
||||
var (
|
||||
speedLimit uint64 = 0
|
||||
enableTLS, enableVless bool
|
||||
alterID uint16 = 0
|
||||
tlsType, transportProtocol string
|
||||
)
|
||||
|
||||
var speedlimit uint64 = 0
|
||||
var EnableTLS, EnableVless bool
|
||||
var AlterID int = 0
|
||||
var TLSType, transportProtocol string
|
||||
|
||||
nodeConfig := new(CustomConfig)
|
||||
json.Unmarshal(nodeInfoResponse.CustomConfig, nodeConfig)
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedlimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedlimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
// Check if custom_config is null
|
||||
if len(nodeInfoResponse.CustomConfig) == 0 {
|
||||
return nil, errors.New("custom_config is empty, disable custom config")
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(nodeConfig.OffsetPortNode)
|
||||
nodeConfig := new(CustomConfig)
|
||||
err := json.Unmarshal(nodeInfoResponse.CustomConfig, nodeConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("custom_config format error: %v", err)
|
||||
}
|
||||
|
||||
if c.SpeedLimit > 0 {
|
||||
speedLimit = uint64((c.SpeedLimit * 1000000) / 8)
|
||||
} else {
|
||||
speedLimit = uint64((nodeInfoResponse.SpeedLimit * 1000000) / 8)
|
||||
}
|
||||
|
||||
parsedPort, err := strconv.ParseInt(nodeConfig.OffsetPortNode, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.NodeType == "Shadowsocks" {
|
||||
port := uint32(parsedPort)
|
||||
|
||||
switch c.NodeType {
|
||||
case "Shadowsocks":
|
||||
transportProtocol = "tcp"
|
||||
}
|
||||
|
||||
if c.NodeType == "V2ray" {
|
||||
case "V2ray":
|
||||
transportProtocol = nodeConfig.Network
|
||||
TLSType = nodeConfig.Security
|
||||
if AlterID, err = strconv.Atoi(nodeConfig.AlterID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if TLSType == "tls" || TLSType == "xtls" {
|
||||
EnableTLS = true
|
||||
tlsType = nodeConfig.Security
|
||||
|
||||
if tlsType == "tls" || tlsType == "xtls" {
|
||||
enableTLS = true
|
||||
}
|
||||
|
||||
if nodeConfig.EnableVless == "1" {
|
||||
EnableVless = true
|
||||
enableVless = true
|
||||
}
|
||||
case "Trojan":
|
||||
enableTLS = true
|
||||
tlsType = "tls"
|
||||
transportProtocol = "tcp"
|
||||
|
||||
if nodeConfig.Security != "" {
|
||||
tlsType = nodeConfig.Security // try to read security from config
|
||||
}
|
||||
|
||||
// Select transport protocol
|
||||
if nodeConfig.Network != "" {
|
||||
transportProtocol = nodeConfig.Network // try to read transport protocol from config
|
||||
}
|
||||
}
|
||||
|
||||
if c.NodeType == "Trojan" {
|
||||
EnableTLS = true
|
||||
TLSType = "tls"
|
||||
if nodeConfig.Grpc == "1" {
|
||||
transportProtocol = "grpc"
|
||||
} else {
|
||||
transportProtocol = "tcp"
|
||||
}
|
||||
|
||||
if nodeConfig.EnableXtls == "1" {
|
||||
TLSType = "xtls"
|
||||
// parse reality config
|
||||
realityConfig := new(api.REALITYConfig)
|
||||
if nodeConfig.RealityOpts != nil {
|
||||
r := nodeConfig.RealityOpts
|
||||
realityConfig = &api.REALITYConfig{
|
||||
Dest: r.Dest,
|
||||
ProxyProtocolVer: r.ProxyProtocolVer,
|
||||
ServerNames: r.ServerNames,
|
||||
PrivateKey: r.PrivateKey,
|
||||
MinClientVer: r.MinClientVer,
|
||||
MaxClientVer: r.MaxClientVer,
|
||||
MaxTimeDiff: r.MaxTimeDiff,
|
||||
ShortIds: r.ShortIds,
|
||||
}
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
SpeedLimit: speedlimit,
|
||||
AlterID: AlterID,
|
||||
SpeedLimit: speedLimit,
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
Host: nodeConfig.Host,
|
||||
Path: nodeConfig.Path,
|
||||
EnableTLS: EnableTLS,
|
||||
TLSType: TLSType,
|
||||
EnableVless: EnableVless,
|
||||
CypherMethod: nodeConfig.MuEncryption,
|
||||
EnableTLS: enableTLS,
|
||||
EnableVless: enableVless,
|
||||
VlessFlow: nodeConfig.Flow,
|
||||
CypherMethod: nodeConfig.Method,
|
||||
ServiceName: nodeConfig.Servicename,
|
||||
Header: nodeConfig.Header,
|
||||
EnableREALITY: nodeConfig.EnableREALITY,
|
||||
REALITYConfig: realityConfig,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// compareVersion, version1 > version2 return 1, version1 < version2 return -1, 0 means equal
|
||||
func compareVersion(version1, version2 string) int {
|
||||
n, m := len(version1), len(version2)
|
||||
i, j := 0, 0
|
||||
for i < n || j < m {
|
||||
x := 0
|
||||
for ; i < n && version1[i] != '.'; i++ {
|
||||
x = x*10 + int(version1[i]-'0')
|
||||
}
|
||||
i++ // jump dot
|
||||
y := 0
|
||||
for ; j < m && version2[j] != '.'; j++ {
|
||||
y = y*10 + int(version2[j]-'0')
|
||||
}
|
||||
j++ // jump dot
|
||||
if x > y {
|
||||
return 1
|
||||
}
|
||||
if x < y {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ func CreateClient() api.API {
|
||||
return client
|
||||
}
|
||||
|
||||
func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
func TestGetV2rayNodeInfo(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
@@ -29,7 +29,7 @@ func TestGetV2rayNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetSSNodeinfo(t *testing.T) {
|
||||
func TestGetSSNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:667",
|
||||
Key: "123",
|
||||
@@ -44,7 +44,7 @@ func TestGetSSNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetTrojanNodeinfo(t *testing.T) {
|
||||
func TestGetTrojanNodeInfo(t *testing.T) {
|
||||
apiConfig := &api.Config{
|
||||
APIHost: "http://127.0.0.1:667",
|
||||
Key: "123",
|
||||
@@ -59,7 +59,7 @@ func TestGetTrojanNodeinfo(t *testing.T) {
|
||||
t.Log(nodeInfo)
|
||||
}
|
||||
|
||||
func TestGetSSinfo(t *testing.T) {
|
||||
func TestGetSSInfo(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
nodeInfo, err := client.GetNodeInfo()
|
||||
@@ -83,7 +83,7 @@ func TestGetUserList(t *testing.T) {
|
||||
func TestReportNodeStatus(t *testing.T) {
|
||||
client := CreateClient()
|
||||
nodeStatus := &api.NodeStatus{
|
||||
1, 1, 1, 256,
|
||||
CPU: 1, Mem: 1, Disk: 1, Uptime: 256,
|
||||
}
|
||||
err := client.ReportNodeStatus(nodeStatus)
|
||||
if err != nil {
|
||||
@@ -105,7 +105,7 @@ func TestReportReportNodeOnlineUsers(t *testing.T) {
|
||||
IP: fmt.Sprintf("1.1.1.%d", i),
|
||||
}
|
||||
}
|
||||
//client.Debug()
|
||||
// client.Debug()
|
||||
err = client.ReportNodeOnlineUsers(&onlineUserList)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -126,7 +126,7 @@ func TestReportReportUserTraffic(t *testing.T) {
|
||||
Download: 114514,
|
||||
}
|
||||
}
|
||||
//client.Debug()
|
||||
// client.Debug()
|
||||
err = client.ReportUserTraffic(&generalUserTraffic)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -148,8 +148,8 @@ func TestReportIllegal(t *testing.T) {
|
||||
client := CreateClient()
|
||||
|
||||
detectResult := []api.DetectResult{
|
||||
api.DetectResult{1, 2},
|
||||
api.DetectResult{1, 3},
|
||||
{1, 2},
|
||||
{1, 3},
|
||||
}
|
||||
client.Debug()
|
||||
err := client.ReportIllegal(&detectResult)
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package v2board
|
||||
|
||||
type UserTraffic struct {
|
||||
UID int `json:"user_id"`
|
||||
Upload int64 `json:"u"`
|
||||
Download int64 `json:"d"`
|
||||
}
|
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"`
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package v2board
|
||||
package v2raysocks
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -8,12 +8,16 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/bitly/go-simplejson"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||
C "github.com/sagernet/sing/common"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
)
|
||||
|
||||
// APIClient create an api client to the panel.
|
||||
@@ -24,7 +28,7 @@ type APIClient struct {
|
||||
Key string
|
||||
NodeType string
|
||||
EnableVless bool
|
||||
EnableXTLS bool
|
||||
VlessFlow string
|
||||
SpeedLimit float64
|
||||
DeviceLimit int
|
||||
LocalRuleList []api.DetectRule
|
||||
@@ -49,7 +53,6 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
log.Print(v.Err)
|
||||
}
|
||||
})
|
||||
client.SetBaseURL(apiConfig.APIHost)
|
||||
// Create Key for each requests
|
||||
client.SetQueryParams(map[string]string{
|
||||
"node_id": strconv.Itoa(apiConfig.NodeID),
|
||||
@@ -64,7 +67,7 @@ func New(apiConfig *api.Config) *APIClient {
|
||||
APIHost: apiConfig.APIHost,
|
||||
NodeType: apiConfig.NodeType,
|
||||
EnableVless: apiConfig.EnableVless,
|
||||
EnableXTLS: apiConfig.EnableXTLS,
|
||||
VlessFlow: apiConfig.VlessFlow,
|
||||
SpeedLimit: apiConfig.SpeedLimit,
|
||||
DeviceLimit: apiConfig.DeviceLimit,
|
||||
LocalRuleList: localRuleList,
|
||||
@@ -80,7 +83,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
// open the file
|
||||
file, err := os.Open(path)
|
||||
|
||||
//handle errors while opening
|
||||
// handle errors while opening
|
||||
if err != nil {
|
||||
log.Printf("Error when opening file: %s", err)
|
||||
return LocalRuleList
|
||||
@@ -98,7 +101,7 @@ func readLocalRuleList(path string) (LocalRuleList []api.DetectRule) {
|
||||
// handle first encountered error while reading
|
||||
if err := fileScanner.Err(); err != nil {
|
||||
log.Fatalf("Error while reading file: %s", err)
|
||||
return make([]api.DetectRule, 0)
|
||||
return
|
||||
}
|
||||
|
||||
file.Close()
|
||||
@@ -132,34 +135,29 @@ func (c *APIClient) parseResponse(res *resty.Response, path string, err error) (
|
||||
}
|
||||
rtn, err := simplejson.NewJson(res.Body())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Ret %s invalid", res.String())
|
||||
return nil, fmt.Errorf("ret %s invalid", res.String())
|
||||
}
|
||||
return rtn, nil
|
||||
}
|
||||
|
||||
// GetNodeInfo will pull NodeInfo Config from sspanel
|
||||
// GetNodeInfo will pull NodeInfo Config from panel
|
||||
func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
var path string
|
||||
var nodeType string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
path = "/api/v1/server/Deepbwork/config"
|
||||
case "Trojan":
|
||||
path = "/api/v1/server/TrojanTidalab/config"
|
||||
case "Shadowsocks":
|
||||
if nodeInfo, err = c.ParseSSNodeResponse(); err == nil {
|
||||
return nodeInfo, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
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().
|
||||
SetQueryParam("local_port", "1").
|
||||
SetQueryParams(map[string]string{
|
||||
"act": "config",
|
||||
"nodetype": nodeType,
|
||||
}).
|
||||
ForceContentType("application/json").
|
||||
Get(path)
|
||||
Get(c.APIHost)
|
||||
|
||||
response, err := c.parseResponse(res, path, err)
|
||||
response, err := c.parseResponse(res, "", err)
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
c.ConfigResp = response
|
||||
@@ -173,7 +171,7 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
case "Trojan":
|
||||
nodeInfo, err = c.ParseTrojanNodeResponse(response)
|
||||
case "Shadowsocks":
|
||||
nodeInfo, err = c.ParseSSNodeResponse()
|
||||
nodeInfo, err = c.ParseSSNodeResponse(response)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
}
|
||||
@@ -186,24 +184,24 @@ func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error) {
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// GetUserList will pull user form sspanel
|
||||
// GetUserList will pull user form panel
|
||||
func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
var path string
|
||||
var nodeType string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
path = "/api/v1/server/Deepbwork/user"
|
||||
case "Trojan":
|
||||
path = "/api/v1/server/TrojanTidalab/user"
|
||||
case "Shadowsocks":
|
||||
path = "/api/v1/server/ShadowsocksTidalab/user"
|
||||
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(path)
|
||||
Get(c.APIHost)
|
||||
|
||||
response, err := c.parseResponse(res, path, err)
|
||||
response, err := c.parseResponse(res, "", err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -212,22 +210,26 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
for i := 0; i < numOfUsers; i++ {
|
||||
user := api.UserInfo{}
|
||||
user.UID = response.Get("data").GetIndex(i).Get("id").MustInt()
|
||||
user.SpeedLimit = uint64(c.SpeedLimit * 1000000 / 8)
|
||||
user.DeviceLimit = c.DeviceLimit
|
||||
switch c.NodeType {
|
||||
case "Shadowsocks":
|
||||
user.Email = response.Get("data").GetIndex(i).Get("secret").MustString()
|
||||
user.Passwd = response.Get("data").GetIndex(i).Get("secret").MustString()
|
||||
user.Method = response.Get("data").GetIndex(i).Get("cipher").MustString()
|
||||
user.Port = response.Get("data").GetIndex(i).Get("port").MustInt()
|
||||
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 = 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 = 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 = 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())
|
||||
user.SpeedLimit = 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
|
||||
@@ -235,15 +237,6 @@ func (c *APIClient) GetUserList() (UserList *[]api.UserInfo, err error) {
|
||||
|
||||
// ReportUserTraffic reports the user traffic
|
||||
func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
var path string
|
||||
switch c.NodeType {
|
||||
case "V2ray":
|
||||
path = "/api/v1/server/Deepbwork/submit"
|
||||
case "Trojan":
|
||||
path = "/api/v1/server/TrojanTidalab/submit"
|
||||
case "Shadowsocks":
|
||||
path = "/api/v1/server/ShadowsocksTidalab/submit"
|
||||
}
|
||||
|
||||
data := make([]UserTraffic, len(*userTraffic))
|
||||
for i, traffic := range *userTraffic {
|
||||
@@ -255,10 +248,14 @@ func (c *APIClient) ReportUserTraffic(userTraffic *[]api.UserTraffic) error {
|
||||
|
||||
res, err := c.client.R().
|
||||
SetQueryParam("node_id", strconv.Itoa(c.NodeID)).
|
||||
SetQueryParams(map[string]string{
|
||||
"act": "submit",
|
||||
"nodetype": strings.ToLower(c.NodeType),
|
||||
}).
|
||||
SetBody(data).
|
||||
ForceContentType("application/json").
|
||||
Post(path)
|
||||
_, err = c.parseResponse(res, path, err)
|
||||
Post(c.APIHost)
|
||||
_, err = c.parseResponse(res, "", err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -272,12 +269,13 @@ func (c *APIClient) GetNodeRule() (*[]api.DetectRule, error) {
|
||||
return &ruleList, nil
|
||||
}
|
||||
|
||||
// V2board only support the rule for v2ray
|
||||
// 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),
|
||||
@@ -292,7 +290,7 @@ func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
//ReportNodeOnlineUsers implements the API interface
|
||||
// ReportNodeOnlineUsers implements the API interface
|
||||
func (c *APIClient) ReportNodeOnlineUsers(onlineUserList *[]api.OnlineUser) error {
|
||||
return nil
|
||||
}
|
||||
@@ -302,77 +300,74 @@ func (c *APIClient) ReportIllegal(detectResultList *[]api.DetectResult) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseTrojanNodeResponse parse the response for the given nodeinfor format
|
||||
// ParseTrojanNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||
var TLSType = "tls"
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
}
|
||||
port := nodeInfoResponse.Get("local_port").MustInt()
|
||||
host := nodeInfoResponse.Get("ssl").Get("sni").MustString()
|
||||
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{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
TransportProtocol: "tcp",
|
||||
EnableTLS: true,
|
||||
TLSType: TLSType,
|
||||
Host: host,
|
||||
}
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseSSNodeResponse parse the response for the given nodeinfor format
|
||||
func (c *APIClient) ParseSSNodeResponse() (*api.NodeInfo, error) {
|
||||
var port int
|
||||
var method string
|
||||
userInfo, err := c.GetUserList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(*userInfo) > 0 {
|
||||
port = (*userInfo)[0].Port
|
||||
method = (*userInfo)[0].Method
|
||||
// ParseSSNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||
var method, serverPsk string
|
||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||
|
||||
port := uint32(inboundInfo.Get("port").MustUint64())
|
||||
method = inboundInfo.Get("settings").Get("method").MustString()
|
||||
// Shadowsocks 2022
|
||||
if C.Contains(shadowaead_2022.List, method) {
|
||||
serverPsk = inboundInfo.Get("settings").Get("password").MustString()
|
||||
} else {
|
||||
userInfo, err := c.GetUserList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(*userInfo) > 0 {
|
||||
method = (*userInfo)[0].Method
|
||||
}
|
||||
}
|
||||
|
||||
// Create GeneralNodeInfo
|
||||
nodeinfo := &api.NodeInfo{
|
||||
nodeInfo := &api.NodeInfo{
|
||||
NodeType: c.NodeType,
|
||||
NodeID: c.NodeID,
|
||||
Port: port,
|
||||
TransportProtocol: "tcp",
|
||||
CypherMethod: method,
|
||||
ServerKey: serverPsk,
|
||||
}
|
||||
|
||||
return nodeinfo, nil
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
// ParseV2rayNodeResponse parse the response for the given nodeinfor format
|
||||
// ParseV2rayNodeResponse parse the response for the given nodeInfo format
|
||||
func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*api.NodeInfo, error) {
|
||||
var TLSType string = "tls"
|
||||
var path, host, serviceName string
|
||||
var header json.RawMessage
|
||||
var enableTLS bool
|
||||
var alterID int = 0
|
||||
if c.EnableXTLS {
|
||||
TLSType = "xtls"
|
||||
}
|
||||
var alterID uint16 = 0
|
||||
|
||||
inboundInfo := simplejson.New()
|
||||
if tmpInboundInfo, ok := nodeInfoResponse.CheckGet("inbound"); ok {
|
||||
inboundInfo = tmpInboundInfo
|
||||
// Compatible with v2board 1.5.5-dev
|
||||
} else if tmpInboundInfo, ok := nodeInfoResponse.CheckGet("inbounds"); ok {
|
||||
tmpInboundInfo := tmpInboundInfo.MustArray()
|
||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||
inboundInfo, _ = simplejson.NewJson(marshalByte)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unable to find inbound(s) in the nodeInfo.")
|
||||
}
|
||||
tmpInboundInfo := nodeInfoResponse.Get("inbounds").MustArray()
|
||||
marshalByte, _ := json.Marshal(tmpInboundInfo[0].(map[string]interface{}))
|
||||
inboundInfo, _ := simplejson.NewJson(marshalByte)
|
||||
|
||||
port := inboundInfo.Get("port").MustInt()
|
||||
port := uint32(inboundInfo.Get("port").MustUint64())
|
||||
transportProtocol := inboundInfo.Get("streamSettings").Get("network").MustString()
|
||||
|
||||
switch transportProtocol {
|
||||
@@ -408,10 +403,10 @@ func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *simplejson.Json) (*
|
||||
AlterID: alterID,
|
||||
TransportProtocol: transportProtocol,
|
||||
EnableTLS: enableTLS,
|
||||
TLSType: TLSType,
|
||||
Path: path,
|
||||
Host: host,
|
||||
EnableVless: c.EnableVless,
|
||||
VlessFlow: c.VlessFlow,
|
||||
ServiceName: serviceName,
|
||||
Header: header,
|
||||
}
|
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,7 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xrayr.app.mydispatcher;
|
||||
option csharp_namespace = "XrayR.App.Myispatcher";
|
||||
option csharp_namespace = "XrayR.App.Mydispatcher";
|
||||
option go_package = "github.com/XrayR-project/XrayR/app/mydispatcher";
|
||||
option java_package = "com.xrayr.app.mydispatcher";
|
||||
option java_multiple_files = true;
|
||||
|
@@ -9,8 +9,6 @@ import (
|
||||
"sync"
|
||||
"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/buf"
|
||||
"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/policy"
|
||||
"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/transport"
|
||||
"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")
|
||||
@@ -98,7 +99,7 @@ type DefaultDispatcher struct {
|
||||
dns dns.Client
|
||||
fdns dns.FakeDNSEngine
|
||||
Limiter *limiter.Limiter
|
||||
RuleManager *rule.RuleManager
|
||||
RuleManager *rule.Manager
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -139,79 +140,14 @@ func (*DefaultDispatcher) Start() error {
|
||||
}
|
||||
|
||||
// Close implements common.Closable.
|
||||
func (*DefaultDispatcher) Close() error { return nil }
|
||||
func (*DefaultDispatcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, error) {
|
||||
downOpt := pipe.OptionsFromContext(ctx)
|
||||
upOpt := downOpt
|
||||
|
||||
if network == net.Network_UDP {
|
||||
var ip2domain *sync.Map // net.IP.String() => domain, this map is used by server side when client turn on fakedns
|
||||
// Client will send domain address in the buffer.UDP.Address, server record all possible target IP addrs.
|
||||
// When target replies, server will restore the domain and send back to client.
|
||||
// Note: this map is not global but per connection context
|
||||
upOpt = append(upOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
||||
for i, buffer := range mb {
|
||||
if buffer.UDP == nil {
|
||||
continue
|
||||
}
|
||||
addr := buffer.UDP.Address
|
||||
if addr.Family().IsIP() {
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(addr) && sniffing.Enabled {
|
||||
domain := fkr0.GetDomainFromFakeDNS(addr)
|
||||
if len(domain) > 0 {
|
||||
buffer.UDP.Address = net.DomainAddress(domain)
|
||||
newError("[fakedns client] override with domain: ", domain, " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
} else {
|
||||
newError("[fakedns client] failed to find domain! :", addr.String(), " for xUDP buffer at ", i).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ip2domain == nil {
|
||||
ip2domain = new(sync.Map)
|
||||
newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
domain := addr.Domain()
|
||||
ips, err := d.dns.LookupIP(domain, dns.IPOption{true, true, false})
|
||||
if err == nil {
|
||||
for _, ip := range ips {
|
||||
ip2domain.Store(ip.String(), domain)
|
||||
}
|
||||
newError("[fakedns client] candidate ip: "+fmt.Sprintf("%v", ips), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
} else {
|
||||
newError("[fakedns client] failed to look up IP for ", domain, " for xUDP buffer at ", i).Base(err).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mb
|
||||
}))
|
||||
downOpt = append(downOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
|
||||
for i, buffer := range mb {
|
||||
if buffer.UDP == nil {
|
||||
continue
|
||||
}
|
||||
addr := buffer.UDP.Address
|
||||
if addr.Family().IsIP() {
|
||||
if ip2domain == nil {
|
||||
continue
|
||||
}
|
||||
if domain, found := ip2domain.Load(addr.IP().String()); found {
|
||||
buffer.UDP.Address = net.DomainAddress(domain.(string))
|
||||
newError("[fakedns client] restore domain: ", domain.(string), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
} else {
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok {
|
||||
fakeIp := fkr0.GetFakeIPForDomain(addr.Domain())
|
||||
buffer.UDP.Address = fakeIp[0]
|
||||
newError("[fakedns client] restore FakeIP: ", buffer.UDP, fmt.Sprintf("%v", fakeIp), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mb
|
||||
}))
|
||||
}
|
||||
uplinkReader, uplinkWriter := pipe.New(upOpt...)
|
||||
downlinkReader, downlinkWriter := pipe.New(downOpt...)
|
||||
opt := pipe.OptionsFromContext(ctx)
|
||||
uplinkReader, uplinkWriter := pipe.New(opt...)
|
||||
downlinkReader, downlinkWriter := pipe.New(opt...)
|
||||
|
||||
inboundLink := &transport.Link{
|
||||
Reader: downlinkReader,
|
||||
@@ -233,7 +169,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
|
||||
// Speed Limit and Device Limit
|
||||
bucket, ok, reject := d.Limiter.GetUserBucket(sessionInbound.Tag, user.Email, sessionInbound.Source.Address.IP().String())
|
||||
if reject {
|
||||
newError("Devices reach the limit: ", user.Email).AtError().WriteToLog()
|
||||
newError("Devices reach the limit: ", user.Email).AtWarning().WriteToLog()
|
||||
common.Close(outboundLink.Writer)
|
||||
common.Close(inboundLink.Writer)
|
||||
common.Interrupt(outboundLink.Reader)
|
||||
@@ -244,6 +180,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sn
|
||||
inboundLink.Writer = d.Limiter.RateWriter(inboundLink.Writer, bucket)
|
||||
outboundLink.Writer = d.Limiter.RateWriter(outboundLink.Writer, bucket)
|
||||
}
|
||||
|
||||
p := d.policy.ForLevel(user.Level)
|
||||
if p.Stats.UserUplink {
|
||||
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
|
||||
@@ -318,33 +255,15 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case !sniffingRequest.Enabled:
|
||||
if !sniffingRequest.Enabled {
|
||||
go d.routedDispatch(ctx, outbound, destination)
|
||||
case destination.Network != net.Network_TCP:
|
||||
// Only metadata sniff will be used for non tcp connection
|
||||
result, err := sniffer(ctx, nil, true)
|
||||
if err == nil {
|
||||
content.Protocol = result.Protocol()
|
||||
if d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
||||
ob.RouteTarget = destination
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
}
|
||||
}
|
||||
go d.routedDispatch(ctx, outbound, destination)
|
||||
default:
|
||||
} else {
|
||||
go func() {
|
||||
cReader := &cachedReader{
|
||||
reader: outbound.Reader.(*pipe.Reader),
|
||||
}
|
||||
outbound.Reader = cReader
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||
if err == nil {
|
||||
content.Protocol = result.Protocol()
|
||||
}
|
||||
@@ -379,33 +298,15 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
ctx = session.ContextWithContent(ctx, content)
|
||||
}
|
||||
sniffingRequest := content.SniffingRequest
|
||||
switch {
|
||||
case !sniffingRequest.Enabled:
|
||||
if !sniffingRequest.Enabled {
|
||||
go d.routedDispatch(ctx, outbound, destination)
|
||||
case destination.Network != net.Network_TCP:
|
||||
// Only metadata sniff will be used for non tcp connection
|
||||
result, err := sniffer(ctx, nil, true)
|
||||
if err == nil {
|
||||
content.Protocol = result.Protocol()
|
||||
if d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
|
||||
ob.RouteTarget = destination
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
}
|
||||
}
|
||||
go d.routedDispatch(ctx, outbound, destination)
|
||||
default:
|
||||
} else {
|
||||
go func() {
|
||||
cReader := &cachedReader{
|
||||
reader: outbound.Reader.(*pipe.Reader),
|
||||
}
|
||||
outbound.Reader = cReader
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly)
|
||||
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network)
|
||||
if err == nil {
|
||||
content.Protocol = result.Protocol()
|
||||
}
|
||||
@@ -422,10 +323,11 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
d.routedDispatch(ctx, outbound, destination)
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (SniffResult, error) {
|
||||
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
|
||||
payload := buf.New()
|
||||
defer payload.Release()
|
||||
|
||||
@@ -451,7 +353,7 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
|
||||
|
||||
cReader.Cache(payload)
|
||||
if !payload.IsEmpty() {
|
||||
result, err := sniffer.Sniff(ctx, payload.Bytes())
|
||||
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
|
||||
if err != common.ErrNoClue {
|
||||
return result, err
|
||||
}
|
||||
@@ -501,7 +403,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
}
|
||||
}
|
||||
|
||||
routingLink := routing_session.AsRoutingContext(ctx)
|
||||
routingLink := routingSession.AsRoutingContext(ctx)
|
||||
inTag := routingLink.GetInboundTag()
|
||||
isPickRoute := 0
|
||||
if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" {
|
||||
@@ -532,7 +434,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
handler = d.ohm.GetHandler(inTag) // Default outbound hander tag should be as same as the inbound tag
|
||||
handler = d.ohm.GetHandler(inTag) // Default outbound handler tag should be as same as the inbound tag
|
||||
}
|
||||
|
||||
// If there is no outbound with tag as same as the inbound tag
|
||||
|
@@ -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
|
||||
|
||||
//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"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
return errors.New(values...)
|
||||
}
|
||||
|
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
||||
"github.com/xtls/xray-core/common/protocol/http"
|
||||
"github.com/xtls/xray-core/common/protocol/quic"
|
||||
"github.com/xtls/xray-core/common/protocol/tls"
|
||||
)
|
||||
|
||||
@@ -22,6 +24,7 @@ type protocolSnifferWithMetadata struct {
|
||||
// for both TCP and UDP connections
|
||||
// It will not be shown as a traffic type for routing unless there is no other successful sniffing.
|
||||
metadataSniffer bool
|
||||
network net.Network
|
||||
}
|
||||
|
||||
type Sniffer struct {
|
||||
@@ -31,9 +34,11 @@ type Sniffer struct {
|
||||
func NewSniffer(ctx context.Context) *Sniffer {
|
||||
ret := &Sniffer{
|
||||
sniffer: []protocolSnifferWithMetadata{
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP},
|
||||
},
|
||||
}
|
||||
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
|
||||
@@ -49,11 +54,11 @@ func NewSniffer(ctx context.Context) *Sniffer {
|
||||
|
||||
var errUnknownContent = newError("unknown content")
|
||||
|
||||
func (s *Sniffer) Sniff(c context.Context, payload []byte) (SniffResult, error) {
|
||||
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
||||
var pendingSniffer []protocolSnifferWithMetadata
|
||||
for _, si := range s.sniffer {
|
||||
s := si.protocolSniffer
|
||||
if si.metadataSniffer {
|
||||
if si.metadataSniffer || si.network != network {
|
||||
continue
|
||||
}
|
||||
result, err := s(c, payload)
|
||||
|
@@ -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"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"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"
|
||||
goCache "github.com/patrickmn/go-cache"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/juju/ratelimit"
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
@@ -20,8 +31,12 @@ type InboundInfo struct {
|
||||
Tag string
|
||||
NodeSpeedLimit uint64
|
||||
UserInfo *sync.Map // Key: Email value: UserInfo
|
||||
BucketHub *sync.Map // key: Email, value: *ratelimit.Bucket
|
||||
UserOnlineIP *sync.Map // Key: Email Value: *sync.Map: Key: IP, Value: UID
|
||||
BucketHub *sync.Map // key: Email, value: *rate.Limiter
|
||||
UserOnlineIP *sync.Map // Key: Email, value: {Key: IP, value: UID}
|
||||
GlobalLimit struct {
|
||||
config *GlobalDeviceLimitConfig
|
||||
globalOnlineIP *marshaler.Marshaler
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
Tag: tag,
|
||||
NodeSpeedLimit: nodeSpeedLimit,
|
||||
BucketHub: 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)
|
||||
for _, u := range *userList {
|
||||
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 {
|
||||
|
||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
// Update User info
|
||||
@@ -65,7 +103,17 @@ func (l *Limiter) UpdateInboundLimiter(tag string, updatedUserList *[]api.UserIn
|
||||
SpeedLimit: u.SpeedLimit,
|
||||
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 {
|
||||
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) {
|
||||
onlineUser := make([]api.OnlineUser, 0)
|
||||
var onlineUser []api.OnlineUser
|
||||
|
||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
// 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
|
||||
})
|
||||
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
|
||||
email := key.(string)
|
||||
ipMap := value.(*sync.Map)
|
||||
ipMap.Range(func(key, value interface{}) bool {
|
||||
ip := key.(string)
|
||||
uid := value.(int)
|
||||
ip := key.(string)
|
||||
onlineUser = append(onlineUser, api.OnlineUser{UID: uid, IP: ip})
|
||||
return true
|
||||
})
|
||||
email := key.(string)
|
||||
inboundInfo.UserOnlineIP.Delete(email) // Reset online device
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
return nil, fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||
}
|
||||
|
||||
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 {
|
||||
var (
|
||||
userLimit uint64 = 0
|
||||
deviceLimit, uid int
|
||||
)
|
||||
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
nodeLimit := inboundInfo.NodeSpeedLimit
|
||||
var userLimit uint64 = 0
|
||||
var deviceLimit int = 0
|
||||
var uid int = 0
|
||||
|
||||
if v, ok := inboundInfo.UserInfo.Load(email); ok {
|
||||
u := v.(UserInfo)
|
||||
uid = u.UID
|
||||
userLimit = u.SpeedLimit
|
||||
deviceLimit = u.DeviceLimit
|
||||
}
|
||||
// Report online device
|
||||
|
||||
// Local device limit
|
||||
ipMap := new(sync.Map)
|
||||
ipMap.Store(ip, uid)
|
||||
// If any device is online
|
||||
if v, ok := inboundInfo.UserOnlineIP.LoadOrStore(email, ipMap); ok {
|
||||
ipMap := v.(*sync.Map)
|
||||
// If this ip is a new device
|
||||
// If this is a new ip
|
||||
if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
|
||||
counter := 0
|
||||
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 {
|
||||
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 {
|
||||
bucket := v.(*ratelimit.Bucket)
|
||||
bucket := v.(*rate.Limiter)
|
||||
return bucket, true, false
|
||||
} else {
|
||||
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, uniqueKey, ipMap)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// push the ip to cache
|
||||
func pushIP(inboundInfo *InboundInfo, uniqueKey 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, uniqueKey, ipMap); err != nil {
|
||||
newError("cache service").Base(err).AtError().WriteToLog()
|
||||
}
|
||||
}
|
||||
|
||||
// determineRate returns the minimum non-zero rate
|
||||
func determineRate(nodeLimit, userLimit uint64) (limit uint64) {
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/juju/ratelimit"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
writer buf.Writer
|
||||
limiter *ratelimit.Bucket
|
||||
limiter *rate.Limiter
|
||||
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{
|
||||
writer: writer,
|
||||
limiter: limiter,
|
||||
@@ -26,6 +27,7 @@ func (w *Writer) Close() 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)
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package mylego
|
||||
|
||||
import (
|
||||
"crypto"
|
@@ -1,4 +1,4 @@
|
||||
package cmd
|
||||
package mylego
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
@@ -6,18 +6,16 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"golang.org/x/crypto/acme"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,56 +28,53 @@ const (
|
||||
//
|
||||
// rootPath:
|
||||
//
|
||||
// ./.lego/accounts/
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
// ./.lego/accounts/
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
//
|
||||
// rootUserPath:
|
||||
//
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
//
|
||||
// keysPath:
|
||||
//
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/keys/
|
||||
// │ │ │ │ └── root keys directory
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/keys/
|
||||
// │ │ │ │ └── root keys directory
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
//
|
||||
// accountFilePath:
|
||||
//
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/account.json
|
||||
// │ │ │ │ └── account file
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
//
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/account.json
|
||||
// │ │ │ │ └── account file
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
type AccountsStorage struct {
|
||||
userID string
|
||||
rootPath string
|
||||
rootUserPath string
|
||||
keysPath string
|
||||
accountFilePath string
|
||||
ctx *cli.Context
|
||||
}
|
||||
|
||||
// NewAccountsStorage Creates a new AccountsStorage.
|
||||
func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
||||
// TODO: move to account struct? Currently MUST pass email.
|
||||
email := getEmail(ctx)
|
||||
func NewAccountsStorage(l *LegoCMD) *AccountsStorage {
|
||||
email := l.C.Email
|
||||
|
||||
serverURL, err := url.Parse(ctx.GlobalString("server"))
|
||||
serverURL, err := url.Parse(acme.LetsEncryptURL)
|
||||
if err != nil {
|
||||
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)
|
||||
accountsPath := filepath.Join(rootPath, serverPath)
|
||||
rootUserPath := filepath.Join(accountsPath, email)
|
||||
@@ -90,7 +85,6 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
||||
rootUserPath: rootUserPath,
|
||||
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
|
||||
accountFilePath: filepath.Join(rootUserPath, accountFileName),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,11 +116,11 @@ func (s *AccountsStorage) Save(account *Account) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(s.accountFilePath, jsonBytes, filePerm)
|
||||
return os.WriteFile(s.accountFilePath, jsonBytes, filePerm)
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
||||
fileBytes, err := ioutil.ReadFile(s.accountFilePath)
|
||||
fileBytes, err := os.ReadFile(s.accountFilePath)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
if account.Registration == nil || account.Registration.Body.Status == "" {
|
||||
reg, err := tryRecoverRegistration(s.ctx, privateKey)
|
||||
reg, err := tryRecoverRegistration(privateKey)
|
||||
if err != nil {
|
||||
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) {
|
||||
keyBytes, err := ioutil.ReadFile(file)
|
||||
keyBytes, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -224,11 +218,11 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
||||
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.
|
||||
config := lego.NewConfig(&Account{key: privateKey})
|
||||
config.CADirURL = ctx.GlobalString("server")
|
||||
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
|
||||
config.CADirURL = acme.LetsEncryptURL
|
||||
config.UserAgent = "lego-cli/dev"
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
@@ -1,56 +1,45 @@
|
||||
package cmd
|
||||
package mylego
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/urfave/cli"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
const (
|
||||
baseCertificatesFolderName = "certificates"
|
||||
baseArchivesFolderName = "archives"
|
||||
)
|
||||
|
||||
// CertificatesStorage a certificates storage.
|
||||
// CertificatesStorage a certificates' storage.
|
||||
//
|
||||
// rootPath:
|
||||
//
|
||||
// ./.lego/certificates/
|
||||
// │ └── root certificates directory
|
||||
// └── "path" option
|
||||
// ./.lego/certificates/
|
||||
// │ └── root certificates directory
|
||||
// └── "path" option
|
||||
//
|
||||
// archivePath:
|
||||
//
|
||||
// ./.lego/archives/
|
||||
// │ └── archived certificates directory
|
||||
// └── "path" option
|
||||
//
|
||||
// ./.lego/archives/
|
||||
// │ └── archived certificates directory
|
||||
// └── "path" option
|
||||
type CertificatesStorage struct {
|
||||
rootPath string
|
||||
archivePath string
|
||||
pem bool
|
||||
filename string // Deprecated
|
||||
rootPath string
|
||||
pem bool
|
||||
}
|
||||
|
||||
// NewCertificatesStorage create a new certificates storage.
|
||||
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
|
||||
func NewCertificatesStorage(path string) *CertificatesStorage {
|
||||
return &CertificatesStorage{
|
||||
rootPath: filepath.Join(ctx.GlobalString("path"), baseCertificatesFolderName),
|
||||
archivePath: filepath.Join(ctx.GlobalString("path"), baseArchivesFolderName),
|
||||
pem: ctx.GlobalBool("pem"),
|
||||
filename: ctx.GlobalString("filename"),
|
||||
rootPath: filepath.Join(path, baseCertificatesFolderName),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
return s.rootPath
|
||||
}
|
||||
@@ -144,7 +126,7 @@ func (s *CertificatesStorage) ExistsFile(domain, extension string) bool {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -163,36 +145,11 @@ func (s *CertificatesStorage) ReadCertificate(domain, extension string) ([]*x509
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
|
||||
var baseFileName string
|
||||
if s.filename != "" {
|
||||
baseFileName = s.filename
|
||||
} else {
|
||||
baseFileName = sanitizedDomain(domain)
|
||||
}
|
||||
var baseFileName = sanitizedDomain(domain)
|
||||
|
||||
filePath := filepath.Join(s.rootPath, baseFileName+extension)
|
||||
|
||||
return ioutil.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
|
||||
return os.WriteFile(filePath, data, filePerm)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
"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"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
return errors.New(values...)
|
||||
}
|
||||
|
@@ -8,23 +8,24 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
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
|
||||
InboundDetectResult *sync.Map // key: Tag, Value: mapset.NewSet []api.DetectResult
|
||||
}
|
||||
|
||||
func New() *RuleManager {
|
||||
return &RuleManager{
|
||||
func New() *Manager {
|
||||
return &Manager{
|
||||
InboundRule: 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 {
|
||||
oldRuleList := value.([]api.DetectRule)
|
||||
if !reflect.DeepEqual(oldRuleList, newRuleList) {
|
||||
@@ -34,7 +35,7 @@ func (r *RuleManager) UpdateRule(tag string, newRuleList []api.DetectRule) error
|
||||
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)
|
||||
if value, ok := r.InboundDetectResult.LoadAndDelete(tag); ok {
|
||||
resultSet := value.(mapset.Set)
|
||||
@@ -46,9 +47,9 @@ func (r *RuleManager) GetDetectResult(tag string) (*[]api.DetectResult, error) {
|
||||
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
|
||||
var hitRuleID int = -1
|
||||
var hitRuleID = -1
|
||||
// If we have some rule for this inbound
|
||||
if value, ok := r.InboundRule.Load(tag); ok {
|
||||
ruleList := value.([]api.DetectRule)
|
||||
|
@@ -3,39 +3,51 @@ package serverstatus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
// 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)
|
||||
// Check if cpuPercent is empty
|
||||
if len(cpuPercent) > 0 {
|
||||
if len(cpuPercent) > 0 && err == nil {
|
||||
Cpu = cpuPercent[0]
|
||||
} else {
|
||||
Cpu = 0
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, fmt.Errorf("get cpu usage failed: %s", err)
|
||||
errorString += fmt.Sprintf("get cpu usage failed: %s ", err)
|
||||
}
|
||||
|
||||
memUsage, err := mem.VirtualMemory()
|
||||
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("/")
|
||||
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())
|
||||
return Cpu, memUsage.UsedPercent, diskUsage.UsedPercent, Uptime, nil
|
||||
uptime, err := host.Uptime()
|
||||
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
|
||||
}
|
||||
|
285
go.mod
285
go.mod
@@ -1,175 +1,232 @@
|
||||
module github.com/XrayR-project/XrayR
|
||||
|
||||
go 1.18
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.1
|
||||
|
||||
require (
|
||||
github.com/bitly/go-simplejson v0.5.0
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||
dario.cat/mergo v1.0.0
|
||||
github.com/bitly/go-simplejson v0.5.1
|
||||
github.com/deckarep/golang-set v1.8.0
|
||||
github.com/fsnotify/fsnotify v1.5.4
|
||||
github.com/go-acme/lego/v4 v4.7.0
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
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/juju/ratelimit v1.0.1
|
||||
github.com/eko/gocache/lib/v4 v4.1.5
|
||||
github.com/eko/gocache/store/go_cache/v4 v4.2.1
|
||||
github.com/eko/gocache/store/redis/v4 v4.2.1
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-acme/lego/v4 v4.14.2
|
||||
github.com/go-resty/resty/v2 v2.9.1
|
||||
github.com/gogf/gf/v2 v2.5.4
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/r3labs/diff/v2 v2.15.1
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/urfave/cli v1.22.9
|
||||
github.com/xtls/xray-core v1.5.9
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0
|
||||
github.com/redis/go-redis/v9 v9.2.1
|
||||
github.com/sagernet/sing v0.2.13
|
||||
github.com/sagernet/sing-shadowsocks v0.2.5
|
||||
github.com/shirou/gopsutil/v3 v3.23.9
|
||||
github.com/spf13/viper v1.17.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/xtls/xray-core v1.8.4
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/net v0.16.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.6.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
|
||||
cloud.google.com/go/compute v1.23.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1621 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // 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/aws/aws-sdk-go v1.44.26 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.40.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/civo/civogo v0.3.11 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.70.0 // indirect
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.11.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 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-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dnsimple/dnsimple-go v0.71.1 // indirect
|
||||
github.com/exoscale/egoscale v1.19.0 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||
github.com/exoscale/egoscale v0.100.1 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // 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.4.1 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // 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.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
|
||||
github.com/gophercloud/gophercloud v0.25.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20220307143606-8e7800759d16 // indirect
|
||||
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||
github.com/jarcoal/httpmock v1.2.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||
github.com/linode/linodego v1.6.0 // indirect
|
||||
github.com/linode/linodego v1.17.2 // 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/lucas-clemente/quic-go v0.28.0 // 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-17 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/miekg/dns v1.1.50 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.7.1 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.0.1 // indirect
|
||||
github.com/nrdcg/desec v0.6.0 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||
github.com/nrdcg/desec v0.7.0 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.8.1 // indirect
|
||||
github.com/nrdcg/goinwx v0.8.2 // indirect
|
||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||
github.com/nrdcg/porkbun v0.1.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.2.0 // indirect
|
||||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
github.com/ovh/go-ovh v1.1.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/ovh/go-ovh v1.4.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/otp v1.3.0 // indirect
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 // indirect
|
||||
github.com/refraction-networking/utls v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/pquerna/otp v1.4.0 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||
github.com/quic-go/quic-go v0.38.1 // indirect
|
||||
github.com/refraction-networking/utls v1.4.3 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sacloud/libsacloud v1.36.2 // indirect
|
||||
github.com/sagernet/sing v0.0.0-20220714145306-09b55ce4b6d0 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220716012931-952ae62e05d7 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.8 // indirect
|
||||
github.com/sacloud/go-http v0.1.6 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.11.1 // indirect
|
||||
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.0.5 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.2 // indirect
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/subosito/gotenv v1.3.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.412 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.412 // indirect
|
||||
github.com/tklauser/numcpus v0.5.0 // indirect
|
||||
github.com/transip/gotransip/v6 v6.17.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/transip/gotransip/v6 v6.20.0 // indirect
|
||||
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/vultr/govultr/v2 v2.17.0 // indirect
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.starlark.net v0.0.0-20220714194419-4cadf0a12139 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 // 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.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
google.golang.org/api v0.82.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.6.5 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/oauth2 v0.12.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
google.golang.org/api v0.143.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
google.golang.org/grpc v1.58.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.1.7 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // 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
|
||||
replace github.com/exoscale/egoscale => github.com/exoscale/egoscale v0.102.0
|
||||
|
@@ -6,15 +6,14 @@ 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
|
||||
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
|
||||
ConnetionConfig:
|
||||
ConnectionConfig:
|
||||
Handshake: 4 # Handshake time limit, Second
|
||||
ConnIdle: 30 # Connection idle time limit, Second
|
||||
UplinkOnly: 2 # Time limit when the connection downstream is closed, Second
|
||||
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
|
||||
BufferSize: 64 # The internal cache size of each connection, kB
|
||||
BufferSize: 64 # The internal cache size of each connection, kB
|
||||
Nodes:
|
||||
-
|
||||
PanelType: "SSpanel" # Panel type: SSpanel, V2board, PMpanel, , Proxypanel
|
||||
- PanelType: "SSpanel" # Panel type: SSpanel, NewV2board, PMpanel, Proxypanel, V2RaySocks, GoV2Panel
|
||||
ApiConfig:
|
||||
ApiHost: "http://127.0.0.1:667"
|
||||
ApiKey: "123"
|
||||
@@ -22,10 +21,11 @@ Nodes:
|
||||
NodeType: V2ray # Node type: V2ray, Shadowsocks, Trojan, Shadowsocks-Plugin
|
||||
Timeout: 30 # Timeout for the api request
|
||||
EnableVless: false # Enable Vless for V2ray Type
|
||||
EnableXTLS: false # Enable XTLS for V2ray and Trojan
|
||||
VlessFlow: "xtls-rprx-vision" # Only support vless
|
||||
SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable
|
||||
DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable
|
||||
RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file
|
||||
DisableCustomConfig: false # disable custom config for sspanel
|
||||
ControllerConfig:
|
||||
ListenIP: 0.0.0.0 # IP address you want to listen
|
||||
SendIP: 0.0.0.0 # IP address you want to send pacakage
|
||||
@@ -33,16 +33,42 @@ Nodes:
|
||||
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
||||
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
||||
EnableProxyProtocol: false # Only works for WebSocket and TCP
|
||||
AutoSpeedLimitConfig:
|
||||
Limit: 0 # Warned speed. Set to 0 to disable AutoSpeedLimit (mbps)
|
||||
WarnTimes: 0 # After (WarnTimes) consecutive warnings, the user will be limited. Set to 0 to punish overspeed user immediately.
|
||||
LimitSpeed: 0 # The speedlimit of a limited user (unit: mbps)
|
||||
LimitDuration: 0 # How many minutes will the limiting last (unit: minute)
|
||||
GlobalDeviceLimitConfig:
|
||||
Enable: false # Enable the global device limit of a user
|
||||
RedisAddr: 127.0.0.1:6379 # The redis server address
|
||||
RedisPassword: YOUR PASSWORD # Redis password
|
||||
RedisDB: 0 # Redis DB
|
||||
Timeout: 5 # Timeout for redis request
|
||||
Expiry: 60 # Expiry time (second)
|
||||
EnableFallback: false # Only support for Trojan and Vless
|
||||
FallBackConfigs: # Support multiple fallbacks
|
||||
-
|
||||
SNI: # TLS SNI(Server Name Indication), Empty for any
|
||||
- SNI: # TLS SNI(Server Name Indication), Empty for any
|
||||
Alpn: # Alpn, Empty for any
|
||||
Path: # HTTP PATH, Empty for any
|
||||
Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
|
||||
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for dsable
|
||||
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||
DisableLocalREALITYConfig: false # disable local reality config
|
||||
EnableREALITY: true # Enable REALITY
|
||||
REALITYConfigs:
|
||||
Show: true # Show REALITY debug
|
||||
Dest: www.smzdm.com:443 # Required, Same as fallback
|
||||
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||
ServerNames: # Required, list of available serverNames for the client, * wildcard is not supported at the moment.
|
||||
- www.smzdm.com
|
||||
PrivateKey: YOUR_PRIVATE_KEY # Required, execute './xray x25519' to generate.
|
||||
MinClientVer: # Optional, minimum version of Xray client, format is x.y.z.
|
||||
MaxClientVer: # Optional, maximum version of Xray client, format is x.y.z.
|
||||
MaxTimeDiff: 0 # Optional, maximum allowed time difference, unit is in milliseconds.
|
||||
ShortIds: # Required, list of available shortIds for the client, can be used to differentiate between different clients.
|
||||
- ""
|
||||
- 0123456789abcdef
|
||||
CertConfig:
|
||||
CertMode: dns # Option about how to get certificate: none, file, http, 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
|
||||
CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
||||
KeyFile: /etc/XrayR/cert/node1.test.com.key
|
||||
@@ -51,30 +77,66 @@ Nodes:
|
||||
DNSEnv: # DNS ENV option used by DNS provider
|
||||
ALICLOUD_ACCESS_KEY: aaa
|
||||
ALICLOUD_SECRET_KEY: bbb
|
||||
# -
|
||||
# PanelType: "V2board" # Panel type: SSpanel, V2board
|
||||
# ApiConfig:
|
||||
# ApiHost: "http://127.0.0.1:668"
|
||||
# ApiKey: "123"
|
||||
# NodeID: 4
|
||||
# NodeType: Shadowsocks # Node type: V2ray, Shadowsocks, Trojan
|
||||
# Timeout: 30 # Timeout for the api request
|
||||
# EnableVless: false # Enable Vless for V2ray Type
|
||||
# EnableXTLS: false # Enable XTLS for V2ray and Trojan
|
||||
# SpeedLimit: 0 # Mbps, Local settings will replace remote settings
|
||||
# DeviceLimit: 0 # Local settings will replace remote settings
|
||||
# ControllerConfig:
|
||||
# ListenIP: 0.0.0.0 # IP address you want to listen
|
||||
# UpdatePeriodic: 10 # Time to update the nodeinfo, how many sec.
|
||||
# EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
||||
# CertConfig:
|
||||
# CertMode: dns # Option about how to get certificate: none, file, http, dns
|
||||
# CertDomain: "node1.test.com" # Domain to cert
|
||||
# CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
||||
# KeyFile: /etc/XrayR/cert/node1.test.com.pem
|
||||
# Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
|
||||
# Email: test@me.com
|
||||
# DNSEnv: # DNS ENV option used by DNS provider
|
||||
# ALICLOUD_ACCESS_KEY: aaa
|
||||
# ALICLOUD_SECRET_KEY: bbb
|
||||
|
||||
# - PanelType: "SSpanel" # Panel type: SSpanel, V2board, NewV2board, PMpanel, Proxypanel, V2RaySocks, GoV2Panel
|
||||
# ApiConfig:
|
||||
# ApiHost: "http://127.0.0.1:668"
|
||||
# ApiKey: "123"
|
||||
# NodeID: 41
|
||||
# NodeType: V2ray # Node type: V2ray, Shadowsocks, Trojan, Shadowsocks-Plugin
|
||||
# Timeout: 30 # Timeout for the api request
|
||||
# EnableVless: false # Enable Vless for V2ray Type
|
||||
# VlessFlow: "xtls-rprx-vision" # Only support vless
|
||||
# SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable
|
||||
# DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable
|
||||
# RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file
|
||||
# ControllerConfig:
|
||||
# ListenIP: 0.0.0.0 # IP address you want to listen
|
||||
# SendIP: 0.0.0.0 # IP address you want to send pacakage
|
||||
# UpdatePeriodic: 60 # Time to update the nodeinfo, how many sec.
|
||||
# EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
|
||||
# DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
|
||||
# EnableProxyProtocol: false # Only works for WebSocket and TCP
|
||||
# AutoSpeedLimitConfig:
|
||||
# Limit: 0 # Warned speed. Set to 0 to disable AutoSpeedLimit (mbps)
|
||||
# WarnTimes: 0 # After (WarnTimes) consecutive warnings, the user will be limited. Set to 0 to punish overspeed user immediately.
|
||||
# LimitSpeed: 0 # The speedlimit of a limited user (unit: mbps)
|
||||
# LimitDuration: 0 # How many minutes will the limiting last (unit: minute)
|
||||
# GlobalDeviceLimitConfig:
|
||||
# Enable: false # Enable the global device limit of a user
|
||||
# RedisAddr: 127.0.0.1:6379 # The redis server address
|
||||
# RedisPassword: YOUR PASSWORD # Redis password
|
||||
# RedisDB: 0 # Redis DB
|
||||
# Timeout: 5 # Timeout for redis request
|
||||
# Expiry: 60 # Expiry time (second)
|
||||
# EnableFallback: false # Only support for Trojan and Vless
|
||||
# FallBackConfigs: # Support multiple fallbacks
|
||||
# - SNI: # TLS SNI(Server Name Indication), Empty for any
|
||||
# Alpn: # Alpn, Empty for any
|
||||
# Path: # HTTP PATH, Empty for any
|
||||
# Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
|
||||
# ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||
# EnableREALITY: true # Enable REALITY
|
||||
# REALITYConfigs:
|
||||
# Show: true # Show REALITY debug
|
||||
# Dest: www.smzdm.com:443 # Required, Same as fallback
|
||||
# ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
|
||||
# ServerNames: # Required, list of available serverNames for the client, * wildcard is not supported at the moment.
|
||||
# - www.smzdm.com
|
||||
# PrivateKey: YOUR_PRIVATE_KEY # Required, execute './xray x25519' to generate.
|
||||
# MinClientVer: # Optional, minimum version of Xray client, format is x.y.z.
|
||||
# MaxClientVer: # Optional, maximum version of Xray client, format is x.y.z.
|
||||
# MaxTimeDiff: 0 # Optional, maximum allowed time difference, unit is in milliseconds.
|
||||
# ShortIds: # Required, list of available shortIds for the client, can be used to differentiate between different clients.
|
||||
# - ""
|
||||
# - 0123456789abcdef
|
||||
# CertConfig:
|
||||
# CertMode: dns # Option about how to get certificate: none, file, http, tls, dns. Choose "none" will forcedly disable the tls config.
|
||||
# CertDomain: "node1.test.com" # Domain to cert
|
||||
# CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
|
||||
# KeyFile: /etc/XrayR/cert/node1.test.com.key
|
||||
# Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
|
||||
# Email: test@me.com
|
||||
# DNSEnv: # DNS ENV option used by DNS provider
|
||||
# ALICLOUD_ACCESS_KEY: aaa
|
||||
# ALICLOUD_SECRET_KEY: bbb
|
||||
|
@@ -1,19 +1,19 @@
|
||||
[
|
||||
{
|
||||
"listen": "0.0.0.0",
|
||||
"port": 1234,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"auth": "noauth",
|
||||
"accounts": [
|
||||
{
|
||||
"user": "my-username",
|
||||
"pass": "my-password"
|
||||
}
|
||||
],
|
||||
"udp": false,
|
||||
"ip": "127.0.0.1",
|
||||
"userLevel": 0
|
||||
{
|
||||
"listen": "0.0.0.0",
|
||||
"port": 1234,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"auth": "noauth",
|
||||
"accounts": [
|
||||
{
|
||||
"user": "my-username",
|
||||
"pass": "my-password"
|
||||
}
|
||||
],
|
||||
"udp": false,
|
||||
"ip": "127.0.0.1",
|
||||
"userLevel": 0
|
||||
}
|
||||
}
|
||||
]
|
@@ -1,28 +1,30 @@
|
||||
[
|
||||
{
|
||||
"tag": "IPv4_out",
|
||||
"protocol": "freedom",
|
||||
"settings": {}
|
||||
},
|
||||
{
|
||||
"tag": "IPv6_out",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIPv6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "socks5-warp",
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"servers": [{
|
||||
"address": "127.0.0.1",
|
||||
"port": 1080
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"protocol": "blackhole",
|
||||
"tag": "block"
|
||||
{
|
||||
"tag": "IPv4_out",
|
||||
"protocol": "freedom",
|
||||
"settings": {}
|
||||
},
|
||||
{
|
||||
"tag": "IPv6_out",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIPv6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "socks5-warp",
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 1080
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"protocol": "blackhole",
|
||||
"tag": "block"
|
||||
}
|
||||
]
|
@@ -3,11 +3,12 @@ package all
|
||||
import (
|
||||
// The following are necessary as they register handlers in their init functions.
|
||||
|
||||
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
||||
_ "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"
|
||||
_ "github.com/xtls/xray-core/app/proxyman/inbound"
|
||||
_ "github.com/xtls/xray-core/app/proxyman/outbound"
|
||||
|
||||
// Default commander and all its services. This is an optional feature.
|
||||
_ "github.com/xtls/xray-core/app/commander"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
_ "github.com/xtls/xray-core/proxy/dokodemo"
|
||||
_ "github.com/xtls/xray-core/proxy/freedom"
|
||||
_ "github.com/xtls/xray-core/proxy/http"
|
||||
_ "github.com/xtls/xray-core/proxy/mtproto"
|
||||
_ "github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
_ "github.com/xtls/xray-core/proxy/socks"
|
||||
_ "github.com/xtls/xray-core/proxy/trojan"
|
||||
@@ -44,11 +44,11 @@ import (
|
||||
_ "github.com/xtls/xray-core/transport/internet/http"
|
||||
_ "github.com/xtls/xray-core/transport/internet/kcp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/quic"
|
||||
_ "github.com/xtls/xray-core/transport/internet/reality"
|
||||
_ "github.com/xtls/xray-core/transport/internet/tcp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/tls"
|
||||
_ "github.com/xtls/xray-core/transport/internet/udp"
|
||||
_ "github.com/xtls/xray-core/transport/internet/websocket"
|
||||
_ "github.com/xtls/xray-core/transport/internet/xtls"
|
||||
|
||||
// Transport headers
|
||||
_ "github.com/xtls/xray-core/transport/internet/headers/http"
|
||||
|
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"servers": [
|
||||
"1.1.1.1",
|
||||
"8.8.8.8",
|
||||
"localhost"
|
||||
],
|
||||
"tag": "dns_inbound"
|
||||
"servers": [
|
||||
"1.1.1.1",
|
||||
"8.8.8.8",
|
||||
"localhost"
|
||||
],
|
||||
"tag": "dns_inbound"
|
||||
}
|
17
main/main.go
17
main/main.go
@@ -12,9 +12,10 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/XrayR-project/XrayR/panel"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/XrayR-project/XrayR/panel"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,7 +24,7 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "0.8.1"
|
||||
version = "0.9.1"
|
||||
codename = "XrayR"
|
||||
intro = "A Xray backend that supports many panels"
|
||||
)
|
||||
@@ -56,7 +57,7 @@ func getConfig() *viper.Viper {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -73,7 +74,9 @@ func main() {
|
||||
|
||||
config := getConfig()
|
||||
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)
|
||||
lastTime := time.Now()
|
||||
config.OnConfigChange(func(e fsnotify.Event) {
|
||||
@@ -84,7 +87,9 @@ func main() {
|
||||
p.Close()
|
||||
// Delete old instance and trigger 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()
|
||||
lastTime = time.Now()
|
||||
}
|
||||
@@ -92,7 +97,7 @@ func main() {
|
||||
p.Start()
|
||||
defer p.Close()
|
||||
|
||||
//Explicitly triggering GC to remove garbage from config loading.
|
||||
// Explicitly triggering GC to remove garbage from config loading.
|
||||
runtime.GC()
|
||||
// Running backend
|
||||
{
|
||||
|
@@ -1,36 +1,36 @@
|
||||
{
|
||||
"domainStrategy": "IPOnDemand",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "block",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "block",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "socks5-warp",
|
||||
"domain": []
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "IPv6_out",
|
||||
"domain": [
|
||||
"geosite:netflix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "IPv4_out",
|
||||
"network": "udp,tcp"
|
||||
}
|
||||
]
|
||||
"domainStrategy": "IPOnDemand",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "block",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "block",
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "socks5-warp",
|
||||
"domain": []
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "IPv6_out",
|
||||
"domain": [
|
||||
"geosite:netflix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "IPv4_out",
|
||||
"network": "udp,tcp"
|
||||
}
|
||||
]
|
||||
}
|
@@ -6,13 +6,13 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
LogConfig *LogConfig `mapstructure:"Log"`
|
||||
DnsConfigPath string `mapstructure:"DnsConfigPath"`
|
||||
InboundConfigPath string `mapstructure:"InboundConfigPath"`
|
||||
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
|
||||
RouteConfigPath string `mapstructure:"RouteConfigPath"`
|
||||
ConnetionConfig *ConnetionConfig `mapstructure:"ConnetionConfig"`
|
||||
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
|
||||
LogConfig *LogConfig `mapstructure:"Log"`
|
||||
DnsConfigPath string `mapstructure:"DnsConfigPath"`
|
||||
InboundConfigPath string `mapstructure:"InboundConfigPath"`
|
||||
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
|
||||
RouteConfigPath string `mapstructure:"RouteConfigPath"`
|
||||
ConnectionConfig *ConnectionConfig `mapstructure:"ConnectionConfig"`
|
||||
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
|
||||
}
|
||||
|
||||
type NodesConfig struct {
|
||||
@@ -27,7 +27,7 @@ type LogConfig struct {
|
||||
ErrorPath string `mapstructure:"ErrorPath"`
|
||||
}
|
||||
|
||||
type ConnetionConfig struct {
|
||||
type ConnectionConfig struct {
|
||||
Handshake uint32 `mapstructure:"handshake"`
|
||||
ConnIdle uint32 `mapstructure:"connIdle"`
|
||||
UplinkOnly uint32 `mapstructure:"uplinkOnly"`
|
||||
|
@@ -10,8 +10,8 @@ func getDefaultLogConfig() *LogConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultConnetionConfig() *ConnetionConfig {
|
||||
return &ConnetionConfig{
|
||||
func getDefaultConnectionConfig() *ConnectionConfig {
|
||||
return &ConnectionConfig{
|
||||
Handshake: 4,
|
||||
ConnIdle: 30,
|
||||
UplinkOnly: 2,
|
||||
|
@@ -2,26 +2,30 @@ package panel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||
io "io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"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/main/distro/all"
|
||||
"github.com/XrayR-project/XrayR/service"
|
||||
"github.com/XrayR-project/XrayR/service/controller"
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/XrayR-project/XrayR/api/gov2panel"
|
||||
"github.com/XrayR-project/XrayR/api/newV2board"
|
||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/r3labs/diff/v2"
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/app/stats"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"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/v2raysocks"
|
||||
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
||||
"github.com/XrayR-project/XrayR/service"
|
||||
"github.com/XrayR-project/XrayR/service/controller"
|
||||
)
|
||||
|
||||
// Panel Structure
|
||||
@@ -54,7 +58,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
// DNS config
|
||||
coreDnsConfig := &conf.DNSConfig{}
|
||||
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)
|
||||
} else {
|
||||
if err = json.Unmarshal(data, coreDnsConfig); err != nil {
|
||||
@@ -62,14 +66,21 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// init controller's DNS config
|
||||
// for _, config := range p.panelConfig.NodesConfig {
|
||||
// config.ControllerConfig.DNSConfig = coreDnsConfig
|
||||
// }
|
||||
|
||||
dnsConfig, err := coreDnsConfig.Build()
|
||||
if err != nil {
|
||||
log.Panicf("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help: %s", err)
|
||||
}
|
||||
|
||||
// Routing config
|
||||
coreRouterConfig := &conf.RouterConfig{}
|
||||
if panelConfig.RouteConfigPath != "" {
|
||||
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)
|
||||
} else {
|
||||
if err = json.Unmarshal(data, coreRouterConfig); err != nil {
|
||||
@@ -82,9 +93,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)
|
||||
}
|
||||
// Custom Inbound config
|
||||
coreCustomInboundConfig := []conf.InboundDetourConfig{}
|
||||
var coreCustomInboundConfig []conf.InboundDetourConfig
|
||||
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)
|
||||
} else {
|
||||
if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil {
|
||||
@@ -92,7 +103,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
}
|
||||
}
|
||||
}
|
||||
inBoundConfig := []*core.InboundHandlerConfig{}
|
||||
var inBoundConfig []*core.InboundHandlerConfig
|
||||
for _, config := range coreCustomInboundConfig {
|
||||
oc, err := config.Build()
|
||||
if err != nil {
|
||||
@@ -101,9 +112,9 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
inBoundConfig = append(inBoundConfig, oc)
|
||||
}
|
||||
// Custom Outbound config
|
||||
coreCustomOutboundConfig := []conf.OutboundDetourConfig{}
|
||||
var coreCustomOutboundConfig []conf.OutboundDetourConfig
|
||||
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)
|
||||
} else {
|
||||
if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil {
|
||||
@@ -111,7 +122,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
}
|
||||
}
|
||||
}
|
||||
outBoundConfig := []*core.OutboundHandlerConfig{}
|
||||
var outBoundConfig []*core.OutboundHandlerConfig
|
||||
for _, config := range coreCustomOutboundConfig {
|
||||
oc, err := config.Build()
|
||||
if err != nil {
|
||||
@@ -120,7 +131,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
outBoundConfig = append(outBoundConfig, oc)
|
||||
}
|
||||
// Policy config
|
||||
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnetionConfig)
|
||||
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnectionConfig)
|
||||
corePolicyConfig := &conf.PolicyConfig{}
|
||||
corePolicyConfig.Levels = map[uint32]*conf.Policy{0: levelPolicyConfig}
|
||||
policyConfig, _ := corePolicyConfig.Build()
|
||||
@@ -148,7 +159,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
||||
return server
|
||||
}
|
||||
|
||||
// Start Start the panel
|
||||
// Start the panel
|
||||
func (p *Panel) Start() {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
@@ -159,18 +170,23 @@ func (p *Panel) Start() {
|
||||
log.Panicf("Failed to start instance: %s", err)
|
||||
}
|
||||
p.Server = server
|
||||
|
||||
// Load Nodes config
|
||||
for _, nodeConfig := range p.panelConfig.NodesConfig {
|
||||
var apiClient api.API
|
||||
switch nodeConfig.PanelType {
|
||||
case "SSpanel":
|
||||
apiClient = sspanel.New(nodeConfig.ApiConfig)
|
||||
case "V2board":
|
||||
apiClient = v2board.New(nodeConfig.ApiConfig)
|
||||
case "NewV2board":
|
||||
apiClient = newV2board.New(nodeConfig.ApiConfig)
|
||||
case "PMpanel":
|
||||
apiClient = pmpanel.New(nodeConfig.ApiConfig)
|
||||
case "Proxypanel":
|
||||
apiClient = proxypanel.New(nodeConfig.ApiConfig)
|
||||
case "V2RaySocks":
|
||||
apiClient = v2raysocks.New(nodeConfig.ApiConfig)
|
||||
case "GoV2Panel":
|
||||
apiClient = gov2panel.New(nodeConfig.ApiConfig)
|
||||
default:
|
||||
log.Panicf("Unsupport panel type: %s", nodeConfig.PanelType)
|
||||
}
|
||||
@@ -198,7 +214,7 @@ func (p *Panel) Start() {
|
||||
return
|
||||
}
|
||||
|
||||
// Close Close the panel
|
||||
// Close the panel
|
||||
func (p *Panel) Close() {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
@@ -214,21 +230,21 @@ func (p *Panel) Close() {
|
||||
return
|
||||
}
|
||||
|
||||
func parseConnectionConfig(c *ConnetionConfig) (policy *conf.Policy) {
|
||||
connetionConfig := getDefaultConnetionConfig()
|
||||
func parseConnectionConfig(c *ConnectionConfig) (policy *conf.Policy) {
|
||||
connectionConfig := getDefaultConnectionConfig()
|
||||
if c != nil {
|
||||
if _, err := diff.Merge(connetionConfig, c, connetionConfig); err != nil {
|
||||
log.Panicf("Read ConnetionConfig failed: %s", err)
|
||||
if _, err := diff.Merge(connectionConfig, c, connectionConfig); err != nil {
|
||||
log.Panicf("Read ConnectionConfig failed: %s", err)
|
||||
}
|
||||
}
|
||||
policy = &conf.Policy{
|
||||
StatsUserUplink: true,
|
||||
StatsUserDownlink: true,
|
||||
Handshake: &connetionConfig.Handshake,
|
||||
ConnectionIdle: &connetionConfig.ConnIdle,
|
||||
UplinkOnly: &connetionConfig.UplinkOnly,
|
||||
DownlinkOnly: &connetionConfig.DownlinkOnly,
|
||||
BufferSize: &connetionConfig.BufferSize,
|
||||
Handshake: &connectionConfig.Handshake,
|
||||
ConnectionIdle: &connectionConfig.ConnIdle,
|
||||
UplinkOnly: &connectionConfig.UplinkOnly,
|
||||
DownlinkOnly: &connectionConfig.DownlinkOnly,
|
||||
BufferSize: &connectionConfig.BufferSize,
|
||||
}
|
||||
|
||||
return
|
||||
|
@@ -1,30 +1,36 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/XrayR-project/XrayR/common/limiter"
|
||||
"github.com/XrayR-project/XrayR/common/mylego"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ListenIP string `mapstructure:"ListenIP"`
|
||||
SendIP string `mapstructure:"SendIP"`
|
||||
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
||||
CertConfig *CertConfig `mapstructure:"CertConfig"`
|
||||
EnableDNS bool `mapstructure:"EnableDNS"`
|
||||
DNSType string `mapstructure:"DNSType"`
|
||||
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
||||
DisableGetRule bool `mapstructure:"DisableGetRule"`
|
||||
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
|
||||
EnableFallback bool `mapstructure:"EnableFallback"`
|
||||
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
||||
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
||||
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
||||
ListenIP string `mapstructure:"ListenIP"`
|
||||
SendIP string `mapstructure:"SendIP"`
|
||||
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
||||
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
|
||||
EnableDNS bool `mapstructure:"EnableDNS"`
|
||||
DNSType string `mapstructure:"DNSType"`
|
||||
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
||||
DisableGetRule bool `mapstructure:"DisableGetRule"`
|
||||
EnableProxyProtocol bool `mapstructure:"EnableProxyProtocol"`
|
||||
EnableFallback bool `mapstructure:"EnableFallback"`
|
||||
DisableIVCheck bool `mapstructure:"DisableIVCheck"`
|
||||
DisableSniffing bool `mapstructure:"DisableSniffing"`
|
||||
AutoSpeedLimitConfig *AutoSpeedLimitConfig `mapstructure:"AutoSpeedLimitConfig"`
|
||||
GlobalDeviceLimitConfig *limiter.GlobalDeviceLimitConfig `mapstructure:"GlobalDeviceLimitConfig"`
|
||||
FallBackConfigs []*FallBackConfig `mapstructure:"FallBackConfigs"`
|
||||
DisableLocalREALITYConfig bool `mapstructure:"DisableLocalREALITYConfig"`
|
||||
EnableREALITY bool `mapstructure:"EnableREALITY"`
|
||||
REALITYConfigs *REALITYConfig `mapstructure:"REALITYConfigs"`
|
||||
}
|
||||
|
||||
type CertConfig struct {
|
||||
CertMode string `mapstructure:"CertMode"` // none, file, http, dns
|
||||
RejectUnknownSni bool `mapstructure:"RejectUnknownSni"`
|
||||
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"`
|
||||
type AutoSpeedLimitConfig struct {
|
||||
Limit int `mapstructure:"Limit"` // mbps
|
||||
WarnTimes int `mapstructure:"WarnTimes"`
|
||||
LimitSpeed int `mapstructure:"LimitSpeed"` // mbps
|
||||
LimitDuration int `mapstructure:"LimitDuration"` // minute
|
||||
}
|
||||
|
||||
type FallBackConfig struct {
|
||||
@@ -34,3 +40,15 @@ type FallBackConfig struct {
|
||||
Dest string `mapstructure:"Dest"`
|
||||
ProxyProtocolVer uint64 `mapstructure:"ProxyProtocolVer"`
|
||||
}
|
||||
|
||||
type REALITYConfig struct {
|
||||
Show bool `mapstructure:"Show"`
|
||||
Dest string `mapstructure:"Dest"`
|
||||
ProxyProtocolVer uint64 `mapstructure:"ProxyProtocolVer"`
|
||||
ServerNames []string `mapstructure:"ServerNames"`
|
||||
PrivateKey string `mapstructure:"PrivateKey"`
|
||||
MinClientVer string `mapstructure:"MinClientVer"`
|
||||
MaxClientVer string `mapstructure:"MaxClientVer"`
|
||||
MaxTimeDiff uint64 `mapstructure:"MaxTimeDiff"`
|
||||
ShortIds []string `mapstructure:"ShortIds"`
|
||||
}
|
||||
|
@@ -4,21 +4,24 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"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/stats"
|
||||
"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 {
|
||||
err := c.ihm.RemoveHandler(context.Background(), tag)
|
||||
err := c.ibm.RemoveHandler(context.Background(), tag)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Controller) removeOutbound(tag string) error {
|
||||
err := c.ohm.RemoveHandler(context.Background(), tag)
|
||||
err := c.obm.RemoveHandler(context.Background(), tag)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -31,7 +34,7 @@ func (c *Controller) addInbound(config *core.InboundHandlerConfig) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("not an InboundHandler: %s", err)
|
||||
}
|
||||
if err := c.ihm.AddHandler(context.Background(), handler); err != nil {
|
||||
if err := c.ibm.AddHandler(context.Background(), handler); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -46,16 +49,16 @@ func (c *Controller) addOutbound(config *core.OutboundHandlerConfig) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("not an InboundHandler: %s", err)
|
||||
}
|
||||
if err := c.ohm.AddHandler(context.Background(), handler); err != nil {
|
||||
if err := c.obm.AddHandler(context.Background(), handler); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) addUsers(users []*protocol.User, tag string) error {
|
||||
handler, err := c.ihm.GetHandler(context.Background(), tag)
|
||||
handler, err := c.ibm.GetHandler(context.Background(), tag)
|
||||
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)
|
||||
if !ok {
|
||||
@@ -80,9 +83,9 @@ func (c *Controller) addUsers(users []*protocol.User, tag string) error {
|
||||
}
|
||||
|
||||
func (c *Controller) removeUsers(users []string, tag string) error {
|
||||
handler, err := c.ihm.GetHandler(context.Background(), tag)
|
||||
handler, err := c.ibm.GetHandler(context.Background(), tag)
|
||||
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)
|
||||
if !ok {
|
||||
@@ -102,25 +105,35 @@ func (c *Controller) removeUsers(users []string, tag string) error {
|
||||
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"
|
||||
downName := "user>>>" + email + ">>>traffic>>>downlink"
|
||||
upCounter := c.stm.GetCounter(upName)
|
||||
downCounter := c.stm.GetCounter(downName)
|
||||
if upCounter != nil {
|
||||
upCounter = c.stm.GetCounter(upName)
|
||||
downCounter = c.stm.GetCounter(downName)
|
||||
if upCounter != nil && upCounter.Value() != 0 {
|
||||
up = upCounter.Value()
|
||||
upCounter.Set(0)
|
||||
} else {
|
||||
upCounter = nil
|
||||
}
|
||||
if downCounter != nil {
|
||||
if downCounter != nil && downCounter.Value() != 0 {
|
||||
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 {
|
||||
err := c.dispatcher.Limiter.AddInboundLimiter(tag, nodeSpeedLimit, userList)
|
||||
func (c *Controller) resetTraffic(upCounterList *[]stats.Counter, downCounterList *[]stats.Counter) {
|
||||
for _, upCounter := range *upCounterList {
|
||||
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
|
||||
}
|
||||
|
||||
|
@@ -1,16 +1,12 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||
"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/task"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -18,23 +14,41 @@ import (
|
||||
"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 {
|
||||
server *core.Instance
|
||||
config *Config
|
||||
clientInfo api.ClientInfo
|
||||
apiClient api.API
|
||||
nodeInfo *api.NodeInfo
|
||||
Tag string
|
||||
userList *[]api.UserInfo
|
||||
nodeInfoMonitorPeriodic *task.Periodic
|
||||
userReportPeriodic *task.Periodic
|
||||
panelType string
|
||||
ihm inbound.Manager
|
||||
ohm outbound.Manager
|
||||
stm stats.Manager
|
||||
dispatcher *mydispatcher.DefaultDispatcher
|
||||
server *core.Instance
|
||||
config *Config
|
||||
clientInfo api.ClientInfo
|
||||
apiClient api.API
|
||||
nodeInfo *api.NodeInfo
|
||||
Tag string
|
||||
userList *[]api.UserInfo
|
||||
tasks []periodicTask
|
||||
limitedUsers map[api.UserInfo]LimitInfo
|
||||
warnedUsers map[api.UserInfo]int
|
||||
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.
|
||||
@@ -44,11 +58,13 @@ func New(server *core.Instance, api api.API, config *Config, panelType string) *
|
||||
config: config,
|
||||
apiClient: api,
|
||||
panelType: panelType,
|
||||
ihm: server.GetFeature(inbound.ManagerType()).(inbound.Manager),
|
||||
ohm: server.GetFeature(outbound.ManagerType()).(outbound.Manager),
|
||||
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
|
||||
}
|
||||
|
||||
@@ -60,8 +76,12 @@ func (c *Controller) Start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newNodeInfo.Port == 0 {
|
||||
return errors.New("server port must > 0")
|
||||
}
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
|
||||
// Add new tag
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
@@ -74,17 +94,19 @@ func (c *Controller) Start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// sync controller userList
|
||||
c.userList = userInfo
|
||||
|
||||
err = c.addNewUser(userInfo, newNodeInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//sync controller userList
|
||||
c.userList = userInfo
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Add Rule Manager
|
||||
if !c.config.DisableGetRule {
|
||||
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
||||
@@ -95,100 +117,141 @@ func (c *Controller) Start() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
c.nodeInfoMonitorPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
||||
Execute: c.nodeInfoMonitor,
|
||||
}
|
||||
c.userReportPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
||||
Execute: c.userInfoMonitor,
|
||||
}
|
||||
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
||||
// delay to start nodeInfoMonitor
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.config.UpdatePeriodic) * time.Second)
|
||||
_ = c.nodeInfoMonitorPeriodic.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()
|
||||
}()
|
||||
// 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,
|
||||
Execute: c.nodeInfoMonitor,
|
||||
}},
|
||||
periodicTask{
|
||||
tag: "user monitor",
|
||||
Periodic: &task.Periodic{
|
||||
Interval: time.Duration(c.config.UpdatePeriodic) * time.Second,
|
||||
Execute: c.userInfoMonitor,
|
||||
}},
|
||||
)
|
||||
|
||||
// Check cert service in need
|
||||
if c.nodeInfo.EnableTLS && c.config.EnableREALITY == false {
|
||||
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()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implement the Close() function of the service interface
|
||||
func (c *Controller) Close() error {
|
||||
if c.nodeInfoMonitorPeriodic != nil {
|
||||
err := c.nodeInfoMonitorPeriodic.Close()
|
||||
if err != nil {
|
||||
log.Panicf("node info periodic close failed: %s", err)
|
||||
for i := range c.tasks {
|
||||
if c.tasks[i].Periodic != nil {
|
||||
if err := c.tasks[i].Periodic.Close(); err != nil {
|
||||
log.Panicf("%s %s periodic task close failed: %s", c.logPrefix(), c.tasks[i].tag, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.nodeInfoMonitorPeriodic != nil {
|
||||
err := c.userReportPeriodic.Close()
|
||||
if err != nil {
|
||||
log.Panicf("user report periodic close failed: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
var nodeInfoChanged = true
|
||||
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
if err.Error() == api.NodeNotModified {
|
||||
nodeInfoChanged = false
|
||||
newNodeInfo = c.nodeInfo
|
||||
} else {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if newNodeInfo.Port == 0 {
|
||||
return errors.New("server port must > 0")
|
||||
}
|
||||
|
||||
// Update User
|
||||
var usersChanged = true
|
||||
newUserInfo, err := c.apiClient.GetUserList()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
if err.Error() == api.UserNotModified {
|
||||
usersChanged = false
|
||||
newUserInfo = c.userList
|
||||
} else {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var nodeInfoChanged = false
|
||||
// If nodeInfo changed
|
||||
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
||||
// Remove old tag
|
||||
oldtag := c.Tag
|
||||
err := c.removeOldTag(oldtag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
if c.nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||
err = c.removeOldTag(fmt.Sprintf("dokodemo-door_%s+1", c.Tag))
|
||||
}
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
// Add new tag
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
nodeInfoChanged = true
|
||||
// Remove Old limiter
|
||||
if err = c.DeleteInboundLimiter(oldtag); err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
if nodeInfoChanged {
|
||||
if !reflect.DeepEqual(c.nodeInfo, newNodeInfo) {
|
||||
// Remove old tag
|
||||
oldTag := c.Tag
|
||||
err := c.removeOldTag(oldTag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
if c.nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||
err = c.removeOldTag(fmt.Sprintf("dokodemo-door_%s+1", c.Tag))
|
||||
}
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
// Add new tag
|
||||
c.nodeInfo = newNodeInfo
|
||||
c.Tag = c.buildNodeTag()
|
||||
err = c.addNewTag(newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
nodeInfoChanged = true
|
||||
// Remove Old limiter
|
||||
if err = c.DeleteInboundLimiter(oldTag); err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
nodeInfoChanged = false
|
||||
}
|
||||
}
|
||||
|
||||
// Check Rule
|
||||
if !c.config.DisableGetRule {
|
||||
if ruleList, err := c.apiClient.GetNodeRule(); err != nil {
|
||||
log.Printf("Get rule list filed: %s", err)
|
||||
if err.Error() != api.RuleNotModified {
|
||||
log.Printf("Get rule list filed: %s", err)
|
||||
}
|
||||
} else if len(*ruleList) > 0 {
|
||||
if err := c.UpdateRule(c.Tag, *ruleList); err != nil {
|
||||
log.Print(err)
|
||||
@@ -196,64 +259,56 @@ 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 {
|
||||
err = c.addNewUser(newUserInfo, newNodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
return nil
|
||||
}
|
||||
|
||||
} else {
|
||||
deleted, added := compareUserList(c.userList, newUserInfo)
|
||||
if len(deleted) > 0 {
|
||||
deletedEmail := make([]string, len(deleted))
|
||||
for i, u := range deleted {
|
||||
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, u.Email, u.UID)
|
||||
var deleted, added []api.UserInfo
|
||||
if usersChanged {
|
||||
deleted, added = compareUserList(c.userList, newUserInfo)
|
||||
if len(deleted) > 0 {
|
||||
deletedEmail := make([]string, len(deleted))
|
||||
for i, u := range deleted {
|
||||
deletedEmail[i] = fmt.Sprintf("%s|%s|%d", c.Tag, u.Email, u.UID)
|
||||
}
|
||||
err := c.removeUsers(deletedEmail, c.Tag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
err := c.removeUsers(deletedEmail, c.Tag)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
if len(added) > 0 {
|
||||
err = c.addNewUser(&added, c.nodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
// Update Limiter
|
||||
if err := c.UpdateInboundLimiter(c.Tag, &added); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(added) > 0 {
|
||||
err = c.addNewUser(&added, c.nodeInfo)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
// Update Limiter
|
||||
if err := c.UpdateInboundLimiter(c.Tag, &added); err != nil {
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) removeOldTag(oldtag string) (err error) {
|
||||
err = c.removeInbound(oldtag)
|
||||
func (c *Controller) removeOldTag(oldTag string) (err error) {
|
||||
err = c.removeInbound(oldTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.removeOutbound(oldtag)
|
||||
err = c.removeOutbound(oldTag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -289,7 +344,7 @@ func (c *Controller) addNewTag(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.TransportProtocol = "tcp"
|
||||
fakeNodeInfo.EnableTLS = false
|
||||
@@ -342,69 +397,59 @@ func (c *Controller) addInboundForSSPlugin(newNodeInfo api.NodeInfo) (err error)
|
||||
|
||||
func (c *Controller) addNewUser(userInfo *[]api.UserInfo, nodeInfo *api.NodeInfo) (err error) {
|
||||
users := make([]*protocol.User, 0)
|
||||
if nodeInfo.NodeType == "V2ray" {
|
||||
switch nodeInfo.NodeType {
|
||||
case "V2ray":
|
||||
if nodeInfo.EnableVless {
|
||||
users = c.buildVlessUser(userInfo)
|
||||
} else {
|
||||
alterID := 0
|
||||
if c.panelType == "V2board" {
|
||||
// use latest userInfo
|
||||
alterID = (*userInfo)[0].AlterID
|
||||
} else {
|
||||
alterID = nodeInfo.AlterID
|
||||
}
|
||||
if alterID >= 0 && alterID < math.MaxUint16 {
|
||||
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")
|
||||
}
|
||||
users = c.buildVmessUser(userInfo)
|
||||
}
|
||||
} else if nodeInfo.NodeType == "Trojan" {
|
||||
case "Trojan":
|
||||
users = c.buildTrojanUser(userInfo)
|
||||
} else if nodeInfo.NodeType == "Shadowsocks" {
|
||||
case "Shadowsocks":
|
||||
users = c.buildSSUser(userInfo, nodeInfo.CypherMethod)
|
||||
} else if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||
case "Shadowsocks-Plugin":
|
||||
users = c.buildSSPluginUser(userInfo)
|
||||
} else {
|
||||
default:
|
||||
return fmt.Errorf("unsupported node type: %s", nodeInfo.NodeType)
|
||||
}
|
||||
|
||||
err = c.addUsers(users, c.Tag)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func compareUserList(old, new *[]api.UserInfo) (deleted, added []api.UserInfo) {
|
||||
msrc := make(map[api.UserInfo]byte) //按源数组建索引
|
||||
mall := make(map[api.UserInfo]byte) //源+目所有元素建索引
|
||||
mSrc := 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 {
|
||||
msrc[v] = 0
|
||||
mall[v] = 0
|
||||
mSrc[v] = 0
|
||||
mAll[v] = 0
|
||||
}
|
||||
//2.目数组中,存不进去,即重复元素,所有存不进去的集合就是并集
|
||||
// 2.目数组中,存不进去,即重复元素,所有存不进去的集合就是并集
|
||||
for _, v := range *new {
|
||||
l := len(mall)
|
||||
mall[v] = 1
|
||||
if l != len(mall) { //长度变化,即可以存
|
||||
l = len(mall)
|
||||
} else { //存不了,进并集
|
||||
l := len(mAll)
|
||||
mAll[v] = 1
|
||||
if l != len(mAll) { // 长度变化,即可以存
|
||||
l = len(mAll)
|
||||
} else { // 存不了,进并集
|
||||
set = append(set, v)
|
||||
}
|
||||
}
|
||||
//3.遍历交集,在并集中找,找到就从并集中删,删完后就是补集(即并-交=所有变化的元素)
|
||||
// 3.遍历交集,在并集中找,找到就从并集中删,删完后就是补集(即并-交=所有变化的元素)
|
||||
for _, v := range set {
|
||||
delete(mall, v)
|
||||
delete(mAll, v)
|
||||
}
|
||||
//4.此时,mall是补集,所有元素去源中找,找到就是删除的,找不到的必定能在目数组中找到,即新加的
|
||||
for v := range mall {
|
||||
_, exist := msrc[v]
|
||||
// 4.此时,mall是补集,所有元素去源中找,找到就是删除的,找不到的必定能在目数组中找到,即新加的
|
||||
for v := range mAll {
|
||||
_, exist := mSrc[v]
|
||||
if exist {
|
||||
deleted = append(deleted, v)
|
||||
} else {
|
||||
@@ -415,7 +460,23 @@ func compareUserList(old, new *[]api.UserInfo) (deleted, added []api.UserInfo) {
|
||||
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) {
|
||||
// delay to start
|
||||
if time.Since(c.startAt) < time.Duration(c.config.UpdatePeriodic)*time.Second {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get server status
|
||||
CPU, Mem, Disk, Uptime, err := serverstatus.GetSystemInfo()
|
||||
if err != nil {
|
||||
@@ -431,23 +492,86 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
if err != nil {
|
||||
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
|
||||
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 {
|
||||
up, down := c.getTraffic(c.buildUserTag(&user))
|
||||
up, down, upCounter, downCounter := c.getTraffic(c.buildUserTag(&user))
|
||||
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{
|
||||
UID: user.UID,
|
||||
Email: user.Email,
|
||||
Upload: up,
|
||||
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 {
|
||||
err = c.apiClient.ReportUserTraffic(&userTraffic)
|
||||
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)
|
||||
}
|
||||
// If report traffic error, not clear the traffic
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
} else {
|
||||
c.resetTraffic(&upCounterList, &downCounterList)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,9 +582,10 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
if err = c.apiClient.ReportNodeOnlineUsers(onlineDevice); err != nil {
|
||||
log.Print(err)
|
||||
} 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
|
||||
if detectResult, err := c.GetDetectResult(c.Tag); err != nil {
|
||||
log.Print(err)
|
||||
@@ -468,7 +593,7 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
if err = c.apiClient.ReportIllegal(detectResult); err != nil {
|
||||
log.Print(err)
|
||||
} 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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -478,3 +603,26 @@ func (c *Controller) userInfoMonitor() (err error) {
|
||||
func (c *Controller) buildNodeTag() string {
|
||||
return fmt.Sprintf("%s_%s_%d", c.nodeInfo.NodeType, c.config.ListenIP, c.nodeInfo.Port)
|
||||
}
|
||||
|
||||
func (c *Controller) logPrefix() string {
|
||||
return fmt.Sprintf("[%s] %s(ID=%d)", c.clientInfo.APIHost, c.nodeInfo.NodeType, c.nodeInfo.NodeID)
|
||||
}
|
||||
|
||||
// Check Cert
|
||||
func (c *Controller) certMonitor() error {
|
||||
if c.nodeInfo.EnableTLS && c.config.EnableREALITY == false {
|
||||
switch c.config.CertConfig.CertMode {
|
||||
case "dns", "http", "tls":
|
||||
lego, err := mylego.New(c.config.CertConfig)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
// 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"
|
||||
"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/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) {
|
||||
@@ -22,7 +24,7 @@ func TestController(t *testing.T) {
|
||||
LogConfig: &conf.LogConfig{LogLevel: "debug"},
|
||||
}
|
||||
policyConfig := &conf.PolicyConfig{}
|
||||
policyConfig.Levels = map[uint32]*conf.Policy{0: &conf.Policy{
|
||||
policyConfig.Levels = map[uint32]*conf.Policy{0: {
|
||||
StatsUserUplink: true,
|
||||
StatsUserDownlink: true,
|
||||
}}
|
||||
@@ -45,13 +47,13 @@ func TestController(t *testing.T) {
|
||||
if err = server.Start(); err != nil {
|
||||
t.Errorf("Failed to start instance: %s", err)
|
||||
}
|
||||
certConfig := &CertConfig{
|
||||
certConfig := &mylego.CertConfig{
|
||||
CertMode: "http",
|
||||
CertDomain: "test.ss.tk",
|
||||
Provider: "alidns",
|
||||
Email: "ss@ss.com",
|
||||
}
|
||||
controlerconfig := &Config{
|
||||
controlerConfig := &Config{
|
||||
UpdatePeriodic: 5,
|
||||
CertConfig: certConfig,
|
||||
}
|
||||
@@ -61,14 +63,14 @@ func TestController(t *testing.T) {
|
||||
NodeID: 41,
|
||||
NodeType: "V2ray",
|
||||
}
|
||||
apiclient := sspanel.New(apiConfig)
|
||||
c := New(server, apiclient, controlerconfig)
|
||||
apiClient := sspanel.New(apiConfig)
|
||||
c := New(server, apiClient, controlerConfig, "SSpanel")
|
||||
fmt.Println("Sleep 1s")
|
||||
err = c.Start()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
//Explicitly triggering GC to remove garbage from config loading.
|
||||
// Explicitly triggering GC to remove garbage from config loading.
|
||||
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
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/XrayR-project/XrayR/common/legocmd"
|
||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||
C "github.com/sagernet/sing/common"
|
||||
"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/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) {
|
||||
inboundDetourConfig := &conf.InboundDetourConfig{}
|
||||
// Build Listen IP address
|
||||
if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||
// 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 != "" {
|
||||
ipAddress := net.ParseAddress(config.ListenIP)
|
||||
inboundDetourConfig.ListenOn = &conf.Address{ipAddress}
|
||||
inboundDetourConfig.ListenOn = &conf.Address{Address: ipAddress}
|
||||
}
|
||||
|
||||
// Build Port
|
||||
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
|
||||
// Build Tag
|
||||
@@ -48,9 +54,10 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
setting json.RawMessage
|
||||
)
|
||||
|
||||
var proxySetting interface{}
|
||||
var proxySetting any
|
||||
// Build Protocol and Protocol setting
|
||||
if nodeInfo.NodeType == "V2ray" {
|
||||
switch nodeInfo.NodeType {
|
||||
case "V2ray":
|
||||
if nodeInfo.EnableVless {
|
||||
protocol = "vless"
|
||||
// Enable fallback
|
||||
@@ -73,7 +80,7 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
protocol = "vmess"
|
||||
proxySetting = &conf.VMessInboundConfig{}
|
||||
}
|
||||
} else if nodeInfo.NodeType == "Trojan" {
|
||||
case "Trojan":
|
||||
protocol = "trojan"
|
||||
// Enable fallback
|
||||
if config.EnableFallback {
|
||||
@@ -88,23 +95,36 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
} else {
|
||||
proxySetting = &conf.TrojanServerConfig{}
|
||||
}
|
||||
} else if nodeInfo.NodeType == "Shadowsocks" || nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||
case "Shadowsocks", "Shadowsocks-Plugin":
|
||||
protocol = "shadowsocks"
|
||||
proxySetting = &conf.ShadowsocksServerConfig{}
|
||||
randomPasswd := uuid.New()
|
||||
defaultSSuser := &conf.ShadowsocksUserConfig{
|
||||
Cipher: "aes-128-gcm",
|
||||
Password: randomPasswd.String(),
|
||||
cipher := strings.ToLower(nodeInfo.CypherMethod)
|
||||
|
||||
proxySetting = &conf.ShadowsocksServerConfig{
|
||||
Cipher: cipher,
|
||||
Password: nodeInfo.ServerKey, // shadowsocks2022 shareKey
|
||||
}
|
||||
|
||||
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.IVCheck = true
|
||||
if config.DisableIVCheck {
|
||||
proxySetting.IVCheck = false
|
||||
}
|
||||
|
||||
} else if nodeInfo.NodeType == "dokodemo-door" {
|
||||
case "dokodemo-door":
|
||||
protocol = "dokodemo-door"
|
||||
proxySetting = struct {
|
||||
Host string `json:"address"`
|
||||
@@ -113,14 +133,16 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
Host: "v1.mux.cool",
|
||||
NetworkList: []string{"tcp", "udp"},
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
||||
}
|
||||
|
||||
setting, err := json.Marshal(proxySetting)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||
}
|
||||
inboundDetourConfig.Protocol = protocol
|
||||
inboundDetourConfig.Settings = &setting
|
||||
|
||||
// Build streamSettings
|
||||
streamSetting = new(conf.StreamConfig)
|
||||
@@ -129,13 +151,15 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert TransportProtocol failed: %s", err)
|
||||
}
|
||||
if networkType == "tcp" {
|
||||
|
||||
switch networkType {
|
||||
case "tcp":
|
||||
tcpSetting := &conf.TCPConfig{
|
||||
AcceptProxyProtocol: config.EnableProxyProtocol,
|
||||
HeaderConfig: nodeInfo.Header,
|
||||
}
|
||||
streamSetting.TCPSettings = tcpSetting
|
||||
} else if networkType == "websocket" {
|
||||
case "websocket":
|
||||
headers := make(map[string]string)
|
||||
headers["Host"] = nodeInfo.Host
|
||||
wsSettings := &conf.WebSocketConfig{
|
||||
@@ -144,14 +168,14 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
Headers: headers,
|
||||
}
|
||||
streamSetting.WSSettings = wsSettings
|
||||
} else if networkType == "http" {
|
||||
case "http":
|
||||
hosts := conf.StringList{nodeInfo.Host}
|
||||
httpSettings := &conf.HTTPConfig{
|
||||
Host: &hosts,
|
||||
Path: nodeInfo.Path,
|
||||
}
|
||||
streamSetting.HTTPSettings = httpSettings
|
||||
} else if networkType == "grpc" {
|
||||
case "grpc":
|
||||
grpcSettings := &conf.GRPCConfig{
|
||||
ServiceName: nodeInfo.ServiceName,
|
||||
}
|
||||
@@ -159,28 +183,57 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
}
|
||||
|
||||
streamSetting.Network = &transportProtocol
|
||||
// Build TLS and XTLS settings
|
||||
if nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
||||
streamSetting.Security = nodeInfo.TLSType
|
||||
|
||||
// Build TLS and REALITY settings
|
||||
var isREALITY bool
|
||||
if config.DisableLocalREALITYConfig {
|
||||
if nodeInfo.REALITYConfig != nil && nodeInfo.EnableREALITY {
|
||||
isREALITY = true
|
||||
streamSetting.Security = "reality"
|
||||
|
||||
r := nodeInfo.REALITYConfig
|
||||
streamSetting.REALITYSettings = &conf.REALITYConfig{
|
||||
Show: config.REALITYConfigs.Show,
|
||||
Dest: []byte(`"` + r.Dest + `"`),
|
||||
Xver: r.ProxyProtocolVer,
|
||||
ServerNames: r.ServerNames,
|
||||
PrivateKey: r.PrivateKey,
|
||||
MinClientVer: r.MinClientVer,
|
||||
MaxClientVer: r.MaxClientVer,
|
||||
MaxTimeDiff: r.MaxTimeDiff,
|
||||
ShortIds: r.ShortIds,
|
||||
}
|
||||
}
|
||||
} else if config.EnableREALITY && config.REALITYConfigs != nil {
|
||||
isREALITY = true
|
||||
streamSetting.Security = "reality"
|
||||
|
||||
streamSetting.REALITYSettings = &conf.REALITYConfig{
|
||||
Show: config.REALITYConfigs.Show,
|
||||
Dest: []byte(`"` + config.REALITYConfigs.Dest + `"`),
|
||||
Xver: config.REALITYConfigs.ProxyProtocolVer,
|
||||
ServerNames: config.REALITYConfigs.ServerNames,
|
||||
PrivateKey: config.REALITYConfigs.PrivateKey,
|
||||
MinClientVer: config.REALITYConfigs.MinClientVer,
|
||||
MaxClientVer: config.REALITYConfigs.MaxClientVer,
|
||||
MaxTimeDiff: config.REALITYConfigs.MaxTimeDiff,
|
||||
ShortIds: config.REALITYConfigs.ShortIds,
|
||||
}
|
||||
}
|
||||
|
||||
if !isREALITY && nodeInfo.EnableTLS && config.CertConfig.CertMode != "none" {
|
||||
streamSetting.Security = "tls"
|
||||
certFile, keyFile, err := getCertFile(config.CertConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nodeInfo.TLSType == "tls" {
|
||||
tlsSettings := &conf.TLSConfig{
|
||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
||||
}
|
||||
tlsSettings.Certs = append(tlsSettings.Certs, &conf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
|
||||
|
||||
streamSetting.TLSSettings = tlsSettings
|
||||
} else if nodeInfo.TLSType == "xtls" {
|
||||
xtlsSettings := &conf.XTLSConfig{
|
||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
||||
}
|
||||
xtlsSettings.Certs = append(xtlsSettings.Certs, &conf.XTLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
|
||||
streamSetting.XTLSSettings = xtlsSettings
|
||||
tlsSettings := &conf.TLSConfig{
|
||||
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
|
||||
}
|
||||
tlsSettings.Certs = append(tlsSettings.Certs, &conf.TLSCertConfig{CertFile: certFile, KeyFile: keyFile, OcspStapling: 3600})
|
||||
streamSetting.TLSSettings = tlsSettings
|
||||
}
|
||||
|
||||
// Support ProxyProtocol for any transport protocol
|
||||
if networkType != "tcp" && networkType != "ws" && config.EnableProxyProtocol {
|
||||
sockoptConfig := &conf.SocketConfig{
|
||||
@@ -188,60 +241,59 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
||||
}
|
||||
streamSetting.SocketSettings = sockoptConfig
|
||||
}
|
||||
inboundDetourConfig.Protocol = protocol
|
||||
inboundDetourConfig.StreamSetting = streamSetting
|
||||
inboundDetourConfig.Settings = &setting
|
||||
|
||||
return inboundDetourConfig.Build()
|
||||
}
|
||||
|
||||
func getCertFile(certConfig *CertConfig) (certFile string, keyFile string, err error) {
|
||||
if certConfig.CertMode == "file" {
|
||||
func getCertFile(certConfig *mylego.CertConfig) (certFile string, keyFile string, err error) {
|
||||
switch certConfig.CertMode {
|
||||
case "file":
|
||||
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
|
||||
} else if certConfig.CertMode == "dns" {
|
||||
lego, err := legocmd.New()
|
||||
case "dns":
|
||||
lego, err := mylego.New(certConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
certPath, keyPath, err := lego.DNSCert(certConfig.CertDomain, certConfig.Email, certConfig.Provider, certConfig.DNSEnv)
|
||||
certPath, keyPath, err := lego.DNSCert()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return certPath, keyPath, err
|
||||
} else if certConfig.CertMode == "http" {
|
||||
lego, err := legocmd.New()
|
||||
case "http", "tls":
|
||||
lego, err := mylego.New(certConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
certPath, keyPath, err := lego.HTTPCert(certConfig.CertDomain, certConfig.Email)
|
||||
certPath, keyPath, err := lego.HTTPCert()
|
||||
if err != nil {
|
||||
return "", "", 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) {
|
||||
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))
|
||||
for i, c := range fallbackConfigs {
|
||||
|
||||
if c.Dest == "" {
|
||||
return nil, fmt.Errorf("Dest is required for fallback fialed")
|
||||
return nil, fmt.Errorf("dest is required for fallback fialed")
|
||||
}
|
||||
|
||||
var dest json.RawMessage
|
||||
dest, err := json.Marshal(c.Dest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
|
||||
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
||||
}
|
||||
vlessFallBacks[i] = &conf.VLessInboundFallback{
|
||||
Name: c.SNI,
|
||||
@@ -256,20 +308,20 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun
|
||||
|
||||
func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInboundFallback, error) {
|
||||
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))
|
||||
for i, c := range fallbackConfigs {
|
||||
|
||||
if c.Dest == "" {
|
||||
return nil, fmt.Errorf("Dest is required for fallback fialed")
|
||||
return nil, fmt.Errorf("dest is required for fallback fialed")
|
||||
}
|
||||
|
||||
var dest json.RawMessage
|
||||
dest, err := json.Marshal(c.Dest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
|
||||
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
||||
}
|
||||
trojanFallBacks[i] = &conf.TrojanInboundFallback{
|
||||
Name: c.SNI,
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/XrayR-project/XrayR/common/mylego"
|
||||
. "github.com/XrayR-project/XrayR/service/controller"
|
||||
)
|
||||
|
||||
@@ -18,9 +19,8 @@ func TestBuildV2ray(t *testing.T) {
|
||||
Host: "test.test.tk",
|
||||
Path: "v2ray",
|
||||
EnableTLS: false,
|
||||
TLSType: "tls",
|
||||
}
|
||||
certConfig := &CertConfig{
|
||||
certConfig := &mylego.CertConfig{
|
||||
CertMode: "http",
|
||||
CertDomain: "test.test.tk",
|
||||
Provider: "alidns",
|
||||
@@ -29,7 +29,7 @@ func TestBuildV2ray(t *testing.T) {
|
||||
config := &Config{
|
||||
CertConfig: certConfig,
|
||||
}
|
||||
_, err := InboundBuilder(config, nodeInfo)
|
||||
_, err := InboundBuilder(config, nodeInfo, "test_tag")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -46,12 +46,11 @@ func TestBuildTrojan(t *testing.T) {
|
||||
Host: "trojan.test.tk",
|
||||
Path: "v2ray",
|
||||
EnableTLS: false,
|
||||
TLSType: "tls",
|
||||
}
|
||||
DNSEnv := make(map[string]string)
|
||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||
certConfig := &CertConfig{
|
||||
certConfig := &mylego.CertConfig{
|
||||
CertMode: "dns",
|
||||
CertDomain: "trojan.test.tk",
|
||||
Provider: "alidns",
|
||||
@@ -61,7 +60,7 @@ func TestBuildTrojan(t *testing.T) {
|
||||
config := &Config{
|
||||
CertConfig: certConfig,
|
||||
}
|
||||
_, err := InboundBuilder(config, nodeInfo)
|
||||
_, err := InboundBuilder(config, nodeInfo, "test_tag")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -78,12 +77,11 @@ func TestBuildSS(t *testing.T) {
|
||||
Host: "test.test.tk",
|
||||
Path: "v2ray",
|
||||
EnableTLS: false,
|
||||
TLSType: "tls",
|
||||
}
|
||||
DNSEnv := make(map[string]string)
|
||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||
certConfig := &CertConfig{
|
||||
certConfig := &mylego.CertConfig{
|
||||
CertMode: "dns",
|
||||
CertDomain: "trojan.test.tk",
|
||||
Provider: "alidns",
|
||||
@@ -93,7 +91,7 @@ func TestBuildSS(t *testing.T) {
|
||||
config := &Config{
|
||||
CertConfig: certConfig,
|
||||
}
|
||||
_, err := InboundBuilder(config, nodeInfo)
|
||||
_, err := InboundBuilder(config, nodeInfo, "test_tag")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@@ -4,13 +4,14 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/XrayR-project/XrayR/api"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"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) {
|
||||
outboundDetourConfig := &conf.OutboundDetourConfig{}
|
||||
outboundDetourConfig.Protocol = "freedom"
|
||||
@@ -19,11 +20,11 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
|
||||
// Build Send IP address
|
||||
if config.SendIP != "" {
|
||||
ipAddress := net.ParseAddress(config.SendIP)
|
||||
outboundDetourConfig.SendThrough = &conf.Address{ipAddress}
|
||||
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
|
||||
}
|
||||
|
||||
// Freedom Protocol setting
|
||||
var domainStrategy string = "Asis"
|
||||
var domainStrategy = "Asis"
|
||||
if config.EnableDNS {
|
||||
if config.DNSType != "" {
|
||||
domainStrategy = config.DNSType
|
||||
@@ -41,7 +42,7 @@ func OutboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.
|
||||
var setting json.RawMessage
|
||||
setting, err := json.Marshal(proxySetting)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||
}
|
||||
outboundDetourConfig.Settings = &setting
|
||||
return outboundDetourConfig.Build()
|
||||
|
@@ -1,26 +1,35 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"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/serial"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
"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/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) (users []*protocol.User) {
|
||||
users = make([]*protocol.User, len(*userInfo))
|
||||
for i, user := range *userInfo {
|
||||
vmessAccount := &conf.VMessAccount{
|
||||
ID: user.UUID,
|
||||
AlterIds: serverAlterID,
|
||||
Security: "auto",
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
@@ -37,7 +46,7 @@ func (c *Controller) buildVlessUser(userInfo *[]api.UserInfo) (users []*protocol
|
||||
for i, user := range *userInfo {
|
||||
vlessAccount := &vless.Account{
|
||||
Id: user.UUID,
|
||||
Flow: "xtls-rprx-direct",
|
||||
Flow: c.nodeInfo.VlessFlow,
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
@@ -53,7 +62,6 @@ func (c *Controller) buildTrojanUser(userInfo *[]api.UserInfo) (users []*protoco
|
||||
for i, user := range *userInfo {
|
||||
trojanAccount := &trojan.Account{
|
||||
Password: user.UUID,
|
||||
Flow: "xtls-rprx-direct",
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
@@ -65,43 +73,75 @@ func (c *Controller) buildTrojanUser(userInfo *[]api.UserInfo) (users []*protoco
|
||||
}
|
||||
|
||||
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 _, user := range *userInfo {
|
||||
ssAccount := &shadowsocks.Account{
|
||||
Password: user.Passwd,
|
||||
CipherType: cypherMethod,
|
||||
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(method)) {
|
||||
e := c.buildUserTag(&user)
|
||||
userKey, err := c.checkShadowsocksPassword(user.Passwd, method)
|
||||
if err != nil {
|
||||
newError(fmt.Errorf("[UID: %d] %s", user.UID, err)).AtError().WriteToLog()
|
||||
continue
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
Email: e,
|
||||
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||
Key: userKey,
|
||||
Email: e,
|
||||
Level: 0,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
Email: c.buildUserTag(&user),
|
||||
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||
Password: user.Passwd,
|
||||
CipherType: cipherFromString(method),
|
||||
}),
|
||||
}
|
||||
}
|
||||
users = append(users, &protocol.User{
|
||||
Level: 0,
|
||||
Email: c.buildUserTag(&user),
|
||||
Account: serial.ToTypedMessage(ssAccount),
|
||||
})
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
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 {
|
||||
// Check if the cypher method is AEAD
|
||||
cypherMethod := cipherFromString(user.Method)
|
||||
for _, aeadMethod := range AEADMethod {
|
||||
if aeadMethod == cypherMethod {
|
||||
ssAccount := &shadowsocks.Account{
|
||||
Password: user.Passwd,
|
||||
CipherType: cypherMethod,
|
||||
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)
|
||||
userKey, err := c.checkShadowsocksPassword(user.Passwd, user.Method)
|
||||
if err != nil {
|
||||
newError(fmt.Errorf("[UID: %d] %s", user.UID, err)).AtError().WriteToLog()
|
||||
continue
|
||||
}
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
Email: e,
|
||||
Account: serial.ToTypedMessage(&shadowsocks_2022.User{
|
||||
Key: userKey,
|
||||
Email: e,
|
||||
Level: 0,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
// Check if the cypher method is AEAD
|
||||
cypherMethod := cipherFromString(user.Method)
|
||||
if _, ok := AEADMethod[cypherMethod]; ok {
|
||||
users[i] = &protocol.User{
|
||||
Level: 0,
|
||||
Email: c.buildUserTag(&user),
|
||||
Account: serial.ToTypedMessage(&shadowsocks.Account{
|
||||
Password: user.Passwd,
|
||||
CipherType: cypherMethod,
|
||||
}),
|
||||
}
|
||||
users = append(users, &protocol.User{
|
||||
Level: 0,
|
||||
Email: c.buildUserTag(&user),
|
||||
Account: serial.ToTypedMessage(ssAccount),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return users
|
||||
}
|
||||
@@ -124,3 +164,23 @@ func cipherFromString(c string) shadowsocks.CipherType {
|
||||
func (c *Controller) buildUserTag(user *api.UserInfo) string {
|
||||
return fmt.Sprintf("%s|%s|%d", c.Tag, user.Email, user.UID)
|
||||
}
|
||||
|
||||
func (c *Controller) checkShadowsocksPassword(password string, method string) (string, error) {
|
||||
if strings.Contains(c.panelType, "V2board") {
|
||||
var userKey string
|
||||
if len(password) < 16 {
|
||||
return "", newError("shadowsocks2022 key's length must be greater than 16").AtWarning()
|
||||
}
|
||||
if method == "2022-blake3-aes-128-gcm" {
|
||||
userKey = password[:16]
|
||||
} else {
|
||||
if len(password) < 32 {
|
||||
return "", newError("shadowsocks2022 key's length must be greater than 32").AtWarning()
|
||||
}
|
||||
userKey = password[:32]
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString([]byte(userKey)), nil
|
||||
} else {
|
||||
return password, nil
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// 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
|
||||
|
||||
// Service is the interface of all the services running in the panel
|
||||
|
Reference in New Issue
Block a user