Compare commits

..

1 Commits

Author SHA1 Message Date
wyx2685
7715ea038d Update release.yml 2023-11-18 20:01:16 +08:00
59 changed files with 1385 additions and 3344 deletions

View File

@@ -1,25 +1,27 @@
--- ---
name: "Bug 反馈" name: "Bug 反馈"
about: 创建一个报告以帮助我们修复并改进V2bX about: 创建一个报告以帮助我们修复并改进XrayR
title: '' title: ''
labels: labels: awaiting reply, bug
assignees: '' assignees: ''
--- ---
**描述该错误** **描述该错误**
简单地描述一下这个bug是什么 简单地描述一下这个bug是什么
**复现** **复现**
请自行复现,并贴出详细步骤操作过程 复现该bug的步骤
**环境和版本**
- 系统 [例如Debian 11]
- 架构 [例如AMD64]
- 面板 [例如V2board]
- 协议 [例如vmess]
- 版本 [例如0.8.2.2]
- 部署方式 [例如:一键脚本]
**日志和错误** **日志和错误**
请使用`v2bx log`查看并添加日志,没有日志的issue不会得到答复并且会被直接关闭 请使用`xrayr log`查看并添加日志,以帮助解释你的问题
**额外的内容** **额外的内容**
在这里添加关于问题的任何其他内容 在这里添加关于问题的任何其他内容

View File

@@ -0,0 +1,19 @@
---
name: "功能建议"
about: 给XrayR提出建议让我们做得更好
title: ''
labels: awaiting reply, feature-request
assignees: ''
---
**描述您想要的功能**
清晰简洁的功能描述。
**描述您考虑过的替代方案**
是否有任何替代方案可以解决这个问题?
**附加上下文**
在此处添加有关功能请求的任何其他上下文或截图。

View File

@@ -1,94 +0,0 @@
name: Publish Docker image
on:
workflow_dispatch:
release:
types: [published]
pull_request:
branches:
- 'dev_new'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/v2bx
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v3
with:
name: digests
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v3
with:
name: digests
path: /tmp/digests
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
ls -al
echo docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

View File

@@ -34,38 +34,34 @@ jobs:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed # 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: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified 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. # 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 # queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Install Go
uses: actions/setup-go@v4
with:
go-version-file: go.mod
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# 📚 https://git.io/JvXDl # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# If the Autobuild fails above, remove it and uncomment the following three lines # Command-line programs to run using the OS shell.
# and modify them (or add more) to build your code if your project # 📚 https://git.io/JvXDl
# uses a compiled language
#- run: | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# make bootstrap # and modify them (or add more) to build your code if your project
# make release # uses a compiled language
- name: Perform CodeQL Analysis #- run: |
uses: github/codeql-action/analyze@v2 # make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -5,7 +5,6 @@ on:
push: push:
branches: branches:
- master - master
- dev_new
paths: paths:
- "**/*.go" - "**/*.go"
- "go.mod" - "go.mod"
@@ -98,7 +97,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Show workflow information - name: Show workflow information
id: get_filename id: get_filename
run: | run: |
@@ -107,9 +106,9 @@ jobs:
echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '1.23.2' go-version: '1.21.4'
- name: Get project dependencies - name: Get project dependencies
run: go mod download run: go mod download
@@ -125,13 +124,13 @@ jobs:
run: | run: |
echo "version: $version" echo "version: $version"
mkdir -p build_assets mkdir -p build_assets
go build -v -o build_assets/V2bX -tags "sing xray hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme with_gvisor" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid=" go build -v -o build_assets/V2bX -tags "sing xray with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid="
- name: Build Mips softfloat V2bX - name: Build Mips softfloat V2bX
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle' if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
run: | run: |
echo "version: $version" echo "version: $version"
GOMIPS=softfloat go build -v -o build_assets/V2bX_softfloat -tags "sing xray hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme with_gvisor" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid=" GOMIPS=softfloat go build -v -o build_assets/V2bX_softfloat -tags "sing xray with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid="
- name: Rename Windows V2bX - name: Rename Windows V2bX
if: matrix.goos == 'windows' if: matrix.goos == 'windows'
run: | run: |
@@ -142,13 +141,16 @@ jobs:
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
cp ${GITHUB_WORKSPACE}/example/*.json ./build_assets/ cp ${GITHUB_WORKSPACE}/example/*.json ./build_assets/
LIST=('geoip' 'geosite') LIST=('geoip geoip geoip' 'domain-list-community dlc geosite')
for i in "${LIST[@]}" for i in "${LIST[@]}"
do do
DOWNLOAD_URL="https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/${i}.dat" INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}'))
FILE_NAME="${i}.dat" FILE_NAME="${INFO[2]}.dat"
echo -e "Downloading ${DOWNLOAD_URL}..." echo -e "Downloading ${FILE_NAME}..."
curl -L "${DOWNLOAD_URL}" -o ./build_assets/${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 done
- name: Create ZIP archive - name: Create ZIP archive
shell: bash shell: bash
@@ -167,7 +169,7 @@ jobs:
run: | run: |
mv build_assets V2bX-$ASSET_NAME mv build_assets V2bX-$ASSET_NAME
- name: Upload files to Artifacts - name: Upload files to Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }} name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}
path: | path: |

4
.gitignore vendored
View File

@@ -12,6 +12,4 @@ app/legocmd/.lego/
example/.lego example/.lego
example/cert example/cert
./vscode ./vscode
output/* .idea/*
.idea/*
newV2bX.sh

View File

@@ -1,17 +0,0 @@
# Build go
FROM golang:1.23.2-alpine AS builder
WORKDIR /app
COPY . .
ENV CGO_ENABLED=0
RUN go mod download
RUN go build -v -o V2bX -tags "sing xray hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme with_gvisor"
# Release
FROM alpine
# 安装必要的工具包
RUN apk --update --no-cache add tzdata ca-certificates \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN mkdir /etc/V2bX/
COPY --from=builder /app/V2bX /usr/local/bin
ENTRYPOINT [ "V2bX", "server", "--config", "/etc/V2bX/config.json"]

View File

@@ -1,17 +1,16 @@
# V2bX # V2bX
[![](https://img.shields.io/badge/TgChat-UnOfficialV2Board%E4%BA%A4%E6%B5%81%E7%BE%A4-green)](https://t.me/unofficialV2board) [![](https://img.shields.io/badge/TgChat-%E4%BA%A4%E6%B5%81%E7%BE%A4-blue)](https://t.me/YuzukiProjects)
[![](https://img.shields.io/badge/TgChat-YuzukiProjects%E4%BA%A4%E6%B5%81%E7%BE%A4-blue)](https://t.me/YuzukiProjects)
A V2board node server based on multi core, modified from XrayR. A V2board node server based on multi core, modified from XrayR.
一个基于多种内核的V2board节点服务端修改自XrayR支持V2ay,Trojan,Shadowsocks协议。 一个基于多种内核的V2board节点服务端修改自XrayR支持V2ay,Trojan,Shadowsocks协议。
**注意: 本项目需要搭配[修改版V2board](https://github.com/wyx2685/v2board)** **注意: 本项目需要V2board版本 >= 1.7.0**
## 特点 ## 特点
* 永久开源且免费。 * 永久开源且免费。
* 支持Vmess/Vless, Trojan Shadowsocks, Hysteria1/2多种协议。 * 支持Vmess/Vless, Trojan Shadowsocks, Hysteria多种协议。
* 支持Vless和XTLS等新特性。 * 支持Vless和XTLS等新特性。
* 支持单实例对接多节点,无需重复启动。 * 支持单实例对接多节点,无需重复启动。
* 支持限制在线IP。 * 支持限制在线IP。
@@ -24,7 +23,7 @@ A V2board node server based on multi core, modified from XrayR.
## 功能介绍 ## 功能介绍
| 功能 | v2ray | trojan | shadowsocks | hysteria1/2 | | 功能 | v2ray | trojan | shadowsocks | hysteria |
|-----------|-------|--------|-------------|----------| |-----------|-------|--------|-------------|----------|
| 自动申请tls证书 | √ | √ | √ | √ | | 自动申请tls证书 | √ | √ | √ | √ |
| 自动续签tls证书 | √ | √ | √ | √ | | 自动续签tls证书 | √ | √ | √ | √ |
@@ -33,7 +32,7 @@ A V2board node server based on multi core, modified from XrayR.
| 自定义DNS | √ | √ | √ | √ | | 自定义DNS | √ | √ | √ | √ |
| 在线IP数限制 | √ | √ | √ | √ | | 在线IP数限制 | √ | √ | √ | √ |
| 连接数限制 | √ | √ | √ | √ | | 连接数限制 | √ | √ | √ | √ |
| 跨节点IP数限制 | | | | | | 跨节点IP数限制 | | | | |
| 按照用户限速 | √ | √ | √ | √ | | 按照用户限速 | √ | √ | √ | √ |
| 动态限速(未测试) | √ | √ | √ | √ | | 动态限速(未测试) | √ | √ | √ | √ |
@@ -48,22 +47,22 @@ A V2board node server based on multi core, modified from XrayR.
### 一键安装 ### 一键安装
``` ```
wget -N https://raw.githubusercontent.com/wyx2685/V2bX-script/master/install.sh && bash install.sh wget -N https://raw.githubusercontents.com/wyx2685/V2bX-script/master/install.sh && bash install.sh
``` ```
### 手动安装 ### 手动安装
[手动安装教程](https://v2bx.v-50.me/v2bx/v2bx-xia-zai-he-an-zhuang/install/manual) [手动安装教程(过时待更新)](https://yuzuki-1.gitbook.io/v2bx-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
## 构建 ## 构建
``` bash ``` bash
# 通过-tags选项指定要编译的内核 可选 xray sing, hysteria2 # 通过-tags选项指定要编译的内核 可选 xray sing
go build -v -o ./V2bX -tags "xray sing hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-s -w -buildid=" go build -o V2bX -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD} -tags "xray sing"
``` ```
## 配置文件及详细使用教程 ## 配置文件及详细使用教程
[详细使用教程](https://v2bx.v-50.me/) [详细使用教程](https://yuzuki-1.gitbook.io/v2bx-doc/)
## 免责声明 ## 免责声明
@@ -83,4 +82,4 @@ go build -v -o ./V2bX -tags "xray sing hysteria2 with_reality_server with_quic w
## Stars 增长记录 ## Stars 增长记录
[![Stargazers over time](https://starchart.cc/wyx2685/V2bX.svg)](https://starchart.cc/wyx2685/V2bX) [![Stargazers over time](https://starchart.cc/InazumaV/V2bX.svg)](https://starchart.cc/InazumaV/V2bX)

View File

@@ -1,14 +1,14 @@
package panel package panel
import ( import (
"crypto/sha256" "encoding/base64"
"encoding/hex"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/InazumaV/V2bX/common/crypt"
"github.com/goccy/go-json" "github.com/goccy/go-json"
) )
@@ -78,7 +78,6 @@ type TlsSettings struct {
ServerPort string `json:"server_port"` ServerPort string `json:"server_port"`
ShortId string `json:"short_id"` ShortId string `json:"short_id"`
PrivateKey string `json:"private_key"` PrivateKey string `json:"private_key"`
Xver uint64 `json:"xver,string"`
} }
type RealityConfig struct { type RealityConfig struct {
@@ -94,11 +93,7 @@ type ShadowsocksNode struct {
ServerKey string `json:"server_key"` ServerKey string `json:"server_key"`
} }
type TrojanNode struct { type TrojanNode CommonNode
CommonNode
Network string `json:"network"`
NetworkSettings json.RawMessage `json:"networkSettings"`
}
type HysteriaNode struct { type HysteriaNode struct {
CommonNode CommonNode
@@ -130,32 +125,13 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
r, err := c.client. r, err := c.client.
R(). R().
SetHeader("If-None-Match", c.nodeEtag). SetHeader("If-None-Match", c.nodeEtag).
ForceContentType("application/json").
Get(path) Get(path)
if err = c.checkResponse(r, path, err); err != nil {
return
}
if r.StatusCode() == 304 { if r.StatusCode() == 304 {
return nil, nil return nil, nil
} }
hash := sha256.Sum256(r.Body())
newBodyHash := hex.EncodeToString(hash[:])
if c.responseBodyHash == newBodyHash {
return nil, nil
}
c.responseBodyHash = newBodyHash
c.nodeEtag = r.Header().Get("ETag")
if err = c.checkResponse(r, path, err); err != nil {
return nil, err
}
if r != nil {
defer func() {
if r.RawBody() != nil {
r.RawBody().Close()
}
}()
} else {
return nil, fmt.Errorf("received nil response")
}
node = &NodeInfo{ node = &NodeInfo{
Id: c.NodeId, Id: c.NodeId,
Type: c.NodeType, Type: c.NodeType,
@@ -184,6 +160,18 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
cm = &rsp.CommonNode cm = &rsp.CommonNode
node.VAllss = rsp node.VAllss = rsp
node.Security = node.VAllss.Tls node.Security = node.VAllss.Tls
if len(rsp.NetworkSettings) > 0 {
err = json.Unmarshal(rsp.NetworkSettings, &rsp.RealityConfig)
if err != nil {
return nil, fmt.Errorf("decode reality config error: %s", err)
}
}
if node.Security == Reality {
if rsp.TlsSettings.PrivateKey == "" {
key := crypt.GenX25519Private([]byte("vless" + c.Token))
rsp.TlsSettings.PrivateKey = base64.RawURLEncoding.EncodeToString(key)
}
}
case "shadowsocks": case "shadowsocks":
rsp := &ShadowsocksNode{} rsp := &ShadowsocksNode{}
err = json.Unmarshal(r.Body(), rsp) err = json.Unmarshal(r.Body(), rsp)
@@ -199,7 +187,7 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("decode trojan params error: %s", err) return nil, fmt.Errorf("decode trojan params error: %s", err)
} }
cm = &rsp.CommonNode cm = (*CommonNode)(rsp)
node.Trojan = rsp node.Trojan = rsp
node.Security = Tls node.Security = Tls
case "hysteria": case "hysteria":
@@ -249,7 +237,9 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
} }
case "dns": case "dns":
var domains []string var domains []string
domains = append(domains, matchs...) for _, v := range matchs {
domains = append(domains, v)
}
if matchs[0] != "main" { if matchs[0] != "main" {
node.RawDNS.DNSMap[strconv.Itoa(i)] = map[string]interface{}{ node.RawDNS.DNSMap[strconv.Itoa(i)] = map[string]interface{}{
"address": cm.Routes[i].ActionValue, "address": cm.Routes[i].ActionValue,
@@ -258,6 +248,7 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
} else { } else {
dns := []byte(strings.Join(matchs[1:], "")) dns := []byte(strings.Join(matchs[1:], ""))
node.RawDNS.DNSJson = dns node.RawDNS.DNSJson = dns
break
} }
} }
} }
@@ -271,7 +262,8 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
cm.Routes = nil cm.Routes = nil
cm.BaseConfig = nil cm.BaseConfig = nil
return node, nil c.nodeEtag = r.Header().Get("ETag")
return
} }
func intervalToTime(i interface{}) time.Duration { func intervalToTime(i interface{}) time.Duration {

View File

@@ -23,9 +23,7 @@ type Client struct {
NodeId int NodeId int
nodeEtag string nodeEtag string
userEtag string userEtag string
responseBodyHash string LastReportOnline map[int]int
UserList *UserListBody
AliveMap *AliveMap
} }
func New(c *conf.ApiConfig) (*Client, error) { func New(c *conf.ApiConfig) (*Client, error) {
@@ -72,7 +70,5 @@ func New(c *conf.ApiConfig) (*Client, error) {
APIHost: c.APIHost, APIHost: c.APIHost,
NodeType: c.NodeType, NodeType: c.NodeType,
NodeId: c.NodeID, NodeId: c.NodeID,
UserList: &UserListBody{},
AliveMap: &AliveMap{},
}, nil }, nil
} }

View File

@@ -16,6 +16,7 @@ type UserInfo struct {
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
SpeedLimit int `json:"speed_limit"` SpeedLimit int `json:"speed_limit"`
DeviceLimit int `json:"device_limit"` DeviceLimit int `json:"device_limit"`
AliveIp int `json:"alive_ip"`
} }
type UserListBody struct { type UserListBody struct {
@@ -23,60 +24,50 @@ type UserListBody struct {
Users []UserInfo `json:"users"` Users []UserInfo `json:"users"`
} }
type AliveMap struct { // GetUserList will pull user form sspanel
Alive map[int]int `json:"alive"` func (c *Client) GetUserList() (UserList []UserInfo, err error) {
}
// GetUserList will pull user from v2board
func (c *Client) GetUserList() ([]UserInfo, error) {
const path = "/api/v1/server/UniProxy/user" const path = "/api/v1/server/UniProxy/user"
r, err := c.client.R(). r, err := c.client.R().
SetHeader("If-None-Match", c.userEtag). SetHeader("If-None-Match", c.userEtag).
ForceContentType("application/json").
Get(path) Get(path)
if r == nil || r.RawResponse == nil { err = c.checkResponse(r, path, err)
return nil, fmt.Errorf("received nil response or raw response") if err != nil {
return nil, err
} }
defer r.RawResponse.Body.Close()
if r.StatusCode() == 304 { if r.StatusCode() == 304 {
return nil, nil return nil, nil
} }
var userList *UserListBody
if err = c.checkResponse(r, path, err); err != nil { err = json.Unmarshal(r.Body(), &userList)
return nil, err if err != nil {
} return nil, fmt.Errorf("unmarshal userlist error: %s", err)
userlist := &UserListBody{}
if err := json.Unmarshal(r.Body(), userlist); err != nil {
return nil, fmt.Errorf("unmarshal user list error: %w", err)
} }
c.userEtag = r.Header().Get("ETag") c.userEtag = r.Header().Get("ETag")
return userlist.Users, nil
}
// GetUserAlive will fetch the alive_ip count for users var userinfos []UserInfo
func (c *Client) GetUserAlive() (map[int]int, error) { var localDeviceLimit int = 0
c.AliveMap = &AliveMap{} for _, user := range userList.Users {
const path = "/api/v1/server/UniProxy/alivelist" // If there is still device available, add the user
r, err := c.client.R(). if user.DeviceLimit > 0 && user.AliveIp > 0 {
ForceContentType("application/json"). lastOnline := 0
Get(path) if v, ok := c.LastReportOnline[user.Id]; ok {
if err != nil || r.StatusCode() >= 399 { lastOnline = v
c.AliveMap.Alive = make(map[int]int) }
return c.AliveMap.Alive, nil // If there are any available device.
} localDeviceLimit = user.DeviceLimit - user.AliveIp + lastOnline
if r == nil || r.RawResponse == nil { if localDeviceLimit > 0 {
fmt.Printf("received nil response or raw response")
c.AliveMap.Alive = make(map[int]int) } else if lastOnline > 0 {
return c.AliveMap.Alive, nil
} } else {
defer r.RawResponse.Body.Close() continue
if err := json.Unmarshal(r.Body(), c.AliveMap); err != nil { }
fmt.Printf("unmarshal user alive list error: %s", err) }
c.AliveMap.Alive = make(map[int]int) userinfos = append(userinfos, user)
} }
return c.AliveMap.Alive, nil return userinfos, nil
} }
type UserTraffic struct { type UserTraffic struct {
@@ -103,7 +94,8 @@ func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
return nil return nil
} }
func (c *Client) ReportNodeOnlineUsers(data *map[int][]string) error { func (c *Client) ReportNodeOnlineUsers(data *map[int][]string, reportOnline *map[int]int) error {
c.LastReportOnline = *reportOnline
const path = "/api/v1/server/UniProxy/alive" const path = "/api/v1/server/UniProxy/alive"
r, err := c.client.R(). r, err := c.client.R().
SetBody(data). SetBody(data).

View File

@@ -1,11 +1,6 @@
package cmd package cmd
import ( import (
"os"
"os/signal"
"runtime"
"syscall"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"
vCore "github.com/InazumaV/V2bX/core" vCore "github.com/InazumaV/V2bX/core"
"github.com/InazumaV/V2bX/limiter" "github.com/InazumaV/V2bX/limiter"
@@ -13,6 +8,10 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
"os"
"os/signal"
"runtime"
"syscall"
) )
var ( var (

View File

@@ -2,7 +2,9 @@ package cmd
import ( import (
"fmt" "fmt"
"strings"
vCore "github.com/InazumaV/V2bX/core"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -33,8 +35,8 @@ _/ _/ _/ _/ _/ _/
_/ _/_/_/_/ _/_/_/ _/ _/ _/ _/_/_/_/ _/_/_/ _/ _/
`) `)
fmt.Printf("%s %s (%s) \n", codename, version, intro) fmt.Printf("%s %s (%s) \n", codename, version, intro)
//fmt.Printf("Supported cores: %s\n", strings.Join(vCore.RegisteredCore(), ", ")) fmt.Printf("Supported cores: %s\n", strings.Join(vCore.RegisteredCore(), ", "))
// Warning // Warning
//fmt.Println(Warn("This version need V2board version >= 1.7.0.")) 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")) fmt.Println(Warn("The version have many changed for config, please check your config file"))
} }

View File

@@ -6,7 +6,8 @@ import (
) )
type TrafficCounter struct { type TrafficCounter struct {
counters sync.Map counters map[string]*TrafficStorage
lock sync.RWMutex
} }
type TrafficStorage struct { type TrafficStorage struct {
@@ -15,52 +16,60 @@ type TrafficStorage struct {
} }
func NewTrafficCounter() *TrafficCounter { func NewTrafficCounter() *TrafficCounter {
return &TrafficCounter{} return &TrafficCounter{
counters: map[string]*TrafficStorage{},
}
} }
func (c *TrafficCounter) GetCounter(id string) *TrafficStorage { func (c *TrafficCounter) GetCounter(id string) *TrafficStorage {
if cts, ok := c.counters.Load(id); ok { c.lock.RLock()
return cts.(*TrafficStorage) cts, ok := c.counters[id]
c.lock.RUnlock()
if !ok {
cts = &TrafficStorage{}
c.counters[id] = cts
} }
newStorage := &TrafficStorage{} return cts
if cts, loaded := c.counters.LoadOrStore(id, newStorage); loaded {
return cts.(*TrafficStorage)
}
return newStorage
} }
func (c *TrafficCounter) GetUpCount(id string) int64 { func (c *TrafficCounter) GetUpCount(id string) int64 {
if cts, ok := c.counters.Load(id); ok { c.lock.RLock()
return cts.(*TrafficStorage).UpCounter.Load() cts, ok := c.counters[id]
c.lock.RUnlock()
if ok {
return cts.UpCounter.Load()
} }
return 0 return 0
} }
func (c *TrafficCounter) GetDownCount(id string) int64 { func (c *TrafficCounter) GetDownCount(id string) int64 {
if cts, ok := c.counters.Load(id); ok { c.lock.RLock()
return cts.(*TrafficStorage).DownCounter.Load() cts, ok := c.counters[id]
c.lock.RUnlock()
if ok {
return cts.DownCounter.Load()
} }
return 0 return 0
} }
func (c *TrafficCounter) Len() int { func (c *TrafficCounter) Len() int {
length := 0 c.lock.RLock()
c.counters.Range(func(_, _ interface{}) bool { defer c.lock.RUnlock()
length++ return len(c.counters)
return true
})
return length
} }
func (c *TrafficCounter) Reset(id string) { func (c *TrafficCounter) Reset(id string) {
if cts, ok := c.counters.Load(id); ok { c.lock.RLock()
cts.(*TrafficStorage).UpCounter.Store(0) cts := c.GetCounter(id)
cts.(*TrafficStorage).DownCounter.Store(0) c.lock.RUnlock()
} cts.UpCounter.Store(0)
cts.DownCounter.Store(0)
} }
func (c *TrafficCounter) Delete(id string) { func (c *TrafficCounter) Delete(id string) {
c.counters.Delete(id) c.lock.Lock()
delete(c.counters, id)
c.lock.Unlock()
} }
func (c *TrafficCounter) Rx(id string, n int) { func (c *TrafficCounter) Rx(id string, n int) {
@@ -72,3 +81,11 @@ func (c *TrafficCounter) Tx(id string, n int) {
cts := c.GetCounter(id) cts := c.GetCounter(id)
cts.UpCounter.Add(int64(n)) cts.UpCounter.Add(int64(n))
} }
func (c *TrafficCounter) IncConn(auth string) {
return
}
func (c *TrafficCounter) DecConn(auth string) {
return
}

View File

@@ -4,6 +4,9 @@ import (
"net" "net"
"github.com/juju/ratelimit" "github.com/juju/ratelimit"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/network"
) )
func NewConnRateLimiter(c net.Conn, l *ratelimit.Bucket) *Conn { func NewConnRateLimiter(c net.Conn, l *ratelimit.Bucket) *Conn {
@@ -28,7 +31,6 @@ func (c *Conn) Write(b []byte) (n int, err error) {
return c.Conn.Write(b) return c.Conn.Write(b)
} }
/*
type PacketConnCounter struct { type PacketConnCounter struct {
network.PacketConn network.PacketConn
limiter *ratelimit.Bucket limiter *ratelimit.Bucket
@@ -45,11 +47,10 @@ func (p *PacketConnCounter) ReadPacket(buff *buf.Buffer) (destination M.Socksadd
pLen := buff.Len() pLen := buff.Len()
destination, err = p.PacketConn.ReadPacket(buff) destination, err = p.PacketConn.ReadPacket(buff)
p.limiter.Wait(int64(buff.Len() - pLen)) p.limiter.Wait(int64(buff.Len() - pLen))
return destination, err return
} }
func (p *PacketConnCounter) WritePacket(buff *buf.Buffer, destination M.Socksaddr) (err error) { func (p *PacketConnCounter) WritePacket(buff *buf.Buffer, destination M.Socksaddr) (err error) {
p.limiter.Wait(int64(buff.Len())) p.limiter.Wait(int64(buff.Len()))
return p.PacketConn.WritePacket(buff, destination) return p.PacketConn.WritePacket(buff, destination)
} }
*/

