Merge pull request #2010 from EricLuehrsen/unbound_20180721

luci-app-unbound: add LuCI for forward stub and auth zone clauses
This commit is contained in:
Jo-Philipp Wich 2018-07-30 07:01:46 +02:00 committed by GitHub
commit 9db490ab1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 640 additions and 333 deletions

View file

@ -1,151 +1,200 @@
-- Copyright 2008 Steven Barth <steven@midlink.org> -- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org> -- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
-- Copyright 2017 Eric Luehrsen <ericluehrsen@hotmail.com> -- Copyright 2017 Eric Luehrsen <ericluehrsen@gmail.com>
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
module("luci.controller.unbound", package.seeall) module("luci.controller.unbound", package.seeall)
function index() function index()
local fs = require "nixio.fs"
local ucl = luci.model.uci.cursor() local ucl = luci.model.uci.cursor()
local valexp = ucl:get_first("unbound", "unbound", "extended_luci")
local valman = ucl:get_first("unbound", "unbound", "manual_conf") local valman = ucl:get_first("unbound", "unbound", "manual_conf")
if not nixio.fs.access("/etc/config/unbound") then if not fs.access("/etc/config/unbound") then
return return
end end
if valexp == "1" then
-- Expanded View -- Expanded View
entry({"admin", "services", "unbound"}, firstchild(), _("Recursive DNS")).dependent = false entry({"admin", "services", "unbound"},
firstchild(), _("Recursive DNS")).dependent = false
-- UCI Tab(s) -- UCI Tab(s)
entry({"admin", "services", "unbound", "configure"}, cbi("unbound/configure"), _("Settings"), 10) entry({"admin", "services", "unbound", "configure"},
cbi("unbound/configure"), _("Unbound"), 10)
if (valman == "0") then
entry({"admin", "services", "unbound", "zones"},
cbi("unbound/zones"), _("Zones"), 15)
end
-- Status Tab(s) -- Status Tab(s)
entry({"admin", "services", "unbound", "status"}, firstchild(), _("Status"), 20) entry({"admin", "services", "unbound", "status"},
entry({"admin", "services", "unbound", "status", "syslog"}, call("QuerySysLog"), _("Log"), 50).leaf = true firstchild(), _("Status"), 20)
entry({"admin", "services", "unbound", "status", "syslog"},
call("QuerySysLog"), _("Log"), 50).leaf = true
if nixio.fs.access("/usr/sbin/unbound-control") then if fs.access("/usr/sbin/unbound-control") then
-- Require unbound-control to execute -- Require unbound-control to execute
entry({"admin", "services", "unbound", "status", "statistics"}, call("QueryStatistics"), _("Statistics"), 10).leaf = true entry({"admin", "services", "unbound", "status", "statistics"},
entry({"admin", "services", "unbound", "status", "localdata"}, call("QueryLocalData"), _("Local Data"), 20).leaf = true call("QueryStatistics"), _("Statistics"), 10).leaf = true
entry({"admin", "services", "unbound", "status", "localzone"}, call("QueryLocalZone"), _("Local Zones"), 30).leaf = true
entry({"admin", "services", "unbound", "status", "localdata"},
call("QueryLocalData"), _("Local Data"), 20).leaf = true
entry({"admin", "services", "unbound", "status", "localzone"},
call("QueryLocalZone"), _("Local Zones"), 30).leaf = true
else else
entry({"admin", "services", "unbound", "status", "statistics"}, call("ShowEmpty"), _("Statistics"), 10).leaf = true entry({"admin", "services", "unbound", "status", "statistics"},
call("ShowEmpty"), _("Statistics"), 10).leaf = true
end end
-- Raw File Tab(s) -- Raw File Tab(s)
entry({"admin", "services", "unbound", "files"}, firstchild(), _("Files"), 30) entry({"admin", "services", "unbound", "files"},
firstchild(), _("Files"), 30)
if valman ~= "1" then if (valman == "0") then
entry({"admin", "services", "unbound", "files", "base"}, call("ShowUnboundConf"), _("UCI: Unbound"), 10).leaf = true entry({"admin", "services", "unbound", "files", "uci"},
else form("unbound/uciedit"), _("Edit: UCI"), 5).leaf = true
entry({"admin", "services", "unbound", "files", "base"}, form("unbound/manual"), _("Edit: Unbound"), 10).leaf = true
end
entry({"admin", "services", "unbound", "files", "base"},
entry({"admin", "services", "unbound", "files", "server"}, form("unbound/server"), _("Edit: Server"), 20).leaf = true call("ShowUnboundConf"), _("Show: Unbound"), 10).leaf = true
entry({"admin", "services", "unbound", "files", "extended"}, form("unbound/extended"), _("Edit: Extended"), 30).leaf = true
if nixio.fs.access("/var/lib/unbound/unbound_dhcp.conf") then
entry({"admin", "services", "unbound", "files", "dhcp"}, call("ShowDHCPConf"), _("Include: DHCP"), 40).leaf = true
end
if nixio.fs.access("/var/lib/unbound/adb_list.overall") then
entry({"admin", "services", "unbound", "files", "adblock"}, call("ShowAdblock"), _("Include: Adblock"), 50).leaf = true
end
else else
-- Simple View to UCI only entry({"admin", "services", "unbound", "files", "base"},
entry({"admin", "services", "unbound"}, cbi("unbound/configure"), _("Recursive DNS")).dependent = false form("unbound/manual"), _("Edit: Unbound"), 10).leaf = true
end
entry({"admin", "services", "unbound", "files", "server"},
form("unbound/server"), _("Edit: Server"), 20).leaf = true
entry({"admin", "services", "unbound", "files", "extended"},
form("unbound/extended"), _("Edit: Extended"), 30).leaf = true
if fs.access("/var/lib/unbound/dhcp.conf") then
entry({"admin", "services", "unbound", "files", "dhcp"},
call("ShowDHCPConf"), _("Show: DHCP"), 40).leaf = true
end
if fs.access("/var/lib/unbound/adb_list.overall") then
entry({"admin", "services", "unbound", "files", "adblock"},
call("ShowAdblock"), _("Show: Adblock"), 50).leaf = true
end end
end end
function ShowEmpty() function ShowEmpty()
local lclhead = "Unbound Control" local lclhead = "Unbound Control"
local lcldesc = luci.i18n.translate("This could display more statistics with the unbound-control package.") local lcldesc = luci.i18n.translate(
luci.template.render("unbound/show-empty", {heading = lclhead, description = lcldesc}) "This could display more statistics with the unbound-control package.")
luci.template.render("unbound/show-empty",
{heading = lclhead, description = lcldesc})
end end
function QuerySysLog() function QuerySysLog()
local lclhead = "System Log"
local lcldata = luci.util.exec("logread | grep -i unbound") local lcldata = luci.util.exec("logread | grep -i unbound")
local lcldesc = luci.i18n.translate("This shows syslog filtered for events involving Unbound.") local lcldesc = luci.i18n.translate(
luci.template.render("unbound/show-textbox", {heading = lclhead, description = lcldesc, content = lcldata}) "This shows syslog filtered for events involving Unbound.")
luci.template.render("unbound/show-textbox",
{heading = "", description = lcldesc, content = lcldata})
end end
function QueryStatistics() function QueryStatistics()
local lclhead = "Unbound Control Stats" local lcldata = luci.util.exec(
local lcldata = luci.util.exec("unbound-control -c /var/lib/unbound/unbound.conf stats_noreset") "unbound-control -c /var/lib/unbound/unbound.conf stats_noreset")
local lcldesc = luci.i18n.translate("This shows some performance statistics tracked by Unbound.")
luci.template.render("unbound/show-textbox", {heading = lclhead, description = lcldesc, content = lcldata}) local lcldesc = luci.i18n.translate(
"This shows Unbound self reported performance statistics.")
luci.template.render("unbound/show-textbox",
{heading = "", description = lcldesc, content = lcldata})
end end
function QueryLocalData() function QueryLocalData()
local lclhead = "Unbound Control Local Data" local lcldata = luci.util.exec(
local lcldata = luci.util.exec("unbound-control -c /var/lib/unbound/unbound.conf list_local_data") "unbound-control -c /var/lib/unbound/unbound.conf list_local_data")
local lcldesc = luci.i18n.translate("This shows local host records that shortcut recursion.")
luci.template.render("unbound/show-textbox", {heading = lclhead, description = lcldesc, content = lcldata}) local lcldesc = luci.i18n.translate(
"This shows Unbound 'local-data:' entries from default, .conf, or control.")
luci.template.render("unbound/show-textbox",
{heading = "", description = lcldesc, content = lcldata})
end end
function QueryLocalZone() function QueryLocalZone()
local lclhead = "Unbound Control Local Zones" local lcldata = luci.util.exec(
local lcldata = luci.util.exec("unbound-control -c /var/lib/unbound/unbound.conf list_local_zones") "unbound-control -c /var/lib/unbound/unbound.conf list_local_zones")
local lcldesc = luci.i18n.translate("This shows local zone definitions that affect recursion routing or processing. ")
luci.template.render("unbound/show-textbox", {heading = lclhead, description = lcldesc, content = lcldata}) local lcldesc = luci.i18n.translate(
"This shows Unbound 'local-zone:' entries from default, .conf, or control.")
luci.template.render("unbound/show-textbox",
{heading = "", description = lcldesc, content = lcldata})
end end
function ShowUnboundConf() function ShowUnboundConf()
local unboundfile = "/var/lib/unbound/unbound.conf" local unboundfile = "/var/lib/unbound/unbound.conf"
local lclhead = "Unbound Conf"
local lcldata = nixio.fs.readfile(unboundfile) local lcldata = nixio.fs.readfile(unboundfile)
local lcldesc = luci.i18n.translate("This shows configuration generated by UCI:") local lcldesc = luci.i18n.translate(
lcldesc = lcldesc .. " (" .. unboundfile .. ")" "This shows '" .. unboundfile .. "' generated from UCI configuration.")
luci.template.render("unbound/show-textbox", {heading = lclhead, description = lcldesc, content = lcldata})
luci.template.render("unbound/show-textbox",
{heading = "", description = lcldesc, content = lcldata})
end end
function ShowDHCPConf() function ShowDHCPConf()
local dhcpfile = "/var/lib/unbound/unbound_dhcp.conf" local dhcpfile = "/var/lib/unbound/dhcp.conf"
local lclhead = "DHCP Conf"
local lcldata = nixio.fs.readfile(dhcpfile) local lcldata = nixio.fs.readfile(dhcpfile)
local lcldesc = luci.i18n.translate("This shows LAN hosts added by DHCP hook scripts:") local lcldesc = luci.i18n.translate(
lcldesc = lcldesc .. " (" .. dhcpfile .. ")" "This shows '" .. dhcpfile .. "' list of hosts from DHCP hook scripts.")
luci.template.render("unbound/show-textbox", {heading = lclhead, description = lcldesc, content = lcldata})
luci.template.render("unbound/show-textbox",
{heading = "", description = lcldesc, content = lcldata})
end end
function ShowAdblock() function ShowAdblock()
local fs = require "nixio.fs"
local tp = require "luci.template"
local tr = require "luci.i18n"
local adblockfile = "/var/lib/unbound/adb_list.overall" local adblockfile = "/var/lib/unbound/adb_list.overall"
local lclhead = "Adblock Conf"
local lcldata, lcldesc local lcldata, lcldesc
if nixio.fs.stat(adblockfile).size > 262144 then if fs.stat(adblockfile).size > 262144 then
lcldesc = luci.i18n.translate("Adblock domain list is too large for LuCI:") lcldesc = tr.translate(
lcldesc = lcldesc .. " (" .. adblockfile .. ")" "Adblock domain list '" .. adblockfile .. "' is too large for LuCI.")
luci.template.render("unbound/show-empty", {heading = lclhead, description = lcldesc})
tp.render("unbound/show-empty",
{heading = "", description = lcldesc})
else else
lcldata = nixio.fs.readfile(adblockfile) lcldata = fs.readfile(adblockfile)
lcldesc = luci.i18n.translate("This shows blocked domains provided by Adblock scripts:") lcldesc = tr.translate(
lcldesc = lcldesc .. " (" .. adblockfile .. ")" "This shows '" .. adblockfile .. "' list of adblock domains." )
luci.template.render("unbound/show-textbox", {heading = lclhead, description = lcldesc, content = lcldata})
tp.render("unbound/show-textbox",
{heading = "", description = lcldesc, content = lcldata})
end end
end end

