1 // Copyright 2023 MOSSDeF, Stan Grishin <stangri@melmac.ca>
2 // This code wouldn't have been possible without help from:
3 // - [@jow-](https://github.com/jow-)
4 // - [@stokito](https://github.com/stokito)
5 // - [@vsviridov](https://github.com/vsviridov)
6 // noinspection JSAnnotator
13 "require https-dns-proxy.status as hdp";
18 return "https-dns-proxy";
22 return "https://docs.openwrt.melmac.net/" + pkg
.Name
+ "/";
25 templateToRegexp: function (template
) {
31 let placeholder
= part
.match(/^\{(\w+)\}$/);
32 if (placeholder
) return `(?<${placeholder[1]}>.*?)`;
33 else return part
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
40 templateToResolver: function (template
, args
) {
41 return template
.replace(/{(\w+)}/g, (_
, v
) => args
[v
]);
48 L
.resolveDefault(hdp
.getPlatformSupport(pkg
.Name
), {}),
49 L
.resolveDefault(hdp
.getProviders(pkg
.Name
), {}),
55 render: function (data
) {
57 platform
: (data
[0] && data
[0][pkg
.Name
]) || {
61 providers
: (data
[1] && data
[1][pkg
.Name
]) || { providers
: [] },
63 reply
.providers
.sort(function (a
, b
) {
64 return _(a
.title
).localeCompare(_(b
.title
));
66 reply
.providers
.push({
69 params
: { option
: { type
: "text" } },
72 var status
, m
, s
, o
, p
;
75 status
= new hdp
.status();
77 m
= new form
.Map(pkg
.Name
, _("HTTPS DNS Proxy - Configuration"));
79 s
= m
.section(form
.NamedSection
, "config", pkg
.Name
);
83 "dnsmasq_config_update",
84 _("Update DNSMASQ Config on Start/Stop"),
86 "If update option is selected, the %s'DNS forwardings' section of DHCP and DNS%s will be automatically updated to use selected DoH providers (%smore information%s)."
88 '<a href="' + L
.url("admin", "network", "dhcp") + '">',
90 '<a href="' + pkg
.URL
+ "#default-settings" + '" target="_blank">',
94 o
.value("*", _("Update all configs"));
96 var sections
= uci
.sections("dhcp", "dnsmasq");
97 sections
.forEach((element
) => {
100 if (element
[".name"] === uci
.resolveSID("dhcp", element
[".name"])) {
101 key
= element
[".index"];
102 description
= "dnsmasq[" + element
[".index"] + "]";
104 key
= element
[".name"];
105 description
= element
[".name"];
107 o
.value(key
, _("Update %s only").format(description
));
109 o
.value("-", _("Do not update configs"));
115 _("Force Router DNS"),
116 _("Forces Router DNS use on local devices, also known as DNS Hijacking.")
118 o
.value("0", _("Let local devices use their own DNS servers if set"));
119 o
.value("1", _("Force Router DNS server to all local devices"));
124 "canary_domains_icloud",
125 _("Canary Domains iCloud"),
127 "Blocks access to iCloud Private Relay resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
129 '<a href="' + pkg
.URL
+ "#canary_domains_icloud" + '" target="_blank">',
133 o
.value("0", _("Let local devices use iCloud Private Relay"));
134 o
.value("1", _("Force Router DNS server to all local devices"));
135 o
.depends("force_dns", "1");
140 "canary_domains_mozilla",
141 _("Canary Domains Mozilla"),
143 "Blocks access to Mozilla Encrypted resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
147 "#canary_domains_mozilla" +
148 '" target="_blank">',
152 o
.value("0", _("Let local devices use Mozilla Private Relay"));
153 o
.value("1", _("Force Router DNS server to all local devices"));
154 o
.depends("force_dns", "1");
158 if (!reply
.platform
.http2_support
)
161 "Please note that %s is not supported on this system (%smore information%s)."
164 '<a href="' + pkg
.URL
+ "#http2-support" + '" target="_blank">',
167 if (!reply
.platform
.http3_support
)
170 "Please note that %s is not supported on this system (%smore information%s)."
172 "<i>HTTP/3 (QUIC)</i>",
173 '<a href="' + pkg
.URL
+ "#http3-quic-support" + '" target="_blank">',
180 _("HTTPS DNS Proxy - Instances"),
188 s
.sectiontitle
= (section_id
) => {
191 reply
.providers
.forEach((prov
) => {
193 let regexp
= pkg
.templateToRegexp(prov
.template
);
194 let resolver
= uci
.get(pkg
.Name
, section_id
, "resolver_url");
195 resolver
= resolver
=== undefined ? null : resolver
;
196 if (!found
&& resolver
&& regexp
.test(resolver
)) {
198 provText
= _(prov
.title
);
199 let match
= resolver
.match(regexp
);
200 if (match
[1] != null) {
203 prov
.params
.option
&&
204 prov
.params
.option
.options
206 prov
.params
.option
.options
.forEach((opt
) => {
207 if (opt
.value
=== match
[1]) {
208 option
= _(opt
.description
);
211 provText
+= " (" + option
+ ")";
213 if (match
[1] !== "") provText
+= " (" + match
[1] + ")";
218 return provText
|| _("Unknown");
222 _provider
= s
.option(form
.ListValue
, "_provider", _("Provider"));
223 _provider
.modalonly
= true;
224 _provider
.cfgvalue = function (section_id
) {
225 let resolver
= this.map
.data
.get(
230 if (resolver
=== undefined || resolver
=== null) return null;
233 reply
.providers
.forEach((prov
, i
) => {
234 let regexp
= pkg
.templateToRegexp(prov
.template
);
235 if (!found
&& regexp
.test(resolver
)) {
242 _provider
.write = function (section_id
, formvalue
) {
243 uci
.set(pkg
.Name
, section_id
, "resolver_url", formvalue
);
246 reply
.providers
.forEach((prov
, i
) => {
247 if (prov
.http2_only
&& !reply
.platform
.http2_support
) return;
248 if (prov
.http3_only
&& !reply
.platform
.http3_support
) return;
249 _provider
.value(prov
.template
, _(prov
.title
));
252 prov
.params
.option
&&
253 prov
.params
.option
.type
&&
254 prov
.params
.option
.type
=== "select"
256 let optName
= prov
.params
.option
.description
|| _("Parameter");
257 var _paramList
= s
.option(form
.ListValue
, "_paramList_" + i
, optName
);
258 _paramList
.template
= prov
.template
;
259 _paramList
.modalonly
= true;
260 if (prov
.params
.option
.default) {
261 _paramList
.default = prov
.params
.option
.default;
263 prov
.params
.option
.options
.forEach((opt
) => {
264 let val
= opt
.value
|| "";
265 let descr
= opt
.description
|| "";
266 _paramList
.value(val
, descr
);
268 _paramList
.depends("_provider", prov
.template
);
269 _paramList
.write = function (section_id
, formvalue
) {
270 let template
= this.map
.data
.get(
275 if (_paramList
.template
!== template
) return 0;
276 let resolver
= pkg
.templateToResolver(template
, {
277 option
: formvalue
|| "",
279 uci
.set(pkg
.Name
, section_id
, "resolver_url", resolver
);
281 _paramList
.remove
= _paramList
.write
;
284 prov
.params
.option
&&
285 prov
.params
.option
.type
&&
286 prov
.params
.option
.type
=== "text"
288 let optName
= prov
.params
.option
.description
|| _("Parameter");
289 var _paramText
= s
.option(form
.Value
, "_paramText_" + i
, optName
);
290 _paramText
.template
= prov
.template
;
291 _paramText
.modalonly
= true;
292 _paramText
.depends("_provider", prov
.template
);
293 _paramText
.optional
= !(
294 prov
.params
.option
.default && prov
.params
.option
.default !== ""
296 _paramText
.cfgvalue = function (section_id
) {
297 let resolver
= this.map
.data
.get(
302 if (resolver
=== undefined || resolver
=== null) return null;
303 let regexp
= pkg
.templateToRegexp(prov
.template
);
304 let match
= resolver
.match(regexp
);
305 return (match
&& match
[1]) || null;
307 _paramText
.write = function (section_id
, formvalue
) {
308 let template
= this.map
.data
.get(
313 if (_paramText
.template
!== template
) return 0;
314 let resolver
= pkg
.templateToResolver(template
, {
315 option
: formvalue
|| "",
317 uci
.set(pkg
.Name
, section_id
, "resolver_url", resolver
);
319 _paramText
.remove
= _paramText
.write
;
323 o
= s
.option(form
.Value
, "bootstrap_dns", _("Bootstrap DNS"));
328 o
= s
.option(form
.Value
, "listen_addr", _("Listen Address"));
329 o
.datatype
= "ipaddr";
332 o
.placeholder
= "127.0.0.1";
334 o
= s
.option(form
.Value
, "listen_port", _("Listen Port"));
338 o
.placeholder
= "5053";
340 o
= s
.option(form
.Value
, "user", _("Run As User"));
345 o
= s
.option(form
.Value
, "group", _("Run As Group"));
350 o
= s
.option(form
.Value
, "dscp_codepoint", _("DSCP Codepoint"));
351 o
.datatype
= "and(uinteger, range(0,63))";
356 o
= s
.option(form
.Value
, "verbosity", _("Logging Verbosity"));
357 o
.datatype
= "and(uinteger, range(0,4))";
362 o
= s
.option(form
.Value
, "logfile", _("Logging File Path"));
367 o
= s
.option(form
.Value
, "polling_interval", _("Polling Interval"));
368 o
.datatype
= "and(uinteger, range(5,3600))";
373 o
= s
.option(form
.Value
, "proxy_server", _("Proxy Server"));
378 o
= s
.option(form
.ListValue
, "use_http1", _("Use HTTP/1"));
382 o
.value("", _("Use negotiated HTTP version"));
383 o
.value("1", _("Force use of HTTP/1"));
388 "use_ipv6_resolvers_only",
389 _("Use IPv6 resolvers")
394 o
.value("", _("Use any family DNS resolvers"));
395 o
.value("1", _("Force use of IPv6 DNS resolvers"));
398 return Promise
.all([status
.render(), m
.render()]);