189 lines
4.8 KiB
Go
189 lines
4.8 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"hy2xs-admin/dao"
|
|
"hy2xs-admin/model/constant"
|
|
"hy2xs-admin/proxy"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func resolvePublicEndpoint() (string, int, error) {
|
|
host := strings.TrimSpace(os.Getenv("HY2XS_PUBLIC_HOST"))
|
|
if host == "" || host == "0.0.0.0" {
|
|
return "", 0, errors.New("HY2XS_PUBLIC_HOST must be set to public domain or IPv4")
|
|
}
|
|
portRaw := strings.TrimSpace(os.Getenv("HY2XS_PUBLIC_PORT"))
|
|
if portRaw == "" {
|
|
return "", 0, errors.New("HY2XS_PUBLIC_PORT is required")
|
|
}
|
|
port, err := strconv.Atoi(portRaw)
|
|
if err != nil || port < 1 || port > 65535 {
|
|
return "", 0, errors.New("HY2XS_PUBLIC_PORT must be a valid TCP port")
|
|
}
|
|
return host, port, nil
|
|
}
|
|
|
|
func Hysteria2Auth(conPass string) (int64, string, error) {
|
|
if !Hysteria2IsRunning() {
|
|
return 0, "", errors.New("hysteria2 is not running")
|
|
}
|
|
|
|
now := time.Now().UnixMilli()
|
|
secretDigest, digestErr := PeerSecretDigest(conPass)
|
|
if digestErr != nil {
|
|
return 0, "", digestErr
|
|
}
|
|
peer, err := dao.GetPeer(`secret_digest = ?
|
|
and disabled = 0
|
|
and (quota_bytes < 0 or quota_bytes > download_bytes + upload_bytes)
|
|
and (expires_at = 0 or ? < expires_at)
|
|
and ? > banned_until`, secretDigest, now, now)
|
|
if err != nil {
|
|
return 0, "", err
|
|
}
|
|
|
|
// Ограничение количества устройств
|
|
onlineUsers, err := Hysteria2Online()
|
|
if err != nil {
|
|
return 0, "", err
|
|
}
|
|
device, exist := onlineUsers[*peer.AuthId]
|
|
if exist && *peer.MaxDevices <= device {
|
|
return 0, "", errors.New("device limited")
|
|
}
|
|
|
|
return *peer.Id, *peer.AuthId, 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.UpdatePeer(ids, map[string]interface{}{"banned_until": kickUtilTime}); err != nil {
|
|
return err
|
|
}
|
|
|
|
peers, err := dao.ListPeer("id in ?", ids)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var keys []string
|
|
for _, item := range peers {
|
|
keys = append(keys, *item.AuthId)
|
|
}
|
|
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 Hysteria2Url(accountId int64) (string, error) {
|
|
hysteria2Config, err := GetHysteria2Config()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
|
|
return "", errors.New("hysteria2 config is empty")
|
|
}
|
|
hostname, port, err := resolvePublicEndpoint()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
peer, err := dao.GetPeer("id = ?", accountId)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
remark := ""
|
|
if hysteria2ConfigRemark.Value != nil {
|
|
remark = *hysteria2ConfigRemark.Value
|
|
}
|
|
|
|
obfsType := ""
|
|
obfsPassword := ""
|
|
if hysteria2Config.Obfs != nil &&
|
|
hysteria2Config.Obfs.Type != nil &&
|
|
hysteria2Config.Obfs.Salamander != nil &&
|
|
hysteria2Config.Obfs.Salamander.Password != nil {
|
|
obfsType = *hysteria2Config.Obfs.Type
|
|
obfsPassword = *hysteria2Config.Obfs.Salamander.Password
|
|
}
|
|
|
|
sni := ""
|
|
if hysteria2Config.ACME != nil && len(hysteria2Config.ACME.Domains) > 0 {
|
|
sni = hysteria2Config.ACME.Domains[0]
|
|
}
|
|
|
|
secret := ""
|
|
if peer.SecretEncrypted != nil {
|
|
decrypted, decErr := DecryptPeerSecret(*peer.SecretEncrypted)
|
|
if decErr != nil {
|
|
return "", decErr
|
|
}
|
|
secret = decrypted
|
|
}
|
|
return buildHysteria2Url(secret, hostname, port, obfsType, obfsPassword, sni, remark), nil
|
|
}
|
|
|
|
func buildHysteria2Url(conPass string, hostname string, port int, obfsType string, obfsPassword string, sni string, remark string) string {
|
|
query := url.Values{}
|
|
if obfsType == "salamander" && obfsPassword != "" {
|
|
query.Set("obfs", "salamander")
|
|
query.Set("obfs-password", obfsPassword)
|
|
}
|
|
if sni != "" {
|
|
query.Set("sni", sni)
|
|
}
|
|
query.Set("insecure", "0")
|
|
|
|
u := url.URL{
|
|
Scheme: "hysteria2",
|
|
User: url.User(conPass),
|
|
Host: net.JoinHostPort(hostname, strconv.Itoa(port)),
|
|
Path: "/",
|
|
RawQuery: query.Encode(),
|
|
}
|
|
if strings.TrimSpace(remark) != "" {
|
|
u.Fragment = remark
|
|
}
|
|
return u.String()
|
|
}
|