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)
12 "require https-dns-proxy.status as hdp";
16 return "https-dns-proxy";
19 return "https://docs.openwrt.melmac.net/" + pkg
.Name
+ "/";
21 templateToRegexp: function (template
) {
27 let placeholder
= part
.match(/^\{(\w+)\}$/);
28 if (placeholder
) return `(?<${placeholder[1]}>.*?)`;
29 else return part
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
35 templateToResolver: function (template
, args
) {
36 return template
.replace(/{(\w+)}/g, (_
, v
) => args
[v
]);
43 L
.resolveDefault(hdp
.getPlatformSupport(pkg
.Name
), {}),
44 L
.resolveDefault(hdp
.getProviders(pkg
.Name
), {}),
50 render: function (data
) {
52 platform
: (data
[0] && data
[0][pkg
.Name
]) || {
56 providers
: (data
[1] && data
[1][pkg
.Name
]) || { providers
: [] },
58 reply
.providers
.sort(function (a
, b
) {
59 return _(a
.title
).localeCompare(_(b
.title
));
61 reply
.providers
.push({
64 params
: { option
: { type
: "text" } },
67 var status
, m
, s
, o
, p
;
70 status
= new hdp
.status();
72 m
= new form
.Map(pkg
.Name
, _("HTTPS DNS Proxy - Configuration"));
74 s
= m
.section(form
.NamedSection
, "config", pkg
.Name
);
77 "dnsmasq_config_update",
78 _("Update DNSMASQ Config on Start/Stop"),
80 "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)."
82 '<a href="' + L
.url("admin", "network", "dhcp") + '">',
84 '<a href="' + pkg
.URL
+ "#default-settings" + '" target="_blank">',
88 o
.value("*", _("Update all configs"));
89 var sections
= uci
.sections("dhcp", "dnsmasq");
90 sections
.forEach((element
) => {
93 if (element
[".name"] === uci
.resolveSID("dhcp", element
[".name"])) {
94 key
= element
[".index"];
95 description
= "dnsmasq[" + element
[".index"] + "]";
97 key
= element
[".name"];
98 description
= element
[".name"];
100 o
.value(key
, _("Update %s only").format(description
));
102 o
.value("-", _("Do not update configs"));
108 _("Force Router DNS"),
109 _("Forces Router DNS use on local devices, also known as DNS Hijacking.")
111 o
.value("0", _("Let local devices use their own DNS servers if set"));
112 o
.value("1", _("Force Router DNS server to all local devices"));
117 "canary_domains_icloud",
118 _("Canary Domains iCloud"),
120 "Blocks access to iCloud Private Relay resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
122 '<a href="' + pkg
.URL
+ "#canary_domains_icloud" + '" target="_blank">',
126 o
.value("0", _("Let local devices use iCloud Private Relay"));
127 o
.value("1", _("Force Router DNS server to all local devices"));
128 o
.depends("force_dns", "1");
133 "canary_domains_mozilla",
134 _("Canary Domains Mozilla"),
136 "Blocks access to Mozilla Encrypted resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
140 "#canary_domains_mozilla" +
141 '" target="_blank">',
145 o
.value("0", _("Let local devices use Mozilla Private Relay"));
146 o
.value("1", _("Force Router DNS server to all local devices"));
147 o
.depends("force_dns", "1");
151 if (!reply
.platform
.http2_support
)
154 "Please note that %s is not supported on this system (%smore information%s)."
157 '<a href="' + pkg
.URL
+ "#http2-support" + '" target="_blank">',
160 if (!reply
.platform
.http3_support
)
163 "Please note that %s is not supported on this system (%smore information%s)."
165 "<i>HTTP/3 (QUIC)</i>",
166 '<a href="' + pkg
.URL
+ "#http3-quic-support" + '" target="_blank">',
173 _("HTTPS DNS Proxy - Instances"),
181 s
.sectiontitle
= (section_id
) => {
184 reply
.providers
.forEach((prov
) => {
186 let regexp
= pkg
.templateToRegexp(prov
.template
);
187 let resolver
= uci
.get(pkg
.Name
, section_id
, "resolver_url");
188 resolver
= resolver
=== undefined ? null : resolver
;
189 if (!found
&& resolver
&& regexp
.test(resolver
)) {
191 provText
= _(prov
.title
);
192 let match
= resolver
.match(regexp
);
193 if (match
[1] != null) {
196 prov
.params
.option
&&
197 prov
.params
.option
.options
199 prov
.params
.option
.options
.forEach((opt
) => {
200 if (opt
.value
=== match
[1]) {
201 option
= _(opt
.description
);
204 provText
+= " (" + option
+ ")";
206 if (match
[1] !== "") provText
+= " (" + match
[1] + ")";
211 return provText
|| _("Unknown");
215 _provider
= s
.option(form
.ListValue
, "_provider", _("Provider"));
216 _provider
.modalonly
= true;
217 _provider
.cfgvalue = function (section_id
) {
218 let resolver
= this.map
.data
.get(
223 if (resolver
=== undefined || resolver
=== null) return null;
226 reply
.providers
.forEach((prov
, i
) => {
227 let regexp
= pkg
.templateToRegexp(prov
.template
);
228 if (!found
&& regexp
.test(resolver
)) {
235 _provider
.write = function (section_id
, formvalue
) {
236 uci
.set(pkg
.Name
, section_id
, "resolver_url", formvalue
);
239 reply
.providers
.forEach((prov
, i
) => {
240 if (prov
.http2_only
&& !reply
.platform
.http2_support
) return;
241 if (prov
.http3_only
&& !reply
.platform
.http3_support
) return;
242 _provider
.value(prov
.template
, _(prov
.title
));
245 prov
.params
.option
&&
246 prov
.params
.option
.type
&&
247 prov
.params
.option
.type
=== "select"
249 let optName
= prov
.params
.option
.description
|| _("Parameter");
250 var _paramList
= s
.option(form
.ListValue
, "_paramList_" + i
, optName
);
251 _paramList
.template
= prov
.template
;
252 _paramList
.modalonly
= true;
253 if (prov
.params
.option
.default) {
254 _paramList
.default = prov
.params
.option
.default;
256 prov
.params
.option
.options
.forEach((opt
) => {
257 let val
= opt
.value
|| "";
258 let descr
= opt
.description
|| "";
259 _paramList
.value(val
, descr
);
261 _paramList
.depends("_provider", prov
.template
);
262 _paramList
.write = function (section_id
, formvalue
) {
263 let template
= this.map
.data
.get(
268 if (_paramList
.template
!== template
) return 0;
269 let resolver
= pkg
.templateToResolver(template
, {
270 option
: formvalue
|| "",
272 uci
.set(pkg
.Name
, section_id
, "resolver_url", resolver
);
274 _paramList
.remove
= _paramList
.write
;
277 prov
.params
.option
&&
278 prov
.params
.option
.type
&&
279 prov
.params
.option
.type
=== "text"
281 let optName
= prov
.params
.option
.description
|| _("Parameter");
282 var _paramText
= s
.option(form
.Value
, "_paramText_" + i
, optName
);
283 _paramText
.template
= prov
.template
;
284 _paramText
.modalonly
= true;
285 _paramText
.depends("_provider", prov
.template
);
286 _paramText
.optional
= !(
287 prov
.params
.option
.default && prov
.params
.option
.default !== ""
289 _paramText
.cfgvalue = function (section_id
) {
290 let resolver
= this.map
.data
.get(
295 if (resolver
=== undefined || resolver
=== null) return null;
296 let regexp
= pkg
.templateToRegexp(prov
.template
);
297 let match
= resolver
.match(regexp
);
298 return (match
&& match
[1]) || null;
300 _paramText
.write = function (section_id
, formvalue
) {
301 let template
= this.map
.data
.get(
306 if (_paramText
.template
!== template
) return 0;
307 let resolver
= pkg
.templateToResolver(template
, {
308 option
: formvalue
|| "",
310 uci
.set(pkg
.Name
, section_id
, "resolver_url", resolver
);
312 _paramText
.remove
= _paramText
.write
;
316 o
= s
.option(form
.Value
, "bootstrap_dns", _("Bootstrap DNS"));
320 o
= s
.option(form
.Value
, "listen_addr", _("Listen Address"));
321 o
.datatype
= "ipaddr";
324 o
.placeholder
= "127.0.0.1";
326 o
= s
.option(form
.Value
, "listen_port", _("Listen Port"));
330 o
.placeholder
= n
+ 5053;
331 o
= s
.option(form
.Value
, "user", _("Run As User"));
335 o
= s
.option(form
.Value
, "group", _("Run As Group"));
339 o
= s
.option(form
.Value
, "dscp_codepoint", _("DSCP Codepoint"));
340 o
.datatype
= "and(uinteger, range(0,63))";
344 o
= s
.option(form
.Value
, "verbosity", _("Logging Verbosity"));
345 o
.datatype
= "and(uinteger, range(0,4))";
349 o
= s
.option(form
.Value
, "logfile", _("Logging File Path"));
353 o
= s
.option(form
.Value
, "polling_interval", _("Polling Interval"));
354 o
.datatype
= "and(uinteger, range(5,3600))";
358 o
= s
.option(form
.Value
, "proxy_server", _("Proxy Server"));
362 o
= s
.option(form
.ListValue
, "use_http1", _("Use HTTP/1"));
366 o
.value("", _("Use negotiated HTTP version"));
367 o
.value("1", _("Force use of HTTP/1"));
371 "use_ipv6_resolvers_only",
372 _("Use IPv6 resolvers")
377 o
.value("", _("Use any family DNS resolvers"));
378 o
.value("1", _("Force use of IPv6 DNS resolvers"));
381 return Promise
.all([status
.render(), m
.render()]);