radvd: move plugin code to its own space #8351

This commit is contained in:
Franco Fichtner 2025-03-20 10:06:48 +01:00
parent c9347fe0d4
commit ec634601fe
4 changed files with 483 additions and 433 deletions

1
plist
View File

@ -39,6 +39,7 @@
/usr/local/etc/inc/plugins.inc.d/openvpn/ovpn-linkup
/usr/local/etc/inc/plugins.inc.d/openvpn/tunnel_endpoint.php
/usr/local/etc/inc/plugins.inc.d/pf.inc
/usr/local/etc/inc/plugins.inc.d/radvd.inc
/usr/local/etc/inc/plugins.inc.d/suricata.inc
/usr/local/etc/inc/plugins.inc.d/unbound.inc
/usr/local/etc/inc/plugins.inc.d/vxlan.inc

View File

@ -44,27 +44,6 @@ function dhcpd_run()
];
}
function dhcpd_radvd_enabled()
{
global $config;
/* handle manually configured DHCP6 server settings first */
foreach (config_read_array('dhcpdv6') as $dhcpv6if => $dhcpv6ifconf) {
if (isset($config['interfaces'][$dhcpv6if]['enable']) && isset($dhcpv6ifconf['ramode']) && $dhcpv6ifconf['ramode'] != 'disabled') {
return true;
}
}
/* handle DHCP-PD prefixes and 6RD dynamic interfaces */
foreach (legacy_config_get_interfaces(array('virtual' => false)) as $ifcfg) {
if (isset($ifcfg['enable']) && !empty($ifcfg['track6-interface']) && !isset($ifcfg['dhcpd6track6allowoverride'])) {
return true;
}
}
return false;
}
function dhcpd_dhcpv6_enabled()
{
global $config;
@ -101,20 +80,8 @@ function dhcpd_dhcpv4_enabled()
function dhcpd_services()
{
global $config;
$services = array();
if (dhcpd_radvd_enabled()) {
$pconfig = array();
$pconfig['name'] = "radvd";
$pconfig['description'] = gettext("Router Advertisement Daemon");
$pconfig['php']['restart'] = array('dhcpd_radvd_configure');
$pconfig['php']['start'] = array('dhcpd_radvd_configure');
$pconfig['pidfile'] = '/var/run/radvd.pid';
$services[] = $pconfig;
}
if (dhcpd_dhcpv4_enabled()) {
$pconfig = array();
$pconfig['name'] = 'dhcpd';
@ -138,403 +105,6 @@ function dhcpd_services()
return $services;
}
function dhcpd_radvd_configure($verbose = false, $blacklist = [])
{
global $config;
if (!dhcpd_radvd_enabled()) {
killbypid('/var/run/radvd.pid');
return;
}
service_log('Starting router advertisement service...', $verbose);
$ifconfig_details = legacy_interfaces_details();
$radvdconf = "# Automatically generated, do not edit\n";
/* Process all links which need the router advertise daemon */
$radvdifs = array();
/* handle manually configured DHCP6 server settings first */
foreach (config_read_array('dhcpdv6') as $dhcpv6if => $dhcpv6ifconf) {
if (isset($config['interfaces'][$dhcpv6if]['track6-interface']) && !isset($config['interfaces'][$dhcpv6if]['dhcpd6track6allowoverride'])) {
/* handled by automatic case */
continue;
} elseif (!isset($config['interfaces'][$dhcpv6if]['enable'])) {
$radvdconf .= "# Skipping disabled interface {$dhcpv6if}\n";
continue;
} elseif (isset($blacklist[$dhcpv6if])) {
$radvdconf .= "# Skipping blacklisted interface {$dhcpv6if}\n";
continue;
} elseif (!isset($dhcpv6ifconf['ramode']) || $dhcpv6ifconf['ramode'] == 'disabled') {
$radvdconf .= "# Skipping unset interface {$dhcpv6if}\n";
continue;
}
$carp_mode = false;
$src_addr = false;
$ifcfgipv6 = get_interface_ipv6(!empty($dhcpv6ifconf['rainterface']) ? $dhcpv6ifconf['rainterface'] : $dhcpv6if);
if (!is_ipaddrv6($ifcfgipv6) && !isset($config['interfaces'][$dhcpv6if]['dhcpd6track6allowoverride'])) {
$radvdconf .= "# Skipping addressless interface {$dhcpv6if}\n";
continue;
}
$realif = get_real_interface($dhcpv6if, 'inet6');
$radvdifs[$realif] = 1;
$mtu = legacy_interface_stats($realif)['mtu'];
if (isset($config['interfaces'][$dhcpv6if]['track6-interface'])) {
$realtrackif = get_real_interface($config['interfaces'][$dhcpv6if]['track6-interface'], 'inet6');
$trackmtu = legacy_interface_stats($realtrackif)['mtu'];
if (!empty($trackmtu) && !empty($mtu)) {
if ($trackmtu < $mtu) {
$mtu = $trackmtu;
}
}
}
if (!empty($dhcpv6ifconf['AdvLinkMTU']) && !empty($mtu)) {
if ($dhcpv6ifconf['AdvLinkMTU'] < $mtu) {
$mtu = $dhcpv6ifconf['AdvLinkMTU'];
} else {
log_msg("Skipping AdvLinkMTU configuration since it cannot be applied on {$dhcpv6if}", LOG_WARNING);
}
}
$radvdconf .= "# Generated RADVD config for manual assignment on {$dhcpv6if}\n";
$radvdconf .= "interface {$realif} {\n";
$radvdconf .= "\tAdvSendAdvert on;\n";
$radvdconf .= sprintf("\tMinRtrAdvInterval %s;\n", !empty($dhcpv6ifconf['ramininterval']) ? $dhcpv6ifconf['ramininterval'] : '200');
$radvdconf .= sprintf("\tMaxRtrAdvInterval %s;\n", !empty($dhcpv6ifconf['ramaxinterval']) ? $dhcpv6ifconf['ramaxinterval'] : '600');
if (!empty($dhcpv6ifconf['AdvDefaultLifetime'])) {
$radvdconf .= sprintf("\tAdvDefaultLifetime %s;\n", $dhcpv6ifconf['AdvDefaultLifetime']);
}
$radvdconf .= sprintf("\tAdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
switch ($dhcpv6ifconf['rapriority']) {
case "low":
$radvdconf .= "\tAdvDefaultPreference low;\n";
break;
case "high":
$radvdconf .= "\tAdvDefaultPreference high;\n";
break;
default:
$radvdconf .= "\tAdvDefaultPreference medium;\n";
break;
}
switch ($dhcpv6ifconf['ramode']) {
case 'assist':
case 'managed':
$radvdconf .= "\tAdvManagedFlag on;\n";
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
break;
case 'stateless':
$radvdconf .= "\tAdvManagedFlag off;\n";
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
break;
default:
break;
}
if (!empty($dhcpv6ifconf['ranodefault'])) {
$radvdconf .= "\tAdvDefaultLifetime 0;\n";
}
$stanzas = [];
list (, $networkv6) = interfaces_primary_address6($dhcpv6if, $ifconfig_details);
if (is_subnetv6($networkv6)) {
$stanzas[] = $networkv6;
}
foreach (config_read_array('virtualip', 'vip') as $vip) {
if ($vip['interface'] != $dhcpv6if || !is_ipaddrv6($vip['subnet'])) {
continue;
}
if (is_linklocal($vip['subnet'])) {
if ($ifcfgipv6 == $vip['subnet']) {
$carp_mode = !empty($vip['vhid']);
$src_addr = true;
}
continue;
}
if ($vip['subnet_bits'] == '128' || !empty($vip['nobind'])) {
continue;
}
/* force subnet to 64 as per radvd complaint "prefix length should be 64 for xzy" */
$subnetv6 = gen_subnetv6($vip['subnet'], 64);
$stanzas[] = "{$subnetv6}/64";
}
if ($src_addr) {
/* inject configured link-local address into the RA message */
$radvdconf .= "\tAdvRASrcAddress {\n";
$radvdconf .= "\t\t{$ifcfgipv6};\n";
$radvdconf .= "\t};\n";
}
if ($carp_mode) {
/* to avoid wrong MAC being stuck during failover */
$radvdconf .= "\tAdvSourceLLAddress off;\n";
/* to avoid final advertisement with zero router lifetime */
$radvdconf .= "\tRemoveAdvOnExit off;\n";
}
/* VIPs may duplicate readings from system */
$stanzas = array_unique($stanzas);
foreach ($stanzas as $stanza) {
$radvdconf .= "\tprefix {$stanza} {\n";
$radvdconf .= "\t\tDeprecatePrefix " . (!empty($dhcpv6ifconf['AdvDeprecatePrefix']) ? $dhcpv6ifconf['AdvDeprecatePrefix'] : ($carp_mode ? 'off' : 'on')) . ";\n";
switch ($dhcpv6ifconf['ramode']) {
case 'assist':
case 'stateless':
case 'unmanaged':
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous on;\n";
break;
case 'managed':
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous off;\n";
break;
case 'router':
$radvdconf .= "\t\tAdvOnLink off;\n";
$radvdconf .= "\t\tAdvAutonomous off;\n";
break;
default:
break;
}
if (!empty($dhcpv6ifconf['AdvValidLifetime'])) {
$radvdconf .= sprintf("\t\tAdvValidLifetime %s;\n", $dhcpv6ifconf['AdvValidLifetime']);
}
if (!empty($dhcpv6ifconf['AdvPreferredLifetime'])) {
$radvdconf .= sprintf("\t\tAdvPreferredLifetime %s;\n", $dhcpv6ifconf['AdvPreferredLifetime']);
}
$radvdconf .= "\t};\n";
}
if (!empty($dhcpv6ifconf['raroutes'])) {
foreach (explode(',', $dhcpv6ifconf['raroutes']) as $raroute) {
$radvdconf .= "\troute {$raroute} {\n";
$radvdconf .= "\t\tRemoveRoute " . (!empty($dhcpv6ifconf['AdvRemoveRoute']) ? $dhcpv6ifconf['AdvRemoveRoute'] : ($carp_mode ? 'off' : 'on')) . ";\n";
if (!empty($dhcpv6ifconf['AdvRouteLifetime'])) {
$radvdconf .= "\t\tAdvRouteLifetime {$dhcpv6ifconf['AdvRouteLifetime']};\n";
}
$radvdconf .= "\t};\n";
}
}
$dnslist = [];
$dnssl = null;
/* advertise both DNS servers and domains via RA (RFC 8106) if allowed */
if (!isset($dhcpv6ifconf['radisablerdnss'])) {
$dnslist_tmp = [];
if (isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['dnsserver'][0])) {
$dnslist_tmp = $dhcpv6ifconf['dnsserver'];
} elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['radnsserver'][0])) {
$dnslist_tmp = $dhcpv6ifconf['radnsserver'];
} elseif (!empty(service_by_filter(['dns_ports' => '53']))) {
if (is_ipaddrv6($ifcfgipv6)) {
$dnslist_tmp[] = $ifcfgipv6;
} else {
log_msg("dhcpd_radvd_configure(manual) found no suitable IPv6 address on {$dhcpv6if}({$realif})", LOG_WARNING);
}
} elseif (!empty($config['system']['dnsserver'][0])) {
$dnslist_tmp = $config['system']['dnsserver'];
}
foreach ($dnslist_tmp as $server) {
if (!is_ipaddrv6($server)) {
continue;
}
if (count($dnslist) >= 3) {
log_msg("The radvd RDNSS entry $server cannot be added due to too many addresses.", LOG_WARNING);
continue;
}
$dnslist[] = $server;
}
if (isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['domainsearchlist'])) {
$dnssl = implode(' ', explode(';', $dhcpv6ifconf['domainsearchlist']));
} elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['radomainsearchlist'])) {
$dnssl = implode(' ', explode(';', $dhcpv6ifconf['radomainsearchlist']));
} elseif (!empty($config['system']['domain'])) {
$dnssl = $config['system']['domain'];
}
}
if (count($dnslist) > 0) {
$radvdconf .= "\tRDNSS " . implode(" ", $dnslist) . " {\n";
if (!empty($dhcpv6ifconf['AdvRDNSSLifetime'])) {
$radvdconf .= "\t\tAdvRDNSSLifetime {$dhcpv6ifconf['AdvRDNSSLifetime']};\n";
}
$radvdconf .= "\t};\n";
}
if (!empty($dnssl)) {
$radvdconf .= "\tDNSSL {$dnssl} {\n";
if (!empty($dhcpv6ifconf['AdvDNSSLLifetime'])) {
$radvdconf .= "\t\tAdvDNSSLLifetime {$dhcpv6ifconf['AdvDNSSLLifetime']};\n";
}
$radvdconf .= "\t};\n";
}
$radvdconf .= "};\n";
}
/* handle DHCP-PD prefixes and 6RD dynamic interfaces */
foreach (array_keys(get_configured_interface_with_descr()) as $if) {
if (!isset($config['interfaces'][$if]['track6-interface']) || isset($config['interfaces'][$if]['dhcpd6track6allowoverride'])) {
/* handled by manual case */
continue;
} elseif (empty($config['interfaces'][$config['interfaces'][$if]['track6-interface']])) {
$radvdconf .= "# Skipping defunct interface {$if}\n";
continue;
} elseif (!isset($config['interfaces'][$if]['enable'])) {
$radvdconf .= "# Skipping disabled interface {$if}\n";
continue;
} elseif (isset($blacklist[$if])) {
$radvdconf .= "# Skipping blacklisted interface {$if}\n";
continue;
}
$trackif = $config['interfaces'][$if]['track6-interface'];
$realif = get_real_interface($if, 'inet6');
/* prevent duplicate entries, manual overrides */
if (isset($radvdifs[$realif])) {
continue;
}
$radvdifs[$realif] = 1;
$autotype = 'unknown';
if (isset($config['interfaces'][$trackif]['ipaddrv6'])) {
$autotype = $config['interfaces'][$trackif]['ipaddrv6'];
}
$realtrackif = get_real_interface($trackif, 'inet6');
$mtu = legacy_interface_stats($realif)['mtu'];
$trackmtu = legacy_interface_stats($realtrackif)['mtu'];
if (!empty($trackmtu) && !empty($mtu)) {
if ($trackmtu < $mtu) {
$mtu = $trackmtu;
}
}
$dnslist = [];
list ($ifcfgipv6, $networkv6) = interfaces_primary_address6($if, $ifconfig_details);
if ($autotype == 'slaac') {
/* XXX this may be incorrect and needs an override or revisit */
$networkv6 = '2000::/64';
}
if (!empty(service_by_filter(['dns_ports' => '53']))) {
if (is_ipaddrv6($ifcfgipv6)) {
$dnslist[] = $ifcfgipv6;
} else {
log_msg("dhcpd_radvd_configure(auto) found no suitable IPv6 address on {$if}({$realif})", LOG_WARNING);
}
} elseif (!empty($config['system']['dnsserver'])) {
foreach ($config['system']['dnsserver'] as $server) {
if (!is_ipaddrv6($server)) {
continue;
}
if (count($dnslist) >= 3) {
log_msg("The radvd RDNSS entry $server cannot be added due to too many addresses.", LOG_WARNING);
continue;
}
$dnslist[] = $server;
}
}
$radvdconf .= "# Generated RADVD config for {$autotype} assignment from {$trackif} on {$if}\n";
$radvdconf .= "interface {$realif} {\n";
$radvdconf .= "\tAdvSendAdvert on;\n";
$radvdconf .= sprintf("\tAdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
$radvdconf .= "\tAdvManagedFlag on;\n";
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
if (!empty($networkv6)) {
$radvdconf .= "\tprefix {$networkv6} {\n";
if ($autotype == 'slaac') {
/* XXX also of interest in the future, see hardcoded prefix above */
$radvdconf .= "\t\tBase6Interface $realtrackif;\n";
}
$radvdconf .= "\t\tDeprecatePrefix on;\n";
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous on;\n";
$radvdconf .= "\t};\n";
}
foreach (config_read_array('virtualip', 'vip') as $vip) {
if ($vip['interface'] != $if || !is_ipaddrv6($vip['subnet']) || $vip['subnet_bits'] == '128') {
continue;
}
if (is_linklocal($vip['subnet']) || !empty($vip['nobind'])) {
continue;
}
/* force subnet to 64 as per radvd complaint "prefix length should be 64 for xzy" */
$subnetv6 = gen_subnetv6($vip['subnet'], 64);
$vipnetv6 = "{$subnetv6}/64";
if ($vipnetv6 == $networkv6) {
continue;
}
$radvdconf .= "\tprefix {$vipnetv6} {\n";
$radvdconf .= "\t\tDeprecatePrefix on;\n";
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous on;\n";
$radvdconf .= "\t};\n";
}
if (count($dnslist) > 0) {
$radvdconf .= "\tRDNSS " . implode(" ", $dnslist) . " { };\n";
}
if (!empty($config['system']['domain'])) {
$radvdconf .= "\tDNSSL {$config['system']['domain']} { };\n";
}
$radvdconf .= "};\n";
}
$radvd_conf_file = '/var/etc/radvd.conf';
file_put_contents($radvd_conf_file, $radvdconf);
if (count($radvdifs)) {
$last_version = @file_get_contents("{$radvd_conf_file}.last");
$this_version = shell_safe('/bin/cat %s | sha256', $radvd_conf_file);
if (isvalidpid('/var/run/radvd.pid') && $last_version == $this_version) {
killbypid('/var/run/radvd.pid', 'HUP');
} else {
killbypid('/var/run/radvd.pid');
file_put_contents("{$radvd_conf_file}.last", $this_version);
mwexec('/usr/local/sbin/radvd -p /var/run/radvd.pid -C /var/etc/radvd.conf -m syslog');
}
} else {
/* stop on invalid configuration for legacy condition above */
killbypid('/var/run/radvd.pid');
@unlink("{$radvd_conf_file}.last");
}
service_log("done.\n", $verbose);
}
function dhcpd_dhcp_configure($verbose = false, $family = null, $blacklist = [])
{
$dirs = ['/dev', '/etc', '/lib', '/run', '/usr', '/usr/local/sbin', '/var/db', '/var/run'];
@ -555,7 +125,6 @@ function dhcpd_dhcp_configure($verbose = false, $family = null, $blacklist = [])
if ($family == null || $family == 'inet6') {
dhcpd_dhcp6_configure($verbose, $blacklist);
dhcpd_radvd_configure($verbose, $blacklist);
}
}

