mirror of
https://github.com/wyx2685/V2bX.git
synced 2026-02-04 04:30:08 +00:00
Compare commits
83 Commits
v0.0.4-202
...
v0.2.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb51d3e13c | ||
|
|
dadeb6304b | ||
|
|
4c7b9f5eb9 | ||
|
|
63d88843b6 | ||
|
|
8d225f811b | ||
|
|
f6b587b275 | ||
|
|
d9b3d24465 | ||
|
|
fc284b3b9f | ||
|
|
eb92c4912d | ||
|
|
a68378670f | ||
|
|
d200a3336e | ||
|
|
6a95d576f1 | ||
|
|
dbe529bd48 | ||
|
|
8254e49297 | ||
|
|
9e8ad2619a | ||
|
|
d5fff6c433 | ||
|
|
fe896a61a3 | ||
|
|
a63198c20b | ||
|
|
180fb14dd1 | ||
|
|
44db7512d7 | ||
|
|
95263fea99 | ||
|
|
0b155bbf89 | ||
|
|
1c8c17b067 | ||
|
|
61606646b9 | ||
|
|
e2904ad126 | ||
|
|
903aef1fb5 | ||
|
|
2c43704090 | ||
|
|
d71df3a0df | ||
|
|
96baa0a99c | ||
|
|
e502624fe4 | ||
|
|
484faaf0c3 | ||
|
|
ec5dcc3c8a | ||
|
|
2f1362067b | ||
|
|
c755e9800b | ||
|
|
29e0d7e56e | ||
|
|
08ebbed9fb | ||
|
|
792841d073 | ||
|
|
981e59b836 | ||
|
|
7dbe5fda85 | ||
|
|
4f9ccdf8db | ||
|
|
c2d5861d7d | ||
|
|
0e29c19f0e | ||
|
|
a1c40bb1c8 | ||
|
|
a0de94efff | ||
|
|
29928a1135 | ||
|
|
ab1ca837de | ||
|
|
1f61446fa9 | ||
|
|
c0325227db | ||
|
|
ba3036a7ac | ||
|
|
f99e2b4489 | ||
|
|
0af952be10 | ||
|
|
ad5971f164 | ||
|
|
f7d5d891c3 | ||
|
|
6936a76724 | ||
|
|
7184e49650 | ||
|
|
ea0b7d8f40 | ||
|
|
12fbcb1460 | ||
|
|
c6d48e1edf | ||
|
|
8d7168c6a4 | ||
|
|
173c48a76f | ||
|
|
130e94cf45 | ||
|
|
89ddfff060 | ||
|
|
07d49293d8 | ||
|
|
9e8f87740e | ||
|
|
29a99985c8 | ||
|
|
248ff3764f | ||
|
|
3dfeba7e68 | ||
|
|
8eb623b3f0 | ||
|
|
cdcbddd464 | ||
|
|
e81d47321b | ||
|
|
4d82eff518 | ||
|
|
b96545649b | ||
|
|
8757b955a6 | ||
|
|
33d9ab4b0a | ||
|
|
a7637d436f | ||
|
|
4dda65a636 | ||
|
|
6c725b424f | ||
|
|
a052a1f1e8 | ||
|
|
85ad40d098 | ||
|
|
a85352c402 | ||
|
|
206af0216c | ||
|
|
ed5edda28a | ||
|
|
995e606694 |
22
.github/ISSUE_TEMPLATE/bug-report.md
vendored
22
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,27 +1,25 @@
|
||||
---
|
||||
name: "Bug 反馈"
|
||||
about: 创建一个报告以帮助我们修复并改进XrayR
|
||||
about: 创建一个报告以帮助我们修复并改进V2bX
|
||||
title: ''
|
||||
labels: awaiting reply, bug
|
||||
labels:
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**描述该错误**
|
||||
简单地描述一下这个bug是什么
|
||||
|
||||
**复现**
|
||||
复现该bug的步骤
|
||||
|
||||
**环境和版本**
|
||||
- 系统 [例如:Debian 11]
|
||||
- 架构 [例如:AMD64]
|
||||
- 面板 [例如:V2board]
|
||||
- 协议 [例如:vmess]
|
||||
- 版本 [例如:0.8.2.2]
|
||||
- 部署方式 [例如:一键脚本]
|
||||
|
||||
**复现**
|
||||
请自行复现,并贴出详细步骤操作过程
|
||||
|
||||
|
||||
|
||||
**日志和错误**
|
||||
请使用`xrayr log`查看并添加日志,以帮助解释你的问题
|
||||
请使用`v2bx log`查看并添加日志,没有日志的issue不会得到答复并且会被直接关闭
|
||||
|
||||
|
||||
|
||||
**额外的内容**
|
||||
在这里添加关于问题的任何其他内容
|
||||
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: "功能建议"
|
||||
about: 给XrayR提出建议,让我们做得更好
|
||||
title: ''
|
||||
labels: awaiting reply, feature-request
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**描述您想要的功能**
|
||||
|
||||
清晰简洁的功能描述。
|
||||
|
||||
**描述您考虑过的替代方案**
|
||||
|
||||
是否有任何替代方案可以解决这个问题?
|
||||
|
||||
**附加上下文**
|
||||
|
||||
在此处添加有关功能请求的任何其他上下文或截图。
|
||||
59
.github/workflows/Publish Docker image.yml
vendored
59
.github/workflows/Publish Docker image.yml
vendored
@@ -1,19 +1,13 @@
|
||||
name: Publish Docker image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- dev_new
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
tags:
|
||||
- 'v*'
|
||||
release:
|
||||
types: [published]
|
||||
pull_request:
|
||||
branches:
|
||||
- 'dev_new'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/v2bx
|
||||
@@ -28,40 +22,51 @@ jobs:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
echo "${digest#sha256:}" > "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
@@ -72,31 +77,37 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: digests
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
ls -al
|
||||
echo docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
|
||||
cd /tmp/digests
|
||||
tags=$(echo '${{ steps.meta.outputs.json }}' | jq -cr '.tags | map("-t " + .) | join(" ")')
|
||||
images=$(printf "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s " $(find . -type f -exec cat {} \;))
|
||||
echo "Creating manifest with tags: $tags"
|
||||
echo "Using images: $images"
|
||||
docker buildx imagetools create $tags $images
|
||||
|
||||
- name: Inspect image
|
||||
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 }}
|
||||
19
.github/workflows/release.yml
vendored
19
.github/workflows/release.yml
vendored
@@ -10,14 +10,14 @@ on:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
- ".github/workflows/release.yml"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
- ".github/workflows/release.yml"
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Show workflow information
|
||||
id: get_filename
|
||||
run: |
|
||||
@@ -107,12 +107,13 @@ jobs:
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22.0'
|
||||
go-version: '1.24.5'
|
||||
|
||||
- name: Get project dependencies
|
||||
run: go mod download
|
||||
run: |
|
||||
go mod download
|
||||
- name: Get release version
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
run: |
|
||||
@@ -125,13 +126,13 @@ jobs:
|
||||
run: |
|
||||
echo "version: $version"
|
||||
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="
|
||||
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
|
||||
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
|
||||
run: |
|
||||
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="
|
||||
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
|
||||
if: matrix.goos == 'windows'
|
||||
run: |
|
||||
@@ -167,7 +168,7 @@ jobs:
|
||||
run: |
|
||||
mv build_assets V2bX-$ASSET_NAME
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}
|
||||
path: |
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "sing-box_mod"]
|
||||
path = sing-box_mod
|
||||
url = https://github.com/wyx2685/sing-box_mod.git
|
||||
@@ -1,10 +1,10 @@
|
||||
# Build go
|
||||
FROM golang:1.22.0-alpine AS builder
|
||||
FROM golang:1.24.5-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
ENV CGO_ENABLED=0
|
||||
RUN go mod download
|
||||
RUN go build -v -o V2bX -tags "sing xray hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme"
|
||||
RUN go build -v -o V2bX -tags "sing xray hysteria2 with_quic with_grpc with_utls with_wireguard with_acme with_gvisor"
|
||||
|
||||
# Release
|
||||
FROM alpine
|
||||
|
||||
21
README.md
21
README.md
@@ -1,16 +1,17 @@
|
||||
# V2bX
|
||||
|
||||
[](https://t.me/YuzukiProjects)
|
||||
[](https://t.me/unofficialV2board)
|
||||
[](https://t.me/YuzukiProjects)
|
||||
|
||||
A V2board node server based on multi core, modified from XrayR.
|
||||
一个基于多种内核的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等新特性。
|
||||
* 支持单实例对接多节点,无需重复启动。
|
||||
* 支持限制在线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证书 | √ | √ | √ | √ |
|
||||
@@ -32,7 +33,7 @@ A V2board node server based on multi core, modified from XrayR.
|
||||
| 自定义DNS | √ | √ | √ | √ |
|
||||
| 在线IP数限制 | √ | √ | √ | √ |
|
||||
| 连接数限制 | √ | √ | √ | √ |
|
||||
| 跨节点IP数限制 | | | | |
|
||||
| 跨节点IP数限制 |√ |√ |√ |√ |
|
||||
| 按照用户限速 | √ | √ | √ | √ |
|
||||
| 动态限速(未测试) | √ | √ | √ | √ |
|
||||
|
||||
@@ -52,17 +53,17 @@ wget -N https://raw.githubusercontent.com/wyx2685/V2bX-script/master/install.sh
|
||||
|
||||
### 手动安装
|
||||
|
||||
[手动安装教程(过时待更新)](https://yuzuki-1.gitbook.io/v2bx-doc/xrayr-xia-zai-he-an-zhuang/install/manual)
|
||||
[手动安装教程](https://v2bx.v-50.me/v2bx/v2bx-xia-zai-he-an-zhuang/install/manual)
|
||||
|
||||
## 构建
|
||||
``` bash
|
||||
# 通过-tags选项指定要编译的内核, 可选 xray, sing
|
||||
go build -o V2bX -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD} -tags "xray sing"
|
||||
# 通过-tags选项指定要编译的内核, 可选 xray, sing, hysteria2
|
||||
go build -v -o ./V2bX -tags "xray sing hysteria2 with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-s -w -buildid="
|
||||
```
|
||||
|
||||
## 配置文件及详细使用教程
|
||||
|
||||
[详细使用教程](https://yuzuki-1.gitbook.io/v2bx-doc/)
|
||||
[详细使用教程](https://v2bx.v-50.me/)
|
||||
|
||||
## 免责声明
|
||||
|
||||
@@ -82,4 +83,4 @@ go build -o V2bX -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all
|
||||
|
||||
## Stars 增长记录
|
||||
|
||||
[](https://starchart.cc/InazumaV/V2bX)
|
||||
[](https://starchart.cc/wyx2685/V2bX)
|
||||
|
||||
@@ -2,7 +2,6 @@ package panel
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/InazumaV/V2bX/common/crypt"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
@@ -34,6 +32,8 @@ type NodeInfo struct {
|
||||
VAllss *VAllssNode
|
||||
Shadowsocks *ShadowsocksNode
|
||||
Trojan *TrojanNode
|
||||
Tuic *TuicNode
|
||||
AnyTls *AnyTlsNode
|
||||
Hysteria *HysteriaNode
|
||||
Hysteria2 *Hysteria2Node
|
||||
Common *CommonNode
|
||||
@@ -80,7 +80,7 @@ type TlsSettings struct {
|
||||
ServerPort string `json:"server_port"`
|
||||
ShortId string `json:"short_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
Xver uint8 `json:"xver,string"`
|
||||
Xver uint64 `json:"xver,string"`
|
||||
}
|
||||
|
||||
type RealityConfig struct {
|
||||
@@ -102,6 +102,17 @@ type TrojanNode struct {
|
||||
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 {
|
||||
CommonNode
|
||||
UpMbps int `json:"up_mbps"`
|
||||
@@ -111,10 +122,11 @@ type HysteriaNode struct {
|
||||
|
||||
type Hysteria2Node struct {
|
||||
CommonNode
|
||||
UpMbps int `json:"up_mbps"`
|
||||
DownMbps int `json:"down_mbps"`
|
||||
ObfsType string `json:"obfs"`
|
||||
ObfsPassword string `json:"obfs-password"`
|
||||
Ignore_Client_Bandwidth bool `json:"ignore_client_bandwidth"`
|
||||
UpMbps int `json:"up_mbps"`
|
||||
DownMbps int `json:"down_mbps"`
|
||||
ObfsType string `json:"obfs"`
|
||||
ObfsPassword string `json:"obfs-password"`
|
||||
}
|
||||
|
||||
type RawDNS struct {
|
||||
@@ -186,18 +198,6 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
|
||||
cm = &rsp.CommonNode
|
||||
node.VAllss = rsp
|
||||
node.Security = node.VAllss.Tls
|
||||
if len(rsp.NetworkSettings) > 0 {
|
||||
err = json.Unmarshal(rsp.NetworkSettings, &rsp.RealityConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode reality config error: %s", err)
|
||||
}
|
||||
}
|
||||
if node.Security == Reality {
|
||||
if rsp.TlsSettings.PrivateKey == "" {
|
||||
key := crypt.GenX25519Private([]byte("vless" + c.Token))
|
||||
rsp.TlsSettings.PrivateKey = base64.RawURLEncoding.EncodeToString(key)
|
||||
}
|
||||
}
|
||||
case "shadowsocks":
|
||||
rsp := &ShadowsocksNode{}
|
||||
err = json.Unmarshal(r.Body(), rsp)
|
||||
@@ -216,6 +216,24 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
|
||||
cm = &rsp.CommonNode
|
||||
node.Trojan = rsp
|
||||
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":
|
||||
rsp := &HysteriaNode{}
|
||||
err = json.Unmarshal(r.Body(), rsp)
|
||||
|
||||
@@ -24,7 +24,8 @@ type Client struct {
|
||||
nodeEtag string
|
||||
userEtag string
|
||||
responseBodyHash string
|
||||
LastReportOnline map[int]int
|
||||
UserList *UserListBody
|
||||
AliveMap *AliveMap
|
||||
}
|
||||
|
||||
func New(c *conf.ApiConfig) (*Client, error) {
|
||||
@@ -55,6 +56,8 @@ func New(c *conf.ApiConfig) (*Client, error) {
|
||||
"shadowsocks",
|
||||
"hysteria",
|
||||
"hysteria2",
|
||||
"tuic",
|
||||
"anytls",
|
||||
"vless":
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported Node type: %s", c.NodeType)
|
||||
@@ -71,5 +74,7 @@ func New(c *conf.ApiConfig) (*Client, error) {
|
||||
APIHost: c.APIHost,
|
||||
NodeType: c.NodeType,
|
||||
NodeId: c.NodeID,
|
||||
UserList: &UserListBody{},
|
||||
AliveMap: &AliveMap{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package panel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
type OnlineUser struct {
|
||||
@@ -12,73 +15,82 @@ type OnlineUser struct {
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
Id int `json:"id"`
|
||||
Uuid string `json:"uuid"`
|
||||
SpeedLimit int `json:"speed_limit"`
|
||||
DeviceLimit int `json:"device_limit"`
|
||||
AliveIp int `json:"alive_ip"`
|
||||
Id int `json:"id" msgpack:"id"`
|
||||
Uuid string `json:"uuid" msgpack:"uuid"`
|
||||
SpeedLimit int `json:"speed_limit" msgpack:"speed_limit"`
|
||||
DeviceLimit int `json:"device_limit" msgpack:"device_limit"`
|
||||
}
|
||||
|
||||
type UserListBody struct {
|
||||
//Msg string `json:"msg"`
|
||||
Users []UserInfo `json:"users"`
|
||||
Users []UserInfo `json:"users" msgpack:"users"`
|
||||
}
|
||||
|
||||
// GetUserList will pull user form sspanel
|
||||
func (c *Client) GetUserList() (UserList []UserInfo, err error) {
|
||||
type AliveMap struct {
|
||||
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"
|
||||
r, err := c.client.R().
|
||||
SetHeader("If-None-Match", c.userEtag).
|
||||
ForceContentType("application/json").
|
||||
SetHeader("X-Response-Format", "msgpack").
|
||||
SetDoNotParseResponse(true).
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r != nil {
|
||||
defer func() {
|
||||
if r.RawBody() != nil {
|
||||
r.RawBody().Close()
|
||||
}
|
||||
}()
|
||||
if r.StatusCode() == 304 {
|
||||
return nil, nil
|
||||
userlist := &UserListBody{}
|
||||
if strings.Contains(r.Header().Get("Content-Type"), "application/x-msgpack") {
|
||||
decoder := msgpack.NewDecoder(r.RawResponse.Body)
|
||||
if err := decoder.Decode(userlist); err != nil {
|
||||
return nil, fmt.Errorf("decode user list error: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("received nil response")
|
||||
}
|
||||
var userList *UserListBody
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read body error: %s", err)
|
||||
}
|
||||
if err := json.Unmarshal(r.Body(), &userList); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal userlist error: %s", err)
|
||||
bodyBytes, err := io.ReadAll(r.RawResponse.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response body error: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(bodyBytes, userlist); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal user list error: %w", err)
|
||||
}
|
||||
}
|
||||
c.userEtag = r.Header().Get("ETag")
|
||||
return userlist.Users, nil
|
||||
}
|
||||
|
||||
var userinfos []UserInfo
|
||||
var localDeviceLimit int = 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.
|
||||
localDeviceLimit = user.DeviceLimit - user.AliveIp + lastOnline
|
||||
if localDeviceLimit > 0 {
|
||||
|
||||
} else if lastOnline > 0 {
|
||||
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
userinfos = append(userinfos, user)
|
||||
// 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 {
|
||||
@@ -105,8 +117,7 @@ func (c *Client) ReportUserTraffic(userTraffic []UserTraffic) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) ReportNodeOnlineUsers(data *map[int][]string, reportOnline *map[int]int) error {
|
||||
c.LastReportOnline = *reportOnline
|
||||
func (c *Client) ReportNodeOnlineUsers(data *map[int][]string) error {
|
||||
const path = "/api/v1/server/UniProxy/alive"
|
||||
r, err := c.client.R().
|
||||
SetBody(data).
|
||||
|
||||
@@ -6,8 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type TrafficCounter struct {
|
||||
counters map[string]*TrafficStorage
|
||||
lock sync.RWMutex
|
||||
counters sync.Map
|
||||
}
|
||||
|
||||
type TrafficStorage struct {
|
||||
@@ -16,60 +15,52 @@ type TrafficStorage struct {
|
||||
}
|
||||
|
||||
func NewTrafficCounter() *TrafficCounter {
|
||||
return &TrafficCounter{
|
||||
counters: map[string]*TrafficStorage{},
|
||||
}
|
||||
return &TrafficCounter{}
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) GetCounter(id string) *TrafficStorage {
|
||||
c.lock.RLock()
|
||||
cts, ok := c.counters[id]
|
||||
c.lock.RUnlock()
|
||||
if !ok {
|
||||
cts = &TrafficStorage{}
|
||||
c.counters[id] = cts
|
||||
if cts, ok := c.counters.Load(id); ok {
|
||||
return cts.(*TrafficStorage)
|
||||
}
|
||||
return cts
|
||||
newStorage := &TrafficStorage{}
|
||||
if cts, loaded := c.counters.LoadOrStore(id, newStorage); loaded {
|
||||
return cts.(*TrafficStorage)
|
||||
}
|
||||
return newStorage
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) GetUpCount(id string) int64 {
|
||||
c.lock.RLock()
|
||||
cts, ok := c.counters[id]
|
||||
c.lock.RUnlock()
|
||||
if ok {
|
||||
return cts.UpCounter.Load()
|
||||
if cts, ok := c.counters.Load(id); ok {
|
||||
return cts.(*TrafficStorage).UpCounter.Load()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) GetDownCount(id string) int64 {
|
||||
c.lock.RLock()
|
||||
cts, ok := c.counters[id]
|
||||
c.lock.RUnlock()
|
||||
if ok {
|
||||
return cts.DownCounter.Load()
|
||||
if cts, ok := c.counters.Load(id); ok {
|
||||
return cts.(*TrafficStorage).DownCounter.Load()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return len(c.counters)
|
||||
length := 0
|
||||
c.counters.Range(func(_, _ interface{}) bool {
|
||||
length++
|
||||
return true
|
||||
})
|
||||
return length
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) Reset(id string) {
|
||||
c.lock.RLock()
|
||||
cts := c.GetCounter(id)
|
||||
c.lock.RUnlock()
|
||||
cts.UpCounter.Store(0)
|
||||
cts.DownCounter.Store(0)
|
||||
if cts, ok := c.counters.Load(id); ok {
|
||||
cts.(*TrafficStorage).UpCounter.Store(0)
|
||||
cts.(*TrafficStorage).DownCounter.Store(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) Delete(id string) {
|
||||
c.lock.Lock()
|
||||
delete(c.counters, id)
|
||||
c.lock.Unlock()
|
||||
c.counters.Delete(id)
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) Rx(id string, n int) {
|
||||
@@ -81,11 +72,3 @@ func (c *TrafficCounter) Tx(id string, n int) {
|
||||
cts := c.GetCounter(id)
|
||||
cts.UpCounter.Add(int64(n))
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) IncConn(auth string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *TrafficCounter) DecConn(auth string) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,9 +4,6 @@ import (
|
||||
"net"
|
||||
|
||||
"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 {
|
||||
@@ -31,6 +28,7 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
/*
|
||||
type PacketConnCounter struct {
|
||||
network.PacketConn
|
||||
limiter *ratelimit.Bucket
|
||||
@@ -47,10 +45,11 @@ func (p *PacketConnCounter) ReadPacket(buff *buf.Buffer) (destination M.Socksadd
|
||||
pLen := buff.Len()
|
||||
destination, err = p.PacketConn.ReadPacket(buff)
|
||||
p.limiter.Wait(int64(buff.Len() - pLen))
|
||||
return
|
||||
return destination, err
|
||||
}
|
||||
|
||||
func (p *PacketConnCounter) WritePacket(buff *buf.Buffer, destination M.Socksaddr) (err error) {
|
||||
p.limiter.Wait(int64(buff.Len()))
|
||||
return p.PacketConn.WritePacket(buff, destination)
|
||||
}
|
||||
*/
|
||||
|
||||
84
conf/hy.go
84
conf/hy.go
@@ -1,7 +1,5 @@
|
||||
package conf
|
||||
|
||||
import "time"
|
||||
|
||||
type Hysteria2Config struct {
|
||||
LogConfig Hysteria2LogConfig `json:"Log"`
|
||||
}
|
||||
@@ -17,85 +15,3 @@ func NewHysteria2Config() *Hysteria2Config {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Hysteria2Options struct {
|
||||
Hysteria2ConfigPath string `json:"Hysteria2ConfigPath"`
|
||||
QUICConfig QUICConfig `json:"QUICConfig"`
|
||||
Outbounds []Outbounds `json:"Outbounds"`
|
||||
IgnoreClientBandwidth bool `json:"IgnoreClientBandwidth"`
|
||||
DisableUDP bool `json:"DisableUDP"`
|
||||
UDPIdleTimeout time.Duration `json:"UDPIdleTimeout"`
|
||||
Masquerade serverConfigMasquerade `json:"Masquerade"`
|
||||
}
|
||||
|
||||
type QUICConfig struct {
|
||||
InitialStreamReceiveWindow uint64
|
||||
MaxStreamReceiveWindow uint64
|
||||
InitialConnectionReceiveWindow uint64
|
||||
MaxConnectionReceiveWindow uint64
|
||||
MaxIdleTimeout time.Duration
|
||||
MaxIncomingStreams int64
|
||||
DisablePathMTUDiscovery bool // The server may still override this to true on unsupported platforms.
|
||||
}
|
||||
|
||||
type ServerConfigOutboundDirect struct {
|
||||
Mode string `json:"mode"`
|
||||
BindIPv4 string `json:"bindIPv4"`
|
||||
BindIPv6 string `json:"bindIPv6"`
|
||||
BindDevice string `json:"bindDevice"`
|
||||
}
|
||||
|
||||
type ServerConfigOutboundSOCKS5 struct {
|
||||
Addr string `json:"addr"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ServerConfigOutboundHTTP struct {
|
||||
URL string `json:"url"`
|
||||
Insecure bool `json:"insecure"`
|
||||
}
|
||||
|
||||
type Outbounds struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Direct ServerConfigOutboundDirect `json:"direct"`
|
||||
SOCKS5 ServerConfigOutboundSOCKS5 `json:"socks5"`
|
||||
HTTP ServerConfigOutboundHTTP `json:"http"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeFile struct {
|
||||
Dir string `json:"dir"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeProxy struct {
|
||||
URL string `json:"url"`
|
||||
RewriteHost bool `json:"rewriteHost"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeString struct {
|
||||
Content string `json:"content"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
}
|
||||
|
||||
type serverConfigMasquerade struct {
|
||||
Type string `json:"type"`
|
||||
File serverConfigMasqueradeFile `json:"file"`
|
||||
Proxy serverConfigMasqueradeProxy `json:"proxy"`
|
||||
String serverConfigMasqueradeString `json:"string"`
|
||||
ListenHTTP string `json:"listenHTTP"`
|
||||
ListenHTTPS string `json:"listenHTTPS"`
|
||||
ForceHTTPS bool `json:"forceHTTPS"`
|
||||
}
|
||||
|
||||
func NewHysteria2Options() *Hysteria2Options {
|
||||
return &Hysteria2Options{
|
||||
QUICConfig: QUICConfig{},
|
||||
Outbounds: []Outbounds{},
|
||||
IgnoreClientBandwidth: false,
|
||||
DisableUDP: false,
|
||||
UDPIdleTimeout: time.Duration(time.Duration.Seconds(30)),
|
||||
Masquerade: serverConfigMasquerade{},
|
||||
}
|
||||
}
|
||||
|
||||
28
conf/node.go
28
conf/node.go
@@ -103,18 +103,18 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) (err error) {
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Name string `json:"Name"`
|
||||
Core string `json:"Core"`
|
||||
CoreName string `json:"CoreName"`
|
||||
ListenIP string `json:"ListenIP"`
|
||||
SendIP string `json:"SendIP"`
|
||||
DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"`
|
||||
LimitConfig LimitConfig `json:"LimitConfig"`
|
||||
RawOptions json.RawMessage `json:"RawOptions"`
|
||||
XrayOptions *XrayOptions `json:"XrayOptions"`
|
||||
SingOptions *SingOptions `json:"SingOptions"`
|
||||
Hysteria2Options *Hysteria2Options `json:"Hysteria2Options"`
|
||||
CertConfig *CertConfig `json:"CertConfig"`
|
||||
Name string `json:"Name"`
|
||||
Core string `json:"Core"`
|
||||
CoreName string `json:"CoreName"`
|
||||
ListenIP string `json:"ListenIP"`
|
||||
SendIP string `json:"SendIP"`
|
||||
DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"`
|
||||
LimitConfig LimitConfig `json:"LimitConfig"`
|
||||
RawOptions json.RawMessage `json:"RawOptions"`
|
||||
XrayOptions *XrayOptions `json:"XrayOptions"`
|
||||
SingOptions *SingOptions `json:"SingOptions"`
|
||||
Hysteria2ConfigPath string `json:"Hysteria2ConfigPath"`
|
||||
CertConfig *CertConfig `json:"CertConfig"`
|
||||
}
|
||||
|
||||
func (o *Options) UnmarshalJSON(data []byte) error {
|
||||
@@ -131,8 +131,8 @@ func (o *Options) UnmarshalJSON(data []byte) error {
|
||||
o.SingOptions = NewSingOptions()
|
||||
return json.Unmarshal(data, o.SingOptions)
|
||||
case "hysteria2":
|
||||
o.Hysteria2Options = NewHysteria2Options()
|
||||
return json.Unmarshal(data, o.Hysteria2Options)
|
||||
o.RawOptions = data
|
||||
return nil
|
||||
default:
|
||||
o.Core = ""
|
||||
o.RawOptions = data
|
||||
|
||||
26
conf/sing.go
26
conf/sing.go
@@ -5,11 +5,9 @@ import (
|
||||
)
|
||||
|
||||
type SingConfig struct {
|
||||
LogConfig SingLogConfig `json:"Log"`
|
||||
NtpConfig SingNtpConfig `json:"NTP"`
|
||||
EnableConnClear bool `json:"EnableConnClear"`
|
||||
DnsConfigPath string `json:"DnsConfigPath"`
|
||||
OriginalPath string `json:"OriginalPath"`
|
||||
LogConfig SingLogConfig `json:"Log"`
|
||||
NtpConfig SingNtpConfig `json:"NTP"`
|
||||
OriginalPath string `json:"OriginalPath"`
|
||||
}
|
||||
|
||||
type SingLogConfig struct {
|
||||
@@ -34,13 +32,13 @@ func NewSingConfig() *SingConfig {
|
||||
}
|
||||
|
||||
type SingOptions struct {
|
||||
EnableProxyProtocol bool `json:"EnableProxyProtocol"`
|
||||
TCPFastOpen bool `json:"EnableTFO"`
|
||||
SniffEnabled bool `json:"EnableSniff"`
|
||||
SniffOverrideDestination bool `json:"SniffOverrideDestination"`
|
||||
EnableDNS bool `json:"EnableDNS"`
|
||||
DomainStrategy option.DomainStrategy `json:"DomainStrategy"`
|
||||
SniffOverrideDestination bool `json:"SniffOverrideDestination"`
|
||||
FallBackConfigs *FallBackConfigForSing `json:"FallBackConfigs"`
|
||||
Multiplex *MultiplexConfig `json:"MultiplexConfig"`
|
||||
}
|
||||
|
||||
type SingNtpConfig struct {
|
||||
@@ -60,13 +58,25 @@ type FallBack struct {
|
||||
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 {
|
||||
return &SingOptions{
|
||||
EnableDNS: false,
|
||||
EnableProxyProtocol: false,
|
||||
TCPFastOpen: false,
|
||||
SniffEnabled: true,
|
||||
SniffOverrideDestination: true,
|
||||
FallBackConfigs: &FallBackConfigForSing{},
|
||||
Multiplex: &MultiplexConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ import (
|
||||
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/apernet/hysteria/extras/correctnet"
|
||||
"github.com/apernet/hysteria/extras/masq"
|
||||
"github.com/apernet/hysteria/extras/obfs"
|
||||
"github.com/apernet/hysteria/extras/outbounds"
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
"github.com/apernet/hysteria/extras/v2/correctnet"
|
||||
"github.com/apernet/hysteria/extras/v2/masq"
|
||||
"github.com/apernet/hysteria/extras/v2/obfs"
|
||||
"github.com/apernet/hysteria/extras/v2/outbounds"
|
||||
"github.com/apernet/hysteria/extras/v2/sniff"
|
||||
eUtils "github.com/apernet/hysteria/extras/v2/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -49,7 +51,7 @@ const (
|
||||
defaultStreamReceiveWindow = 8388608 // 8MB
|
||||
defaultConnReceiveWindow = defaultStreamReceiveWindow * 5 / 2 // 20MB
|
||||
defaultMaxIdleTimeout = 30 * time.Second
|
||||
defaultMaxIncomingStreams = 1024
|
||||
defaultMaxIncomingStreams = 4096
|
||||
defaultUDPIdleTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
@@ -77,37 +79,49 @@ func (n *Hysteria2node) getTLSConfig(config *conf.Options) (*server.TLSConfig, e
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getQUICConfig(config *conf.Options) (*server.QUICConfig, error) {
|
||||
func (n *Hysteria2node) getQUICConfig(config *serverConfig) (*server.QUICConfig, error) {
|
||||
quic := &server.QUICConfig{}
|
||||
if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow == 0 {
|
||||
if config.QUIC.InitStreamReceiveWindow == 0 {
|
||||
quic.InitialStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow < 16384 {
|
||||
} else if config.QUIC.InitStreamReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.InitialStreamReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow == 0 {
|
||||
if config.QUIC.MaxStreamReceiveWindow == 0 {
|
||||
quic.MaxStreamReceiveWindow = defaultStreamReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow < 16384 {
|
||||
} else if config.QUIC.MaxStreamReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxStreamReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.MaxStreamReceiveWindow = config.QUIC.MaxStreamReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow == 0 {
|
||||
if config.QUIC.InitConnectionReceiveWindow == 0 {
|
||||
quic.InitialConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow < 16384 {
|
||||
} else if config.QUIC.InitConnectionReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.InitialConnectionReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow == 0 {
|
||||
if config.QUIC.MaxConnectionReceiveWindow == 0 {
|
||||
quic.MaxConnectionReceiveWindow = defaultConnReceiveWindow
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow < 16384 {
|
||||
} else if config.QUIC.MaxConnectionReceiveWindow < 16384 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxConnectionReceiveWindowf must be at least 16384")
|
||||
} else {
|
||||
quic.MaxConnectionReceiveWindow = config.QUIC.MaxConnectionReceiveWindow
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxIdleTimeout == 0 {
|
||||
if config.QUIC.MaxIdleTimeout == 0 {
|
||||
quic.MaxIdleTimeout = defaultMaxIdleTimeout
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxIdleTimeout < 4*time.Second || config.Hysteria2Options.QUICConfig.MaxIdleTimeout > 120*time.Second {
|
||||
} else if config.QUIC.MaxIdleTimeout < 4*time.Second || config.QUIC.MaxIdleTimeout > 120*time.Second {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxIdleTimeoutf must be between 4s and 120s")
|
||||
} else {
|
||||
quic.MaxIdleTimeout = config.QUIC.MaxIdleTimeout
|
||||
}
|
||||
if config.Hysteria2Options.QUICConfig.MaxIncomingStreams == 0 {
|
||||
if config.QUIC.MaxIncomingStreams == 0 {
|
||||
quic.MaxIncomingStreams = defaultMaxIncomingStreams
|
||||
} else if config.Hysteria2Options.QUICConfig.MaxIncomingStreams < 8 {
|
||||
} else if config.QUIC.MaxIncomingStreams < 8 {
|
||||
return nil, fmt.Errorf("QUICConfig.MaxIncomingStreamsf must be at least 8")
|
||||
} else {
|
||||
quic.MaxIncomingStreams = config.QUIC.MaxIncomingStreams
|
||||
}
|
||||
// todo fix !linux && !windows && !darwin
|
||||
quic.DisablePathMTUDiscovery = false
|
||||
@@ -149,19 +163,47 @@ func (n *Hysteria2node) getBandwidthConfig(info *panel.NodeInfo) *server.Bandwid
|
||||
return band
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getOutboundConfig(config *conf.Options) (server.Outbound, error) {
|
||||
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) {
|
||||
// Resolver, ACL, actual outbound are all implemented through the Outbound interface.
|
||||
// Depending on the config, we build a chain like this:
|
||||
// Resolver(ACL(Outbounds...))
|
||||
|
||||
// Outbounds
|
||||
var obs []outbounds.OutboundEntry
|
||||
if len(config.Hysteria2Options.Outbounds) == 0 {
|
||||
if len(c.Outbounds) == 0 {
|
||||
// Guarantee we have at least one outbound
|
||||
obs = []outbounds.OutboundEntry{{
|
||||
Name: "default",
|
||||
Outbound: outbounds.NewDirectOutboundSimple(outbounds.DirectOutboundModeAuto),
|
||||
}}
|
||||
} else {
|
||||
obs = make([]outbounds.OutboundEntry, len(config.Hysteria2Options.Outbounds))
|
||||
for i, entry := range config.Hysteria2Options.Outbounds {
|
||||
obs = make([]outbounds.OutboundEntry, len(c.Outbounds))
|
||||
for i, entry := range c.Outbounds {
|
||||
if entry.Name == "" {
|
||||
return nil, fmt.Errorf("outbounds.name empty outbound name")
|
||||
return nil, fmt.Errorf("empty outbound name")
|
||||
}
|
||||
var ob outbounds.PluggableOutbound
|
||||
var err error
|
||||
@@ -183,43 +225,107 @@ func (n *Hysteria2node) getOutboundConfig(config *conf.Options) (server.Outbound
|
||||
}
|
||||
var uOb outbounds.PluggableOutbound // "unified" outbound
|
||||
|
||||
// ACL
|
||||
hasACL := false
|
||||
if hasACL {
|
||||
// todo fix ACL
|
||||
if c.ACL.File != "" && len(c.ACL.Inline) > 0 {
|
||||
return nil, fmt.Errorf("cannot set both acl.file and acl.inline")
|
||||
}
|
||||
gLoader := &GeoLoader{
|
||||
GeoIPFilename: c.ACL.GeoIP,
|
||||
GeoSiteFilename: c.ACL.GeoSite,
|
||||
UpdateInterval: c.ACL.GeoUpdateInterval,
|
||||
Logger: n.Logger,
|
||||
}
|
||||
|
||||
if c.ACL.File != "" {
|
||||
hasACL = true
|
||||
acl, err := outbounds.NewACLEngineFromFile(c.ACL.File, obs, gLoader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uOb = acl
|
||||
} else if len(c.ACL.Inline) > 0 {
|
||||
n.Logger.Debug("found ACL Inline:", zap.Strings("Inline", c.ACL.Inline))
|
||||
hasACL = true
|
||||
acl, err := outbounds.NewACLEngineFromString(strings.Join(c.ACL.Inline, "\n"), obs, gLoader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uOb = acl
|
||||
} else {
|
||||
// No ACL, use the first outbound
|
||||
uOb = obs[0].Outbound
|
||||
}
|
||||
|
||||
switch strings.ToLower(c.Resolver.Type) {
|
||||
case "", "system":
|
||||
if hasACL {
|
||||
// If the user uses ACL, we must put a resolver in front of it,
|
||||
// for IP rules to work on domain requests.
|
||||
uOb = outbounds.NewSystemResolver(uOb)
|
||||
}
|
||||
// Otherwise we can just rely on outbound handling on its own.
|
||||
case "tcp":
|
||||
if c.Resolver.TCP.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewStandardResolverTCP(c.Resolver.TCP.Addr, c.Resolver.TCP.Timeout, uOb)
|
||||
case "udp":
|
||||
if c.Resolver.UDP.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewStandardResolverUDP(c.Resolver.UDP.Addr, c.Resolver.UDP.Timeout, uOb)
|
||||
case "tls", "tcp-tls":
|
||||
if c.Resolver.TLS.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewStandardResolverTLS(c.Resolver.TLS.Addr, c.Resolver.TLS.Timeout, c.Resolver.TLS.SNI, c.Resolver.TLS.Insecure, uOb)
|
||||
case "https", "http":
|
||||
if c.Resolver.HTTPS.Addr == "" {
|
||||
return nil, fmt.Errorf("empty resolver address")
|
||||
}
|
||||
uOb = outbounds.NewDoHResolver(c.Resolver.HTTPS.Addr, c.Resolver.HTTPS.Timeout, c.Resolver.HTTPS.SNI, c.Resolver.HTTPS.Insecure, uOb)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported resolver type")
|
||||
}
|
||||
Outbound := &outbounds.PluggableOutboundAdapter{PluggableOutbound: uOb}
|
||||
|
||||
return Outbound, nil
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.PacketConn, info *panel.NodeInfo, config *conf.Options) (http.Handler, error) {
|
||||
func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.PacketConn, c *serverConfig) (http.Handler, error) {
|
||||
var handler http.Handler
|
||||
switch strings.ToLower(config.Hysteria2Options.Masquerade.Type) {
|
||||
switch strings.ToLower(c.Masquerade.Type) {
|
||||
case "", "404":
|
||||
handler = http.NotFoundHandler()
|
||||
case "file":
|
||||
if config.Hysteria2Options.Masquerade.File.Dir == "" {
|
||||
if c.Masquerade.File.Dir == "" {
|
||||
return nil, fmt.Errorf("masquerade.file.dir empty file directory")
|
||||
}
|
||||
handler = http.FileServer(http.Dir(config.Hysteria2Options.Masquerade.File.Dir))
|
||||
handler = http.FileServer(http.Dir(c.Masquerade.File.Dir))
|
||||
case "proxy":
|
||||
if config.Hysteria2Options.Masquerade.Proxy.URL == "" {
|
||||
if c.Masquerade.Proxy.URL == "" {
|
||||
return nil, fmt.Errorf("masquerade.proxy.url empty proxy url")
|
||||
}
|
||||
u, err := url.Parse(config.Hysteria2Options.Masquerade.Proxy.URL)
|
||||
u, err := url.Parse(c.Masquerade.Proxy.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(fmt.Sprintf("masquerade.proxy.url %s", err))
|
||||
return nil, fmt.Errorf("masquerade.proxy.url %s", err)
|
||||
}
|
||||
handler = &httputil.ReverseProxy{
|
||||
Rewrite: func(r *httputil.ProxyRequest) {
|
||||
r.SetURL(u)
|
||||
// SetURL rewrites the Host header,
|
||||
// but we don't want that if rewriteHost is false
|
||||
if !config.Hysteria2Options.Masquerade.Proxy.RewriteHost {
|
||||
r.Out.Host = r.In.Host
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = u.Scheme
|
||||
req.URL.Host = u.Host
|
||||
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
xff := req.Header.Get("X-Forwarded-For")
|
||||
if xff != "" {
|
||||
clientIP = xff + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
|
||||
if c.Masquerade.Proxy.RewriteHost {
|
||||
req.Host = req.URL.Host
|
||||
}
|
||||
},
|
||||
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
@@ -228,52 +334,93 @@ func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.Pac
|
||||
},
|
||||
}
|
||||
case "string":
|
||||
if config.Hysteria2Options.Masquerade.String.Content == "" {
|
||||
if c.Masquerade.String.Content == "" {
|
||||
return nil, fmt.Errorf("masquerade.string.content empty string content")
|
||||
}
|
||||
if config.Hysteria2Options.Masquerade.String.StatusCode != 0 &&
|
||||
(config.Hysteria2Options.Masquerade.String.StatusCode < 200 ||
|
||||
config.Hysteria2Options.Masquerade.String.StatusCode > 599 ||
|
||||
config.Hysteria2Options.Masquerade.String.StatusCode == 233) {
|
||||
if c.Masquerade.String.StatusCode != 0 &&
|
||||
(c.Masquerade.String.StatusCode < 200 ||
|
||||
c.Masquerade.String.StatusCode > 599 ||
|
||||
c.Masquerade.String.StatusCode == 233) {
|
||||
// 233 is reserved for Hysteria authentication
|
||||
return nil, fmt.Errorf("masquerade.string.statusCode invalid status code (must be 200-599, except 233)")
|
||||
}
|
||||
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for k, v := range config.Hysteria2Options.Masquerade.String.Headers {
|
||||
for k, v := range c.Masquerade.String.Headers {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
if config.Hysteria2Options.Masquerade.String.StatusCode != 0 {
|
||||
w.WriteHeader(config.Hysteria2Options.Masquerade.String.StatusCode)
|
||||
if c.Masquerade.String.StatusCode != 0 {
|
||||
w.WriteHeader(c.Masquerade.String.StatusCode)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK) // Use 200 OK by default
|
||||
}
|
||||
_, _ = w.Write([]byte(config.Hysteria2Options.Masquerade.String.Content))
|
||||
_, _ = w.Write([]byte(c.Masquerade.String.Content))
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("masquerade.type unsupported masquerade type")
|
||||
}
|
||||
MasqHandler := &masqHandlerLogWrapper{H: handler, QUIC: true, Logger: n.Logger}
|
||||
|
||||
if config.Hysteria2Options.Masquerade.ListenHTTP != "" || config.Hysteria2Options.Masquerade.ListenHTTPS != "" {
|
||||
if config.Hysteria2Options.Masquerade.ListenHTTP != "" && config.Hysteria2Options.Masquerade.ListenHTTPS == "" {
|
||||
if c.Masquerade.ListenHTTP != "" || c.Masquerade.ListenHTTPS != "" {
|
||||
if c.Masquerade.ListenHTTP != "" && c.Masquerade.ListenHTTPS == "" {
|
||||
return nil, fmt.Errorf("masquerade.listenHTTPS having only HTTP server without HTTPS is not supported")
|
||||
}
|
||||
s := masq.MasqTCPServer{
|
||||
QUICPort: extractPortFromAddr(conn.LocalAddr().String()),
|
||||
HTTPSPort: extractPortFromAddr(config.Hysteria2Options.Masquerade.ListenHTTPS),
|
||||
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false},
|
||||
HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS),
|
||||
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false, Logger: n.Logger},
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: tlsconfig.Certificates,
|
||||
GetCertificate: tlsconfig.GetCertificate,
|
||||
},
|
||||
ForceHTTPS: config.Hysteria2Options.Masquerade.ForceHTTPS,
|
||||
ForceHTTPS: c.Masquerade.ForceHTTPS,
|
||||
}
|
||||
go runMasqTCPServer(&s, config.Hysteria2Options.Masquerade.ListenHTTP, config.Hysteria2Options.Masquerade.ListenHTTPS, n.Logger)
|
||||
go runMasqTCPServer(&s, c.Masquerade.ListenHTTP, c.Masquerade.ListenHTTPS, n.Logger)
|
||||
}
|
||||
|
||||
return MasqHandler, nil
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getHyConfig(info *panel.NodeInfo, config *conf.Options, c *serverConfig) (*server.Config, error) {
|
||||
tls, err := n.getTLSConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quic, err := n.getQUICConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := n.getConn(info, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sniff, err := n.getRequestHook(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Outbound, err := n.getOutboundConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Masq, err := n.getMasqHandler(tls, conn, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &server.Config{
|
||||
TLSConfig: *tls,
|
||||
QUICConfig: *quic,
|
||||
Conn: conn,
|
||||
RequestHook: sniff,
|
||||
Outbound: Outbound,
|
||||
BandwidthConfig: *n.getBandwidthConfig(info),
|
||||
IgnoreClientBandwidth: info.Hysteria2.Ignore_Client_Bandwidth,
|
||||
DisableUDP: c.DisableUDP,
|
||||
UDPIdleTimeout: c.UDPIdleTimeout,
|
||||
EventLogger: n.EventLogger,
|
||||
TrafficLogger: n.TrafficLogger,
|
||||
MasqHandler: Masq,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func runMasqTCPServer(s *masq.MasqTCPServer, httpAddr, httpsAddr string, logger *zap.Logger) {
|
||||
errChan := make(chan error, 2)
|
||||
if httpAddr != "" {
|
||||
|
||||
181
core/hy2/geoloader.go
Normal file
181
core/hy2/geoloader.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/extras/v2/outbounds/acl"
|
||||
"github.com/apernet/hysteria/extras/v2/outbounds/acl/v2geo"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
geoipFilename = "geoip.dat"
|
||||
geoipURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||
geositeFilename = "geosite.dat"
|
||||
geositeURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||
geoDlTmpPattern = ".hysteria-geoloader.dlpart.*"
|
||||
|
||||
geoDefaultUpdateInterval = 7 * 24 * time.Hour // 7 days
|
||||
)
|
||||
|
||||
var _ acl.GeoLoader = (*GeoLoader)(nil)
|
||||
|
||||
// GeoLoader provides the on-demand GeoIP/GeoSite database
|
||||
// loading functionality required by the ACL engine.
|
||||
// Empty filenames = automatic download from built-in URLs.
|
||||
type GeoLoader struct {
|
||||
GeoIPFilename string
|
||||
GeoSiteFilename string
|
||||
UpdateInterval time.Duration
|
||||
|
||||
geoipMap map[string]*v2geo.GeoIP
|
||||
geositeMap map[string]*v2geo.GeoSite
|
||||
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
func (l *GeoLoader) shouldDownload(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
if info.Size() == 0 {
|
||||
// empty files are loadable by v2geo, but we consider it broken
|
||||
return true
|
||||
}
|
||||
dt := time.Since(info.ModTime())
|
||||
if l.UpdateInterval == 0 {
|
||||
return dt > geoDefaultUpdateInterval
|
||||
} else {
|
||||
return dt > l.UpdateInterval
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GeoLoader) downloadAndCheck(filename, url string, checkFunc func(filename string) error) error {
|
||||
l.geoDownloadFunc(filename, url)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.CreateTemp(".", geoDlTmpPattern)
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(err)
|
||||
return err
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
l.geoDownloadErrFunc(err)
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
|
||||
err = checkFunc(f.Name())
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(fmt.Errorf("integrity check failed: %w", err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(f.Name(), filename)
|
||||
if err != nil {
|
||||
l.geoDownloadErrFunc(fmt.Errorf("rename failed: %w", err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (l *GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) {
|
||||
if l.geoipMap != nil {
|
||||
return l.geoipMap, nil
|
||||
}
|
||||
autoDL := false
|
||||
filename := l.GeoIPFilename
|
||||
if filename == "" {
|
||||
autoDL = true
|
||||
filename = geoipFilename
|
||||
}
|
||||
if autoDL {
|
||||
if !l.shouldDownload(filename) {
|
||||
m, err := v2geo.LoadGeoIP(filename)
|
||||
if err == nil {
|
||||
l.geoipMap = m
|
||||
return m, nil
|
||||
}
|
||||
// file is broken, download it again
|
||||
}
|
||||
err := l.downloadAndCheck(filename, geoipURL, func(filename string) error {
|
||||
_, err := v2geo.LoadGeoIP(filename)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
// as long as the previous download exists, fallback to it
|
||||
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
m, err := v2geo.LoadGeoIP(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.geoipMap = m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (l *GeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) {
|
||||
if l.geositeMap != nil {
|
||||
return l.geositeMap, nil
|
||||
}
|
||||
autoDL := false
|
||||
filename := l.GeoSiteFilename
|
||||
if filename == "" {
|
||||
autoDL = true
|
||||
filename = geositeFilename
|
||||
}
|
||||
if autoDL {
|
||||
if !l.shouldDownload(filename) {
|
||||
m, err := v2geo.LoadGeoSite(filename)
|
||||
if err == nil {
|
||||
l.geositeMap = m
|
||||
return m, nil
|
||||
}
|
||||
// file is broken, download it again
|
||||
}
|
||||
err := l.downloadAndCheck(filename, geositeURL, func(filename string) error {
|
||||
_, err := v2geo.LoadGeoSite(filename)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
// as long as the previous download exists, fallback to it
|
||||
if _, serr := os.Stat(filename); os.IsNotExist(serr) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
m, err := v2geo.LoadGeoSite(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.geositeMap = m
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (l *GeoLoader) geoDownloadFunc(filename, url string) {
|
||||
l.Logger.Info("downloading database", zap.String("filename", filename), zap.String("url", url))
|
||||
}
|
||||
|
||||
func (l *GeoLoader) geoDownloadErrFunc(err error) {
|
||||
if err != nil {
|
||||
l.Logger.Error("failed to download database", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,46 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/InazumaV/V2bX/common/counter"
|
||||
"github.com/InazumaV/V2bX/common/format"
|
||||
"github.com/InazumaV/V2bX/limiter"
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
quic "github.com/apernet/quic-go"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ server.TrafficLogger = (*HookServer)(nil)
|
||||
|
||||
type HookServer struct {
|
||||
Tag string
|
||||
logger *zap.Logger
|
||||
Counter sync.Map
|
||||
}
|
||||
|
||||
func (h *HookServer) Log(id string, tx, rx uint64) (ok bool) {
|
||||
func (h *HookServer) TraceStream(stream quic.Stream, stats *server.StreamStats) {
|
||||
}
|
||||
|
||||
func (h *HookServer) UntraceStream(stream quic.Stream) {
|
||||
}
|
||||
|
||||
func (h *HookServer) LogTraffic(id string, tx, rx uint64) (ok bool) {
|
||||
var c interface{}
|
||||
var exists bool
|
||||
|
||||
limiterinfo, err := limiter.GetLimiter(h.Tag)
|
||||
if err != nil {
|
||||
h.logger.Error("Get limiter error", zap.String("tag", h.Tag), zap.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(h.Tag, id))
|
||||
if ok {
|
||||
userlimitInfo := userLimit.(*limiter.UserLimitInfo)
|
||||
if userlimitInfo.OverLimit {
|
||||
userlimitInfo.OverLimit = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if c, exists = h.Counter.Load(h.Tag); !exists {
|
||||
c = counter.NewTrafficCounter()
|
||||
h.Counter.Store(h.Tag, c)
|
||||
@@ -28,3 +57,6 @@ func (h *HookServer) Log(id string, tx, rx uint64) (ok bool) {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *HookServer) LogOnlineState(id string, online bool) {
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/InazumaV/V2bX/common/format"
|
||||
"github.com/InazumaV/V2bX/limiter"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
@@ -46,12 +47,18 @@ var logFormatMap = map[string]zapcore.EncoderConfig{
|
||||
}
|
||||
|
||||
func (l *serverLogger) Connect(addr net.Addr, uuid string, tx uint64) {
|
||||
limiter, err := limiter.GetLimiter(l.Tag)
|
||||
limiterinfo, err := limiter.GetLimiter(l.Tag)
|
||||
if err != nil {
|
||||
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
|
||||
}
|
||||
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r {
|
||||
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid))
|
||||
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
|
||||
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
|
||||
userLimit.(*limiter.UserLimitInfo).OverLimit = true
|
||||
}
|
||||
} else {
|
||||
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
|
||||
userLimit.(*limiter.UserLimitInfo).OverLimit = false
|
||||
}
|
||||
}
|
||||
l.logger.Info("client connected", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint64("tx", tx))
|
||||
}
|
||||
@@ -61,12 +68,18 @@ func (l *serverLogger) Disconnect(addr net.Addr, uuid string, err error) {
|
||||
}
|
||||
|
||||
func (l *serverLogger) TCPRequest(addr net.Addr, uuid, reqAddr string) {
|
||||
limiter, err := limiter.GetLimiter(l.Tag)
|
||||
limiterinfo, err := limiter.GetLimiter(l.Tag)
|
||||
if err != nil {
|
||||
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
|
||||
}
|
||||
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r {
|
||||
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid))
|
||||
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
|
||||
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
|
||||
userLimit.(*limiter.UserLimitInfo).OverLimit = true
|
||||
}
|
||||
} else {
|
||||
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
|
||||
userLimit.(*limiter.UserLimitInfo).OverLimit = false
|
||||
}
|
||||
}
|
||||
l.logger.Debug("TCP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr))
|
||||
}
|
||||
@@ -80,12 +93,18 @@ func (l *serverLogger) TCPError(addr net.Addr, uuid, reqAddr string, err error)
|
||||
}
|
||||
|
||||
func (l *serverLogger) UDPRequest(addr net.Addr, uuid string, sessionId uint32, reqAddr string) {
|
||||
limiter, err := limiter.GetLimiter(l.Tag)
|
||||
limiterinfo, err := limiter.GetLimiter(l.Tag)
|
||||
if err != nil {
|
||||
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
|
||||
}
|
||||
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r {
|
||||
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid))
|
||||
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
|
||||
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
|
||||
userLimit.(*limiter.UserLimitInfo).OverLimit = true
|
||||
}
|
||||
} else {
|
||||
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
|
||||
userLimit.(*limiter.UserLimitInfo).OverLimit = false
|
||||
}
|
||||
}
|
||||
l.logger.Debug("UDP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId), zap.String("reqAddr", reqAddr))
|
||||
}
|
||||
@@ -101,11 +120,11 @@ func (l *serverLogger) UDPError(addr net.Addr, uuid string, sessionId uint32, er
|
||||
func initLogger(logLevel string, logFormat string) (*zap.Logger, error) {
|
||||
level, ok := logLevelMap[strings.ToLower(logLevel)]
|
||||
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)]
|
||||
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{
|
||||
Level: zap.NewAtomicLevelAt(level),
|
||||
@@ -118,7 +137,7 @@ func initLogger(logLevel string, logFormat string) (*zap.Logger, error) {
|
||||
}
|
||||
logger, err := c.Build()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -19,53 +18,18 @@ type Hysteria2node struct {
|
||||
TrafficLogger server.TrafficLogger
|
||||
}
|
||||
|
||||
func (n *Hysteria2node) getHyConfig(tag string, info *panel.NodeInfo, config *conf.Options) (*server.Config, error) {
|
||||
tls, err := n.getTLSConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quic, err := n.getQUICConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := n.getConn(info, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Outbound, err := n.getOutboundConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Masq, err := n.getMasqHandler(tls, conn, info, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &server.Config{
|
||||
TLSConfig: *tls,
|
||||
QUICConfig: *quic,
|
||||
Conn: conn,
|
||||
Outbound: Outbound,
|
||||
BandwidthConfig: *n.getBandwidthConfig(info),
|
||||
IgnoreClientBandwidth: config.Hysteria2Options.IgnoreClientBandwidth,
|
||||
DisableUDP: config.Hysteria2Options.DisableUDP,
|
||||
UDPIdleTimeout: config.Hysteria2Options.UDPIdleTimeout,
|
||||
EventLogger: n.EventLogger,
|
||||
TrafficLogger: n.TrafficLogger,
|
||||
MasqHandler: Masq,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error {
|
||||
var err error
|
||||
hyconfig := &server.Config{}
|
||||
if len(config.Hysteria2Options.Hysteria2ConfigPath) != 0 {
|
||||
data, err := os.ReadFile(config.Hysteria2Options.Hysteria2ConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read hysteria2 config error: %s", err)
|
||||
var c serverConfig
|
||||
v := viper.New()
|
||||
if len(config.Hysteria2ConfigPath) != 0 {
|
||||
v.SetConfigFile(config.Hysteria2ConfigPath)
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
h.Logger.Fatal("failed to read server config", zap.Error(err))
|
||||
}
|
||||
err = json.Unmarshal(data, hyconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal original config error: %s", err)
|
||||
if err := v.Unmarshal(&c); err != nil {
|
||||
h.Logger.Fatal("failed to parse server config", zap.Error(err))
|
||||
}
|
||||
}
|
||||
n := Hysteria2node{
|
||||
@@ -76,11 +40,12 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio
|
||||
logger: h.Logger,
|
||||
},
|
||||
TrafficLogger: &HookServer{
|
||||
Tag: tag,
|
||||
Tag: tag,
|
||||
logger: h.Logger,
|
||||
},
|
||||
}
|
||||
|
||||
hyconfig, err = n.getHyConfig(tag, info, config)
|
||||
hyconfig, err = n.getHyConfig(info, config, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,7 +58,9 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio
|
||||
h.Hy2nodes[tag] = n
|
||||
go func() {
|
||||
if err := s.Serve(); err != nil {
|
||||
h.Logger.Error("Server Error", zap.Error(err))
|
||||
if !strings.Contains(err.Error(), "quic: server closed") {
|
||||
h.Logger.Error("Server Error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/apernet/hysteria/extras/outbounds"
|
||||
)
|
||||
|
||||
func serverConfigOutboundDirectToOutbound(c conf.ServerConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
|
||||
var mode outbounds.DirectOutboundMode
|
||||
switch strings.ToLower(c.Mode) {
|
||||
case "", "auto":
|
||||
mode = outbounds.DirectOutboundModeAuto
|
||||
case "64":
|
||||
mode = outbounds.DirectOutboundMode64
|
||||
case "46":
|
||||
mode = outbounds.DirectOutboundMode46
|
||||
case "6":
|
||||
mode = outbounds.DirectOutboundMode6
|
||||
case "4":
|
||||
mode = outbounds.DirectOutboundMode4
|
||||
default:
|
||||
return nil, fmt.Errorf("outbounds.direct.mode unsupported mode")
|
||||
}
|
||||
bindIP := len(c.BindIPv4) > 0 || len(c.BindIPv6) > 0
|
||||
bindDevice := len(c.BindDevice) > 0
|
||||
if bindIP && bindDevice {
|
||||
return nil, fmt.Errorf("outbounds.direct cannot bind both IP and device")
|
||||
}
|
||||
if bindIP {
|
||||
ip4, ip6 := net.ParseIP(c.BindIPv4), net.ParseIP(c.BindIPv6)
|
||||
if len(c.BindIPv4) > 0 && ip4 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv4 invalid IPv4 address")
|
||||
}
|
||||
if len(c.BindIPv6) > 0 && ip6 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv6 invalid IPv6 address")
|
||||
}
|
||||
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
|
||||
}
|
||||
if bindDevice {
|
||||
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
|
||||
}
|
||||
return outbounds.NewDirectOutboundSimple(mode), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundSOCKS5ToOutbound(c conf.ServerConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
|
||||
if c.Addr == "" {
|
||||
return nil, fmt.Errorf("outbounds.socks5.addr empty socks5 address")
|
||||
}
|
||||
return outbounds.NewSOCKS5Outbound(c.Addr, c.Username, c.Password), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundHTTPToOutbound(c conf.ServerConfigOutboundHTTP) (outbounds.PluggableOutbound, error) {
|
||||
if c.URL == "" {
|
||||
return nil, fmt.Errorf("outbounds.http.url empty http address")
|
||||
}
|
||||
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
|
||||
}
|
||||
238
core/hy2/serverConfig.go
Normal file
238
core/hy2/serverConfig.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package hy2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/extras/v2/outbounds"
|
||||
)
|
||||
|
||||
type serverConfig struct {
|
||||
Listen string `mapstructure:"listen"`
|
||||
Obfs serverConfigObfs `mapstructure:"obfs"`
|
||||
TLS *serverConfigTLS `mapstructure:"tls"`
|
||||
ACME *serverConfigACME `mapstructure:"acme"`
|
||||
QUIC serverConfigQUIC `mapstructure:"quic"`
|
||||
Bandwidth serverConfigBandwidth `mapstructure:"bandwidth"`
|
||||
IgnoreClientBandwidth bool `mapstructure:"ignoreClientBandwidth"`
|
||||
SpeedTest bool `mapstructure:"speedTest"`
|
||||
DisableUDP bool `mapstructure:"disableUDP"`
|
||||
UDPIdleTimeout time.Duration `mapstructure:"udpIdleTimeout"`
|
||||
Auth serverConfigAuth `mapstructure:"auth"`
|
||||
Resolver serverConfigResolver `mapstructure:"resolver"`
|
||||
Sniff serverConfigSniff `mapstructure:"sniff"`
|
||||
ACL serverConfigACL `mapstructure:"acl"`
|
||||
Outbounds []serverConfigOutboundEntry `mapstructure:"outbounds"`
|
||||
TrafficStats serverConfigTrafficStats `mapstructure:"trafficStats"`
|
||||
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
|
||||
}
|
||||
|
||||
type serverConfigObfsSalamander struct {
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
type serverConfigObfs struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Salamander serverConfigObfsSalamander `mapstructure:"salamander"`
|
||||
}
|
||||
|
||||
type serverConfigTLS struct {
|
||||
Cert string `mapstructure:"cert"`
|
||||
Key string `mapstructure:"key"`
|
||||
}
|
||||
|
||||
type serverConfigACME struct {
|
||||
Domains []string `mapstructure:"domains"`
|
||||
Email string `mapstructure:"email"`
|
||||
CA string `mapstructure:"ca"`
|
||||
DisableHTTP bool `mapstructure:"disableHTTP"`
|
||||
DisableTLSALPN bool `mapstructure:"disableTLSALPN"`
|
||||
AltHTTPPort int `mapstructure:"altHTTPPort"`
|
||||
AltTLSALPNPort int `mapstructure:"altTLSALPNPort"`
|
||||
Dir string `mapstructure:"dir"`
|
||||
}
|
||||
|
||||
type serverConfigQUIC struct {
|
||||
InitStreamReceiveWindow uint64 `mapstructure:"initStreamReceiveWindow"`
|
||||
MaxStreamReceiveWindow uint64 `mapstructure:"maxStreamReceiveWindow"`
|
||||
InitConnectionReceiveWindow uint64 `mapstructure:"initConnReceiveWindow"`
|
||||
MaxConnectionReceiveWindow uint64 `mapstructure:"maxConnReceiveWindow"`
|
||||
MaxIdleTimeout time.Duration `mapstructure:"maxIdleTimeout"`
|
||||
MaxIncomingStreams int64 `mapstructure:"maxIncomingStreams"`
|
||||
DisablePathMTUDiscovery bool `mapstructure:"disablePathMTUDiscovery"`
|
||||
}
|
||||
|
||||
type serverConfigBandwidth struct {
|
||||
Up string `mapstructure:"up"`
|
||||
Down string `mapstructure:"down"`
|
||||
}
|
||||
|
||||
type serverConfigAuthHTTP struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigAuth struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Password string `mapstructure:"password"`
|
||||
UserPass map[string]string `mapstructure:"userpass"`
|
||||
HTTP serverConfigAuthHTTP `mapstructure:"http"`
|
||||
Command string `mapstructure:"command"`
|
||||
}
|
||||
|
||||
type serverConfigResolverTCP struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
type serverConfigResolverUDP struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
type serverConfigResolverTLS struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
SNI string `mapstructure:"sni"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigResolverHTTPS struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
SNI string `mapstructure:"sni"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigResolver struct {
|
||||
Type string `mapstructure:"type"`
|
||||
TCP serverConfigResolverTCP `mapstructure:"tcp"`
|
||||
UDP serverConfigResolverUDP `mapstructure:"udp"`
|
||||
TLS serverConfigResolverTLS `mapstructure:"tls"`
|
||||
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
|
||||
}
|
||||
|
||||
type 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 {
|
||||
File string `mapstructure:"file"`
|
||||
Inline []string `mapstructure:"inline"`
|
||||
GeoIP string `mapstructure:"geoip"`
|
||||
GeoSite string `mapstructure:"geosite"`
|
||||
GeoUpdateInterval time.Duration `mapstructure:"geoUpdateInterval"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundDirect struct {
|
||||
Mode string `mapstructure:"mode"`
|
||||
BindIPv4 string `mapstructure:"bindIPv4"`
|
||||
BindIPv6 string `mapstructure:"bindIPv6"`
|
||||
BindDevice string `mapstructure:"bindDevice"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundSOCKS5 struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundHTTP struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Insecure bool `mapstructure:"insecure"`
|
||||
}
|
||||
|
||||
type serverConfigOutboundEntry struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Type string `mapstructure:"type"`
|
||||
Direct serverConfigOutboundDirect `mapstructure:"direct"`
|
||||
SOCKS5 serverConfigOutboundSOCKS5 `mapstructure:"socks5"`
|
||||
HTTP serverConfigOutboundHTTP `mapstructure:"http"`
|
||||
}
|
||||
|
||||
type serverConfigTrafficStats struct {
|
||||
Listen string `mapstructure:"listen"`
|
||||
Secret string `mapstructure:"secret"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeFile struct {
|
||||
Dir string `mapstructure:"dir"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeProxy struct {
|
||||
URL string `mapstructure:"url"`
|
||||
RewriteHost bool `mapstructure:"rewriteHost"`
|
||||
}
|
||||
|
||||
type serverConfigMasqueradeString struct {
|
||||
Content string `mapstructure:"content"`
|
||||
Headers map[string]string `mapstructure:"headers"`
|
||||
StatusCode int `mapstructure:"statusCode"`
|
||||
}
|
||||
|
||||
type serverConfigMasquerade struct {
|
||||
Type string `mapstructure:"type"`
|
||||
File serverConfigMasqueradeFile `mapstructure:"file"`
|
||||
Proxy serverConfigMasqueradeProxy `mapstructure:"proxy"`
|
||||
String serverConfigMasqueradeString `mapstructure:"string"`
|
||||
ListenHTTP string `mapstructure:"listenHTTP"`
|
||||
ListenHTTPS string `mapstructure:"listenHTTPS"`
|
||||
ForceHTTPS bool `mapstructure:"forceHTTPS"`
|
||||
}
|
||||
|
||||
func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
|
||||
var mode outbounds.DirectOutboundMode
|
||||
switch strings.ToLower(c.Mode) {
|
||||
case "", "auto":
|
||||
mode = outbounds.DirectOutboundModeAuto
|
||||
case "64":
|
||||
mode = outbounds.DirectOutboundMode64
|
||||
case "46":
|
||||
mode = outbounds.DirectOutboundMode46
|
||||
case "6":
|
||||
mode = outbounds.DirectOutboundMode6
|
||||
case "4":
|
||||
mode = outbounds.DirectOutboundMode4
|
||||
default:
|
||||
return nil, fmt.Errorf("outbounds.direct.mode unsupported mode")
|
||||
}
|
||||
bindIP := len(c.BindIPv4) > 0 || len(c.BindIPv6) > 0
|
||||
bindDevice := len(c.BindDevice) > 0
|
||||
if bindIP && bindDevice {
|
||||
return nil, fmt.Errorf("outbounds.direct cannot bind both IP and device")
|
||||
}
|
||||
if bindIP {
|
||||
ip4, ip6 := net.ParseIP(c.BindIPv4), net.ParseIP(c.BindIPv6)
|
||||
if len(c.BindIPv4) > 0 && ip4 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv4 invalid IPv4 address")
|
||||
}
|
||||
if len(c.BindIPv6) > 0 && ip6 == nil {
|
||||
return nil, fmt.Errorf("outbounds.direct.bindIPv6 invalid IPv6 address")
|
||||
}
|
||||
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
|
||||
}
|
||||
if bindDevice {
|
||||
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
|
||||
}
|
||||
return outbounds.NewDirectOutboundSimple(mode), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundSOCKS5ToOutbound(c serverConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
|
||||
if c.Addr == "" {
|
||||
return nil, fmt.Errorf("outbounds.socks5.addr empty socks5 address")
|
||||
}
|
||||
return outbounds.NewSOCKS5Outbound(c.Addr, c.Username, c.Password), nil
|
||||
}
|
||||
|
||||
func serverConfigOutboundHTTPToOutbound(c serverConfigOutboundHTTP) (outbounds.PluggableOutbound, error) {
|
||||
if c.URL == "" {
|
||||
return nil, fmt.Errorf("outbounds.http.url empty http address")
|
||||
}
|
||||
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/common/counter"
|
||||
vCore "github.com/InazumaV/V2bX/core"
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
)
|
||||
|
||||
var _ server.Authenticator = &V2bX{}
|
||||
@@ -41,7 +41,7 @@ func (h *Hysteria2) AddUsers(p *vCore.AddUsersParams) (added int, err error) {
|
||||
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
|
||||
for _, user := range users {
|
||||
wg.Add(1)
|
||||
|
||||
@@ -18,7 +18,7 @@ type Core interface {
|
||||
DelNode(tag string) error
|
||||
AddUsers(p *AddUsersParams) (added int, err error)
|
||||
GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64)
|
||||
DelUsers(users []panel.UserInfo, tag string) error
|
||||
DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error
|
||||
Protocols() []string
|
||||
Type() string
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ package core
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
)
|
||||
@@ -135,12 +136,12 @@ func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up int64, down
|
||||
return t.(Core).GetUserTraffic(tag, uuid, 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)
|
||||
if !e {
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -2,12 +2,11 @@ package sing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
|
||||
"github.com/InazumaV/V2bX/common/format"
|
||||
"github.com/InazumaV/V2bX/common/rate"
|
||||
|
||||
"github.com/InazumaV/V2bX/limiter"
|
||||
@@ -18,204 +17,115 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ adapter.ConnectionTracker = (*HookServer)(nil)
|
||||
|
||||
type HookServer struct {
|
||||
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()
|
||||
}
|
||||
counter sync.Map //map[string]*counter.TrafficCounter
|
||||
}
|
||||
|
||||
func (h *HookServer) ModeList() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHookServer(enableClear bool) *HookServer {
|
||||
return &HookServer{
|
||||
EnableConnClear: enableClear,
|
||||
counter: sync.Map{},
|
||||
connClears: sync.Map{},
|
||||
func NewHookServer() *HookServer {
|
||||
server := &HookServer{
|
||||
counter: sync.Map{},
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
func (h *HookServer) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HookServer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HookServer) PreStart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||
t := &Tracker{}
|
||||
func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule, _ adapter.Outbound) net.Conn {
|
||||
l, err := limiter.GetLimiter(m.Inbound)
|
||||
if err != nil {
|
||||
log.Warn("get limiter for ", m.Inbound, " error: ", err)
|
||||
return conn, t
|
||||
}
|
||||
if l.CheckDomainRule(m.Domain) {
|
||||
conn.Close()
|
||||
log.Error("[", m.Inbound, "] ",
|
||||
"Limited ", m.User, " access to ", m.Domain, " by domain rule")
|
||||
return conn, t
|
||||
}
|
||||
if l.CheckProtocolRule(m.Protocol) {
|
||||
conn.Close()
|
||||
log.Error("[", m.Inbound, "] ",
|
||||
"Limited ", m.User, " use ", m.Domain, " by protocol rule")
|
||||
return conn, t
|
||||
return conn
|
||||
}
|
||||
taguuid := format.UserTag(m.Inbound, m.User)
|
||||
ip := m.Source.Addr.String()
|
||||
if b, r := l.CheckLimit(m.User, ip, true); r {
|
||||
if b, r := l.CheckLimit(taguuid, ip, true, true); r {
|
||||
conn.Close()
|
||||
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
|
||||
return conn, t
|
||||
return conn
|
||||
} else if b != nil {
|
||||
conn = rate.NewConnRateLimiter(conn, b)
|
||||
}
|
||||
t.AddLeave(func() {
|
||||
l.ConnLimiter.DelConnCount(m.User, ip)
|
||||
})
|
||||
if h.EnableConnClear {
|
||||
var key int
|
||||
cc := &ConnClear{
|
||||
conns: map[int]io.Closer{
|
||||
0: conn,
|
||||
},
|
||||
if l != nil {
|
||||
destStr := m.Destination.AddrString()
|
||||
protocol := m.Protocol
|
||||
if l.CheckDomainRule(destStr) {
|
||||
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 {
|
||||
cc = v.(*ConnClear)
|
||||
key = cc.AddConn(conn)
|
||||
if len(protocol) != 0 {
|
||||
if l.CheckProtocolRule(protocol) {
|
||||
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 {
|
||||
return counter.NewConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
|
||||
var t *counter.TrafficCounter
|
||||
if c, ok := h.counter.Load(m.Inbound); !ok {
|
||||
t = counter.NewTrafficCounter()
|
||||
h.counter.Store(m.Inbound, t)
|
||||
} else {
|
||||
c := counter.NewTrafficCounter()
|
||||
h.counter.Store(m.Inbound, c)
|
||||
return counter.NewConnCounter(conn, c.GetCounter(m.User)), t
|
||||
t = c.(*counter.TrafficCounter)
|
||||
}
|
||||
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) {
|
||||
t := &Tracker{}
|
||||
func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn, m adapter.InboundContext, _ adapter.Rule, _ adapter.Outbound) N.PacketConn {
|
||||
l, err := limiter.GetLimiter(m.Inbound)
|
||||
if err != nil {
|
||||
log.Warn("get limiter for ", m.Inbound, " error: ", err)
|
||||
return conn, t
|
||||
}
|
||||
if l.CheckDomainRule(m.Domain) {
|
||||
conn.Close()
|
||||
log.Error("[", m.Inbound, "] ",
|
||||
"Limited ", m.User, " access to ", m.Domain, " by domain rule")
|
||||
return conn, t
|
||||
}
|
||||
if l.CheckProtocolRule(m.Protocol) {
|
||||
conn.Close()
|
||||
log.Error("[", m.Inbound, "] ",
|
||||
"Limited ", m.User, " use ", m.Domain, " by protocol rule")
|
||||
return conn, t
|
||||
return conn
|
||||
}
|
||||
ip := m.Source.Addr.String()
|
||||
if b, r := l.CheckLimit(m.User, ip, true); r {
|
||||
taguuid := format.UserTag(m.Inbound, m.User)
|
||||
if b, r := l.CheckLimit(taguuid, ip, false, false); r {
|
||||
conn.Close()
|
||||
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
|
||||
return conn, t
|
||||
return conn
|
||||
} else if b != nil {
|
||||
conn = rate.NewPacketConnCounter(conn, b)
|
||||
//conn = rate.NewPacketConnCounter(conn, b)
|
||||
}
|
||||
if h.EnableConnClear {
|
||||
var key int
|
||||
cc := &ConnClear{
|
||||
conns: map[int]io.Closer{
|
||||
0: conn,
|
||||
},
|
||||
if l != nil {
|
||||
destStr := m.Destination.AddrString()
|
||||
protocol := m.Destination.Network()
|
||||
if l.CheckDomainRule(destStr) {
|
||||
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 {
|
||||
cc = v.(*ConnClear)
|
||||
key = cc.AddConn(conn)
|
||||
if len(protocol) != 0 {
|
||||
if l.CheckProtocolRule(protocol) {
|
||||
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 {
|
||||
return counter.NewPacketConnCounter(conn, c.(*counter.TrafficCounter).GetCounter(m.User)), t
|
||||
var t *counter.TrafficCounter
|
||||
if c, ok := h.counter.Load(m.Inbound); !ok {
|
||||
t = counter.NewTrafficCounter()
|
||||
h.counter.Store(m.Inbound, t)
|
||||
} else {
|
||||
c := counter.NewTrafficCounter()
|
||||
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 nil
|
||||
}
|
||||
|
||||
func (h *HookServer) StoreFakeIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *HookServer) ClearConn(inbound string, user string) {
|
||||
if v, ok := h.connClears.Load(inbound + user); ok {
|
||||
v.(*ConnClear).ClearConn()
|
||||
h.connClears.Delete(inbound + user)
|
||||
}
|
||||
}
|
||||
|
||||
type Tracker struct {
|
||||
l []func()
|
||||
}
|
||||
|
||||
func (t *Tracker) AddLeave(f func()) {
|
||||
t.l = append(t.l, f)
|
||||
}
|
||||
|
||||
func (t *Tracker) Leave() {
|
||||
for i := range t.l {
|
||||
t.l[i]()
|
||||
t = c.(*counter.TrafficCounter)
|
||||
}
|
||||
conn = counter.NewPacketConnCounter(conn, t.GetCounter(m.User))
|
||||
return conn
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/sagernet/sing-box/inbound"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
)
|
||||
|
||||
type HttpNetworkConfig struct {
|
||||
@@ -40,25 +40,37 @@ type WsNetworkConfig struct {
|
||||
Headers map[string]string `json:"headers"`
|
||||
}
|
||||
|
||||
type GrpcNetworkConfig struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
}
|
||||
|
||||
type HttpupgradeNetworkConfig struct {
|
||||
Path string `json:"path"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (option.Inbound, error) {
|
||||
addr, err := netip.ParseAddr(c.ListenIP)
|
||||
if err != nil {
|
||||
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.ListenAddress)(&addr),
|
||||
ListenPort: uint16(info.Common.ServerPort),
|
||||
ProxyProtocol: c.SingOptions.EnableProxyProtocol,
|
||||
TCPFastOpen: c.SingOptions.TCPFastOpen,
|
||||
InboundOptions: option.InboundOptions{
|
||||
SniffEnabled: c.SingOptions.SniffEnabled,
|
||||
SniffOverrideDestination: c.SingOptions.SniffOverrideDestination,
|
||||
DomainStrategy: domainStrategy,
|
||||
},
|
||||
Listen: (*badoption.Addr)(&addr),
|
||||
ListenPort: uint16(info.Common.ServerPort),
|
||||
TCPFastOpen: c.SingOptions.TCPFastOpen,
|
||||
}
|
||||
var multiplex *option.InboundMultiplexOptions
|
||||
if c.SingOptions.Multiplex != nil {
|
||||
multiplexOption := option.InboundMultiplexOptions{
|
||||
Enabled: c.SingOptions.Multiplex.Enabled,
|
||||
Padding: c.SingOptions.Multiplex.Padding,
|
||||
Brutal: &option.BrutalOptions{
|
||||
Enabled: c.SingOptions.Multiplex.Brutal.Enabled,
|
||||
UpMbps: c.SingOptions.Multiplex.Brutal.UpMbps,
|
||||
DownMbps: c.SingOptions.Multiplex.Brutal.DownMbps,
|
||||
},
|
||||
}
|
||||
multiplex = &multiplexOption
|
||||
}
|
||||
var tls option.InboundTLSOptions
|
||||
switch info.Security {
|
||||
@@ -91,14 +103,14 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
Enabled: true,
|
||||
ShortID: []string{v.TlsSettings.ShortId},
|
||||
PrivateKey: v.TlsSettings.PrivateKey,
|
||||
Xver: v.TlsSettings.Xver,
|
||||
Xver: uint8(v.TlsSettings.Xver),
|
||||
Handshake: option.InboundRealityHandshakeOptions{
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: dest,
|
||||
ServerPort: uint16(port),
|
||||
},
|
||||
},
|
||||
MaxTimeDifference: option.Duration(mtd),
|
||||
MaxTimeDifference: badoption.Duration(mtd),
|
||||
}
|
||||
}
|
||||
in := option.Inbound{
|
||||
@@ -141,7 +153,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
var (
|
||||
path string
|
||||
ed int
|
||||
headers map[string]option.Listable[string]
|
||||
headers map[string]badoption.Listable[string]
|
||||
)
|
||||
if len(n.NetworkSettings) != 0 {
|
||||
network := WsNetworkConfig{}
|
||||
@@ -156,9 +168,9 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
}
|
||||
path = u.Path
|
||||
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 {
|
||||
headers[k] = option.Listable[string]{
|
||||
headers[k] = badoption.Listable[string]{
|
||||
v,
|
||||
}
|
||||
}
|
||||
@@ -170,30 +182,48 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
Headers: headers,
|
||||
}
|
||||
case "grpc":
|
||||
network := GrpcNetworkConfig{}
|
||||
if len(n.NetworkSettings) != 0 {
|
||||
err := json.Unmarshal(n.NetworkSettings, &t.GRPCOptions)
|
||||
err := json.Unmarshal(n.NetworkSettings, &network)
|
||||
if err != nil {
|
||||
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
|
||||
}
|
||||
}
|
||||
t.GRPCOptions = option.V2RayGRPCOptions{
|
||||
ServiceName: network.ServiceName,
|
||||
}
|
||||
case "httpupgrade":
|
||||
network := HttpupgradeNetworkConfig{}
|
||||
if len(n.NetworkSettings) != 0 {
|
||||
err := json.Unmarshal(n.NetworkSettings, &network)
|
||||
if err != nil {
|
||||
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
|
||||
}
|
||||
}
|
||||
t.HTTPUpgradeOptions = option.V2RayHTTPUpgradeOptions{
|
||||
Path: network.Path,
|
||||
Host: network.Host,
|
||||
}
|
||||
}
|
||||
if info.Type == "vless" {
|
||||
in.Type = "vless"
|
||||
in.VLESSOptions = option.VLESSInboundOptions{
|
||||
in.Options = &option.VLESSInboundOptions{
|
||||
ListenOptions: listen,
|
||||
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
|
||||
TLS: &tls,
|
||||
},
|
||||
Transport: &t,
|
||||
Multiplex: multiplex,
|
||||
}
|
||||
} else {
|
||||
in.Type = "vmess"
|
||||
in.VMessOptions = option.VMessInboundOptions{
|
||||
in.Options = &option.VMessInboundOptions{
|
||||
ListenOptions: listen,
|
||||
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
|
||||
TLS: &tls,
|
||||
},
|
||||
Transport: &t,
|
||||
Multiplex: multiplex,
|
||||
}
|
||||
}
|
||||
case "shadowsocks":
|
||||
@@ -203,25 +233,27 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
switch n.Cipher {
|
||||
case "2022-blake3-aes-128-gcm":
|
||||
keyLength = 16
|
||||
case "2022-blake3-aes-256-gcm":
|
||||
case "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305":
|
||||
keyLength = 32
|
||||
default:
|
||||
keyLength = 16
|
||||
}
|
||||
in.ShadowsocksOptions = option.ShadowsocksInboundOptions{
|
||||
ssoption := &option.ShadowsocksInboundOptions{
|
||||
ListenOptions: listen,
|
||||
Method: n.Cipher,
|
||||
Multiplex: multiplex,
|
||||
}
|
||||
p := make([]byte, keyLength)
|
||||
_, _ = rand.Read(p)
|
||||
randomPasswd := string(p)
|
||||
if strings.Contains(n.Cipher, "2022") {
|
||||
in.ShadowsocksOptions.Password = n.ServerKey
|
||||
ssoption.Password = n.ServerKey
|
||||
randomPasswd = base64.StdEncoding.EncodeToString([]byte(randomPasswd))
|
||||
}
|
||||
in.ShadowsocksOptions.Users = []option.ShadowsocksUser{{
|
||||
ssoption.Users = []option.ShadowsocksUser{{
|
||||
Password: randomPasswd,
|
||||
}}
|
||||
in.Options = ssoption
|
||||
case "trojan":
|
||||
n := info.Trojan
|
||||
t := option.V2RayTransportOptions{
|
||||
@@ -234,7 +266,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
var (
|
||||
path string
|
||||
ed int
|
||||
headers map[string]option.Listable[string]
|
||||
headers map[string]badoption.Listable[string]
|
||||
)
|
||||
if len(n.NetworkSettings) != 0 {
|
||||
network := WsNetworkConfig{}
|
||||
@@ -249,9 +281,9 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
}
|
||||
path = u.Path
|
||||
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 {
|
||||
headers[k] = option.Listable[string]{
|
||||
headers[k] = badoption.Listable[string]{
|
||||
v,
|
||||
}
|
||||
}
|
||||
@@ -263,29 +295,34 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
Headers: headers,
|
||||
}
|
||||
case "grpc":
|
||||
network := GrpcNetworkConfig{}
|
||||
if len(n.NetworkSettings) != 0 {
|
||||
err := json.Unmarshal(n.NetworkSettings, &t.GRPCOptions)
|
||||
err := json.Unmarshal(n.NetworkSettings, &network)
|
||||
if err != nil {
|
||||
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
|
||||
}
|
||||
}
|
||||
t.GRPCOptions = option.V2RayGRPCOptions{
|
||||
ServiceName: network.ServiceName,
|
||||
}
|
||||
default:
|
||||
t.Type = ""
|
||||
}
|
||||
in.Type = "trojan"
|
||||
in.TrojanOptions = option.TrojanInboundOptions{
|
||||
trojanoption := &option.TrojanInboundOptions{
|
||||
ListenOptions: listen,
|
||||
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
|
||||
TLS: &tls,
|
||||
},
|
||||
Transport: &t,
|
||||
Multiplex: multiplex,
|
||||
}
|
||||
if c.SingOptions.FallBackConfigs != nil {
|
||||
// fallback handling
|
||||
fallback := c.SingOptions.FallBackConfigs.FallBack
|
||||
fallbackPort, err := strconv.Atoi(fallback.ServerPort)
|
||||
if err == nil {
|
||||
in.TrojanOptions.Fallback = &option.ServerOptions{
|
||||
trojanoption.Fallback = &option.ServerOptions{
|
||||
Server: fallback.Server,
|
||||
ServerPort: uint16(fallbackPort),
|
||||
}
|
||||
@@ -293,12 +330,33 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
fallbackForALPNMap := c.SingOptions.FallBackConfigs.FallBackForALPN
|
||||
fallbackForALPN := make(map[string]*option.ServerOptions, len(fallbackForALPNMap))
|
||||
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":
|
||||
in.Type = "hysteria"
|
||||
in.HysteriaOptions = option.HysteriaInboundOptions{
|
||||
in.Options = &option.HysteriaInboundOptions{
|
||||
ListenOptions: listen,
|
||||
UpMbps: info.Hysteria.UpMbps,
|
||||
DownMbps: info.Hysteria.DownMbps,
|
||||
@@ -321,11 +379,12 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
Password: info.Hysteria2.ObfsType,
|
||||
}
|
||||
}
|
||||
in.Hysteria2Options = option.Hysteria2InboundOptions{
|
||||
ListenOptions: listen,
|
||||
UpMbps: info.Hysteria2.UpMbps,
|
||||
DownMbps: info.Hysteria2.DownMbps,
|
||||
Obfs: obfs,
|
||||
in.Options = &option.Hysteria2InboundOptions{
|
||||
ListenOptions: listen,
|
||||
UpMbps: info.Hysteria2.UpMbps,
|
||||
DownMbps: info.Hysteria2.DownMbps,
|
||||
IgnoreClientBandwidth: info.Hysteria2.Ignore_Client_Bandwidth,
|
||||
Obfs: obfs,
|
||||
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
|
||||
TLS: &tls,
|
||||
},
|
||||
@@ -335,31 +394,20 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
|
||||
}
|
||||
|
||||
func (b *Sing) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error {
|
||||
err := updateDNSConfig(info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build dns error: %s", err)
|
||||
}
|
||||
c, err := getInboundOptions(tag, info, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in, err := inbound.New(
|
||||
in := b.box.Inbound()
|
||||
err = in.Create(
|
||||
b.ctx,
|
||||
b.box.Router(),
|
||||
b.logFactory.NewLogger(F.ToString("inbound/", c.Type, "[", tag, "]")),
|
||||
c,
|
||||
nil,
|
||||
tag,
|
||||
c.Type,
|
||||
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 {
|
||||
return fmt.Errorf("add inbound error: %s", err)
|
||||
}
|
||||
@@ -367,11 +415,8 @@ func (b *Sing) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) e
|
||||
}
|
||||
|
||||
func (b *Sing) DelNode(tag string) error {
|
||||
err := b.inbounds[tag].Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("close inbound error: %s", err)
|
||||
}
|
||||
err = b.router.DelInbound(tag)
|
||||
in := b.box.Inbound()
|
||||
err := in.Remove(tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete inbound error: %s", err)
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ package sing
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
vCore "github.com/InazumaV/V2bX/core"
|
||||
"github.com/goccy/go-json"
|
||||
box "github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
)
|
||||
|
||||
var _ vCore.Core = (*Sing)(nil)
|
||||
@@ -29,7 +29,6 @@ type Sing struct {
|
||||
hookServer *HookServer
|
||||
router adapter.Router
|
||||
logFactory log.Factory
|
||||
inbounds map[string]adapter.Inbound
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -37,13 +36,15 @@ func init() {
|
||||
}
|
||||
|
||||
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{}
|
||||
if len(c.SingConfig.OriginalPath) != 0 {
|
||||
data, err := os.ReadFile(c.SingConfig.OriginalPath)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, fmt.Errorf("unmarshal original config error: %s", err)
|
||||
}
|
||||
@@ -57,49 +58,27 @@ func New(c *conf.CoreConfig) (vCore.Core, error) {
|
||||
options.NTP = &option.NTPOptions{
|
||||
Enabled: c.SingConfig.NtpConfig.Enable,
|
||||
WriteToSystem: true,
|
||||
Server: c.SingConfig.NtpConfig.Server,
|
||||
ServerPort: c.SingConfig.NtpConfig.ServerPort,
|
||||
ServerOptions: option.ServerOptions{
|
||||
Server: c.SingConfig.NtpConfig.Server,
|
||||
ServerPort: c.SingConfig.NtpConfig.ServerPort,
|
||||
},
|
||||
}
|
||||
os.Setenv("SING_DNS_PATH", "")
|
||||
options.DNS = &option.DNSOptions{}
|
||||
if c.SingConfig.DnsConfigPath != "" {
|
||||
f, err := os.OpenFile(c.SingConfig.DnsConfigPath, os.O_RDWR|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open or create sing dns config file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf(
|
||||
"Failed to read sing dns config from file '%v': %v. Using default DNS options",
|
||||
f.Name(), err))
|
||||
options.DNS = &option.DNSOptions{}
|
||||
} else {
|
||||
if err := json.Unmarshal(data, options.DNS); err != nil {
|
||||
log.Warn(fmt.Sprintf(
|
||||
"Failed to unmarshal sing dns config from file '%v': %v. Using default DNS options",
|
||||
f.Name(), err))
|
||||
options.DNS = &option.DNSOptions{}
|
||||
}
|
||||
}
|
||||
os.Setenv("SING_DNS_PATH", c.SingConfig.DnsConfigPath)
|
||||
}
|
||||
b, err := box.New(box.Options{
|
||||
Context: context.Background(),
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hs := NewHookServer(c.SingConfig.EnableConnClear)
|
||||
b.Router().SetClashServer(hs)
|
||||
hs := NewHookServer()
|
||||
b.Router().AppendTracker(hs)
|
||||
return &Sing{
|
||||
ctx: b.Router().GetCtx(),
|
||||
box: b,
|
||||
hookServer: hs,
|
||||
router: b.Router(),
|
||||
logFactory: b.LogFactory(),
|
||||
inbounds: make(map[string]adapter.Inbound),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -117,6 +96,8 @@ func (b *Sing) Protocols() []string {
|
||||
"vless",
|
||||
"shadowsocks",
|
||||
"trojan",
|
||||
"tuic",
|
||||
"anytls",
|
||||
"hysteria",
|
||||
"hysteria2",
|
||||
}
|
||||
|
||||
@@ -7,33 +7,42 @@ import (
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
"github.com/InazumaV/V2bX/common/counter"
|
||||
"github.com/InazumaV/V2bX/core"
|
||||
"github.com/sagernet/sing-box/inbound"
|
||||
"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) {
|
||||
in, found := b.box.Inbound().Get(p.Tag)
|
||||
if !found {
|
||||
return 0, errors.New("the inbound not found")
|
||||
}
|
||||
switch p.NodeInfo.Type {
|
||||
case "vmess", "vless":
|
||||
if p.NodeInfo.Type == "vless" {
|
||||
us := make([]option.VLESSUser, len(p.Users))
|
||||
for i := range p.Users {
|
||||
us[i] = option.VLESSUser{
|
||||
Name: p.Users[i].Uuid,
|
||||
Flow: p.VAllss.Flow,
|
||||
UUID: p.Users[i].Uuid,
|
||||
}
|
||||
case "vless":
|
||||
us := make([]option.VLESSUser, len(p.Users))
|
||||
for i := range p.Users {
|
||||
us[i] = option.VLESSUser{
|
||||
Name: p.Users[i].Uuid,
|
||||
Flow: p.VAllss.Flow,
|
||||
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":
|
||||
us := make([]option.ShadowsocksUser, len(p.Users))
|
||||
for i := range p.Users {
|
||||
@@ -49,7 +58,7 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
err = b.inbounds[p.Tag].(*inbound.ShadowsocksMulti).AddUsers(us)
|
||||
err = in.(*shadowsocks.MultiInbound).AddUsers(us)
|
||||
case "trojan":
|
||||
us := make([]option.TrojanUser, len(p.Users))
|
||||
for i := range p.Users {
|
||||
@@ -58,7 +67,19 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
|
||||
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":
|
||||
us := make([]option.HysteriaUser, len(p.Users))
|
||||
for i := range p.Users {
|
||||
@@ -67,7 +88,7 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
|
||||
AuthString: p.Users[i].Uuid,
|
||||
}
|
||||
}
|
||||
err = b.inbounds[p.Tag].(*inbound.Hysteria).AddUsers(us)
|
||||
err = in.(*hysteria.Inbound).AddUsers(us)
|
||||
case "hysteria2":
|
||||
us := make([]option.Hysteria2User, len(p.Users))
|
||||
id := make([]int, len(p.Users))
|
||||
@@ -78,7 +99,16 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
|
||||
}
|
||||
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 {
|
||||
return 0, err
|
||||
@@ -103,29 +133,32 @@ type UserDeleter interface {
|
||||
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
|
||||
if i, ok := b.inbounds[tag]; ok {
|
||||
switch i.Type() {
|
||||
if i, found := b.box.Inbound().Get(tag); found {
|
||||
switch info.Type {
|
||||
case "vmess":
|
||||
del = i.(*inbound.VMess)
|
||||
del = i.(*vmess.Inbound)
|
||||
case "vless":
|
||||
del = i.(*inbound.VLESS)
|
||||
del = i.(*vless.Inbound)
|
||||
case "shadowsocks":
|
||||
del = i.(*inbound.ShadowsocksMulti)
|
||||
del = i.(*shadowsocks.MultiInbound)
|
||||
case "trojan":
|
||||
del = i.(*inbound.Trojan)
|
||||
del = i.(*trojan.Inbound)
|
||||
case "tuic":
|
||||
del = i.(*tuic.Inbound)
|
||||
case "hysteria":
|
||||
del = i.(*inbound.Hysteria)
|
||||
del = i.(*hysteria.Inbound)
|
||||
case "hysteria2":
|
||||
del = i.(*inbound.Hysteria2)
|
||||
del = i.(*hysteria2.Inbound)
|
||||
case "anytls":
|
||||
del = i.(*anytls.Inbound)
|
||||
}
|
||||
} else {
|
||||
return errors.New("the inbound not found")
|
||||
}
|
||||
uuids := make([]string, len(users))
|
||||
for i := range users {
|
||||
b.hookServer.ClearConn(tag, users[i].Uuid)
|
||||
uuids[i] = users[i].Uuid
|
||||
}
|
||||
err := del.DelUsers(uuids)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v4.23.4
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc v3.21.12
|
||||
// source: config.proto
|
||||
|
||||
package dispatcher
|
||||
@@ -28,11 +28,9 @@ type SessionConfig struct {
|
||||
|
||||
func (x *SessionConfig) Reset() {
|
||||
*x = SessionConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SessionConfig) String() string {
|
||||
@@ -43,7 +41,7 @@ func (*SessionConfig) ProtoMessage() {}
|
||||
|
||||
func (x *SessionConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
@@ -68,11 +66,9 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
mi := &file_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
@@ -83,7 +79,7 @@ func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
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_goTypes = []interface{}{
|
||||
var file_config_proto_goTypes = []any{
|
||||
(*SessionConfig)(nil), // 0: v2bx.core.app.dispatcher.SessionConfig
|
||||
(*Config)(nil), // 1: v2bx.core.app.dispatcher.Config
|
||||
}
|
||||
@@ -158,32 +154,6 @@ func file_config_proto_init() {
|
||||
if File_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SessionConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -5,6 +5,7 @@ package dispatcher
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/log"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
@@ -29,7 +31,7 @@ import (
|
||||
"github.com/xtls/xray-core/transport/pipe"
|
||||
)
|
||||
|
||||
var errSniffingTimeout = newError("timeout on sniffing")
|
||||
var errSniffingTimeout = errors.New("timeout on sniffing")
|
||||
|
||||
type cachedReader struct {
|
||||
sync.Mutex
|
||||
@@ -37,17 +39,21 @@ type cachedReader struct {
|
||||
cache buf.MultiBuffer
|
||||
}
|
||||
|
||||
func (r *cachedReader) Cache(b *buf.Buffer) {
|
||||
mb, _ := r.reader.ReadMultiBufferTimeout(time.Millisecond * 100)
|
||||
func (r *cachedReader) Cache(b *buf.Buffer, deadline time.Duration) error {
|
||||
mb, err := r.reader.ReadMultiBufferTimeout(deadline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Lock()
|
||||
if !mb.IsEmpty() {
|
||||
r.cache, _ = buf.MergeMulti(r.cache, mb)
|
||||
}
|
||||
b.Clear()
|
||||
rawBytes := b.Extend(buf.Size)
|
||||
rawBytes := b.Extend(min(r.cache.Len(), b.Cap()))
|
||||
n := r.cache.Copy(rawBytes)
|
||||
b.Resize(0, int32(n))
|
||||
r.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *cachedReader) readInternal() buf.MultiBuffer {
|
||||
@@ -96,18 +102,18 @@ type DefaultDispatcher struct {
|
||||
router routing.Router
|
||||
policy policy.Manager
|
||||
stats stats.Manager
|
||||
dns dns.Client
|
||||
fdns dns.FakeDNSEngine
|
||||
Wm *WriterManager
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
|
||||
d := new(DefaultDispatcher)
|
||||
if err := core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager, dc dns.Client) error {
|
||||
core.RequireFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||
core.OptionalFeatures(ctx, func(fdns dns.FakeDNSEngine) {
|
||||
d.fdns = fdns
|
||||
})
|
||||
return d.Init(config.(*Config), om, router, pm, sm, dc)
|
||||
return d.Init(config.(*Config), om, router, pm, sm)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -116,12 +122,14 @@ func init() {
|
||||
}
|
||||
|
||||
// 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.router = router
|
||||
d.policy = pm
|
||||
d.stats = sm
|
||||
d.dns = dns
|
||||
d.Wm = &WriterManager{
|
||||
writers: make(map[string]map[*ManagedWriter]struct{}),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,9 +151,14 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
|
||||
uplinkReader, uplinkWriter := pipe.New(opt...)
|
||||
downlinkReader, downlinkWriter := pipe.New(opt...)
|
||||
|
||||
managedWriter := &ManagedWriter{
|
||||
writer: uplinkWriter,
|
||||
manager: d.Wm,
|
||||
}
|
||||
|
||||
inboundLink := &transport.Link{
|
||||
Reader: downlinkReader,
|
||||
Writer: uplinkWriter,
|
||||
Writer: managedWriter,
|
||||
}
|
||||
|
||||
outboundLink := &transport.Link{
|
||||
@@ -157,6 +170,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
|
||||
var user *protocol.MemoryUser
|
||||
if sessionInbound != nil {
|
||||
user = sessionInbound.User
|
||||
sessionInbound.CanSpliceCopy = 3
|
||||
}
|
||||
|
||||
var limit *limiter.Limiter
|
||||
@@ -164,24 +178,25 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
|
||||
if user != nil && len(user.Email) > 0 {
|
||||
limit, err = limiter.GetLimiter(sessionInbound.Tag)
|
||||
if err != nil {
|
||||
newError("get limiter ", sessionInbound.Tag, " error: ", err).AtError().WriteToLog()
|
||||
errors.LogInfo(ctx, "get limiter ", sessionInbound.Tag, " error: ", err)
|
||||
common.Close(outboundLink.Writer)
|
||||
common.Close(inboundLink.Writer)
|
||||
common.Interrupt(outboundLink.Reader)
|
||||
common.Interrupt(inboundLink.Reader)
|
||||
return nil, nil, nil, newError("get limiter ", sessionInbound.Tag, " error: ", err)
|
||||
return nil, nil, nil, errors.New("get limiter ", sessionInbound.Tag, " error: ", err)
|
||||
}
|
||||
// Speed Limit and Device Limit
|
||||
w, reject := limit.CheckLimit(user.Email,
|
||||
sessionInbound.Source.Address.IP().String(),
|
||||
network == net.Network_TCP)
|
||||
network == net.Network_TCP,
|
||||
sessionInbound.Source.Network == net.Network_TCP)
|
||||
if reject {
|
||||
newError("Limited ", user.Email, " by conn or ip").AtWarning().WriteToLog()
|
||||
errors.LogInfo(ctx, "Limited ", user.Email, " by conn or ip")
|
||||
common.Close(outboundLink.Writer)
|
||||
common.Close(inboundLink.Writer)
|
||||
common.Interrupt(outboundLink.Reader)
|
||||
common.Interrupt(inboundLink.Reader)
|
||||
return nil, nil, nil, newError("Limited ", user.Email, " by conn or ip")
|
||||
return nil, nil, nil, errors.New("Limited ", user.Email, " by conn or ip")
|
||||
}
|
||||
if w != nil {
|
||||
inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w)
|
||||
@@ -207,6 +222,8 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
|
||||
}
|
||||
}
|
||||
}
|
||||
managedWriter.email = user.Email
|
||||
d.Wm.AddWriter(managedWriter)
|
||||
|
||||
return inboundLink, outboundLink, limit, nil
|
||||
}
|
||||
@@ -217,8 +234,20 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
|
||||
return false
|
||||
}
|
||||
for _, d := range request.ExcludeForDomain {
|
||||
if strings.ToLower(domain) == d {
|
||||
return false
|
||||
if strings.HasPrefix(d, "regexp:") {
|
||||
pattern := d[7:]
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
errors.LogInfo(ctx, "Unable to compile regex")
|
||||
continue
|
||||
}
|
||||
if re.MatchString(domain) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if strings.ToLower(domain) == d {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
protocolString := result.Protocol()
|
||||
@@ -226,12 +255,12 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
|
||||
protocolString = resComp.ProtocolForDomainResult()
|
||||
}
|
||||
for _, p := range request.OverrideDestinationForProtocol {
|
||||
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(protocolString, p) {
|
||||
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) {
|
||||
return true
|
||||
}
|
||||
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
|
||||
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
|
||||
newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "Using sniffer ", protocolString, " since the fake DNS missed")
|
||||
return true
|
||||
}
|
||||
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
|
||||
@@ -249,11 +278,14 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
if !destination.IsValid() {
|
||||
panic("Dispatcher: Invalid destination.")
|
||||
}
|
||||
ob := &session.Outbound{
|
||||
OriginalTarget: destination,
|
||||
Target: destination,
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
if len(outbounds) == 0 {
|
||||
outbounds = []*session.Outbound{{}}
|
||||
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||
}
|
||||
ctx = session.ContextWithOutbound(ctx, ob)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
ob.OriginalTarget = destination
|
||||
ob.Target = destination
|
||||
content := session.ContentFromContext(ctx)
|
||||
if content == nil {
|
||||
content = new(session.Content)
|
||||
@@ -278,7 +310,7 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
}
|
||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
protocol := result.Protocol()
|
||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||
@@ -303,13 +335,16 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
|
||||
// DispatchLink implements routing.Dispatcher.
|
||||
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
|
||||
if !destination.IsValid() {
|
||||
return newError("Dispatcher: Invalid destination.")
|
||||
return errors.New("Dispatcher: Invalid destination.")
|
||||
}
|
||||
ob := &session.Outbound{
|
||||
OriginalTarget: destination,
|
||||
Target: destination,
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
if len(outbounds) == 0 {
|
||||
outbounds = []*session.Outbound{{}}
|
||||
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||
}
|
||||
ctx = session.ContextWithOutbound(ctx, ob)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
ob.OriginalTarget = destination
|
||||
ob.Target = destination
|
||||
content := session.ContentFromContext(ctx)
|
||||
if content == nil {
|
||||
content = new(session.Content)
|
||||
@@ -329,14 +364,14 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
}
|
||||
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
|
||||
domain := result.Domain()
|
||||
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "sniffed domain: ", domain)
|
||||
destination.Address = net.ParseAddress(domain)
|
||||
protocol := result.Protocol()
|
||||
if resComp, ok := result.(SnifferResultComposite); ok {
|
||||
protocol = resComp.ProtocolForDomainResult()
|
||||
}
|
||||
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
|
||||
}
|
||||
if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP {
|
||||
@@ -344,7 +379,6 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
destination.Address.Family()
|
||||
}
|
||||
d.routedDispatch(ctx, outbound, destination, nil, content.Protocol)
|
||||
}
|
||||
@@ -353,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) {
|
||||
payload := buf.New()
|
||||
payload := buf.NewWithSize(32767)
|
||||
defer payload.Release()
|
||||
|
||||
sniffer := NewSniffer(ctx)
|
||||
@@ -365,26 +399,36 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
|
||||
}
|
||||
|
||||
contentResult, contentErr := func() (SniffResult, error) {
|
||||
cacheDeadline := 200 * time.Millisecond
|
||||
totalAttempt := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
totalAttempt++
|
||||
if totalAttempt > 2 {
|
||||
return nil, errSniffingTimeout
|
||||
cachingStartingTimeStamp := time.Now()
|
||||
err := cReader.Cache(payload, cacheDeadline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cachingTimeElapsed := time.Since(cachingStartingTimeStamp)
|
||||
cacheDeadline -= cachingTimeElapsed
|
||||
|
||||
cReader.Cache(payload)
|
||||
if !payload.IsEmpty() {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
totalAttempt++
|
||||
}
|
||||
if payload.IsFull() {
|
||||
return nil, errUnknownContent
|
||||
if totalAttempt >= 2 || cacheDeadline <= 0 {
|
||||
return nil, errSniffingTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,34 +443,16 @@ 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) {
|
||||
ob := session.OutboundFromContext(ctx)
|
||||
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
|
||||
proxied := hosts.LookupHosts(ob.Target.String())
|
||||
if proxied != nil {
|
||||
ro := ob.RouteTarget == destination
|
||||
destination.Address = *proxied
|
||||
if ro {
|
||||
ob.RouteTarget = destination
|
||||
} else {
|
||||
ob.Target = destination
|
||||
}
|
||||
}
|
||||
}
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
|
||||
sessionInbound := session.InboundFromContext(ctx)
|
||||
if sessionInbound.User != 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 {
|
||||
if l == nil {
|
||||
var err error
|
||||
l, err = limiter.GetLimiter(sessionInbound.Tag)
|
||||
if err != nil {
|
||||
newError("get limiter ", sessionInbound.Tag, " error: ", err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogError(ctx, "get limiter ", sessionInbound.Tag, " error: ", err)
|
||||
}
|
||||
}
|
||||
if l != nil {
|
||||
@@ -437,20 +463,20 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
destStr = destination.Address.IP().String()
|
||||
}
|
||||
if l.CheckDomainRule(destStr) {
|
||||
newError(fmt.Sprintf(
|
||||
errors.LogError(ctx, fmt.Sprintf(
|
||||
"User %s access domain %s reject by rule",
|
||||
sessionInbound.User.Email,
|
||||
destStr)).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
destStr))
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
}
|
||||
if len(protocol) != 0 {
|
||||
if l.CheckProtocolRule(protocol) {
|
||||
newError(fmt.Sprintf(
|
||||
errors.LogError(ctx, fmt.Sprintf(
|
||||
"User %s access protocol %s reject by rule",
|
||||
sessionInbound.User.Email,
|
||||
protocol)).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
protocol))
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
@@ -468,10 +494,10 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
ctx = session.SetForcedOutboundTagToContext(ctx, "")
|
||||
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
|
||||
isPickRoute = 1
|
||||
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]")
|
||||
handler = h
|
||||
} else {
|
||||
newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogError(ctx, "non existing tag for platform initialized detour: ", forcedOutboundTag)
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
@@ -481,31 +507,32 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
|
||||
outTag := route.GetOutboundTag()
|
||||
if h := d.ohm.GetHandler(outTag); h != nil {
|
||||
isPickRoute = 2
|
||||
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx))
|
||||
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
|
||||
} else {
|
||||
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogWarning(ctx, "non existing outTag: ", outTag)
|
||||
}
|
||||
} else {
|
||||
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "default route for ", destination)
|
||||
}
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
handler = d.ohm.GetHandler(inTag)
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
handler = d.ohm.GetDefaultHandler()
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
newError("default outbound handler not exist").WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "default outbound handler not exist")
|
||||
common.Close(link.Writer)
|
||||
common.Interrupt(link.Reader)
|
||||
return
|
||||
}
|
||||
|
||||
ob.Tag = handler.Tag()
|
||||
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
|
||||
if tag := handler.Tag(); tag != "" {
|
||||
if inTag == "" {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package dispatcher
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -22,15 +23,16 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
|
||||
}
|
||||
|
||||
if fakeDNSEngine == nil {
|
||||
errNotInit := newError("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
||||
errNotInit := errors.New("FakeDNSEngine is not initialized, but such a sniffer is used").AtError()
|
||||
return protocolSnifferWithMetadata{}, errNotInit
|
||||
}
|
||||
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
|
||||
Target := session.OutboundFromContext(ctx).Target
|
||||
if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP {
|
||||
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address)
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
if ob.Target.Network == net.Network_TCP || ob.Target.Network == net.Network_UDP {
|
||||
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(ob.Target.Address)
|
||||
if domainFromFakeDNS != "" {
|
||||
newError("fake dns got domain: ", domainFromFakeDNS, " for ip: ", Target.Address.String()).WriteToLog(session.ExportIDToError(ctx))
|
||||
errors.LogInfo(ctx, "fake dns got domain: ", domainFromFakeDNS, " for ip: ", ob.Target.Address.String())
|
||||
return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
|
||||
}
|
||||
}
|
||||
@@ -38,7 +40,7 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
|
||||
if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
|
||||
ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
|
||||
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
|
||||
inPool := fkr0.IsIPInIPPool(Target.Address)
|
||||
inPool := fkr0.IsIPInIPPool(ob.Target.Address)
|
||||
ipAddressInRangeValue.addressInRange = &inPool
|
||||
}
|
||||
}
|
||||
@@ -108,10 +110,10 @@ func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWit
|
||||
}
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
|
||||
errors.LogDebug(ctx, "ip address not in fake dns range, return as is")
|
||||
return nil, common.ErrNoClue
|
||||
}
|
||||
newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
|
||||
errors.LogWarning(ctx, "fake dns sniffer did not set address in range option, assume false.")
|
||||
return nil, common.ErrNoClue
|
||||
},
|
||||
metadataSniffer: false,
|
||||
|
||||
61
core/xray/app/dispatcher/linkmanager.go
Normal file
61
core/xray/app/dispatcher/linkmanager.go
Normal 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)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol/bittorrent"
|
||||
"github.com/xtls/xray-core/common/protocol/http"
|
||||
@@ -34,7 +35,7 @@ type Sniffer struct {
|
||||
func NewSniffer(ctx context.Context) *Sniffer {
|
||||
ret := &Sniffer{
|
||||
sniffer: []protocolSnifferWithMetadata{
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return 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 bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
|
||||
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
|
||||
@@ -52,7 +53,7 @@ func NewSniffer(ctx context.Context) *Sniffer {
|
||||
return ret
|
||||
}
|
||||
|
||||
var errUnknownContent = newError("unknown content")
|
||||
var errUnknownContent = errors.New("unknown content")
|
||||
|
||||
func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
|
||||
var pendingSniffer []protocolSnifferWithMetadata
|
||||
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
_ "github.com/xtls/xray-core/proxy/http"
|
||||
_ "github.com/xtls/xray-core/proxy/loopback"
|
||||
_ "github.com/xtls/xray-core/proxy/shadowsocks"
|
||||
_ "github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||
_ "github.com/xtls/xray-core/proxy/socks"
|
||||
_ "github.com/xtls/xray-core/proxy/trojan"
|
||||
_ "github.com/xtls/xray-core/proxy/vless/inbound"
|
||||
@@ -51,13 +52,10 @@ import (
|
||||
//_ "github.com/xtls/xray-core/proxy/wireguard"
|
||||
|
||||
// Transports
|
||||
//_ "github.com/xtls/xray-core/transport/internet/domainsocket"
|
||||
_ "github.com/xtls/xray-core/transport/internet/grpc"
|
||||
_ "github.com/xtls/xray-core/transport/internet/http"
|
||||
|
||||
//_ "github.com/xtls/xray-core/transport/internet/kcp"
|
||||
//_ "github.com/xtls/xray-core/transport/internet/quic"
|
||||
_ "github.com/xtls/xray-core/transport/internet/kcp"
|
||||
_ "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/tls"
|
||||
_ "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/utp"
|
||||
_ "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"
|
||||
)
|
||||
|
||||
@@ -26,8 +26,12 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
|
||||
err = buildV2ray(option, nodeInfo, in)
|
||||
network = nodeInfo.VAllss.Network
|
||||
case "trojan":
|
||||
err = buildTrojan(option, in)
|
||||
network = "tcp"
|
||||
err = buildTrojan(option, nodeInfo, in)
|
||||
if nodeInfo.Trojan.Network != "" {
|
||||
network = nodeInfo.Trojan.Network
|
||||
} else {
|
||||
network = "tcp"
|
||||
}
|
||||
case "shadowsocks":
|
||||
err = buildShadowsocks(option, nodeInfo, in)
|
||||
network = "tcp"
|
||||
@@ -69,8 +73,13 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
|
||||
in.StreamSetting.TCPSettings = tcpSetting
|
||||
}
|
||||
case "ws":
|
||||
in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
|
||||
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol} //Enable proxy protocol
|
||||
if in.StreamSetting.WSSettings != nil {
|
||||
in.StreamSetting.WSSettings.AcceptProxyProtocol = option.XrayOptions.EnableProxyProtocol
|
||||
} else {
|
||||
in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
|
||||
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
|
||||
} //Enable proxy protocol
|
||||
}
|
||||
default:
|
||||
socketConfig := &coreConf.SocketConfig{
|
||||
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
|
||||
@@ -105,9 +114,17 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
|
||||
// Reality
|
||||
in.StreamSetting.Security = "reality"
|
||||
v := nodeInfo.VAllss
|
||||
dest := v.TlsSettings.Dest
|
||||
if dest == "" {
|
||||
dest = v.TlsSettings.ServerName
|
||||
}
|
||||
xver := v.TlsSettings.Xver
|
||||
if xver == 0 {
|
||||
xver = v.RealityConfig.Xver
|
||||
}
|
||||
d, err := json.Marshal(fmt.Sprintf(
|
||||
"%s:%s",
|
||||
v.TlsSettings.ServerName,
|
||||
dest,
|
||||
v.TlsSettings.ServerPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal reality dest error: %s", err)
|
||||
@@ -115,7 +132,7 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
|
||||
mtd, _ := time.ParseDuration(v.RealityConfig.MaxTimeDiff)
|
||||
in.StreamSetting.REALITYSettings = &coreConf.REALITYConfig{
|
||||
Dest: d,
|
||||
Xver: v.RealityConfig.Xver,
|
||||
Xver: xver,
|
||||
ServerNames: []string{v.TlsSettings.ServerName},
|
||||
PrivateKey: v.TlsSettings.PrivateKey,
|
||||
MinClientVer: v.RealityConfig.MinClientVer,
|
||||
@@ -123,6 +140,7 @@ func buildInbound(option *conf.Options, nodeInfo *panel.NodeInfo, tag string) (*
|
||||
MaxTimeDiff: uint64(mtd.Microseconds()),
|
||||
ShortIds: []string{v.TlsSettings.ShortId},
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
in.Tag = tag
|
||||
@@ -172,7 +190,7 @@ func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCon
|
||||
return nil
|
||||
}
|
||||
|
||||
t := coreConf.TransportProtocol(nodeInfo.VAllss.Network)
|
||||
t := coreConf.TransportProtocol(v.Network)
|
||||
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
|
||||
switch v.Network {
|
||||
case "tcp":
|
||||
@@ -186,18 +204,29 @@ func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCon
|
||||
return fmt.Errorf("unmarshal ws settings error: %s", err)
|
||||
}
|
||||
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 "httpupgrade":
|
||||
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.HTTPUPGRADESettings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal httpupgrade settings error: %s", err)
|
||||
}
|
||||
case "splithttp", "xhttp":
|
||||
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.SplitHTTPSettings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal xhttp settings error: %s", err)
|
||||
}
|
||||
default:
|
||||
return errors.New("the network type is not vail")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildTrojan(config *conf.Options, inbound *coreConf.InboundDetourConfig) error {
|
||||
func buildTrojan(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreConf.InboundDetourConfig) error {
|
||||
inbound.Protocol = "trojan"
|
||||
v := nodeInfo.Trojan
|
||||
if config.XrayOptions.EnableFallback {
|
||||
// Set fallback
|
||||
fallbackConfigs, err := buildTrojanFallbacks(config.XrayOptions.FallBackConfigs)
|
||||
@@ -215,8 +244,31 @@ func buildTrojan(config *conf.Options, inbound *coreConf.InboundDetourConfig) er
|
||||
s := []byte("{}")
|
||||
inbound.Settings = (*json.RawMessage)(&s)
|
||||
}
|
||||
t := coreConf.TransportProtocol("tcp")
|
||||
network := v.Network
|
||||
if network == "" {
|
||||
network = "tcp"
|
||||
}
|
||||
t := coreConf.TransportProtocol(network)
|
||||
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
|
||||
switch network {
|
||||
case "tcp":
|
||||
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.TCPSettings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal tcp settings error: %s", err)
|
||||
}
|
||||
case "ws":
|
||||
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.WSSettings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal ws settings error: %s", err)
|
||||
}
|
||||
case "grpc":
|
||||
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.GRPCSettings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal grpc settings error: %s", err)
|
||||
}
|
||||
default:
|
||||
return errors.New("the network type is not vail")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
conf2 "github.com/InazumaV/V2bX/conf"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/infra/conf"
|
||||
)
|
||||
@@ -18,8 +17,7 @@ func buildOutbound(config *conf2.Options, tag string) (*core.OutboundHandlerConf
|
||||
|
||||
// Build Send IP address
|
||||
if config.SendIP != "" {
|
||||
ipAddress := net.ParseAddress(config.SendIP)
|
||||
outboundDetourConfig.SendThrough = &conf.Address{Address: ipAddress}
|
||||
outboundDetourConfig.SendThrough = &config.SendIP
|
||||
}
|
||||
|
||||
// Freedom Protocol setting
|
||||
|
||||
@@ -38,9 +38,11 @@ func buildSSUser(tag string, userInfo *panel.UserInfo, cypher string, serverKey
|
||||
keyLength = 16
|
||||
case "2022-blake3-aes-256-gcm":
|
||||
keyLength = 32
|
||||
case "2022-blake3-chacha20-poly1305":
|
||||
keyLength = 32
|
||||
}
|
||||
ssAccount := &shadowsocks_2022.User{
|
||||
Key: base64.StdEncoding.EncodeToString([]byte(userInfo.Uuid[:keyLength])),
|
||||
ssAccount := &shadowsocks_2022.Account{
|
||||
Key: base64.StdEncoding.EncodeToString([]byte(userInfo.Uuid[:keyLength])),
|
||||
}
|
||||
return &protocol.User{
|
||||
Level: 0,
|
||||
|
||||
@@ -27,7 +27,7 @@ func (c *Xray) GetUserManager(tag string) (proxy.UserManager, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user manager error: %s", err)
|
||||
@@ -43,6 +43,7 @@ func (c *Xray) DelUsers(users []panel.UserInfo, tag string) error {
|
||||
down = "user>>>" + user + ">>>traffic>>>downlink"
|
||||
c.shm.UnregisterCounter(up)
|
||||
c.shm.UnregisterCounter(down)
|
||||
c.dispatcher.Wm.RemoveWritersForUser(user)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
{
|
||||
"Type": "sing",
|
||||
"Log": {
|
||||
"Level": "error",
|
||||
"Level": "info",
|
||||
"Timestamp": true
|
||||
},
|
||||
"NTP": {
|
||||
"Enable": true,
|
||||
"Enable": false,
|
||||
"Server": "time.apple.com",
|
||||
"ServerPort": 0
|
||||
}
|
||||
},
|
||||
"OriginalPath": "/etc/V2bX/sing_origin.json"
|
||||
}
|
||||
],
|
||||
"Nodes": [
|
||||
@@ -27,20 +28,18 @@
|
||||
"Timeout": 30,
|
||||
"ListenIP": "0.0.0.0",
|
||||
"SendIP": "0.0.0.0",
|
||||
"EnableProxyProtocol": false,
|
||||
"EnableDNS": true,
|
||||
"DomainStrategy": "ipv4_only",
|
||||
"LimitConfig": {
|
||||
"EnableRealtime": false,
|
||||
"SpeedLimit": 0,
|
||||
"IPLimit": 0,
|
||||
"ConnLimit": 0,
|
||||
"EnableDynamicSpeedLimit": false,
|
||||
"DynamicSpeedLimitConfig": {
|
||||
"Periodic": 60,
|
||||
"Traffic": 1000,
|
||||
"SpeedLimit": 100,
|
||||
"ExpireTime": 60
|
||||
"DeviceOnlineMinTraffic": 200,
|
||||
"TCPFastOpen": false,
|
||||
"SniffEnabled": true,
|
||||
"CertConfig": {
|
||||
"CertMode": "self",
|
||||
"RejectUnknownSni": false,
|
||||
"CertDomain": "example.com",
|
||||
"CertFile": "/etc/V2bX/fullchain.cer",
|
||||
"KeyFile": "/etc/V2bX/cert.key",
|
||||
"Provider": "cloudflare",
|
||||
"DNSEnv": {
|
||||
"EnvName": "env1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
404
go.mod
404
go.mod
@@ -1,234 +1,332 @@
|
||||
module github.com/InazumaV/V2bX
|
||||
|
||||
go 1.22
|
||||
go 1.24
|
||||
|
||||
toolchain go1.22.0
|
||||
toolchain go1.24.5
|
||||
|
||||
require (
|
||||
github.com/apernet/hysteria/core v1.3.5-0.20240201034858-bb99579bb92c
|
||||
github.com/apernet/hysteria/extras v0.0.0-20240201034858-bb99579bb92c
|
||||
github.com/beevik/ntp v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-acme/lego/v4 v4.13.2
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/apernet/hysteria/core/v2 v2.6.2
|
||||
github.com/apernet/hysteria/extras/v2 v2.6.2
|
||||
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431
|
||||
github.com/beevik/ntp v1.4.4-0.20240716062501-06ef196b89ec
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/go-acme/lego/v4 v4.21.1-0.20241220151055-ee7a9e4fa04f
|
||||
github.com/go-resty/resty/v2 v2.16.2
|
||||
github.com/goccy/go-json v0.10.4
|
||||
github.com/hashicorp/go-multierror v1.1.2-0.20241119060415-613124da9385
|
||||
github.com/juju/ratelimit v1.0.2
|
||||
github.com/sagernet/sing v0.3.1-beta.2
|
||||
github.com/sagernet/sing-box v1.9.0
|
||||
github.com/sagernet/sing v0.7.0-beta.1.0.20250722151551-64142925accb
|
||||
github.com/sagernet/sing-box v1.12.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/xtls/xray-core v1.8.8-0.20240219151643-ad3dd3df56b9
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/sys v0.17.0
|
||||
google.golang.org/protobuf v1.32.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/xtls/xray-core v1.250608.1-0.20250723105259-dbd912568602
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/sys v0.34.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
berty.tech/go-libtor v1.0.385 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/auth v0.13.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/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.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.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/privatedns/armprivatedns v1.3.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/autorest v0.11.24 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // 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/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f // indirect
|
||||
github.com/aws/aws-sdk-go v1.39.0 // 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/anytls/sing-anytls v0.0.9-0.20250508103614-8bc6dd599731 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
|
||||
github.com/aws/smithy-go v1.22.1 // indirect
|
||||
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/caddyserver/certmagic v0.20.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // 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/civo/civogo v0.3.11 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.70.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // 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/cretz/bine v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // 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/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.2.0 // indirect
|
||||
github.com/exoscale/egoscale v0.100.1 // indirect
|
||||
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||
github.com/exoscale/egoscale/v3 v3.1.7 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // 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/go-chi/chi/v5 v5.0.11 // 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-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // 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-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // 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-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.0.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // 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/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.0.0 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.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/s2a-go v0.1.8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.14.1 // 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/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // 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/illarion/gonotify/v2 v2.0.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // 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/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||
github.com/libdns/alidns v1.0.3 // indirect
|
||||
github.com/libdns/cloudflare v0.1.0 // indirect
|
||||
github.com/libdns/libdns v0.2.1 // indirect
|
||||
github.com/linode/linodego v1.17.2 // indirect
|
||||
github.com/liquidweb/go-lwApi v0.0.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect
|
||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // 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-go v1.6.3 // indirect
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||
github.com/mdlayher/sdnotify v1.0.0 // 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/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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/desec v0.7.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
|
||||
github.com/nrdcg/desec v0.10.0 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.8.2 // indirect
|
||||
github.com/nrdcg/freemyip v0.3.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.2.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
||||
github.com/ooni/go-libtor v1.1.8 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/ovh/go-ovh v1.4.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||
github.com/oracle/oci-go-sdk/v65 v65.81.1 // indirect
|
||||
github.com/ovh/go-ovh v1.6.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // 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/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/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/quic-go/quic-go v0.41.0 // indirect
|
||||
github.com/refraction-networking/utls v1.6.2 // indirect
|
||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // 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/sacloud/api-client-go v0.2.8 // indirect
|
||||
github.com/sacloud/go-http v0.1.6 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.11.1 // indirect
|
||||
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||
github.com/sacloud/go-http v0.1.8 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.14.0 // 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/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
|
||||
github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||
github.com/sagernet/quic-go v0.41.0-beta.2 // indirect
|
||||
github.com/sagernet/sing-dns v0.2.0-beta.6 // indirect
|
||||
github.com/sagernet/sing-mux v0.2.0 // indirect
|
||||
github.com/sagernet/sing-quic v0.1.9-beta.1 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
|
||||
github.com/sagernet/sing-tun v0.2.2-beta.3 // indirect
|
||||
github.com/sagernet/sing-vmess v0.1.8 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // 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/cors v1.2.1 // indirect
|
||||
github.com/sagernet/fswatch v0.1.1 // indirect
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/quic-go v0.52.0-beta.1 // indirect
|
||||
github.com/sagernet/sing-mux v0.3.2 // indirect
|
||||
github.com/sagernet/sing-quic v0.5.0-beta.3 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect
|
||||
github.com/sagernet/sing-tun v0.6.10-0.20250721014417-ebbe32588cfb // indirect
|
||||
github.com/sagernet/sing-vmess v0.2.4 // indirect
|
||||
github.com/sagernet/smux v1.5.34-mod.2 // indirect
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // 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/selectel/domains-go v1.1.0 // indirect
|
||||
github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.2 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.7 // indirect
|
||||
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/transip/gotransip/v6 v6.20.0 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // 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/socks5 v0.0.0-20230325130024-4230056ae301 // indirect
|
||||
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // 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/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/volcengine/volc-sdk-golang v1.0.189 // indirect
|
||||
github.com/vultr/govultr/v3 v3.9.1 // indirect
|
||||
github.com/wyx2685/sing-vmess v0.0.0-20250723121437-95d5ab59ff92 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xtls/reality v0.0.0-20250723115123-fadd8146daab // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.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/ratelimit v0.2.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
|
||||
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/oauth2 v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.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/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/api v0.149.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/grpc v1.61.1 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/api v0.214.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // 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/ns1/ns1-go.v2 v2.7.6 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 // indirect
|
||||
lukechampine.com/blake3 v1.4.1 // indirect
|
||||
)
|
||||
|
||||
//github.com/apernet/hysteria/core v1.3.5-0.20240201034858-bb99579bb92c => /root/hysteria/core
|
||||
replace github.com/sagernet/sing-box v1.9.0 => github.com/wyx2685/sing-box_mod v0.0.4
|
||||
//replace github.com/sagernet/sing-box v1.12.0 => ../sing-box_mod
|
||||
|
||||
replace github.com/sagernet/sing-box v1.12.0 => github.com/wyx2685/sing-box_mod v1.12.0-rc.2
|
||||
|
||||
@@ -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
|
||||
}
|
||||
165
limiter/conn.go
165
limiter/conn.go
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package limiter
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -10,8 +11,6 @@ import (
|
||||
"github.com/InazumaV/V2bX/common/format"
|
||||
"github.com/InazumaV/V2bX/conf"
|
||||
"github.com/juju/ratelimit"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
)
|
||||
|
||||
var limitLock sync.RWMutex
|
||||
@@ -19,54 +18,51 @@ var limiter map[string]*Limiter
|
||||
|
||||
func Init() {
|
||||
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 {
|
||||
DomainRules []*regexp.Regexp
|
||||
ProtocolRules []string
|
||||
SpeedLimit int
|
||||
UserOnlineIP *sync.Map // Key: Name, value: {Key: Ip, value: Uid}
|
||||
UUIDtoUID map[string]int // Key: UUID, value: UID
|
||||
UserLimitInfo *sync.Map // Key: Uid value: UserLimitInfo
|
||||
ConnLimiter *ConnLimiter // Key: Uid value: ConnLimiter
|
||||
SpeedLimiter *sync.Map // key: Uid, value: *ratelimit.Bucket
|
||||
UserOnlineIP *sync.Map // Key: TagUUID, value: {Key: Ip, value: Uid}
|
||||
OldUserOnline *sync.Map // Key: Ip, value: Uid
|
||||
UUIDtoUID map[string]int // Key: UUID, value: Uid
|
||||
UserLimitInfo *sync.Map // Key: TagUUID value: UserLimitInfo
|
||||
SpeedLimiter *sync.Map // key: TagUUID, value: *ratelimit.Bucket
|
||||
AliveList map[int]int // Key: Uid, value: alive_ip
|
||||
}
|
||||
|
||||
type UserLimitInfo struct {
|
||||
UID int
|
||||
SpeedLimit int
|
||||
DeviceLimit int
|
||||
DynamicSpeedLimit int
|
||||
ExpireTime int64
|
||||
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{
|
||||
SpeedLimit: l.SpeedLimit,
|
||||
UserOnlineIP: new(sync.Map),
|
||||
UserLimitInfo: new(sync.Map),
|
||||
ConnLimiter: NewConnLimiter(l.ConnLimit, l.IPLimit, l.EnableRealtime),
|
||||
SpeedLimiter: new(sync.Map),
|
||||
AliveList: aliveList,
|
||||
OldUserOnline: new(sync.Map),
|
||||
}
|
||||
uuidmap := make(map[string]int)
|
||||
for i := range users {
|
||||
uuidmap[users[i].Uuid] = users[i].Id
|
||||
userLimit := &UserLimitInfo{}
|
||||
userLimit.UID = users[i].Id
|
||||
if users[i].SpeedLimit != 0 {
|
||||
userLimit := &UserLimitInfo{
|
||||
UID: users[i].Id,
|
||||
SpeedLimit: users[i].SpeedLimit,
|
||||
}
|
||||
info.UserLimitInfo.Store(format.UserTag(tag, users[i].Uuid), userLimit)
|
||||
userLimit.SpeedLimit = users[i].SpeedLimit
|
||||
}
|
||||
if users[i].DeviceLimit != 0 {
|
||||
userLimit.DeviceLimit = users[i].DeviceLimit
|
||||
}
|
||||
userLimit.OverLimit = false
|
||||
info.UserLimitInfo.Store(format.UserTag(tag, users[i].Uuid), userLimit)
|
||||
}
|
||||
info.UUIDtoUID = uuidmap
|
||||
limitLock.Lock()
|
||||
@@ -94,17 +90,24 @@ func DeleteLimiter(tag string) {
|
||||
func (l *Limiter) UpdateUser(tag string, added []panel.UserInfo, deleted []panel.UserInfo) {
|
||||
for i := range deleted {
|
||||
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.AliveList, deleted[i].Id)
|
||||
}
|
||||
for i := range added {
|
||||
if added[i].SpeedLimit != 0 {
|
||||
userLimit := &UserLimitInfo{
|
||||
UID: added[i].Id,
|
||||
SpeedLimit: added[i].SpeedLimit,
|
||||
ExpireTime: 0,
|
||||
}
|
||||
l.UserLimitInfo.Store(format.UserTag(tag, added[i].Uuid), userLimit)
|
||||
userLimit := &UserLimitInfo{
|
||||
UID: added[i].Id,
|
||||
}
|
||||
if added[i].SpeedLimit != 0 {
|
||||
userLimit.SpeedLimit = added[i].SpeedLimit
|
||||
userLimit.ExpireTime = 0
|
||||
}
|
||||
if added[i].DeviceLimit != 0 {
|
||||
userLimit.DeviceLimit = added[i].DeviceLimit
|
||||
}
|
||||
userLimit.OverLimit = false
|
||||
l.UserLimitInfo.Store(format.UserTag(tag, added[i].Uuid), userLimit)
|
||||
l.UUIDtoUID[added[i].Uuid] = added[i].Id
|
||||
}
|
||||
}
|
||||
@@ -120,53 +123,75 @@ func (l *Limiter) UpdateDynamicSpeedLimit(tag, uuid string, limit int, expire ti
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Limiter) CheckLimit(uuid string, ip string, isTcp bool) (Bucket *ratelimit.Bucket, Reject bool) {
|
||||
// ip and conn limiter
|
||||
if l.ConnLimiter.AddConnCount(uuid, ip, isTcp) {
|
||||
return nil, true
|
||||
}
|
||||
func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool, noSSUDP bool) (Bucket *ratelimit.Bucket, Reject bool) {
|
||||
// check if ipv4 mapped ipv6
|
||||
ip = strings.TrimPrefix(ip, "::ffff:")
|
||||
|
||||
// check and gen speed limit Bucket
|
||||
nodeLimit := l.SpeedLimit
|
||||
userLimit := 0
|
||||
if v, ok := l.UserLimitInfo.Load(uuid); ok {
|
||||
deviceLimit := 0
|
||||
var uid int
|
||||
if v, ok := l.UserLimitInfo.Load(taguuid); ok {
|
||||
u := v.(*UserLimitInfo)
|
||||
deviceLimit = u.DeviceLimit
|
||||
uid = u.UID
|
||||
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
|
||||
if u.SpeedLimit != 0 {
|
||||
userLimit = u.SpeedLimit
|
||||
u.DynamicSpeedLimit = 0
|
||||
u.ExpireTime = 0
|
||||
} else {
|
||||
l.UserLimitInfo.Delete(uuid)
|
||||
l.UserLimitInfo.Delete(taguuid)
|
||||
}
|
||||
} else {
|
||||
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
|
||||
}
|
||||
} else {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
// Store online user for device limit
|
||||
ipMap := new(sync.Map)
|
||||
uid := l.UUIDtoUID[uuid]
|
||||
ipMap.Store(ip, uid)
|
||||
// If any device is online
|
||||
if v, ok := l.UserOnlineIP.LoadOrStore(uuid, ipMap); ok {
|
||||
ipMap := v.(*sync.Map)
|
||||
// If this is a new ip
|
||||
if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
|
||||
counter := 0
|
||||
ipMap.Range(func(key, value interface{}) bool {
|
||||
counter++
|
||||
return true
|
||||
})
|
||||
if noSSUDP {
|
||||
// Store online user for device limit
|
||||
newipMap := new(sync.Map)
|
||||
newipMap.Store(ip, uid)
|
||||
aliveIp := l.AliveList[uid]
|
||||
// If any device is online
|
||||
if v, loaded := l.UserOnlineIP.LoadOrStore(taguuid, newipMap); loaded {
|
||||
oldipMap := v.(*sync.Map)
|
||||
// If this is a new ip
|
||||
if _, loaded := oldipMap.LoadOrStore(ip, uid); !loaded {
|
||||
if v, loaded := l.OldUserOnline.Load(ip); loaded {
|
||||
if v.(int) == uid {
|
||||
l.OldUserOnline.Delete(ip)
|
||||
}
|
||||
} else if deviceLimit > 0 {
|
||||
if deviceLimit <= aliveIp {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
|
||||
if limit > 0 {
|
||||
Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
|
||||
if v, ok := l.SpeedLimiter.LoadOrStore(uuid, Bucket); ok {
|
||||
if v, ok := l.SpeedLimiter.LoadOrStore(taguuid, Bucket); ok {
|
||||
return v.(*ratelimit.Bucket), false
|
||||
} else {
|
||||
l.SpeedLimiter.Store(uuid, Bucket)
|
||||
l.SpeedLimiter.Store(taguuid, Bucket)
|
||||
return Bucket, false
|
||||
}
|
||||
} else {
|
||||
@@ -176,17 +201,18 @@ func (l *Limiter) CheckLimit(uuid string, ip string, isTcp bool) (Bucket *rateli
|
||||
|
||||
func (l *Limiter) GetOnlineDevice() (*[]panel.OnlineUser, error) {
|
||||
var onlineUser []panel.OnlineUser
|
||||
|
||||
l.OldUserOnline = new(sync.Map)
|
||||
l.UserOnlineIP.Range(func(key, value interface{}) bool {
|
||||
email := key.(string)
|
||||
taguuid := key.(string)
|
||||
ipMap := value.(*sync.Map)
|
||||
ipMap.Range(func(key, value interface{}) bool {
|
||||
uid := value.(int)
|
||||
ip := key.(string)
|
||||
l.OldUserOnline.Store(ip, uid)
|
||||
onlineUser = append(onlineUser, panel.OnlineUser{UID: uid, IP: ip})
|
||||
return true
|
||||
})
|
||||
l.UserOnlineIP.Delete(email) // Reset online device
|
||||
l.UserOnlineIP.Delete(taguuid) // Reset online device
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -197,23 +223,3 @@ type UserIpList struct {
|
||||
Uid int `json:"Uid"`
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
main.go
7
main.go
@@ -1,16 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
//"net/http"
|
||||
//_ "net/http/pprof"
|
||||
|
||||
"github.com/InazumaV/V2bX/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//内存泄漏排查
|
||||
//go func() {
|
||||
// http.ListenAndServe("127.0.0.1:6060", nil)
|
||||
//}()
|
||||
cmd.Run()
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type Controller struct {
|
||||
limiter *limiter.Limiter
|
||||
traffic map[string]int64
|
||||
userList []panel.UserInfo
|
||||
aliveMap map[int]int
|
||||
info *panel.NodeInfo
|
||||
nodeInfoMonitorPeriodic *task.Task
|
||||
userReportPeriodic *task.Task
|
||||
@@ -54,6 +55,10 @@ func (c *Controller) Start() error {
|
||||
if len(c.userList) == 0 {
|
||||
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 {
|
||||
c.tag = c.buildNodeTag(node)
|
||||
} else {
|
||||
@@ -61,7 +66,7 @@ func (c *Controller) Start() error {
|
||||
}
|
||||
|
||||
// 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
|
||||
if err = l.UpdateRule(&node.Rules); err != nil {
|
||||
return fmt.Errorf("update rule error: %s", err)
|
||||
|
||||
26
node/task.go
26
node/task.go
@@ -68,6 +68,15 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
||||
}).Error("Get user list failed")
|
||||
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 {
|
||||
c.info = newN
|
||||
// nodeInfo changed
|
||||
@@ -82,7 +91,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
||||
log.WithFields(log.Fields{
|
||||
"tag": c.tag,
|
||||
"err": err,
|
||||
}).Error("Delete node failed")
|
||||
}).Panic("Delete node failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,9 +101,13 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
||||
// Remove Old limiter
|
||||
limiter.DeleteLimiter(c.tag)
|
||||
// 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
|
||||
}
|
||||
// update alive list
|
||||
if newA != nil {
|
||||
c.limiter.AliveList = newA
|
||||
}
|
||||
// Update rule
|
||||
err = c.limiter.UpdateRule(&newN.Rules)
|
||||
if err != nil {
|
||||
@@ -122,7 +135,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
||||
log.WithFields(log.Fields{
|
||||
"tag": c.tag,
|
||||
"err": err,
|
||||
}).Error("Add node failed")
|
||||
}).Panic("Add node failed")
|
||||
return nil
|
||||
}
|
||||
_, err = c.server.AddUsers(&vCore.AddUsersParams{
|
||||
@@ -154,7 +167,10 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
||||
// exit
|
||||
return nil
|
||||
}
|
||||
|
||||
// update alive list
|
||||
if newA != nil {
|
||||
c.limiter.AliveList = newA
|
||||
}
|
||||
// node no changed, check users
|
||||
if len(newU) == 0 {
|
||||
return nil
|
||||
@@ -162,7 +178,7 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
||||
deleted, added := compareUserList(c.userList, newU)
|
||||
if len(deleted) > 0 {
|
||||
// have deleted users
|
||||
err = c.server.DelUsers(deleted, c.tag)
|
||||
err = c.server.DelUsers(deleted, c.tag, c.info)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"tag": c.tag,
|
||||
|
||||
17
node/user.go
17
node/user.go
@@ -1,7 +1,6 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/InazumaV/V2bX/api/panel"
|
||||
@@ -15,11 +14,7 @@ func (c *Controller) reportUserTrafficTask() (err error) {
|
||||
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
|
||||
}
|
||||
c.traffic[c.userList[i].Uuid] += up + down
|
||||
}
|
||||
userTraffic = append(userTraffic, panel.UserTraffic{
|
||||
UID: (c.userList)[i].Id,
|
||||
@@ -56,18 +51,12 @@ func (c *Controller) reportUserTrafficTask() (err error) {
|
||||
result = append(result, online)
|
||||
}
|
||||
}
|
||||
reportOnline := make(map[int]int)
|
||||
data := make(map[int][]string)
|
||||
for _, onlineuser := range result {
|
||||
// json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] }
|
||||
data[onlineuser.UID] = append(data[onlineuser.UID], fmt.Sprintf("%s_%d", onlineuser.IP, c.info.Id))
|
||||
if _, ok := reportOnline[onlineuser.UID]; ok {
|
||||
reportOnline[onlineuser.UID]++
|
||||
} else {
|
||||
reportOnline[onlineuser.UID] = 1
|
||||
}
|
||||
data[onlineuser.UID] = append(data[onlineuser.UID], onlineuser.IP)
|
||||
}
|
||||
if err = c.apiClient.ReportNodeOnlineUsers(&data, &reportOnline); err != nil {
|
||||
if err = c.apiClient.ReportNodeOnlineUsers(&data); err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"tag": c.tag,
|
||||
"err": err,
|
||||
|
||||
Reference in New Issue
Block a user