mirror of
https://github.com/cedar2025/hysteria.git
synced 2025-06-08 13:29: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
|
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"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
@ -40,6 +41,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type serverConfig struct {
|
type serverConfig struct {
|
||||||
|
V2board *v2boardConfig `mapstructure:"v2board"`
|
||||||
Listen string `mapstructure:"listen"`
|
Listen string `mapstructure:"listen"`
|
||||||
Obfs serverConfigObfs `mapstructure:"obfs"`
|
Obfs serverConfigObfs `mapstructure:"obfs"`
|
||||||
TLS *serverConfigTLS `mapstructure:"tls"`
|
TLS *serverConfigTLS `mapstructure:"tls"`
|
||||||
@ -57,6 +59,12 @@ type serverConfig struct {
|
|||||||
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
|
Masquerade serverConfigMasquerade `mapstructure:"masquerade"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type v2boardConfig struct {
|
||||||
|
ApiHost string `mapstructure:"apiHost"`
|
||||||
|
ApiKey string `mapstructure:"apiKey"`
|
||||||
|
NodeID uint `mapstructure:"nodeID"`
|
||||||
|
}
|
||||||
|
|
||||||
type serverConfigObfsSalamander struct {
|
type serverConfigObfsSalamander struct {
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
}
|
}
|
||||||
@ -582,6 +590,31 @@ func (c *serverConfig) fillAuthenticator(hyConfig *server.Config) error {
|
|||||||
}
|
}
|
||||||
hyConfig.Authenticator = &auth.CommandAuthenticator{Cmd: c.Auth.Command}
|
hyConfig.Authenticator = &auth.CommandAuthenticator{Cmd: c.Auth.Command}
|
||||||
return nil
|
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:
|
default:
|
||||||
return configError{Field: "auth.type", Err: errors.New("unsupported auth type")}
|
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 != "" {
|
if c.TrafficStats.Listen != "" {
|
||||||
tss := trafficlogger.NewTrafficStatsServer()
|
tss := trafficlogger.NewTrafficStatsServer()
|
||||||
hyConfig.TrafficLogger = tss
|
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)
|
go runTrafficStatsServer(c.TrafficStats.Listen, tss)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -707,6 +749,19 @@ func (c *serverConfig) Config() (*server.Config, error) {
|
|||||||
return hyConfig, nil
|
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) {
|
func runServer(cmd *cobra.Command, args []string) {
|
||||||
logger.Info("server mode")
|
logger.Info("server mode")
|
||||||
|
|
||||||
@ -717,11 +772,68 @@ func runServer(cmd *cobra.Command, args []string) {
|
|||||||
if err := viper.Unmarshal(&config); err != nil {
|
if err := viper.Unmarshal(&config); err != nil {
|
||||||
logger.Fatal("failed to parse server config", zap.Error(err))
|
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()
|
hyConfig, err := config.Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("failed to load server config", zap.Error(err))
|
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)
|
s, err := server.NewServer(hyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal("failed to initialize server", zap.Error(err))
|
logger.Fatal("failed to initialize server", zap.Error(err))
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
v2board:
|
||||||
|
apiHost: http://localhost
|
||||||
|
apiKey: test
|
||||||
|
nodeID: 1
|
||||||
|
|
||||||
listen: :8443
|
listen: :8443
|
||||||
|
|
||||||
obfs:
|
obfs:
|
||||||
|
@ -196,4 +196,5 @@ type EventLogger interface {
|
|||||||
// The implementation of this interface must be thread-safe.
|
// The implementation of this interface must be thread-safe.
|
||||||
type TrafficLogger interface {
|
type TrafficLogger interface {
|
||||||
Log(id string, tx, rx uint64) (ok bool)
|
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
|
package trafficlogger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/apernet/hysteria/core/server"
|
"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 {
|
type trafficStatsServerImpl struct {
|
||||||
Mutex sync.RWMutex
|
Mutex sync.RWMutex
|
||||||
StatsMap map[string]*trafficStatsEntry
|
StatsMap map[string]*trafficStatsEntry
|
||||||
|
Loading…
x
Reference in New Issue
Block a user