Showing preview only (343K chars total). Download the full file or copy to clipboard to get everything.
Repository: Yuzuki616/V2bX
Branch: master
Commit: b54e9041dfab
Files: 110
Total size: 315.0 KB
Directory structure:
gitextract_ebfl6u56/
├── .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/
│ ├── iprecoder/
│ │ ├── interface.go
│ │ ├── recorder.go
│ │ ├── redis.go
│ │ └── redis_test.go
│ └── panel/
│ ├── node.go
│ ├── node_test.go
│ ├── panel.go
│ ├── user.go
│ └── utils.go
├── cmd/
│ ├── action_linux.go
│ ├── cmd.go
│ ├── common.go
│ ├── common_test.go
│ ├── install_linux.go
│ ├── server.go
│ ├── server_test.go
│ ├── synctime.go
│ ├── version.go
│ ├── x25519.go
│ └── x25519_test.go
├── common/
│ ├── crypt/
│ │ ├── aes.go
│ │ └── x25519.go
│ ├── exec/
│ │ └── exec.go
│ ├── file/
│ │ └── file.go
│ ├── format/
│ │ └── user.go
│ ├── rate/
│ │ └── rate.go
│ ├── systime/
│ │ ├── time_stub.go
│ │ ├── time_unix.go
│ │ └── time_windows.go
│ └── task/
│ ├── task.go
│ └── task_test.go
├── conf/
│ ├── conf.go
│ ├── conf_test.go
│ ├── core.go
│ ├── log.go
│ ├── node.go
│ ├── old.go
│ └── watch.go
├── core/
│ ├── core.go
│ ├── hy/
│ │ ├── config.go
│ │ ├── counter.go
│ │ ├── counter_test.go
│ │ ├── hy.go
│ │ ├── ipmasker.go
│ │ ├── kploader.go
│ │ ├── mmdb.go
│ │ ├── node.go
│ │ ├── resolver.go
│ │ ├── server.go
│ │ ├── server_test.go
│ │ └── user.go
│ ├── imports/
│ │ ├── hy.go
│ │ ├── imports.go
│ │ └── xray.go
│ ├── interface.go
│ ├── selector.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
│ ├── inbound.go
│ ├── node.go
│ ├── outbound.go
│ ├── ss.go
│ ├── trojan.go
│ ├── user.go
│ ├── vmess.go
│ └── xray.go
├── example/
│ ├── config.yml.example
│ ├── custom_inbound.json
│ ├── custom_outbound.json
│ ├── dns.json
│ ├── route.json
│ └── rulelist
├── go.mod
├── go.sum
├── limiter/
│ ├── clear.go
│ ├── conn.go
│ ├── conn_test.go
│ ├── dynamic.go
│ ├── limiter.go
│ └── rule.go
├── main.go
├── node/
│ ├── cert.go
│ ├── controller.go
│ ├── lego/
│ │ ├── cert.go
│ │ ├── lego.go
│ │ ├── lego_test.go
│ │ └── user.go
│ ├── node.go
│ ├── task.go
│ └── user.go
└── test_data/
├── 1.key
└── 1.pem
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: "Bug 反馈"
about: 创建一个报告以帮助我们修复并改进XrayR
title: ''
labels: awaiting reply, bug
assignees: ''
---
**描述该错误**
简单地描述一下这个bug是什么
**复现**
复现该bug的步骤
**环境和版本**
- 系统 [例如:Debian 11]
- 架构 [例如:AMD64]
- 面板 [例如:V2board]
- 协议 [例如:vmess]
- 版本 [例如:0.8.2.2]
- 部署方式 [例如:一键脚本]
**日志和错误**
请使用`xrayr log`查看并添加日志,以帮助解释你的问题
**额外的内容**
在这里添加关于问题的任何其他内容
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: "功能建议"
about: 给XrayR提出建议,让我们做得更好
title: ''
labels: awaiting reply, feature-request
assignees: ''
---
**描述您想要的功能**
清晰简洁的功能描述。
**描述您考虑过的替代方案**
是否有任何替代方案可以解决这个问题?
**附加上下文**
在此处添加有关功能请求的任何其他上下文或截图。
================================================
FILE: .github/build/friendly-filenames.json
================================================
{
"android-arm64": { "friendlyName": "android-arm64-v8a" },
"darwin-amd64": { "friendlyName": "macos-64" },
"darwin-arm64": { "friendlyName": "macos-arm64-v8a" },
"dragonfly-amd64": { "friendlyName": "dragonfly-64" },
"freebsd-386": { "friendlyName": "freebsd-32" },
"freebsd-amd64": { "friendlyName": "freebsd-64" },
"freebsd-arm64": { "friendlyName": "freebsd-arm64-v8a" },
"freebsd-arm7": { "friendlyName": "freebsd-arm32-v7a" },
"linux-386": { "friendlyName": "linux-32" },
"linux-amd64": { "friendlyName": "linux-64" },
"linux-arm5": { "friendlyName": "linux-arm32-v5" },
"linux-arm64": { "friendlyName": "linux-arm64-v8a" },
"linux-arm6": { "friendlyName": "linux-arm32-v6" },
"linux-arm7": { "friendlyName": "linux-arm32-v7a" },
"linux-mips64le": { "friendlyName": "linux-mips64le" },
"linux-mips64": { "friendlyName": "linux-mips64" },
"linux-mipslesoftfloat": { "friendlyName": "linux-mips32le-softfloat" },
"linux-mipsle": { "friendlyName": "linux-mips32le" },
"linux-mipssoftfloat": { "friendlyName": "linux-mips32-softfloat" },
"linux-mips": { "friendlyName": "linux-mips32" },
"linux-ppc64le": { "friendlyName": "linux-ppc64le" },
"linux-ppc64": { "friendlyName": "linux-ppc64" },
"linux-riscv64": { "friendlyName": "linux-riscv64" },
"linux-s390x": { "friendlyName": "linux-s390x" },
"openbsd-386": { "friendlyName": "openbsd-32" },
"openbsd-amd64": { "friendlyName": "openbsd-64" },
"openbsd-arm64": { "friendlyName": "openbsd-arm64-v8a" },
"openbsd-arm7": { "friendlyName": "openbsd-arm32-v7a" },
"windows-386": { "friendlyName": "windows-32" },
"windows-amd64": { "friendlyName": "windows-64" },
"windows-arm7": { "friendlyName": "windows-arm32-v7a" }
}
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '43 22 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
================================================
FILE: .github/workflows/release.yml
================================================
name: Build and Release
on:
workflow_dispatch:
push:
branches:
- master
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
pull_request:
types: [opened, synchronize, reopened]
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
release:
types: [published]
jobs:
build:
strategy:
matrix:
# Include amd64 on all platforms.
goos: [windows, freebsd, linux, dragonfly, darwin]
goarch: [amd64, 386]
exclude:
# Exclude i386 on darwin and dragonfly.
- goarch: 386
goos: dragonfly
- goarch: 386
goos: darwin
include:
# BEIGIN MacOS ARM64
- goos: darwin
goarch: arm64
# END MacOS ARM64
# BEGIN Linux ARM 5 6 7
- goos: linux
goarch: arm
goarm: 7
- goos: linux
goarch: arm
goarm: 6
- goos: linux
goarch: arm
goarm: 5
# END Linux ARM 5 6 7
# BEGIN Android ARM 8
- goos: android
goarch: arm64
# END Android ARM 8
# BEGIN Other architectures
# BEGIN riscv64 & ARM64
- goos: linux
goarch: arm64
- goos: linux
goarch: riscv64
# END riscv64 & ARM64
# BEGIN MIPS
- goos: linux
goarch: mips64
- goos: linux
goarch: mips64le
- goos: linux
goarch: mipsle
- goos: linux
goarch: mips
# END MIPS
# BEGIN PPC
- goos: linux
goarch: ppc64
- goos: linux
goarch: ppc64le
# END PPC
# BEGIN FreeBSD ARM
- goos: freebsd
goarch: arm64
- goos: freebsd
goarch: arm
goarm: 7
# END FreeBSD ARM
# BEGIN S390X
- goos: linux
goarch: s390x
# END S390X
# END Other architectures
fail-fast: false
runs-on: ubuntu-latest
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v3
- name: Show workflow information
id: get_filename
run: |
export _NAME=$(jq ".[\"$GOOS-$GOARCH$GOARM$GOMIPS\"].friendlyName" -r < .github/build/friendly-filenames.json)
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, GOMIPS: $GOMIPS, RELEASE_NAME: $_NAME"
echo "::set-output name=ASSET_NAME::$_NAME"
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
- name: Get project dependencies
run: go mod download
- name: Get release version
if: ${{ github.event_name == 'release' }}
run: |
echo "version=$(echo $GITHUB_REF | cut -d / -f 3)" >> $GITHUB_ENV
- name: Get other version
if: ${{ github.event_name != 'release' }}
run: |
echo "version=${{ github.sha }}" >> $GITHUB_ENV
- name: Build V2bX
run: |
echo "version: $version"
mkdir -p build_assets
go build -v -o build_assets/V2bX -tags "xray hy" -trimpath -ldflags "-X 'github.com/Yuzuki616/V2bX/cmd.version=$version' -s -w -buildid="
- name: Build Mips softfloat V2bX
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
run: |
echo "version: $version"
GOMIPS=softfloat go build -v -o build_assets/V2bX_softfloat -tags "xray hy" -trimpath -ldflags "-X 'github.com/Yuzuki616/V2bX/cmd.version=$version' -s -w -buildid="
- name: Rename Windows V2bX
if: matrix.goos == 'windows'
run: |
cd ./build_assets || exit 1
mv V2bX V2bX.exe
- name: Prepare to release
run: |
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
cp ${GITHUB_WORKSPACE}/example/dns.json ./build_assets/dns.json
cp ${GITHUB_WORKSPACE}/example/route.json ./build_assets/route.json
cp ${GITHUB_WORKSPACE}/example/custom_outbound.json ./build_assets/custom_outbound.json
cp ${GITHUB_WORKSPACE}/example/custom_inbound.json ./build_assets/custom_inbound.json
cp ${GITHUB_WORKSPACE}/example/rulelist ./build_assets/rulelist
cp ${GITHUB_WORKSPACE}/example/config.yml.example ./build_assets/config.yml
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
for i in "${LIST[@]}"
do
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
FILE_NAME="${INFO[2]}.dat"
echo -e "Downloading ${FILE_NAME}..."
curl -L "https://github.com/v2fly/${INFO[0]}/releases/latest/download/${INFO[1]}.dat" -o ./build_assets/${FILE_NAME}
echo -e "Verifying HASH key..."
HASH="$(curl -sL "https://github.com/v2fly/${INFO[0]}/releases/latest/download/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
[ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
done
- name: Create ZIP archive
shell: bash
run: |
pushd build_assets || exit 1
touch -mt $(date +%Y01010000) *
zip -9vr ../V2bX-$ASSET_NAME.zip .
popd || exit 1
FILE=./V2bX-$ASSET_NAME.zip
DGST=$FILE.dgst
for METHOD in {"md5","sha1","sha256","sha512"}
do
openssl dgst -$METHOD $FILE | sed 's/([^)]*)//g' >>$DGST
done
- name: Change the name
run: |
mv build_assets V2bX-$ASSET_NAME
- name: Upload files to Artifacts
uses: actions/upload-artifact@v3
with:
name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}
path: |
./V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}/*
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v2
if: github.event_name == 'release'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}.zip*
tag: ${{ github.ref }}
file_glob: true
================================================
FILE: .gitignore
================================================
example/config.yml
example/main
example/XrayR
example/XrayR*
example/mytest
example/access.logo
example/error.log
api/chooseparser.go.bak
app/Inboundbuilder/.lego/
app/legocmd/.lego/
.vscode/launch.json
example/.lego
example/cert
./vscode
.idea/*
================================================
FILE: LICENSE
================================================
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
================================================
FILE: README.md
================================================
# V2bX
**项目转移至[V2bX](https://github.com/InazumaV/V2bX),此仓库不再维护**
[](https://t.me/YuzukiProjects)
A V2board node server based on multi core, modified from XrayR.
一个基于多种内核的V2board节点服务端,修改自XrayR,支持V2ay,Trojan,Shadowsocks协议。
**注意:1.1.0将更换为V2board1.7.0之后新增的Api,原Api将被移除,请1.7.0之前的用户使用1.1.0之前的版本。**
## 特点
* 永久开源且免费。
* 支持V2ray,Trojan, Shadowsocks多种协议。
* 支持Vless和XTLS等新特性。
* 支持单实例对接多节点,无需重复启动。
* 支持限制在线IP。
* 支持限制Tcp连接数。
* 支持节点端口级别、用户级别限速。
* 配置简单明了。
* 修改配置自动重启实例。
* 支持多种内核,易扩展。
* 支持条件编译,可仅编译需要的内核。
## 功能介绍
| 功能 | v2ray | trojan | shadowsocks | hysteria |
|-----------|-------|--------|-------------|----------|
| 自动申请tls证书 | √ | √ | √ | √ |
| 自动续签tls证书 | √ | √ | √ | √ |
| 在线人数统计 | √ | √ | √ | √ |
| 审计规则 | √ | √ | √ | |
| 自定义DNS | √ | √ | √ | √ |
| 在线IP数限制 | √ | √ | √ | √ |
| 连接数限制 | √ | √ | √ | |
| 跨节点IP数限制 | √ | √ | √ | |
| 按照用户限速 | √ | √ | √ | |
| 动态限速(未测试) | √ | √ | √ | |
## TODO
- [ ] 重新实现动态限速
- [ ] 重新实现在线IP同步(跨节点在线IP限制)
- [x] 集成基本操作Command(Start, Stop, Restart, Status, Uninstall)
- [ ] 完善Hysteria内核支持
- [ ] 完善使用文档
- [ ] 尽可能统一日志输出格式
## 软件安装
### 一键安装
```
wget -N https://raw.githubusercontents.com/Yuzuki616/V2bX-script/master/install.sh && bash install.sh
```
### 手动安装
[手动安装教程(过时待更新)](https://yuzuki-1.gitbook.io/v2bx-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
## 构建
``` bash
# 通过-tag选项指定要编译的内核, 可选 xray, hy
go build -o V2bX -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD} -tags "xray hy"
```
## 配置文件及详细使用教程
[详细使用教程](https://yuzuki-1.gitbook.io/v2bx-doc/)
## 免责声明
* 此项目用于本人自用,因此本人不能保证向后兼容性。
* 由于本人能力有限,不能保证所有功能的可用性,如果出现问题请在Issues反馈。
* 本人不对任何人使用本项目造成的任何后果承担责任。
* 本人比较多变,因此本项目可能会随想法或思路的变动随性更改项目结构或大规模重构代码,若不能接受请勿使用。
## Thanks
* [Project X](https://github.com/XTLS/)
* [V2Fly](https://github.com/v2fly)
* [VNet-V2ray](https://github.com/ProxyPanel/VNet-V2ray)
* [Air-Universe](https://github.com/crossfw/Air-Universe)
* [XrayR](https://github.com/XrayR/XrayR)
## Stars 增长记录
[](https://starchart.cc/Yuzuki616/V2bX)
================================================
FILE: api/iprecoder/interface.go
================================================
package iprecoder
import (
"github.com/Yuzuki616/V2bX/limiter"
)
type IpRecorder interface {
SyncOnlineIp(Ips []limiter.UserIpList) ([]limiter.UserIpList, error)
}
================================================
FILE: api/iprecoder/recorder.go
================================================
package iprecoder
import (
"errors"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/go-resty/resty/v2"
"github.com/goccy/go-json"
"time"
)
type Recorder struct {
client *resty.Client
*conf.RecorderConfig
}
func NewRecorder(c *conf.RecorderConfig) *Recorder {
return &Recorder{
client: resty.New().SetTimeout(time.Duration(c.Timeout) * time.Second),
RecorderConfig: c,
}
}
func (r *Recorder) SyncOnlineIp(ips []limiter.UserIpList) ([]limiter.UserIpList, error) {
rsp, err := r.client.R().
SetBody(ips).
Post(r.Url + "/api/v1/SyncOnlineIp?token=" + r.Token)
if err != nil {
return nil, err
}
if rsp.StatusCode() != 200 {
return nil, errors.New(rsp.String())
}
ips = []limiter.UserIpList{}
err = json.Unmarshal(rsp.Body(), &ips)
if err != nil {
return nil, err
}
return ips, nil
}
================================================
FILE: api/iprecoder/redis.go
================================================
package iprecoder
import (
"context"
"fmt"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/go-redis/redis/v8"
"strconv"
"time"
)
type Redis struct {
*conf.RedisConfig
client *redis.Client
}
func NewRedis(c *conf.RedisConfig) *Redis {
return &Redis{
RedisConfig: c,
client: redis.NewClient(&redis.Options{
Addr: c.Address,
Password: c.Password,
DB: c.Db,
}),
}
}
func (r *Redis) SyncOnlineIp(Ips []limiter.UserIpList) ([]limiter.UserIpList, error) {
ctx := context.Background()
for i := range Ips {
err := r.client.SAdd(ctx, "UserList", Ips[i].Uid).Err()
if err != nil {
return nil, fmt.Errorf("add user failed: %s", err)
}
r.client.Expire(ctx, "UserList", time.Second*time.Duration(r.Expiry))
for _, ip := range Ips[i].IpList {
err := r.client.SAdd(ctx, strconv.Itoa(Ips[i].Uid), ip).Err()
if err != nil {
return nil, fmt.Errorf("add ip failed: %s", err)
}
r.client.Expire(ctx, strconv.Itoa(Ips[i].Uid), time.Second*time.Duration(r.Expiry))
}
}
c := r.client.SMembers(ctx, "UserList")
if c.Err() != nil {
return nil, fmt.Errorf("get user list failed: %s", c.Err())
}
Ips = make([]limiter.UserIpList, 0, len(c.Val()))
for _, uid := range c.Val() {
uidInt, err := strconv.Atoi(uid)
if err != nil {
return nil, fmt.Errorf("convert uid failed: %s", err)
}
ips := r.client.SMembers(ctx, uid)
if ips.Err() != nil {
return nil, fmt.Errorf("get ip list failed: %s", ips.Err())
}
Ips = append(Ips, limiter.UserIpList{
Uid: uidInt,
IpList: ips.Val(),
})
}
return Ips, nil
}
================================================
FILE: api/iprecoder/redis_test.go
================================================
package iprecoder
import (
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/limiter"
"log"
"testing"
)
func TestRedis_SyncOnlineIp(t *testing.T) {
r := NewRedis(&conf.RedisConfig{
Address: "127.0.0.1:6379",
Password: "",
Db: 0,
})
users, err := r.SyncOnlineIp([]limiter.UserIpList{
{1, []string{"5.5.5.5", "4.4.4.4"}},
})
if err != nil {
t.Fatal(err)
}
log.Println(users)
}
================================================
FILE: api/panel/node.go
================================================
package panel
import (
"bytes"
"encoding/base64"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
"github.com/Yuzuki616/V2bX/common/crypt"
"github.com/goccy/go-json"
log "github.com/sirupsen/logrus"
coreConf "github.com/xtls/xray-core/infra/conf"
)
type CommonNodeRsp struct {
Host string `json:"host"`
ServerPort int `json:"server_port"`
ServerName string `json:"server_name"`
Routes []Route `json:"routes"`
BaseConfig BaseConfig `json:"base_config"`
}
type Route struct {
Id int `json:"id"`
Match interface{} `json:"match"`
Action string `json:"action"`
//ActionValue interface{} `json:"action_value"`
}
type BaseConfig struct {
PushInterval any `json:"push_interval"`
PullInterval any `json:"pull_interval"`
}
type V2rayNodeRsp struct {
Tls int `json:"tls"`
Network string `json:"network"`
NetworkSettings json.RawMessage `json:"networkSettings"`
ServerName string `json:"server_name"`
}
type ShadowsocksNodeRsp struct {
Cipher string `json:"cipher"`
ServerKey string `json:"server_key"`
}
type HysteriaNodeRsp struct {
UpMbps int `json:"up_mbps"`
DownMbps int `json:"down_mbps"`
Obfs string `json:"obfs"`
}
type NodeInfo struct {
Id int
Type string
Rules Rules
Host string
Port int
Network string
ExtraConfig V2rayExtraConfig
NetworkSettings json.RawMessage
Tls bool
ServerName string
UpMbps int
DownMbps int
ServerKey string
Cipher string
HyObfs string
PushInterval time.Duration
PullInterval time.Duration
}
type Rules struct {
Regexp []string
Protocol []string
}
type V2rayExtraConfig struct {
EnableVless string `json:"EnableVless"`
VlessFlow string `json:"VlessFlow"`
EnableReality string `json:"EnableReality"`
RealityConfig *RealityConfig `json:"RealityConfig"`
}
type RealityConfig struct {
Dest interface{} `yaml:"Dest" json:"Dest"`
Xver string `yaml:"Xver" json:"Xver"`
ServerNames []string `yaml:"ServerNames" json:"ServerNames"`
PrivateKey string `yaml:"PrivateKey" json:"PrivateKey"`
MinClientVer string `yaml:"MinClientVer" json:"MinClientVer"`
MaxClientVer string `yaml:"MaxClientVer" json:"MaxClientVer"`
MaxTimeDiff string `yaml:"MaxTimeDiff" json:"MaxTimeDiff"`
ShortIds []string `yaml:"ShortIds" json:"ShortIds"`
}
func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
const path = "/api/v1/server/UniProxy/config"
r, err := c.client.
R().
SetHeader("If-None-Match", c.nodeEtag).
Get(path)
if err = c.checkResponse(r, path, err); err != nil {
return
}
if r.StatusCode() == 304 {
return nil, nil
}
// parse common params
node = &NodeInfo{
Id: c.NodeId,
Type: c.NodeType,
}
common := CommonNodeRsp{}
err = json.Unmarshal(r.Body(), &common)
if err != nil {
return nil, fmt.Errorf("decode common params error: %s", err)
}
for i := range common.Routes { // parse rules from routes
var matchs []string
if _, ok := common.Routes[i].Match.(string); ok {
matchs = strings.Split(common.Routes[i].Match.(string), ",")
} else if _, ok = common.Routes[i].Match.([]string); ok {
matchs = common.Routes[i].Match.([]string)
} else {
temp := common.Routes[i].Match.([]interface{})
matchs = make([]string, len(temp))
for i := range temp {
matchs[i] = temp[i].(string)
}
}
switch common.Routes[i].Action {
case "block":
for _, v := range matchs {
if strings.HasPrefix(v, "protocol:") {
// protocol
node.Rules.Protocol = append(node.Rules.Protocol, strings.TrimPrefix(v, "protocol:"))
} else {
// domain
node.Rules.Regexp = append(node.Rules.Regexp, strings.TrimPrefix(v, "regexp:"))
}
}
case "dns":
if matchs[0] != "main" {
break
}
dnsPath := os.Getenv("XRAY_DNS_PATH")
if dnsPath == "" {
break
}
dns := []byte(strings.Join(matchs[1:], ""))
currentData, err := os.ReadFile(dnsPath)
if err != nil {
log.WithField("err", err).Panic("Failed to read XRAY_DNS_PATH")
break
}
if !bytes.Equal(currentData, dns) {
coreDnsConfig := &coreConf.DNSConfig{}
if err = json.NewDecoder(bytes.NewReader(dns)).Decode(coreDnsConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal DNS config")
}
_, err := coreDnsConfig.Build()
if err != nil {
log.WithField("err", err).Panic("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help")
break
}
if err = os.Truncate(dnsPath, 0); err != nil {
log.WithField("err", err).Panic("Failed to clear XRAY DNS PATH file")
}
if err = os.WriteFile(dnsPath, dns, 0644); err != nil {
log.WithField("err", err).Panic("Failed to write DNS to XRAY DNS PATH file")
}
}
}
}
node.ServerName = common.ServerName
node.Host = common.Host
node.Port = common.ServerPort
node.PullInterval = intervalToTime(common.BaseConfig.PullInterval)
node.PushInterval = intervalToTime(common.BaseConfig.PushInterval)
// parse protocol params
switch c.NodeType {
case "v2ray":
rsp := V2rayNodeRsp{}
err = json.Unmarshal(r.Body(), &rsp)
if err != nil {
return nil, fmt.Errorf("decode v2ray params error: %s", err)
}
node.Network = rsp.Network
node.NetworkSettings = rsp.NetworkSettings
node.ServerName = rsp.ServerName
if rsp.Tls == 1 {
node.Tls = true
}
err = json.Unmarshal(rsp.NetworkSettings, &node.ExtraConfig)
if err != nil {
return nil, fmt.Errorf("decode v2ray extra error: %s", err)
}
if node.ExtraConfig.EnableReality == "true" {
if node.ExtraConfig.RealityConfig == nil {
node.ExtraConfig.EnableReality = "false"
} else {
key := crypt.GenX25519Private([]byte(strconv.Itoa(c.NodeId) + c.NodeType + c.Token +
node.ExtraConfig.RealityConfig.PrivateKey))
node.ExtraConfig.RealityConfig.PrivateKey = base64.RawURLEncoding.EncodeToString(key)
}
}
case "shadowsocks":
rsp := ShadowsocksNodeRsp{}
err = json.Unmarshal(r.Body(), &rsp)
if err != nil {
return nil, fmt.Errorf("decode v2ray params error: %s", err)
}
node.ServerKey = rsp.ServerKey
node.Cipher = rsp.Cipher
case "trojan":
node.Tls = true
case "hysteria":
rsp := HysteriaNodeRsp{}
err = json.Unmarshal(r.Body(), &rsp)
if err != nil {
return nil, fmt.Errorf("decode v2ray params error: %s", err)
}
node.DownMbps = rsp.DownMbps
node.UpMbps = rsp.UpMbps
node.HyObfs = rsp.Obfs
}
c.nodeEtag = r.Header().Get("ETag")
return
}
func intervalToTime(i interface{}) time.Duration {
switch reflect.TypeOf(i).Kind() {
case reflect.Int:
return time.Duration(i.(int)) * time.Second
case reflect.String:
i, _ := strconv.Atoi(i.(string))
return time.Duration(i) * time.Second
case reflect.Float64:
return time.Duration(i.(float64)) * time.Second
default:
return time.Duration(reflect.ValueOf(i).Int()) * time.Second
}
}
================================================
FILE: api/panel/node_test.go
================================================
package panel
import (
"github.com/Yuzuki616/V2bX/conf"
"log"
"testing"
)
var client *Client
func init() {
c, err := New(&conf.ApiConfig{
APIHost: "http://127.0.0.1",
Key: "token",
NodeType: "V2ray",
NodeID: 1,
})
if err != nil {
log.Panic(err)
}
client = c
}
func TestClient_GetNodeInfo(t *testing.T) {
log.Println(client.GetNodeInfo())
log.Println(client.GetNodeInfo())
}
func TestClient_ReportUserTraffic(t *testing.T) {
log.Println(client.ReportUserTraffic([]UserTraffic{
{
UID: 10372,
Upload: 1000,
Download: 1000,
},
}))
}
================================================
FILE: api/panel/panel.go
================================================
package panel
import (
"bufio"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/Yuzuki616/V2bX/conf"
"github.com/go-resty/resty/v2"
)
// Panel is the interface for different panel's api.
type Client struct {
client *resty.Client
APIHost string
Token string
NodeType string
NodeId int
LocalRuleList []*regexp.Regexp
nodeEtag string
userEtag string
}
func New(c *conf.ApiConfig) (*Client, error) {
client := resty.New()
client.SetRetryCount(3)
if c.Timeout > 0 {
client.SetTimeout(time.Duration(c.Timeout) * time.Second)
} else {
client.SetTimeout(5 * time.Second)
}
client.OnError(func(req *resty.Request, err error) {
if v, ok := err.(*resty.ResponseError); ok {
// v.Response contains the last response from the server
// v.Err contains the original error
log.Print(v.Err)
}
})
client.SetBaseURL(c.APIHost)
// Check node type
c.NodeType = strings.ToLower(c.NodeType)
switch c.NodeType {
case "v2ray", "trojan", "shadowsocks", "hysteria":
default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
}
// set params
client.SetQueryParams(map[string]string{
"node_type": c.NodeType,
"node_id": strconv.Itoa(c.NodeID),
"token": c.Key,
})
// Read local rule list
localRuleList := readLocalRuleList(c.RuleListPath)
return &Client{
client: client,
Token: c.Key,
APIHost: c.APIHost,
NodeType: c.NodeType,
NodeId: c.NodeID,
LocalRuleList: localRuleList,
}, nil
}
// readLocalRuleList reads the local rule list file
func readLocalRuleList(path string) (LocalRuleList []*regexp.Regexp) {
LocalRuleList = make([]*regexp.Regexp, 0)
if path != "" {
// open the file
file, err := os.Open(path)
//handle errors while opening
if err != nil {
log.Printf("Error when opening file: %s", err)
return
}
fileScanner := bufio.NewScanner(file)
// read line by line
for fileScanner.Scan() {
LocalRuleList = append(LocalRuleList, regexp.MustCompile(fileScanner.Text()))
}
// handle first encountered error while reading
if err := fileScanner.Err(); err != nil {
log.Fatalf("Error while reading file: %s", err)
return
}
}
return
}
================================================
FILE: api/panel/user.go
================================================
package panel
import (
"fmt"
"github.com/goccy/go-json"
)
type OnlineUser struct {
UID int
IP string
}
type UserInfo struct {
Id int `json:"id"`
Uuid string `json:"uuid"`
SpeedLimit int `json:"speed_limit"`
}
type UserListBody struct {
//Msg string `json:"msg"`
Users []UserInfo `json:"users"`
}
// GetUserList will pull user form sspanel
func (c *Client) GetUserList() (UserList []UserInfo, err error) {
const path = "/api/v1/server/UniProxy/user"
r, err := c.client.R().
SetHeader("If-None-Match", c.userEtag).
Get(path)
err = c.checkResponse(r, path, err)
if err != nil {
return nil, err
}
err = c.checkResponse(r, path, err)
if r.StatusCode() == 304 {
return nil, nil
}
var userList *UserListBody
err = json.Unmarshal(r.Body(), &userList)
if err != nil {
return nil, fmt.Errorf("unmarshal userlist error: %s", err)
}
c.userEtag = r.Header().Get("ETag")
return userList.Users, nil
}
type UserTraffic struct {
UID int
Upload int64
Download int64
}
// ReportUserTraffic reports the user traffic
func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
data := make(map[int][]int64, len(userTraffic))
for i := range userTraffic {
data[userTraffic[i].UID] = []int64{userTraffic[i].Upload, userTraffic[i].Download}
}
const path = "/api/v1/server/UniProxy/push"
r, err := c.client.R().
SetBody(data).
ForceContentType("application/json").
Post(path)
err = c.checkResponse(r, path, err)
if err != nil {
return err
}
return nil
}
================================================
FILE: api/panel/utils.go
================================================
package panel
import (
"fmt"
"github.com/go-resty/resty/v2"
path2 "path"
)
// Debug set the client debug for client
func (c *Client) Debug() {
c.client.SetDebug(true)
}
func (c *Client) assembleURL(path string) string {
return path2.Join(c.APIHost + path)
}
func (c *Client) checkResponse(res *resty.Response, path string, err error) error {
if err != nil {
return fmt.Errorf("request %s failed: %s", c.assembleURL(path), err)
}
if res.StatusCode() >= 400 {
body := res.Body()
return fmt.Errorf("request %s failed: %s", c.assembleURL(path), string(body))
}
return nil
}
================================================
FILE: cmd/action_linux.go
================================================
package cmd
import (
"fmt"
"github.com/Yuzuki616/V2bX/common/exec"
"github.com/spf13/cobra"
"time"
)
var (
startCommand = cobra.Command{
Use: "start",
Short: "Start V2bX service",
Run: startHandle,
}
stopCommand = cobra.Command{
Use: "stop",
Short: "Stop V2bX service",
Run: stopHandle,
}
restartCommand = cobra.Command{
Use: "restart",
Short: "Restart V2bX service",
Run: restartHandle,
}
logCommand = cobra.Command{
Use: "log",
Short: "Output V2bX log",
Run: func(_ *cobra.Command, _ []string) {
exec.RunCommandStd("journalctl", "-u", "V2bX.service", "-e", "--no-pager", "-f")
},
}
)
func init() {
command.AddCommand(&startCommand)
command.AddCommand(&stopCommand)
command.AddCommand(&restartCommand)
command.AddCommand(&logCommand)
}
func startHandle(_ *cobra.Command, _ []string) {
r, err := checkRunning()
if err != nil {
fmt.Println(Err("check status error: ", err))
fmt.Println(Err("V2bX启动失败"))
return
}
if r {
fmt.Println(Ok("V2bX已运行,无需再次启动,如需重启请选择重启"))
}
_, err = exec.RunCommandByShell("systemctl start V2bX.service")
if err != nil {
fmt.Println(Err("exec start cmd error: ", err))
fmt.Println(Err("V2bX启动失败"))
return
}
time.Sleep(time.Second * 3)
r, err = checkRunning()
if err != nil {
fmt.Println(Err("check status error: ", err))
fmt.Println(Err("V2bX启动失败"))
}
if !r {
fmt.Println(Err("V2bX可能启动失败,请稍后使用 V2bX log 查看日志信息"))
return
}
fmt.Println(Ok("V2bX 启动成功,请使用 V2bX log 查看运行日志"))
}
func stopHandle(_ *cobra.Command, _ []string) {
_, err := exec.RunCommandByShell("systemctl stop V2bX.service")
if err != nil {
fmt.Println(Err("exec stop cmd error: ", err))
fmt.Println(Err("V2bX停止失败"))
return
}
time.Sleep(2 * time.Second)
r, err := checkRunning()
if err != nil {
fmt.Println(Err("check status error:", err))
fmt.Println(Err("V2bX停止失败"))
return
}
if r {
fmt.Println(Err("V2bX停止失败,可能是因为停止时间超过了两秒,请稍后查看日志信息"))
return
}
fmt.Println(Ok("V2bX 停止成功"))
}
func restartHandle(_ *cobra.Command, _ []string) {
_, err := exec.RunCommandByShell("systemctl restart V2bX.service")
if err != nil {
fmt.Println(Err("exec restart cmd error: ", err))
fmt.Println(Err("V2bX重启失败"))
return
}
r, err := checkRunning()
if err != nil {
fmt.Println(Err("check status error: ", err))
fmt.Println(Err("V2bX重启失败"))
return
}
if !r {
fmt.Println(Err("V2bX可能启动失败,请稍后使用 V2bX log 查看日志信息"))
return
}
fmt.Println(Ok("V2bX重启成功"))
}
================================================
FILE: cmd/cmd.go
================================================
package cmd
import (
log "github.com/sirupsen/logrus"
_ "github.com/Yuzuki616/V2bX/core/imports"
"github.com/spf13/cobra"
)
var command = &cobra.Command{
Use: "V2bX",
}
func Run() {
err := command.Execute()
if err != nil {
log.WithField("err", err).Error("Execute command failed")
}
}
================================================
FILE: cmd/common.go
================================================
package cmd
import (
"fmt"
"github.com/Yuzuki616/V2bX/common/exec"
"strings"
)
const (
red = "\033[0;31m"
green = "\033[0;32m"
yellow = "\033[0;33m"
plain = "\033[0m"
)
func checkRunning() (bool, error) {
o, err := exec.RunCommandByShell("systemctl status V2bX | grep Active")
if err != nil {
return false, err
}
return strings.Contains(o, "running"), nil
}
func Err(msg ...any) string {
return red + fmt.Sprint(msg...) + plain
}
func Ok(msg ...any) string {
return green + fmt.Sprint(msg...) + plain
}
func Warn(msg ...any) string {
return yellow + fmt.Sprint(msg...) + plain
}
================================================
FILE: cmd/common_test.go
================================================
package cmd
import "testing"
func Test_printFailed(t *testing.T) {
t.Log(Err("test"))
}
================================================
FILE: cmd/install_linux.go
================================================
package cmd
import (
"fmt"
"github.com/Yuzuki616/V2bX/common/exec"
"github.com/spf13/cobra"
"os"
"strings"
)
var targetVersion string
var (
updateCommand = cobra.Command{
Use: "update",
Short: "Update V2bX version",
Run: func(_ *cobra.Command, _ []string) {
exec.RunCommandStd("bash",
"<(curl -Ls https://raw.githubusercontents.com/Yuzuki616/V2bX-script/master/install.sh)",
targetVersion)
},
Args: cobra.NoArgs,
}
uninstallCommand = cobra.Command{
Use: "uninstall",
Short: "Uninstall V2bX",
Run: uninstallHandle,
}
)
func init() {
updateCommand.PersistentFlags().StringVar(&targetVersion, "version", "", "update target version")
command.AddCommand(&updateCommand)
command.AddCommand(&uninstallCommand)
}
func uninstallHandle(_ *cobra.Command, _ []string) {
var yes string
fmt.Println(Warn("确定要卸载 V2bX 吗?(Y/n)"))
fmt.Scan(&yes)
if strings.ToLower(yes) != "y" {
fmt.Println("已取消卸载")
}
_, err := exec.RunCommandByShell("systemctl stop V2bX&&systemctl disable V2bX")
if err != nil {
fmt.Println(Err("exec cmd error: ", err))
fmt.Println(Err("卸载失败"))
return
}
_ = os.RemoveAll("/etc/systemd/system/V2bX.service")
_ = os.RemoveAll("/etc/V2bX/")
_ = os.RemoveAll("/usr/local/V2bX/")
_ = os.RemoveAll("/bin/V2bX")
_, err = exec.RunCommandByShell("systemctl daemon-reload&&systemctl reset-failed")
if err != nil {
fmt.Println(Err("exec cmd error: ", err))
fmt.Println(Err("卸载失败"))
return
}
fmt.Println(Ok("卸载成功"))
}
================================================
FILE: cmd/server.go
================================================
package cmd
import (
log "github.com/sirupsen/logrus"
"os"
"os/signal"
"runtime"
"syscall"
vCore "github.com/Yuzuki616/V2bX/core"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/Yuzuki616/V2bX/node"
"github.com/spf13/cobra"
)
var (
config string
watch bool
)
var serverCommand = cobra.Command{
Use: "server",
Short: "Run V2bX server",
Run: serverHandle,
Args: cobra.NoArgs,
}
func init() {
serverCommand.PersistentFlags().
StringVarP(&config, "config", "c",
"/etc/V2bX/config.yml", "config file path")
serverCommand.PersistentFlags().
BoolVarP(&watch, "watch", "w",
true, "watch file path change")
command.AddCommand(&serverCommand)
}
func serverHandle(_ *cobra.Command, _ []string) {
showVersion()
c := conf.New()
err := c.LoadFromPath(config)
if err != nil {
log.WithField("err", err).Error("Load config file failed")
return
}
limiter.Init()
log.Info("Start V2bX...")
vc, err := vCore.NewCore(&c.CoreConfig)
if err != nil {
log.WithField("err", err).Error("new core failed")
return
}
err = vc.Start()
if err != nil {
log.WithField("err", err).Error("Start core failed")
return
}
defer vc.Close()
nodes := node.New()
err = nodes.Start(c.NodesConfig, vc)
if err != nil {
log.WithField("err", err).Error("Run nodes failed")
return
}
dns := os.Getenv("XRAY_DNS_PATH")
if watch {
err = c.Watch(config, dns, func() {
nodes.Close()
err = vc.Close()
if err != nil {
log.WithField("err", err).Error("Restart node failed")
return
}
vc, err = vCore.NewCore(&c.CoreConfig)
if err != nil {
log.WithField("err", err).Error("New core failed")
return
}
err = vc.Start()
if err != nil {
log.WithField("err", err).Error("Start core failed")
return
}
err = nodes.Start(c.NodesConfig, vc)
if err != nil {
log.WithField("err", err).Error("Run nodes failed")
return
}
runtime.GC()
})
if err != nil {
log.WithField("err", err).Error("start watch failed")
return
}
}
// clear memory
runtime.GC()
// wait exit signal
{
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM)
<-osSignals
}
}
================================================
FILE: cmd/server_test.go
================================================
package cmd
import "testing"
func TestRun(t *testing.T) {
Run()
}
================================================
FILE: cmd/synctime.go
================================================
package cmd
import (
"fmt"
"github.com/Yuzuki616/V2bX/common/systime"
"github.com/beevik/ntp"
"github.com/spf13/cobra"
)
var ntpServer string
var commandSyncTime = &cobra.Command{
Use: "synctime",
Short: "Sync time from ntp server",
Args: cobra.NoArgs,
Run: synctimeHandle,
}
func init() {
commandSyncTime.Flags().StringVar(&ntpServer, "server", "time.apple.com", "ntp server")
command.AddCommand(commandSyncTime)
}
func synctimeHandle(_ *cobra.Command, _ []string) {
t, err := ntp.Time(ntpServer)
if err != nil {
fmt.Println(Err("get time from server error: ", err))
fmt.Println(Err("同步时间失败"))
return
}
err = systime.SetSystemTime(t)
if err != nil {
fmt.Println(Err("set system time error: ", err))
fmt.Println(Err("同步时间失败"))
return
}
fmt.Println(Ok("同步时间成功"))
}
================================================
FILE: cmd/version.go
================================================
package cmd
import (
"fmt"
"strings"
vCore "github.com/Yuzuki616/V2bX/core"
"github.com/spf13/cobra"
)
var (
version = "TempVersion" //use ldflags replace
codename = "V2bX"
intro = "A V2board backend based on multi core"
)
var versionCommand = cobra.Command{
Use: "version",
Short: "Print version info",
Run: func(_ *cobra.Command, _ []string) {
showVersion()
},
}
func init() {
command.AddCommand(&versionCommand)
}
func showVersion() {
fmt.Printf("%s %s (%s) \n", codename, version, intro)
fmt.Printf("Supported cores: %s\n", strings.Join(vCore.RegisteredCore(), ", "))
// Warning
fmt.Println(Warn("This version need V2board version >= 1.7.0."))
fmt.Println(Warn("The version have many changed for config, please check your config file"))
}
================================================
FILE: cmd/x25519.go
================================================
package cmd
import (
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
"github.com/Yuzuki616/V2bX/common/crypt"
"github.com/spf13/cobra"
"golang.org/x/crypto/curve25519"
)
var x25519Command = cobra.Command{
Use: "x25519",
Short: "Generate key pair for x25519 key exchange",
Run: func(cmd *cobra.Command, args []string) {
executeX25519()
},
}
func init() {
command.AddCommand(&x25519Command)
}
func executeX25519() {
var output string
var err error
defer func() {
fmt.Println(output)
}()
var privateKey []byte
var publicKey []byte
var yes, key string
fmt.Println("要基于节点信息生成密钥吗?(Y/n)")
fmt.Scan(&yes)
if strings.ToLower(yes) == "y" {
var temp string
fmt.Println("请输入节点id:")
fmt.Scan(&temp)
key = temp
fmt.Println("请输入节点类型:")
fmt.Scan(&temp)
key += strings.ToLower(temp)
fmt.Println("请输入Token:")
fmt.Scan(&temp)
key += temp
privateKey = crypt.GenX25519Private([]byte(key))
} else {
privateKey = make([]byte, curve25519.ScalarSize)
if _, err = rand.Read(privateKey); err != nil {
output = Err("read rand error: ", err)
return
}
}
if publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint); err != nil {
output = Err("gen X25519 error: ", err)
return
}
p := base64.RawURLEncoding.EncodeToString(privateKey)
output = fmt.Sprint("Private key: ",
p,
"\nPublic key: ",
base64.RawURLEncoding.EncodeToString(publicKey))
}
================================================
FILE: cmd/x25519_test.go
================================================
package cmd
import "testing"
func Test_executeX25519(t *testing.T) {
executeX25519()
}
================================================
FILE: common/crypt/aes.go
================================================
package crypt
import (
"crypto/aes"
"encoding/base64"
)
func AesEncrypt(data []byte, key []byte) (string, error) {
a, err := aes.NewCipher(key)
if err != nil {
return "", err
}
en := make([]byte, len(data))
a.Encrypt(en, data)
return base64.StdEncoding.EncodeToString(en), nil
}
func AesDecrypt(data string, key []byte) (string, error) {
d, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return "", err
}
a, err := aes.NewCipher(key)
if err != nil {
return "", err
}
de := make([]byte, len(data))
a.Decrypt(de, d)
return string(de), nil
}
================================================
FILE: common/crypt/x25519.go
================================================
package crypt
import (
"crypto/sha256"
)
func GenX25519Private(data []byte) []byte {
key := sha256.Sum256(data)
key[0] &= 248
key[31] &= 127
key[31] |= 64
return key[:32]
}
================================================
FILE: common/exec/exec.go
================================================
package exec
import (
"errors"
"os"
"os/exec"
)
func RunCommandByShell(cmd string) (string, error) {
e := exec.Command("bash", "-c", cmd)
out, err := e.CombinedOutput()
if errors.Unwrap(err) == exec.ErrNotFound {
e = exec.Command("sh", "-c", cmd)
out, err = e.CombinedOutput()
}
return string(out), err
}
func RunCommandStd(name string, args ...string) {
e := exec.Command(name, args...)
e.Stdout = os.Stdout
e.Stdin = os.Stdin
e.Stderr = os.Stderr
_ = e.Run()
}
================================================
FILE: common/file/file.go
================================================
package file
import "os"
func IsExist(path string) bool {
_, err := os.Stat(path)
return err == nil || !os.IsNotExist(err)
}
================================================
FILE: common/format/user.go
================================================
package format
import (
"fmt"
)
func UserTag(tag string, uuid string) string {
return fmt.Sprintf("%s|%s", tag, uuid)
}
================================================
FILE: common/rate/rate.go
================================================
package rate
import (
"github.com/juju/ratelimit"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
)
type Writer struct {
writer buf.Writer
limiter *ratelimit.Bucket
}
func NewRateLimitWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer {
return &Writer{
writer: writer,
limiter: limiter,
}
}
func (w *Writer) Close() error {
return common.Close(w.writer)
}
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.limiter.Wait(int64(mb.Len()))
return w.writer.WriteMultiBuffer(mb)
}
================================================
FILE: common/systime/time_stub.go
================================================
//go:build !(windows || linux || darwin)
package systime
import (
"os"
"time"
)
func SetSystemTime(nowTime time.Time) error {
return os.ErrInvalid
}
================================================
FILE: common/systime/time_unix.go
================================================
//go:build linux || darwin
package systime
import (
"time"
"golang.org/x/sys/unix"
)
func SetSystemTime(nowTime time.Time) error {
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
return unix.Settimeofday(&timeVal)
}
================================================
FILE: common/systime/time_windows.go
================================================
package systime
import (
"time"
"unsafe"
"golang.org/x/sys/windows"
)
func SetSystemTime(nowTime time.Time) error {
var systemTime windows.Systemtime
systemTime.Year = uint16(nowTime.Year())
systemTime.Month = uint16(nowTime.Month())
systemTime.Day = uint16(nowTime.Day())
systemTime.Hour = uint16(nowTime.Hour())
systemTime.Minute = uint16(nowTime.Minute())
systemTime.Second = uint16(nowTime.Second())
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
proc := dllKernel32.NewProc("SetSystemTime")
_, _, err := proc.Call(
uintptr(unsafe.Pointer(&systemTime)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return err
}
return nil
}
================================================
FILE: common/task/task.go
================================================
package task
import (
"sync"
"time"
)
// Task is a task that runs periodically.
type Task struct {
// Interval of the task being run
Interval time.Duration
// Execute is the task function
Execute func() error
access sync.Mutex
timer *time.Timer
running bool
}
func (t *Task) hasClosed() bool {
t.access.Lock()
defer t.access.Unlock()
return !t.running
}
func (t *Task) checkedExecute(first bool) error {
if t.hasClosed() {
return nil
}
t.access.Lock()
defer t.access.Unlock()
if first {
if err := t.Execute(); err != nil {
t.running = false
return err
}
}
if !t.running {
return nil
}
t.timer = time.AfterFunc(t.Interval, func() {
t.checkedExecute(true)
})
return nil
}
// Start implements common.Runnable.
func (t *Task) Start(first bool) error {
t.access.Lock()
if t.running {
t.access.Unlock()
return nil
}
t.running = true
t.access.Unlock()
if err := t.checkedExecute(first); err != nil {
t.access.Lock()
t.running = false
t.access.Unlock()
return err
}
return nil
}
// Close implements common.Closable.
func (t *Task) Close() {
t.access.Lock()
defer t.access.Unlock()
t.running = false
if t.timer != nil {
t.timer.Stop()
t.timer = nil
}
}
================================================
FILE: common/task/task_test.go
================================================
package task
import (
"log"
"testing"
"time"
)
func TestTask(t *testing.T) {
ts := Task{Execute: func() error {
log.Println("q")
return nil
}, Interval: time.Second}
ts.Start(false)
}
================================================
FILE: conf/conf.go
================================================
package conf
import (
"fmt"
"gopkg.in/yaml.v3"
"io"
"os"
)
type Conf struct {
CoreConfig CoreConfig `yaml:"CoreConfig"`
NodesConfig []*NodeConfig `yaml:"Nodes"`
}
func New() *Conf {
return &Conf{
CoreConfig: CoreConfig{
Type: "xray",
XrayConfig: &XrayConfig{
LogConfig: NewLogConfig(),
AssetPath: "/etc/V2bX/",
DnsConfigPath: "",
InboundConfigPath: "",
OutboundConfigPath: "",
RouteConfigPath: "",
ConnectionConfig: NewConnectionConfig(),
},
},
NodesConfig: []*NodeConfig{},
}
}
func (p *Conf) LoadFromPath(filePath string) error {
f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("open config file error: %s", err)
}
defer f.Close()
content, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("read file error: %s", err)
}
err = yaml.Unmarshal(content, p)
if err != nil {
return fmt.Errorf("decode config error: %s", err)
}
old := &OldConfig{}
err = yaml.Unmarshal(content, old)
if err == nil {
migrateOldConfig(p, old)
}
return nil
}
================================================
FILE: conf/conf_test.go
================================================
package conf
import (
"log"
"testing"
)
func TestConf_LoadFromPath(t *testing.T) {
c := New()
t.Log(c.LoadFromPath("../example/config.yml.example"))
}
func TestConf_Watch(t *testing.T) {
c := New()
log.Println(c.Watch("../example/config.yml.example", func() {
log.Println(1)
}))
select {}
}
================================================
FILE: conf/core.go
================================================
package conf
type CoreConfig struct {
Type string `yaml:"Type"`
XrayConfig *XrayConfig `yaml:"XrayConfig"`
}
type XrayConfig struct {
LogConfig *LogConfig `yaml:"Log"`
AssetPath string `yaml:"AssetPath"`
DnsConfigPath string `yaml:"DnsConfigPath"`
RouteConfigPath string `yaml:"RouteConfigPath"`
ConnectionConfig *ConnectionConfig `yaml:"ConnectionConfig"`
InboundConfigPath string `yaml:"InboundConfigPath"`
OutboundConfigPath string `yaml:"OutboundConfigPath"`
}
type ConnectionConfig struct {
Handshake uint32 `yaml:"handshake"`
ConnIdle uint32 `yaml:"connIdle"`
UplinkOnly uint32 `yaml:"uplinkOnly"`
DownlinkOnly uint32 `yaml:"downlinkOnly"`
BufferSize int32 `yaml:"bufferSize"`
}
func NewConnectionConfig() *ConnectionConfig {
return &ConnectionConfig{
Handshake: 4,
ConnIdle: 30,
UplinkOnly: 2,
DownlinkOnly: 4,
BufferSize: 64,
}
}
================================================
FILE: conf/log.go
================================================
package conf
type LogConfig struct {
Level string `yaml:"Level"`
AccessPath string `yaml:"AccessPath"`
ErrorPath string `yaml:"ErrorPath"`
}
func NewLogConfig() *LogConfig {
return &LogConfig{
Level: "warning",
AccessPath: "",
ErrorPath: "",
}
}
================================================
FILE: conf/node.go
================================================
package conf
type NodeConfig struct {
ApiConfig *ApiConfig `yaml:"ApiConfig"`
ControllerConfig *ControllerConfig `yaml:"ControllerConfig"`
}
type ApiConfig struct {
APIHost string `yaml:"ApiHost"`
NodeID int `yaml:"NodeID"`
Key string `yaml:"ApiKey"`
NodeType string `yaml:"NodeType"`
Timeout int `yaml:"Timeout"`
RuleListPath string `yaml:"RuleListPath"`
}
type ControllerConfig struct {
ListenIP string `yaml:"ListenIP"`
SendIP string `yaml:"SendIP"`
XrayOptions XrayOptions `yaml:"XrayOptions"`
HyOptions HyOptions `yaml:"HyOptions"`
LimitConfig LimitConfig `yaml:"LimitConfig"`
CertConfig *CertConfig `yaml:"CertConfig"`
}
type RealityConfig struct {
Dest interface{} `yaml:"Dest" json:"Dest"`
Xver uint64 `yaml:"Xver" json:"Xver"`
ServerNames []string `yaml:"ServerNames" json:"ServerNames"`
PrivateKey string `yaml:"PrivateKey" json:"PrivateKey"`
MinClientVer string `yaml:"MinClientVer" json:"MinClientVer"`
MaxClientVer string `yaml:"MaxClientVer" json:"MaxClientVer"`
MaxTimeDiff uint64 `yaml:"MaxTimeDiff" json:"MaxTimeDiff"`
ShortIds []string `yaml:"ShortIds" json:"ShortIds"`
}
type XrayOptions struct {
EnableProxyProtocol bool `yaml:"EnableProxyProtocol"`
EnableDNS bool `yaml:"EnableDNS"`
DNSType string `yaml:"DNSType"`
EnableUot bool `yaml:"EnableUot"`
EnableTFO bool `yaml:"EnableTFO"`
DisableIVCheck bool `yaml:"DisableIVCheck"`
DisableSniffing bool `yaml:"DisableSniffing"`
EnableFallback bool `yaml:"EnableFallback"`
FallBackConfigs []FallBackConfig `yaml:"FallBackConfigs"`
}
type HyOptions struct {
Resolver string `yaml:"Resolver"`
ResolvePreference string `yaml:"ResolvePreference"`
SendDevice string `yaml:"SendDevice"`
}
type LimitConfig struct {
EnableRealtime bool `yaml:"EnableRealtime"`
SpeedLimit int `yaml:"SpeedLimit"`
IPLimit int `yaml:"DeviceLimit"`
ConnLimit int `yaml:"ConnLimit"`
EnableIpRecorder bool `yaml:"EnableIpRecorder"`
IpRecorderConfig *IpReportConfig `yaml:"IpRecorderConfig"`
EnableDynamicSpeedLimit bool `yaml:"EnableDynamicSpeedLimit"`
DynamicSpeedLimitConfig *DynamicSpeedLimitConfig `yaml:"DynamicSpeedLimitConfig"`
}
type FallBackConfig struct {
SNI string `yaml:"SNI"`
Alpn string `yaml:"Alpn"`
Path string `yaml:"Path"`
Dest string `yaml:"Dest"`
ProxyProtocolVer uint64 `yaml:"ProxyProtocolVer"`
}
type RecorderConfig struct {
Url string `yaml:"Url"`
Token string `yaml:"Token"`
Timeout int `yaml:"Timeout"`
}
type RedisConfig struct {
Address string `yaml:"Address"`
Password string `yaml:"Password"`
Db int `yaml:"Db"`
Expiry int `json:"Expiry"`
}
type IpReportConfig struct {
Periodic int `yaml:"Periodic"`
Type string `yaml:"Type"`
RecorderConfig *RecorderConfig `yaml:"RecorderConfig"`
RedisConfig *RedisConfig `yaml:"RedisConfig"`
EnableIpSync bool `yaml:"EnableIpSync"`
}
type DynamicSpeedLimitConfig struct {
Periodic int `yaml:"Periodic"`
Traffic int64 `yaml:"Traffic"`
SpeedLimit int `yaml:"SpeedLimit"`
ExpireTime int `yaml:"ExpireTime"`
}
type CertConfig struct {
CertMode string `yaml:"CertMode"` // none, file, http, dns
RejectUnknownSni bool `yaml:"RejectUnknownSni"`
CertDomain string `yaml:"CertDomain"`
CertFile string `yaml:"CertFile"`
KeyFile string `yaml:"KeyFile"`
Provider string `yaml:"Provider"` // alidns, cloudflare, gandi, godaddy....
Email string `yaml:"Email"`
DNSEnv map[string]string `yaml:"DNSEnv"`
RealityConfig *RealityConfig `yaml:"RealityConfig"`
}
================================================
FILE: conf/old.go
================================================
package conf
import "log"
type OldConfig struct {
NodesConfig []*struct {
ApiConfig *OldApiConfig `yaml:"ApiConfig"`
ControllerConfig *OldControllerConfig `yaml:"ControllerConfig"`
} `yaml:"Nodes"`
}
type OldControllerConfig struct {
ListenIP string `yaml:"ListenIP"`
SendIP string `yaml:"SendIP"`
EnableDNS bool `yaml:"EnableDNS"`
DNSType string `yaml:"DNSType"`
DisableUploadTraffic bool `yaml:"DisableUploadTraffic"`
DisableGetRule bool `yaml:"DisableGetRule"`
EnableProxyProtocol bool `yaml:"EnableProxyProtocol"`
EnableFallback bool `yaml:"EnableFallback"`
DisableIVCheck bool `yaml:"DisableIVCheck"`
DisableSniffing bool `yaml:"DisableSniffing"`
FallBackConfigs []*FallBackConfig `yaml:"FallBackConfigs"`
EnableIpRecorder bool `yaml:"EnableIpRecorder"`
IpRecorderConfig *IpReportConfig `yaml:"IpRecorderConfig"`
EnableDynamicSpeedLimit bool `yaml:"EnableDynamicSpeedLimit"`
DynamicSpeedLimitConfig *DynamicSpeedLimitConfig `yaml:"DynamicSpeedLimitConfig"`
CertConfig *CertConfig `yaml:"CertConfig"`
}
type OldApiConfig struct {
APIHost string `yaml:"ApiHost"`
NodeID int `yaml:"NodeID"`
Key string `yaml:"ApiKey"`
NodeType string `yaml:"NodeType"`
EnableVless bool `yaml:"EnableVless"`
Timeout int `yaml:"Timeout"`
SpeedLimit int `yaml:"SpeedLimit"`
DeviceLimit int `yaml:"DeviceLimit"`
RuleListPath string `yaml:"RuleListPath"`
DisableCustomConfig bool `yaml:"DisableCustomConfig"`
}
func migrateOldConfig(c *Conf, old *OldConfig) {
changed := false
for i, n := range c.NodesConfig {
if i >= len(old.NodesConfig) {
break
}
// limit config
if old.NodesConfig[i].ApiConfig.SpeedLimit != 0 {
n.ControllerConfig.LimitConfig.SpeedLimit = old.NodesConfig[i].ApiConfig.SpeedLimit
changed = true
}
if old.NodesConfig[i].ApiConfig.DeviceLimit != 0 {
n.ControllerConfig.LimitConfig.IPLimit = old.NodesConfig[i].ApiConfig.DeviceLimit
changed = true
}
if old.NodesConfig[i].ControllerConfig.EnableDynamicSpeedLimit {
n.ControllerConfig.LimitConfig.EnableDynamicSpeedLimit = true
changed = true
}
if old.NodesConfig[i].ControllerConfig.DynamicSpeedLimitConfig != nil {
n.ControllerConfig.LimitConfig.DynamicSpeedLimitConfig =
old.NodesConfig[i].ControllerConfig.DynamicSpeedLimitConfig
changed = true
}
if old.NodesConfig[i].ControllerConfig.EnableIpRecorder {
n.ControllerConfig.LimitConfig.EnableIpRecorder = true
changed = true
}
if old.NodesConfig[i].ControllerConfig.IpRecorderConfig != nil {
n.ControllerConfig.LimitConfig.IpRecorderConfig =
old.NodesConfig[i].ControllerConfig.IpRecorderConfig
changed = true
}
}
if changed {
log.Println("Warning: This config file is old.")
}
}
================================================
FILE: conf/watch.go
================================================
package conf
import (
"fmt"
"github.com/fsnotify/fsnotify"
"log"
"path"
"time"
)
func (p *Conf) Watch(filePath, dnsPath string, reload func()) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("new watcher error: %s", err)
}
go func() {
var pre time.Time
defer watcher.Close()
for {
select {
case e := <-watcher.Events:
if e.Has(fsnotify.Chmod) {
continue
}
if pre.Add(10 * time.Second).After(time.Now()) {
continue
}
pre = time.Now()
go func() {
time.Sleep(10 * time.Second)
if e.Name == dnsPath {
log.Println("DNS file changed, reloading...")
} else {
log.Println("config dir changed, reloading...")
}
*p = *New()
err := p.LoadFromPath(filePath)
if err != nil {
log.Printf("reload config error: %s", err)
}
reload()
log.Println("reload config success")
}()
case err := <-watcher.Errors:
if err != nil {
log.Printf("File watcher error: %s", err)
}
}
}
}()
err = watcher.Add(path.Dir(filePath))
if err != nil {
return fmt.Errorf("watch file error: %s", err)
}
if dnsPath != "" {
err = watcher.Add(path.Dir(dnsPath))
if err != nil {
return fmt.Errorf("watch dns file error: %s", err)
}
}
return nil
}
================================================
FILE: core/core.go
================================================
package core
import (
"errors"
"strings"
"github.com/Yuzuki616/V2bX/conf"
)
var (
cores = map[string]func(c *conf.CoreConfig) (Core, error){}
)
func NewCore(c *conf.CoreConfig) (Core, error) {
// multi core
if types := strings.Split(c.Type, " "); len(types) > 1 {
var cs []Core
for _, t := range types {
f, ok := cores[strings.ToLower(t)]
if !ok {
return nil, errors.New("unknown core type: " + t)
}
core1, err := f(c)
if err != nil {
return nil, err
}
cs = append(cs, core1)
}
return &Selector{
cores: cs,
}, nil
}
// one core
if f, ok := cores[strings.ToLower(c.Type)]; ok {
return f(c)
} else {
return nil, errors.New("unknown core type")
}
}
func RegisterCore(t string, f func(c *conf.CoreConfig) (Core, error)) {
cores[t] = f
}
func RegisteredCore() []string {
cs := make([]string, 0, len(cores))
for k := range cores {
cs = append(cs, k)
}
return cs
}
================================================
FILE: core/hy/config.go
================================================
package hy
const (
mbpsToBps = 125000
minSpeedBPS = 16384
DefaultALPN = "hysteria"
DefaultStreamReceiveWindow = 16777216 // 16 MB
DefaultConnectionReceiveWindow = DefaultStreamReceiveWindow * 5 / 2 // 40 MB
DefaultMaxIncomingStreams = 1024
DefaultMMDBFilename = "GeoLite2-Country.mmdb"
ServerMaxIdleTimeoutSec = 60
DefaultClientIdleTimeoutSec = 20
DefaultClientHopIntervalSec = 10
)
func SpeedTrans(upM, downM int) (uint64, uint64) {
up := uint64(upM) * mbpsToBps
down := uint64(downM) * mbpsToBps
return up, down
}
================================================
FILE: core/hy/counter.go
================================================
package hy
import (
"sync"
"sync/atomic"
)
type UserTrafficCounter struct {
counters map[string]*counters
lock sync.RWMutex
}
type counters struct {
UpCounter atomic.Int64
DownCounter atomic.Int64
//ConnGauge atomic.Int64
}
func NewUserTrafficCounter() *UserTrafficCounter {
return &UserTrafficCounter{
counters: map[string]*counters{},
}
}
func (c *UserTrafficCounter) getCounters(auth string) *counters {
c.lock.RLock()
cts, ok := c.counters[auth]
c.lock.RUnlock()
if !ok {
cts = &counters{}
c.counters[auth] = cts
}
return cts
}
func (c *UserTrafficCounter) Rx(auth string, n int) {
cts := c.getCounters(auth)
cts.DownCounter.Add(int64(n))
}
func (c *UserTrafficCounter) Tx(auth string, n int) {
cts := c.getCounters(auth)
cts.UpCounter.Add(int64(n))
}
func (c *UserTrafficCounter) IncConn(_ string) {
/*cts := c.getCounters(auth)
cts.ConnGauge.Add(1)*/
return
}
func (c *UserTrafficCounter) DecConn(_ string) {
/*cts := c.getCounters(auth)
cts.ConnGauge.Add(1)*/
return
}
func (c *UserTrafficCounter) Reset(auth string) {
cts := c.getCounters(auth)
cts.UpCounter.Store(0)
cts.DownCounter.Store(0)
}
func (c *UserTrafficCounter) Delete(auth string) {
c.lock.Lock()
delete(c.counters, auth)
c.lock.Unlock()
}
================================================
FILE: core/hy/counter_test.go
================================================
package hy
import "testing"
func TestUserTrafficCounter_Rx(t *testing.T) {
}
================================================
FILE: core/hy/hy.go
================================================
package hy
import (
"fmt"
"sync"
"github.com/Yuzuki616/V2bX/conf"
vCore "github.com/Yuzuki616/V2bX/core"
"github.com/hashicorp/go-multierror"
)
func init() {
vCore.RegisterCore("hy", NewHy)
}
type Hy struct {
servers sync.Map
}
func NewHy(_ *conf.CoreConfig) (vCore.Core, error) {
return &Hy{
servers: sync.Map{},
}, nil
}
func (h *Hy) Start() error {
return nil
}
func (h *Hy) Close() error {
var errs error
h.servers.Range(func(tag, s any) bool {
err := s.(*Server).Close()
if err != nil {
errs = multierror.Append(errs, fmt.Errorf("close %s error: %s", tag, err))
}
return true
})
if errs != nil {
return errs
}
return nil
}
func (h *Hy) Protocols() []string {
return []string{
"hysteria",
}
}
================================================
FILE: core/hy/ipmasker.go
================================================
package hy
import (
"net"
)
type ipMasker struct {
IPv4Mask net.IPMask
IPv6Mask net.IPMask
}
// Mask masks an address with the configured CIDR.
// addr can be "host:port" or just host.
func (m *ipMasker) Mask(addr string) string {
if m.IPv4Mask == nil && m.IPv6Mask == nil {
return addr
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
// just host
host, port = addr, ""
}
ip := net.ParseIP(host)
if ip == nil {
// not an IP address, return as is
return addr
}
if ip4 := ip.To4(); ip4 != nil && m.IPv4Mask != nil {
// IPv4
host = ip4.Mask(m.IPv4Mask).String()
} else if ip6 := ip.To16(); ip6 != nil && m.IPv6Mask != nil {
// IPv6
host = ip6.Mask(m.IPv6Mask).String()
}
if port != "" {
return net.JoinHostPort(host, port)
} else {
return host
}
}
var defaultIPMasker = &ipMasker{}
================================================
FILE: core/hy/kploader.go
================================================
package hy
import (
"crypto/tls"
"sync"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
)
type keypairLoader struct {
certMu sync.RWMutex
cert *tls.Certificate
certPath string
keyPath string
}
func newKeypairLoader(certPath, keyPath string) (*keypairLoader, error) {
loader := &keypairLoader{
certPath: certPath,
keyPath: keyPath,
}
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
loader.cert = &cert
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
switch event.Op {
case fsnotify.Create, fsnotify.Write, fsnotify.Rename, fsnotify.Chmod:
logrus.WithFields(logrus.Fields{
"file": event.Name,
}).Info("Keypair change detected, reloading...")
if err := loader.load(); err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to reload keypair")
} else {
logrus.Info("Keypair successfully reloaded")
}
case fsnotify.Remove:
_ = watcher.Add(event.Name) // Workaround for vim
// https://github.com/fsnotify/fsnotify/issues/92
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Failed to watch keypair files for changes")
}
}
}()
err = watcher.Add(certPath)
if err != nil {
_ = watcher.Close()
return nil, err
}
err = watcher.Add(keyPath)
if err != nil {
_ = watcher.Close()
return nil, err
}
return loader, nil
}
func (kpr *keypairLoader) load() error {
cert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath)
if err != nil {
return err
}
kpr.certMu.Lock()
kpr.cert = &cert
kpr.certMu.Unlock()
return nil
}
func (kpr *keypairLoader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
kpr.certMu.RLock()
defer kpr.certMu.RUnlock()
return kpr.cert, nil
}
}
================================================
FILE: core/hy/mmdb.go
================================================
package hy
import (
"os"
"github.com/oschwald/geoip2-golang"
"github.com/sirupsen/logrus"
)
func loadMMDBReader(filename string) (*geoip2.Reader, error) {
if _, err := os.Stat(filename); err != nil {
if os.IsNotExist(err) {
logrus.Info("GeoLite2 database not found, downloading...")
logrus.WithFields(logrus.Fields{
"file": filename,
}).Info("GeoLite2 database downloaded")
return geoip2.Open(filename)
} else {
// some other error
return nil, err
}
} else {
// file exists, just open it
return geoip2.Open(filename)
}
}
================================================
FILE: core/hy/node.go
================================================
package hy
import (
"errors"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/limiter"
)
func (h *Hy) AddNode(tag string, info *panel.NodeInfo, c *conf.ControllerConfig) error {
if info.Type != "hysteria" {
return errors.New("the core not support " + info.Type)
}
switch c.CertConfig.CertMode {
case "reality", "none", "":
return errors.New("hysteria need normal tls cert")
}
l, err := limiter.GetLimiter(tag)
if err != nil {
return fmt.Errorf("get limiter error: %s", err)
}
s := NewServer(tag, l)
err = s.runServer(info, c)
if err != nil {
return fmt.Errorf("run hy server error: %s", err)
}
h.servers.Store(tag, s)
return nil
}
func (h *Hy) DelNode(tag string) error {
if s, e := h.servers.Load(tag); e {
err := s.(*Server).Close()
if err != nil {
return err
}
h.servers.Delete(tag)
return nil
}
return errors.New("the node is not have")
}
================================================
FILE: core/hy/resolver.go
================================================
package hy
import (
"crypto/tls"
"errors"
"github.com/Yuzuki616/hysteria/core/utils"
rdns "github.com/folbricht/routedns"
"net"
"net/url"
"strings"
)
var errInvalidSyntax = errors.New("invalid syntax")
func setResolver(dns string) error {
if net.ParseIP(dns) != nil {
// Just an IP address, treat as UDP 53
dns = "udp://" + net.JoinHostPort(dns, "53")
}
var r rdns.Resolver
if strings.HasPrefix(dns, "udp://") {
// Standard UDP DNS resolver
dns = strings.TrimPrefix(dns, "udp://")
if dns == "" {
return errInvalidSyntax
}
if _, _, err := utils.SplitHostPort(dns); err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "53")
}
client, err := rdns.NewDNSClient("dns-udp", dns, "udp", rdns.DNSClientOptions{})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "tcp://") {
// Standard TCP DNS resolver
dns = strings.TrimPrefix(dns, "tcp://")
if dns == "" {
return errInvalidSyntax
}
if _, _, err := utils.SplitHostPort(dns); err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "53")
}
client, err := rdns.NewDNSClient("dns-tcp", dns, "tcp", rdns.DNSClientOptions{})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "https://") {
// DoH resolver
if dohURL, err := url.Parse(dns); err != nil {
return err
} else {
// Need to set bootstrap address to avoid loopback DNS lookup
dohIPAddr, err := net.ResolveIPAddr("ip", dohURL.Hostname())
if err != nil {
return err
}
client, err := rdns.NewDoHClient("doh", dns, rdns.DoHClientOptions{
BootstrapAddr: dohIPAddr.String(),
})
if err != nil {
return err
}
r = client
}
} else if strings.HasPrefix(dns, "tls://") {
// DoT resolver
dns = strings.TrimPrefix(dns, "tls://")
if dns == "" {
return errInvalidSyntax
}
dotHost, _, err := utils.SplitHostPort(dns)
if err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "853")
}
// Need to set bootstrap address to avoid loopback DNS lookup
dotIPAddr, err := net.ResolveIPAddr("ip", dotHost)
if err != nil {
return err
}
client, err := rdns.NewDoTClient("dot", dns, rdns.DoTClientOptions{
BootstrapAddr: dotIPAddr.String(),
TLSConfig: new(tls.Config),
})
if err != nil {
return err
}
r = client
} else if strings.HasPrefix(dns, "quic://") {
// DoQ resolver
dns = strings.TrimPrefix(dns, "quic://")
if dns == "" {
return errInvalidSyntax
}
doqHost, _, err := utils.SplitHostPort(dns)
if err != nil {
// Append the default DNS port
dns = net.JoinHostPort(dns, "853")
}
// Need to set bootstrap address to avoid loopback DNS lookup
doqIPAddr, err := net.ResolveIPAddr("ip", doqHost)
if err != nil {
return err
}
client, err := rdns.NewDoQClient("doq", dns, rdns.DoQClientOptions{
BootstrapAddr: doqIPAddr.String(),
})
if err != nil {
return err
}
r = client
} else {
return errInvalidSyntax
}
cache := rdns.NewCache("cache", r, rdns.CacheOptions{})
net.DefaultResolver = rdns.NewNetResolver(cache)
return nil
}
================================================
FILE: core/hy/server.go
================================================
package hy
import (
"crypto/tls"
"fmt"
"io"
"net"
"sync"
"sync/atomic"
"time"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/Yuzuki616/hysteria/core/sockopt"
"github.com/Yuzuki616/quic-go"
"github.com/Yuzuki616/hysteria/core/acl"
"github.com/Yuzuki616/hysteria/core/cs"
"github.com/Yuzuki616/hysteria/core/pktconns"
"github.com/Yuzuki616/hysteria/core/pmtud"
"github.com/Yuzuki616/hysteria/core/transport"
"github.com/sirupsen/logrus"
)
var serverPacketConnFuncFactoryMap = map[string]pktconns.ServerPacketConnFuncFactory{
"": pktconns.NewServerUDPConnFunc,
"udp": pktconns.NewServerUDPConnFunc,
"wechat": pktconns.NewServerWeChatConnFunc,
"wechat-video": pktconns.NewServerWeChatConnFunc,
"faketcp": pktconns.NewServerFakeTCPConnFunc,
}
type Server struct {
tag string
l *limiter.Limiter
counter *UserTrafficCounter
users sync.Map
running atomic.Bool
*cs.Server
}
func NewServer(tag string, l *limiter.Limiter) *Server {
return &Server{
tag: tag,
l: l,
}
}
func (s *Server) runServer(node *panel.NodeInfo, c *conf.ControllerConfig) error {
/*if c.HyOptions == nil {
return errors.New("hy options is not vail")
}*/
// Resolver
if len(c.HyOptions.Resolver) > 0 {
err := setResolver(c.HyOptions.Resolver)
if err != nil {
return fmt.Errorf("set resolver error: %s", err)
}
}
// tls config
kpl, err := newKeypairLoader(c.CertConfig.CertFile, c.CertConfig.KeyFile)
if err != nil {
return fmt.Errorf("load cert error: %s", err)
}
tlsConfig := &tls.Config{
GetCertificate: kpl.GetCertificateFunc(),
NextProtos: []string{DefaultALPN},
MinVersion: tls.VersionTLS13,
}
// QUIC config
quicConfig := &quic.Config{
InitialStreamReceiveWindow: DefaultStreamReceiveWindow,
MaxStreamReceiveWindow: DefaultStreamReceiveWindow,
InitialConnectionReceiveWindow: DefaultConnectionReceiveWindow,
MaxConnectionReceiveWindow: DefaultConnectionReceiveWindow,
MaxIncomingStreams: int64(DefaultMaxIncomingStreams),
MaxIdleTimeout: ServerMaxIdleTimeoutSec * time.Second,
KeepAlivePeriod: 0, // Keep alive should solely be client's responsibility
DisablePathMTUDiscovery: false,
EnableDatagrams: true,
}
if !quicConfig.DisablePathMTUDiscovery && pmtud.DisablePathMTUDiscovery {
logrus.Info("Path MTU Discovery is not yet supported on this platform")
}
// Resolve preference
if len(c.HyOptions.ResolvePreference) > 0 {
pref, err := transport.ResolvePreferenceFromString(c.HyOptions.Resolver)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to parse the resolve preference")
}
transport.DefaultServerTransport.ResolvePreference = pref
}
/*// SOCKS5 outbound
if config.SOCKS5Outbound.Server != "" {
transport.DefaultServerTransport.SOCKS5Client = transport.NewSOCKS5Client(config.SOCKS5Outbound.Server,
config.SOCKS5Outbound.User, config.SOCKS5Outbound.Password)
}*/
// Bind outbound
if c.HyOptions.SendDevice != "" {
iface, err := net.InterfaceByName(c.HyOptions.SendDevice)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to find the interface")
}
transport.DefaultServerTransport.LocalUDPIntf = iface
sockopt.BindDialer(transport.DefaultServerTransport.Dialer, iface)
}
if c.SendIP != "" {
ip := net.ParseIP(c.SendIP)
if ip == nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Fatal("Failed to parse the address")
}
transport.DefaultServerTransport.Dialer.LocalAddr = &net.TCPAddr{IP: ip}
transport.DefaultServerTransport.LocalUDPAddr = &net.UDPAddr{IP: ip}
}
// ACL
var aclEngine *acl.Engine
// Prometheus
s.counter = NewUserTrafficCounter()
// Packet conn
pktConnFuncFactory := serverPacketConnFuncFactoryMap[""]
if pktConnFuncFactory == nil {
return fmt.Errorf("unsopport protocol")
}
pktConnFunc := pktConnFuncFactory(node.HyObfs)
addr := fmt.Sprintf("%s:%d", c.ListenIP, node.Port)
pktConn, err := pktConnFunc(addr)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
"addr": addr,
}).Fatal("Failed to listen on the UDP address")
}
// Server
up, down := SpeedTrans(node.UpMbps, node.DownMbps)
s.Server, err = cs.NewServer(tlsConfig, quicConfig, pktConn,
transport.DefaultServerTransport, up, down, false, aclEngine,
s.connectFunc, s.disconnectFunc, tcpRequestFunc, tcpErrorFunc, udpRequestFunc, udpErrorFunc, s.counter)
if err != nil {
return fmt.Errorf("new server error: %s", err)
}
logrus.WithField("addr", addr).Info("Server up and running")
go func() {
s.running.Store(true)
defer func() {
s.running.Store(false)
}()
err = s.Server.Serve()
if err != nil {
logrus.WithField("addr", addr).Errorf("serve error: %s", err)
}
}()
return nil
}
func (s *Server) authByUser(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
if _, r := s.l.CheckLimit(string(auth), addr.String(), false); r {
return false, "device limited"
}
if _, ok := s.users.Load(string(auth)); ok {
return true, "Done"
}
return false, "Failed"
}
func (s *Server) connectFunc(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) {
s.l.ConnLimiter.AddConnCount(addr.String(), string(auth), false)
ok, msg := s.authByUser(addr, auth, sSend, sRecv)
if !ok {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
}).Info("Authentication failed, client rejected")
return false, msg
}
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"Uuid": string(auth),
"Tag": s.tag,
}).Info("Client connected")
return ok, msg
}
func (s *Server) disconnectFunc(addr net.Addr, auth []byte, err error) {
s.l.ConnLimiter.DelConnCount(addr.String(), string(auth))
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"error": err,
}).Info("Client disconnected")
}
func tcpRequestFunc(addr net.Addr, auth []byte, reqAddr string, action acl.Action, arg string) {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
"action": actionToString(action, arg),
}).Debug("TCP request")
}
func tcpErrorFunc(addr net.Addr, auth []byte, reqAddr string, err error) {
if err != io.EOF {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
"error": err,
}).Info("TCP error")
} else {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"dst": defaultIPMasker.Mask(reqAddr),
}).Debug("TCP EOF")
}
}
func udpRequestFunc(addr net.Addr, auth []byte, sessionID uint32) {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"session": sessionID,
}).Debug("UDP request")
}
func udpErrorFunc(addr net.Addr, auth []byte, sessionID uint32, err error) {
if err != io.EOF {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"session": sessionID,
"error": err,
}).Info("UDP error")
} else {
logrus.WithFields(logrus.Fields{
"src": defaultIPMasker.Mask(addr.String()),
"session": sessionID,
}).Debug("UDP EOF")
}
}
func actionToString(action acl.Action, arg string) string {
switch action {
case acl.ActionDirect:
return "Direct"
case acl.ActionProxy:
return "Proxy"
case acl.ActionBlock:
return "Block"
case acl.ActionHijack:
return "Hijack to " + arg
default:
return "Unknown"
}
}
================================================
FILE: core/hy/server_test.go
================================================
package hy
import (
"encoding/base64"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/Yuzuki616/V2bX/limiter"
"github.com/sirupsen/logrus"
"log"
"testing"
"time"
)
func TestServer(t *testing.T) {
logrus.SetLevel(logrus.DebugLevel)
limiter.Init()
l := limiter.AddLimiter("test", &conf.LimitConfig{}, nil)
s := NewServer("test", l)
t.Log(s.runServer(&panel.NodeInfo{
Port: 1145,
UpMbps: 100,
DownMbps: 100,
HyObfs: "atresssdaaaadd",
}, &conf.ControllerConfig{
ListenIP: "127.0.0.1",
HyOptions: conf.HyOptions{},
CertConfig: &conf.CertConfig{
CertFile: "../../test_data/1.pem",
KeyFile: "../../test_data/1.key",
},
}))
s.users.Store("test1111", struct{}{})
go func() {
for {
time.Sleep(10 * time.Second)
auth := base64.StdEncoding.EncodeToString([]byte("test1111"))
log.Println(auth)
log.Println(s.counter.getCounters(auth).UpCounter.Load())
}
}()
select {}
}
================================================
FILE: core/hy/user.go
================================================
package hy
import (
"encoding/base64"
"errors"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/core"
)
func (h *Hy) AddUsers(p *core.AddUsersParams) (int, error) {
s, ok := h.servers.Load(p.Tag)
if !ok {
return 0, errors.New("the node not have")
}
u := &s.(*Server).users
for i := range p.UserInfo {
u.Store(p.UserInfo[i].Uuid, struct{}{})
}
return len(p.UserInfo), nil
}
func (h *Hy) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
v, _ := h.servers.Load(tag)
s := v.(*Server)
auth := base64.StdEncoding.EncodeToString([]byte(uuid))
up = s.counter.getCounters(auth).UpCounter.Load()
down = s.counter.getCounters(auth).DownCounter.Load()
if reset {
s.counter.Reset(auth)
}
return
}
func (h *Hy) DelUsers(users []panel.UserInfo, tag string) error {
v, e := h.servers.Load(tag)
if !e {
return errors.New("the node is not have")
}
s := v.(*Server)
for i := range users {
s.users.Delete(users[i].Uuid)
s.counter.Delete(base64.StdEncoding.EncodeToString([]byte(users[i].Uuid)))
}
return nil
}
================================================
FILE: core/imports/hy.go
================================================
//go:build hy
package imports
// not yet tested
import _ "github.com/Yuzuki616/V2bX/core/hy"
================================================
FILE: core/imports/imports.go
================================================
package imports
================================================
FILE: core/imports/xray.go
================================================
//go:build xray
package imports
import _ "github.com/Yuzuki616/V2bX/core/xray"
================================================
FILE: core/interface.go
================================================
package core
import (
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
)
type AddUsersParams struct {
Tag string
Config *conf.ControllerConfig
UserInfo []panel.UserInfo
NodeInfo *panel.NodeInfo
}
type Core interface {
Start() error
Close() error
AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error
DelNode(tag string) error
AddUsers(p *AddUsersParams) (added int, err error)
GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64)
DelUsers(users []panel.UserInfo, tag string) error
Protocols() []string
}
================================================
FILE: core/selector.go
================================================
package core
import (
"errors"
"sync"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/hashicorp/go-multierror"
)
type Selector struct {
cores []Core
nodes sync.Map
}
func (s *Selector) Start() error {
for i := range s.cores {
err := s.cores[i].Start()
return err
}
return nil
}
func (s *Selector) Close() error {
var errs error
for i := range s.cores {
errs = multierror.Append(errs, s.cores[i].Close())
}
return errs
}
func isSupported(protocol string, protocols []string) bool {
for i := range protocols {
if protocol == protocols[i] {
return true
}
}
return false
}
func (s *Selector) AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error {
for i := range s.cores {
if !isSupported(info.Type, s.cores[i].Protocols()) {
continue
}
err := s.cores[i].AddNode(tag, info, config)
if err != nil {
return err
}
s.nodes.Store(tag, i)
return nil
}
return errors.New("the node type is not support")
}
func (s *Selector) DelNode(tag string) error {
if t, e := s.nodes.Load(tag); e {
err := s.cores[t.(int)].DelNode(tag)
if err != nil {
return err
}
s.nodes.Delete(tag)
return nil
}
return errors.New("the node is not have")
}
func (s *Selector) AddUsers(p *AddUsersParams) (added int, err error) {
t, e := s.nodes.Load(p.Tag)
if !e {
return 0, errors.New("the node is not have")
}
return s.cores[t.(int)].AddUsers(p)
}
func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
t, e := s.nodes.Load(tag)
if !e {
return 0, 0
}
return s.cores[t.(int)].GetUserTraffic(tag, uuid, reset)
}
func (s *Selector) DelUsers(users []panel.UserInfo, tag string) error {
t, e := s.nodes.Load(tag)
if !e {
return errors.New("the node is not have")
}
return s.cores[t.(int)].DelUsers(users, tag)
}
func (s *Selector) Protocols() []string {
protocols := make([]string, 0)
for i := range s.cores {
protocols = append(protocols, s.cores[i].Protocols()...)
}
return protocols
}
================================================
FILE: core/xray/app/app.go
================================================
// Package app contains the third-party app used to replace the default app in xray-core
package app
================================================
FILE: core/xray/app/dispatcher/config.pb.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.12
// source: config.proto
package dispatcher
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type SessionConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *SessionConfig) Reset() {
*x = SessionConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SessionConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SessionConfig) ProtoMessage() {}
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SessionConfig.ProtoReflect.Descriptor instead.
func (*SessionConfig) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{0}
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Settings *SessionConfig `protobuf:"bytes,1,opt,name=settings,proto3" json:"settings,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_config_proto_rawDescGZIP(), []int{1}
}
func (x *Config) GetSettings() *SessionConfig {
if x != nil {
return x.Settings
}
return nil
}
var File_config_proto protoreflect.FileDescriptor
var file_config_proto_rawDesc = []byte{
0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x18,
0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69,
0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x65, 0x73, 0x73,
0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22,
0x4d, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x43, 0x0a, 0x08, 0x73, 0x65, 0x74,
0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32,
0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70,
0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x6a,
0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x62, 0x78, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x61, 0x70, 0x70, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x50, 0x01,
0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x59, 0x75, 0x7a,
0x75, 0x6b, 0x69, 0x36, 0x31, 0x36, 0x2f, 0x56, 0x32, 0x62, 0x58, 0x2f, 0x63, 0x6f, 0x72, 0x65,
0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0xaa,
0x02, 0x18, 0x56, 0x32, 0x62, 0x58, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e,
0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_config_proto_rawDescOnce sync.Once
file_config_proto_rawDescData = file_config_proto_rawDesc
)
func file_config_proto_rawDescGZIP() []byte {
file_config_proto_rawDescOnce.Do(func() {
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
})
return file_config_proto_rawDescData
}
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_config_proto_goTypes = []interface{}{
(*SessionConfig)(nil), // 0: v2bx.core.app.dispatcher.SessionConfig
(*Config)(nil), // 1: v2bx.core.app.dispatcher.Config
}
var file_config_proto_depIdxs = []int32{
0, // 0: v2bx.core.app.dispatcher.Config.settings:type_name -> v2bx.core.app.dispatcher.SessionConfig
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_config_proto_init() }
func file_config_proto_init() {
if File_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SessionConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_config_proto_goTypes,
DependencyIndexes: file_config_proto_depIdxs,
MessageInfos: file_config_proto_msgTypes,
}.Build()
File_config_proto = out.File
file_config_proto_rawDesc = nil
file_config_proto_goTypes = nil
file_config_proto_depIdxs = nil
}
================================================
FILE: core/xray/app/dispatcher/config.proto
================================================
syntax = "proto3";
package v2bx.core.app.dispatcher;
option csharp_namespace = "V2bX.core.app.dispatcher";
option go_package = "github.com/Yuzuki616/V2bX/core/xray/app/dispatcher";
option java_package = "com.v2bx.core.app.dispatcher";
option java_multiple_files = true;
message SessionConfig {
reserved 1;
}
message Config {
SessionConfig settings = 1;
}
================================================
FILE: core/xray/app/dispatcher/default.go
================================================
package dispatcher
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/Yuzuki616/V2bX/common/rate"
"github.com/Yuzuki616/V2bX/limiter"
routingSession "github.com/xtls/xray-core/features/routing/session"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/pipe"
)
var errSniffingTimeout = newError("timeout on sniffing")
type cachedReader struct {
sync.Mutex
reader *pipe.Reader
cache buf.MultiBuffer
}
func (r *cachedReader) Cache(b *buf.Buffer) {
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100)
r.Lock()
if !mb.IsEmpty() {
r.cache, _ = buf.MergeMulti(r.cache, mb)
}
b.Clear()
rawBytes := b.Extend(buf.Size)
n := r.cache.Copy(rawBytes)
b.Resize(0, int32(n))
r.Unlock()
}
func (r *cachedReader) readInternal() buf.MultiBuffer {
r.Lock()
defer r.Unlock()
if r.cache != nil && !r.cache.IsEmpty() {
mb := r.cache
r.cache = nil
return mb
}
return nil
}
func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
mb := r.readInternal()
if mb != nil {
return mb, nil
}
return r.reader.ReadMultiBuffer()
}
func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (buf.MultiBuffer, error) {
mb := r.readInternal()
if mb != nil {
return mb, nil
}
return r.reader.ReadMultiBufferTimeout(timeout)
}
func (r *cachedReader) Interrupt() {
r.Lock()
if r.cache != nil {
r.cache = buf.ReleaseMulti(r.cache)
}
r.Unlock()
r.reader.Interrupt()
}
// DefaultDispatcher is a default implementation of Dispatcher.
type DefaultDispatcher struct {
ohm outbound.Manager
router routing.Router
policy policy.Manager
stats stats.Manager
dns dns.Client
fdns dns.FakeDNSEngine
}
func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
d := new(DefaultDispatcher)
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
d.fdns = fdns
})
return d.Init(config.(*Config), om, router, pm, sm, dc)
}); err != nil {
return nil, err
}
return d, nil
}))
}
// Init initializes DefaultDispatcher.
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error {
d.ohm = om
d.router = router
d.policy = pm
d.stats = sm
d.dns = dns
return nil
}
// Type implements common.HasType.
func (*DefaultDispatcher) Type() interface{} {
return routing.DispatcherType()
}
// Start implements common.Runnable.
func (*DefaultDispatcher) Start() error {
return nil
}
// Close implements common.Closable.
func (*DefaultDispatcher) Close() error { return nil }
func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network, sniffing session.SniffingRequest) (*transport.Link, *transport.Link, *limiter.Limiter, error) {
downOpt := pipe.OptionsFromContext(ctx)
upOpt := downOpt
if network == net.Network_UDP {
var ip2domain *sync.Map // net.IP.String() => domain, this map is used by server side when client turn on fakedns
// Client will send domain address in the buffer.UDP.Address, server record all possible target IP addrs.
// When target replies, server will restore the domain and send back to client.
// Note: this map is not global but per connection context
upOpt = append(upOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
for i, buffer := range mb {
if buffer.UDP == nil {
continue
}
addr := buffer.UDP.Address
if addr.Family().IsIP() {
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(addr) && sniffing.Enabled {
domain := fkr0.GetDomainFromFakeDNS(addr)
if len(domain) > 0 {
buffer.UDP.Address = net.DomainAddress(domain)
newError("[fakedns client] override with domain: ", domain, " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
} else {
newError("[fakedns client] failed to find domain! :", addr.String(), " for xUDP buffer at ", i).AtWarning().WriteToLog(session.ExportIDToError(ctx))
}
}
} else {
if ip2domain == nil {
ip2domain = new(sync.Map)
newError("[fakedns client] create a new map").WriteToLog(session.ExportIDToError(ctx))
}
domain := addr.Domain()
ips, err := d.dns.LookupIP(domain, dns.IPOption{true, true, false})
if err == nil {
for _, ip := range ips {
ip2domain.Store(ip.String(), domain)
}
newError("[fakedns client] candidate ip: "+fmt.Sprintf("%v", ips), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
} else {
newError("[fakedns client] failed to look up IP for ", domain, " for xUDP buffer at ", i).Base(err).WriteToLog(session.ExportIDToError(ctx))
}
}
}
return mb
}))
downOpt = append(downOpt, pipe.OnTransmission(func(mb buf.MultiBuffer) buf.MultiBuffer {
for i, buffer := range mb {
if buffer.UDP == nil {
continue
}
addr := buffer.UDP.Address
if addr.Family().IsIP() {
if ip2domain == nil {
continue
}
if domain, found := ip2domain.Load(addr.IP().String()); found {
buffer.UDP.Address = net.DomainAddress(domain.(string))
newError("[fakedns client] restore domain: ", domain.(string), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
}
} else {
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok {
fakeIp := fkr0.GetFakeIPForDomain(addr.Domain())
buffer.UDP.Address = fakeIp[0]
newError("[fakedns client] restore FakeIP: ", buffer.UDP, fmt.Sprintf("%v", fakeIp), " for xUDP buffer at ", i).WriteToLog(session.ExportIDToError(ctx))
}
}
}
return mb
}))
}
uplinkReader, uplinkWriter := pipe.New(upOpt...)
downlinkReader, downlinkWriter := pipe.New(downOpt...)
inboundLink := &transport.Link{
Reader: downlinkReader,
Writer: uplinkWriter,
}
outboundLink := &transport.Link{
Reader: uplinkReader,
Writer: downlinkWriter,
}
sessionInbound := session.InboundFromContext(ctx)
var user *protocol.MemoryUser
if sessionInbound != nil {
user = sessionInbound.User
}
var limit *limiter.Limiter
if user != nil && len(user.Email) > 0 {
var err error
limit, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil {
newError("Get limit info error: ", err).AtError().WriteToLog()
common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader)
return nil, nil, nil, newError("Get limit info error: ", err)
}
// Speed Limit and Device Limit
w, reject := limit.CheckLimit(user.Email,
sessionInbound.Source.Address.IP().String(),
network == net.Network_TCP)
if reject {
newError("Limited ", user.Email, " by conn or ip").AtWarning().WriteToLog()
common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader)
return nil, nil, nil, newError("Limited ", user.Email, " by conn or ip")
}
if w != nil {
inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w)
outboundLink.Writer = rate.NewRateLimitWriter(outboundLink.Writer, w)
}
p := d.policy.ForLevel(user.Level)
if p.Stats.UserUplink {
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
inboundLink.Writer = &SizeStatWriter{
Counter: c,
Writer: inboundLink.Writer,
}
}
}
if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
outboundLink.Writer = &SizeStatWriter{
Counter: c,
Writer: outboundLink.Writer,
}
}
}
}
return inboundLink, outboundLink, limit, nil
}
func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
domain := result.Domain()
if domain == "" {
return false
}
for _, d := range request.ExcludeForDomain {
if strings.ToLower(domain) == d {
return false
}
}
protocolString := result.Protocol()
if resComp, ok := result.(SnifferResultComposite); ok {
protocolString = resComp.ProtocolForDomainResult()
}
for _, p := range request.OverrideDestinationForProtocol {
if strings.HasPrefix(protocolString, p) {
return true
}
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
return true
}
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
if resultSubset.IsProtoSubsetOf(p) {
return true
}
}
}
return false
}
// Dispatch implements routing.Dispatcher.
func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {
if !destination.IsValid() {
panic("Dispatcher: Invalid destination.")
}
ob := &session.Outbound{
Target: destination,
}
ctx = session.ContextWithOutbound(ctx, ob)
content := session.ContentFromContext(ctx)
if content == nil {
content = new(session.Content)
ctx = session.ContextWithContent(ctx, content)
}
sniffingRequest := content.SniffingRequest
inbound, outbound, l, err := d.getLink(ctx, destination.Network, sniffingRequest)
if err != nil {
return nil, err
}
if !sniffingRequest.Enabled {
go d.routedDispatch(ctx, outbound, destination, l)
} else {
go func() {
cReader := &cachedReader{
reader: outbound.Reader.(*pipe.Reader),
}
outbound.Reader = cReader
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network, l)
if _, ok := err.(limitedError); ok {
newError(err).AtInfo().WriteToLog()
common.Close(outbound.Writer)
common.Interrupt(outbound.Reader)
return
}
if err == nil {
content.Protocol = result.Protocol()
}
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
domain := result.Domain()
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain)
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
ob.RouteTarget = destination
} else {
ob.Target = destination
}
}
d.routedDispatch(ctx, outbound, destination, l)
}()
}
return inbound, nil
}
// DispatchLink implements routing.Dispatcher.
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
if !destination.IsValid() {
return newError("Dispatcher: Invalid destination.")
}
ob := &session.Outbound{
Target: destination,
}
ctx = session.ContextWithOutbound(ctx, ob)
content := session.ContentFromContext(ctx)
if content == nil {
content = new(session.Content)
ctx = session.ContextWithContent(ctx, content)
}
sniffingRequest := content.SniffingRequest
if !sniffingRequest.Enabled {
go d.routedDispatch(ctx, outbound, destination, nil)
} else {
go func() {
cReader := &cachedReader{
reader: outbound.Reader.(*pipe.Reader),
}
outbound.Reader = cReader
result, err := sniffer(ctx, cReader, sniffingRequest.MetadataOnly, destination.Network, nil)
if _, ok := err.(limitedError); ok {
newError(err).AtInfo().WriteToLog()
common.Close(outbound.Writer)
common.Interrupt(outbound.Reader)
return
}
if err == nil {
content.Protocol = result.Protocol()
}
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
domain := result.Domain()
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain)
if sniffingRequest.RouteOnly && result.Protocol() != "fakedns" {
ob.RouteTarget = destination
} else {
ob.Target = destination
}
}
d.routedDispatch(ctx, outbound, destination, nil)
}()
}
return nil
}
type limitedError string
func (l limitedError) Error() string {
return string(l)
}
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network, l *limiter.Limiter) (result SniffResult, err error) {
payload := buf.New()
defer payload.Release()
defer func() {
if err != nil {
return
}
// Check if domain and protocol hit the rule
sessionInbound := session.InboundFromContext(ctx)
// Whether the inbound connection contains a user
if sessionInbound.User != nil {
if l == nil {
l, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil {
return
}
}
if l.CheckDomainRule(result.Domain()) {
err = limitedError(fmt.Sprintf(
"User %s access domain %s reject by rule",
sessionInbound.User.Email,
result.Domain()))
}
if l.CheckProtocolRule(result.Protocol()) {
err = limitedError(fmt.Sprintf(
"User %s access protocol %s reject by rule",
sessionInbound.User.Email,
result.Protocol()))
}
}
}()
sniffer := NewSniffer(ctx)
metaresult, metadataErr := sniffer.SniffMetadata(ctx)
if metadataOnly {
return metaresult, metadataErr
}
contentResult, contentErr := func() (SniffResult, error) {
totalAttempt := 0
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
totalAttempt++
if totalAttempt > 2 {
return nil, errSniffingTimeout
}
cReader.Cache(payload)
if !payload.IsEmpty() {
result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
if err != common.ErrNoClue {
return result, err
}
}
if payload.IsFull() {
return nil, errUnknownContent
}
}
}
}()
if contentErr != nil && metadataErr == nil {
return metaresult, nil
}
if contentErr == nil && metadataErr == nil {
return CompositeResult(metaresult, contentResult), nil
}
return contentResult, contentErr
}
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter) {
ob := session.OutboundFromContext(ctx)
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
proxied := hosts.LookupHosts(ob.Target.String())
if proxied != nil {
ro := ob.RouteTarget == destination
destination.Address = *proxied
if ro {
ob.RouteTarget = destination
} else {
ob.Target = destination
}
}
}
var handler outbound.Handler
// del connect count
if l != nil {
sessionInbound := session.InboundFromContext(ctx)
if sessionInbound.User != nil {
if destination.Network == net.Network_TCP {
defer func() {
l.ConnLimiter.DelConnCount(sessionInbound.User.Email, sessionInbound.Source.Address.IP().String())
}()
}
}
}
routingLink := routingSession.AsRoutingContext(ctx)
inTag := routingLink.GetInboundTag()
isPickRoute := 0
if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" {
ctx = session.SetForcedOutboundTagToContext(ctx, "")
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
isPickRoute = 1
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
handler = h
} else {
newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer)
common.Interrupt(link.Reader)
return
}
} else if d.router != nil {
if route, err := d.router.PickRoute(routingLink); err == nil {
outTag := route.GetOutboundTag()
if h := d.ohm.GetHandler(outTag); h != nil {
isPickRoute = 2
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
handler = h
} else {
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
}
} else {
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
}
}
if handler == nil {
handler = d.ohm.GetHandler(inTag) // Default outbound handier tag should be as same as the inbound tag
}
// If there is no outbound with tag as same as the inbound tag
if handler == nil {
handler = d.ohm.GetDefaultHandler()
}
if handler == nil {
newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer)
common.Interrupt(link.Reader)
return
}
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
if tag := handler.Tag(); tag != "" {
if inTag == "" {
accessMessage.Detour = tag
} else if isPickRoute == 1 {
accessMessage.Detour = inTag + " ==> " + tag
} else if isPickRoute == 2 {
accessMessage.Detour = inTag + " -> " + tag
} else {
accessMessage.Detour = inTag + " >> " + tag
}
}
log.Record(accessMessage)
}
handler.Dispatch(ctx, link)
}
================================================
FILE: core/xray/app/dispatcher/dispatcher.go
================================================
package dispatcher
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
================================================
FILE: core/xray/app/dispatcher/errors.generated.go
================================================
package dispatcher
import "github.com/xtls/xray-core/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}
================================================
FILE: core/xray/app/dispatcher/fakednssniffer.go
================================================
package dispatcher
import (
"context"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/dns"
)
// newFakeDNSSniffer Creates a Fake DNS metadata sniffer
func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error) {
var fakeDNSEngine dns.FakeDNSEngine
{
fakeDNSEngineFeat := core.MustFromContext(ctx).GetFeature((*dns.FakeDNSEngine)(nil))
if fakeDNSEngineFeat != nil {
fakeDNSEngine = fakeDNSEngineFeat.(dns.FakeDNSEngine)
}
}
if fakeDNSEngine == nil {
errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
return protocolSnifferWithMetadata{}, errNotInit
}
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
Target := session.OutboundFromContext(ctx).Target
if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP {
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
if domainFromFakeDNS != "" {
newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx))
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
}
}
if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
inPool := fkr0.IsIPInIPPool(Target.Address)
ipAddressInRangeValue.addressInRange = &inPool
}
}
return nil, common.ErrNoClue
}, metadataSniffer: true}, nil
}
type fakeDNSSniffResult struct {
domainName string
}
func (fakeDNSSniffResult) Protocol() string {
return "fakedns"
}
func (f fakeDNSSniffResult) Domain() string {
return f.domainName
}
type fakeDNSExtraOpts int
const ipAddressInRange fakeDNSExtraOpts = 1
type ipAddressInRangeOpt struct {
addressInRange *bool
}
type DNSThenOthersSniffResult struct {
domainName string
protocolOriginalName string
}
func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool {
return strings.HasPrefix(protocolName, f.protocolOriginalName)
}
func (DNSThenOthersSniffResult) Protocol() string {
return "fakedns+others"
}
func (f DNSThenOthersSniffResult) Domain() string {
return f.domainName
}
func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) (
protocolSnifferWithMetadata, error,
) { // nolint: unparam
// ctx may be used in the future
_ = ctx
return protocolSnifferWithMetadata{
protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
ipAddressInRangeValue := &ipAddressInRangeOpt{}
ctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue)
result, err := fakeDNSSniffer.protocolSniffer(ctx, bytes)
if err == nil {
return result, nil
}
if ipAddressInRangeValue.addressInRange != nil {
if *ipAddressInRangeValue.addressInRange {
for _, v := range others {
if v.metadataSniffer || bytes != nil {
if result, err := v.protocolSniffer(ctx, bytes); err == nil {
return DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil
}
}
}
return nil, common.ErrNoClue
}
newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
return nil, common.ErrNoClue
}
newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
return nil, common.ErrNoClue
},
metadataSniffer: false,
}, nil
}
================================================
FILE: core/xray/app/dispatcher/sniffer.go
================================================
package dispatcher
import (
"context"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol/bittorrent"
"github.com/xtls/xray-core/common/protocol/http"
"github.com/xtls/xray-core/common/protocol/quic"
"github.com/xtls/xray-core/common/protocol/tls"
)
type SniffResult interface {
Protocol() string
Domain() string
}
type protocolSniffer func(context.Context, []byte) (SniffResult, error)
type protocolSnifferWithMetadata struct {
protocolSniffer protocolSniffer
// A Metadata sniffer will be invoked on connection establishment only, with nil body,
// for both TCP and UDP connections
// It will not be shown as a traffic type for routing unless there is no other successful sniffing.
metadataSniffer bool
network net.Network
}
type Sniffer struct {
sniffer []protocolSnifferWithMetadata
}
func NewSniffer(ctx context.Context) *Sniffer {
ret := &Sniffer{
sniffer: []protocolSnifferWithMetadata{
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP},
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffUTP(b) }, false, net.Network_UDP},
},
}
if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
others := ret.sniffer
ret.sniffer = append(ret.sniffer, sniffer)
fakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others)
if err == nil {
ret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...)
}
}
return ret
}
var errUnknownContent = newError("unknown content")
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
var pendingSniffer []protocolSnifferWithMetadata
for _, si := range s.sniffer {
s := si.protocolSniffer
if si.metadataSniffer || si.network != network {
continue
}
result, err := s(c, payload)
if err == common.ErrNoClue {
pendingSniffer = append(pendingSniffer, si)
continue
}
if err == nil && result != nil {
return result, nil
}
}
if len(pendingSniffer) > 0 {
s.sniffer = pendingSniffer
return nil, common.ErrNoClue
}
return nil, errUnknownContent
}
func (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) {
var pendingSniffer []protocolSnifferWithMetadata
for _, si := range s.sniffer {
s := si.protocolSniffer
if !si.metadataSniffer {
pendingSniffer = append(pendingSniffer, si)
continue
}
result, err := s(c, nil)
if err == common.ErrNoClue {
pendingSniffer = append(pendingSniffer, si)
continue
}
if err == nil && result != nil {
return result, nil
}
}
if len(pendingSniffer) > 0 {
s.sniffer = pendingSniffer
return nil, common.ErrNoClue
}
return nil, errUnknownContent
}
func CompositeResult(domainResult SniffResult, protocolResult SniffResult) SniffResult {
return &compositeResult{domainResult: domainResult, protocolResult: protocolResult}
}
type compositeResult struct {
domainResult SniffResult
protocolResult SniffResult
}
func (c compositeResult) Protocol() string {
return c.protocolResult.Protocol()
}
func (c compositeResult) Domain() string {
return c.domainResult.Domain()
}
func (c compositeResult) ProtocolForDomainResult() string {
return c.domainResult.Protocol()
}
type SnifferResultComposite interface {
ProtocolForDomainResult() string
}
type SnifferIsProtoSubsetOf interface {
IsProtoSubsetOf(protocolName string) bool
}
================================================
FILE: core/xray/app/dispatcher/stats.go
================================================
package dispatcher
import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/features/stats"
)
type SizeStatWriter struct {
Counter stats.Counter
Writer buf.Writer
}
func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.Counter.Add(int64(mb.Len()))
return w.Writer.WriteMultiBuffer(mb)
}
func (w *SizeStatWriter) Close() error {
return common.Close(w.Writer)
}
func (w *SizeStatWriter) Interrupt() {
common.Interrupt(w.Writer)
}
================================================
FILE: core/xray/app/dispatcher/stats_test.go
================================================
package dispatcher_test
import (
"testing"
. "github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
)
type TestCounter int64
func (c *TestCounter) Value() int64 {
return int64(*c)
}
func (c *TestCounter) Add(v int64) int64 {
x := int64(*c) + v
*c = TestCounter(x)
return x
}
func (c *TestCounter) Set(v int64) int64 {
*c = TestCounter(v)
return v
}
func TestStatsWriter(t *testing.T) {
var c TestCounter
writer := &SizeStatWriter{
Counter: &c,
Writer: buf.Discard,
}
mb := buf.MergeBytes(nil, []byte("abcd"))
common.Must(writer.WriteMultiBuffer(mb))
mb = buf.MergeBytes(nil, []byte("efg"))
common.Must(writer.WriteMultiBuffer(mb))
if c.Value() != 7 {
t.Fatal("unexpected counter value. want 7, but got ", c.Value())
}
}
================================================
FILE: core/xray/distro/all/all.go
================================================
package all
import (
// The following are necessary as they register handlers in their init functions.
// Mandatory features. Can't remove unless there are replacements.
_ "github.com/xtls/xray-core/app/dispatcher"
_ "github.com/xtls/xray-core/app/proxyman/inbound"
_ "github.com/xtls/xray-core/app/proxyman/outbound"
// Default commander and all its services. This is an optional feature.
//_ "github.com/xtls/xray-core/app/commander"
//_ "github.com/xtls/xray-core/app/log/command"
//_ "github.com/xtls/xray-core/app/proxyman/command"
//_ "github.com/xtls/xray-core/app/stats/command"
// Developer preview services
//_ "github.com/xtls/xray-core/app/observatory/command"
// Other optional features.
_ "github.com/xtls/xray-core/app/dns"
_ "github.com/xtls/xray-core/app/dns/fakedns"
_ "github.com/xtls/xray-core/app/log"
_ "github.com/xtls/xray-core/app/metrics"
_ "github.com/xtls/xray-core/app/policy"
_ "github.com/xtls/xray-core/app/reverse"
_ "github.com/xtls/xray-core/app/router"
_ "github.com/xtls/xray-core/app/stats"
// Fix dependency cycle caused by core import in internet package
_ "github.com/xtls/xray-core/transport/internet/tagged/taggedimpl"
// Developer preview features
//_ "github.com/xtls/xray-core/app/observatory"
// Inbound and outbound proxies.
_ "github.com/xtls/xray-core/proxy/blackhole"
_ "github.com/xtls/xray-core/proxy/dns"
_ "github.com/xtls/xray-core/proxy/dokodemo"
_ "github.com/xtls/xray-core/proxy/freedom"
_ "github.com/xtls/xray-core/proxy/http"
_ "github.com/xtls/xray-core/proxy/loopback"
_ "github.com/xtls/xray-core/proxy/shadowsocks"
_ "github.com/xtls/xray-core/proxy/socks"
_ "github.com/xtls/xray-core/proxy/trojan"
_ "github.com/xtls/xray-core/proxy/vless/inbound"
_ "github.com/xtls/xray-core/proxy/vless/outbound"
_ "github.com/xtls/xray-core/proxy/vmess/inbound"
_ "github.com/xtls/xray-core/proxy/vmess/outbound"
//_ "github.com/xtls/xray-core/proxy/wireguard"
// Transports
//_ "github.com/xtls/xray-core/transport/internet/domainsocket"
_ "github.com/xtls/xray-core/transport/internet/grpc"
_ "github.com/xtls/xray-core/transport/internet/http"
//_ "github.com/xtls/xray-core/transport/internet/kcp"
//_ "github.com/xtls/xray-core/transport/internet/quic"
_ "github.com/xtls/xray-core/transport/internet/reality"
_ "github.com/xtls/xray-core/transport/internet/tcp"
_ "github.com/xtls/xray-core/transport/internet/tls"
_ "github.com/xtls/xray-core/transport/internet/udp"
_ "github.com/xtls/xray-core/transport/internet/websocket"
// Transport headers
_ "github.com/xtls/xray-core/transport/internet/headers/http"
_ "github.com/xtls/xray-core/transport/internet/headers/noop"
_ "github.com/xtls/xray-core/transport/internet/headers/srtp"
_ "github.com/xtls/xray-core/transport/internet/headers/tls"
_ "github.com/xtls/xray-core/transport/internet/headers/utp"
_ "github.com/xtls/xray-core/transport/internet/headers/wechat"
_ "github.com/xtls/xray-core/transport/internet/headers/wireguard"
)
================================================
FILE: core/xray/inbound.go
================================================
package xray
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strconv"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core"
coreConf "github.com/xtls/xray-core/infra/conf"
)
// BuildInbound build Inbound config for different protocol
func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, tag string) (*core.InboundHandlerConfig, error) {
in := &coreConf.InboundDetourConfig{}
// Set network protocol
t := coreConf.TransportProtocol(nodeInfo.Network)
in.StreamSetting = &coreConf.StreamConfig{Network: &t}
var err error
switch nodeInfo.Type {
case "v2ray":
err = buildV2ray(config, nodeInfo, in)
case "trojan":
err = buildTrojan(config, in)
case "shadowsocks":
err = buildShadowsocks(config, nodeInfo, in)
default:
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks", nodeInfo.Type)
}
if err != nil {
return nil, err
}
// Set server port
in.PortList = &coreConf.PortList{
Range: []coreConf.PortRange{{From: uint32(nodeInfo.Port), To: uint32(nodeInfo.Port)}},
}
// Set Listen IP address
ipAddress := net.ParseAddress(config.ListenIP)
in.ListenOn = &coreConf.Address{Address: ipAddress}
// Set SniffingConfig
sniffingConfig := &coreConf.SniffingConfig{
Enabled: true,
DestOverride: &coreConf.StringList{"http", "tls"},
}
if config.XrayOptions.DisableSniffing {
sniffingConfig.Enabled = false
}
in.SniffingConfig = sniffingConfig
if *in.StreamSetting.Network == "tcp" {
if in.StreamSetting.TCPSettings != nil {
in.StreamSetting.TCPSettings.AcceptProxyProtocol = config.XrayOptions.EnableProxyProtocol
} else {
tcpSetting := &coreConf.TCPConfig{
AcceptProxyProtocol: config.XrayOptions.EnableProxyProtocol,
} //Enable proxy protocol
in.StreamSetting.TCPSettings = tcpSetting
}
} else if *in.StreamSetting.Network == "ws" {
in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
AcceptProxyProtocol: config.XrayOptions.EnableProxyProtocol} //Enable proxy protocol
}
// Set TLS or Reality settings
if nodeInfo.Tls {
if config.CertConfig == nil {
return nil, errors.New("the CertConfig is not vail")
}
switch config.CertConfig.CertMode {
case "none", "":
break // disable
case "reality":
// Reality
in.StreamSetting.Security = "reality"
d, err := json.Marshal(config.CertConfig.RealityConfig.Dest)
if err != nil {
return nil, fmt.Errorf("marshal reality dest error: %s", err)
}
if len(config.CertConfig.RealityConfig.ShortIds) == 0 {
config.CertConfig.RealityConfig.ShortIds = []string{""}
}
in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{
Dest: d,
Xver: config.CertConfig.RealityConfig.Xver,
ServerNames: config.CertConfig.RealityConfig.ServerNames,
PrivateKey: config.CertConfig.RealityConfig.PrivateKey,
MinClientVer: config.CertConfig.RealityConfig.MinClientVer,
MaxClientVer: config.CertConfig.RealityConfig.MaxClientVer,
MaxTimeDiff: config.CertConfig.RealityConfig.MaxTimeDiff,
ShortIds: config.CertConfig.RealityConfig.ShortIds,
}
break
case "remote":
if nodeInfo.ExtraConfig.EnableReality == "true" {
rc := nodeInfo.ExtraConfig.RealityConfig
in.StreamSetting.Security = "reality"
d, err := json.Marshal(rc.Dest)
if err != nil {
return nil, fmt.Errorf("marshal reality dest error: %s", err)
}
if len(rc.ShortIds) == 0 {
rc.ShortIds = []string{""}
}
Xver, _ := strconv.ParseUint(rc.Xver, 10, 64)
MaxTimeDiff, _ := strconv.ParseUint(rc.Xver, 10, 64)
in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{
Dest: d,
Xver: Xver,
ServerNames: rc.ServerNames,
PrivateKey: rc.PrivateKey,
MinClientVer: rc.MinClientVer,
MaxClientVer: rc.MaxClientVer,
MaxTimeDiff: MaxTimeDiff,
ShortIds: rc.ShortIds,
}
break
}
default:
{
// Normal tls
in.StreamSetting.Security = "tls"
in.StreamSetting.TLSSettings = &coreConf.TLSConfig{
Certs: []*coreConf.TLSCertConfig{
{
CertFile: config.CertConfig.CertFile,
KeyFile: config.CertConfig.KeyFile,
OcspStapling: 3600,
},
},
RejectUnknownSNI: config.CertConfig.RejectUnknownSni,
}
}
}
}
// Support ProxyProtocol for any transport protocol
if *in.StreamSetting.Network != "tcp" &&
*in.StreamSetting.Network != "ws" &&
config.XrayOptions.EnableProxyProtocol {
socketConfig := &coreConf.SocketConfig{
AcceptProxyProtocol: config.XrayOptions.EnableProxyProtocol,
TFO: config.XrayOptions.EnableTFO,
} //Enable proxy protocol
in.StreamSetting.SocketSettings = socketConfig
}
in.Tag = tag
return in.Build()
}
func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
if nodeInfo.ExtraConfig.EnableVless == "true" {
//Set vless
inbound.Protocol = "vless"
if config.XrayOptions.EnableFallback {
// Set fallback
fallbackConfigs, err := buildVlessFallbacks(config.XrayOptions.FallBackConfigs)
if err != nil {
return err
}
s, err := json.Marshal(&coreConf.VLessInboundConfig{
Decryption: "none",
Fallbacks: fallbackConfigs,
})
if err != nil {
return fmt.Errorf("marshal vless fallback config error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
} else {
var err error
s, err := json.Marshal(&coreConf.VLessInboundConfig{
Decryption: "none",
})
if err != nil {
return fmt.Errorf("marshal vless config error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
}
} else {
// Set vmess
inbound.Protocol = "vmess"
var err error
s, err := json.Marshal(&coreConf.VMessInboundConfig{})
if err != nil {
return fmt.Errorf("marshal vmess settings error: %s", err)
}
inbound.Settings = (*json.RawMessage)(&s)
}
if len(nodeInfo.NetworkSettings) == 0 {
return nil
}
switch nodeInfo.Network {
case "tcp":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.TCPSettings)
if err != nil {
return fmt.Errorf("unmarshal tcp settings error: %s", err)
}
case "ws":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.WSSettings)
if err != nil {
return fmt.Errorf("unmarshal ws settings error: %s", err)
}
case "grpc":
err := json.Unmarshal(nodeInfo.NetworkSettings, &inbound.StreamSetting.GRPCConfig)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
default:
return errors.New("the network type is not vail")
}
return nil
}
func buildTrojan(config *conf.ControllerConfig, inbound *coreConf.InboundDetourConfig) error {
inbound.Protocol = "trojan"
if config.XrayOptions.EnableFallback {
// Set fallback
fallbackConfigs, err := buildTrojanFallbacks(config.XrayOptions.FallBackConfigs)
if err != nil {
return err
}
s, err := json.Marshal(&coreConf.TrojanServerConfig{
Fallbacks: fallbackConfigs,
})
inbound.Settings = (*json.RawMessage)(&s)
if err != nil {
return fmt.Errorf("marshal trojan fallback config error: %s", err)
}
} else {
s := []byte("{}")
inbound.Settings = (*json.RawMessage)(&s)
}
t := coreConf.TransportProtocol("tcp")
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
return nil
}
func buildShadowsocks(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
inbound.Protocol = "shadowsocks"
settings := &coreConf.ShadowsocksServerConfig{
Cipher: nodeInfo.Cipher,
}
p := make([]byte, 32)
_, err := rand.Read(p)
if err != nil {
return fmt.Errorf("generate random password error: %s", err)
}
randomPasswd := hex.EncodeToString(p)
cipher := nodeInfo.Cipher
if nodeInfo.ServerKey != "" {
settings.Password = nodeInfo.ServerKey
randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
cipher = ""
}
defaultSSuser := &coreConf.ShadowsocksUserConfig{
Cipher: cipher,
Password: randomPasswd,
}
settings.Users = append(settings.Users, defaultSSuser)
settings.NetworkList = &coreConf.NetworkList{"tcp", "udp"}
settings.IVCheck = true
if config.XrayOptions.DisableIVCheck {
settings.IVCheck = false
}
t := coreConf.TransportProtocol("tcp")
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
s, err := json.Marshal(settings)
inbound.Settings = (*json.RawMessage)(&s)
if err != nil {
return fmt.Errorf("marshal shadowsocks settings error: %s", err)
}
return nil
}
func buildVlessFallbacks(fallbackConfigs []conf.FallBackConfig) ([]*coreConf.VLessInboundFallback, error) {
if fallbackConfigs == nil {
return nil, fmt.Errorf("you must provide FallBackConfigs")
}
vlessFallBacks := make([]*coreConf.VLessInboundFallback, len(fallbackConfigs))
for i, c := range fallbackConfigs {
if c.Dest == "" {
return nil, fmt.Errorf("dest is required for fallback fialed")
}
var dest json.RawMessage
dest, err := json.Marshal(c.Dest)
if err != nil {
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
}
vlessFallBacks[i] = &coreConf.VLessInboundFallback{
Name: c.SNI,
Alpn: c.Alpn,
Path: c.Path,
Dest: dest,
Xver: c.ProxyProtocolVer,
}
}
return vlessFallBacks, nil
}
func buildTrojanFallbacks(fallbackConfigs []conf.FallBackConfig) ([]*coreConf.TrojanInboundFallback, error) {
if fallbackConfigs == nil {
return nil, fmt.Errorf("you must provide FallBackConfigs")
}
trojanFallBacks := make([]*coreConf.TrojanInboundFallback, len(fallbackConfigs))
for i, c := range fallbackConfigs {
if c.Dest == "" {
return nil, fmt.Errorf("dest is required for fallback fialed")
}
var dest json.RawMessage
dest, err := json.Marshal(c.Dest)
if err != nil {
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
}
trojanFallBacks[i] = &coreConf.TrojanInboundFallback{
Name: c.SNI,
Alpn: c.Alpn,
Path: c.Path,
Dest: dest,
Xver: c.ProxyProtocolVer,
}
}
return trojanFallBacks, nil
}
================================================
FILE: core/xray/node.go
================================================
package xray
import (
"context"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/conf"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/inbound"
"github.com/xtls/xray-core/features/outbound"
)
func (c *Core) AddNode(tag string, info *panel.NodeInfo, config *conf.ControllerConfig) error {
inboundConfig, err := buildInbound(config, info, tag)
if err != nil {
return fmt.Errorf("build inbound error: %s", err)
}
err = c.addInbound(inboundConfig)
if err != nil {
return fmt.Errorf("add inbound error: %s", err)
}
outBoundConfig, err := buildOutbound(config, tag)
if err != nil {
return fmt.Errorf("build outbound error: %s", err)
}
err = c.addOutbound(outBoundConfig)
if err != nil {
return fmt.Errorf("add outbound error: %s", err)
}
return nil
}
func (c *Core) addInbound(config *core.InboundHandlerConfig) error {
rawHandler, err := core.CreateObject(c.Server, config)
if err != nil {
return err
}
handler, ok := rawHandler.(inbound.Handler)
if !ok {
return fmt.Errorf("not an InboundHandler: %s", err)
}
if err := c.ihm.AddHandler(context.Background(), handler); err != nil {
return err
}
return nil
}
func (c *Core) addOutbound(config *core.OutboundHandlerConfig) error {
rawHandler, err := core.CreateObject(c.Server, config)
if err != nil {
return err
}
handler, ok := rawHandler.(outbound.Handler)
if !ok {
return fmt.Errorf("not an InboundHandler: %s", err)
}
if err := c.ohm.AddHandler(context.Background(), handler); err != nil {
return err
}
return nil
}
func (c *Core) DelNode(tag string) error {
err := c.removeInbound(tag)
if err != nil {
return fmt.Errorf("remove in error: %s", err)
}
err = c.removeOutbound(tag)
if err != nil {
return fmt.Errorf("remove out error: %s", err)
}
return nil
}
func (c *Core) removeInbound(tag string) error {
return c.ihm.RemoveHandler(context.Background(), tag)
}
func (c *Core) removeOutbound(tag string) error {
err := c.ohm.RemoveHandler(context.Background(), tag)
return err
}
================================================
FILE: core/xray/outbound.go
================================================
package xray
import (
"fmt"
conf2 "github.com/Yuzuki616/V2bX/conf"
"github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf"
)
// BuildOutbound build freedom outbund config for addoutbound
func buildOutbound(config *conf2.ControllerConfig, tag string) (*core.OutboundHandlerConfig, error) {
outboundDetourConfig := &conf.OutboundDetourConfig{}
outboundDetourConfig.Protocol = "freedom"
outboundDetourConfig.Tag = tag
// Build Send IP address
if config.SendIP != "" {
ipAddress := net.ParseAddress(config.SendIP)
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
}
// Freedom Protocol setting
var domainStrategy = "Asis"
if config.XrayOptions.EnableDNS {
if config.XrayOptions.DNSType != "" {
domainStrategy = config.XrayOptions.DNSType
} else {
domainStrategy = "UseIP"
}
}
proxySetting := &conf.FreedomConfig{
DomainStrategy: domainStrategy,
}
var setting json.RawMessage
setting, err := json.Marshal(proxySetting)
if err != nil {
return nil, fmt.Errorf("marshal proxy config error: %s", err)
}
outboundDetourConfig.Settings = &setting
return outboundDetourConfig.Build()
}
================================================
FILE: core/xray/ss.go
================================================
package xray
import (
"encoding/base64"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/common/format"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/proxy/shadowsocks"
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
"strings"
)
func buildSSUsers(tag string, userInfo []panel.UserInfo, cypher string, serverKey string) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i := range userInfo {
users[i] = buildSSUser(tag, &userInfo[i], cypher, serverKey)
}
return users
}
func buildSSUser(tag string, userInfo *panel.UserInfo, cypher string, serverKey string) (user *protocol.User) {
if serverKey == "" {
ssAccount := &shadowsocks.Account{
Password: userInfo.Uuid,
CipherType: getCipherFromString(cypher),
}
return &protocol.User{
Level: 0,
Email: format.UserTag(tag, userInfo.Uuid),
Account: serial.ToTypedMessage(ssAccount),
}
} else {
var keyLength int
switch cypher {
case "2022-blake3-aes-128-gcm":
keyLength = 16
case "2022-blake3-aes-256-gcm":
keyLength = 32
}
ssAccount := &shadowsocks_2022.User{
Key: base64.StdEncoding.EncodeToString([]byte(userInfo.Uuid[:keyLength])),
}
return &protocol.User{
Level: 0,
Email: format.UserTag(tag, userInfo.Uuid),
Account: serial.ToTypedMessage(ssAccount),
}
}
}
func getCipherFromString(c string) shadowsocks.CipherType {
switch strings.ToLower(c) {
case "aes-128-gcm", "aead_aes_128_gcm":
return shadowsocks.CipherType_AES_128_GCM
case "aes-256-gcm", "aead_aes_256_gcm":
return shadowsocks.CipherType_AES_256_GCM
case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305":
return shadowsocks.CipherType_CHACHA20_POLY1305
case "none", "plain":
return shadowsocks.CipherType_NONE
default:
return shadowsocks.CipherType_UNKNOWN
}
}
================================================
FILE: core/xray/trojan.go
================================================
package xray
import (
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/common/format"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/proxy/trojan"
)
func buildTrojanUsers(tag string, userInfo []panel.UserInfo) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i := range userInfo {
users[i] = buildTrojanUser(tag, &(userInfo)[i])
}
return users
}
func buildTrojanUser(tag string, userInfo *panel.UserInfo) (user *protocol.User) {
trojanAccount := &trojan.Account{
Password: userInfo.Uuid,
}
return &protocol.User{
Level: 0,
Email: format.UserTag(tag, userInfo.Uuid),
Account: serial.ToTypedMessage(trojanAccount),
}
}
================================================
FILE: core/xray/user.go
================================================
package xray
import (
"context"
"fmt"
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/common/format"
vCore "github.com/Yuzuki616/V2bX/core"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy"
)
func (c *Core) GetUserManager(tag string) (proxy.UserManager, error) {
handler, err := c.ihm.GetHandler(context.Background(), tag)
if err != nil {
return nil, fmt.Errorf("no such inbound tag: %s", err)
}
inboundInstance, ok := handler.(proxy.GetInbound)
if !ok {
return nil, fmt.Errorf("handler %s is not implement proxy.GetInbound", tag)
}
userManager, ok := inboundInstance.GetInbound().(proxy.UserManager)
if !ok {
return nil, fmt.Errorf("handler %s is not implement proxy.UserManager", tag)
}
return userManager, nil
}
func (c *Core) DelUsers(users []panel.UserInfo, tag string) error {
userManager, err := c.GetUserManager(tag)
if err != nil {
return fmt.Errorf("get user manager error: %s", err)
}
var up, down, user string
for i := range users {
user = format.UserTag(tag, users[i].Uuid)
err = userManager.RemoveUser(context.Background(), user)
if err != nil {
return err
}
up = "user>>>" + user + ">>>traffic>>>uplink"
down = "user>>>" + user + ">>>traffic>>>downlink"
c.shm.UnregisterCounter(up)
c.shm.UnregisterCounter(down)
}
return nil
}
func (c *Core) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) {
upName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>uplink"
downName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>downlink"
upCounter := c.shm.GetCounter(upName)
downCounter := c.shm.GetCounter(downName)
if reset {
if upCounter != nil {
up = upCounter.Set(0)
}
if downCounter != nil {
down = downCounter.Set(0)
}
} else {
if upCounter != nil {
up = upCounter.Value()
}
if downCounter != nil {
down = downCounter.Value()
}
}
return up, down
}
func (c *Core) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
users := make([]*protocol.User, 0, len(p.UserInfo))
switch p.NodeInfo.Type {
case "v2ray":
if p.NodeInfo.ExtraConfig.EnableVless == "true" {
users = buildVlessUsers(p.Tag, p.UserInfo, p.NodeInfo.ExtraConfig.VlessFlow)
} else {
users = buildVmessUsers(p.Tag, p.UserInfo)
}
case "trojan":
users = buildTrojanUsers(p.Tag, p.UserInfo)
case "shadowsocks":
users = buildSSUsers(p.Tag,
p.UserInfo,
p.NodeInfo.Cipher,
p.NodeInfo.ServerKey)
default:
return 0, fmt.Errorf("unsupported node type: %s", p.NodeInfo.Type)
}
man, err := c.GetUserManager(p.Tag)
if err != nil {
return 0, fmt.Errorf("get user manager error: %s", err)
}
for _, u := range users {
mUser, err := u.ToMemoryUser()
if err != nil {
return 0, err
}
err = man.AddUser(context.Background(), mUser)
if err != nil {
return 0, err
}
}
return len(users), nil
}
================================================
FILE: core/xray/vmess.go
================================================
package xray
import (
"github.com/Yuzuki616/V2bX/api/panel"
"github.com/Yuzuki616/V2bX/common/format"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/infra/conf"
"github.com/xtls/xray-core/proxy/vless"
)
func buildVmessUsers(tag string, userInfo []panel.UserInfo) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i, user := range userInfo {
users[i] = buildVmessUser(tag, &user)
}
return users
}
func buildVmessUser(tag string, userInfo *panel.UserInfo) (user *protocol.User) {
vmessAccount := &conf.VMessAccount{
ID: userInfo.Uuid,
Security: "auto",
}
return &protocol.User{
Level: 0,
Email: format.UserTag(tag, userInfo.Uuid), // Uid: InboundTag|email
Account: serial.ToTypedMessage(vmessAccount.Build()),
}
}
func buildVlessUsers(tag string, userInfo []panel.UserInfo, flow string) (users []*protocol.User) {
users = make([]*protocol.User, len(userInfo))
for i := range userInfo {
users[i] = buildVlessUser(tag, &(userInfo)[i], flow)
}
return users
}
func buildVlessUser(tag string, userInfo *panel.UserInfo, flow string) (user *protocol.User) {
vlessAccount := &vless.Account{
Id: userInfo.Uuid,
}
vlessAccount.Flow = flow
return &protocol.User{
Level: 0,
Email: format.UserTag(tag, userInfo.Uuid),
Account: serial.ToTypedMessage(vlessAccount),
}
}
================================================
FILE: core/xray/xray.go
================================================
package xray
import (
"os"
"sync"
"github.com/Yuzuki616/V2bX/conf"
vCore "github.com/Yuzuki616/V2bX/core"
"github.com/Yuzuki616/V2bX/core/xray/app/dispatcher"
_ "github.com/Yuzuki616/V2bX/core/xray/distro/all"
"github.com/goccy/go-json"
log "github.com/sirupsen/logrus"
"github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/app/stats"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/inbound"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/routing"
statsFeature "github.com/xtls/xray-core/features/stats"
coreConf "github.com/xtls/xray-core/infra/conf"
)
func init() {
vCore.RegisterCore("xray", New)
}
// Core Structure
type Core struct {
access sync.Mutex
Server *core.Instance
ihm inbound.Manager
ohm outbound.Manager
shm statsFeature.Manager
dispatcher *dispatcher.DefaultDispatcher
}
func New(c *conf.CoreConfig) (vCore.Core, error) {
return &Core{Server: getCore(c.XrayConfig)}, nil
}
func parseConnectionConfig(c *conf.ConnectionConfig) (policy *coreConf.Policy) {
policy = &coreConf.Policy{
StatsUserUplink: true,
StatsUserDownlink: true,
Handshake: &c.Handshake,
ConnectionIdle: &c.ConnIdle,
UplinkOnly: &c.UplinkOnly,
DownlinkOnly: &c.DownlinkOnly,
BufferSize: &c.BufferSize,
}
return
}
func getCore(c *conf.XrayConfig) *core.Instance {
os.Setenv("XRAY_LOCATION_ASSET", c.AssetPath)
// Log Config
coreLogConfig := &coreConf.LogConfig{}
coreLogConfig.LogLevel = c.LogConfig.Level
coreLogConfig.AccessLog = c.LogConfig.AccessPath
coreLogConfig.ErrorLog = c.LogConfig.ErrorPath
// DNS config
coreDnsConfig := &coreConf.DNSConfig{}
os.Setenv("XRAY_DNS_PATH", "")
if c.DnsConfigPath != "" {
if f, err := os.Open(c.DnsConfigPath); err != nil {
log.WithField("err", err).Panic("Failed to read DNS config file")
} else {
if err = json.NewDecoder(f).Decode(coreDnsConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal DNS config")
}
}
os.Setenv("XRAY_DNS_PATH", c.DnsConfigPath)
}
dnsConfig, err := coreDnsConfig.Build()
if err != nil {
log.WithField("err", err).Panic("Failed to understand DNS config, Please check: https://xtls.github.io/config/dns.html for help")
}
// Routing config
coreRouterConfig := &coreConf.RouterConfig{}
if c.RouteConfigPath != "" {
if f, err := os.Open(c.RouteConfigPath); err != nil {
log.WithField("err", err).Panic("Failed to read Routing config file")
} else {
if err = json.NewDecoder(f).Decode(coreRouterConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal Routing config")
}
}
}
routeConfig, err := coreRouterConfig.Build()
if err != nil {
log.WithField("err", err).Panic("Failed to understand Routing config Please check: https://xtls.github.io/config/routing.html")
}
// Custom Inbound config
var coreCustomInboundConfig []coreConf.InboundDetourConfig
if c.InboundConfigPath != "" {
if f, err := os.Open(c.InboundConfigPath); err != nil {
log.WithField("err", err).Panic("Failed to read Custom Inbound config file")
} else {
if err = json.NewDecoder(f).Decode(&coreCustomInboundConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal Custom Inbound config")
}
}
}
var inBoundConfig []*core.InboundHandlerConfig
for _, config := range coreCustomInboundConfig {
oc, err := config.Build()
if err != nil {
log.WithField("err", err).Panic("Failed to understand Inbound config, Please check: https://xtls.github.io/config/inbound.html for help")
}
inBoundConfig = append(inBoundConfig, oc)
}
// Custom Outbound config
var coreCustomOutboundConfig []coreConf.OutboundDetourConfig
if c.OutboundConfigPath != "" {
if f, err := os.Open(c.OutboundConfigPath); err != nil {
log.WithField("err", err).Panic("Failed to read Custom Outbound config file")
} else {
if err = json.NewDecoder(f).Decode(&coreCustomOutboundConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal Custom Outbound config")
}
}
}
var outBoundConfig []*core.OutboundHandlerConfig
for _, config := range coreCustomOutboundConfig {
oc, err := config.Build()
if err != nil {
log.WithField("err", err).Panic("Failed to understand Outbound config, Please check: https://xtls.github.io/config/outbound.html for help")
}
outBoundConfig = append(outBoundConfig, oc)
}
// Policy config
levelPolicyConfig := parseConnectionConfig(c.ConnectionConfig)
corePolicyConfig := &coreConf.PolicyConfig{}
corePolicyConfig.Levels = map[uint32]*coreConf.Policy{0: levelPolicyConfig}
policyConfig, _ := corePolicyConfig.Build()
// Build Core conf
config := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(coreLogConfig.Build()),
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&stats.Config{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(policyConfig),
serial.ToTypedMessage(dnsConfig),
serial.ToTypedMessage(routeConfig),
},
Inbound: inBoundConfig,
Outbound: outBoundConfig,
}
server, err := core.New(config)
if err != nil {
log.WithField("err", err).Panic("failed to create instance")
}
log.Info("Xray Core Version: ", core.Version())
return server
}
// Start the Core
func (c *Core) Start() error {
c.access.Lock()
defer c.access.Unlock()
if err := c.Server.Start(); err != nil {
return err
}
c.shm = c.Server.GetFeature(statsFeature.ManagerType()).(statsFeature.Manager)
c.ihm = c.Server.GetFeature(inbound.ManagerType()).(inbound.Manager)
c.ohm = c.Server.GetFeature(outbound.ManagerType()).(outbound.Manager)
c.dispatcher = c.Server.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher)
return nil
}
// Close the core
func (c *Core) Close() error {
c.access.Lock()
defer c.access.Unlock()
c.ihm = nil
c.ohm = nil
c.shm = nil
c.dispatcher = nil
err := c.Server.Close()
if err != nil {
return err
}
return nil
}
func (c *Core) Protocols() []string {
return []string{
"v2ray",
"shadowsocks",
"trojan",
}
}
================================================
FILE: example/config.yml.example
================================================
CoreConfig:
Type: "xray" # Core type, default support "xray" and "hy". If you need many cores, use " " to split
XrayConfig:
Log:
Level: warning # Log level: none, error, warning, info, debug
AccessPath: # /etc/XrayR/access.Log
ErrorPath: # /etc/XrayR/error.log
DnsConfigPath: # /etc/XrayR/dns.json # Path to dns config, check https://xtls.github.io/config/dns.html for help
RouteConfigPath: # /etc/XrayR/route.json # Path to route config, check https://xtls.github.io/config/routing.html for help
InboundConfigPath: # /etc/XrayR/custom_inbound.json # Path to custom inbound config, check https://xtls.github.io/config/inbound.html for help
OutboundConfigPath: # /etc/XrayR/custom_outbound.json # Path to custom outbound config, check https://xtls.github.io/config/outbound.html for help
ConnectionConfig:
Handshake: 4 # Handshake time limit, Second
ConnIdle: 30 # Connection idle time limit, Second
UplinkOnly: 2 # Time limit when the connection downstream is closed, Second
DownlinkOnly: 4 # Time limit when the connection is closed after the uplink is closed, Second
BufferSize: 64 # The internal cache size of each connection, kB
Nodes:
- ApiConfig:
ApiHost: "http://127.0.0.1:667"
ApiKey: "123"
NodeID: 41
NodeType: V2ray # Node type: V2ray, Shadowsocks, Trojan
Timeout: 30 # Timeout for the api request
RuleListPath: # /etc/XrayR/rulelist Path to local rulelist file
ControllerConfig:
ListenIP: 0.0.0.0 # IP address you want to listen
SendIP: 0.0.0.0 # IP address you want to send pacakage
XrayOptions:
EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
DNSType: AsIs # AsIs, UseIP, UseIPv4, UseIPv6, DNS strategy
EnableTFO: false # Enable TCP Fast Open
EnableProxyProtocol: false # Only works for WebSocket and TCP
EnableFallback: false # Only support for Trojan and Vless
FallBackConfigs: # Support multiple fallbacks
- SNI: # TLS SNI(Server Name Indication), Empty for any
Alpn: # Alpn, Empty for any
Path: # HTTP PATH, Empty for any
Dest: 80 # Required, Destination of fallback, check https://xtls.github.io/config/features/fallback.html for details.
ProxyProtocolVer: 0 # Send PROXY protocol version, 0 for disable
HyOptions:
Resolver: "udp://1.1.1.1:53" # DNS resolver address
ResolvePreference: 64 # DNS IPv4/IPv6 preference. Available options: "64" (IPv6 first, fallback to IPv4), "46" (IPv4 first, fallback to IPv6), "6" (IPv6 only), "4" (IPv4 only)
SendDevice: "eth0" # Bind device for outbound connections (usually requires root)
LimitConfig:
EnableRealtime: false # Check device limit on real time
SpeedLimit: 0 # Mbps, Local settings will replace remote settings, 0 means disable
DeviceLimit: 0 # Local settings will replace remote settings, 0 means disable
ConnLimit: 0 # Connecting limit, only working for TCP, 0mean
EnableIpRecorder: false # Enable online ip report
IpRecorderConfig:
Type: "Recorder" # Recorder type: Recorder, Redis
RecorderConfig:
Url: "http://127.0.0.1:123" # Report url
Token: "123" # Report token
Timeout: 10 # Report timeout, sec.
RedisConfig:
Address: "127.0.0.1:6379" # Redis address
Password: "" # Redis password
DB: 0 # Redis DB
Expiry: 60 # redis expiry time, sec.
Periodic: 60 # Report interval, sec.
EnableIpSync: false # Enable online ip sync
EnableDynamicSpeedLimit: false # Enable dynamic speed limit
DynamicSpeedLimitConfig:
Periodic: 60 # Time to check the user traffic , sec.
Traffic: 0 # Traffic limit, MB
SpeedLimit: 0 # Speed limit, Mbps
ExpireTime: 0 # Time limit, sec.
CertConfig:
CertMode: dns # Option about how to get certificate: none, file, http, dns, reality, remote. Choose "none" will forcedly disable the tls config.
CertDomain: "node1.test.com" # Domain to cert
CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
KeyFile: /etc/XrayR/cert/node1.test.com.key
Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
Email: test@me.com
DNSEnv: # DNS ENV option used by DNS provider
ALICLOUD_ACCESS_KEY: aaa
ALICLOUD_SECRET_KEY: bbb
RealityConfig: # This config like RealityObject for xray-core, please check https://xtls.github.io/config/transport.html#realityobject
Dest: 80 # Same fallback dest
Xver: 0 # Same fallback xver
ServerNames:
- "example.com"
- "www.example.com"
PrivateKey: "" # Private key for server
MinClientVer: "" # Min client version
MaxClientVer: "" # Max client version
MaxTimeDiff: 0 # Max time difference, ms
ShortIds: # Short ids
- ""
- "0123456789abcdef"
# -
# ApiConfig:
# ApiHost: "http://127.0.0.1:668"
# ApiKey: "123"
# NodeID: 4
# NodeType: Shadowsocks # Node type: V2ray, Shadowsocks, Trojan
# Timeout: 30 # Timeout for the api request
# EnableVless: false # Enable Vless for V2ray Type
# EnableXTLS: false # Enable XTLS for V2ray and Trojan
# SpeedLimit: 0 # Mbps, Local settings will replace remote settings
# DeviceLimit: 0 # Local settings will replace remote settings
# ControllerConfig:
# ListenIP: 0.0.0.0 # IP address you want to listen
# EnableDNS: false # Use custom DNS config, Please ensure that you set the dns.json well
# CertConfig:
# CertMode: dns # Option about how to get certificate: none, file, http, dns
# CertDomain: "node1.test.com" # Domain to cert
# CertFile: /etc/XrayR/cert/node1.test.com.cert # Provided if the CertMode is file
# KeyFile: /etc/XrayR/cert/node1.test.com.pem
# Provider: alidns # DNS cert provider, Get the full support list here: https://go-acme.github.io/lego/dns/
# Email: test@me.com
# DNSEnv: # DNS ENV option used by DNS provider
# ALICLOUD_ACCESS_KEY: aaa
# ALICLOUD_SECRET_KEY: bbb
================================================
FILE: example/custom_inbound.json
================================================
[
{
"listen": "0.0.0.0",
"port": 1234,
"protocol": "socks",
"settings": {
"auth": "noauth",
"accounts": [
{
"user": "my-username",
"pass": "my-password"
}
],
"udp": false,
"ip": "127.0.0.1",
"userLevel": 0
}
}
]
================================================
FILE: example/custom_outbound.json
================================================
[
{
"tag": "IPv4_out",
"protocol": "freedom",
"settings": {}
},
{
"tag": "IPv6_out",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIPv6"
}
},
{
"tag": "socks5-warp",
"protocol": "socks",
"settings": {
"servers": [{
"address": "127.0.0.1",
"port": 40000
}]
}
},
{
"protocol": "blackhole",
"tag": "block"
}
]
================================================
FILE: example/dns.json
================================================
{
"servers": [
"1.1.1.1",
"8.8.8.8",
"localhost"
],
"tag": "dns_inbound"
}
================================================
FILE: example/route.json
================================================
{
"domainStrategy": "IPOnDemand",
"rules": [
{
"type": "field",
"outboundTag": "block",
"ip": [
"geoip:private"
]
},
{
"type": "field",
"outboundTag": "block",
"protocol": [
"bittorrent"
]
},
{
"type": "field",
"outboundTag": "socks5-warp",
"domain": [""]
},
{
"type": "field",
"outboundTag": "IPv6_out",
"domain": [
"geosite:netflix"
]
},
{
"type": "field",
"outboundTag": "IPv4_out",
"network": "udp,tcp"
}
]
}
================================================
FILE: example/rulelist
================================================
(.+\.|^)(360|so)\.(cn|com)
baidu.com
google.com
================================================
FILE: go.mod
================================================
module github.com/Yuzuki616/V2bX
go 1.19
require (
github.com/Yuzuki616/hysteria/core v0.0.0-20230722103310-05508b7e5490
github.com/Yuzuki616/quic-go v0.34.1
github.com/beevik/ntp v1.2.0
github.com/folbricht/routedns v0.1.20
github.com/fsnotify/fsnotify v1.6.0
github.com/go-acme/lego/v4 v4.13.3
github.com/go-redis/redis/v8 v8.11.5
github.com/go-resty/resty/v2 v2.7.0
github.com/goccy/go-json v0.10.2
github.com/hashicorp/go-multierror v1.1.1
github.com/juju/ratelimit v1.0.2
github.com/oschwald/geoip2-golang v1.9.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/xtls/xray-core v1.8.3
golang.org/x/crypto v0.11.0
golang.org/x/sys v0.10.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
)
require (
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/aws/aws-sdk-go v1.39.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/cloudflare-go v0.70.0 // indirect
github.com/coreos/go-iptables v0.6.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
github.com/exoscale/egoscale v0.100.1 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gaukas/godicttls v0.0.3 // indirect
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
github.com/gophercloud/gophercloud v1.0.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/jtacoma/uritemplates v1.0.0 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
github.com/labbsr0x/goh v1.0.1 // indirect
github.com/linode/linodego v1.17.2 // indirect
github.com/liquidweb/go-lwApi v0.0.5 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.3 // indirect
github.com/lucas-clemente/quic-go v0.31.1 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/marten-seemann/qpack v0.3.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.4 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/desec v0.7.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.2.0 // indirect
github.com/nrdcg/goinwx v0.8.2 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/nrdcg/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.2.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
github.com/oschwald/maxminddb-golang v1.11.0 // indirect
github.com/ovh/go-ovh v1.4.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pion/dtls/v2 v2.2.4 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport/v2 v2.0.0 // indirect
github.com/pion/udp v0.1.4 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/quic-go/quic-go v0.35.1 // indirect
github.com/refraction-networking/utls v1.3.2 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/sacloud/api-client-go v0.2.8 // indirect
github.com/sacloud/go-http v0.1.6 // indirect
github.com/sacloud/iaas-api-go v1.11.1 // indirect
github.com/sacloud/packages-go v0.0.9 // indirect
github.com/sagernet/sing v0.2.5 // indirect
github.com/sagernet/sing-shadowsocks v0.2.2 // indirect
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.1.2 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
github.com/transip/gotransip/v6 v6.20.0 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a // indirect
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/vultr/govultr/v2 v2.17.2 // indirect
github.com/xtls/reality v0.0.0-20230613075828-e07c3b04b983 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/oauth2 v0.9.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.10.0 // indirect
google.golang.org/api v0.114.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)
exclude gvisor.dev/gvisor v0.0.0-20230313184804-9bf6dd27710d
================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE=
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91 h1:vX+gnvBc56EbWYrmlhYbFYRaeikAke1GL84N4BEYOFE=
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91/go.mod h1:cDLGBht23g0XQdLjzn6xOGXDkLK182YfINAaZEQLCHQ=
github.com/Yuzuki616/hysteria/core v0.0.0-20230722103310-05508b7e5490 h1:OqS0ywNFR0fEWteLyTcexUNkpdb3vTl5EdMn9gMJMCc=
github.com/Yuzuki616/hysteria/core v0.0.0-20230722103310-05508b7e5490/go.mod h1:Byg39a10tXQ6ysRz5r59YhVMUKgXAThI+0/LcVr+WOE=
github.com/Yuzuki616/quic-go v0.34.1 h1:9Is+Dofzn6qJ9a9t4Ixe4oX7Cb4LU7u4zDozVB8DBDY=
github.com/Yuzuki616/quic-go v0.34.1/go.mod h1:089qZpsXn7CL8kE7G2HurF0bLiZnzQdRIrT7PM0MMQs=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH
gitextract_ebfl6u56/
├── .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/
│ ├── iprecoder/
│ │ ├── interface.go
│ │ ├── recorder.go
│ │ ├── redis.go
│ │ └── redis_test.go
│ └── panel/
│ ├── node.go
│ ├── node_test.go
│ ├── panel.go
│ ├── user.go
│ └── utils.go
├── cmd/
│ ├── action_linux.go
│ ├── cmd.go
│ ├── common.go
│ ├── common_test.go
│ ├── install_linux.go
│ ├── server.go
│ ├── server_test.go
│ ├── synctime.go
│ ├── version.go
│ ├── x25519.go
│ └── x25519_test.go
├── common/
│ ├── crypt/
│ │ ├── aes.go
│ │ └── x25519.go
│ ├── exec/
│ │ └── exec.go
│ ├── file/
│ │ └── file.go
│ ├── format/
│ │ └── user.go
│ ├── rate/
│ │ └── rate.go
│ ├── systime/
│ │ ├── time_stub.go
│ │ ├── time_unix.go
│ │ └── time_windows.go
│ └── task/
│ ├── task.go
│ └── task_test.go
├── conf/
│ ├── conf.go
│ ├── conf_test.go
│ ├── core.go
│ ├── log.go
│ ├── node.go
│ ├── old.go
│ └── watch.go
├── core/
│ ├── core.go
│ ├── hy/
│ │ ├── config.go
│ │ ├── counter.go
│ │ ├── counter_test.go
│ │ ├── hy.go
│ │ ├── ipmasker.go
│ │ ├── kploader.go
│ │ ├── mmdb.go
│ │ ├── node.go
│ │ ├── resolver.go
│ │ ├── server.go
│ │ ├── server_test.go
│ │ └── user.go
│ ├── imports/
│ │ ├── hy.go
│ │ ├── imports.go
│ │ └── xray.go
│ ├── interface.go
│ ├── selector.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
│ ├── inbound.go
│ ├── node.go
│ ├── outbound.go
│ ├── ss.go
│ ├── trojan.go
│ ├── user.go
│ ├── vmess.go
│ └── xray.go
├── example/
│ ├── config.yml.example
│ ├── custom_inbound.json
│ ├── custom_outbound.json
│ ├── dns.json
│ ├── route.json
│ └── rulelist
├── go.mod
├── go.sum
├── limiter/
│ ├── clear.go
│ ├── conn.go
│ ├── conn_test.go
│ ├── dynamic.go
│ ├── limiter.go
│ └── rule.go
├── main.go
├── node/
│ ├── cert.go
│ ├── controller.go
│ ├── lego/
│ │ ├── cert.go
│ │ ├── lego.go
│ │ ├── lego_test.go
│ │ └── user.go
│ ├── node.go
│ ├── task.go
│ └── user.go
└── test_data/
├── 1.key
└── 1.pem
SYMBOL INDEX (353 symbols across 84 files)
FILE: api/iprecoder/interface.go
type IpRecorder (line 7) | type IpRecorder interface
FILE: api/iprecoder/recorder.go
type Recorder (line 12) | type Recorder struct
method SyncOnlineIp (line 24) | func (r *Recorder) SyncOnlineIp(ips []limiter.UserIpList) ([]limiter.U...
function NewRecorder (line 17) | func NewRecorder(c *conf.RecorderConfig) *Recorder {
FILE: api/iprecoder/redis.go
type Redis (line 13) | type Redis struct
method SyncOnlineIp (line 29) | func (r *Redis) SyncOnlineIp(Ips []limiter.UserIpList) ([]limiter.User...
function NewRedis (line 18) | func NewRedis(c *conf.RedisConfig) *Redis {
FILE: api/iprecoder/redis_test.go
function TestRedis_SyncOnlineIp (line 10) | func TestRedis_SyncOnlineIp(t *testing.T) {
FILE: api/panel/node.go
type CommonNodeRsp (line 19) | type CommonNodeRsp struct
type Route (line 27) | type Route struct
type BaseConfig (line 33) | type BaseConfig struct
type V2rayNodeRsp (line 38) | type V2rayNodeRsp struct
type ShadowsocksNodeRsp (line 45) | type ShadowsocksNodeRsp struct
type HysteriaNodeRsp (line 50) | type HysteriaNodeRsp struct
type NodeInfo (line 56) | type NodeInfo struct
type Rules (line 76) | type Rules struct
type V2rayExtraConfig (line 81) | type V2rayExtraConfig struct
type RealityConfig (line 88) | type RealityConfig struct
method GetNodeInfo (line 99) | func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
function intervalToTime (line 234) | func intervalToTime(i interface{}) time.Duration {
FILE: api/panel/node_test.go
function init (line 11) | func init() {
function TestClient_GetNodeInfo (line 24) | func TestClient_GetNodeInfo(t *testing.T) {
function TestClient_ReportUserTraffic (line 29) | func TestClient_ReportUserTraffic(t *testing.T) {
FILE: api/panel/panel.go
type Client (line 19) | type Client struct
function New (line 30) | func New(c *conf.ApiConfig) (*Client, error) {
function readLocalRuleList (line 72) | func readLocalRuleList(path string) (LocalRuleList []*regexp.Regexp) {
FILE: api/panel/user.go
type OnlineUser (line 9) | type OnlineUser struct
type UserInfo (line 14) | type UserInfo struct
type UserListBody (line 20) | type UserListBody struct
method GetUserList (line 26) | func (c *Client) GetUserList() (UserList []UserInfo, err error) {
type UserTraffic (line 48) | type UserTraffic struct
method ReportUserTraffic (line 55) | func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
FILE: api/panel/utils.go
method Debug (line 10) | func (c *Client) Debug() {
method assembleURL (line 14) | func (c *Client) assembleURL(path string) string {
method checkResponse (line 17) | func (c *Client) checkResponse(res *resty.Response, path string, err err...
FILE: cmd/action_linux.go
function init (line 35) | func init() {
function startHandle (line 42) | func startHandle(_ *cobra.Command, _ []string) {
function stopHandle (line 71) | func stopHandle(_ *cobra.Command, _ []string) {
function restartHandle (line 92) | func restartHandle(_ *cobra.Command, _ []string) {
FILE: cmd/cmd.go
function Run (line 14) | func Run() {
FILE: cmd/common.go
constant red (line 10) | red = "\033[0;31m"
constant green (line 11) | green = "\033[0;32m"
constant yellow (line 12) | yellow = "\033[0;33m"
constant plain (line 13) | plain = "\033[0m"
function checkRunning (line 16) | func checkRunning() (bool, error) {
function Err (line 24) | func Err(msg ...any) string {
function Ok (line 28) | func Ok(msg ...any) string {
function Warn (line 32) | func Warn(msg ...any) string {
FILE: cmd/common_test.go
function Test_printFailed (line 5) | func Test_printFailed(t *testing.T) {
FILE: cmd/install_linux.go
function init (line 31) | func init() {
function uninstallHandle (line 37) | func uninstallHandle(_ *cobra.Command, _ []string) {
FILE: cmd/server.go
function init (line 30) | func init() {
function serverHandle (line 40) | func serverHandle(_ *cobra.Command, _ []string) {
FILE: cmd/server_test.go
function TestRun (line 5) | func TestRun(t *testing.T) {
FILE: cmd/synctime.go
function init (line 20) | func init() {
function synctimeHandle (line 25) | func synctimeHandle(_ *cobra.Command, _ []string) {
FILE: cmd/version.go
function init (line 25) | func init() {
function showVersion (line 29) | func showVersion() {
FILE: cmd/x25519.go
function init (line 23) | func init() {
function executeX25519 (line 27) | func executeX25519() {
FILE: cmd/x25519_test.go
function Test_executeX25519 (line 5) | func Test_executeX25519(t *testing.T) {
FILE: common/crypt/aes.go
function AesEncrypt (line 8) | func AesEncrypt(data []byte, key []byte) (string, error) {
function AesDecrypt (line 18) | func AesDecrypt(data string, key []byte) (string, error) {
FILE: common/crypt/x25519.go
function GenX25519Private (line 7) | func GenX25519Private(data []byte) []byte {
FILE: common/exec/exec.go
function RunCommandByShell (line 9) | func RunCommandByShell(cmd string) (string, error) {
function RunCommandStd (line 19) | func RunCommandStd(name string, args ...string) {
FILE: common/file/file.go
function IsExist (line 5) | func IsExist(path string) bool {
FILE: common/format/user.go
function UserTag (line 7) | func UserTag(tag string, uuid string) string {
FILE: common/rate/rate.go
type Writer (line 9) | type Writer struct
method Close (line 21) | func (w *Writer) Close() error {
method WriteMultiBuffer (line 25) | func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
function NewRateLimitWriter (line 14) | func NewRateLimitWriter(writer buf.Writer, limiter *ratelimit.Bucket) bu...
FILE: common/systime/time_stub.go
function SetSystemTime (line 10) | func SetSystemTime(nowTime time.Time) error {
FILE: common/systime/time_unix.go
function SetSystemTime (line 11) | func SetSystemTime(nowTime time.Time) error {
FILE: common/systime/time_windows.go
function SetSystemTime (line 10) | func SetSystemTime(nowTime time.Time) error {
FILE: common/task/task.go
type Task (line 9) | type Task struct
method hasClosed (line 20) | func (t *Task) hasClosed() bool {
method checkedExecute (line 27) | func (t *Task) checkedExecute(first bool) error {
method Start (line 51) | func (t *Task) Start(first bool) error {
method Close (line 69) | func (t *Task) Close() {
FILE: common/task/task_test.go
function TestTask (line 9) | func TestTask(t *testing.T) {
FILE: conf/conf.go
type Conf (line 10) | type Conf struct
method LoadFromPath (line 33) | func (p *Conf) LoadFromPath(filePath string) error {
function New (line 15) | func New() *Conf {
FILE: conf/conf_test.go
function TestConf_LoadFromPath (line 8) | func TestConf_LoadFromPath(t *testing.T) {
function TestConf_Watch (line 13) | func TestConf_Watch(t *testing.T) {
FILE: conf/core.go
type CoreConfig (line 3) | type CoreConfig struct
type XrayConfig (line 8) | type XrayConfig struct
type ConnectionConfig (line 18) | type ConnectionConfig struct
function NewConnectionConfig (line 26) | func NewConnectionConfig() *ConnectionConfig {
FILE: conf/log.go
type LogConfig (line 3) | type LogConfig struct
function NewLogConfig (line 9) | func NewLogConfig() *LogConfig {
FILE: conf/node.go
type NodeConfig (line 3) | type NodeConfig struct
type ApiConfig (line 8) | type ApiConfig struct
type ControllerConfig (line 17) | type ControllerConfig struct
type RealityConfig (line 26) | type RealityConfig struct
type XrayOptions (line 37) | type XrayOptions struct
type HyOptions (line 49) | type HyOptions struct
type LimitConfig (line 55) | type LimitConfig struct
type FallBackConfig (line 66) | type FallBackConfig struct
type RecorderConfig (line 74) | type RecorderConfig struct
type RedisConfig (line 80) | type RedisConfig struct
type IpReportConfig (line 87) | type IpReportConfig struct
type DynamicSpeedLimitConfig (line 95) | type DynamicSpeedLimitConfig struct
type CertConfig (line 102) | type CertConfig struct
FILE: conf/old.go
type OldConfig (line 5) | type OldConfig struct
type OldControllerConfig (line 12) | type OldControllerConfig struct
type OldApiConfig (line 31) | type OldApiConfig struct
function migrateOldConfig (line 44) | func migrateOldConfig(c *Conf, old *OldConfig) {
FILE: conf/watch.go
method Watch (line 11) | func (p *Conf) Watch(filePath, dnsPath string, reload func()) error {
FILE: core/core.go
function NewCore (line 14) | func NewCore(c *conf.CoreConfig) (Core, error) {
function RegisterCore (line 41) | func RegisterCore(t string, f func(c *conf.CoreConfig) (Core, error)) {
function RegisteredCore (line 45) | func RegisteredCore() []string {
FILE: core/hy/config.go
constant mbpsToBps (line 4) | mbpsToBps = 125000
constant minSpeedBPS (line 5) | minSpeedBPS = 16384
constant DefaultALPN (line 7) | DefaultALPN = "hysteria"
constant DefaultStreamReceiveWindow (line 9) | DefaultStreamReceiveWindow = 16777216
constant DefaultConnectionReceiveWindow (line 10) | DefaultConnectionReceiveWindow = DefaultStreamReceiveWindow * 5 / 2
constant DefaultMaxIncomingStreams (line 12) | DefaultMaxIncomingStreams = 1024
constant DefaultMMDBFilename (line 14) | DefaultMMDBFilename = "GeoLite2-Country.mmdb"
constant ServerMaxIdleTimeoutSec (line 16) | ServerMaxIdleTimeoutSec = 60
constant DefaultClientIdleTimeoutSec (line 17) | DefaultClientIdleTimeoutSec = 20
constant DefaultClientHopIntervalSec (line 19) | DefaultClientHopIntervalSec = 10
function SpeedTrans (line 22) | func SpeedTrans(upM, downM int) (uint64, uint64) {
FILE: core/hy/counter.go
type UserTrafficCounter (line 8) | type UserTrafficCounter struct
method getCounters (line 25) | func (c *UserTrafficCounter) getCounters(auth string) *counters {
method Rx (line 36) | func (c *UserTrafficCounter) Rx(auth string, n int) {
method Tx (line 41) | func (c *UserTrafficCounter) Tx(auth string, n int) {
method IncConn (line 46) | func (c *UserTrafficCounter) IncConn(_ string) {
method DecConn (line 52) | func (c *UserTrafficCounter) DecConn(_ string) {
method Reset (line 58) | func (c *UserTrafficCounter) Reset(auth string) {
method Delete (line 64) | func (c *UserTrafficCounter) Delete(auth string) {
type counters (line 13) | type counters struct
function NewUserTrafficCounter (line 19) | func NewUserTrafficCounter() *UserTrafficCounter {
FILE: core/hy/counter_test.go
function TestUserTrafficCounter_Rx (line 5) | func TestUserTrafficCounter_Rx(t *testing.T) {
FILE: core/hy/hy.go
function init (line 12) | func init() {
type Hy (line 16) | type Hy struct
method Start (line 26) | func (h *Hy) Start() error {
method Close (line 30) | func (h *Hy) Close() error {
method Protocols (line 45) | func (h *Hy) Protocols() []string {
function NewHy (line 20) | func NewHy(_ *conf.CoreConfig) (vCore.Core, error) {
FILE: core/hy/ipmasker.go
type ipMasker (line 7) | type ipMasker struct
method Mask (line 14) | func (m *ipMasker) Mask(addr string) string {
FILE: core/hy/kploader.go
type keypairLoader (line 11) | type keypairLoader struct
method load (line 78) | func (kpr *keypairLoader) load() error {
method GetCertificateFunc (line 89) | func (kpr *keypairLoader) GetCertificateFunc() func(*tls.ClientHelloIn...
function newKeypairLoader (line 18) | func newKeypairLoader(certPath, keyPath string) (*keypairLoader, error) {
FILE: core/hy/mmdb.go
function loadMMDBReader (line 10) | func loadMMDBReader(filename string) (*geoip2.Reader, error) {
FILE: core/hy/node.go
method AddNode (line 11) | func (h *Hy) AddNode(tag string, info *panel.NodeInfo, c *conf.Controlle...
method DelNode (line 32) | func (h *Hy) DelNode(tag string) error {
FILE: core/hy/resolver.go
function setResolver (line 15) | func setResolver(dns string) error {
FILE: core/hy/server.go
type Server (line 34) | type Server struct
method runServer (line 50) | func (s *Server) runServer(node *panel.NodeInfo, c *conf.ControllerCon...
method authByUser (line 162) | func (s *Server) authByUser(addr net.Addr, auth []byte, sSend uint64, ...
method connectFunc (line 172) | func (s *Server) connectFunc(addr net.Addr, auth []byte, sSend uint64,...
method disconnectFunc (line 189) | func (s *Server) disconnectFunc(addr net.Addr, auth []byte, err error) {
function NewServer (line 43) | func NewServer(tag string, l *limiter.Limiter) *Server {
function tcpRequestFunc (line 197) | func tcpRequestFunc(addr net.Addr, auth []byte, reqAddr string, action a...
function tcpErrorFunc (line 205) | func tcpErrorFunc(addr net.Addr, auth []byte, reqAddr string, err error) {
function udpRequestFunc (line 220) | func udpRequestFunc(addr net.Addr, auth []byte, sessionID uint32) {
function udpErrorFunc (line 227) | func udpErrorFunc(addr net.Addr, auth []byte, sessionID uint32, err erro...
function actionToString (line 242) | func actionToString(action acl.Action, arg string) string {
FILE: core/hy/server_test.go
function TestServer (line 14) | func TestServer(t *testing.T) {
FILE: core/hy/user.go
method AddUsers (line 10) | func (h *Hy) AddUsers(p *core.AddUsersParams) (int, error) {
method GetUserTraffic (line 22) | func (h *Hy) GetUserTraffic(tag, uuid string, reset bool) (up int64, dow...
method DelUsers (line 34) | func (h *Hy) DelUsers(users []panel.UserInfo, tag string) error {
FILE: core/interface.go
type AddUsersParams (line 8) | type AddUsersParams struct
type Core (line 14) | type Core interface
FILE: core/selector.go
type Selector (line 12) | type Selector struct
method Start (line 17) | func (s *Selector) Start() error {
method Close (line 25) | func (s *Selector) Close() error {
method AddNode (line 42) | func (s *Selector) AddNode(tag string, info *panel.NodeInfo, config *c...
method DelNode (line 57) | func (s *Selector) DelNode(tag string) error {
method AddUsers (line 69) | func (s *Selector) AddUsers(p *AddUsersParams) (added int, err error) {
method GetUserTraffic (line 77) | func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up in...
method DelUsers (line 85) | func (s *Selector) DelUsers(users []panel.UserInfo, tag string) error {
method Protocols (line 93) | func (s *Selector) Protocols() []string {
function isSupported (line 33) | func isSupported(protocol string, protocols []string) bool {
FILE: core/xray/app/dispatcher/config.pb.go
constant _ (line 18) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
constant _ (line 20) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
type SessionConfig (line 23) | type SessionConfig struct
method Reset (line 29) | func (x *SessionConfig) Reset() {
method String (line 38) | func (x *SessionConfig) String() string {
method ProtoMessage (line 42) | func (*SessionConfig) ProtoMessage() {}
method ProtoReflect (line 44) | func (x *SessionConfig) ProtoReflect() protoreflect.Message {
method Descriptor (line 57) | func (*SessionConfig) Descriptor() ([]byte, []int) {
type Config (line 61) | type Config struct
method Reset (line 69) | func (x *Config) Reset() {
method String (line 78) | func (x *Config) String() string {
method ProtoMessage (line 82) | func (*Config) ProtoMessage() {}
method ProtoReflect (line 84) | func (x *Config) ProtoReflect() protoreflect.Message {
method Descriptor (line 97) | func (*Config) Descriptor() ([]byte, []int) {
method GetSettings (line 101) | func (x *Config) GetSettings() *SessionConfig {
function file_config_proto_rawDescGZIP (line 135) | func file_config_proto_rawDescGZIP() []byte {
function init (line 156) | func init() { file_config_proto_init() }
function file_config_proto_init (line 157) | func file_config_proto_init() {
FILE: core/xray/app/dispatcher/default.go
type cachedReader (line 34) | type cachedReader struct
method Cache (line 40) | func (r *cachedReader) Cache(b *buf.Buffer) {
method readInternal (line 53) | func (r *cachedReader) readInternal() buf.MultiBuffer {
method ReadMultiBuffer (line 66) | func (r *cachedReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
method ReadMultiBufferTimeout (line 75) | func (r *cachedReader) ReadMultiBufferTimeout(timeout time.Duration) (...
method Interrupt (line 84) | func (r *cachedReader) Interrupt() {
type DefaultDispatcher (line 94) | type DefaultDispatcher struct
method Init (line 119) | func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, ...
method Type (line 129) | func (*DefaultDispatcher) Type() interface{} {
method Start (line 134) | func (*DefaultDispatcher) Start() error {
method Close (line 139) | func (*DefaultDispatcher) Close() error { return nil }
method getLink (line 141) | func (d *DefaultDispatcher) getLink(ctx context.Context, network net.N...
method shouldOverride (line 280) | func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result...
method Dispatch (line 314) | func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination ...
method DispatchLink (line 367) | func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destinat...
method routedDispatch (line 493) | func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *...
function init (line 103) | func init() {
type limitedError (line 415) | type limitedError
method Error (line 417) | func (l limitedError) Error() string {
function sniffer (line 421) | func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bo...
FILE: core/xray/app/dispatcher/errors.generated.go
type errPathObjHolder (line 5) | type errPathObjHolder struct
function newError (line 7) | func newError(values ...interface{}) *errors.Error {
FILE: core/xray/app/dispatcher/fakednssniffer.go
function newFakeDNSSniffer (line 15) | func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata...
type fakeDNSSniffResult (line 50) | type fakeDNSSniffResult struct
method Protocol (line 54) | func (fakeDNSSniffResult) Protocol() string {
method Domain (line 58) | func (f fakeDNSSniffResult) Domain() string {
type fakeDNSExtraOpts (line 62) | type fakeDNSExtraOpts
constant ipAddressInRange (line 64) | ipAddressInRange fakeDNSExtraOpts = 1
type ipAddressInRangeOpt (line 66) | type ipAddressInRangeOpt struct
type DNSThenOthersSniffResult (line 70) | type DNSThenOthersSniffResult struct
method IsProtoSubsetOf (line 75) | func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string)...
method Protocol (line 79) | func (DNSThenOthersSniffResult) Protocol() string {
method Domain (line 83) | func (f DNSThenOthersSniffResult) Domain() string {
function newFakeDNSThenOthers (line 87) | func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSn...
FILE: core/xray/app/dispatcher/sniffer.go
type SniffResult (line 14) | type SniffResult interface
type protocolSniffer (line 19) | type protocolSniffer
type protocolSnifferWithMetadata (line 21) | type protocolSnifferWithMetadata struct
type Sniffer (line 30) | type Sniffer struct
method Sniff (line 57) | func (s *Sniffer) Sniff(c context.Context, payload []byte, network net...
method SniffMetadata (line 83) | func (s *Sniffer) SniffMetadata(c context.Context) (SniffResult, error) {
function NewSniffer (line 34) | func NewSniffer(ctx context.Context) *Sniffer {
function CompositeResult (line 110) | func CompositeResult(domainResult SniffResult, protocolResult SniffResul...
type compositeResult (line 114) | type compositeResult struct
method Protocol (line 119) | func (c compositeResult) Protocol() string {
method Domain (line 123) | func (c compositeResult) Domain() string {
method ProtocolForDomainResult (line 127) | func (c compositeResult) ProtocolForDomainResult() string {
type SnifferResultComposite (line 131) | type SnifferResultComposite interface
type SnifferIsProtoSubsetOf (line 135) | type SnifferIsProtoSubsetOf interface
FILE: core/xray/app/dispatcher/stats.go
type SizeStatWriter (line 9) | type SizeStatWriter struct
method WriteMultiBuffer (line 14) | func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
method Close (line 19) | func (w *SizeStatWriter) Close() error {
method Interrupt (line 23) | func (w *SizeStatWriter) Interrupt() {
FILE: core/xray/app/dispatcher/stats_test.go
type TestCounter (line 11) | type TestCounter
method Value (line 13) | func (c *TestCounter) Value() int64 {
method Add (line 17) | func (c *TestCounter) Add(v int64) int64 {
method Set (line 23) | func (c *TestCounter) Set(v int64) int64 {
function TestStatsWriter (line 28) | func TestStatsWriter(t *testing.T) {
FILE: core/xray/inbound.go
function buildInbound (line 20) | func buildInbound(config *conf.ControllerConfig, nodeInfo *panel.NodeInf...
function buildV2ray (line 153) | func buildV2ray(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo,...
function buildTrojan (line 216) | func buildTrojan(config *conf.ControllerConfig, inbound *coreConf.Inboun...
function buildShadowsocks (line 240) | func buildShadowsocks(config *conf.ControllerConfig, nodeInfo *panel.Nod...
function buildVlessFallbacks (line 277) | func buildVlessFallbacks(fallbackConfigs []conf.FallBackConfig) ([]*core...
function buildTrojanFallbacks (line 302) | func buildTrojanFallbacks(fallbackConfigs []conf.FallBackConfig) ([]*cor...
FILE: core/xray/node.go
method AddNode (line 13) | func (c *Core) AddNode(tag string, info *panel.NodeInfo, config *conf.Co...
method addInbound (line 33) | func (c *Core) addInbound(config *core.InboundHandlerConfig) error {
method addOutbound (line 48) | func (c *Core) addOutbound(config *core.OutboundHandlerConfig) error {
method DelNode (line 63) | func (c *Core) DelNode(tag string) error {
method removeInbound (line 75) | func (c *Core) removeInbound(tag string) error {
method removeOutbound (line 79) | func (c *Core) removeOutbound(tag string) error {
FILE: core/xray/outbound.go
function buildOutbound (line 13) | func buildOutbound(config *conf2.ControllerConfig, tag string) (*core.Ou...
FILE: core/xray/ss.go
function buildSSUsers (line 14) | func buildSSUsers(tag string, userInfo []panel.UserInfo, cypher string, ...
function buildSSUser (line 22) | func buildSSUser(tag string, userInfo *panel.UserInfo, cypher string, se...
function getCipherFromString (line 52) | func getCipherFromString(c string) shadowsocks.CipherType {
FILE: core/xray/trojan.go
function buildTrojanUsers (line 11) | func buildTrojanUsers(tag string, userInfo []panel.UserInfo) (users []*p...
function buildTrojanUser (line 19) | func buildTrojanUser(tag string, userInfo *panel.UserInfo) (user *protoc...
FILE: core/xray/user.go
method GetUserManager (line 14) | func (c *Core) GetUserManager(tag string) (proxy.UserManager, error) {
method DelUsers (line 30) | func (c *Core) DelUsers(users []panel.UserInfo, tag string) error {
method GetUserTraffic (line 50) | func (c *Core) GetUserTraffic(tag, uuid string, reset bool) (up int64, d...
method AddUsers (line 73) | func (c *Core) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
FILE: core/xray/vmess.go
function buildVmessUsers (line 12) | func buildVmessUsers(tag string, userInfo []panel.UserInfo) (users []*pr...
function buildVmessUser (line 20) | func buildVmessUser(tag string, userInfo *panel.UserInfo) (user *protoco...
function buildVlessUsers (line 32) | func buildVlessUsers(tag string, userInfo []panel.UserInfo, flow string)...
function buildVlessUser (line 40) | func buildVlessUser(tag string, userInfo *panel.UserInfo, flow string) (...
FILE: core/xray/xray.go
function init (line 24) | func init() {
type Core (line 29) | type Core struct
method Start (line 161) | func (c *Core) Start() error {
method Close (line 175) | func (c *Core) Close() error {
method Protocols (line 189) | func (c *Core) Protocols() []string {
function New (line 38) | func New(c *conf.CoreConfig) (vCore.Core, error) {
function parseConnectionConfig (line 42) | func parseConnectionConfig(c *conf.ConnectionConfig) (policy *coreConf.P...
function getCore (line 55) | func getCore(c *conf.XrayConfig) *core.Instance {
FILE: limiter/clear.go
function ClearOnlineIP (line 5) | func ClearOnlineIP() error {
FILE: limiter/conn.go
type ConnLimiter (line 8) | type ConnLimiter struct
method AddConnCount (line 26) | func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool)...
method DelConnCount (line 102) | func (c *ConnLimiter) DelConnCount(user string, ip string) {
method ClearOnlineIP (line 139) | func (c *ConnLimiter) ClearOnlineIP() {
function NewConnLimiter (line 16) | func NewConnLimiter(conn int, ip int, realtime bool) *ConnLimiter {
FILE: limiter/conn_test.go
function init (line 11) | func init() {
function TestConnLimiter_AddConnCount (line 15) | func TestConnLimiter_AddConnCount(t *testing.T) {
function TestConnLimiter_DelConnCount (line 20) | func TestConnLimiter_DelConnCount(t *testing.T) {
function TestConnLimiter_ClearOnlineIP (line 27) | func TestConnLimiter_ClearOnlineIP(t *testing.T) {
function BenchmarkConnLimiter (line 44) | func BenchmarkConnLimiter(b *testing.B) {
FILE: limiter/dynamic.go
method AddDynamicSpeedLimit (line 9) | func (l *Limiter) AddDynamicSpeedLimit(tag string, userInfo *panel.UserI...
function determineSpeedLimit (line 19) | func determineSpeedLimit(limit1, limit2 int) (limit int) {
FILE: limiter/limiter.go
function Init (line 20) | func Init() {
type Limiter (line 34) | type Limiter struct
method UpdateUser (line 88) | func (l *Limiter) UpdateUser(tag string, added []panel.UserInfo, delet...
method UpdateDynamicSpeedLimit (line 104) | func (l *Limiter) UpdateDynamicSpeedLimit(tag, uuid string, limit int,...
method CheckLimit (line 115) | func (l *Limiter) CheckLimit(email string, ip string, isTcp bool) (Buc...
type UserLimitInfo (line 43) | type UserLimitInfo struct
function AddLimiter (line 50) | func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo)...
function GetLimiter (line 72) | func GetLimiter(tag string) (info *Limiter, err error) {
function DeleteLimiter (line 82) | func DeleteLimiter(tag string) {
type UserIpList (line 151) | type UserIpList struct
function determineDeviceLimit (line 156) | func determineDeviceLimit(nodeLimit, userLimit int) (limit int) {
FILE: limiter/rule.go
method CheckDomainRule (line 9) | func (l *Limiter) CheckDomainRule(destination string) (reject bool) {
method CheckProtocolRule (line 20) | func (l *Limiter) CheckProtocolRule(protocol string) (reject bool) {
method UpdateRule (line 30) | func (l *Limiter) UpdateRule(rule *panel.Rules) error {
FILE: main.go
function main (line 5) | func main() {
FILE: node/cert.go
method renewCertTask (line 10) | func (c *Controller) renewCertTask() error {
method requestCert (line 23) | func (c *Controller) requestCert() error {
FILE: node/controller.go
type Controller (line 16) | type Controller struct
method Start (line 44) | func (c *Controller) Start() error {
method Close (line 95) | func (c *Controller) Close() error {
method buildNodeTag (line 115) | func (c *Controller) buildNodeTag(node *panel.NodeInfo) string {
function NewController (line 34) | func NewController(server vCore.Core, api *panel.Client, config *conf.Co...
FILE: node/lego/cert.go
method SetProvider (line 14) | func (l *Lego) SetProvider() error {
method CreateCert (line 37) | func (l *Lego) CreateCert() (err error) {
method RenewCert (line 49) | func (l *Lego) RenewCert() error {
method CheckCert (line 70) | func (l *Lego) CheckCert(file []byte) (bool, error) {
method parseParams (line 81) | func (l *Lego) parseParams(path string) string {
method writeCert (line 86) | func (l *Lego) writeCert(certificates *certificate.Resource) error {
FILE: node/lego/lego.go
type Lego (line 13) | type Lego struct
function New (line 18) | func New(config *conf.CertConfig) (*Lego, error) {
function checkPath (line 44) | func checkPath(p string) error {
FILE: node/lego/lego_test.go
function init (line 12) | func init() {
function TestLego_CreateCertByDns (line 31) | func TestLego_CreateCertByDns(t *testing.T) {
function TestLego_RenewCert (line 38) | func TestLego_RenewCert(t *testing.T) {
FILE: node/lego/user.go
type User (line 18) | type User struct
method GetEmail (line 25) | func (u *User) GetEmail() string {
method GetRegistration (line 28) | func (u *User) GetRegistration() *registration.Resource {
method GetPrivateKey (line 31) | func (u *User) GetPrivateKey() crypto.PrivateKey {
method Save (line 91) | func (u *User) Save(path string) error {
method DecodePrivate (line 109) | func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey...
method Load (line 115) | func (u *User) Load(path string) error {
function NewUser (line 35) | func NewUser(path string, email string) (*User, error) {
function registerUser (line 60) | func registerUser(user *User, path string) error {
function EncodePrivate (line 83) | func EncodePrivate(privKey *ecdsa.PrivateKey) (string, error) {
FILE: node/node.go
type Node (line 10) | type Node struct
method Start (line 18) | func (n *Node) Start(nodes []*conf.NodeConfig, core vCore.Core) error {
method Close (line 39) | func (n *Node) Close() {
function New (line 14) | func New() *Node {
FILE: node/task.go
method startTasks (line 13) | func (c *Controller) startTasks(node *panel.NodeInfo) {
method nodeInfoMonitor (line 51) | func (c *Controller) nodeInfoMonitor() (err error) {
method SpeedChecker (line 209) | func (c *Controller) SpeedChecker() error {
FILE: node/user.go
method reportUserTrafficTask (line 11) | func (c *Controller) reportUserTrafficTask() (err error) {
function compareUserList (line 46) | func compareUserList(old, new []panel.UserInfo) (deleted, added []panel....
Condensed preview — 110 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (350K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug-report.md",
"chars": 341,
"preview": "---\nname: \"Bug 反馈\"\nabout: 创建一个报告以帮助我们修复并改进XrayR\ntitle: ''\nlabels: awaiting reply, bug\nassignees: ''\n---\n\n**描述该错误**\n简单地描述"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.md",
"chars": 212,
"preview": "---\nname: \"功能建议\"\nabout: 给XrayR提出建议,让我们做得更好\ntitle: ''\nlabels: awaiting reply, feature-request\nassignees: ''\n---\n\n**描述您想要的"
},
{
"path": ".github/build/friendly-filenames.json",
"chars": 1796,
"preview": "{\n \"android-arm64\": { \"friendlyName\": \"android-arm64-v8a\" },\n \"darwin-amd64\": { \"friendlyName\": \"macos-64\" },\n "
},
{
"path": ".github/dependabot.yml",
"chars": 503,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2346,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".github/workflows/release.yml",
"chars": 6624,
"preview": "name: Build and Release\n\non:\n workflow_dispatch:\n push:\n branches:\n - master\n paths:\n - \"**/*.go\"\n "
},
{
"path": ".gitignore",
"chars": 246,
"preview": "example/config.yml\nexample/main\nexample/XrayR\nexample/XrayR*\nexample/mytest\nexample/access.logo\nexample/error.log\napi/ch"
},
{
"path": "LICENSE",
"chars": 16725,
"preview": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\""
},
{
"path": "README.md",
"chars": 2409,
"preview": "# V2bX\n**项目转移至[V2bX](https://github.com/InazumaV/V2bX),此仓库不再维护**\n\n[\n\ntype IpRecorder interface {\n\tSyncOnlineIp(Ips []limi"
},
{
"path": "api/iprecoder/recorder.go",
"chars": 857,
"preview": "package iprecoder\n\nimport (\n\t\"errors\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"github.com/Yuzuki616/V2bX/limiter\"\n\t\"github.co"
},
{
"path": "api/iprecoder/redis.go",
"chars": 1611,
"preview": "package iprecoder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"github.com/Yuzuki616/V2bX/limiter\"\n\t\"g"
},
{
"path": "api/iprecoder/redis_test.go",
"chars": 415,
"preview": "package iprecoder\n\nimport (\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"github.com/Yuzuki616/V2bX/limiter\"\n\t\"log\"\n\t\"testing\"\n)\n\n"
},
{
"path": "api/panel/node.go",
"chars": 7087,
"preview": "package panel\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/"
},
{
"path": "api/panel/node_test.go",
"chars": 584,
"preview": "package panel\n\nimport (\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"log\"\n\t\"testing\"\n)\n\nvar client *Client\n\nfunc init() {\n\tc, err"
},
{
"path": "api/panel/panel.go",
"chars": 2240,
"preview": "package panel\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Yuzuki616/V2b"
},
{
"path": "api/panel/user.go",
"chars": 1530,
"preview": "package panel\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/goccy/go-json\"\n)\n\ntype OnlineUser struct {\n\tUID int\n\tIP string\n}\n\ntype Use"
},
{
"path": "api/panel/utils.go",
"chars": 589,
"preview": "package panel\n\nimport (\n\t\"fmt\"\n\t\"github.com/go-resty/resty/v2\"\n\tpath2 \"path\"\n)\n\n// Debug set the client debug for client"
},
{
"path": "cmd/action_linux.go",
"chars": 2462,
"preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/common/exec\"\n\t\"github.com/spf13/cobra\"\n\t\"time\"\n)\n\nvar (\n\tstartC"
},
{
"path": "cmd/cmd.go",
"chars": 298,
"preview": "package cmd\n\nimport (\n\tlog \"github.com/sirupsen/logrus\"\n\n\t_ \"github.com/Yuzuki616/V2bX/core/imports\"\n\t\"github.com/spf13/"
},
{
"path": "cmd/common.go",
"chars": 606,
"preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/common/exec\"\n\t\"strings\"\n)\n\nconst (\n\tred = \"\\033[0;31m\"\n\tgree"
},
{
"path": "cmd/common_test.go",
"chars": 91,
"preview": "package cmd\n\nimport \"testing\"\n\nfunc Test_printFailed(t *testing.T) {\n\tt.Log(Err(\"test\"))\n}\n"
},
{
"path": "cmd/install_linux.go",
"chars": 1488,
"preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/common/exec\"\n\t\"github.com/spf13/cobra\"\n\t\"os\"\n\t\"strings\"\n)\n\nvar "
},
{
"path": "cmd/server.go",
"chars": 2235,
"preview": "package cmd\n\nimport (\n\tlog \"github.com/sirupsen/logrus\"\n\t\"os\"\n\t\"os/signal\"\n\t\"runtime\"\n\t\"syscall\"\n\n\tvCore \"github.com/Yuz"
},
{
"path": "cmd/server_test.go",
"chars": 69,
"preview": "package cmd\n\nimport \"testing\"\n\nfunc TestRun(t *testing.T) {\n\tRun()\n}\n"
},
{
"path": "cmd/synctime.go",
"chars": 804,
"preview": "package cmd\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/Yuzuki616/V2bX/common/systime\"\n\t\"github.com/beevik/ntp\"\n\t\"github.com/spf13/co"
},
{
"path": "cmd/version.go",
"chars": 774,
"preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tvCore \"github.com/Yuzuki616/V2bX/core\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tver"
},
{
"path": "cmd/x25519.go",
"chars": 1406,
"preview": "package cmd\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/Yuzuki616/V2bX/common/crypt\"\n\n\t\""
},
{
"path": "cmd/x25519_test.go",
"chars": 90,
"preview": "package cmd\n\nimport \"testing\"\n\nfunc Test_executeX25519(t *testing.T) {\n\texecuteX25519()\n}\n"
},
{
"path": "common/crypt/aes.go",
"chars": 579,
"preview": "package crypt\n\nimport (\n\t\"crypto/aes\"\n\t\"encoding/base64\"\n)\n\nfunc AesEncrypt(data []byte, key []byte) (string, error) {\n\t"
},
{
"path": "common/crypt/x25519.go",
"chars": 181,
"preview": "package crypt\n\nimport (\n\t\"crypto/sha256\"\n)\n\nfunc GenX25519Private(data []byte) []byte {\n\tkey := sha256.Sum256(data)\n\tkey"
},
{
"path": "common/exec/exec.go",
"chars": 483,
"preview": "package exec\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"os/exec\"\n)\n\nfunc RunCommandByShell(cmd string) (string, error) {\n\te := exec.Com"
},
{
"path": "common/file/file.go",
"chars": 129,
"preview": "package file\n\nimport \"os\"\n\nfunc IsExist(path string) bool {\n\t_, err := os.Stat(path)\n\treturn err == nil || !os.IsNotExis"
},
{
"path": "common/format/user.go",
"chars": 124,
"preview": "package format\n\nimport (\n\t\"fmt\"\n)\n\nfunc UserTag(tag string, uuid string) string {\n\treturn fmt.Sprintf(\"%s|%s\", tag, uuid"
},
{
"path": "common/rate/rate.go",
"chars": 549,
"preview": "package rate\n\nimport (\n\t\"github.com/juju/ratelimit\"\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/comm"
},
{
"path": "common/systime/time_stub.go",
"chars": 155,
"preview": "//go:build !(windows || linux || darwin)\n\npackage systime\n\nimport (\n\t\"os\"\n\t\"time\"\n)\n\nfunc SetSystemTime(nowTime time.Tim"
},
{
"path": "common/systime/time_unix.go",
"chars": 226,
"preview": "//go:build linux || darwin\n\npackage systime\n\nimport (\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc SetSystemTime(nowTime ti"
},
{
"path": "common/systime/time_windows.go",
"chars": 774,
"preview": "package systime\n\nimport (\n\t\"time\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nfunc SetSystemTime(nowTime time.Time) error "
},
{
"path": "common/task/task.go",
"chars": 1226,
"preview": "package task\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// Task is a task that runs periodically.\ntype Task struct {\n\t// Interval of t"
},
{
"path": "common/task/task_test.go",
"chars": 196,
"preview": "package task\n\nimport (\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestTask(t *testing.T) {\n\tts := Task{Execute: func() error {\n\t\t"
},
{
"path": "conf/conf.go",
"chars": 1064,
"preview": "package conf\n\nimport (\n\t\"fmt\"\n\t\"gopkg.in/yaml.v3\"\n\t\"io\"\n\t\"os\"\n)\n\ntype Conf struct {\n\tCoreConfig CoreConfig `yaml:\"Co"
},
{
"path": "conf/conf_test.go",
"chars": 304,
"preview": "package conf\n\nimport (\n\t\"log\"\n\t\"testing\"\n)\n\nfunc TestConf_LoadFromPath(t *testing.T) {\n\tc := New()\n\tt.Log(c.LoadFromPath"
},
{
"path": "conf/core.go",
"chars": 993,
"preview": "package conf\n\ntype CoreConfig struct {\n\tType string `yaml:\"Type\"`\n\tXrayConfig *XrayConfig `yaml:\"XrayConfig\"`"
},
{
"path": "conf/log.go",
"chars": 271,
"preview": "package conf\n\ntype LogConfig struct {\n\tLevel string `yaml:\"Level\"`\n\tAccessPath string `yaml:\"AccessPath\"`\n\tErrorPat"
},
{
"path": "conf/node.go",
"chars": 4208,
"preview": "package conf\n\ntype NodeConfig struct {\n\tApiConfig *ApiConfig `yaml:\"ApiConfig\"`\n\tControllerConfig *Control"
},
{
"path": "conf/old.go",
"chars": 3229,
"preview": "package conf\n\nimport \"log\"\n\ntype OldConfig struct {\n\tNodesConfig []*struct {\n\t\tApiConfig *OldApiConfig `ya"
},
{
"path": "conf/watch.go",
"chars": 1300,
"preview": "package conf\n\nimport (\n\t\"fmt\"\n\t\"github.com/fsnotify/fsnotify\"\n\t\"log\"\n\t\"path\"\n\t\"time\"\n)\n\nfunc (p *Conf) Watch(filePath, d"
},
{
"path": "core/core.go",
"chars": 928,
"preview": "package core\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/Yuzuki616/V2bX/conf\"\n)\n\nvar (\n\tcores = map[string]func(c *conf"
},
{
"path": "core/hy/config.go",
"chars": 573,
"preview": "package hy\n\nconst (\n\tmbpsToBps = 125000\n\tminSpeedBPS = 16384\n\n\tDefaultALPN = \"hysteria\"\n\n\tDefaultStreamReceiveWindow "
},
{
"path": "core/hy/counter.go",
"chars": 1267,
"preview": "package hy\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\ntype UserTrafficCounter struct {\n\tcounters map[string]*counters\n\tlock "
},
{
"path": "core/hy/counter_test.go",
"chars": 80,
"preview": "package hy\n\nimport \"testing\"\n\nfunc TestUserTrafficCounter_Rx(t *testing.T) {\n\n}\n"
},
{
"path": "core/hy/hy.go",
"chars": 740,
"preview": "package hy\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\tvCore \"github.com/Yuzuki616/V2bX/core\"\n\t\"github."
},
{
"path": "core/hy/ipmasker.go",
"chars": 836,
"preview": "package hy\n\nimport (\n\t\"net\"\n)\n\ntype ipMasker struct {\n\tIPv4Mask net.IPMask\n\tIPv6Mask net.IPMask\n}\n\n// Mask masks an addr"
},
{
"path": "core/hy/kploader.go",
"chars": 2097,
"preview": "package hy\n\nimport (\n\t\"crypto/tls\"\n\t\"sync\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype keypai"
},
{
"path": "core/hy/mmdb.go",
"chars": 562,
"preview": "package hy\n\nimport (\n\t\"os\"\n\n\t\"github.com/oschwald/geoip2-golang\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc loadMMDBReader(fi"
},
{
"path": "core/hy/node.go",
"chars": 944,
"preview": "package hy\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"github."
},
{
"path": "core/hy/resolver.go",
"chars": 3142,
"preview": "package hy\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"github.com/Yuzuki616/hysteria/core/utils\"\n\trdns \"github.com/folbricht/rou"
},
{
"path": "core/hy/server.go",
"chars": 7664,
"preview": "package hy\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/Yuzuki616/V2bX/api/p"
},
{
"path": "core/hy/server_test.go",
"chars": 961,
"preview": "package hy\n\nimport (\n\t\"encoding/base64\"\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"githu"
},
{
"path": "core/hy/user.go",
"chars": 1069,
"preview": "package hy\n\nimport (\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/cor"
},
{
"path": "core/imports/hy.go",
"chars": 95,
"preview": "//go:build hy\n\npackage imports\n\n// not yet tested\nimport _ \"github.com/Yuzuki616/V2bX/core/hy\"\n"
},
{
"path": "core/imports/imports.go",
"chars": 16,
"preview": "package imports\n"
},
{
"path": "core/imports/xray.go",
"chars": 81,
"preview": "//go:build xray\n\npackage imports\n\nimport _ \"github.com/Yuzuki616/V2bX/core/xray\"\n"
},
{
"path": "core/interface.go",
"chars": 589,
"preview": "package core\n\nimport (\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n)\n\ntype AddUsersParams s"
},
{
"path": "core/selector.go",
"chars": 2045,
"preview": "package core\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"git"
},
{
"path": "core/xray/app/app.go",
"chars": 101,
"preview": "// Package app contains the third-party app used to replace the default app in xray-core\npackage app\n"
},
{
"path": "core/xray/app/dispatcher/config.pb.go",
"chars": 6504,
"preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.28.1\n// \tprotoc v3.21.12\n// so"
},
{
"path": "core/xray/app/dispatcher/config.proto",
"chars": 362,
"preview": "syntax = \"proto3\";\n\npackage v2bx.core.app.dispatcher;\noption csharp_namespace = \"V2bX.core.app.dispatcher\";\noption go_pa"
},
{
"path": "core/xray/app/dispatcher/default.go",
"chars": 17794,
"preview": "package dispatcher\n\n//go:generate go run github.com/xtls/xray-core/common/errors/errorgen\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\""
},
{
"path": "core/xray/app/dispatcher/dispatcher.go",
"chars": 90,
"preview": "package dispatcher\n\n//go:generate go run github.com/xtls/xray-core/common/errors/errorgen\n"
},
{
"path": "core/xray/app/dispatcher/errors.generated.go",
"chars": 219,
"preview": "package dispatcher\n\nimport \"github.com/xtls/xray-core/common/errors\"\n\ntype errPathObjHolder struct{}\n\nfunc newError(valu"
},
{
"path": "core/xray/app/dispatcher/fakednssniffer.go",
"chars": 3802,
"preview": "package dispatcher\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/comm"
},
{
"path": "core/xray/app/dispatcher/sniffer.go",
"chars": 3837,
"preview": "package dispatcher\n\nimport (\n\t\"context\"\n\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/net\"\n\t\"g"
},
{
"path": "core/xray/app/dispatcher/stats.go",
"chars": 514,
"preview": "package dispatcher\n\nimport (\n\t\"github.com/xtls/xray-core/common\"\n\t\"github.com/xtls/xray-core/common/buf\"\n\t\"github.com/xt"
},
{
"path": "core/xray/app/dispatcher/stats_test.go",
"chars": 818,
"preview": "package dispatcher_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/xtls/xray-core/app/dispatcher\"\n\t\"github.com/xtls/xray-core/"
},
{
"path": "core/xray/distro/all/all.go",
"chars": 3022,
"preview": "package all\n\nimport (\n\t// The following are necessary as they register handlers in their init functions.\n\n\t// Mandatory "
},
{
"path": "core/xray/inbound.go",
"chars": 10195,
"preview": "package xray\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/Yuzuk"
},
{
"path": "core/xray/node.go",
"chars": 2049,
"preview": "package xray\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"gith"
},
{
"path": "core/xray/outbound.go",
"chars": 1222,
"preview": "package xray\n\nimport (\n\t\"fmt\"\n\tconf2 \"github.com/Yuzuki616/V2bX/conf\"\n\t\"github.com/goccy/go-json\"\n\t\"github.com/xtls/xray"
},
{
"path": "core/xray/ss.go",
"chars": 1910,
"preview": "package xray\n\nimport (\n\t\"encoding/base64\"\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/common/form"
},
{
"path": "core/xray/trojan.go",
"chars": 757,
"preview": "package xray\n\nimport (\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/common/format\"\n\t\"github.com/xt"
},
{
"path": "core/xray/user.go",
"chars": 2880,
"preview": "package xray\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/common/form"
},
{
"path": "core/xray/vmess.go",
"chars": 1410,
"preview": "package xray\n\nimport (\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/common/format\"\n\t\"github.com/xt"
},
{
"path": "core/xray/xray.go",
"chars": 6240,
"preview": "package xray\n\nimport (\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\tvCore \"github.com/Yuzuki616/V2bX/core\"\n\t\"github"
},
{
"path": "example/config.yml.example",
"chars": 6441,
"preview": "CoreConfig:\n Type: \"xray\" # Core type, default support \"xray\" and \"hy\". If you need many cores, use \" \" to split\n Xray"
},
{
"path": "example/custom_inbound.json",
"chars": 403,
"preview": "[\n {\n \"listen\": \"0.0.0.0\",\n \"port\": 1234,\n \"protocol\": \"socks\",\n \"settings\": {\n "
},
{
"path": "example/custom_outbound.json",
"chars": 523,
"preview": "[\n {\n \"tag\": \"IPv4_out\",\n \"protocol\": \"freedom\",\n \"settings\": {}\n },\n {\n \"tag\": \"IP"
},
{
"path": "example/dns.json",
"chars": 110,
"preview": "{\n \"servers\": [\n \"1.1.1.1\",\n \"8.8.8.8\",\n \"localhost\"\n ],\n \"tag\": \"dns_inbound\"\n}"
},
{
"path": "example/route.json",
"chars": 768,
"preview": "{\n \"domainStrategy\": \"IPOnDemand\",\n \"rules\": [\n {\n \"type\": \"field\",\n \"outboundTag\": \""
},
{
"path": "example/rulelist",
"chars": 47,
"preview": "(.+\\.|^)(360|so)\\.(cn|com)\nbaidu.com\ngoogle.com"
},
{
"path": "go.mod",
"chars": 10628,
"preview": "module github.com/Yuzuki616/V2bX\n\ngo 1.19\n\nrequire (\n\tgithub.com/Yuzuki616/hysteria/core v0.0.0-20230722103310-05508b7e5"
},
{
"path": "go.sum",
"chars": 110081,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.31.0/go.mod h1"
},
{
"path": "limiter/clear.go",
"chars": 337,
"preview": "package limiter\n\nimport log \"github.com/sirupsen/logrus\"\n\nfunc ClearOnlineIP() error {\n\tlog.WithField(\"Type\", \"Limiter\")"
},
{
"path": "limiter/conn.go",
"chars": 3103,
"preview": "package limiter\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype ConnLimiter struct {\n\trealtime bool\n\tipLimit int\n\tconnLimit int\n\tco"
},
{
"path": "limiter/conn_test.go",
"chars": 1113,
"preview": "package limiter\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar c *ConnLimiter\n\nfunc init() {\n\tc = NewConnLimiter(1, 1, true"
},
{
"path": "limiter/dynamic.go",
"chars": 859,
"preview": "package limiter\n\nimport (\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/common/format\"\n\t\"time\"\n)\n\nf"
},
{
"path": "limiter/limiter.go",
"chars": 4216,
"preview": "package limiter\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuk"
},
{
"path": "limiter/rule.go",
"chars": 723,
"preview": "package limiter\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n)\n\nfunc (l *Limiter) CheckDomainRule(destina"
},
{
"path": "main.go",
"chars": 81,
"preview": "package main\n\nimport \"github.com/Yuzuki616/V2bX/cmd\"\n\nfunc main() {\n\tcmd.Run()\n}\n"
},
{
"path": "node/cert.go",
"chars": 1197,
"preview": "package node\n\nimport (\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/common/file\"\n\t\"github.com/Yuzuki616/V2bX/node/lego\"\n\t\"log\"\n)\n\n"
},
{
"path": "node/controller.go",
"chars": 3194,
"preview": "package node\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/Yuzuki616/V2bX/api/iprecoder\"\n\t\"github.com/Yuzuki616/V2bX/api/pane"
},
{
"path": "node/lego/cert.go",
"chars": 2564,
"preview": "package lego\n\nimport (\n\t\"fmt\"\n\t\"github.com/go-acme/lego/v4/certcrypto\"\n\t\"github.com/go-acme/lego/v4/certificate\"\n\t\"githu"
},
{
"path": "node/lego/lego.go",
"chars": 1085,
"preview": "package lego\n\nimport (\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/common/file\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"github.com/go"
},
{
"path": "node/lego/lego_test.go",
"chars": 625,
"preview": "package lego\n\nimport (\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n)\n\nvar l *Lego\n\nfunc init() {\n\tvar err "
},
{
"path": "node/lego/user.go",
"chars": 3094,
"preview": "package lego\n\nimport (\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\""
},
{
"path": "node/node.go",
"chars": 914,
"preview": "package node\n\nimport (\n\t\"fmt\"\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/conf\"\n\tvCore \"github.co"
},
{
"path": "node/task.go",
"chars": 5673,
"preview": "package node\n\nimport (\n\t\"time\"\n\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\t\"github.com/Yuzuki616/V2bX/common/task\"\n\tvCore \""
},
{
"path": "node/user.go",
"chars": 1722,
"preview": "package node\n\nimport (\n\t\"runtime\"\n\t\"strconv\"\n\n\t\"github.com/Yuzuki616/V2bX/api/panel\"\n\tlog \"github.com/sirupsen/logrus\"\n)"
},
{
"path": "test_data/1.key",
"chars": 1675,
"preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAyryUpSI01T4jgPywpt4CIaf+MNgPn9DxHtJmuo1VB7Ysk13Z\nuOBVfS9HJoikbrcVeqENkR8"
},
{
"path": "test_data/1.pem",
"chars": 1399,
"preview": "-----BEGIN CERTIFICATE-----\nMIID2zCCAsOgAwIBAgIRAJP0pRlp9k2eiBLK2a2B7LYwDQYJKoZIhvcNAQELBQAw\nXjELMAkGA1UEBhMCQ04xDjAMBgN"
}
]
About this extraction
This page contains the full source code of the Yuzuki616/V2bX GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 110 files (315.0 KB), approximately 125.3k tokens, and a symbol index with 353 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.