Compare commits

...

49 Commits

Author SHA1 Message Date
wyx2685
89ddfff060 升级sing-box内核 2024-08-14 01:48:33 +09:00
wyx2685
07d49293d8 BUG修复前不对UDP链接进行限速 2024-08-14 01:35:30 +09:00
wyx2685
9e8f87740e update xray-core 1.8.23 2024-07-30 01:28:39 +09:00
wyx2685
29a99985c8 Fix:优化流量统计并发情况可能存在的冲突 2024-07-28 13:54:47 +09:00
wyx2685
248ff3764f update sing-box core 2024-07-24 19:50:55 +09:00
wyx2685
3dfeba7e68 xray-core 1.8.21 2024-07-22 07:49:04 +09:00
wyx2685
8eb623b3f0 update xray core 2024-07-21 20:56:35 +09:00
wyx2685
cdcbddd464 update core 2024-07-21 06:14:16 +09:00
wyx2685
e81d47321b xray-core 1.8.19 2024-07-17 23:26:09 +09:00
wyx2685
4d82eff518 update Xray-core 1.8.18 2024-07-17 09:21:57 +09:00
wyx2685
b96545649b Fix:修复无法使用Sing-box内核urltest出口的问题 2024-07-15 00:33:54 +09:00
wyx2685
8757b955a6 修复hy2内核设备数统计错误的BUG 2024-07-08 20:32:52 +09:00
wyx2685
33d9ab4b0a 适应xray1.8.16更改 2024-07-08 18:12:09 +09:00
wyx2685
a7637d436f 更新所有内核 2024-07-08 17:44:22 +09:00
wyx2685
4dda65a636 Fix:修复Xray内核VMess/VLess的Websocket传输方式参数设置错误 2024-06-24 18:55:04 +09:00
wyx2685
6c725b424f 更新文档链接 2024-06-24 04:48:15 +09:00
wyx2685
a052a1f1e8 升级Xray内核,完善singbox内核自定义配置 2024-06-24 04:41:56 +09:00
wyx2685
85ad40d098 升级全部内核 2024-06-13 00:06:22 +09:00
wyx2685
206af0216c 修复xray内核vless协议bug,升级xray/sing内核 2024-03-13 22:15:04 +09:00
wyx2685
ed5edda28a 修复Sing内核trojan用户列表BUG、修复用户设备限制不生效 2024-03-04 22:25:06 +09:00
wyx2685
995e606694 hy2内核增加自定义配置 2024-02-26 22:00:55 +09:00
wyx2685
96b457d679 修正singbox内核hy2用户列表问题 2024-02-21 13:02:13 +09:00
wyx2685
55b20f5550 升级内核. 2024-02-20 06:27:31 +09:00
wyx2685
c0b31837e4 update singbox 2024-02-16 22:00:06 +09:00
wyx2685
bf4a52df4d Merge branch 'dev_new' of github.com:wyx2685/V2bX into dev_new 2024-02-16 21:46:37 +09:00
wyx2685
423ac622b5 fix traffic counting 2024-02-16 21:46:23 +09:00
wyx2685
625265148f 更新 Dockerfile
Fix hysteria2 core
2024-02-08 13:52:37 +08:00
wyx2685
73f9b19222 增加本地节点Hash辅助判断信息 2024-02-06 20:26:30 +09:00
wyx2685
0d274bcf61 测试:增加hysteria2内核 2024-02-06 10:59:37 +09:00
wyx2685
77ec03016c 尝试解决内存泄漏问题 2024-01-28 01:04:47 +09:00
wyx2685
1d4945af8d 更新内核,尝试优化代码 2024-01-27 13:19:55 +09:00
wyx2685
f92c5b37d5 尝试优化代码 2024-01-27 01:55:43 +09:00
wyx2685
91e78fbc20 尝试优化代码 2024-01-25 23:06:37 +09:00
wyx2685
e292b3b0e7 升级sing和xray内核 2024-01-22 13:38:55 +09:00
wyx2685
c4d404d979 update Sing and Xray core 2024-01-08 00:58:21 +09:00
wyx2685
16221d17fb update singbox and xray 2024-01-02 00:33:13 +09:00
wyx2685
f33d7a3bf9 fix:trojan修复Trojan未应用传输方式 2023-12-19 00:21:07 +09:00
wyx2685
50183e70b3 Merge branch 'dev_new' of github.com:wyx2685/V2bX into dev_new 2023-12-19 00:17:28 +09:00
wyx2685
d19ca6863e 尝试添加trojan的ws/grpc传输 2023-12-19 00:17:16 +09:00
wyx2685
8d116b19d1 更新 Dockerfile 尝试修复启动问题 2023-12-18 13:36:22 +08:00
wyx2685
4ec6426234 Merge pull request #2 from rebecca554owen/dev_new
增加支持docker。
2023-12-17 22:56:11 +08:00
rebecca554owen
73b5c37d94 Create Publish Docker image.yml 2023-12-17 14:01:52 +08:00
rebecca554owen
e7997f9896 Create Dockerfile 2023-12-17 14:01:08 +08:00
wyx2685
1f6cccbb9f Try to fix vmess/vless tcp http 2023-12-17 02:58:13 +09:00
wyx2685
a25c3d2759 geoip和geosite更换为增强版 2023-12-15 03:31:50 +09:00
wyx2685
5aef9cf0de 增加dev_new分支push时编译 2023-12-15 03:25:21 +09:00
wyx2685
39972f5cf9 Merge pull request #1 from TheMrJC/patch-1
Update README.md typo
2023-12-14 18:09:02 +08:00
wyx2685
0af24e4646 更新sing依赖 2023-12-14 18:35:20 +09:00
JC
ea6ef41c60 Update README.md typo
remove the typo of the installation link
2023-12-13 12:43:22 +08:00
46 changed files with 2724 additions and 741 deletions

View File

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

View File

