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

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

View File

@ -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/

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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 */

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,

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

View File

@ -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 %}

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