├── .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://img.shields.io/github/downloads/Github-Aiko/Aiko-Server/total.svg?style=flat-square)](https://github.com/Github-Aiko/Aiko-Server/releases) 10 | [![](https://img.shields.io/github/v/release/Github-Aiko/Aiko-Server?style=flat-square)](https://github.com/Github-Aiko/Aiko-Server/releases) 11 | [![docker](https://img.shields.io/docker/v/aikocute/aiko-server?label=Docker%20image&sort=semver)](https://hub.docker.com/r/aikocute/aiko-server) 12 | [![Go-Report](https://goreportcard.com/badge/github.com/Github-Aiko/Aiko-Server?style=flat-square)](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 | [![Stargazers over time](https://starchart.cc/Github-Aiko/Aiko-Server.svg)](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 | --------------------------------------------------------------------------------