diff --git a/sub/subJsonService.go b/sub/subJsonService.go index c675cc5d..e636b48f 100644 --- a/sub/subJsonService.go +++ b/sub/subJsonService.go @@ -182,6 +182,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, inbound.Listen = extPrxy["dest"].(string) inbound.Port = int(extPrxy["port"].(float64)) newStream := stream + var newOutbounds []json_util.RawMessage switch extPrxy["forceTls"].(string) { case "tls": if newStream["security"] != "tls" { @@ -194,9 +195,37 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, delete(newStream, "tlsSettings") } } - streamSettings, _ := json.MarshalIndent(newStream, "", " ") + if newStream["security"] != "none" { + newTlsSettings := newStream["tlsSettings"].(map[string]interface{}) + if utlsValue, ok := extPrxy["utls"].(string); ok && len(utlsValue) > 0 { + newTlsSettings["fingerprint"] = utlsValue + } + if sniValue, ok := extPrxy["sni"].(string); ok && len(sniValue) > 0 { + newTlsSettings["serverName"] = sniValue + } + if alpnValue, ok := extPrxy["alpn"].([]interface{}); ok && len(alpnValue) > 0 { + newTlsSettings["alpn"] = alpnValue + } + if allowInsecureValue, ok := extPrxy["allowInsecure"].(bool); ok && allowInsecureValue { + newTlsSettings["allowInsecure"] = "1" + } + newStream["tlsSettings"] = newTlsSettings + } - var newOutbounds []json_util.RawMessage + if fragmentValue, ok := extPrxy["fragment"].(map[string]interface{}); ok { + newTag := "freedom_out_" + extPrxy["remark"].(string) + "_" + random.Seq(10) + newOutboundFreedom := map[string]interface{}{ + "protocol": "freedom", + "settings": map[string]interface{}{ + "fragment": fragmentValue, + }, + "tag": newTag, + } + newOutboundFreedomJson, _ := json.MarshalIndent(newOutboundFreedom, "", " ") + newOutbounds = append(newOutbounds, newOutboundFreedomJson) + newStream["sockopt"] = json_util.RawMessage(fmt.Sprintf(`{"dialerProxy": "%s"}`, newTag)) + } + streamSettings, _ := json.MarshalIndent(newStream, "", " ") switch inbound.Protocol { case "vmess": diff --git a/sub/subService.go b/sub/subService.go index f730be52..58a32772 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -295,6 +295,27 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { if newSecurity != "same" { newObj["tls"] = newSecurity } + if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 { + newObj["fp"] = utlsValue + } + if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 { + newObj["sni"] = sniValue + } + if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 { + alpn := make([]string, len(alpnValue)) + for i, a := range alpnValue { + alpn[i] = a.(string) + } + newObj["alpn"] = strings.Join(alpn, ",") + } + if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue { + newObj["allowInsecure"] = "1" + } + if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok { + newObj["packets"], _ = fragmentValue["packets"].(string) + newObj["length"], _ = fragmentValue["length"].(string) + newObj["interval"], _ = fragmentValue["interval"].(string) + } if index > 0 { links += "\n" } @@ -481,6 +502,27 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { newSecurity, _ := ep["forceTls"].(string) dest, _ := ep["dest"].(string) port := int(ep["port"].(float64)) + if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 { + params["fp"] = utlsValue + } + if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 { + params["sni"] = sniValue + } + if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 { + alpn := make([]string, len(alpnValue)) + for i, a := range alpnValue { + alpn[i] = a.(string) + } + params["alpn"] = strings.Join(alpn, ",") + } + if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue { + params["allowInsecure"] = "1" + } + if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok { + params["packets"] = fragmentValue["packets"].(string) + params["length"] = fragmentValue["length"].(string) + params["interval"] = fragmentValue["interval"].(string) + } link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port) if newSecurity != "same" { @@ -682,6 +724,27 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string newSecurity, _ := ep["forceTls"].(string) dest, _ := ep["dest"].(string) port := int(ep["port"].(float64)) + if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 { + params["fp"] = utlsValue + } + if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 { + params["sni"] = sniValue + } + if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 { + alpn := make([]string, len(alpnValue)) + for i, a := range alpnValue { + alpn[i] = a.(string) + } + params["alpn"] = strings.Join(alpn, ",") + } + if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue { + params["allowInsecure"] = "1" + } + if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok { + params["packets"] = fragmentValue["packets"].(string) + params["length"] = fragmentValue["length"].(string) + params["interval"] = fragmentValue["interval"].(string) + } link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port) if newSecurity != "same" { @@ -859,6 +922,27 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st newSecurity, _ := ep["forceTls"].(string) dest, _ := ep["dest"].(string) port := int(ep["port"].(float64)) + if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 { + params["fp"] = utlsValue + } + if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 { + params["sni"] = sniValue + } + if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 { + alpn := make([]string, len(alpnValue)) + for i, a := range alpnValue { + alpn[i] = a.(string) + } + params["alpn"] = strings.Join(alpn, ",") + } + if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue { + params["allowInsecure"] = "1" + } + if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok { + params["packets"] = fragmentValue["packets"].(string) + params["length"] = fragmentValue["length"].(string) + params["interval"] = fragmentValue["interval"].(string) + } link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port) if newSecurity != "same" { @@ -956,6 +1040,57 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin protocol = "hysteria" } + externalProxies, _ := stream["externalProxy"].([]interface{}) + + if len(externalProxies) > 0 { + links := "" + for index, externalProxy := range externalProxies { + ep, _ := externalProxy.(map[string]interface{}) + newSecurity, _ := ep["forceTls"].(string) + dest, _ := ep["dest"].(string) + port := int(ep["port"].(float64)) + if utlsValue, ok := ep["utls"].(string); ok && len(utlsValue) > 0 { + params["fp"] = utlsValue + } + if sniValue, ok := ep["sni"].(string); ok && len(sniValue) > 0 { + params["sni"] = sniValue + } + if alpnValue, ok := ep["alpn"].([]interface{}); ok && len(alpnValue) > 0 { + alpn := make([]string, len(alpnValue)) + for i, a := range alpnValue { + alpn[i] = a.(string) + } + params["alpn"] = strings.Join(alpn, ",") + } + if allowInsecureValue, ok := ep["allowInsecure"].(bool); ok && allowInsecureValue { + params["allowInsecure"] = "1" + } + if fragmentValue, ok := ep["fragment"].(map[string]interface{}); ok { + params["packets"] = fragmentValue["packets"].(string) + params["length"] = fragmentValue["length"].(string) + params["interval"] = fragmentValue["interval"].(string) + } + link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, dest, port) + if newSecurity != "same" { + params["security"] = newSecurity + } else { + params["security"] = "tls" + } + url, _ := url.Parse(link) + q := url.Query() + for k, v := range params { + q.Add(k, v) + } + url.RawQuery = q.Encode() + url.Fragment = s.genRemark(inbound, email, ep["remark"].(string)) + if index > 0 { + links += "\n" + } + links += url.String() + } + return links + } + link := fmt.Sprintf("%s://%s@%s:%d", protocol, auth, address, port) url, _ := url.Parse(link) q := url.Query() diff --git a/web/assets/css/custom.css b/web/assets/css/custom.css index d50213b5..1bfb2803 100644 --- a/web/assets/css/custom.css +++ b/web/assets/css/custom.css @@ -250,6 +250,10 @@ style attribute { margin-bottom: 2px; } +.ant-select-selection--multiple { + padding-bottom: 0 !important; +} + @media (min-width: 769px) { .drawer-handle { display: none; diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index 69632eaf..259a0307 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -1417,6 +1417,66 @@ class FinalMaskStreamSettings extends XrayCommonClass { } } +class ExternalProxy extends XrayCommonClass { + constructor( + forceTls = 'same', + dest = '', + port = 443, + remark = '', + fragment = undefined, + utls = '', + sni = '', + alpn = [], + allowInsecure = false, + ) { + super(); + this.forceTls = forceTls; + this.dest = dest; + this.port = port; + this.remark = remark; + this.fragment = fragment; + this.utls = utls; + this.sni = sni; + this.alpn = alpn; + this.allowInsecure = allowInsecure; + } + + get hasFragment() { + return this.fragment !== undefined; + } + + set hasFragment(value) { + this.fragment = value ? {packets: 'tlshello', length: '100-200', interval: '10-20'} : undefined; + } + + static fromJson(json = {}) { + return new ExternalProxy( + json.forceTls, + json.dest, + json.port, + json.remark, + json.fragment, + json.utls, + json.sni, + json.alpn, + json.allowInsecure, + ); + } + + toJson() { + return { + forceTls: this.forceTls, + dest: this.dest, + port: this.port, + remark: this.remark, + fragment: this.hasFragment ? this.fragment : undefined, + utls: this.utls, + sni: this.sni, + alpn: this.alpn, + allowInsecure: this.allowInsecure, + }; + } +} class StreamSettings extends XrayCommonClass { constructor(network = 'tcp', security = 'none', @@ -1513,7 +1573,7 @@ class StreamSettings extends XrayCommonClass { return new StreamSettings( json.network, json.security, - json.externalProxy, + json.externalProxy ? json.externalProxy.map(ep => ExternalProxy.fromJson(ep)) : [], TlsStreamSettings.fromJson(json.tlsSettings), RealityStreamSettings.fromJson(json.realitySettings), TcpStreamSettings.fromJson(json.tcpSettings), @@ -1533,7 +1593,7 @@ class StreamSettings extends XrayCommonClass { return { network: network, security: this.security, - externalProxy: this.externalProxy, + externalProxy: this.externalProxy.map(ep => ep.toJson()), tlsSettings: this.isTls ? this.tls.toJson() : undefined, realitySettings: this.isReality ? this.reality.toJson() : undefined, tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined, @@ -1796,11 +1856,11 @@ class Inbound extends XrayCommonClass { this.sniffing = new Sniffing(); } - genVmessLink(address = '', port = this.port, forceTls, remark = '', clientId, security) { + genVmessLink(address = '', port = this.port, extProxy, remark = '', clientId, security) { if (this.protocol !== Protocols.VMESS) { return ''; } - const tls = forceTls == 'same' ? this.stream.security : forceTls; + const tls = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls; let obj = { v: '2', ps: remark, @@ -1861,13 +1921,33 @@ class Inbound extends XrayCommonClass { } } + if (extProxy) { + if (!ObjectUtil.isEmpty(extProxy.sni)) { + obj.sni = extProxy.sni; + } + if (!ObjectUtil.isEmpty(extProxy.utls)) { + obj.fp = extProxy.utls; + } + if (!ObjectUtil.isArrEmpty(extProxy.alpn)) { + obj.alpn = extProxy.alpn.join(','); + } + if (extProxy.allowInsecure === 'true') { + obj.allowInsecure = true; + } + if (extProxy.fragment) { + obj.packets = extProxy.fragment.packets; + obj.length = extProxy.fragment.length; + obj.interval = extProxy.fragment.interval; + } + } + return 'vmess://' + Base64.encode(JSON.stringify(obj, null, 2)); } - genVLESSLink(address = '', port = this.port, forceTls, remark = '', clientId, flow) { + genVLESSLink(address = '', port = this.port, extProxy, remark = '', clientId, flow) { const uuid = clientId; const type = this.stream.network; - const security = forceTls == 'same' ? this.stream.security : forceTls; + const security = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls; const params = new Map(); params.set("type", this.stream.network); params.set("encryption", this.settings.encryption); @@ -1961,6 +2041,26 @@ class Inbound extends XrayCommonClass { params.set("security", "none"); } + if (extProxy) { + if (extProxy.sni?.length > 0) { + params.set("sni", extProxy.sni); + } + if (extProxy.alpn?.length > 0) { + params.set("alpn", extProxy.alpn.join(',')); + } + if (extProxy.allowInsecure === 'true') { + params.set("allowInsecure", "1"); + } + if (extProxy.utls?.length > 0) { + params.set("fp", extProxy.utls); + } + if (extProxy.fragment) { + params.set("packets", extProxy.fragment.packets); + params.set("length", extProxy.fragment.length); + params.set("interval", extProxy.fragment.interval); + } + } + const link = `vless://${uuid}@${address}:${port}`; const url = new URL(link); for (const [key, value] of params) { @@ -1970,10 +2070,10 @@ class Inbound extends XrayCommonClass { return url.toString(); } - genSSLink(address = '', port = this.port, forceTls, remark = '', clientPassword) { + genSSLink(address = '', port = this.port, extProxy, remark = '', clientPassword) { let settings = this.settings; const type = this.stream.network; - const security = forceTls == 'same' ? this.stream.security : forceTls; + const security = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls; const params = new Map(); params.set("type", this.stream.network); switch (type) { @@ -2038,6 +2138,26 @@ class Inbound extends XrayCommonClass { } } + if (extProxy) { + if (extProxy.sni?.length > 0) { + params.set("sni", extProxy.sni); + } + if (extProxy.alpn?.length > 0) { + params.set("alpn", extProxy.alpn.join(',')); + } + if (extProxy.allowInsecure === 'true') { + params.set("allowInsecure", "1"); + } + if (extProxy.utls?.length > 0) { + params.set("fp", extProxy.utls); + } + if (extProxy.fragment) { + params.set("packets", extProxy.fragment.packets); + params.set("length", extProxy.fragment.length); + params.set("interval", extProxy.fragment.interval); + } + } + let password = new Array(); if (this.isSS2022) password.push(settings.password); @@ -2052,8 +2172,8 @@ class Inbound extends XrayCommonClass { return url.toString(); } - genTrojanLink(address = '', port = this.port, forceTls, remark = '', clientPassword) { - const security = forceTls == 'same' ? this.stream.security : forceTls; + genTrojanLink(address = '', port = this.port, extProxy, remark = '', clientPassword) { + const security = extProxy.forceTls == 'same' ? this.stream.security : extProxy.forceTls; const type = this.stream.network; const params = new Map(); params.set("type", this.stream.network); @@ -2141,6 +2261,25 @@ class Inbound extends XrayCommonClass { params.set("security", "none"); } + if (extProxy) { + if (extProxy.sni?.length > 0) { + params.set("sni", extProxy.sni); + } + if (extProxy.alpn?.length > 0) { + params.set("alpn", extProxy.alpn.join(',')); + } + if (extProxy.allowInsecure === 'true') { + params.set("allowInsecure", "1"); + } + if (extProxy.utls?.length > 0) { + params.set("fp", extProxy.utls); + } + if (extProxy.fragment) { + params.set("packets", extProxy.fragment.packets); + params.set("length", extProxy.fragment.length); + params.set("interval", extProxy.fragment.interval); + } + } const link = `trojan://${clientPassword}@${address}:${port}`; const url = new URL(link); for (const [key, value] of params) { @@ -2150,7 +2289,7 @@ class Inbound extends XrayCommonClass { return url.toString(); } - genHysteriaLink(address = '', port = this.port, remark = '', clientAuth) { + genHysteriaLink(address = '', port = this.port, extProxy, remark = '', clientAuth) { const protocol = this.settings.version == 2 ? "hysteria2" : "hysteria"; const link = `${protocol}://${clientAuth}@${address}:${port}`; @@ -2162,6 +2301,25 @@ class Inbound extends XrayCommonClass { if (this.stream.tls.settings.echConfigList?.length > 0) params.set("ech", this.stream.tls.settings.echConfigList.join(',')); if (this.stream.tls.sni?.length > 0) params.set("sni", this.stream.tls.sni); + if (extProxy) { + if (extProxy.sni?.length > 0) { + params.set("sni", extProxy.sni); + } + if (extProxy.alpn?.length > 0) { + params.set("alpn", extProxy.alpn.join(',')); + } + if (extProxy.allowInsecure === 'true') { + params.set("insecure", "1"); + } + if (extProxy.utls?.length > 0) { + params.set("fp", extProxy.utls); + } + if (extProxy.fragment) { + params.set("packets", extProxy.fragment.packets); + params.set("length", extProxy.fragment.length); + params.set("interval", extProxy.fragment.interval); + } + } const url = new URL(link); for (const [key, value] of params) { url.searchParams.set(key, value); @@ -2192,18 +2350,19 @@ class Inbound extends XrayCommonClass { return txt; } - genLink(address = '', port = this.port, forceTls = 'same', remark = '', client) { + genLink(address = '', port = this.port, extProxy = new ExternalProxy('same'), remark = '', client) { + console.log(extProxy); switch (this.protocol) { case Protocols.VMESS: - return this.genVmessLink(address, port, forceTls, remark, client.id, client.security); + return this.genVmessLink(address, port, extProxy, remark, client.id, client.security); case Protocols.VLESS: - return this.genVLESSLink(address, port, forceTls, remark, client.id, client.flow); + return this.genVLESSLink(address, port, extProxy, remark, client.id, client.flow); case Protocols.SHADOWSOCKS: - return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : ''); + return this.genSSLink(address, port, extProxy, remark, this.isSSMultiUser ? client.password : ''); case Protocols.TROJAN: - return this.genTrojanLink(address, port, forceTls, remark, client.password); + return this.genTrojanLink(address, port, extProxy, remark, client.password); case Protocols.HYSTERIA: - return this.genHysteriaLink(address, port, remark, client.auth.length > 0 ? client.auth : this.stream.hysteria.auth); + return this.genHysteriaLink(address, port, remark, extProxy, client.auth.length > 0 ? client.auth : this.stream.hysteria.auth); default: return ''; } } @@ -2224,7 +2383,7 @@ class Inbound extends XrayCommonClass { let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar); result.push({ remark: r, - link: this.genLink(addr, port, 'same', r, client) + link: this.genLink(addr, port, new ExternalProxy('same'), r, client) }); } else { this.stream.externalProxy.forEach((ep) => { @@ -2232,7 +2391,7 @@ class Inbound extends XrayCommonClass { let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar); result.push({ remark: r, - link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client) + link: this.genLink(ep.dest, ep.port, ep, r, client) }); }); } diff --git a/web/html/xui/form/stream/external_proxy.html b/web/html/xui/form/stream/external_proxy.html index a09e8330..202df576 100644 --- a/web/html/xui/form/stream/external_proxy.html +++ b/web/html/xui/form/stream/external_proxy.html @@ -1,24 +1,77 @@ {{define "form/externalProxy"}} - + + + - - {{end}} \ No newline at end of file diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 5cf8a57f..9a5bbc90 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -398,8 +398,8 @@ [pages.xray.outbound.freedom] "strategy" = "استراتژی" "redirect" = "تغییر مسیر" -"fragment" = "Fragment" -"packets" = "Packets" +"fragment" = "فرگمنت" +"packets" = "بسته‌ها" "length" = "طول" "interval" = "بازه" "noises" = "نویزها"