View File

@@ -2,10 +2,8 @@ package conf
import ( import (
"fmt" "fmt"
"io"
"os"
"github.com/InazumaV/V2bX/common/json5" "github.com/InazumaV/V2bX/common/json5"
"os"
"github.com/goccy/go-json" "github.com/goccy/go-json"
) )
@@ -31,17 +29,5 @@ func (p *Conf) LoadFromPath(filePath string) error {
return fmt.Errorf("open config file error: %s", err) return fmt.Errorf("open config file error: %s", err)
} }
defer f.Close() defer f.Close()
return json.NewDecoder(json5.NewTrimNodeReader(f)).Decode(p)
reader := json5.NewTrimNodeReader(f)
data, err := io.ReadAll(reader)
if err != nil {
return fmt.Errorf("read config file error: %s", err)
}
err = json.Unmarshal(data, p)
if err != nil {
return fmt.Errorf("unmarshal config error: %s", err)
}
return nil
} }

View File

@@ -5,11 +5,10 @@ import (
) )
type CoreConfig struct { type CoreConfig struct {
Type string `json:"Type"` Type string `json:"Type"`
Name string `json:"Name"` Name string `json:"Name"`
XrayConfig *XrayConfig `json:"-"` XrayConfig *XrayConfig `json:"-"`
SingConfig *SingConfig `json:"-"` SingConfig *SingConfig `json:"-"`
Hysteria2Config *Hysteria2Config `json:"-"`
} }
type _CoreConfig CoreConfig type _CoreConfig CoreConfig
@@ -26,9 +25,6 @@ func (c *CoreConfig) UnmarshalJSON(b []byte) error {
case "sing": case "sing":
c.SingConfig = NewSingConfig() c.SingConfig = NewSingConfig()
return json.Unmarshal(b, c.SingConfig) return json.Unmarshal(b, c.SingConfig)
case "hysteria2":
c.Hysteria2Config = NewHysteria2Config()
return json.Unmarshal(b, c.Hysteria2Config)
} }
return nil return nil
} }

View File

@@ -1,17 +0,0 @@
package conf
type Hysteria2Config struct {
LogConfig Hysteria2LogConfig `json:"Log"`
}
type Hysteria2LogConfig struct {
Level string `json:"Level"`
}
func NewHysteria2Config() *Hysteria2Config {
return &Hysteria2Config{
LogConfig: Hysteria2LogConfig{
Level: "error",
},
}
}

View File

@@ -113,7 +113,6 @@ type Options struct {
RawOptions json.RawMessage `json:"RawOptions"` RawOptions json.RawMessage `json:"RawOptions"`
XrayOptions *XrayOptions `json:"XrayOptions"` XrayOptions *XrayOptions `json:"XrayOptions"`
SingOptions *SingOptions `json:"SingOptions"` SingOptions *SingOptions `json:"SingOptions"`
Hysteria2ConfigPath string `json:"Hysteria2ConfigPath"`
CertConfig *CertConfig `json:"CertConfig"` CertConfig *CertConfig `json:"CertConfig"`
} }
@@ -130,9 +129,6 @@ func (o *Options) UnmarshalJSON(data []byte) error {
case "sing": case "sing":
o.SingOptions = NewSingOptions() o.SingOptions = NewSingOptions()
return json.Unmarshal(data, o.SingOptions) return json.Unmarshal(data, o.SingOptions)
case "hysteria2":
o.RawOptions = data
return nil
default: default:
o.Core = "" o.Core = ""
o.RawOptions = data o.RawOptions = data

View File

@@ -5,9 +5,11 @@ import (
) )
type SingConfig struct { type SingConfig struct {
LogConfig SingLogConfig `json:"Log"` LogConfig SingLogConfig `json:"Log"`
NtpConfig SingNtpConfig `json:"NTP"` NtpConfig SingNtpConfig `json:"NTP"`
OriginalPath string `json:"OriginalPath"` EnableConnClear bool `json:"EnableConnClear"`
DnsConfigPath string `json:"DnsConfigPath"`
OriginalPath string `json:"OriginalPath"`
} }
type SingLogConfig struct { type SingLogConfig struct {
@@ -32,13 +34,13 @@ func NewSingConfig() *SingConfig {
} }
type SingOptions struct { type SingOptions struct {
EnableProxyProtocol bool `json:"EnableProxyProtocol"`
TCPFastOpen bool `json:"EnableTFO"` TCPFastOpen bool `json:"EnableTFO"`
SniffEnabled bool `json:"EnableSniff"` SniffEnabled bool `json:"EnableSniff"`
SniffOverrideDestination bool `json:"SniffOverrideDestination"`
EnableDNS bool `json:"EnableDNS"` EnableDNS bool `json:"EnableDNS"`
DomainStrategy option.DomainStrategy `json:"DomainStrategy"` DomainStrategy option.DomainStrategy `json:"DomainStrategy"`
SniffOverrideDestination bool `json:"SniffOverrideDestination"`
FallBackConfigs *FallBackConfigForSing `json:"FallBackConfigs"` FallBackConfigs *FallBackConfigForSing `json:"FallBackConfigs"`
Multiplex *MultiplexConfig `json:"MultiplexConfig"`
} }
type SingNtpConfig struct { type SingNtpConfig struct {
@@ -58,25 +60,13 @@ type FallBack struct {
ServerPort string `json:"ServerPort"` ServerPort string `json:"ServerPort"`
} }
type MultiplexConfig struct {
Enabled bool `json:"Enable"`
Padding bool `json:"Padding"`
Brutal BrutalOptions `json:"Brutal"`
}
type BrutalOptions struct {
Enabled bool `json:"Enable"`
UpMbps int `json:"UpMbps"`
DownMbps int `json:"DownMbps"`
}
func NewSingOptions() *SingOptions { func NewSingOptions() *SingOptions {
return &SingOptions{ return &SingOptions{
EnableDNS: false, EnableDNS: false,
EnableProxyProtocol: false,
TCPFastOpen: false, TCPFastOpen: false,
SniffEnabled: true, SniffEnabled: true,
SniffOverrideDestination: true, SniffOverrideDestination: true,
FallBackConfigs: &FallBackConfigForSing{}, FallBackConfigs: &FallBackConfigForSing{},
Multiplex: &MultiplexConfig{},
} }
} }

View File

@@ -2,12 +2,12 @@ package conf
import ( import (
"fmt" "fmt"
"github.com/fsnotify/fsnotify"
"log" "log"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/fsnotify/fsnotify"
) )
func (p *Conf) Watch(filePath, xDnsPath string, sDnsPath string, reload func()) error { func (p *Conf) Watch(filePath, xDnsPath string, sDnsPath string, reload func()) error {
@@ -34,7 +34,7 @@ func (p *Conf) Watch(filePath, xDnsPath string, sDnsPath string, reload func())
case filepath.Base(xDnsPath), filepath.Base(sDnsPath): case filepath.Base(xDnsPath), filepath.Base(sDnsPath):
log.Println("DNS file changed, reloading...") log.Println("DNS file changed, reloading...")
default: default:
log.Println("config file changed, reloading...") log.Println("config dir changed, reloading...")
} }
*p = *New() *p = *New()
err := p.LoadFromPath(filePath) err := p.LoadFromPath(filePath)
@@ -51,18 +51,18 @@ func (p *Conf) Watch(filePath, xDnsPath string, sDnsPath string, reload func())
} }
} }
}() }()
err = watcher.Add(filePath) err = watcher.Add(path.Dir(filePath))
if err != nil { if err != nil {
return fmt.Errorf("watch file error: %s", err) return fmt.Errorf("watch file error: %s", err)
} }
if xDnsPath != "" { if xDnsPath != "" {
err = watcher.Add(xDnsPath) err = watcher.Add(path.Dir(xDnsPath))
if err != nil { if err != nil {
return fmt.Errorf("watch dns file error: %s", err) return fmt.Errorf("watch dns file error: %s", err)
} }
} }
if sDnsPath != "" { if sDnsPath != "" {
err = watcher.Add(sDnsPath) err = watcher.Add(path.Dir(sDnsPath))
if err != nil { if err != nil {
return fmt.Errorf("watch dns file error: %s", err) return fmt.Errorf("watch dns file error: %s", err)
} }

View File

@@ -1,431 +0,0 @@
package hy2
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"time"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf"
"github.com/apernet/hysteria/core/v2/server"
"github.com/apernet/hysteria/extras/v2/correctnet"
"github.com/apernet/hysteria/extras/v2/masq"
"github.com/apernet/hysteria/extras/v2/obfs"
"github.com/apernet/hysteria/extras/v2/outbounds"
"go.uber.org/zap"
)
type masqHandlerLogWrapper struct {
H http.Handler
QUIC bool
Logger *zap.Logger
}
func (m *masqHandlerLogWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m.Logger.Debug("masquerade request",
zap.String("addr", r.RemoteAddr),
zap.String("method", r.Method),
zap.String("host", r.Host),
zap.String("url", r.URL.String()),
zap.Bool("quic", m.QUIC))
m.H.ServeHTTP(w, r)
}
const (
Byte = 1
Kilobyte = Byte * 1000
Megabyte = Kilobyte * 1000
Gigabyte = Megabyte * 1000
Terabyte = Gigabyte * 1000
)
const (
defaultStreamReceiveWindow = 8388608 // 8MB
defaultConnReceiveWindow = defaultStreamReceiveWindow * 5 / 2 // 20MB
defaultMaxIdleTimeout = 30 * time.Second
defaultMaxIncomingStreams = 4096
defaultUDPIdleTimeout = 60 * time.Second
)
func (n *Hysteria2node) getTLSConfig(config *conf.Options) (*server.TLSConfig, error) {
if config.CertConfig == nil {
return nil, fmt.Errorf("the CertConfig is not vail")
}
switch config.CertConfig.CertMode {
case "none", "":
return nil, fmt.Errorf("the CertMode cannot be none")
default:
var certs []tls.Certificate
cert, err := tls.LoadX509KeyPair(config.CertConfig.CertFile, config.CertConfig.KeyFile)
if err != nil {
return nil, err
}
certs = append(certs, cert)
return &server.TLSConfig{
Certificates: certs,
GetCertificate: func(tlsinfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(config.CertConfig.CertFile, config.CertConfig.KeyFile)
return &cert, err
},
}, nil
}
}
func (n *Hysteria2node) getQUICConfig(config *serverConfig) (*server.QUICConfig, error) {
quic := &server.QUICConfig{}
if config.QUIC.InitStreamReceiveWindow == 0 {
quic.InitialStreamReceiveWindow = defaultStreamReceiveWindow
} else if config.QUIC.InitStreamReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.InitialStreamReceiveWindowf must be at least 16384")
} else {
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
}
if config.QUIC.MaxStreamReceiveWindow == 0 {
quic.MaxStreamReceiveWindow = defaultStreamReceiveWindow
} else if config.QUIC.MaxStreamReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.MaxStreamReceiveWindowf must be at least 16384")
} else {
quic.MaxStreamReceiveWindow = config.QUIC.MaxStreamReceiveWindow
}
if config.QUIC.InitConnectionReceiveWindow == 0 {
quic.InitialConnectionReceiveWindow = defaultConnReceiveWindow
} else if config.QUIC.InitConnectionReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.InitialConnectionReceiveWindowf must be at least 16384")
} else {
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
}
if config.QUIC.MaxConnectionReceiveWindow == 0 {
quic.MaxConnectionReceiveWindow = defaultConnReceiveWindow
} else if config.QUIC.MaxConnectionReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.MaxConnectionReceiveWindowf must be at least 16384")
} else {
quic.MaxConnectionReceiveWindow = config.QUIC.MaxConnectionReceiveWindow
}
if config.QUIC.MaxIdleTimeout == 0 {
quic.MaxIdleTimeout = defaultMaxIdleTimeout
} else if config.QUIC.MaxIdleTimeout < 4*time.Second || config.QUIC.MaxIdleTimeout > 120*time.Second {
return nil, fmt.Errorf("QUICConfig.MaxIdleTimeoutf must be between 4s and 120s")
} else {
quic.MaxIdleTimeout = config.QUIC.MaxIdleTimeout
}
if config.QUIC.MaxIncomingStreams == 0 {
quic.MaxIncomingStreams = defaultMaxIncomingStreams
} else if config.QUIC.MaxIncomingStreams < 8 {
return nil, fmt.Errorf("QUICConfig.MaxIncomingStreamsf must be at least 8")
} else {
quic.MaxIncomingStreams = config.QUIC.MaxIncomingStreams
}
// todo fix !linux && !windows && !darwin
quic.DisablePathMTUDiscovery = false
return quic, nil
}
func (n *Hysteria2node) getConn(info *panel.NodeInfo, config *conf.Options) (net.PacketConn, error) {
uAddr, err := net.ResolveUDPAddr("udp", formatAddress(config.ListenIP, info.Common.ServerPort))
if err != nil {
return nil, err
}
conn, err := correctnet.ListenUDP("udp", uAddr)
if err != nil {
return nil, err
}
switch strings.ToLower(info.Hysteria2.ObfsType) {
case "", "plain":
return conn, nil
case "salamander":
ob, err := obfs.NewSalamanderObfuscator([]byte(info.Hysteria2.ObfsPassword))
if err != nil {
return nil, err
}
return obfs.WrapPacketConn(conn, ob), nil
default:
return nil, fmt.Errorf("unsupported obfuscation type")
}
}
func (n *Hysteria2node) getBandwidthConfig(info *panel.NodeInfo) *server.BandwidthConfig {
band := &server.BandwidthConfig{}
if info.Hysteria2.UpMbps != 0 {
band.MaxTx = (uint64)(info.Hysteria2.UpMbps * Megabyte / 8)
}
if info.Hysteria2.DownMbps != 0 {
band.MaxRx = (uint64)(info.Hysteria2.DownMbps * Megabyte / 8)
}
return band
}
func (n *Hysteria2node) getOutboundConfig(c *serverConfig) (server.Outbound, error) {
// Resolver, ACL, actual outbound are all implemented through the Outbound interface.
// Depending on the config, we build a chain like this:
// Resolver(ACL(Outbounds...))
// Outbounds
var obs []outbounds.OutboundEntry
if len(c.Outbounds) == 0 {
// Guarantee we have at least one outbound
obs = []outbounds.OutboundEntry{{
Name: "default",
Outbound: outbounds.NewDirectOutboundSimple(outbounds.DirectOutboundModeAuto),
}}
} else {
obs = make([]outbounds.OutboundEntry, len(c.Outbounds))
for i, entry := range c.Outbounds {
if entry.Name == "" {
return nil, fmt.Errorf("empty outbound name")
}
var ob outbounds.PluggableOutbound
var err error
switch strings.ToLower(entry.Type) {
case "direct":
ob, err = serverConfigOutboundDirectToOutbound(entry.Direct)
case "socks5":
ob, err = serverConfigOutboundSOCKS5ToOutbound(entry.SOCKS5)
case "http":
ob, err = serverConfigOutboundHTTPToOutbound(entry.HTTP)
default:
err = fmt.Errorf("outbounds.type unsupported outbound type")
}
if err != nil {
return nil, err
}
obs[i] = outbounds.OutboundEntry{Name: entry.Name, Outbound: ob}
}
}
var uOb outbounds.PluggableOutbound // "unified" outbound
// ACL
hasACL := false
if c.ACL.File != "" && len(c.ACL.Inline) > 0 {
return nil, fmt.Errorf("cannot set both acl.file and acl.inline")
}
gLoader := &GeoLoader{
GeoIPFilename: c.ACL.GeoIP,
GeoSiteFilename: c.ACL.GeoSite,
UpdateInterval: c.ACL.GeoUpdateInterval,
Logger: n.Logger,
}
if c.ACL.File != "" {
hasACL = true
acl, err := outbounds.NewACLEngineFromFile(c.ACL.File, obs, gLoader)
if err != nil {
return nil, err
}
uOb = acl
} else if len(c.ACL.Inline) > 0 {
n.Logger.Debug("found ACL Inline:", zap.Strings("Inline", c.ACL.Inline))
hasACL = true
acl, err := outbounds.NewACLEngineFromString(strings.Join(c.ACL.Inline, "\n"), obs, gLoader)
if err != nil {
return nil, err
}
uOb = acl
} else {
// No ACL, use the first outbound
uOb = obs[0].Outbound
}
switch strings.ToLower(c.Resolver.Type) {
case "", "system":
if hasACL {
// If the user uses ACL, we must put a resolver in front of it,
// for IP rules to work on domain requests.
uOb = outbounds.NewSystemResolver(uOb)
}
// Otherwise we can just rely on outbound handling on its own.
case "tcp":
if c.Resolver.TCP.Addr == "" {
return nil, fmt.Errorf("empty resolver address")
}
uOb = outbounds.NewStandardResolverTCP(c.Resolver.TCP.Addr, c.Resolver.TCP.Timeout, uOb)
case "udp":
if c.Resolver.UDP.Addr == "" {
return nil, fmt.Errorf("empty resolver address")
}
uOb = outbounds.NewStandardResolverUDP(c.Resolver.UDP.Addr, c.Resolver.UDP.Timeout, uOb)
case "tls", "tcp-tls":
if c.Resolver.TLS.Addr == "" {
return nil, fmt.Errorf("empty resolver address")
}
uOb = outbounds.NewStandardResolverTLS(c.Resolver.TLS.Addr, c.Resolver.TLS.Timeout, c.Resolver.TLS.SNI, c.Resolver.TLS.Insecure, uOb)
case "https", "http":
if c.Resolver.HTTPS.Addr == "" {
return nil, fmt.Errorf("empty resolver address")
}
uOb = outbounds.NewDoHResolver(c.Resolver.HTTPS.Addr, c.Resolver.HTTPS.Timeout, c.Resolver.HTTPS.SNI, c.Resolver.HTTPS.Insecure, uOb)
default:
return nil, fmt.Errorf("unsupported resolver type")
}
Outbound := &outbounds.PluggableOutboundAdapter{PluggableOutbound: uOb}
return Outbound, nil
}
func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.PacketConn, c *serverConfig) (http.Handler, error) {
var handler http.Handler
switch strings.ToLower(c.Masquerade.Type) {
case "", "404":
handler = http.NotFoundHandler()
case "file":
if c.Masquerade.File.Dir == "" {
return nil, fmt.Errorf("masquerade.file.dir empty file directory")
}
handler = http.FileServer(http.Dir(c.Masquerade.File.Dir))
case "proxy":
if c.Masquerade.Proxy.URL == "" {
return nil, fmt.Errorf("masquerade.proxy.url empty proxy url")
}
u, err := url.Parse(c.Masquerade.Proxy.URL)
if err != nil {
return nil, fmt.Errorf("masquerade.proxy.url %s", err)
}
handler = &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = u.Scheme
req.URL.Host = u.Host
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
xff := req.Header.Get("X-Forwarded-For")
if xff != "" {
clientIP = xff + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}
if c.Masquerade.Proxy.RewriteHost {
req.Host = req.URL.Host
}
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
n.Logger.Error("HTTP reverse proxy error", zap.Error(err))
w.WriteHeader(http.StatusBadGateway)
},
}
case "string":
if c.Masquerade.String.Content == "" {
return nil, fmt.Errorf("masquerade.string.content empty string content")
}
if c.Masquerade.String.StatusCode != 0 &&
(c.Masquerade.String.StatusCode < 200 ||
c.Masquerade.String.StatusCode > 599 ||
c.Masquerade.String.StatusCode == 233) {
// 233 is reserved for Hysteria authentication
return nil, fmt.Errorf("masquerade.string.statusCode invalid status code (must be 200-599, except 233)")
}
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for k, v := range c.Masquerade.String.Headers {
w.Header().Set(k, v)
}
if c.Masquerade.String.StatusCode != 0 {
w.WriteHeader(c.Masquerade.String.StatusCode)
} else {
w.WriteHeader(http.StatusOK) // Use 200 OK by default
}
_, _ = w.Write([]byte(c.Masquerade.String.Content))
})
default:
return nil, fmt.Errorf("masquerade.type unsupported masquerade type")
}
MasqHandler := &masqHandlerLogWrapper{H: handler, QUIC: true, Logger: n.Logger}
if c.Masquerade.ListenHTTP != "" || c.Masquerade.ListenHTTPS != "" {
if c.Masquerade.ListenHTTP != "" && c.Masquerade.ListenHTTPS == "" {
return nil, fmt.Errorf("masquerade.listenHTTPS having only HTTP server without HTTPS is not supported")
}
s := masq.MasqTCPServer{
QUICPort: extractPortFromAddr(conn.LocalAddr().String()),
HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS),
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false, Logger: n.Logger},
TLSConfig: &tls.Config{
Certificates: tlsconfig.Certificates,
GetCertificate: tlsconfig.GetCertificate,
},
ForceHTTPS: c.Masquerade.ForceHTTPS,
}
go runMasqTCPServer(&s, c.Masquerade.ListenHTTP, c.Masquerade.ListenHTTPS, n.Logger)
}
return MasqHandler, nil
}
func (n *Hysteria2node) getHyConfig(info *panel.NodeInfo, config *conf.Options, c *serverConfig) (*server.Config, error) {
tls, err := n.getTLSConfig(config)
if err != nil {
return nil, err
}
quic, err := n.getQUICConfig(c)
if err != nil {
return nil, err
}
conn, err := n.getConn(info, config)
if err != nil {
return nil, err
}
Outbound, err := n.getOutboundConfig(c)
if err != nil {
return nil, err
}
Masq, err := n.getMasqHandler(tls, conn, c)
if err != nil {
return nil, err
}
return &server.Config{
TLSConfig: *tls,
QUICConfig: *quic,
Conn: conn,
Outbound: Outbound,
BandwidthConfig: *n.getBandwidthConfig(info),
IgnoreClientBandwidth: c.IgnoreClientBandwidth,
DisableUDP: c.DisableUDP,
UDPIdleTimeout: c.UDPIdleTimeout,
EventLogger: n.EventLogger,
TrafficLogger: n.TrafficLogger,
MasqHandler: Masq,
}, nil
}
func runMasqTCPServer(s *masq.MasqTCPServer, httpAddr, httpsAddr string, logger *zap.Logger) {
errChan := make(chan error, 2)
if httpAddr != "" {
go func() {
logger.Info("masquerade HTTP server up and running", zap.String("listen", httpAddr))
errChan <- s.ListenAndServeHTTP(httpAddr)
}()
}
if httpsAddr != "" {
go func() {
logger.Info("masquerade HTTPS server up and running", zap.String("listen", httpsAddr))
errChan <- s.ListenAndServeHTTPS(httpsAddr)
}()
}
err := <-errChan
if err != nil {
logger.Fatal("failed to serve masquerade HTTP(S)", zap.Error(err))
}
}
func extractPortFromAddr(addr string) int {
_, portStr, err := net.SplitHostPort(addr)
if err != nil {
return 0
}
port, err := strconv.Atoi(portStr)
if err != nil {
return 0
}
return port
}
func formatAddress(ip string, port int) string {
if strings.Contains(ip, ":") {
return fmt.Sprintf("[%s]:%d", ip, port)
}
return fmt.Sprintf("%s:%d", ip, port)
}

