diff --git a/src/etc/inc/plugins.inc.d/unbound.inc b/src/etc/inc/plugins.inc.d/unbound.inc index f74ba58e78b..8e968478ae6 100644 --- a/src/etc/inc/plugins.inc.d/unbound.inc +++ b/src/etc/inc/plugins.inc.d/unbound.inc @@ -30,6 +30,19 @@ * POSSIBILITY OF SUCH DAMAGE. */ +require_once('certs.inc'); + +function export_pem_file($filename, $data, $post_append = null) +{ + $pem_content = trim(str_replace("\n\n", "\n", str_replace( + "\r", + "", + base64_decode((string)$data) + ) . ($post_append == null ? '' : "\n" . $post_append))); + file_put_contents($filename, $pem_content); + chmod($filename, 0640); +} + function unbound_enabled() { $mdl = new \OPNsense\Unbound\Unbound(); @@ -176,6 +189,10 @@ function unbound_generate_config() } } + $port = $general['port']; + $port_doh = $general['port_doh']; + $port_dot = $general['port_dot']; + $bindints = ''; if (!empty($general['active_interface'])) { $active_interfaces = explode(',', $general['active_interface']); @@ -196,12 +213,25 @@ function unbound_generate_config() } foreach ($addresses as $address) { - $bindints .= "interface: $address\n"; + $bindints .= "interface: $address@$port\n"; + if (!empty($general['enable_dot'])) { + $bindints .= "interface: $address@$port_dot\n"; + } + if (!empty($general['enable_doh'])) { + $bindints .= "interface: $address@$port_doh\n"; + } } } else { - $bindints .= "interface: 0.0.0.0\n"; - $bindints .= "interface: ::\n"; - $bindints .= "interface-automatic: yes\n"; + $bindints .= "interface: 0.0.0.0@$port\n"; + $bindints .= "interface: ::@$port\n"; + if (!empty($general['enable_dot'])) { + $bindints .= "interface: 0.0.0.0@$port_dot\n"; + $bindints .= "interface: ::@$port_dot\n"; + } + if (!empty($general['enable_doh'])) { + $bindints .= "interface: 0.0.0.0@$port_doh\n"; + $bindints .= "interface: ::@$port_doh\n"; + } } $outgoingints = ''; @@ -223,7 +253,7 @@ function unbound_generate_config() unbound_add_host_entries($ifconfig_details); - $port = $general['port']; + /* do not touch prefer-ip6 as it is defaulting to 'no' anyway */ $do_ip6 = isset($config['system']['ipv6allow']) ? 'yes' : 'no'; @@ -270,6 +300,38 @@ EOD; $so_reuseport = empty(system_sysctl_get()['net.inet.rss.enabled']) ? 'yes' : 'no'; + $dohdot_settings = ''; + if (!empty($general['enable_doh']) || !empty($general['enable_dot'])) { + $cert =& lookup_cert($general['dohdot_cert']); + $chain = []; + $ca_chain = ca_chain_array($cert); + if (is_array($ca_chain)) { + foreach ($ca_chain as $entry) { + $chain[] = base64_decode($entry['crt']); + } + } + if (isset($cert)) { + export_pem_file( + '/var/unbound/dohdot.pem', + $cert['crt'], + implode("\n", $chain) + ); + export_pem_file( + '/var/unbound/dohdot.key', + $cert['prv'] + ); + $dohdot_settings .= "# DoH and DoT\n"; + if (!empty($general['enable_dot'])) { + $dohdot_settings .= "tls-port: $port_dot\n"; + } + if (!empty($general['enable_doh'])) { + $dohdot_settings .= "https-port: $port_doh\n"; + } + $dohdot_settings .= "tls-service-key: /var/unbound/dohdot.key\n"; + $dohdot_settings .= "tls-service-pem: /var/unbound/dohdot.pem\n"; + } + } + $unboundconf = <<text The TCP/UDP port used for responding to DNS queries. + + unbound.general.enable_doh + + checkbox + Only enable this on port 443 when the web-interface is not listening on the same port! + + + unbound.general.port_doh + + text + The TCP/UDP port used for responding to DoH queries. + + + unbound.general.enable_dot + + checkbox + + + unbound.general.port_dot + + text + The TCP/UDP port used for responding to DoT queries. + + + unbound.general.dohdot_cert + + dropdown + DoH and DoT Certificate to use + unbound.general.active_interface diff --git a/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml b/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml index 5e9405aff85..9949899627d 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Unbound/Unbound.xml @@ -12,6 +12,26 @@ 53 Y + + 0 + Y + + + 443 + Y + + + 0 + Y + + + 853 + Y + + + N + Please select a valid certificate from the list + Y diff --git a/src/opnsense/mvc/app/views/OPNsense/Unbound/stats.volt b/src/opnsense/mvc/app/views/OPNsense/Unbound/stats.volt index 2f45b1f4136..bee4360a7a8 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Unbound/stats.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Unbound/stats.volt @@ -62,6 +62,26 @@ 'elapsed': "{{ lang._('Elapsed') }}" }; + const descriptionNumQuery = { + 'query': { + 'type': { + 'A': "{{ lang._('A') }}", + 'AAAA': "{{ lang._('AAAA') }}", + 'CNAME': "{{ lang._('CNAME') }}", + 'SOA': "{{ lang._('SOA') }}", + 'PTR': "{{ lang._('PTR') }}", + 'TXT': "{{ lang._('TXT') }}", + 'SRV': "{{ lang._('SRV') }}", + 'NAPTR': "{{ lang._('NAPTR') }}", + }, + 'tls': { + '__value__': "{{ lang._('DoT') }}" + }, + 'https': "{{ lang._('DoH') }}", + 'ipv6': "{{ lang._('IPv6') }}" + } + }; + function writeDescs(parent, data, descriptions) { $.each(descriptions, function(descKey, descValue) { if (typeof descValue !== 'object') { @@ -136,6 +156,19 @@ writeDescs(tbody, value, descriptionMapTime); table.append(tbody); statsView.append(table); + } else if (key === "num") { + let title = document.createElement("h2"); + title.innerHTML = "Query Types"; + statsView.append(title); + + let table = document.createElement('table'); + table.classList.add('table'); + table.classList.add('table-striped'); + table.style.width = 'auto'; + let tbody = document.createElement('tbody'); + writeDescs(tbody, value, descriptionNumQuery); + table.append(tbody); + statsView.append(table); } }); } @@ -155,7 +188,7 @@ // initial fetch updateStats(); - updateServiceControlUI('unbound'); + updateServiceControlUI('unbound'); });