🎉 Sync 2026-07-14 20:29:41

This commit is contained in:
github-actions[bot] committed 2026-07-14 20:29:41 +08:00
1 parent 9307433a4c
commit 09a1b41f45
15 files changed
+3478 -3132

No files matched your search

@@ -165,6 +165,7 @@ const inbound_type = [
['tuic', _('TUIC') + ' - ' + _('UDP')],
['hysteria2', _('Hysteria2') + ' - ' + _('UDP')],
['hysteria2-realm', _('Hysteria2 Realm Server') + ' - ' + _('TCP/UDP')],
['shadowquic', _('ShadowQUIC') + ' - ' + _('UDP')],
['trusttunnel', _('TrustTunnel') + ' - ' + _('TCP/UDP')],
['tunnel', _('Tunnel') + ' - ' + _('TCP/UDP')]
];
@@ -201,6 +202,7 @@ const outbound_type = [
['tuic', _('TUIC') + ' - ' + _('UDP')],
//['hysteria', _('Hysteria') + ' - ' + _('UDP')],
['hysteria2', _('Hysteria2') + ' - ' + _('UDP')],
['shadowquic', _('ShadowQUIC') + ' - ' + _('UDP')],
['trusttunnel', _('TrustTunnel') + ' - ' + _('TCP/UDP')],
['wireguard', _('WireGuard') + ' - ' + _('UDP')],
['masque', _('Masque') + ' - ' + _('UDP')], // https://blog.cloudflare.com/post-quantum-warp/
@@ -117,6 +117,7 @@ function renderListeners(s, uciconfig, isClient) {
let o;
s.tab('field_general', _('General fields'));
s.tab('field_plugin', _('Plugin fields'));
s.tab('field_vless_encryption', _('Vless Encryption fields'));
s.tab('field_hysteria2_realm', _('Hysteria2 Realm fields'));
s.tab('field_tls', _('TLS fields'));
@@ -160,14 +161,14 @@ function renderListeners(s, uciconfig, isClient) {
/* hm.validateAuth */
o = s.taboption('field_general', form.Value, 'username', _('Username'));
o.validate = hm.validateAuthUsername;
o.depends({type: /^(http|socks|mixed|mieru|trojan|anytls|hysteria2|trusttunnel)$/});
o.depends({type: /^(http|socks|mixed|mieru|trojan|anytls|hysteria2|shadowquic|trusttunnel)$/});
o.modalonly = true;
o = s.taboption('field_general', hm.GenValue, 'password', _('Password'));
o.password = true;
o.validate = hm.validateAuthPassword;
o.rmempty = false;
o.depends({type: /^(http|socks|mixed|mieru|trojan|anytls|hysteria2|trusttunnel)$/, username: /.+/});
o.depends({type: /^(http|socks|mixed|mieru|trojan|anytls|hysteria2|shadowquic|trusttunnel)$/, username: /.+/});
o.depends({type: /^(tuic)$/, uuid: /.+/});
o.modalonly = true;
@@ -453,13 +454,6 @@ function renderListeners(s, uciconfig, isClient) {
o.depends('type', 'tuic');
o.modalonly = true;
o = s.taboption('field_general', form.Value, 'tuic_max_idle_time', _('Idle timeout'),
_('In seconds.'));
o.default = '15000';
o.validate = hm.validateTimeDuration;
o.depends('type', 'tuic');
o.modalonly = true;
o = s.taboption('field_general', form.Value, 'tuic_authentication_timeout', _('Auth timeout'),
_('In seconds.'));
o.default = '1000';
@@ -547,6 +541,19 @@ function renderListeners(s, uciconfig, isClient) {
o.depends('type', 'hysteria2-realm');
o.modalonly = true;
/* ShadowQUIC fields */
o = s.taboption('field_general', form.DynamicList, 'shadowquic_quic_versions', _('QUIC versions'),
_('Default version, Support %s.').format('v1/v2'));
o.default = 'v1';
o.rmempty = false;
o.depends('type', 'shadowquic');
o.modalonly = true;
o = s.taboption('field_general', form.Flag, 'shadowquic_zero_rtt', _('QUIC based 0-RTT'));
o.default = o.disabled;
o.depends('type', 'shadowquic');
o.modalonly = true;
/* TrustTunnel fields */
/* Tunnel fields */
@@ -556,68 +563,6 @@ function renderListeners(s, uciconfig, isClient) {
o.depends('type', 'tunnel');
o.modalonly = true;
/* Plugin fields */
o = s.taboption('field_general', form.ListValue, 'plugin', _('Plugin'));
o.value('', _('none'));
o.value('obfs', _('obfs-simple'));
o.value('shadow-tls', _('shadow-tls'));
o.value('restls', _('restls'));
//o.value('kcp-tun', _('kcp-tun'));
o.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
if (value) {
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
return _('Expecting: only support %s.').format(_('obfs-simple') +
' / ' + _('shadow-tls'));
}
}
return true;
}
o.depends({type: /^(shadowsocks|snell)$/});
o.modalonly = true;
o = s.taboption('field_general', form.ListValue, 'plugin_opts_obfsmode', _('Plugin: ') + _('Obfs Mode'));
o.value('http', _('HTTP'));
o.value('tls', _('TLS'));
o.depends('plugin', 'obfs');
o.modalonly = true;
o = s.taboption('field_general', form.Value, 'plugin_opts_host', _('Plugin: ') + _('Host that supports TLS 1.3'));
o.datatype = 'hostname';
o.placeholder = 'cloud.tencent.com';
o.rmempty = false;
o.depends({plugin: 'obfs', type: 'snell'});
o.modalonly = true;
o = s.taboption('field_general', form.Value, 'plugin_opts_handshake_dest', _('Plugin: ') + _('Handshake target that supports TLS 1.3'));
o.datatype = 'hostport';
o.placeholder = 'cloud.tencent.com:443';
o.rmempty = false;
o.depends({plugin: /^(shadow-tls|restls)$/});
o.modalonly = true;
o = s.taboption('field_general', hm.GenValue, 'plugin_opts_thetlspassword', _('Plugin: ') + _('Password'));
o.password = true;
o.rmempty = false;
o.depends({plugin: /^(shadow-tls|restls)$/});
o.modalonly = true;
o = s.taboption('field_general', form.ListValue, 'plugin_opts_shadowtls_version', _('Plugin: ') + _('Version'));
o.value('1', _('v1'));
o.value('2', _('v2'));
o.value('3', _('v3'));
o.default = '3';
o.depends({plugin: 'shadow-tls'});
o.modalonly = true;
o = s.taboption('field_general', form.Value, 'plugin_opts_restls_script', _('Plugin: ') + _('Restls script'));
o.default = '300?100<1,400~100,350~100,600~100,300~200,300~100';
o.rmempty = false;
o.depends({plugin: 'restls'});
o.modalonly = true;
/* Extra fields */
if (isClient) {
o = s.taboption('field_general', form.Value, 'routing_mark', _('Routing mark (Fwmark)'));
@@ -658,7 +603,7 @@ function renderListeners(s, uciconfig, isClient) {
hm.congestion_controller.forEach((res) => {
o.value.apply(o, res);
})
o.depends({type: /^(tuic|trusttunnel)$/});
o.depends({type: /^(tuic|shadowquic|trusttunnel)$/});
o.modalonly = true;
o = s.taboption('field_general', form.ListValue, 'bbr_profile', _('BBR profile'));
@@ -666,8 +611,15 @@ function renderListeners(s, uciconfig, isClient) {
hm.bbr_profiles.forEach((res) => {
o.value.apply(o, res);
})
o.depends({congestion_controller: 'bbr'});
o.depends({type: 'hysteria2'});
o.depends('congestion_controller', 'bbr');
o.depends('type', 'hysteria2');
o.modalonly = true;
o = s.taboption('field_general', form.Value, 'max_idle_time', _('Idle timeout'),
_('In seconds.'));
o.placeholder = '15000';
o.validate = hm.validateTimeDuration;
o.depends({type: /^(tuic|shadowquic)$/});
o.modalonly = true;
o = s.taboption('field_general', form.MultiValue, 'network', _('Network type'));
@@ -682,6 +634,111 @@ function renderListeners(s, uciconfig, isClient) {
o.depends({type: /^(socks|mixed|shadowsocks|snell)$/});
o.modalonly = true;
/* Plugin fields */
o = s.taboption('field_general', form.Flag, 'plugin', _('Plugin'));
o.default = o.disabled;
o.depends({type: /^(shadowsocks|snell)$/});
o.modalonly = true;
o = s.taboption('field_plugin', form.ListValue, 'plugin_type', _('Plugin type'));
o.value('obfs', _('obfs-simple'));
o.value('shadow-tls', _('ShadowTLS'));
o.value('restls', _('Restls'));
o.value('jls', _('JLS'));
//o.value('kcp-tun', _('kcp-tun'));
o.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
if (value) {
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
return _('Expecting: only support %s.').format(_('obfs-simple') +
' / ' + _('ShadowTLS'));
}
}
return true;
}
o.depends('plugin', '1');
o.modalonly = true;
o = s.taboption('field_plugin', form.ListValue, 'plugin_opts_obfsmode', _('Obfs Mode'));
o.value('http', _('HTTP'));
o.value('tls', _('TLS'));
o.depends('plugin_type', 'obfs');
o.modalonly = true;
o = s.taboption('field_plugin', form.Value, 'plugin_opts_host', _('Host that supports TLS 1.3'));
o.datatype = 'hostname';
o.placeholder = 'cloud.tencent.com';
o.rmempty = false;
o.depends({plugin_type: 'obfs', type: 'snell'});
o.modalonly = true;
o = s.taboption('field_plugin', form.Value, 'plugin_opts_handshake_dest', _('Handshake target that supports TLS 1.3'));
o.datatype = 'hostport';
o.placeholder = 'cloud.tencent.com:443';
o.rmempty = false;
o.depends({plugin_type: /^(shadow-tls|restls|jls)$/});
o.depends({type: 'shadowquic'});
o.modalonly = true;
o = s.taboption('field_plugin', form.Value, 'plugin_opts_thetlsusername', _('Username'));
o.validate = hm.validateAuthUsername;
o.rmempty = false;
o.depends({plugin_type: 'jls'});
o.modalonly = true;
o = s.taboption('field_plugin', hm.GenValue, 'plugin_opts_thetlspassword', _('Password'));
o.password = true;
o.rmempty = false;
o.depends({plugin_type: /^(shadow-tls|restls|jls)$/});
o.modalonly = true;
o = s.taboption('field_plugin', form.ListValue, 'plugin_opts_shadowtls_version', _('Version'));
o.value('1', _('v1'));
o.value('2', _('v2'));
o.value('3', _('v3'));
o.default = '3';
o.depends({plugin_type: 'shadow-tls'});
o.modalonly = true;
o = s.taboption('field_plugin', form.Value, 'plugin_opts_restls_script', _('Restls script'));
o.default = '300?100<1,400~100,350~100,600~100,300~200,300~100';
o.rmempty = false;
o.depends({plugin_type: 'restls'});
o.modalonly = true;
if (isClient) {
o = s.taboption('field_plugin', form.ListValue, 'plugin_opts_dest_proxy', _('Handshake target proxy'),
_('The proxy used to connect to the handshake target.'));
o.default = hm.preset_outbound.direct[0][0];
hm.preset_outbound.direct.forEach((res) => {
o.value.apply(o, res);
})
o.load = function(section_id) {
return hm.loadLabel.call(this, [
...hm.preset_outbound.direct,
...hm.loadLabelValues(this.config, 'proxy_group')
], section_id);
}
o.depends({plugin_type: /^(restls|jls)$/});
o.depends({type: 'shadowquic'});
o.modalonly = true;
}
o = s.taboption('field_plugin', form.Value, 'plugin_opts_rate_limit', _('Forwarding rate limit'),
_('In bps. 0 means no speed limit.'));
o.datatype = 'uinteger';
o.depends({plugin_type: 'jls'});
o.depends({type: 'shadowquic'});
o.modalonly = true;
o = s.taboption('field_plugin', form.Flag, 'plugin_opts_quic_version_probe', _('QUIC version probe'),
_('Probe the QUIC version of the handshake target during the first connection.'));
o.default = o.disabled;
o.depends({type: 'shadowquic'});
o.modalonly = true;
/* Vless Encryption fields */
o = s.taboption('field_general', form.Flag, 'vless_decryption', _('decryption'));
o.default = o.disabled;
@@ -925,7 +982,7 @@ function renderListeners(s, uciconfig, isClient) {
o.depends('hysteria2_realm', '1');
o.modalonly = true;
// @ 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, fingerprint, certificate, private-key, alpn)
// @ 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, name-cert-verify, fingerprint, certificate, private-key, alpn)
/* TLS fields */
o = s.taboption('field_general', form.Flag, 'tls', _('TLS'));
@@ -934,24 +991,65 @@ function renderListeners(s, uciconfig, isClient) {
const type = this.section.getOption('type').formvalue(section_id);
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
let allow_insecure = this.section.getUIElement(section_id, 'allow_insecure').node.querySelector('input');
let tls_alpn = this.section.getUIElement(section_id, 'tls_alpn');
let tls_reality = this.section.getUIElement(section_id, 'tls_reality').node.querySelector('input');
// Force enabled
if (['trojan', 'anytls', 'tuic', 'hysteria2', 'trusttunnel'].includes(type)) {
if (['trojan', 'anytls', 'tuic', 'hysteria2', 'shadowquic', 'trusttunnel'].includes(type)) {
tls.checked = true;
tls.disabled = true;
} else {
tls.removeAttribute('disabled');
}
// Force disabled
if (!['vless', 'trojan', 'anytls'].includes(type)) {
allow_insecure.checked = false;
} else if (allow_insecure.checked) {
tls.checked = false;
tls.disabled = true;
}
if (!['vmess', 'vless', 'trojan'].includes(type)) {
tls_reality.checked = false;
tls_reality.disabled = true;
} else {
tls_reality.removeAttribute('disabled');
}
return true;
}
o.depends({type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|shadowquic|trusttunnel)$/});
o.modalonly = true;
o = s.taboption('field_general', form.Flag, 'allow_insecure', _('Allow insecure connections'),
_('Only applicable when %s are used as a frontend.').format('nginx/caddy'));
o.default = o.disabled;
o.depends({type: /^(vless|trojan|anytls)$/});
o.modalonly = true;
o = s.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
_('Hostname that the client attempts to connect to at the start of the TLS handshake process.'));
o.depends('plugin_type', 'jls');
o.depends({tls: '1', type: 'shadowquic'});
o.modalonly = true;
o = s.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
_('List of supported application level protocols, in order of preference.'));
o.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
//const plugin_type = this.section.getOption('plugin_type').formvalue(section_id);
let tls_alpn = this.section.getUIElement(section_id, 'tls_alpn');
// Default alpn
if (!`${tls_alpn.getValue()}`) {
let def_alpn;
switch (type) {
case 'shadowsocks':
def_alpn = ['h2', 'http/1.1']; // when plugin_type in ['jls']
break;
case 'tuic':
case 'hysteria2':
case 'shadowquic':
def_alpn = ['h3'];
break;
case 'hysteria2-realm':
@@ -965,45 +1063,23 @@ function renderListeners(s, uciconfig, isClient) {
}
// Force disabled
if (!['vless', 'trojan', 'anytls'].includes(type)) {
allow_insecure.checked = false;
} else if (allow_insecure.checked) {
tls.checked = false;
tls.disabled = true;
}
if (['trusttunnel'].includes(type)) {
tls_alpn.node.querySelector('input').disabled = true;
tls_alpn.setValue('');
} else {
tls_alpn.node.querySelector('input').removeAttribute('disabled');
}
if (!['vmess', 'vless', 'trojan'].includes(type)) {
tls_reality.checked = false;
tls_reality.disabled = true;
} else {
tls_reality.removeAttribute('disabled');
}
return true;
}
o.depends({type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
o.modalonly = true;
o = s.taboption('field_general', form.Flag, 'allow_insecure', _('Allow insecure connections'),
_('Only applicable when %s are used as a frontend.').format('nginx/caddy'));
o.default = o.disabled;
o.depends({type: /^(vless|trojan|anytls)$/});
o.modalonly = true;
o = s.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
_('List of supported application level protocols, in order of preference.'));
o.depends('tls', '1');
o.depends({type: 'shadowsocks', plugin_type: 'jls'});
o.modalonly = true;
o = s.taboption('field_tls', form.Value, 'tls_cert_path', _('Certificate path'),
_('The %s public key, in PEM format.').format(_('Server')));
o.value('/etc/fchomo/certs/server_publickey.pem');
o.depends({tls: '1', tls_reality: '0'});
o.depends({tls: '1', tls_reality: '0', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
o.rmempty = false;
o.modalonly = true;
@@ -104,7 +104,7 @@ const parseProviderYaml = hm.parseYaml.extend({
if (!cfg.type)
return null;
// key mapping // 2026/06/06
// key mapping // 2026/07/12
let config = hm.removeBlankAttrs({
id: this.id,
label: this.label,
@@ -130,6 +130,8 @@ const parseProviderYaml = hm.parseYaml.extend({
override_prefix: this.jq(cfg, "override.additional-prefix"),
override_suffix: this.jq(cfg, "override.additional-suffix"),
override_replace: (this.jq(cfg, "override.proxy-name") || []).map((obj) => JSON.stringify(obj)), // array.string: array.object
// Programmable replacement
override_expr: this.jq(cfg, "override.override-expr") || [], // array.string
// Other configuration items
override_tfo: this.bool2str(this.jq(cfg, "override.tfo")), // bool
override_mptcp: this.bool2str(this.jq(cfg, "override.mptcp")), // bool
@@ -137,6 +139,7 @@ const parseProviderYaml = hm.parseYaml.extend({
override_uot: this.bool2str(this.jq(cfg, "override.udp-over-tcp")), // bool
override_up: this.jq(cfg, "override.up"),
override_down: this.jq(cfg, "override.down"),
override_name_cert_verify: this.jq(cfg, "override.name-cert-verify"),
override_skip_cert_verify: this.bool2str(this.jq(cfg, "override.skip-cert-verify")), // bool
//override_dialer_proxy: this.jq(cfg, "override.dialer-proxy"),
override_interface_name: this.jq(cfg, "override.interface-name"),
@@ -231,6 +234,7 @@ return view.extend({
ss.hm_lowcase_only = true;
ss.tab('field_general', _('General fields'));
ss.tab('field_plugin', _('Plugin fields'));
ss.tab('field_vless_encryption', _('Vless Encryption fields'));
ss.tab('field_hysteria2_realm', _('Hysteria2 Realm fields'));
ss.tab('field_tls', _('TLS fields'));
@@ -300,13 +304,13 @@ return view.extend({
/* hm.validateAuth */
so = ss.taboption('field_general', form.Value, 'username', _('Username'));
so.validate = hm.validateAuthUsername;
so.depends({type: /^(http|socks5|mieru|trusttunnel|ssh)$/});
so.depends({type: /^(http|socks5|mieru|shadowquic|trusttunnel|ssh)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'password', _('Password'));
so.password = true;
so.validate = hm.validateAuthPassword;
so.depends({type: /^(http|socks5|mieru|trojan|anytls|tuic|hysteria2|trusttunnel|ssh)$/});
so.depends({type: /^(http|socks5|mieru|trojan|anytls|tuic|hysteria2|shadowquic|trusttunnel|ssh)$/});
so.modalonly = true;
so = ss.taboption('field_general', hm.TextValue, 'headers', _('HTTP header'));
@@ -663,12 +667,6 @@ return view.extend({
so.depends('type', 'tuic');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'tuic_max_open_streams', _('Max open streams'));
so.datatype = 'uinteger';
so.placeholder = '100';
so.depends('type', 'tuic');
so.modalonly = true;
/* Hysteria / Hysteria2 fields */
so = ss.taboption('field_general', form.DynamicList, 'hysteria_ports', _('Ports pool'));
so.datatype = 'or(port, portrange)';
@@ -729,6 +727,31 @@ return view.extend({
so.depends('type', 'trusttunnel');
so.modalonly = true;
/* ShadowQUIC fields */
so = ss.taboption('field_general', form.DynamicList, 'shadowquic_quic_versions', _('QUIC versions'),
_('Support %s, default %s.').format('v1/v2', 'v1'));
so.default = 'v1';
so.rmempty = false;
so.depends('type', 'shadowquic');
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'shadowquic_udp_over_stream', _('UDP over stream'));
so.default = so.disabled;
so.depends('type', 'shadowquic');
so.modalonly = true;
so = ss.taboption('field_general', form.Flag, 'shadowquic_zero_rtt', _('QUIC based 0-RTT'));
so.default = so.disabled;
so.depends('type', 'shadowquic');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'shadowquic_heartbeat', _('Heartbeat interval'),
_('In millisecond.'));
so.datatype = 'uinteger';
so.placeholder = '10000';
so.depends('type', 'shadowquic');
so.modalonly = true;
/* WireGuard fields */
so = ss.taboption('field_general', form.Value, 'wireguard_ip', _('Local address'),
_('The %s address used by local machine in the Wireguard network.').format('IPv4'));
@@ -888,76 +911,13 @@ return view.extend({
so.depends({type: 'ssh', ssh_host_key_algorithms: /.+/});
so.modalonly = true;
/* Plugin fields */
so = ss.taboption('field_general', form.ListValue, 'plugin', _('Plugin'));
so.value('', _('none'));
so.value('obfs', _('obfs-simple'));
//so.value('v2ray-plugin', _('v2ray-plugin'));
//so.value('gost-plugin', _('gost-plugin'));
so.value('shadow-tls', _('shadow-tls'));
so.value('restls', _('restls'));
//so.value('kcptun', _('kcptun'));
so.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
if (value) {
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
return _('Expecting: only support %s.').format(_('obfs-simple') +
' / ' + _('shadow-tls'));
}
}
return true;
}
so.depends({type: /^(ss|snell)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'plugin_opts_obfsmode', _('Plugin: ') + _('Obfs Mode'));
so.value('http', _('HTTP'));
so.value('tls', _('TLS'));
so.depends('plugin', 'obfs');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_host', _('Plugin: ') + _('Host that supports TLS 1.3'));
so.datatype = 'hostname';
so.placeholder = 'cloud.tencent.com';
so.rmempty = false;
so.depends({plugin: /^(obfs|v2ray-plugin|shadow-tls|restls)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_thetlspassword', _('Plugin: ') + _('Password'));
so.password = true;
so.rmempty = false;
so.depends({plugin: /^(shadow-tls|restls)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.ListValue, 'plugin_opts_shadowtls_version', _('Plugin: ') + _('Version'));
so.value('1', _('v1'));
so.value('2', _('v2'));
so.value('3', _('v3'));
so.default = '2';
so.depends({plugin: 'shadow-tls'});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_restls_versionhint', _('Plugin: ') + _('Version hint'));
so.default = 'tls13';
so.rmempty = false;
so.depends({plugin: 'restls'});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'plugin_opts_restls_script', _('Plugin: ') + _('Restls script'));
so.default = '300?100<1,400~100,350~100,600~100,300~200,300~100';
so.rmempty = false;
so.depends({plugin: 'restls'});
so.modalonly = true;
/* Extra fields */
so = ss.taboption('field_general', form.ListValue, 'congestion_controller', _('Congestion controller'));
so.default = hm.congestion_controller[0][0];
hm.congestion_controller.forEach((res) => {
so.value.apply(so, res);
})
so.depends({type: /^(tuic|trusttunnel)$/});
so.depends({type: /^(tuic|shadowquic|trusttunnel)$/});
so.depends({type: 'masque', masque_network: /^(|h3-l4proxy)$/});
so.modalonly = true;
@@ -966,8 +926,14 @@ return view.extend({
hm.bbr_profiles.forEach((res) => {
so.value.apply(so, res);
})
so.depends({congestion_controller: 'bbr'});
so.depends({type: 'hysteria2'});
so.depends('congestion_controller', 'bbr');
so.depends('type', 'hysteria2');
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'max_open_streams', _('Max open streams'));
so.datatype = 'uinteger';
so.placeholder = '1024';
so.depends({type: /^(tuic|shadowquic)$/});
so.modalonly = true;
so = ss.taboption('field_general', form.Value, 'handshake_timeout', _('Handshake timeout'),
@@ -997,6 +963,80 @@ return view.extend({
so.depends('uot', '1');
so.modalonly = true;
/* Plugin fields */
so = ss.taboption('field_general', form.Flag, 'plugin', _('Plugin'));
so.default = so.disabled;
so.depends({type: /^(ss|snell)$/});
so.modalonly = true;
so = ss.taboption('field_plugin', form.ListValue, 'plugin_type', _('Plugin type'));
so.value('obfs', _('obfs-simple'));
//so.value('v2ray-plugin', _('v2ray-plugin'));
//so.value('gost-plugin', _('gost-plugin'));
so.value('shadow-tls', _('ShadowTLS'));
so.value('restls', _('Restls'));
so.value('jls', _('JLS'));
//so.value('kcptun', _('kcptun'));
so.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
if (value) {
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
return _('Expecting: only support %s.').format(_('obfs-simple') +
' / ' + _('ShadowTLS'));
}
}
return true;
}
so.depends('plugin', '1');
so.modalonly = true;
so = ss.taboption('field_plugin', form.ListValue, 'plugin_opts_obfsmode', _('Obfs Mode'));
so.value('http', _('HTTP'));
so.value('tls', _('TLS'));
so.depends('plugin_type', 'obfs');
so.modalonly = true;
so = ss.taboption('field_plugin', form.Value, 'plugin_opts_host', _('Host that supports TLS 1.3'));
so.datatype = 'hostname';
so.placeholder = 'cloud.tencent.com';
so.rmempty = false;
so.depends({plugin_type: /^(obfs|v2ray-plugin|shadow-tls|restls|jls)$/});
so.modalonly = true;
so = ss.taboption('field_plugin', form.Value, 'plugin_opts_thetlsusername', _('Username'));
so.validate = hm.validateAuthUsername;
so.rmempty = false;
so.depends({plugin_type: 'jls'});
so.modalonly = true;
so = ss.taboption('field_plugin', form.Value, 'plugin_opts_thetlspassword', _('Password'));
so.password = true;
so.rmempty = false;
so.depends({plugin_type: /^(shadow-tls|restls|jls)$/});
so.modalonly = true;
so = ss.taboption('field_plugin', form.ListValue, 'plugin_opts_shadowtls_version', _('Version'));
so.value('1', _('v1'));
so.value('2', _('v2'));
so.value('3', _('v3'));
so.default = '2';
so.depends({plugin_type: 'shadow-tls'});
so.modalonly = true;
so = ss.taboption('field_plugin', form.Value, 'plugin_opts_restls_versionhint', _('Version hint'));
so.default = 'tls13';
so.rmempty = false;
so.depends({plugin_type: 'restls'});
so.modalonly = true;
so = ss.taboption('field_plugin', form.Value, 'plugin_opts_restls_script', _('Restls script'));
so.default = '300?100<1,400~100,350~100,600~100,300~200,300~100';
so.rmempty = false;
so.depends({plugin_type: 'restls'});
so.modalonly = true;
/* Vless Encryption fields */
so = ss.taboption('field_general', form.Flag, 'vless_encryption', _('encryption'));
so.default = so.disabled;
@@ -1096,7 +1136,7 @@ return view.extend({
so.depends('hysteria2_realm', '1');
so.modalonly = true;
// @ 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, fingerprint, certificate, private-key, alpn)
// @ 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, name-cert-verify, fingerprint, certificate, private-key, alpn)
/* TLS fields */
so = ss.taboption('field_general', form.Flag, 'tls', _('TLS'));
@@ -1106,7 +1146,7 @@ return view.extend({
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
// Force enabled
if (['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic', 'trusttunnel', 'masque'].includes(type)) {
if (['trojan', 'anytls', 'tuic', 'hysteria', 'hysteria2', 'shadowquic', 'trusttunnel', 'masque'].includes(type)) {
tls.checked = true;
tls.disabled = true;
} else {
@@ -1115,7 +1155,7 @@ return view.extend({
return true;
}
so.depends({type: /^(http|socks5|vmess|vless|trojan|anytls|tuic|hysteria|hysteria2|trusttunnel|masque)$/});
so.depends({type: /^(http|socks5|vmess|vless|trojan|anytls|tuic|hysteria|hysteria2|shadowquic|trusttunnel|masque)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Flag, 'tls_disable_sni', _('Disable SNI'),
@@ -1125,8 +1165,8 @@ return view.extend({
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
_('Used to verify the hostname on the returned certificates.'));
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|trusttunnel|masque)$/});
_('Hostname that the client attempts to connect to at the start of the TLS handshake process.'));
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|shadowquic|trusttunnel|masque)$/});
so.depends({tls: '1', type: /^(tuic)$/, tls_disable_sni: '0'});
so.modalonly = true;
@@ -1134,7 +1174,7 @@ return view.extend({
_('List of supported application level protocols, in order of preference.'));
so.validate = function(section_id, value) {
const type = this.section.getOption('type').formvalue(section_id);
//const plugin = this.section.getOption('plugin').formvalue(section_id);
//const plugin_type = this.section.getOption('plugin_type').formvalue(section_id);
let tls_alpn = this.section.getUIElement(section_id, 'tls_alpn');
// Default alpn
@@ -1144,11 +1184,12 @@ return view.extend({
switch (type) {
case 'ss':
case 'snell':
def_alpn = ['h2', 'http/1.1']; // when plugin === 'shadow-tls'
def_alpn = ['h2', 'http/1.1']; // when plugin_type in ['shadow-tls', 'jls']
break;
case 'tuic':
case 'hysteria':
case 'hysteria2':
case 'shadowquic':
def_alpn = ['h3'];
break;
case 'vmess':
@@ -1171,8 +1212,9 @@ return view.extend({
return true;
}
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|tuic|hysteria|hysteria2|trusttunnel)$/});
so.depends({type: /^(ss|snell)$/, plugin: 'shadow-tls'});
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|tuic|hysteria|hysteria2|shadowquic|trusttunnel)$/});
so.depends({type: /^(ss|snell)$/, plugin_type: 'shadow-tls'});
so.depends({type: 'ss', plugin_type: 'jls'});
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_fingerprint', _('Cert fingerprint'),
@@ -1188,6 +1230,12 @@ return view.extend({
so.depends({tls: '1', type: /^(http|socks5|vmess|vless|trojan|hysteria|hysteria2)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_name_cert_verify', _('Override cert DNSName'),
_('Used to verify the hostname on the returned certificates.'));
so.datatype = 'hostname';
so.depends({tls: '1', type: /^(http|socks5|vmess|vless|trojan|anytls|tuic|hysteria|hysteria2|trusttunnel)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Flag, 'tls_skip_cert_verify', _('Skip cert verify'),
_('Donot verifying server certificate.') +
'<br/>' +
@@ -1227,7 +1275,7 @@ return view.extend({
so = ss.taboption('field_tls', form.Flag, 'tls_ech', _('Enable ECH'));
so.default = so.disabled;
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|tuic|hysteria|hysteria2)$/});
so.depends({type: 'ss', plugin: /^(v2ray-plugin|gost-plugin)$/});
so.depends({type: 'ss', plugin_type: /^(v2ray-plugin|gost-plugin)$/});
so.modalonly = true;
so = ss.taboption('field_tls', form.Value, 'tls_ech_config', _('ECH config'),
@@ -1248,7 +1296,8 @@ return view.extend({
so.value.apply(so, res);
})
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|trusttunnel)$/});
so.depends({type: /^(ss|snell)$/, plugin: /^(shadow-tls|restls)$/});
so.depends({type: /^(ss|snell)$/, plugin_type: /^(shadow-tls|restls)$/});
so.depends({type: 'ss', plugin_type: 'jls'});
so.modalonly = true;
so = ss.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
@@ -1638,6 +1687,7 @@ return view.extend({
' udp-over-tcp: false\n' +
' down: "50 Mbps"\n' +
' up: "10 Mbps"\n' +
' name-cert-verify: example.com\n' +
' skip-cert-verify: true\n' +
' dialer-proxy: proxy\n' +
' interface-name: tailscale0\n' +
@@ -1650,6 +1700,19 @@ return view.extend({
' target: "TEST"\n' +
' - pattern: "IPLC-(.*?)倍"\n' +
' target: "iplc x $1"\n' +
' override-expr:\n' +
" - '.name = \"[provider1] \" + .name' # 普通赋值\n" +
" - '.plugin-opts.mode = \"tls\"' # 自动创建缺失的 mapping\n" +
" - '.alpn[] |= upcase' # 更新数组中的每一项\n" +
" - 'del(.skip-cert-verify)' # 删除字段\n" +
" - '.name = (.name | trim | upcase)' # 赋值右侧的管道需加括号\n" +
" - '.name = \"[\\(.type)] \\(.name):\\(.port)\"' # 字符串插值\n" +
" - '(select(.port == 443) | .tls) = true' # 条件不匹配时不修改\n" +
" - '.tags |= (unique | sort)' # 去重后排序\n" +
" - '.names = [.servers[] | select(.enabled) | .name]' # 收集多个结果\n" +
" - '.servers |= map(select(.enabled))' # 筛选数组\n" +
" - '.options |= with_entries(.key |= upcase)' # 转换 mapping\n" +
" - '. | with_entries(.key |= upcase)' # 整体过滤结果仍须为 mapping\n" +
' filter: "(?i)港|hk|hongkong|hong kong"\n' +
' exclude-filter: "xxx"\n' +
' exclude-type: "ss|http"\n' +
@@ -1896,6 +1959,13 @@ return view.extend({
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.DynamicList, 'override_expr', _('Programmable replacement'),
_('For format see <a target="_blank" href="%s" rel="noreferrer noopener">%s</a>.')
.format('https://wiki.metacubex.one/config/proxy-providers/#override_expr', _('override.override-expr')));
so.placeholder = '.name = "[provider1] " + .name';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.DummyValue, '_config_items', null);
so.load = function() {
return '<a target="_blank" href="%s" rel="noreferrer noopener">%s</a>'
@@ -1938,6 +2008,12 @@ return view.extend({
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.Value, 'override_name_cert_verify', _('Override cert DNSName'),
_('Used to verify the hostname on the returned certificates.'));
so.datatype = 'hostname';
so.depends({type: 'inline', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_override', form.ListValue, 'override_skip_cert_verify', _('Skip cert verify'),
_('Donot verifying server certificate.') +
'<br/>' +
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,41 @@
#!/bin/sh
# Migration script for fchomo inbound
# Used to migrate LuCI application inbound option.
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
CONF=fchomo
config_load "$CONF"
# isDefined <section> <option>
isDefined() {
local opt=CONFIG_${1}_${2};
eval "[ -n \"\${$opt+x}\" ] && return 0 || return 1"
}
migrate() {
# tuic_max_idle_time -> max_idle_time
if isDefined "$1" tuic_max_idle_time; then
local tuic_max_idle_time
config_get tuic_max_idle_time "$1" tuic_max_idle_time ""
uci_remove "$CONF" "$1" tuic_max_idle_time
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
fi
# plugin -> plugin_type
if isDefined "$1" plugin; then
local plugin
config_get plugin "$1" plugin ""
uci_set "$CONF" "$1" plugin "1"
uci_set "$CONF" "$1" plugin_type "$plugin"
fi
}
config_foreach migrate inbound
uci_commit "$CONF"
exit 0
@@ -50,6 +50,22 @@ migrate() {
uci_remove "$CONF" "$1" masque_congestion_controller
uci_set "$CONF" "$1" congestion_controller "$masque_congestion_controller"
fi
# tuic_max_open_streams -> max_open_streams
if isDefined "$1" tuic_max_open_streams; then
local tuic_max_open_streams
config_get tuic_max_open_streams "$1" tuic_max_open_streams ""
uci_remove "$CONF" "$1" tuic_max_open_streams
uci_set "$CONF" "$1" max_open_streams "$tuic_max_open_streams"
fi
# plugin -> plugin_type
if isDefined "$1" plugin; then
local plugin
config_get plugin "$1" plugin ""
uci_set "$CONF" "$1" plugin "1"
uci_set "$CONF" "$1" plugin_type "$plugin"
fi
}
config_foreach migrate node
@@ -24,6 +24,22 @@ migrate() {
uci_remove "$CONF" "$1" tuic_congestion_controller
uci_set "$CONF" "$1" congestion_controller "$tuic_congestion_controller"
fi
# tuic_max_idle_time -> max_idle_time
if isDefined "$1" tuic_max_idle_time; then
local tuic_max_idle_time
config_get tuic_max_idle_time "$1" tuic_max_idle_time ""
uci_remove "$CONF" "$1" tuic_max_idle_time
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
fi
# plugin -> plugin_type
if isDefined "$1" plugin; then
local plugin
config_get plugin "$1" plugin ""
uci_set "$CONF" "$1" plugin "1"
uci_set "$CONF" "$1" plugin_type "$plugin"
fi
}
config_foreach migrate server
@@ -121,7 +121,7 @@ function parse_filter(cfg) {
return cfg;
}
function get_proxy(cfg, pass_undefined) {
function get_proxy(cfg, no_verify) {
if (isEmpty(cfg))
return null;
@@ -130,7 +130,7 @@ function get_proxy(cfg, pass_undefined) {
const label = uci.get(uciconf, cfg, 'label');
if (isEmpty(label)) {
if (pass_undefined)
if (no_verify)
return cfg;
else
die(sprintf("%s's label is missing, please check your configuration.", cfg));
@@ -343,7 +343,16 @@ uci.foreach(uciconf, uciinbd, (cfg) => {
if (cfg.enabled === '0')
return;
push(config.listeners, parseListener(cfg, true, get_proxy(cfg.proxy)));
const listener = parseListener(cfg);
listener.proxy = get_proxy(listener.proxy);
if (listener["res-tls"])
listener["res-tls"].proxy = get_proxy(listener["res-tls"].proxy);
if (listener["jls-config"])
listener["jls-config"].proxy = get_proxy(listener["jls-config"].proxy);
if (listener["jls-upstream"])
listener["jls-upstream"].proxy = get_proxy(listener["jls-upstream"].proxy);
push(config.listeners, listener);
});
/* Tun settings */
if (match(proxy_mode, /tun/))
@@ -490,7 +499,7 @@ uci.foreach(uciconf, ucinode, (cfg) => {
"target-rematch-name": cfg.target_rematch_name,
"target-sub-rule": cfg.target_sub_rule,
/* HTTP / SOCKS / Shadowsocks / VMess / VLESS / Trojan / TUIC / hysteria2 / WireGuard / Masque */
/* HTTP / SOCKS / Shadowsocks / VMess / VLESS / Trojan / TUIC / hysteria2 / ShadowQUIC / WireGuard / Masque */
username: cfg.username,
uuid: cfg.vmess_uuid || cfg.uuid,
cipher: cfg.vmess_chipher || cfg.shadowsocks_chipher,
@@ -558,14 +567,11 @@ uci.foreach(uciconf, ucinode, (cfg) => {
/* TUIC */
ip: cfg.tuic_ip,
"udp-relay-mode": cfg.tuic_udp_relay_mode,
"udp-over-stream": strToBool(cfg.tuic_udp_over_stream),
"udp-over-stream-version": cfg.tuic_udp_over_stream_version,
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size) || null,
"fast-open": strToBool(cfg.tuic_fast_open),
"reduce-rtt": strToBool(cfg.tuic_reduce_rtt),
"heartbeat-interval": strToInt(cfg.tuic_heartbeat) || null,
"request-timeout": strToInt(cfg.tuic_request_timeout) || null,
"max-open-streams": strToInt(cfg.tuic_max_open_streams) || null,
/* Hysteria / Hysteria2 */
ports: isEmpty(cfg.hysteria_ports) ? null : join(',', cfg.hysteria_ports),
@@ -591,6 +597,15 @@ uci.foreach(uciconf, ucinode, (cfg) => {
//"private-key"
} : null,
/* ShadowQUIC */
"quic-versions": cfg.shadowquic_quic_versions,
"zero-rtt": strToBool(cfg.shadowquic_zero_rtt),
// @# cwnd: 10 # default: 32,
// @# max-datagram-frame-size: 1400,
// @# recv-window-conn: 0,
// @# recv-window: 0,
// @# disable-mtu-discovery: false,
/* TrustTunnel */
"health-check": cfg.type === 'trusttunnel' ? (cfg.trusttunnel_health_check === '0' ? false : true) : null,
quic: strToBool(cfg.trusttunnel_quic),
@@ -609,12 +624,25 @@ uci.foreach(uciconf, ucinode, (cfg) => {
"host-key-algorithms": cfg.ssh_host_key_algorithms,
"host-key": cfg.ssh_host_key,
/* Extra fields */
"udp-over-stream": strToBool(cfg.shadowquic_udp_over_stream || cfg.tuic_udp_over_stream),
"heartbeat-interval": strToInt(cfg.tuic_heartbeat) || null,
"keep-alive-interval": strToInt(cfg.shadowquic_heartbeat) || null,
"congestion-controller": cfg.congestion_controller,
"bbr-profile": cfg.bbr_profile,
"max-open-streams": strToInt(cfg.max_open_streams) || null,
"handshake-timeout": strToInt(cfg.handshake_timeout),
udp: strToBool(cfg.udp),
"udp-over-tcp": strToBool(cfg.uot),
"udp-over-tcp-version": cfg.uot_version,
/* Plugin fields */
...(cfg.plugin ? (
...(cfg.plugin === '1' ? (
cfg.type === 'snell' ? {
// snell
"obfs-opts": {
mode: cfg.plugin in ['shadow-tls'] ? cfg.plugin : cfg.plugin_opts_obfsmode,
mode: cfg.plugin_type === 'shadow-tls' ? cfg.plugin_type : cfg.plugin_opts_obfsmode,
host: cfg.plugin_opts_host,
password: cfg.plugin_opts_thetlspassword,
version: strToInt(cfg.plugin_opts_shadowtls_version),
@@ -622,10 +650,11 @@ uci.foreach(uciconf, ucinode, (cfg) => {
}
} : {
// others
plugin: cfg.plugin,
plugin: cfg.plugin_type,
"plugin-opts": {
mode: cfg.plugin_opts_obfsmode,
host: cfg.plugin_opts_host,
username: cfg.plugin_opts_thetlsusername,
password: cfg.plugin_opts_thetlspassword,
version: strToInt(cfg.plugin_opts_shadowtls_version),
alpn: cfg.tls_alpn, // Array
@@ -635,21 +664,14 @@ uci.foreach(uciconf, ucinode, (cfg) => {
}
) : {}),
/* Extra fields */
"congestion-controller": cfg.congestion_controller,
"bbr-profile": cfg.bbr_profile,
"handshake-timeout": strToInt(cfg.handshake_timeout),
udp: strToBool(cfg.udp),
"udp-over-tcp": strToBool(cfg.uot),
"udp-over-tcp-version": cfg.uot_version,
/* SSH / WireGuard / Masque */
/* TLS fields */
tls: (cfg.type in ['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic', 'trusttunnel']) ? null : strToBool(cfg.tls),
tls: (cfg.type in ['trojan', 'anytls', 'tuic', 'hysteria', 'hysteria2', 'shadowquic', 'trusttunnel']) ? null : strToBool(cfg.tls),
"disable-sni": strToBool(cfg.tls_disable_sni),
...arrToObj([[(cfg.type in ['vmess', 'vless']) ? 'servername' : 'sni', cfg.tls_sni]]),
fingerprint: cfg.tls_fingerprint,
alpn: cfg.plugin in ['shadow-tls'] ? null : cfg.tls_alpn, // Array
alpn: cfg.plugin_type in ['shadow-tls', 'jls'] ? null : cfg.tls_alpn, // Array
"name-cert-verify": cfg.tls_name_cert_verify,
"skip-cert-verify": strToBool(cfg.tls_skip_cert_verify),
certificate: cfg.tls_cert_path, // mTLS
"private-key": cfg.masque_private_key || cfg.wireguard_private_key || cfg.ssh_priv_key || cfg.tls_key_path, // mTLS/SSH/WireGuard/Masque
@@ -827,11 +849,13 @@ uci.foreach(uciconf, uciprov, (cfg) => {
"udp-over-tcp": strToBool(cfg.override_uot),
up: cfg.override_up ? cfg.override_up + ' Mbps' : null,
down: cfg.override_down ? cfg.override_down + ' Mbps' : null,
"name-cert-verify": cfg.override_name_cert_verify,
"skip-cert-verify": cfg.override_skip_cert_verify ? strToBool(cfg.override_skip_cert_verify) || false : null,
"dialer-proxy": dialerproxy[cfg['.name']]?.detour,
"interface-name": cfg.override_interface_name,
"routing-mark": strToInt(cfg.override_routing_mark) || null,
"ip-version": cfg.override_ip_version
"ip-version": cfg.override_ip_version,
"override-expr": cfg.override_expr
},
/* General fields */
filter: parse_filter(cfg.filter),
@@ -53,7 +53,7 @@ uci.foreach(uciconf, uciserver, (cfg) => {
if (cfg.enabled === '0')
return;
push(config.listeners, parseListener(cfg, false));
push(config.listeners, parseListener(cfg));
});
/* Inbound END */
+52 -21
View File
@@ -211,23 +211,21 @@ export function parseVlessEncryption(payload, side) {
(isEmpty(content.keypairs) ? '' : '.' + join('.', map(content.keypairs, e => e[side]))); // Required
};
export function parseListener(cfg, isClient, label) {
export function parseListener(cfg) {
return {
name: cfg['.name'],
type: cfg.type,
listen: cfg.listen || '::',
port: strToInt(cfg.port),
...(isClient ? {
"routing-mark": strToInt(cfg.routing_mark) || null,
rule: cfg.rule,
proxy: label,
} : {}),
"routing-mark": strToInt(cfg.routing_mark) || null,
rule: cfg.rule,
proxy: cfg.proxy, // raw data need post-processing
/* HTTP / SOCKS / VMess / VLESS / Trojan / AnyTLS / Tuic / Hysteria2 */
users: (cfg.type in ['http', 'socks', 'mixed', 'vmess', 'vless', 'trojan', 'trusttunnel']) ? [
/* HTTP / SOCKS / Mieru / VMess / VLESS / Trojan / AnyTLS / Tuic / Hysteria2 / ShadowQUIC / TrustTunnel */
users: (cfg.type in ['http', 'socks', 'mixed', 'vmess', 'vless', 'trojan', 'shadowquic', 'trusttunnel']) ? [
(cfg.username || cfg.vmess_uuid) ? {
/* HTTP / SOCKS */
/* HTTP / SOCKS / Trojan / ShadowQUIC / TrustTunnel */
username: cfg.username,
password: cfg.password,
@@ -291,7 +289,6 @@ export function parseListener(cfg, isClient, label) {
"padding-scheme": cfg.anytls_padding_scheme,
/* Tuic */
"max-idle-time": durationToSecond(cfg.tuic_max_idle_time),
"authentication-timeout": durationToSecond(cfg.tuic_authentication_timeout),
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size),
@@ -326,14 +323,38 @@ export function parseListener(cfg, isClient, label) {
"trusted-proxy-header": cfg.hysteria2_realmserver_trusted_proxy_header,
"realm-name-pattern": cfg.hysteria2_realmserver_realm_name_pattern,
/* ShadowQUIC */
"quic-versions": cfg.shadowquic_quic_versions,
"zero-rtt": strToBool(cfg.shadowquic_zero_rtt),
"jls-upstream": cfg.type === 'shadowquic' ? {
addr: cfg.plugin_opts_handshake_dest,
sni: cfg.tls_sni,
proxy: cfg.plugin_opts_dest_proxy, // raw data need post-processing
"rate-limit": strToInt(cfg.plugin_opts_rate_limit),
"quic-version-probe": strToBool(cfg.plugin_opts_quic_version_probe)
} : null,
// @# cwnd: 10 # default: 32,
// @# max-datagram-frame-size: 1400,
// @# recv-window-conn: 0,
// @# recv-window: 0,
// @# disable-mtu-discovery: false,
/* TrustTunnel */
/* Tunnel */
target: cfg.tunnel_target,
/* Extra fields */
"congestion-controller": cfg.congestion_controller,
"bbr-profile": cfg.bbr_profile,
"max-idle-time": durationToSecond(cfg.max_idle_time),
network: cfg.network,
udp: cfg.udp === '0' ? false : true,
/* Plugin fields */
...(cfg.plugin ? (
cfg.plugin === 'obfs' ? (
...(cfg.plugin === '1' ? (
cfg.plugin_type === 'obfs' ? (
// obfs-simple
cfg.type === 'snell' ? {
// snell
@@ -348,7 +369,7 @@ export function parseListener(cfg, isClient, label) {
mode: cfg.plugin_opts_obfsmode
}
}
) : cfg.plugin === 'shadow-tls' ? {
) : cfg.plugin_type === 'shadow-tls' ? {
// shadow-tls
"shadow-tls": {
enable: true,
@@ -365,7 +386,7 @@ export function parseListener(cfg, isClient, label) {
dest: cfg.plugin_opts_handshake_dest
}
}
} : cfg.plugin === 'restls' ? {
} : cfg.plugin_type === 'restls' ? {
// restls
"res-tls": {
enable: true,
@@ -373,17 +394,27 @@ export function parseListener(cfg, isClient, label) {
password: cfg.plugin_opts_thetlspassword,
"restls-script": cfg.plugin_opts_restls_script,
//"min-record-len": 0,
//proxy: ""
proxy: cfg.plugin_opts_dest_proxy // raw data need post-processing
}
} : cfg.plugin_type === 'jls' ? {
// jls
"jls-config": {
enable: true,
users: [
{
username: cfg.plugin_opts_thetlsusername,
password: cfg.plugin_opts_thetlspassword
}
],
dest: cfg.plugin_opts_handshake_dest,
sni: cfg.tls_sni,
alpn: cfg.tls_alpn,
proxy: cfg.plugin_opts_dest_proxy, // raw data need post-processing
"rate-limit": strToInt(cfg.plugin_opts_rate_limit)
}
} : {}
) : {}),
/* Extra fields */
"congestion-controller": cfg.congestion_controller,
"bbr-profile": cfg.bbr_profile,
network: cfg.network,
udp: cfg.udp === '0' ? false : true,
/* TLS fields */
...(cfg.allow_insecure === '1' ? { "allow-insecure": true } : cfg.tls === '1' ? {
alpn: cfg.tls_alpn,
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-xray
PKG_VERSION:=3.7.1
PKG_RELEASE:=5
PKG_RELEASE:=6
PKG_LICENSE:=MPLv2
PKG_LICENSE_FILES:=LICENSE
@@ -481,6 +481,7 @@
chain xray_output {
type route hook output priority mangle {{ firewall_priority }}; policy accept;
ct direction reply {{ counter }} accept comment "Xray reply to inbound connection"
{% if (length(uids_direct) > 0): %}
meta skuid { {{ join(", ", uids_direct) }} } {{ counter }} accept
{% endif %}
+2 -2
View File
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=natflow
PKG_VERSION:=20260531
PKG_RELEASE:=47
PKG_RELEASE:=48
PKG_SOURCE:=$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=d86173db3839524e6125bc729866aefe2e18976c
PKG_SOURCE_VERSION:=a850d7324770f8893ac7373f5fbcde7d61e51857
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
PKG_LICENSE:=GPL-2.0