Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 67 additions & 5 deletions src/etc/inc/plugins.inc.d/unbound.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
global $config;
Expand Down Expand Up @@ -181,6 +194,10 @@ private-address: fe80::/10 # Link-local address (LLA)
EOF;
}

$port = is_port($config['unbound']['port'] ?? null) ? $config['unbound']['port'] : '53';
$port_doh = is_port($config['unbound']['port_doh'] ?? null) ? $config['unbound']['port_doh'] : "443";
$port_dot = is_port($config['unbound']['port_dot'] ?? null) ? $config['unbound']['port_dot'] : "853";

$bindints = '';
if (!empty($config['unbound']['active_interface'])) {
$active_interfaces = explode(',', $config['unbound']['active_interface']);
Expand All @@ -201,12 +218,25 @@ EOF;
}

foreach ($addresses as $address) {
$bindints .= "interface: $address\n";
$bindints .= "interface: $address@$port\n";
if (isset($config['unbound']['enable_dot'])) {
$bindints .= "interface: $address@$port_dot\n";
}
if (isset($config['unbound']['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 (isset($config['unbound']['enable_dot'])) {
$bindints .= "interface: 0.0.0.0@$port_dot\n";
$bindints .= "interface: ::@$port_dot\n";
}
if (isset($config['unbound']['enable_doh'])) {
$bindints .= "interface: 0.0.0.0@$port_doh\n";
$bindints .= "interface: ::@$port_doh\n";
}
}

$outgoingints = '';
Expand All @@ -229,7 +259,6 @@ EOF;
unbound_add_host_entries($ifconfig_details);
unbound_acls_config();

$port = is_port($config['unbound']['port'] ?? null) ? $config['unbound']['port'] : '53';

/* do not touch prefer-ip6 as it is defaulting to 'no' anyway */
$do_ip6 = isset($config['system']['ipv6allow']) ? 'yes' : 'no';
Expand Down Expand Up @@ -276,6 +305,38 @@ EOD;

$so_reuseport = empty(system_sysctl_get()['net.inet.rss.enabled']) ? 'yes' : 'no';

$dohdot_settings = '';
if (isset($config['unbound']['enable_doh']) || isset($config['unbound']['enable_dot'])) {
$cert =& lookup_cert($config['unbound']['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 (isset($config['unbound']['enable_dot'])) {
$dohdot_settings .= "tls-port: $port_dot\n";
}
if (isset($config['unbound']['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 = <<<EOD
##########################
# Unbound Configuration
Expand Down Expand Up @@ -309,6 +370,7 @@ module-config: "{$module_config}"
{$anchor_file}
{$forward_local}
{$dns64_config}
{$dohdot_settings}

# Interface IP(s) to bind to
{$bindints}
Expand Down
35 changes: 34 additions & 1 deletion src/opnsense/mvc/app/views/OPNsense/Unbound/stats.volt
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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);
}
});
}
Expand All @@ -155,7 +188,7 @@
// initial fetch
updateStats();

updateServiceControlUI('unbound');
updateServiceControlUI('unbound');
});
</script>

Expand Down
89 changes: 89 additions & 0 deletions src/www/services_unbound.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
$pconfig = array();
// boolean values
$pconfig['enable'] = isset($a_unboundcfg['enable']);
$pconfig['enable_dot'] = isset($a_unboundcfg['enable_dot']);
$pconfig['enable_doh'] = isset($a_unboundcfg['enable_doh']);
$pconfig['enable_wpad'] = isset($a_unboundcfg['enable_wpad']);
$pconfig['dnssec'] = isset($a_unboundcfg['dnssec']);
$pconfig['dns64'] = isset($a_unboundcfg['dns64']);
Expand All @@ -52,12 +54,16 @@
$pconfig['noregrecords'] = isset($a_unboundcfg['noregrecords']);
// text values
$pconfig['port'] = !empty($a_unboundcfg['port']) ? $a_unboundcfg['port'] : null;
$pconfig['port_dot'] = !empty($a_unboundcfg['port_dot']) ? $a_unboundcfg['port_dot'] : null;
$pconfig['port_doh'] = !empty($a_unboundcfg['port_doh']) ? $a_unboundcfg['port_doh'] : null;
$pconfig['regdhcpdomain'] = !empty($a_unboundcfg['regdhcpdomain']) ? $a_unboundcfg['regdhcpdomain'] : null;
$pconfig['dns64prefix'] = !empty($a_unboundcfg['dns64prefix']) ? $a_unboundcfg['dns64prefix'] : null;
// array types
$pconfig['active_interface'] = !empty($a_unboundcfg['active_interface']) ? explode(",", $a_unboundcfg['active_interface']) : array();
$pconfig['outgoing_interface'] = !empty($a_unboundcfg['outgoing_interface']) ? explode(",", $a_unboundcfg['outgoing_interface']) : array();
$pconfig['local_zone_type'] = !empty($a_unboundcfg['local_zone_type']) ? $a_unboundcfg['local_zone_type'] : null;
// dropdown
$pconfig['dohdot_cert'] = $a_unboundcfg['dohdot_cert'];
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input_errors = array();
$pconfig = $_POST;
Expand Down Expand Up @@ -85,17 +91,46 @@
if (!empty($pconfig['port']) && !is_port($pconfig['port'])) {
$input_errors[] = gettext("You must specify a valid port number.");
}
if (!empty($pconfig['port_dot']) && !is_port($pconfig['port_dot'])) {
$input_errors[] = gettext("You must specify a valid DoT port number.");
}
if (!empty($pconfig['port_doh']) && !is_port($pconfig['port_doh'])) {
$input_errors[] = gettext("You must specify a valid DoH port number.");
}
if (!empty($pconfig['local_zone_type']) && !array_key_exists($pconfig['local_zone_type'], unbound_local_zone_types())) {
$input_errors[] = sprintf(gettext('Local zone type "%s" is not known.'), $pconfig['local_zone_type']);
}

