Compare commits

...

51 Commits

Author SHA1 Message Date
wyx2685
7dbe5fda85 fix import 2024-12-02 08:54:47 +09:00
wyx2685
4f9ccdf8db update xray-core&hy2-core 2024-12-02 08:38:23 +09:00
wyx2685
c2d5861d7d splithttp传输方式更名为xhttp 2024-11-26 10:29:12 +09:00
wyx2685
0e29c19f0e fix compile 2024-10-31 17:57:06 +09:00
wyx2685
a1c40bb1c8 update cores 2024-10-31 17:46:31 +09:00
wyx2685
a0de94efff Merge pull request #30 from KorenKrita/dev_new
add sing mux config support
2024-10-31 16:21:22 +08:00
KorenKrita
29928a1135 add sing mux config support 2024-10-31 14:21:02 +08:00
wyx2685
ab1ca837de update action 2024-10-06 18:19:58 +09:00
wyx2685
1f61446fa9 update core 2024-10-06 18:13:16 +09:00
wyx2685
c0325227db update xray-core v24.9.19 2024-09-19 10:27:13 +09:00
wyx2685
ba3036a7ac 更新 bug-report.md 2024-09-18 09:57:58 +08:00
wyx2685
f99e2b4489 移除fr模板 2024-09-18 10:53:35 +09:00
wyx2685
0af952be10 Update README.md 2024-09-16 23:17:49 +08:00
wyx2685
ad5971f164 update xray-core v24.9.16 2024-09-16 22:00:08 +09:00
wyx2685
f7d5d891c3 Fix:修复获取用户列表和在线设备列表时未正常重置资源的BUG 2024-09-12 00:10:31 +09:00
wyx2685
6936a76724 更新xray-core版本,回退sing-box版本 2024-09-12 00:08:15 +09:00
wyx2685
7184e49650 尝试修复其他面板兼容问题 2024-09-03 01:03:37 +09:00
wyx2685
ea0b7d8f40 面板无设备数列表接口时暂不报错 2024-09-01 05:27:11 +09:00
wyx2685
12fbcb1460 修复MAP未初始化 2024-08-31 17:19:13 +09:00
wyx2685
c6d48e1edf 升级内核 2024-08-31 17:06:00 +09:00
wyx2685
8d7168c6a4 尝试修复在线设备数上报后有概率被限制链接 2024-08-31 16:02:50 +09:00
wyx2685
173c48a76f 调整设备数限制实现方式 2024-08-30 06:48:41 +09:00
wyx2685
130e94cf45 Merge branch 'InazumaV:dev_new' into dev_new 2024-08-29 09:49:15 +08:00
wyx2685
89ddfff060 升级sing-box内核 2024-08-14 01:48:33 +09:00
wyx2685
07d49293d8 BUG修复前不对UDP链接进行限速 2024-08-14 01:35:30 +09:00
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
riolu.rs
a85352c402 fix(xray.ss) ss2022 users 2024-05-02 22:53:09 +08: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
42 changed files with 2126 additions and 1259 deletions

View File

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

View File

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

View File

@@ -1,16 +1,8 @@
name: Publish Docker image name: Publish Docker image
on: on:
workflow_dispatch: workflow_dispatch:
push: release:
branches: types: [published]
- dev_new
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
tags:
- 'v*'
pull_request: pull_request:
branches: branches:
- 'dev_new' - 'dev_new'

View File

@@ -98,7 +98,7 @@ jobs:
CGO_ENABLED: 0 CGO_ENABLED: 0
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Show workflow information - name: Show workflow information
id: get_filename id: get_filename
run: | run: |
@@ -107,9 +107,9 @@ jobs:
echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT echo "ASSET_NAME=$_NAME" >> $GITHUB_OUTPUT
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: '1.21.4' go-version: '1.23.2'
- name: Get project dependencies - name: Get project dependencies
run: go mod download run: go mod download
@@ -167,7 +167,7 @@ jobs:
run: | run: |
mv build_assets V2bX-$ASSET_NAME mv build_assets V2bX-$ASSET_NAME
- name: Upload files to Artifacts - name: Upload files to Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }} name: V2bX-${{ steps.get_filename.outputs.ASSET_NAME }}
path: | path: |

View File

@@ -1,10 +1,10 @@
# Build go # Build go
FROM golang:1.21.4-alpine AS builder FROM golang:1.23.2-alpine AS builder
WORKDIR /app WORKDIR /app
COPY . . COPY . .
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
RUN go mod download RUN go mod download
RUN go build -v -o V2bX -tags "sing xray with_reality_server with_quic with_grpc with_utls with_wireguard with_acme" RUN go build -v -o V2bX -tags "sing xray hysteria2 with_reality_server with_quic with_grpc with_utls with_wireguard with_acme"
# Release # Release
FROM alpine FROM alpine

View File

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

@@ -2,7 +2,6 @@ package panel
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/base64"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"reflect" "reflect"
@@ -10,7 +9,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/InazumaV/V2bX/common/crypt"
"github.com/goccy/go-json" "github.com/goccy/go-json"
) )
@@ -80,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 {
@@ -186,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)

View File

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

View File

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

View File

@@ -6,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