View File

@@ -1,181 +0,0 @@
package hy2
import (
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/apernet/hysteria/extras/v2/outbounds/acl"
"github.com/apernet/hysteria/extras/v2/outbounds/acl/v2geo"
"go.uber.org/zap"
)
const (
geoipFilename = "geoip.dat"
geoipURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
geositeFilename = "geosite.dat"
geositeURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
geoDlTmpPattern = ".hysteria-geoloader.dlpart.*"
geoDefaultUpdateInterval = 7 * 24 * time.Hour // 7 days
)
var _ acl.GeoLoader = (*GeoLoader)(nil)
// GeoLoader provides the on-demand GeoIP/GeoSite database
// loading functionality required by the ACL engine.
// Empty filenames = automatic download from built-in URLs.
type GeoLoader struct {
GeoIPFilename string
GeoSiteFilename string
UpdateInterval time.Duration
geoipMap map[string]*v2geo.GeoIP
geositeMap map[string]*v2geo.GeoSite
Logger *zap.Logger
}
func (l *GeoLoader) shouldDownload(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return true
}
if info.Size() == 0 {
// empty files are loadable by v2geo, but we consider it broken
return true
}
dt := time.Since(info.ModTime())
if l.UpdateInterval == 0 {
return dt > geoDefaultUpdateInterval
} else {
return dt > l.UpdateInterval
}
}
func (l *GeoLoader) downloadAndCheck(filename, url string, checkFunc func(filename string) error) error {
l.geoDownloadFunc(filename, url)
resp, err := http.Get(url)
if err != nil {
l.geoDownloadErrFunc(err)
return err
}
defer resp.Body.Close()
f, err := os.CreateTemp(".", geoDlTmpPattern)
if err != nil {
l.geoDownloadErrFunc(err)
return err
}
defer os.Remove(f.Name())
_, err = io.Copy(f, resp.Body)
if err != nil {
f.Close()
l.geoDownloadErrFunc(err)
return err
}
f.Close()
err = checkFunc(f.Name())
if err != nil {
l.geoDownloadErrFunc(fmt.Errorf("integrity check failed: %w", err))
return err
}
err = os.Rename(f.Name(), filename)
if err != nil {
l.geoDownloadErrFunc(fmt.Errorf("rename failed: %w", err))
return err
}
return nil
}
func (l *GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) {
if l.geoipMap != nil {
return l.geoipMap, nil
}
autoDL := false
filename := l.GeoIPFilename
if filename == "" {
autoDL = true
filename = geoipFilename
}
if autoDL {
if !l.shouldDownload(filename) {
m, err := v2geo.LoadGeoIP(filename)
if err == nil {
l.geoipMap = m
return m, nil
}
// file is broken, download it again
}
err := l.downloadAndCheck(filename, geoipURL, func(filename string) error {
_, err := v2geo.LoadGeoIP(filename)
return err
})
if err != nil {
// as long as the previous download exists, fallback to it
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
return nil, err
}
}
}
m, err := v2geo.LoadGeoIP(filename)
if err != nil {
return nil, err
}
l.geoipMap = m
return m, nil
}
func (l *GeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) {
if l.geositeMap != nil {
return l.geositeMap, nil
}
autoDL := false
filename := l.GeoSiteFilename
if filename == "" {
autoDL = true
filename = geositeFilename
}
if autoDL {
if !l.shouldDownload(filename) {
m, err := v2geo.LoadGeoSite(filename)
if err == nil {
l.geositeMap = m
return m, nil
}
// file is broken, download it again
}
err := l.downloadAndCheck(filename, geositeURL, func(filename string) error {
_, err := v2geo.LoadGeoSite(filename)
return err
})
if err != nil {
// as long as the previous download exists, fallback to it
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
return nil, err
}
}
}
m, err := v2geo.LoadGeoSite(filename)
if err != nil {
return nil, err
}
l.geositeMap = m
return m, nil
}
func (l *GeoLoader) geoDownloadFunc(filename, url string) {
l.Logger.Info("downloading database", zap.String("filename", filename), zap.String("url", url))
}
func (l *GeoLoader) geoDownloadErrFunc(err error) {
if err != nil {
l.Logger.Error("failed to download database", zap.Error(err))
}
}

View File

@@ -1,62 +0,0 @@
package hy2
import (
"sync"
"github.com/InazumaV/V2bX/common/counter"
"github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/limiter"
"github.com/apernet/hysteria/core/v2/server"
quic "github.com/apernet/quic-go"
"go.uber.org/zap"
)
var _ server.TrafficLogger = (*HookServer)(nil)
type HookServer struct {
Tag string
logger *zap.Logger
Counter sync.Map
}
func (h *HookServer) TraceStream(stream quic.Stream, stats *server.StreamStats) {
}
func (h *HookServer) UntraceStream(stream quic.Stream) {
}
func (h *HookServer) LogTraffic(id string, tx, rx uint64) (ok bool) {
var c interface{}
var exists bool
limiterinfo, err := limiter.GetLimiter(h.Tag)
if err != nil {
h.logger.Error("Get limiter error", zap.String("tag", h.Tag), zap.Error(err))
return false
}
userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(h.Tag, id))
if ok {
userlimitInfo := userLimit.(*limiter.UserLimitInfo)
if userlimitInfo.OverLimit {
userlimitInfo.OverLimit = false
return false
}
}
if c, exists = h.Counter.Load(h.Tag); !exists {
c = counter.NewTrafficCounter()
h.Counter.Store(h.Tag, c)
}
if tc, ok := c.(*counter.TrafficCounter); ok {
tc.Rx(id, int(rx))
tc.Tx(id, int(tx))
return true
}
return false
}
func (s *HookServer) LogOnlineState(id string, online bool) {
}

View File

@@ -1,61 +0,0 @@
package hy2
import (
"github.com/InazumaV/V2bX/conf"
vCore "github.com/InazumaV/V2bX/core"
"go.uber.org/zap"
)
var _ vCore.Core = (*Hysteria2)(nil)
type Hysteria2 struct {
Hy2nodes map[string]Hysteria2node
Auth *V2bX
Logger *zap.Logger
}
func init() {
vCore.RegisterCore("hysteria2", New)
}
func New(c *conf.CoreConfig) (vCore.Core, error) {
loglever := "error"
if c.Hysteria2Config.LogConfig.Level != "" {
loglever = c.Hysteria2Config.LogConfig.Level
}
log, err := initLogger(loglever, "console")
if err != nil {
return nil, err
}
return &Hysteria2{
Hy2nodes: make(map[string]Hysteria2node),
Auth: &V2bX{
usersMap: make(map[string]int),
},
Logger: log,
}, nil
}
func (h *Hysteria2) Protocols() []string {
return []string{
"hysteria2",
}
}
func (h *Hysteria2) Start() error {
return nil
}
func (h *Hysteria2) Close() error {
for _, n := range h.Hy2nodes {
err := n.Hy2server.Close()
if err != nil {
return err
}
}
return nil
}
func (h *Hysteria2) Type() string {
return "hysteria2"
}

View File