View file

@ -1,5 +1,5 @@
-- Copyright 2008 Steven Barth <steven@midlink.org> -- Copyright 2008 Steven Barth <steven@midlink.org>
-- Copyright 2016 Eric Luehrsen <ericluehrsen@hotmail.com> -- Copyright 2016 Eric Luehrsen <ericluehrsen@gmail.com>
-- Copyright 2016 Dan Luedtke <mail@danrl.com> -- Copyright 2016 Dan Luedtke <mail@danrl.com>
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
@ -9,56 +9,36 @@ local rlh, rpv, vld, nvd, eds, prt, tlm
local ctl, dlk, dom, dty, lfq, wfq, exa local ctl, dlk, dom, dty, lfq, wfq, exa
local dp6, d64, pfx, qry, qrs local dp6, d64, pfx, qry, qrs
local pro, tgr, rsc, rsn, ag2, stt local pro, tgr, rsc, rsn, ag2, stt
local rpn, din, dfw, ath local rpn, din, ath
local ut = require "luci.util"
local sy = require "luci.sys"
local ht = require "luci.http"
local ds = require "luci.dispatcher"
local ucl = luci.model.uci.cursor() local ucl = luci.model.uci.cursor()
local valman = ucl:get_first("unbound", "unbound", "manual_conf") local valman = ucl:get_first("unbound", "unbound", "manual_conf")
m1 = Map("unbound") m1 = Map("unbound")
s1 = m1:section(TypedSection, "unbound", translate("DNS Resolver"),
translatef("Unbound <a href=\"%s\" target=\"_blank\">(NLnet Labs)</a>"
.. " is a validating, recursive, and caching DNS resolver.",
"https://www.unbound.net/"))
s1 = m1:section(TypedSection, "unbound")
s1.addremove = false s1.addremove = false
s1.anonymous = true s1.anonymous = true
--LuCI, Unbound, or Not --LuCI, Unbound, or Not
s1:tab("basic", translate("Basic"), s1:tab("basic", translate("Basic"))
translatef("<h3>Unbound Basic Settings</h3>\n"
.. "<a href=\"%s\" target=\"_blank\">Unbound (link)</a>"
.. " is a validating, recursive, and caching DNS resolver. "
.. "UCI documentation can be found on "
.. "<a href=\"%s\" target=\"_blank\">github (link)</a>.",
"https://www.unbound.net/",
"https://github.com/openwrt/packages/blob/master/net/unbound/files/README.md"))
if valman ~= "1" then if (valman == "0") then
-- Not in manual configuration mode; show UCI -- Not in manual configuration mode; show UCI
s1:tab("advanced", translate("Advanced"), s1:tab("advanced", translate("Advanced"))
translatef("<h3>Unbound Advanced Settings</h3>\n" s1:tab("DHCP", translate("DHCP"))
.. "Domain manipulation, lookup protection, and workarounds for " s1:tab("resource", translate("Resource"))
.. "<a href=\"%s\" target=\"_blank\">Unbound </a>"
.. " DNS resolver.", "https://www.unbound.net/"))
s1:tab("DHCP", translate("DHCP"),
translatef("<h3>Unbound DHCP Settings</h3>\n"
.. "Link your DHCP server to "
.. "<a href=\"%s\" target=\"_blank\">Unbound </a>"
.. " DNS resolver.", "https://www.unbound.net/ "))
s1:tab("resource", translate("Resource"),
translatef("<h3>Unbound Resource Settings</h3>\n"
.. "Memory and protocol setttings for "
.. "<a href=\"%s\" target=\"_blank\">Unbound </a>"
.. " DNS resolver.", "https://www.unbound.net/"))
end end
s1:tab("trigger", translate("Trigger"),
translatef("<h3>Unbound Event Trigger Settings</h3>\n"
.. "Start, reload, and save RFC5011 DNSKEY records for "
.. "<a href=\"%s\" target=\"_blank\">Unbound </a>"
.. " DNS resolver.", "https://www.unbound.net/"))
--Basic Tab, unconditional pieces --Basic Tab, unconditional pieces
ena = s1:taboption("basic", Flag, "enabled", translate("Enable Unbound:"), ena = s1:taboption("basic", Flag, "enabled", translate("Enable Unbound:"),
translate("Enable the initialization scripts for Unbound")) translate("Enable the initialization scripts for Unbound"))
@ -68,103 +48,116 @@ mcf = s1:taboption("basic", Flag, "manual_conf", translate("Manual Conf:"),
translate("Skip UCI and use /etc/unbound/unbound.conf")) translate("Skip UCI and use /etc/unbound/unbound.conf"))
mcf.rmempty = false mcf.rmempty = false
lci = s1:taboption("basic", Flag, "extended_luci", translate("Extended Tabs:"),
translate("See detailed tabs for statistics, debug, and manual configuration"))
lci.rmempty = false
if (valman == "0") then
if valman ~= "1" then
-- Not in manual configuration mode; show UCI -- Not in manual configuration mode; show UCI
--Basic Tab --Basic Tab
lsv = s1:taboption("basic", Flag, "localservice", translate("Local Service:"), lsv = s1:taboption("basic", Flag, "localservice",
translate("Local Service:"),
translate("Accept queries only from local subnets")) translate("Accept queries only from local subnets"))
lsv.rmempty = false lsv.rmempty = false
vld = s1:taboption("basic", Flag, "validator", translate("Enable DNSSEC:"), vld = s1:taboption("basic", Flag, "validator",
translate("Enable DNSSEC:"),
translate("Enable the DNSSEC validator module")) translate("Enable the DNSSEC validator module"))
vld.rmempty = false vld.rmempty = false
nvd = s1:taboption("basic", Flag, "validator_ntp", translate("DNSSEC NTP Fix:"), nvd = s1:taboption("basic", Flag, "validator_ntp",
translate("DNSSEC NTP Fix:"),
translate("Break the loop where DNSSEC needs NTP and NTP needs DNS")) translate("Break the loop where DNSSEC needs NTP and NTP needs DNS"))
nvd.rmempty = false nvd.rmempty = false
nvd:depends({ validator = true }) nvd:depends({ validator = true })
d64 = s1:taboption("basic", Flag, "dns64", translate("Enable DNS64:"), prt = s1:taboption("basic", Value, "listen_port",
translate("Enable the DNS64 module")) translate("Listening Port:"),
d64.rmempty = false
pfx = s1:taboption("basic", Value, "dns64_prefix", translate("DNS64 Prefix:"),
translate("Prefix for generated DNS64 addresses"))
pfx.datatype = "ip6addr"
pfx.placeholder = "64:ff9b::/96"
pfx.optional = true
pfx:depends({ dns64 = true })
prt = s1:taboption("basic", Value, "listen_port", translate("Listening Port:"),
translate("Choose Unbounds listening port")) translate("Choose Unbounds listening port"))
prt.datatype = "port" prt.datatype = "port"
prt.rmempty = false prt.rmempty = false
--Avanced Tab --Avanced Tab
din = s1:taboption("advanced", DynamicList, "domain_insecure", rlh = s1:taboption("advanced", Flag, "rebind_localhost",
translate("Domain Insecure:"), translate("Filter Localhost Rebind:"),
translate("List domains to bypass checks of DNSSEC"))
din:depends({ validator = true })
dfw = s1:taboption("advanced", DynamicList, "domain_forward",
translate("Domain Forward:"),
translate("List domains to simply forward to stub resolvers in /tmp/resolve.auto"))
rlh = s1:taboption("advanced", Flag, "rebind_localhost", translate("Filter Localhost Rebind:"),
translate("Protect against upstream response of 127.0.0.0/8")) translate("Protect against upstream response of 127.0.0.0/8"))
rlh.rmempty = false rlh.rmempty = false
rpv = s1:taboption("advanced", ListValue, "rebind_protection", translate("Filter Private Rebind:"), rpv = s1:taboption("advanced", ListValue, "rebind_protection",
translate("Filter Private Rebind:"),
translate("Protect against upstream responses within local subnets")) translate("Protect against upstream responses within local subnets"))
rpv:value("0", translate("No Filter")) rpv:value("0", translate("No Filter"))
rpv:value("1", translate("Filter RFC1918/4193")) rpv:value("1", translate("Filter RFC1918/4193"))
rpv:value("2", translate("Filter Entire Subnet")) rpv:value("2", translate("Filter Entire Subnet"))
rpv.rmempty = false rpv.rmempty = false
rpn = s1:taboption("advanced", Value, "rebind_interface", translate("Rebind Network Filter:"), d64 = s1:taboption("advanced", Flag, "dns64", translate("Enable DNS64:"),
translate("Network subnets to filter from upstream responses")) translate("Enable the DNS64 module"))
rpn.template = "cbi/network_netlist" d64.rmempty = false
rpn.widget = "checkbox"
rpn.rmempty = true pfx = s1:taboption("advanced", Value, "dns64_prefix",
rpn.cast = "string" translate("DNS64 Prefix:"),
rpn.nocreate = true translate("Prefix for generated DNS64 addresses"))
rpn:depends({ rebind_protection = 2 }) pfx.datatype = "ip6addr"
rpn:depends({ rebind_protection = 3 }) pfx.placeholder = "64:ff9b::/96"
pfx.optional = true
pfx:depends({ dns64 = true })
din = s1:taboption("advanced", DynamicList, "domain_insecure",
translate("Domain Insecure:"),
translate("List domains to bypass checks of DNSSEC"))
din:depends({ validator = true })
ag2 = s1:taboption("advanced", Value, "root_age",
translate("Root DSKEY Age:"),
translate("Limit days between RFC5011 copies to reduce flash writes"))
ag2.datatype = "and(uinteger,min(1),max(99))"
ag2:value("3", "3")
ag2:value("9", "9 ("..translate("default")..")")
ag2:value("12", "12")
ag2:value("24", "24")
ag2:value("99", "99 ("..translate("never")..")")
tgr = s1:taboption("advanced", Value, "trigger_interface",
translate("Trigger Networks:"),
translate("Networks that may trigger Unbound to reload (avoid wan6)"))
tgr.template = "cbi/network_netlist"
tgr.widget = "checkbox"
tgr.rmempty = true
tgr.cast = "string"
tgr.nocreate = true
--DHCP Tab --DHCP Tab
dlk = s1:taboption("DHCP", ListValue, "dhcp_link", translate("DHCP Link:"), dlk = s1:taboption("DHCP", ListValue, "dhcp_link",
translate("DHCP Link:"),
translate("Link to supported programs to load DHCP into DNS")) translate("Link to supported programs to load DHCP into DNS"))
dlk:value("none", translate("No Link")) dlk:value("none", translate("No Link"))
dlk:value("dnsmasq", "dnsmasq") dlk:value("dnsmasq", "dnsmasq")
dlk:value("odhcpd", "odhcpd") dlk:value("odhcpd", "odhcpd")
dlk.rmempty = false dlk.rmempty = false
dp6 = s1:taboption("DHCP", Flag, "dhcp4_slaac6", translate("DHCPv4 to SLAAC:"), dp6 = s1:taboption("DHCP", Flag, "dhcp4_slaac6",
translate("DHCPv4 to SLAAC:"),
translate("Use DHCPv4 MAC to discover IP6 hosts SLAAC (EUI64)")) translate("Use DHCPv4 MAC to discover IP6 hosts SLAAC (EUI64)"))
dp6.rmempty = false dp6.rmempty = false
dp6:depends({ dhcp_link = "odhcpd" }) dp6:depends({ dhcp_link = "odhcpd" })
dom = s1:taboption("DHCP", Value, "domain", translate("Local Domain:"), dom = s1:taboption("DHCP", Value, "domain",
translate("Local Domain:"),
translate("Domain suffix for this router and DHCP clients")) translate("Domain suffix for this router and DHCP clients"))
dom.placeholder = "lan" dom.placeholder = "lan"
dom:depends({ dhcp_link = "none" }) dom:depends({ dhcp_link = "none" })
dom:depends({ dhcp_link = "odhcpd" }) dom:depends({ dhcp_link = "odhcpd" })
dty = s1:taboption("DHCP", ListValue, "domain_type", translate("Local Domain Type:"), dty = s1:taboption("DHCP", ListValue, "domain_type",
translate("Local Domain Type:"),
translate("How to treat queries of this local domain")) translate("How to treat queries of this local domain"))
dty:value("deny", translate("Ignored")) dty:value("deny", translate("Denied (nxdomain)"))
dty:value("refuse", translate("Refused")) dty:value("refuse", translate("Refused"))
dty:value("static", translate("Only Local")) dty:value("static", translate("Static (local only)"))
dty:value("transparent", translate("Also Forwarded")) dty:value("transparent", translate("Transparent (local/global)"))
dty:depends({ dhcp_link = "none" }) dty:depends({ dhcp_link = "none" })
dty:depends({ dhcp_link = "odhcpd" }) dty:depends({ dhcp_link = "odhcpd" })
lfq = s1:taboption("DHCP", ListValue, "add_local_fqdn", translate("LAN DNS:"), lfq = s1:taboption("DHCP", ListValue, "add_local_fqdn",
translate("LAN DNS:"),
translate("How to enter the LAN or local network router in DNS")) translate("How to enter the LAN or local network router in DNS"))
lfq:value("0", translate("No Entry")) lfq:value("0", translate("No Entry"))
lfq:value("1", translate("Hostname, Primary Address")) lfq:value("1", translate("Hostname, Primary Address"))
@ -174,7 +167,8 @@ if valman ~= "1" then
lfq:depends({ dhcp_link = "none" }) lfq:depends({ dhcp_link = "none" })
lfq:depends({ dhcp_link = "odhcpd" }) lfq:depends({ dhcp_link = "odhcpd" })
wfq = s1:taboption("DHCP", ListValue, "add_wan_fqdn", translate("WAN DNS:"), wfq = s1:taboption("DHCP", ListValue, "add_wan_fqdn",
translate("WAN DNS:"),
translate("Override the WAN side router entry in DNS")) translate("Override the WAN side router entry in DNS"))
wfq:value("0", translate("Use Upstream")) wfq:value("0", translate("Use Upstream"))
wfq:value("1", translate("Hostname, Primary Address")) wfq:value("1", translate("Hostname, Primary Address"))
@ -184,19 +178,21 @@ if valman ~= "1" then
wfq:depends({ dhcp_link = "none" }) wfq:depends({ dhcp_link = "none" })
wfq:depends({ dhcp_link = "odhcpd" }) wfq:depends({ dhcp_link = "odhcpd" })
exa = s1:taboption("DHCP", ListValue, "add_extra_dns", translate("Extra DNS:"), exa = s1:taboption("DHCP", ListValue, "add_extra_dns",
translate("Extra DNS:"),
translate("Use extra DNS entries found in /etc/config/dhcp")) translate("Use extra DNS entries found in /etc/config/dhcp"))
exa:value("0", translate("Ignore")) exa:value("0", translate("Ignore"))
exa:value("1", translate("Include Network/Hostnames")) exa:value("1", translate("Host Records"))
exa:value("2", translate("Advanced MX/SRV RR")) exa:value("2", translate("Host/MX/SRV RR"))
exa:value("3", translate("Advanced CNAME RR")) exa:value("3", translate("Host/MX/SRV/CNAME RR"))
exa:depends({ dhcp_link = "none" }) exa:depends({ dhcp_link = "none" })
exa:depends({ dhcp_link = "odhcpd" }) exa:depends({ dhcp_link = "odhcpd" })
--TODO: dnsmasq needs to not reference resolve-file and get off port 53. --TODO: dnsmasq needs to not reference resolve-file and get off port 53.
--Resource Tuning Tab --Resource Tuning Tab
ctl = s1:taboption("resource", ListValue, "unbound_control", translate("Unbound Control App:"), ctl = s1:taboption("resource", ListValue, "unbound_control",
translate("Unbound Control App:"),
translate("Enable access for unbound-control")) translate("Enable access for unbound-control"))
ctl.rmempty = false ctl.rmempty = false
ctl:value("0", translate("No Remote Control")) ctl:value("0", translate("No Remote Control"))
@ -205,7 +201,8 @@ if valman ~= "1" then
ctl:value("3", translate("Local Subnet, Encrypted")) ctl:value("3", translate("Local Subnet, Encrypted"))
ctl:value("4", translate("Local Subnet, Static Encryption")) ctl:value("4", translate("Local Subnet, Static Encryption"))
pro = s1:taboption("resource", ListValue, "protocol", translate("Recursion Protocol:"), pro = s1:taboption("resource", ListValue, "protocol",
translate("Recursion Protocol:"),
translate("Chose the protocol recursion queries leave on")) translate("Chose the protocol recursion queries leave on"))
pro:value("default", translate("Default")) pro:value("default", translate("Default"))
pro:value("ip4_only", translate("IP4 Only")) pro:value("ip4_only", translate("IP4 Only"))
@ -214,7 +211,8 @@ if valman ~= "1" then
pro:value("mixed", translate("IP4 and IP6")) pro:value("mixed", translate("IP4 and IP6"))
pro.rmempty = false pro.rmempty = false
rsc = s1:taboption("resource", ListValue, "resource", translate("Memory Resource:"), rsc = s1:taboption("resource", ListValue, "resource",
translate("Memory Resource:"),
translate("Use menu System/Processes to observe any memory growth")) translate("Use menu System/Processes to observe any memory growth"))
rsc:value("default", translate("Default")) rsc:value("default", translate("Default"))
rsc:value("tiny", translate("Tiny")) rsc:value("tiny", translate("Tiny"))
@ -223,46 +221,47 @@ if valman ~= "1" then
rsc:value("large", translate("Large")) rsc:value("large", translate("Large"))
rsc.rmempty = false rsc.rmempty = false
rsn = s1:taboption("resource", ListValue, "recursion", translate("Recursion Strength:"), rsn = s1:taboption("resource", ListValue, "recursion",
translate("Recursion Strength:"),
translate("Recursion activity affects memory growth and CPU load")) translate("Recursion activity affects memory growth and CPU load"))
rsn:value("default", translate("Default")) rsn:value("default", translate("Default"))
rsn:value("passive", translate("Passive")) rsn:value("passive", translate("Passive"))
rsn:value("aggressive", translate("Aggressive")) rsn:value("aggressive", translate("Aggressive"))
rsn.rmempty = false rsn.rmempty = false
qry = s1:taboption("resource", Flag, "query_minimize", translate("Query Minimize:"), qry = s1:taboption("resource", Flag, "query_minimize",
translate("Query Minimize:"),
translate("Break down query components for limited added privacy")) translate("Break down query components for limited added privacy"))
qry.rmempty = false qry.rmempty = false
qry:depends({ recursion = "passive" }) qry:depends({ recursion = "passive" })
qry:depends({ recursion = "aggressive" }) qry:depends({ recursion = "aggressive" })
qrs = s1:taboption("resource", Flag, "query_min_strict", translate("Strict Minimize:"), qrs = s1:taboption("resource", Flag, "query_min_strict",
translate("Strict Minimize:"),
translate("Strict version of 'query minimize' but it can break DNS")) translate("Strict version of 'query minimize' but it can break DNS"))
qrs.rmempty = false qrs.rmempty = false
qrs:depends({ query_minimize = true }) qrs:depends({ query_minimize = true })
ath = s1:taboption("resource", Flag, "prefetch_root", translate("Prefetch Root:"), eds = s1:taboption("resource", Value, "edns_size",
translate("Obtain complete root zone files and install in auth-zone: clause")) translate("EDNS Size:"),
ath.rmempty = false
eds = s1:taboption("resource", Value, "edns_size", translate("EDNS Size:"),
translate("Limit extended DNS packet size")) translate("Limit extended DNS packet size"))
eds.datatype = "and(uinteger,min(512),max(4096))" eds.datatype = "and(uinteger,min(512),max(4096))"
eds.rmempty = false eds.rmempty = false
tlm = s1:taboption("resource", Value, "ttl_min", translate("TTL Minimum:"), tlm = s1:taboption("resource", Value, "ttl_min",
translate("TTL Minimum:"),
translate("Prevent excessively short cache periods")) translate("Prevent excessively short cache periods"))
tlm.datatype = "and(uinteger,min(0),max(600))" tlm.datatype = "and(uinteger,min(0),max(600))"
tlm.rmempty = false tlm.rmempty = false
stt = s1:taboption("resource", Flag, "extended_stats", translate("Extended Statistics:"), stt = s1:taboption("resource", Flag, "extended_stats",
translate("Extended Statistics:"),
translate("Extended statistics are printed from unbound-control")) translate("Extended statistics are printed from unbound-control"))
stt.rmempty = false stt.rmempty = false
end
else
--Trigger Tab, always unconditional ag2 = s1:taboption("basic", Value, "root_age",
ag2 = s1:taboption("trigger", Value, "root_age", translate("Root DSKEY Age:"), translate("Root DSKEY Age:"),
translate("Limit days between RFC5011 copies to reduce flash writes")) translate("Limit days between RFC5011 copies to reduce flash writes"))
ag2.datatype = "and(uinteger,min(1),max(99))" ag2.datatype = "and(uinteger,min(1),max(99))"
ag2:value("3", "3") ag2:value("3", "3")
@ -271,45 +270,51 @@ ag2:value("12", "12")
ag2:value("24", "24") ag2:value("24", "24")
ag2:value("99", "99 ("..translate("never")..")") ag2:value("99", "99 ("..translate("never")..")")
tgr = s1:taboption("trigger", Value, "trigger_interface", translate("Trigger Networks:"), tgr = s1:taboption("basic", Value, "trigger_interface",
translate("Trigger Networks:"),
translate("Networks that may trigger Unbound to reload (avoid wan6)")) translate("Networks that may trigger Unbound to reload (avoid wan6)"))
tgr.template = "cbi/network_netlist" tgr.template = "cbi/network_netlist"
tgr.widget = "checkbox" tgr.widget = "checkbox"
tgr.rmempty = true tgr.rmempty = true
tgr.cast = "string" tgr.cast = "string"
tgr.nocreate = true tgr.nocreate = true
end
function ena.cfgvalue(self, section) function ena.cfgvalue(self, section)
return luci.sys.init.enabled("unbound") and self.enabled or self.disabled return sy.init.enabled("unbound") and self.enabled or self.disabled
end end
function ena.write(self, section, value) function ena.write(self, section, value)
if value == "1" then if (value == "1") then
luci.sys.init.enable("unbound") sy.init.enable("unbound")
luci.sys.call("/etc/init.d/unbound start >/dev/null") sy.call("/etc/init.d/unbound start >/dev/null 2>&1")
else else
luci.sys.call("/etc/init.d/unbound stop >/dev/null") sy.call("/etc/init.d/unbound stop >/dev/null 2>&1")
luci.sys.init.disable("unbound") sy.init.disable("unbound")
end end
return Flag.write(self, section, value) return Flag.write(self, section, value)
end end
function m1.on_apply(self) function m1.on_commit(self)
function ena.validate(self, value) if sy.init.enabled("unbound") then
if value ~= "0" then -- Restart Unbound with configuration
luci.sys.call("/etc/init.d/unbound restart >/dev/null 2>&1") sy.call("/etc/init.d/unbound restart >/dev/null 2>&1")
else else
luci.sys.call("/etc/init.d/unbound stop >/dev/null 2>&1") sy.call("/etc/init.d/unbound stop >/dev/null 2>&1")
end end
end end
-- Restart Unbound with configuration and reload the page (some options hide) function m1.on_apply(self)
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "unbound")) -- reload the page because some options hide
ht.redirect(ds.build_url("admin", "services", "unbound", "configure"))
end end