@@ -1,7 +1,5 @@
package conf package conf
import "time"
type Hysteria2Config struct { type Hysteria2Config struct {
LogConfig Hysteria2LogConfig `json:"Log"` LogConfig Hysteria2LogConfig `json:"Log"`
} }
@@ -17,84 +15,3 @@ func NewHysteria2Config() *Hysteria2Config {
}, },
} }
} }
type Hysteria2Options struct {
QUICConfig QUICConfig `json:"QUICConfig"`
Outbounds []Outbounds `json:"Outbounds"`
IgnoreClientBandwidth bool `json:"IgnoreClientBandwidth"`
DisableUDP bool `json:"DisableUDP"`
UDPIdleTimeout time.Duration `json:"UDPIdleTimeout"`
Masquerade serverConfigMasquerade `json:"masquerade"`
}
type QUICConfig struct {
InitialStreamReceiveWindow uint64
MaxStreamReceiveWindow uint64
InitialConnectionReceiveWindow uint64
MaxConnectionReceiveWindow uint64
MaxIdleTimeout time.Duration
MaxIncomingStreams int64
DisablePathMTUDiscovery bool // The server may still override this to true on unsupported platforms.
}
type ServerConfigOutboundDirect struct {
Mode string `json:"mode"`
BindIPv4 string `json:"bindIPv4"`
BindIPv6 string `json:"bindIPv6"`
BindDevice string `json:"bindDevice"`
}
type ServerConfigOutboundSOCKS5 struct {
Addr string `json:"addr"`
Username string `json:"username"`
Password string `json:"password"`
}
type ServerConfigOutboundHTTP struct {
URL string `json:"url"`
Insecure bool `json:"insecure"`
}
type Outbounds struct {
Name string `json:"name"`
Type string `json:"type"`
Direct ServerConfigOutboundDirect `json:"direct"`
SOCKS5 ServerConfigOutboundSOCKS5 `json:"socks5"`
HTTP ServerConfigOutboundHTTP `json:"http"`
}
type serverConfigMasqueradeFile struct {
Dir string `json:"dir"`
}
type serverConfigMasqueradeProxy struct {
URL string `json:"url"`
RewriteHost bool `json:"rewriteHost"`
}
type serverConfigMasqueradeString struct {
Content string `json:"content"`
Headers map[string]string `json:"headers"`
StatusCode int `json:"statusCode"`
}
type serverConfigMasquerade struct {
Type string `json:"type"`
File serverConfigMasqueradeFile `json:"file"`
Proxy serverConfigMasqueradeProxy `json:"proxy"`
String serverConfigMasqueradeString `json:"string"`
ListenHTTP string `json:"listenHTTP"`
ListenHTTPS string `json:"listenHTTPS"`
ForceHTTPS bool `json:"forceHTTPS"`
}
func NewHysteria2Options() *Hysteria2Options {
return &Hysteria2Options{
QUICConfig: QUICConfig{},
Outbounds: []Outbounds{},
IgnoreClientBandwidth: false,
DisableUDP: false,
UDPIdleTimeout: time.Duration(time.Duration.Seconds(30)),
Masquerade: serverConfigMasquerade{},
}
}

View File

