Compare commits

..

74 Commits

Author SHA1 Message Date
wyx2685
7d52a8932d test: update go 1.25.0 2025-08-13 21:37:42 +09:00
wyx2685
06441afa79 fix: 流量判断阈值去除=号 2025-08-07 15:46:12 +09:00
wyx2685
f7b588fb45 test: 增加MinReportTraffic最低流量上报阈值 2025-08-07 14:18:12 +09:00
wyx2685
9be082ede6 update xray 25.8.3 & sing-box 1.12.0 2025-08-05 00:28:47 +09:00
wyx2685
dce3ec1079 fix: update xray-core 25.7.24 2025-07-24 11:52:31 +09:00
wyx2685
2990999f7b 发布临时修复版本 2025-07-24 02:10:57 +09:00
wyx2685
fe003fcb19 回滚等待xray修复 2025-07-24 01:04:15 +09:00
wyx2685
ea26985d7c fix: hy2和tuic处理错误 2025-07-24 00:07:44 +09:00
wyx2685
32437f5e48 fix: 缺少mldsa65参数导致无法启动 2025-07-24 00:07:19 +09:00
wyx2685
39dfd8b6dd fix: xray内核使用自有连接时报空指针BUG 2025-07-23 22:02:48 +09:00
wyx2685
eb51d3e13c fix: sing内核reality server不能正常连接 2025-07-23 21:40:07 +09:00
wyx2685
dadeb6304b update xray core 2025-07-23 02:06:11 +09:00
wyx2685
4c7b9f5eb9 remove trash example 2025-07-19 04:05:59 +09:00
wyx2685
63d88843b6 test: Xray内核删除用户时尝试关闭连接 2025-07-16 15:49:17 +09:00
wyx2685
8d225f811b fix: 用户限速信息不能同步更新BUG 2025-07-13 09:23:06 +09:00
wyx2685
f6b587b275 prefer msgpack for userlist 2025-07-04 11:37:23 +09:00
wyx2685
d9b3d24465 update cores 2025-06-10 01:42:31 +09:00
wyx2685
fc284b3b9f fix: 设备数限制某些情况误拒绝旧IP链接BUG 2025-05-29 00:23:21 +09:00
wyx2685
eb92c4912d fix: reality 2025-05-24 18:53:11 +09:00
wyx2685
a68378670f test: add anytls 2025-05-24 08:33:36 +09:00
wyx2685
d200a3336e test: Add Tuic 2025-04-30 07:50:41 +09:00
wyx2685
6a95d576f1 Merge branch 'dev_new' of github.com:wyx2685/V2bX into dev_new 2025-04-01 06:03:05 +09:00
wyx2685
dbe529bd48 update xray-core v25.3.31 2025-04-01 06:01:52 +09:00
wyx2685
8254e49297 Update Dockerfile 2025-03-06 23:50:24 +08:00
wyx2685
9e8ad2619a Update Dockerfile 2025-03-06 23:48:14 +08:00
wyx2685
d5fff6c433 Update Dockerfile 2025-03-06 23:45:34 +08:00
wyx2685
fe896a61a3 update Dockerfile 2025-03-07 00:38:36 +09:00
wyx2685
a63198c20b Update release.yml 2025-03-06 23:34:14 +08:00
wyx2685
180fb14dd1 update xray core 2025-03-07 00:29:14 +09:00
wyx2685
44db7512d7 use submodule 2025-03-07 00:28:11 +09:00
wyx2685
95263fea99 update sing-box core 2025-03-06 23:54:00 +09:00
wyx2685
0b155bbf89 test: Singbox内核定期释放TCP会话列表内存 2025-03-06 15:57:56 +09:00
wyx2685
1c8c17b067 test: Singbox内核删除用户时尝试关闭该用户所有TCP会话 2025-03-05 19:54:06 +09:00
wyx2685
61606646b9 fix docker build 2025-03-04 21:53:05 +09:00
wyx2685
e2904ad126 fix workflow 2025-03-04 21:28:24 +09:00
wyx2685
903aef1fb5 Update Dockerfile 2025-03-04 19:08:18 +08:00
wyx2685
2c43704090 update xray-core v25.3.3 2025-03-04 05:05:02 +09:00
wyx2685
d71df3a0df hy2内核支持sniff设置 2025-03-02 23:19:27 +09:00
wyx2685
96baa0a99c fix docker workflows 2025-02-20 21:02:37 +09:00
wyx2685
e502624fe4 读取ignore_client_bandwidth参数 2025-02-15 22:09:56 +09:00
wyx2685
484faaf0c3 update sing-box core v1.12.0 2025-02-08 13:38:05 +09:00
wyx2685
ec5dcc3c8a update cores 2025-01-15 14:31:12 +09:00
wyx2685
2f1362067b sing内核支持面板rule规则 2025-01-10 16:33:05 +09:00
wyx2685
c755e9800b fix: sing内核删除用户逻辑不正确BUG 2025-01-05 20:57:18 +09:00
wyx2685
29e0d7e56e fix hy2 core logger nil pointer 2024-12-20 16:46:05 +09:00
wyx2685
08ebbed9fb update xray-core 2024-12-15 10:13:39 +09:00
wyx2685
792841d073 Fix reality 2024-12-13 20:31:06 +09:00
wyx2685
981e59b836 update sing-box core v1.11 2024-12-13 06:22:44 +09:00
wyx2685
7dbe5fda85 fix import 2024-12-02 08:54:47 +09:00
wyx2685
4f9ccdf8db update xray-core&hy2-core 2024-12-02 08:38:23 +09:00
wyx2685
c2d5861d7d splithttp传输方式更名为xhttp 2024-11-26 10:29:12 +09:00
wyx2685
0e29c19f0e fix compile 2024-10-31 17:57:06 +09:00
wyx2685
a1c40bb1c8 update cores 2024-10-31 17:46:31 +09:00
wyx2685
a0de94efff Merge pull request #30 from KorenKrita/dev_new
add sing mux config support
2024-10-31 16:21:22 +08:00
KorenKrita
29928a1135 add sing mux config support 2024-10-31 14:21:02 +08:00
wyx2685
ab1ca837de update action 2024-10-06 18:19:58 +09:00
wyx2685
1f61446fa9 update core 2024-10-06 18:13:16 +09:00
wyx2685
c0325227db update xray-core v24.9.19 2024-09-19 10:27:13 +09:00
wyx2685
ba3036a7ac 更新 bug-report.md 2024-09-18 09:57:58 +08:00
wyx2685
f99e2b4489 移除fr模板 2024-09-18 10:53:35 +09:00
wyx2685
0af952be10 Update README.md 2024-09-16 23:17:49 +08:00
wyx2685
ad5971f164 update xray-core v24.9.16 2024-09-16 22:00:08 +09:00
wyx2685
f7d5d891c3 Fix:修复获取用户列表和在线设备列表时未正常重置资源的BUG 2024-09-12 00:10:31 +09:00
wyx2685
6936a76724 更新xray-core版本,回退sing-box版本 2024-09-12 00:08:15 +09:00
wyx2685
7184e49650 尝试修复其他面板兼容问题 2024-09-03 01:03:37 +09:00
wyx2685
ea0b7d8f40 面板无设备数列表接口时暂不报错 2024-09-01 05:27:11 +09:00
wyx2685
12fbcb1460 修复MAP未初始化 2024-08-31 17:19:13 +09:00
wyx2685
c6d48e1edf 升级内核 2024-08-31 17:06:00 +09:00
wyx2685
8d7168c6a4 尝试修复在线设备数上报后有概率被限制链接 2024-08-31 16:02:50 +09:00
wyx2685
173c48a76f 调整设备数限制实现方式 2024-08-30 06:48:41 +09:00
wyx2685
130e94cf45 Merge branch 'InazumaV:dev_new' into dev_new 2024-08-29 09:49:15 +08:00
wyx2685
89ddfff060 升级sing-box内核 2024-08-14 01:48:33 +09:00
wyx2685
07d49293d8 BUG修复前不对UDP链接进行限速 2024-08-14 01:35:30 +09:00
riolu.rs
a85352c402 fix(xray.ss) ss2022 users 2024-05-02 22:53:09 +08:00
54 changed files with 1846 additions and 2025 deletions

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
name: Publish Docker image name: Publish Docker image
on: on:
workflow_dispatch: workflow_dispatch:
release: release:
@@ -6,6 +7,7 @@ on:
pull_request: pull_request:
branches: branches:
- 'dev_new' - 'dev_new'
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/v2bx IMAGE_NAME: ${{ github.repository_owner }}/v2bx
@@ -20,40 +22,51 @@ jobs:
- linux/amd64 - linux/amd64
- linux/arm64 - linux/arm64
steps: steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
context: .
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest - name: Export digest
run: | run: |
mkdir -p /tmp/digests mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}" digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}" echo "${digest#sha256:}" > "/tmp/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: digests name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/* path: /tmp/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
@@ -64,31 +77,37 @@ jobs:
- build - build
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: digests
path: /tmp/digests path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push - name: Create manifest list and push
working-directory: /tmp/digests
run: | run: |
ls -al cd /tmp/digests
echo docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ tags=$(echo '${{ steps.meta.outputs.json }}' | jq -cr '.tags | map("-t " + .) | join(" ")')
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) images=$(printf "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s " $(find . -type f -exec cat {} \;))
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ echo "Creating manifest with tags: $tags"
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) echo "Using images: $images"
docker buildx imagetools create $tags $images
- name: Inspect image - name: Inspect image
run: | run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

View File

@@ -10,14 +10,14 @@ on:
- "**/*.go" - "**/*.go"
- "go.mod" - "go.mod"
- "go.sum" - "go.sum"
- ".github/workflows/*.yml" - ".github/workflows/release.yml"
pull_request: pull_request:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
paths: paths:
- "**/*.go" - "**/*.go"
- "go.mod" - "go.mod"
- "go.sum" - "go.sum"
- ".github/workflows/*.yml" - ".github/workflows/release.yml"
release: release:
types: [published] types: [published]
@@ -98,7 +98,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Show workflow information - name: Show workflow information
id: get_filename id: get_filename
run: | run: |
@@ -107,12 +107,13 @@ 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@v3 uses: actions/setup-go@v5
with: with:
go-version: '1.22.0' go-version: '1.25.0'
- name: Get project dependencies - name: Get project dependencies
run: go mod download run: |
go mod download
- name: Get release version - name: Get release version
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}
run: | run: |
@@ -125,13 +126,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" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid=" GOEXPERIMENT=jsonv2 go build -v -o build_assets/V2bX -tags "sing xray hysteria2 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="
- 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" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid=" GOEXPERIMENT=jsonv2 GOMIPS=softfloat go build -v -o build_assets/V2bX_softfloat -tags "sing xray hysteria2 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="
- name: Rename Windows V2bX - name: Rename Windows V2bX
if: matrix.goos == 'windows' if: matrix.goos == 'windows'
run: | run: |
@@ -167,7 +168,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@v3 uses: actions/upload-artifact@v4
with: with:
name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }} name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}
path: | path: |

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "sing-box_mod"]
path = sing-box_mod
url = https://github.com/wyx2685/sing-box_mod.git

View File

@@ -1,10 +1,10 @@
# Build go # Build go
FROM golang:1.22.0-alpine AS builder FROM golang:1.25.0-alpine AS builder
WORKDIR /app WORKDIR /app
COPY . . COPY . .
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
RUN go mod download RUN GOEXPERIMENT=jsonv2 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" RUN GOEXPERIMENT=jsonv2 go build -v -o V2bX -tags "sing xray hysteria2 with_quic with_grpc with_utls with_wireguard with_acme with_gvisor"
# Release # Release
FROM alpine FROM alpine

View File