View file

@ -1,28 +1,31 @@
-- Copyright 2016 Eric Luehrsen <ericluehrsen@hotmail.com> -- Copyright 2016 Eric Luehrsen <ericluehrsen@gmail.com>
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
local m4, s4, frm local m4, s4, frm
local filename = "/etc/unbound/unbound_ext.conf" local filename = "/etc/unbound/unbound_ext.conf"
local description = translatef("Here you may edit 'forward:' and 'remote-control:' in an extended 'include:'") local fs = require "nixio.fs"
description = description .. " (" .. filename .. ")" local ut = require "luci.util"
m4 = SimpleForm("editing", nil) m4 = SimpleForm("editing", nil)
m4:append(Template("unbound/css-editing")) m4:append(Template("unbound/css-editing"))
m4.submit = translate("Save") m4.submit = translate("Save")
m4.reset = false m4.reset = false
s4 = m4:section(SimpleSection, "Unbound Extended Conf", description) s4 = m4:section(SimpleSection, "",
translatef(
"Edit clauses such as 'forward-zone:' for 'include: " .. filename .. "'"))
frm = s4:option(TextValue, "data") frm = s4:option(TextValue, "data")
frm.datatype = "string" frm.datatype = "string"
frm.rows = 20 frm.rows = 20
function frm.cfgvalue() function frm.cfgvalue()
return nixio.fs.readfile(filename) or "" return fs.readfile(filename) or ""
end end
function frm.write(self, section, data) function frm.write(self, section, data)
return nixio.fs.writefile(filename, luci.util.trim(data:gsub("\r\n", "\n"))) return fs.writefile(filename, ut.trim(data:gsub("\r\n", "\n")))
end end