@@ -103,18 +103,18 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) (err error) {
} }
type Options struct { type Options struct {
Name string `json:"Name"` Name string `json:"Name"`
Core string `json:"Core"` Core string `json:"Core"`
CoreName string `json:"CoreName"` CoreName string `json:"CoreName"`
ListenIP string `json:"ListenIP"` ListenIP string `json:"ListenIP"`
SendIP string `json:"SendIP"` SendIP string `json:"SendIP"`
DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"` DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"`
LimitConfig LimitConfig `json:"LimitConfig"` LimitConfig LimitConfig `json:"LimitConfig"`
RawOptions json.RawMessage `json:"RawOptions"` RawOptions json.RawMessage `json:"RawOptions"`
XrayOptions *XrayOptions `json:"XrayOptions"` XrayOptions *XrayOptions `json:"XrayOptions"`
SingOptions *SingOptions `json:"SingOptions"` SingOptions *SingOptions `json:"SingOptions"`
Hysteria2Options *Hysteria2Options `json:"Hysteria2Options"` Hysteria2ConfigPath string `json:"Hysteria2ConfigPath"`
CertConfig *CertConfig `json:"CertConfig"` CertConfig *CertConfig `json:"CertConfig"`
} }
func (o *Options) UnmarshalJSON(data []byte) error { func (o *Options) UnmarshalJSON(data []byte) error {
@@ -131,8 +131,8 @@ func (o *Options) UnmarshalJSON(data []byte) error {
o.SingOptions = NewSingOptions() o.SingOptions = NewSingOptions()
return json.Unmarshal(data, o.SingOptions) return json.Unmarshal(data, o.SingOptions)
case "hysteria2": case "hysteria2":
o.Hysteria2Options = NewHysteria2Options() o.RawOptions = data
return json.Unmarshal(data, o.Hysteria2Options) 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"`
} }
@@ -34,13 +33,13 @@ func NewSingConfig() *SingConfig {
} }
type SingOptions struct { type SingOptions struct {
EnableProxyProtocol bool `json:"EnableProxyProtocol"`
TCPFastOpen bool `json:"EnableTFO"` TCPFastOpen bool `json:"EnableTFO"`
SniffEnabled bool `json:"EnableSniff"` SniffEnabled bool `json:"EnableSniff"`
SniffOverrideDestination bool `json:"SniffOverrideDestination"`
EnableDNS bool `json:"EnableDNS"` EnableDNS bool `json:"EnableDNS"`
DomainStrategy option.DomainStrategy `json:"DomainStrategy"` DomainStrategy option.DomainStrategy `json:"DomainStrategy"`
SniffOverrideDestination bool `json:"SniffOverrideDestination"`
FallBackConfigs *FallBackConfigForSing `json:"FallBackConfigs"` FallBackConfigs *FallBackConfigForSing `json:"FallBackConfigs"`
Multiplex *MultiplexConfig `json:"MultiplexConfig"`
} }
type SingNtpConfig struct { type SingNtpConfig struct {
@@ -60,13 +59,25 @@ type FallBack struct {
ServerPort string `json:"ServerPort"` ServerPort string `json:"ServerPort"`
} }
type MultiplexConfig struct {
Enabled bool `json:"Enable"`
Padding bool `json:"Padding"`
Brutal BrutalOptions `json:"Brutal"`
}
type BrutalOptions struct {
Enabled bool `json:"Enable"`
UpMbps int `json:"UpMbps"`
DownMbps int `json:"DownMbps"`
}
func NewSingOptions() *SingOptions { func NewSingOptions() *SingOptions {
return &SingOptions{ return &SingOptions{
EnableDNS: false, EnableDNS: false,
EnableProxyProtocol: false,
TCPFastOpen: false, TCPFastOpen: false,
SniffEnabled: true, SniffEnabled: true,
SniffOverrideDestination: true, SniffOverrideDestination: true,
FallBackConfigs: &FallBackConfigForSing{}, FallBackConfigs: &FallBackConfigForSing{},
Multiplex: &MultiplexConfig{},
} }
} }

View File

@@ -13,11 +13,11 @@ import (
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"
"github.com/apernet/hysteria/core/server" "github.com/apernet/hysteria/core/v2/server"
"github.com/apernet/hysteria/extras/correctnet" "github.com/apernet/hysteria/extras/v2/correctnet"
"github.com/apernet/hysteria/extras/masq" "github.com/apernet/hysteria/extras/v2/masq"
"github.com/apernet/hysteria/extras/obfs" "github.com/apernet/hysteria/extras/v2/obfs"
"github.com/apernet/hysteria/extras/outbounds" "github.com/apernet/hysteria/extras/v2/outbounds"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -49,7 +49,7 @@ const (
defaultStreamReceiveWindow = 8388608 // 8MB defaultStreamReceiveWindow = 8388608 // 8MB
defaultConnReceiveWindow = defaultStreamReceiveWindow * 5 / 2 // 20MB defaultConnReceiveWindow = defaultStreamReceiveWindow * 5 / 2 // 20MB
defaultMaxIdleTimeout = 30 * time.Second defaultMaxIdleTimeout = 30 * time.Second
defaultMaxIncomingStreams = 1024 defaultMaxIncomingStreams = 4096
defaultUDPIdleTimeout = 60 * time.Second defaultUDPIdleTimeout = 60 * time.Second
) )
@@ -77,37 +77,49 @@ func (n *Hysteria2node) getTLSConfig(config *conf.Options) (*server.TLSConfig, e
} }
} }
func (n *Hysteria2node) getQUICConfig(config *conf.Options) (*server.QUICConfig, error) { func (n *Hysteria2node) getQUICConfig(config *serverConfig) (*server.QUICConfig, error) {
quic := &server.QUICConfig{} quic := &server.QUICConfig{}
if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow == 0 { if config.QUIC.InitStreamReceiveWindow == 0 {
quic.InitialStreamReceiveWindow = defaultStreamReceiveWindow quic.InitialStreamReceiveWindow = defaultStreamReceiveWindow
} else if config.Hysteria2Options.QUICConfig.InitialStreamReceiveWindow < 16384 { } else if config.QUIC.InitStreamReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.InitialStreamReceiveWindowf must be at least 16384") return nil, fmt.Errorf("QUICConfig.InitialStreamReceiveWindowf must be at least 16384")
} else {
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
} }
if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow == 0 { if config.QUIC.MaxStreamReceiveWindow == 0 {
quic.MaxStreamReceiveWindow = defaultStreamReceiveWindow quic.MaxStreamReceiveWindow = defaultStreamReceiveWindow
} else if config.Hysteria2Options.QUICConfig.MaxStreamReceiveWindow < 16384 { } else if config.QUIC.MaxStreamReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.MaxStreamReceiveWindowf must be at least 16384") return nil, fmt.Errorf("QUICConfig.MaxStreamReceiveWindowf must be at least 16384")
} else {
quic.MaxStreamReceiveWindow = config.QUIC.MaxStreamReceiveWindow
} }
if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow == 0 { if config.QUIC.InitConnectionReceiveWindow == 0 {
quic.InitialConnectionReceiveWindow = defaultConnReceiveWindow quic.InitialConnectionReceiveWindow = defaultConnReceiveWindow
} else if config.Hysteria2Options.QUICConfig.InitialConnectionReceiveWindow < 16384 { } else if config.QUIC.InitConnectionReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.InitialConnectionReceiveWindowf must be at least 16384") return nil, fmt.Errorf("QUICConfig.InitialConnectionReceiveWindowf must be at least 16384")
} else {
quic.InitialConnectionReceiveWindow = config.QUIC.InitConnectionReceiveWindow
} }
if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow == 0 { if config.QUIC.MaxConnectionReceiveWindow == 0 {
quic.MaxConnectionReceiveWindow = defaultConnReceiveWindow quic.MaxConnectionReceiveWindow = defaultConnReceiveWindow
} else if config.Hysteria2Options.QUICConfig.MaxConnectionReceiveWindow < 16384 { } else if config.QUIC.MaxConnectionReceiveWindow < 16384 {
return nil, fmt.Errorf("QUICConfig.MaxConnectionReceiveWindowf must be at least 16384") return nil, fmt.Errorf("QUICConfig.MaxConnectionReceiveWindowf must be at least 16384")
} else {
quic.MaxConnectionReceiveWindow = config.QUIC.MaxConnectionReceiveWindow
} }
if config.Hysteria2Options.QUICConfig.MaxIdleTimeout == 0 { if config.QUIC.MaxIdleTimeout == 0 {
quic.MaxIdleTimeout = defaultMaxIdleTimeout quic.MaxIdleTimeout = defaultMaxIdleTimeout
} else if config.Hysteria2Options.QUICConfig.MaxIdleTimeout < 4*time.Second || config.Hysteria2Options.QUICConfig.MaxIdleTimeout > 120*time.Second { } else if config.QUIC.MaxIdleTimeout < 4*time.Second || config.QUIC.MaxIdleTimeout > 120*time.Second {
return nil, fmt.Errorf("QUICConfig.MaxIdleTimeoutf must be between 4s and 120s") return nil, fmt.Errorf("QUICConfig.MaxIdleTimeoutf must be between 4s and 120s")
} else {
quic.MaxIdleTimeout = config.QUIC.MaxIdleTimeout
} }
if config.Hysteria2Options.QUICConfig.MaxIncomingStreams == 0 { if config.QUIC.MaxIncomingStreams == 0 {
quic.MaxIncomingStreams = defaultMaxIncomingStreams quic.MaxIncomingStreams = defaultMaxIncomingStreams
} else if config.Hysteria2Options.QUICConfig.MaxIncomingStreams < 8 { } else if config.QUIC.MaxIncomingStreams < 8 {
return nil, fmt.Errorf("QUICConfig.MaxIncomingStreamsf must be at least 8") return nil, fmt.Errorf("QUICConfig.MaxIncomingStreamsf must be at least 8")
} else {
quic.MaxIncomingStreams = config.QUIC.MaxIncomingStreams
} }
// todo fix !linux && !windows && !darwin // todo fix !linux && !windows && !darwin
quic.DisablePathMTUDiscovery = false quic.DisablePathMTUDiscovery = false
@@ -149,19 +161,24 @@ func (n *Hysteria2node) getBandwidthConfig(info *panel.NodeInfo) *server.Bandwid
return band return band
} }
func (n *Hysteria2node) getOutboundConfig(config *conf.Options) (server.Outbound, error) { 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 var obs []outbounds.OutboundEntry
if len(config.Hysteria2Options.Outbounds) == 0 { if len(c.Outbounds) == 0 {
// Guarantee we have at least one outbound // Guarantee we have at least one outbound
obs = []outbounds.OutboundEntry{{ obs = []outbounds.OutboundEntry{{
Name: "default", Name: "default",
Outbound: outbounds.NewDirectOutboundSimple(outbounds.DirectOutboundModeAuto), Outbound: outbounds.NewDirectOutboundSimple(outbounds.DirectOutboundModeAuto),
}} }}
} else { } else {
obs = make([]outbounds.OutboundEntry, len(config.Hysteria2Options.Outbounds)) obs = make([]outbounds.OutboundEntry, len(c.Outbounds))
for i, entry := range config.Hysteria2Options.Outbounds { for i, entry := range c.Outbounds {
if entry.Name == "" { if entry.Name == "" {
return nil, fmt.Errorf("outbounds.name empty outbound name") return nil, fmt.Errorf("empty outbound name")
} }
var ob outbounds.PluggableOutbound var ob outbounds.PluggableOutbound
var err error var err error
@@ -183,43 +200,107 @@ func (n *Hysteria2node) getOutboundConfig(config *conf.Options) (server.Outbound
} }
var uOb outbounds.PluggableOutbound // "unified" outbound var uOb outbounds.PluggableOutbound // "unified" outbound
// ACL
hasACL := false hasACL := false
if hasACL { if c.ACL.File != "" && len(c.ACL.Inline) > 0 {
// todo fix ACL 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 { } else {
// No ACL, use the first outbound // No ACL, use the first outbound
uOb = obs[0].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} Outbound := &outbounds.PluggableOutboundAdapter{PluggableOutbound: uOb}
return Outbound, nil return Outbound, nil
} }
func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.PacketConn, info *panel.NodeInfo, config *conf.Options) (http.Handler, error) { func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.PacketConn, c *serverConfig) (http.Handler, error) {
var handler http.Handler var handler http.Handler
switch strings.ToLower(config.Hysteria2Options.Masquerade.Type) { switch strings.ToLower(c.Masquerade.Type) {
case "", "404": case "", "404":
handler = http.NotFoundHandler() handler = http.NotFoundHandler()
case "file": case "file":
if config.Hysteria2Options.Masquerade.File.Dir == "" { if c.Masquerade.File.Dir == "" {
return nil, fmt.Errorf("masquerade.file.dir empty file directory") return nil, fmt.Errorf("masquerade.file.dir empty file directory")
} }
handler = http.FileServer(http.Dir(config.Hysteria2Options.Masquerade.File.Dir)) handler = http.FileServer(http.Dir(c.Masquerade.File.Dir))
case "proxy": case "proxy":
if config.Hysteria2Options.Masquerade.Proxy.URL == "" { if c.Masquerade.Proxy.URL == "" {
return nil, fmt.Errorf("masquerade.proxy.url empty proxy url") return nil, fmt.Errorf("masquerade.proxy.url empty proxy url")
} }
u, err := url.Parse(config.Hysteria2Options.Masquerade.Proxy.URL) u, err := url.Parse(c.Masquerade.Proxy.URL)
if err != nil { if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("masquerade.proxy.url %s", err)) return nil, fmt.Errorf("masquerade.proxy.url %s", err)
} }
handler = &httputil.ReverseProxy{ handler = &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) { Director: func(req *http.Request) {
r.SetURL(u) req.URL.Scheme = u.Scheme
// SetURL rewrites the Host header, req.URL.Host = u.Host
// but we don't want that if rewriteHost is false
if !config.Hysteria2Options.Masquerade.Proxy.RewriteHost { if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
r.Out.Host = r.In.Host 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) { ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
@@ -228,52 +309,88 @@ func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.Pac
}, },
} }
case "string": case "string":
if config.Hysteria2Options.Masquerade.String.Content == "" { if c.Masquerade.String.Content == "" {
return nil, fmt.Errorf("masquerade.string.content empty string content") return nil, fmt.Errorf("masquerade.string.content empty string content")
} }
if config.Hysteria2Options.Masquerade.String.StatusCode != 0 && if c.Masquerade.String.StatusCode != 0 &&
(config.Hysteria2Options.Masquerade.String.StatusCode < 200 || (c.Masquerade.String.StatusCode < 200 ||
config.Hysteria2Options.Masquerade.String.StatusCode > 599 || c.Masquerade.String.StatusCode > 599 ||
config.Hysteria2Options.Masquerade.String.StatusCode == 233) { c.Masquerade.String.StatusCode == 233) {
// 233 is reserved for Hysteria authentication // 233 is reserved for Hysteria authentication
return nil, fmt.Errorf("masquerade.string.statusCode invalid status code (must be 200-599, except 233)") 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) { handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for k, v := range config.Hysteria2Options.Masquerade.String.Headers { for k, v := range c.Masquerade.String.Headers {
w.Header().Set(k, v) w.Header().Set(k, v)
} }
if config.Hysteria2Options.Masquerade.String.StatusCode != 0 { if c.Masquerade.String.StatusCode != 0 {
w.WriteHeader(config.Hysteria2Options.Masquerade.String.StatusCode) w.WriteHeader(c.Masquerade.String.StatusCode)
} else { } else {
w.WriteHeader(http.StatusOK) // Use 200 OK by default w.WriteHeader(http.StatusOK) // Use 200 OK by default
} }
_, _ = w.Write([]byte(config.Hysteria2Options.Masquerade.String.Content)) _, _ = w.Write([]byte(c.Masquerade.String.Content))
}) })
default: default:
return nil, fmt.Errorf("masquerade.type unsupported masquerade type") return nil, fmt.Errorf("masquerade.type unsupported masquerade type")
} }
MasqHandler := &masqHandlerLogWrapper{H: handler, QUIC: true, Logger: n.Logger} MasqHandler := &masqHandlerLogWrapper{H: handler, QUIC: true, Logger: n.Logger}
if config.Hysteria2Options.Masquerade.ListenHTTP != "" || config.Hysteria2Options.Masquerade.ListenHTTPS != "" { if c.Masquerade.ListenHTTP != "" || c.Masquerade.ListenHTTPS != "" {
if config.Hysteria2Options.Masquerade.ListenHTTP != "" && config.Hysteria2Options.Masquerade.ListenHTTPS == "" { if c.Masquerade.ListenHTTP != "" && c.Masquerade.ListenHTTPS == "" {
return nil, fmt.Errorf("masquerade.listenHTTPS having only HTTP server without HTTPS is not supported") return nil, fmt.Errorf("masquerade.listenHTTPS having only HTTP server without HTTPS is not supported")
} }
s := masq.MasqTCPServer{ s := masq.MasqTCPServer{
QUICPort: extractPortFromAddr(conn.LocalAddr().String()), QUICPort: extractPortFromAddr(conn.LocalAddr().String()),
HTTPSPort: extractPortFromAddr(config.Hysteria2Options.Masquerade.ListenHTTPS), HTTPSPort: extractPortFromAddr(c.Masquerade.ListenHTTPS),
Handler: &masqHandlerLogWrapper{H: handler, QUIC: false}, Handler: &masqHandlerLogWrapper{H: handler, QUIC: false},
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
Certificates: tlsconfig.Certificates, Certificates: tlsconfig.Certificates,
GetCertificate: tlsconfig.GetCertificate, GetCertificate: tlsconfig.GetCertificate,
}, },
ForceHTTPS: config.Hysteria2Options.Masquerade.ForceHTTPS, ForceHTTPS: c.Masquerade.ForceHTTPS,
} }
go runMasqTCPServer(&s, config.Hysteria2Options.Masquerade.ListenHTTP, config.Hysteria2Options.Masquerade.ListenHTTPS, n.Logger) go runMasqTCPServer(&s, c.Masquerade.ListenHTTP, c.Masquerade.ListenHTTPS, n.Logger)
} }
return MasqHandler, nil 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) { func runMasqTCPServer(s *masq.MasqTCPServer, httpAddr, httpsAddr string, logger *zap.Logger) {
errChan := make(chan error, 2) errChan := make(chan error, 2)
if httpAddr != "" { if httpAddr != "" {

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

View File

@@ -4,23 +4,59 @@ import (
"sync" "sync"
"github.com/InazumaV/V2bX/common/counter" "github.com/InazumaV/V2bX/common/counter"
"github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/limiter"
"github.com/apernet/hysteria/core/v2/server"
quic "github.com/apernet/quic-go"
"go.uber.org/zap"
) )
var _ server.TrafficLogger = (*HookServer)(nil)
type HookServer struct { type HookServer struct {
Tag string Tag string
logger *zap.Logger
Counter sync.Map Counter sync.Map
} }
func (h *HookServer) Log(id string, tx, rx uint64) (ok bool) { func (h *HookServer) TraceStream(stream quic.Stream, stats *server.StreamStats) {
if c, ok := h.Counter.Load(h.Tag); ok { }
c.(*counter.TrafficCounter).Rx(id, int(rx))
c.(*counter.TrafficCounter).Tx(id, int(rx)) func (h *HookServer) UntraceStream(stream quic.Stream) {
return true }
} else {
c := counter.NewTrafficCounter() 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) h.Counter.Store(h.Tag, c)
c.Rx(id, int(rx)) }
c.Tx(id, int(rx))
if tc, ok := c.(*counter.TrafficCounter); ok {
tc.Rx(id, int(rx))
tc.Tx(id, int(tx))
return true return true
} }
return false
}
func (s *HookServer) LogOnlineState(id string, online bool) {
} }

