Services: Dnsmasq DNS & DHCP - allow ipv6 dhcp leases as described in https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html for https://github.com/opnsense/core/issues/8329

This commit is contained in:
Ad Schellevis 2025-03-04 20:16:00 +01:00
parent b514aafac6
commit d343bdf8ce
6 changed files with 172 additions and 19 deletions

1
plist
View File

@ -738,6 +738,7 @@
/usr/local/opnsense/mvc/app/models/OPNsense/Dnsmasq/Dnsmasq.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Dnsmasq/FieldTypes/AliasesField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Dnsmasq/FieldTypes/DomainIPField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Dnsmasq/FieldTypes/RangeAddressField.php
/usr/local/opnsense/mvc/app/models/OPNsense/Dnsmasq/Menu/Menu.xml
/usr/local/opnsense/mvc/app/models/OPNsense/Dnsmasq/Migrations/M1_0_0.php
/usr/local/opnsense/mvc/app/models/OPNsense/Firewall/ACL/ACL.xml

View File

@ -15,7 +15,7 @@
<id>range.start_addr</id>
<label>Start address</label>
<type>text</type>
<help>Start of the range.</help>
<help>Start of the range, e.g. 192.168.1.100, 2000::1 or when constructor is used a partial like ::1.</help>
</field>
<field>
<id>range.end_addr</id>
@ -23,6 +23,19 @@
<type>text</type>
<help>End of the range.</help>
</field>
<field>
<id>range.constructor</id>
<label>Constructor</label>
<type>dropdown</type>
<help>Interface to use to calculate the proper range, when selected, a range maybe specified as partial (e.g. ::1, ::400)</help>
</field>
<field>
<id>range.prefix_len</id>
<label>Prefix length (ipv6)</label>
<type>text</type>
<hint>64</hint>
<help>Prefix lenght offered to the client.</help>
</field>
<field>
<id>range.mode</id>
<label>Mode</label>

View File

@ -83,6 +83,8 @@ class Dnsmasq extends BaseModel
if (!$validateFullModel && !$range->isFieldChanged()) {
continue;
}
$start_inet = strpos($range->start_addr, ':') !== false ? 'inet6' : 'inet';
$end_inet = strpos($range->end_addr, ':') !== false ? 'inet6' : 'inet';
$key = $range->__reference;
if (!$range->domain->isEmpty() && $range->end_addr->isEmpty()) {
$messages->appendMessage(
@ -92,6 +94,71 @@ class Dnsmasq extends BaseModel
)
);
}
if ($start_inet != $end_inet && !$range->end_addr->isEmpty()) {
$messages->appendMessage(
new Message(
gettext("Protocol family doesn't match."),
$key . ".end_addr"
)
);
}
if (!$range->constructor->isEmpty()) {
if ($start_inet == 'inet') {
$messages->appendMessage(
new Message(
gettext("A constructor can only be configured for ipv6."),
$key . ".constructor"
)
);
}
if (!str_starts_with($range->start_addr, '::')) {
$messages->appendMessage(
new Message(
gettext("A constructor expects a partial address (e.g. ::1)."),
$key . ".start_addr"
)
);
}
if (!$range->end_addr->isEmpty() && !str_starts_with($range->end_addr, '::')) {
$messages->appendMessage(
new Message(
gettext("A constructor expects a partial address (e.g. ::1)."),
$key . ".end_addr"
)
);
}
}
if ($range->constructor->isEmpty() &&
(str_starts_with($range->start_addr, '::') || str_starts_with($range->end_addr, '::'))
) {
$messages->appendMessage(
new Message(
gettext("Partial addresses can only be used with a constructor."),
$key . ".start_addr"
)
);
}
if (!$range->prefix_len->isEmpty() && $start_inet != 'inet6') {
$messages->appendMessage(
new Message(
gettext("Prefix length can only be used for IPv6."),
$key . ".prefix_len"
)
);
}
if (in_array('static', explode(',', $range->mode)) && $start_inet == 'inet6') {
$messages->appendMessage(
new Message(
gettext("Static is only available IPv4."),
$key . ".mode"
)
);
}
}
if (

View File

@ -134,21 +134,24 @@
</tag>
</Model>
</set_tag>
<start_addr type="NetworkField">
<start_addr type=".\RangeAddressField">
<NetMaskAllowed>N</NetMaskAllowed>
<AddressFamily>ipv4</AddressFamily>
<Required>Y</Required>
</start_addr>
<end_addr type="NetworkField">
<end_addr type=".\RangeAddressField">
<NetMaskAllowed>N</NetMaskAllowed>
<AddressFamily>ipv4</AddressFamily>
</end_addr>
<constructor type="InterfaceField"/>
<mode type="OptionField">
<OptionValues>
<static>static</static>
</OptionValues>
<Multiple>Y</Multiple>
</mode>
<prefix_len type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>64</MaximumValue>
</prefix_len>
<lease_time type="IntegerField">
<MinimumValue>1</MinimumValue>
</lease_time>

View File

@ -0,0 +1,44 @@
<?php
/*
* Copyright (C) 2025 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Dnsmasq\FieldTypes;
use OPNsense\Base\FieldTypes\NetworkField;
class RangeAddressField extends NetworkField
{
protected function isValidInput($input)
{
if (str_starts_with($input, '::')) {
/* special case, if we prefix a partial range with a network, we should end up with a valid address */
return filter_var('2000'.$input, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
} else {
return parent::isValidInput($input);
}
}
}

