'use strict'; 'require fs'; 'require form'; 'require rpc'; 'require uci'; 'require ui'; 'require view'; 'require tools.widgets as widgets'; const UBNET = 'https://nlnetlabs.nl/projects/unbound/about/'; const README = 'https://github.com/openwrt/packages/blob/master/net/unbound/files/README.md'; const callRcInit = rpc.declare({ object: 'rc', method: 'init', params: [ 'name', 'action' ], expect: { result: false } }); return view.extend({ load() { return Promise.all([ uci.load('unbound'), uci.load('dhcp'), ]); }, render([unboundCfg, dhcpCfg]) { const m = new form.Map('unbound', _('Recursive DNS'), _("Unbound (NLnet Labs) is a validating, recursive, and caching DNS resolver (help).".format(UBNET, README))); const s = m.section(form.NamedSection, 'ub_main', 'unbound'); s.anonymous = true; s.addremove = false; const manual = uci.get('unbound', 'ub_main', 'manual_conf'); const dhcpLink = uci.get('unbound', 'ub_main', 'dhcp_link'); const leasetrig = uci.get('dhcp', 'odhcpd', 'leasetrigger') || 'undefined'; if (manual === '0' && dhcpLink === 'odhcpd' && leasetrig !== '/usr/lib/unbound/odhcpd.sh') { ui.addTimeLimitedNotification(null, E('p', _('Note: local DNS is configured to look at odhpcd, but odhpcd UCI lease trigger is incorrectly set:') + "dhcp.odhcpd.leasetrigger='" + leasetrig + "'"), 15000, 'note'); } // Tabs s.tab('basic', _('Basic')); if (manual === '0') { s.tab('advanced', _('Advanced')); s.tab('dhcp', _('DHCP')); s.tab('resource', _('Resource')); } // Basic tab items const ena = s.taboption('basic', form.Flag, 'enabled', _('Enable Unbound'), _('Enable the initialization scripts for Unbound')); ena.rmempty = false; const mcf = s.taboption('basic', form.Flag, 'manual_conf', _('Manual Conf'), _('Skip UCI and use /etc/unbound/unbound.conf')); mcf.rmempty = false; if (manual === '0') { s.taboption('basic', form.Flag, 'localservice', _('Local Service'), _('Accept queries only from local subnets')).rmempty = false; s.taboption('basic', form.Flag, 'validator', _('Enable DNSSEC'), _('Enable the DNSSEC validator module')).rmempty = false; const nvd = s.taboption('basic', form.Flag, 'validator_ntp', _('DNSSEC NTP Fix'), _('Break the loop where DNSSEC needs NTP and NTP needs DNS')); nvd.optional = true; nvd.default = '1'; nvd.depends('validator', '1'); const prt = s.taboption('basic', form.Value, 'listen_port', _('Listening Port'), _('Choose Unbounds listening port')); prt.datatype = 'port'; prt.placeholder = '53'; } else { const ag2b = s.taboption('basic', form.Value, 'root_age', _('Root DSKEY Age'), _('Limit days between RFC5011 copies to reduce flash writes')); ag2b.datatype = 'and(uinteger,min(1),max(99))'; ag2b.value('3', '3'); ag2b.value('9', '9 (' + _('default') + ')'); ag2b.value('12', '12'); ag2b.value('24', '24'); ag2b.value('99', '99 (' + _('never') + ')'); const tgrb = s.taboption('basic', widgets.NetworkSelect, 'trigger_interface', _('Trigger Networks'), _('Networks that may trigger Unbound to reload (avoid wan6)')); tgrb.multiple = true; tgrb.rmempty = true; tgrb.nocreate = true; } // Advanced, DHCP & Resource tabs only if manual===0 if (manual === '0') { const rlh = s.taboption('advanced', form.Flag, 'rebind_localhost', _('Filter Localhost Rebind'), _('Protect against upstream response of 127.0.0.0/8')); rlh.rmempty = false; const rpv = s.taboption('advanced', form.ListValue, 'rebind_protection', _('Filter Private Rebind'), _('Protect against upstream responses within local subnets')); rpv.rmempty = false; rpv.value('0', _('No Filter')); rpv.value('1', _('Filter Private Address')); rpv.value('2', _('Filter Entire Subnet')); const d64 = s.taboption('advanced', form.Flag, 'dns64', _('Enable DNS64'), _('Enable the DNS64 module')); d64.rmempty = false; const pfx = s.taboption('advanced', form.Value, 'dns64_prefix', _('DNS64 Prefix'), _('Prefix for generated DNS64 addresses')); pfx.datatype = 'ip6addr'; pfx.placeholder = '64:ff9b::/96'; pfx.optional = true; pfx.depends('dns64', '1'); const exga = s.taboption('advanced', form.Flag, 'exclude_ipv6_ga', _('Exclude IPv6 GA')); exga.rmempty = false; const din = s.taboption('advanced', form.DynamicList, 'domain_insecure', _('Domain Insecure'), _('List domains to bypass checks of DNSSEC')); din.depends('validator', '1'); const ag2 = s.taboption('advanced', form.Value, 'root_age', _('Root DSKEY Age')); ag2.datatype = 'and(uinteger,min(1),max(99))'; ag2.value('3', '3'); ag2.value('9', '9 (' + _('default') + ')'); ag2.value('12', '12'); ag2.value('24', '24'); ag2.value('99', '99 (' + _('never') + ')'); const ifc = s.taboption('advanced', widgets.NetworkSelect, 'iface_lan', _('LAN Networks'), _('Networks to consider LAN (served) beyond those served by DHCP')); ifc.multiple = true; ifc.rmempty = true; ifc.nocreate = true; const wfc = s.taboption('advanced', widgets.NetworkSelect, 'iface_wan', _('WAN Networks'), _('Networks to consider WAN (unserved)')); wfc.multiple = true; wfc.rmempty = true; wfc.nocreate = true; const tgr = s.taboption('advanced', widgets.NetworkSelect, 'iface_trig', _('Trigger Networks'), _('Networks that may trigger Unbound to reload (avoid wan6)')); tgr.multiple = true; tgr.rmempty = true; tgr.nocreate = true; const bindhide = s.taboption('advanced', form.Flag, 'hide_binddata', _('Refuse possible attack queries')); const verb = s.taboption('advanced', form.ListValue, 'verbosity', _('Verbosity')); verb.datatype = 'and(uinteger,min(0),max(5))'; verb.value('0'); verb.value('1'); verb.value('2'); verb.value('3'); verb.value('4'); verb.value('5'); // DHCP tab const dlk = s.taboption('dhcp', form.ListValue, 'dhcp_link', _('DHCP Link'), _('Link to supported programs to load DHCP into DNS')); dlk.rmempty = false; ['none','dnsmasq','odhcpd'].forEach(v => dlk.value(v, v === 'none' ? _('(none)') : v)); const dp6 = s.taboption('dhcp', form.Flag, 'dhcp4_slaac6', _('DHCPv4 to SLAAC'), _('Use DHCPv4 MAC to discover IP6 hosts SLAAC (EUI64)')); dp6.optional = true; dp6.depends('dhcp_link', 'odhcpd'); const dom = s.taboption('dhcp', form.Value, 'domain', _('Local Domain'), _('Domain suffix for this router and DHCP clients')); dom.placeholder = 'lan'; dom.optional = true; const dty = s.taboption('dhcp', form.ListValue, 'domain_type', _('Local Domain Type'), _('How to treat queries of this local domain')); dty.optional = true; dty.value('deny', _('Denied (nxdomain)')); dty.value('refuse', _('Refused')); dty.value('static', _('Static (local only)')); dty.value('transparent', _('Transparent (local/global)')); dty.depends('dhcp_link', 'none'); dty.depends('dhcp_link', 'odhcpd'); const addDnsOpts = [ ['add_local_fqdn', _('LAN DNS'), _('How to enter the LAN or local network router in DNS')], ['add_wan_fqdn', _('WAN DNS'), _('Override the WAN side router entry in DNS')], ]; const addDnsValues = [ ['1', _('Hostname, Primary Address')], ['2', _('Hostname, All Addresses')], ['3', _('Host FQDN, All Addresses')], ['4', _('Interface FQDN, All Addresses')] ]; addDnsOpts.forEach(([optName,label]) => { const opt = s.taboption('dhcp', form.ListValue, optName, label, ''); opt.optional = true; opt.value('0', (optName == 'add_local_fqdn') ? _('No Entry') : _('Use Upstream')); addDnsValues.forEach(([val,text]) => opt.value(val, text)); opt.depends('dhcp_link', 'none'); opt.depends('dhcp_link', 'odhcpd'); }); const exa = s.taboption('dhcp', form.ListValue, 'add_extra_dns', _('Extra DNS'), _('Use extra DNS entries found in /etc/config/dhcp')); exa.optional = true; exa.value('0', _('Ignore')); exa.value('1', _('Host Records')); exa.value('2', _('Host/MX/SRV RR')); exa.value('3', _('Host/MX/SRV/CNAME RR')); // Resource tab const ctl = s.taboption('resource', form.ListValue, 'unbound_control', _('Unbound Control App'), _('Enable access for unbound-control')); ctl.value('0', _('No Remote Control')); ctl.value('1', _('Local Host, No Encryption')); ctl.value('2', _('Local Host, Encrypted')); ctl.value('3', _('Local Subnet, Encrypted')); ctl.value('4', _('Local Subnet, Static Encryption')); ctl.rmempty = false; const pro = s.taboption('resource', form.ListValue, 'protocol', _('Recursion Protocol'), _('Choose the IP versions used upstream and downstream')); pro.value('default', _('Default')); pro.value('ip4_only', _('IP4 Only')); pro.value('ip6_local', _('IP4 All and IP6 Local')); pro.value('ip6_only', _('IP6 Only*')); pro.value('ip6_prefer', _('IP6 Preferred')); pro.value('mixed', _('IP4 and IP6')); pro.rmempty = false; const rsc = s.taboption('resource', form.ListValue, 'resource', _('Memory Resource'), _('Use menu System/Processes to observe any memory growth')); rsc.value('default', _('Default')); rsc.value('tiny', _('Tiny')); rsc.value('small', _('Small')); rsc.value('medium', _('Medium')); rsc.value('large', _('Large')); rsc.rmempty = false; const rsn = s.taboption('resource', form.ListValue, 'recursion', _('Recursion Strength'), _('Recursion activity affects memory growth and CPU load')); rsn.value('default', _('Default')); rsn.value('passive', _('Passive')); rsn.value('aggressive', _('Aggressive')); rsn.rmempty = false; const qry = s.taboption('resource', form.Flag, 'query_minimize', _('Query Minimize'), _('Break down query components for limited added privacy')); qry.optional = true; qry.depends('recursion', 'passive'); qry.depends('recursion', 'aggressive'); const qrs = s.taboption('resource', form.Flag, 'query_min_strict', _('Strict Minimize'), _("Strict version of 'query minimize' but it can break DNS")); qrs.optional = true; qrs.depends('query_minimize', '1'); const eds = s.taboption('resource', form.Value, 'edns_size', _('EDNS Size'), _('Limit extended DNS packet size')); eds.datatype = 'and(uinteger,min(512),max(4096))'; eds.placeholder = '1280'; const tlm = s.taboption('resource', form.Value, 'ttl_min', _('TTL Minimum'), _('Prevent excessively short cache periods')); tlm.datatype = 'and(uinteger,min(0),max(1800))'; tlm.placeholder = '120'; const tlnm = s.taboption('resource', form.Value, 'ttl_neg_max', _('TTL Neg Max')); tlnm.datatype = 'and(uinteger,min(0),max(1200))'; tlnm.placeholder = '1000'; const rtt = s.taboption('resource', form.Value, 'rate_limit', _('Query Rate Limit'), _('Prevent client query overload; zero is off')); rtt.datatype = 'and(uinteger,min(0),max(5000))'; rtt.placeholder = '0'; const stt = s.taboption('resource', form.Flag, 'extended_stats', _('Extended Statistics'), _('Extended statistics are printed from unbound-control')); stt.rmempty = false; } return m.render(); }, handleSaveApply(ev, mode) { const enabled = uci.get('unbound', 'ub_main', 'enabled'); const cmd = enabled ? 'reload' : 'stop'; const Fn = L.bind(() => { callRcInit('unbound', cmd); document.removeEventListener('uci-applied', Fn); }); document.addEventListener('uci-applied', Fn); this.super('handleSaveApply', [ev, mode]); } });