View File

@@ -5,6 +5,7 @@ import (
"net" "net"
"strings" "strings"
"github.com/InazumaV/V2bX/common/format"
"github.com/InazumaV/V2bX/limiter" "github.com/InazumaV/V2bX/limiter"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
@@ -46,12 +47,18 @@ var logFormatMap = map[string]zapcore.EncoderConfig{
} }
func (l *serverLogger) Connect(addr net.Addr, uuid string, tx uint64) { func (l *serverLogger) Connect(addr net.Addr, uuid string, tx uint64) {
limiter, err := limiter.GetLimiter(l.Tag) limiterinfo, err := limiter.GetLimiter(l.Tag)
if err != nil { if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err)) l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
} }
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r { if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid)) 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)) l.logger.Info("client connected", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint64("tx", tx))
} }
@@ -61,12 +68,18 @@ func (l *serverLogger) Disconnect(addr net.Addr, uuid string, err error) {
} }
func (l *serverLogger) TCPRequest(addr net.Addr, uuid, reqAddr string) { func (l *serverLogger) TCPRequest(addr net.Addr, uuid, reqAddr string) {
limiter, err := limiter.GetLimiter(l.Tag) limiterinfo, err := limiter.GetLimiter(l.Tag)
if err != nil { if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err)) l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
} }
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r { if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid)) 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)) l.logger.Debug("TCP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.String("reqAddr", reqAddr))
} }
@@ -80,12 +93,18 @@ func (l *serverLogger) TCPError(addr net.Addr, uuid, reqAddr string, err error)
} }
func (l *serverLogger) UDPRequest(addr net.Addr, uuid string, sessionId uint32, reqAddr string) { func (l *serverLogger) UDPRequest(addr net.Addr, uuid string, sessionId uint32, reqAddr string) {
limiter, err := limiter.GetLimiter(l.Tag) limiterinfo, err := limiter.GetLimiter(l.Tag)
if err != nil { if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err)) l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
} }
if _, r := limiter.CheckLimit(uuid, extractIPFromAddr(addr), addr.Network() == "tcp"); r { if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
l.logger.Warn("Need Reject", zap.String("addr", addr.String()), zap.String("uuid", uuid)) 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)) l.logger.Debug("UDP request", zap.String("addr", addr.String()), zap.String("uuid", uuid), zap.Uint32("sessionId", sessionId), zap.String("reqAddr", reqAddr))
} }