View File

@ -112,20 +112,45 @@ conf-dir=/usr/local/etc/dnsmasq.conf.d,\*.conf
{% for dhcp_range in helpers.toList('dnsmasq.dhcp_ranges') %}
dhcp-range={%
if dhcp_range.interface
%}{{helpers.physical_interface(dhcp_range.interface)}},{% endif %}{%
if dhcp_range.set_tag
%}set:{{dhcp_range.set_tag|replace('-','')}},{%
endif
%}{{dhcp_range.start_addr}},{%
if dhcp_range.mode
%}{{dhcp_range.mode}},{% endif %}{%
if dhcp_range.end_addr
%}{{ dhcp_range.end_addr }},{%
endif
%}{{dhcp_range.lease_time|default('86400')}}
{% if dhcp_range.start_addr and ':' not in dhcp_range.start_addr %}
{# IPv4 range #}
dhcp-range={%if dhcp_range.interface -%}
{{helpers.physical_interface(dhcp_range.interface)}},
{%- endif -%}
{%- if dhcp_range.set_tag -%}
set:{{dhcp_range.set_tag|replace('-','')}},
{%- endif -%}
{{dhcp_range.start_addr}},
{%- if dhcp_range.end_addr -%}
{{ dhcp_range.end_addr }},
{%- endif -%}
{%- if dhcp_range.mode -%}
{{dhcp_range.mode}},
{%- endif -%}
{{dhcp_range.lease_time|default('86400')}}
{% else %}
{# IPv6 range #}
dhcp-range={%if dhcp_range.interface -%}
{{helpers.physical_interface(dhcp_range.interface)}},
{%- endif -%}
{%- if dhcp_range.set_tag -%}
set:{{dhcp_range.set_tag|replace('-','')}},
{%- endif -%}
{{dhcp_range.start_addr}},
{%- if dhcp_range.end_addr -%}
{{ dhcp_range.end_addr }},
{%- endif -%}
{%- if dhcp_range.constructor -%}
constructor:{{helpers.physical_interface(dhcp_range.constructor)}},
{%- endif -%}
{%- if dhcp_range.mode -%}
{{dhcp_range.mode}},
{%- endif -%}
{%- if dhcp_range.prefix_len -%}
{{ dhcp_range.prefix_len }},
{%- endif -%}
{{dhcp_range.lease_time|default('86400')}}
{% endif %}
{% if dhcp_range.domain %}
domain={{ dhcp_range.domain }},{{dhcp_range.start_addr}},{{dhcp_range.end_addr}}
{% endif %}