View file

@ -1,28 +1,31 @@
-- Copyright 2016 Eric Luehrsen <ericluehrsen@hotmail.com> -- Copyright 2016 Eric Luehrsen <ericluehrsen@gmail.com>
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
local m2, s2, frm local m2, s2, frm
local filename = "/etc/unbound/unbound.conf" local filename = "/etc/unbound/unbound.conf"
local description = translatef("Here you may edit raw 'unbound.conf' when you don't use UCI:") local fs = require "nixio.fs"
description = description .. " (" .. filename .. ")" local ut = require "luci.util"
m2 = SimpleForm("editing", nil) m2 = SimpleForm("editing", nil)
m2:append(Template("unbound/css-editing")) m2:append(Template("unbound/css-editing"))
m2.submit = translate("Save") m2.submit = translate("Save")
m2.reset = false m2.reset = false
s2 = m2:section(SimpleSection, "Unbound Conf", description) s2 = m2:section(SimpleSection, "",
translatef(
"Edit '" .. filename .. "' when you do not use UCI."))
frm = s2:option(TextValue, "data") frm = s2:option(TextValue, "data")
frm.datatype = "string" frm.datatype = "string"
frm.rows = 20 frm.rows = 20
function frm.cfgvalue() function frm.cfgvalue()
return nixio.fs.readfile(filename) or "" return fs.readfile(filename) or ""
end end
function frm.write(self, section, data) function frm.write(self, section, data)
return nixio.fs.writefile(filename, luci.util.trim(data:gsub("\r\n", "\n"))) return fs.writefile(filename, ut.trim(data:gsub("\r\n", "\n")))
end end