View File

@@ -1,9 +1,12 @@
package hy2 package hy2
import ( import (
"strings"
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf" "github.com/InazumaV/V2bX/conf"
"github.com/apernet/hysteria/core/server" "github.com/apernet/hysteria/core/v2/server"
"github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -15,43 +18,20 @@ type Hysteria2node struct {
TrafficLogger server.TrafficLogger TrafficLogger server.TrafficLogger
} }
func (n *Hysteria2node) getHyConfig(tag string, info *panel.NodeInfo, config *conf.Options) (*server.Config, error) {
tls, err := n.getTLSConfig(config)
if err != nil {
return nil, err
}
quic, err := n.getQUICConfig(config)
if err != nil {
return nil, err
}
conn, err := n.getConn(info, config)
if err != nil {
return nil, err
}
Outbound, err := n.getOutboundConfig(config)
if err != nil {
return nil, err
}
Masq, err := n.getMasqHandler(tls, conn, info, config)
if err != nil {
return nil, err
}
return &server.Config{
TLSConfig: *tls,
QUICConfig: *quic,
Conn: conn,
Outbound: Outbound,
BandwidthConfig: *n.getBandwidthConfig(info),
IgnoreClientBandwidth: config.Hysteria2Options.IgnoreClientBandwidth,
DisableUDP: config.Hysteria2Options.DisableUDP,
UDPIdleTimeout: config.Hysteria2Options.UDPIdleTimeout,
EventLogger: n.EventLogger,
TrafficLogger: n.TrafficLogger,
MasqHandler: Masq,
}, nil
}
func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error { 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{ n := Hysteria2node{
Tag: tag, Tag: tag,
Logger: h.Logger, Logger: h.Logger,
@@ -60,10 +40,12 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio
logger: h.Logger, logger: h.Logger,
}, },
TrafficLogger: &HookServer{ TrafficLogger: &HookServer{
Tag: tag, Tag: tag,
logger: h.Logger,
}, },
} }
hyconfig, err := n.getHyConfig(tag, info, config)
hyconfig, err = n.getHyConfig(info, config, &c)
if err != nil { if err != nil {
return err return err
} }
@@ -76,7 +58,9 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio
h.Hy2nodes[tag] = n h.Hy2nodes[tag] = n
go func() { go func() {
if err := s.Serve(); err != nil { if err := s.Serve(); err != nil {
h.Logger.Error("Server Error", zap.Error(err)) if !strings.Contains(err.Error(), "quic: server closed") {
h.Logger.Error("Server Error", zap.Error(err))
}
} }
}() }()
return nil return nil