@@ -35,11 +35,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -47,10 +47,14 @@ jobs:
# Prefix the list here with "+" to use these queries and those in the config file. # Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main # queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Install Go
uses: actions/setup-go@v4
with:
go-version-file: go.mod
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -64,4 +68,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@@ -5,6 +5,7 @@ on:
push: push:
branches: branches:
- master - master
- dev_new
paths: paths:
- "**/*.go" - "**/*.go"
- "go.mod" - "go.mod"
@@ -108,7 +109,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.21.4' go-version: '1.22.0'
- name: Get project dependencies - name: Get project dependencies
run: go mod download run: go mod download
@@ -124,13 +125,13 @@ jobs:
run: | run: |
echo "version: $version" echo "version: $version"
mkdir -p build_assets mkdir -p build_assets
go build -v -o build_assets/V2bX -tags "sing xray 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_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid="
- name: Build Mips softfloat V2bX - name: Build Mips softfloat V2bX
if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle' if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle'
run: | run: |
echo "version: $version" echo "version: $version"
GOMIPS=softfloat go build -v -o build_assets/V2bX_softfloat -tags "sing xray 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_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-X 'github.com/InazumaV/V2bX/cmd.version=$version' -s -w -buildid="
- name: Rename Windows V2bX - name: Rename Windows V2bX
if: matrix.goos == 'windows' if: matrix.goos == 'windows'
run: | run: |
@@ -141,16 +142,13 @@ jobs:
cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md cp ${GITHUB_WORKSPACE}/README.md ./build_assets/README.md
cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE cp ${GITHUB_WORKSPACE}/LICENSE ./build_assets/LICENSE
cp ${GITHUB_WORKSPACE}/example/*.json ./build_assets/ cp ${GITHUB_WORKSPACE}/example/*.json ./build_assets/
LIST=('geoip geoip geoip' 'domain-list-community dlc geosite') LIST=('geoip' 'geosite')
for i in "${LIST[@]}" for i in "${LIST[@]}"
do do
INFO=($(echo $i | awk 'BEGIN{FS=" ";OFS=" "} {print $1,$2,$3}')) DOWNLOAD_URL="https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/${i}.dat"
FILE_NAME="${INFO[2]}.dat" FILE_NAME="${i}.dat"
echo -e "Downloading ${FILE_NAME}..." echo -e "Downloading ${DOWNLOAD_URL}..."
curl -L "https://github.com/v2fly/${INFO[0]}/releases/latest/download/${INFO[1]}.dat" -o ./build_assets/${FILE_NAME} curl -L "${DOWNLOAD_URL}" -o ./build_assets/${FILE_NAME}
echo -e "Verifying HASH key..."
HASH="$(curl -sL "https://github.com/v2fly/${INFO[0]}/releases/latest/download/${INFO[1]}.dat.sha256sum" | awk -F ' ' '{print $1}')"
[ "$(sha256sum "./build_assets/${FILE_NAME}" | awk -F ' ' '{print $1}')" == "${HASH}" ] || { echo -e "The HASH key of ${FILE_NAME} does not match cloud one."; exit 1; }
done done
- name: Create ZIP archive - name: Create ZIP archive
shell: bash shell: bash

2
.gitignore vendored
View File

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

17
Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# Build go
FROM golang:1.22.0-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"
# Release
FROM alpine
# 安装必要的工具包
RUN apk --update --no-cache add tzdata ca-certificates \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN mkdir /etc/V2bX/
COPY --from=builder /app/V2bX /usr/local/bin
ENTRYPOINT [ "V2bX", "server", "--config", "/etc/V2bX/config.json"]

View File

@@ -47,22 +47,22 @@ A V2board node server based on multi core, modified from XrayR.
### 一键安装 ### 一键安装
``` ```
wget -N https://raw.githubusercontents.com/wyx2685/V2bX-script/master/install.sh && bash install.sh wget -N https://raw.githubusercontent.com/wyx2685/V2bX-script/master/install.sh && bash 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 ``` bash
# 通过-tags选项指定要编译的内核 可选 xray sing # 通过-tags选项指定要编译的内核 可选 xray sing
go build -o V2bX -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD} -tags "xray sing" go build -v -o ./V2bX -tags "sing hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" -trimpath -ldflags "-s -w -buildid="
``` ```
## 配置文件及详细使用教程 ## 配置文件及详细使用教程
[详细使用教程](https://yuzuki-1.gitbook.io/v2bx-doc/) [详细使用教程](https://v2bx.v-50.me/)
## 免责声明 ## 免责声明
@@ -82,4 +82,4 @@ go build -o V2bX -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all
## Stars 增长记录 ## Stars 增长记录
[![Stargazers over time](https://starchart.cc/InazumaV/V2bX.svg)](https://starchart.cc/InazumaV/V2bX) [![Stargazers over time](https://starchart.cc/wyx2685/V2bX.svg)](https://starchart.cc/wyx2685/V2bX)

View File

@@ -1,14 +1,14 @@
package panel package panel
import ( import (
"encoding/base64" "crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/InazumaV/V2bX/common/crypt"
"github.com/goccy/go-json" "github.com/goccy/go-json"
) )
@@ -78,7 +78,7 @@ type TlsSettings struct {
ServerPort string `json:"server_port"` ServerPort string `json:"server_port"`
ShortId string `json:"short_id"` ShortId string `json:"short_id"`
PrivateKey string `json:"private_key"` PrivateKey string `json:"private_key"`
Xver uint8 `json:"xver,string"` Xver uint64 `json:"xver,string"`
} }
type RealityConfig struct { type RealityConfig struct {
@@ -94,7 +94,11 @@ type ShadowsocksNode struct {
ServerKey string `json:"server_key"` ServerKey string `json:"server_key"`
} }
type TrojanNode CommonNode type TrojanNode struct {
CommonNode
Network string `json:"network"`
NetworkSettings json.RawMessage `json:"networkSettings"`
}
type HysteriaNode struct { type HysteriaNode struct {
CommonNode CommonNode
@@ -126,12 +130,31 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
r, err := c.client. r, err := c.client.
R(). R().
SetHeader("If-None-Match", c.nodeEtag). SetHeader("If-None-Match", c.nodeEtag).
ForceContentType("application/json").
Get(path) Get(path)
if r.StatusCode() == 304 {
return nil, nil
}
hash := sha256.Sum256(r.Body())
newBodyHash := hex.EncodeToString(hash[:])
if c.responseBodyHash == newBodyHash {
return nil, nil
}
c.responseBodyHash = newBodyHash
c.nodeEtag = r.Header().Get("ETag")
if err = c.checkResponse(r, path, err); err != nil { if err = c.checkResponse(r, path, err); err != nil {
return nil, err return nil, err
} }
if r.StatusCode() == 304 {
return nil, nil if r != nil {
defer func() {
if r.RawBody() != nil {
r.RawBody().Close()
}
}()
} else {
return nil, fmt.Errorf("received nil response")
} }
node = &NodeInfo{ node = &NodeInfo{
Id: c.NodeId, Id: c.NodeId,
@@ -161,18 +184,6 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
cm = &rsp.CommonNode cm = &rsp.CommonNode
node.VAllss = rsp node.VAllss = rsp
node.Security = node.VAllss.Tls node.Security = node.VAllss.Tls
if len(rsp.NetworkSettings) > 0 {
err = json.Unmarshal(rsp.NetworkSettings, &rsp.RealityConfig)
if err != nil {
return nil, fmt.Errorf("decode reality config error: %s", err)
}
}
if node.Security == Reality {
if rsp.TlsSettings.PrivateKey == "" {
key := crypt.GenX25519Private([]byte("vless" + c.Token))
rsp.TlsSettings.PrivateKey = base64.RawURLEncoding.EncodeToString(key)
}
}
case "shadowsocks": case "shadowsocks":
rsp := &ShadowsocksNode{} rsp := &ShadowsocksNode{}
err = json.Unmarshal(r.Body(), rsp) err = json.Unmarshal(r.Body(), rsp)
@@ -188,7 +199,7 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("decode trojan params error: %s", err) return nil, fmt.Errorf("decode trojan params error: %s", err)
} }
cm = (*CommonNode)(rsp) cm = &rsp.CommonNode
node.Trojan = rsp node.Trojan = rsp
node.Security = Tls node.Security = Tls
case "hysteria": case "hysteria":
@@ -260,8 +271,7 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
cm.Routes = nil cm.Routes = nil
cm.BaseConfig = nil cm.BaseConfig = nil
c.nodeEtag = r.Header().Get("ETag") return node, nil
return
} }
func intervalToTime(i interface{}) time.Duration { func intervalToTime(i interface{}) time.Duration {

View File

@@ -23,6 +23,7 @@ type Client struct {
NodeId int NodeId int
nodeEtag string nodeEtag string
userEtag string userEtag string
responseBodyHash string
LastReportOnline map[int]int LastReportOnline map[int]int
} }

View File

@@ -29,24 +29,35 @@ func (c *Client) GetUserList() (UserList []UserInfo, err error) {
const path = "/api/v1/server/UniProxy/user" const path = "/api/v1/server/UniProxy/user"
r, err := c.client.R(). r, err := c.client.R().
SetHeader("If-None-Match", c.userEtag). SetHeader("If-None-Match", c.userEtag).
ForceContentType("application/json").
Get(path) Get(path)
err = c.checkResponse(r, path, err) if err = c.checkResponse(r, path, err); err != nil {
if err != nil {
return nil, err return nil, err
} }
if r != nil {
defer func() {
if r.RawBody() != nil {
r.RawBody().Close()
}
}()
if r.StatusCode() == 304 { if r.StatusCode() == 304 {
return nil, nil return nil, nil
} }
} else {
return nil, fmt.Errorf("received nil response")
}
var userList *UserListBody var userList *UserListBody
err = json.Unmarshal(r.Body(), &userList)
if err != nil { 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) return nil, fmt.Errorf("unmarshal userlist error: %s", err)
} }
c.userEtag = r.Header().Get("ETag") c.userEtag = r.Header().Get("ETag")
var userinfos []UserInfo var userinfos []UserInfo
var localDeviceLimit int = 0 var deviceLimit, localDeviceLimit int = 0, 0
for _, user := range userList.Users { for _, user := range userList.Users {
// If there is still device available, add the user // If there is still device available, add the user
if user.DeviceLimit > 0 && user.AliveIp > 0 { if user.DeviceLimit > 0 && user.AliveIp > 0 {
@@ -57,13 +68,14 @@ func (c *Client) GetUserList() (UserList []UserInfo, err error) {
// If there are any available device. // If there are any available device.
localDeviceLimit = user.DeviceLimit - user.AliveIp + lastOnline localDeviceLimit = user.DeviceLimit - user.AliveIp + lastOnline
if localDeviceLimit > 0 { if localDeviceLimit > 0 {
deviceLimit = localDeviceLimit
} else if lastOnline > 0 { } else if lastOnline > 0 {
deviceLimit = lastOnline
} else { } else {
continue continue
} }
} }
user.DeviceLimit = deviceLimit
userinfos = append(userinfos, user) userinfos = append(userinfos, user)
} }

View File

@@ -96,7 +96,6 @@ func serverHandle(_ *cobra.Command, _ []string) {
log.WithField("err", err).Error("Restart node failed") log.WithField("err", err).Error("Restart node failed")
return return
} }
runtime.GC()
vc, err = vCore.NewCore(c.CoresConfig) vc, err = vCore.NewCore(c.CoresConfig)
if err != nil { if err != nil {
log.WithField("err", err).Error("New core failed") log.WithField("err", err).Error("New core failed")
@@ -114,6 +113,7 @@ func serverHandle(_ *cobra.Command, _ []string) {
return return
} }
log.Info("Nodes restarted") log.Info("Nodes restarted")
runtime.GC()
}) })
if err != nil { if err != nil {
log.WithField("err", err).Error("start watch failed") log.WithField("err", err).Error("start watch failed")

View File

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

View File

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

View File

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

View File

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

View File

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

17
conf/hy.go Normal file
View File

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

View File

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

View File

@@ -8,7 +8,6 @@ type SingConfig struct {
LogConfig SingLogConfig `json:"Log"` LogConfig SingLogConfig `json:"Log"`
NtpConfig SingNtpConfig `json:"NTP"` NtpConfig SingNtpConfig `json:"NTP"`
EnableConnClear bool `json:"EnableConnClear"` EnableConnClear bool `json:"EnableConnClear"`
DnsConfigPath string `json:"DnsConfigPath"`
OriginalPath string `json:"OriginalPath"` OriginalPath string `json:"OriginalPath"`
} }

View File

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

431
core/hy2/config.go Normal file
View File

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

181
core/hy2/geoloader.go Normal file
View 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))
}
}

52
core/hy2/hook.go Normal file
View File

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

61
core/hy2/hy2.go Normal file
View File

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

156
core/hy2/logger.go Normal file
View File

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

76
core/hy2/node.go Normal file
View File

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

228
core/hy2/serverConfig.go Normal file
View File

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

70
core/hy2/user.go Normal file
View File

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

5
core/imports/hy2.go Normal file
View File

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

View File

@@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
"github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/common/rate" "github.com/InazumaV/V2bX/common/rate"
"github.com/InazumaV/V2bX/limiter" "github.com/InazumaV/V2bX/limiter"
@@ -16,9 +17,14 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
) )
var _ adapter.ClashServer = (*HookServer)(nil)
type HookServer struct { type HookServer struct {
ctx context.Context
urlTestHistory *urltest.HistoryStorage
EnableConnClear bool EnableConnClear bool
counter sync.Map counter sync.Map
connClears sync.Map connClears sync.Map
@@ -55,12 +61,18 @@ func (h *HookServer) ModeList() []string {
return nil return nil
} }
func NewHookServer(enableClear bool) *HookServer { func NewHookServer(ctx context.Context, enableClear bool) *HookServer {
return &HookServer{ server := &HookServer{
ctx: ctx,
EnableConnClear: enableClear, EnableConnClear: enableClear,
counter: sync.Map{}, counter: sync.Map{},
connClears: sync.Map{}, connClears: sync.Map{},
} }
server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
if server.urlTestHistory == nil {
server.urlTestHistory = urltest.NewHistoryStorage()
}
return server
} }
func (h *HookServer) Start() error { func (h *HookServer) Start() error {
@@ -68,6 +80,7 @@ func (h *HookServer) Start() error {
} }
func (h *HookServer) Close() error { func (h *HookServer) Close() error {
h.urlTestHistory.Close()
return nil return nil
} }
@@ -95,7 +108,7 @@ func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapte
return conn, t return conn, t
} }
ip := m.Source.Addr.String() ip := m.Source.Addr.String()
if b, r := l.CheckLimit(m.User, ip, true); r { if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, true, true); r {
conn.Close() conn.Close()
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn") log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
return conn, t return conn, t
@@ -149,12 +162,12 @@ func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn
return conn, t return conn, t
} }
ip := m.Source.Addr.String() ip := m.Source.Addr.String()
if b, r := l.CheckLimit(m.User, ip, true); r { if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, false, false); r {
conn.Close() conn.Close()
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn") log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
return conn, t return conn, t
} else if b != nil { } else if b != nil {
conn = rate.NewPacketConnCounter(conn, b) //conn = rate.NewPacketConnCounter(conn, b)
} }
if h.EnableConnClear { if h.EnableConnClear {
var key int var key int
@@ -192,7 +205,7 @@ func (h *HookServer) CacheFile() adapter.CacheFile {
return nil return nil
} }
func (h *HookServer) HistoryStorage() *urltest.HistoryStorage { func (h *HookServer) HistoryStorage() *urltest.HistoryStorage {
return nil return h.urlTestHistory
} }
func (h *HookServer) StoreFakeIP() bool { func (h *HookServer) StoreFakeIP() bool {

View File

@@ -26,11 +26,29 @@ type HttpNetworkConfig struct {
} `json:"header"` } `json:"header"`
} }
type HttpRequest struct {
Version string `json:"version"`
Method string `json:"method"`
Path []string `json:"path"`
Headers struct {
Host []string `json:"Host"`
} `json:"headers"`
}
type WsNetworkConfig struct { type WsNetworkConfig struct {
Path string `json:"path"` Path string `json:"path"`
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers"`
} }
type GrpcNetworkConfig struct {
ServiceName string `json:"serviceName"`
}
type HttpupgradeNetworkConfig struct {
Path string `json:"path"`
Host string `json:"host"`
}
func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (option.Inbound, error) { func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (option.Inbound, error) {
addr, err := netip.ParseAddr(c.ListenIP) addr, err := netip.ParseAddr(c.ListenIP)
if err != nil { if err != nil {
@@ -82,7 +100,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Enabled: true, Enabled: true,
ShortID: []string{v.TlsSettings.ShortId}, ShortID: []string{v.TlsSettings.ShortId},
PrivateKey: v.TlsSettings.PrivateKey, PrivateKey: v.TlsSettings.PrivateKey,
Xver: v.TlsSettings.Xver, Xver: uint8(v.TlsSettings.Xver),
Handshake: option.InboundRealityHandshakeOptions{ Handshake: option.InboundRealityHandshakeOptions{
ServerOptions: option.ServerOptions{ ServerOptions: option.ServerOptions{
Server: dest, Server: dest,
@@ -109,10 +127,19 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
if err != nil { if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err) return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
} }
//Todo fix http options
if network.Header.Type == "http" { if network.Header.Type == "http" {
t.Type = network.Header.Type t.Type = network.Header.Type
//Todo fix http options var request HttpRequest
//t.HTTPOptions.Host = if network.Header.Request != nil {
err = json.Unmarshal(*network.Header.Request, &request)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode HttpRequest error: %s", err)
}
t.HTTPOptions.Host = request.Headers.Host
t.HTTPOptions.Path = request.Path[0]
t.HTTPOptions.Method = request.Method
}
} else { } else {
t.Type = "" t.Type = ""
} }
@@ -152,12 +179,28 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Headers: headers, Headers: headers,
} }
case "grpc": case "grpc":
network := GrpcNetworkConfig{}
if len(n.NetworkSettings) != 0 { if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &t.GRPCOptions) err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil { if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err) return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
} }
} }
t.GRPCOptions = option.V2RayGRPCOptions{
ServiceName: network.ServiceName,
}
case "httpupgrade":
network := HttpupgradeNetworkConfig{}
if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
}
t.HTTPUpgradeOptions = option.V2RayHTTPUpgradeOptions{
Path: network.Path,
Host: network.Host,
}
} }
if info.Type == "vless" { if info.Type == "vless" {
in.Type = "vless" in.Type = "vless"
@@ -185,7 +228,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
switch n.Cipher { switch n.Cipher {
case "2022-blake3-aes-128-gcm": case "2022-blake3-aes-128-gcm":
keyLength = 16 keyLength = 16
case "2022-blake3-aes-256-gcm": case "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305":
keyLength = 32 keyLength = 32
default: default:
keyLength = 16 keyLength = 16
@@ -205,12 +248,66 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Password: randomPasswd, Password: randomPasswd,
}} }}
case "trojan": case "trojan":
n := info.Trojan
t := option.V2RayTransportOptions{
Type: n.Network,
}
switch n.Network {
case "tcp":
t.Type = ""
case "ws":
var (
path string
ed int
headers map[string]option.Listable[string]
)
if len(n.NetworkSettings) != 0 {
network := WsNetworkConfig{}
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
var u *url.URL
u, err = url.Parse(network.Path)
if err != nil {
return option.Inbound{}, fmt.Errorf("parse path error: %s", err)
}
path = u.Path
ed, _ = strconv.Atoi(u.Query().Get("ed"))
headers = make(map[string]option.Listable[string], len(network.Headers))
for k, v := range network.Headers {
headers[k] = option.Listable[string]{
v,
}
}
}
t.WebsocketOptions = option.V2RayWebsocketOptions{
Path: path,
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
MaxEarlyData: uint32(ed),
Headers: headers,
}
case "grpc":
network := GrpcNetworkConfig{}
if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
}
t.GRPCOptions = option.V2RayGRPCOptions{
ServiceName: network.ServiceName,
}
default:
t.Type = ""
}
in.Type = "trojan" in.Type = "trojan"
in.TrojanOptions = option.TrojanInboundOptions{ in.TrojanOptions = option.TrojanInboundOptions{
ListenOptions: listen, ListenOptions: listen,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &tls, TLS: &tls,
}, },
Transport: &t,
} }
if c.SingOptions.FallBackConfigs != nil { if c.SingOptions.FallBackConfigs != nil {
// fallback handling // fallback handling
@@ -280,6 +377,7 @@ func (b *Sing) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) e
b.ctx, b.ctx,
b.box.Router(), b.box.Router(),
b.logFactory.NewLogger(F.ToString("inbound/", c.Type, "[", tag, "]")), b.logFactory.NewLogger(F.ToString("inbound/", c.Type, "[", tag, "]")),
tag,
c, c,
nil, nil,
) )

View File

@@ -38,14 +38,13 @@ func init() {
func New(c *conf.CoreConfig) (vCore.Core, error) { func New(c *conf.CoreConfig) (vCore.Core, error) {
options := option.Options{} options := option.Options{}
if len(c.SingConfig.OriginalPath) != 0 { if len(c.SingConfig.OriginalPath) != 0 {
f, err := os.Open(c.SingConfig.OriginalPath) data, err := os.ReadFile(c.SingConfig.OriginalPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("open original config error: %s", err) return nil, fmt.Errorf("read original config error: %s", err)
} }
defer f.Close() err = json.Unmarshal(data, &options)
err = json.NewDecoder(f).Decode(&options)
if err != nil { if err != nil {
return nil, fmt.Errorf("decode original config error: %s", err) return nil, fmt.Errorf("unmarshal original config error: %s", err)
} }
} }
options.Log = &option.LogOptions{ options.Log = &option.LogOptions{
@@ -57,34 +56,20 @@ func New(c *conf.CoreConfig) (vCore.Core, error) {
options.NTP = &option.NTPOptions{ options.NTP = &option.NTPOptions{
Enabled: c.SingConfig.NtpConfig.Enable, Enabled: c.SingConfig.NtpConfig.Enable,
WriteToSystem: true, WriteToSystem: true,
ServerOptions: option.ServerOptions{
Server: c.SingConfig.NtpConfig.Server, Server: c.SingConfig.NtpConfig.Server,
ServerPort: c.SingConfig.NtpConfig.ServerPort, ServerPort: c.SingConfig.NtpConfig.ServerPort,
},
} }
os.Setenv("SING_DNS_PATH", "") os.Setenv("SING_DNS_PATH", "")
options.DNS = &option.DNSOptions{}
if c.SingConfig.DnsConfigPath != "" {
f, err := os.OpenFile(c.SingConfig.DnsConfigPath, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return nil, fmt.Errorf("failed to open or create sing dns config file: %s", err)
}
defer f.Close()
if err := json.NewDecoder(f).Decode(options.DNS); err != nil {
log.Warn(fmt.Sprintf(
"Failed to unmarshal sing dns config from file '%v': %v. Using default DNS options",
f.Name(), err))
options.DNS = &option.DNSOptions{}
}
os.Setenv("SING_DNS_PATH", c.SingConfig.DnsConfigPath)
}
ctx := context.Background()
b, err := box.New(box.Options{ b, err := box.New(box.Options{
Context: ctx, Context: context.Background(),
Options: options, Options: options,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
hs := NewHookServer(c.SingConfig.EnableConnClear) hs := NewHookServer(b.Router().GetCtx(), c.SingConfig.EnableConnClear)
b.Router().SetClashServer(hs) b.Router().SetClashServer(hs)
return &Sing{ return &Sing{
ctx: b.Router().GetCtx(), ctx: b.Router().GetCtx(),

View File

@@ -70,13 +70,15 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) {
err = b.inbounds[p.Tag].(*inbound.Hysteria).AddUsers(us) err = b.inbounds[p.Tag].(*inbound.Hysteria).AddUsers(us)
case "hysteria2": case "hysteria2":
us := make([]option.Hysteria2User, len(p.Users)) us := make([]option.Hysteria2User, len(p.Users))
id := make([]int, len(p.Users))
for i := range p.Users { for i := range p.Users {
us[i] = option.Hysteria2User{ us[i] = option.Hysteria2User{
Name: p.Users[i].Uuid, Name: p.Users[i].Uuid,
Password: p.Users[i].Uuid, Password: p.Users[i].Uuid,
} }
id[i] = p.Users[i].Id
} }
err = b.inbounds[p.Tag].(*inbound.Hysteria2).AddUsers(us) err = b.inbounds[p.Tag].(*inbound.Hysteria2).AddUsers(us, id)
} }
if err != nil { if err != nil {
return 0, err return 0, err

View File

@@ -5,6 +5,7 @@ package dispatcher
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -14,6 +15,7 @@ import (
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/log" "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
@@ -29,7 +31,7 @@ import (
"github.com/xtls/xray-core/transport/pipe" "github.com/xtls/xray-core/transport/pipe"
) )
var errSniffingTimeout = newError("timeout on sniffing") var errSniffingTimeout = errors.New("timeout on sniffing")
type cachedReader struct { type cachedReader struct {
sync.Mutex sync.Mutex
@@ -157,6 +159,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
var user *protocol.MemoryUser var user *protocol.MemoryUser
if sessionInbound != nil { if sessionInbound != nil {
user = sessionInbound.User user = sessionInbound.User
sessionInbound.CanSpliceCopy = 3
} }
var limit *limiter.Limiter var limit *limiter.Limiter
@@ -164,24 +167,25 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
if user != nil && len(user.Email) > 0 { if user != nil && len(user.Email) > 0 {
limit, err = limiter.GetLimiter(sessionInbound.Tag) limit, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil { if err != nil {
newError("get limiter ", sessionInbound.Tag, " error: ", err).AtError().WriteToLog() errors.LogInfo(ctx, "get limiter ", sessionInbound.Tag, " error: ", err)
common.Close(outboundLink.Writer) common.Close(outboundLink.Writer)
common.Close(inboundLink.Writer) common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader) common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader) common.Interrupt(inboundLink.Reader)
return nil, nil, nil, newError("get limiter ", sessionInbound.Tag, " error: ", err) return nil, nil, nil, errors.New("get limiter ", sessionInbound.Tag, " error: ", err)
} }
// Speed Limit and Device Limit // Speed Limit and Device Limit
w, reject := limit.CheckLimit(user.Email, w, reject := limit.CheckLimit(user.Email,
sessionInbound.Source.Address.IP().String(), sessionInbound.Source.Address.IP().String(),
network == net.Network_TCP) network == net.Network_TCP,
sessionInbound.Source.Network == net.Network_TCP)
if reject { if reject {
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(outboundLink.Writer)
common.Close(inboundLink.Writer) common.Close(inboundLink.Writer)
common.Interrupt(outboundLink.Reader) common.Interrupt(outboundLink.Reader)
common.Interrupt(inboundLink.Reader) common.Interrupt(inboundLink.Reader)
return nil, nil, nil, newError("Limited ", user.Email, " by conn or ip") return nil, nil, nil, errors.New("Limited ", user.Email, " by conn or ip")
} }
if w != nil { if w != nil {
inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w) inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w)
@@ -217,21 +221,33 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
return false return false
} }
for _, d := range request.ExcludeForDomain { for _, d := range request.ExcludeForDomain {
if strings.HasPrefix(d, "regexp:") {
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 { if strings.ToLower(domain) == d {
return false return false
} }
} }
}
protocolString := result.Protocol() protocolString := result.Protocol()
if resComp, ok := result.(SnifferResultComposite); ok { if resComp, ok := result.(SnifferResultComposite); ok {
protocolString = resComp.ProtocolForDomainResult() protocolString = resComp.ProtocolForDomainResult()
} }
for _, p := range request.OverrideDestinationForProtocol { 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 return true
} }
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" && if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) { destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
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 return true
} }
if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok { if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
@@ -249,11 +265,14 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
if !destination.IsValid() { if !destination.IsValid() {
panic("Dispatcher: Invalid destination.") panic("Dispatcher: Invalid destination.")
} }
ob := &session.Outbound{ outbounds := session.OutboundsFromContext(ctx)
OriginalTarget: destination, if len(outbounds) == 0 {
Target: destination, 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) content := session.ContentFromContext(ctx)
if content == nil { if content == nil {
content = new(session.Content) content = new(session.Content)
@@ -278,7 +297,7 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
} }
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
domain := result.Domain() domain := result.Domain()
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) errors.LogInfo(ctx, "sniffed domain: ", domain)
destination.Address = net.ParseAddress(domain) destination.Address = net.ParseAddress(domain)
protocol := result.Protocol() protocol := result.Protocol()
if resComp, ok := result.(SnifferResultComposite); ok { if resComp, ok := result.(SnifferResultComposite); ok {
@@ -303,13 +322,16 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
// DispatchLink implements routing.Dispatcher. // DispatchLink implements routing.Dispatcher.
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error { func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
if !destination.IsValid() { if !destination.IsValid() {
return newError("Dispatcher: Invalid destination.") return errors.New("Dispatcher: Invalid destination.")
} }
ob := &session.Outbound{ outbounds := session.OutboundsFromContext(ctx)
OriginalTarget: destination, if len(outbounds) == 0 {
Target: destination, 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) content := session.ContentFromContext(ctx)
if content == nil { if content == nil {
content = new(session.Content) content = new(session.Content)
@@ -329,7 +351,7 @@ func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.De
} }
if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) { if err == nil && d.shouldOverride(ctx, result, sniffingRequest, destination) {
domain := result.Domain() domain := result.Domain()
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) errors.LogInfo(ctx, "sniffed domain: ", domain)
destination.Address = net.ParseAddress(domain) destination.Address = net.ParseAddress(domain)
protocol := result.Protocol() protocol := result.Protocol()
if resComp, ok := result.(SnifferResultComposite); ok { if resComp, ok := result.(SnifferResultComposite); ok {
@@ -399,7 +421,8 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw
} }
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter, protocol string) { func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination, l *limiter.Limiter, protocol string) {
ob := session.OutboundFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() { if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() {
proxied := hosts.LookupHosts(ob.Target.String()) proxied := hosts.LookupHosts(ob.Target.String())
if proxied != nil { if proxied != nil {
@@ -426,7 +449,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
var err error var err error
l, err = limiter.GetLimiter(sessionInbound.Tag) l, err = limiter.GetLimiter(sessionInbound.Tag)
if err != nil { if err != nil {
newError("get limiter ", sessionInbound.Tag, " error: ", err).AtWarning().WriteToLog(session.ExportIDToError(ctx)) errors.LogError(ctx, "get limiter ", sessionInbound.Tag, " error: ", err)
} }
} }
if l != nil { if l != nil {
@@ -437,20 +460,20 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
destStr = destination.Address.IP().String() destStr = destination.Address.IP().String()
} }
if l.CheckDomainRule(destStr) { if l.CheckDomainRule(destStr) {
newError(fmt.Sprintf( errors.LogError(ctx, fmt.Sprintf(
"User %s access domain %s reject by rule", "User %s access domain %s reject by rule",
sessionInbound.User.Email, sessionInbound.User.Email,
destStr)).AtWarning().WriteToLog(session.ExportIDToError(ctx)) destStr))
common.Close(link.Writer) common.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return
} }
if len(protocol) != 0 { if len(protocol) != 0 {
if l.CheckProtocolRule(protocol) { if l.CheckProtocolRule(protocol) {
newError(fmt.Sprintf( errors.LogError(ctx, fmt.Sprintf(
"User %s access protocol %s reject by rule", "User %s access protocol %s reject by rule",
sessionInbound.User.Email, sessionInbound.User.Email,
protocol)).AtWarning().WriteToLog(session.ExportIDToError(ctx)) protocol))
common.Close(link.Writer) common.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return
@@ -468,10 +491,10 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
ctx = session.SetForcedOutboundTagToContext(ctx, "") ctx = session.SetForcedOutboundTagToContext(ctx, "")
if h := d.ohm.GetHandler(forcedOutboundTag); h != nil { if h := d.ohm.GetHandler(forcedOutboundTag); h != nil {
isPickRoute = 1 isPickRoute = 1
newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) errors.LogInfo(ctx, "taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]")
handler = h handler = h
} else { } 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.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return
@@ -481,13 +504,13 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
outTag := route.GetOutboundTag() outTag := route.GetOutboundTag()
if h := d.ohm.GetHandler(outTag); h != nil { if h := d.ohm.GetHandler(outTag); h != nil {
isPickRoute = 2 isPickRoute = 2
newError("taking detour [", outTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) errors.LogInfo(ctx, "taking detour [", outTag, "] for [", destination, "]")
handler = h handler = h
} else { } else {
newError("non existing outTag: ", outTag).AtWarning().WriteToLog(session.ExportIDToError(ctx)) errors.LogWarning(ctx, "non existing outTag: ", outTag)
} }
} else { } else {
newError("default route for ", destination).WriteToLog(session.ExportIDToError(ctx)) errors.LogInfo(ctx, "default route for ", destination)
} }
} }
@@ -500,7 +523,7 @@ func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.
} }
if handler == nil { 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.Close(link.Writer)
common.Interrupt(link.Reader) common.Interrupt(link.Reader)
return return

View File

@@ -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{})
}

View File

@@ -5,6 +5,7 @@ import (
"strings" "strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
@@ -22,15 +23,16 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
} }
if fakeDNSEngine == nil { 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{}, errNotInit
} }
return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) { return protocolSnifferWithMetadata{protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
Target := session.OutboundFromContext(ctx).Target outbounds := session.OutboundsFromContext(ctx)
if Target.Network == net.Network_TCP || Target.Network == net.Network_UDP { ob := outbounds[len(outbounds)-1]
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(Target.Address) if ob.Target.Network == net.Network_TCP || ob.Target.Network == net.Network_UDP {
domainFromFakeDNS := fakeDNSEngine.GetDomainFromFakeDNS(ob.Target.Address)
if domainFromFakeDNS != "" { 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 return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
} }
} }
@@ -38,7 +40,7 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil { if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt) ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok { if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
inPool := fkr0.IsIPInIPPool(Target.Address) inPool := fkr0.IsIPInIPPool(ob.Target.Address)
ipAddressInRangeValue.addressInRange = &inPool ipAddressInRangeValue.addressInRange = &inPool
} }
} }
@@ -108,10 +110,10 @@ func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWit
} }
return nil, common.ErrNoClue 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 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 return nil, common.ErrNoClue
}, },
metadataSniffer: false, metadataSniffer: false,

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol/bittorrent" "github.com/xtls/xray-core/common/protocol/bittorrent"
"github.com/xtls/xray-core/common/protocol/http" "github.com/xtls/xray-core/common/protocol/http"
@@ -52,7 +53,7 @@ func NewSniffer(ctx context.Context) *Sniffer {
return ret 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) { func (s *Sniffer) Sniff(c context.Context, payload []byte, network net.Network) (SniffResult, error) {
var pendingSniffer []protocolSnifferWithMetadata var pendingSniffer []protocolSnifferWithMetadata

View File

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

View File

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

View File

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

View File

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

289
go.mod
View File

@@ -1,125 +1,155 @@
module github.com/InazumaV/V2bX module github.com/InazumaV/V2bX
go 1.21.4 go 1.22
toolchain go1.22.5
require ( require (
github.com/apernet/hysteria/core/v2 v2.5.1-0.20240710201643-b563f3981fc6
github.com/apernet/hysteria/extras/v2 v2.5.1-0.20240710201643-b563f3981fc6
github.com/beevik/ntp v1.2.0 github.com/beevik/ntp v1.2.0
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/go-acme/lego/v4 v4.13.2 github.com/go-acme/lego/v4 v4.17.4
github.com/go-resty/resty/v2 v2.7.0 github.com/go-resty/resty/v2 v2.13.1
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.3
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/juju/ratelimit v1.0.2 github.com/juju/ratelimit v1.0.2
github.com/sagernet/sing v0.2.20-0.20231211084415-35e7014b0898 github.com/sagernet/sing v0.5.0-alpha.15
github.com/sagernet/sing-box v1.8.0 github.com/sagernet/sing-box v1.10.0-alpha.29
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/xtls/xray-core v1.8.7-0.20231212015254-46d6b9f57a2c github.com/spf13/viper v1.15.0
golang.org/x/crypto v0.16.0 github.com/xtls/xray-core v1.8.23
golang.org/x/sys v0.15.0 go.uber.org/zap v1.27.0
google.golang.org/protobuf v1.31.0 golang.org/x/crypto v0.25.0
golang.org/x/sys v0.22.0
google.golang.org/protobuf v1.34.2
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
berty.tech/go-libtor v1.0.385 // indirect berty.tech/go-libtor v1.0.385 // indirect
cloud.google.com/go/compute v1.23.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.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/dns/armdns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // 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/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.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/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/apernet/quic-go v0.45.2-0.20240702221538-ed74cfbe8b6e // indirect
github.com/aws/aws-sdk-go v1.39.0 // indirect github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.18 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect
github.com/aws/aws-sdk-go-v2/service/lightsail v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/caddyserver/certmagic v0.20.0 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/civo/civogo v0.3.11 // indirect github.com/civo/civogo v0.3.11 // indirect
github.com/cloudflare/circl v1.3.6 // indirect github.com/cloudflare/circl v1.3.9 // indirect
github.com/cloudflare/cloudflare-go v0.70.0 // indirect github.com/cloudflare/cloudflare-go v0.97.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cretz/bine v0.2.0 // indirect github.com/cretz/bine v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dnsimple/dnsimple-go v1.2.0 // indirect github.com/dnsimple/dnsimple-go v1.7.0 // indirect
github.com/exoscale/egoscale v0.100.1 // indirect github.com/exoscale/egoscale v0.102.3 // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/go-chi/render v1.0.3 // indirect
github.com/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gofrs/uuid/v5 v5.2.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/google/s2a-go v0.1.4 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.3.1 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/googleapis/gax-go/v2 v2.12.3 // indirect
github.com/gophercloud/gophercloud v1.0.0 // indirect github.com/gophercloud/gophercloud v1.12.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
github.com/gorilla/websocket v1.5.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.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/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
github.com/labbsr0x/goh v1.0.1 // indirect github.com/labbsr0x/goh v1.0.1 // indirect
github.com/libdns/alidns v1.0.3 // indirect github.com/libdns/alidns v1.0.3 // indirect
github.com/libdns/cloudflare v0.1.0 // indirect github.com/libdns/cloudflare v0.1.1 // indirect
github.com/libdns/libdns v0.2.1 // indirect github.com/libdns/libdns v0.2.2 // indirect
github.com/linode/linodego v1.17.2 // indirect github.com/linode/linodego v1.28.0 // indirect
github.com/liquidweb/go-lwApi v0.0.5 // indirect
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.3 // indirect github.com/liquidweb/liquidweb-go v1.6.4 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mholt/acmez v1.2.0 // indirect github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.57 // indirect github.com/miekg/dns v1.1.61 // indirect
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -127,108 +157,121 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/desec v0.7.0 // indirect github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
github.com/nrdcg/desec v0.8.0 // indirect
github.com/nrdcg/dnspod-go v0.4.0 // indirect github.com/nrdcg/dnspod-go v0.4.0 // indirect
github.com/nrdcg/freemyip v0.2.0 // indirect github.com/nrdcg/freemyip v0.2.0 // indirect
github.com/nrdcg/goinwx v0.8.2 // 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/namesilo v0.2.1 // indirect
github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.2.0 // indirect github.com/nrdcg/porkbun v0.3.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.13.1 // indirect github.com/onsi/ginkgo/v2 v2.19.0 // indirect
github.com/ooni/go-libtor v1.1.8 // indirect github.com/ooni/go-libtor v1.1.8 // indirect
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/oracle/oci-go-sdk/v65 v65.63.1 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/ovh/go-ovh v1.4.1 // indirect github.com/ovh/go-ovh v1.5.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/otp v1.4.0 // indirect github.com/pquerna/otp v1.4.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/quic-go/quic-go v0.40.0 // indirect github.com/quic-go/quic-go v0.45.1 // indirect
github.com/refraction-networking/utls v1.5.4 // indirect github.com/refraction-networking/utls v1.6.7 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/sacloud/api-client-go v0.2.8 // indirect github.com/sacloud/api-client-go v0.2.10 // indirect
github.com/sacloud/go-http v0.1.6 // indirect github.com/sacloud/go-http v0.1.8 // indirect
github.com/sacloud/iaas-api-go v1.11.1 // indirect github.com/sacloud/iaas-api-go v1.12.0 // indirect
github.com/sacloud/packages-go v0.0.9 // indirect github.com/sacloud/packages-go v0.0.10 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e // indirect github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect
github.com/sagernet/quic-go v0.40.0 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/sing-dns v0.1.11 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/sing-mux v0.1.6-0.20231208180947-9053c29513a2 // indirect github.com/sagernet/quic-go v0.46.0-beta.2 // indirect
github.com/sagernet/sing-quic v0.1.6-0.20231207143711-eb3cbf9ed054 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect github.com/sagernet/sing-dns v0.3.0-beta.14 // indirect
github.com/sagernet/sing-shadowsocks2 v0.1.6-0.20231207143709-50439739601a // indirect github.com/sagernet/sing-mux v0.2.0 // indirect
github.com/sagernet/sing-quic v0.2.0-beta.12 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/sing-tun v0.1.24-0.20231211145236-1cd52bf7fa00 // indirect github.com/sagernet/sing-tun v0.4.0-beta.13.0.20240703164908-1f043289199d // indirect
github.com/sagernet/sing-vmess v0.1.8 // indirect github.com/sagernet/sing-vmess v0.1.12 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // 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/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect
github.com/sagernet/utls v1.5.4 // indirect github.com/sagernet/utls v1.5.4 // indirect
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect github.com/selectel/domains-go v1.1.0 // indirect
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/softlayer/softlayer-go v1.1.2 // indirect github.com/softlayer/softlayer-go v1.1.5 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/sony/gobreaker v0.5.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.8.4 // indirect github.com/stretchr/testify v1.9.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect github.com/subosito/gotenv v1.4.2 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 // indirect
github.com/transip/gotransip/v6 v6.20.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/transip/gotransip/v6 v6.23.0 // indirect
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // 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.6.1-20231103022937-8589b6a // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/vultr/govultr/v2 v2.17.2 // indirect github.com/vultr/govultr/v2 v2.17.2 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.uber.org/mock v0.3.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect
go.uber.org/zap v1.26.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.0 // indirect golang.org/x/tools v0.23.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/api v0.128.0 // indirect google.golang.org/api v0.172.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/grpc v1.65.0 // indirect
google.golang.org/grpc v1.60.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
lukechampine.com/blake3 v1.2.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect
) )
replace ( // replace github.com/sagernet/sing-box v1.10.0-alpha.22 => /root/sing-box_mod
github.com/sagernet/sing-box v1.8.0 => github.com/wyx2685/sing-box_mod v0.0.0-20231211235528-165301110710 replace github.com/sagernet/sing-box v1.10.0-alpha.29 => github.com/wyx2685/sing-box_mod v1.10.0-alpha.29
github.com/xtls/xray-core v1.8.7-0.20231212015254-46d6b9f57a2c => github.com/wyx2685/Xray-core_mod v0.0.0-20231212110503-2cf5155077d9
)

882
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ package limiter
import ( import (
"errors" "errors"
"regexp" "regexp"
"strings"
"sync" "sync"
"time" "time"
@@ -45,8 +46,10 @@ type Limiter struct {
type UserLimitInfo struct { type UserLimitInfo struct {
UID int UID int
SpeedLimit int SpeedLimit int
DeviceLimit int
DynamicSpeedLimit int DynamicSpeedLimit int
ExpireTime int64 ExpireTime int64
OverLimit bool
} }
func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limiter { func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limiter {
@@ -60,14 +63,17 @@ func AddLimiter(tag string, l *conf.LimitConfig, users []panel.UserInfo) *Limite
uuidmap := make(map[string]int) uuidmap := make(map[string]int)
for i := range users { for i := range users {
uuidmap[users[i].Uuid] = users[i].Id uuidmap[users[i].Uuid] = users[i].Id
userLimit := &UserLimitInfo{}
userLimit.UID = users[i].Id
if users[i].SpeedLimit != 0 { if users[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{ userLimit.SpeedLimit = users[i].SpeedLimit
UID: users[i].Id,
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.UserLimitInfo.Store(format.UserTag(tag, users[i].Uuid), userLimit)
} }
}
info.UUIDtoUID = uuidmap info.UUIDtoUID = uuidmap
limitLock.Lock() limitLock.Lock()
limiter[tag] = info limiter[tag] = info
@@ -97,14 +103,18 @@ func (l *Limiter) UpdateUser(tag string, added []panel.UserInfo, deleted []panel
delete(l.UUIDtoUID, deleted[i].Uuid) delete(l.UUIDtoUID, deleted[i].Uuid)
} }
for i := range added { for i := range added {
if added[i].SpeedLimit != 0 {
userLimit := &UserLimitInfo{ userLimit := &UserLimitInfo{
UID: added[i].Id, UID: added[i].Id,
SpeedLimit: added[i].SpeedLimit,
ExpireTime: 0,
} }
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.UserLimitInfo.Store(format.UserTag(tag, added[i].Uuid), userLimit)
}
l.UUIDtoUID[added[i].Uuid] = added[i].Id l.UUIDtoUID[added[i].Uuid] = added[i].Id
} }
} }
@@ -120,35 +130,41 @@ func (l *Limiter) UpdateDynamicSpeedLimit(tag, uuid string, limit int, expire ti
return nil return nil
} }
func (l *Limiter) CheckLimit(email string, ip string, isTcp bool) (Bucket *ratelimit.Bucket, Reject bool) { 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:")
// ip and conn limiter // ip and conn limiter
if l.ConnLimiter.AddConnCount(email, ip, isTcp) { if l.ConnLimiter.AddConnCount(taguuid, ip, isTcp) {
return nil, true return nil, true
} }
// check and gen speed limit Bucket // check and gen speed limit Bucket
nodeLimit := l.SpeedLimit nodeLimit := l.SpeedLimit
userLimit := 0 userLimit := 0
if v, ok := l.UserLimitInfo.Load(email); ok { deviceLimit := 0
var uid int
if v, ok := l.UserLimitInfo.Load(taguuid); ok {
u := v.(*UserLimitInfo) u := v.(*UserLimitInfo)
deviceLimit = u.DeviceLimit
uid = u.UID
if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 { if u.ExpireTime < time.Now().Unix() && u.ExpireTime != 0 {
if u.SpeedLimit != 0 { if u.SpeedLimit != 0 {
userLimit = u.SpeedLimit userLimit = u.SpeedLimit
u.DynamicSpeedLimit = 0 u.DynamicSpeedLimit = 0
u.ExpireTime = 0 u.ExpireTime = 0
} else { } else {
l.UserLimitInfo.Delete(email) l.UserLimitInfo.Delete(taguuid)
} }
} else { } else {
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit) userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
} }
} }
if noSSUDP {
// Store online user for device limit // Store online user for device limit
ipMap := new(sync.Map) ipMap := new(sync.Map)
uid := l.UUIDtoUID[email]
ipMap.Store(ip, uid) ipMap.Store(ip, uid)
// If any device is online // If any device is online
if v, ok := l.UserOnlineIP.LoadOrStore(email, ipMap); ok { if v, ok := l.UserOnlineIP.LoadOrStore(taguuid, ipMap); ok {
ipMap := v.(*sync.Map) ipMap := v.(*sync.Map)
// If this is a new ip // If this is a new ip
if _, ok := ipMap.LoadOrStore(ip, uid); !ok { if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
@@ -157,16 +173,21 @@ func (l *Limiter) CheckLimit(email string, ip string, isTcp bool) (Bucket *ratel
counter++ counter++
return true return true
}) })
if counter > deviceLimit && deviceLimit > 0 {
ipMap.Delete(ip)
return nil, true
}
}
} }
} }
limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit limit := int64(determineSpeedLimit(nodeLimit, userLimit)) * 1000000 / 8 // If you need the Speed limit
if limit > 0 { if limit > 0 {
Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s Bucket = ratelimit.NewBucketWithQuantum(time.Second, limit, limit) // Byte/s
if v, ok := l.SpeedLimiter.LoadOrStore(email, Bucket); ok { if v, ok := l.SpeedLimiter.LoadOrStore(taguuid, Bucket); ok {
return v.(*ratelimit.Bucket), false return v.(*ratelimit.Bucket), false
} else { } else {
l.SpeedLimiter.Store(email, Bucket) l.SpeedLimiter.Store(taguuid, Bucket)
return Bucket, false return Bucket, false
} }
} else { } else {

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
package node package node
import ( import (
"fmt"
"strconv" "strconv"
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
@@ -60,7 +59,7 @@ func (c *Controller) reportUserTrafficTask() (err error) {
data := make(map[int][]string) data := make(map[int][]string)
for _, onlineuser := range result { for _, onlineuser := range result {
// json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] } // json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] }
data[onlineuser.UID] = append(data[onlineuser.UID], fmt.Sprintf("%s_%d", onlineuser.IP, c.info.Id)) data[onlineuser.UID] = append(data[onlineuser.UID], onlineuser.IP)
if _, ok := reportOnline[onlineuser.UID]; ok { if _, ok := reportOnline[onlineuser.UID]; ok {
reportOnline[onlineuser.UID]++ reportOnline[onlineuser.UID]++
} else { } else {
@@ -82,29 +81,24 @@ func (c *Controller) reportUserTrafficTask() (err error) {
} }
func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo) { func compareUserList(old, new []panel.UserInfo) (deleted, added []panel.UserInfo) {
tmp := map[string]struct{}{} oldMap := make(map[string]int)
tmp2 := map[string]struct{}{} for i, user := range old {
for i := range old { key := user.Uuid + strconv.Itoa(user.SpeedLimit)
tmp[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{} oldMap[key] = i
} }
l := len(tmp)
for i := range new { for _, user := range new {
e := new[i].Uuid + strconv.Itoa(new[i].SpeedLimit) key := user.Uuid + strconv.Itoa(user.SpeedLimit)
tmp[e] = struct{}{} if _, exists := oldMap[key]; !exists {
tmp2[e] = struct{}{} added = append(added, user)
if l != len(tmp) { } else {
added = append(added, new[i]) delete(oldMap, key)
l++
} }
} }
tmp = nil
l = len(tmp2) for _, index := range oldMap {
for i := range old { deleted = append(deleted, old[index])
tmp2[old[i].Uuid+strconv.Itoa(old[i].SpeedLimit)] = struct{}{}
if l != len(tmp2) {
deleted = append(deleted, old[i])
l++
}
} }
return deleted, added return deleted, added
} }