if (!empty($pconfig['dohdot_cert'])) {
foreach ($config['cert'] as $cert) {
if ($cert['refid'] == $pconfig['dohdot_cert']) {
if (cert_get_purpose($cert['crt'])['server'] == 'No') {
$input_errors[] = gettext(
sprintf('Certificate %s is not intended for server use.', $cert['descr'])
);
break;
}
}
}
}

if (count($input_errors) == 0) {
// text types
if (!empty($pconfig['port'])) {
$a_unboundcfg['port'] = $pconfig['port'];
} elseif (isset($a_unboundcfg['port'])) {
unset($a_unboundcfg['port']);
}
if (!empty($pconfig['port_dot'])) {
$a_unboundcfg['port_dot'] = $pconfig['port_dot'];
} elseif (isset($a_unboundcfg['port_dot'])) {
unset($a_unboundcfg['port_dot']);
}
if (!empty($pconfig['port_doh'])) {
$a_unboundcfg['port_doh'] = $pconfig['port_doh'];
} elseif (isset($a_unboundcfg['port_doh'])) {
unset($a_unboundcfg['port_doh']);
}
if (!empty($pconfig['regdhcpdomain'])) {
$a_unboundcfg['regdhcpdomain'] = $pconfig['regdhcpdomain'];
} elseif (isset($a_unboundcfg['regdhcpdomain'])) {
Expand All @@ -119,6 +154,8 @@
$a_unboundcfg['noarecords'] = !empty($pconfig['noarecords']);
$a_unboundcfg['dnssec'] = !empty($pconfig['dnssec']);
$a_unboundcfg['enable'] = !empty($pconfig['enable']);
$a_unboundcfg['enable_dot'] = !empty($pconfig['enable_dot']);
$a_unboundcfg['enable_doh'] = !empty($pconfig['enable_doh']);
$a_unboundcfg['enable_wpad'] = !empty($pconfig['enable_wpad']);
$a_unboundcfg['noreglladdr6'] = empty($pconfig['reglladdr6']);
$a_unboundcfg['regdhcp'] = !empty($pconfig['regdhcp']);
Expand All @@ -136,6 +173,8 @@
} elseif (isset($a_unboundcfg['outgoing_interface'])) {
unset($a_unboundcfg['outgoing_interface']);
}
// dropdown
$a_unboundcfg['dohdot_cert'] = $pconfig['dohdot_cert'];

write_config('Unbound general configuration changed.');
mark_subsystem_dirty('unbound');
Expand All @@ -145,6 +184,7 @@
}
}

$a_cert = isset($config['cert']) ? $config['cert'] : array();
$interfaces = get_configured_interface_with_descr();

foreach (array('server', 'client') as $mode) {
Expand Down Expand Up @@ -216,6 +256,55 @@
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("DoH");?></td>
<td>
<input name="enable_doh" type="checkbox" value="yes" <?=!empty($pconfig['enable_doh']) ? 'checked="checked"' : '';?> />
<?= gettext('Enable DoH') ?>
</td>
</tr>
<tr>
<td><a id="help_for_port_doh" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Listen Port for DoH");?></td>
<td>
<input name="port_doh" type="text" id="port_doh" placeholder="443" size="6" value="<?=$pconfig['port_doh'];?>" />
<div class="hidden" data-for="help_for_port_doh">
<?=gettext("The port used for responding to DoH queries. It should normally be left blank unless another service needs to bind to TCP/UDP port 443. The OPNsense webinterface or another http server (like nginx) may be already bound to the default https port on one of the selected network interfaces! You'll need to allow access to this port in the firewall configuration.");?>
</div>
</td>
</tr>
<tr>
<td><i class="fa fa-info-circle text-muted"></i> <?=gettext("DoT");?></td>
<td>
<input name="enable_dot" type="checkbox" value="yes" <?=!empty($pconfig['enable_dot']) ? 'checked="checked"' : '';?> />
<?= gettext('Enable DoT') ?>
</td>
</tr>
<tr>
<td><a id="help_for_port_dot" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Listen Port for DoT");?></td>
<td>
<input name="port_dot" type="text" id="port_dot" placeholder="853" size="6" value="<?=$pconfig['port_dot'];?>" />
<div class="hidden" data-for="help_for_port_dot">
<?=gettext("The port used for responding to DoT queries. It should normally be left blank unless another service needs to bind to TCP/UDP port 853. You'll need to allow access to this port in the firewall configuration.");?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_dotdohcert" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("DoH and DoT Certificate"); ?></td>
<td>
<select name="dohdot_cert" class="selectpicker" data-style="btn-default">
<?php foreach ($a_cert as $cert): ?>
<?php if (isset($cert['prv'])): ?>
<option value="<?=$cert['refid'];?>" <?=$pconfig['dohdot_cert'] == $cert['refid'] ? "selected=\"selected\"" : "";?>>
<?=$cert['descr'];?>
</option>
<?php endif ?>
<?php endforeach ?>
</select>
<div class='hidden' data-for="help_for_dotdohcert">
<?=gettext('DoH and DoT Certificate to use.');?>
</div>
</td>
</tr>
<tr>
<td><a id="help_for_active_interface" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Network Interfaces"); ?></td>
<td>
Expand Down