@@ -1,16 +1,17 @@
# V2bX # V2bX
[![](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-UnOfficialV2Board%E4%BA%A4%E6%B5%81%E7%BE%A4-green)](https://t.me/unofficialV2board)
[![](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版本 >= 1.7.0** **注意: 本项目需要搭配[修改版V2board](https://github.com/wyx2685/v2board)**
## 特点 ## 特点
* 永久开源且免费。 * 永久开源且免费。
* 支持Vmess/Vless, Trojan Shadowsocks, Hysteria多种协议。 * 支持Vmess/Vless, Trojan Shadowsocks, Hysteria1/2多种协议。
* 支持Vless和XTLS等新特性。 * 支持Vless和XTLS等新特性。
* 支持单实例对接多节点,无需重复启动。 * 支持单实例对接多节点,无需重复启动。
* 支持限制在线IP。 * 支持限制在线IP。
@@ -23,7 +24,7 @@ A V2board node server based on multi core, modified from XrayR.
## 功能介绍 ## 功能介绍
| 功能 | v2ray | trojan | shadowsocks | hysteria | | 功能 | v2ray | trojan | shadowsocks | hysteria1/2 |
|-----------|-------|--------|-------------|----------| |-----------|-------|--------|-------------|----------|
| 自动申请tls证书 | √ | √ | √ | √ | | 自动申请tls证书 | √ | √ | √ | √ |
| 自动续签tls证书 | √ | √ | √ | √ | | 自动续签tls证书 | √ | √ | √ | √ |
@@ -32,7 +33,7 @@ A V2board node server based on multi core, modified from XrayR.
| 自定义DNS | √ | √ | √ | √ | | 自定义DNS | √ | √ | √ | √ |
| 在线IP数限制 | √ | √ | √ | √ | | 在线IP数限制 | √ | √ | √ | √ |
| 连接数限制 | √ | √ | √ | √ | | 连接数限制 | √ | √ | √ | √ |
| 跨节点IP数限制 | | | | | | 跨节点IP数限制 | | | | |
| 按照用户限速 | √ | √ | √ | √ | | 按照用户限速 | √ | √ | √ | √ |
| 动态限速(未测试) | √ | √ | √ | √ | | 动态限速(未测试) | √ | √ | √ | √ |
@@ -56,8 +57,8 @@ wget -N https://raw.githubusercontent.com/wyx2685/V2bX-script/master/install.sh
## 构建 ## 构建
``` bash ``` bash
# 通过-tags选项指定要编译的内核 可选 xray sing # 通过-tags选项指定要编译的内核 可选 xray sing, hysteria2
go build -v -o ./V2bX -tags "sing hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-s -w -buildid=" go build -v -o ./V2bX -tags "xray sing hysteria2 with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-s -w -buildid="
``` ```
## 配置文件及详细使用教程 ## 配置文件及详细使用教程

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/goccy/go-json" "encoding/json"
) )
// Security type // Security type
@@ -32,6 +32,8 @@ type NodeInfo struct {
VAllss *VAllssNode VAllss *VAllssNode
Shadowsocks *ShadowsocksNode Shadowsocks *ShadowsocksNode
Trojan *TrojanNode Trojan *TrojanNode
Tuic *TuicNode
AnyTls *AnyTlsNode
Hysteria *HysteriaNode Hysteria *HysteriaNode
Hysteria2 *Hysteria2Node Hysteria2 *Hysteria2Node
Common *CommonNode Common *CommonNode
@@ -73,12 +75,13 @@ type VAllssNode struct {
} }
type TlsSettings struct { type TlsSettings struct {
ServerName string `json:"server_name"` ServerName string `json:"server_name"`
Dest string `json:"dest"` Dest string `json:"dest"`
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"` Mldsa65Seed string `json:"mldsa65Seed"`
Xver uint64 `json:"xver,string"`
} }
type RealityConfig struct { type RealityConfig struct {
@@ -100,6 +103,17 @@ type TrojanNode struct {
NetworkSettings json.RawMessage `json:"networkSettings"` NetworkSettings json.RawMessage `json:"networkSettings"`
} }
type TuicNode struct {
CommonNode
CongestionControl string `json:"congestion_control"`
ZeroRTTHandshake bool `json:"zero_rtt_handshake"`
}
type AnyTlsNode struct {
CommonNode
PaddingScheme []string `json:"padding_scheme,omitempty"`
}
type HysteriaNode struct { type HysteriaNode struct {
CommonNode CommonNode
UpMbps int `json:"up_mbps"` UpMbps int `json:"up_mbps"`
@@ -109,10 +123,11 @@ type HysteriaNode struct {
type Hysteria2Node struct { type Hysteria2Node struct {
CommonNode CommonNode
UpMbps int `json:"up_mbps"` Ignore_Client_Bandwidth bool `json:"ignore_client_bandwidth"`
DownMbps int `json:"down_mbps"` UpMbps int `json:"up_mbps"`
ObfsType string `json:"obfs"` DownMbps int `json:"down_mbps"`
ObfsPassword string `json:"obfs-password"` ObfsType string `json:"obfs"`
ObfsPassword string `json:"obfs-password"`
} }
type RawDNS struct { type RawDNS struct {
@@ -202,6 +217,24 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
cm = &rsp.CommonNode cm = &rsp.CommonNode
node.Trojan = rsp node.Trojan = rsp
node.Security = Tls node.Security = Tls
case "tuic":
rsp := &TuicNode{}
err = json.Unmarshal(r.Body(), rsp)
if err != nil {
return nil, fmt.Errorf("decode tuic params error: %s", err)
}
cm = &rsp.CommonNode
node.Tuic = rsp
node.Security = Tls
case "anytls":
rsp := &AnyTlsNode{}
err = json.Unmarshal(r.Body(), rsp)
if err != nil {
return nil, fmt.Errorf("decode anytls params error: %s", err)
}
cm = &rsp.CommonNode
node.AnyTls = rsp
node.Security = Tls
case "hysteria": case "hysteria":
rsp := &HysteriaNode{} rsp := &HysteriaNode{}
err = json.Unmarshal(r.Body(), rsp) err = json.Unmarshal(r.Body(), rsp)

View File

@@ -24,7 +24,8 @@ type Client struct {
nodeEtag string nodeEtag string
userEtag string userEtag string
responseBodyHash 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) {
@@ -55,6 +56,8 @@ func New(c *conf.ApiConfig) (*Client, error) {
"shadowsocks", "shadowsocks",
"hysteria", "hysteria",
"hysteria2", "hysteria2",
"tuic",
"anytls",
"vless": "vless":
default: default:
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType) return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
@@ -71,5 +74,7 @@ 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

@@ -2,8 +2,12 @@ package panel
import ( import (
"fmt" "fmt"
"strings"
"github.com/goccy/go-json" "encoding/json/jsontext"
"encoding/json/v2"
"github.com/vmihailenco/msgpack/v5"
) )
type OnlineUser struct { type OnlineUser struct {
@@ -12,74 +16,103 @@ type OnlineUser struct {
} }
type UserInfo struct { type UserInfo struct {
Id int `json:"id"` Id int `json:"id" msgpack:"id"`
Uuid string `json:"uuid"` Uuid string `json:"uuid" msgpack:"uuid"`
SpeedLimit int `json:"speed_limit"` SpeedLimit int `json:"speed_limit" msgpack:"speed_limit"`
DeviceLimit int `json:"device_limit"` DeviceLimit int `json:"device_limit" msgpack:"device_limit"`
AliveIp int `json:"alive_ip"`
} }
type UserListBody struct { type UserListBody struct {
//Msg string `json:"msg"` Users []UserInfo `json:"users" msgpack:"users"`
Users []UserInfo `json:"users"`
} }
// GetUserList will pull user form sspanel type AliveMap struct {
func (c *Client) GetUserList() (UserList []UserInfo, err error) { Alive map[int]int `json:"alive"`
}
// 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"). SetHeader("X-Response-Format", "msgpack").
SetDoNotParseResponse(true).
Get(path) Get(path)
if r == nil || r.RawResponse == nil {
return nil, fmt.Errorf("received nil response or raw response")
}
defer r.RawResponse.Body.Close()
if r.StatusCode() == 304 {
return nil, nil
}
if err = c.checkResponse(r, path, err); err != nil { if err = c.checkResponse(r, path, err); err != nil {
return nil, err return nil, err
} }
userlist := &UserListBody{}
if r != nil { if strings.Contains(r.Header().Get("Content-Type"), "application/x-msgpack") {
defer func() { decoder := msgpack.NewDecoder(r.RawResponse.Body)
if r.RawBody() != nil { if err := decoder.Decode(userlist); err != nil {
r.RawBody().Close() return nil, fmt.Errorf("decode user list error: %w", err)
}
}()
if r.StatusCode() == 304 {
return nil, nil
} }
} else { } else {
return nil, fmt.Errorf("received nil response") dec := jsontext.NewDecoder(r.RawResponse.Body)
} for {
var userList *UserListBody tok, err := dec.ReadToken()
if err != nil { if err != nil {
return nil, fmt.Errorf("read body error: %s", err) return nil, fmt.Errorf("decode user list error: %w", err)
}
if err := json.Unmarshal(r.Body(), &userList); err != nil {
return nil, fmt.Errorf("unmarshal userlist error: %s", err)
}
c.userEtag = r.Header().Get("ETag")
var userinfos []UserInfo
var deviceLimit, localDeviceLimit int = 0, 0
for _, user := range userList.Users {
// If there is still device available, add the user
if user.DeviceLimit > 0 && user.AliveIp > 0 {
lastOnline := 0
if v, ok := c.LastReportOnline[user.Id]; ok {
lastOnline = v
} }
// If there are any available device. if tok.Kind() == '"' && tok.String() == "users" {
localDeviceLimit = user.DeviceLimit - user.AliveIp + lastOnline break
if localDeviceLimit > 0 {
deviceLimit = localDeviceLimit
} else if lastOnline > 0 {
deviceLimit = lastOnline
} else {
continue
} }
} }
user.DeviceLimit = deviceLimit tok, err := dec.ReadToken()
userinfos = append(userinfos, user) if err != nil {
return nil, fmt.Errorf("decode user list error: %w", err)
}
if tok.Kind() != '[' {
return nil, fmt.Errorf(`decode user list error: expected "users" array`)
}
for dec.PeekKind() != ']' {
val, err := dec.ReadValue()
if err != nil {
return nil, fmt.Errorf("decode user list error: read user object: %w", err)
}
var u UserInfo
if err := json.Unmarshal(val, &u); err != nil {
return nil, fmt.Errorf("decode user list error: unmarshal user error: %w", err)
}
userlist.Users = append(userlist.Users, u)
}
}
c.userEtag = r.Header().Get("ETag")
return userlist.Users, nil
}
// GetUserAlive will fetch the alive_ip count for users
func (c *Client) GetUserAlive() (map[int]int, error) {
c.AliveMap = &AliveMap{}
const path = "/api/v1/server/UniProxy/alivelist"
r, err := c.client.R().
ForceContentType("application/json").
Get(path)
if err != nil || r.StatusCode() >= 399 {
c.AliveMap.Alive = make(map[int]int)
return c.AliveMap.Alive, nil
}
if r == nil || r.RawResponse == nil {
fmt.Printf("received nil response or raw response")
c.AliveMap.Alive = make(map[int]int)
return c.AliveMap.Alive, nil
}
defer r.RawResponse.Body.Close()
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)
} }
return userinfos, nil return c.AliveMap.Alive, nil
} }
type UserTraffic struct { type UserTraffic struct {
@@ -106,8 +139,7 @@ func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
return nil return nil
} }
func (c *Client) ReportNodeOnlineUsers(data *map[int][]string, reportOnline *map[int]int) error { func (c *Client) ReportNodeOnlineUsers(data *map[int][]string) 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

@@ -6,7 +6,7 @@ import (
) )
type TrafficCounter struct { type TrafficCounter struct {
counters sync.Map Counters sync.Map
} }
type TrafficStorage struct { type TrafficStorage struct {
@@ -18,26 +18,26 @@ func NewTrafficCounter() *TrafficCounter {
return &TrafficCounter{} return &TrafficCounter{}
} }
func (c *TrafficCounter) GetCounter(id string) *TrafficStorage { func (c *TrafficCounter) GetCounter(uuid string) *TrafficStorage {
if cts, ok := c.counters.Load(id); ok { if cts, ok := c.Counters.Load(uuid); ok {
return cts.(*TrafficStorage) return cts.(*TrafficStorage)
} }
newStorage := &TrafficStorage{} newStorage := &TrafficStorage{}
if cts, loaded := c.counters.LoadOrStore(id, newStorage); loaded { if cts, loaded := c.Counters.LoadOrStore(uuid, newStorage); loaded {
return cts.(*TrafficStorage) return cts.(*TrafficStorage)
} }
return newStorage return newStorage
} }
func (c *TrafficCounter) GetUpCount(id string) int64 { func (c *TrafficCounter) GetUpCount(uuid string) int64 {
if cts, ok := c.counters.Load(id); ok { if cts, ok := c.Counters.Load(uuid); ok {
return cts.(*TrafficStorage).UpCounter.Load() return cts.(*TrafficStorage).UpCounter.Load()
} }
return 0 return 0
} }
func (c *TrafficCounter) GetDownCount(id string) int64 { func (c *TrafficCounter) GetDownCount(uuid string) int64 {
if cts, ok := c.counters.Load(id); ok { if cts, ok := c.Counters.Load(uuid); ok {
return cts.(*TrafficStorage).DownCounter.Load() return cts.(*TrafficStorage).DownCounter.Load()
} }
return 0 return 0
@@ -45,30 +45,30 @@ func (c *TrafficCounter) GetDownCount(id string) int64 {
func (c *TrafficCounter) Len() int { func (c *TrafficCounter) Len() int {
length := 0 length := 0
c.counters.Range(func(_, _ interface{}) bool { c.Counters.Range(func(_, _ interface{}) bool {
length++ length++
return true return true
}) })
return length return length
} }
func (c *TrafficCounter) Reset(id string) { func (c *TrafficCounter) Reset(uuid string) {
if cts, ok := c.counters.Load(id); ok { if cts, ok := c.Counters.Load(uuid); ok {
cts.(*TrafficStorage).UpCounter.Store(0) cts.(*TrafficStorage).UpCounter.Store(0)
cts.(*TrafficStorage).DownCounter.Store(0) cts.(*TrafficStorage).DownCounter.Store(0)
} }
} }
func (c *TrafficCounter) Delete(id string) { func (c *TrafficCounter) Delete(uuid string) {
c.counters.Delete(id) c.Counters.Delete(uuid)
} }
func (c *TrafficCounter) Rx(id string, n int) { func (c *TrafficCounter) Rx(uuid string, n int) {
cts := c.GetCounter(id) cts := c.GetCounter(uuid)
cts.DownCounter.Add(int64(n)) cts.DownCounter.Add(int64(n))
} }
func (c *TrafficCounter) Tx(id string, n int) { func (c *TrafficCounter) Tx(uuid string, n int) {
cts := c.GetCounter(id) cts := c.GetCounter(uuid)
cts.UpCounter.Add(int64(n)) cts.UpCounter.Add(int64(n))
} }

View File

@@ -4,9 +4,6 @@ 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 {
@@ -31,6 +28,7 @@ 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
@@ -47,10 +45,11 @@ 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 return destination, err
} }
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

@@ -7,7 +7,7 @@ import (
"github.com/InazumaV/V2bX/common/json5" "github.com/InazumaV/V2bX/common/json5"
"github.com/goccy/go-json" "encoding/json/v2"
) )
type Conf struct { type Conf struct {

View File

@@ -7,8 +7,9 @@ import (
"os" "os"
"strings" "strings"
"encoding/json"
"github.com/InazumaV/V2bX/common/json5" "github.com/InazumaV/V2bX/common/json5"
"github.com/goccy/go-json"
) )
type NodeConfig struct { type NodeConfig struct {
@@ -109,6 +110,7 @@ type Options struct {
ListenIP string `json:"ListenIP"` ListenIP string `json:"ListenIP"`
SendIP string `json:"SendIP"` SendIP string `json:"SendIP"`
DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"` DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"`
ReportMinTraffic int64 `json:"ReportMinTraffic"`
LimitConfig LimitConfig `json:"LimitConfig"` LimitConfig LimitConfig `json:"LimitConfig"`
RawOptions json.RawMessage `json:"RawOptions"` RawOptions json.RawMessage `json:"RawOptions"`
XrayOptions *XrayOptions `json:"XrayOptions"` XrayOptions *XrayOptions `json:"XrayOptions"`

View File

@@ -5,10 +5,9 @@ import (
) )
type SingConfig struct { type SingConfig struct {
LogConfig SingLogConfig `json:"Log"` LogConfig SingLogConfig `json:"Log"`
NtpConfig SingNtpConfig `json:"NTP"` NtpConfig SingNtpConfig `json:"NTP"`
EnableConnClear bool `json:"EnableConnClear"` OriginalPath string `json:"OriginalPath"`
OriginalPath string `json:"OriginalPath"`
} }
type SingLogConfig struct { type SingLogConfig struct {
@@ -33,13 +32,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 {
@@ -59,13 +58,25 @@ 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

@@ -18,6 +18,8 @@ import (
"github.com/apernet/hysteria/extras/v2/masq" "github.com/apernet/hysteria/extras/v2/masq"
"github.com/apernet/hysteria/extras/v2/obfs" "github.com/apernet/hysteria/extras/v2/obfs"
"github.com/apernet/hysteria/extras/v2/outbounds" "github.com/apernet/hysteria/extras/v2/outbounds"
"github.com/apernet/hysteria/extras/v2/sniff"
eUtils "github.com/apernet/hysteria/extras/v2/utils"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -161,6 +163,29 @@ func (n *Hysteria2node) getBandwidthConfig(info *panel.NodeInfo) *server.Bandwid
return band return band
} }
func (n *Hysteria2node) getRequestHook(c *serverConfig) (server.RequestHook, error) {
if c.Sniff.Enable {
s := &sniff.Sniffer{
Timeout: c.Sniff.Timeout,
RewriteDomain: c.Sniff.RewriteDomain,
}
if c.Sniff.TCPPorts != "" {
s.TCPPorts = eUtils.ParsePortUnion(c.Sniff.TCPPorts)
if s.TCPPorts == nil {
return nil, fmt.Errorf("sniff.tcpPorts: invalid port union")
}
}
if c.Sniff.UDPPorts != "" {
s.UDPPorts = eUtils.ParsePortUnion(c.Sniff.UDPPorts)
if s.UDPPorts == nil {
return nil, fmt.Errorf("sniff.udpPorts: invalid port union")
}
}
return s, nil
}
return nil, nil
}
func (n *Hysteria2node) getOutboundConfig(c *serverConfig) (server.Outbound, error) { func (n *Hysteria2node) getOutboundConfig(c *serverConfig) (server.Outbound, error) {
// Resolver, ACL, actual outbound are all implemented through the Outbound interface. // Resolver, ACL, actual outbound are all implemented through the Outbound interface.
// Depending on the config, we build a chain like this: // Depending on the config, we build a chain like this:
@@ -299,7 +324,7 @@ func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.Pac
req.Header.Set("X-Forwarded-For", clientIP) req.Header.Set("X-Forwarded-For", clientIP)
} }
if !c.Masquerade.Proxy.RewriteHost { if c.Masquerade.Proxy.RewriteHost {
req.Host = req.URL.Host req.Host = req.URL.Host
} }
}, },
@@ -342,7 +367,7 @@ func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.Pac
s := masq.MasqTCPServer{ s := masq.MasqTCPServer{
QUICPort: extractPortFromAddr(conn.LocalAddr().String()), QUICPort: extractPortFromAddr(conn.LocalAddr().String()),
HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS), HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS),
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false}, Handler: &masqHandlerLogWrapper{H: handler, QUIC: false, Logger: n.Logger},
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
Certificates: tlsconfig.Certificates, Certificates: tlsconfig.Certificates,
GetCertificate: tlsconfig.GetCertificate, GetCertificate: tlsconfig.GetCertificate,
@@ -368,6 +393,10 @@ func (n *Hysteria2node) getHyConfig(info *panel.NodeInfo, config *conf.Options,
if err != nil { if err != nil {
return nil, err return nil, err
} }
sniff, err := n.getRequestHook(c)
if err != nil {
return nil, err
}
Outbound, err := n.getOutboundConfig(c) Outbound, err := n.getOutboundConfig(c)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -380,9 +409,10 @@ func (n *Hysteria2node) getHyConfig(info *panel.NodeInfo, config *conf.Options,
TLSConfig: *tls, TLSConfig: *tls,
QUICConfig: *quic, QUICConfig: *quic,
Conn: conn, Conn: conn,
RequestHook: sniff,
Outbound: Outbound, Outbound: Outbound,
BandwidthConfig: *n.getBandwidthConfig(info), BandwidthConfig: *n.getBandwidthConfig(info),
IgnoreClientBandwidth: c.IgnoreClientBandwidth, IgnoreClientBandwidth: info.Hysteria2.Ignore_Client_Bandwidth,
DisableUDP: c.DisableUDP, DisableUDP: c.DisableUDP,
UDPIdleTimeout: c.UDPIdleTimeout, UDPIdleTimeout: c.UDPIdleTimeout,
EventLogger: n.EventLogger, EventLogger: n.EventLogger,

View File

@@ -6,13 +6,24 @@ import (
"github.com/InazumaV/V2bX/common/counter" "github.com/InazumaV/V2bX/common/counter"
"github.com/InazumaV/V2bX/common/format" "github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/limiter" "github.com/InazumaV/V2bX/limiter"
"github.com/apernet/hysteria/core/v2/server"
quic "github.com/apernet/quic-go"
"go.uber.org/zap" "go.uber.org/zap"
) )
var _ server.TrafficLogger = (*HookServer)(nil)
type HookServer struct { type HookServer struct {
Tag string Tag string
logger *zap.Logger logger *zap.Logger
Counter sync.Map Counter sync.Map
ReportMinTrafficBytes int64
}
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) { func (h *HookServer) LogTraffic(id string, tx, rx uint64) (ok bool) {

View File

@@ -120,11 +120,11 @@ func (l *serverLogger) UDPError(addr net.Addr, uuid string, sessionId uint32, er
func initLogger(logLevel string, logFormat string) (*zap.Logger, error) { func initLogger(logLevel string, logFormat string) (*zap.Logger, error) {
level, ok := logLevelMap[strings.ToLower(logLevel)] level, ok := logLevelMap[strings.ToLower(logLevel)]
if !ok { if !ok {
return nil, fmt.Errorf(fmt.Sprintf("unsupported log level: %s\n", logLevel)) return nil, fmt.Errorf("unsupported log level: %s", logLevel)
} }
enc, ok := logFormatMap[strings.ToLower(logFormat)] enc, ok := logFormatMap[strings.ToLower(logFormat)]
if !ok { if !ok {
return nil, fmt.Errorf(fmt.Sprintf("unsupported log format: %s\n", logFormat)) return nil, fmt.Errorf("unsupported log format: %s", logFormat)
} }
c := zap.Config{ c := zap.Config{
Level: zap.NewAtomicLevelAt(level), Level: zap.NewAtomicLevelAt(level),
@@ -137,7 +137,7 @@ func initLogger(logLevel string, logFormat string) (*zap.Logger, error) {
} }
logger, err := c.Build() logger, err := c.Build()
if err != nil { if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("failed to initialize logger: %s\n", err)) return nil, fmt.Errorf("failed to initialize logger: %s", err)
} }
return logger, nil return logger, nil
} }

View File

@@ -40,8 +40,9 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio
logger: h.Logger, logger: h.Logger,
}, },
TrafficLogger: &HookServer{ TrafficLogger: &HookServer{
Tag: tag, Tag: tag,
logger: h.Logger, logger: h.Logger,
ReportMinTrafficBytes: config.ReportMinTraffic * 1024,
}, },
} }

View File

@@ -17,10 +17,12 @@ type serverConfig struct {
QUIC serverConfigQUIC `mapstructure:"quic"` QUIC serverConfigQUIC `mapstructure:"quic"`
Bandwidth serverConfigBandwidth `mapstructure:"bandwidth"` Bandwidth serverConfigBandwidth `mapstructure:"bandwidth"`
IgnoreClientBandwidth bool `mapstructure:"ignoreClientBandwidth"` IgnoreClientBandwidth bool `mapstructure:"ignoreClientBandwidth"`
SpeedTest bool `mapstructure:"speedTest"`
DisableUDP bool `mapstructure:"disableUDP"` DisableUDP bool `mapstructure:"disableUDP"`
UDPIdleTimeout time.Duration `mapstructure:"udpIdleTimeout"` UDPIdleTimeout time.Duration `mapstructure:"udpIdleTimeout"`
Auth serverConfigAuth `mapstructure:"auth"` Auth serverConfigAuth `mapstructure:"auth"`
Resolver serverConfigResolver `mapstructure:"resolver"` Resolver serverConfigResolver `mapstructure:"resolver"`
Sniff serverConfigSniff `mapstructure:"sniff"`
ACL serverConfigACL `mapstructure:"acl"` ACL serverConfigACL `mapstructure:"acl"`
Outbounds []serverConfigOutboundEntry `mapstructure:"outbounds"` Outbounds []serverConfigOutboundEntry `mapstructure:"outbounds"`
TrafficStats serverConfigTrafficStats `mapstructure:"trafficStats"` TrafficStats serverConfigTrafficStats `mapstructure:"trafficStats"`
@@ -112,6 +114,14 @@ type serverConfigResolver struct {
HTTPS serverConfigResolverHTTPS `mapstructure:"https"` HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
} }
type serverConfigSniff struct {
Enable bool `mapstructure:"enable"`
Timeout time.Duration `mapstructure:"timeout"`
RewriteDomain bool `mapstructure:"rewriteDomain"`
TCPPorts string `mapstructure:"tcpPorts"`
UDPPorts string `mapstructure:"udpPorts"`
}
type serverConfigACL struct { type serverConfigACL struct {
File string `mapstructure:"file"` File string `mapstructure:"file"`
Inline []string `mapstructure:"inline"` Inline []string `mapstructure:"inline"`

View File

@@ -14,12 +14,12 @@ var _ server.Authenticator = &V2bX{}
type V2bX struct { type V2bX struct {
usersMap map[string]int usersMap map[string]int
mutex sync.Mutex mutex sync.RWMutex
} }
func (v *V2bX) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) { func (v *V2bX) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
v.mutex.Lock() v.mutex.RLock()
defer v.mutex.Unlock() defer v.mutex.RUnlock()
if _, exists := v.usersMap[auth]; exists { if _, exists := v.usersMap[auth]; exists {
return true, auth return true, auth
} }
@@ -41,7 +41,7 @@ func (h *Hysteria2) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
return len(p.Users), nil return len(p.Users), nil
} }
func (h *Hysteria2) DelUsers(users []panel.UserInfo, tag string) error { func (h *Hysteria2) DelUsers(users []panel.UserInfo, tag string, _ *panel.NodeInfo) error {
var wg sync.WaitGroup var wg sync.WaitGroup
for _, user := range users { for _, user := range users {
wg.Add(1) wg.Add(1)
@@ -56,15 +56,38 @@ func (h *Hysteria2) DelUsers(users []panel.UserInfo, tag string) error {
return nil return nil
} }
func (h *Hysteria2) GetUserTraffic(tag string, uuid string, reset bool) (up int64, down int64) { func (h *Hysteria2) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) {
if v, ok := h.Hy2nodes[tag].TrafficLogger.(*HookServer).Counter.Load(tag); ok { trafficSlice := make([]panel.UserTraffic, 0)
c := v.(*counter.TrafficCounter) h.Auth.mutex.RLock()
up = c.GetUpCount(uuid) defer h.Auth.mutex.RUnlock()
down = c.GetDownCount(uuid) if _, ok := h.Hy2nodes[tag]; !ok {
if reset { return nil, nil
c.Reset(uuid)
}
return up, down
} }
return 0, 0 hook := h.Hy2nodes[tag].TrafficLogger.(*HookServer)
if v, ok := hook.Counter.Load(tag); ok {
c := v.(*counter.TrafficCounter)
c.Counters.Range(func(key, value interface{}) bool {
uuid := key.(string)
traffic := value.(*counter.TrafficStorage)
up := traffic.UpCounter.Load()
down := traffic.DownCounter.Load()
if up+down > hook.ReportMinTrafficBytes {
if reset {
traffic.UpCounter.Store(0)
traffic.DownCounter.Store(0)
}
trafficSlice = append(trafficSlice, panel.UserTraffic{
UID: h.Auth.usersMap[uuid],
Upload: up,
Download: down,
})
}
return true
})
if len(trafficSlice) == 0 {
return nil, nil
}
return trafficSlice, nil
}
return nil, nil
} }

View File

@@ -17,8 +17,8 @@ type Core interface {
AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error
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) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error)
DelUsers(users []panel.UserInfo, tag string) error DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error
Protocols() []string Protocols() []string
Type() string Type() string
} }

View File

@@ -3,10 +3,11 @@ 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"
) )
@@ -127,20 +128,20 @@ func (s *Selector) AddUsers(p *AddUsersParams) (added int, err error) {
return t.(Core).AddUsers(p) return t.(Core).AddUsers(p)
} }
func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) { func (s *Selector) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) {
t, e := s.nodes.Load(tag) t, e := s.nodes.Load(tag)
if !e { if !e {
return 0, 0 return nil, errors.New("the node is not have")
} }
return t.(Core).GetUserTraffic(tag, uuid, reset) return t.(Core).GetUserTrafficSlice(tag, reset)
} }
func (s *Selector) DelUsers(users []panel.UserInfo, tag string) error { func (s *Selector) DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) 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) return t.(Core).DelUsers(users, tag, info)
} }
func (s *Selector) Protocols() []string { func (s *Selector) Protocols() []string {

View File

@@ -1,85 +0,0 @@
package sing
import (
"bytes"
"github.com/InazumaV/V2bX/api/panel"
"github.com/goccy/go-json"
log "github.com/sirupsen/logrus"
"os"
"strings"
)
func updateDNSConfig(node *panel.NodeInfo) (err error) {
dnsPath := os.Getenv("SING_DNS_PATH")
if len(node.RawDNS.DNSJson) != 0 {
var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, node.RawDNS.DNSJson, "", " "); err != nil {
return err
}
err = saveDnsConfig(prettyJSON.Bytes(), dnsPath)
} else if len(node.RawDNS.DNSMap) != 0 {
dnsConfig := DNSConfig{
Servers: []map[string]interface{}{
{
"tag": "default",
"address": "https://8.8.8.8/dns-query",
"detour": "direct",
},
},
}
for id, value := range node.RawDNS.DNSMap {
dnsConfig.Servers = append(dnsConfig.Servers,
map[string]interface{}{
"tag": id,
"address": value["address"],
"address_resolver": "default",
"detour": "direct",
},
)
rule := map[string]interface{}{
"server": id,
"disable_cache": true,
}
for _, ruleType := range []string{"domain_suffix", "domain_keyword", "domain_regex", "geosite"} {
var domains []string
for _, v := range value["domains"].([]string) {
split := strings.SplitN(v, ":", 2)
prefix := strings.ToLower(split[0])
if prefix == ruleType || (prefix == "domain" && ruleType == "domain_suffix") {
if len(split) > 1 {
domains = append(domains, split[1])
}
if len(domains) > 0 {
rule[ruleType] = domains
}
}
}
}
dnsConfig.Rules = append(dnsConfig.Rules, rule)
}
dnsConfigJSON, err := json.MarshalIndent(dnsConfig, "", " ")
if err != nil {
log.WithField("err", err).Error("Error marshaling dnsConfig to JSON")
return err
}
err = saveDnsConfig(dnsConfigJSON, dnsPath)
}
return err
}
func saveDnsConfig(dns []byte, dnsPath string) (err error) {
currentData, err := os.ReadFile(dnsPath)
if err != nil {
log.WithField("err", err).Error("Failed to read SING_DNS_PATH")
return err
}
if !bytes.Equal(currentData, dns) {
if err = os.Truncate(dnsPath, 0); err != nil {
log.WithField("err", err).Error("Failed to clear SING DNS PATH file")
}
if err = os.WriteFile(dnsPath, dns, 0644); err != nil {
log.WithField("err", err).Error("Failed to write DNS to SING DNS PATH file")
}
}
return err
}

View File

@@ -2,12 +2,10 @@ package sing
import ( import (
"context" "context"
"io" "fmt"
"net" "net"
"sync" "sync"
"github.com/sagernet/sing-box/common/urltest"
"github.com/InazumaV/V2bX/common/format" "github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/common/rate" "github.com/InazumaV/V2bX/common/rate"
@@ -17,218 +15,110 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
) )
var _ adapter.ClashServer = (*HookServer)(nil) var _ adapter.ConnectionTracker = (*HookServer)(nil)
type HookServer struct { type HookServer struct {
ctx context.Context counter sync.Map //map[string]*counter.TrafficCounter
urlTestHistory *urltest.HistoryStorage
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(ctx context.Context, enableClear bool) *HookServer { func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule, _ adapter.Outbound) net.Conn {
server := &HookServer{
ctx: ctx,
EnableConnClear: enableClear,
counter: sync.Map{},
connClears: sync.Map{},
}
server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
if server.urlTestHistory == nil {
server.urlTestHistory = urltest.NewHistoryStorage()
}
return server
}
func (h *HookServer) Start() error {
return nil
}
func (h *HookServer) Close() error {
h.urlTestHistory.Close()
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, t return conn
}
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
} }
taguuid := format.UserTag(m.Inbound, m.User)
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(taguuid, ip, true, 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, t return conn
} else if b != nil { } else if b != nil {
conn = rate.NewConnRateLimiter(conn, b) conn = rate.NewConnRateLimiter(conn, b)
} }
t.AddLeave(func() { if l != nil {
l.ConnLimiter.DelConnCount(m.User, ip) destStr := m.Destination.AddrString()
}) protocol := m.Protocol
if h.EnableConnClear { if l.CheckDomainRule(destStr) {
var key int log.Error(fmt.Sprintf(
cc := &ConnClear{ "User %s access domain %s reject by rule",
conns: map[int]io.Closer{ m.User,
0: conn, destStr))
}, conn.Close()
return conn
} }
if v, ok := h.connClears.LoadOrStore(m.Inbound+m.User, cc); ok { if len(protocol) != 0 {
cc = v.(*ConnClear) if l.CheckProtocolRule(protocol) {
key = cc.AddConn(conn) log.Error(fmt.Sprintf(
"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 { var t *counter.TrafficCounter
return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t if c, ok := h.counter.Load(m.Inbound); !ok {
t = counter.NewTrafficCounter()
h.counter.Store(m.Inbound, t)
} else { } else {
c := counter.NewTrafficCounter() t = c.(*counter.TrafficCounter)
h.counter.Store(m.Inbound, c)
return counter.NewConnCounter(conn, c.GetCounter(m.User)), t
} }
conn = counter.NewConnCounter(conn, t.GetCounter(m.User))
return conn
} }
func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn, m adapter.InboundContext, _ adapter.Rule) (N.PacketConn, adapter.Tracker) { func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn, m adapter.InboundContext, _ adapter.Rule, _ adapter.Outbound) N.PacketConn {
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, t return conn
}
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 { taguuid := format.UserTag(m.Inbound, m.User)
if b, r := l.CheckLimit(taguuid, ip, false, false); 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, t return conn
} else if b != nil { } else if b != nil {
conn = rate.NewPacketConnCounter(conn, b) //conn = rate.NewPacketConnCounter(conn, b)
} }
if h.EnableConnClear { if l != nil {
var key int destStr := m.Destination.AddrString()
cc := &ConnClear{ protocol := m.Destination.Network()
conns: map[int]io.Closer{ if l.CheckDomainRule(destStr) {
0: conn, log.Error(fmt.Sprintf(
}, "User %s access domain %s reject by rule",
m.User,
destStr))
conn.Close()
return conn
} }
if v, ok := h.connClears.LoadOrStore(m.Inbound+m.User, cc); ok { if len(protocol) != 0 {
cc = v.(*ConnClear) if l.CheckProtocolRule(protocol) {
key = cc.AddConn(conn) log.Error(fmt.Sprintf(
"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 { var t *counter.TrafficCounter
return counter.NewPacketConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t if c, ok := h.counter.Load(m.Inbound); !ok {
t = counter.NewTrafficCounter()
h.counter.Store(m.Inbound, t)
} else { } else {
c := counter.NewTrafficCounter() t = c.(*counter.TrafficCounter)
h.counter.Store(m.Inbound, c)
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.CacheFile {
return nil
}
func (h *HookServer) HistoryStorage() *urltest.HistoryStorage {
return h.urlTestHistory
}
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]()
} }
conn = counter.NewPacketConnCounter(conn, t.GetCounter(m.User))
return conn
} }

View File

@@ -10,12 +10,13 @@ import (
"strings" "strings"
"time" "time"
"encoding/json"
"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/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 { type HttpNetworkConfig struct {
@@ -54,20 +55,23 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
if err != nil { if err != nil {
return option.Inbound{}, fmt.Errorf("the listen ip not vail") return option.Inbound{}, fmt.Errorf("the listen ip not vail")
} }
var domainStrategy option.DomainStrategy
if c.SingOptions.EnableDNS {
domainStrategy = c.SingOptions.DomainStrategy
}
listen := option.ListenOptions{ listen := option.ListenOptions{
Listen: (*option.ListenAddress)(&addr), Listen: (*badoption.Addr)(&addr),
ListenPort: uint16(info.Common.ServerPort), ListenPort: uint16(info.Common.ServerPort),
ProxyProtocol: c.SingOptions.EnableProxyProtocol, TCPFastOpen: c.SingOptions.TCPFastOpen,
TCPFastOpen: c.SingOptions.TCPFastOpen, }
InboundOptions: option.InboundOptions{ var multiplex *option.InboundMultiplexOptions
SniffEnabled: c.SingOptions.SniffEnabled, if c.SingOptions.Multiplex != nil {
SniffOverrideDestination: c.SingOptions.SniffOverrideDestination, multiplexOption := option.InboundMultiplexOptions{
DomainStrategy: domainStrategy, 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 {
@@ -107,7 +111,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
ServerPort: uint16(port), ServerPort: uint16(port),
}, },
}, },
MaxTimeDifference: option.Duration(mtd), MaxTimeDifference: badoption.Duration(mtd),
} }
} }
in := option.Inbound{ in := option.Inbound{
@@ -150,7 +154,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
var ( var (
path string path string
ed int ed int
headers map[string]option.Listable[string] headers map[string]badoption.Listable[string]
) )
if len(n.NetworkSettings) != 0 { if len(n.NetworkSettings) != 0 {
network := WsNetworkConfig{} network := WsNetworkConfig{}
@@ -165,9 +169,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]option.Listable[string], len(network.Headers)) headers = make(map[string]badoption.Listable[string], len(network.Headers))
for k, v := range network.Headers { for k, v := range network.Headers {
headers[k] = option.Listable[string]{ headers[k] = badoption.Listable[string]{
v, v,
} }
} }
@@ -204,21 +208,23 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
} }
if info.Type == "vless" { if info.Type == "vless" {
in.Type = "vless" in.Type = "vless"
in.VLESSOptions = option.VLESSInboundOptions{ in.Options = &option.VLESSInboundOptions{
ListenOptions: listen, ListenOptions: listen,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &tls, TLS: &tls,
}, },
Transport: &t, Transport: &t,
Multiplex: multiplex,
} }
} else { } else {
in.Type = "vmess" in.Type = "vmess"
in.VMessOptions = option.VMessInboundOptions{ in.Options = &option.VMessInboundOptions{
ListenOptions: listen, ListenOptions: listen,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &tls, TLS: &tls,
}, },
Transport: &t, Transport: &t,
Multiplex: multiplex,
} }
} }
case "shadowsocks": case "shadowsocks":
@@ -233,20 +239,22 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
default: default:
keyLength = 16 keyLength = 16
} }
in.ShadowsocksOptions = option.ShadowsocksInboundOptions{ ssoption := &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") {
in.ShadowsocksOptions.Password = n.ServerKey ssoption.Password = n.ServerKey
randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd)) randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
} }
in.ShadowsocksOptions.Users = []option.ShadowsocksUser{{ ssoption.Users = []option.ShadowsocksUser{{
Password: randomPasswd, Password: randomPasswd,
}} }}
in.Options = ssoption
case "trojan": case "trojan":
n := info.Trojan n := info.Trojan
t := option.V2RayTransportOptions{ t := option.V2RayTransportOptions{
@@ -259,7 +267,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
var ( var (
path string path string
ed int ed int
headers map[string]option.Listable[string] headers map[string]badoption.Listable[string]
) )
if len(n.NetworkSettings) != 0 { if len(n.NetworkSettings) != 0 {
network := WsNetworkConfig{} network := WsNetworkConfig{}
@@ -274,9 +282,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]option.Listable[string], len(network.Headers)) headers = make(map[string]badoption.Listable[string], len(network.Headers))
for k, v := range network.Headers { for k, v := range network.Headers {
headers[k] = option.Listable[string]{ headers[k] = badoption.Listable[string]{
v, v,
} }
} }
@@ -302,19 +310,20 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
t.Type = "" t.Type = ""
} }
in.Type = "trojan" in.Type = "trojan"
in.TrojanOptions = option.TrojanInboundOptions{ trojanoption := &option.TrojanInboundOptions{
ListenOptions: listen, ListenOptions: listen,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &tls, TLS: &tls,
}, },
Transport: &t, 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 {
in.TrojanOptions.Fallback = &option.ServerOptions{ trojanoption.Fallback = &option.ServerOptions{
Server: fallback.Server, Server: fallback.Server,
ServerPort: uint16(fallbackPort), ServerPort: uint16(fallbackPort),
} }
@@ -322,12 +331,33 @@ 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 {
in.TrojanOptions.FallbackForALPN = fallbackForALPN trojanoption.FallbackForALPN = fallbackForALPN
} }
} }
in.Options = trojanoption
case "tuic":
in.Type = "tuic"
tls.ALPN = append(tls.ALPN, "h3")
in.Options = &option.TUICInboundOptions{
ListenOptions: listen,
CongestionControl: info.Tuic.CongestionControl,
ZeroRTTHandshake: info.Tuic.ZeroRTTHandshake,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &tls,
},
}
case "anytls":
in.Type = "anytls"
in.Options = &option.AnyTLSInboundOptions{
ListenOptions: listen,
PaddingScheme: info.AnyTls.PaddingScheme,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &tls,
},
}
case "hysteria": case "hysteria":
in.Type = "hysteria" in.Type = "hysteria"
in.HysteriaOptions = option.HysteriaInboundOptions{ in.Options = &option.HysteriaInboundOptions{
ListenOptions: listen, ListenOptions: listen,
UpMbps: info.Hysteria.UpMbps, UpMbps: info.Hysteria.UpMbps,
DownMbps: info.Hysteria.DownMbps, DownMbps: info.Hysteria.DownMbps,
@@ -350,11 +380,12 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Password: info.Hysteria2.ObfsType, Password: info.Hysteria2.ObfsType,
} }
} }
in.Hysteria2Options = option.Hysteria2InboundOptions{ in.Options = &option.Hysteria2InboundOptions{
ListenOptions: listen, ListenOptions: listen,
UpMbps: info.Hysteria2.UpMbps, UpMbps: info.Hysteria2.UpMbps,
DownMbps: info.Hysteria2.DownMbps, DownMbps: info.Hysteria2.DownMbps,
Obfs: obfs, IgnoreClientBandwidth: info.Hysteria2.Ignore_Client_Bandwidth,
Obfs: obfs,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &tls, TLS: &tls,
}, },
@@ -364,32 +395,21 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
} }
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) b.nodeReportMinTrafficBytes[tag] = config.ReportMinTraffic * 1024
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()
in, err := inbound.New( err = in.Create(
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, 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)
} }
@@ -397,11 +417,8 @@ 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 {
err := b.inbounds[tag].Close() in := b.box.Inbound()
if err != nil { err := in.Remove(tag)
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

@@ -4,15 +4,17 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"sync"
"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)
@@ -23,12 +25,18 @@ type DNSConfig struct {
} }
type Sing struct { type Sing struct {
box *box.Box box *box.Box
ctx context.Context ctx context.Context
hookServer *HookServer hookServer *HookServer
router adapter.Router router adapter.Router
logFactory log.Factory logFactory log.Factory
inbounds map[string]adapter.Inbound users *UserMap
nodeReportMinTrafficBytes map[string]int64
}
type UserMap struct {
uidMap map[string]int
mapLock sync.RWMutex
} }
func init() { func init() {
@@ -36,13 +44,15 @@ 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(), include.DNSTransportRegistry(), include.ServiceRegistry())
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) data, err := os.ReadFile(c.SingConfig.OriginalPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("read original config error: %s", err) return nil, fmt.Errorf("read original config error: %s", err)
} }
err = json.Unmarshal(data, &options) options, err = json.UnmarshalExtendedContext[option.Options](ctx, data)
if err != nil { if err != nil {
return nil, fmt.Errorf("unmarshal original config error: %s", err) return nil, fmt.Errorf("unmarshal original config error: %s", err)
} }
@@ -63,21 +73,26 @@ func New(c *conf.CoreConfig) (vCore.Core, error) {
} }
os.Setenv("SING_DNS_PATH", "") os.Setenv("SING_DNS_PATH", "")
b, err := box.New(box.Options{ b, err := box.New(box.Options{
Context: context.Background(), Context: ctx,
Options: options, Options: options,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
hs := NewHookServer(b.Router().GetCtx(), c.SingConfig.EnableConnClear) hs := &HookServer{
b.Router().SetClashServer(hs) counter: sync.Map{},
}
b.Router().AppendTracker(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), users: &UserMap{
uidMap: make(map[string]int),
},
nodeReportMinTrafficBytes: make(map[string]int64),
}, nil }, nil
} }
@@ -95,6 +110,8 @@ func (b *Sing) Protocols() []string {
"vless", "vless",
"shadowsocks", "shadowsocks",
"trojan", "trojan",
"tuic",
"anytls",
"hysteria", "hysteria",
"hysteria2", "hysteria2",
} }

View File

@@ -7,33 +7,47 @@ 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/anytls"
"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/tuic"
"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")
}
b.users.mapLock.Lock()
defer b.users.mapLock.Unlock()
for i := range p.Users {
b.users.uidMap[p.Users[i].Uuid] = p.Users[i].Id
}
switch p.NodeInfo.Type { switch p.NodeInfo.Type {
case "vmess", "vless": case "vless":
if p.NodeInfo.Type == "vless" { us := make([]option.VLESSUser, len(p.Users))
us := make([]option.VLESSUser, len(p.Users)) for i := range p.Users {
for i := range p.Users { us[i] = option.VLESSUser{
us[i] = option.VLESSUser{ Name: p.Users[i].Uuid,
Name: p.Users[i].Uuid, Flow: p.VAllss.Flow,
Flow: p.VAllss.Flow, UUID: p.Users[i].Uuid,
UUID: p.Users[i].Uuid,
}
} }
err = b.inbounds[p.Tag].(*inbound.VLESS).AddUsers(us)
} else {
us := make([]option.VMessUser, len(p.Users))
for i := range p.Users {
us[i] = option.VMessUser{
Name: p.Users[i].Uuid,
UUID: p.Users[i].Uuid,
}
}
err = b.inbounds[p.Tag].(*inbound.VMess).AddUsers(us)
} }
err = in.(*vless.Inbound).AddUsers(us)
case "vmess":
us := make([]option.VMessUser, len(p.Users))
for i := range p.Users {
us[i] = option.VMessUser{
Name: p.Users[i].Uuid,
UUID: p.Users[i].Uuid,
}
}
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 {
@@ -49,7 +63,7 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
Password: password, Password: password,
} }
} }
err = b.inbounds[p.Tag].(*inbound.ShadowsocksMulti).AddUsers(us) err = in.(*shadowsocks.MultiInbound).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 {
@@ -58,7 +72,19 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
Password: p.Users[i].Uuid, Password: p.Users[i].Uuid,
} }
} }
err = b.inbounds[p.Tag].(*inbound.Trojan).AddUsers(us) err = in.(*trojan.Inbound).AddUsers(us)
case "tuic":
us := make([]option.TUICUser, len(p.Users))
id := make([]int, len(p.Users))
for i := range p.Users {
us[i] = option.TUICUser{
Name: p.Users[i].Uuid,
UUID: p.Users[i].Uuid,
Password: p.Users[i].Uuid,
}
id[i] = p.Users[i].Id
}
err = in.(*tuic.Inbound).AddUsers(us, id)
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 {
@@ -67,7 +93,7 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
AuthString: p.Users[i].Uuid, AuthString: p.Users[i].Uuid,
} }
} }
err = b.inbounds[p.Tag].(*inbound.Hysteria).AddUsers(us) err = in.(*hysteria.Inbound).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)) id := make([]int, len(p.Users))
@@ -78,7 +104,16 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
} }
id[i] = p.Users[i].Id id[i] = p.Users[i].Id
} }
err = b.inbounds[p.Tag].(*inbound.Hysteria2).AddUsers(us, id) err = in.(*hysteria2.Inbound).AddUsers(us, id)
case "anytls":
us := make([]option.AnyTLSUser, len(p.Users))
for i := range p.Users {
us[i] = option.AnyTLSUser{
Name: p.Users[i].Uuid,
Password: p.Users[i].Uuid,
}
}
err = in.(*anytls.Inbound).AddUsers(us)
} }
if err != nil { if err != nil {
return 0, err return 0, err
@@ -99,33 +134,72 @@ func (b *Sing) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int6
return 0, 0 return 0, 0
} }
func (b *Sing) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) {
trafficSlice := make([]panel.UserTraffic, 0)
hook := b.hookServer
b.users.mapLock.RLock()
defer b.users.mapLock.RUnlock()
if v, ok := hook.counter.Load(tag); ok {
c := v.(*counter.TrafficCounter)
c.Counters.Range(func(key, value interface{}) bool {
uuid := key.(string)
traffic := value.(*counter.TrafficStorage)
up := traffic.UpCounter.Load()
down := traffic.DownCounter.Load()
if up+down > b.nodeReportMinTrafficBytes[tag] {
if reset {
traffic.UpCounter.Store(0)
traffic.DownCounter.Store(0)
}
trafficSlice = append(trafficSlice, panel.UserTraffic{
UID: b.users.uidMap[uuid],
Upload: up,
Download: down,
})
}
return true
})
if len(trafficSlice) == 0 {
return nil, nil
}
return trafficSlice, nil
}
return nil, nil
}
type UserDeleter interface { type UserDeleter interface {
DelUsers(uuid []string) error DelUsers(uuid []string) error
} }
func (b *Sing) DelUsers(users []panel.UserInfo, tag string) error { func (b *Sing) DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error {
var del UserDeleter var del UserDeleter
if i, ok := b.inbounds[tag]; ok { if i, found := b.box.Inbound().Get(tag); found {
switch i.Type() { switch info.Type {
case "vmess": case "vmess":
del = i.(*inbound.VMess) del = i.(*vmess.Inbound)
case "vless": case "vless":
del = i.(*inbound.VLESS) del = i.(*vless.Inbound)
case "shadowsocks": case "shadowsocks":
del = i.(*inbound.ShadowsocksMulti) del = i.(*shadowsocks.MultiInbound)
case "trojan": case "trojan":
del = i.(*inbound.Trojan) del = i.(*trojan.Inbound)
case "tuic":
del = i.(*tuic.Inbound)
case "hysteria": case "hysteria":
del = i.(*inbound.Hysteria) del = i.(*hysteria.Inbound)
case "hysteria2": case "hysteria2":
del = i.(*inbound.Hysteria2) del = i.(*hysteria2.Inbound)
case "anytls":
del = i.(*anytls.Inbound)
} }
} 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))
b.users.mapLock.Lock()
defer b.users.mapLock.Unlock()
for i := range users { for i := range users {
b.hookServer.ClearConn(tag, users[i].Uuid) delete(b.users.uidMap, 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.31.0 // protoc-gen-go v1.35.1
// protoc v4.23.4 // protoc v3.21.12
// source: config.proto // source: config.proto
package dispatcher package dispatcher
@@ -28,11 +28,9 @@ type SessionConfig struct {
func (x *SessionConfig) Reset() { func (x *SessionConfig) Reset() {
*x = SessionConfig{} *x = SessionConfig{}
if protoimpl.UnsafeEnabled { mi := &file_config_proto_msgTypes[0]
mi := &file_config_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *SessionConfig) String() string { func (x *SessionConfig) String() string {
@@ -43,7 +41,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 protoimpl.UnsafeEnabled && x != nil { if 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)
@@ -68,11 +66,9 @@ type Config struct {
func (x *Config) Reset() { func (x *Config) Reset() {
*x = Config{} *x = Config{}
if protoimpl.UnsafeEnabled { mi := &file_config_proto_msgTypes[1]
mi := &file_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *Config) String() string { func (x *Config) String() string {
@@ -83,7 +79,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 protoimpl.UnsafeEnabled && x != nil { if 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)
@@ -140,7 +136,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 = []interface{}{ var file_config_proto_goTypes = []any{
(*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
} }
@@ -158,32 +154,6 @@ 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

@@ -10,6 +10,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/InazumaV/V2bX/common/counter"
"github.com/InazumaV/V2bX/common/rate" "github.com/InazumaV/V2bX/common/rate"
"github.com/InazumaV/V2bX/limiter" "github.com/InazumaV/V2bX/limiter"
@@ -39,17 +40,21 @@ type cachedReader struct {
cache buf.MultiBuffer cache buf.MultiBuffer
} }
func (r *cachedReader) Cache(b *buf.Buffer) { func (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100) mb, err := r.reader.ReadMultiBufferTimeout(deadline)
if err != nil {
return err
}
r.Lock() r.Lock()
if !mb.IsEmpty() { if !mb.IsEmpty() {
r.cache, _ = buf.MergeMulti(r.cache, mb) r.cache, _ = buf.MergeMulti(r.cache, mb)
} }
b.Clear() b.Clear()
rawBytes := b.Extend(buf.Size) rawBytes := b.Extend(min(r.cache.Len(), b.Cap()))
n := r.cache.Copy(rawBytes) n := r.cache.Copy(rawBytes)
b.Resize(0, int32(n)) b.Resize(0, int32(n))
r.Unlock() r.Unlock()
return nil
} }
func (r *cachedReader) readInternal() buf.MultiBuffer { func (r *cachedReader) readInternal() buf.MultiBuffer {
@@ -94,22 +99,23 @@ func (r *cachedReader) Interrupt() {
// DefaultDispatcher is a default implementation of Dispatcher. // DefaultDispatcher is a default implementation of Dispatcher.
type DefaultDispatcher struct { type DefaultDispatcher struct {
ohm outbound.Manager ohm outbound.Manager
router routing.Router router routing.Router
policy policy.Manager policy policy.Manager
stats stats.Manager stats stats.Manager
dns dns.Client fdns dns.FakeDNSEngine
fdns dns.FakeDNSEngine Wm *WriterManager
Counter sync.Map
} }
func init() { 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.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) { core.OptionalFeatures(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)
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
@@ -118,12 +124,14 @@ func init() {
} }
// Init initializes DefaultDispatcher. // Init initializes DefaultDispatcher.
func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dns dns.Client) error { func (d *DefaultDispatcher) Init(config *Config, om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) error {
d.ohm = om d.ohm = om
d.router = router d.router = router
d.policy = pm d.policy = pm
d.stats = sm d.stats = sm
d.dns = dns d.Wm = &WriterManager{
writers: make(map[string]map[*ManagedWriter]struct{}),
}
return nil return nil
} }
@@ -187,28 +195,33 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
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, errors.New("Limited ", user.Email, " by conn or ip")
} }
managedWriter := &ManagedWriter{
writer: uplinkWriter,
email: user.Email,
manager: d.Wm,
}
d.Wm.AddWriter(managedWriter)
inboundLink.Writer = managedWriter
if w != nil { if w != nil {
inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w) inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w)
outboundLink.Writer = rate.NewRateLimitWriter(outboundLink.Writer, w) outboundLink.Writer = rate.NewRateLimitWriter(outboundLink.Writer, w)
} }
p := d.policy.ForLevel(user.Level) var t *counter.TrafficCounter
if p.Stats.UserUplink { if c, ok := d.Counter.Load(sessionInbound.Tag); !ok {
name := "user>>>" + user.Email + ">>>traffic>>>uplink" t = counter.NewTrafficCounter()
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { d.Counter.Store(sessionInbound.Tag, t)
inboundLink.Writer = &SizeStatWriter{ } else {
Counter: c, t = c.(*counter.TrafficCounter)
Writer: inboundLink.Writer,
}
}
} }
if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink" inboundLink.Writer = &UploadTrafficWriter{
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { Counter: t.GetCounter(user.Email),
outboundLink.Writer = &SizeStatWriter{ Writer: inboundLink.Writer,
Counter: c, }
Writer: outboundLink.Writer,
} outboundLink.Writer = &DownloadTrafficWriter{
} Counter: t.GetCounter(user.Email),
Writer: outboundLink.Writer,
} }
} }
@@ -358,7 +371,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
protocol = resComp.ProtocolForDomainResult() protocol = resComp.ProtocolForDomainResult()
} }
isFakeIP := false isFakeIP := false
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && ob.Target.Address.Family().IsIP() && fkr0.IsIPInIPPool(ob.Target.Address) { if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(ob.Target.Address) {
isFakeIP = true isFakeIP = true
} }
if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP { if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP {
@@ -366,7 +379,6 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
} else { } else {
ob.Target = destination ob.Target = destination
} }
destination.Address.Family()
} }
d.routedDispatch(ctx, outbound, destination, nil, content.Protocol) d.routedDispatch(ctx, outbound, destination, nil, content.Protocol)
} }
@@ -375,7 +387,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
} }
func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) { func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, network net.Network) (SniffResult, error) {
payload := buf.New() payload := buf.NewWithSize(32767)
defer payload.Release() defer payload.Release()
sniffer := NewSniffer(ctx) sniffer := NewSniffer(ctx)
@@ -387,26 +399,36 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
} }
contentResult, contentErr := func() (SniffResult, error) { contentResult, contentErr := func() (SniffResult, error) {
cacheDeadline := 200 * time.Millisecond
totalAttempt := 0 totalAttempt := 0
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
default: default:
totalAttempt++ cachingStartingTimeStamp := time.Now()
if totalAttempt > 2 { err := cReader.Cache(payload, cacheDeadline)
return nil, errSniffingTimeout if err != nil {
return nil, err
} }
cachingTimeElapsed := time.Since(cachingStartingTimeStamp)
cacheDeadline -= cachingTimeElapsed
cReader.Cache(payload)
if !payload.IsEmpty() { if !payload.IsEmpty() {
result, err := sniffer.Sniff(ctx, payload.Bytes(), network) result, err := sniffer.Sniff(ctx, payload.Bytes(), network)
if err != common.ErrNoClue { switch err {
case common.ErrNoClue: // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not
totalAttempt++
case protocol.ErrProtoNeedMoreData: // Protocol Need More Data: protocol matches, but need more data to complete sniffing
// in this case, do not add totalAttempt(allow to read until timeout)
default:
return result, err return result, err
} }
} else {
totalAttempt++
} }
if payload.IsFull() { if totalAttempt >= 2 || cacheDeadline <= 0 {
return nil, errUnknownContent return nil, errSniffingTimeout
} }
} }
} }
@@ -423,29 +445,10 @@ 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) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
proxied := hosts.LookupHosts(ob.Target.String())
if proxied != nil {
ro := ob.RouteTarget == destination
destination.Address = *proxied
if ro {
ob.RouteTarget = destination
} else {
ob.Target = destination
}
}
}
sessionInbound := session.InboundFromContext(ctx) sessionInbound := session.InboundFromContext(ctx)
if sessionInbound.User != nil { if sessionInbound.User != nil {
if l != nil { if l == nil {
// del connect count
if destination.Network == net.Network_TCP {
defer func() {
l.ConnLimiter.DelConnCount(sessionInbound.User.Email, sessionInbound.Source.Address.IP().String())
}()
}
} else {
var err error var err error
l, err = limiter.GetLimiter(sessionInbound.Tag) l, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil { if err != nil {
@@ -504,7 +507,11 @@ 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, "]") if route.GetRuleTag() == "" {
errors.LogInfo(ctx, "taking detour [", outTag, "] for [", destination, "]")
} else {
errors.LogInfo(ctx, "Hit route rule: [", route.GetRuleTag(), "] so taking detour [", outTag, "] for [", destination, "]")
}
handler = h handler = h
} else { } else {
errors.LogWarning(ctx, "non existing outTag: ", outTag) errors.LogWarning(ctx, "non existing outTag: ", outTag)
@@ -514,10 +521,6 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
} }
} }
if handler == nil {
handler = d.ohm.GetHandler(inTag)
}
if handler == nil { if handler == nil {
handler = d.ohm.GetDefaultHandler() handler = d.ohm.GetDefaultHandler()
} }
@@ -529,6 +532,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
return return
} }
ob.Tag = handler.Tag()
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil { if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
if tag := handler.Tag(); tag != "" { if tag := handler.Tag(); tag != "" {
if inTag == "" { if inTag == "" {

View File

@@ -0,0 +1,61 @@
package dispatcher
import (
sync "sync"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
)
type WriterManager struct {
writers map[string]map[*ManagedWriter]struct{}
mu sync.Mutex
}
func (m *WriterManager) AddWriter(writer *ManagedWriter) {
m.mu.Lock()
defer m.mu.Unlock()
if _, exists := m.writers[writer.email]; !exists {
m.writers[writer.email] = make(map[*ManagedWriter]struct{})
}
m.writers[writer.email][writer] = struct{}{}
}
func (m *WriterManager) RemoveWriter(writer *ManagedWriter) {
m.mu.Lock()
defer m.mu.Unlock()
if _, exists := m.writers[writer.email]; !exists {
return
}
delete(m.writers[writer.email], writer)
}
func (m *WriterManager) RemoveWritersForUser(email string) {
m.mu.Lock()
defer m.mu.Unlock()
if _, exists := m.writers[email]; !exists {
return
}
for writer := range m.writers[email] {
delete(m.writers[email], writer)
common.Close(writer.writer)
}
delete(m.writers, email)
}
type ManagedWriter struct {
writer buf.Writer
email string
manager *WriterManager
}
func (w *ManagedWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
return w.writer.WriteMultiBuffer(mb)
}
func (w *ManagedWriter) Close() error {
w.manager.RemoveWriter(w)
return common.Close(w.writer)
}

View File

@@ -35,7 +35,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) }, false, net.Network_TCP}, {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 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},

View File

@@ -1,25 +1,43 @@
package dispatcher package dispatcher
import ( import (
"github.com/InazumaV/V2bX/common/counter"
"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/features/stats"
) )
type SizeStatWriter struct { type UploadTrafficWriter struct {
Counter stats.Counter Counter *counter.TrafficStorage
Writer buf.Writer Writer buf.Writer
} }
func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { type DownloadTrafficWriter struct {
w.Counter.Add(int64(mb.Len())) Counter *counter.TrafficStorage
Writer buf.Writer
}
func (w *UploadTrafficWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.Counter.UpCounter.Add(int64(mb.Len()))
return w.Writer.WriteMultiBuffer(mb) return w.Writer.WriteMultiBuffer(mb)
} }
func (w *SizeStatWriter) Close() error { func (w *UploadTrafficWriter) Close() error {
return common.Close(w.Writer) return common.Close(w.Writer)
} }
func (w *SizeStatWriter) Interrupt() { func (w *UploadTrafficWriter) Interrupt() {
common.Interrupt(w.Writer)
}
func (w *DownloadTrafficWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
w.Counter.DownCounter.Add(int64(mb.Len()))
return w.Writer.WriteMultiBuffer(mb)
}
func (w *DownloadTrafficWriter) Close() error {
return common.Close(w.Writer)
}
func (w *DownloadTrafficWriter) Interrupt() {
common.Interrupt(w.Writer) common.Interrupt(w.Writer)
} }

View File

@@ -41,6 +41,7 @@ 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"
@@ -51,13 +52,10 @@ 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/http" _ "github.com/xtls/xray-core/transport/internet/kcp"
//_ "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"
@@ -70,5 +68,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

@@ -7,8 +7,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"encoding/json"
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"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"
) )

View File

@@ -8,9 +8,10 @@ import (
"fmt" "fmt"
"time" "time"
"encoding/json"
"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/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
coreConf "github.com/xtls/xray-core/infra/conf" coreConf "github.com/xtls/xray-core/infra/conf"
@@ -133,12 +134,14 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{ in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{
Dest: d, Dest: d,
Xver: xver, Xver: xver,
Show: false,
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,
MaxClientVer: v.RealityConfig.MaxClientVer, MaxClientVer: v.RealityConfig.MaxClientVer,
MaxTimeDiff: uint64(mtd.Microseconds()), MaxTimeDiff: uint64(mtd.Microseconds()),
ShortIds: []string{v.TlsSettings.ShortId}, ShortIds: []string{v.TlsSettings.ShortId},
Mldsa65Seed: v.TlsSettings.Mldsa65Seed,
} }
default: default:
break break
@@ -204,17 +207,7 @@ 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.GRPCConfig) err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.GRPCSettings)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
case "http":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.HTTPSettings)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
case "quic":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.QUICSettings)
if err != nil { if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err) return fmt.Errorf("unmarshal grpc settings error: %s", err)
} }
@@ -223,10 +216,10 @@ func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCon
if err != nil { if err != nil {
return fmt.Errorf("unmarshal httpupgrade settings error: %s", err) return fmt.Errorf("unmarshal httpupgrade settings error: %s", err)
} }
case "splithttp": case "splithttp", "xhttp":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.SplitHTTPSettings) err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.SplitHTTPSettings)
if err != nil { if err != nil {
return fmt.Errorf("unmarshal splithttp settings error: %s", err) 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")
@@ -272,7 +265,7 @@ func buildTrojan(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCo
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.GRPCConfig) err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.GRPCSettings)
if err != nil { if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err) return fmt.Errorf("unmarshal grpc settings error: %s", err)
} }

View File

@@ -17,6 +17,7 @@ type DNSConfig struct {
} }
func (c *Xray) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error { func (c *Xray) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error {
c.nodeReportMinTrafficBytes[tag] = config.ReportMinTraffic * 1024
err := updateDNSConfig(info) err := updateDNSConfig(info)
if err != nil { if err != nil {
return fmt.Errorf("build dns error: %s", err) return fmt.Errorf("build dns error: %s", err)

View File

@@ -3,8 +3,9 @@ package xray
import ( import (
"fmt" "fmt"
"encoding/json"
conf2 "github.com/InazumaV/V2bX/conf" conf2 "github.com/InazumaV/V2bX/conf"
"github.com/goccy/go-json"
"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"
) )

View File

@@ -38,9 +38,11 @@ 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.User{ ssAccount := &shadowsocks_2022.Account{
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

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/common/counter"
"github.com/InazumaV/V2bX/common/format" "github.com/InazumaV/V2bX/common/format"
vCore "github.com/InazumaV/V2bX/core" vCore "github.com/InazumaV/V2bX/core"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
@@ -27,51 +28,66 @@ func (c *Xray) GetUserManager(tag string) (proxy.UserManager, error) {
return userManager, nil return userManager, nil
} }
func (c *Xray) DelUsers(users []panel.UserInfo, tag string) error { func (c *Xray) DelUsers(users []panel.UserInfo, tag string, _ *panel.NodeInfo) 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)
} }
var up, down, user string var user string
c.users.mapLock.Lock()
defer c.users.mapLock.Unlock()
for i := range users { for i := range users {
user = format.UserTag(tag, users[i].Uuid) user = format.UserTag(tag, users[i].Uuid)
err = userManager.RemoveUser(context.Background(), user) err = userManager.RemoveUser(context.Background(), user)
if err != nil { if err != nil {
return err return err
} }
up = "user>>>" + user + ">>>traffic>>>uplink" delete(c.users.uidMap, user)
down = "user>>>" + user + ">>>traffic>>>downlink" c.dispatcher.Counter.Delete(user)
c.shm.UnregisterCounter(up) c.dispatcher.Wm.RemoveWritersForUser(user)
c.shm.UnregisterCounter(down)
} }
return nil return nil
} }
func (c *Xray) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) { func (x *Xray) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) {
upName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>uplink" trafficSlice := make([]panel.UserTraffic, 0)
downName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>downlink" x.users.mapLock.RLock()
upCounter := c.shm.GetCounter(upName) defer x.users.mapLock.RUnlock()
downCounter := c.shm.GetCounter(downName) if v, ok := x.dispatcher.Counter.Load(tag); ok {
if reset { c := v.(*counter.TrafficCounter)
if upCounter != nil { c.Counters.Range(func(key, value interface{}) bool {
up = upCounter.Set(0) email := key.(string)
} traffic := value.(*counter.TrafficStorage)
if downCounter != nil { up := traffic.UpCounter.Load()
down = downCounter.Set(0) down := traffic.DownCounter.Load()
} if up+down > x.nodeReportMinTrafficBytes[tag] {
} else { if reset {
if upCounter != nil { traffic.UpCounter.Store(0)
up = upCounter.Value() traffic.DownCounter.Store(0)
} }
if downCounter != nil { trafficSlice = append(trafficSlice, panel.UserTraffic{
down = downCounter.Value() UID: x.users.uidMap[email],
Upload: up,
Download: down,
})
}
return true
})
if len(trafficSlice) == 0 {
return nil, nil
} }
return trafficSlice, nil
} }
return up, down return nil, nil
} }
func (c *Xray) AddUsers(p *vCore.AddUsersParams) (added int, err error) { func (c *Xray) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
users := make([]*protocol.User, 0, len(p.Users)) c.users.mapLock.Lock()
defer c.users.mapLock.Unlock()
for i := range p.Users {
c.users.uidMap[format.UserTag(p.Tag, p.Users[i].Uuid)] = p.Users[i].Id
}
var users []*protocol.User
switch p.NodeInfo.Type { switch p.NodeInfo.Type {
case "vmess": case "vmess":
users = buildVmessUsers(p.Tag, p.Users) users = buildVmessUsers(p.Tag, p.Users)

View File

@@ -5,11 +5,12 @@ import (
"os" "os"
"sync" "sync"
"encoding/json/v2"
"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/core/xray/app/dispatcher" "github.com/InazumaV/V2bX/core/xray/app/dispatcher"
_ "github.com/InazumaV/V2bX/core/xray/distro/all" _ "github.com/InazumaV/V2bX/core/xray/distro/all"
"github.com/goccy/go-json"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/xtls/xray-core/app/proxyman" "github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/app/stats" "github.com/xtls/xray-core/app/stats"
@@ -30,16 +31,29 @@ func init() {
// Xray Structure // Xray Structure
type Xray struct { type Xray struct {
access sync.Mutex access sync.Mutex
Server *core.Instance Server *core.Instance
ihm inbound.Manager ihm inbound.Manager
ohm outbound.Manager ohm outbound.Manager
shm statsFeature.Manager shm statsFeature.Manager
dispatcher *dispatcher.DefaultDispatcher dispatcher *dispatcher.DefaultDispatcher
users *UserMap
nodeReportMinTrafficBytes map[string]int64
}
type UserMap struct {
uidMap map[string]int
mapLock sync.RWMutex
} }
func New(c *conf.CoreConfig) (vCore.Core, error) { func New(c *conf.CoreConfig) (vCore.Core, error) {
return &Xray{Server: getCore(c.XrayConfig)}, nil return &Xray{
Server: getCore(c.XrayConfig),
users: &UserMap{
uidMap: make(map[string]int),
},
nodeReportMinTrafficBytes: make(map[string]int64),
}, nil
} }
func parseConnectionConfig(c *conf.XrayConnectionConfig) (policy *coreConf.Policy) { func parseConnectionConfig(c *conf.XrayConnectionConfig) (policy *coreConf.Policy) {

View File

@@ -7,14 +7,15 @@
{ {
"Type": "sing", "Type": "sing",
"Log": { "Log": {
"Level": "error", "Level": "info",
"Timestamp": true "Timestamp": true
}, },
"NTP": { "NTP": {
"Enable": true, "Enable": false,
"Server": "time.apple.com", "Server": "time.apple.com",
"ServerPort": 0 "ServerPort": 0
} },
"OriginalPath": "/etc/V2bX/sing_origin.json"
} }
], ],
"Nodes": [ "Nodes": [
@@ -27,20 +28,19 @@
"Timeout": 30, "Timeout": 30,
"ListenIP": "0.0.0.0", "ListenIP": "0.0.0.0",
"SendIP": "0.0.0.0", "SendIP": "0.0.0.0",
"EnableProxyProtocol": false, "DeviceOnlineMinTraffic": 200,
"EnableDNS": true, "MinReportTraffic": 0,
"DomainStrategy": "ipv4_only", "TCPFastOpen": false,
"LimitConfig": { "SniffEnabled": true,
"EnableRealtime": false, "CertConfig": {
"SpeedLimit": 0, "CertMode": "self",
"IPLimit": 0, "RejectUnknownSni": false,
"ConnLimit": 0, "CertDomain": "example.com",
"EnableDynamicSpeedLimit": false, "CertFile": "/etc/V2bX/fullchain.cer",
"DynamicSpeedLimitConfig": { "KeyFile": "/etc/V2bX/cert.key",
"Periodic": 60, "Provider": "cloudflare",
"Traffic": 1000, "DNSEnv": {
"SpeedLimit": 100, "EnvName": "env1"
"ExpireTime": 60
} }
} }
} }

View File

@@ -1,261 +0,0 @@
{
"Log": {
// V2bX 的日志配置,独立于各 Core 的 log 配置
// 日志等级info, warn, error, none
"Level": "error",
// 日志输出路径,默认输出到标准输出
"Output": ""
},
"Cores": [
{
// Core类型
"Type": "sing",
// Core标识名可选如果需要启动多个同类型内核则必填
"Name": "sing1",
"Log": {
// 同 SingBox log 部分配置
"Level": "error",
"Timestamp": true
},
"NTP": {
// 同 SingBox ntp 部分配置
// VMess VLESS 建议开启
"Enable": true,
"Server": "time.apple.com",
"ServerPort": 0
},
"DnsConfigPath": "/etc/V2bX/dns.json",
// SingBox源配置文件目录用于引用标准SingBox配置文件
"OriginalPath": "/etc/V2bX/sing_origin.json",
// 在删除用户时清理已建立的连接
"EnableConnClear": false,
},
{
"Type": "sing",
"Name": "sing2",
"Log": {
"Level": "info",
"Timestamp": false
}
},
{
"Type": "xray",
"Log": {
// 同 Xray-core log 部分配置
"Level": "error"
},
// 静态资源文件目录
"AssetPath": "",
// DNS配置文件目录
"DnsConfigPath": "",
// 路由配置文件目录
"RouteConfigPath": "",
// 本地策略相关配置
"ConnectionConfig": {
// 详见 https://xtls.github.io/config/policy.html#levelpolicyobject
"handshake": 4,
"connIdle": 300,
"uplinkOnly": 2,
"downlinkOnly": 5,
"statsUserUplink": false,
"statsUserDownlink": false,
"bufferSize": 4
},
// Inbound配置文件目录
"InboundConfigPath": "",
// Outbound配置文件目录
"OutboundConfigPath": ""
}
],
"Nodes": [
// Node配置有两种写法
{
// 写法1
// sing内核
// Node标识名便于查看日志不填将通过下发的节点配置自动生成
// 务必注意不要重复,否则会出现问题
"Name": "sing_node1",
// 要使用的Core的类型
// 如果填写了CoreName可不填但单内核务必填写
// 建议视情况填写Core和CoreName其中一个如果均没有填写将随机选择支持的内核
"Core": "sing",
// 要使用的Core的标识名如果没有定义多个同类型内核可不填
"CoreName": "sing1",
// API接口地址
"ApiHost": "http://127.0.0.1",
// API密钥即Token
"ApiKey": "test",
// 节点ID
"NodeID": 33,
// 节点类型
"NodeType": "shadowsocks",
// 请求超时时间
"Timeout": 30,
// 监听IP
"ListenIP": "0.0.0.0",
// 发送IP
"SendIP": "0.0.0.0",
// 开启 Proxy Protocol参见 https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt
"EnableProxyProtocol": false,
// 开启 TCP Fast Open
"EnableTFO": true,
// 开启 DNS
"EnableDNS" : true,
// 设置 Domain Strategy 需要开启 DNS ,默认 AsIS
// 可选 prefer_ipv4 / prefer_ipv6 / ipv4_only / ipv6_only
"DomainStrategy": "ipv4_only",
// 限制器相关配置
"LimitConfig": {
// 开启实时连接数及IP数限制
"EnableRealtime": false,
// 用户速度限制
"SpeedLimit": 0,
// 用户IP限制
"IPLimit": 0,
// 用户连接数限制
"ConnLimit": 0,
// 开启动态限速
"EnableDynamicSpeedLimit": false,
// 动态限速相关配置
"DynamicSpeedLimitConfig": {
// 检查周期
"Periodic": 60,
// 检查周期内触发限制的流量数
"Traffic": 1000,
// 触发限制后的速度限制
"SpeedLimit": 100,
// 速度限制过期时间
"ExpireTime": 60
}
},
// 证书相关配置
"CertConfig": {
// 证书申请模式none、http、dns、self
"CertMode": "none",
"RejectUnknownSni": false,
// 证书域名
"CertDomain": "test.com",
// 证书文件目录
"CertFile": "/etc/V2bX/cert/1.pem",
// 密钥文件目录
"KeyFile": "/etc/V2bX/cert/1.key",
// 申请证书时使用的用户邮箱
"Email": "1@test.com",
// DNS解析提供者
"Provider": "cloudflare",
// DNS解析提供者的环境变量详见 https://go-acme.github.io/lego/dns/
"DNSEnv": {
"EnvName": "env1"
}
}
},
{
// xray内核
"Name": "xray_node1",
"Core": "xray",
"CoreName": "",
"ApiHost": "http://127.0.0.1",
"ApiKey": "test",
"NodeID": 33,
"NodeType": "shadowsocks",
"Timeout": 30,
"ListenIP": "0.0.0.0",
"SendIP": "0.0.0.0",
"EnableProxyProtocol": true,
"EnableTFO": true,
// 以上同 sing
// 开启自定义DNS
"EnableDNS": false,
// DNS解析类型AsIs、UseIP、UseIPv4、UseIPv6
"DNSType": "AsIs",
// 开启udp over tcp
"EnableUot": false,
// 禁用IVCheck
"DisableIVCheck": false,
// 禁用嗅探
"DisableSniffing": false,
// 开启回落
"EnableFallback": false,
// 回落相关配置
"FallBackConfigs":{
// 详见 https://xtls.github.io/config/features/fallback.html#fallbackobject
"SNI": "",
"Alpn": "",
"Path": "",
"Dest": "",
"ProxyProtocolVer": 0
}
},
{
// 写法2
// 类似旧配置文件 ApiConfig 部分
"ApiConfig": {
"ApiHost": "http://127.0.0.1",
"ApiKey": "test",
"NodeID": 33,
"Timeout": 30
},
// 类似旧配置文件 ControllerConfig 部分
"Options": {
"Core": "sing",
"EnableProxyProtocol": true,
"EnableTFO": true,
"DomainStrategy": "ipv4_only"
// More
}
},
{
// 引用本地其他配置文件
"Include": "../example/config_full_node1.json"
},
{
// 通过Http引用远端配置文件
"Include": "http://127.0.0.1:11451/config_full_node1.json"
}
]
}

View File

@@ -1,13 +0,0 @@
{
"Core": "xray",
"ApiHost": "https://127.0.0.1",
"ApiKey": "key",
"NodeID": 1,
"NodeType": "vmess",
"Timeout": 30,
"ListenIP": "0.0.0.0",
"SendIP": "0.0.0.0",
"EnableProxyProtocol": false,
"EnableTFO": true,
"DNSType": "ipv4_only"
}

359
go.mod
View File

@@ -1,42 +1,45 @@
module github.com/InazumaV/V2bX module github.com/InazumaV/V2bX
go 1.22 go 1.25
toolchain go1.22.5 toolchain go1.25.0
require ( require (
github.com/apernet/hysteria/core/v2 v2.5.1-0.20240710201643-b563f3981fc6 github.com/apernet/hysteria/core/v2 v2.6.2
github.com/apernet/hysteria/extras/v2 v2.5.1-0.20240710201643-b563f3981fc6 github.com/apernet/hysteria/extras/v2 v2.6.2
github.com/beevik/ntp v1.2.0 github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431
github.com/fsnotify/fsnotify v1.7.0 github.com/beevik/ntp v1.4.4-0.20240716062501-06ef196b89ec
github.com/go-acme/lego/v4 v4.17.4 github.com/fsnotify/fsnotify v1.8.0
github.com/go-resty/resty/v2 v2.13.1 github.com/go-acme/lego/v4 v4.21.1-0.20241220151055-ee7a9e4fa04f
github.com/goccy/go-json v0.10.3 github.com/go-resty/resty/v2 v2.16.2
github.com/hashicorp/go-multierror v1.1.1 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.5.0-alpha.13 github.com/sagernet/sing v0.7.0-beta.2
github.com/sagernet/sing-box v1.10.0-alpha.23 github.com/sagernet/sing-box v1.12.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.15.0 github.com/spf13/viper v1.19.0
github.com/xtls/xray-core v1.8.23 github.com/vmihailenco/msgpack/v5 v5.4.1
github.com/xtls/xray-core v1.250803.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/crypto v0.25.0 golang.org/x/crypto v0.40.0
golang.org/x/sys v0.22.0 golang.org/x/sys v0.34.0
google.golang.org/protobuf v1.34.2 google.golang.org/protobuf v1.36.6
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
berty.tech/go-libtor v1.0.385 // indirect cloud.google.com/go/auth v0.13.0 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
filippo.io/edwards25519 v1.1.0 // 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.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity 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/internal v1.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.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.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.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/resourcegraph/armresourcegraph v0.9.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.29 // indirect
@@ -49,229 +52,281 @@ require (
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.2.2 // 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/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.62.712 // indirect github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/apernet/quic-go v0.45.2-0.20240702221538-ed74cfbe8b6e // indirect github.com/anytls/sing-anytls v0.0.9-0.20250508103614-8bc6dd599731 // indirect
github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.18 // indirect github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 // 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.5 // 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.9 // 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.9 // 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.0 // 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.11.2 // 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.11.11 // 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.38.3 // indirect github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.10 // indirect github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
github.com/aws/smithy-go v1.20.2 // indirect github.com/aws/smithy-go v1.22.1 // indirect
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect github.com/benbjohnson/clock v1.3.0 // indirect
github.com/bits-and-blooms/bitset v1.13.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.23.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/civo/civogo v0.3.11 // indirect github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/circl v1.3.9 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudflare/cloudflare-go v0.97.0 // indirect github.com/cloudflare/cloudflare-go v0.112.0 // indirect
github.com/coder/websocket v1.8.13 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // 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/davecgh/go-spew v1.1.1 // indirect github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect github.com/database64128/tfo-go/v2 v2.2.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // 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/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // 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.7.0 // indirect
github.com/exoscale/egoscale v0.102.3 // indirect github.com/exoscale/egoscale/v3 v3.1.7 // 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/felixge/httpsnoop v1.0.4 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gaissmai/bart v0.11.1 // 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.0.12 // indirect github.com/go-chi/chi/v5 v5.2.2 // 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.2 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.1 // indirect github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.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-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-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.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.8.1 // indirect github.com/goccy/go-json v0.10.4 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/gofrs/uuid/v5 v5.2.0 // indirect github.com/gofrs/flock v0.12.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/gofrs/uuid/v5 v5.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.7.0 // 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/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/google/s2a-go v0.1.7 // indirect github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/googleapis/gax-go/v2 v2.14.0 // indirect
github.com/gophercloud/gophercloud v1.12.0 // indirect github.com/gophercloud/gophercloud v1.14.1 // indirect
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.1.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.7 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // 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/illarion/gonotify/v2 v2.0.3 // 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-20250417080101-5f8cf70e8c5f // 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/jsimonetti/rtnetlink v1.4.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.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // 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/libdns/alidns v1.0.3 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/libdns/cloudflare v0.1.1 // indirect github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect
github.com/linode/linodego v1.28.0 // indirect github.com/libdns/libdns v1.1.0 // indirect
github.com/linode/linodego v1.44.0 // 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.4 // 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/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mholt/acmez v1.2.0 // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/miekg/dns v1.1.61 // indirect github.com/mdlayher/socket v0.5.1 // indirect
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
github.com/metacubex/utls v1.8.0 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.67 // 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/go-ps v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
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/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
github.com/nrdcg/desec v0.8.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.2.0 // indirect github.com/nrdcg/freemyip v0.3.0 // indirect
github.com/nrdcg/goinwx v0.10.0 // indirect github.com/nrdcg/goinwx v0.10.0 // indirect
github.com/nrdcg/mailinabox v0.2.0 // 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.3.0 // indirect github.com/nrdcg/porkbun v0.4.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.19.0 // indirect
github.com/ooni/go-libtor v1.1.8 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/oracle/oci-go-sdk/v65 v65.63.1 // indirect github.com/oracle/oci-go-sdk/v65 v65.81.1 // 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.5.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.0.6 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // 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.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/otp v1.4.0 // indirect github.com/pquerna/otp v1.4.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.45.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect
github.com/refraction-networking/utls v1.6.7 // indirect github.com/refraction-networking/utls v1.8.0 // indirect
github.com/regfish/regfish-dnsapi-go v0.1.1 // 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.10 // indirect
github.com/sacloud/go-http v0.1.8 // indirect github.com/sacloud/go-http v0.1.8 // indirect
github.com/sacloud/iaas-api-go v1.12.0 // indirect github.com/sacloud/iaas-api-go v1.14.0 // indirect
github.com/sacloud/packages-go v0.0.10 // indirect github.com/sacloud/packages-go v0.0.10 // indirect
github.com/safchain/ethtool v0.3.0 // 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/cors v1.2.1 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/quic-go v0.45.1-beta.2 // indirect github.com/sagernet/quic-go v0.52.0-beta.1 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing-mux v0.3.2 // indirect
github.com/sagernet/sing-dns v0.3.0-beta.10 // indirect github.com/sagernet/sing-quic v0.5.0-beta.3 // indirect
github.com/sagernet/sing-mux v0.2.0 // indirect github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
github.com/sagernet/sing-quic v0.2.0-beta.12 // indirect github.com/sagernet/sing-shadowsocks2 v0.2.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect github.com/sagernet/sing-tun v0.7.0-beta.1 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-vmess v0.2.6 // indirect
github.com/sagernet/sing-tun v0.4.0-beta.13.0.20240703164908-1f043289199d // indirect github.com/sagernet/smux v1.5.34-mod.2 // indirect
github.com/sagernet/sing-vmess v0.1.12 // indirect github.com/sagernet/tailscale v1.80.3-mod.5 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect
github.com/sagernet/utls v1.5.4 // indirect
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/selectel/domains-go v1.1.0 // indirect github.com/selectel/domains-go v1.1.0 // indirect
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect
github.com/shopspring/decimal v1.3.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.5 // indirect github.com/softlayer/softlayer-go v1.1.7 // 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/sony/gobreaker v0.5.0 // indirect
github.com/spf13/afero v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/transip/gotransip/v6 v6.23.0 // indirect github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // 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/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 // indirect github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 // indirect
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // 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.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect github.com/vishvananda/netlink v1.3.1 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.5 // indirect
github.com/vultr/govultr/v2 v2.17.2 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d // indirect github.com/volcengine/volc-sdk-golang v1.0.189 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e // indirect github.com/vultr/govultr/v3 v3.9.1 // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 // indirect github.com/wyx2685/sing-vmess v0.0.0-20250723121437-95d5ab59ff92 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/x448/float16 v0.8.4 // indirect
go.opencensus.io v0.24.0 // indirect github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
go.opentelemetry.io/otel v1.24.0 // indirect github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect
go.uber.org/mock v0.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/mock v0.5.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.3.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.42.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/term v0.33.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.34.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-20231211153847-12269c276173 // indirect
google.golang.org/api v0.172.0 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/api v0.214.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/grpc v1.65.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/grpc v1.74.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect gopkg.in/ns1/ns1-go.v2 v2.13.0 // 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-20231202080848-1f7806d17489 // indirect gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.4.1 // indirect
) )
// replace github.com/sagernet/sing-box v1.10.0-alpha.22 => /root/sing-box_mod //replace github.com/sagernet/sing-box v1.12.0 => ../sing-box_mod
replace github.com/sagernet/sing-box v1.10.0-alpha.23 => github.com/wyx2685/sing-box_mod v1.10.0-alpha.23
replace github.com/sagernet/sing-box v1.12.0 => github.com/wyx2685/sing-box_mod v1.12.0

1190
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
package limiter
import log "github.com/sirupsen/logrus"
func ClearOnlineIP() error {
log.WithField("Type", "Limiter").
Debug("Clear online ip...")
limitLock.RLock()
for _, l := range limiter {
l.ConnLimiter.ClearOnlineIP()
}
limitLock.RUnlock()
log.WithField("Type", "Limiter").
Debug("Clear online ip done")
return nil
}

View File

@@ -1,165 +0,0 @@
package limiter
import (
"sync"
"time"
)
type ConnLimiter struct {
realtime bool
ipLimit int
connLimit int
count sync.Map // map[string]int
ip sync.Map // map[string]map[string]int
}
func NewConnLimiter(conn int, ip int, realtime bool) *ConnLimiter {
return &ConnLimiter{
realtime: realtime,
connLimit: conn,
ipLimit: ip,
count: sync.Map{},
ip: sync.Map{},
}
}
func (c *ConnLimiter) AddConnCount(user string, ip string, isTcp bool) (limit bool) {
if c.connLimit != 0 {
if v, ok := c.count.Load(user); ok {
if v.(int) >= c.connLimit {
// over connection limit
return true
} else if isTcp {
// tcp protocol
// connection count add
c.count.Store(user, v.(int)+1)
}
} else if isTcp {
// tcp protocol
// store connection count
c.count.Store(user, 1)
}
}
if c.ipLimit == 0 {
return false
}
// first user map
ipMap := new(sync.Map)
if c.realtime {
if isTcp {
ipMap.Store(ip, 2)
} else {
ipMap.Store(ip, 1)
}
} else {
ipMap.Store(ip, time.Now())
}
// check user online ip
if v, ok := c.ip.LoadOrStore(user, ipMap); ok {
// have user
ips := v.(*sync.Map)
cn := 0
if online, ok := ips.Load(ip); ok {
// online ip
if c.realtime {
if isTcp {
// tcp count add
ips.Store(ip, online.(int)+2)
}
} else {
// update connect time for not realtime
ips.Store(ip, time.Now())
}
} else {
// not online ip
ips.Range(func(_, _ interface{}) bool {
cn++
if cn >= c.ipLimit {
limit = true
return false
}
return true
})
if limit {
// over ip limit
return
}
if c.realtime {
if isTcp {
ips.Store(ip, 2)
} else {
ips.Store(ip, 1)
}
} else {
ips.Store(ip, time.Now())
}
}
}
return
}
// DelConnCount Delete tcp connection count, no tcp do not use
func (c *ConnLimiter) DelConnCount(user string, ip string) {
if !c.realtime {
return
}
if c.connLimit != 0 {
if v, ok := c.count.Load(user); ok {
if v.(int) == 1 {
c.count.Delete(user)
} else {
c.count.Store(user, v.(int)-1)
}
}
}
if c.ipLimit == 0 {
return
}
if i, ok := c.ip.Load(user); ok {
is := i.(*sync.Map)
if i, ok := is.Load(ip); ok {
if i.(int) == 2 {
is.Delete(ip)
} else {
is.Store(user, i.(int)-2)
}
notDel := false
c.ip.Range(func(_, _ any) bool {
notDel = true
return false
})
if !notDel {
c.ip.Delete(user)
}
}
}
}
// ClearOnlineIP Clear udp,icmp and other packet protocol online ip
func (c *ConnLimiter) ClearOnlineIP() {
c.ip.Range(func(u, v any) bool {
userIp := v.(*sync.Map)
notDel := false
userIp.Range(func(ip, v any) bool {
notDel = true
if _, ok := v.(int); ok {
if v.(int) == 1 {
// clear packet ip for realtime
userIp.Delete(ip)
}
return true
} else {
// clear ip for not realtime
if v.(time.Time).Before(time.Now().Add(time.Minute)) {
// 1 minute no active
userIp.Delete(ip)
}
}
return true
})
if !notDel {
c.ip.Delete(u)
}
return true
})
}

View File

@@ -1,56 +0,0 @@
package limiter
import (
"sync"
"testing"
"time"
)
var c *ConnLimiter
func init() {
c = NewConnLimiter(1, 1, true)
}
func TestConnLimiter_AddConnCount(t *testing.T) {
t.Log(c.AddConnCount("1", "1", true))
t.Log(c.AddConnCount("1", "2", true))
}
func TestConnLimiter_DelConnCount(t *testing.T) {
t.Log(c.AddConnCount("1", "1", true))
t.Log(c.AddConnCount("1", "2", true))
c.DelConnCount("1", "1")
t.Log(c.AddConnCount("1", "2", true))
}
func TestConnLimiter_ClearOnlineIP(t *testing.T) {
t.Log(c.AddConnCount("1", "1", false))
t.Log(c.AddConnCount("1", "2", false))
c.ClearOnlineIP()
t.Log(c.AddConnCount("1", "2", true))
c.DelConnCount("1", "2")
t.Log(c.AddConnCount("1", "1", false))
// not realtime
c.realtime = false
t.Log(c.AddConnCount("3", "2", true))
c.ClearOnlineIP()
t.Log(c.ip.Load("3"))
time.Sleep(time.Minute)
c.ClearOnlineIP()
t.Log(c.ip.Load("3"))
}
func BenchmarkConnLimiter(b *testing.B) {
wg := sync.WaitGroup{}
for i := 0; i < b.N; i++ {
wg.Add(1)
go func() {
c.AddConnCount("1", "2", true)
c.DelConnCount("1", "2")
wg.Done()
}()
}
wg.Wait()
}

View File

@@ -11,8 +11,6 @@ import (
"github.com/InazumaV/V2bX/common/format" "github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"
"github.com/juju/ratelimit" "github.com/juju/ratelimit"
log "github.com/sirupsen/logrus"
"github.com/xtls/xray-core/common/task"
) )
var limitLock sync.RWMutex var limitLock sync.RWMutex
@@ -20,27 +18,18 @@ var limiter map[string]*Limiter
func Init() { func Init() {
limiter = map[string]*Limiter{} limiter = map[string]*Limiter{}
c := task.Periodic{
Interval: time.Minute * 2,
Execute: ClearOnlineIP,
}
go func() {
log.WithField("Type", "Limiter").
Debug("ClearOnlineIP started")
time.Sleep(time.Minute * 2)
_ = c.Start()
}()
} }
type Limiter struct { type Limiter struct {
DomainRules []*regexp.Regexp DomainRules []*regexp.Regexp
ProtocolRules []string ProtocolRules []string
SpeedLimit int SpeedLimit int
UserOnlineIP *sync.Map // Key: Name, value: {Key: Ip, value: Uid} UserOnlineIP *sync.Map // Key: TagUUID, value: {Key: Ip, value: Uid}
UUIDtoUID map[string]int // Key: UUID, value: UID OldUserOnline *sync.Map // Key: Ip, value: Uid
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo UUIDtoUID map[string]int // Key: UUID, value: Uid
ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter UserLimitInfo *sync.Map // Key: TagUUID value: UserLimitInfo
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket SpeedLimiter *sync.Map // key: TagUUID, value: *ratelimit.Bucket
AliveList map[int]int // Key: Uid, value: alive_ip
} }
type UserLimitInfo struct { type UserLimitInfo struct {
@@ -52,13 +41,14 @@ type UserLimitInfo struct {
OverLimit bool OverLimit bool
} }
func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limiter { func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo, aliveList map[int]int) *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),
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 {
@@ -100,7 +90,10 @@ 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))
l.SpeedLimiter.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{ userLimit := &UserLimitInfo{
@@ -134,10 +127,6 @@ func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool, noSSUDP bool
// check if ipv4 mapped ipv6 // check if ipv4 mapped ipv6
ip = strings.TrimPrefix(ip, "::ffff:") ip = strings.TrimPrefix(ip, "::ffff:")
// ip and conn limiter
if l.ConnLimiter.AddConnCount(taguuid, ip, isTcp) {
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
@@ -158,23 +147,38 @@ func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool, noSSUDP bool
} else { } else {
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit) userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
} }
} else {
return nil, true
} }
if noSSUDP { if noSSUDP {
// Store online user for device limit // Store online user for device limit
ipMap := new(sync.Map) newipMap := new(sync.Map)
ipMap.Store(ip, uid) newipMap.Store(ip, uid)
aliveIp := l.AliveList[uid]
// If any device is online // If any device is online
if v, ok := l.UserOnlineIP.LoadOrStore(taguuid, ipMap); ok { if v, loaded := l.UserOnlineIP.LoadOrStore(taguuid, newipMap); loaded {
ipMap := v.(*sync.Map) oldipMap := v.(*sync.Map)
// If this is a new ip // If this is a new ip
if _, ok := ipMap.LoadOrStore(ip, uid); !ok { if _, loaded := oldipMap.LoadOrStore(ip, uid); !loaded {
counter := 0 if v, loaded := l.OldUserOnline.Load(ip); loaded {
ipMap.Range(func(key, value interface{}) bool { if v.(int) == uid {
counter++ l.OldUserOnline.Delete(ip)
return true }
}) } else if deviceLimit > 0 {
if counter > deviceLimit && deviceLimit > 0 { if deviceLimit <= aliveIp {
ipMap.Delete(ip) oldipMap.Delete(ip)
return nil, 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 return nil, true
} }
} }
@@ -183,7 +187,7 @@ func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool, noSSUDP bool
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, 5*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(taguuid, Bucket); ok {
return v.(*ratelimit.Bucket), false return v.(*ratelimit.Bucket), false
} else { } else {
@@ -197,17 +201,18 @@ 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.OldUserOnline = new(sync.Map)
l.UserOnlineIP.Range(func(key, value interface{}) bool { l.UserOnlineIP.Range(func(key, value interface{}) bool {
email := key.(string) taguuid := 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(email) // Reset online device l.UserOnlineIP.Delete(taguuid) // Reset online device
return true return true
}) })
@@ -218,23 +223,3 @@ 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

@@ -19,6 +19,7 @@ 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
@@ -54,6 +55,10 @@ 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 {
@@ -61,7 +66,7 @@ func (c *Controller) Start() error {
} }
// add limiter // add limiter
l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList) l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList, c.aliveMap)
// 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)

View File

@@ -13,11 +13,12 @@ import (
"strings" "strings"
"time" "time"
"encoding/json"
"github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/http01" "github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/providers/dns" "github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/registration" "github.com/go-acme/lego/v4/registration"
"github.com/goccy/go-json"
"github.com/InazumaV/V2bX/common/file" "github.com/InazumaV/V2bX/common/file"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"

View File

@@ -68,6 +68,15 @@ 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
@@ -82,7 +91,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"tag": c.tag, "tag": c.tag,
"err": err, "err": err,
}).Error("Delete node failed") }).Panic("Delete node failed")
return nil return nil
} }
@@ -92,9 +101,13 @@ 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) l := limiter.AddLimiter(c.tag, &c.LimitConfig, c.userList, newA)
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 {
@@ -122,7 +135,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"tag": c.tag, "tag": c.tag,
"err": err, "err": err,
}).Error("Add node failed") }).Panic("Add node failed")
return nil return nil
} }
_, err = c.server.AddUsers(&vCore.AddUsersParams{ _, err = c.server.AddUsers(&vCore.AddUsersParams{
@@ -154,7 +167,10 @@ 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
@@ -162,7 +178,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) err = c.server.DelUsers(deleted, c.tag, c.info)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"tag": c.tag, "tag": c.tag,

View File

@@ -8,24 +8,7 @@ import (
) )
func (c *Controller) reportUserTrafficTask() (err error) { func (c *Controller) reportUserTrafficTask() (err error) {
// Get User traffic userTraffic, err := c.server.GetUserTrafficSlice(c.tag, true)
userTraffic := make([]panel.UserTraffic, 0)
for i := range c.userList {
up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true)
if up > 0 || down > 0 {
if c.LimitConfig.EnableDynamicSpeedLimit {
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{
UID: (c.userList)[i].Id,
Upload: up,
Download: down})
}
}
if len(userTraffic) > 0 { if len(userTraffic) > 0 {
err = c.apiClient.ReportUserTraffic(userTraffic) err = c.apiClient.ReportUserTraffic(userTraffic)
if err != nil { if err != nil {
@@ -55,18 +38,12 @@ 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], onlineuser.IP)
if _, ok := reportOnline[onlineuser.UID]; ok {
reportOnline[onlineuser.UID]++
} else {
reportOnline[onlineuser.UID] = 1
}
} }
if err = c.apiClient.ReportNodeOnlineUsers(&data, &reportOnline); err != nil { if err = c.apiClient.ReportNodeOnlineUsers(&data); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"tag": c.tag, "tag": c.tag,
"err": err, "err": err,