mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
refactor(backend): retire hysteria2 as a top-level protocol
Hysteria v2 is not a separate xray protocol — it is plain "hysteria" with streamSettings.version = 2. The frontend already dropped hysteria2 from the protocol enum in 5a90f7e3; the backend was still carrying the literal as a compat alias. Removed: - model.Hysteria2 constant - model.IsHysteria helper (only callers were buildProxy + genHysteriaLink) - TestIsHysteria - "hysteria2" from the Inbound.Protocol validate oneof enum - All `case model.Hysteria, model.Hysteria2:` and `case "hysteria", "hysteria2":` branches across client.go, inbound.go, outbound.go, xray.go, port_conflict.go, xray/api.go, subService.go, subJsonService.go, subClashService.go - Stale #4081 comments Kept (correctly — these are client-side URI/config schemes that are independent of the xray protocol type): - hysteria2:// share-link URI in subService.genHysteriaLink - "hysteria2" Clash proxy type in subClashService.buildHysteriaProxy - Comments referring to Hysteria v2 as a transport version Note: this change does not include a DB migration. Existing rows with protocol = 'hysteria2' will fall through to the default switch arms after upgrade. A separate `UPDATE inbounds SET protocol = 'hysteria' WHERE protocol = 'hysteria2'` is required for installs that still hold legacy data.
This commit is contained in:
parent
15787dbdfe
commit
d843014461
15 changed files with 52 additions and 82 deletions
|
|
@ -14,7 +14,11 @@ import (
|
||||||
// Protocol represents the protocol type for Xray inbounds.
|
// Protocol represents the protocol type for Xray inbounds.
|
||||||
type Protocol string
|
type Protocol string
|
||||||
|
|
||||||
// Protocol constants for different Xray inbound protocols
|
// Protocol constants for different Xray inbound protocols.
|
||||||
|
// Hysteria v2 is not a distinct protocol — it is plain "hysteria"
|
||||||
|
// with streamSettings.version = 2. The share-link URI scheme
|
||||||
|
// "hysteria2://" is independent of this and is still emitted by the
|
||||||
|
// link generator when the stream version is 2.
|
||||||
const (
|
const (
|
||||||
VMESS Protocol = "vmess"
|
VMESS Protocol = "vmess"
|
||||||
VLESS Protocol = "vless"
|
VLESS Protocol = "vless"
|
||||||
|
|
@ -25,16 +29,8 @@ const (
|
||||||
Mixed Protocol = "mixed"
|
Mixed Protocol = "mixed"
|
||||||
WireGuard Protocol = "wireguard"
|
WireGuard Protocol = "wireguard"
|
||||||
Hysteria Protocol = "hysteria"
|
Hysteria Protocol = "hysteria"
|
||||||
Hysteria2 Protocol = "hysteria2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsHysteria returns true for both "hysteria" and "hysteria2".
|
|
||||||
// Use instead of a bare ==model.Hysteria check: a v2 inbound stored
|
|
||||||
// with the literal v2 string would otherwise fall through (#4081).
|
|
||||||
func IsHysteria(p Protocol) bool {
|
|
||||||
return p == Hysteria || p == Hysteria2
|
|
||||||
}
|
|
||||||
|
|
||||||
// User represents a user account in the 3x-ui panel.
|
// User represents a user account in the 3x-ui panel.
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
|
@ -60,7 +56,7 @@ type Inbound struct {
|
||||||
// Xray configuration fields
|
// Xray configuration fields
|
||||||
Listen string `json:"listen" form:"listen"`
|
Listen string `json:"listen" form:"listen"`
|
||||||
Port int `json:"port" form:"port" validate:"gte=1,lte=65535"`
|
Port int `json:"port" form:"port" validate:"gte=1,lte=65535"`
|
||||||
Protocol Protocol `json:"protocol" form:"protocol" validate:"required,oneof=vmess vless trojan shadowsocks wireguard hysteria hysteria2 http mixed tunnel"`
|
Protocol Protocol `json:"protocol" form:"protocol" validate:"required,oneof=vmess vless trojan shadowsocks wireguard hysteria http mixed tunnel"`
|
||||||
Settings string `json:"settings" form:"settings"`
|
Settings string `json:"settings" form:"settings"`
|
||||||
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
|
|
||||||
|
|
@ -189,21 +189,3 @@ func TestInboundClientIpsUnmarshalJSONAcceptsBothShapes(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsHysteria(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
in Protocol
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{Hysteria, true},
|
|
||||||
{Hysteria2, true},
|
|
||||||
{VLESS, false},
|
|
||||||
{Shadowsocks, false},
|
|
||||||
{Protocol(""), false},
|
|
||||||
{Protocol("hysteria3"), false},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
if got := IsHysteria(c.in); got != c.want {
|
|
||||||
t.Errorf("IsHysteria(%q) = %v, want %v", c.in, got, c.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -170,9 +170,8 @@ func (s *SubClashService) getProxies(inbound *model.Inbound, client model.Client
|
||||||
|
|
||||||
func (s *SubClashService) buildProxy(inbound *model.Inbound, client model.Client, stream map[string]any, extraRemark string) map[string]any {
|
func (s *SubClashService) buildProxy(inbound *model.Inbound, client model.Client, stream map[string]any, extraRemark string) map[string]any {
|
||||||
// Hysteria has its own transport + TLS model, applyTransport /
|
// Hysteria has its own transport + TLS model, applyTransport /
|
||||||
// applySecurity don't fit. IsHysteria also covers the literal
|
// applySecurity don't fit.
|
||||||
// "hysteria2" protocol string (#4081).
|
if inbound.Protocol == model.Hysteria {
|
||||||
if model.IsHysteria(inbound.Protocol) {
|
|
||||||
return s.buildHysteriaProxy(inbound, client, extraRemark)
|
return s.buildHysteriaProxy(inbound, client, extraRemark)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||||
newOutbounds = append(newOutbounds, s.genVless(inbound, streamSettings, client))
|
newOutbounds = append(newOutbounds, s.genVless(inbound, streamSettings, client))
|
||||||
case "trojan", "shadowsocks":
|
case "trojan", "shadowsocks":
|
||||||
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
newOutbounds = append(newOutbounds, s.genHy(inbound, newStream, client))
|
newOutbounds = append(newOutbounds, s.genHy(inbound, newStream, client))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error)
|
||||||
JOIN client_inbounds ON client_inbounds.inbound_id = inbounds.id
|
JOIN client_inbounds ON client_inbounds.inbound_id = inbounds.id
|
||||||
JOIN clients ON clients.id = client_inbounds.client_id
|
JOIN clients ON clients.id = client_inbounds.client_id
|
||||||
WHERE
|
WHERE
|
||||||
inbounds.protocol in ('vmess','vless','trojan','shadowsocks','hysteria','hysteria2')
|
inbounds.protocol in ('vmess','vless','trojan','shadowsocks','hysteria')
|
||||||
AND clients.sub_id = ? AND inbounds.enable = ?
|
AND clients.sub_id = ? AND inbounds.enable = ?
|
||||||
)`, subId, true).Find(&inbounds).Error
|
)`, subId, true).Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -279,7 +279,7 @@ func (s *SubService) GetLink(inbound *model.Inbound, email string) string {
|
||||||
return s.genTrojanLink(inbound, email)
|
return s.genTrojanLink(inbound, email)
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
return s.genShadowsocksLink(inbound, email)
|
return s.genShadowsocksLink(inbound, email)
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
return s.genHysteriaLink(inbound, email)
|
return s.genHysteriaLink(inbound, email)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -492,7 +492,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) string {
|
||||||
if !model.IsHysteria(inbound.Protocol) {
|
if inbound.Protocol != model.Hysteria {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var stream map[string]any
|
var stream map[string]any
|
||||||
|
|
|
||||||
|
|
@ -103,15 +103,17 @@ func applyZodValidations(expr string, t TypeRef, rules []ValidateRule) string {
|
||||||
expr += fmt.Sprintf(".lt(%s)", r.Param)
|
expr += fmt.Sprintf(".lt(%s)", r.Param)
|
||||||
}
|
}
|
||||||
case "min":
|
case "min":
|
||||||
if t.Kind == KindString {
|
switch t.Kind {
|
||||||
|
case KindString:
|
||||||
expr += fmt.Sprintf(".min(%s)", r.Param)
|
expr += fmt.Sprintf(".min(%s)", r.Param)
|
||||||
} else if t.Kind == KindInt || t.Kind == KindNumber {
|
case KindInt, KindNumber:
|
||||||
expr += fmt.Sprintf(".min(%s)", r.Param)
|
expr += fmt.Sprintf(".min(%s)", r.Param)
|
||||||
}
|
}
|
||||||
case "max":
|
case "max":
|
||||||
if t.Kind == KindString {
|
switch t.Kind {
|
||||||
|
case KindString:
|
||||||
expr += fmt.Sprintf(".max(%s)", r.Param)
|
expr += fmt.Sprintf(".max(%s)", r.Param)
|
||||||
} else if t.Kind == KindInt || t.Kind == KindNumber {
|
case KindInt, KindNumber:
|
||||||
expr += fmt.Sprintf(".max(%s)", r.Param)
|
expr += fmt.Sprintf(".max(%s)", r.Param)
|
||||||
}
|
}
|
||||||
case "url":
|
case "url":
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ func parseStructTag(raw string) (json string, validate string, gormHasDash bool)
|
||||||
json = tag.Get("json")
|
json = tag.Get("json")
|
||||||
validate = tag.Get("validate")
|
validate = tag.Get("validate")
|
||||||
if g := tag.Get("gorm"); g != "" {
|
if g := tag.Get("gorm"); g != "" {
|
||||||
for _, part := range strings.Split(g, ";") {
|
for part := range strings.SplitSeq(g, ";") {
|
||||||
if strings.TrimSpace(part) == "-" {
|
if strings.TrimSpace(part) == "-" {
|
||||||
gormHasDash = true
|
gormHasDash = true
|
||||||
}
|
}
|
||||||
|
|
@ -95,17 +95,17 @@ func parseValidateTag(tag string) []ValidateRule {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var rules []ValidateRule
|
var rules []ValidateRule
|
||||||
for _, part := range strings.Split(tag, ",") {
|
for part := range strings.SplitSeq(tag, ",") {
|
||||||
part = strings.TrimSpace(part)
|
part = strings.TrimSpace(part)
|
||||||
if part == "" {
|
if part == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
eq := strings.IndexByte(part, '=')
|
before, after, ok := strings.Cut(part, "=")
|
||||||
if eq < 0 {
|
if !ok {
|
||||||
rules = append(rules, ValidateRule{Name: part})
|
rules = append(rules, ValidateRule{Name: part})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rules = append(rules, ValidateRule{Name: part[:eq], Param: part[eq+1:]})
|
rules = append(rules, ValidateRule{Name: before, Param: after})
|
||||||
}
|
}
|
||||||
return rules
|
return rules
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -70,7 +71,7 @@ func clientKeyForProtocol(p model.Protocol, rec *model.ClientRecord) string {
|
||||||
return rec.Password
|
return rec.Password
|
||||||
case model.Shadowsocks:
|
case model.Shadowsocks:
|
||||||
return rec.Email
|
return rec.Email
|
||||||
case model.Hysteria, model.Hysteria2:
|
case model.Hysteria:
|
||||||
return rec.Auth
|
return rec.Auth
|
||||||
default:
|
default:
|
||||||
return rec.UUID
|
return rec.UUID
|
||||||
|
|
@ -478,7 +479,7 @@ func (s *ClientService) fillProtocolDefaults(c *model.Client, ib *model.Inbound)
|
||||||
if c.Password == "" || !validShadowsocksClientKey(method, c.Password) {
|
if c.Password == "" || !validShadowsocksClientKey(method, c.Password) {
|
||||||
c.Password = randomShadowsocksClientKey(method)
|
c.Password = randomShadowsocksClientKey(method)
|
||||||
}
|
}
|
||||||
case model.Hysteria, model.Hysteria2:
|
case model.Hysteria:
|
||||||
if c.Auth == "" {
|
if c.Auth == "" {
|
||||||
c.Auth = strings.ReplaceAll(uuid.NewString(), "-", "")
|
c.Auth = strings.ReplaceAll(uuid.NewString(), "-", "")
|
||||||
}
|
}
|
||||||
|
|
@ -1064,12 +1065,7 @@ func clientMatchesInbound(c ClientWithAttachments, inboundId int) bool {
|
||||||
if inboundId <= 0 {
|
if inboundId <= 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, id := range c.InboundIds {
|
return slices.Contains(c.InboundIds, inboundId)
|
||||||
if id == inboundId {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func clientMatchesBucket(c ClientWithAttachments, bucket string, onlineSet map[string]struct{}, nowMs, expireDiffMs, trafficDiffBytes int64) bool {
|
func clientMatchesBucket(c ClientWithAttachments, bucket string, onlineSet map[string]struct{}, nowMs, expireDiffMs, trafficDiffBytes int64) bool {
|
||||||
|
|
@ -1284,10 +1280,7 @@ func (s *ClientService) BulkAdjust(inboundSvc *InboundService, emails []string,
|
||||||
skippedReasons[email] = "unlimited traffic"
|
skippedReasons[email] = "unlimited traffic"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
next := rec.TotalGB + addBytes
|
next := max(rec.TotalGB+addBytes, 0)
|
||||||
if next < 0 {
|
|
||||||
next = 0
|
|
||||||
}
|
|
||||||
entry.applyTotal = true
|
entry.applyTotal = true
|
||||||
entry.newTotal = next
|
entry.newTotal = next
|
||||||
}
|
}
|
||||||
|
|
@ -1410,7 +1403,7 @@ func (s *ClientService) bulkAdjustInboundClients(
|
||||||
clientKey = "password"
|
clientKey = "password"
|
||||||
case model.Shadowsocks:
|
case model.Shadowsocks:
|
||||||
clientKey = "email"
|
clientKey = "email"
|
||||||
case model.Hysteria, model.Hysteria2:
|
case model.Hysteria:
|
||||||
clientKey = "auth"
|
clientKey = "auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1690,7 +1683,7 @@ func (s *ClientService) bulkDelInboundClients(
|
||||||
clientKey = "password"
|
clientKey = "password"
|
||||||
case model.Shadowsocks:
|
case model.Shadowsocks:
|
||||||
clientKey = "email"
|
clientKey = "email"
|
||||||
case model.Hysteria, model.Hysteria2:
|
case model.Hysteria:
|
||||||
clientKey = "auth"
|
clientKey = "auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2105,7 +2098,7 @@ func (s *ClientService) AddInboundClient(inboundSvc *InboundService, data *model
|
||||||
if client.Email == "" {
|
if client.Email == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
if client.Auth == "" {
|
if client.Auth == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
|
|
@ -2252,7 +2245,7 @@ func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *mo
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
oldClientId = oldClient.Email
|
oldClientId = oldClient.Email
|
||||||
newClientId = clients[0].Email
|
newClientId = clients[0].Email
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
oldClientId = oldClient.Auth
|
oldClientId = oldClient.Auth
|
||||||
newClientId = clients[0].Auth
|
newClientId = clients[0].Auth
|
||||||
default:
|
default:
|
||||||
|
|
@ -2274,7 +2267,7 @@ func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *mo
|
||||||
lookupErr = database.GetDB().Where("password = ?", clientId).First(&rec).Error
|
lookupErr = database.GetDB().Where("password = ?", clientId).First(&rec).Error
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
lookupErr = database.GetDB().Where("email = ?", clientId).First(&rec).Error
|
lookupErr = database.GetDB().Where("email = ?", clientId).First(&rec).Error
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
lookupErr = database.GetDB().Where("auth = ?", clientId).First(&rec).Error
|
lookupErr = database.GetDB().Where("auth = ?", clientId).First(&rec).Error
|
||||||
default:
|
default:
|
||||||
lookupErr = database.GetDB().Where("uuid = ?", clientId).First(&rec).Error
|
lookupErr = database.GetDB().Where("uuid = ?", clientId).First(&rec).Error
|
||||||
|
|
@ -2512,7 +2505,7 @@ func (s *ClientService) DelInboundClient(inboundSvc *InboundService, inboundId i
|
||||||
client_key = "password"
|
client_key = "password"
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
client_key = "email"
|
client_key = "email"
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
client_key = "auth"
|
client_key = "auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ func TestSyncInbound_PreservesCredentialsAcrossProtocols(t *testing.T) {
|
||||||
if err := db.Create(vlessInbound).Error; err != nil {
|
if err := db.Create(vlessInbound).Error; err != nil {
|
||||||
t.Fatalf("create vless inbound: %v", err)
|
t.Fatalf("create vless inbound: %v", err)
|
||||||
}
|
}
|
||||||
hysteriaInbound := &model.Inbound{Tag: "hy-in", Enable: true, Port: 10002, Protocol: model.Hysteria2}
|
hysteriaInbound := &model.Inbound{Tag: "hy-in", Enable: true, Port: 10002, Protocol: model.Hysteria}
|
||||||
if err := db.Create(hysteriaInbound).Error; err != nil {
|
if err := db.Create(hysteriaInbound).Error; err != nil {
|
||||||
t.Fatalf("create hysteria inbound: %v", err)
|
t.Fatalf("create hysteria inbound: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -452,7 +452,6 @@ func (s *InboundService) normalizeStreamSettings(inbound *model.Inbound) {
|
||||||
model.Trojan: true,
|
model.Trojan: true,
|
||||||
model.Shadowsocks: true,
|
model.Shadowsocks: true,
|
||||||
model.Hysteria: true,
|
model.Hysteria: true,
|
||||||
model.Hysteria2: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !protocolsWithStream[inbound.Protocol] {
|
if !protocolsWithStream[inbound.Protocol] {
|
||||||
|
|
@ -528,7 +527,7 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||||
if client.Email == "" {
|
if client.Email == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
if client.Auth == "" {
|
if client.Auth == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
|
|
@ -2913,7 +2912,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||||
|
|
||||||
// Fix inbounds based problems
|
// Fix inbounds based problems
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan", "shadowsocks", "hysteria", "hysteria2"}).Find(&inbounds).Error
|
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan", "shadowsocks", "hysteria"}).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -298,9 +298,9 @@ func extractOutboundEndpoints(ob map[string]any) []outboundEndpoint {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hysteria (and hysteria2 over trojan) is QUIC/UDP. Detect it via the
|
// Hysteria is QUIC/UDP — detect via the outer protocol or via
|
||||||
// outer protocol or via streamSettings.network so trojan-with-hysteria2
|
// streamSettings.network so a trojan-with-hysteria transport gets
|
||||||
// transport gets probed over UDP too. kcp and quic are also UDP-based.
|
// probed over UDP too. kcp and quic are also UDP-based.
|
||||||
network := "tcp"
|
network := "tcp"
|
||||||
if protocol == "hysteria" || protocol == "wireguard" {
|
if protocol == "hysteria" || protocol == "wireguard" {
|
||||||
network = "udp"
|
network = "udp"
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func (b transportBits) conflicts(o transportBits) bool { return b&o != 0 }
|
||||||
// the validator never gets looser than the old port-only check.
|
// the validator never gets looser than the old port-only check.
|
||||||
//
|
//
|
||||||
// the rules:
|
// the rules:
|
||||||
// - hysteria, hysteria2, wireguard: udp regardless of streamSettings
|
// - hysteria, wireguard: udp regardless of streamSettings
|
||||||
// - streamSettings.network=kcp: udp
|
// - streamSettings.network=kcp: udp
|
||||||
// - shadowsocks: whatever settings.network says ("tcp" / "udp" / "tcp,udp")
|
// - shadowsocks: whatever settings.network says ("tcp" / "udp" / "tcp,udp")
|
||||||
// - mixed (socks/http combo): tcp + udp when settings.udp is true
|
// - mixed (socks/http combo): tcp + udp when settings.udp is true
|
||||||
|
|
@ -36,7 +36,7 @@ func (b transportBits) conflicts(o transportBits) bool { return b&o != 0 }
|
||||||
func inboundTransports(protocol model.Protocol, streamSettings, settings string) transportBits {
|
func inboundTransports(protocol model.Protocol, streamSettings, settings string) transportBits {
|
||||||
// protocols that ignore streamSettings entirely.
|
// protocols that ignore streamSettings entirely.
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case model.Hysteria, model.Hysteria2, model.WireGuard:
|
case model.Hysteria, model.WireGuard:
|
||||||
return transportUDP
|
return transportUDP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,6 @@ func TestInboundTransports(t *testing.T) {
|
||||||
{"trojan grpc is tcp", model.Trojan, `{"network":"grpc"}`, ``, transportTCP},
|
{"trojan grpc is tcp", model.Trojan, `{"network":"grpc"}`, ``, transportTCP},
|
||||||
|
|
||||||
{"hysteria forced udp", model.Hysteria, `{"network":"tcp"}`, ``, transportUDP},
|
{"hysteria forced udp", model.Hysteria, `{"network":"tcp"}`, ``, transportUDP},
|
||||||
{"hysteria2 forced udp", model.Hysteria2, ``, ``, transportUDP},
|
|
||||||
{"wireguard forced udp", model.WireGuard, ``, ``, transportUDP},
|
{"wireguard forced udp", model.WireGuard, ``, ``, transportUDP},
|
||||||
|
|
||||||
{"shadowsocks tcp,udp", model.Shadowsocks, ``, `{"network":"tcp,udp"}`, transportTCP | transportUDP},
|
{"shadowsocks tcp,udp", model.Shadowsocks, ``, `{"network":"tcp,udp"}`, transportTCP | transportUDP},
|
||||||
|
|
@ -122,7 +121,7 @@ func TestListenOverlaps(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// the actual case from #4103: tcp/443 vless reality and udp/443
|
// the actual case from #4103: tcp/443 vless reality and udp/443
|
||||||
// hysteria2 must be allowed to coexist on the same port.
|
// hysteria must be allowed to coexist on the same port.
|
||||||
func TestCheckPortConflict_TCPandUDPCoexistOnSamePort(t *testing.T) {
|
func TestCheckPortConflict_TCPandUDPCoexistOnSamePort(t *testing.T) {
|
||||||
setupConflictDB(t)
|
setupConflictDB(t)
|
||||||
seedInboundConflict(t, "vless-443-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
|
seedInboundConflict(t, "vless-443-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
|
||||||
|
|
@ -132,7 +131,7 @@ func TestCheckPortConflict_TCPandUDPCoexistOnSamePort(t *testing.T) {
|
||||||
Tag: "hyst2-443-udp",
|
Tag: "hyst2-443-udp",
|
||||||
Listen: "0.0.0.0",
|
Listen: "0.0.0.0",
|
||||||
Port: 443,
|
Port: 443,
|
||||||
Protocol: model.Hysteria2,
|
Protocol: model.Hysteria,
|
||||||
}
|
}
|
||||||
exist, err := svc.checkPortConflict(hyst2, 0)
|
exist, err := svc.checkPortConflict(hyst2, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -169,7 +168,7 @@ func TestCheckPortConflict_TCPCollidesWithTCP(t *testing.T) {
|
||||||
// conflict, since they fight for the same socket.
|
// conflict, since they fight for the same socket.
|
||||||
func TestCheckPortConflict_UDPCollidesWithUDP(t *testing.T) {
|
func TestCheckPortConflict_UDPCollidesWithUDP(t *testing.T) {
|
||||||
setupConflictDB(t)
|
setupConflictDB(t)
|
||||||
seedInboundConflict(t, "hyst2-443", "0.0.0.0", 443, model.Hysteria2, ``, ``)
|
seedInboundConflict(t, "hyst2-443", "0.0.0.0", 443, model.Hysteria, ``, ``)
|
||||||
|
|
||||||
svc := &InboundService{}
|
svc := &InboundService{}
|
||||||
wg := &model.Inbound{
|
wg := &model.Inbound{
|
||||||
|
|
@ -210,7 +209,7 @@ func TestCheckPortConflict_ShadowsocksDualListenBlocksBoth(t *testing.T) {
|
||||||
Tag: "hyst2-443",
|
Tag: "hyst2-443",
|
||||||
Listen: "0.0.0.0",
|
Listen: "0.0.0.0",
|
||||||
Port: 443,
|
Port: 443,
|
||||||
Protocol: model.Hysteria2,
|
Protocol: model.Hysteria,
|
||||||
}
|
}
|
||||||
if exist, err := svc.checkPortConflict(udpClash, 0); err != nil || !exist {
|
if exist, err := svc.checkPortConflict(udpClash, 0); err != nil || !exist {
|
||||||
t.Fatalf("udp inbound should clash with shadowsocks tcp,udp; exist=%v err=%v", exist, err)
|
t.Fatalf("udp inbound should clash with shadowsocks tcp,udp; exist=%v err=%v", exist, err)
|
||||||
|
|
@ -281,7 +280,7 @@ func TestGenerateInboundTag_DisambiguatesByTransportOnSamePort(t *testing.T) {
|
||||||
udp := &model.Inbound{
|
udp := &model.Inbound{
|
||||||
Listen: "0.0.0.0",
|
Listen: "0.0.0.0",
|
||||||
Port: 443,
|
Port: 443,
|
||||||
Protocol: model.Hysteria2,
|
Protocol: model.Hysteria,
|
||||||
}
|
}
|
||||||
got, err := svc.generateInboundTag(udp, 0)
|
got, err := svc.generateInboundTag(udp, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -343,7 +342,7 @@ func TestGenerateInboundTag_SpecificListenSameDisambiguation(t *testing.T) {
|
||||||
udp := &model.Inbound{
|
udp := &model.Inbound{
|
||||||
Listen: "1.2.3.4",
|
Listen: "1.2.3.4",
|
||||||
Port: 443,
|
Port: 443,
|
||||||
Protocol: model.Hysteria2,
|
Protocol: model.Hysteria,
|
||||||
}
|
}
|
||||||
got, err := svc.generateInboundTag(udp, 0)
|
got, err := svc.generateInboundTag(udp, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -403,7 +402,7 @@ func TestCheckPortConflict_NodeScope(t *testing.T) {
|
||||||
func TestResolveInboundTag_RespectsCallerTagWhenFree(t *testing.T) {
|
func TestResolveInboundTag_RespectsCallerTagWhenFree(t *testing.T) {
|
||||||
setupConflictDB(t)
|
setupConflictDB(t)
|
||||||
seedInboundConflictNode(t, "inbound-5000", "0.0.0.0", 5000, model.VLESS, `{"network":"tcp"}`, `{}`, nil)
|
seedInboundConflictNode(t, "inbound-5000", "0.0.0.0", 5000, model.VLESS, `{"network":"tcp"}`, `{}`, nil)
|
||||||
seedInboundConflictNode(t, "inbound-5000-udp", "0.0.0.0", 5000, model.Hysteria2, ``, ``, nil)
|
seedInboundConflictNode(t, "inbound-5000-udp", "0.0.0.0", 5000, model.Hysteria, ``, ``, nil)
|
||||||
|
|
||||||
svc := &InboundService{}
|
svc := &InboundService{}
|
||||||
pushed := &model.Inbound{
|
pushed := &model.Inbound{
|
||||||
|
|
@ -458,7 +457,7 @@ func TestResolveInboundTag_RegeneratesOnCollision(t *testing.T) {
|
||||||
Tag: "inbound-5000-tcp",
|
Tag: "inbound-5000-tcp",
|
||||||
Listen: "0.0.0.0",
|
Listen: "0.0.0.0",
|
||||||
Port: 5000,
|
Port: 5000,
|
||||||
Protocol: model.Hysteria2,
|
Protocol: model.Hysteria,
|
||||||
StreamSettings: ``,
|
StreamSettings: ``,
|
||||||
Settings: ``,
|
Settings: ``,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||||
if c.Security != "" {
|
if c.Security != "" {
|
||||||
entry["method"] = c.Security
|
entry["method"] = c.Security
|
||||||
}
|
}
|
||||||
case model.Hysteria, model.Hysteria2:
|
case model.Hysteria:
|
||||||
if c.Auth != "" {
|
if c.Auth != "" {
|
||||||
entry["auth"] = c.Auth
|
entry["auth"] = c.Auth
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]an
|
||||||
Email: userEmail,
|
Email: userEmail,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case "hysteria", "hysteria2":
|
case "hysteria":
|
||||||
auth, err := getRequiredUserString(user, "auth")
|
auth, err := getRequiredUserString(user, "auth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue