feat: добавлена поддержка TUIC протокола и DNS over QUIC (DoQ) с ECH
This commit is contained in:
@@ -5,6 +5,7 @@ import { validateVmessUrl } from './validateVmessUrl';
|
||||
import { validateTrojanUrl } from './validateTrojanUrl';
|
||||
import { validateSocksUrl } from './validateSocksUrl';
|
||||
import { validateHysteria2Url } from './validateHysteriaUrl';
|
||||
import { validateTuicUrl } from './validateTuicUrl';
|
||||
|
||||
// TODO refactor current validation and add tests
|
||||
export function validateProxyUrl(url: string): ValidationResult {
|
||||
@@ -37,10 +38,14 @@ export function validateProxyUrl(url: string): ValidationResult {
|
||||
return validateHysteria2Url(trimmedUrl);
|
||||
}
|
||||
|
||||
if (trimmedUrl.startsWith('tuic://')) {
|
||||
return validateTuicUrl(trimmedUrl);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
'URL must start with vless://, vmess://, ss://, trojan://, socks4://, socks4a://, socks5://, hysteria2://, or hy2://',
|
||||
'URL must start with vless://, vmess://, ss://, trojan://, socks4://, socks4a://, socks5://, hysteria2://, hy2://, or tuic://',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import { ValidationResult } from './types';
|
||||
import { parseQueryString } from '../helpers/parseQueryString';
|
||||
import { isValidPort, parseHostPort } from './hostPort';
|
||||
|
||||
function isValidUuid(value: string): boolean {
|
||||
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
export function validateTuicUrl(url: string): ValidationResult {
|
||||
try {
|
||||
if (!url.startsWith('tuic://')) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: must start with tuic://'),
|
||||
};
|
||||
}
|
||||
|
||||
if (/\s/.test(url)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: must not contain spaces'),
|
||||
};
|
||||
}
|
||||
|
||||
const body = url.slice('tuic://'.length);
|
||||
|
||||
const [mainPart] = body.split('#');
|
||||
const [authHostPort, queryString] = mainPart.split('?');
|
||||
|
||||
if (!authHostPort)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: missing credentials/server'),
|
||||
};
|
||||
|
||||
const [uuidPasswordPart, hostPortPart] = authHostPort.split('@');
|
||||
|
||||
if (!uuidPasswordPart)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: missing UUID'),
|
||||
};
|
||||
|
||||
if (!hostPortPart)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: missing host & port'),
|
||||
};
|
||||
|
||||
const [uuidPart, passwordPart] = uuidPasswordPart.split(':');
|
||||
const uuid = uuidPart || '';
|
||||
const _password = passwordPart || '';
|
||||
|
||||
if (!uuid) {
|
||||
return { valid: false, message: _('Invalid TUIC URL: missing UUID') };
|
||||
}
|
||||
|
||||
if (!isValidUuid(uuid)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: UUID must be a valid UUID v4 format'),
|
||||
};
|
||||
}
|
||||
|
||||
const parsedHostPort = parseHostPort(hostPortPart);
|
||||
if (!parsedHostPort) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: invalid host & port'),
|
||||
};
|
||||
}
|
||||
const { host, port } = parsedHostPort;
|
||||
|
||||
if (!host) {
|
||||
return { valid: false, message: _('Invalid TUIC URL: missing host') };
|
||||
}
|
||||
|
||||
if (!port) {
|
||||
return { valid: false, message: _('Invalid TUIC URL: missing port') };
|
||||
}
|
||||
|
||||
const cleanedPort = port.replace('/', '');
|
||||
if (!isValidPort(cleanedPort)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: invalid port number'),
|
||||
};
|
||||
}
|
||||
|
||||
if (queryString) {
|
||||
const params = parseQueryString(queryString);
|
||||
const paramsKeys = Object.keys(params);
|
||||
|
||||
const validCc = ['bbr', 'cubic', 'new_reno'];
|
||||
if (
|
||||
paramsKeys.includes('congestion_control') &&
|
||||
!validCc.includes(params.congestion_control)
|
||||
) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
'Invalid TUIC URL: congestion_control must be bbr, cubic, or new_reno',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const validUdpMode = ['native', 'quic', 'qux'];
|
||||
if (
|
||||
paramsKeys.includes('udp_relay_mode') &&
|
||||
!validUdpMode.includes(params.udp_relay_mode)
|
||||
) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
'Invalid TUIC URL: udp_relay_mode must be native, quic, or qux',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
paramsKeys.includes('insecure') &&
|
||||
!['0', '1'].includes(params.insecure)
|
||||
) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: insecure must be 0 or 1'),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
paramsKeys.includes('zero_rtt_handshake') &&
|
||||
!['0', '1', 'true', 'false'].includes(params.zero_rtt_handshake)
|
||||
) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: zero_rtt_handshake must be 0, 1, true, or false'),
|
||||
};
|
||||
}
|
||||
|
||||
if (paramsKeys.includes('sni') && !params.sni) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid TUIC URL: sni cannot be empty'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true, message: _('Valid') };
|
||||
} catch (_e) {
|
||||
return { valid: false, message: _('Invalid TUIC URL: parsing failed') };
|
||||
}
|
||||
}
|
||||
@@ -903,8 +903,8 @@ function dns_setting_values(settings, key) {
|
||||
|
||||
function validate_dns_settings(settings, sections, context) {
|
||||
let dns_type = option(settings, "dns_type", "udp");
|
||||
if (!contains([ "udp", "dot", "doh" ], dns_type))
|
||||
fail_validation("Unsupported DNS protocol type '" + dns_type + "'. Use udp, dot, or doh. Aborted.");
|
||||
if (!contains([ "udp", "dot", "doh", "doq" ], dns_type))
|
||||
fail_validation("Unsupported DNS protocol type '" + dns_type + "'. Use udp, dot, doh, or doq. Aborted.");
|
||||
|
||||
let dns_strategy = option(settings, "dns_strategy", "prefer_ipv4");
|
||||
if (!contains([ "prefer_ipv4", "ipv4_only", "prefer_ipv6", "ipv6_only" ], dns_strategy))
|
||||
@@ -1069,7 +1069,7 @@ function validate_proxy_parameter_value(value, allowed, parameter, section) {
|
||||
function validate_proxy_parameter_filters(protocols, transports, securities, section) {
|
||||
for (let value in protocols)
|
||||
validate_proxy_parameter_value(value,
|
||||
[ "awg", "direct", "http", "hysteria2", "shadowsocks", "socks", "trojan", "vless", "vmess" ],
|
||||
[ "awg", "direct", "http", "hysteria2", "shadowsocks", "socks", "trojan", "tuic", "vless", "vmess" ],
|
||||
"protocol", section);
|
||||
for (let value in transports)
|
||||
validate_proxy_parameter_value(value,
|
||||
@@ -1405,8 +1405,8 @@ function dns_action_has_domain_matchers(section) {
|
||||
function validate_dns_action(section, sections, context) {
|
||||
let name = section_name(section);
|
||||
let dns_type = option(section, "dns_type", "udp");
|
||||
if (!contains([ "udp", "dot", "doh" ], dns_type))
|
||||
fail_validation("DNS rule '" + name + "' uses unsupported protocol '" + dns_type + "'. Use udp, dot, or doh. Aborted.");
|
||||
if (!contains([ "udp", "dot", "doh", "doq" ], dns_type))
|
||||
fail_validation("DNS rule '" + name + "' uses unsupported protocol '" + dns_type + "'. Use udp, dot, doh, or doq. Aborted.");
|
||||
let dns_servers = list_option(section, "dns_server");
|
||||
if (length(dns_servers) == 0)
|
||||
fail_validation("DNS rule '" + name + "' has no DNS server specified. Aborted.");
|
||||
|
||||
@@ -537,7 +537,7 @@ function server_required_inbound_proto(protocol) {
|
||||
protocol = as_string(protocol);
|
||||
if (protocol == "json_inbound")
|
||||
return "";
|
||||
return protocol == "hysteria2" ? "udp" : "tcp";
|
||||
return protocol == "hysteria2" || protocol == "tuic" ? "udp" : "tcp";
|
||||
}
|
||||
|
||||
function server_runtime_type_for_protocol(protocol) {
|
||||
|
||||
@@ -383,6 +383,7 @@ function tachyon_config_masked_line(line) {
|
||||
line = mask_after_token_space(line, "option tailscale_hostname");
|
||||
line = mask_after_token_space(line, "list tailscale_advertise_routes");
|
||||
line = mask_after_token(line, "option hysteria2_obfs_password");
|
||||
line = mask_after_token(line, "option tuic_password");
|
||||
line = mask_after_token(line, "option reality_private_key");
|
||||
line = mask_after_token(line, "option reality_public_key");
|
||||
line = mask_after_token(line, "option reality_short_id");
|
||||
@@ -558,7 +559,7 @@ function server_required_inbound_proto(protocol) {
|
||||
if (protocol == "json_inbound")
|
||||
print("\n");
|
||||
else
|
||||
print(protocol == "hysteria2" ? "udp" : "tcp", "\n");
|
||||
print(protocol == "hysteria2" || protocol == "tuic" ? "udp" : "tcp", "\n");
|
||||
}
|
||||
|
||||
function server_runtime_type_for_protocol(protocol) {
|
||||
|
||||
@@ -1366,6 +1366,9 @@ function append_sing_box_server_signature_body(body, server) {
|
||||
[ "hysteria2_down_mbps", "" ],
|
||||
[ "hysteria2_obfs_type", "" ],
|
||||
[ "hysteria2_obfs_password", "" ],
|
||||
[ "tuic_congestion_control", "" ],
|
||||
[ "tuic_udp_relay_mode", "" ],
|
||||
[ "tuic_password", "" ],
|
||||
[ "mtproto_secret", "" ],
|
||||
[ "mtproto_faketls", "" ],
|
||||
[ "mtproto_padding", "" ],
|
||||
|
||||
@@ -145,6 +145,11 @@ function server_from_options(tag_name, dns_type, dns_server, detour) {
|
||||
if (path != "")
|
||||
result.path = path;
|
||||
}
|
||||
else if (dns_type == "doq") {
|
||||
result.type = "quic";
|
||||
result.server_port = port != "" ? int(port) : 784;
|
||||
result.tls = { enabled: true };
|
||||
}
|
||||
else {
|
||||
return { unsupported: "unsupported dns_type " + dns_type };
|
||||
}
|
||||
@@ -178,7 +183,12 @@ function server_config(settings, override_state) {
|
||||
);
|
||||
|
||||
if (bool_option(settings, "dns_doq_ech", false)) {
|
||||
if (result.tls) {
|
||||
if (result.type == "tls" || result.type == "quic") {
|
||||
if (!result.tls) result.tls = { enabled: true };
|
||||
result.tls.ech = { enabled: true };
|
||||
}
|
||||
if (result.type == "https") {
|
||||
if (!result.tls) result.tls = { enabled: true };
|
||||
result.tls.ech = { enabled: true };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ function supported_subscription_outbound(outbound) {
|
||||
if (t == "direct" || t == "selector" || t == "urltest" || t == "dns" || t == "block")
|
||||
return false;
|
||||
return t == "vless" || t == "vmess" || t == "trojan" || t == "shadowsocks" ||
|
||||
t == "socks" || t == "hysteria2";
|
||||
t == "socks" || t == "hysteria2" || t == "tuic";
|
||||
}
|
||||
|
||||
function outbound_uses_xhttp(outbound) {
|
||||
@@ -254,6 +254,16 @@ function copy_subscription_outbound(outbound, new_tag) {
|
||||
}
|
||||
copy.tls = tls;
|
||||
}
|
||||
if (as_string(copy.type || "") == "tuic" &&
|
||||
type(copy.tls) == "object" &&
|
||||
copy.tls.utls != null) {
|
||||
let tls = {};
|
||||
for (let key, value in copy.tls) {
|
||||
if (key != "utls")
|
||||
tls[key] = value;
|
||||
}
|
||||
copy.tls = tls;
|
||||
}
|
||||
copy.tag = new_tag;
|
||||
return copy;
|
||||
}
|
||||
@@ -429,7 +439,7 @@ function tls_alpn_array(value, transport) {
|
||||
function apply_link_tls(outbound, scheme, query) {
|
||||
query = object_or_empty(query);
|
||||
let security = as_string(query.security || "");
|
||||
if (security == "" && (scheme == "hysteria2" || scheme == "hy2"))
|
||||
if (security == "" && (scheme == "hysteria2" || scheme == "hy2" || scheme == "tuic"))
|
||||
security = "tls";
|
||||
|
||||
if (security == "" || security == "none")
|
||||
@@ -449,7 +459,7 @@ function apply_link_tls(outbound, scheme, query) {
|
||||
if (length(alpn) > 0)
|
||||
tls.alpn = alpn;
|
||||
|
||||
if (scheme != "hysteria2" && scheme != "hy2" && as_string(query.fp || "") != "") {
|
||||
if (scheme != "hysteria2" && scheme != "hy2" && scheme != "tuic" && as_string(query.fp || "") != "") {
|
||||
tls.utls = {
|
||||
enabled: true,
|
||||
fingerprint: as_string(query.fp)
|
||||
@@ -779,6 +789,58 @@ function manual_hysteria2_outbound(link, tag_name) {
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function manual_tuic_outbound(link, tag_name) {
|
||||
let query = url_query_params(link);
|
||||
let host = url_host(link);
|
||||
let port = parse_port(url_port(link));
|
||||
let userinfo = url_userinfo(link);
|
||||
if (host == "" || port == null || userinfo == "")
|
||||
ctx.runtime_generate_unsupported("manual TUIC proxy link is invalid");
|
||||
|
||||
let uuid = "";
|
||||
let password = "";
|
||||
let colon_index = index(userinfo, ":");
|
||||
if (colon_index >= 0) {
|
||||
uuid = substr(userinfo, 0, colon_index);
|
||||
password = substr(userinfo, colon_index + 1);
|
||||
} else {
|
||||
uuid = userinfo;
|
||||
}
|
||||
|
||||
if (uuid == "")
|
||||
ctx.runtime_generate_unsupported("manual TUIC proxy link is missing UUID");
|
||||
|
||||
let outbound = {
|
||||
type: "tuic",
|
||||
tag: tag_name,
|
||||
server: host,
|
||||
server_port: port,
|
||||
uuid: uuid
|
||||
};
|
||||
if (password != "")
|
||||
outbound.password = password;
|
||||
|
||||
let cc = as_string(query.congestion_control || "");
|
||||
if (cc != "")
|
||||
outbound.congestion_control = cc;
|
||||
else
|
||||
outbound.congestion_control = "bbr";
|
||||
|
||||
let udp_mode = as_string(query.udp_relay_mode || "");
|
||||
if (udp_mode != "")
|
||||
outbound.udp_relay_mode = udp_mode;
|
||||
|
||||
if (is_true(query.zero_rtt_handshake))
|
||||
outbound.zero_rtt_handshake = true;
|
||||
|
||||
let hb = as_string(query.heartbeat || "");
|
||||
if (hb != "")
|
||||
outbound.heartbeat = hb;
|
||||
|
||||
apply_link_tls(outbound, url_scheme(link), query);
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function manual_link_outbound(link, tag_name) {
|
||||
let scheme = url_scheme(link);
|
||||
if (scheme == "vmess")
|
||||
@@ -796,6 +858,8 @@ function manual_link_outbound(link, tag_name) {
|
||||
return manual_trojan_outbound(link, tag_name);
|
||||
if (scheme == "hysteria2" || scheme == "hy2")
|
||||
return manual_hysteria2_outbound(link, tag_name);
|
||||
if (scheme == "tuic")
|
||||
return manual_tuic_outbound(link, tag_name);
|
||||
ctx.runtime_generate_unsupported("manual proxy link scheme is not supported by sing-box config generation yet");
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ function is_supported_share_link(line) {
|
||||
starts_with(line, "trojan://") ||
|
||||
starts_with(line, "hysteria2://") ||
|
||||
starts_with(line, "hy2://") ||
|
||||
starts_with(line, "tuic://") ||
|
||||
starts_with(line, "socks://") ||
|
||||
starts_with(line, "socks4://") ||
|
||||
starts_with(line, "socks4a://") ||
|
||||
@@ -952,6 +953,65 @@ function process_hysteria2(raw, url) {
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function process_tuic(raw, url) {
|
||||
let userinfo = as_string(url.userinfo || "");
|
||||
if (userinfo == "" || url.host == "" || url.port == "")
|
||||
return null;
|
||||
|
||||
let uuid = "";
|
||||
let password = "";
|
||||
let colon_index = index(userinfo, ":");
|
||||
if (colon_index >= 0) {
|
||||
uuid = substr(userinfo, 0, colon_index);
|
||||
password = substr(userinfo, colon_index + 1);
|
||||
} else {
|
||||
uuid = userinfo;
|
||||
}
|
||||
|
||||
if (uuid == "")
|
||||
return null;
|
||||
|
||||
let tls = { enabled: true };
|
||||
if ((url.query.sni || "") != "")
|
||||
tls.server_name = url.query.sni;
|
||||
if (is_true(url.query.allowInsecure || url.query.insecure))
|
||||
tls.insecure = true;
|
||||
if ((url.query.alpn || "") != "")
|
||||
tls.alpn = split_csv(url.query.alpn);
|
||||
|
||||
let outbound = {
|
||||
type: "tuic",
|
||||
tag: url.fragment != "" ? url.fragment : (url.host + ":" + url.port),
|
||||
share_link: raw,
|
||||
server: url.host,
|
||||
server_port: int(url.port),
|
||||
uuid: uuid,
|
||||
tls: tls
|
||||
};
|
||||
|
||||
if (password != "")
|
||||
outbound.password = password;
|
||||
|
||||
let cc = as_string(url.query.congestion_control || "");
|
||||
if (cc != "")
|
||||
outbound.congestion_control = cc;
|
||||
else
|
||||
outbound.congestion_control = "bbr";
|
||||
|
||||
let udp_mode = as_string(url.query.udp_relay_mode || "");
|
||||
if (udp_mode != "")
|
||||
outbound.udp_relay_mode = udp_mode;
|
||||
|
||||
if (is_true(url.query.zero_rtt_handshake))
|
||||
outbound.zero_rtt_handshake = true;
|
||||
|
||||
let heartbeat = as_string(url.query.heartbeat || "");
|
||||
if (heartbeat != "")
|
||||
outbound.heartbeat = heartbeat;
|
||||
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function string_value(value) {
|
||||
return value == null ? "" : as_string(value);
|
||||
}
|
||||
@@ -1054,6 +1114,8 @@ function parse_share_link(line) {
|
||||
return process_trojan(line, url);
|
||||
if (url.scheme == "hysteria2" || url.scheme == "hy2")
|
||||
return process_hysteria2(line, url);
|
||||
if (url.scheme == "tuic")
|
||||
return process_tuic(line, url);
|
||||
if (match(url.scheme, /^socks/))
|
||||
return process_socks(line, url);
|
||||
return null;
|
||||
@@ -1451,6 +1513,30 @@ function parse_clash_record(record) {
|
||||
}
|
||||
return outbound;
|
||||
}
|
||||
if (proxy_type == "tuic") {
|
||||
let uuid = as_string(record.uuid || "");
|
||||
let password = as_string(record.password || "");
|
||||
if (uuid == "")
|
||||
return null;
|
||||
let outbound = { type: "tuic", tag: name, server: server, server_port: port, uuid: uuid, tls: { enabled: true } };
|
||||
if (password != "")
|
||||
outbound.password = password;
|
||||
if (options.sni != "")
|
||||
outbound.tls.server_name = options.sni;
|
||||
if (is_true(options.skip_verify) || is_true(record["skip-cert-verify"]))
|
||||
outbound.tls.insecure = true;
|
||||
if (options.alpn != "")
|
||||
outbound.tls.alpn = split_csv(options.alpn);
|
||||
let cc = as_string(record["congestion-control"] || record.congestion_control || "");
|
||||
if (cc != "")
|
||||
outbound.congestion_control = cc;
|
||||
let udp_mode = as_string(record["udp-relay-mode"] || record.udp_relay_mode || "");
|
||||
if (udp_mode != "")
|
||||
outbound.udp_relay_mode = udp_mode;
|
||||
if (is_true(record["zero-rtt-握手"]) || is_true(record["zero_rtt_handshake"]))
|
||||
outbound.zero_rtt_handshake = true;
|
||||
return outbound;
|
||||
}
|
||||
if (proxy_type == "socks5" || proxy_type == "socks") {
|
||||
let outbound = { type: "socks", tag: name, server: server, server_port: port, version: "5" };
|
||||
if (as_string(record.username) != "")
|
||||
@@ -2594,6 +2680,16 @@ function normalize_sing_box_hysteria2_outbound(outbound) {
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function normalize_sing_box_tuic_outbound(outbound) {
|
||||
if (type(outbound) == "object" &&
|
||||
lc(as_string(outbound.type || "")) == "tuic" &&
|
||||
type(outbound.tls) == "object" &&
|
||||
outbound.tls.utls != null) {
|
||||
delete outbound.tls.utls;
|
||||
}
|
||||
return outbound;
|
||||
}
|
||||
|
||||
function sing_box_urltest_group(outbound) {
|
||||
return type(outbound) == "object" && lc(as_string(outbound.type || "")) == "urltest";
|
||||
}
|
||||
@@ -2611,6 +2707,7 @@ function normalize_sing_box_json_outbounds(candidates) {
|
||||
if (type(outbound) != "object")
|
||||
continue;
|
||||
outbound = normalize_sing_box_hysteria2_outbound(outbound);
|
||||
outbound = normalize_sing_box_tuic_outbound(outbound);
|
||||
push(outbounds, normalize_sing_box_xhttp_transport(outbound));
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,40 @@ function serialize_hysteria2(outbound) {
|
||||
host_port(outbound.server, port) + query_string(params) + fragment(outbound);
|
||||
}
|
||||
|
||||
function serialize_tuic(outbound) {
|
||||
let port = as_string(outbound.server_port || "");
|
||||
if (as_string(outbound.uuid || "") == "" || as_string(outbound.server || "") == "" || port == "")
|
||||
return "";
|
||||
|
||||
let userinfo = as_string(outbound.uuid);
|
||||
if (as_string(outbound.password || "") != "")
|
||||
userinfo += ":" + outbound.password;
|
||||
|
||||
let params = [];
|
||||
let tls = type(outbound.tls) == "object" ? outbound.tls : null;
|
||||
if (tls) {
|
||||
add_query(params, "sni", tls.server_name);
|
||||
if (tls.insecure === true)
|
||||
add_query(params, "insecure", "1");
|
||||
if (type(tls.alpn) == "array" && length(tls.alpn) > 0)
|
||||
add_query(params, "alpn", join(",", tls.alpn));
|
||||
}
|
||||
let cc = as_string(outbound.congestion_control || "");
|
||||
if (cc != "" && cc != "bbr")
|
||||
add_query(params, "congestion_control", cc);
|
||||
let udp_mode = as_string(outbound.udp_relay_mode || "");
|
||||
if (udp_mode != "" && udp_mode != "native")
|
||||
add_query(params, "udp_relay_mode", udp_mode);
|
||||
if (outbound.zero_rtt_handshake === true)
|
||||
add_query(params, "zero_rtt_handshake", "1");
|
||||
let hb = as_string(outbound.heartbeat || "");
|
||||
if (hb != "")
|
||||
add_query(params, "heartbeat", hb);
|
||||
|
||||
return "tuic://" + uri_encode(userinfo) + "@" +
|
||||
host_port(outbound.server, port) + query_string(params) + fragment(outbound);
|
||||
}
|
||||
|
||||
function serialize_vmess(outbound) {
|
||||
if (as_string(outbound.uuid) == "" || as_string(outbound.server) == "" || outbound.server_port == null)
|
||||
return "";
|
||||
@@ -319,6 +353,8 @@ function serialize_outbound_link(outbound) {
|
||||
return serialize_socks(outbound);
|
||||
if (outbound_type == "hysteria2")
|
||||
return serialize_hysteria2(outbound);
|
||||
if (outbound_type == "tuic")
|
||||
return serialize_tuic(outbound);
|
||||
if (outbound_type == "vmess")
|
||||
return serialize_vmess(outbound);
|
||||
return "";
|
||||
|
||||
Reference in New Issue
Block a user