mirror of
https://github.com/cedar2025/hysteria.git
synced 2025-06-08 05:19:56 +00:00
feat: 初步添加v2board 支持
This commit is contained in:
parent
e57eeb986b
commit
47f354927c
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
./github
|
||||
build
|
@ -36,4 +36,4 @@ RUN set -ex \
|
||||
|
||||
COPY --from=builder /go/bin/hysteria /usr/local/bin/hysteria
|
||||
|
||||
ENTRYPOINT ["hysteria"]
|
||||
ENTRYPOINT ["hysteria" , "server", "-c", "/etc/hysteria/server.yaml"]
|
@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
@ -40,6 +41,7 @@ func init() {
|
||||
}
|
||||
|
||||
type serverConfig struct {
|
||||
V2board *v2boardConfig `mapstructure:"v2board"`
|
||||
Listen string `mapstructure:"listen"`
|
||||
Obfs serverConfigObfs `mapstructure:"obfs"`
|
||||
TLS *serverConfigTLS `mapstructure:"tls"`
|
||||
@ -57,6 +59,12 @@ type serverConfig struct {
|
||||
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
|
||||
}
|
||||
|
||||
type v2boardConfig struct {
|
||||
ApiHost string `mapstructure:"apiHost"`
|
||||
ApiKey string `mapstructure:"apiKey"`
|
||||
NodeID uint `mapstructure:"nodeID"`
|
||||
}
|
||||
|
||||
type serverConfigObfsSalamander struct {
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
@ -582,6 +590,31 @@ func (c *serverConfig) fillAuthenticator(hyConfig *server.Config) error {
|
||||
}
|
||||
hyConfig.Authenticator = &auth.CommandAuthenticator{Cmd: c.Auth.Command}
|
||||
return nil
|
||||
case "v2board":
|
||||
// 定时获取用户列表并储存
|
||||
// 判断URL是否存在
|
||||
v2boardConfig := c.V2board
|
||||
if v2boardConfig.ApiHost == "" || v2boardConfig.ApiKey == "" || v2boardConfig.NodeID == 0 {
|
||||
return configError{Field: "auth.v2board", Err: errors.New("v2board config error")}
|
||||
}
|
||||
// 创建一个url.Values来存储查询参数
|
||||
queryParams := url.Values{}
|
||||
|
||||
// 添加token、node_id和node_type参数
|
||||
queryParams.Add("token", v2boardConfig.ApiKey)
|
||||
queryParams.Add("node_id", strconv.Itoa(int(v2boardConfig.NodeID)))
|
||||
queryParams.Add("node_type", "hysteria")
|
||||
|
||||
// 创建完整的URL,包括查询参数
|
||||
url := v2boardConfig.ApiHost + "/api/v1/server/UniProxy/user?" + queryParams.Encode()
|
||||
|
||||
// 创建定时更新用户UUID协程
|
||||
go auth.UpdateUsers(url, time.Second*5)
|
||||
|
||||
hyConfig.Authenticator = &auth.V2boardApiProvider{URL: url}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return configError{Field: "auth.type", Err: errors.New("unsupported auth type")}
|
||||
}
|
||||
@ -596,6 +629,15 @@ func (c *serverConfig) fillTrafficLogger(hyConfig *server.Config) error {
|
||||
if c.TrafficStats.Listen != "" {
|
||||
tss := trafficlogger.NewTrafficStatsServer()
|
||||
hyConfig.TrafficLogger = tss
|
||||
// 添加定时更新用户使用流量协程
|
||||
if c.V2board != nil && c.V2board.ApiHost != "" {
|
||||
// 创建一个url.Values来存储查询参数
|
||||
queryParams := url.Values{}
|
||||
queryParams.Add("token", c.V2board.ApiKey)
|
||||
queryParams.Add("node_id", strconv.Itoa(int(c.V2board.NodeID)))
|
||||
queryParams.Add("node_type", "hysteria")
|
||||
go hyConfig.TrafficLogger.PushTrafficToV2boardInterval(c.V2board.ApiHost+"/api/v1/server/UniProxy/push?"+queryParams.Encode(), time.Second*60)
|
||||
}
|
||||
go runTrafficStatsServer(c.TrafficStats.Listen, tss)
|
||||
}
|
||||
return nil
|
||||
@ -707,6 +749,19 @@ func (c *serverConfig) Config() (*server.Config, error) {
|
||||
return hyConfig, nil
|
||||
}
|
||||
|
||||
type ResponseNodeInfo struct {
|
||||
Host string `json:"host"`
|
||||
ServerPort uint `json:"server_port"`
|
||||
ServerName string `json:"server_name"`
|
||||
UpMbps uint `json:"up_mbps"`
|
||||
DownMbps uint `json:"down_mbps"`
|
||||
Obfs string `json:"obfs"`
|
||||
BaseConfig struct {
|
||||
PushInterval int `json:"push_interval"`
|
||||
PullInterval int `json:"pull_interval"`
|
||||
} `json:"base_config"`
|
||||
}
|
||||
|
||||
func runServer(cmd *cobra.Command, args []string) {
|
||||
logger.Info("server mode")
|
||||
|
||||
@ -717,11 +772,68 @@ func runServer(cmd *cobra.Command, args []string) {
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
logger.Fatal("failed to parse server config", zap.Error(err))
|
||||
}
|
||||
// 如果配置了v2board 则自动获取监听端口、obfs
|
||||
if config.V2board != nil && config.V2board.ApiHost != "" {
|
||||
// 创建一个url.Values来存储查询参数
|
||||
queryParams := url.Values{}
|
||||
queryParams.Add("token", config.V2board.ApiKey)
|
||||
queryParams.Add("node_id", strconv.Itoa(int(config.V2board.NodeID)))
|
||||
queryParams.Add("node_type", "hysteria")
|
||||
|
||||
// 创建完整的URL,包括查询参数
|
||||
nodeInfoUrl := config.V2board.ApiHost + "/api/v1/server/UniProxy/config?" + queryParams.Encode()
|
||||
|
||||
// 发起 HTTP GET 请求
|
||||
resp, err := http.Get(nodeInfoUrl)
|
||||
if err != nil {
|
||||
// 处理错误
|
||||
fmt.Println("HTTP GET 请求出错:", err)
|
||||
logger.Fatal("failed to client v2board api to get nodeInfo", zap.Error(err))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// 读取响应数据
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to read v2board reaponse", zap.Error(err))
|
||||
}
|
||||
// 解析JSON数据
|
||||
var responseNodeInfo ResponseNodeInfo
|
||||
err = json.Unmarshal(body, &responseNodeInfo)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to unmarshal v2board reaponse", zap.Error(err))
|
||||
}
|
||||
// 给 hy的端口、obfs、上行下行进行赋值
|
||||
if responseNodeInfo.ServerPort != 0 {
|
||||
config.Listen = ":" + strconv.Itoa(int(responseNodeInfo.ServerPort))
|
||||
}
|
||||
if responseNodeInfo.DownMbps != 0 {
|
||||
config.Bandwidth.Down = strconv.Itoa(int(responseNodeInfo.DownMbps)) + "Mbps"
|
||||
}
|
||||
if responseNodeInfo.UpMbps != 0 {
|
||||
config.Bandwidth.Up = strconv.Itoa(int(responseNodeInfo.UpMbps)) + "Mbps"
|
||||
}
|
||||
if responseNodeInfo.Obfs != "" {
|
||||
config.Obfs.Type = "salamander"
|
||||
config.Obfs.Salamander.Password = responseNodeInfo.Obfs
|
||||
}
|
||||
|
||||
}
|
||||
hyConfig, err := config.Config()
|
||||
if err != nil {
|
||||
logger.Fatal("failed to load server config", zap.Error(err))
|
||||
}
|
||||
|
||||
// // 添加定时更新用户使用流量协程
|
||||
// if config.V2board != nil && config.V2board.ApiHost != "" {
|
||||
// // 创建一个url.Values来存储查询参数
|
||||
// queryParams := url.Values{}
|
||||
// queryParams.Add("token", config.V2board.ApiKey)
|
||||
// queryParams.Add("node_id", strconv.Itoa(int(config.V2board.NodeID)))
|
||||
// queryParams.Add("node_type", "hysteria")
|
||||
|
||||
// go hyConfig.TrafficLogger.PushTrafficToV2boardInterval(config.V2board.ApiHost+"/api/v1/server/UniProxy/push?"+queryParams.Encode(), time.Second*10)
|
||||
// }
|
||||
|
||||
s, err := server.NewServer(hyConfig)
|
||||
if err != nil {
|
||||
logger.Fatal("failed to initialize server", zap.Error(err))
|
||||
|
@ -1,3 +1,8 @@
|
||||
v2board:
|
||||
apiHost: http://localhost
|
||||
apiKey: test
|
||||
nodeID: 1
|
||||
|
||||
listen: :8443
|
||||
|
||||
obfs:
|
||||
|
@ -196,4 +196,5 @@ type EventLogger interface {
|
||||
// The implementation of this interface must be thread-safe.
|
||||
type TrafficLogger interface {
|
||||
Log(id string, tx, rx uint64) (ok bool)
|
||||
PushTrafficToV2boardInterval(url string, interval time.Duration)
|
||||
}
|
||||
|
88
extras/auth/v2board.go
Normal file
88
extras/auth/v2board.go
Normal file
@ -0,0 +1,88 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
|
||||
var _ server.Authenticator = &V2boardApiProvider{}
|
||||
|
||||
type V2boardApiProvider struct {
|
||||
Client *http.Client
|
||||
URL string
|
||||
}
|
||||
|
||||
// 用户列表
|
||||
var (
|
||||
users []User
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
SpeedLimit *uint32 `json:"speed_limit"`
|
||||
}
|
||||
type ResponseData struct {
|
||||
Users []User `json:"users"`
|
||||
}
|
||||
|
||||
func getUserList(url string) ([]User, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var responseData ResponseData
|
||||
err = json.NewDecoder(resp.Body).Decode(&responseData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return responseData.Users, nil
|
||||
}
|
||||
|
||||
func UpdateUsers(url string, interval time.Duration) {
|
||||
|
||||
fmt.Println("定时更新用户列表进程已开启")
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
userList, err := getUserList(url)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
continue
|
||||
}
|
||||
lock.Lock()
|
||||
users = userList
|
||||
lock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证代码
|
||||
func (v *V2boardApiProvider) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
|
||||
|
||||
// 获取判断连接用户是否在用户列表内
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
for _, s := range users {
|
||||
if s.UUID == string(auth) {
|
||||
return true, strconv.Itoa(s.ID)
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package trafficlogger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
)
|
||||
@ -27,6 +31,69 @@ func NewTrafficStatsServer() TrafficStatsServer {
|
||||
}
|
||||
}
|
||||
|
||||
type TrafficPushRequest struct {
|
||||
Data map[string][2]int64
|
||||
}
|
||||
|
||||
// 定时提交用户流量情况
|
||||
func (s *trafficStatsServerImpl) PushTrafficToV2boardInterval(url string, interval time.Duration) {
|
||||
fmt.Println("提交用户流量情况进程已开启")
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := s.PushTrafficToV2board(url); err != nil {
|
||||
fmt.Println("提交用户流量情况失败:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 向v2board 提交用户流量使用情况
|
||||
func (s *trafficStatsServerImpl) PushTrafficToV2board(url string) error {
|
||||
s.Mutex.Lock() // 写锁,阻止其他操作 StatsMap 的并发访问
|
||||
defer s.Mutex.Unlock() // 确保在函数退出时释放写锁
|
||||
|
||||
// 创建一个请求对象并填充数据
|
||||
request := TrafficPushRequest{
|
||||
Data: make(map[string][2]int64),
|
||||
}
|
||||
for id, stats := range s.StatsMap {
|
||||
request.Data[id] = [2]int64{int64(stats.Tx), int64(stats.Rx)}
|
||||
}
|
||||
// 如果不存在数据则跳过
|
||||
if len(request.Data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将请求对象转换为JSON
|
||||
jsonData, err := json.Marshal(request.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 发起HTTP请求并提交数据
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
fmt.Println(resp)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查HTTP响应状态,处理错误等
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("HTTP request failed with status code: " + resp.Status)
|
||||
}
|
||||
|
||||
// 清空流量记录
|
||||
s.StatsMap = make(map[string]*trafficStatsEntry)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type trafficStatsServerImpl struct {
|
||||
Mutex sync.RWMutex
|
||||
StatsMap map[string]*trafficStatsEntry
|
||||
|
Loading…
x
Reference in New Issue
Block a user