View File

@ -0,0 +1,480 @@
<?php
/*
* Copyright (C) 2014-2024 Franco Fichtner <franco@opnsense.org>
* Copyright (C) 2010 Ermal Luçi
* Copyright (C) 2005-2006 Colin Smith <ethethlay@gmail.com>
* Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>
* 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.
*/
function radvd_configure()
{
return [
'dhcp' => ['radvd_configure_dhcp:3'],
'local' => ['radvd_configure_do'],
];
}
function radvd_enabled()
{
global $config;
/* handle manually configured DHCP6 server settings first */
foreach (config_read_array('dhcpdv6') as $dhcpv6if => $dhcpv6ifconf) {
if (isset($config['interfaces'][$dhcpv6if]['enable']) && isset($dhcpv6ifconf['ramode']) && $dhcpv6ifconf['ramode'] != 'disabled') {
return true;
}
}
/* handle DHCP-PD prefixes and 6RD dynamic interfaces */
foreach (legacy_config_get_interfaces(array('virtual' => false)) as $ifcfg) {
if (isset($ifcfg['enable']) && !empty($ifcfg['track6-interface']) && !isset($ifcfg['dhcpd6track6allowoverride'])) {
return true;
}
}
return false;
}
function radvd_services()
{
$services = [];
if (radvd_enabled()) {
$pconfig = [];
$pconfig['name'] = 'radvd';
$pconfig['description'] = gettext('Router Advertisement Daemon');
$pconfig['php']['restart'] = ['radvd_configure_do'];
$pconfig['php']['start'] = ['radvd_configure_do'];
$pconfig['pidfile'] = '/var/run/radvd.pid';
$services[] = $pconfig;
}
return $services;
}
function radvd_configure_dhcp($verbose = false, $family = null, $blacklist = [])
{
if ($family == null || $family == 'inet6') {
radvd_configure_do($verbose, $blacklist);
}
}
function radvd_configure_do($verbose = false, $blacklist = [])
{
global $config;
if (!radvd_enabled()) {
killbypid('/var/run/radvd.pid');
return;
}
service_log('Starting router advertisement service...', $verbose);
$ifconfig_details = legacy_interfaces_details();
$radvdconf = "# Automatically generated, do not edit\n";
/* Process all links which need the router advertise daemon */
$radvdifs = array();
/* handle manually configured DHCP6 server settings first */
foreach (config_read_array('dhcpdv6') as $dhcpv6if => $dhcpv6ifconf) {
if (isset($config['interfaces'][$dhcpv6if]['track6-interface']) && !isset($config['interfaces'][$dhcpv6if]['dhcpd6track6allowoverride'])) {
/* handled by automatic case */
continue;
} elseif (!isset($config['interfaces'][$dhcpv6if]['enable'])) {
$radvdconf .= "# Skipping disabled interface {$dhcpv6if}\n";
continue;
} elseif (isset($blacklist[$dhcpv6if])) {
$radvdconf .= "# Skipping blacklisted interface {$dhcpv6if}\n";
continue;
} elseif (!isset($dhcpv6ifconf['ramode']) || $dhcpv6ifconf['ramode'] == 'disabled') {
$radvdconf .= "# Skipping unset interface {$dhcpv6if}\n";
continue;
}
$carp_mode = false;
$src_addr = false;
$ifcfgipv6 = get_interface_ipv6(!empty($dhcpv6ifconf['rainterface']) ? $dhcpv6ifconf['rainterface'] : $dhcpv6if);
if (!is_ipaddrv6($ifcfgipv6) && !isset($config['interfaces'][$dhcpv6if]['dhcpd6track6allowoverride'])) {
$radvdconf .= "# Skipping addressless interface {$dhcpv6if}\n";
continue;
}
$realif = get_real_interface($dhcpv6if, 'inet6');
$radvdifs[$realif] = 1;
$mtu = legacy_interface_stats($realif)['mtu'];
if (isset($config['interfaces'][$dhcpv6if]['track6-interface'])) {
$realtrackif = get_real_interface($config['interfaces'][$dhcpv6if]['track6-interface'], 'inet6');
$trackmtu = legacy_interface_stats($realtrackif)['mtu'];
if (!empty($trackmtu) && !empty($mtu)) {
if ($trackmtu < $mtu) {
$mtu = $trackmtu;
}
}
}
if (!empty($dhcpv6ifconf['AdvLinkMTU']) && !empty($mtu)) {
if ($dhcpv6ifconf['AdvLinkMTU'] < $mtu) {
$mtu = $dhcpv6ifconf['AdvLinkMTU'];
} else {
log_msg("Skipping AdvLinkMTU configuration since it cannot be applied on {$dhcpv6if}", LOG_WARNING);
}
}
$radvdconf .= "# Generated RADVD config for manual assignment on {$dhcpv6if}\n";
$radvdconf .= "interface {$realif} {\n";
$radvdconf .= "\tAdvSendAdvert on;\n";
$radvdconf .= sprintf("\tMinRtrAdvInterval %s;\n", !empty($dhcpv6ifconf['ramininterval']) ? $dhcpv6ifconf['ramininterval'] : '200');
$radvdconf .= sprintf("\tMaxRtrAdvInterval %s;\n", !empty($dhcpv6ifconf['ramaxinterval']) ? $dhcpv6ifconf['ramaxinterval'] : '600');
if (!empty($dhcpv6ifconf['AdvDefaultLifetime'])) {
$radvdconf .= sprintf("\tAdvDefaultLifetime %s;\n", $dhcpv6ifconf['AdvDefaultLifetime']);
}
$radvdconf .= sprintf("\tAdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
switch ($dhcpv6ifconf['rapriority']) {
case "low":
$radvdconf .= "\tAdvDefaultPreference low;\n";
break;
case "high":
$radvdconf .= "\tAdvDefaultPreference high;\n";
break;
default:
$radvdconf .= "\tAdvDefaultPreference medium;\n";
break;
}
switch ($dhcpv6ifconf['ramode']) {
case 'assist':
case 'managed':
$radvdconf .= "\tAdvManagedFlag on;\n";
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
break;
case 'stateless':
$radvdconf .= "\tAdvManagedFlag off;\n";
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
break;
default:
break;
}
if (!empty($dhcpv6ifconf['ranodefault'])) {
$radvdconf .= "\tAdvDefaultLifetime 0;\n";
}
$stanzas = [];
list (, $networkv6) = interfaces_primary_address6($dhcpv6if, $ifconfig_details);
if (is_subnetv6($networkv6)) {
$stanzas[] = $networkv6;
}
foreach (config_read_array('virtualip', 'vip') as $vip) {
if ($vip['interface'] != $dhcpv6if || !is_ipaddrv6($vip['subnet'])) {
continue;
}
if (is_linklocal($vip['subnet'])) {
if ($ifcfgipv6 == $vip['subnet']) {
$carp_mode = !empty($vip['vhid']);
$src_addr = true;
}
continue;
}
if ($vip['subnet_bits'] == '128' || !empty($vip['nobind'])) {
continue;
}
/* force subnet to 64 as per radvd complaint "prefix length should be 64 for xzy" */
$subnetv6 = gen_subnetv6($vip['subnet'], 64);
$stanzas[] = "{$subnetv6}/64";
}
if ($src_addr) {
/* inject configured link-local address into the RA message */
$radvdconf .= "\tAdvRASrcAddress {\n";
$radvdconf .= "\t\t{$ifcfgipv6};\n";
$radvdconf .= "\t};\n";
}
if ($carp_mode) {
/* to avoid wrong MAC being stuck during failover */
$radvdconf .= "\tAdvSourceLLAddress off;\n";
/* to avoid final advertisement with zero router lifetime */
$radvdconf .= "\tRemoveAdvOnExit off;\n";
}
/* VIPs may duplicate readings from system */
$stanzas = array_unique($stanzas);
foreach ($stanzas as $stanza) {
$radvdconf .= "\tprefix {$stanza} {\n";
$radvdconf .= "\t\tDeprecatePrefix " . (!empty($dhcpv6ifconf['AdvDeprecatePrefix']) ? $dhcpv6ifconf['AdvDeprecatePrefix'] : ($carp_mode ? 'off' : 'on')) . ";\n";
switch ($dhcpv6ifconf['ramode']) {
case 'assist':
case 'stateless':
case 'unmanaged':
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous on;\n";
break;
case 'managed':
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous off;\n";
break;
case 'router':
$radvdconf .= "\t\tAdvOnLink off;\n";
$radvdconf .= "\t\tAdvAutonomous off;\n";
break;
default:
break;
}
if (!empty($dhcpv6ifconf['AdvValidLifetime'])) {
$radvdconf .= sprintf("\t\tAdvValidLifetime %s;\n", $dhcpv6ifconf['AdvValidLifetime']);
}
if (!empty($dhcpv6ifconf['AdvPreferredLifetime'])) {
$radvdconf .= sprintf("\t\tAdvPreferredLifetime %s;\n", $dhcpv6ifconf['AdvPreferredLifetime']);
}
$radvdconf .= "\t};\n";
}
if (!empty($dhcpv6ifconf['raroutes'])) {
foreach (explode(',', $dhcpv6ifconf['raroutes']) as $raroute) {
$radvdconf .= "\troute {$raroute} {\n";
$radvdconf .= "\t\tRemoveRoute " . (!empty($dhcpv6ifconf['AdvRemoveRoute']) ? $dhcpv6ifconf['AdvRemoveRoute'] : ($carp_mode ? 'off' : 'on')) . ";\n";
if (!empty($dhcpv6ifconf['AdvRouteLifetime'])) {
$radvdconf .= "\t\tAdvRouteLifetime {$dhcpv6ifconf['AdvRouteLifetime']};\n";
}
$radvdconf .= "\t};\n";
}
}
$dnslist = [];
$dnssl = null;
/* advertise both DNS servers and domains via RA (RFC 8106) if allowed */
if (!isset($dhcpv6ifconf['radisablerdnss'])) {
$dnslist_tmp = [];
if (isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['dnsserver'][0])) {
$dnslist_tmp = $dhcpv6ifconf['dnsserver'];
} elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['radnsserver'][0])) {
$dnslist_tmp = $dhcpv6ifconf['radnsserver'];
} elseif (!empty(service_by_filter(['dns_ports' => '53']))) {
if (is_ipaddrv6($ifcfgipv6)) {
$dnslist_tmp[] = $ifcfgipv6;
} else {
log_msg("radvd_configure_do(manual) found no suitable IPv6 address on {$dhcpv6if}({$realif})", LOG_WARNING);
}
} elseif (!empty($config['system']['dnsserver'][0])) {
$dnslist_tmp = $config['system']['dnsserver'];
}
foreach ($dnslist_tmp as $server) {
if (!is_ipaddrv6($server)) {
continue;
}
if (count($dnslist) >= 3) {
log_msg("The radvd RDNSS entry $server cannot be added due to too many addresses.", LOG_WARNING);
continue;
}
$dnslist[] = $server;
}
if (isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['domainsearchlist'])) {
$dnssl = implode(' ', explode(';', $dhcpv6ifconf['domainsearchlist']));
} elseif (!isset($dhcpv6ifconf['rasamednsasdhcp6']) && !empty($dhcpv6ifconf['radomainsearchlist'])) {
$dnssl = implode(' ', explode(';', $dhcpv6ifconf['radomainsearchlist']));
} elseif (!empty($config['system']['domain'])) {
$dnssl = $config['system']['domain'];
}
}
if (count($dnslist) > 0) {
$radvdconf .= "\tRDNSS " . implode(" ", $dnslist) . " {\n";
if (!empty($dhcpv6ifconf['AdvRDNSSLifetime'])) {
$radvdconf .= "\t\tAdvRDNSSLifetime {$dhcpv6ifconf['AdvRDNSSLifetime']};\n";
}
$radvdconf .= "\t};\n";
}
if (!empty($dnssl)) {
$radvdconf .= "\tDNSSL {$dnssl} {\n";
if (!empty($dhcpv6ifconf['AdvDNSSLLifetime'])) {
$radvdconf .= "\t\tAdvDNSSLLifetime {$dhcpv6ifconf['AdvDNSSLLifetime']};\n";
}
$radvdconf .= "\t};\n";
}
$radvdconf .= "};\n";
}
/* handle DHCP-PD prefixes and 6RD dynamic interfaces */
foreach (array_keys(get_configured_interface_with_descr()) as $if) {
if (!isset($config['interfaces'][$if]['track6-interface']) || isset($config['interfaces'][$if]['dhcpd6track6allowoverride'])) {
/* handled by manual case */
continue;
} elseif (empty($config['interfaces'][$config['interfaces'][$if]['track6-interface']])) {
$radvdconf .= "# Skipping defunct interface {$if}\n";
continue;
} elseif (!isset($config['interfaces'][$if]['enable'])) {
$radvdconf .= "# Skipping disabled interface {$if}\n";
continue;
} elseif (isset($blacklist[$if])) {
$radvdconf .= "# Skipping blacklisted interface {$if}\n";
continue;
}
$trackif = $config['interfaces'][$if]['track6-interface'];
$realif = get_real_interface($if, 'inet6');
/* prevent duplicate entries, manual overrides */
if (isset($radvdifs[$realif])) {
continue;
}
$radvdifs[$realif] = 1;
$autotype = 'unknown';
if (isset($config['interfaces'][$trackif]['ipaddrv6'])) {
$autotype = $config['interfaces'][$trackif]['ipaddrv6'];
}
$realtrackif = get_real_interface($trackif, 'inet6');
$mtu = legacy_interface_stats($realif)['mtu'];
$trackmtu = legacy_interface_stats($realtrackif)['mtu'];
if (!empty($trackmtu) && !empty($mtu)) {
if ($trackmtu < $mtu) {
$mtu = $trackmtu;
}
}
$dnslist = [];
list ($ifcfgipv6, $networkv6) = interfaces_primary_address6($if, $ifconfig_details);
if ($autotype == 'slaac') {
/* XXX this may be incorrect and needs an override or revisit */
$networkv6 = '2000::/64';
}
if (!empty(service_by_filter(['dns_ports' => '53']))) {
if (is_ipaddrv6($ifcfgipv6)) {
$dnslist[] = $ifcfgipv6;
} else {
log_msg("radvd_configure_do(auto) found no suitable IPv6 address on {$if}({$realif})", LOG_WARNING);
}
} elseif (!empty($config['system']['dnsserver'])) {
foreach ($config['system']['dnsserver'] as $server) {
if (!is_ipaddrv6($server)) {
continue;
}
if (count($dnslist) >= 3) {
log_msg("The radvd RDNSS entry $server cannot be added due to too many addresses.", LOG_WARNING);
continue;
}
$dnslist[] = $server;
}
}
$radvdconf .= "# Generated RADVD config for {$autotype} assignment from {$trackif} on {$if}\n";
$radvdconf .= "interface {$realif} {\n";
$radvdconf .= "\tAdvSendAdvert on;\n";
$radvdconf .= sprintf("\tAdvLinkMTU %s;\n", !empty($mtu) ? $mtu : 0);
$radvdconf .= "\tAdvManagedFlag on;\n";
$radvdconf .= "\tAdvOtherConfigFlag on;\n";
if (!empty($networkv6)) {
$radvdconf .= "\tprefix {$networkv6} {\n";
if ($autotype == 'slaac') {
/* XXX also of interest in the future, see hardcoded prefix above */
$radvdconf .= "\t\tBase6Interface $realtrackif;\n";
}
$radvdconf .= "\t\tDeprecatePrefix on;\n";
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous on;\n";
$radvdconf .= "\t};\n";
}
foreach (config_read_array('virtualip', 'vip') as $vip) {
if ($vip['interface'] != $if || !is_ipaddrv6($vip['subnet']) || $vip['subnet_bits'] == '128') {
continue;
}
if (is_linklocal($vip['subnet']) || !empty($vip['nobind'])) {
continue;
}
/* force subnet to 64 as per radvd complaint "prefix length should be 64 for xzy" */
$subnetv6 = gen_subnetv6($vip['subnet'], 64);
$vipnetv6 = "{$subnetv6}/64";
if ($vipnetv6 == $networkv6) {
continue;
}
$radvdconf .= "\tprefix {$vipnetv6} {\n";
$radvdconf .= "\t\tDeprecatePrefix on;\n";
$radvdconf .= "\t\tAdvOnLink on;\n";
$radvdconf .= "\t\tAdvAutonomous on;\n";
$radvdconf .= "\t};\n";
}
if (count($dnslist) > 0) {
$radvdconf .= "\tRDNSS " . implode(" ", $dnslist) . " { };\n";
}
if (!empty($config['system']['domain'])) {
$radvdconf .= "\tDNSSL {$config['system']['domain']} { };\n";
}
$radvdconf .= "};\n";
}
$radvd_conf_file = '/var/etc/radvd.conf';
file_put_contents($radvd_conf_file, $radvdconf);
if (count($radvdifs)) {
$last_version = @file_get_contents("{$radvd_conf_file}.last");
$this_version = shell_safe('/bin/cat %s | sha256', $radvd_conf_file);
if (isvalidpid('/var/run/radvd.pid') && $last_version == $this_version) {
killbypid('/var/run/radvd.pid', 'HUP');
} else {
killbypid('/var/run/radvd.pid');
file_put_contents("{$radvd_conf_file}.last", $this_version);
mwexec('/usr/local/sbin/radvd -p /var/run/radvd.pid -C /var/etc/radvd.conf -m syslog');
}
} else {
/* stop on invalid configuration for legacy condition above */
killbypid('/var/run/radvd.pid');
@unlink("{$radvd_conf_file}.last");
}
service_log("done.\n", $verbose);
}

View File

@ -31,7 +31,7 @@
require_once("guiconfig.inc");
require_once("interfaces.inc");
require_once("plugins.inc.d/dhcpd.inc");
require_once("plugins.inc.d/radvd.inc");
function val_int_in_range($value, $min, $max) {
return (((string)(int)$value) == $value) && $value >= $min && $value <= $max;
@ -205,7 +205,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
}
write_config();
dhcpd_radvd_configure();
radvd_configure_do();
$savemsg = get_std_save_message();
}
}