Files
HY2XS_flamy/apps/service/hysteria2_api.go
T

297 lines
8.1 KiB
Go

package service
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
"hy2xs-admin/dao"
"hy2xs-admin/model/bo"
"hy2xs-admin/model/constant"
"hy2xs-admin/proxy"
"net"
"net/url"
"strconv"
"strings"
"time"
)
func parseListenPort(listen string) (int, error) {
host, port, err := net.SplitHostPort(listen)
if err != nil {
return 0, err
}
if host == "" || port == "" {
return 0, errors.New("invalid listen address")
}
value, convErr := strconv.Atoi(port)
if convErr != nil || value < 1 || value > 65535 {
return 0, errors.New("invalid listen port")
}
return value, nil
}
func Hysteria2Auth(conPass string) (int64, string, error) {
if !Hysteria2IsRunning() {
return 0, "", errors.New("hysteria2 is not running")
}
now := time.Now().UnixMilli()
account, err := dao.GetAccount("con_pass = ? and deleted = 0 and (quota < 0 or quota > download + upload) and ? < expire_time and ? > kick_util_time", conPass, now, now)
if err != nil {
return 0, "", err
}
// Ограничение количества устройств
onlineUsers, err := Hysteria2Online()
if err != nil {
return 0, "", err
}
device, exist := onlineUsers[*account.Username]
if exist && *account.DeviceNo <= device {
return 0, "", errors.New("device limited")
}
return *account.Id, *account.Username, nil
}
func Hysteria2Online() (map[string]int64, error) {
if !Hysteria2IsRunning() {
return map[string]int64{}, nil
}
apiPort, err := GetHysteria2ApiPort()
if err != nil {
return nil, errors.New("get hysteria2 apiPort err")
}
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return nil, err
}
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*trafficSecretConfig.Value)
if err != nil {
return nil, err
}
return onlineUsers, nil
}
func Hysteria2Kick(ids []int64, kickUtilTime int64) error {
if !Hysteria2IsRunning() {
return errors.New("hysteria2 is not running")
}
if err := dao.UpdateAccount(ids, map[string]interface{}{"kick_util_time": kickUtilTime}); err != nil {
return err
}
accounts, err := dao.ListAccount("id in ?", ids)
if err != nil {
return err
}
var keys []string
for _, item := range accounts {
keys = append(keys, *item.Username)
}
apiPort, err := GetHysteria2ApiPort()
if err != nil {
return errors.New("get hysteria2 apiPort err")
}
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return err
}
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *trafficSecretConfig.Value); err != nil {
return err
}
return nil
}
func Hysteria2SubscribeUrl(accountId int64, protocol string, host string) (string, error) {
account, err := dao.GetAccount("id = ?", accountId)
if err != nil {
return "", err
}
config, err := dao.GetConfig("key = ?", constant.HUIWebContext)
if err != nil {
return "", err
}
webContext := ""
if config.Value != nil && *config.Value != "/" && strings.HasPrefix(*config.Value, "/") {
webContext = *config.Value
}
return fmt.Sprintf("%s//%s%s/hui/%s", protocol, host, webContext, url.QueryEscape(*account.ConPass)), nil
}
func Hysteria2Subscribe(conPass string, clientType string, host string) (string, string, error) {
hysteria2Config, err := GetHysteria2Config()
if err != nil {
return "", "", err
}
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
return "", "", errors.New("hysteria2 config is empty")
}
account, err := dao.GetAccount("con_pass = ?", conPass)
if err != nil {
return "", "", err
}
hysteria2Name := "hysteria2"
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
if err != nil {
return "", "", err
}
if *hysteria2ConfigRemark.Value != "" {
hysteria2Name = *hysteria2ConfigRemark.Value
}
userInfo := ""
configStr := ""
listenPort, err := parseListenPort(*hysteria2Config.Listen)
if err != nil {
return "", "", err
}
publicHost := strings.Split(host, ":")[0]
if clientType == constant.Shadowrocket || clientType == constant.Clash {
userInfo = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d",
*account.Upload,
*account.Download,
*account.Quota,
*account.ExpireTime/1000)
hysteria2 := bo.Hysteria2{
Name: hysteria2Name,
Type: "hysteria2",
Server: publicHost,
Port: strconv.Itoa(listenPort),
Password: conPass,
}
if hysteria2Config.Bandwidth != nil {
if hysteria2Config.Bandwidth.Up != nil &&
*hysteria2Config.Bandwidth.Up != "" {
hysteria2.Up = *hysteria2Config.Bandwidth.Up
}
if hysteria2Config.Bandwidth.Down != nil &&
*hysteria2Config.Bandwidth.Down != "" {
hysteria2.Down = *hysteria2Config.Bandwidth.Down
}
}
if hysteria2Config.Obfs != nil &&
hysteria2Config.Obfs.Type != nil &&
*hysteria2Config.Obfs.Type == "salamander" &&
hysteria2Config.Obfs.Salamander != nil &&
hysteria2Config.Obfs.Salamander.Password != nil &&
*hysteria2Config.Obfs.Salamander.Password != "" {
if clientType == constant.Shadowrocket {
hysteria2.Obfs = *hysteria2Config.Obfs.Salamander.Password
} else {
hysteria2.Obfs = "salamander"
hysteria2.ObfsPassword = *hysteria2Config.Obfs.Salamander.Password
}
}
if hysteria2Config.ACME != nil &&
hysteria2Config.ACME.Domains != nil &&
len(hysteria2Config.ACME.Domains) > 0 {
hysteria2.Sni = hysteria2Config.ACME.Domains[0]
}
hysteria2.SkipCertVerify = false
proxyGroup := bo.ProxyGroup{
Name: "PROXY",
Type: "select",
Proxies: []string{hysteria2Name},
}
clashConfig := bo.ClashConfig{
ProxyGroups: []bo.ProxyGroup{
proxyGroup,
},
Proxies: []interface{}{hysteria2},
}
clashConfigYaml, err := yaml.Marshal(&clashConfig)
if err != nil {
return "", "", err
}
configStr = string(clashConfigYaml)
if clientType == constant.Clash {
clashExtension, err := GetConfig(constant.ClashExtension)
if err != nil {
return "", "", err
}
if clashExtension.Value != nil && *clashExtension.Value != "" {
configStr = fmt.Sprintf("%s%s", configStr, *clashExtension.Value)
}
}
} else if clientType == constant.V2rayN {
hysteria2Url, err := Hysteria2Url(*account.Id, strings.Split(host, ":")[0])
if err != nil {
return "", "", err
}
configStr = hysteria2Url
}
return userInfo, configStr, nil
}
func Hysteria2Url(accountId int64, hostname string) (string, error) {
hysteria2Config, err := GetHysteria2Config()
if err != nil {
return "", err
}
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
return "", errors.New("hysteria2 config is empty")
}
port, err := parseListenPort(*hysteria2Config.Listen)
if err != nil {
return "", err
}
if hostname == "" || hostname == "0.0.0.0" {
return "", errors.New("invalid public host")
}
account, err := dao.GetAccount("id = ?", accountId)
if err != nil {
return "", err
}
urlConfig := ""
if hysteria2Config.Obfs != nil &&
hysteria2Config.Obfs.Type != nil &&
*hysteria2Config.Obfs.Type == "salamander" &&
hysteria2Config.Obfs.Salamander != nil &&
hysteria2Config.Obfs.Salamander.Password != nil &&
*hysteria2Config.Obfs.Salamander.Password != "" {
urlConfig += fmt.Sprintf("&obfs=salamander&obfs-password=%s", *hysteria2Config.Obfs.Salamander.Password)
}
if hysteria2Config.ACME != nil &&
hysteria2Config.ACME.Domains != nil &&
len(hysteria2Config.ACME.Domains) > 0 {
urlConfig += fmt.Sprintf("&sni=%s", hysteria2Config.ACME.Domains[0])
// shadowrocket
urlConfig += fmt.Sprintf("&peer=%s", hysteria2Config.ACME.Domains[0])
}
urlConfig += "&insecure=0"
if hysteria2Config.Bandwidth != nil &&
hysteria2Config.Bandwidth.Down != nil &&
*hysteria2Config.Bandwidth.Down != "" {
// shadowrocket
urlConfig += fmt.Sprintf("&downmbps=%s", url.PathEscape(*hysteria2Config.Bandwidth.Down))
}
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
if err != nil {
return "", err
}
if *hysteria2ConfigRemark.Value != "" {
urlConfig += fmt.Sprintf("#%s", *hysteria2ConfigRemark.Value)
}
if urlConfig != "" {
urlConfig = "/?" + strings.TrimPrefix(urlConfig, "&")
}
return fmt.Sprintf("hysteria2://%s@%s:%d", *account.ConPass, hostname, port) + urlConfig, nil
}