Skip to content

Commit

Permalink
feat(client): support URLTest outbound in custom routing mode
Browse files Browse the repository at this point in the history
Signed-off-by: Tianling Shen <[email protected]>
  • Loading branch information
1715173329 committed Dec 31, 2024
1 parent 5bc6a29 commit 73cc3b2
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 14 deletions.
76 changes: 69 additions & 7 deletions htdocs/luci-static/resources/view/homeproxy/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,22 +378,23 @@ return view.extend({

so = ss.option(form.ListValue, 'node', _('Node'),
_('Outbound node'));
so.value('urltest', _('URLTest'));
for (var i in proxy_nodes)
so.value(i, proxy_nodes[i]);
so.validate = L.bind(hp.validateUniqueValue, this, data[0], 'routing_node', 'node');
so.editable = true;

so = ss.option(form.ListValue, 'domain_strategy', _('Domain strategy'),
_('If set, the server domain name will be resolved to IP before connecting.<br/>'));
for (var i in hp.dns_strategy)
so.value(i, hp.dns_strategy[i]);
so.depends({'node': 'urltest', '!reverse': true});
so.modalonly = true;

so = ss.option(widgets.DeviceSelect, 'bind_interface', _('Bind interface'),
_('The network interface to bind to.'));
so.multiple = false;
so.noaliases = true;
so.depends('outbound', '');
so.depends({'outbound': '', 'node': /^((?!urltest$).)+$/});
so.modalonly = true;

so = ss.option(form.ListValue, 'outbound', _('Outbound'),
Expand All @@ -416,16 +417,79 @@ return view.extend({

var conflict = false;
uci.sections(data[0], 'routing_node', (res) => {
if (res['.name'] !== section_id)
if (res['.name'] !== section_id) {
if (res.outbound === section_id && res['.name'] == value)
conflict = true;
else if (res?.urltest_nodes?.includes(node) && res['.name'] == value)
conflict = true;
}
});
if (conflict)
return _('Recursive outbound detected!');
}

return true;
}
so.depends({'node': 'urltest', '!reverse': true});

so = ss.option(hp.CBIStaticList, 'urltest_nodes', _('URLTest nodes'),
_('List of nodes to test.'));
for (var i in proxy_nodes)
so.value(i, proxy_nodes[i]);
so.depends('node', 'urltest');
so.modalonly = true;

so = ss.option(form.Value, 'urltest_url', _('Test URL'),
_('The URL to test. <code>https://www.gstatic.com/generate_204</code> will be used if empty.'));
so.validate = function(section_id, value) {
if (section_id && value) {
try {
var url = new URL(value);
if (!url.hostname)
return _('Expecting: %s').format(_('valid URL'));
}
catch(e) {
return _('Expecting: %s').format(_('valid URL'));
}
}

return true;
}
so.depends('node', 'urltest');
so.modalonly = true;

so = ss.option(form.Value, 'urltest_interval', _('Test interval'),
_('The test interval in seconds. <code>180</code> will be used if empty.'));
so.datatype = 'uinteger';
so.validate = function(section_id, value) {
if (section_id && value) {
var idle_timeout = this.map.lookupOption('urltest_idle_timeout', section_id)[0].formvalue(section_id) || '1800';
if (parseInt(value) > parseInt(idle_timeout))
return _('Test interval must be less or equal than idle timeout.');
}

return true;
}
so.depends('node', 'urltest');
so.modalonly = true;

so = ss.option(form.Value, 'urltest_tolerance', _('Test tolerance'),
_('The test tolerance in milliseconds. <code>50</code> will be used if empty.'));
so.datatype = 'uinteger';
so.depends('node', 'urltest');
so.modalonly = true;

so = ss.option(form.Value, 'urltest_idle_timeout', _('Idle timeout'),
_('The idle timeout in seconds. <code>1800</code> will be used if empty.'));
so.datatype = 'uinteger';
so.depends('node', 'urltest');
so.modalonly = true;

so = ss.option(form.Flag, 'urltest_interrupt_exist_connections', _('Interrupt existing connections'),
_('Interrupt existing connections when the selected outbound has changed.'));
so.default = so.disabled;
so.depends('node', 'urltest');
so.modalonly = true;
/* Routing nodes end */

/* Routing rules start */
Expand Down Expand Up @@ -580,13 +644,12 @@ return view.extend({
_('Match user name.'));
so.modalonly = true;

so = ss.taboption('field_other', form.MultiValue, 'rule_set', _('Rule set'),
so = ss.taboption('field_other', hp.CBIStaticList, 'rule_set', _('Rule set'),
_('Match rule set.'));
so.load = function(section_id) {
delete this.keylist;
delete this.vallist;

this.value('', _('-- Please choose --'));
uci.sections(data[0], 'ruleset', (res) => {
if (res.enabled === '1')
this.value(res['.name'], res.label);
Expand Down Expand Up @@ -920,13 +983,12 @@ return view.extend({
_('Match user name.'));
so.modalonly = true;

so = ss.taboption('field_other', form.MultiValue, 'rule_set', _('Rule set'),
so = ss.taboption('field_other', hp.CBIStaticList, 'rule_set', _('Rule set'),
_('Match rule set.'));
so.load = function(section_id) {
delete this.keylist;
delete this.vallist;

this.value('', _('-- Please choose --'));
uci.sections(data[0], 'ruleset', (res) => {
if (res.enabled === '1')
this.value(res['.name'], res.label);
Expand Down
38 changes: 31 additions & 7 deletions root/etc/homeproxy/scripts/generate_client.uc
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ function get_outbound(cfg) {
const node = uci.get(uciconfig, cfg, 'node');
if (isEmpty(node))
die(sprintf("%s's node is missing, please check your configuration.", cfg));
else if (node === 'urltest')
return 'cfg-' + cfg + '-out';
else
return 'cfg-' + node + '-out';
}
Expand Down Expand Up @@ -571,17 +573,39 @@ if (!isEmpty(main_node)) {
push(config.outbounds, generate_outbound(main_udp_node_cfg));
config.outbounds[length(config.outbounds)-1].tag = 'main-udp-out';
}
} else if (!isEmpty(default_outbound))
} else if (!isEmpty(default_outbound)) {
let urltest_nodes = [],
routing_nodes = [];

uci.foreach(uciconfig, uciroutingnode, (cfg) => {
if (cfg.enabled !== '1')
return;

const outbound = uci.get_all(uciconfig, cfg.node) || {};
push(config.outbounds, generate_outbound(outbound));
config.outbounds[length(config.outbounds)-1].domain_strategy = cfg.domain_strategy;
config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface;
config.outbounds[length(config.outbounds)-1].detour = get_outbound(cfg.outbound);
if (cfg.node === 'urltest') {
push(config.outbounds, {
type: 'urltest',
tag: 'cfg-' + cfg['.name'] + '-out',
outbounds: map(cfg.urltest_nodes, (k) => `cfg-${k}-out`),
url: cfg.urltest_url,
interval: cfg.urltest_interval ? (cfg.urltest_interval + 's') : null,
tolerance: strToInt(cfg.urltest_tolerance),
idle_timeout: cfg.urltest_idle_timeout ? (cfg.urltest_idle_timeout + 's') : null,
interrupt_exist_connections: (cfg.urltest_interrupt_exist_connections === '1')
});
urltest_nodes = [...urltest_nodes, ...filter(cfg.urltest_nodes, ((l) => !~index(urltest_nodes, l)))];
} else {
const outbound = uci.get_all(uciconfig, cfg.node) || {};
push(config.outbounds, generate_outbound(outbound));
config.outbounds[length(config.outbounds)-1].domain_strategy = cfg.domain_strategy;
config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface;
config.outbounds[length(config.outbounds)-1].detour = get_outbound(cfg.outbound);
push(routing_nodes, cfg.node);
}
});

for (let i in filter(urltest_nodes, ((l) => !~index(routing_nodes, l))))
push(config.outbounds, generate_outbound(uci.get_all(uciconfig, i)));
}
/* Outbound end */

/* Routing rules start */
Expand All @@ -593,7 +617,6 @@ config.route = {
outbound: 'dns-out'
}
],
rule_set: [],
auto_detect_interface: isEmpty(default_interface) ? true : null,
default_interface: default_interface
};
Expand Down Expand Up @@ -654,6 +677,7 @@ if (!isEmpty(main_node)) {

/* Rule set */
if (routing_mode === 'custom') {
config.route.rule_set = [];
uci.foreach(uciconfig, uciruleset, (cfg) => {
if (cfg.enabled !== '1')
return null;
Expand Down

0 comments on commit 73cc3b2

Please sign in to comment.