@@ -1,156 +0,0 @@
package hy2
import (
"fmt"
"net"
"strings"
"github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/limiter"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type serverLogger struct {
Tag string
logger *zap.Logger
}
var logLevelMap = map[string]zapcore.Level{
"debug": zapcore.DebugLevel,
"info": zapcore.InfoLevel,
"warn": zapcore.WarnLevel,
"error": zapcore.ErrorLevel,
}
var logFormatMap = map[string]zapcore.EncoderConfig{
"console": {
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
MessageKey: "msg",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: zapcore.RFC3339TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
},
"json": {
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
MessageKey: "msg",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochMillisTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
},
}
func (l *serverLogger) Connect(addr net.Addr, uuid string, tx uint64) {
limiterinfo, err := limiter.GetLimiter(l.Tag)
if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
}
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = true
}
} else {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = false
}
}
l.logger.Info("client connected", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint64("tx", tx))
}
func (l *serverLogger) Disconnect(addr net.Addr, uuid string, err error) {
l.logger.Info("client disconnected", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Error(err))
}
func (l *serverLogger) TCPRequest(addr net.Addr, uuid, reqAddr string) {
limiterinfo, err := limiter.GetLimiter(l.Tag)
if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
}
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = true
}
} else {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = false
}
}
l.logger.Debug("TCP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr))
}
func (l *serverLogger) TCPError(addr net.Addr, uuid, reqAddr string, err error) {
if err == nil {
l.logger.Debug("TCP closed", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr))
} else {
l.logger.Debug("TCP error", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr), zap.Error(err))
}
}
func (l *serverLogger) UDPRequest(addr net.Addr, uuid string, sessionId uint32, reqAddr string) {
limiterinfo, err := limiter.GetLimiter(l.Tag)
if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
}
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = true
}
} else {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = false
}
}
l.logger.Debug("UDP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId), zap.String("reqAddr", reqAddr))
}
func (l *serverLogger) UDPError(addr net.Addr, uuid string, sessionId uint32, err error) {
if err == nil {
l.logger.Debug("UDP closed", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId))
} else {
l.logger.Debug("UDP error", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId), zap.Error(err))
}
}
func initLogger(logLevel string, logFormat string) (*zap.Logger, error) {
level, ok := logLevelMap[strings.ToLower(logLevel)]
if !ok {
return nil, fmt.Errorf("unsupported log level: %s", logLevel)
}
enc, ok := logFormatMap[strings.ToLower(logFormat)]
if !ok {
return nil, fmt.Errorf("unsupported log format: %s", logFormat)
}
c := zap.Config{
Level: zap.NewAtomicLevelAt(level),
DisableCaller: true,
DisableStacktrace: true,
Encoding: strings.ToLower(logFormat),
EncoderConfig: enc,
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
logger, err := c.Build()
if err != nil {
return nil, fmt.Errorf("failed to initialize logger: %s", err)
}
return logger, nil
}
func extractIPFromAddr(addr net.Addr) string {
switch v := addr.(type) {
case *net.TCPAddr:
return v.IP.String()
case *net.UDPAddr:
return v.IP.String()
case *net.IPAddr:
return v.IP.String()
default:
return ""
}
}

View File

@@ -1,76 +0,0 @@
package hy2
import (
"strings"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf"
"github.com/apernet/hysteria/core/v2/server"
"github.com/spf13/viper"
"go.uber.org/zap"
)
type Hysteria2node struct {
Hy2server server.Server
Tag string
Logger *zap.Logger
EventLogger server.EventLogger
TrafficLogger server.TrafficLogger
}
func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error {
var err error
hyconfig := &server.Config{}
var c serverConfig
v := viper.New()
if len(config.Hysteria2ConfigPath) != 0 {
v.SetConfigFile(config.Hysteria2ConfigPath)
if err := v.ReadInConfig(); err != nil {
h.Logger.Fatal("failed to read server config", zap.Error(err))
}
if err := v.Unmarshal(&c); err != nil {
h.Logger.Fatal("failed to parse server config", zap.Error(err))
}
}
n := Hysteria2node{
Tag: tag,
Logger: h.Logger,
EventLogger: &serverLogger{
Tag: tag,
logger: h.Logger,
},
TrafficLogger: &HookServer{
Tag: tag,
logger: h.Logger,
},
}
hyconfig, err = n.getHyConfig(info, config, &c)
if err != nil {
return err
}
hyconfig.Authenticator = h.Auth
s, err := server.NewServer(hyconfig)
if err != nil {
return err
}
n.Hy2server = s
h.Hy2nodes[tag] = n
go func() {
if err := s.Serve(); err != nil {
if !strings.Contains(err.Error(), "quic: server closed") {
h.Logger.Error("Server Error", zap.Error(err))
}
}
}()
return nil
}
func (h *Hysteria2) DelNode(tag string) error {
err := h.Hy2nodes[tag].Hy2server.Close()
if err != nil {
return err
}
delete(h.Hy2nodes, tag)
return nil
}

View File

@@ -1,228 +0,0 @@
package hy2
import (
"fmt"
"net"
"strings"
"time"
"github.com/apernet/hysteria/extras/v2/outbounds"
)
type serverConfig struct {
Listen string `mapstructure:"listen"`
Obfs serverConfigObfs `mapstructure:"obfs"`
TLS *serverConfigTLS `mapstructure:"tls"`
ACME *serverConfigACME `mapstructure:"acme"`
QUIC serverConfigQUIC `mapstructure:"quic"`
Bandwidth serverConfigBandwidth `mapstructure:"bandwidth"`
IgnoreClientBandwidth bool `mapstructure:"ignoreClientBandwidth"`
DisableUDP bool `mapstructure:"disableUDP"`
UDPIdleTimeout time.Duration `mapstructure:"udpIdleTimeout"`
Auth serverConfigAuth `mapstructure:"auth"`
Resolver serverConfigResolver `mapstructure:"resolver"`
ACL serverConfigACL `mapstructure:"acl"`
Outbounds []serverConfigOutboundEntry `mapstructure:"outbounds"`
TrafficStats serverConfigTrafficStats `mapstructure:"trafficStats"`
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
}
type serverConfigObfsSalamander struct {
Password string `mapstructure:"password"`
}
type serverConfigObfs struct {
Type string `mapstructure:"type"`
Salamander serverConfigObfsSalamander `mapstructure:"salamander"`
}
type serverConfigTLS struct {
Cert string `mapstructure:"cert"`
Key string `mapstructure:"key"`
}
type serverConfigACME struct {
Domains []string `mapstructure:"domains"`
Email string `mapstructure:"email"`
CA string `mapstructure:"ca"`
DisableHTTP bool `mapstructure:"disableHTTP"`
DisableTLSALPN bool `mapstructure:"disableTLSALPN"`
AltHTTPPort int `mapstructure:"altHTTPPort"`
AltTLSALPNPort int `mapstructure:"altTLSALPNPort"`
Dir string `mapstructure:"dir"`
}
type serverConfigQUIC struct {
InitStreamReceiveWindow uint64 `mapstructure:"initStreamReceiveWindow"`
MaxStreamReceiveWindow uint64 `mapstructure:"maxStreamReceiveWindow"`
InitConnectionReceiveWindow uint64 `mapstructure:"initConnReceiveWindow"`
MaxConnectionReceiveWindow uint64 `mapstructure:"maxConnReceiveWindow"`
MaxIdleTimeout time.Duration `mapstructure:"maxIdleTimeout"`
MaxIncomingStreams int64 `mapstructure:"maxIncomingStreams"`
DisablePathMTUDiscovery bool `mapstructure:"disablePathMTUDiscovery"`
}
type serverConfigBandwidth struct {
Up string `mapstructure:"up"`
Down string `mapstructure:"down"`
}
type serverConfigAuthHTTP struct {
URL string `mapstructure:"url"`
Insecure bool `mapstructure:"insecure"`
}
type serverConfigAuth struct {
Type string `mapstructure:"type"`
Password string `mapstructure:"password"`
UserPass map[string]string `mapstructure:"userpass"`
HTTP serverConfigAuthHTTP `mapstructure:"http"`
Command string `mapstructure:"command"`
}
type serverConfigResolverTCP struct {
Addr string `mapstructure:"addr"`
Timeout time.Duration `mapstructure:"timeout"`
}
type serverConfigResolverUDP struct {
Addr string `mapstructure:"addr"`
Timeout time.Duration `mapstructure:"timeout"`
}
type serverConfigResolverTLS struct {
Addr string `mapstructure:"addr"`
Timeout time.Duration `mapstructure:"timeout"`
SNI string `mapstructure:"sni"`
Insecure bool `mapstructure:"insecure"`
}
type serverConfigResolverHTTPS struct {
Addr string `mapstructure:"addr"`
Timeout time.Duration `mapstructure:"timeout"`
SNI string `mapstructure:"sni"`
Insecure bool `mapstructure:"insecure"`
}
type serverConfigResolver struct {
Type string `mapstructure:"type"`
TCP serverConfigResolverTCP `mapstructure:"tcp"`
UDP serverConfigResolverUDP `mapstructure:"udp"`
TLS serverConfigResolverTLS `mapstructure:"tls"`
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
}
type serverConfigACL struct {
File string `mapstructure:"file"`
Inline []string `mapstructure:"inline"`
GeoIP string `mapstructure:"geoip"`
GeoSite string `mapstructure:"geosite"`
GeoUpdateInterval time.Duration `mapstructure:"geoUpdateInterval"`
}
type serverConfigOutboundDirect struct {
Mode string `mapstructure:"mode"`
BindIPv4 string `mapstructure:"bindIPv4"`
BindIPv6 string `mapstructure:"bindIPv6"`
BindDevice string `mapstructure:"bindDevice"`
}
type serverConfigOutboundSOCKS5 struct {
Addr string `mapstructure:"addr"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
type serverConfigOutboundHTTP struct {
URL string `mapstructure:"url"`
Insecure bool `mapstructure:"insecure"`
}
type serverConfigOutboundEntry struct {
Name string `mapstructure:"name"`
Type string `mapstructure:"type"`
Direct serverConfigOutboundDirect `mapstructure:"direct"`
SOCKS5 serverConfigOutboundSOCKS5 `mapstructure:"socks5"`
HTTP serverConfigOutboundHTTP `mapstructure:"http"`
}
type serverConfigTrafficStats struct {
Listen string `mapstructure:"listen"`
Secret string `mapstructure:"secret"`
}
type serverConfigMasqueradeFile struct {
Dir string `mapstructure:"dir"`
}
type serverConfigMasqueradeProxy struct {
URL string `mapstructure:"url"`
RewriteHost bool `mapstructure:"rewriteHost"`
}
type serverConfigMasqueradeString struct {
Content string `mapstructure:"content"`
Headers map[string]string `mapstructure:"headers"`
StatusCode int `mapstructure:"statusCode"`
}
type serverConfigMasquerade struct {
Type string `mapstructure:"type"`
File serverConfigMasqueradeFile `mapstructure:"file"`
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
String serverConfigMasqueradeString `mapstructure:"string"`
ListenHTTP string `mapstructure:"listenHTTP"`
ListenHTTPS string `mapstructure:"listenHTTPS"`
ForceHTTPS bool `mapstructure:"forceHTTPS"`
}
func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
var mode outbounds.DirectOutboundMode
switch strings.ToLower(c.Mode) {
case "", "auto":
mode = outbounds.DirectOutboundModeAuto
case "64":
mode = outbounds.DirectOutboundMode64
case "46":
mode = outbounds.DirectOutboundMode46
case "6":
mode = outbounds.DirectOutboundMode6
case "4":
mode = outbounds.DirectOutboundMode4
default:
return nil, fmt.Errorf("outbounds.direct.mode unsupported mode")
}
bindIP := len(c.BindIPv4) > 0 || len(c.BindIPv6) > 0
bindDevice := len(c.BindDevice) > 0
if bindIP && bindDevice {
return nil, fmt.Errorf("outbounds.direct cannot bind both IP and device")
}
if bindIP {
ip4, ip6 := net.ParseIP(c.BindIPv4), net.ParseIP(c.BindIPv6)
if len(c.BindIPv4) > 0 && ip4 == nil {
return nil, fmt.Errorf("outbounds.direct.bindIPv4 invalid IPv4 address")
}
if len(c.BindIPv6) > 0 && ip6 == nil {
return nil, fmt.Errorf("outbounds.direct.bindIPv6 invalid IPv6 address")
}
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
}
if bindDevice {
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
}
return outbounds.NewDirectOutboundSimple(mode), nil
}
func serverConfigOutboundSOCKS5ToOutbound(c serverConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
if c.Addr == "" {
return nil, fmt.Errorf("outbounds.socks5.addr empty socks5 address")
}
return outbounds.NewSOCKS5Outbound(c.Addr, c.Username, c.Password), nil
}
func serverConfigOutboundHTTPToOutbound(c serverConfigOutboundHTTP) (outbounds.PluggableOutbound, error) {
if c.URL == "" {
return nil, fmt.Errorf("outbounds.http.url empty http address")
}
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
}

View File

@@ -1,70 +0,0 @@
package hy2
import (
"net"
"sync"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/common/counter"
vCore "github.com/InazumaV/V2bX/core"
"github.com/apernet/hysteria/core/v2/server"
)
var _ server.Authenticator = &V2bX{}
type V2bX struct {
usersMap map[string]int
mutex sync.Mutex
}
func (v *V2bX) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
v.mutex.Lock()
defer v.mutex.Unlock()
if _, exists := v.usersMap[auth]; exists {
return true, auth
}
return false, ""
}
func (h *Hysteria2) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
var wg sync.WaitGroup
for _, user := range p.Users {
wg.Add(1)
go func(u panel.UserInfo) {
defer wg.Done()
h.Auth.mutex.Lock()
h.Auth.usersMap[u.Uuid] = u.Id
h.Auth.mutex.Unlock()
}(user)
}
wg.Wait()
return len(p.Users), nil
}
func (h *Hysteria2) DelUsers(users []panel.UserInfo, tag string, _ *panel.NodeInfo) error {
var wg sync.WaitGroup
for _, user := range users {
wg.Add(1)
go func(u panel.UserInfo) {
defer wg.Done()
h.Auth.mutex.Lock()
delete(h.Auth.usersMap, u.Uuid)
h.Auth.mutex.Unlock()
}(user)
}
wg.Wait()
return nil
}
func (h *Hysteria2) GetUserTraffic(tag string, uuid string, reset bool) (up int64, down int64) {
if v, ok := h.Hy2nodes[tag].TrafficLogger.(*HookServer).Counter.Load(tag); ok {
c := v.(*counter.TrafficCounter)
up = c.GetUpCount(uuid)
down = c.GetDownCount(uuid)
if reset {
c.Reset(uuid)
}
return up, down
}
return 0, 0
}

View File

@@ -1,5 +0,0 @@
//go:build hysteria2
package imports
import _ "github.com/InazumaV/V2bX/core/hy2"

View File

@@ -18,7 +18,7 @@ type Core interface {
DelNode(tag string) error DelNode(tag string) error
AddUsers(p *AddUsersParams) (added int, err error) AddUsers(p *AddUsersParams) (added int, err error)
GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64)
DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error DelUsers(users []panel.UserInfo, tag string) error
Protocols() []string Protocols() []string
Type() string Type() string
} }

View File

@@ -3,11 +3,10 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/hashicorp/go-multierror"
"strings" "strings"
"sync" "sync"
"github.com/hashicorp/go-multierror"
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"
) )
@@ -136,12 +135,12 @@ func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up int64, down
return t.(Core).GetUserTraffic(tag, uuid, reset) return t.(Core).GetUserTraffic(tag, uuid, reset)
} }
func (s *Selector) DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error { func (s *Selector) DelUsers(users []panel.UserInfo, tag string) error {
t, e := s.nodes.Load(tag) t, e := s.nodes.Load(tag)
if !e { if !e {
return errors.New("the node is not have") return errors.New("the node is not have")
} }
return t.(Core).DelUsers(users, tag, info) return t.(Core).DelUsers(users, tag)
} }
func (s *Selector) Protocols() []string { func (s *Selector) Protocols() []string {

View File

@@ -2,11 +2,12 @@ package sing
import ( import (
"context" "context"
"fmt" "io"
"net" "net"
"sync" "sync"
"github.com/InazumaV/V2bX/common/format" "github.com/sagernet/sing-box/common/urltest"
"github.com/InazumaV/V2bX/common/rate" "github.com/InazumaV/V2bX/common/rate"
"github.com/InazumaV/V2bX/limiter" "github.com/InazumaV/V2bX/limiter"
@@ -17,109 +18,204 @@ import (
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
var _ adapter.ConnectionTracker = (*HookServer)(nil)
type HookServer struct { type HookServer struct {
counter sync.Map EnableConnClear bool
counter sync.Map
connClears sync.Map
}
type ConnClear struct {
lock sync.RWMutex
conns map[int]io.Closer
}
func (c *ConnClear) AddConn(cn io.Closer) (key int) {
c.lock.Lock()
defer c.lock.Unlock()
key = len(c.conns)
c.conns[key] = cn
return
}
func (c *ConnClear) DelConn(key int) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.conns, key)
}
func (c *ConnClear) ClearConn() {
c.lock.Lock()
defer c.lock.Unlock()
for _, c := range c.conns {
c.Close()
}
} }
func (h *HookServer) ModeList() []string { func (h *HookServer) ModeList() []string {
return nil return nil
} }
func NewHookServer() *HookServer { func NewHookServer(enableClear bool) *HookServer {
server := &HookServer{ return &HookServer{
counter: sync.Map{}, EnableConnClear: enableClear,
counter: sync.Map{},
connClears: sync.Map{},
} }
return server
} }
func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule, _ adapter.Outbound) net.Conn { func (h *HookServer) Start() error {
return nil
}
func (h *HookServer) Close() error {
return nil
}
func (h *HookServer) PreStart() error {
return nil
}
func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule) (net.Conn, adapter.Tracker) {
t := &Tracker{}
l, err := limiter.GetLimiter(m.Inbound) l, err := limiter.GetLimiter(m.Inbound)
if err != nil { if err != nil {
log.Warn("get limiter for ", m.Inbound, " error: ", err) log.Warn("get limiter for ", m.Inbound, " error: ", err)
return conn return conn, t
}
if l.CheckDomainRule(m.Domain) {
conn.Close()
log.Error("[", m.Inbound, "] ",
"Limited ", m.User, " access to ", m.Domain, " by domain rule")
return conn, t
}
if l.CheckProtocolRule(m.Protocol) {
conn.Close()
log.Error("[", m.Inbound, "] ",
"Limited ", m.User, " use ", m.Domain, " by protocol rule")
return conn, t
} }
ip := m.Source.Addr.String() ip := m.Source.Addr.String()
if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, true, true); r { if b, r := l.CheckLimit(m.User, ip, true); r {
conn.Close() conn.Close()
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn") log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
return conn return conn, t
} else if b != nil { } else if b != nil {
conn = rate.NewConnRateLimiter(conn, b) conn = rate.NewConnRateLimiter(conn, b)
} }
if l != nil { t.AddLeave(func() {
destStr := m.Destination.AddrString() l.ConnLimiter.DelConnCount(m.User, ip)
protocol := m.Destination.Network() })
if l.CheckDomainRule(destStr) { if h.EnableConnClear {
log.Error(fmt.Sprintf( var key int
"User %s access domain %s reject by rule", cc := &ConnClear{
m.User, conns: map[int]io.Closer{
destStr)) 0: conn,
conn.Close() },
return conn
} }
if len(protocol) != 0 { if v, ok := h.connClears.LoadOrStore(m.Inbound+m.User, cc); ok {
if l.CheckProtocolRule(protocol) { cc = v.(*ConnClear)
log.Error(fmt.Sprintf( key = cc.AddConn(conn)
"User %s access protocol %s reject by rule",
m.User,
protocol))
conn.Close()
return conn
}
} }
t.AddLeave(func() {
cc.DelConn(key)
})
} }
if c, ok := h.counter.Load(m.Inbound); ok { if c, ok := h.counter.Load(m.Inbound); ok {
return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)) return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
} else { } else {
c := counter.NewTrafficCounter() c := counter.NewTrafficCounter()
h.counter.Store(m.Inbound, c) h.counter.Store(m.Inbound, c)
return counter.NewConnCounter(conn, c.GetCounter(m.User)) return counter.NewConnCounter(conn, c.GetCounter(m.User)), t
} }
} }
func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn, m adapter.InboundContext, _ adapter.Rule, _ adapter.Outbound) N.PacketConn { func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn, m adapter.InboundContext, _ adapter.Rule) (N.PacketConn, adapter.Tracker) {
t := &Tracker{}
l, err := limiter.GetLimiter(m.Inbound) l, err := limiter.GetLimiter(m.Inbound)
if err != nil { if err != nil {
log.Warn("get limiter for ", m.Inbound, " error: ", err) log.Warn("get limiter for ", m.Inbound, " error: ", err)
return conn return conn, t
}
if l.CheckDomainRule(m.Domain) {
conn.Close()
log.Error("[", m.Inbound, "] ",
"Limited ", m.User, " access to ", m.Domain, " by domain rule")
return conn, t
}
if l.CheckProtocolRule(m.Protocol) {
conn.Close()
log.Error("[", m.Inbound, "] ",
"Limited ", m.User, " use ", m.Domain, " by protocol rule")
return conn, t
} }
ip := m.Source.Addr.String() ip := m.Source.Addr.String()
if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, false, false); r { if b, r := l.CheckLimit(m.User, ip, true); r {
conn.Close() conn.Close()
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn") log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
return conn return conn, t
} else if b != nil { } else if b != nil {
//conn = rate.NewPacketConnCounter(conn, b) conn = rate.NewPacketConnCounter(conn, b)
} }
if l != nil { if h.EnableConnClear {
destStr := m.Destination.AddrString() var key int
protocol := m.Destination.Network() cc := &ConnClear{
if l.CheckDomainRule(destStr) { conns: map[int]io.Closer{
log.Error(fmt.Sprintf( 0: conn,
"User %s access domain %s reject by rule", },
m.User,
destStr))
conn.Close()
return conn
} }
if len(protocol) != 0 { if v, ok := h.connClears.LoadOrStore(m.Inbound+m.User, cc); ok {
if l.CheckProtocolRule(protocol) { cc = v.(*ConnClear)
log.Error(fmt.Sprintf( key = cc.AddConn(conn)
"User %s access protocol %s reject by rule",
m.User,
protocol))
conn.Close()
return conn
}
} }
t.AddLeave(func() {
cc.DelConn(key)
})
} }
if c, ok := h.counter.Load(m.Inbound); ok { if c, ok := h.counter.Load(m.Inbound); ok {
return counter.NewPacketConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)) return counter.NewPacketConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
} else { } else {
c := counter.NewTrafficCounter() c := counter.NewTrafficCounter()
h.counter.Store(m.Inbound, c) h.counter.Store(m.Inbound, c)
return counter.NewPacketConnCounter(conn, c.GetCounter(m.User)) return counter.NewPacketConnCounter(conn, c.GetCounter(m.User)), t
}
}
// not need
func (h *HookServer) Mode() string {
return ""
}
func (h *HookServer) StoreSelected() bool {
return false
}
func (h *HookServer) CacheFile() adapter.ClashCacheFile {
return nil
}
func (h *HookServer) HistoryStorage() *urltest.HistoryStorage {
return nil
}
func (h *HookServer) StoreFakeIP() bool {
return false
}
func (h *HookServer) ClearConn(inbound string, user string) {
if v, ok := h.connClears.Load(inbound + user); ok {
v.(*ConnClear).ClearConn()
h.connClears.Delete(inbound + user)
}
}
type Tracker struct {
l []func()
}
func (t *Tracker) AddLeave(f func()) {
t.l = append(t.l, f)
}
func (t *Tracker) Leave() {
for i := range t.l {
t.l[i]()
} }
} }

View File

@@ -13,42 +13,16 @@ import (
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json/badoption"
) )
type HttpNetworkConfig struct {
Header struct {
Type string `json:"type"`
Request *json.RawMessage `json:"request"`
Response *json.RawMessage `json:"response"`
} `json:"header"`
}
type HttpRequest struct {
Version string `json:"version"`
Method string `json:"method"`
Path []string `json:"path"`
Headers struct {
Host []string `json:"Host"`
} `json:"headers"`
}
type WsNetworkConfig struct { type WsNetworkConfig struct {
Path string `json:"path"` Path string `json:"path"`
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers"`
} }
type GrpcNetworkConfig struct {
ServiceName string `json:"serviceName"`
}
type HttpupgradeNetworkConfig struct {
Path string `json:"path"`
Host string `json:"host"`
}
func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (option.Inbound, error) { func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (option.Inbound, error) {
addr, err := netip.ParseAddr(c.ListenIP) addr, err := netip.ParseAddr(c.ListenIP)
if err != nil { if err != nil {
@@ -59,28 +33,16 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
domainStrategy = c.SingOptions.DomainStrategy domainStrategy = c.SingOptions.DomainStrategy
} }
listen := option.ListenOptions{ listen := option.ListenOptions{
Listen: (*badoption.Addr)(&addr), Listen: (*option.ListenAddress)(&addr),
ListenPort: uint16(info.Common.ServerPort), ListenPort: uint16(info.Common.ServerPort),
TCPFastOpen: c.SingOptions.TCPFastOpen, ProxyProtocol: c.SingOptions.EnableProxyProtocol,
TCPFastOpen: c.SingOptions.TCPFastOpen,
InboundOptions: option.InboundOptions{ InboundOptions: option.InboundOptions{
SniffEnabled: c.SingOptions.SniffEnabled, SniffEnabled: c.SingOptions.SniffEnabled,
SniffOverrideDestination: c.SingOptions.SniffOverrideDestination, SniffOverrideDestination: c.SingOptions.SniffOverrideDestination,
DomainStrategy: domainStrategy, DomainStrategy: domainStrategy,
}, },
} }
var multiplex *option.InboundMultiplexOptions
if c.SingOptions.Multiplex != nil {
multiplexOption := option.InboundMultiplexOptions{
Enabled: c.SingOptions.Multiplex.Enabled,
Padding: c.SingOptions.Multiplex.Padding,
Brutal: &option.BrutalOptions{
Enabled: c.SingOptions.Multiplex.Brutal.Enabled,
UpMbps: c.SingOptions.Multiplex.Brutal.UpMbps,
DownMbps: c.SingOptions.Multiplex.Brutal.DownMbps,
},
}
multiplex = &multiplexOption
}
var tls option.InboundTLSOptions var tls option.InboundTLSOptions
switch info.Security { switch info.Security {
case panel.Tls: case panel.Tls:
@@ -112,14 +74,13 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Enabled: true, Enabled: true,
ShortID: []string{v.TlsSettings.ShortId}, ShortID: []string{v.TlsSettings.ShortId},
PrivateKey: v.TlsSettings.PrivateKey, PrivateKey: v.TlsSettings.PrivateKey,
Xver: uint8(v.TlsSettings.Xver),
Handshake: option.InboundRealityHandshakeOptions{ Handshake: option.InboundRealityHandshakeOptions{
ServerOptions: option.ServerOptions{ ServerOptions: option.ServerOptions{
Server: dest, Server: dest,
ServerPort: uint16(port), ServerPort: uint16(port),
}, },
}, },
MaxTimeDifference: badoption.Duration(mtd), MaxTimeDifference: option.Duration(mtd),
} }
} }
in := option.Inbound{ in := option.Inbound{
@@ -133,36 +94,12 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
} }
switch n.Network { switch n.Network {
case "tcp": case "tcp":
if len(n.NetworkSettings) != 0 { t.Type = ""
network := HttpNetworkConfig{}
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
//Todo fix http options
if network.Header.Type == "http" {
t.Type = network.Header.Type
var request HttpRequest
if network.Header.Request != nil {
err = json.Unmarshal(*network.Header.Request, &request)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode HttpRequest error: %s", err)
}
t.HTTPOptions.Host = request.Headers.Host
t.HTTPOptions.Path = request.Path[0]
t.HTTPOptions.Method = request.Method
}
} else {
t.Type = ""
}
} else {
t.Type = ""
}
case "ws": case "ws":
var ( var (
path string path string
ed int ed int
headers map[string]badoption.Listable[string] headers map[string]option.Listable[string]
) )
if len(n.NetworkSettings) != 0 { if len(n.NetworkSettings) != 0 {
network := WsNetworkConfig{} network := WsNetworkConfig{}
@@ -177,9 +114,9 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
} }
path = u.Path path = u.Path
ed, _ = strconv.Atoi(u.Query().Get("ed")) ed, _ = strconv.Atoi(u.Query().Get("ed"))
headers = make(map[string]badoption.Listable[string], len(network.Headers)) headers = make(map[string]option.Listable[string], len(network.Headers))
for k, v := range network.Headers { for k, v := range network.Headers {
headers[k] = badoption.Listable[string]{ headers[k] = option.Listable[string]{
v, v,
} }
} }
@@ -191,48 +128,26 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Headers: headers, Headers: headers,
} }
case "grpc": case "grpc":
network := GrpcNetworkConfig{}
if len(n.NetworkSettings) != 0 { if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &network) err := json.Unmarshal(n.NetworkSettings, &t.GRPCOptions)
if err != nil { if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err) return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
} }
} }
t.GRPCOptions = option.V2RayGRPCOptions{
ServiceName: network.ServiceName,
}
case "httpupgrade":
network := HttpupgradeNetworkConfig{}
if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
}
t.HTTPUpgradeOptions = option.V2RayHTTPUpgradeOptions{
Path: network.Path,
Host: network.Host,
}
} }
if info.Type == "vless" { if info.Type == "vless" {
in.Type = "vless" in.Type = "vless"
in.Options = &option.VLESSInboundOptions{ in.VLESSOptions = option.VLESSInboundOptions{
ListenOptions: listen, ListenOptions: listen,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tls,
TLS: &tls, Transport: &t,
},
Transport: &t,
Multiplex: multiplex,
} }
} else { } else {
in.Type = "vmess" in.Type = "vmess"
in.Options = &option.VMessInboundOptions{ in.VMessOptions = option.VMessInboundOptions{
ListenOptions: listen, ListenOptions: listen,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tls,
TLS: &tls, Transport: &t,
},
Transport: &t,
Multiplex: multiplex,
} }
} }
case "shadowsocks": case "shadowsocks":
@@ -242,96 +157,37 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
switch n.Cipher { switch n.Cipher {
case "2022-blake3-aes-128-gcm": case "2022-blake3-aes-128-gcm":
keyLength = 16 keyLength = 16
case "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305": case "2022-blake3-aes-256-gcm":
keyLength = 32 keyLength = 32
default: default:
keyLength = 16 keyLength = 16
} }
ssoption := &option.ShadowsocksInboundOptions{ in.ShadowsocksOptions = option.ShadowsocksInboundOptions{
ListenOptions: listen, ListenOptions: listen,
Method: n.Cipher, Method: n.Cipher,
Multiplex: multiplex,
} }
p := make([]byte, keyLength) p := make([]byte, keyLength)
_, _ = rand.Read(p) _, _ = rand.Read(p)
randomPasswd := string(p) randomPasswd := string(p)
if strings.Contains(n.Cipher, "2022") { if strings.Contains(n.Cipher, "2022") {
ssoption.Password = n.ServerKey in.ShadowsocksOptions.Password = n.ServerKey
randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd)) randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
} }
ssoption.Users = []option.ShadowsocksUser{{ in.ShadowsocksOptions.Users = []option.ShadowsocksUser{{
Password: randomPasswd, Password: randomPasswd,
}} }}
in.Options = ssoption
case "trojan": case "trojan":
n := info.Trojan
t := option.V2RayTransportOptions{
Type: n.Network,
}
switch n.Network {
case "tcp":
t.Type = ""
case "ws":
var (
path string
ed int
headers map[string]badoption.Listable[string]
)
if len(n.NetworkSettings) != 0 {
network := WsNetworkConfig{}
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
var u *url.URL
u, err = url.Parse(network.Path)
if err != nil {
return option.Inbound{}, fmt.Errorf("parse path error: %s", err)
}
path = u.Path
ed, _ = strconv.Atoi(u.Query().Get("ed"))
headers = make(map[string]badoption.Listable[string], len(network.Headers))
for k, v := range network.Headers {
headers[k] = badoption.Listable[string]{
v,
}
}
}
t.WebsocketOptions = option.V2RayWebsocketOptions{
Path: path,
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
MaxEarlyData: uint32(ed),
Headers: headers,
}
case "grpc":
network := GrpcNetworkConfig{}
if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
}
t.GRPCOptions = option.V2RayGRPCOptions{
ServiceName: network.ServiceName,
}
default:
t.Type = ""
}
in.Type = "trojan" in.Type = "trojan"
trojanoption := &option.TrojanInboundOptions{ in.TrojanOptions = option.TrojanInboundOptions{
ListenOptions: listen, ListenOptions: listen,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tls,
TLS: &tls,
},
Transport: &t,
Multiplex: multiplex,
} }
if c.SingOptions.FallBackConfigs != nil { if c.SingOptions.FallBackConfigs != nil {
// fallback handling // fallback handling
fallback := c.SingOptions.FallBackConfigs.FallBack fallback := c.SingOptions.FallBackConfigs.FallBack
fallbackPort, err := strconv.Atoi(fallback.ServerPort) fallbackPort, err := strconv.Atoi(fallback.ServerPort)
if err == nil { if err == nil {
trojanoption.Fallback = &option.ServerOptions{ in.TrojanOptions.Fallback = &option.ServerOptions{
Server: fallback.Server, Server: fallback.Server,
ServerPort: uint16(fallbackPort), ServerPort: uint16(fallbackPort),
} }
@@ -339,63 +195,64 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
fallbackForALPNMap := c.SingOptions.FallBackConfigs.FallBackForALPN fallbackForALPNMap := c.SingOptions.FallBackConfigs.FallBackForALPN
fallbackForALPN := make(map[string]*option.ServerOptions, len(fallbackForALPNMap)) fallbackForALPN := make(map[string]*option.ServerOptions, len(fallbackForALPNMap))
if err := processFallback(c, fallbackForALPN); err == nil { if err := processFallback(c, fallbackForALPN); err == nil {
trojanoption.FallbackForALPN = fallbackForALPN in.TrojanOptions.FallbackForALPN = fallbackForALPN
} }
} }
in.Options = trojanoption
case "hysteria": case "hysteria":
in.Type = "hysteria" in.Type = "hysteria"
in.Options = &option.HysteriaInboundOptions{ in.HysteriaOptions = option.HysteriaInboundOptions{
ListenOptions: listen, ListenOptions: listen,
UpMbps: info.Hysteria.UpMbps, UpMbps: info.Hysteria.UpMbps,
DownMbps: info.Hysteria.DownMbps, DownMbps: info.Hysteria.DownMbps,
Obfs: info.Hysteria.Obfs, Obfs: info.Hysteria.Obfs,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tls,
TLS: &tls,
},
} }
case "hysteria2": case "hysteria2":
in.Type = "hysteria2" in.Type = "hysteria2"
var obfs *option.Hysteria2Obfs var obfs *option.Hysteria2Obfs
if info.Hysteria2.ObfsType != "" && info.Hysteria2.ObfsPassword != "" { if info.Hysteria2.ObfsType != "" {
obfs = &option.Hysteria2Obfs{ obfs = &option.Hysteria2Obfs{
Type: info.Hysteria2.ObfsType, Type: info.Hysteria2.ObfsType,
Password: info.Hysteria2.ObfsPassword, Password: info.Hysteria2.ObfsPassword,
} }
} else if info.Hysteria2.ObfsType != "" {
obfs = &option.Hysteria2Obfs{
Type: "salamander",
Password: info.Hysteria2.ObfsType,
}
} }
in.Options = &option.Hysteria2InboundOptions{ in.Hysteria2Options = option.Hysteria2InboundOptions{
ListenOptions: listen, ListenOptions: listen,
UpMbps: info.Hysteria2.UpMbps, UpMbps: info.Hysteria2.UpMbps,
DownMbps: info.Hysteria2.DownMbps, DownMbps: info.Hysteria2.DownMbps,
Obfs: obfs, Obfs: obfs,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ TLS: &tls,
TLS: &tls,
},
} }
} }
return in, nil return in, nil
} }
func (b *Sing) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error { func (b *Sing) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error {
err := updateDNSConfig(info)
if err != nil {
return fmt.Errorf("build dns error: %s", err)
}
c, err := getInboundOptions(tag, info, config) c, err := getInboundOptions(tag, info, config)
if err != nil { if err != nil {
return err return err
} }
in := b.box.Inbound()
err = in.Create( in, err := inbound.New(
b.ctx, b.ctx,
b.box.Router(), b.box.Router(),
b.logFactory.NewLogger(F.ToString("inbound/", c.Type, "[", tag, "]")), b.logFactory.NewLogger(F.ToString("inbound/", c.Type, "[", tag, "]")),
tag, c,
c.Type, nil,
c.Options,
) )
if err != nil {
return fmt.Errorf("init inbound error %s", err)
}
err = in.Start()
if err != nil {
return fmt.Errorf("start inbound error: %s", err)
}
b.inbounds[tag] = in
err = b.router.AddInbound(in)
if err != nil { if err != nil {
return fmt.Errorf("add inbound error: %s", err) return fmt.Errorf("add inbound error: %s", err)
} }
@@ -403,8 +260,11 @@ func (b *Sing) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) e
} }
func (b *Sing) DelNode(tag string) error { func (b *Sing) DelNode(tag string) error {
in := b.box.Inbound() err := b.inbounds[tag].Close()
err := in.Remove(tag) if err != nil {
return fmt.Errorf("close inbound error: %s", err)
}
err = b.router.DelInbound(tag)
if err != nil { if err != nil {
return fmt.Errorf("delete inbound error: %s", err) return fmt.Errorf("delete inbound error: %s", err)
} }

View File

@@ -5,15 +5,14 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"
vCore "github.com/InazumaV/V2bX/core" vCore "github.com/InazumaV/V2bX/core"
"github.com/goccy/go-json"
box "github.com/sagernet/sing-box" box "github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
) )
var _ vCore.Core = (*Sing)(nil) var _ vCore.Core = (*Sing)(nil)
@@ -29,6 +28,7 @@ type Sing struct {
hookServer *HookServer hookServer *HookServer
router adapter.Router router adapter.Router
logFactory log.Factory logFactory log.Factory
inbounds map[string]adapter.Inbound
} }
func init() { func init() {
@@ -36,17 +36,16 @@ func init() {
} }
func New(c *conf.CoreConfig) (vCore.Core, error) { func New(c *conf.CoreConfig) (vCore.Core, error) {
ctx := context.Background()
ctx = box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
options := option.Options{} options := option.Options{}
if len(c.SingConfig.OriginalPath) != 0 { if len(c.SingConfig.OriginalPath) != 0 {
data, err := os.ReadFile(c.SingConfig.OriginalPath) f, err := os.Open(c.SingConfig.OriginalPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("read original config error: %s", err) return nil, fmt.Errorf("open original config error: %s", err)
} }
options, err = json.UnmarshalExtendedContext[option.Options](ctx, data) defer f.Close()
err = json.NewDecoder(f).Decode(&options)
if err != nil { if err != nil {
return nil, fmt.Errorf("unmarshal original config error: %s", err) return nil, fmt.Errorf("decode original config error: %s", err)
} }
} }
options.Log = &option.LogOptions{ options.Log = &option.LogOptions{
@@ -64,6 +63,22 @@ func New(c *conf.CoreConfig) (vCore.Core, error) {
}, },
} }
os.Setenv("SING_DNS_PATH", "") os.Setenv("SING_DNS_PATH", "")
options.DNS = &option.DNSOptions{}
if c.SingConfig.DnsConfigPath != "" {
f, err := os.OpenFile(c.SingConfig.DnsConfigPath, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return nil, fmt.Errorf("failed to open or create sing dns config file: %s", err)
}
defer f.Close()
if err := json.NewDecoder(f).Decode(options.DNS); err != nil {
log.Warn(fmt.Sprintf(
"Failed to unmarshal sing dns config from file '%v': %v. Using default DNS options",
f.Name(), err))
options.DNS = &option.DNSOptions{}
}
os.Setenv("SING_DNS_PATH", c.SingConfig.DnsConfigPath)
}
ctx := context.Background()
b, err := box.New(box.Options{ b, err := box.New(box.Options{
Context: ctx, Context: ctx,
Options: options, Options: options,
@@ -71,14 +86,15 @@ func New(c *conf.CoreConfig) (vCore.Core, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
hs := NewHookServer() hs := NewHookServer(c.SingConfig.EnableConnClear)
b.Router().SetTracker(hs) b.Router().SetClashServer(hs)
return &Sing{ return &Sing{
ctx: b.Router().GetCtx(), ctx: b.Router().GetCtx(),
box: b, box: b,
hookServer: hs, hookServer: hs,
router: b.Router(), router: b.Router(),
logFactory: b.LogFactory(), logFactory: b.LogFactory(),
inbounds: make(map[string]adapter.Inbound),
}, nil }, nil
} }

View File

@@ -7,40 +7,33 @@ import (
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/common/counter" "github.com/InazumaV/V2bX/common/counter"
"github.com/InazumaV/V2bX/core" "github.com/InazumaV/V2bX/core"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/hysteria"
"github.com/sagernet/sing-box/protocol/hysteria2"
"github.com/sagernet/sing-box/protocol/shadowsocks"
"github.com/sagernet/sing-box/protocol/trojan"
"github.com/sagernet/sing-box/protocol/vless"
"github.com/sagernet/sing-box/protocol/vmess"
) )
func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) { func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
in, found := b.box.Inbound().Get(p.Tag)
if !found {
return 0, errors.New("the inbound not found")
}
switch p.NodeInfo.Type { switch p.NodeInfo.Type {
case "vless": case "vmess", "vless":
us := make([]option.VLESSUser, len(p.Users)) if p.NodeInfo.Type == "vless" {
for i := range p.Users { us := make([]option.VLESSUser, len(p.Users))
us[i] = option.VLESSUser{ for i := range p.Users {
Name: p.Users[i].Uuid, us[i] = option.VLESSUser{
Flow: p.VAllss.Flow, Name: p.Users[i].Uuid,
UUID: p.Users[i].Uuid, Flow: p.VAllss.Flow,
UUID: p.Users[i].Uuid,
}
} }
} err = b.inbounds[p.Tag].(*inbound.VLESS).AddUsers(us)
err = in.(*vless.Inbound).AddUsers(us) } else {
case "vmess": us := make([]option.VMessUser, len(p.Users))
us := make([]option.VMessUser, len(p.Users)) for i := range p.Users {
for i := range p.Users { us[i] = option.VMessUser{
us[i] = option.VMessUser{ Name: p.Users[i].Uuid,
Name: p.Users[i].Uuid, UUID: p.Users[i].Uuid,
UUID: p.Users[i].Uuid, }
} }
err = b.inbounds[p.Tag].(*inbound.VMess).AddUsers(us)
} }
err = in.(*vmess.Inbound).AddUsers(us)
case "shadowsocks": case "shadowsocks":
us := make([]option.ShadowsocksUser, len(p.Users)) us := make([]option.ShadowsocksUser, len(p.Users))
for i := range p.Users { for i := range p.Users {
@@ -56,7 +49,7 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
Password: password, Password: password,
} }
} }
err = in.(*shadowsocks.MultiInbound).AddUsers(us) err = b.inbounds[p.Tag].(*inbound.ShadowsocksMulti).AddUsers(us)
case "trojan": case "trojan":
us := make([]option.TrojanUser, len(p.Users)) us := make([]option.TrojanUser, len(p.Users))
for i := range p.Users { for i := range p.Users {
@@ -65,7 +58,7 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
Password: p.Users[i].Uuid, Password: p.Users[i].Uuid,
} }
} }
err = in.(*trojan.Inbound).AddUsers(us) err = b.inbounds[p.Tag].(*inbound.Trojan).AddUsers(us)
case "hysteria": case "hysteria":
us := make([]option.HysteriaUser, len(p.Users)) us := make([]option.HysteriaUser, len(p.Users))
for i := range p.Users { for i := range p.Users {
@@ -74,18 +67,16 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
AuthString: p.Users[i].Uuid, AuthString: p.Users[i].Uuid,
} }
} }
err = in.(*hysteria.Inbound).AddUsers(us) err = b.inbounds[p.Tag].(*inbound.Hysteria).AddUsers(us)
case "hysteria2": case "hysteria2":
us := make([]option.Hysteria2User, len(p.Users)) us := make([]option.Hysteria2User, len(p.Users))
id := make([]int, len(p.Users))
for i := range p.Users { for i := range p.Users {
us[i] = option.Hysteria2User{ us[i] = option.Hysteria2User{
Name: p.Users[i].Uuid, Name: p.Users[i].Uuid,
Password: p.Users[i].Uuid, Password: p.Users[i].Uuid,
} }
id[i] = p.Users[i].Id
} }
err = in.(*hysteria2.Inbound).AddUsers(us, id) err = b.inbounds[p.Tag].(*inbound.Hysteria2).AddUsers(us)
} }
if err != nil { if err != nil {
return 0, err return 0, err
@@ -110,28 +101,29 @@ type UserDeleter interface {
DelUsers(uuid []string) error DelUsers(uuid []string) error
} }
func (b *Sing) DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error { func (b *Sing) DelUsers(users []panel.UserInfo, tag string) error {
var del UserDeleter var del UserDeleter
if i, found := b.box.Inbound().Get(tag); found { if i, ok := b.inbounds[tag]; ok {
switch info.Type { switch i.Type() {
case "vmess": case "vmess":
del = i.(*vmess.Inbound) del = i.(*inbound.VMess)
case "vless": case "vless":
del = i.(*vless.Inbound) del = i.(*inbound.VLESS)
case "shadowsocks": case "shadowsocks":
del = i.(*shadowsocks.MultiInbound) del = i.(*inbound.ShadowsocksMulti)
case "trojan": case "trojan":
del = i.(*trojan.Inbound) del = i.(*inbound.Trojan)
case "hysteria": case "hysteria":
del = i.(*hysteria.Inbound) del = i.(*inbound.Hysteria)
case "hysteria2": case "hysteria2":
del = i.(*hysteria2.Inbound) del = i.(*inbound.Hysteria2)
} }
} else { } else {
return errors.New("the inbound not found") return errors.New("the inbound not found")
} }
uuids := make([]string, len(users)) uuids := make([]string, len(users))
for i := range users { for i := range users {
b.hookServer.ClearConn(tag, users[i].Uuid)
uuids[i] = users[i].Uuid uuids[i] = users[i].Uuid
} }
err := del.DelUsers(uuids) err := del.DelUsers(uuids)

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.35.1 // protoc-gen-go v1.31.0
// protoc v3.21.12 // protoc v4.23.4
// source: config.proto // source: config.proto
package dispatcher package dispatcher
@@ -28,9 +28,11 @@ type SessionConfig struct {
func (x *SessionConfig) Reset() { func (x *SessionConfig) Reset() {
*x = SessionConfig{} *x = SessionConfig{}
mi := &file_config_proto_msgTypes[0] if protoimpl.UnsafeEnabled {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) mi := &file_config_proto_msgTypes[0]
ms.StoreMessageInfo(mi) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
} }
func (x *SessionConfig) String() string { func (x *SessionConfig) String() string {
@@ -41,7 +43,7 @@ func (*SessionConfig) ProtoMessage() {}
func (x *SessionConfig) ProtoReflect() protoreflect.Message { func (x *SessionConfig) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[0] mi := &file_config_proto_msgTypes[0]
if x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -66,9 +68,11 @@ type Config struct {
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
mi := &file_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) mi := &file_config_proto_msgTypes[1]
ms.StoreMessageInfo(mi) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
} }
func (x *Config) String() string { func (x *Config) String() string {
@@ -79,7 +83,7 @@ func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message { func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_config_proto_msgTypes[1] mi := &file_config_proto_msgTypes[1]
if x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -136,7 +140,7 @@ func file_config_proto_rawDescGZIP() []byte {
} }
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_config_proto_goTypes = []any{ var file_config_proto_goTypes = []interface{}{
(*SessionConfig)(nil), // 0: v2bx.core.app.dispatcher.SessionConfig (*SessionConfig)(nil), // 0: v2bx.core.app.dispatcher.SessionConfig
(*Config)(nil), // 1: v2bx.core.app.dispatcher.Config (*Config)(nil), // 1: v2bx.core.app.dispatcher.Config
} }
@@ -154,6 +158,32 @@ func file_config_proto_init() {
if File_config_proto != nil { if File_config_proto != nil {
return 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{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{

View File

@@ -5,7 +5,6 @@ package dispatcher
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -15,7 +14,6 @@ import (
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
@@ -31,7 +29,7 @@ import (
"github.com/xtls/xray-core/transport/pipe" "github.com/xtls/xray-core/transport/pipe"
) )
var errSniffingTimeout = errors.New("timeout on sniffing") var errSniffingTimeout = newError("timeout on sniffing")
type cachedReader struct { type cachedReader struct {
sync.Mutex sync.Mutex
@@ -45,14 +43,8 @@ func (r *cachedReader) Cache(b *buf.Buffer) {
if !mb.IsEmpty() { if !mb.IsEmpty() {
r.cache, _ = buf.MergeMulti(r.cache, mb) r.cache, _ = buf.MergeMulti(r.cache, mb)
} }
cacheLen := r.cache.Len() b.Clear()
if cacheLen <= b.Cap() { rawBytes := b.Extend(buf.Size)
b.Clear()
} else {
b.Release()
*b = *buf.NewWithSize(cacheLen)
}
rawBytes := b.Extend(cacheLen)
n := r.cache.Copy(rawBytes) n := r.cache.Copy(rawBytes)
b.Resize(0, int32(n)) b.Resize(0, int32(n))
r.Unlock() r.Unlock()
@@ -112,7 +104,7 @@ func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
d := new(DefaultDispatcher) d := new(DefaultDispatcher)
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error { if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) { core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
d.fdns = fdns d.fdns = fdns
}) })
return d.Init(config.(*Config), om, router, pm, sm, dc) return d.Init(config.(*Config), om, router, pm, sm, dc)
@@ -165,7 +157,6 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
var user *protocol.MemoryUser var user *protocol.MemoryUser
if sessionInbound != nil { if sessionInbound != nil {
user = sessionInbound.User user = sessionInbound.User
sessionInbound.CanSpliceCopy = 3
} }
var limit *limiter.Limiter var limit *limiter.Limiter
@@ -173,25 +164,24 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
if user != nil && len(user.Email) > 0 { if user != nil && len(user.Email) > 0 {
limit, err = limiter.GetLimiter(sessionInbound.Tag) limit, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil { if err != nil {
errors.LogInfo(ctx, "get limiter ", sessionInbound.Tag, " error: ", err) newError("get limiter ", sessionInbound.Tag, " error: ", err).AtError().WriteToLog()
common.Close(outboundLink.Writer) common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer) common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader) common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader) common.Interrupt(inboundLink.Reader)
return nil, nil, nil, errors.New("get limiter ", sessionInbound.Tag, " error: ", err) return nil, nil, nil, newError("get limiter ", sessionInbound.Tag, " error: ", err)
} }
// Speed Limit and Device Limit // Speed Limit and Device Limit
w, reject := limit.CheckLimit(user.Email, w, reject := limit.CheckLimit(user.Email,
sessionInbound.Source.Address.IP().String(), sessionInbound.Source.Address.IP().String(),
network == net.Network_TCP, network == net.Network_TCP)
sessionInbound.Source.Network == net.Network_TCP)
if reject { if reject {
errors.LogInfo(ctx, "Limited ", user.Email, " by conn or ip") newError("Limited ", user.Email, " by conn or ip").AtWarning().WriteToLog()
common.Close(outboundLink.Writer) common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer) common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader) common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader) common.Interrupt(inboundLink.Reader)
return nil, nil, nil, errors.New("Limited ", user.Email, " by conn or ip") return nil, nil, nil, newError("Limited ", user.Email, " by conn or ip")
} }
if w != nil { if w != nil {
inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w) inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w)
@@ -227,20 +217,8 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
return false return false
} }
for _, d := range request.ExcludeForDomain { for _, d := range request.ExcludeForDomain {
if strings.HasPrefix(d, "regexp:") { if strings.ToLower(domain) == d {
pattern := d[7:] return false
re, err := regexp.Compile(pattern)
if err != nil {
errors.LogInfo(ctx, "Unable to compile regex")
continue
}
if re.MatchString(domain) {
return false
}
} else {
if strings.ToLower(domain) == d {
return false
}
} }
} }
protocolString := result.Protocol() protocolString := result.Protocol()
@@ -248,12 +226,12 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
protocolString = resComp.ProtocolForDomainResult() protocolString = resComp.ProtocolForDomainResult()
} }
for _, p := range request.OverrideDestinationForProtocol { for _, p := range request.OverrideDestinationForProtocol {
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) { if strings.HasPrefix(protocolString, p) || strings.HasPrefix(protocolString, p) {
return true return true
} }
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" && if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) { destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
errors.LogInfo(ctx, "Using sniffer ", protocolString, " since the fake DNS missed") newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
return true return true
} }
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok { if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
@@ -271,14 +249,11 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
if !destination.IsValid() { if !destination.IsValid() {
panic("Dispatcher: Invalid destination.") panic("Dispatcher: Invalid destination.")
} }
outbounds := session.OutboundsFromContext(ctx) ob := &session.Outbound{
if len(outbounds) == 0 { OriginalTarget: destination,
outbounds = []*session.Outbound{{}} Target: destination,
ctx = session.ContextWithOutbounds(ctx, outbounds)
} }
ob := outbounds[len(outbounds)-1] ctx = session.ContextWithOutbound(ctx, ob)
ob.OriginalTarget = destination
ob.Target = destination
content := session.ContentFromContext(ctx) content := session.ContentFromContext(ctx)
if content == nil { if content == nil {
content = new(session.Content) content = new(session.Content)
@@ -303,7 +278,7 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
} }
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
domain := result.Domain() domain := result.Domain()
errors.LogInfo(ctx, "sniffed domain: ", domain) newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain) destination.Address = net.ParseAddress(domain)
protocol := result.Protocol() protocol := result.Protocol()
if resComp, ok := result.(SnifferResultComposite); ok { if resComp, ok := result.(SnifferResultComposite); ok {
@@ -328,16 +303,13 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
// DispatchLink implements routing.Dispatcher. // DispatchLink implements routing.Dispatcher.
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error { func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
if !destination.IsValid() { if !destination.IsValid() {
return errors.New("Dispatcher: Invalid destination.") return newError("Dispatcher: Invalid destination.")
} }
outbounds := session.OutboundsFromContext(ctx) ob := &session.Outbound{
if len(outbounds) == 0 { OriginalTarget: destination,
outbounds = []*session.Outbound{{}} Target: destination,
ctx = session.ContextWithOutbounds(ctx, outbounds)
} }
ob := outbounds[len(outbounds)-1] ctx = session.ContextWithOutbound(ctx, ob)
ob.OriginalTarget = destination
ob.Target = destination
content := session.ContentFromContext(ctx) content := session.ContentFromContext(ctx)
if content == nil { if content == nil {
content = new(session.Content) content = new(session.Content)
@@ -357,7 +329,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
} }
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
domain := result.Domain() domain := result.Domain()
errors.LogInfo(ctx, "sniffed domain: ", domain) newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain) destination.Address = net.ParseAddress(domain)
protocol := result.Protocol() protocol := result.Protocol()
if resComp, ok := result.(SnifferResultComposite); ok { if resComp, ok := result.(SnifferResultComposite); ok {
@@ -427,8 +399,7 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
} }
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter, protocol string) { func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter, protocol string) {
outbounds := session.OutboundsFromContext(ctx) ob := session.OutboundFromContext(ctx)
ob := outbounds[len(outbounds)-1]
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() { if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
proxied := hosts.LookupHosts(ob.Target.String()) proxied := hosts.LookupHosts(ob.Target.String())
if proxied != nil { if proxied != nil {
@@ -455,7 +426,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
var err error var err error
l, err = limiter.GetLimiter(sessionInbound.Tag) l, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil { if err != nil {
errors.LogError(ctx, "get limiter ", sessionInbound.Tag, " error: ", err) newError("get limiter ", sessionInbound.Tag, " error: ", err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
} }
} }
if l != nil { if l != nil {
@@ -466,20 +437,20 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
destStr = destination.Address.IP().String() destStr = destination.Address.IP().String()
} }
if l.CheckDomainRule(destStr) { if l.CheckDomainRule(destStr) {
errors.LogError(ctx, fmt.Sprintf( newError(fmt.Sprintf(
"User %s access domain %s reject by rule", "User %s access domain %s reject by rule",
sessionInbound.User.Email, sessionInbound.User.Email,
destStr)) destStr)).AtWarning().WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer) common.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return
} }
if len(protocol) != 0 { if len(protocol) != 0 {
if l.CheckProtocolRule(protocol) { if l.CheckProtocolRule(protocol) {
errors.LogError(ctx, fmt.Sprintf( newError(fmt.Sprintf(
"User %s access protocol %s reject by rule", "User %s access protocol %s reject by rule",
sessionInbound.User.Email, sessionInbound.User.Email,
protocol)) protocol)).AtWarning().WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer) common.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return
@@ -497,10 +468,10 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
ctx = session.SetForcedOutboundTagToContext(ctx, "") ctx = session.SetForcedOutboundTagToContext(ctx, "")
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil { if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
isPickRoute = 1 isPickRoute = 1
errors.LogInfo(ctx, "taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]") newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
handler = h handler = h
} else { } else {
errors.LogError(ctx, "non existing tag for platform initialized detour: ", forcedOutboundTag) newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer) common.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return
@@ -510,13 +481,13 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
outTag := route.GetOutboundTag() outTag := route.GetOutboundTag()
if h := d.ohm.GetHandler(outTag); h != nil { if h := d.ohm.GetHandler(outTag); h != nil {
isPickRoute = 2 isPickRoute = 2
errors.LogInfo(ctx, "taking detour [", outTag, "] for [", destination, "]") newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
handler = h handler = h
} else { } else {
errors.LogWarning(ctx, "non existing outTag: ", outTag) newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
} }
} else { } else {
errors.LogInfo(ctx, "default route for ", destination) newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
} }
} }
@@ -529,7 +500,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
} }
if handler == nil { if handler == nil {
errors.LogInfo(ctx, "default outbound handler not exist") newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer) common.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return

View File

@@ -0,0 +1,9 @@
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{})
}

View File

@@ -5,7 +5,6 @@ import (
"strings" "strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
@@ -23,16 +22,15 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
} }
if fakeDNSEngine == nil { if fakeDNSEngine == nil {
errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used").AtError() errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
return protocolSnifferWithMetadata{}, errNotInit return protocolSnifferWithMetadata{}, errNotInit
} }
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) { return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
outbounds := session.OutboundsFromContext(ctx) Target := session.OutboundFromContext(ctx).Target
ob := outbounds[len(outbounds)-1] if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP {
if ob.Target.Network == net.Network_TCP || ob.Target.Network == net.Network_UDP { domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(ob.Target.Address)
if domainFromFakeDNS != "" { if domainFromFakeDNS != "" {
errors.LogInfo(ctx, "fake dns got domain: ", domainFromFakeDNS, " for ip: ", ob.Target.Address.String()) newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx))
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
} }
} }
@@ -40,7 +38,7 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil { if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt) ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok { if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
inPool := fkr0.IsIPInIPPool(ob.Target.Address) inPool := fkr0.IsIPInIPPool(Target.Address)
ipAddressInRangeValue.addressInRange = &inPool ipAddressInRangeValue.addressInRange = &inPool
} }
} }
@@ -110,10 +108,10 @@ func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWit
} }
return nil, common.ErrNoClue return nil, common.ErrNoClue
} }
errors.LogDebug(ctx, "ip address not in fake dns range, return as is") newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
return nil, common.ErrNoClue return nil, common.ErrNoClue
} }
errors.LogWarning(ctx, "fake dns sniffer did not set address in range option, assume false.") newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
return nil, common.ErrNoClue return nil, common.ErrNoClue
}, },
metadataSniffer: false, metadataSniffer: false,

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol/bittorrent" "github.com/xtls/xray-core/common/protocol/bittorrent"
"github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/protocol/http"
@@ -35,7 +34,7 @@ type Sniffer struct {
func NewSniffer(ctx context.Context) *Sniffer { func NewSniffer(ctx context.Context) *Sniffer {
ret := &Sniffer{ ret := &Sniffer{
sniffer: []protocolSnifferWithMetadata{ sniffer: []protocolSnifferWithMetadata{
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b, ctx) }, false, net.Network_TCP}, {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 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 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 quic.SniffQUIC(b) }, false, net.Network_UDP},
@@ -53,7 +52,7 @@ func NewSniffer(ctx context.Context) *Sniffer {
return ret return ret
} }
var errUnknownContent = errors.New("unknown content") var errUnknownContent = newError("unknown content")
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) { func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
var pendingSniffer []protocolSnifferWithMetadata var pendingSniffer []protocolSnifferWithMetadata

View File

@@ -41,7 +41,6 @@ import (
_ "github.com/xtls/xray-core/proxy/http" _ "github.com/xtls/xray-core/proxy/http"
_ "github.com/xtls/xray-core/proxy/loopback" _ "github.com/xtls/xray-core/proxy/loopback"
_ "github.com/xtls/xray-core/proxy/shadowsocks" _ "github.com/xtls/xray-core/proxy/shadowsocks"
_ "github.com/xtls/xray-core/proxy/shadowsocks_2022"
_ "github.com/xtls/xray-core/proxy/socks" _ "github.com/xtls/xray-core/proxy/socks"
_ "github.com/xtls/xray-core/proxy/trojan" _ "github.com/xtls/xray-core/proxy/trojan"
_ "github.com/xtls/xray-core/proxy/vless/inbound" _ "github.com/xtls/xray-core/proxy/vless/inbound"
@@ -52,10 +51,13 @@ import (
//_ "github.com/xtls/xray-core/proxy/wireguard" //_ "github.com/xtls/xray-core/proxy/wireguard"
// Transports // Transports
//_ "github.com/xtls/xray-core/transport/internet/domainsocket"
_ "github.com/xtls/xray-core/transport/internet/grpc" _ "github.com/xtls/xray-core/transport/internet/grpc"
_ "github.com/xtls/xray-core/transport/internet/kcp" _ "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/reality"
_ "github.com/xtls/xray-core/transport/internet/splithttp"
_ "github.com/xtls/xray-core/transport/internet/tcp" _ "github.com/xtls/xray-core/transport/internet/tcp"
_ "github.com/xtls/xray-core/transport/internet/tls" _ "github.com/xtls/xray-core/transport/internet/tls"
_ "github.com/xtls/xray-core/transport/internet/udp" _ "github.com/xtls/xray-core/transport/internet/udp"
@@ -68,5 +70,5 @@ import (
_ "github.com/xtls/xray-core/transport/internet/headers/tls" _ "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/utp"
_ "github.com/xtls/xray-core/transport/internet/headers/wechat" _ "github.com/xtls/xray-core/transport/internet/headers/wechat"
_ "github.com/xtls/xray-core/transport/internet/headers/wireguard" //_ "github.com/xtls/xray-core/transport/internet/headers/wireguard"
) )

View File

@@ -2,15 +2,14 @@ package xray
import ( import (
"bytes" "bytes"
"net"
"os"
"strconv"
"strings"
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/goccy/go-json" "github.com/goccy/go-json"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
coreConf "github.com/xtls/xray-core/infra/conf" coreConf "github.com/xtls/xray-core/infra/conf"
"net"
"os"
"strconv"
"strings"
) )
func updateDNSConfig(node *panel.NodeInfo) (err error) { func updateDNSConfig(node *panel.NodeInfo) (err error) {
@@ -63,7 +62,7 @@ func saveDnsConfig(dns []byte, dnsPath string) (err error) {
} }
if !bytes.Equal(currentData, dns) { if !bytes.Equal(currentData, dns) {
coreDnsConfig := &coreConf.DNSConfig{} coreDnsConfig := &coreConf.DNSConfig{}
if err = json.Unmarshal(dns, coreDnsConfig); err != nil { if err = json.NewDecoder(bytes.NewReader(dns)).Decode(coreDnsConfig); err != nil {
log.WithField("err", err).Error("Failed to unmarshal DNS config") log.WithField("err", err).Error("Failed to unmarshal DNS config")
} }
_, err := coreDnsConfig.Build() _, err := coreDnsConfig.Build()

View File

@@ -26,12 +26,8 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
err = buildV2ray(option, nodeInfo, in) err = buildV2ray(option, nodeInfo, in)
network = nodeInfo.VAllss.Network network = nodeInfo.VAllss.Network
case "trojan": case "trojan":
err = buildTrojan(option, nodeInfo, in) err = buildTrojan(option, in)
if nodeInfo.Trojan.Network != "" { network = "tcp"
network = nodeInfo.Trojan.Network
} else {
network = "tcp"
}
case "shadowsocks": case "shadowsocks":
err = buildShadowsocks(option, nodeInfo, in) err = buildShadowsocks(option, nodeInfo, in)
network = "tcp" network = "tcp"
@@ -73,13 +69,8 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
in.StreamSetting.TCPSettings = tcpSetting in.StreamSetting.TCPSettings = tcpSetting
} }
case "ws": case "ws":
if in.StreamSetting.WSSettings != nil { in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
in.StreamSetting.WSSettings.AcceptProxyProtocol = option.XrayOptions.EnableProxyProtocol AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol} //Enable proxy protocol
} else {
in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
} //Enable proxy protocol
}
default: default:
socketConfig := &coreConf.SocketConfig{ socketConfig := &coreConf.SocketConfig{
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol, AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
@@ -114,17 +105,9 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
// Reality // Reality
in.StreamSetting.Security = "reality" in.StreamSetting.Security = "reality"
v := nodeInfo.VAllss v := nodeInfo.VAllss
dest := v.TlsSettings.Dest
if dest == "" {
dest = v.TlsSettings.ServerName
}
xver := v.TlsSettings.Xver
if xver == 0 {
xver = v.RealityConfig.Xver
}
d, err := json.Marshal(fmt.Sprintf( d, err := json.Marshal(fmt.Sprintf(
"%s:%s", "%s:%s",
dest, v.TlsSettings.ServerName,
v.TlsSettings.ServerPort)) v.TlsSettings.ServerPort))
if err != nil { if err != nil {
return nil, fmt.Errorf("marshal reality dest error: %s", err) return nil, fmt.Errorf("marshal reality dest error: %s", err)
@@ -132,7 +115,7 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
mtd, _ := time.ParseDuration(v.RealityConfig.MaxTimeDiff) mtd, _ := time.ParseDuration(v.RealityConfig.MaxTimeDiff)
in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{ in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{
Dest: d, Dest: d,
Xver: xver, Xver: v.RealityConfig.Xver,
ServerNames: []string{v.TlsSettings.ServerName}, ServerNames: []string{v.TlsSettings.ServerName},
PrivateKey: v.TlsSettings.PrivateKey, PrivateKey: v.TlsSettings.PrivateKey,
MinClientVer: v.RealityConfig.MinClientVer, MinClientVer: v.RealityConfig.MinClientVer,
@@ -140,7 +123,6 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
MaxTimeDiff: uint64(mtd.Microseconds()), MaxTimeDiff: uint64(mtd.Microseconds()),
ShortIds: []string{v.TlsSettings.ShortId}, ShortIds: []string{v.TlsSettings.ShortId},
} }
default:
break break
} }
in.Tag = tag in.Tag = tag
@@ -190,7 +172,7 @@ func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCon
return nil return nil
} }
t := coreConf.TransportProtocol(v.Network) t := coreConf.TransportProtocol(nodeInfo.VAllss.Network)
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t} inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
switch v.Network { switch v.Network {
case "tcp": case "tcp":
@@ -204,29 +186,18 @@ func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCon
return fmt.Errorf("unmarshal ws settings error: %s", err) return fmt.Errorf("unmarshal ws settings error: %s", err)
} }
case "grpc": case "grpc":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.GRPCSettings) err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.GRPCConfig)
if err != nil { if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err) return fmt.Errorf("unmarshal grpc settings error: %s", err)
} }
case "httpupgrade":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.HTTPUPGRADESettings)
if err != nil {
return fmt.Errorf("unmarshal httpupgrade settings error: %s", err)
}
case "splithttp", "xhttp":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.SplitHTTPSettings)
if err != nil {
return fmt.Errorf("unmarshal xhttp settings error: %s", err)
}
default: default:
return errors.New("the network type is not vail") return errors.New("the network type is not vail")
} }
return nil return nil
} }
func buildTrojan(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error { func buildTrojan(config *conf.Options, inbound *coreConf.InboundDetourConfig) error {
inbound.Protocol = "trojan" inbound.Protocol = "trojan"
v := nodeInfo.Trojan
if config.XrayOptions.EnableFallback { if config.XrayOptions.EnableFallback {
// Set fallback // Set fallback
fallbackConfigs, err := buildTrojanFallbacks(config.XrayOptions.FallBackConfigs) fallbackConfigs, err := buildTrojanFallbacks(config.XrayOptions.FallBackConfigs)
@@ -244,31 +215,8 @@ func buildTrojan(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCo
s := []byte("{}") s := []byte("{}")
inbound.Settings = (*json.RawMessage)(&s) inbound.Settings = (*json.RawMessage)(&s)
} }
network := v.Network t := coreConf.TransportProtocol("tcp")
if network == "" {
network = "tcp"
}
t := coreConf.TransportProtocol(network)
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t} inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
switch network {
case "tcp":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.TCPSettings)
if err != nil {
return fmt.Errorf("unmarshal tcp settings error: %s", err)
}
case "ws":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.WSSettings)
if err != nil {
return fmt.Errorf("unmarshal ws settings error: %s", err)
}
case "grpc":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.GRPCSettings)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
default:
return errors.New("the network type is not vail")
}
return nil return nil
} }

