mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-04 11:03:56 +08:00
230 lines
9.6 KiB
JavaScript
230 lines
9.6 KiB
JavaScript
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Copyright (C) 2022-2025 ImmortalWrt.org
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
'require baseclass';
|
|
'require form';
|
|
'require uci';
|
|
|
|
'require homeproxy-pro as hp';
|
|
'require view.homeproxy-pro.client.common as common';
|
|
|
|
/* The four cache options exist on the 'dns' NamedSection in both the custom
|
|
* mode tab (renderDnsSettings) and the preset mode tab (renderDnsCache).
|
|
* Both used to spell them out separately, which registered the same four
|
|
* option names twice in one map: LuCI keeps the first registration for writes
|
|
* and lookupOption(), so the second copy's dependencies did nothing while its
|
|
* description could drift. They are declared here once and rendered from
|
|
* wherever the tab needs them, in the original order.
|
|
*
|
|
* The depends() are the custom-mode copy's: with the cache disabled the
|
|
* optimistic cache and its persisted file have no effect, so both are hidden
|
|
* (disable_cache_expire is itself conditional on disable_cache). */
|
|
function renderDnsCacheOptions(ss) {
|
|
let so;
|
|
|
|
so = ss.option(form.Flag, 'optimistic_cache', _('Optimistic DNS cache'),
|
|
_('Return expired cache immediately and refresh in background (sing-box 1.14).'));
|
|
so.depends('disable_cache', '0');
|
|
so.depends('disable_cache_expire', '0');
|
|
so.rmempty = false;
|
|
|
|
so = ss.option(form.Value, 'optimistic_timeout', _('Optimistic cache timeout'),
|
|
_('Max time an expired entry may be served. Examples: 3d, 1h.'));
|
|
|
|
so = ss.option(form.Value, 'dns_timeout', _('DNS query timeout'),
|
|
_('Default timeout per DNS query in seconds (sing-box default: 10).'));
|
|
so.datatype = 'uinteger';
|
|
|
|
so = ss.option(form.Flag, 'cache_file_store_dns', _('Store DNS cache'),
|
|
_('Persist DNS cache across restarts (sing-box 1.14, replaces Store RDRC).'));
|
|
so.depends('disable_cache', '0');
|
|
so.rmempty = false;
|
|
}
|
|
|
|
/* DNS settings tab plus the wrapping 'dns' NamedSection. Called after
|
|
* routing.renderRoutingRules() so the NamedSection chain reads
|
|
* config -> routing_node -> routing_rule -> dns from left to right. */
|
|
function renderDnsSettings(ctx) {
|
|
const { s } = ctx;
|
|
let o, ss, so;
|
|
|
|
s.tab('dns', _('DNS Settings'));
|
|
o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'homeproxy-pro');
|
|
o.depends('routing_mode', 'custom');
|
|
|
|
ss = o.subsection;
|
|
so = ss.option(form.ListValue, 'default_strategy', _('Default DNS strategy'),
|
|
_('The DNS strategy for resolving the domain name in the address.'));
|
|
for (let i in hp.dns_strategy)
|
|
so.value(i, hp.dns_strategy[i]);
|
|
|
|
so = ss.option(form.ListValue, 'default_server', _('Default DNS server'));
|
|
so.load = function(section_id) {
|
|
delete this.keylist;
|
|
delete this.vallist;
|
|
|
|
this.value('default-dns', _('Default DNS (issued by WAN)'));
|
|
this.value('system-dns', _('System DNS'));
|
|
uci.sections('homeproxy-pro', 'dns_server', (res) => {
|
|
if (res.enabled === '1')
|
|
this.value(res['.name'], res.label);
|
|
});
|
|
|
|
return this.super('load', section_id);
|
|
}
|
|
so.default = 'default-dns';
|
|
so.rmempty = false;
|
|
|
|
so = ss.option(form.Flag, 'disable_cache', _('Disable DNS cache'));
|
|
|
|
so = ss.option(form.Flag, 'disable_cache_expire', _('Disable cache expire'));
|
|
so.depends('disable_cache', '0');
|
|
|
|
so = ss.option(form.Value, 'client_subnet', _('EDNS Client subnet'),
|
|
_('Append a <code>edns0-subnet</code> OPT extra record with the specified IP prefix to every query by default.<br/>' +
|
|
'If value is an IP address instead of prefix, <code>/32</code> or <code>/128</code> will be appended automatically.'));
|
|
so.datatype = 'or(cidr, ipaddr)';
|
|
|
|
renderDnsCacheOptions(ss);
|
|
/* DNS settings end */
|
|
}
|
|
|
|
/* DNS rules sub-section. Split out from renderDnsSettings() so the
|
|
* orchestrator can call it after nodes.renderDnsServers() and preserve
|
|
* the original tab ordering (dns, dns_server, dns_rule). */
|
|
function renderDnsRules(ctx) {
|
|
const { s, self } = ctx;
|
|
|
|
/* DNS rules start */
|
|
common.renderRuleSection(s, 'dns', self);
|
|
/* DNS rules end */
|
|
}
|
|
|
|
/* DNS tab for the preset routing modes. Titled 'DNS Settings' exactly like
|
|
* the custom-mode tab in renderDnsSettings(): the two are mutually exclusive
|
|
* (custom vs the presets), so whichever one a user lands on is called the
|
|
* same thing.
|
|
*
|
|
* The three server options below are config.* (not dns.*) options that used
|
|
* to live on the routing tab. They are DNS settings, and the routing page
|
|
* only carried them because of the order the old monolithic client.js
|
|
* happened to be written in. They stay on the 'config' NamedSection, so
|
|
* only the tab they render under changed. */
|
|
function renderDnsCache(ctx) {
|
|
const { s, stubValidator } = ctx;
|
|
let o, ss;
|
|
|
|
s.tab('dns_cache', _('DNS Settings'));
|
|
|
|
/* The name says overseas because that is the only thing that belongs
|
|
* here: every query this field answers goes to a domain that is not on
|
|
* the China path, and a domestic resolver answers those with polluted or
|
|
* NXDOMAIN results. The Chinese public resolvers that used to be listed
|
|
* below a divider were therefore a footgun with a helpful-looking label -
|
|
* the one combination that reliably breaks a working proxy. Only the
|
|
* overseas public resolvers are offered now. */
|
|
o = s.taboption('dns_cache', form.Value, 'dns_server', _('Overseas DNS server'),
|
|
_('Resolves every domain that is not on the China DNS path, through the selected proxy node. ' +
|
|
'A domestic resolver answers these queries with polluted or NXDOMAIN results, which is what ' +
|
|
'makes a foreign site fail to resolve while the proxy itself works - so only overseas services ' +
|
|
'are offered here. Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified.'));
|
|
o.value('1.1.1.1', _('CloudFlare Public DNS (1.1.1.1)'));
|
|
o.value('208.67.222.222', _('Cisco Public DNS (208.67.222.222)'));
|
|
o.value('8.8.8.8', _('Google Public DNS (8.8.8.8)'));
|
|
o.default = '8.8.8.8';
|
|
o.rmempty = false;
|
|
o.depends({'routing_mode': 'custom', '!reverse': true});
|
|
o.validate = function(section_id, value) {
|
|
/* 'wan' is the value this field stored while the dropdown still
|
|
* offered "WAN DNS (read from interface)" - the resolver the WAN
|
|
* interface handed us. It is no longer offered, because the ISP
|
|
* resolver is precisely what this field must not be, but the
|
|
* generator still maps the literal to wan_dns
|
|
* (generator/context.uc), so a configuration that chose it back
|
|
* then keeps working. Rejecting it here would block saving that
|
|
* untouched, working configuration - hence accepted, but never
|
|
* offered again. */
|
|
if (section_id && !['wan'].includes(value)) {
|
|
if (!value)
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
|
|
|
let ipv6_support = this.section.formvalue(section_id, 'ipv6_support');
|
|
try {
|
|
let url = new URL(value.replace(/^.*:\/\//, 'http://'));
|
|
if (stubValidator.apply('hostname', url.hostname))
|
|
return true;
|
|
else if (stubValidator.apply('ip4addr', url.hostname))
|
|
return true;
|
|
else if ((ipv6_support === '1') && stubValidator.apply('ip6addr', url.hostname.match(/^\[(.+)\]$/)?.[1]))
|
|
return true;
|
|
else
|
|
return _('Expecting: %s').format(_('valid DNS server address'));
|
|
} catch(e) {}
|
|
|
|
if (!stubValidator.apply((ipv6_support === '1') ? 'ipaddr' : 'ip4addr', value))
|
|
return _('Expecting: %s').format(_('valid DNS server address'));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
o = s.taboption('dns_cache', form.Value, 'china_dns_server', _('China DNS server'),
|
|
_('Resolves the domains on the China path, so it has to be a domestic service to get the ' +
|
|
'correct local CDN answers. Keep the default; the upstream may block UDP/53, in which case ' +
|
|
'a DoH address (https://dns.alidns.com/dns-query, https://doh.pub/dns-query) also works. ' +
|
|
'Support UDP, TCP, DoH, DoQ, DoT. This address is also the resolver the proxy DNS server ' +
|
|
'above uses to look up its own hostname when that one is a domain - and a URL here is a ' +
|
|
'domain, which cannot do that lookup itself, so with a URL here the proxy DNS server falls ' +
|
|
'back to the WAN resolver. Enter the bare IP form of the same service to keep that lookup ' +
|
|
'on this router.'));
|
|
o.value('wan', _('WAN DNS (read from interface)'));
|
|
o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)'));
|
|
o.value('210.2.4.8', _('CNNIC Public DNS (210.2.4.8)'));
|
|
o.value('119.29.29.29', _('Tencent Public DNS (119.29.29.29)'));
|
|
o.value('117.50.10.10', _('ThreatBook Public DNS (117.50.10.10)'));
|
|
o.depends('routing_mode', 'bypass_mainland_china');
|
|
o.default = '223.5.5.5';
|
|
o.rmempty = false;
|
|
o.validate = function(section_id, value) {
|
|
if (section_id && !['wan'].includes(value)) {
|
|
if (!value)
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
|
|
|
try {
|
|
let url = new URL(value.replace(/^.*:\/\//, 'http://'));
|
|
if (stubValidator.apply('hostname', url.hostname))
|
|
return true;
|
|
else if (stubValidator.apply('ip4addr', url.hostname))
|
|
return true;
|
|
else if (stubValidator.apply('ip6addr', url.hostname.match(/^\[(.+)\]$/)?.[1]))
|
|
return true;
|
|
else
|
|
return _('Expecting: %s').format(_('valid DNS server address'));
|
|
} catch(e) {}
|
|
|
|
if (!stubValidator.apply('ipaddr', value))
|
|
return _('Expecting: %s').format(_('valid DNS server address'));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
o = s.taboption('dns_cache', form.Flag, 'cn_ip_fallback', _('CN-IP DNS fallback (sing-box 1.14)'),
|
|
_('When the main DNS returns a mainland China IP, re-resolve via China DNS using evaluate/match_response.'));
|
|
o.depends('routing_mode', 'bypass_mainland_china');
|
|
o.rmempty = false;
|
|
|
|
o = s.taboption('dns_cache', form.SectionValue, '_dns_cache', form.NamedSection, 'dns', 'homeproxy-pro');
|
|
o.depends({'routing_mode': 'custom', '!reverse': true});
|
|
ss = o.subsection;
|
|
|
|
renderDnsCacheOptions(ss);
|
|
}
|
|
|
|
return baseclass.extend({ renderDnsSettings, renderDnsRules, renderDnsCache });
|