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
This commit is contained in:
Luiz Souza 2017-10-17 17:36:05 -02:00
parent 93d3a06526
commit 0793de1aad
8 changed files with 215 additions and 80 deletions

View File

@ -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",

View File

@ -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);

View File

@ -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

View File

@ -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++;

View File

@ -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;
}
}
}

View File

@ -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]);

View File

@ -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) {

View File

@ -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'])) {