View File

@@ -1,61 +0,0 @@
package hy2
import (
"fmt"
"net"
"strings"
"github.com/InazumaV/V2bX/conf"
"github.com/apernet/hysteria/extras/outbounds"
)
func serverConfigOutboundDirectToOutbound(c conf.ServerConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
var mode outbounds.DirectOutboundMode
switch strings.ToLower(c.Mode) {
case "", "auto":
mode = outbounds.DirectOutboundModeAuto
case "64":
mode = outbounds.DirectOutboundMode64
case "46":
mode = outbounds.DirectOutboundMode46
case "6":
mode = outbounds.DirectOutboundMode6
case "4":
mode = outbounds.DirectOutboundMode4
default:
return nil, fmt.Errorf("outbounds.direct.mode unsupported mode")
}
bindIP := len(c.BindIPv4) > 0 || len(c.BindIPv6) > 0
bindDevice := len(c.BindDevice) > 0
if bindIP && bindDevice {
return nil, fmt.Errorf("outbounds.direct cannot bind both IP and device")
}
if bindIP {
ip4, ip6 := net.ParseIP(c.BindIPv4), net.ParseIP(c.BindIPv6)
if len(c.BindIPv4) > 0 && ip4 == nil {
return nil, fmt.Errorf("outbounds.direct.bindIPv4 invalid IPv4 address")
}
if len(c.BindIPv6) > 0 && ip6 == nil {
return nil, fmt.Errorf("outbounds.direct.bindIPv6 invalid IPv6 address")
}
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
}
if bindDevice {
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
}
return outbounds.NewDirectOutboundSimple(mode), nil
}
func serverConfigOutboundSOCKS5ToOutbound(c conf.ServerConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
if c.Addr == "" {
return nil, fmt.Errorf("outbounds.socks5.addr empty socks5 address")
}
return outbounds.NewSOCKS5Outbound(c.Addr, c.Username, c.Password), nil
}
func serverConfigOutboundHTTPToOutbound(c conf.ServerConfigOutboundHTTP) (outbounds.PluggableOutbound, error) {
if c.URL == "" {
return nil, fmt.Errorf("outbounds.http.url empty http address")
}
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
}

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

View File

@@ -7,7 +7,7 @@ import (
"github.com/InazumaV/V2bX/api/panel" "github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/common/counter" "github.com/InazumaV/V2bX/common/counter"
vCore "github.com/InazumaV/V2bX/core" vCore "github.com/InazumaV/V2bX/core"
"github.com/apernet/hysteria/core/server" "github.com/apernet/hysteria/core/v2/server"
) )
var _ server.Authenticator = &V2bX{} var _ server.Authenticator = &V2bX{}

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

