├── .DS_Store
├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature-request.md
├── build
│ └── friendly-filenames.json
├── dependabot.yml
└── workflows
│ ├── codeql-analysis.yml
│ └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── api
└── panel
│ ├── node.go
│ ├── node_test.go
│ ├── panel.go
│ ├── user.go
│ └── utils.go
├── cmd
├── action_linux.go
├── cert.go
├── cmd.go
├── common.go
├── common_test.go
├── install_linux.go
├── server_test.go
├── synctime.go
├── version.go
├── x25519.go
└── x25519_test.go
├── common
├── counter
│ ├── conn.go
│ └── traffic.go
├── crypt
│ ├── aes.go
│ └── x25519.go
├── exec
│ └── exec.go
├── file
│ └── file.go
├── format
│ └── user.go
├── json5
│ └── json5.go
├── rate
│ ├── conn.go
│ └── writer.go
├── systime
│ ├── time_stub.go
│ ├── time_unix.go
│ └── time_windows.go
└── task
│ ├── task.go
│ └── task_test.go
├── conf
├── cert.go
├── conf.go
├── conf_test.go
├── core.go
├── limit.go
├── log.go
├── node.go
├── sing.go
├── watch.go
└── xray.go
├── core
├── core.go
├── imports
│ ├── imports.go
│ ├── sing.go
│ └── xray.go
├── interface.go
├── selector.go
├── sing
│ ├── dns.go
│ ├── hook.go
│ ├── node.go
│ ├── sing.go
│ ├── user.go
│ └── utils.go
└── xray
│ ├── app
│ ├── app.go
│ └── dispatcher
│ │ ├── config.pb.go
│ │ ├── config.proto
│ │ ├── default.go
│ │ ├── dispatcher.go
│ │ ├── errors.generated.go
│ │ ├── fakednssniffer.go
│ │ ├── sniffer.go
│ │ ├── stats.go
│ │ └── stats_test.go
│ ├── distro
│ └── all
│ │ └── all.go
│ ├── dns.go
│ ├── inbound.go
│ ├── node.go
│ ├── outbound.go
│ ├── ss.go
│ ├── trojan.go
│ ├── user.go
│ ├── vmess.go
│ └── xray.go
├── go.mod
├── go.sum
├── limiter
├── clear.go
├── conn.go
├── conn_test.go
├── dynamic.go
├── limiter.go
└── rule.go
├── main.go
├── node
├── cert.go
├── cert_test.go
├── controller.go
├── lego.go
├── lego_test.go
├── node.go
├── task.go
└── user.go
├── release
├── aiko.json
├── custom_inbound.json
├── custom_outbound.json
├── dns.json
├── geoip.dat
├── geoip.db
├── geosite.dat
├── geosite.db
└── route.json
└── test_data
├── 1.key
└── 1.pem
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Github-Aiko/Aiko-Server/17580837ffc52ee4681d5b652b7e2e93876b6ded/.DS_Store
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Bug report"
3 | about: Create a report to help us improve Aiko-Server by identifying issues
4 | title: ''
5 | labels: awaiting reply, bug
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior.
14 |
15 | **Environment and version**
16 | - Operating system [e.g. Debian 11]
17 | - Architecture [e.g. AMD64]
18 | - Control panel [e.g. V2board]
19 | - Protocol [e.g. vmess]
20 | - Version [e.g. 0.8.2.2]
21 | - Deployment method [e.g. one command]
22 |
23 | **Logs and error**
24 | Please use `aiko-server log` to view and add logs and errors to help explain your issue.
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "Feature Request"
3 | about: Suggest a feature for Aiko-Server to help us improve
4 | title: ''
5 | labels: awaiting reply, feature-request
6 | assignees: ''
7 | ---
8 |
9 | **Describe the feature you'd like**
10 |
11 | Describe the feature you'd like in a clear and concise manner.
12 |
13 | **Describe any alternative solutions you've considered**
14 |
15 | Have you considered any alternative solutions to address this issue?
16 |
17 | **Additional context**
18 |
19 | Add any relevant context or screenshots related to the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/build/friendly-filenames.json:
--------------------------------------------------------------------------------
1 | {
2 | "android-arm64": { "friendlyName": "android-arm64-v8a" },
3 | "darwin-amd64": { "friendlyName": "macos-64" },
4 | "darwin-arm64": { "friendlyName": "macos-arm64-v8a" },
5 | "dragonfly-amd64": { "friendlyName": "dragonfly-64" },
6 | "freebsd-386": { "friendlyName": "freebsd-32" },
7 | "freebsd-amd64": { "friendlyName": "freebsd-64" },
8 | "freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" },
9 | "freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" },
10 | "linux-386": { "friendlyName": "linux-32" },
11 | "linux-amd64": { "friendlyName": "linux-64" },
12 | "linux-arm5": { "friendlyName": "linux-arm32-v5" },
13 | "linux-arm64": { "friendlyName": "linux-arm64-v8a" },
14 | "linux-arm6": { "friendlyName": "linux-arm32-v6" },
15 | "linux-arm7": { "friendlyName": "linux-arm32-v7a" },
16 | "linux-mips64le": { "friendlyName": "linux-mips64le" },
17 | "linux-mips64": { "friendlyName": "linux-mips64" },
18 | "linux-mipslesoftfloat": { "friendlyName": "linux-mips32le-softfloat" },
19 | "linux-mipsle": { "friendlyName": "linux-mips32le" },
20 | "linux-mipssoftfloat": { "friendlyName": "linux-mips32-softfloat" },
21 | "linux-mips": { "friendlyName": "linux-mips32" },
22 | "linux-ppc64le": { "friendlyName": "linux-ppc64le" },
23 | "linux-ppc64": { "friendlyName": "linux-ppc64" },
24 | "linux-riscv64": { "friendlyName": "linux-riscv64" },
25 | "linux-s390x": { "friendlyName": "linux-s390x" },
26 | "openbsd-386": { "friendlyName": "openbsd-32" },
27 | "openbsd-amd64": { "friendlyName": "openbsd-64" },
28 | "openbsd-arm64": { "friendlyName": "openbsd-arm64-v8a" },
29 | "openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
30 | "windows-386": { "friendlyName": "windows-32" },
31 | "windows-amd64": { "friendlyName": "windows-64" },
32 | "windows-arm7": { "friendlyName": "windows-arm32-v7a" }
33 | }
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gomod" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '43 22 * * 3'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'go' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # 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
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@v3
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v2
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | - name: Install Go
51 | uses: actions/setup-go@v4
52 | with:
53 | go-version-file: go.mod
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v2
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v2
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build and Release
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - master
8 | - dev_new
9 | paths:
10 | - "**/*.go"
11 | - "go.mod"
12 | - "go.sum"
13 | - ".github/workflows/*.yml"
14 | pull_request:
15 | types: [opened, synchronize, reopened]
16 | paths:
17 | - "**/*.go"
18 | - "go.mod"
19 | - "go.sum"
20 | - ".github/workflows/*.yml"
21 | release:
22 | types: [published]
23 |
24 | jobs:
25 |
26 | build:
27 | strategy:
28 | matrix:
29 | # Include amd64 on all platforms.
30 | goos: [windows, freebsd, linux, darwin]
31 | goarch: [amd64, 386]
32 | exclude:
33 | # Exclude i386 on darwin.
34 | - goarch: 386
35 | goos: darwin
36 | include:
37 | # BEIGIN MacOS ARM64
38 | - goos: darwin
39 | goarch: arm64
40 | # END MacOS ARM64
41 | # BEGIN Linux ARM 5 6 7
42 | - goos: linux
43 | goarch: arm
44 | goarm: 7
45 | - goos: linux
46 | goarch: arm
47 | goarm: 6
48 | - goos: linux
49 | goarch: arm
50 | goarm: 5
51 | # END Linux ARM 5 6 7
52 | # BEGIN Android ARM 8
53 | - goos: android
54 | goarch: arm64
55 | # END Android ARM 8
56 | # BEGIN Other architectures
57 | # BEGIN riscv64 & ARM64
58 | - goos: linux
59 | goarch: arm64
60 | - goos: linux
61 | goarch: riscv64
62 | # END riscv64 & ARM64
63 | # BEGIN MIPS
64 | - goos: linux
65 | goarch: mips64
66 | - goos: linux
67 | goarch: mips64le
68 | - goos: linux
69 | goarch: mipsle
70 | - goos: linux
71 | goarch: mips
72 | # END MIPS
73 | # BEGIN PPC
74 | - goos: linux
75 | goarch: ppc64
76 | - goos: linux
77 | goarch: ppc64le
78 | # END PPC
79 | # BEGIN FreeBSD ARM
80 | - goos: freebsd
81 | goarch: arm64
82 | - goos: freebsd
83 | goarch: arm
84 | goarm: 7
85 | # END FreeBSD ARM
86 | # BEGIN S390X
87 | - goos: linux
88 | goarch: s390x
89 | # END S390X
90 | # END Other architectures
91 | fail-fast: false
92 |
93 | runs-on: ubuntu-latest
94 | env:
95 | GOOS: ${{ matrix.goos }}
96 | GOARCH: ${{ matrix.goarch }}
97 | GOARM: ${{ matrix.goarm }}
98 | CGO_ENABLED: 0
99 | steps:
100 | - name: Checkout codebase
101 | uses: actions/checkout@v3
102 | - name: Show workflow information
103 | id: get_filename
104 | run: |
105 | export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
106 | echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
107 | echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT
108 | echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
109 | - name: Set up Go
110 | uses: actions/setup-go@v3
111 | with:
112 | go-version: '1.21.4'
113 |
114 | - name: Get project dependencies
115 | run: go mod download
116 | - name: Get release version
117 | if: ${{ github.event_name == 'release' }}
118 | run: |
119 | echo "version=$(echo $GITHUB_REF | cut -d / -f 3)" >> $GITHUB_ENV
120 | - name: Get other version
121 | if: ${{ github.event_name != 'release' }}
122 | run: |
123 | echo "version=${{ github.sha }}" >> $GITHUB_ENV
124 | - name: Build Aiko-Server
125 | run: |
126 | echo "version: $version"
127 | mkdir -p build_assets
128 | go build -v -o build_assets/Aiko-Server -tags "sing xray with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-X 'github.com/Github-Aiko/Aiko-Server/cmd.version=$version' -s -w -buildid="
129 |
130 | - name: Build Mips softfloat Aiko-Server
131 | if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
132 | run: |
133 | echo "version: $version"
134 | GOMIPS=softfloat go build -v -o build_assets/Aiko-Server_softfloat -tags "sing xray with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-X 'github.com/Github-Aiko/Aiko-Server/cmd.version=$version' -s -w -buildid="
135 | - name: Rename Windows Aiko-Server
136 | if: matrix.goos == 'windows'
137 | run: |
138 | cd ./build_assets || exit 1
139 | mv Aiko-Server Aiko-Server.exe
140 | - name: Prepare to release
141 | run: |
142 | cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
143 | cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
144 | cp ${GITHUB_WORKSPACE}/release/*.json ./build_assets/
145 | LIST=('geoip' 'geosite')
146 | for i in "${LIST[@]}"
147 | do
148 | DOWNLOAD_URL="https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/${i}.dat"
149 | FILE_NAME="${i}.dat"
150 | echo -e "Downloading ${DOWNLOAD_URL}..."
151 | curl -L "${DOWNLOAD_URL}" -o ./build_assets/${FILE_NAME}
152 | done
153 | - name: Create ZIP archive
154 | shell: bash
155 | run: |
156 | pushd build_assets || exit 1
157 | touch -mt $(date +%Y01010000) *
158 | zip -9vr ../Aiko-Server-$ASSET_NAME.zip .
159 | popd || exit 1
160 | FILE=./Aiko-Server-$ASSET_NAME.zip
161 | DGST=$FILE.dgst
162 | for METHOD in {"md5","sha1","sha256","sha512"}
163 | do
164 | openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
165 | done
166 | - name: Change the name
167 | run: |
168 | mv build_assets Aiko-Server-$ASSET_NAME
169 | - name: Upload files to Artifacts
170 | uses: actions/upload-artifact@v3
171 | with:
172 | name: Aiko-Server-${{ steps.get_filename.outputs.ASSET_NAME }}
173 | path: |
174 | ./Aiko-Server-${{ steps.get_filename.outputs.ASSET_NAME }}/*
175 | - name: Upload binaries to release
176 | uses: svenstaro/upload-release-action@v2
177 | if: github.event_name == 'release'
178 | with:
179 | repo_token: ${{ secrets.GITHUB_TOKEN }}
180 | file: ./Aiko-Server-${{ steps.get_filename.outputs.ASSET_NAME }}.zip*
181 | tag: ${{ github.ref }}
182 | file_glob: true
183 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | release/config.yml
2 | release/main
3 | release/XrayR
4 | release/XrayR*
5 | release/mytest
6 | release/access.logo
7 | release/error.log
8 | api/chooseparser.go.bak
9 | app/Inboundbuilder/.lego/
10 | app/legocmd/.lego/
11 | .vscode/launch.json
12 | release/.lego
13 | release/cert
14 | ./vscode
15 | .idea/*
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aiko-Server
2 |
3 | Aiko Server For AikoPanel
4 |
5 |

6 |
7 |
8 |
9 | [](https://github.com/Github-Aiko/Aiko-Server/releases)
10 | [](https://github.com/Github-Aiko/Aiko-Server/releases)
11 | [](https://hub.docker.com/r/aikocute/aiko-server)
12 | [](https://goreportcard.com/report/github.com/Github-Aiko/Aiko-Server)
13 |
14 |
15 |
16 | # Description of Aiko-Server
17 |
18 | Aiko-Server Supports for AikoPanel
19 |
20 | An Xray-based back-end framework, supporting V2ay, Trojan, Shadowsocks protocols, extremely easily extensible and supporting multi-panel connection。
21 |
22 | If you like this project, you can click the star + view in the upper right corner to track the progress of this project.
23 |
24 | ## Disclaimer
25 |
26 | This project is for my personal learning, development and maintenance only, I do not guarantee the availability and I am not responsible for any consequences resulting from using this software.
27 |
28 | ## Featured
29 |
30 | - Open source `This version depends on the happy mood`
31 | - Supports multiple protocols V2ray, Trojan, Shadowsocks.
32 | - Supports new features like Vless and XTLS.
33 | - Supports single connection to multiple boards and nodes without rebooting.
34 | - Online IP support is limited
35 | - Support node port level, user level rate limit.
36 | - Simple and clear configuration.
37 | - Modify the configuration to automatically restart the instance.
38 | - Easy to compile and upgrade, can quickly update core version, support new Xray-core features.
39 | - Support UDP and many other functions
40 |
41 | ## Featured
42 |
43 | | Feature | v2ray | trojan | shadowsocks | hysteria |
44 | | ------------------------------------ | ----- | ------ | ----------- | -------- |
45 | | Automatically apply tls certificates | √ | √ | √ | √ |
46 | | Automatically renew tls certificates | √ | √ | √ | √ |
47 | | Online user statistics | √ | √ | √ | √ |
48 | | Audit rules | √ | √ | √ | √ |
49 | | Custom DNS | √ | √ | √ | √ |
50 | | Limit online IP numbers | √ | √ | √ | √ |
51 | | Connection limit | √ | √ | √ | √ |
52 | | Cross-node IP number limit | √ | √ | √ | √ |
53 | | Limit speed according to users | √ | √ | √ | √ |
54 | | Dynamic speed limit (untested) | √ | √ | √ | √ |
55 |
56 | ## User interface support
57 |
58 | | Panel | v2ray | trojan | shadowsocks | hysteria | hysteria2 |
59 | | ----------- | ----- | ------ | ----------- | -------- | --------- |
60 | | AikoPanel | √ | √ | √ | √ | √ |
61 |
62 | ## Software installation - release
63 |
64 | ```
65 | wget --no-check-certificate -O install.sh https://raw.githubusercontent.com/AikoPanel/Aiko-Server-Script/master/install_v2.sh && bash install.sh
66 | ```
67 |
68 | ### Docker installation
69 |
70 | ```
71 | docker pull aikocute/aiko-server:latest && docker run --restart=always --name aiko-server -d -v ${PATCH_TO_CONFIG}/aiko.json:/etc/Aiko-Server/aiko.json --network=host aikocute/aiko-server:latest
72 | ```
73 |
74 | ### Docker-compose installation
75 |
76 | Step 1 : Create Config File `aiko.json` in `/etc/Aiko-Server/aiko.json`
77 |
78 | ```
79 | mkdir -p /etc/Aiko-Server/config && cd /etc/Aiko-Server/config && wget https://raw.githubusercontent.com/Github-Aiko/Aiko-Server-Script/master/aiko.json
80 | ```
81 |
82 | Step 2 : Create `docker-compose.yml` in `/etc/Aiko-Server/docker-compose.yml`
83 |
84 | ```
85 | mkdir -p /etc/Aiko-Server && cd /etc/Aiko-Server && wget https://raw.githubusercontent.com/Github-Aiko/Aiko-Server-Script/master/docker-compose.yml
86 | ```
87 |
88 | Step 3 : Run `docker-compose up -d` in `/etc/Aiko-Server/`
89 |
90 | ```
91 | cd /etc/Aiko-Server/ && docker-compose up -d
92 | ```
93 |
94 | ## Telgram
95 |
96 | [Tele Aiko](https://t.me/Tele_Aiko)
97 |
98 | ## Stargazers over time
99 |
100 | [](https://starchart.cc/Github-Aiko/Aiko-Server)
101 |
--------------------------------------------------------------------------------
/api/panel/node.go:
--------------------------------------------------------------------------------
1 | package panel
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "reflect"
7 | "strconv"
8 | "strings"
9 | "time"
10 |
11 | "github.com/Github-Aiko/Aiko-Server/common/crypt"
12 | "github.com/goccy/go-json"
13 | )
14 |
15 | // Security type
16 | const (
17 | None = 0
18 | Tls = 1
19 | Reality = 2
20 | )
21 |
22 | type NodeInfo struct {
23 | Id int
24 | Type string
25 | Security int
26 | PushInterval time.Duration
27 | PullInterval time.Duration
28 | RawDNS RawDNS
29 | Rules Rules
30 |
31 | // origin
32 | VAllss *VAllssNode
33 | Shadowsocks *ShadowsocksNode
34 | Trojan *TrojanNode
35 | Hysteria *HysteriaNode
36 | Hysteria2 *Hysteria2Node
37 | Common *CommonNode
38 | }
39 |
40 | type CommonNode struct {
41 | Host string `json:"host"`
42 | ServerPort int `json:"server_port"`
43 | ServerName string `json:"server_name"`
44 | Routes []Route `json:"routes"`
45 | BaseConfig *BaseConfig `json:"base_config"`
46 | }
47 |
48 | type Route struct {
49 | Id int `json:"id"`
50 | Match interface{} `json:"match"`
51 | Action string `json:"action"`
52 | ActionValue string `json:"action_value"`
53 | }
54 | type BaseConfig struct {
55 | PushInterval any `json:"push_interval"`
56 | PullInterval any `json:"pull_interval"`
57 | }
58 |
59 | // VAllssNode is vmess and vless node info
60 | type VAllssNode struct {
61 | CommonNode
62 | Tls int `json:"tls"`
63 | TlsSettings TlsSettings `json:"tls_settings"`
64 | TlsSettingsBack *TlsSettings `json:"tlsSettings"`
65 | Network string `json:"network"`
66 | NetworkSettings json.RawMessage `json:"network_settings"`
67 | NetworkSettingsBack json.RawMessage `json:"networkSettings"`
68 | ServerName string `json:"server_name"`
69 |
70 | // vless only
71 | Flow string `json:"flow"`
72 | RealityConfig RealityConfig `json:"-"`
73 | }
74 |
75 | type TlsSettings struct {
76 | ServerName string `json:"server_name"`
77 | Dest string `json:"dest"`
78 | ServerPort string `json:"server_port"`
79 | ShortId string `json:"short_id"`
80 | PrivateKey string `json:"private_key"`
81 | Xver uint8 `json:"xver,string"`
82 | }
83 |
84 | type RealityConfig struct {
85 | Xver uint64 `json:"Xver"`
86 | MinClientVer string `json:"MinClientVer"`
87 | MaxClientVer string `json:"MaxClientVer"`
88 | MaxTimeDiff string `json:"MaxTimeDiff"`
89 | }
90 |
91 | type ShadowsocksNode struct {
92 | CommonNode
93 | Cipher string `json:"cipher"`
94 | ServerKey string `json:"server_key"`
95 | }
96 |
97 | type TrojanNode struct {
98 | CommonNode
99 | Network string `json:"network"`
100 | NetworkSettings json.RawMessage `json:"networkSettings"`
101 | }
102 |
103 | type HysteriaNode struct {
104 | CommonNode
105 | UpMbps int `json:"up_mbps"`
106 | DownMbps int `json:"down_mbps"`
107 | Obfs string `json:"obfs"`
108 | }
109 |
110 | type Hysteria2Node struct {
111 | CommonNode
112 | UpMbps int `json:"up_mbps"`
113 | DownMbps int `json:"down_mbps"`
114 | ObfsType string `json:"obfs"`
115 | ObfsPassword string `json:"obfs-password"`
116 | }
117 |
118 | type RawDNS struct {
119 | DNSMap map[string]map[string]interface{}
120 | DNSJson []byte
121 | }
122 |
123 | type Rules struct {
124 | Regexp []string
125 | Protocol []string
126 | }
127 |
128 | func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
129 | const path = "/api/v1/server/Aiko/config"
130 | r, err := c.client.
131 | R().
132 | SetHeader("If-None-Match", c.nodeEtag).
133 | Get(path)
134 | if err = c.checkResponse(r, path, err); err != nil {
135 | return nil, err
136 | }
137 |
138 | if r != nil {
139 | defer func() {
140 | if r.RawBody() != nil {
141 | r.RawBody().Close()
142 | }
143 | }()
144 | if r.StatusCode() == 304 {
145 | return nil, nil
146 | }
147 | } else {
148 | return nil, fmt.Errorf("received nil response")
149 | }
150 | node = &NodeInfo{
151 | Id: c.NodeId,
152 | Type: c.NodeType,
153 | RawDNS: RawDNS{
154 | DNSMap: make(map[string]map[string]interface{}),
155 | DNSJson: []byte(""),
156 | },
157 | }
158 | // parse protocol params
159 | var cm *CommonNode
160 | switch c.NodeType {
161 | case "vmess", "vless":
162 | rsp := &VAllssNode{}
163 | err = json.Unmarshal(r.Body(), rsp)
164 | if err != nil {
165 | return nil, fmt.Errorf("decode v2ray params error: %s", err)
166 | }
167 | if len(rsp.NetworkSettingsBack) > 0 {
168 | rsp.NetworkSettings = rsp.NetworkSettingsBack
169 | rsp.NetworkSettingsBack = nil
170 | }
171 | if rsp.TlsSettingsBack != nil {
172 | rsp.TlsSettings = *rsp.TlsSettingsBack
173 | rsp.TlsSettingsBack = nil
174 | }
175 | cm = &rsp.CommonNode
176 | node.VAllss = rsp
177 | node.Security = node.VAllss.Tls
178 | if len(rsp.NetworkSettings) > 0 {
179 | err = json.Unmarshal(rsp.NetworkSettings, &rsp.RealityConfig)
180 | if err != nil {
181 | return nil, fmt.Errorf("decode reality config error: %s", err)
182 | }
183 | }
184 | if node.Security == Reality {
185 | if rsp.TlsSettings.PrivateKey == "" {
186 | key := crypt.GenX25519Private([]byte("vless" + c.Token))
187 | rsp.TlsSettings.PrivateKey = base64.RawURLEncoding.EncodeToString(key)
188 | }
189 | }
190 | case "shadowsocks":
191 | rsp := &ShadowsocksNode{}
192 | err = json.Unmarshal(r.Body(), rsp)
193 | if err != nil {
194 | return nil, fmt.Errorf("decode shadowsocks params error: %s", err)
195 | }
196 | cm = &rsp.CommonNode
197 | node.Shadowsocks = rsp
198 | node.Security = None
199 | case "trojan":
200 | rsp := &TrojanNode{}
201 | err = json.Unmarshal(r.Body(), rsp)
202 | if err != nil {
203 | return nil, fmt.Errorf("decode trojan params error: %s", err)
204 | }
205 | cm = &rsp.CommonNode
206 | node.Trojan = rsp
207 | node.Security = Tls
208 | case "hysteria":
209 | rsp := &HysteriaNode{}
210 | err = json.Unmarshal(r.Body(), rsp)
211 | if err != nil {
212 | return nil, fmt.Errorf("decode hysteria params error: %s", err)
213 | }
214 | cm = &rsp.CommonNode
215 | node.Hysteria = rsp
216 | node.Security = Tls
217 | case "hysteria2":
218 | rsp := &Hysteria2Node{}
219 | err = json.Unmarshal(r.Body(), rsp)
220 | if err != nil {
221 | return nil, fmt.Errorf("decode hysteria2 params error: %s", err)
222 | }
223 | cm = &rsp.CommonNode
224 | node.Hysteria2 = rsp
225 | node.Security = Tls
226 | }
227 |
228 | // parse rules and dns
229 | for i := range cm.Routes {
230 | var matchs []string
231 | if _, ok := cm.Routes[i].Match.(string); ok {
232 | matchs = strings.Split(cm.Routes[i].Match.(string), ",")
233 | } else if _, ok = cm.Routes[i].Match.([]string); ok {
234 | matchs = cm.Routes[i].Match.([]string)
235 | } else {
236 | temp := cm.Routes[i].Match.([]interface{})
237 | matchs = make([]string, len(temp))
238 | for i := range temp {
239 | matchs[i] = temp[i].(string)
240 | }
241 | }
242 | switch cm.Routes[i].Action {
243 | case "block":
244 | for _, v := range matchs {
245 | if strings.HasPrefix(v, "protocol:") {
246 | // protocol
247 | node.Rules.Protocol = append(node.Rules.Protocol, strings.TrimPrefix(v, "protocol:"))
248 | } else {
249 | // domain
250 | node.Rules.Regexp = append(node.Rules.Regexp, strings.TrimPrefix(v, "regexp:"))
251 | }
252 | }
253 | case "dns":
254 | var domains []string
255 | domains = append(domains, matchs...)
256 | if matchs[0] != "main" {
257 | node.RawDNS.DNSMap[strconv.Itoa(i)] = map[string]interface{}{
258 | "address": cm.Routes[i].ActionValue,
259 | "domains": domains,
260 | }
261 | } else {
262 | dns := []byte(strings.Join(matchs[1:], ""))
263 | node.RawDNS.DNSJson = dns
264 | }
265 | }
266 | }
267 |
268 | // set interval
269 | node.PushInterval = intervalToTime(cm.BaseConfig.PushInterval)
270 | node.PullInterval = intervalToTime(cm.BaseConfig.PullInterval)
271 |
272 | node.Common = cm
273 | // clear
274 | cm.Routes = nil
275 | cm.BaseConfig = nil
276 |
277 | c.nodeEtag = r.Header().Get("ETag")
278 | return
279 | }
280 |
281 | func intervalToTime(i interface{}) time.Duration {
282 | switch reflect.TypeOf(i).Kind() {
283 | case reflect.Int:
284 | return time.Duration(i.(int)) * time.Second
285 | case reflect.String:
286 | i, _ := strconv.Atoi(i.(string))
287 | return time.Duration(i) * time.Second
288 | case reflect.Float64:
289 | return time.Duration(i.(float64)) * time.Second
290 | default:
291 | return time.Duration(reflect.ValueOf(i).Int()) * time.Second
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/api/panel/node_test.go:
--------------------------------------------------------------------------------
1 | package panel
2 |
3 | import (
4 | "log"
5 | "testing"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/conf"
8 | )
9 |
10 | var client *Client
11 |
12 | func init() {
13 | c, err := New(&conf.ApiConfig{
14 | APIHost: "http://127.0.0.1",
15 | Key: "token",
16 | NodeType: "V2ray",
17 | NodeID: 1,
18 | })
19 | if err != nil {
20 | log.Panic(err)
21 | }
22 | client = c
23 | }
24 |
25 | func TestClient_GetNodeInfo(t *testing.T) {
26 | log.Println(client.GetNodeInfo())
27 | log.Println(client.GetNodeInfo())
28 | }
29 |
30 | func TestClient_ReportUserTraffic(t *testing.T) {
31 | log.Println(client.ReportUserTraffic([]UserTraffic{
32 | {
33 | UID: 10372,
34 | Upload: 1000,
35 | Download: 1000,
36 | },
37 | }))
38 | }
39 |
--------------------------------------------------------------------------------
/api/panel/panel.go:
--------------------------------------------------------------------------------
1 | package panel
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 | "time"
9 |
10 | "github.com/sirupsen/logrus"
11 |
12 | "github.com/Github-Aiko/Aiko-Server/conf"
13 | "github.com/go-resty/resty/v2"
14 | )
15 |
16 | // Panel is the interface for different panel's api.
17 |
18 | type Client struct {
19 | client *resty.Client
20 | APIHost string
21 | Token string
22 | NodeType string
23 | NodeId int
24 | nodeEtag string
25 | userEtag string
26 | LastReportOnline map[int]int
27 | }
28 |
29 | func New(c *conf.ApiConfig) (*Client, error) {
30 | client := resty.New()
31 | client.SetRetryCount(3)
32 | if c.Timeout > 0 {
33 | client.SetTimeout(time.Duration(c.Timeout) * time.Second)
34 | } else {
35 | client.SetTimeout(5 * time.Second)
36 | }
37 | client.OnError(func(req *resty.Request, err error) {
38 | var v *resty.ResponseError
39 | if errors.As(err, &v) {
40 | // v.Response contains the last response from the server
41 | // v.Err contains the original error
42 | logrus.Error(v.Err)
43 | }
44 | })
45 | client.SetBaseURL(c.APIHost)
46 | // Check node type
47 | c.NodeType = strings.ToLower(c.NodeType)
48 | switch c.NodeType {
49 | case "v2ray":
50 | c.NodeType = "vmess"
51 | case
52 | "vmess",
53 | "trojan",
54 | "shadowsocks",
55 | "hysteria",
56 | "hysteria2",
57 | "vless":
58 | default:
59 | return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
60 | }
61 | // set params
62 | client.SetQueryParams(map[string]string{
63 | "node_type": c.NodeType,
64 | "node_id": strconv.Itoa(c.NodeID),
65 | "token": c.Key,
66 | })
67 | return &Client{
68 | client: client,
69 | Token: c.Key,
70 | APIHost: c.APIHost,
71 | NodeType: c.NodeType,
72 | NodeId: c.NodeID,
73 | }, nil
74 | }
75 |
--------------------------------------------------------------------------------
/api/panel/user.go:
--------------------------------------------------------------------------------
1 | package panel
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/goccy/go-json"
7 | )
8 |
9 | type OnlineUser struct {
10 | UID int
11 | IP string
12 | }
13 |
14 | type UserInfo struct {
15 | Id int `json:"id"`
16 | Uuid string `json:"uuid"`
17 | SpeedLimit int `json:"speed_limit"`
18 | DeviceLimit int `json:"device_limit"`
19 | AliveIp int `json:"alive_ip"`
20 | }
21 |
22 | type UserListBody struct {
23 | //Msg string `json:"msg"`
24 | Users []UserInfo `json:"users"`
25 | }
26 |
27 | // GetUserList will pull user form sspanel
28 | func (c *Client) GetUserList() (UserList []UserInfo, err error) {
29 | const path = "/api/v1/server/Aiko/user"
30 | r, err := c.client.R().
31 | SetHeader("If-None-Match", c.userEtag).
32 | ForceContentType("application/json").
33 | Get(path)
34 | if err = c.checkResponse(r, path, err); err != nil {
35 | return nil, err
36 | }
37 |
38 | if r != nil {
39 | defer func() {
40 | if r.RawBody() != nil {
41 | r.RawBody().Close()
42 | }
43 | }()
44 | if r.StatusCode() == 304 {
45 | return nil, nil
46 | }
47 | } else {
48 | return nil, fmt.Errorf("received nil response")
49 | }
50 | var userList *UserListBody
51 | if err != nil {
52 | return nil, fmt.Errorf("read body error: %s", err)
53 | }
54 | if err := json.Unmarshal(r.Body(), &userList); err != nil {
55 | return nil, fmt.Errorf("unmarshal userlist error: %s", err)
56 | }
57 | c.userEtag = r.Header().Get("ETag")
58 |
59 | var userinfos []UserInfo
60 | var localDeviceLimit int = 0
61 | for _, user := range userList.Users {
62 | // If there is still device available, add the user
63 | if user.DeviceLimit > 0 && user.AliveIp > 0 {
64 | lastOnline := 0
65 | if v, ok := c.LastReportOnline[user.Id]; ok {
66 | lastOnline = v
67 | }
68 | // If there are any available device.
69 | localDeviceLimit = user.DeviceLimit - user.AliveIp + lastOnline
70 | if localDeviceLimit > 0 {
71 |
72 | } else if lastOnline > 0 {
73 |
74 | } else {
75 | continue
76 | }
77 | }
78 | userinfos = append(userinfos, user)
79 | }
80 |
81 | return userinfos, nil
82 | }
83 |
84 | type UserTraffic struct {
85 | UID int
86 | Upload int64
87 | Download int64
88 | }
89 |
90 | // ReportUserTraffic reports the user traffic
91 | func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
92 | data := make(map[int][]int64, len(userTraffic))
93 | for i := range userTraffic {
94 | data[userTraffic[i].UID] = []int64{userTraffic[i].Upload, userTraffic[i].Download}
95 | }
96 | const path = "/api/v1/server/Aiko/push"
97 | r, err := c.client.R().
98 | SetBody(data).
99 | ForceContentType("application/json").
100 | Post(path)
101 | err = c.checkResponse(r, path, err)
102 | if err != nil {
103 | return err
104 | }
105 | return nil
106 | }
107 |
108 | func (c *Client) ReportNodeOnlineUsers(data *map[int][]string, reportOnline *map[int]int) error {
109 | c.LastReportOnline = *reportOnline
110 | const path = "/api/v1/server/Aiko/alive"
111 | r, err := c.client.R().
112 | SetBody(data).
113 | ForceContentType("application/json").
114 | Post(path)
115 | err = c.checkResponse(r, path, err)
116 |
117 | if err != nil {
118 | return nil
119 | }
120 |
121 | return nil
122 | }
123 |
--------------------------------------------------------------------------------
/api/panel/utils.go:
--------------------------------------------------------------------------------
1 | package panel
2 |
3 | import (
4 | "fmt"
5 | "github.com/go-resty/resty/v2"
6 | path2 "path"
7 | )
8 |
9 | // Debug set the client debug for client
10 | func (c *Client) Debug() {
11 | c.client.SetDebug(true)
12 | }
13 |
14 | func (c *Client) assembleURL(path string) string {
15 | return path2.Join(c.APIHost + path)
16 | }
17 | func (c *Client) checkResponse(res *resty.Response, path string, err error) error {
18 | if err != nil {
19 | return fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
20 | }
21 | if res.StatusCode() >= 400 {
22 | body := res.Body()
23 | return fmt.Errorf("request %s failed: %s", c.assembleURL(path), string(body))
24 | }
25 | return nil
26 | }
27 |
--------------------------------------------------------------------------------
/cmd/action_linux.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/common/exec"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | var (
12 | startCommand = cobra.Command{
13 | Use: "start",
14 | Short: "Start Aiko-Server service",
15 | Run: startHandle,
16 | }
17 | stopCommand = cobra.Command{
18 | Use: "stop",
19 | Short: "Stop Aiko-Server service",
20 | Run: stopHandle,
21 | }
22 | restartCommand = cobra.Command{
23 | Use: "restart",
24 | Short: "Restart Aiko-Server service",
25 | Run: restartHandle,
26 | }
27 | logCommand = cobra.Command{
28 | Use: "log",
29 | Short: "Output Aiko-Server log",
30 | Run: func(_ *cobra.Command, _ []string) {
31 | exec.RunCommandStd("journalctl", "-u", "Aiko-Server.service", "-e", "--no-pager", "-f")
32 | },
33 | }
34 | )
35 |
36 | func init() {
37 | command.AddCommand(&startCommand)
38 | command.AddCommand(&stopCommand)
39 | command.AddCommand(&restartCommand)
40 | command.AddCommand(&logCommand)
41 | }
42 |
43 | func startHandle(_ *cobra.Command, _ []string) {
44 | r, err := checkRunning()
45 | if err != nil {
46 | fmt.Println(Err("check status error: ", err))
47 | fmt.Println(Err("Failed to start Aiko-Server"))
48 | return
49 | }
50 | if r {
51 | fmt.Println(Ok("Aiko-Server is already running, no need to start again. If you want to restart, please use the restart command"))
52 | }
53 | _, err = exec.RunCommandByShell("systemctl start Aiko-Server.service")
54 | if err != nil {
55 | fmt.Println(Err("exec start cmd error: ", err))
56 | fmt.Println(Err("Failed to start Aiko-Server"))
57 | return
58 | }
59 | time.Sleep(time.Second * 3)
60 | r, err = checkRunning()
61 | if err != nil {
62 | fmt.Println(Err("check status error: ", err))
63 | fmt.Println(Err("Failed to start Aiko-Server"))
64 | }
65 | if !r {
66 | fmt.Println(Err("Aiko-Server may have failed to start, please use the Aiko-Server log command to view the log information later"))
67 | return
68 | }
69 | fmt.Println(Ok("Aiko-Server started successfully, please use the Aiko-Server log command to view the running log"))
70 | }
71 |
72 | func stopHandle(_ *cobra.Command, _ []string) {
73 | _, err := exec.RunCommandByShell("systemctl stop Aiko-Server.service")
74 | if err != nil {
75 | fmt.Println(Err("exec stop cmd error: ", err))
76 | fmt.Println(Err("Failed to stop Aiko-Server"))
77 | return
78 | }
79 | time.Sleep(2 * time.Second)
80 | r, err := checkRunning()
81 | if err != nil {
82 | fmt.Println(Err("check status error:", err))
83 | fmt.Println(Err("Failed to stop Aiko-Server"))
84 | return
85 | }
86 | if r {
87 | fmt.Println(Err("Failed to stop Aiko-Server, it may be because the stop time exceeded two seconds, please check the log information later"))
88 | return
89 | }
90 | fmt.Println(Ok("Aiko-Server stopped successfully"))
91 | }
92 |
93 | func restartHandle(_ *cobra.Command, _ []string) {
94 | _, err := exec.RunCommandByShell("systemctl restart Aiko-Server.service")
95 | if err != nil {
96 | fmt.Println(Err("exec restart cmd error: ", err))
97 | fmt.Println(Err("Failed to restart Aiko-Server"))
98 | return
99 | }
100 | r, err := checkRunning()
101 | if err != nil {
102 | fmt.Println(Err("check status error: ", err))
103 | fmt.Println(Err("Failed to restart Aiko-Server"))
104 | return
105 | }
106 | if !r {
107 | fmt.Println(Err("Aiko-Server may have failed to start, please use the Aiko-Server log command to view the log information later"))
108 | return
109 | }
110 | fmt.Println(Ok("Aiko-Server restarted successfully"))
111 | }
112 |
--------------------------------------------------------------------------------
/cmd/cert.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "crypto/x509"
5 | "log"
6 | "os"
7 | "time"
8 |
9 | "github.com/spf13/cobra"
10 | "github.com/xtls/xray-core/common"
11 | "github.com/xtls/xray-core/common/protocol/tls/cert"
12 | )
13 |
14 | var certCommand = &cobra.Command{
15 | Use: "cert",
16 | Short: "Generate TLS certificates",
17 | Run: executeCert,
18 | }
19 |
20 | var (
21 | certDomainNames []string
22 | certCommonName string
23 | certOrganization string
24 | certIsCA bool
25 | certExpireDays int
26 | certFilePath string
27 | )
28 |
29 | func init() {
30 | command.AddCommand(certCommand)
31 | certCommand.Flags().StringArrayVarP(&certDomainNames, "domain", "", nil, "Domain name for the certificate")
32 | certCommand.Flags().StringVarP(&certCommonName, "name", "", "Aiko-Server Inc", "The common name of this certificate")
33 | certCommand.Flags().StringVarP(&certOrganization, "org", "", "Aiko-Server Inc", "Organization of the certificate")
34 | certCommand.Flags().BoolVarP(&certIsCA, "ca", "", false, "Whether this certificate is a CA")
35 | certCommand.Flags().IntVarP(&certExpireDays, "expire", "", 90, "Number of days until the certificate expires. Default value 90 days.")
36 | certCommand.Flags().StringVarP(&certFilePath, "output", "", "/etc/Aiko-Server/cert", "Save certificate in file.")
37 | }
38 |
39 | func executeCert(cmd *cobra.Command, args []string) {
40 | var opts []cert.Option
41 | if certIsCA {
42 | opts = append(opts, cert.Authority(certIsCA))
43 | opts = append(opts, cert.KeyUsage(x509.KeyUsageCertSign|x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature))
44 | }
45 |
46 | opts = append(opts, cert.NotAfter(time.Now().Add(time.Duration(certExpireDays)*24*time.Hour)))
47 | opts = append(opts, cert.CommonName(certCommonName))
48 | if len(certDomainNames) > 0 {
49 | opts = append(opts, cert.DNSNames(certDomainNames...))
50 | }
51 | opts = append(opts, cert.Organization(certOrganization))
52 |
53 | certificate, err := cert.Generate(nil, opts...)
54 | if err != nil {
55 | log.Fatalf("failed to generate TLS certificate: %s", err)
56 | }
57 |
58 | if certFilePath != "" {
59 | cert_FilePath := certFilePath + "/aiko_server.cert"
60 | key_FilePath := certFilePath + "/aiko_server.key"
61 |
62 | if err := createDirectoryIfNotExists(certFilePath); err != nil {
63 | log.Fatalf("failed to create directory: %s", err)
64 | }
65 |
66 | if err := saveCertificateAndKey(certificate, cert_FilePath, key_FilePath); err != nil {
67 | log.Fatalf("failed to save files: %s", err)
68 | }
69 | }
70 | }
71 |
72 | func createDirectoryIfNotExists(path string) error {
73 | _, err := os.Stat(path)
74 | if os.IsNotExist(err) {
75 | if err := os.MkdirAll(path, 0755); err != nil {
76 | return err
77 | }
78 | }
79 | return nil
80 | }
81 |
82 | func saveCertificateAndKey(certificate *cert.Certificate, certFilePath, keyFilePath string) error {
83 | certPEM, keyPEM := certificate.ToPEM()
84 |
85 | if err := writeFile(certPEM, certFilePath); err != nil {
86 | return err
87 | }
88 |
89 | if err := writeFile(keyPEM, keyFilePath); err != nil {
90 | return err
91 | }
92 |
93 | return nil
94 | }
95 |
96 | func writeFile(content []byte, name string) error {
97 | f, err := os.Create(name)
98 | if err != nil {
99 | return err
100 | }
101 | defer f.Close()
102 |
103 | return common.Error2(f.Write(content))
104 | }
105 |
--------------------------------------------------------------------------------
/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 | "os/signal"
6 | "runtime"
7 | "syscall"
8 |
9 | log "github.com/sirupsen/logrus"
10 | "gopkg.in/natefinch/lumberjack.v2"
11 |
12 | "github.com/Github-Aiko/Aiko-Server/conf"
13 | vCore "github.com/Github-Aiko/Aiko-Server/core"
14 | _ "github.com/Github-Aiko/Aiko-Server/core/imports"
15 | "github.com/Github-Aiko/Aiko-Server/limiter"
16 | "github.com/Github-Aiko/Aiko-Server/node"
17 | "github.com/spf13/cobra"
18 | )
19 |
20 | var (
21 | config string
22 | watch bool
23 | )
24 |
25 | func init() {
26 | command.PersistentFlags().
27 | StringVarP(&config, "config", "c",
28 | "/etc/Aiko-Server/aiko.json", "config file path")
29 | command.PersistentFlags().
30 | BoolVarP(&watch, "watch", "w",
31 | true, "watch file path change")
32 | }
33 |
34 | var command = &cobra.Command{
35 | Use: "Aiko-Server",
36 | Short: "Run Aiko-Server server",
37 | Run: serverHandle,
38 | Args: cobra.NoArgs,
39 | }
40 |
41 | func Run() {
42 | err := command.Execute()
43 | if err != nil {
44 | log.WithField("err", err).Error("Execute command failed")
45 | }
46 | }
47 |
48 | func serverHandle(_ *cobra.Command, _ []string) {
49 | showVersion()
50 | c := conf.New()
51 | err := c.LoadFromPath(config)
52 | if err != nil {
53 | log.WithField("err", err).Error("Load config file failed")
54 | return
55 | }
56 | switch c.LogConfig.Level {
57 | case "debug":
58 | log.SetLevel(log.DebugLevel)
59 | case "info":
60 | log.SetLevel(log.InfoLevel)
61 | case "warn":
62 | log.SetLevel(log.WarnLevel)
63 | case "error":
64 | log.SetLevel(log.ErrorLevel)
65 | }
66 | if c.LogConfig.Output != "" {
67 | w := &lumberjack.Logger{
68 | Filename: c.LogConfig.Output,
69 | MaxSize: 100,
70 | MaxBackups: 3,
71 | MaxAge: 28,
72 | Compress: true,
73 | }
74 | log.SetOutput(w)
75 | }
76 | limiter.Init()
77 | log.Info("Start Aiko-Server...")
78 | vc, err := vCore.NewCore(c.CoresConfig)
79 | if err != nil {
80 | log.WithField("err", err).Error("new core failed")
81 | return
82 | }
83 | err = vc.Start()
84 | if err != nil {
85 | log.WithField("err", err).Error("Start core failed")
86 | return
87 | }
88 | defer vc.Close()
89 | log.Info("Core ", vc.Type(), " started")
90 | nodes := node.New()
91 | err = nodes.Start(c.NodeConfig, vc)
92 | if err != nil {
93 | log.WithField("err", err).Error("Run nodes failed")
94 | return
95 | }
96 | log.Info("Nodes started")
97 | xdns := os.Getenv("XRAY_DNS_PATH")
98 | sdns := os.Getenv("SING_DNS_PATH")
99 | if watch {
100 | err = c.Watch(config, xdns, sdns, func() {
101 | nodes.Close()
102 | err = vc.Close()
103 | if err != nil {
104 | log.WithField("err", err).Error("Restart node failed")
105 | return
106 | }
107 | vc, err = vCore.NewCore(c.CoresConfig)
108 | if err != nil {
109 | log.WithField("err", err).Error("New core failed")
110 | return
111 | }
112 | err = vc.Start()
113 | if err != nil {
114 | log.WithField("err", err).Error("Start core failed")
115 | return
116 | }
117 | log.Info("Core ", vc.Type(), " restarted")
118 | err = nodes.Start(c.NodeConfig, vc)
119 | if err != nil {
120 | log.WithField("err", err).Error("Run nodes failed")
121 | return
122 | }
123 | log.Info("Nodes restarted")
124 | runtime.GC()
125 | })
126 | if err != nil {
127 | log.WithField("err", err).Error("start watch failed")
128 | return
129 | }
130 | }
131 | // clear memory
132 | runtime.GC()
133 | // wait exit signal
134 | {
135 | osSignals := make(chan os.Signal, 1)
136 | signal.Notify(osSignals, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM)
137 | <-osSignals
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/cmd/common.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/common/exec"
8 | )
9 |
10 | const (
11 | red = "\033[0;31m"
12 | green = "\033[0;32m"
13 | yellow = "\033[0;33m"
14 | plain = "\033[0m"
15 | )
16 |
17 | func checkRunning() (bool, error) {
18 | o, err := exec.RunCommandByShell("systemctl status Aiko-Server | grep Active")
19 | if err != nil {
20 | return false, err
21 | }
22 | return strings.Contains(o, "running"), nil
23 | }
24 |
25 | func Err(msg ...any) string {
26 | return red + fmt.Sprint(msg...) + plain
27 | }
28 |
29 | func Ok(msg ...any) string {
30 | return green + fmt.Sprint(msg...) + plain
31 | }
32 |
33 | func Warn(msg ...any) string {
34 | return yellow + fmt.Sprint(msg...) + plain
35 | }
36 |
--------------------------------------------------------------------------------
/cmd/common_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import "testing"
4 |
5 | func Test_printFailed(t *testing.T) {
6 | t.Log(Err("test"))
7 | }
8 |
--------------------------------------------------------------------------------
/cmd/install_linux.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | "github.com/Github-Aiko/Aiko-Server/common/exec"
9 | "github.com/spf13/cobra"
10 | )
11 |
12 | var targetVersion string
13 |
14 | var (
15 | updateCommand = cobra.Command{
16 | Use: "update",
17 | Short: "Update Aiko-Server version",
18 | Run: func(_ *cobra.Command, _ []string) {
19 | exec.RunCommandStd("bash",
20 | "<(curl -Ls https://raw.githubusercontents.com/Github-Aiko/Aiko-Server-script/master/install.sh)",
21 | targetVersion)
22 | },
23 | Args: cobra.NoArgs,
24 | }
25 | uninstallCommand = cobra.Command{
26 | Use: "uninstall",
27 | Short: "Uninstall Aiko-Server",
28 | Run: uninstallHandle,
29 | }
30 | )
31 |
32 | func init() {
33 | updateCommand.PersistentFlags().StringVar(&targetVersion, "version", "", "update target version")
34 | command.AddCommand(&updateCommand)
35 | command.AddCommand(&uninstallCommand)
36 | }
37 |
38 | func uninstallHandle(_ *cobra.Command, _ []string) {
39 | var yes string
40 | fmt.Println(Warn("Are you sure you want to uninstall Aiko-Server?(Y/n)"))
41 | fmt.Scan(&yes)
42 | if strings.ToLower(yes) != "y" {
43 | fmt.Println("Uninstallation canceled")
44 | }
45 | _, err := exec.RunCommandByShell("systemctl stop Aiko-Server&&systemctl disable Aiko-Server")
46 | if err != nil {
47 | fmt.Println(Err("exec cmd error: ", err))
48 | fmt.Println(Err("Uninstallation failed"))
49 | return
50 | }
51 | _ = os.RemoveAll("/etc/systemd/system/Aiko-Server.service")
52 | _ = os.RemoveAll("/etc/Aiko-Server/")
53 | _ = os.RemoveAll("/usr/local/Aiko-Server/")
54 | _ = os.RemoveAll("/bin/Aiko-Server")
55 | _, err = exec.RunCommandByShell("systemctl daemon-reload && systemctl reset-failed")
56 | if err != nil {
57 | fmt.Println(Err("exec cmd error: ", err))
58 | fmt.Println(Err("Uninstallation failed"))
59 | return
60 | }
61 | fmt.Println(Ok("Uninstallation successful"))
62 | }
63 |
--------------------------------------------------------------------------------
/cmd/server_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import "testing"
4 |
5 | func TestRun(t *testing.T) {
6 | Run()
7 | }
8 |
--------------------------------------------------------------------------------
/cmd/synctime.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "github.com/Github-Aiko/Aiko-Server/common/systime"
6 | "github.com/beevik/ntp"
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | var ntpServer string
11 |
12 | var commandSyncTime = &cobra.Command{
13 | Use: "synctime",
14 | Short: "Sync time from ntp server",
15 | Args: cobra.NoArgs,
16 | Run: synctimeHandle,
17 | }
18 |
19 | func init() {
20 | commandSyncTime.Flags().StringVar(&ntpServer, "server", "time.apple.com", "ntp server")
21 | command.AddCommand(commandSyncTime)
22 | }
23 |
24 | func synctimeHandle(_ *cobra.Command, _ []string) {
25 | t, err := ntp.Time(ntpServer)
26 | if err != nil {
27 | fmt.Println(Err("get time from server error: ", err))
28 | fmt.Println(Err("Failed to synchronize time"))
29 | return
30 | }
31 | err = systime.SetSystemTime(t)
32 | if err != nil {
33 | fmt.Println(Err("set system time error: ", err))
34 | fmt.Println(Err("Failed to synchronize time"))
35 | return
36 | }
37 | fmt.Println(Ok("Time synchronized successfully"))
38 | }
39 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | vCore "github.com/Github-Aiko/Aiko-Server/core"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | var (
12 | version = "TempVersion" //use ldflags replace
13 | codename = "Aiko-Server"
14 | intro = "A AikoBackend backend based on multi core"
15 | )
16 |
17 | var versionCommand = cobra.Command{
18 | Use: "version",
19 | Short: "Print version info",
20 | Run: func(_ *cobra.Command, _ []string) {
21 | showVersion()
22 | },
23 | }
24 |
25 | func init() {
26 | command.AddCommand(&versionCommand)
27 | }
28 |
29 | func showVersion() {
30 | fmt.Println(`
31 | ____
32 | / /\ \ _ _ _ __
33 | / / \ \ | | | | / / / \
34 | / /____\ \ | | | |/ / | _ |
35 | / /______\ \ | | | |\ \ | (_) |
36 | /_/ \_\|_| |_| \_\ \ ___ / `)
37 |
38 | fmt.Printf("%s %s (%s) \n", codename, version, intro)
39 | fmt.Printf("Supported cores: %s\n", strings.Join(vCore.RegisteredCore(), ", "))
40 | // Warning
41 | fmt.Println(Warn("This Backend Support Only AikoPanel."))
42 | fmt.Println(Warn("The version have many changed for config, please check your config file"))
43 | }
44 |
--------------------------------------------------------------------------------
/cmd/x25519.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base64"
6 | "fmt"
7 | "strings"
8 |
9 | "github.com/Github-Aiko/Aiko-Server/common/crypt"
10 |
11 | "github.com/spf13/cobra"
12 | "golang.org/x/crypto/curve25519"
13 | )
14 |
15 | var x25519Command = cobra.Command{
16 | Use: "x25519",
17 | Short: "Generate key pair for x25519 key exchange",
18 | Run: func(cmd *cobra.Command, args []string) {
19 | executeX25519()
20 | },
21 | }
22 |
23 | func init() {
24 | command.AddCommand(&x25519Command)
25 | }
26 |
27 | func executeX25519() {
28 | var output string
29 | var err error
30 | defer func() {
31 | fmt.Println(output)
32 | }()
33 | var privateKey []byte
34 | var publicKey []byte
35 | var yes, key string
36 | fmt.Println("Do you want to generate a key based on the node information?(Y/n)")
37 | fmt.Scan(&yes)
38 | if strings.ToLower(yes) == "y" {
39 | var temp string
40 | fmt.Println("Please enter the node ID:")
41 | fmt.Scan(&temp)
42 | key = temp
43 | fmt.Println("Please enter the node type:")
44 | fmt.Scan(&temp)
45 | key += strings.ToLower(temp)
46 | fmt.Println("Please enter the Token:")
47 | fmt.Scan(&temp)
48 | key += temp
49 | privateKey = crypt.GenX25519Private([]byte(key))
50 | } else {
51 | privateKey = make([]byte, curve25519.ScalarSize)
52 | if _, err = rand.Read(privateKey); err != nil {
53 | output = Err("read rand error: ", err)
54 | return
55 | }
56 | }
57 | if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
58 | output = Err("gen X25519 error: ", err)
59 | return
60 | }
61 | p := base64.RawURLEncoding.EncodeToString(privateKey)
62 | output = fmt.Sprint("Private key: ",
63 | p,
64 | "\nPublic key: ",
65 | base64.RawURLEncoding.EncodeToString(publicKey))
66 | }
67 |
--------------------------------------------------------------------------------
/cmd/x25519_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import "testing"
4 |
5 | func Test_executeX25519(t *testing.T) {
6 | executeX25519()
7 | }
8 |
--------------------------------------------------------------------------------
/common/counter/conn.go:
--------------------------------------------------------------------------------
1 | package counter
2 |
3 | import (
4 | "io"
5 | "net"
6 |
7 | "github.com/sagernet/sing/common/bufio"
8 |
9 | "github.com/sagernet/sing/common/buf"
10 |
11 | M "github.com/sagernet/sing/common/metadata"
12 | "github.com/sagernet/sing/common/network"
13 | )
14 |
15 | type ConnCounter struct {
16 | network.ExtendedConn
17 | storage *TrafficStorage
18 | readFunc network.CountFunc
19 | writeFunc network.CountFunc
20 | }
21 |
22 | func NewConnCounter(conn net.Conn, s *TrafficStorage) net.Conn {
23 | return &ConnCounter{
24 | ExtendedConn: bufio.NewExtendedConn(conn),
25 | storage: s,
26 | readFunc: func(n int64) {
27 | s.UpCounter.Add(n)
28 | },
29 | writeFunc: func(n int64) {
30 | s.DownCounter.Add(n)
31 | },
32 | }
33 | }
34 |
35 | func (c *ConnCounter) Read(b []byte) (n int, err error) {
36 | n, err = c.ExtendedConn.Read(b)
37 | c.storage.UpCounter.Store(int64(n))
38 | return
39 | }
40 |
41 | func (c *ConnCounter) Write(b []byte) (n int, err error) {
42 | n, err = c.ExtendedConn.Write(b)
43 | c.storage.DownCounter.Store(int64(n))
44 | return
45 | }
46 |
47 | func (c *ConnCounter) ReadBuffer(buffer *buf.Buffer) error {
48 | err := c.ExtendedConn.ReadBuffer(buffer)
49 | if err != nil {
50 | return err
51 | }
52 | if buffer.Len() > 0 {
53 | c.storage.UpCounter.Add(int64(buffer.Len()))
54 | }
55 | return nil
56 | }
57 |
58 | func (c *ConnCounter) WriteBuffer(buffer *buf.Buffer) error {
59 | dataLen := int64(buffer.Len())
60 | err := c.ExtendedConn.WriteBuffer(buffer)
61 | if err != nil {
62 | return err
63 | }
64 | if dataLen > 0 {
65 | c.storage.DownCounter.Add(dataLen)
66 | }
67 | return nil
68 | }
69 |
70 | func (c *ConnCounter) UnwrapReader() (io.Reader, []network.CountFunc) {
71 | return c.ExtendedConn, []network.CountFunc{
72 | c.readFunc,
73 | }
74 | }
75 |
76 | func (c *ConnCounter) UnwrapWriter() (io.Writer, []network.CountFunc) {
77 | return c.ExtendedConn, []network.CountFunc{
78 | c.writeFunc,
79 | }
80 | }
81 |
82 | func (c *ConnCounter) Upstream() any {
83 | return c.ExtendedConn
84 | }
85 |
86 | type PacketConnCounter struct {
87 | network.PacketConn
88 | storage *TrafficStorage
89 | readFunc network.CountFunc
90 | writeFunc network.CountFunc
91 | }
92 |
93 | func NewPacketConnCounter(conn network.PacketConn, s *TrafficStorage) network.PacketConn {
94 | return &PacketConnCounter{
95 | PacketConn: conn,
96 | storage: s,
97 | readFunc: func(n int64) {
98 | s.UpCounter.Add(n)
99 | },
100 | writeFunc: func(n int64) {
101 | s.DownCounter.Add(n)
102 | },
103 | }
104 | }
105 |
106 | func (p *PacketConnCounter) ReadPacket(buff *buf.Buffer) (destination M.Socksaddr, err error) {
107 | destination, err = p.PacketConn.ReadPacket(buff)
108 | if err != nil {
109 | return
110 | }
111 | p.storage.UpCounter.Add(int64(buff.Len()))
112 | return
113 | }
114 |
115 | func (p *PacketConnCounter) WritePacket(buff *buf.Buffer, destination M.Socksaddr) (err error) {
116 | n := buff.Len()
117 | err = p.PacketConn.WritePacket(buff, destination)
118 | if err != nil {
119 | return
120 | }
121 | if n > 0 {
122 | p.storage.DownCounter.Add(int64(n))
123 | }
124 | return
125 | }
126 |
127 | func (p *PacketConnCounter) UnwrapPacketReader() (network.PacketReader, []network.CountFunc) {
128 | return p.PacketConn, []network.CountFunc{
129 | p.readFunc,
130 | }
131 | }
132 |
133 | func (p *PacketConnCounter) UnwrapPacketWriter() (network.PacketWriter, []network.CountFunc) {
134 | return p.PacketConn, []network.CountFunc{
135 | p.writeFunc,
136 | }
137 | }
138 |
139 | func (p *PacketConnCounter) Upstream() any {
140 | return p.PacketConn
141 | }
142 |
--------------------------------------------------------------------------------
/common/counter/traffic.go:
--------------------------------------------------------------------------------
1 | package counter
2 |
3 | import (
4 | "sync"
5 | "sync/atomic"
6 | )
7 |
8 | type TrafficCounter struct {
9 | counters map[string]*TrafficStorage
10 | lock sync.RWMutex
11 | }
12 |
13 | type TrafficStorage struct {
14 | UpCounter atomic.Int64
15 | DownCounter atomic.Int64
16 | }
17 |
18 | func NewTrafficCounter() *TrafficCounter {
19 | return &TrafficCounter{
20 | counters: map[string]*TrafficStorage{},
21 | }
22 | }
23 |
24 | func (c *TrafficCounter) GetCounter(id string) *TrafficStorage {
25 | c.lock.RLock()
26 | cts, ok := c.counters[id]
27 | c.lock.RUnlock()
28 | if !ok {
29 | cts = &TrafficStorage{}
30 | c.counters[id] = cts
31 | }
32 | return cts
33 | }
34 |
35 | func (c *TrafficCounter) GetUpCount(id string) int64 {
36 | c.lock.RLock()
37 | cts, ok := c.counters[id]
38 | c.lock.RUnlock()
39 | if ok {
40 | return cts.UpCounter.Load()
41 | }
42 | return 0
43 | }
44 |
45 | func (c *TrafficCounter) GetDownCount(id string) int64 {
46 | c.lock.RLock()
47 | cts, ok := c.counters[id]
48 | c.lock.RUnlock()
49 | if ok {
50 | return cts.DownCounter.Load()
51 | }
52 | return 0
53 | }
54 |
55 | func (c *TrafficCounter) Len() int {
56 | c.lock.RLock()
57 | defer c.lock.RUnlock()
58 | return len(c.counters)
59 | }
60 |
61 | func (c *TrafficCounter) Reset(id string) {
62 | c.lock.RLock()
63 | cts := c.GetCounter(id)
64 | c.lock.RUnlock()
65 | cts.UpCounter.Store(0)
66 | cts.DownCounter.Store(0)
67 | }
68 |
69 | func (c *TrafficCounter) Delete(id string) {
70 | c.lock.Lock()
71 | delete(c.counters, id)
72 | c.lock.Unlock()
73 | }
74 |
75 | func (c *TrafficCounter) Rx(id string, n int) {
76 | cts := c.GetCounter(id)
77 | cts.DownCounter.Add(int64(n))
78 | }
79 |
80 | func (c *TrafficCounter) Tx(id string, n int) {
81 | cts := c.GetCounter(id)
82 | cts.UpCounter.Add(int64(n))
83 | }
84 |
85 | func (c *TrafficCounter) IncConn(auth string) {
86 | return
87 | }
88 |
89 | func (c *TrafficCounter) DecConn(auth string) {
90 | return
91 | }
92 |
--------------------------------------------------------------------------------
/common/crypt/aes.go:
--------------------------------------------------------------------------------
1 | package crypt
2 |
3 | import (
4 | "crypto/aes"
5 | "encoding/base64"
6 | )
7 |
8 | func AesEncrypt(data []byte, key []byte) (string, error) {
9 | a, err := aes.NewCipher(key)
10 | if err != nil {
11 | return "", err
12 | }
13 | en := make([]byte, len(data))
14 | a.Encrypt(en, data)
15 | return base64.StdEncoding.EncodeToString(en), nil
16 | }
17 |
18 | func AesDecrypt(data string, key []byte) (string, error) {
19 | d, err := base64.StdEncoding.DecodeString(data)
20 | if err != nil {
21 | return "", err
22 | }
23 | a, err := aes.NewCipher(key)
24 | if err != nil {
25 | return "", err
26 | }
27 | de := make([]byte, len(data))
28 | a.Decrypt(de, d)
29 | return string(de), nil
30 | }
31 |
--------------------------------------------------------------------------------
/common/crypt/x25519.go:
--------------------------------------------------------------------------------
1 | package crypt
2 |
3 | import (
4 | "crypto/sha256"
5 | )
6 |
7 | func GenX25519Private(data []byte) []byte {
8 | key := sha256.Sum256(data)
9 | key[0] &= 248
10 | key[31] &= 127
11 | key[31] |= 64
12 | return key[:32]
13 | }
14 |
--------------------------------------------------------------------------------
/common/exec/exec.go:
--------------------------------------------------------------------------------
1 | package exec
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "os/exec"
7 | )
8 |
9 | func RunCommandByShell(cmd string) (string, error) {
10 | e := exec.Command("bash", "-c", cmd)
11 | out, err := e.CombinedOutput()
12 | if errors.Unwrap(err) == exec.ErrNotFound {
13 | e = exec.Command("sh", "-c", cmd)
14 | out, err = e.CombinedOutput()
15 | }
16 | return string(out), err
17 | }
18 |
19 | func RunCommandStd(name string, args ...string) {
20 | e := exec.Command(name, args...)
21 | e.Stdout = os.Stdout
22 | e.Stdin = os.Stdin
23 | e.Stderr = os.Stderr
24 | _ = e.Run()
25 | }
26 |
--------------------------------------------------------------------------------
/common/file/file.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import "os"
4 |
5 | func IsExist(path string) bool {
6 | _, err := os.Stat(path)
7 | return err == nil || !os.IsNotExist(err)
8 | }
9 |
--------------------------------------------------------------------------------
/common/format/user.go:
--------------------------------------------------------------------------------
1 | package format
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func UserTag(tag string, uuid string) string {
8 | return fmt.Sprintf("%s|%s", tag, uuid)
9 | }
10 |
--------------------------------------------------------------------------------
/common/json5/json5.go:
--------------------------------------------------------------------------------
1 | package json5
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | )
7 |
8 | type TrimNodeReader struct {
9 | r io.Reader
10 | br *bytes.Reader
11 | }
12 |
13 | func isNL(c byte) bool {
14 | return c == '\n' || c == '\r'
15 | }
16 |
17 | func isWS(c byte) bool {
18 | return c == ' ' || c == '\t' || isNL(c)
19 | }
20 |
21 | func consumeComment(s []byte, i int) int {
22 | if i < len(s) && s[i] == '/' {
23 | s[i-1] = ' '
24 | for ; i < len(s) && !isNL(s[i]); i += 1 {
25 | s[i] = ' '
26 | }
27 | }
28 | if i < len(s) && s[i] == '*' {
29 | s[i-1] = ' '
30 | s[i] = ' '
31 | for ; i < len(s); i += 1 {
32 | if s[i] != '*' {
33 | s[i] = ' '
34 | } else {
35 | s[i] = ' '
36 | i++
37 | if i < len(s) {
38 | if s[i] == '/' {
39 | s[i] = ' '
40 | break
41 | }
42 | }
43 | }
44 | }
45 | }
46 | return i
47 | }
48 |
49 | func prep(r io.Reader) (s []byte, err error) {
50 | buf := &bytes.Buffer{}
51 | _, err = io.Copy(buf, r)
52 | s = buf.Bytes()
53 | if err != nil {
54 | return
55 | }
56 |
57 | i := 0
58 | for i < len(s) {
59 | switch s[i] {
60 | case '"':
61 | i += 1
62 | for i < len(s) {
63 | if s[i] == '"' {
64 | i += 1
65 | break
66 | } else if s[i] == '\\' {
67 | i += 1
68 | }
69 | i += 1
70 | }
71 | case '/':
72 | i = consumeComment(s, i+1)
73 | case ',':
74 | j := i
75 | for {
76 | i += 1
77 | if i >= len(s) {
78 | break
79 | } else if s[i] == '}' || s[i] == ']' {
80 | s[j] = ' '
81 | break
82 | } else if s[i] == '/' {
83 | i = consumeComment(s, i+1)
84 | } else if !isWS(s[i]) {
85 | break
86 | }
87 | }
88 | default:
89 | i += 1
90 | }
91 | }
92 | return
93 | }
94 |
95 | // Read acts as a proxy for the underlying reader and cleans p
96 | // of comments and trailing commas preceeding ] and }
97 | // comments are delimitted by // up until the end the line
98 | func (st *TrimNodeReader) Read(p []byte) (n int, err error) {
99 | if st.br == nil {
100 | var s []byte
101 | if s, err = prep(st.r); err != nil {
102 | return
103 | }
104 | st.br = bytes.NewReader(s)
105 | }
106 | return st.br.Read(p)
107 | }
108 |
109 | // NewTrimNodeReader New returns an io.Reader acting as proxy to r
110 | func NewTrimNodeReader(r io.Reader) io.Reader {
111 | return &TrimNodeReader{r: r}
112 | }
113 |
--------------------------------------------------------------------------------
/common/rate/conn.go:
--------------------------------------------------------------------------------
1 | package rate
2 |
3 | import (
4 | "net"
5 |
6 | "github.com/juju/ratelimit"
7 | "github.com/sagernet/sing/common/buf"
8 | M "github.com/sagernet/sing/common/metadata"
9 | "github.com/sagernet/sing/common/network"
10 | )
11 |
12 | func NewConnRateLimiter(c net.Conn, l *ratelimit.Bucket) *Conn {
13 | return &Conn{
14 | Conn: c,
15 | limiter: l,
16 | }
17 | }
18 |
19 | type Conn struct {
20 | net.Conn
21 | limiter *ratelimit.Bucket
22 | }
23 |
24 | func (c *Conn) Read(b []byte) (n int, err error) {
25 | c.limiter.Wait(int64(len(b)))
26 | return c.Conn.Read(b)
27 | }
28 |
29 | func (c *Conn) Write(b []byte) (n int, err error) {
30 | c.limiter.Wait(int64(len(b)))
31 | return c.Conn.Write(b)
32 | }
33 |
34 | type PacketConnCounter struct {
35 | network.PacketConn
36 | limiter *ratelimit.Bucket
37 | }
38 |
39 | func NewPacketConnCounter(conn network.PacketConn, l *ratelimit.Bucket) network.PacketConn {
40 | return &PacketConnCounter{
41 | PacketConn: conn,
42 | limiter: l,
43 | }
44 | }
45 |
46 | func (p *PacketConnCounter) ReadPacket(buff *buf.Buffer) (destination M.Socksaddr, err error) {
47 | pLen := buff.Len()
48 | destination, err = p.PacketConn.ReadPacket(buff)
49 | p.limiter.Wait(int64(buff.Len() - pLen))
50 | return
51 | }
52 |
53 | func (p *PacketConnCounter) WritePacket(buff *buf.Buffer, destination M.Socksaddr) (err error) {
54 | p.limiter.Wait(int64(buff.Len()))
55 | return p.PacketConn.WritePacket(buff, destination)
56 | }
57 |
--------------------------------------------------------------------------------
/common/rate/writer.go:
--------------------------------------------------------------------------------
1 | package rate
2 |
3 | import (
4 | "github.com/juju/ratelimit"
5 | "github.com/xtls/xray-core/common"
6 | "github.com/xtls/xray-core/common/buf"
7 | )
8 |
9 | type Writer struct {
10 | writer buf.Writer
11 | limiter *ratelimit.Bucket
12 | }
13 |
14 | func NewRateLimitWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer {
15 | return &Writer{
16 | writer: writer,
17 | limiter: limiter,
18 | }
19 | }
20 |
21 | func (w *Writer) Close() error {
22 | return common.Close(w.writer)
23 | }
24 |
25 | func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
26 | w.limiter.Wait(int64(mb.Len()))
27 | return w.writer.WriteMultiBuffer(mb)
28 | }
29 |
--------------------------------------------------------------------------------
/common/systime/time_stub.go:
--------------------------------------------------------------------------------
1 | //go:build !(windows || linux || darwin)
2 |
3 | package systime
4 |
5 | import (
6 | "os"
7 | "time"
8 | )
9 |
10 | func SetSystemTime(nowTime time.Time) error {
11 | return os.ErrInvalid
12 | }
13 |
--------------------------------------------------------------------------------
/common/systime/time_unix.go:
--------------------------------------------------------------------------------
1 | //go:build linux || darwin
2 |
3 | package systime
4 |
5 | import (
6 | "time"
7 |
8 | "golang.org/x/sys/unix"
9 | )
10 |
11 | func SetSystemTime(nowTime time.Time) error {
12 | timeVal := unix.NsecToTimeval(nowTime.UnixNano())
13 | return unix.Settimeofday(&timeVal)
14 | }
15 |
--------------------------------------------------------------------------------
/common/systime/time_windows.go:
--------------------------------------------------------------------------------
1 | package systime
2 |
3 | import (
4 | "time"
5 | "unsafe"
6 |
7 | "golang.org/x/sys/windows"
8 | )
9 |
10 | func SetSystemTime(nowTime time.Time) error {
11 | var systemTime windows.Systemtime
12 | systemTime.Year = uint16(nowTime.Year())
13 | systemTime.Month = uint16(nowTime.Month())
14 | systemTime.Day = uint16(nowTime.Day())
15 | systemTime.Hour = uint16(nowTime.Hour())
16 | systemTime.Minute = uint16(nowTime.Minute())
17 | systemTime.Second = uint16(nowTime.Second())
18 | systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
19 |
20 | dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
21 | proc := dllKernel32.NewProc("SetSystemTime")
22 |
23 | _, _, err := proc.Call(
24 | uintptr(unsafe.Pointer(&systemTime)),
25 | )
26 |
27 | if err != nil && err.Error() != "The operation completed successfully." {
28 | return err
29 | }
30 |
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/common/task/task.go:
--------------------------------------------------------------------------------
1 | package task
2 |
3 | import (
4 | "sync"
5 | "time"
6 | )
7 |
8 | // Task is a task that runs periodically.
9 | type Task struct {
10 | // Interval of the task being run
11 | Interval time.Duration
12 | // Execute is the task function
13 | Execute func() error
14 |
15 | access sync.Mutex
16 | timer *time.Timer
17 | running bool
18 | }
19 |
20 | func (t *Task) hasClosed() bool {
21 | t.access.Lock()
22 | defer t.access.Unlock()
23 |
24 | return !t.running
25 | }
26 |
27 | func (t *Task) checkedExecute(first bool) error {
28 | if t.hasClosed() {
29 | return nil
30 | }
31 |
32 | t.access.Lock()
33 | defer t.access.Unlock()
34 | if first {
35 | if err := t.Execute(); err != nil {
36 | t.running = false
37 | return err
38 | }
39 | }
40 | if !t.running {
41 | return nil
42 | }
43 | t.timer = time.AfterFunc(t.Interval, func() {
44 | t.checkedExecute(true)
45 | })
46 |
47 | return nil
48 | }
49 |
50 | // Start implements common.Runnable.
51 | func (t *Task) Start(first bool) error {
52 | t.access.Lock()
53 | if t.running {
54 | t.access.Unlock()
55 | return nil
56 | }
57 | t.running = true
58 | t.access.Unlock()
59 | if err := t.checkedExecute(first); err != nil {
60 | t.access.Lock()
61 | t.running = false
62 | t.access.Unlock()
63 | return err
64 | }
65 | return nil
66 | }
67 |
68 | // Close implements common.Closable.
69 | func (t *Task) Close() {
70 | t.access.Lock()
71 | defer t.access.Unlock()
72 |
73 | t.running = false
74 | if t.timer != nil {
75 | t.timer.Stop()
76 | t.timer = nil
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/common/task/task_test.go:
--------------------------------------------------------------------------------
1 | package task
2 |
3 | import (
4 | "log"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestTask(t *testing.T) {
10 | ts := Task{Execute: func() error {
11 | log.Println("q")
12 | return nil
13 | }, Interval: time.Second}
14 | ts.Start(false)
15 | }
16 |
--------------------------------------------------------------------------------
/conf/cert.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | type CertConfig struct {
4 | CertMode string `json:"CertMode"` // none, file, http, dns
5 | RejectUnknownSni bool `json:"RejectUnknownSni"`
6 | CertDomain string `json:"CertDomain"`
7 | CertFile string `json:"CertFile"`
8 | KeyFile string `json:"KeyFile"`
9 | Provider string `json:"Provider"` // alidns, cloudflare, gandi, godaddy....
10 | Email string `json:"Email"`
11 | DNSEnv map[string]string `json:"DNSEnv"`
12 | }
13 |
14 | func NewCertConfig() *CertConfig {
15 | return &CertConfig{
16 | CertMode: "none",
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/conf/conf.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 |
8 | "github.com/Github-Aiko/Aiko-Server/common/json5"
9 |
10 | "github.com/goccy/go-json"
11 | )
12 |
13 | type Conf struct {
14 | LogConfig LogConfig `json:"Log"`
15 | CoresConfig []CoreConfig `json:"Cores"`
16 | NodeConfig []NodeConfig `json:"Nodes"`
17 | }
18 |
19 | func New() *Conf {
20 | return &Conf{
21 | LogConfig: LogConfig{
22 | Level: "info",
23 | Output: "",
24 | },
25 | }
26 | }
27 |
28 | func (p *Conf) LoadFromPath(filePath string) error {
29 | f, err := os.Open(filePath)
30 | if err != nil {
31 | return fmt.Errorf("open config file error: %s", err)
32 | }
33 | defer f.Close()
34 |
35 | reader := json5.NewTrimNodeReader(f)
36 | data, err := io.ReadAll(reader)
37 | if err != nil {
38 | return fmt.Errorf("read config file error: %s", err)
39 | }
40 |
41 | err = json.Unmarshal(data, p)
42 | if err != nil {
43 | return fmt.Errorf("unmarshal config error: %s", err)
44 | }
45 |
46 | return nil
47 | }
48 |
--------------------------------------------------------------------------------
/conf/conf_test.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestConf_LoadFromPath(t *testing.T) {
8 | c := New()
9 | t.Log(c.LoadFromPath("../release/aiko.json"), c.NodeConfig)
10 | }
11 |
12 | func TestConf_Watch(t *testing.T) {
13 | c := New()
14 | t.Log(c.Watch("./1.json", "", "", func() {}))
15 | select {}
16 | }
17 |
--------------------------------------------------------------------------------
/conf/core.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | type CoreConfig struct {
8 | Type string `json:"Type"`
9 | Name string `json:"Name"`
10 | XrayConfig *XrayConfig `json:"-"`
11 | SingConfig *SingConfig `json:"-"`
12 | }
13 |
14 | type _CoreConfig CoreConfig
15 |
16 | func (c *CoreConfig) UnmarshalJSON(b []byte) error {
17 | err := json.Unmarshal(b, (*_CoreConfig)(c))
18 | if err != nil {
19 | return err
20 | }
21 | switch c.Type {
22 | case "xray":
23 | c.XrayConfig = NewXrayConfig()
24 | return json.Unmarshal(b, c.XrayConfig)
25 | case "sing":
26 | c.SingConfig = NewSingConfig()
27 | return json.Unmarshal(b, c.SingConfig)
28 | }
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/conf/limit.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | type LimitConfig struct {
4 | EnableRealtime bool `json:"EnableRealtime"`
5 | SpeedLimit int `json:"SpeedLimit"`
6 | IPLimit int `json:"DeviceLimit"`
7 | ConnLimit int `json:"ConnLimit"`
8 | EnableIpRecorder bool `json:"EnableIpRecorder"`
9 | IpRecorderConfig *IpReportConfig `json:"IpRecorderConfig"`
10 | EnableDynamicSpeedLimit bool `json:"EnableDynamicSpeedLimit"`
11 | DynamicSpeedLimitConfig *DynamicSpeedLimitConfig `json:"DynamicSpeedLimitConfig"`
12 | }
13 |
14 | type RecorderConfig struct {
15 | Url string `json:"Url"`
16 | Token string `json:"Token"`
17 | Timeout int `json:"Timeout"`
18 | }
19 |
20 | type RedisConfig struct {
21 | Address string `json:"Address"`
22 | Password string `json:"Password"`
23 | Db int `json:"Db"`
24 | Expiry int `json:"Expiry"`
25 | }
26 |
27 | type IpReportConfig struct {
28 | Periodic int `json:"Periodic"`
29 | Type string `json:"Type"`
30 | RecorderConfig *RecorderConfig `json:"RecorderConfig"`
31 | RedisConfig *RedisConfig `json:"RedisConfig"`
32 | EnableIpSync bool `json:"EnableIpSync"`
33 | }
34 |
35 | type DynamicSpeedLimitConfig struct {
36 | Periodic int `json:"Periodic"`
37 | Traffic int64 `json:"Traffic"`
38 | SpeedLimit int `json:"SpeedLimit"`
39 | ExpireTime int `json:"ExpireTime"`
40 | }
41 |
--------------------------------------------------------------------------------
/conf/log.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | type LogConfig struct {
4 | Level string `json:"Level"`
5 | Output string `json:"Output"`
6 | }
7 |
--------------------------------------------------------------------------------
/conf/node.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 | "os"
8 | "strings"
9 |
10 | "github.com/Github-Aiko/Aiko-Server/common/json5"
11 | "github.com/goccy/go-json"
12 | )
13 |
14 | type NodeConfig struct {
15 | ApiConfig ApiConfig `json:"-"`
16 | Options Options `json:"-"`
17 | }
18 |
19 | type rawNodeConfig struct {
20 | Include string `json:"Include"`
21 | ApiRaw json.RawMessage `json:"ApiConfig"`
22 | OptRaw json.RawMessage `json:"Options"`
23 | }
24 |
25 | type ApiConfig struct {
26 | APIHost string `json:"ApiHost"`
27 | NodeID int `json:"NodeID"`
28 | Key string `json:"ApiKey"`
29 | NodeType string `json:"NodeType"`
30 | Timeout int `json:"Timeout"`
31 | RuleListPath string `json:"RuleListPath"`
32 | }
33 |
34 | func (n *NodeConfig) UnmarshalJSON(data []byte) (err error) {
35 | rn := rawNodeConfig{}
36 | err = json.Unmarshal(data, &rn)
37 | if err != nil {
38 | return err
39 | }
40 | if len(rn.Include) != 0 {
41 | file, _ := strings.CutPrefix(rn.Include, ":")
42 | switch file {
43 | case "http", "https":
44 | rsp, err := http.Get(file)
45 | if err != nil {
46 | return err
47 | }
48 | defer rsp.Body.Close()
49 | data, err = io.ReadAll(json5.NewTrimNodeReader(rsp.Body))
50 | if err != nil {
51 | return fmt.Errorf("open include file error: %s", err)
52 | }
53 | default:
54 | f, err := os.Open(rn.Include)
55 | if err != nil {
56 | return fmt.Errorf("open include file error: %s", err)
57 | }
58 | defer f.Close()
59 | data, err = io.ReadAll(json5.NewTrimNodeReader(f))
60 | if err != nil {
61 | return fmt.Errorf("open include file error: %s", err)
62 | }
63 | }
64 | err = json.Unmarshal(data, &rn)
65 | if err != nil {
66 | return fmt.Errorf("unmarshal include file error: %s", err)
67 | }
68 | }
69 |
70 | n.ApiConfig = ApiConfig{
71 | APIHost: "http://127.0.0.1",
72 | Timeout: 30,
73 | }
74 | if len(rn.ApiRaw) > 0 {
75 | err = json.Unmarshal(rn.ApiRaw, &n.ApiConfig)
76 | if err != nil {
77 | return
78 | }
79 | } else {
80 | err = json.Unmarshal(data, &n.ApiConfig)
81 | if err != nil {
82 | return
83 | }
84 | }
85 |
86 | n.Options = Options{
87 | ListenIP: "0.0.0.0",
88 | SendIP: "0.0.0.0",
89 | CertConfig: NewCertConfig(),
90 | }
91 | if len(rn.OptRaw) > 0 {
92 | err = json.Unmarshal(rn.OptRaw, &n.Options)
93 | if err != nil {
94 | return
95 | }
96 | } else {
97 | err = json.Unmarshal(data, &n.Options)
98 | if err != nil {
99 | return
100 | }
101 | }
102 | return
103 | }
104 |
105 | type Options struct {
106 | Name string `json:"Name"`
107 | Core string `json:"Core"`
108 | CoreName string `json:"CoreName"`
109 | ListenIP string `json:"ListenIP"`
110 | SendIP string `json:"SendIP"`
111 | DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"`
112 | LimitConfig LimitConfig `json:"LimitConfig"`
113 | RawOptions json.RawMessage `json:"RawOptions"`
114 | XrayOptions *XrayOptions `json:"XrayOptions"`
115 | SingOptions *SingOptions `json:"SingOptions"`
116 | CertConfig *CertConfig `json:"CertConfig"`
117 | }
118 |
119 | func (o *Options) UnmarshalJSON(data []byte) error {
120 | type opt Options
121 | err := json.Unmarshal(data, (*opt)(o))
122 | if err != nil {
123 | return err
124 | }
125 | switch o.Core {
126 | case "xray":
127 | o.XrayOptions = NewXrayOptions()
128 | return json.Unmarshal(data, o.XrayOptions)
129 | case "sing":
130 | o.SingOptions = NewSingOptions()
131 | return json.Unmarshal(data, o.SingOptions)
132 | default:
133 | o.Core = ""
134 | o.RawOptions = data
135 | }
136 | return nil
137 | }
138 |
--------------------------------------------------------------------------------
/conf/sing.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "github.com/sagernet/sing-box/option"
5 | )
6 |
7 | type SingConfig struct {
8 | LogConfig SingLogConfig `json:"Log"`
9 | NtpConfig SingNtpConfig `json:"NTP"`
10 | EnableConnClear bool `json:"EnableConnClear"`
11 | DnsConfigPath string `json:"DnsConfigPath"`
12 | OriginalPath string `json:"OriginalPath"`
13 | }
14 |
15 | type SingLogConfig struct {
16 | Disabled bool `json:"Disable"`
17 | Level string `json:"Level"`
18 | Output string `json:"Output"`
19 | Timestamp bool `json:"Timestamp"`
20 | }
21 |
22 | func NewSingConfig() *SingConfig {
23 | return &SingConfig{
24 | LogConfig: SingLogConfig{
25 | Level: "error",
26 | Timestamp: true,
27 | },
28 | NtpConfig: SingNtpConfig{
29 | Enable: false,
30 | Server: "time.apple.com",
31 | ServerPort: 0,
32 | },
33 | }
34 | }
35 |
36 | type SingOptions struct {
37 | EnableProxyProtocol bool `json:"EnableProxyProtocol"`
38 | TCPFastOpen bool `json:"EnableTFO"`
39 | SniffEnabled bool `json:"EnableSniff"`
40 | EnableDNS bool `json:"EnableDNS"`
41 | DomainStrategy option.DomainStrategy `json:"DomainStrategy"`
42 | SniffOverrideDestination bool `json:"SniffOverrideDestination"`
43 | FallBackConfigs *FallBackConfigForSing `json:"FallBackConfigs"`
44 | }
45 |
46 | type SingNtpConfig struct {
47 | Enable bool `json:"Enable"`
48 | Server string `json:"Server"`
49 | ServerPort uint16 `json:"ServerPort"`
50 | }
51 |
52 | type FallBackConfigForSing struct {
53 | // sing-box
54 | FallBack FallBack `json:"FallBack"`
55 | FallBackForALPN map[string]FallBack `json:"FallBackForALPN"`
56 | }
57 |
58 | type FallBack struct {
59 | Server string `json:"Server"`
60 | ServerPort string `json:"ServerPort"`
61 | }
62 |
63 | func NewSingOptions() *SingOptions {
64 | return &SingOptions{
65 | EnableDNS: false,
66 | EnableProxyProtocol: false,
67 | TCPFastOpen: false,
68 | SniffEnabled: true,
69 | SniffOverrideDestination: true,
70 | FallBackConfigs: &FallBackConfigForSing{},
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/conf/watch.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "path/filepath"
7 | "strings"
8 | "time"
9 |
10 | "github.com/fsnotify/fsnotify"
11 | )
12 |
13 | func (p *Conf) Watch(filePath, xDnsPath string, sDnsPath string, reload func()) error {
14 | watcher, err := fsnotify.NewWatcher()
15 | if err != nil {
16 | return fmt.Errorf("new watcher error: %s", err)
17 | }
18 | go func() {
19 | var pre time.Time
20 | defer watcher.Close()
21 | for {
22 | select {
23 | case e := <-watcher.Events:
24 | if e.Has(fsnotify.Chmod) {
25 | continue
26 | }
27 | if pre.Add(10 * time.Second).After(time.Now()) {
28 | continue
29 | }
30 | pre = time.Now()
31 | go func() {
32 | time.Sleep(5 * time.Second)
33 | switch filepath.Base(strings.TrimSuffix(e.Name, "~")) {
34 | case filepath.Base(xDnsPath), filepath.Base(sDnsPath):
35 | log.Println("DNS file changed, reloading...")
36 | default:
37 | log.Println("config file changed, reloading...")
38 | }
39 | *p = *New()
40 | err := p.LoadFromPath(filePath)
41 | if err != nil {
42 | log.Printf("reload config error: %s", err)
43 | }
44 | reload()
45 | log.Println("reload config success")
46 | }()
47 | case err := <-watcher.Errors:
48 | if err != nil {
49 | log.Printf("File watcher error: %s", err)
50 | }
51 | }
52 | }
53 | }()
54 | err = watcher.Add(filePath)
55 | if err != nil {
56 | return fmt.Errorf("watch file error: %s", err)
57 | }
58 | if xDnsPath != "" {
59 | err = watcher.Add(xDnsPath)
60 | if err != nil {
61 | return fmt.Errorf("watch dns file error: %s", err)
62 | }
63 | }
64 | if sDnsPath != "" {
65 | err = watcher.Add(sDnsPath)
66 | if err != nil {
67 | return fmt.Errorf("watch dns file error: %s", err)
68 | }
69 | }
70 | return nil
71 | }
72 |
--------------------------------------------------------------------------------
/conf/xray.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | type XrayConfig struct {
4 | LogConfig *XrayLogConfig `json:"Log"`
5 | AssetPath string `json:"AssetPath"`
6 | DnsConfigPath string `json:"DnsConfigPath"`
7 | RouteConfigPath string `json:"RouteConfigPath"`
8 | ConnectionConfig *XrayConnectionConfig `json:"XrayConnectionConfig"`
9 | InboundConfigPath string `json:"InboundConfigPath"`
10 | OutboundConfigPath string `json:"OutboundConfigPath"`
11 | }
12 |
13 | type XrayLogConfig struct {
14 | Level string `json:"Level"`
15 | AccessPath string `json:"AccessPath"`
16 | ErrorPath string `json:"ErrorPath"`
17 | }
18 |
19 | type XrayConnectionConfig struct {
20 | Handshake uint32 `json:"handshake"`
21 | ConnIdle uint32 `json:"connIdle"`
22 | UplinkOnly uint32 `json:"uplinkOnly"`
23 | DownlinkOnly uint32 `json:"downlinkOnly"`
24 | BufferSize int32 `json:"bufferSize"`
25 | }
26 |
27 | func NewXrayConfig() *XrayConfig {
28 | return &XrayConfig{
29 | LogConfig: &XrayLogConfig{
30 | Level: "warning",
31 | AccessPath: "",
32 | ErrorPath: "",
33 | },
34 | AssetPath: "/etc/Aiko-Server/",
35 | DnsConfigPath: "",
36 | InboundConfigPath: "",
37 | OutboundConfigPath: "",
38 | RouteConfigPath: "",
39 | ConnectionConfig: &XrayConnectionConfig{
40 | Handshake: 4,
41 | ConnIdle: 30,
42 | UplinkOnly: 2,
43 | DownlinkOnly: 4,
44 | BufferSize: 64,
45 | },
46 | }
47 | }
48 |
49 | type XrayOptions struct {
50 | EnableProxyProtocol bool `json:"EnableProxyProtocol"`
51 | EnableDNS bool `json:"EnableDNS"`
52 | DNSType string `json:"DNSType"`
53 | EnableUot bool `json:"EnableUot"`
54 | EnableTFO bool `json:"EnableTFO"`
55 | DisableIVCheck bool `json:"DisableIVCheck"`
56 | DisableSniffing bool `json:"DisableSniffing"`
57 | EnableFallback bool `json:"EnableFallback"`
58 | FallBackConfigs []FallBackConfigForXray `json:"FallBackConfigs"`
59 | }
60 |
61 | type FallBackConfigForXray struct {
62 | SNI string `json:"SNI"`
63 | Alpn string `json:"Alpn"`
64 | Path string `json:"Path"`
65 | Dest string `json:"Dest"`
66 | ProxyProtocolVer uint64 `json:"ProxyProtocolVer"`
67 | }
68 |
69 | func NewXrayOptions() *XrayOptions {
70 | return &XrayOptions{
71 | EnableProxyProtocol: false,
72 | EnableDNS: false,
73 | DNSType: "AsIs",
74 | EnableUot: false,
75 | EnableTFO: false,
76 | DisableIVCheck: false,
77 | DisableSniffing: false,
78 | EnableFallback: false,
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/core/core.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/Github-Aiko/Aiko-Server/conf"
7 | )
8 |
9 | var (
10 | cores = map[string]func(c *conf.CoreConfig) (Core, error){}
11 | )
12 |
13 | func NewCore(c []conf.CoreConfig) (Core, error) {
14 | if len(c) < 0 {
15 | return nil, errors.New("no have vail core")
16 | }
17 | // multi core
18 | if len(c) > 1 {
19 | return NewSelector(c)
20 | }
21 | // one core
22 | if f, ok := cores[c[0].Type]; ok {
23 | return f(&c[0])
24 | } else {
25 | return nil, errors.New("unknown core type")
26 | }
27 | }
28 |
29 | func RegisterCore(t string, f func(c *conf.CoreConfig) (Core, error)) {
30 | cores[t] = f
31 | }
32 |
33 | func RegisteredCore() []string {
34 | cs := make([]string, 0, len(cores))
35 | for k := range cores {
36 | cs = append(cs, k)
37 | }
38 | return cs
39 | }
40 |
--------------------------------------------------------------------------------
/core/imports/imports.go:
--------------------------------------------------------------------------------
1 | package imports
2 |
--------------------------------------------------------------------------------
/core/imports/sing.go:
--------------------------------------------------------------------------------
1 | //go:build sing
2 |
3 | package imports
4 |
5 | import _ "github.com/Github-Aiko/Aiko-Server/core/sing"
6 |
--------------------------------------------------------------------------------
/core/imports/xray.go:
--------------------------------------------------------------------------------
1 | //go:build xray
2 |
3 | package imports
4 |
5 | import _ "github.com/Github-Aiko/Aiko-Server/core/xray"
6 |
--------------------------------------------------------------------------------
/core/interface.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/Github-Aiko/Aiko-Server/api/panel"
5 | "github.com/Github-Aiko/Aiko-Server/conf"
6 | )
7 |
8 | type AddUsersParams struct {
9 | Tag string
10 | Users []panel.UserInfo
11 | *panel.NodeInfo
12 | }
13 |
14 | type Core interface {
15 | Start() error
16 | Close() error
17 | AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error
18 | DelNode(tag string) error
19 | AddUsers(p *AddUsersParams) (added int, err error)
20 | GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64)
21 | DelUsers(users []panel.UserInfo, tag string) error
22 | Protocols() []string
23 | Type() string
24 | }
25 |
--------------------------------------------------------------------------------
/core/selector.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/hashicorp/go-multierror"
7 | "strings"
8 | "sync"
9 |
10 | "github.com/Github-Aiko/Aiko-Server/api/panel"
11 | "github.com/Github-Aiko/Aiko-Server/conf"
12 | )
13 |
14 | type Selector struct {
15 | cores map[string]Core
16 | nodes sync.Map
17 | }
18 |
19 | func NewSelector(c []conf.CoreConfig) (Core, error) {
20 | cs := make(map[string]Core, len(c))
21 | for _, t := range c {
22 | f, ok := cores[strings.ToLower(t.Type)]
23 | if !ok {
24 | return nil, errors.New("unknown core type: " + t.Type)
25 | }
26 | core1, err := f(&t)
27 | if err != nil {
28 | return nil, err
29 | }
30 | if t.Name == "" {
31 | cs[t.Type] = core1
32 | } else {
33 | cs[t.Name] = core1
34 | }
35 | }
36 | return &Selector{
37 | cores: cs,
38 | }, nil
39 | }
40 |
41 | func (s *Selector) Start() error {
42 | for i := range s.cores {
43 | err := s.cores[i].Start()
44 | if err != nil {
45 | return err
46 | }
47 | }
48 | return nil
49 | }
50 |
51 | func (s *Selector) Close() error {
52 | var errs error
53 | for i := range s.cores {
54 | err := s.cores[i].Close()
55 | if err != nil {
56 | errs = multierror.Append(errs, err)
57 | }
58 | }
59 | return errs
60 | }
61 |
62 | func isSupported(protocol string, protocols []string) bool {
63 | for i := range protocols {
64 | if protocol == protocols[i] {
65 | return true
66 | }
67 | }
68 | return false
69 | }
70 |
71 | func (s *Selector) AddNode(tag string, info *panel.NodeInfo, option *conf.Options) error {
72 | var core Core
73 | if len(option.CoreName) > 0 {
74 | // use name to select core
75 | if c, ok := s.cores[option.CoreName]; ok {
76 | core = c
77 | }
78 | } else {
79 | // use type to select core
80 | for _, c := range s.cores {
81 | if len(option.Core) == 0 {
82 | if !isSupported(info.Type, c.Protocols()) {
83 | continue
84 | }
85 | } else if option.Core != c.Type() {
86 | continue
87 | }
88 | core = c
89 | }
90 | }
91 | if core == nil {
92 | return errors.New("the node type is not support")
93 | }
94 | if len(option.Core) == 0 {
95 | option.Core = core.Type()
96 | err := option.UnmarshalJSON(option.RawOptions)
97 | if err != nil {
98 | return fmt.Errorf("unmarshal option error: %s", err)
99 | }
100 | option.RawOptions = nil
101 | }
102 | err := core.AddNode(tag, info, option)
103 | if err != nil {
104 | return err
105 | }
106 | s.nodes.Store(tag, core)
107 | return nil
108 | }
109 |
110 | func (s *Selector) DelNode(tag string) error {
111 | if t, e := s.nodes.Load(tag); e {
112 | err := t.(Core).DelNode(tag)
113 | if err != nil {
114 | return err
115 | }
116 | s.nodes.Delete(tag)
117 | return nil
118 | }
119 | return errors.New("the node is not have")
120 | }
121 |
122 | func (s *Selector) AddUsers(p *AddUsersParams) (added int, err error) {
123 | t, e := s.nodes.Load(p.Tag)
124 | if !e {
125 | return 0, errors.New("the node is not have")
126 | }
127 | return t.(Core).AddUsers(p)
128 | }
129 |
130 | func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
131 | t, e := s.nodes.Load(tag)
132 | if !e {
133 | return 0, 0
134 | }
135 | return t.(Core).GetUserTraffic(tag, uuid, reset)
136 | }
137 |
138 | func (s *Selector) DelUsers(users []panel.UserInfo, tag string) error {
139 | t, e := s.nodes.Load(tag)
140 | if !e {
141 | return errors.New("the node is not have")
142 | }
143 | return t.(Core).DelUsers(users, tag)
144 | }
145 |
146 | func (s *Selector) Protocols() []string {
147 | protocols := make([]string, 0)
148 | for i := range s.cores {
149 | protocols = append(protocols, s.cores[i].Protocols()...)
150 | }
151 | return protocols
152 | }
153 |
154 | func (s *Selector) Type() string {
155 | t := "Selector("
156 | var flag bool
157 | for n, c := range s.cores {
158 | if flag {
159 | t += " "
160 | } else {
161 | flag = true
162 | }
163 | if len(n) == 0 {
164 | t += c.Type()
165 | } else {
166 | t += n
167 | }
168 | }
169 | t += ")"
170 | return t
171 | }
172 |
--------------------------------------------------------------------------------
/core/sing/dns.go:
--------------------------------------------------------------------------------
1 | package sing
2 |
3 | import (
4 | "bytes"
5 | "github.com/Github-Aiko/Aiko-Server/api/panel"
6 | "github.com/goccy/go-json"
7 | log "github.com/sirupsen/logrus"
8 | "os"
9 | "strings"
10 | )
11 |
12 | func updateDNSConfig(node *panel.NodeInfo) (err error) {
13 | dnsPath := os.Getenv("SING_DNS_PATH")
14 | if len(node.RawDNS.DNSJson) != 0 {
15 | var prettyJSON bytes.Buffer
16 | if err := json.Indent(&prettyJSON, node.RawDNS.DNSJson, "", " "); err != nil {
17 | return err
18 | }
19 | err = saveDnsConfig(prettyJSON.Bytes(), dnsPath)
20 | } else if len(node.RawDNS.DNSMap) != 0 {
21 | dnsConfig := DNSConfig{
22 | Servers: []map[string]interface{}{
23 | {
24 | "tag": "default",
25 | "address": "https://8.8.8.8/dns-query",
26 | "detour": "direct",
27 | },
28 | },
29 | }
30 | for id, value := range node.RawDNS.DNSMap {
31 | dnsConfig.Servers = append(dnsConfig.Servers,
32 | map[string]interface{}{
33 | "tag": id,
34 | "address": value["address"],
35 | "address_resolver": "default",
36 | "detour": "direct",
37 | },
38 | )
39 | rule := map[string]interface{}{
40 | "server": id,
41 | "disable_cache": true,
42 | }
43 | for _, ruleType := range []string{"domain_suffix", "domain_keyword", "domain_regex", "geosite"} {
44 | var domains []string
45 | for _, v := range value["domains"].([]string) {
46 | split := strings.SplitN(v, ":", 2)
47 | prefix := strings.ToLower(split[0])
48 | if prefix == ruleType || (prefix == "domain" && ruleType == "domain_suffix") {
49 | if len(split) > 1 {
50 | domains = append(domains, split[1])
51 | }
52 | if len(domains) > 0 {
53 | rule[ruleType] = domains
54 | }
55 | }
56 | }
57 | }
58 | dnsConfig.Rules = append(dnsConfig.Rules, rule)
59 | }
60 | dnsConfigJSON, err := json.MarshalIndent(dnsConfig, "", " ")
61 | if err != nil {
62 | log.WithField("err", err).Error("Error marshaling dnsConfig to JSON")
63 | return err
64 | }
65 | err = saveDnsConfig(dnsConfigJSON, dnsPath)
66 | }
67 | return err
68 | }
69 |
70 | func saveDnsConfig(dns []byte, dnsPath string) (err error) {
71 | currentData, err := os.ReadFile(dnsPath)
72 | if err != nil {
73 | log.WithField("err", err).Error("Failed to read SING_DNS_PATH")
74 | return err
75 | }
76 | if !bytes.Equal(currentData, dns) {
77 | if err = os.Truncate(dnsPath, 0); err != nil {
78 | log.WithField("err", err).Error("Failed to clear SING DNS PATH file")
79 | }
80 | if err = os.WriteFile(dnsPath, dns, 0644); err != nil {
81 | log.WithField("err", err).Error("Failed to write DNS to SING DNS PATH file")
82 | }
83 | }
84 | return err
85 | }
86 |
--------------------------------------------------------------------------------
/core/sing/hook.go:
--------------------------------------------------------------------------------
1 | package sing
2 |
3 | import (
4 | "context"
5 | "io"
6 | "net"
7 | "sync"
8 |
9 | "github.com/sagernet/sing-box/common/urltest"
10 |
11 | "github.com/Github-Aiko/Aiko-Server/common/rate"
12 |
13 | "github.com/Github-Aiko/Aiko-Server/limiter"
14 |
15 | "github.com/Github-Aiko/Aiko-Server/common/counter"
16 | "github.com/sagernet/sing-box/adapter"
17 | "github.com/sagernet/sing-box/log"
18 | N "github.com/sagernet/sing/common/network"
19 | )
20 |
21 | type HookServer struct {
22 | EnableConnClear bool
23 | counter sync.Map
24 | connClears sync.Map
25 | }
26 |
27 | type ConnClear struct {
28 | lock sync.RWMutex
29 | conns map[int]io.Closer
30 | }
31 |
32 | func (c *ConnClear) AddConn(cn io.Closer) (key int) {
33 | c.lock.Lock()
34 | defer c.lock.Unlock()
35 | key = len(c.conns)
36 | c.conns[key] = cn
37 | return
38 | }
39 |
40 | func (c *ConnClear) DelConn(key int) {
41 | c.lock.Lock()
42 | defer c.lock.Unlock()
43 | delete(c.conns, key)
44 | }
45 |
46 | func (c *ConnClear) ClearConn() {
47 | c.lock.Lock()
48 | defer c.lock.Unlock()
49 | for _, c := range c.conns {
50 | c.Close()
51 | }
52 | }
53 |
54 | func (h *HookServer) ModeList() []string {
55 | return nil
56 | }
57 |
58 | func NewHookServer(enableClear bool) *HookServer {
59 | return &HookServer{
60 | EnableConnClear: enableClear,
61 | counter: sync.Map{},
62 | connClears: sync.Map{},
63 | }
64 | }
65 |
66 | func (h *HookServer) Start() error {
67 | return nil
68 | }
69 |
70 | func (h *HookServer) Close() error {
71 | return nil
72 | }
73 |
74 | func (h *HookServer) PreStart() error {
75 | return nil
76 | }
77 |
78 | func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule) (net.Conn, adapter.Tracker) {
79 | t := &Tracker{}
80 | l, err := limiter.GetLimiter(m.Inbound)
81 | if err != nil {
82 | log.Warn("get limiter for ", m.Inbound, " error: ", err)
83 | return conn, t
84 | }
85 | if l.CheckDomainRule(m.Domain) {
86 | conn.Close()
87 | log.Error("[", m.Inbound, "] ",
88 | "Limited ", m.User, " access to ", m.Domain, " by domain rule")
89 | return conn, t
90 | }
91 | if l.CheckProtocolRule(m.Protocol) {
92 | conn.Close()
93 | log.Error("[", m.Inbound, "] ",
94 | "Limited ", m.User, " use ", m.Domain, " by protocol rule")
95 | return conn, t
96 | }
97 | ip := m.Source.Addr.String()
98 | if b, r := l.CheckLimit(m.User, ip, true); r {
99 | conn.Close()
100 | log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
101 | return conn, t
102 | } else if b != nil {
103 | conn = rate.NewConnRateLimiter(conn, b)
104 | }
105 | t.AddLeave(func() {
106 | l.ConnLimiter.DelConnCount(m.User, ip)
107 | })
108 | if h.EnableConnClear {
109 | var key int
110 | cc := &ConnClear{
111 | conns: map[int]io.Closer{
112 | 0: conn,
113 | },
114 | }
115 | if v, ok := h.connClears.LoadOrStore(m.Inbound+m.User, cc); ok {
116 | cc = v.(*ConnClear)
117 | key = cc.AddConn(conn)
118 | }
119 | t.AddLeave(func() {
120 | cc.DelConn(key)
121 | })
122 | }
123 | if c, ok := h.counter.Load(m.Inbound); ok {
124 | return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
125 | } else {
126 | c := counter.NewTrafficCounter()
127 | h.counter.Store(m.Inbound, c)
128 | return counter.NewConnCounter(conn, c.GetCounter(m.User)), t
129 | }
130 | }
131 |
132 | func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn, m adapter.InboundContext, _ adapter.Rule) (N.PacketConn, adapter.Tracker) {
133 | t := &Tracker{}
134 | l, err := limiter.GetLimiter(m.Inbound)
135 | if err != nil {
136 | log.Warn("get limiter for ", m.Inbound, " error: ", err)
137 | return conn, t
138 | }
139 | if l.CheckDomainRule(m.Domain) {
140 | conn.Close()
141 | log.Error("[", m.Inbound, "] ",
142 | "Limited ", m.User, " access to ", m.Domain, " by domain rule")
143 | return conn, t
144 | }
145 | if l.CheckProtocolRule(m.Protocol) {
146 | conn.Close()
147 | log.Error("[", m.Inbound, "] ",
148 | "Limited ", m.User, " use ", m.Domain, " by protocol rule")
149 | return conn, t
150 | }
151 | ip := m.Source.Addr.String()
152 | if b, r := l.CheckLimit(m.User, ip, true); r {
153 | conn.Close()
154 | log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
155 | return conn, t
156 | } else if b != nil {
157 | conn = rate.NewPacketConnCounter(conn, b)
158 | }
159 | if h.EnableConnClear {
160 | var key int
161 | cc := &ConnClear{
162 | conns: map[int]io.Closer{
163 | 0: conn,
164 | },
165 | }
166 | if v, ok := h.connClears.LoadOrStore(m.Inbound+m.User, cc); ok {
167 | cc = v.(*ConnClear)
168 | key = cc.AddConn(conn)
169 | }
170 | t.AddLeave(func() {
171 | cc.DelConn(key)
172 | })
173 | }
174 | if c, ok := h.counter.Load(m.Inbound); ok {
175 | return counter.NewPacketConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
176 | } else {
177 | c := counter.NewTrafficCounter()
178 | h.counter.Store(m.Inbound, c)
179 | return counter.NewPacketConnCounter(conn, c.GetCounter(m.User)), t
180 | }
181 | }
182 |
183 | // not need
184 |
185 | func (h *HookServer) Mode() string {
186 | return ""
187 | }
188 | func (h *HookServer) StoreSelected() bool {
189 | return false
190 | }
191 | func (h *HookServer) CacheFile() adapter.CacheFile {
192 | return nil
193 | }
194 | func (h *HookServer) HistoryStorage() *urltest.HistoryStorage {
195 | return nil
196 | }
197 |
198 | func (h *HookServer) StoreFakeIP() bool {
199 | return false
200 | }
201 |
202 | func (h *HookServer) ClearConn(inbound string, user string) {
203 | if v, ok := h.connClears.Load(inbound + user); ok {
204 | v.(*ConnClear).ClearConn()
205 | h.connClears.Delete(inbound + user)
206 | }
207 | }
208 |
209 | type Tracker struct {
210 | l []func()
211 | }
212 |
213 | func (t *Tracker) AddLeave(f func()) {
214 | t.l = append(t.l, f)
215 | }
216 |
217 | func (t *Tracker) Leave() {
218 | for i := range t.l {
219 | t.l[i]()
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/core/sing/sing.go:
--------------------------------------------------------------------------------
1 | package sing
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "os"
8 |
9 | "github.com/sagernet/sing-box/log"
10 |
11 | "github.com/Github-Aiko/Aiko-Server/conf"
12 | vCore "github.com/Github-Aiko/Aiko-Server/core"
13 | "github.com/goccy/go-json"
14 | box "github.com/sagernet/sing-box"
15 | "github.com/sagernet/sing-box/adapter"
16 | "github.com/sagernet/sing-box/option"
17 | )
18 |
19 | var _ vCore.Core = (*Sing)(nil)
20 |
21 | type DNSConfig struct {
22 | Servers []map[string]interface{} `json:"servers"`
23 | Rules []map[string]interface{} `json:"rules"`
24 | }
25 |
26 | type Sing struct {
27 | box *box.Box
28 | ctx context.Context
29 | hookServer *HookServer
30 | router adapter.Router
31 | logFactory log.Factory
32 | inbounds map[string]adapter.Inbound
33 | }
34 |
35 | func init() {
36 | vCore.RegisterCore("sing", New)
37 | }
38 |
39 | func New(c *conf.CoreConfig) (vCore.Core, error) {
40 | options := option.Options{}
41 | if len(c.SingConfig.OriginalPath) != 0 {
42 | data, err := os.ReadFile(c.SingConfig.OriginalPath)
43 | if err != nil {
44 | return nil, fmt.Errorf("read original config error: %s", err)
45 | }
46 | err = json.Unmarshal(data, &options)
47 | if err != nil {
48 | return nil, fmt.Errorf("unmarshal original config error: %s", err)
49 | }
50 | }
51 | options.Log = &option.LogOptions{
52 | Disabled: c.SingConfig.LogConfig.Disabled,
53 | Level: c.SingConfig.LogConfig.Level,
54 | Timestamp: c.SingConfig.LogConfig.Timestamp,
55 | Output: c.SingConfig.LogConfig.Output,
56 | }
57 | options.NTP = &option.NTPOptions{
58 | Enabled: c.SingConfig.NtpConfig.Enable,
59 | WriteToSystem: true,
60 | Server: c.SingConfig.NtpConfig.Server,
61 | ServerPort: c.SingConfig.NtpConfig.ServerPort,
62 | }
63 | os.Setenv("SING_DNS_PATH", "")
64 | options.DNS = &option.DNSOptions{}
65 | if c.SingConfig.DnsConfigPath != "" {
66 | f, err := os.OpenFile(c.SingConfig.DnsConfigPath, os.O_RDWR|os.O_CREATE, 0755)
67 | if err != nil {
68 | return nil, fmt.Errorf("failed to open or create sing dns config file: %s", err)
69 | }
70 | defer f.Close()
71 | data, err := io.ReadAll(f)
72 | if err != nil {
73 | log.Warn(fmt.Sprintf(
74 | "Failed to read sing dns config from file '%v': %v. Using default DNS options",
75 | f.Name(), err))
76 | options.DNS = &option.DNSOptions{}
77 | } else {
78 | if err := json.Unmarshal(data, options.DNS); err != nil {
79 | log.Warn(fmt.Sprintf(
80 | "Failed to unmarshal sing dns config from file '%v': %v. Using default DNS options",
81 | f.Name(), err))
82 | options.DNS = &option.DNSOptions{}
83 | }
84 | }
85 | os.Setenv("SING_DNS_PATH", c.SingConfig.DnsConfigPath)
86 | }
87 | b, err := box.New(box.Options{
88 | Context: context.Background(),
89 | Options: options,
90 | })
91 | if err != nil {
92 | return nil, err
93 | }
94 | hs := NewHookServer(c.SingConfig.EnableConnClear)
95 | b.Router().SetClashServer(hs)
96 | return &Sing{
97 | ctx: b.Router().GetCtx(),
98 | box: b,
99 | hookServer: hs,
100 | router: b.Router(),
101 | logFactory: b.LogFactory(),
102 | inbounds: make(map[string]adapter.Inbound),
103 | }, nil
104 | }
105 |
106 | func (b *Sing) Start() error {
107 | return b.box.Start()
108 | }
109 |
110 | func (b *Sing) Close() error {
111 | return b.box.Close()
112 | }
113 |
114 | func (b *Sing) Protocols() []string {
115 | return []string{
116 | "vmess",
117 | "vless",
118 | "shadowsocks",
119 | "trojan",
120 | "hysteria",
121 | "hysteria2",
122 | }
123 | }
124 |
125 | func (b *Sing) Type() string {
126 | return "sing"
127 | }
128 |
--------------------------------------------------------------------------------
/core/sing/user.go:
--------------------------------------------------------------------------------
1 | package sing
2 |
3 | import (
4 | "encoding/base64"
5 | "errors"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/api/panel"
8 | "github.com/Github-Aiko/Aiko-Server/common/counter"
9 | "github.com/Github-Aiko/Aiko-Server/core"
10 | "github.com/sagernet/sing-box/inbound"
11 | "github.com/sagernet/sing-box/option"
12 | )
13 |
14 | func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
15 | switch p.NodeInfo.Type {
16 | case "vmess", "vless":
17 | if p.NodeInfo.Type == "vless" {
18 | us := make([]option.VLESSUser, len(p.Users))
19 | for i := range p.Users {
20 | us[i] = option.VLESSUser{
21 | Name: p.Users[i].Uuid,
22 | Flow: p.VAllss.Flow,
23 | UUID: p.Users[i].Uuid,
24 | }
25 | }
26 | err = b.inbounds[p.Tag].(*inbound.VLESS).AddUsers(us)
27 | } else {
28 | us := make([]option.VMessUser, len(p.Users))
29 | for i := range p.Users {
30 | us[i] = option.VMessUser{
31 | Name: p.Users[i].Uuid,
32 | UUID: p.Users[i].Uuid,
33 | }
34 | }
35 | err = b.inbounds[p.Tag].(*inbound.VMess).AddUsers(us)
36 | }
37 | case "shadowsocks":
38 | us := make([]option.ShadowsocksUser, len(p.Users))
39 | for i := range p.Users {
40 | var password = p.Users[i].Uuid
41 | switch p.Shadowsocks.Cipher {
42 | case "2022-blake3-aes-128-gcm":
43 | password = base64.StdEncoding.EncodeToString([]byte(password[:16]))
44 | case "2022-blake3-aes-256-gcm":
45 | password = base64.StdEncoding.EncodeToString([]byte(password[:32]))
46 | }
47 | us[i] = option.ShadowsocksUser{
48 | Name: p.Users[i].Uuid,
49 | Password: password,
50 | }
51 | }
52 | err = b.inbounds[p.Tag].(*inbound.ShadowsocksMulti).AddUsers(us)
53 | case "trojan":
54 | us := make([]option.TrojanUser, len(p.Users))
55 | for i := range p.Users {
56 | us[i] = option.TrojanUser{
57 | Name: p.Users[i].Uuid,
58 | Password: p.Users[i].Uuid,
59 | }
60 | }
61 | err = b.inbounds[p.Tag].(*inbound.Trojan).AddUsers(us)
62 | case "hysteria":
63 | us := make([]option.HysteriaUser, len(p.Users))
64 | for i := range p.Users {
65 | us[i] = option.HysteriaUser{
66 | Name: p.Users[i].Uuid,
67 | AuthString: p.Users[i].Uuid,
68 | }
69 | }
70 | err = b.inbounds[p.Tag].(*inbound.Hysteria).AddUsers(us)
71 | case "hysteria2":
72 | us := make([]option.Hysteria2User, len(p.Users))
73 | for i := range p.Users {
74 | us[i] = option.Hysteria2User{
75 | Name: p.Users[i].Uuid,
76 | Password: p.Users[i].Uuid,
77 | }
78 | }
79 | err = b.inbounds[p.Tag].(*inbound.Hysteria2).AddUsers(us)
80 | }
81 | if err != nil {
82 | return 0, err
83 | }
84 | return len(p.Users), err
85 | }
86 |
87 | func (b *Sing) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
88 | if v, ok := b.hookServer.counter.Load(tag); ok {
89 | c := v.(*counter.TrafficCounter)
90 | up = c.GetUpCount(uuid)
91 | down = c.GetDownCount(uuid)
92 | if reset {
93 | c.Reset(uuid)
94 | }
95 | return
96 | }
97 | return 0, 0
98 | }
99 |
100 | type UserDeleter interface {
101 | DelUsers(uuid []string) error
102 | }
103 |
104 | func (b *Sing) DelUsers(users []panel.UserInfo, tag string) error {
105 | var del UserDeleter
106 | if i, ok := b.inbounds[tag]; ok {
107 | switch i.Type() {
108 | case "vmess":
109 | del = i.(*inbound.VMess)
110 | case "vless":
111 | del = i.(*inbound.VLESS)
112 | case "shadowsocks":
113 | del = i.(*inbound.ShadowsocksMulti)
114 | case "trojan":
115 | del = i.(*inbound.Trojan)
116 | case "hysteria":
117 | del = i.(*inbound.Hysteria)
118 | case "hysteria2":
119 | del = i.(*inbound.Hysteria2)
120 | }
121 | } else {
122 | return errors.New("the inbound not found")
123 | }
124 | uuids := make([]string, len(users))
125 | for i := range users {
126 | b.hookServer.ClearConn(tag, users[i].Uuid)
127 | uuids[i] = users[i].Uuid
128 | }
129 | err := del.DelUsers(uuids)
130 | if err != nil {
131 | return err
132 | }
133 | return nil
134 | }
135 |
--------------------------------------------------------------------------------
/core/sing/utils.go:
--------------------------------------------------------------------------------
1 | package sing
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/conf"
8 | "github.com/sagernet/sing-box/option"
9 | )
10 |
11 | func processFallback(c *conf.Options, fallbackForALPN map[string]*option.ServerOptions) error {
12 | for k, v := range c.SingOptions.FallBackConfigs.FallBackForALPN {
13 | fallbackPort, err := strconv.Atoi(v.ServerPort)
14 | if err != nil {
15 | return fmt.Errorf("unable to parse fallbackForALPN server port error: %s", err)
16 | }
17 | fallbackForALPN[k] = &option.ServerOptions{Server: v.Server, ServerPort: uint16(fallbackPort)}
18 | }
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/core/xray/app/app.go:
--------------------------------------------------------------------------------
1 | // Package app contains the third-party app used to replace the default app in xray-core
2 | package app
3 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/config.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.31.0
4 | // protoc v4.23.4
5 | // source: config.proto
6 |
7 | package dispatcher
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type SessionConfig struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 | }
28 |
29 | func (x *SessionConfig) Reset() {
30 | *x = SessionConfig{}
31 | if protoimpl.UnsafeEnabled {
32 | mi := &file_config_proto_msgTypes[0]
33 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
34 | ms.StoreMessageInfo(mi)
35 | }
36 | }
37 |
38 | func (x *SessionConfig) String() string {
39 | return protoimpl.X.MessageStringOf(x)
40 | }
41 |
42 | func (*SessionConfig) ProtoMessage() {}
43 |
44 | func (x *SessionConfig) ProtoReflect() protoreflect.Message {
45 | mi := &file_config_proto_msgTypes[0]
46 | if protoimpl.UnsafeEnabled && x != nil {
47 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
48 | if ms.LoadMessageInfo() == nil {
49 | ms.StoreMessageInfo(mi)
50 | }
51 | return ms
52 | }
53 | return mi.MessageOf(x)
54 | }
55 |
56 | // Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead.
57 | func (*SessionConfig) Descriptor() ([]byte, []int) {
58 | return file_config_proto_rawDescGZIP(), []int{0}
59 | }
60 |
61 | type Config struct {
62 | state protoimpl.MessageState
63 | sizeCache protoimpl.SizeCache
64 | unknownFields protoimpl.UnknownFields
65 |
66 | Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"`
67 | }
68 |
69 | func (x *Config) Reset() {
70 | *x = Config{}
71 | if protoimpl.UnsafeEnabled {
72 | mi := &file_config_proto_msgTypes[1]
73 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
74 | ms.StoreMessageInfo(mi)
75 | }
76 | }
77 |
78 | func (x *Config) String() string {
79 | return protoimpl.X.MessageStringOf(x)
80 | }
81 |
82 | func (*Config) ProtoMessage() {}
83 |
84 | func (x *Config) ProtoReflect() protoreflect.Message {
85 | mi := &file_config_proto_msgTypes[1]
86 | if protoimpl.UnsafeEnabled && x != nil {
87 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
88 | if ms.LoadMessageInfo() == nil {
89 | ms.StoreMessageInfo(mi)
90 | }
91 | return ms
92 | }
93 | return mi.MessageOf(x)
94 | }
95 |
96 | // Deprecated: Use Config.ProtoReflect.Descriptor instead.
97 | func (*Config) Descriptor() ([]byte, []int) {
98 | return file_config_proto_rawDescGZIP(), []int{1}
99 | }
100 |
101 | func (x *Config) GetSettings() *SessionConfig {
102 | if x != nil {
103 | return x.Settings
104 | }
105 | return nil
106 | }
107 |
108 | var File_config_proto protoreflect.FileDescriptor
109 |
110 | var file_config_proto_rawDesc = []byte{
111 | 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18,
112 | 0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69,
113 | 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73,
114 | 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22,
115 | 0x4d, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x73, 0x65, 0x74,
116 | 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32,
117 | 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70,
118 | 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
119 | 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x6e,
120 | 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
121 | 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01,
122 | 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x49, 0x6e, 0x61,
123 | 0x7a, 0x75, 0x6d, 0x61, 0x56, 0x2f, 0x56, 0x32, 0x62, 0x58, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f,
124 | 0x78, 0x72, 0x61, 0x79, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63,
125 | 0x68, 0x65, 0x72, 0xaa, 0x02, 0x18, 0x56, 0x32, 0x62, 0x58, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
126 | 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x62, 0x06,
127 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
128 | }
129 |
130 | var (
131 | file_config_proto_rawDescOnce sync.Once
132 | file_config_proto_rawDescData = file_config_proto_rawDesc
133 | )
134 |
135 | func file_config_proto_rawDescGZIP() []byte {
136 | file_config_proto_rawDescOnce.Do(func() {
137 | file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
138 | })
139 | return file_config_proto_rawDescData
140 | }
141 |
142 | var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
143 | var file_config_proto_goTypes = []interface{}{
144 | (*SessionConfig)(nil), // 0: Aiko-Server.core.app.dispatcher.SessionConfig
145 | (*Config)(nil), // 1: Aiko-Server.core.app.dispatcher.Config
146 | }
147 | var file_config_proto_depIdxs = []int32{
148 | 0, // 0: Aiko-Server.core.app.dispatcher.Config.settings:type_name -> Aiko-Server.core.app.dispatcher.SessionConfig
149 | 1, // [1:1] is the sub-list for method output_type
150 | 1, // [1:1] is the sub-list for method input_type
151 | 1, // [1:1] is the sub-list for extension type_name
152 | 1, // [1:1] is the sub-list for extension extendee
153 | 0, // [0:1] is the sub-list for field type_name
154 | }
155 |
156 | func init() { file_config_proto_init() }
157 | func file_config_proto_init() {
158 | if File_config_proto != nil {
159 | return
160 | }
161 | if !protoimpl.UnsafeEnabled {
162 | file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
163 | switch v := v.(*SessionConfig); i {
164 | case 0:
165 | return &v.state
166 | case 1:
167 | return &v.sizeCache
168 | case 2:
169 | return &v.unknownFields
170 | default:
171 | return nil
172 | }
173 | }
174 | file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
175 | switch v := v.(*Config); i {
176 | case 0:
177 | return &v.state
178 | case 1:
179 | return &v.sizeCache
180 | case 2:
181 | return &v.unknownFields
182 | default:
183 | return nil
184 | }
185 | }
186 | }
187 | type x struct{}
188 | out := protoimpl.TypeBuilder{
189 | File: protoimpl.DescBuilder{
190 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
191 | RawDescriptor: file_config_proto_rawDesc,
192 | NumEnums: 0,
193 | NumMessages: 2,
194 | NumExtensions: 0,
195 | NumServices: 0,
196 | },
197 | GoTypes: file_config_proto_goTypes,
198 | DependencyIndexes: file_config_proto_depIdxs,
199 | MessageInfos: file_config_proto_msgTypes,
200 | }.Build()
201 | File_config_proto = out.File
202 | file_config_proto_rawDesc = nil
203 | file_config_proto_goTypes = nil
204 | file_config_proto_depIdxs = nil
205 | }
206 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/config.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package aiko-server.core.app.dispatcher;
4 | option csharp_namespace = "Aiko-Server.core.app.dispatcher";
5 | option go_package = "github.com/Github-Aiko/Aiko-Server/core/xray/app/dispatcher";
6 | option java_package = "com.aiko-server.core.app.dispatcher";
7 | option java_multiple_files = true;
8 |
9 | message SessionConfig {
10 | reserved 1;
11 | }
12 |
13 | message Config {
14 | SessionConfig settings = 1;
15 | }
16 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/dispatcher.go:
--------------------------------------------------------------------------------
1 | package dispatcher
2 |
3 | //go:generate go run github.com/xtls/xray-core/common/errors/errorgen
4 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/errors.generated.go:
--------------------------------------------------------------------------------
1 | package dispatcher
2 |
3 | import "github.com/xtls/xray-core/common/errors"
4 |
5 | type errPathObjHolder struct{}
6 |
7 | func newError(values ...interface{}) *errors.Error {
8 | return errors.New(values...).WithPathObj(errPathObjHolder{})
9 | }
10 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/fakednssniffer.go:
--------------------------------------------------------------------------------
1 | package dispatcher
2 |
3 | import (
4 | "context"
5 | "strings"
6 |
7 | "github.com/xtls/xray-core/common"
8 | "github.com/xtls/xray-core/common/net"
9 | "github.com/xtls/xray-core/common/session"
10 | "github.com/xtls/xray-core/core"
11 | "github.com/xtls/xray-core/features/dns"
12 | )
13 |
14 | // newFakeDNSSniffer Creates a Fake DNS metadata sniffer
15 | func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) {
16 | var fakeDNSEngine dns.FakeDNSEngine
17 | {
18 | fakeDNSEngineFeat := core.MustFromContext(ctx).GetFeature((*dns.FakeDNSEngine)(nil))
19 | if fakeDNSEngineFeat != nil {
20 | fakeDNSEngine = fakeDNSEngineFeat.(dns.FakeDNSEngine)
21 | }
22 | }
23 |
24 | if fakeDNSEngine == nil {
25 | errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
26 | return protocolSnifferWithMetadata{}, errNotInit
27 | }
28 | return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
29 | Target := session.OutboundFromContext(ctx).Target
30 | if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP {
31 | domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
32 | if domainFromFakeDNS != "" {
33 | newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx))
34 | return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
35 | }
36 | }
37 |
38 | if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
39 | ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
40 | if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
41 | inPool := fkr0.IsIPInIPPool(Target.Address)
42 | ipAddressInRangeValue.addressInRange = &inPool
43 | }
44 | }
45 |
46 | return nil, common.ErrNoClue
47 | }, metadataSniffer: true}, nil
48 | }
49 |
50 | type fakeDNSSniffResult struct {
51 | domainName string
52 | }
53 |
54 | func (fakeDNSSniffResult) Protocol() string {
55 | return "fakedns"
56 | }
57 |
58 | func (f fakeDNSSniffResult) Domain() string {
59 | return f.domainName
60 | }
61 |
62 | type fakeDNSExtraOpts int
63 |
64 | const ipAddressInRange fakeDNSExtraOpts = 1
65 |
66 | type ipAddressInRangeOpt struct {
67 | addressInRange *bool
68 | }
69 |
70 | type DNSThenOthersSniffResult struct {
71 | domainName string
72 | protocolOriginalName string
73 | }
74 |
75 | func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool {
76 | return strings.HasPrefix(protocolName, f.protocolOriginalName)
77 | }
78 |
79 | func (DNSThenOthersSniffResult) Protocol() string {
80 | return "fakedns+others"
81 | }
82 |
83 | func (f DNSThenOthersSniffResult) Domain() string {
84 | return f.domainName
85 | }
86 |
87 | func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) (
88 | protocolSnifferWithMetadata, error,
89 | ) { // nolint: unparam
90 | // ctx may be used in the future
91 | _ = ctx
92 | return protocolSnifferWithMetadata{
93 | protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
94 | ipAddressInRangeValue := &ipAddressInRangeOpt{}
95 | ctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue)
96 | result, err := fakeDNSSniffer.protocolSniffer(ctx, bytes)
97 | if err == nil {
98 | return result, nil
99 | }
100 | if ipAddressInRangeValue.addressInRange != nil {
101 | if *ipAddressInRangeValue.addressInRange {
102 | for _, v := range others {
103 | if v.metadataSniffer || bytes != nil {
104 | if result, err := v.protocolSniffer(ctx, bytes); err == nil {
105 | return DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil
106 | }
107 | }
108 | }
109 | return nil, common.ErrNoClue
110 | }
111 | newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
112 | return nil, common.ErrNoClue
113 | }
114 | newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
115 | return nil, common.ErrNoClue
116 | },
117 | metadataSniffer: false,
118 | }, nil
119 | }
120 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/sniffer.go:
--------------------------------------------------------------------------------
1 | package dispatcher
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/xtls/xray-core/common"
7 | "github.com/xtls/xray-core/common/net"
8 | "github.com/xtls/xray-core/common/protocol/bittorrent"
9 | "github.com/xtls/xray-core/common/protocol/http"
10 | "github.com/xtls/xray-core/common/protocol/quic"
11 | "github.com/xtls/xray-core/common/protocol/tls"
12 | )
13 |
14 | type SniffResult interface {
15 | Protocol() string
16 | Domain() string
17 | }
18 |
19 | type protocolSniffer func(context.Context, []byte) (SniffResult, error)
20 |
21 | type protocolSnifferWithMetadata struct {
22 | protocolSniffer protocolSniffer
23 | // A Metadata sniffer will be invoked on connection establishment only, with nil body,
24 | // for both TCP and UDP connections
25 | // It will not be shown as a traffic type for routing unless there is no other successful sniffing.
26 | metadataSniffer bool
27 | network net.Network
28 | }
29 |
30 | type Sniffer struct {
31 | sniffer []protocolSnifferWithMetadata
32 | }
33 |
34 | func NewSniffer(ctx context.Context) *Sniffer {
35 | ret := &Sniffer{
36 | sniffer: []protocolSnifferWithMetadata{
37 | {func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP},
38 | {func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
39 | {func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
40 | {func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
41 | {func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP},
42 | },
43 | }
44 | if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
45 | others := ret.sniffer
46 | ret.sniffer = append(ret.sniffer, sniffer)
47 | fakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others)
48 | if err == nil {
49 | ret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...)
50 | }
51 | }
52 | return ret
53 | }
54 |
55 | var errUnknownContent = newError("unknown content")
56 |
57 | func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
58 | var pendingSniffer []protocolSnifferWithMetadata
59 | for _, si := range s.sniffer {
60 | s := si.protocolSniffer
61 | if si.metadataSniffer || si.network != network {
62 | continue
63 | }
64 | result, err := s(c, payload)
65 | if err == common.ErrNoClue {
66 | pendingSniffer = append(pendingSniffer, si)
67 | continue
68 | }
69 |
70 | if err == nil && result != nil {
71 | return result, nil
72 | }
73 | }
74 |
75 | if len(pendingSniffer) > 0 {
76 | s.sniffer = pendingSniffer
77 | return nil, common.ErrNoClue
78 | }
79 |
80 | return nil, errUnknownContent
81 | }
82 |
83 | func (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) {
84 | var pendingSniffer []protocolSnifferWithMetadata
85 | for _, si := range s.sniffer {
86 | s := si.protocolSniffer
87 | if !si.metadataSniffer {
88 | pendingSniffer = append(pendingSniffer, si)
89 | continue
90 | }
91 | result, err := s(c, nil)
92 | if err == common.ErrNoClue {
93 | pendingSniffer = append(pendingSniffer, si)
94 | continue
95 | }
96 |
97 | if err == nil && result != nil {
98 | return result, nil
99 | }
100 | }
101 |
102 | if len(pendingSniffer) > 0 {
103 | s.sniffer = pendingSniffer
104 | return nil, common.ErrNoClue
105 | }
106 |
107 | return nil, errUnknownContent
108 | }
109 |
110 | func CompositeResult(domainResult SniffResult, protocolResult SniffResult) SniffResult {
111 | return &compositeResult{domainResult: domainResult, protocolResult: protocolResult}
112 | }
113 |
114 | type compositeResult struct {
115 | domainResult SniffResult
116 | protocolResult SniffResult
117 | }
118 |
119 | func (c compositeResult) Protocol() string {
120 | return c.protocolResult.Protocol()
121 | }
122 |
123 | func (c compositeResult) Domain() string {
124 | return c.domainResult.Domain()
125 | }
126 |
127 | func (c compositeResult) ProtocolForDomainResult() string {
128 | return c.domainResult.Protocol()
129 | }
130 |
131 | type SnifferResultComposite interface {
132 | ProtocolForDomainResult() string
133 | }
134 |
135 | type SnifferIsProtoSubsetOf interface {
136 | IsProtoSubsetOf(protocolName string) bool
137 | }
138 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/stats.go:
--------------------------------------------------------------------------------
1 | package dispatcher
2 |
3 | import (
4 | "github.com/xtls/xray-core/common"
5 | "github.com/xtls/xray-core/common/buf"
6 | "github.com/xtls/xray-core/features/stats"
7 | )
8 |
9 | type SizeStatWriter struct {
10 | Counter stats.Counter
11 | Writer buf.Writer
12 | }
13 |
14 | func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
15 | w.Counter.Add(int64(mb.Len()))
16 | return w.Writer.WriteMultiBuffer(mb)
17 | }
18 |
19 | func (w *SizeStatWriter) Close() error {
20 | return common.Close(w.Writer)
21 | }
22 |
23 | func (w *SizeStatWriter) Interrupt() {
24 | common.Interrupt(w.Writer)
25 | }
26 |
--------------------------------------------------------------------------------
/core/xray/app/dispatcher/stats_test.go:
--------------------------------------------------------------------------------
1 | package dispatcher_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/xtls/xray-core/app/dispatcher"
7 | "github.com/xtls/xray-core/common"
8 | "github.com/xtls/xray-core/common/buf"
9 | )
10 |
11 | type TestCounter int64
12 |
13 | func (c *TestCounter) Value() int64 {
14 | return int64(*c)
15 | }
16 |
17 | func (c *TestCounter) Add(v int64) int64 {
18 | x := int64(*c) + v
19 | *c = TestCounter(x)
20 | return x
21 | }
22 |
23 | func (c *TestCounter) Set(v int64) int64 {
24 | *c = TestCounter(v)
25 | return v
26 | }
27 |
28 | func TestStatsWriter(t *testing.T) {
29 | var c TestCounter
30 | writer := &SizeStatWriter{
31 | Counter: &c,
32 | Writer: buf.Discard,
33 | }
34 |
35 | mb := buf.MergeBytes(nil, []byte("abcd"))
36 | common.Must(writer.WriteMultiBuffer(mb))
37 |
38 | mb = buf.MergeBytes(nil, []byte("efg"))
39 | common.Must(writer.WriteMultiBuffer(mb))
40 |
41 | if c.Value() != 7 {
42 | t.Fatal("unexpected counter value. want 7, but got ", c.Value())
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/core/xray/distro/all/all.go:
--------------------------------------------------------------------------------
1 | package all
2 |
3 | import (
4 | // The following are necessary as they register handlers in their init functions.
5 |
6 | // Mandatory features. Can't remove unless there are replacements.
7 | _ "github.com/xtls/xray-core/app/dispatcher"
8 | _ "github.com/xtls/xray-core/app/proxyman/inbound"
9 | _ "github.com/xtls/xray-core/app/proxyman/outbound"
10 |
11 | // Default commander and all its services. This is an optional feature.
12 | //_ "github.com/xtls/xray-core/app/commander"
13 | //_ "github.com/xtls/xray-core/app/log/command"
14 | //_ "github.com/xtls/xray-core/app/proxyman/command"
15 | //_ "github.com/xtls/xray-core/app/stats/command"
16 |
17 | // Developer preview services
18 | //_ "github.com/xtls/xray-core/app/observatory/command"
19 |
20 | // Other optional features.
21 | _ "github.com/xtls/xray-core/app/dns"
22 | _ "github.com/xtls/xray-core/app/dns/fakedns"
23 | _ "github.com/xtls/xray-core/app/log"
24 | _ "github.com/xtls/xray-core/app/metrics"
25 | _ "github.com/xtls/xray-core/app/policy"
26 | _ "github.com/xtls/xray-core/app/reverse"
27 | _ "github.com/xtls/xray-core/app/router"
28 | _ "github.com/xtls/xray-core/app/stats"
29 |
30 | // Fix dependency cycle caused by core import in internet package
31 | _ "github.com/xtls/xray-core/transport/internet/tagged/taggedimpl"
32 |
33 | // Developer preview features
34 | //_ "github.com/xtls/xray-core/app/observatory"
35 |
36 | // Inbound and outbound proxies.
37 | _ "github.com/xtls/xray-core/proxy/blackhole"
38 | _ "github.com/xtls/xray-core/proxy/dns"
39 | _ "github.com/xtls/xray-core/proxy/dokodemo"
40 | _ "github.com/xtls/xray-core/proxy/freedom"
41 | _ "github.com/xtls/xray-core/proxy/http"
42 | _ "github.com/xtls/xray-core/proxy/loopback"
43 | _ "github.com/xtls/xray-core/proxy/shadowsocks"
44 | _ "github.com/xtls/xray-core/proxy/socks"
45 | _ "github.com/xtls/xray-core/proxy/trojan"
46 | _ "github.com/xtls/xray-core/proxy/vless/inbound"
47 | _ "github.com/xtls/xray-core/proxy/vless/outbound"
48 | _ "github.com/xtls/xray-core/proxy/vmess/inbound"
49 | _ "github.com/xtls/xray-core/proxy/vmess/outbound"
50 |
51 | //_ "github.com/xtls/xray-core/proxy/wireguard"
52 |
53 | // Transports
54 | //_ "github.com/xtls/xray-core/transport/internet/domainsocket"
55 | _ "github.com/xtls/xray-core/transport/internet/grpc"
56 | _ "github.com/xtls/xray-core/transport/internet/http"
57 |
58 | //_ "github.com/xtls/xray-core/transport/internet/kcp"
59 | //_ "github.com/xtls/xray-core/transport/internet/quic"
60 | _ "github.com/xtls/xray-core/transport/internet/reality"
61 | _ "github.com/xtls/xray-core/transport/internet/tcp"
62 | _ "github.com/xtls/xray-core/transport/internet/tls"
63 | _ "github.com/xtls/xray-core/transport/internet/udp"
64 | _ "github.com/xtls/xray-core/transport/internet/websocket"
65 |
66 | // Transport headers
67 | _ "github.com/xtls/xray-core/transport/internet/headers/http"
68 | _ "github.com/xtls/xray-core/transport/internet/headers/noop"
69 | _ "github.com/xtls/xray-core/transport/internet/headers/srtp"
70 | _ "github.com/xtls/xray-core/transport/internet/headers/tls"
71 | _ "github.com/xtls/xray-core/transport/internet/headers/utp"
72 | _ "github.com/xtls/xray-core/transport/internet/headers/wechat"
73 | //_ "github.com/xtls/xray-core/transport/internet/headers/wireguard"
74 | )
75 |
--------------------------------------------------------------------------------
/core/xray/dns.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "bytes"
5 | "net"
6 | "os"
7 | "strconv"
8 | "strings"
9 |
10 | "github.com/Github-Aiko/Aiko-Server/api/panel"
11 | "github.com/goccy/go-json"
12 | log "github.com/sirupsen/logrus"
13 | coreConf "github.com/xtls/xray-core/infra/conf"
14 | )
15 |
16 | func updateDNSConfig(node *panel.NodeInfo) (err error) {
17 | dnsPath := os.Getenv("XRAY_DNS_PATH")
18 | if len(node.RawDNS.DNSJson) != 0 {
19 | var prettyJSON bytes.Buffer
20 | if err := json.Indent(&prettyJSON, node.RawDNS.DNSJson, "", " "); err != nil {
21 | return err
22 | }
23 | err = saveDnsConfig(prettyJSON.Bytes(), dnsPath)
24 | } else if len(node.RawDNS.DNSMap) != 0 {
25 | dnsConfig := DNSConfig{
26 | Servers: []interface{}{
27 | "1.1.1.1",
28 | "localhost"},
29 | Tag: "dns_inbound",
30 | }
31 | for _, value := range node.RawDNS.DNSMap {
32 | address := value["address"].(string)
33 | if strings.Contains(address, ":") && !strings.Contains(address, "/") {
34 | host, port, err := net.SplitHostPort(address)
35 | if err != nil {
36 | return err
37 | }
38 | var uint16Port uint16
39 | if port, err := strconv.ParseUint(port, 10, 16); err == nil {
40 | uint16Port = uint16(port)
41 | }
42 | value["address"] = host
43 | value["port"] = uint16Port
44 | }
45 | dnsConfig.Servers = append(dnsConfig.Servers, value)
46 |
47 | }
48 | dnsConfigJSON, err := json.MarshalIndent(dnsConfig, "", " ")
49 | if err != nil {
50 | log.WithField("err", err).Error("Error marshaling dnsConfig to JSON")
51 | return err
52 | }
53 | err = saveDnsConfig(dnsConfigJSON, dnsPath)
54 | }
55 | return err
56 | }
57 |
58 | func saveDnsConfig(dns []byte, dnsPath string) (err error) {
59 | currentData, err := os.ReadFile(dnsPath)
60 | if err != nil {
61 | log.WithField("err", err).Error("Failed to read XRAY_DNS_PATH")
62 | return err
63 | }
64 | if !bytes.Equal(currentData, dns) {
65 | coreDnsConfig := &coreConf.DNSConfig{}
66 | if err = json.Unmarshal(dns, coreDnsConfig); err != nil {
67 | log.WithField("err", err).Error("Failed to unmarshal DNS config")
68 | }
69 | _, err := coreDnsConfig.Build()
70 | if err != nil {
71 | log.WithField("err", err).Error("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help")
72 | return err
73 | }
74 | if err = os.Truncate(dnsPath, 0); err != nil {
75 | log.WithField("err", err).Error("Failed to clear XRAY DNS PATH file")
76 | }
77 | if err = os.WriteFile(dnsPath, dns, 0644); err != nil {
78 | log.WithField("err", err).Error("Failed to write DNS to XRAY DNS PATH file")
79 | }
80 | }
81 | return err
82 | }
83 |
--------------------------------------------------------------------------------
/core/xray/inbound.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base64"
6 | "encoding/hex"
7 | "errors"
8 | "fmt"
9 | "time"
10 |
11 | "github.com/Github-Aiko/Aiko-Server/api/panel"
12 | "github.com/Github-Aiko/Aiko-Server/conf"
13 | "github.com/goccy/go-json"
14 | "github.com/xtls/xray-core/common/net"
15 | "github.com/xtls/xray-core/core"
16 | coreConf "github.com/xtls/xray-core/infra/conf"
17 | )
18 |
19 | // BuildInbound build Inbound config for different protocol
20 | func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
21 | in := &coreConf.InboundDetourConfig{}
22 | var err error
23 | var network string
24 | switch nodeInfo.Type {
25 | case "vmess", "vless":
26 | err = buildV2ray(option, nodeInfo, in)
27 | network = nodeInfo.VAllss.Network
28 | case "trojan":
29 | err = buildTrojan(option, in)
30 | network = "tcp"
31 | case "shadowsocks":
32 | err = buildShadowsocks(option, nodeInfo, in)
33 | network = "tcp"
34 | default:
35 | return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks", nodeInfo.Type)
36 | }
37 | if err != nil {
38 | return nil, err
39 | }
40 | // Set network protocol
41 | // Set server port
42 | in.PortList = &coreConf.PortList{
43 | Range: []coreConf.PortRange{
44 | {
45 | From: uint32(nodeInfo.Common.ServerPort),
46 | To: uint32(nodeInfo.Common.ServerPort),
47 | }},
48 | }
49 | // Set Listen IP address
50 | ipAddress := net.ParseAddress(option.ListenIP)
51 | in.ListenOn = &coreConf.Address{Address: ipAddress}
52 | // Set SniffingConfig
53 | sniffingConfig := &coreConf.SniffingConfig{
54 | Enabled: true,
55 | DestOverride: &coreConf.StringList{"http", "tls"},
56 | }
57 | if option.XrayOptions.DisableSniffing {
58 | sniffingConfig.Enabled = false
59 | }
60 | in.SniffingConfig = sniffingConfig
61 | switch network {
62 | case "tcp":
63 | if in.StreamSetting.TCPSettings != nil {
64 | in.StreamSetting.TCPSettings.AcceptProxyProtocol = option.XrayOptions.EnableProxyProtocol
65 | } else {
66 | tcpSetting := &coreConf.TCPConfig{
67 | AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
68 | } //Enable proxy protocol
69 | in.StreamSetting.TCPSettings = tcpSetting
70 | }
71 | case "ws":
72 | in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
73 | AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol} //Enable proxy protocol
74 | default:
75 | socketConfig := &coreConf.SocketConfig{
76 | AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
77 | TFO: option.XrayOptions.EnableTFO,
78 | } //Enable proxy protocol
79 | in.StreamSetting.SocketSettings = socketConfig
80 | }
81 | // Set TLS or Reality settings
82 | switch nodeInfo.Security {
83 | case panel.Tls:
84 | // Normal tls
85 | if option.CertConfig == nil {
86 | return nil, errors.New("the CertConfig is not vail")
87 | }
88 | switch option.CertConfig.CertMode {
89 | case "none", "":
90 | break // disable
91 | default:
92 | in.StreamSetting.Security = "tls"
93 | in.StreamSetting.TLSSettings = &coreConf.TLSConfig{
94 | Certs: []*coreConf.TLSCertConfig{
95 | {
96 | CertFile: option.CertConfig.CertFile,
97 | KeyFile: option.CertConfig.KeyFile,
98 | OcspStapling: 3600,
99 | },
100 | },
101 | RejectUnknownSNI: option.CertConfig.RejectUnknownSni,
102 | }
103 | }
104 | case panel.Reality:
105 | // Reality
106 | in.StreamSetting.Security = "reality"
107 | v := nodeInfo.VAllss
108 | d, err := json.Marshal(fmt.Sprintf(
109 | "%s:%s",
110 | v.TlsSettings.ServerName,
111 | v.TlsSettings.ServerPort))
112 | if err != nil {
113 | return nil, fmt.Errorf("marshal reality dest error: %s", err)
114 | }
115 | mtd, _ := time.ParseDuration(v.RealityConfig.MaxTimeDiff)
116 | in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{
117 | Dest: d,
118 | Xver: v.RealityConfig.Xver,
119 | ServerNames: []string{v.TlsSettings.ServerName},
120 | PrivateKey: v.TlsSettings.PrivateKey,
121 | MinClientVer: v.RealityConfig.MinClientVer,
122 | MaxClientVer: v.RealityConfig.MaxClientVer,
123 | MaxTimeDiff: uint64(mtd.Microseconds()),
124 | ShortIds: []string{v.TlsSettings.ShortId},
125 | }
126 | break
127 | }
128 | in.Tag = tag
129 | return in.Build()
130 | }
131 |
132 | func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
133 | v := nodeInfo.VAllss
134 | if nodeInfo.Type == "vless" {
135 | //Set vless
136 | inbound.Protocol = "vless"
137 | if config.XrayOptions.EnableFallback {
138 | // Set fallback
139 | fallbackConfigs, err := buildVlessFallbacks(config.XrayOptions.FallBackConfigs)
140 | if err != nil {
141 | return err
142 | }
143 | s, err := json.Marshal(&coreConf.VLessInboundConfig{
144 | Decryption: "none",
145 | Fallbacks: fallbackConfigs,
146 | })
147 | if err != nil {
148 | return fmt.Errorf("marshal vless fallback config error: %s", err)
149 | }
150 | inbound.Settings = (*json.RawMessage)(&s)
151 | } else {
152 | var err error
153 | s, err := json.Marshal(&coreConf.VLessInboundConfig{
154 | Decryption: "none",
155 | })
156 | if err != nil {
157 | return fmt.Errorf("marshal vless config error: %s", err)
158 | }
159 | inbound.Settings = (*json.RawMessage)(&s)
160 | }
161 | } else {
162 | // Set vmess
163 | inbound.Protocol = "vmess"
164 | var err error
165 | s, err := json.Marshal(&coreConf.VMessInboundConfig{})
166 | if err != nil {
167 | return fmt.Errorf("marshal vmess settings error: %s", err)
168 | }
169 | inbound.Settings = (*json.RawMessage)(&s)
170 | }
171 | if len(v.NetworkSettings) == 0 {
172 | return nil
173 | }
174 |
175 | t := coreConf.TransportProtocol(nodeInfo.VAllss.Network)
176 | inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
177 | switch v.Network {
178 | case "tcp":
179 | err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.TCPSettings)
180 | if err != nil {
181 | return fmt.Errorf("unmarshal tcp settings error: %s", err)
182 | }
183 | case "ws":
184 | err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.WSSettings)
185 | if err != nil {
186 | return fmt.Errorf("unmarshal ws settings error: %s", err)
187 | }
188 | case "grpc":
189 | err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.GRPCConfig)
190 | if err != nil {
191 | return fmt.Errorf("unmarshal grpc settings error: %s", err)
192 | }
193 | default:
194 | return errors.New("the network type is not vail")
195 | }
196 | return nil
197 | }
198 |
199 | func buildTrojan(config *conf.Options, inbound *coreConf.InboundDetourConfig) error {
200 | inbound.Protocol = "trojan"
201 | if config.XrayOptions.EnableFallback {
202 | // Set fallback
203 | fallbackConfigs, err := buildTrojanFallbacks(config.XrayOptions.FallBackConfigs)
204 | if err != nil {
205 | return err
206 | }
207 | s, err := json.Marshal(&coreConf.TrojanServerConfig{
208 | Fallbacks: fallbackConfigs,
209 | })
210 | inbound.Settings = (*json.RawMessage)(&s)
211 | if err != nil {
212 | return fmt.Errorf("marshal trojan fallback config error: %s", err)
213 | }
214 | } else {
215 | s := []byte("{}")
216 | inbound.Settings = (*json.RawMessage)(&s)
217 | }
218 | t := coreConf.TransportProtocol("tcp")
219 | inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
220 | return nil
221 | }
222 |
223 | func buildShadowsocks(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
224 | inbound.Protocol = "shadowsocks"
225 | s := nodeInfo.Shadowsocks
226 | settings := &coreConf.ShadowsocksServerConfig{
227 | Cipher: s.Cipher,
228 | }
229 | p := make([]byte, 32)
230 | _, err := rand.Read(p)
231 | if err != nil {
232 | return fmt.Errorf("generate random password error: %s", err)
233 | }
234 | randomPasswd := hex.EncodeToString(p)
235 | cipher := s.Cipher
236 | if s.ServerKey != "" {
237 | settings.Password = s.ServerKey
238 | randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
239 | cipher = ""
240 | }
241 | defaultSSuser := &coreConf.ShadowsocksUserConfig{
242 | Cipher: cipher,
243 | Password: randomPasswd,
244 | }
245 | settings.Users = append(settings.Users, defaultSSuser)
246 | settings.NetworkList = &coreConf.NetworkList{"tcp", "udp"}
247 | settings.IVCheck = true
248 | if config.XrayOptions.DisableIVCheck {
249 | settings.IVCheck = false
250 | }
251 | t := coreConf.TransportProtocol("tcp")
252 | inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
253 | sets, err := json.Marshal(settings)
254 | inbound.Settings = (*json.RawMessage)(&sets)
255 | if err != nil {
256 | return fmt.Errorf("marshal shadowsocks settings error: %s", err)
257 | }
258 | return nil
259 | }
260 |
261 | func buildVlessFallbacks(fallbackConfigs []conf.FallBackConfigForXray) ([]*coreConf.VLessInboundFallback, error) {
262 | if fallbackConfigs == nil {
263 | return nil, fmt.Errorf("you must provide FallBackConfigs")
264 | }
265 | vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs))
266 | for i, c := range fallbackConfigs {
267 | if c.Dest == "" {
268 | return nil, fmt.Errorf("dest is required for fallback fialed")
269 | }
270 | var dest json.RawMessage
271 | dest, err := json.Marshal(c.Dest)
272 | if err != nil {
273 | return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
274 | }
275 | vlessFallBacks[i] = &coreConf.VLessInboundFallback{
276 | Name: c.SNI,
277 | Alpn: c.Alpn,
278 | Path: c.Path,
279 | Dest: dest,
280 | Xver: c.ProxyProtocolVer,
281 | }
282 | }
283 | return vlessFallBacks, nil
284 | }
285 |
286 | func buildTrojanFallbacks(fallbackConfigs []conf.FallBackConfigForXray) ([]*coreConf.TrojanInboundFallback, error) {
287 | if fallbackConfigs == nil {
288 | return nil, fmt.Errorf("you must provide FallBackConfigs")
289 | }
290 |
291 | trojanFallBacks := make([]*coreConf.TrojanInboundFallback, len(fallbackConfigs))
292 | for i, c := range fallbackConfigs {
293 |
294 | if c.Dest == "" {
295 | return nil, fmt.Errorf("dest is required for fallback fialed")
296 | }
297 |
298 | var dest json.RawMessage
299 | dest, err := json.Marshal(c.Dest)
300 | if err != nil {
301 | return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
302 | }
303 | trojanFallBacks[i] = &coreConf.TrojanInboundFallback{
304 | Name: c.SNI,
305 | Alpn: c.Alpn,
306 | Path: c.Path,
307 | Dest: dest,
308 | Xver: c.ProxyProtocolVer,
309 | }
310 | }
311 | return trojanFallBacks, nil
312 | }
313 |
--------------------------------------------------------------------------------
/core/xray/node.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/api/panel"
8 | "github.com/Github-Aiko/Aiko-Server/conf"
9 | "github.com/xtls/xray-core/core"
10 | "github.com/xtls/xray-core/features/inbound"
11 | "github.com/xtls/xray-core/features/outbound"
12 | )
13 |
14 | type DNSConfig struct {
15 | Servers []interface{} `json:"servers"`
16 | Tag string `json:"tag"`
17 | }
18 |
19 | func (c *Xray) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error {
20 | err := updateDNSConfig(info)
21 | if err != nil {
22 | return fmt.Errorf("build dns error: %s", err)
23 | }
24 | inboundConfig, err := buildInbound(config, info, tag)
25 | if err != nil {
26 | return fmt.Errorf("build inbound error: %s", err)
27 | }
28 | err = c.addInbound(inboundConfig)
29 | if err != nil {
30 | return fmt.Errorf("add inbound error: %s", err)
31 | }
32 | outBoundConfig, err := buildOutbound(config, tag)
33 | if err != nil {
34 | return fmt.Errorf("build outbound error: %s", err)
35 | }
36 | err = c.addOutbound(outBoundConfig)
37 | if err != nil {
38 | return fmt.Errorf("add outbound error: %s", err)
39 | }
40 | return nil
41 | }
42 |
43 | func (c *Xray) addInbound(config *core.InboundHandlerConfig) error {
44 | rawHandler, err := core.CreateObject(c.Server, config)
45 | if err != nil {
46 | return err
47 | }
48 | handler, ok := rawHandler.(inbound.Handler)
49 | if !ok {
50 | return fmt.Errorf("not an InboundHandler: %s", err)
51 | }
52 | if err := c.ihm.AddHandler(context.Background(), handler); err != nil {
53 | return err
54 | }
55 | return nil
56 | }
57 |
58 | func (c *Xray) addOutbound(config *core.OutboundHandlerConfig) error {
59 | rawHandler, err := core.CreateObject(c.Server, config)
60 | if err != nil {
61 | return err
62 | }
63 | handler, ok := rawHandler.(outbound.Handler)
64 | if !ok {
65 | return fmt.Errorf("not an InboundHandler: %s", err)
66 | }
67 | if err := c.ohm.AddHandler(context.Background(), handler); err != nil {
68 | return err
69 | }
70 | return nil
71 | }
72 |
73 | func (c *Xray) DelNode(tag string) error {
74 | err := c.removeInbound(tag)
75 | if err != nil {
76 | return fmt.Errorf("remove in error: %s", err)
77 | }
78 | err = c.removeOutbound(tag)
79 | if err != nil {
80 | return fmt.Errorf("remove out error: %s", err)
81 | }
82 | return nil
83 | }
84 |
85 | func (c *Xray) removeInbound(tag string) error {
86 | return c.ihm.RemoveHandler(context.Background(), tag)
87 | }
88 |
89 | func (c *Xray) removeOutbound(tag string) error {
90 | err := c.ohm.RemoveHandler(context.Background(), tag)
91 | return err
92 | }
93 |
--------------------------------------------------------------------------------
/core/xray/outbound.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "fmt"
5 |
6 | conf2 "github.com/Github-Aiko/Aiko-Server/conf"
7 | "github.com/goccy/go-json"
8 | "github.com/xtls/xray-core/common/net"
9 | "github.com/xtls/xray-core/core"
10 | "github.com/xtls/xray-core/infra/conf"
11 | )
12 |
13 | // BuildOutbound build freedom outbund config for addoutbound
14 | func buildOutbound(config *conf2.Options, tag string) (*core.OutboundHandlerConfig, error) {
15 | outboundDetourConfig := &conf.OutboundDetourConfig{}
16 | outboundDetourConfig.Protocol = "freedom"
17 | outboundDetourConfig.Tag = tag
18 |
19 | // Build Send IP address
20 | if config.SendIP != "" {
21 | ipAddress := net.ParseAddress(config.SendIP)
22 | outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
23 | }
24 |
25 | // Freedom Protocol setting
26 | var domainStrategy = "Asis"
27 | if config.XrayOptions.EnableDNS {
28 | if config.XrayOptions.DNSType != "" {
29 | domainStrategy = config.XrayOptions.DNSType
30 | } else {
31 | domainStrategy = "UseIP"
32 | }
33 | }
34 | proxySetting := &conf.FreedomConfig{
35 | DomainStrategy: domainStrategy,
36 | }
37 | var setting json.RawMessage
38 | setting, err := json.Marshal(proxySetting)
39 | if err != nil {
40 | return nil, fmt.Errorf("marshal proxy config error: %s", err)
41 | }
42 | outboundDetourConfig.Settings = &setting
43 | return outboundDetourConfig.Build()
44 | }
45 |
--------------------------------------------------------------------------------
/core/xray/ss.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "encoding/base64"
5 | "strings"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/api/panel"
8 | "github.com/Github-Aiko/Aiko-Server/common/format"
9 | "github.com/xtls/xray-core/common/protocol"
10 | "github.com/xtls/xray-core/common/serial"
11 | "github.com/xtls/xray-core/proxy/shadowsocks"
12 | "github.com/xtls/xray-core/proxy/shadowsocks_2022"
13 | )
14 |
15 | func buildSSUsers(tag string, userInfo []panel.UserInfo, cypher string, serverKey string) (users []*protocol.User) {
16 | users = make([]*protocol.User, len(userInfo))
17 | for i := range userInfo {
18 | users[i] = buildSSUser(tag, &userInfo[i], cypher, serverKey)
19 | }
20 | return users
21 | }
22 |
23 | func buildSSUser(tag string, userInfo *panel.UserInfo, cypher string, serverKey string) (user *protocol.User) {
24 | if serverKey == "" {
25 | ssAccount := &shadowsocks.Account{
26 | Password: userInfo.Uuid,
27 | CipherType: getCipherFromString(cypher),
28 | }
29 | return &protocol.User{
30 | Level: 0,
31 | Email: format.UserTag(tag, userInfo.Uuid),
32 | Account: serial.ToTypedMessage(ssAccount),
33 | }
34 | } else {
35 | var keyLength int
36 | switch cypher {
37 | case "2022-blake3-aes-128-gcm":
38 | keyLength = 16
39 | case "2022-blake3-aes-256-gcm":
40 | keyLength = 32
41 | }
42 | ssAccount := &shadowsocks_2022.User{
43 | Key: base64.StdEncoding.EncodeToString([]byte(userInfo.Uuid[:keyLength])),
44 | }
45 | return &protocol.User{
46 | Level: 0,
47 | Email: format.UserTag(tag, userInfo.Uuid),
48 | Account: serial.ToTypedMessage(ssAccount),
49 | }
50 | }
51 | }
52 |
53 | func getCipherFromString(c string) shadowsocks.CipherType {
54 | switch strings.ToLower(c) {
55 | case "aes-128-gcm", "aead_aes_128_gcm":
56 | return shadowsocks.CipherType_AES_128_GCM
57 | case "aes-256-gcm", "aead_aes_256_gcm":
58 | return shadowsocks.CipherType_AES_256_GCM
59 | case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305":
60 | return shadowsocks.CipherType_CHACHA20_POLY1305
61 | case "none", "plain":
62 | return shadowsocks.CipherType_NONE
63 | default:
64 | return shadowsocks.CipherType_UNKNOWN
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/core/xray/trojan.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "github.com/Github-Aiko/Aiko-Server/api/panel"
5 | "github.com/Github-Aiko/Aiko-Server/common/format"
6 | "github.com/xtls/xray-core/common/protocol"
7 | "github.com/xtls/xray-core/common/serial"
8 | "github.com/xtls/xray-core/proxy/trojan"
9 | )
10 |
11 | func buildTrojanUsers(tag string, userInfo []panel.UserInfo) (users []*protocol.User) {
12 | users = make([]*protocol.User, len(userInfo))
13 | for i := range userInfo {
14 | users[i] = buildTrojanUser(tag, &(userInfo)[i])
15 | }
16 | return users
17 | }
18 |
19 | func buildTrojanUser(tag string, userInfo *panel.UserInfo) (user *protocol.User) {
20 | trojanAccount := &trojan.Account{
21 | Password: userInfo.Uuid,
22 | }
23 | return &protocol.User{
24 | Level: 0,
25 | Email: format.UserTag(tag, userInfo.Uuid),
26 | Account: serial.ToTypedMessage(trojanAccount),
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/xray/user.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/api/panel"
8 | "github.com/Github-Aiko/Aiko-Server/common/format"
9 | vCore "github.com/Github-Aiko/Aiko-Server/core"
10 | "github.com/xtls/xray-core/common/protocol"
11 | "github.com/xtls/xray-core/proxy"
12 | )
13 |
14 | func (c *Xray) GetUserManager(tag string) (proxy.UserManager, error) {
15 | handler, err := c.ihm.GetHandler(context.Background(), tag)
16 | if err != nil {
17 | return nil, fmt.Errorf("no such inbound tag: %s", err)
18 | }
19 | inboundInstance, ok := handler.(proxy.GetInbound)
20 | if !ok {
21 | return nil, fmt.Errorf("handler %s is not implement proxy.GetInbound", tag)
22 | }
23 | userManager, ok := inboundInstance.GetInbound().(proxy.UserManager)
24 | if !ok {
25 | return nil, fmt.Errorf("handler %s is not implement proxy.UserManager", tag)
26 | }
27 | return userManager, nil
28 | }
29 |
30 | func (c *Xray) DelUsers(users []panel.UserInfo, tag string) error {
31 | userManager, err := c.GetUserManager(tag)
32 | if err != nil {
33 | return fmt.Errorf("get user manager error: %s", err)
34 | }
35 | var up, down, user string
36 | for i := range users {
37 | user = format.UserTag(tag, users[i].Uuid)
38 | err = userManager.RemoveUser(context.Background(), user)
39 | if err != nil {
40 | return err
41 | }
42 | up = "user>>>" + user + ">>>traffic>>>uplink"
43 | down = "user>>>" + user + ">>>traffic>>>downlink"
44 | c.shm.UnregisterCounter(up)
45 | c.shm.UnregisterCounter(down)
46 | }
47 | return nil
48 | }
49 |
50 | func (c *Xray) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
51 | upName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>uplink"
52 | downName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>downlink"
53 | upCounter := c.shm.GetCounter(upName)
54 | downCounter := c.shm.GetCounter(downName)
55 | if reset {
56 | if upCounter != nil {
57 | up = upCounter.Set(0)
58 | }
59 | if downCounter != nil {
60 | down = downCounter.Set(0)
61 | }
62 | } else {
63 | if upCounter != nil {
64 | up = upCounter.Value()
65 | }
66 | if downCounter != nil {
67 | down = downCounter.Value()
68 | }
69 | }
70 | return up, down
71 | }
72 |
73 | func (c *Xray) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
74 | users := make([]*protocol.User, 0, len(p.Users))
75 | switch p.NodeInfo.Type {
76 | case "vmess":
77 | users = buildVmessUsers(p.Tag, p.Users)
78 | case "vless":
79 | users = buildVlessUsers(p.Tag, p.Users, p.VAllss.Flow)
80 | case "trojan":
81 | users = buildTrojanUsers(p.Tag, p.Users)
82 | case "shadowsocks":
83 | users = buildSSUsers(p.Tag,
84 | p.Users,
85 | p.Shadowsocks.Cipher,
86 | p.Shadowsocks.ServerKey)
87 | default:
88 | return 0, fmt.Errorf("unsupported node type: %s", p.NodeInfo.Type)
89 | }
90 | man, err := c.GetUserManager(p.Tag)
91 | if err != nil {
92 | return 0, fmt.Errorf("get user manager error: %s", err)
93 | }
94 | for _, u := range users {
95 | mUser, err := u.ToMemoryUser()
96 | if err != nil {
97 | return 0, err
98 | }
99 | err = man.AddUser(context.Background(), mUser)
100 | if err != nil {
101 | return 0, err
102 | }
103 | }
104 | return len(users), nil
105 | }
106 |
--------------------------------------------------------------------------------
/core/xray/vmess.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "github.com/Github-Aiko/Aiko-Server/api/panel"
5 | "github.com/Github-Aiko/Aiko-Server/common/format"
6 | "github.com/xtls/xray-core/common/protocol"
7 | "github.com/xtls/xray-core/common/serial"
8 | "github.com/xtls/xray-core/infra/conf"
9 | "github.com/xtls/xray-core/proxy/vless"
10 | )
11 |
12 | func buildVmessUsers(tag string, userInfo []panel.UserInfo) (users []*protocol.User) {
13 | users = make([]*protocol.User, len(userInfo))
14 | for i, user := range userInfo {
15 | users[i] = buildVmessUser(tag, &user)
16 | }
17 | return users
18 | }
19 |
20 | func buildVmessUser(tag string, userInfo *panel.UserInfo) (user *protocol.User) {
21 | vmessAccount := &conf.VMessAccount{
22 | ID: userInfo.Uuid,
23 | Security: "auto",
24 | }
25 | return &protocol.User{
26 | Level: 0,
27 | Email: format.UserTag(tag, userInfo.Uuid), // Uid: InboundTag|email
28 | Account: serial.ToTypedMessage(vmessAccount.Build()),
29 | }
30 | }
31 |
32 | func buildVlessUsers(tag string, userInfo []panel.UserInfo, flow string) (users []*protocol.User) {
33 | users = make([]*protocol.User, len(userInfo))
34 | for i := range userInfo {
35 | users[i] = buildVlessUser(tag, &(userInfo)[i], flow)
36 | }
37 | return users
38 | }
39 |
40 | func buildVlessUser(tag string, userInfo *panel.UserInfo, flow string) (user *protocol.User) {
41 | vlessAccount := &vless.Account{
42 | Id: userInfo.Uuid,
43 | }
44 | vlessAccount.Flow = flow
45 | return &protocol.User{
46 | Level: 0,
47 | Email: format.UserTag(tag, userInfo.Uuid),
48 | Account: serial.ToTypedMessage(vlessAccount),
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/core/xray/xray.go:
--------------------------------------------------------------------------------
1 | package xray
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "sync"
7 |
8 | "github.com/Github-Aiko/Aiko-Server/conf"
9 | vCore "github.com/Github-Aiko/Aiko-Server/core"
10 | "github.com/Github-Aiko/Aiko-Server/core/xray/app/dispatcher"
11 | _ "github.com/Github-Aiko/Aiko-Server/core/xray/distro/all"
12 | "github.com/goccy/go-json"
13 | log "github.com/sirupsen/logrus"
14 | "github.com/xtls/xray-core/app/proxyman"
15 | "github.com/xtls/xray-core/app/stats"
16 | "github.com/xtls/xray-core/common/serial"
17 | "github.com/xtls/xray-core/core"
18 | "github.com/xtls/xray-core/features/inbound"
19 | "github.com/xtls/xray-core/features/outbound"
20 | "github.com/xtls/xray-core/features/routing"
21 | statsFeature "github.com/xtls/xray-core/features/stats"
22 | coreConf "github.com/xtls/xray-core/infra/conf"
23 | )
24 |
25 | var _ vCore.Core = (*Xray)(nil)
26 |
27 | func init() {
28 | vCore.RegisterCore("xray", New)
29 | }
30 |
31 | // Xray Structure
32 | type Xray struct {
33 | access sync.Mutex
34 | Server *core.Instance
35 | ihm inbound.Manager
36 | ohm outbound.Manager
37 | shm statsFeature.Manager
38 | dispatcher *dispatcher.DefaultDispatcher
39 | }
40 |
41 | func New(c *conf.CoreConfig) (vCore.Core, error) {
42 | return &Xray{Server: getCore(c.XrayConfig)}, nil
43 | }
44 |
45 | func parseConnectionConfig(c *conf.XrayConnectionConfig) (policy *coreConf.Policy) {
46 | policy = &coreConf.Policy{
47 | StatsUserUplink: true,
48 | StatsUserDownlink: true,
49 | Handshake: &c.Handshake,
50 | ConnectionIdle: &c.ConnIdle,
51 | UplinkOnly: &c.UplinkOnly,
52 | DownlinkOnly: &c.DownlinkOnly,
53 | BufferSize: &c.BufferSize,
54 | }
55 | return
56 | }
57 |
58 | func getCore(c *conf.XrayConfig) *core.Instance {
59 | os.Setenv("XRAY_LOCATION_ASSET", c.AssetPath)
60 | // Log Config
61 | coreLogConfig := &coreConf.LogConfig{
62 | LogLevel: c.LogConfig.Level,
63 | AccessLog: c.LogConfig.AccessPath,
64 | ErrorLog: c.LogConfig.ErrorPath,
65 | }
66 | // DNS config
67 | coreDnsConfig := &coreConf.DNSConfig{}
68 | os.Setenv("XRAY_DNS_PATH", "")
69 | if c.DnsConfigPath != "" {
70 | data, err := os.ReadFile(c.DnsConfigPath)
71 | if err != nil {
72 | log.Error(fmt.Sprintf("Failed to read xray dns config file: %v", err))
73 | coreDnsConfig = &coreConf.DNSConfig{}
74 | } else {
75 | if err := json.Unmarshal(data, coreDnsConfig); err != nil {
76 | log.Error(fmt.Sprintf("Failed to unmarshal xray dns config: %v. Using default DNS options.", err))
77 | coreDnsConfig = &coreConf.DNSConfig{}
78 | }
79 | }
80 | os.Setenv("XRAY_DNS_PATH", c.DnsConfigPath)
81 | }
82 | dnsConfig, err := coreDnsConfig.Build()
83 | if err != nil {
84 | log.WithField("err", err).Panic("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help")
85 | }
86 | // Routing config
87 | coreRouterConfig := &coreConf.RouterConfig{}
88 | if c.RouteConfigPath != "" {
89 | data, err := os.ReadFile(c.RouteConfigPath)
90 | if err != nil {
91 | log.WithField("err", err).Panic("Failed to read Routing config file")
92 | } else {
93 | if err = json.Unmarshal(data, coreRouterConfig); err != nil {
94 | log.WithField("err", err).Panic("Failed to unmarshal Routing config")
95 | }
96 | }
97 | }
98 | routeConfig, err := coreRouterConfig.Build()
99 | if err != nil {
100 | log.WithField("err", err).Panic("Failed to understand Routing config. Please check: https://xtls.github.io/config/routing.html for help")
101 | }
102 | // Custom Inbound config
103 | var coreCustomInboundConfig []coreConf.InboundDetourConfig
104 | if c.InboundConfigPath != "" {
105 | data, err := os.ReadFile(c.InboundConfigPath)
106 | if err != nil {
107 | log.WithField("err", err).Panic("Failed to read Custom Inbound config file")
108 | } else {
109 | if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil {
110 | log.WithField("err", err).Panic("Failed to unmarshal Custom Inbound config")
111 | }
112 | }
113 | }
114 | var inBoundConfig []*core.InboundHandlerConfig
115 | for _, config := range coreCustomInboundConfig {
116 | oc, err := config.Build()
117 | if err != nil {
118 | log.WithField("err", err).Panic("Failed to understand Inbound config. Please check: https://xtls.github.io/config/inbound.html for help")
119 | }
120 | inBoundConfig = append(inBoundConfig, oc)
121 | }
122 | // Custom Outbound config
123 | var coreCustomOutboundConfig []coreConf.OutboundDetourConfig
124 | if c.OutboundConfigPath != "" {
125 | data, err := os.ReadFile(c.OutboundConfigPath)
126 | if err != nil {
127 | log.WithField("err", err).Panic("Failed to read Custom Outbound config file")
128 | } else {
129 | if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil {
130 | log.WithField("err", err).Panic("Failed to unmarshal Custom Outbound config")
131 | }
132 | }
133 | }
134 | var outBoundConfig []*core.OutboundHandlerConfig
135 | for _, config := range coreCustomOutboundConfig {
136 | oc, err := config.Build()
137 | if err != nil {
138 | log.WithField("err", err).Panic("Failed to understand Outbound config, Please check: https://xtls.github.io/config/outbound.html for help")
139 | }
140 | outBoundConfig = append(outBoundConfig, oc)
141 | }
142 | // Policy config
143 | levelPolicyConfig := parseConnectionConfig(c.ConnectionConfig)
144 | corePolicyConfig := &coreConf.PolicyConfig{}
145 | corePolicyConfig.Levels = map[uint32]*coreConf.Policy{0: levelPolicyConfig}
146 | policyConfig, _ := corePolicyConfig.Build()
147 | // Build Xray conf
148 | config := &core.Config{
149 | App: []*serial.TypedMessage{
150 | serial.ToTypedMessage(coreLogConfig.Build()),
151 | serial.ToTypedMessage(&dispatcher.Config{}),
152 | serial.ToTypedMessage(&stats.Config{}),
153 | serial.ToTypedMessage(&proxyman.InboundConfig{}),
154 | serial.ToTypedMessage(&proxyman.OutboundConfig{}),
155 | serial.ToTypedMessage(policyConfig),
156 | serial.ToTypedMessage(dnsConfig),
157 | serial.ToTypedMessage(routeConfig),
158 | },
159 | Inbound: inBoundConfig,
160 | Outbound: outBoundConfig,
161 | }
162 | server, err := core.New(config)
163 | if err != nil {
164 | log.WithField("err", err).Panic("failed to create instance")
165 | }
166 | log.Info("Xray Core Version: ", core.Version())
167 | return server
168 | }
169 |
170 | // Start the Xray
171 | func (c *Xray) Start() error {
172 | c.access.Lock()
173 | defer c.access.Unlock()
174 | if err := c.Server.Start(); err != nil {
175 | return err
176 | }
177 | c.shm = c.Server.GetFeature(statsFeature.ManagerType()).(statsFeature.Manager)
178 | c.ihm = c.Server.GetFeature(inbound.ManagerType()).(inbound.Manager)
179 | c.ohm = c.Server.GetFeature(outbound.ManagerType()).(outbound.Manager)
180 | c.dispatcher = c.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher)
181 | return nil
182 | }
183 |
184 | // Close the core
185 | func (c *Xray) Close() error {
186 | c.access.Lock()
187 | defer c.access.Unlock()
188 | c.ihm = nil
189 | c.ohm = nil
190 | c.shm = nil
191 | c.dispatcher = nil
192 | err := c.Server.Close()
193 | if err != nil {
194 | return err
195 | }
196 | return nil
197 | }
198 |
199 | func (c *Xray) Protocols() []string {
200 | return []string{
201 | "vmess",
202 | "vless",
203 | "shadowsocks",
204 | "trojan",
205 | }
206 | }
207 |
208 | func (c *Xray) Type() string {
209 | return "xray"
210 | }
211 |
--------------------------------------------------------------------------------
/limiter/clear.go:
--------------------------------------------------------------------------------
1 | package limiter
2 |
3 | import log "github.com/sirupsen/logrus"
4 |
5 | func ClearOnlineIP() error {
6 | log.WithField("Type", "Limiter").
7 | Debug("Clear online ip...")
8 | limitLock.RLock()
9 | for _, l := range limiter {
10 | l.ConnLimiter.ClearOnlineIP()
11 | }
12 | limitLock.RUnlock()
13 | log.WithField("Type", "Limiter").
14 | Debug("Clear online ip done")
15 | return nil
16 | }
17 |
--------------------------------------------------------------------------------
/limiter/conn.go:
--------------------------------------------------------------------------------
1 | package limiter
2 |
3 | import (
4 | "sync"
5 | "time"
6 | )
7 |
8 | type ConnLimiter struct {
9 | realtime bool
10 | ipLimit int
11 | connLimit int
12 | count sync.Map // map[string]int
13 | ip sync.Map // map[string]map[string]int
14 | }
15 |
16 | func NewConnLimiter(conn int, ip int, realtime bool) *ConnLimiter {
17 | return &ConnLimiter{
18 | realtime: realtime,
19 | connLimit: conn,
20 | ipLimit: ip,
21 | count: sync.Map{},
22 | ip: sync.Map{},
23 | }
24 | }
25 |
26 | func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool) (limit bool) {
27 | if c.connLimit != 0 {
28 | if v, ok := c.count.Load(user); ok {
29 | if v.(int) >= c.connLimit {
30 | // over connection limit
31 | return true
32 | } else if isTcp {
33 | // tcp protocol
34 | // connection count add
35 | c.count.Store(user, v.(int)+1)
36 | }
37 | } else if isTcp {
38 | // tcp protocol
39 | // store connection count
40 | c.count.Store(user, 1)
41 | }
42 | }
43 | if c.ipLimit == 0 {
44 | return false
45 | }
46 | // first user map
47 | ipMap := new(sync.Map)
48 | if c.realtime {
49 | if isTcp {
50 | ipMap.Store(ip, 2)
51 | } else {
52 | ipMap.Store(ip, 1)
53 | }
54 | } else {
55 | ipMap.Store(ip, time.Now())
56 | }
57 | // check user online ip
58 | if v, ok := c.ip.LoadOrStore(user, ipMap); ok {
59 | // have user
60 | ips := v.(*sync.Map)
61 | cn := 0
62 | if online, ok := ips.Load(ip); ok {
63 | // online ip
64 | if c.realtime {
65 | if isTcp {
66 | // tcp count add
67 | ips.Store(ip, online.(int)+2)
68 | }
69 | } else {
70 | // update connect time for not realtime
71 | ips.Store(ip, time.Now())
72 | }
73 | } else {
74 | // not online ip
75 | ips.Range(func(_, _ interface{}) bool {
76 | cn++
77 | if cn >= c.ipLimit {
78 | limit = true
79 | return false
80 | }
81 | return true
82 | })
83 | if limit {
84 | // over ip limit
85 | return
86 | }
87 | if c.realtime {
88 | if isTcp {
89 | ips.Store(ip, 2)
90 | } else {
91 | ips.Store(ip, 1)
92 | }
93 | } else {
94 | ips.Store(ip, time.Now())
95 | }
96 | }
97 | }
98 | return
99 | }
100 |
101 | // DelConnCount Delete tcp connection count, no tcp do not use
102 | func (c *ConnLimiter) DelConnCount(user string, ip string) {
103 | if !c.realtime {
104 | return
105 | }
106 | if c.connLimit != 0 {
107 | if v, ok := c.count.Load(user); ok {
108 | if v.(int) == 1 {
109 | c.count.Delete(user)
110 | } else {
111 | c.count.Store(user, v.(int)-1)
112 | }
113 | }
114 | }
115 | if c.ipLimit == 0 {
116 | return
117 | }
118 | if i, ok := c.ip.Load(user); ok {
119 | is := i.(*sync.Map)
120 | if i, ok := is.Load(ip); ok {
121 | if i.(int) == 2 {
122 | is.Delete(ip)
123 | } else {
124 | is.Store(user, i.(int)-2)
125 | }
126 | notDel := false
127 | c.ip.Range(func(_, _ any) bool {
128 | notDel = true
129 | return false
130 | })
131 | if !notDel {
132 | c.ip.Delete(user)
133 | }
134 | }
135 | }
136 | }
137 |
138 | // ClearOnlineIP Clear udp,icmp and other packet protocol online ip
139 | func (c *ConnLimiter) ClearOnlineIP() {
140 | c.ip.Range(func(u, v any) bool {
141 | userIp := v.(*sync.Map)
142 | notDel := false
143 | userIp.Range(func(ip, v any) bool {
144 | notDel = true
145 | if _, ok := v.(int); ok {
146 | if v.(int) == 1 {
147 | // clear packet ip for realtime
148 | userIp.Delete(ip)
149 | }
150 | return true
151 | } else {
152 | // clear ip for not realtime
153 | if v.(time.Time).Before(time.Now().Add(time.Minute)) {
154 | // 1 minute no active
155 | userIp.Delete(ip)
156 | }
157 | }
158 | return true
159 | })
160 | if !notDel {
161 | c.ip.Delete(u)
162 | }
163 | return true
164 | })
165 | }
166 |
--------------------------------------------------------------------------------
/limiter/conn_test.go:
--------------------------------------------------------------------------------
1 | package limiter
2 |
3 | import (
4 | "sync"
5 | "testing"
6 | "time"
7 | )
8 |
9 | var c *ConnLimiter
10 |
11 | func init() {
12 | c = NewConnLimiter(1, 1, true)
13 | }
14 |
15 | func TestConnLimiter_AddConnCount(t *testing.T) {
16 | t.Log(c.AddConnCount("1", "1", true))
17 | t.Log(c.AddConnCount("1", "2", true))
18 | }
19 |
20 | func TestConnLimiter_DelConnCount(t *testing.T) {
21 | t.Log(c.AddConnCount("1", "1", true))
22 | t.Log(c.AddConnCount("1", "2", true))
23 | c.DelConnCount("1", "1")
24 | t.Log(c.AddConnCount("1", "2", true))
25 | }
26 |
27 | func TestConnLimiter_ClearOnlineIP(t *testing.T) {
28 | t.Log(c.AddConnCount("1", "1", false))
29 | t.Log(c.AddConnCount("1", "2", false))
30 | c.ClearOnlineIP()
31 | t.Log(c.AddConnCount("1", "2", true))
32 | c.DelConnCount("1", "2")
33 | t.Log(c.AddConnCount("1", "1", false))
34 | // not realtime
35 | c.realtime = false
36 | t.Log(c.AddConnCount("3", "2", true))
37 | c.ClearOnlineIP()
38 | t.Log(c.ip.Load("3"))
39 | time.Sleep(time.Minute)
40 | c.ClearOnlineIP()
41 | t.Log(c.ip.Load("3"))
42 | }
43 |
44 | func BenchmarkConnLimiter(b *testing.B) {
45 | wg := sync.WaitGroup{}
46 | for i := 0; i < b.N; i++ {
47 | wg.Add(1)
48 | go func() {
49 | c.AddConnCount("1", "2", true)
50 | c.DelConnCount("1", "2")
51 | wg.Done()
52 | }()
53 | }
54 | wg.Wait()
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/limiter/dynamic.go:
--------------------------------------------------------------------------------
1 | package limiter
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/Github-Aiko/Aiko-Server/api/panel"
7 | "github.com/Github-Aiko/Aiko-Server/common/format"
8 | )
9 |
10 | func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserInfo, limitNum int, expire int64) error {
11 | userLimit := &UserLimitInfo{
12 | DynamicSpeedLimit: limitNum,
13 | ExpireTime: time.Now().Add(time.Duration(expire) * time.Second).Unix(),
14 | }
15 | l.UserLimitInfo.Store(format.UserTag(tag, userInfo.Uuid), userLimit)
16 | return nil
17 | }
18 |
19 | // determineSpeedLimit returns the minimum non-zero rate
20 | func determineSpeedLimit(limit1, limit2 int) (limit int) {
21 | if limit1 == 0 || limit2 == 0 {
22 | if limit1 > limit2 {
23 | return limit1
24 | } else if limit1 < limit2 {
25 | return limit2
26 | } else {
27 | return 0
28 | }
29 | } else {
30 | if limit1 > limit2 {
31 | return limit2
32 | } else if limit1 < limit2 {
33 | return limit1
34 | } else {
35 | return limit1
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/limiter/limiter.go:
--------------------------------------------------------------------------------
1 | package limiter
2 |
3 | import (
4 | "errors"
5 | "regexp"
6 | "sync"
7 | "time"
8 |
9 | "github.com/Github-Aiko/Aiko-Server/api/panel"
10 | "github.com/Github-Aiko/Aiko-Server/common/format"
11 | "github.com/Github-Aiko/Aiko-Server/conf"
12 | "github.com/juju/ratelimit"
13 | log "github.com/sirupsen/logrus"
14 | "github.com/xtls/xray-core/common/task"
15 | )
16 |
17 | var limitLock sync.RWMutex
18 | var limiter map[string]*Limiter
19 |
20 | func Init() {
21 | limiter = map[string]*Limiter{}
22 | c := task.Periodic{
23 | Interval: time.Minute * 2,
24 | Execute: ClearOnlineIP,
25 | }
26 | go func() {
27 | log.WithField("Type", "Limiter").
28 | Debug("ClearOnlineIP started")
29 | time.Sleep(time.Minute * 2)
30 | _ = c.Start()
31 | }()
32 | }
33 |
34 | type Limiter struct {
35 | DomainRules []*regexp.Regexp
36 | ProtocolRules []string
37 | SpeedLimit int
38 | UserOnlineIP *sync.Map // Key: Name, value: {Key: Ip, value: Uid}
39 | UUIDtoUID map[string]int // Key: UUID, value: UID
40 | UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
41 | ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter
42 | SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
43 | }
44 |
45 | type UserLimitInfo struct {
46 | UID int
47 | SpeedLimit int
48 | DynamicSpeedLimit int
49 | ExpireTime int64
50 | }
51 |
52 | func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limiter {
53 | info := &Limiter{
54 | SpeedLimit: l.SpeedLimit,
55 | UserOnlineIP: new(sync.Map),
56 | UserLimitInfo: new(sync.Map),
57 | ConnLimiter: NewConnLimiter(l.ConnLimit, l.IPLimit, l.EnableRealtime),
58 | SpeedLimiter: new(sync.Map),
59 | }
60 | uuidmap := make(map[string]int)
61 | for i := range users {
62 | uuidmap[users[i].Uuid] = users[i].Id
63 | if users[i].SpeedLimit != 0 {
64 | userLimit := &UserLimitInfo{
65 | UID: users[i].Id,
66 | SpeedLimit: users[i].SpeedLimit,
67 | }
68 | info.UserLimitInfo.Store(format.UserTag(tag, users[i].Uuid), userLimit)
69 | }
70 | }
71 | info.UUIDtoUID = uuidmap
72 | limitLock.Lock()
73 | limiter[tag] = info
74 | limitLock.Unlock()
75 | return info
76 | }
77 |
78 | func GetLimiter(tag string) (info *Limiter, err error) {
79 | limitLock.RLock()
80 | info, ok := limiter[tag]
81 | limitLock.RUnlock()
82 | if !ok {
83 | return nil, errors.New("not found")
84 | }
85 | return info, nil
86 | }
87 |
88 | func DeleteLimiter(tag string) {
89 | limitLock.Lock()
90 | delete(limiter, tag)
91 | limitLock.Unlock()
92 | }
93 |
94 | func (l *Limiter) UpdateUser(tag string, added []panel.UserInfo, deleted []panel.UserInfo) {
95 | for i := range deleted {
96 | l.UserLimitInfo.Delete(format.UserTag(tag, deleted[i].Uuid))
97 | delete(l.UUIDtoUID, deleted[i].Uuid)
98 | }
99 | for i := range added {
100 | if added[i].SpeedLimit != 0 {
101 | userLimit := &UserLimitInfo{
102 | UID: added[i].Id,
103 | SpeedLimit: added[i].SpeedLimit,
104 | ExpireTime: 0,
105 | }
106 | l.UserLimitInfo.Store(format.UserTag(tag, added[i].Uuid), userLimit)
107 | }
108 | l.UUIDtoUID[added[i].Uuid] = added[i].Id
109 | }
110 | }
111 |
112 | func (l *Limiter) UpdateDynamicSpeedLimit(tag, uuid string, limit int, expire time.Time) error {
113 | if v, ok := l.UserLimitInfo.Load(format.UserTag(tag, uuid)); ok {
114 | info := v.(*UserLimitInfo)
115 | info.DynamicSpeedLimit = limit
116 | info.ExpireTime = expire.Unix()
117 | } else {
118 | return errors.New("not found")
119 | }
120 | return nil
121 | }
122 |
123 | func (l *Limiter) CheckLimit(email string, ip string, isTcp bool) (Bucket *ratelimit.Bucket, Reject bool) {
124 | // ip and conn limiter
125 | if l.ConnLimiter.AddConnCount(email, ip, isTcp) {
126 | return nil, true
127 | }
128 | // check and gen speed limit Bucket
129 | nodeLimit := l.SpeedLimit
130 | userLimit := 0
131 | if v, ok := l.UserLimitInfo.Load(email); ok {
132 | u := v.(*UserLimitInfo)
133 | if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
134 | if u.SpeedLimit != 0 {
135 | userLimit = u.SpeedLimit
136 | u.DynamicSpeedLimit = 0
137 | u.ExpireTime = 0
138 | } else {
139 | l.UserLimitInfo.Delete(email)
140 | }
141 | } else {
142 | userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
143 | }
144 | }
145 |
146 | // Store online user for device limit
147 | ipMap := new(sync.Map)
148 | uid := l.UUIDtoUID[email]
149 | ipMap.Store(ip, uid)
150 | // If any device is online
151 | if v, ok := l.UserOnlineIP.LoadOrStore(email, ipMap); ok {
152 | ipMap := v.(*sync.Map)
153 | // If this is a new ip
154 | if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
155 | counter := 0
156 | ipMap.Range(func(key, value interface{}) bool {
157 | counter++
158 | return true
159 | })
160 | }
161 | }
162 |
163 | limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
164 | if limit > 0 {
165 | Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
166 | if v, ok := l.SpeedLimiter.LoadOrStore(email, Bucket); ok {
167 | return v.(*ratelimit.Bucket), false
168 | } else {
169 | l.SpeedLimiter.Store(email, Bucket)
170 | return Bucket, false
171 | }
172 | } else {
173 | return nil, false
174 | }
175 | }
176 |
177 | func (l *Limiter) GetOnlineDevice() (*[]panel.OnlineUser, error) {
178 | var onlineUser []panel.OnlineUser
179 |
180 | l.UserOnlineIP.Range(func(key, value interface{}) bool {
181 | email := key.(string)
182 | ipMap := value.(*sync.Map)
183 | ipMap.Range(func(key, value interface{}) bool {
184 | uid := value.(int)
185 | ip := key.(string)
186 | onlineUser = append(onlineUser, panel.OnlineUser{UID: uid, IP: ip})
187 | return true
188 | })
189 | l.UserOnlineIP.Delete(email) // Reset online device
190 | return true
191 | })
192 |
193 | return &onlineUser, nil
194 | }
195 |
196 | type UserIpList struct {
197 | Uid int `json:"Uid"`
198 | IpList []string `json:"Ips"`
199 | }
200 |
201 | func determineDeviceLimit(nodeLimit, userLimit int) (limit int) {
202 | if nodeLimit == 0 || userLimit == 0 {
203 | if nodeLimit > userLimit {
204 | return nodeLimit
205 | } else if nodeLimit < userLimit {
206 | return userLimit
207 | } else {
208 | return 0
209 | }
210 | } else {
211 | if nodeLimit > userLimit {
212 | return userLimit
213 | } else if nodeLimit < userLimit {
214 | return nodeLimit
215 | } else {
216 | return nodeLimit
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/limiter/rule.go:
--------------------------------------------------------------------------------
1 | package limiter
2 |
3 | import (
4 | "regexp"
5 |
6 | "github.com/Github-Aiko/Aiko-Server/api/panel"
7 | )
8 |
9 | func (l *Limiter) CheckDomainRule(destination string) (reject bool) {
10 | // have rule
11 | for i := range l.DomainRules {
12 | if l.DomainRules[i].MatchString(destination) {
13 | reject = true
14 | break
15 | }
16 | }
17 | return
18 | }
19 |
20 | func (l *Limiter) CheckProtocolRule(protocol string) (reject bool) {
21 | for i := range l.ProtocolRules {
22 | if l.ProtocolRules[i] == protocol {
23 | reject = true
24 | break
25 | }
26 | }
27 | return
28 | }
29 |
30 | func (l *Limiter) UpdateRule(rule *panel.Rules) error {
31 | l.DomainRules = make([]*regexp.Regexp, len(rule.Regexp))
32 | for i := range rule.Regexp {
33 | l.DomainRules[i] = regexp.MustCompile(rule.Regexp[i])
34 | }
35 | l.ProtocolRules = rule.Protocol
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/Github-Aiko/Aiko-Server/cmd"
4 |
5 | func main() {
6 | cmd.Run()
7 | }
8 |
--------------------------------------------------------------------------------
/node/cert.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "crypto/rand"
5 | "crypto/rsa"
6 | "crypto/x509"
7 | "crypto/x509/pkix"
8 | "encoding/pem"
9 | "fmt"
10 | "math/big"
11 | "os"
12 | "time"
13 |
14 | "github.com/Github-Aiko/Aiko-Server/common/file"
15 | log "github.com/sirupsen/logrus"
16 | )
17 |
18 | func (c *Controller) renewCertTask() error {
19 | l, err := NewLego(c.CertConfig)
20 | if err != nil {
21 | log.WithField("tag", c.tag).Info("new lego error: ", err)
22 | return nil
23 | }
24 | err = l.RenewCert()
25 | if err != nil {
26 | log.WithField("tag", c.tag).Info("renew cert error: ", err)
27 | return nil
28 | }
29 | return nil
30 | }
31 |
32 | func (c *Controller) requestCert() error {
33 | switch c.CertConfig.CertMode {
34 | case "none", "":
35 | case "file":
36 | if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
37 | return fmt.Errorf("cert file path or key file path not exist")
38 | }
39 | case "dns", "http":
40 | if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
41 | return fmt.Errorf("cert file path or key file path not exist")
42 | }
43 | if file.IsExist(c.CertConfig.CertFile) && file.IsExist(c.CertConfig.KeyFile) {
44 | return nil
45 | }
46 | l, err := NewLego(c.CertConfig)
47 | if err != nil {
48 | return fmt.Errorf("create lego object error: %s", err)
49 | }
50 | err = l.CreateCert()
51 | if err != nil {
52 | return fmt.Errorf("create lego cert error: %s", err)
53 | }
54 | case "self":
55 | if c.CertConfig.CertFile == "" || c.CertConfig.KeyFile == "" {
56 | return fmt.Errorf("cert file path or key file path not exist")
57 | }
58 | if file.IsExist(c.CertConfig.CertFile) && file.IsExist(c.CertConfig.KeyFile) {
59 | return nil
60 | }
61 | err := generateSelfSslCertificate(
62 | c.CertConfig.CertDomain,
63 | c.CertConfig.CertFile,
64 | c.CertConfig.KeyFile)
65 | if err != nil {
66 | return fmt.Errorf("generate self cert error: %s", err)
67 | }
68 | default:
69 | return fmt.Errorf("unsupported certmode: %s", c.CertConfig.CertMode)
70 | }
71 | return nil
72 | }
73 |
74 | func generateSelfSslCertificate(domain, certPath, keyPath string) error {
75 | key, _ := rsa.GenerateKey(rand.Reader, 2048)
76 | tmpl := &x509.Certificate{
77 | Version: 3,
78 | SerialNumber: big.NewInt(time.Now().Unix()),
79 | Subject: pkix.Name{
80 | CommonName: domain,
81 | },
82 | DNSNames: []string{domain},
83 | BasicConstraintsValid: true,
84 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
85 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
86 | NotBefore: time.Now(),
87 | NotAfter: time.Now().AddDate(30, 0, 0),
88 | }
89 | cert, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
90 | if err != nil {
91 | return err
92 | }
93 | f, err := os.OpenFile(certPath, os.O_CREATE|os.O_RDWR, 0644)
94 | if err != nil {
95 | return err
96 | }
97 | err = pem.Encode(f, &pem.Block{
98 | Type: "CERTIFICATE",
99 | Bytes: cert,
100 | })
101 | if err != nil {
102 | return err
103 | }
104 | f, err = os.OpenFile(keyPath, os.O_CREATE|os.O_RDWR, 0644)
105 | if err != nil {
106 | return err
107 | }
108 | err = pem.Encode(f, &pem.Block{
109 | Type: "EC PRIVATE KEY",
110 | Bytes: x509.MarshalPKCS1PrivateKey(key),
111 | })
112 | if err != nil {
113 | return err
114 | }
115 | return nil
116 | }
117 |
--------------------------------------------------------------------------------
/node/cert_test.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import "testing"
4 |
5 | func Test_generateSelfSslCertificate(t *testing.T) {
6 | t.Log(generateSelfSslCertificate("domain.com", "1.pem", "1.key"))
7 | }
8 |
--------------------------------------------------------------------------------
/node/controller.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "github.com/Github-Aiko/Aiko-Server/api/panel"
8 | "github.com/Github-Aiko/Aiko-Server/common/task"
9 | "github.com/Github-Aiko/Aiko-Server/conf"
10 | vCore "github.com/Github-Aiko/Aiko-Server/core"
11 | "github.com/Github-Aiko/Aiko-Server/limiter"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | type Controller struct {
16 | server vCore.Core
17 | apiClient *panel.Client
18 | tag string
19 | limiter *limiter.Limiter
20 | traffic map[string]int64
21 | userList []panel.UserInfo
22 | info *panel.NodeInfo
23 | nodeInfoMonitorPeriodic *task.Task
24 | userReportPeriodic *task.Task
25 | renewCertPeriodic *task.Task
26 | dynamicSpeedLimitPeriodic *task.Task
27 | onlineIpReportPeriodic *task.Task
28 | *conf.Options
29 | }
30 |
31 | // NewController return a Node controller with default parameters.
32 | func NewController(server vCore.Core, api *panel.Client, config *conf.Options) *Controller {
33 | controller := &Controller{
34 | server: server,
35 | Options: config,
36 | apiClient: api,
37 | }
38 | return controller
39 | }
40 |
41 | // Start implement the Start() function of the service interface
42 | func (c *Controller) Start() error {
43 | // First fetch Node Info
44 | var err error
45 | node, err := c.apiClient.GetNodeInfo()
46 | if err != nil {
47 | return fmt.Errorf("get node info error: %s", err)
48 | }
49 | // Update user
50 | c.userList, err = c.apiClient.GetUserList()
51 | if err != nil {
52 | return fmt.Errorf("get user list error: %s", err)
53 | }
54 | if len(c.userList) == 0 {
55 | return errors.New("add users error: not have any user")
56 | }
57 | if len(c.Options.Name) == 0 {
58 | c.tag = c.buildNodeTag(node)
59 | } else {
60 | c.tag = c.Options.Name
61 | }
62 |
63 | // add limiter
64 | l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList)
65 | // add rule limiter
66 | if err = l.UpdateRule(&node.Rules); err != nil {
67 | return fmt.Errorf("update rule error: %s", err)
68 | }
69 | c.limiter = l
70 | if node.Security == panel.Tls {
71 | err = c.requestCert()
72 | if err != nil {
73 | return fmt.Errorf("request cert error: %s", err)
74 | }
75 | }
76 | // Add new tag
77 | err = c.server.AddNode(c.tag, node, c.Options)
78 | if err != nil {
79 | return fmt.Errorf("add new node error: %s", err)
80 | }
81 | added, err := c.server.AddUsers(&vCore.AddUsersParams{
82 | Tag: c.tag,
83 | Users: c.userList,
84 | NodeInfo: node,
85 | })
86 | if err != nil {
87 | return fmt.Errorf("add users error: %s", err)
88 | }
89 | log.WithField("tag", c.tag).Infof("Added %d new users", added)
90 | c.info = node
91 | c.startTasks(node)
92 | return nil
93 | }
94 |
95 | // Close implement the Close() function of the service interface
96 | func (c *Controller) Close() error {
97 | limiter.DeleteLimiter(c.tag)
98 | if c.nodeInfoMonitorPeriodic != nil {
99 | c.nodeInfoMonitorPeriodic.Close()
100 | }
101 | if c.userReportPeriodic != nil {
102 | c.userReportPeriodic.Close()
103 | }
104 | if c.renewCertPeriodic != nil {
105 | c.renewCertPeriodic.Close()
106 | }
107 | if c.dynamicSpeedLimitPeriodic != nil {
108 | c.dynamicSpeedLimitPeriodic.Close()
109 | }
110 | if c.onlineIpReportPeriodic != nil {
111 | c.onlineIpReportPeriodic.Close()
112 | }
113 | err := c.server.DelNode(c.tag)
114 | if err != nil {
115 | return fmt.Errorf("del node error: %s", err)
116 | }
117 | return nil
118 | }
119 |
120 | func (c *Controller) buildNodeTag(node *panel.NodeInfo) string {
121 | return fmt.Sprintf("[%s]-%s:%d", c.apiClient.APIHost, node.Type, node.Id)
122 | }
123 |
--------------------------------------------------------------------------------
/node/lego.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "crypto"
5 | "crypto/ecdsa"
6 | "crypto/elliptic"
7 | "crypto/rand"
8 | "crypto/x509"
9 | "encoding/pem"
10 | "fmt"
11 | "os"
12 | "path"
13 | "strings"
14 | "time"
15 |
16 | "github.com/go-acme/lego/v4/certificate"
17 | "github.com/go-acme/lego/v4/challenge/http01"
18 | "github.com/go-acme/lego/v4/providers/dns"
19 | "github.com/go-acme/lego/v4/registration"
20 | "github.com/goccy/go-json"
21 |
22 | "github.com/Github-Aiko/Aiko-Server/common/file"
23 | "github.com/Github-Aiko/Aiko-Server/conf"
24 | "github.com/go-acme/lego/v4/certcrypto"
25 | "github.com/go-acme/lego/v4/lego"
26 | )
27 |
28 | type Lego struct {
29 | client *lego.Client
30 | config *conf.CertConfig
31 | }
32 |
33 | func NewLego(config *conf.CertConfig) (*Lego, error) {
34 | user, err := NewLegoUser(path.Join(path.Dir(config.CertFile),
35 | "user",
36 | fmt.Sprintf("user-%s.json", config.Email)),
37 | config.Email)
38 | if err != nil {
39 | return nil, fmt.Errorf("create user error: %s", err)
40 | }
41 | c := lego.NewConfig(user)
42 | //c.CADirURL = "http://192.168.99.100:4000/directory"
43 | c.Certificate.KeyType = certcrypto.RSA2048
44 | client, err := lego.NewClient(c)
45 | if err != nil {
46 | return nil, err
47 | }
48 | l := Lego{
49 | client: client,
50 | config: config,
51 | }
52 | err = l.SetProvider()
53 | if err != nil {
54 | return nil, fmt.Errorf("set provider error: %s", err)
55 | }
56 | return &l, nil
57 | }
58 |
59 | func checkPath(p string) error {
60 | if !file.IsExist(path.Dir(p)) {
61 | err := os.MkdirAll(path.Dir(p), 0755)
62 | if err != nil {
63 | return fmt.Errorf("create dir error: %s", err)
64 | }
65 | }
66 | return nil
67 | }
68 |
69 | func (l *Lego) SetProvider() error {
70 | switch l.config.CertMode {
71 | case "http":
72 | err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80"))
73 | if err != nil {
74 | return err
75 | }
76 | case "dns":
77 | for k, v := range l.config.DNSEnv {
78 | os.Setenv(k, v)
79 | }
80 | p, err := dns.NewDNSChallengeProviderByName(l.config.Provider)
81 | if err != nil {
82 | return fmt.Errorf("create dns challenge provider error: %s", err)
83 | }
84 | err = l.client.Challenge.SetDNS01Provider(p)
85 | if err != nil {
86 | return fmt.Errorf("set dns provider error: %s", err)
87 | }
88 | }
89 | return nil
90 | }
91 |
92 | func (l *Lego) CreateCert() (err error) {
93 | request := certificate.ObtainRequest{
94 | Domains: []string{l.config.CertDomain},
95 | Bundle: true,
96 | }
97 | certificates, err := l.client.Certificate.Obtain(request)
98 | if err != nil {
99 | return fmt.Errorf("obtain certificate error: %s", err)
100 | }
101 | err = l.writeCert(certificates)
102 | if err != nil {
103 | return fmt.Errorf("write certificate error: %s", err)
104 | }
105 | return nil
106 | }
107 |
108 | func (l *Lego) RenewCert() error {
109 | file, err := os.ReadFile(l.config.CertFile)
110 | if err != nil {
111 | return fmt.Errorf("read cert file error: %s", err)
112 | }
113 | if e, err := l.CheckCert(file); !e {
114 | return nil
115 | } else if err != nil {
116 | return fmt.Errorf("check cert error: %s", err)
117 | }
118 | res, err := l.client.Certificate.Renew(certificate.Resource{
119 | Domain: l.config.CertDomain,
120 | Certificate: file,
121 | }, true, false, "")
122 | if err != nil {
123 | return err
124 | }
125 | err = l.writeCert(res)
126 | if err != nil {
127 | return fmt.Errorf("write certificate error: %s", err)
128 | }
129 | return nil
130 | }
131 |
132 | func (l *Lego) CheckCert(file []byte) (bool, error) {
133 | cert, err := certcrypto.ParsePEMCertificate(file)
134 | if err != nil {
135 | return false, err
136 | }
137 | notAfter := int(time.Until(cert.NotAfter).Hours() / 24.0)
138 | if notAfter > 30 {
139 | return false, nil
140 | }
141 | return true, nil
142 | }
143 | func (l *Lego) parseParams(path string) string {
144 | r := strings.NewReplacer("{domain}", l.config.CertDomain,
145 | "{email}", l.config.Email)
146 | return r.Replace(path)
147 | }
148 | func (l *Lego) writeCert(certificates *certificate.Resource) error {
149 | err := checkPath(l.config.CertFile)
150 | if err != nil {
151 | return fmt.Errorf("check path error: %s", err)
152 | }
153 | err = os.WriteFile(l.parseParams(l.config.CertFile), certificates.Certificate, 0644)
154 | if err != nil {
155 | return err
156 | }
157 | err = checkPath(l.config.KeyFile)
158 | if err != nil {
159 | return fmt.Errorf("check path error: %s", err)
160 | }
161 | err = os.WriteFile(l.parseParams(l.config.KeyFile), certificates.PrivateKey, 0644)
162 | if err != nil {
163 | return err
164 | }
165 | return nil
166 | }
167 |
168 | type User struct {
169 | Email string `json:"Email"`
170 | Registration *registration.Resource `json:"Registration"`
171 | key crypto.PrivateKey
172 | KeyEncoded string `json:"Key"`
173 | }
174 |
175 | func (u *User) GetEmail() string {
176 | return u.Email
177 | }
178 | func (u *User) GetRegistration() *registration.Resource {
179 | return u.Registration
180 | }
181 | func (u *User) GetPrivateKey() crypto.PrivateKey {
182 | return u.key
183 | }
184 |
185 | func NewLegoUser(path string, email string) (*User, error) {
186 | var user User
187 | if file.IsExist(path) {
188 | err := user.Load(path)
189 | if err != nil {
190 | return nil, err
191 | }
192 | if user.Email != email {
193 | user.Registration = nil
194 | user.Email = email
195 | err := registerUser(&user, path)
196 | if err != nil {
197 | return nil, err
198 | }
199 | }
200 | } else {
201 | user.Email = email
202 | err := registerUser(&user, path)
203 | if err != nil {
204 | return nil, err
205 | }
206 | }
207 | return &user, nil
208 | }
209 |
210 | func registerUser(user *User, path string) error {
211 | privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
212 | if err != nil {
213 | return fmt.Errorf("generate key error: %s", err)
214 | }
215 | user.key = privateKey
216 | c := lego.NewConfig(user)
217 | client, err := lego.NewClient(c)
218 | if err != nil {
219 | return fmt.Errorf("create lego client error: %s", err)
220 | }
221 | reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
222 | if err != nil {
223 | return err
224 | }
225 | user.Registration = reg
226 | err = user.Save(path)
227 | if err != nil {
228 | return fmt.Errorf("save user error: %s", err)
229 | }
230 | return nil
231 | }
232 |
233 | func EncodePrivate(privKey *ecdsa.PrivateKey) (string, error) {
234 | encoded, err := x509.MarshalECPrivateKey(privKey)
235 | if err != nil {
236 | return "", err
237 | }
238 | pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: encoded})
239 | return string(pemEncoded), nil
240 | }
241 | func (u *User) Save(path string) error {
242 | err := checkPath(path)
243 | if err != nil {
244 | return fmt.Errorf("check path error: %s", err)
245 | }
246 | u.KeyEncoded, _ = EncodePrivate(u.key.(*ecdsa.PrivateKey))
247 | f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
248 | if err != nil {
249 | return err
250 | }
251 | err = json.NewEncoder(f).Encode(u)
252 | if err != nil {
253 | return fmt.Errorf("marshal json error: %s", err)
254 | }
255 | u.KeyEncoded = ""
256 | return nil
257 | }
258 |
259 | func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey, error) {
260 | blockPriv, _ := pem.Decode([]byte(pemEncodedPriv))
261 | x509EncodedPriv := blockPriv.Bytes
262 | privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv)
263 | return privateKey, err
264 | }
265 |
266 | func (u *User) Load(path string) error {
267 | data, err := os.ReadFile(path)
268 | if err != nil {
269 | return fmt.Errorf("open file error: %s", err)
270 | }
271 |
272 | err = json.Unmarshal(data, u)
273 | if err != nil {
274 | return fmt.Errorf("unmarshal json error: %s", err)
275 | }
276 | u.key, err = u.DecodePrivate(u.KeyEncoded)
277 | if err != nil {
278 | return fmt.Errorf("decode private key error: %s", err)
279 | }
280 | return nil
281 | }
282 |
--------------------------------------------------------------------------------
/node/lego_test.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "log"
5 | "os"
6 | "testing"
7 |
8 | "github.com/Github-Aiko/Aiko-Server/conf"
9 | )
10 |
11 | var l *Lego
12 |
13 | func init() {
14 | var err error
15 | l, err = NewLego(&conf.CertConfig{
16 | CertMode: "dns",
17 | Email: "test@test.com",
18 | CertDomain: "test.test.com",
19 | Provider: "cloudflare",
20 | DNSEnv: map[string]string{
21 | "CF_DNS_API_TOKEN": "123",
22 | },
23 | CertFile: "./cert/1.pem",
24 | KeyFile: "./cert/1.key",
25 | })
26 | if err != nil {
27 | log.Println(err)
28 | os.Exit(1)
29 | }
30 | }
31 |
32 | func TestLego_CreateCertByDns(t *testing.T) {
33 | err := l.CreateCert()
34 | if err != nil {
35 | t.Error(err)
36 | }
37 | }
38 |
39 | func TestLego_RenewCert(t *testing.T) {
40 | log.Println(l.RenewCert())
41 | }
42 |
--------------------------------------------------------------------------------
/node/node.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/Github-Aiko/Aiko-Server/api/panel"
7 | "github.com/Github-Aiko/Aiko-Server/conf"
8 | vCore "github.com/Github-Aiko/Aiko-Server/core"
9 | )
10 |
11 | type Node struct {
12 | controllers []*Controller
13 | }
14 |
15 | func New() *Node {
16 | return &Node{}
17 | }
18 |
19 | func (n *Node) Start(nodes []conf.NodeConfig, core vCore.Core) error {
20 | n.controllers = make([]*Controller, len(nodes))
21 | for i := range nodes {
22 | p, err := panel.New(&nodes[i].ApiConfig)
23 | if err != nil {
24 | return err
25 | }
26 | // Register controller service
27 | n.controllers[i] = NewController(core, p, &nodes[i].Options)
28 | err = n.controllers[i].Start()
29 | if err != nil {
30 | return fmt.Errorf("start node controller [%s-%s-%d] error: %s",
31 | nodes[i].ApiConfig.APIHost,
32 | nodes[i].ApiConfig.NodeType,
33 | nodes[i].ApiConfig.NodeID,
34 | err)
35 | }
36 | }
37 | return nil
38 | }
39 |
40 | func (n *Node) Close() {
41 | for _, c := range n.controllers {
42 | err := c.Close()
43 | if err != nil {
44 | panic(err)
45 | }
46 | }
47 | n.controllers = nil
48 | }
49 |
--------------------------------------------------------------------------------
/node/task.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/Github-Aiko/Aiko-Server/api/panel"
7 | "github.com/Github-Aiko/Aiko-Server/common/task"
8 | vCore "github.com/Github-Aiko/Aiko-Server/core"
9 | "github.com/Github-Aiko/Aiko-Server/limiter"
10 | log "github.com/sirupsen/logrus"
11 | )
12 |
13 | func (c *Controller) startTasks(node *panel.NodeInfo) {
14 | // fetch node info task
15 | c.nodeInfoMonitorPeriodic = &task.Task{
16 | Interval: node.PullInterval,
17 | Execute: c.nodeInfoMonitor,
18 | }
19 | // fetch user list task
20 | c.userReportPeriodic = &task.Task{
21 | Interval: node.PushInterval,
22 | Execute: c.reportUserTrafficTask,
23 | }
24 | log.WithField("tag", c.tag).Info("Start monitor node status")
25 | // delay to start nodeInfoMonitor
26 | _ = c.nodeInfoMonitorPeriodic.Start(false)
27 | log.WithField("tag", c.tag).Info("Start report node status")
28 | _ = c.userReportPeriodic.Start(false)
29 | if node.Security == panel.Tls {
30 | switch c.CertConfig.CertMode {
31 | case "none", "", "file", "self":
32 | default:
33 | c.renewCertPeriodic = &task.Task{
34 | Interval: time.Hour * 24,
35 | Execute: c.renewCertTask,
36 | }
37 | log.WithField("tag", c.tag).Info("Start renew cert")
38 | // delay to start renewCert
39 | _ = c.renewCertPeriodic.Start(true)
40 | }
41 | }
42 | if c.LimitConfig.EnableDynamicSpeedLimit {
43 | c.traffic = make(map[string]int64)
44 | c.dynamicSpeedLimitPeriodic = &task.Task{
45 | Interval: time.Duration(c.LimitConfig.DynamicSpeedLimitConfig.Periodic) * time.Second,
46 | Execute: c.SpeedChecker,
47 | }
48 | log.Printf("[%s: %d] Start dynamic speed limit", c.apiClient.NodeType, c.apiClient.NodeId)
49 | }
50 | }
51 |
52 | func (c *Controller) nodeInfoMonitor() (err error) {
53 | // get node info
54 | newN, err := c.apiClient.GetNodeInfo()
55 | if err != nil {
56 | log.WithFields(log.Fields{
57 | "tag": c.tag,
58 | "err": err,
59 | }).Error("Get node info failed")
60 | return nil
61 | }
62 | // get user info
63 | newU, err := c.apiClient.GetUserList()
64 | if err != nil {
65 | log.WithFields(log.Fields{
66 | "tag": c.tag,
67 | "err": err,
68 | }).Error("Get user list failed")
69 | return nil
70 | }
71 | if newN != nil {
72 | c.info = newN
73 | // nodeInfo changed
74 | if newU != nil {
75 | c.userList = newU
76 | }
77 | c.traffic = make(map[string]int64)
78 | // Remove old node
79 | log.WithField("tag", c.tag).Info("Node changed, reload")
80 | err = c.server.DelNode(c.tag)
81 | if err != nil {
82 | log.WithFields(log.Fields{
83 | "tag": c.tag,
84 | "err": err,
85 | }).Error("Delete node failed")
86 | return nil
87 | }
88 |
89 | // Update limiter
90 | if len(c.Options.Name) == 0 {
91 | c.tag = c.buildNodeTag(newN)
92 | // Remove Old limiter
93 | limiter.DeleteLimiter(c.tag)
94 | // Add new Limiter
95 | l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList)
96 | c.limiter = l
97 | }
98 | // Update rule
99 | err = c.limiter.UpdateRule(&newN.Rules)
100 | if err != nil {
101 | log.WithFields(log.Fields{
102 | "tag": c.tag,
103 | "err": err,
104 | }).Error("Update Rule failed")
105 | return nil
106 | }
107 |
108 | // check cert
109 | if newN.Security == panel.Tls {
110 | err = c.requestCert()
111 | if err != nil {
112 | log.WithFields(log.Fields{
113 | "tag": c.tag,
114 | "err": err,
115 | }).Error("Request cert failed")
116 | return nil
117 | }
118 | }
119 | // add new node
120 | err = c.server.AddNode(c.tag, newN, c.Options)
121 | if err != nil {
122 | log.WithFields(log.Fields{
123 | "tag": c.tag,
124 | "err": err,
125 | }).Error("Add node failed")
126 | return nil
127 | }
128 | _, err = c.server.AddUsers(&vCore.AddUsersParams{
129 | Tag: c.tag,
130 | Users: c.userList,
131 | NodeInfo: newN,
132 | })
133 | if err != nil {
134 | log.WithFields(log.Fields{
135 | "tag": c.tag,
136 | "err": err,
137 | }).Error("Add users failed")
138 | return nil
139 | }
140 | // Check interval
141 | if c.nodeInfoMonitorPeriodic.Interval != newN.PullInterval &&
142 | newN.PullInterval != 0 {
143 | c.nodeInfoMonitorPeriodic.Interval = newN.PullInterval
144 | c.nodeInfoMonitorPeriodic.Close()
145 | _ = c.nodeInfoMonitorPeriodic.Start(false)
146 | }
147 | if c.userReportPeriodic.Interval != newN.PushInterval &&
148 | newN.PushInterval != 0 {
149 | c.userReportPeriodic.Interval = newN.PullInterval
150 | c.userReportPeriodic.Close()
151 | _ = c.userReportPeriodic.Start(false)
152 | }
153 | log.WithField("tag", c.tag).Infof("Added %d new users", len(c.userList))
154 | // exit
155 | return nil
156 | }
157 |
158 | // node no changed, check users
159 | if len(newU) == 0 {
160 | return nil
161 | }
162 | deleted, added := compareUserList(c.userList, newU)
163 | if len(deleted) > 0 {
164 | // have deleted users
165 | err = c.server.DelUsers(deleted, c.tag)
166 | if err != nil {
167 | log.WithFields(log.Fields{
168 | "tag": c.tag,
169 | "err": err,
170 | }).Error("Delete users failed")
171 | return nil
172 | }
173 | }
174 | if len(added) > 0 {
175 | // have added users
176 | _, err = c.server.AddUsers(&vCore.AddUsersParams{
177 | Tag: c.tag,
178 | NodeInfo: c.info,
179 | Users: added,
180 | })
181 | if err != nil {
182 | log.WithFields(log.Fields{
183 | "tag": c.tag,
184 | "err": err,
185 | }).Error("Add users failed")
186 | return nil
187 | }
188 | }
189 | if len(added) > 0 || len(deleted) > 0 {
190 | // update Limiter
191 | c.limiter.UpdateUser(c.tag, added, deleted)
192 | if err != nil {
193 | log.WithFields(log.Fields{
194 | "tag": c.tag,
195 | "err": err,
196 | }).Error("limiter users failed")
197 | return nil
198 | }
199 | // clear traffic record
200 | if c.LimitConfig.EnableDynamicSpeedLimit {
201 | for i := range deleted {
202 | delete(c.traffic, deleted[i].Uuid)
203 | }
204 | }
205 | }
206 | c.userList = newU
207 | if len(added)+len(deleted) != 0 {
208 | log.WithField("tag", c.tag).
209 | Infof("%d user deleted, %d user added", len(deleted), len(added))
210 | }
211 | return nil
212 | }
213 |
214 | func (c *Controller) SpeedChecker() error {
215 | for u, t := range c.traffic {
216 | if t >= c.LimitConfig.DynamicSpeedLimitConfig.Traffic {
217 | err := c.limiter.UpdateDynamicSpeedLimit(c.tag, u,
218 | c.LimitConfig.DynamicSpeedLimitConfig.SpeedLimit,
219 | time.Now().Add(time.Duration(c.LimitConfig.DynamicSpeedLimitConfig.ExpireTime)*time.Minute))
220 | log.WithField("err", err).Error("Update dynamic speed limit failed")
221 | delete(c.traffic, u)
222 | }
223 | }
224 | return nil
225 | }
226 |
--------------------------------------------------------------------------------
/node/user.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/Github-Aiko/Aiko-Server/api/panel"
7 | log "github.com/sirupsen/logrus"
8 | )
9 |
10 | func (c *Controller) reportUserTrafficTask() (err error) {
11 | // Get User traffic
12 | userTraffic := make([]panel.UserTraffic, 0)
13 | for i := range c.userList {
14 | up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true)
15 | if up > 0 || down > 0 {
16 | if c.LimitConfig.EnableDynamicSpeedLimit {
17 | if _, ok := c.traffic[c.userList[i].Uuid]; ok {
18 | c.traffic[c.userList[i].Uuid] += up + down
19 | } else {
20 | c.traffic[c.userList[i].Uuid] = up + down
21 | }
22 | }
23 | userTraffic = append(userTraffic, panel.UserTraffic{
24 | UID: (c.userList)[i].Id,
25 | Upload: up,
26 | Download: down})
27 | }
28 | }
29 | if len(userTraffic) > 0 {
30 | err = c.apiClient.ReportUserTraffic(userTraffic)
31 | if err != nil {
32 | log.WithFields(log.Fields{
33 | "tag": c.tag,
34 | "err": err,
35 | }).Info("Report user traffic failed")
36 | } else {
37 | log.WithField("tag", c.tag).Infof("Report %d users traffic", len(userTraffic))
38 | }
39 | }
40 |
41 | if onlineDevice, err := c.limiter.GetOnlineDevice(); err != nil {
42 | log.Print(err)
43 | } else if len(*onlineDevice) > 0 {
44 | // Only report user has traffic > 100kb to allow ping test
45 | var result []panel.OnlineUser
46 | var nocountUID = make(map[int]struct{})
47 | for _, traffic := range userTraffic {
48 | total := traffic.Upload + traffic.Download
49 | if total < int64(c.Options.DeviceOnlineMinTraffic*1000) {
50 | nocountUID[traffic.UID] = struct{}{}
51 | }
52 | }
53 | for _, online := range *onlineDevice {
54 | if _, ok := nocountUID[online.UID]; !ok {
55 | result = append(result, online)
56 | }
57 | }
58 | reportOnline := make(map[int]int)
59 | data := make(map[int][]string)
60 | for _, onlineuser := range result {
61 | data[onlineuser.UID] = append(data[onlineuser.UID], onlineuser.IP)
62 | if _, ok := reportOnline[onlineuser.UID]; ok {
63 | reportOnline[onlineuser.UID]++
64 | } else {
65 | reportOnline[onlineuser.UID] = 1
66 | }
67 | }
68 | if err = c.apiClient.ReportNodeOnlineUsers(&data, &reportOnline); err != nil {
69 | log.WithFields(log.Fields{
70 | "tag": c.tag,
71 | "err": err,
72 | }).Info("Report online users failed")
73 | } else {
74 | log.WithField("tag", c.tag).Infof("Total %d online users, %d Reported", len(*onlineDevice), len(result))
75 | }
76 | }
77 |
78 | userTraffic = nil
79 | return nil
80 | }
81 |
82 | func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo) {
83 | oldMap := make(map[string]int)
84 | for i, user := range old {
85 | key := user.Uuid + strconv.Itoa(user.SpeedLimit)
86 | oldMap[key] = i
87 | }
88 |
89 | for _, user := range new {
90 | key := user.Uuid + strconv.Itoa(user.SpeedLimit)
91 | if _, exists := oldMap[key]; !exists {
92 | added = append(added, user)
93 | } else {
94 | delete(oldMap, key)
95 | }
96 | }
97 |
98 | for _, index := range oldMap {
99 | deleted = append(deleted, old[index])
100 | }
101 |
102 | return deleted, added
103 | }
104 |
--------------------------------------------------------------------------------
/release/aiko.json:
--------------------------------------------------------------------------------
1 | {
2 | "Log": {
3 | "Level": "info",
4 | "Output": ""
5 | },
6 | "Cores": [
7 | {
8 | "Type": "sing",
9 | "Log": {
10 | "Level": "error",
11 | "Timestamp": true
12 | },
13 | "NTP": {
14 | "Enable": true,
15 | "Server": "time.apple.com",
16 | "ServerPort": 0
17 | }
18 | }
19 | ],
20 | "Nodes": [
21 | {
22 | "Core": "sing",
23 | "ApiHost": "http://127.0.0.1",
24 | "ApiKey": "test",
25 | "NodeID": 33,
26 | "NodeType": "shadowsocks",
27 | "Timeout": 30,
28 | "ListenIP": "0.0.0.0",
29 | "SendIP": "0.0.0.0",
30 | "EnableProxyProtocol": false,
31 | "EnableDNS": true,
32 | "DomainStrategy": "ipv4_only",
33 | "LimitConfig": {
34 | "EnableRealtime": false,
35 | "SpeedLimit": 0,
36 | "IPLimit": 0,
37 | "ConnLimit": 0,
38 | "EnableDynamicSpeedLimit": false,
39 | "DynamicSpeedLimitConfig": {
40 | "Periodic": 60,
41 | "Traffic": 1000,
42 | "SpeedLimit": 100,
43 | "ExpireTime": 60
44 | }
45 | }
46 | }
47 | ]
48 | }
--------------------------------------------------------------------------------
/release/custom_inbound.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "listen": "0.0.0.0",
4 | "port": 1234,
5 | "protocol": "socks",
6 | "settings": {
7 | "auth": "noauth",
8 | "accounts": [
9 | {
10 | "user": "my-username",
11 | "pass": "my-password"
12 | }
13 | ],
14 | "udp": false,
15 | "ip": "127.0.0.1",
16 | "userLevel": 0
17 | }
18 | }
19 | ]
--------------------------------------------------------------------------------
/release/custom_outbound.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "tag": "IPv4_out",
4 | "protocol": "freedom",
5 | "settings": {}
6 | },
7 | {
8 | "tag": "IPv6_out",
9 | "protocol": "freedom",
10 | "settings": {
11 | "domainStrategy": "UseIPv6"
12 | }
13 | },
14 | {
15 | "tag": "socks5-warp",
16 | "protocol": "socks",
17 | "settings": {
18 | "servers": [{
19 | "address": "127.0.0.1",
20 | "port": 40000
21 | }]
22 | }
23 | },
24 | {
25 | "protocol": "blackhole",
26 | "tag": "block"
27 | }
28 | ]
--------------------------------------------------------------------------------
/release/dns.json:
--------------------------------------------------------------------------------
1 | {
2 | "servers": [
3 | "1.1.1.1",
4 | "8.8.8.8",
5 | "localhost"
6 | ],
7 | "tag": "dns_inbound"
8 | }
--------------------------------------------------------------------------------
/release/geoip.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Github-Aiko/Aiko-Server/17580837ffc52ee4681d5b652b7e2e93876b6ded/release/geoip.dat
--------------------------------------------------------------------------------
/release/geoip.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Github-Aiko/Aiko-Server/17580837ffc52ee4681d5b652b7e2e93876b6ded/release/geoip.db
--------------------------------------------------------------------------------
/release/geosite.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Github-Aiko/Aiko-Server/17580837ffc52ee4681d5b652b7e2e93876b6ded/release/geosite.db
--------------------------------------------------------------------------------
/release/route.json:
--------------------------------------------------------------------------------
1 | {
2 | "domainStrategy": "IPOnDemand",
3 | "rules": [
4 | {
5 | "type": "field",
6 | "outboundTag": "block",
7 | "ip": [
8 | "geoip:private"
9 | ]
10 | },
11 | {
12 | "type": "field",
13 | "outboundTag": "block",
14 | "protocol": [
15 | "bittorrent"
16 | ]
17 | },
18 | {
19 | "type": "field",
20 | "outboundTag": "socks5-warp",
21 | "domain": [""]
22 | },
23 | {
24 | "type": "field",
25 | "outboundTag": "IPv6_out",
26 | "domain": [
27 | "geosite:netflix"
28 | ]
29 | },
30 | {
31 | "type": "field",
32 | "outboundTag": "IPv4_out",
33 | "network": "udp,tcp"
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/test_data/1.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAyryUpSI01T4jgPywpt4CIaf+MNgPn9DxHtJmuo1VB7Ysk13Z
3 | uOBVfS9HJoikbrcVeqENkR8Bq1vdgMLbHmlxrSTwe/pynmRvWx+L1hKscxEmR6uV
4 | jgqWsPkoUAKVJlHBfjw6oFEQzmgwDOfyS90TVr4gwDo/GCLX1iX5jC+KrteuAMOp
5 | V6Yiu1FAhd0TeUDPMKs4wbyE8qx3+Mn6FqGorIwyefCX9m9QCz1N/5Ut6I5uT5Vr
6 | 28Ox1Q++mZ0s5ZuFpMlzKvrfav7fxt31rgSUro7/NTNuIydHvwMcZYeVm+DT+Vfp
7 | wRZW9uoB4jiwLDxnfMOMLd72yGId+9EJ6HpOIQIDAQABAoIBAHhV/xUVfK6mN4S0
8 | eFZTqIg5otNzK7L83mIhGQDaKwJsy4CdUEJARf4MNftVV+Svn3wuZFMjSGZiHNP0
9 | 1QL0K5lON8AfJDGIA+DelK34X4vdPg+EdTzeZBufiKIVJlqcZHF9Zn8KHyOlDABd
10 | HKCTFIuERwRSjmjRJbPizoC7J2Ina15pi+35T3x3oye4V5kQlJH3WxlMOhp0Z+iO
11 | qGlqJITWeOqxXaLfPIoOYcJ2LBPzvgv4IRVbmhi4mxphfPTEAlGnvITauDa9fx7S
12 | REBAFyt5NyU9M7dusmlLiTIHryOensBzCQOwbMseeUS0Z1rIfbusqwBM3X5oPcx8
13 | jOP1QxECgYEA+qCU4Q84hDBhC2SLfH4D1QyOixeho5re6E0NchSposIhrxLdRDDk
14 | 04GGNTKUflgfoskaI9loe84MkTqqrjxguRWVXf5qPMAtfpXr2GYiPq+VhiEeo7ZG
15 | f1rHYqK7Ity9jzy7z/CpTLekveUT93I7/yF5iEOUYQgnIZhDg+Hr0ysCgYEAzxUu
16 | DmL7GceRauoD9w3tT2nsa23hXc+jo1QWV5I7veZyosXnz8gxpax4SgwZ/iCH8pDJ
17 | RagWTHQHO3X6DdkovzPEWYiLtLCE2APlfClzkZif5tv7kO4B3BuyuDlNleuCH894
18 | 5WlieyCHcPtXqG/60tXxVrlNrevI+ccsYRb+reMCgYBgu8AaybQnmUCrlAgeacjy
19 | 3yDZYKqbqfflM3BAGueKkWFM4HwUiMaZOAHj4Hzd8wdq3jG/qncgadwB5eHg1B8E
20 | 8Oaw27SHdClbFWRtJqaLCVwt4/SefYjiONiCIosWHprvgSKAVMQTf0IPpS46sJWl
21 | mHb++A56ERqBZfKRIY7S9wKBgCKf+flx12ZyFgB4bH1MmNdkcKFt1/bllwjiMHIo
22 | A1E3TQema6I0aQi4k8xdxaLWMaT/TIgXGNNjuynYCh1yp/uAXl5SFHn74dp0nFRs
23 | YeSATow9UAzlnu38u59OBYkBvdovyJkjS9ImmD7t57RENP43w4iqpzBjclFBWkxJ
24 | mf/dAoGBAOZa8+FmFyx2VmgwR4qPrNKg6EuhO6OgDJ8NvRnx4Mu5/0L1JoYoM0Yc
25 | 2NMKh14fqmDB/CvCbMGxCj86WF8534t+eBqhY6/AUkSolh8139KNmYHNB0ynqDYn
26 | QDm/HJ4cux2rkLUCKlbGBnS9M+PSJA/MJeh0JTIrSOu4bH7aanVl
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test_data/1.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIID2zCCAsOgAwIBAgIRAJP0pRlp9k2eiBLK2a2B7LYwDQYJKoZIhvcNAQELBQAw
3 | XjELMAkGA1UEBhMCQ04xDjAMBgNVBAoTBU15U1NMMSswKQYDVQQLEyJNeVNTTCBU
4 | ZXN0IFJTQSAtIEZvciB0ZXN0IHVzZSBvbmx5MRIwEAYDVQQDEwlNeVNTTC5jb20w
5 | HhcNMjMwNjA4MTQxOTQzWhcNMjgwNjA2MTQxOTQzWjAiMQswCQYDVQQGEwJDTjET
6 | MBEGA1UEAxMKMTE0NTE0LmdheTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
7 | ggEBAMq8lKUiNNU+I4D8sKbeAiGn/jDYD5/Q8R7SZrqNVQe2LJNd2bjgVX0vRyaI
8 | pG63FXqhDZEfAatb3YDC2x5pca0k8Hv6cp5kb1sfi9YSrHMRJkerlY4KlrD5KFAC
9 | lSZRwX48OqBREM5oMAzn8kvdE1a+IMA6Pxgi19Yl+Ywviq7XrgDDqVemIrtRQIXd
10 | E3lAzzCrOMG8hPKsd/jJ+hahqKyMMnnwl/ZvUAs9Tf+VLeiObk+Va9vDsdUPvpmd
11 | LOWbhaTJcyr632r+38bd9a4ElK6O/zUzbiMnR78DHGWHlZvg0/lX6cEWVvbqAeI4
12 | sCw8Z3zDjC3e9shiHfvRCeh6TiECAwEAAaOBzzCBzDAOBgNVHQ8BAf8EBAMCBaAw
13 | HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFCiBJgXR
14 | NBo/wXMPu5PPFRw/A79/MGMGCCsGAQUFBwEBBFcwVTAhBggrBgEFBQcwAYYVaHR0
15 | cDovL29jc3AubXlzc2wuY29tMDAGCCsGAQUFBzAChiRodHRwOi8vY2EubXlzc2wu
16 | Y29tL215c3NsdGVzdHJzYS5jcnQwFQYDVR0RBA4wDIIKMTE0NTE0LmdheTANBgkq
17 | hkiG9w0BAQsFAAOCAQEAdlvVKnB1OHojoHfgPKUVTk5+OXk9X/q++wkkrGa1rQs0
18 | bPikKdc1TQoW/ylpX9wN3rwLLYGf/Hs9SqHr4RkAhAb6v+K1O5HUMqJARONdB7j0
19 | /1BuKd0wx5pIqJhs6qRf+grsOGj9EdTfKXqElCljAES0t+2ZbZ2666XftwSjybtF
20 | yKg+9iS9PX5VA1SIsa7XSVTlJ8oXy91KuCm07UxnSEKovpzV4TIlxPgKOfWBUYh9
21 | JfOhwh1rpUy6tqNjFyJdGHxBJsf7HLcO9VJe/RD55c54ovZaTT24Cy/II5DKiQ26
22 | TUeMj1BMedu5Ou0YCH7W9QhH40fvwi/hSQrjysQAoA==
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------