From 0793de1aad720d424f66ec167e2efaf3ede5ed32 Mon Sep 17 00:00:00 2001 From: Luiz Souza Date: Tue, 17 Oct 2017 17:36:05 -0200 Subject: [PATCH] Fix the QinQ support. Bring the QinQ support to the VLAN dotted format. To avoid breaks third party software (such as dhcpd), we silently ignore the interfaces with names bigger than the maximum size in FreeBSD. Ticket #7942 --- src/etc/inc/globals.inc | 5 +- src/etc/inc/interfaces.inc | 170 ++++++++++++++++----- src/etc/inc/upgrade_config.inc | 29 ++++ src/etc/inc/util.inc | 1 + src/usr/local/www/interfaces_assign.php | 4 +- src/usr/local/www/interfaces_qinq.php | 36 +++-- src/usr/local/www/interfaces_qinq_edit.php | 35 ++++- src/usr/local/www/interfaces_vlan.php | 15 +- 8 files changed, 215 insertions(+), 80 deletions(-) diff --git a/src/etc/inc/globals.inc b/src/etc/inc/globals.inc index 9911f1cca7..6aba6653cd 100644 --- a/src/etc/inc/globals.inc +++ b/src/etc/inc/globals.inc @@ -32,6 +32,9 @@ define("IPV6", 6); define("IPV4V6", 2); define("ALIAS", 1); +// Interface Name Size +define("IF_NAMESIZE", 15); /* 16 minus the terminating NULL */ + // AddPassword method defines define('DMYPWD', "********"); @@ -71,7 +74,7 @@ $g = array( "disablecrashreporter" => false, "crashreporterurl" => "https://crashreporter.pfsense.org/crash_reporter.php", "debug" => false, - "latest_config" => "17.1", + "latest_config" => "17.2", "minimum_ram_warning" => "101", "minimum_ram_warning_text" => "128 MB", "wan_interface_name" => "wan", diff --git a/src/etc/inc/interfaces.inc b/src/etc/inc/interfaces.inc index aad7d6ee31..7736e964e6 100644 --- a/src/etc/inc/interfaces.inc +++ b/src/etc/inc/interfaces.inc @@ -200,6 +200,91 @@ function interfaces_loopback_configure() { return 0; } +function vlan_valid_tag($tag = NULL) { + + if ($tag == NULL || empty($tag) || + !is_numericint($tag) || intval($tag) < 1 || intval($tag) > 4094) { + return (false); + } + return (true); +} + +function qinq_inuse($qinq = NULL, $inqtag = NULL) { + global $config; + + if ($qinq == NULL || $inqtag == NULL || + !is_array($qinq) || !vlan_valid_tag($inqtag)) { + return (false); + } + + $iflist = get_configured_interface_list(true); + foreach ($iflist as $if) { + if ($config['interfaces'][$if]['if'] == qinq_interface($qinq, $inqtag)) { + return (true); + } + } + + return (false); +} + +function qinq_interface($qinq = NULL, $inqtag = NULL) { + + if ($qinq == NULL || $inqtag == NULL || !is_array($qinq) || + !isset($qinq['if']) || !isset($qinq['tag']) || + !vlan_valid_tag($qinq['tag']) || !vlan_valid_tag($inqtag)) { + return (NULL); + } + return ("{$qinq['if']}.{$qinq['tag']}.{$inqtag}"); +} + +function interface_is_qinq($if = NULL) { + global $config; + + if ($if == NULL || empty($if) || !is_array($config['qinqs']['qinqentry'])) { + return (NULL); + } + + /* Check basic format. */ + list($qinqif, $vlantag, $inqtag) = explode(".", $if); + if (empty($qinqif) || empty($vlantag) || empty($inqtag) || + !vlan_valid_tag($vlantag) || !vlan_valid_tag($inqtag)) { + return (NULL); + } + + foreach ($config['qinqs']['qinqentry'] as $qinqidx => $qinq) { + if ("{$qinqif}.{$vlantag}" != $qinq['vlanif']) { + continue; + } + if (empty($qinq['members'])) { + continue; + } + foreach (explode(" ", $qinq['members']) as $tag) { + if ($if == qinq_interface($qinq, $tag)) { + return ($qinq); + } + } + } + + return (NULL); +} + +function vlan_inuse($vlan) { + global $config; + + if ($vlan == NULL || !is_array($vlan)) { + return (false); + } + + $iflist = get_configured_interface_list(true); + foreach ($iflist as $if) { + if ($config['interfaces'][$if]['if'] == $vlan['vlanif']) { + return (true); + } + } + + return (false); +} + function interface_is_vlan($if = NULL) { global $config; @@ -227,7 +312,8 @@ function interface_is_vlan($if = NULL) { function vlan_interface($vlan = NULL) { if ($vlan == NULL || !is_array($vlan) || - !isset($vlan['if']) || !isset($vlan['tag'])) { + !isset($vlan['if']) || !isset($vlan['tag']) || + intval($vlan['tag']) < 0 || intval($vlan['tag']) > 4094) { return (NULL); } return ("{$vlan['if']}.{$vlan['tag']}"); @@ -311,16 +397,16 @@ function interface_vlan_configure(&$vlan) { return $vlanif; } -function interface_qinq_configure(&$vlan, $fd = NULL) { +function interface_qinq_configure(&$qinq, $fd = NULL) { global $config, $g; - if (!is_array($vlan)) { + if (!is_array($qinq)) { log_error(sprintf(gettext("QinQ compat VLAN: called with wrong options. Problems with config!%s"), "\n")); return; } - $qinqif = $vlan['if']; - $tag = $vlan['tag']; + $qinqif = $qinq['if']; + $tag = $qinq['tag']; if (empty($qinqif)) { log_error(sprintf(gettext("interface_qinq_configure called with if undefined.%s"), "\n")); return; @@ -331,8 +417,8 @@ function interface_qinq_configure(&$vlan, $fd = NULL) { return; } - $vlanif = interface_vlan_configure($vlan); - if ($vlanif == NULL) { + $vlanif = interface_vlan_configure($qinq); + if ($vlanif == NULL || $vlanif != $qinq['vlanif']) { log_error(gettext("interface_qinq_configure cannot create VLAN interface")); return; } @@ -347,47 +433,53 @@ function interface_qinq_configure(&$vlan, $fd = NULL) { interfaces_bring_up($qinqif); pfSense_ngctl_attach(".", $qinqif); + $ngif = str_replace(".", "_", $vlanif); if (!empty($vlanif) && does_interface_exist($vlanif)) { - fwrite($fd, "shutdown {$vlanif}qinq:\n"); - exec("/usr/sbin/ngctl msg {$vlanif}qinq: gettable", $result); + exec("/usr/sbin/ngctl shutdown {$ngif}qinq: > /dev/null 2>&1"); + exec("/usr/sbin/ngctl msg {$ngif}qinq: gettable > /dev/null 2>&1", $result); if (empty($result)) { - fwrite($fd, "mkpeer {$vlanif}: vlan lower downstream\n"); - fwrite($fd, "name {$vlanif}:lower {$vlanif}qinq\n"); - fwrite($fd, "connect {$vlanif}: {$vlanif}qinq: upper nomatch\n"); + fwrite($fd, "mkpeer {$ngif}: vlan lower downstream\n"); + fwrite($fd, "name {$ngif}:lower {$ngif}qinq\n"); + fwrite($fd, "connect {$ngif}: {$ngif}qinq: upper nomatch\n"); } } else { - fwrite($fd, "mkpeer {$vlanif}: vlan lower downstream\n"); - fwrite($fd, "name {$vlanif}:lower {$vlanif}qinq\n"); - fwrite($fd, "connect {$vlanif}: {$vlanif}qinq: upper nomatch\n"); + fwrite($fd, "mkpeer {$ngif}: vlan lower downstream\n"); + fwrite($fd, "name {$ngif}:lower {$ngif}qinq\n"); + fwrite($fd, "connect {$ngif}: {$ngif}qinq: upper nomatch\n"); } /* invalidate interface cache */ get_interface_arr(true); - if (!stristr($qinqif, "_vlan")) { + if (interface_is_vlan($qinqif) == NULL) { mwexec("/sbin/ifconfig {$qinqif} promisc\n"); } $macaddr = get_interface_mac($qinqif); - if (!empty($vlan['members'])) { - $members = explode(" ", $vlan['members']); + if (!empty($qinq['members'])) { + $qinqcmdbuf = ""; + $members = explode(" ", $qinq['members']); foreach ($members as $qtag) { - $qinq = array(); - $qinq['tag'] = $qtag; - $qinq['if'] = $vlanif; - interface_qinq2_configure($qinq, $fd, $macaddr); + $qinq2 = array(); + $qinq2['tag'] = $qtag; + $qinq2['if'] = $vlanif; + interface_qinq2_configure($qinq2, $qinqcmdbuf, $macaddr); + unset($qinq2); + } + if (strlen($qinqcmdbuf) > 0) { + fwrite($fd, $qinqcmdbuf); } } if ($exec == true) { fclose($fd); - mwexec("/usr/sbin/ngctl -f {$g['tmp_path']}/netgraphcmd"); + mwexec("/usr/sbin/ngctl -f {$g['tmp_path']}/netgraphcmd > /dev/null 2>&1"); } interfaces_bring_up($qinqif); - if (!empty($vlan['members'])) { - $members = explode(" ", $vlan['members']); - foreach ($members as $qif) { - interfaces_bring_up("{$vlanif}_{$qif}"); + if (!empty($qinq['members'])) { + $members = explode(" ", $qinq['members']); + foreach ($members as $qtag) { + interfaces_bring_up(qinq_interface($qinq, $qtag)); } } @@ -410,7 +502,7 @@ function interfaces_qinq_configure() { } } -function interface_qinq2_configure(&$qinq, $fd, $macaddr) { +function interface_qinq2_configure(&$qinq, &$cmdbuf, $macaddr) { global $config, $g; if (!is_array($qinq)) { @@ -419,19 +511,25 @@ function interface_qinq2_configure(&$qinq, $fd, $macaddr) { } $if = $qinq['if']; - $tag = $qinq['tag']; - $vlanif = "{$if}_{$tag}"; if (empty($if)) { log_error(sprintf(gettext("interface_qinq2_configure called with if undefined.%s"), "\n")); return; } + $tag = $qinq['tag']; + $vlanif = "{$if}.{$tag}"; + $ngif = str_replace(".", "_", $if); + if (strlen($vlanif) > IF_NAMESIZE) { + log_error(sprintf(gettext("interface_qinq2_configure interface name too big %s. (max. size: %d).%s"), + $vlanif, IF_NAMESIZE, "\n")); + return; + } - fwrite($fd, "shutdown {$if}h{$tag}:\n"); - fwrite($fd, "mkpeer {$if}qinq: eiface {$if}{$tag} ether\n"); - fwrite($fd, "name {$if}qinq:{$if}{$tag} {$if}h{$tag}\n"); - fwrite($fd, "msg {$if}qinq: addfilter { vlan={$tag} hook=\"{$if}{$tag}\" }\n"); - fwrite($fd, "msg {$if}h{$tag}: setifname \"{$vlanif}\"\n"); - fwrite($fd, "msg {$if}h{$tag}: set {$macaddr}\n"); + exec("/usr/sbin/ngctl shutdown {$ngif}h{$tag}: > /dev/null 2>&1"); + $cmdbuf .= "mkpeer {$ngif}qinq: eiface {$ngif}{$tag} ether\n"; + $cmdbuf .= "name {$ngif}qinq:{$ngif}{$tag} {$ngif}h{$tag}\n"; + $cmdbuf .= "msg {$ngif}qinq: addfilter { vlan={$tag} hook=\"{$ngif}{$tag}\" }\n"; + $cmdbuf .= "msg {$ngif}h{$tag}: setifname \"{$vlanif}\"\n"; + $cmdbuf .= "msg {$ngif}h{$tag}: set {$macaddr}\n"; /* invalidate interface cache */ get_interface_arr(true); diff --git a/src/etc/inc/upgrade_config.inc b/src/etc/inc/upgrade_config.inc index 1eeb4ce795..6983a4d85a 100644 --- a/src/etc/inc/upgrade_config.inc +++ b/src/etc/inc/upgrade_config.inc @@ -5442,6 +5442,35 @@ function upgrade_170_to_171() { } } +/* Upgrade the QinQ interface names to use $if.$tag instead of $if_$tag. + * This helps keep the interface names smaller than the limit (but they are still + * big with the QinQ subtag). + */ +function upgrade_171_to_172() { + global $config; + + if (!is_array($config['qinqs']['qinqentry']) || count($config['qinqs']['qinqentry']) == 0) { + return; + } + $iflist = get_configured_interface_list(true); + foreach ($config['qinqs']['qinqentry'] as $id => $qinq) { + $config['qinqs']['qinqentry'][$id]['vlanif'] = vlan_interface($qinq); + + if (!isset($qinq['members'])) { + continue; + } + foreach (explode(" ", $qinq['members']) as $tag) { + /* Make sure to update the interfaces section with the new name. */ + $vlan_name = "{$qinq['if']}_{$qinq['tag']}_{$tag}"; + foreach ($iflist as $ifname) { + if ($config['interfaces'][$ifname]['if'] == $vlan_name) { + $config['interfaces'][$ifname]['if'] = qinq_interface($qinq, $tag); + } + } + } + } +} + /* * Special function that is called independent of current config version. It's * a workaround to have config_upgrade running on older versions after next diff --git a/src/etc/inc/util.inc b/src/etc/inc/util.inc index ef39ebf592..12cedb5f9d 100644 --- a/src/etc/inc/util.inc +++ b/src/etc/inc/util.inc @@ -2154,6 +2154,7 @@ function is_interface_mismatch() { if (is_array($config['interfaces'])) { foreach ($config['interfaces'] as $ifname => $ifcfg) { if (interface_is_vlan($ifcfg['if']) != NULL || + interface_is_qinq($ifcfg['if']) != NULL || preg_match("/^enc|^cua|^tun|^tap|^l2tp|^pptp|^ppp|^ovpn|^gif|^gre|^lagg|^bridge|vlan|_wlan|_\d{0,4}_\d{0,4}$/i", $ifcfg['if'])) { // Do not check these interfaces. $i++; diff --git a/src/usr/local/www/interfaces_assign.php b/src/usr/local/www/interfaces_assign.php index f949df9439..79d11e398a 100644 --- a/src/usr/local/www/interfaces_assign.php +++ b/src/usr/local/www/interfaces_assign.php @@ -163,8 +163,8 @@ if (is_array($config['qinqs']['qinqentry']) && count($config['qinqs']['qinqentry /* QinQ members */ $qinqifs = explode(' ', $qinq['members']); foreach ($qinqifs as $qinqif) { - $portlist["{$qinq['vlanif']}_{$qinqif}"]['descr'] = "QinQ {$qinqif} on VLAN {$qinq['tag']} on {$qinq['if']}"; - $portlist["{$qinq['vlanif']}_{$qinqif}"]['isqinq'] = true; + $portlist["{$qinq['vlanif']}.{$qinqif}"]['descr'] = "QinQ {$qinqif} on VLAN {$qinq['tag']} on {$qinq['if']}"; + $portlist["{$qinq['vlanif']}.{$qinqif}"]['isqinq'] = true; } } } diff --git a/src/usr/local/www/interfaces_qinq.php b/src/usr/local/www/interfaces_qinq.php index 3fff40bb5f..b2081983bd 100644 --- a/src/usr/local/www/interfaces_qinq.php +++ b/src/usr/local/www/interfaces_qinq.php @@ -35,24 +35,11 @@ if (!is_array($config['qinqs']['qinqentry'])) { $a_qinqs = &$config['qinqs']['qinqentry']; -function qinq_inuse($num) { - global $config, $a_qinqs; - - $iflist = get_configured_interface_list(true); - foreach ($iflist as $if) { - if ($config['interfaces'][$if]['if'] == $a_qinqs[$num]['qinqif']) { - return true; - } - } - - return false; -} - if ($_POST['act'] == "del") { $id = $_POST['id']; /* check if still in use */ - if (qinq_inuse($id)) { + if (isset($a_qinqs) && vlan_inuse($a_qinqs[$id])) { $input_errors[] = gettext("This QinQ cannot be deleted because it is still being used as an interface."); } elseif (empty($a_qinqs[$id]['vlanif']) || !does_interface_exist($a_qinqs[$id]['vlanif'])) { $input_errors[] = gettext("QinQ interface does not exist"); @@ -60,13 +47,24 @@ if ($_POST['act'] == "del") { $qinq =& $a_qinqs[$id]; $delmembers = explode(" ", $qinq['members']); - if (count($delmembers) > 0) { - foreach ($delmembers as $tag) { - mwexec("/usr/sbin/ngctl shutdown {$qinq['vlanif']}h{$tag}:"); + foreach ($delmembers as $tag) { + if (qinq_inuse($qinq, $tag)) { + $input_errors[] = gettext("This QinQ cannot be deleted because one of it tags is still being used as an interface."); + break; } } - mwexec("/usr/sbin/ngctl shutdown {$qinq['vlanif']}qinq:"); - mwexec("/usr/sbin/ngctl shutdown {$qinq['vlanif']}:"); + } + + if (empty($input_errors)) { + $qinq =& $a_qinqs[$id]; + + $ngif = str_replace(".", "_", $qinq['vlanif']); + $delmembers = explode(" ", $qinq['members']); + foreach ($delmembers as $tag) { + mwexec("/usr/sbin/ngctl shutdown {$ngif}h{$tag}: > /dev/null 2>&1"); + } + mwexec("/usr/sbin/ngctl shutdown {$ngif}qinq: > /dev/null 2>&1"); + mwexec("/usr/sbin/ngctl shutdown {$ngif}: > /dev/null 2>&1"); pfSense_interface_destroy($qinq['vlanif']); unset($a_qinqs[$id]); diff --git a/src/usr/local/www/interfaces_qinq_edit.php b/src/usr/local/www/interfaces_qinq_edit.php index e75d02b692..95fa3c974e 100644 --- a/src/usr/local/www/interfaces_qinq_edit.php +++ b/src/usr/local/www/interfaces_qinq_edit.php @@ -143,10 +143,22 @@ if ($_POST['save']) { $input_errors[] = gettext("At least one tag must be entered."); } + $nmembers = explode(" ", $members); + if (isset($id) && $a_qinqs[$id]) { + $omembers = explode(" ", $a_qinqs[$id]['members']); + $delmembers = array_diff($omembers, $nmembers); + foreach ($delmembers as $tag) { + if (qinq_inuse($a_qinqs[$id], $tag)) { + $input_errors[] = gettext("This QinQ tag cannot be deleted because it is still being used as an interface."); + break; + } + } + } + if (!$input_errors) { $qinqentry['members'] = $members; $qinqentry['descr'] = $_POST['descr']; - $qinqentry['vlanif'] = "{$_POST['if']}_{$_POST['tag']}"; + $qinqentry['vlanif'] = vlan_interface($_POST); $nmembers = explode(" ", $members); if (isset($id) && $a_qinqs[$id]) { @@ -155,22 +167,29 @@ if ($_POST['save']) { $addmembers = array_diff($nmembers, $omembers); if ((count($delmembers) > 0) || (count($addmembers) > 0)) { - $fd = fopen("{$g['tmp_path']}/netgraphcmd", "w"); foreach ($delmembers as $tag) { - fwrite($fd, "shutdown {$qinqentry['vlanif']}h{$tag}:\n"); - fwrite($fd, "msg {$qinqentry['vlanif']}qinq: delfilter \\\"{$qinqentry['vlanif']}{$tag}\\\"\n"); + $ngif = str_replace(".", "_", $qinqentry['vlanif']); + exec("/usr/sbin/ngctl shutdown {$ngif}h{$tag}: > /dev/null 2>&1"); + exec("/usr/sbin/ngctl msg {$ngif}qinq: delfilter \\\"{$ngif}{$tag}\\\" > /dev/null 2>&1"); } + $qinqcmdbuf = ""; foreach ($addmembers as $member) { $qinq = array(); $qinq['if'] = $qinqentry['vlanif']; $qinq['tag'] = $member; $macaddr = get_interface_mac($qinqentry['vlanif']); - interface_qinq2_configure($qinq, $fd, $macaddr); + interface_qinq2_configure($qinq, $qinqcmdbuf, $macaddr); } - fclose($fd); - mwexec("/usr/sbin/ngctl -f {$g['tmp_path']}/netgraphcmd"); + if (strlen($qinqcmdbuf) > 0) { + $fd = fopen("{$g['tmp_path']}/netgraphcmd", "w"); + if ($fd) { + fwrite($fd, $qinqcmdbuf); + fclose($fd); + mwexec("/usr/sbin/ngctl -f {$g['tmp_path']}/netgraphcmd > /dev/null 2>&1"); + } + } } $a_qinqs[$id] = $qinqentry; } else { @@ -189,7 +208,7 @@ if ($_POST['save']) { } $additions = ""; foreach ($nmembers as $qtag) { - $additions .= "{$qinqentry['vlanif']}_{$qtag} "; + $additions .= qinq_interface($qinqentry, $qtag) . " "; } $additions .= "{$qinqentry['vlanif']}"; if ($found == true) { diff --git a/src/usr/local/www/interfaces_vlan.php b/src/usr/local/www/interfaces_vlan.php index d944616e4f..6510e3abae 100644 --- a/src/usr/local/www/interfaces_vlan.php +++ b/src/usr/local/www/interfaces_vlan.php @@ -38,26 +38,13 @@ if (!is_array($config['vlans']['vlan'])) { $a_vlans = &$config['vlans']['vlan'] ; -function vlan_inuse($num) { - global $config, $a_vlans; - - $iflist = get_configured_interface_list(true); - foreach ($iflist as $if) { - if ($config['interfaces'][$if]['if'] == $a_vlans[$num]['vlanif']) { - return true; - } - } - - return false; -} - if ($_POST['act'] == "del") { if (!isset($_POST['id'])) { $input_errors[] = gettext("Wrong parameters supplied"); } else if (empty($a_vlans[$_POST['id']])) { $input_errors[] = gettext("Wrong index supplied"); /* check if still in use */ - } else if (vlan_inuse($_POST['id'])) { + } else if (vlan_inuse($a_vlans[$_POST['id']])) { $input_errors[] = gettext("This VLAN cannot be deleted because it is still being used as an interface."); } else { if (does_interface_exist($a_vlans[$_POST['id']]['vlanif'])) {