@@ -40,6 +40,15 @@ type WsNetworkConfig struct {
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 {
@@ -52,7 +61,6 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
listen := option.ListenOptions{ listen := option.ListenOptions{
Listen: (*option.ListenAddress)(&addr), Listen: (*option.ListenAddress)(&addr),
ListenPort: uint16(info.Common.ServerPort), ListenPort: uint16(info.Common.ServerPort),
ProxyProtocol: c.SingOptions.EnableProxyProtocol,
TCPFastOpen: c.SingOptions.TCPFastOpen, TCPFastOpen: c.SingOptions.TCPFastOpen,
InboundOptions: option.InboundOptions{ InboundOptions: option.InboundOptions{
SniffEnabled: c.SingOptions.SniffEnabled, SniffEnabled: c.SingOptions.SniffEnabled,
@@ -60,6 +68,19 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
DomainStrategy: domainStrategy, DomainStrategy: domainStrategy,
}, },
} }
var multiplex *option.InboundMultiplexOptions
if c.SingOptions.Multiplex != nil {
multiplexOption := option.InboundMultiplexOptions{
Enabled: c.SingOptions.Multiplex.Enabled,
Padding: c.SingOptions.Multiplex.Padding,
Brutal: &option.BrutalOptions{
Enabled: c.SingOptions.Multiplex.Brutal.Enabled,
UpMbps: c.SingOptions.Multiplex.Brutal.UpMbps,
DownMbps: c.SingOptions.Multiplex.Brutal.DownMbps,
},
}
multiplex = &multiplexOption
}
var tls option.InboundTLSOptions var tls option.InboundTLSOptions
switch info.Security { switch info.Security {
case panel.Tls: case panel.Tls:
@@ -91,7 +112,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,
@@ -170,12 +191,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,6 +222,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
TLS: &tls, TLS: &tls,
}, },
Transport: &t, Transport: &t,
Multiplex: multiplex,
} }
} else { } else {
in.Type = "vmess" in.Type = "vmess"
@@ -194,6 +232,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
TLS: &tls, TLS: &tls,
}, },
Transport: &t, Transport: &t,
Multiplex: multiplex,
} }
} }
case "shadowsocks": case "shadowsocks":
@@ -203,7 +242,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
@@ -211,6 +250,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
in.ShadowsocksOptions = option.ShadowsocksInboundOptions{ in.ShadowsocksOptions = option.ShadowsocksInboundOptions{
ListenOptions: listen, ListenOptions: listen,
Method: n.Cipher, Method: n.Cipher,
Multiplex: multiplex,
} }
p := make([]byte, keyLength) p := make([]byte, keyLength)
_, _ = rand.Read(p) _, _ = rand.Read(p)
@@ -263,12 +303,16 @@ 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,
}
default: default:
t.Type = "" t.Type = ""
} }
@@ -279,6 +323,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
TLS: &tls, TLS: &tls,
}, },
Transport: &t, Transport: &t,
Multiplex: multiplex,
} }
if c.SingOptions.FallBackConfigs != nil { if c.SingOptions.FallBackConfigs != nil {
// fallback handling // fallback handling
@@ -348,6 +393,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

@@ -3,7 +3,6 @@ package sing
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@@ -57,33 +56,12 @@ 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,
Server: c.SingConfig.NtpConfig.Server, ServerOptions: option.ServerOptions{
ServerPort: c.SingConfig.NtpConfig.ServerPort, Server: c.SingConfig.NtpConfig.Server,
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()
data, err := io.ReadAll(f)
if err != nil {
log.Warn(fmt.Sprintf(
"Failed to read sing dns config from file '%v': %v. Using default DNS options",
f.Name(), err))
options.DNS = &option.DNSOptions{}
} else {
if err := json.Unmarshal(data, options.DNS); err != nil {
log.Warn(fmt.Sprintf(
"Failed to unmarshal sing dns config from file '%v': %v. Using default DNS options",
f.Name(), err))
options.DNS = &option.DNSOptions{}
}
}
os.Setenv("SING_DNS_PATH", c.SingConfig.DnsConfigPath)
}
b, err := box.New(box.Options{ b, err := box.New(box.Options{
Context: context.Background(), Context: context.Background(),
Options: options, Options: options,
@@ -91,7 +69,7 @@ func New(c *conf.CoreConfig) (vCore.Core, error) {
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

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

View File

@@ -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,8 +221,20 @@ 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.ToLower(domain) == d { if strings.HasPrefix(d, "regexp:") {
return false pattern := d[7:]
re, err := regexp.Compile(pattern)
if err != nil {
errors.LogInfo(ctx, "Unable to compile regex")
continue
}
if re.MatchString(domain) {
return false
}
} else {
if strings.ToLower(domain) == d {
return false
}
} }
} }
protocolString := result.Protocol() protocolString := result.Protocol()
@@ -226,12 +242,12 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
protocolString = resComp.ProtocolForDomainResult() protocolString = resComp.ProtocolForDomainResult()
} }
for _, p := range request.OverrideDestinationForProtocol { for _, p := range request.OverrideDestinationForProtocol {
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(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"
@@ -34,7 +35,7 @@ type Sniffer struct {
func NewSniffer(ctx context.Context) *Sniffer { func NewSniffer(ctx context.Context) *Sniffer {
ret := &Sniffer{ ret := &Sniffer{
sniffer: []protocolSnifferWithMetadata{ sniffer: []protocolSnifferWithMetadata{
{func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b) }, false, net.Network_TCP}, {func(c context.Context, b []byte) (SniffResult, error) { return http.SniffHTTP(b, ctx) }, false, net.Network_TCP},
{func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP}, {func(c context.Context, b []byte) (SniffResult, error) { return tls.SniffTLS(b) }, false, net.Network_TCP},
{func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP}, {func(c context.Context, b []byte) (SniffResult, error) { return bittorrent.SniffBittorrent(b) }, false, net.Network_TCP},
{func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP}, {func(c context.Context, b []byte) (SniffResult, error) { return quic.SniffQUIC(b) }, false, net.Network_UDP},
@@ -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

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

View File

@@ -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)
network = "tcp" if nodeInfo.Trojan.Network != "" {
network = nodeInfo.Trojan.Network
} else {
network = "tcp"
}
case "shadowsocks": case "shadowsocks":
err = buildShadowsocks(option, nodeInfo, in) err = buildShadowsocks(option, nodeInfo, in)
network = "tcp" network = "tcp"
@@ -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":
in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{ if in.StreamSetting.WSSettings != nil {
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol} //Enable proxy protocol in.StreamSetting.WSSettings.AcceptProxyProtocol = option.XrayOptions.EnableProxyProtocol
} else {
in.StreamSetting.WSSettings = &coreConf.WebSocketConfig{
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
} //Enable proxy protocol
}
default: default:
socketConfig := &coreConf.SocketConfig{ socketConfig := &coreConf.SocketConfig{
AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol, AcceptProxyProtocol: option.XrayOptions.EnableProxyProtocol,
@@ -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,67 @@ 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 "httpupgrade":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.HTTPUPGRADESettings)
if err != nil {
return fmt.Errorf("unmarshal httpupgrade settings error: %s", err)
}
case "splithttp", "xhttp":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.SplitHTTPSettings)
if err != nil {
return fmt.Errorf("unmarshal xhttp settings error: %s", err)
}
default:
return errors.New("the network type is not vail")
}
return nil
}
func buildTrojan(config *conf.Options, 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 +272,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

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

309
go.mod
View File

@@ -1,126 +1,159 @@
module github.com/InazumaV/V2bX module github.com/InazumaV/V2bX
go 1.21.4 go 1.23
toolchain go1.23.2
require ( require (
github.com/apernet/hysteria/core v1.3.5-0.20240201034858-bb99579bb92c github.com/apernet/hysteria/core/v2 v2.6.0
github.com/apernet/hysteria/extras v0.0.0-20240201034858-bb99579bb92c github.com/apernet/hysteria/extras/v2 v2.6.0
github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7
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.19.3-0.20241028134924-480950181787
github.com/go-resty/resty/v2 v2.7.0 github.com/go-resty/resty/v2 v2.15.3
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.2-0.20240618221538-6fa16eae919d
github.com/juju/ratelimit v1.0.2 github.com/juju/ratelimit v1.0.2
github.com/sagernet/sing v0.3.1-0.20240105061852-782bc05c5573 github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-box v1.8.2 github.com/sagernet/sing-box v1.11.0-alpha.2-1
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.1
github.com/xtls/xray-core v1.8.8-0.20240125151013-25c531c6c358 github.com/spf13/viper v1.18.2
go.uber.org/zap v1.26.0 github.com/xtls/xray-core v1.8.25-0.20241201231301-03d7d07c5b77
golang.org/x/crypto v0.18.0 go.uber.org/zap v1.27.0
golang.org/x/sys v0.16.0 golang.org/x/crypto v0.29.0
google.golang.org/protobuf v1.32.0 golang.org/x/sys v0.27.0
google.golang.org/protobuf v1.35.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.3 // indirect cloud.google.com/go/auth v0.9.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.1 // 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.14.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.7.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.10.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/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.63.15 // 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/aws/aws-sdk-go-v2 v1.30.5 // indirect
github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f // indirect github.com/aws/aws-sdk-go-v2/config v1.27.33 // indirect
github.com/aws/aws-sdk-go v1.39.0 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
github.com/aws/aws-sdk-go-v2/service/lightsail v1.40.6 // indirect
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
github.com/aws/smithy-go v1.20.4 // indirect
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/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.7 // indirect github.com/cloudflare/circl v1.4.0 // indirect
github.com/cloudflare/cloudflare-go v0.70.0 // indirect github.com/cloudflare/cloudflare-go v0.104.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.2-0.20180830191138-d8f796af33cc // 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/v3 v3.1.5 // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // 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.11 // indirect github.com/go-chi/chi/v5 v5.1.0 // 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.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.1.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.12.1 // indirect
github.com/gofrs/uuid/v5 v5.3.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.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/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-20231229205709-960ae82b1e42 // indirect github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/google/s2a-go v0.1.7 // indirect github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.4.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/gophercloud/gophercloud v1.0.0 // indirect github.com/gophercloud/gophercloud v1.14.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.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.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/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/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // 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/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/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/leodido/go-urn v1.2.4 // indirect
github.com/libdns/alidns v1.0.3 // indirect github.com/libdns/alidns v1.0.3 // indirect
github.com/libdns/cloudflare v0.1.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.40.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/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect
github.com/mholt/acmez v1.2.0 // indirect github.com/mholt/acmez v1.2.0 // indirect
github.com/miekg/dns v1.1.58 // indirect github.com/miekg/dns v1.1.62 // 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
@@ -128,105 +161,125 @@ 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.4.0 // indirect
github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect
github.com/onsi/ginkgo/v2 v2.13.2 // 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.73.0 // 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.6.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pires/go-proxyproto v0.8.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/otp v1.4.0 // indirect github.com/pquerna/otp v1.4.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.5.1 // 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.41.0 // indirect github.com/quic-go/quic-go v0.46.0 // indirect
github.com/refraction-networking/utls v1.6.1 // 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.1 // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/sing-dns v0.1.12 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/quic-go v0.48.0-beta.1 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-dns v0.3.0-rc.2 // indirect
github.com/sagernet/sing-mux v0.2.0 // indirect github.com/sagernet/sing-mux v0.2.0 // indirect
github.com/sagernet/sing-quic v0.1.7 // indirect github.com/sagernet/sing-quic v0.3.0-rc.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.0 // 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.2.0 // indirect github.com/sagernet/sing-tun v0.4.0-rc.4 // 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/utls v1.6.7 // indirect
github.com/sagernet/utls v1.5.4 // indirect
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // 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/sagikazarmark/locafero v0.4.0 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/selectel/domains-go v1.1.0 // indirect
github.com/selectel/go-selvpcclient/v3 v3.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/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.8.4 // indirect github.com/stretchr/testify v1.10.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002 // indirect
github.com/transip/gotransip/v6 v6.20.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/transip/gotransip/v6 v6.26.0 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 // indirect github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 // indirect
github.com/ultradns/ultradns-go-sdk v1.5.0-20230427130837-23c9b0c // indirect github.com/ultradns/ultradns-go-sdk v1.7.0-20240913052650-970ca9a // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect github.com/vishvananda/netlink v1.3.0 // 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/volcengine/volc-sdk-golang v1.0.177 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect github.com/vultr/govultr/v3 v3.9.1 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20240911120709-1fa0cb6f47c2 // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20240911121212-e4e74d0d02f5 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.uber.org/mock v0.4.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
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-20240103183307-be819d1f06fc // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.20.0 // indirect golang.org/x/net v0.31.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/sync v0.9.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/text v0.20.0 // indirect
golang.org/x/tools v0.17.0 // indirect golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.25.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/api v0.149.0 // indirect google.golang.org/api v0.197.0 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.67.1 // indirect
google.golang.org/grpc v1.61.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.12.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-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
) )
//github.com/apernet/hysteria/core v1.3.5-0.20240201034858-bb99579bb92c => /root/hysteria/core // replace github.com/sagernet/sing-box v1.10.0-alpha.22 => /root/sing-box_mod
replace github.com/sagernet/sing-box v1.8.2 => github.com/wyx2685/sing-box_mod v0.0.0 replace github.com/sagernet/sing-box v1.11.0-alpha.2-1 => github.com/wyx2685/sing-box_mod v1.11.0-alpha.2-1

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

View File

@@ -1,16 +1,9 @@
package main package main
import ( import (
//"net/http"
//_ "net/http/pprof"
"github.com/InazumaV/V2bX/cmd" "github.com/InazumaV/V2bX/cmd"
) )
func main() { func main() {
//内存泄漏排查
//go func() {
// http.ListenAndServe("127.0.0.1:6060", nil)
//}()
cmd.Run() cmd.Run()
} }

View File

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

View File

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

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"
@@ -15,11 +14,7 @@ func (c *Controller) reportUserTrafficTask() (err error) {
up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true) up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true)
if up > 0 || down > 0 { if up > 0 || down > 0 {
if c.LimitConfig.EnableDynamicSpeedLimit { if c.LimitConfig.EnableDynamicSpeedLimit {
if _, ok := c.traffic[c.userList[i].Uuid]; ok { c.traffic[c.userList[i].Uuid] += up + down
c.traffic[c.userList[i].Uuid] += up + down
} else {
c.traffic[c.userList[i].Uuid] = up + down
}
} }
userTraffic = append(userTraffic, panel.UserTraffic{ userTraffic = append(userTraffic, panel.UserTraffic{
UID: (c.userList)[i].Id, UID: (c.userList)[i].Id,
@@ -56,18 +51,12 @@ func (c *Controller) reportUserTrafficTask() (err error) {
result = append(result, online) result = append(result, online)
} }
} }
reportOnline := make(map[int]int)
data := make(map[int][]string) data := make(map[int][]string)
for _, onlineuser := range result { for _, onlineuser := range result {
// json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] } // json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] }
data[onlineuser.UID] = append(data[onlineuser.UID], fmt.Sprintf("%s_%d", onlineuser.IP, c.info.Id)) data[onlineuser.UID] = append(data[onlineuser.UID], onlineuser.IP)
if _, ok := reportOnline[onlineuser.UID]; ok {
reportOnline[onlineuser.UID]++
} else {
reportOnline[onlineuser.UID] = 1
}
} }
if err = c.apiClient.ReportNodeOnlineUsers(&data, &reportOnline); err != nil { if err = c.apiClient.ReportNodeOnlineUsers(&data); err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"tag": c.tag, "tag": c.tag,
"err": err, "err": err,