View File

@@ -5,6 +5,7 @@ import (
conf2 "github.com/InazumaV/V2bX/conf" conf2 "github.com/InazumaV/V2bX/conf"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/infra/conf" "github.com/xtls/xray-core/infra/conf"
) )
@@ -17,7 +18,8 @@ func buildOutbound(config *conf2.Options, tag string) (*core.OutboundHandlerConf
// Build Send IP address // Build Send IP address
if config.SendIP != "" { if config.SendIP != "" {
outboundDetourConfig.SendThrough = &config.SendIP ipAddress := net.ParseAddress(config.SendIP)
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
} }
// Freedom Protocol setting // Freedom Protocol setting

View File

@@ -38,11 +38,9 @@ func buildSSUser(tag string, userInfo *panel.UserInfo, cypher string, serverKey
keyLength = 16 keyLength = 16
case "2022-blake3-aes-256-gcm": case "2022-blake3-aes-256-gcm":
keyLength = 32 keyLength = 32
case "2022-blake3-chacha20-poly1305":
keyLength = 32
} }
ssAccount := &shadowsocks_2022.Account{ ssAccount := &shadowsocks_2022.User{
Key: base64.StdEncoding.EncodeToString([]byte(userInfo.Uuid[:keyLength])), Key: base64.StdEncoding.EncodeToString([]byte(userInfo.Uuid[:keyLength])),
} }
return &protocol.User{ return &protocol.User{
Level: 0, Level: 0,

View File

@@ -27,7 +27,7 @@ func (c *Xray) GetUserManager(tag string) (proxy.UserManager, error) {
return userManager, nil return userManager, nil
} }
func (c *Xray) DelUsers(users []panel.UserInfo, tag string, _ *panel.NodeInfo) error { func (c *Xray) DelUsers(users []panel.UserInfo, tag string) error {
userManager, err := c.GetUserManager(tag) userManager, err := c.GetUserManager(tag)
if err != nil { if err != nil {
return fmt.Errorf("get user manager error: %s", err) return fmt.Errorf("get user manager error: %s", err)

View File

@@ -58,24 +58,22 @@ func parseConnectionConfig(c *conf.XrayConnectionConfig) (policy *coreConf.Polic
func getCore(c *conf.XrayConfig) *core.Instance { func getCore(c *conf.XrayConfig) *core.Instance {
os.Setenv("XRAY_LOCATION_ASSET", c.AssetPath) os.Setenv("XRAY_LOCATION_ASSET", c.AssetPath)
// Log Config // Log Config
coreLogConfig := &coreConf.LogConfig{ coreLogConfig := &coreConf.LogConfig{}
LogLevel: c.LogConfig.Level, coreLogConfig.LogLevel = c.LogConfig.Level
AccessLog: c.LogConfig.AccessPath, coreLogConfig.AccessLog = c.LogConfig.AccessPath
ErrorLog: c.LogConfig.ErrorPath, coreLogConfig.ErrorLog = c.LogConfig.ErrorPath
}
// DNS config // DNS config
coreDnsConfig := &coreConf.DNSConfig{} coreDnsConfig := &coreConf.DNSConfig{}
os.Setenv("XRAY_DNS_PATH", "") os.Setenv("XRAY_DNS_PATH", "")
if c.DnsConfigPath != "" { if c.DnsConfigPath != "" {
data, err := os.ReadFile(c.DnsConfigPath) f, err := os.OpenFile(c.DnsConfigPath, os.O_RDWR|os.O_CREATE, 0755)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Failed to read xray dns config file: %v", err)) log.Error("Failed to open or create xray dns config file: %v", err)
}
defer f.Close()
if err := json.NewDecoder(f).Decode(coreDnsConfig); err != nil {
log.Error(fmt.Sprintf("Failed to unmarshal xray dns config from file '%v': %v. Using default DNS options.", f.Name(), err))
coreDnsConfig = &coreConf.DNSConfig{} coreDnsConfig = &coreConf.DNSConfig{}
} else {
if err := json.Unmarshal(data, coreDnsConfig); err != nil {
log.Error(fmt.Sprintf("Failed to unmarshal xray dns config: %v. Using default DNS options.", err))
coreDnsConfig = &coreConf.DNSConfig{}
}
} }
os.Setenv("XRAY_DNS_PATH", c.DnsConfigPath) os.Setenv("XRAY_DNS_PATH", c.DnsConfigPath)
} }
@@ -86,27 +84,25 @@ func getCore(c *conf.XrayConfig) *core.Instance {
// Routing config // Routing config
coreRouterConfig := &coreConf.RouterConfig{} coreRouterConfig := &coreConf.RouterConfig{}
if c.RouteConfigPath != "" { if c.RouteConfigPath != "" {
data, err := os.ReadFile(c.RouteConfigPath) if f, err := os.Open(c.RouteConfigPath); err != nil {
if err != nil {
log.WithField("err", err).Panic("Failed to read Routing config file") log.WithField("err", err).Panic("Failed to read Routing config file")
} else { } else {
if err = json.Unmarshal(data, coreRouterConfig); err != nil { if err = json.NewDecoder(f).Decode(coreRouterConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal Routing config") log.WithField("err", err).Panic("Failed to unmarshal Routing config")
} }
} }
} }
routeConfig, err := coreRouterConfig.Build() routeConfig, err := coreRouterConfig.Build()
if err != nil { if err != nil {
log.WithField("err", err).Panic("Failed to understand Routing config. Please check: https://xtls.github.io/config/routing.html for help") log.WithField("err", err).Panic("Failed to understand Routing config Please check: https://xtls.github.io/config/routing.html")
} }
// Custom Inbound config // Custom Inbound config
var coreCustomInboundConfig []coreConf.InboundDetourConfig var coreCustomInboundConfig []coreConf.InboundDetourConfig
if c.InboundConfigPath != "" { if c.InboundConfigPath != "" {
data, err := os.ReadFile(c.InboundConfigPath) if f, err := os.Open(c.InboundConfigPath); err != nil {
if err != nil {
log.WithField("err", err).Panic("Failed to read Custom Inbound config file") log.WithField("err", err).Panic("Failed to read Custom Inbound config file")
} else { } else {
if err = json.Unmarshal(data, &coreCustomInboundConfig); err != nil { if err = json.NewDecoder(f).Decode(&coreCustomInboundConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal Custom Inbound config") log.WithField("err", err).Panic("Failed to unmarshal Custom Inbound config")
} }
} }
@@ -115,18 +111,17 @@ func getCore(c *conf.XrayConfig) *core.Instance {
for _, config := range coreCustomInboundConfig { for _, config := range coreCustomInboundConfig {
oc, err := config.Build() oc, err := config.Build()
if err != nil { if err != nil {
log.WithField("err", err).Panic("Failed to understand Inbound config. Please check: https://xtls.github.io/config/inbound.html for help") 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) inBoundConfig = append(inBoundConfig, oc)
} }
// Custom Outbound config // Custom Outbound config
var coreCustomOutboundConfig []coreConf.OutboundDetourConfig var coreCustomOutboundConfig []coreConf.OutboundDetourConfig
if c.OutboundConfigPath != "" { if c.OutboundConfigPath != "" {
data, err := os.ReadFile(c.OutboundConfigPath) if f, err := os.Open(c.OutboundConfigPath); err != nil {
if err != nil {
log.WithField("err", err).Panic("Failed to read Custom Outbound config file") log.WithField("err", err).Panic("Failed to read Custom Outbound config file")
} else { } else {
if err = json.Unmarshal(data, &coreCustomOutboundConfig); err != nil { if err = json.NewDecoder(f).Decode(&coreCustomOutboundConfig); err != nil {
log.WithField("err", err).Panic("Failed to unmarshal Custom Outbound config") log.WithField("err", err).Panic("Failed to unmarshal Custom Outbound config")
} }
} }

View File

@@ -28,7 +28,9 @@
}, },
"DnsConfigPath": "/etc/V2bX/dns.json", "DnsConfigPath": "/etc/V2bX/dns.json",
// SingBox源配置文件目录用于引用标准SingBox配置文件 // SingBox源配置文件目录用于引用标准SingBox配置文件
"OriginalPath": "/etc/V2bX/sing_origin.json" "OriginalPath": "/etc/V2bX/sing_origin.json",
// 在删除用户时清理已建立的连接
"EnableConnClear": false,
}, },
{ {
"Type": "sing", "Type": "sing",

Binary file not shown.

Binary file not shown.

362
go.mod
View File

@@ -1,162 +1,125 @@
module github.com/InazumaV/V2bX module github.com/InazumaV/V2bX
go 1.23 go 1.21.4
toolchain go1.23.2
require ( require (
github.com/apernet/hysteria/core/v2 v2.6.1-0.20250113035753-8c0521759079 github.com/beevik/ntp v1.2.0
github.com/apernet/hysteria/extras/v2 v2.6.1-0.20250113035753-8c0521759079 github.com/fsnotify/fsnotify v1.7.0
github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7 github.com/go-acme/lego/v4 v4.13.2
github.com/beevik/ntp v1.4.4-0.20240716062501-06ef196b89ec github.com/go-resty/resty/v2 v2.7.0
github.com/fsnotify/fsnotify v1.8.0 github.com/goccy/go-json v0.10.2
github.com/go-acme/lego/v4 v4.21.1-0.20241220151055-ee7a9e4fa04f github.com/hashicorp/go-multierror v1.1.1
github.com/go-resty/resty/v2 v2.16.2
github.com/goccy/go-json v0.10.4
github.com/hashicorp/go-multierror v1.1.2-0.20241119060415-613124da9385
github.com/juju/ratelimit v1.0.2 github.com/juju/ratelimit v1.0.2
github.com/sagernet/sing v0.6.0-beta.6 github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c
github.com/sagernet/sing-box v1.10.4 github.com/sagernet/sing-box v1.7.0-beta.3
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.19.0 github.com/xtls/xray-core v1.8.6-0.20231114151426-f1c81557dcf8
github.com/xtls/xray-core v1.8.25-0.20250115034836-4576f56ec82e golang.org/x/crypto v0.15.0
go.uber.org/zap v1.27.0 golang.org/x/sys v0.14.0
golang.org/x/crypto v0.32.0 google.golang.org/protobuf v1.31.0
golang.org/x/sys v0.29.0
google.golang.org/protobuf v1.36.2
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
cloud.google.com/go/auth v0.13.0 // indirect berty.tech/go-libtor v1.0.385 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute v1.23.2 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // 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 v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.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.10.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.2.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.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // 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/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.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/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // 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/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect github.com/aws/aws-sdk-go v1.39.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/caddyserver/certmagic v0.20.0 // indirect github.com/caddyserver/certmagic v0.19.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/civo/civogo v0.3.11 // indirect github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/circl v1.5.0 // indirect github.com/cloudflare/circl v1.3.6 // indirect
github.com/cloudflare/cloudflare-go v0.112.0 // indirect github.com/cloudflare/cloudflare-go v0.70.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cretz/bine v0.2.0 // indirect github.com/cretz/bine v0.2.0 // indirect
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/database64128/tfo-go/v2 v2.2.2 // indirect github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dnsimple/dnsimple-go v1.7.0 // indirect github.com/dnsimple/dnsimple-go v1.2.0 // indirect
github.com/exoscale/egoscale/v3 v3.1.7 // indirect github.com/exoscale/egoscale v0.100.1 // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect github.com/go-chi/render v1.0.3 // indirect
github.com/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gofrs/uuid/v5 v5.3.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/google/s2a-go v0.1.8 // indirect github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.3.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gophercloud/gophercloud v1.14.1 // indirect github.com/gophercloud/gophercloud v1.0.0 // indirect
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
github.com/labbsr0x/goh v1.0.1 // indirect github.com/labbsr0x/goh v1.0.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/libdns/alidns v1.0.3 // indirect github.com/libdns/alidns v1.0.3 // indirect
github.com/libdns/cloudflare v0.1.1 // indirect github.com/libdns/cloudflare v0.1.0 // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.1 // indirect
github.com/linode/linodego v1.44.0 // indirect github.com/linode/linodego v1.17.2 // indirect
github.com/liquidweb/go-lwApi v0.0.5 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.4 // indirect github.com/liquidweb/liquidweb-go v1.6.3 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect
github.com/mholt/acmez v1.2.0 // indirect github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.62 // indirect github.com/miekg/dns v1.1.56 // indirect
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -164,129 +127,110 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect github.com/nrdcg/desec v0.7.0 // indirect
github.com/nrdcg/desec v0.10.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.3.0 // indirect github.com/nrdcg/freemyip v0.2.0 // indirect
github.com/nrdcg/goinwx v0.10.0 // indirect github.com/nrdcg/goinwx v0.8.2 // indirect
github.com/nrdcg/mailinabox v0.2.0 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect github.com/nrdcg/porkbun v0.2.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/onsi/ginkgo/v2 v2.13.1 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/ooni/go-libtor v1.1.8 // indirect
github.com/oracle/oci-go-sdk/v65 v65.81.1 // indirect github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/ovh/go-ovh v1.6.0 // indirect github.com/ovh/go-ovh v1.4.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/peterhellberg/link v1.2.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pires/go-proxyproto v0.8.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/otp v1.4.0 // indirect github.com/pquerna/otp v1.4.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/refraction-networking/utls v1.6.7 // indirect github.com/quic-go/quic-go v0.40.0 // indirect
github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect github.com/refraction-networking/utls v1.5.4 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/sacloud/api-client-go v0.2.10 // indirect github.com/sacloud/api-client-go v0.2.8 // indirect
github.com/sacloud/go-http v0.1.8 // indirect github.com/sacloud/go-http v0.1.6 // indirect
github.com/sacloud/iaas-api-go v1.14.0 // indirect github.com/sacloud/iaas-api-go v1.11.1 // indirect
github.com/sacloud/packages-go v0.0.10 // indirect github.com/sacloud/packages-go v0.0.9 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a // indirect
github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab // indirect
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/quic-go v0.40.0 // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/quic-go v0.48.2-beta.1 // indirect github.com/sagernet/sing-dns v0.1.11-0.20231116102430-5a2133f5d358 // indirect
github.com/sagernet/sing-dns v0.4.0-beta.1 // indirect github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 // indirect
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect github.com/sagernet/sing-quic v0.1.4-0.20231114135334-e2a6aab55cca // indirect
github.com/sagernet/sing-quic v0.4.0-alpha.4 // indirect github.com/sagernet/sing-shadowsocks v0.2.5 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/sagernet/sing-shadowsocks2 v0.1.4 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect github.com/sagernet/sing-tun v0.1.20-0.20231116102736-3fa4ee409a9d // indirect
github.com/sagernet/sing-tun v0.6.0-beta.2 // indirect github.com/sagernet/sing-vmess v0.1.8 // indirect
github.com/sagernet/sing-vmess v0.2.0-beta.1 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect
github.com/selectel/domains-go v1.1.0 // indirect
github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.1.7 // indirect github.com/softlayer/softlayer-go v1.1.2 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/sony/gobreaker v0.5.0 // indirect github.com/spf13/cast v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect github.com/transip/gotransip/v6 v6.20.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/transip/gotransip/v6 v6.26.0 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // 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/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/vishvananda/netlink v1.3.0 // indirect github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/volcengine/volc-sdk-golang v1.0.189 // indirect github.com/vultr/govultr/v2 v2.17.2 // indirect
github.com/vultr/govultr/v3 v3.9.1 // indirect github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
github.com/wyx2685/sing-vmess v0.0.0-20241213093025-7de21a950272 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
github.com/xtls/quic-go v0.48.2 // indirect github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.uber.org/mock v0.3.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/ratelimit v0.3.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/net v0.18.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.28.0 // indirect golang.org/x/tools v0.15.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb // indirect
google.golang.org/api v0.214.0 // indirect google.golang.org/api v0.128.0 // indirect
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/grpc v1.69.4 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/grpc v1.59.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // indirect gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.2.1 // indirect
) )
//replace github.com/sagernet/sing-box v1.10.4 => /root/sing-box_mod replace (
github.com/sagernet/sing-box v1.7.0-beta.3 => github.com/wyx2685/sing-box_mod v0.0.0-20231118013441-da6082ccc536
replace github.com/sagernet/sing-box v1.10.4 => github.com/wyx2685/sing-box_mod v0.0.0-20241213112838-c7faac61b3fe github.com/sagernet/sing-shadowsocks v0.2.4 => github.com/inazumav/sing-shadowsocks v0.0.0-20230815111927-7c68cbaeec5c
)

1379
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@ package limiter
import ( import (
"errors" "errors"
"regexp" "regexp"
"strings"
"sync" "sync"
"time" "time"
@@ -21,13 +20,13 @@ var limiter map[string]*Limiter
func Init() { func Init() {
limiter = map[string]*Limiter{} limiter = map[string]*Limiter{}
c := task.Periodic{ c := task.Periodic{
Interval: time.Minute * 3, Interval: time.Minute * 2,
Execute: ClearOnlineIP, Execute: ClearOnlineIP,
} }
go func() { go func() {
log.WithField("Type", "Limiter"). log.WithField("Type", "Limiter").
Debug("ClearOnlineIP started") Debug("ClearOnlineIP started")
time.Sleep(time.Minute * 3) time.Sleep(time.Minute * 2)
_ = c.Start() _ = c.Start()
}() }()
} }
@@ -37,46 +36,37 @@ type Limiter struct {
ProtocolRules []string ProtocolRules []string
SpeedLimit int SpeedLimit int
UserOnlineIP *sync.Map // Key: Name, value: {Key: Ip, value: Uid} UserOnlineIP *sync.Map // Key: Name, value: {Key: Ip, value: Uid}
OldUserOnline *sync.Map // Key: Ip, value: Uid UUIDtoUID map[string]int // Key: UUID, value: UID
UUIDtoUID map[string]int // Key: UUID, value: Uid
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
AliveList map[int]int // Key: Uid, value: alive_ip
} }
type UserLimitInfo struct { type UserLimitInfo struct {
UID int UID int
SpeedLimit int SpeedLimit int
DeviceLimit int
DynamicSpeedLimit int DynamicSpeedLimit int
ExpireTime int64 ExpireTime int64
OverLimit bool
} }
func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo, aliveList map[int]int) *Limiter { func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limiter {
info := &Limiter{ info := &Limiter{
SpeedLimit: l.SpeedLimit, SpeedLimit: l.SpeedLimit,
UserOnlineIP: new(sync.Map), UserOnlineIP: new(sync.Map),
UserLimitInfo: new(sync.Map), UserLimitInfo: new(sync.Map),
ConnLimiter: NewConnLimiter(l.ConnLimit, l.IPLimit, l.EnableRealtime), ConnLimiter: NewConnLimiter(l.ConnLimit, l.IPLimit, l.EnableRealtime),
SpeedLimiter: new(sync.Map), SpeedLimiter: new(sync.Map),
AliveList: aliveList,
OldUserOnline: new(sync.Map),
} }
uuidmap := make(map[string]int) uuidmap := make(map[string]int)
for i := range users { for i := range users {
uuidmap[users[i].Uuid] = users[i].Id uuidmap[users[i].Uuid] = users[i].Id
userLimit := &UserLimitInfo{}
userLimit.UID = users[i].Id
if users[i].SpeedLimit != 0 { if users[i].SpeedLimit != 0 {
userLimit.SpeedLimit = users[i].SpeedLimit userLimit := &UserLimitInfo{
UID: users[i].Id,
SpeedLimit: users[i].SpeedLimit,
}
info.UserLimitInfo.Store(format.UserTag(tag, users[i].Uuid), userLimit)
} }
if users[i].DeviceLimit != 0 {
userLimit.DeviceLimit = users[i].DeviceLimit
}
userLimit.OverLimit = false
info.UserLimitInfo.Store(format.UserTag(tag, users[i].Uuid), userLimit)
} }
info.UUIDtoUID = uuidmap info.UUIDtoUID = uuidmap
limitLock.Lock() limitLock.Lock()
@@ -104,23 +94,17 @@ func DeleteLimiter(tag string) {
func (l *Limiter) UpdateUser(tag string, added []panel.UserInfo, deleted []panel.UserInfo) { func (l *Limiter) UpdateUser(tag string, added []panel.UserInfo, deleted []panel.UserInfo) {
for i := range deleted { for i := range deleted {
l.UserLimitInfo.Delete(format.UserTag(tag, deleted[i].Uuid)) l.UserLimitInfo.Delete(format.UserTag(tag, deleted[i].Uuid))
l.UserOnlineIP.Delete(format.UserTag(tag, deleted[i].Uuid))
delete(l.UUIDtoUID, deleted[i].Uuid) delete(l.UUIDtoUID, deleted[i].Uuid)
delete(l.AliveList, deleted[i].Id)
} }
for i := range added { for i := range added {
userLimit := &UserLimitInfo{
UID: added[i].Id,
}
if added[i].SpeedLimit != 0 { if added[i].SpeedLimit != 0 {
userLimit.SpeedLimit = added[i].SpeedLimit userLimit := &UserLimitInfo{
userLimit.ExpireTime = 0 UID: added[i].Id,
SpeedLimit: added[i].SpeedLimit,
ExpireTime: 0,
}
l.UserLimitInfo.Store(format.UserTag(tag, added[i].Uuid), userLimit)
} }
if added[i].DeviceLimit != 0 {
userLimit.DeviceLimit = added[i].DeviceLimit
}
userLimit.OverLimit = false
l.UserLimitInfo.Store(format.UserTag(tag, added[i].Uuid), userLimit)
l.UUIDtoUID[added[i].Uuid] = added[i].Id l.UUIDtoUID[added[i].Uuid] = added[i].Id
} }
} }
@@ -136,73 +120,53 @@ func (l *Limiter) UpdateDynamicSpeedLimit(tag, uuid string, limit int, expire ti
return nil return nil
} }
func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool, noSSUDP bool) (Bucket *ratelimit.Bucket, Reject bool) { func (l *Limiter) CheckLimit(email string, ip string, isTcp bool) (Bucket *ratelimit.Bucket, Reject bool) {
// check if ipv4 mapped ipv6
ip = strings.TrimPrefix(ip, "::ffff:")
// ip and conn limiter // ip and conn limiter
if l.ConnLimiter.AddConnCount(taguuid, ip, isTcp) { if l.ConnLimiter.AddConnCount(email, ip, isTcp) {
return nil, true return nil, true
} }
// check and gen speed limit Bucket // check and gen speed limit Bucket
nodeLimit := l.SpeedLimit nodeLimit := l.SpeedLimit
userLimit := 0 userLimit := 0
deviceLimit := 0 if v, ok := l.UserLimitInfo.Load(email); ok {
var uid int
if v, ok := l.UserLimitInfo.Load(taguuid); ok {
u := v.(*UserLimitInfo) u := v.(*UserLimitInfo)
deviceLimit = u.DeviceLimit
uid = u.UID
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 { if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
if u.SpeedLimit != 0 { if u.SpeedLimit != 0 {
userLimit = u.SpeedLimit userLimit = u.SpeedLimit
u.DynamicSpeedLimit = 0 u.DynamicSpeedLimit = 0
u.ExpireTime = 0 u.ExpireTime = 0
} else { } else {
l.UserLimitInfo.Delete(taguuid) l.UserLimitInfo.Delete(email)
} }
} else { } else {
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit) userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
} }
} }
if noSSUDP {
// Store online user for device limit // Store online user for device limit
ipMap := new(sync.Map) ipMap := new(sync.Map)
ipMap.Store(ip, uid) uid := l.UUIDtoUID[email]
aliveIp := l.AliveList[uid] ipMap.Store(ip, uid)
// If any device is online // If any device is online
if v, ok := l.UserOnlineIP.LoadOrStore(taguuid, ipMap); ok { if v, ok := l.UserOnlineIP.LoadOrStore(email, ipMap); ok {
ipMap := v.(*sync.Map) ipMap := v.(*sync.Map)
// If this is a new ip // If this is a new ip
if _, ok := ipMap.LoadOrStore(ip, uid); !ok { if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
if deviceLimit > 0 { counter := 0
if deviceLimit <= aliveIp { ipMap.Range(func(key, value interface{}) bool {
ipMap.Delete(ip) counter++
return nil, true return true
} })
}
}
} else if v, ok := l.OldUserOnline.Load(ip); ok{
if v.(int) == uid {
l.OldUserOnline.Delete(ip)
}
} else {
if deviceLimit > 0 {
if deviceLimit <= aliveIp {
l.UserOnlineIP.Delete(taguuid)
return nil, true
}
}
} }
} }
limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
if limit > 0 { if limit > 0 {
Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
if v, ok := l.SpeedLimiter.LoadOrStore(taguuid, Bucket); ok { if v, ok := l.SpeedLimiter.LoadOrStore(email, Bucket); ok {
return v.(*ratelimit.Bucket), false return v.(*ratelimit.Bucket), false
} else { } else {
l.SpeedLimiter.Store(taguuid, Bucket) l.SpeedLimiter.Store(email, Bucket)
return Bucket, false return Bucket, false
} }
} else { } else {
@@ -212,17 +176,17 @@ func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool, noSSUDP bool
func (l *Limiter) GetOnlineDevice() (*[]panel.OnlineUser, error) { func (l *Limiter) GetOnlineDevice() (*[]panel.OnlineUser, error) {
var onlineUser []panel.OnlineUser var onlineUser []panel.OnlineUser
l.UserOnlineIP.Range(func(key, value interface{}) bool { l.UserOnlineIP.Range(func(key, value interface{}) bool {
taguuid := key.(string) email := key.(string)
ipMap := value.(*sync.Map) ipMap := value.(*sync.Map)
ipMap.Range(func(key, value interface{}) bool { ipMap.Range(func(key, value interface{}) bool {
uid := value.(int) uid := value.(int)
ip := key.(string) ip := key.(string)
l.OldUserOnline.Store(ip, uid)
onlineUser = append(onlineUser, panel.OnlineUser{UID: uid, IP: ip}) onlineUser = append(onlineUser, panel.OnlineUser{UID: uid, IP: ip})
return true return true
}) })
l.UserOnlineIP.Delete(taguuid) // Reset online device l.UserOnlineIP.Delete(email) // Reset online device
return true return true
}) })
@@ -233,3 +197,23 @@ type UserIpList struct {
Uid int `json:"Uid"` Uid int `json:"Uid"`
IpList []string `json:"Ips"` IpList []string `json:"Ips"`
} }
func determineDeviceLimit(nodeLimit, userLimit int) (limit int) {
if nodeLimit == 0 || userLimit == 0 {
if nodeLimit > userLimit {
return nodeLimit
} else if nodeLimit < userLimit {
return userLimit
} else {
return 0
}
} else {
if nodeLimit > userLimit {
return userLimit
} else if nodeLimit < userLimit {
return nodeLimit
} else {
return nodeLimit
}
}
}

View File

@@ -1,8 +1,6 @@
package main package main
import ( import "github.com/InazumaV/V2bX/cmd"
"github.com/InazumaV/V2bX/cmd"
)
func main() { func main() {
cmd.Run() cmd.Run()

View File

@@ -19,7 +19,6 @@ type Controller struct {
limiter *limiter.Limiter limiter *limiter.Limiter
traffic map[string]int64 traffic map[string]int64
userList []panel.UserInfo userList []panel.UserInfo
aliveMap map[int]int
info *panel.NodeInfo info *panel.NodeInfo
nodeInfoMonitorPeriodic *task.Task nodeInfoMonitorPeriodic *task.Task
userReportPeriodic *task.Task userReportPeriodic *task.Task
@@ -55,10 +54,6 @@ func (c *Controller) Start() error {
if len(c.userList) == 0 { if len(c.userList) == 0 {
return errors.New("add users error: not have any user") return errors.New("add users error: not have any user")
} }
c.aliveMap, err = c.apiClient.GetUserAlive()
if err != nil {
return fmt.Errorf("failed to get user alive list: %s", err)
}
if len(c.Options.Name) == 0 { if len(c.Options.Name) == 0 {
c.tag = c.buildNodeTag(node) c.tag = c.buildNodeTag(node)
} else { } else {
@@ -66,7 +61,7 @@ func (c *Controller) Start() error {
} }
// add limiter // add limiter
l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList, c.aliveMap) l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList)
// add rule limiter // add rule limiter
if err = l.UpdateRule(&node.Rules); err != nil { if err = l.UpdateRule(&node.Rules); err != nil {
return fmt.Errorf("update rule error: %s", err) return fmt.Errorf("update rule error: %s", err)
@@ -115,10 +110,6 @@ func (c *Controller) Close() error {
if c.onlineIpReportPeriodic != nil { if c.onlineIpReportPeriodic != nil {
c.onlineIpReportPeriodic.Close() c.onlineIpReportPeriodic.Close()
} }
err := c.server.DelNode(c.tag)
if err != nil {
return fmt.Errorf("del node error: %s", err)
}
return nil return nil
} }

View File

@@ -262,14 +262,12 @@ func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey, error) {
privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv) privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv)
return privateKey, err return privateKey, err
} }
func (u *User) Load(path string) error { func (u *User) Load(path string) error {
data, err := os.ReadFile(path) f, err := os.Open(path)
if err != nil { if err != nil {
return fmt.Errorf("open file error: %s", err) return fmt.Errorf("open file error: %s", err)
} }
err = json.NewDecoder(f).Decode(u)
err = json.Unmarshal(data, u)
if err != nil { if err != nil {
return fmt.Errorf("unmarshal json error: %s", err) return fmt.Errorf("unmarshal json error: %s", err)
} }

View File

@@ -68,15 +68,6 @@ func (c *Controller) nodeInfoMonitor() (err error) {
}).Error("Get user list failed") }).Error("Get user list failed")
return nil return nil
} }
// get user alive
newA, err := c.apiClient.GetUserAlive()
if err != nil {
log.WithFields(log.Fields{
"tag": c.tag,
"err": err,
}).Error("Get alive list failed")
return nil
}
if newN != nil { if newN != nil {
c.info = newN c.info = newN
// nodeInfo changed // nodeInfo changed
@@ -101,13 +92,9 @@ func (c *Controller) nodeInfoMonitor() (err error) {
// Remove Old limiter // Remove Old limiter
limiter.DeleteLimiter(c.tag) limiter.DeleteLimiter(c.tag)
// Add new Limiter // Add new Limiter
l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList, newA) l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList)
c.limiter = l c.limiter = l
} }
// update alive list
if newA != nil {
c.limiter.AliveList = newA
}
// Update rule // Update rule
err = c.limiter.UpdateRule(&newN.Rules) err = c.limiter.UpdateRule(&newN.Rules)
if err != nil { if err != nil {
@@ -167,10 +154,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
// exit // exit
return nil return nil
} }
// update alive list
if newA != nil {
c.limiter.AliveList = newA
}
// node no changed, check users // node no changed, check users
if len(newU) == 0 { if len(newU) == 0 {
return nil return nil
@@ -178,7 +162,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
deleted, added := compareUserList(c.userList, newU) deleted, added := compareUserList(c.userList, newU)
if len(deleted) > 0 { if len(deleted) > 0 {
// have deleted users // have deleted users
err = c.server.DelUsers(deleted, c.tag, c.info) err = c.server.DelUsers(deleted, c.tag)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"tag": c.tag, "tag": c.tag,

View File

@@ -1,6 +1,7 @@
package node package node
import ( import (
"fmt"
"strconv" "strconv"
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
@@ -14,7 +15,11 @@ func (c *Controller) reportUserTrafficTask() (err error) {
up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true) up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true)
if up > 0 || down > 0 { if up > 0 || down > 0 {
if c.LimitConfig.EnableDynamicSpeedLimit { if c.LimitConfig.EnableDynamicSpeedLimit {
c.traffic[c.userList[i].Uuid] += up + down if _, ok := c.traffic[c.userList[i].Uuid]; ok {
c.traffic[c.userList[i].Uuid] += up + down
} else {
c.traffic[c.userList[i].Uuid] = up + down
}
} }
userTraffic = append(userTraffic, panel.UserTraffic{ userTraffic = append(userTraffic, panel.UserTraffic{
UID: (c.userList)[i].Id, UID: (c.userList)[i].Id,
@@ -51,12 +56,18 @@ func (c *Controller) reportUserTrafficTask() (err error) {
result = append(result, online) result = append(result, online)
} }
} }
reportOnline := make(map[int]int)
data := make(map[int][]string) data := make(map[int][]string)
for _, onlineuser := range result { for _, onlineuser := range result {
// json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] } // json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] }
data[onlineuser.UID] = append(data[onlineuser.UID], onlineuser.IP) data[onlineuser.UID] = append(data[onlineuser.UID], fmt.Sprintf("%s_%d", onlineuser.IP, c.info.Id))
if _, ok := reportOnline[onlineuser.UID]; ok {
reportOnline[onlineuser.UID]++
} else {
reportOnline[onlineuser.UID] = 1
}
} }
if err = c.apiClient.ReportNodeOnlineUsers(&data); err != nil { if err = c.apiClient.ReportNodeOnlineUsers(&data, &reportOnline); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"tag": c.tag, "tag": c.tag,
"err": err, "err": err,
@@ -71,24 +82,29 @@ func (c *Controller) reportUserTrafficTask() (err error) {
} }
func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo) { func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo) {
oldMap := make(map[string]int) tmp := map[string]struct{}{}
for i, user := range old { tmp2 := map[string]struct{}{}
key := user.Uuid + strconv.Itoa(user.SpeedLimit) for i := range old {
oldMap[key] = i tmp[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
} }
l := len(tmp)
for _, user := range new { for i := range new {
key := user.Uuid + strconv.Itoa(user.SpeedLimit) e := new[i].Uuid + strconv.Itoa(new[i].SpeedLimit)
if _, exists := oldMap[key]; !exists { tmp[e] = struct{}{}
added = append(added, user) tmp2[e] = struct{}{}
} else { if l != len(tmp) {
delete(oldMap, key) added = append(added, new[i])
l++
} }
} }
tmp = nil
for _, index := range oldMap { l = len(tmp2)
deleted = append(deleted, old[index]) for i := range old {
tmp2[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
if l != len(tmp2) {
deleted = append(deleted, old[i])
l++
}
} }
return deleted, added return deleted, added
} }