View file

@ -1,28 +1,31 @@
-- Copyright 2016 Eric Luehrsen <ericluehrsen@hotmail.com> -- Copyright 2016 Eric Luehrsen <ericluehrsen@gmail.com>
-- Licensed to the public under the Apache License 2.0. -- Licensed to the public under the Apache License 2.0.
local m3, s3, frm local m3, s3, frm
local filename = "/etc/unbound/unbound_srv.conf" local filename = "/etc/unbound/unbound_srv.conf"
local description = translatef("Here you may edit the 'server:' clause in an internal 'include:'") local fs = require "nixio.fs"
description = description .. " (" .. filename .. ")" local ut = require "luci.util"
m3 = SimpleForm("editing", nil) m3 = SimpleForm("editing", nil)
m3:append(Template("unbound/css-editing")) m3:append(Template("unbound/css-editing"))
m3.submit = translate("Save") m3.submit = translate("Save")
m3.reset = false m3.reset = false
s3 = m3:section(SimpleSection, "Unbound Server Conf", description) s3 = m3:section(SimpleSection, "",
translatef(
"Edit 'server:' clause options for 'include: " .. filename .. "'"))
frm = s3:option(TextValue, "data") frm = s3:option(TextValue, "data")
frm.datatype = "string" frm.datatype = "string"
frm.rows = 20 frm.rows = 20
function frm.cfgvalue() function frm.cfgvalue()
return nixio.fs.readfile(filename) or "" return fs.readfile(filename) or ""
end end
function frm.write(self, section, data) function frm.write(self, section, data)
return nixio.fs.writefile(filename, luci.util.trim(data:gsub("\r\n", "\n"))) return fs.writefile(filename, ut.trim(data:gsub("\r\n", "\n")))
end end

View file

@ -0,0 +1,37 @@
-- Copyright 2016 Eric Luehrsen <ericluehrsen@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local m6, s6, frm
local filename = "/etc/config/unbound"
local fs = require "nixio.fs"
local ut = require "luci.util"
m6 = SimpleForm("editing", nil)
m6:append(Template("unbound/css-editing"))
m6.submit = translate("Save")
m6.reset = false
s6 = m6:section(SimpleSection, "",
translatef("Edit '" .. filename .. "' "
.. "and help can be found in OpenWrt "
.. "<a href=\"%s\" target=\"_blank\">Guides</a> "
.. "and <a href=\"%s\" target=\"_blank\">Github</a>.",
"https://openwrt.org/docs/guide-user/services/dns/unbound",
"https://github.com/openwrt/packages/blob/master/net/unbound/files/README.md"))
frm = s6:option(TextValue, "data")
frm.datatype = "string"
frm.rows = 20
function frm.cfgvalue()
return fs.readfile(filename) or ""
end
function frm.write(self, section, data)
return fs.writefile(filename, ut.trim(data:gsub("\r\n", "\n")))
end
return m6

View file

@ -0,0 +1,207 @@
-- Copyright 2017 Eric Luehrsen <ericluehrsen@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local m5, s5
local ztype, zones, servers, fallback, enabled
local fs = require "nixio.fs"
local ut = require "luci.util"
local sy = require "luci.sys"
local resolvfile = "/tmp/resolv.conf.auto"
m5 = Map("unbound")
s5 = m5:section(TypedSection, "zone", "Zones",
translatef("This shows extended zones and more details can be "
.. "changed in Files tab and <a href=\"%s\">Edit:UCI</a> subtab.",
"/cgi-bin/luci/admin/services/unbound/files" ))
s5.addremove = false
s5.anonymous = true
s5.sortable = true
s5.template = "cbi/tblsection"
ztype = s5:option(DummyValue, "DummyType", translate("Type"))
ztype.rawhtml = true
zones = s5:option(DummyValue, "DummyZones", translate("Zones"))
zones.rawhtml = true
servers = s5:option(DummyValue, "DummyServers", translate("Servers"))
servers.rawhtml = true
fallback = s5:option(Flag, "fallback", translate("Fallback"))
fallback.rmempty = false
enabled = s5:option(Flag, "enabled", translate("Enable"))
enabled.rmempty = false
function ztype.cfgvalue(self, s)
-- Format a meaninful tile for the Zone Type column
local itxt = self.map:get(s, "zone_type")
local itls = self.map:get(s, "tls_upstream")
if itxt and itxt:match("forward") then
if itls and (itls == "1") then
return translate("Forward TLS")
else
return translate("Forward")
end
elseif itxt and itxt:match("stub") then
return translate("Recurse")
elseif itxt and itxt:match("auth") then
return translate("AXFR")
else
return translate("Error")
end
end
function zones.cfgvalue(self, s)
-- Format a meaninful sentence for the Zones viewed column
local xtxt, otxt
local itxt = self.map:get(s, "zone_name")
local itype = self.map:get(s, "zone_type")
for xtxt in ut.imatch(itxt) do
if (xtxt == ".") then
-- zone_name lists
xtxt = translate("(root)")
end
if otxt and (#otxt > 0) then
otxt = otxt .. ", <var>%s</var>" % xtxt
else
otxt = "<var>%s</var>" % xtxt
end
end
if itype and itype:match("forward") then
-- from zone_type create a readable hint for the action
otxt = translate("accept upstream results for ") .. otxt
elseif itype and itype:match("stub") then
otxt = translate("select recursion for ") .. otxt
elseif itype and itype:match("auth") then
otxt = translate("prefetch zone files for ") .. otxt
else
otxt = translate("unknown action for ") .. otxt
end
if otxt and (#otxt > 0) then
return otxt
else
return "(empty)"
end
end
function servers.cfgvalue(self, s)
-- Format a meaninful sentence for the Servers (and URL) column
local xtxt, otxt, rtxt, found
local itxt = self.map:get(s, "server")
local iurl = self.map:get(s, "url_dir")
local itype = self.map:get(s, "zone_type")
local itls = self.map:get(s, "tls_upstream")
local iidx = self.map:get(s, "tls_index")
local irslv = self.map:get(s, "resolv_conf")
for xtxt in ut.imatch(itxt) do
if otxt and (#otxt > 0) then
-- bundle and make pretty the server list
otxt = otxt .. ", <var>%s</var>" % xtxt
else
otxt = "<var>%s</var>" % xtxt
end
end
if otxt and (#otxt > 0)
and itls and (itls == "1")
and iidx and (#iidx > 0) then
-- show TLS certificate name index if provided
otxt = translatef("use nameservers by <var>%s</var> at ", iidx) .. otxt
elseif otxt and (#otxt > 0) then
otxt = translate("use nameservers ") .. otxt
end
if iurl and (#iurl > 0) and itype and itype:match("auth") then
if otxt and (#otxt > 0) then
-- include optional URL filed for auth-zone: type
otxt = otxt .. translatef(", and try <var>%s</var>", iurl)
else
otxt = translatef("download from <var>%s</var>", iurl)
end
end
if irslv and (irslv == "1") and itype and itype:match("forward") then
for xtxt in ut.imatch(fs.readfile(resolvfile)) do
if xtxt:match("nameserver") then
found = true
elseif (found == true) then
if rtxt and (#rtxt > 0) then
-- fetch name servers from resolv.conf
rtxt = rtxt .. ", <var>%s</var>" % xtxt
else
rtxt = "<var>%s</var>" % xtxt
end
found = false
end
end
if otxt and (#otxt > 0) and rtxt and (#rtxt > 0) then
otxt = otxt
.. translatef(", and <var>%s</var> entries ", resolvfile) .. rtxt
elseif rtxt and (#rtxt > 0) then
otxt = translatef("use <var>%s</var> nameservers ", resolvfile) .. rtxt
end
end
if otxt and (#otxt > 0) then
return otxt
else
return "(empty)"
end
end
function m5.on_commit(self)
if sy.init.enabled("unbound") then
-- Restart Unbound with configuration
sy.call("/etc/init.d/unbound restart >/dev/null 2>&1")
else
sy.call("/etc/init.d/unbound stop >/dev/null 2>&1")
end
end
return m5