Routing, gateways. more work on gateway priorities for https://github.com/opnsense/core/issues/2279

- getGateways() is cached now and returns an ordered list of gateways, highest priority first
- getDefaultGW() returns the default gateway for the selected ipproto, excluding a list of down gateways. when no default is found, other gateways ordered by priority are considered
- gatewaysIndexedByName() is a drop in replacement for return_gateways_array()
This commit is contained in:
Ad Schellevis 2019-04-10 17:55:23 +02:00
parent 0dddfd14cd
commit da5f3cb175

View File

@ -38,7 +38,9 @@ use \OPNsense\Firewall\Util;
class Gateways
{
var $configHandle = null;
var $gatewaySeq = 0;
var $ifconfig = array();
var $cached_gateways = array();
/**
* Construct new gateways object
@ -54,6 +56,7 @@ class Gateways
public function setIfconfig($payload)
{
$this->ifconfig = $payload;
return $this;
}
/**
@ -76,78 +79,219 @@ class Gateways
return $result;
}
/**
* return the type of the interface, for backwards compatibility
* @param string $ipproto inet/inet6 type
* @param array $ifcfg
* @return string type name
*/
private static function convertType($ipproto, $ifcfg)
{
if (!empty($ifcfg['if'])) {
if ($ipproto == "inet") {
if (substr($ifcfg['if'], 0, 5) == "ovpnc") {
return "VPNv4";
} elseif (in_array(substr($ifcfg['if'], 0, 3), array('gif', 'gre'))) {
return "TUNNELv4";
}
} elseif ($ipproto == "inet6" && !empty($ifcfg['if'])) {
if (substr($ifcfg['if'], 0, 5) == "ovpnc") {
return 'VPNv6';
} elseif (in_array(substr($ifcfg['if'], 0, 3), array('gif', 'gre'))) {
return 'TUNNELv6';
}
}
}
// default
if ($ipproto == "inet") {
return !empty($ifcfg['ipaddr']) && !Util::isIpAddress($ifcfg['ipaddr']) ? $ifcfg['ipaddr'] : null;
} else {
return !empty($ifcfg['ipaddrv6']) && !Util::isIpAddress($ifcfg['ipaddrv6']) ? $ifcfg['ipaddrv6'] : null;
}
}
/**
* generate new sort key for a gateway
* @param string|int $prio priority
* @return string key
*/
private function newKey($prio)
{
if (empty($this->cached_gateways)) {
$this->gatewaySeq = 1;
}
return sprintf("%05d%010d", $prio, $this->gatewaySeq++);
}
/**
* return all defined gateways
* @return array
*/
public function getGateways()
{
$result = array();
$definedIntf = $this->getDefinedInterfaces();
$dynamic_gw = array();
$gatewaySeq = 1;
// iterate configured gateways
if (!empty($this->configHandle->gateways)) {
foreach ($this->configHandle->gateways->children() as $tag => $gateway) {
$gw_arr = array();
foreach ($gateway as $key => $value) {
$gw_arr[(string)$key] = (string)$value;
}
$gw_arr['priority'] = 1; // XXX define in gateway
if ($tag == "gateway_item") {
$gw_arr["if"] = $definedIntf[$gw_arr["interface"]]['if'];
if (Util::isIpAddress($gateway->gateway)) {
$gwkey = sprintf("%d%010d", $gw_arr['priority'], $gatewaySeq);
$result[$gwkey] = $gw_arr;
} else {
// dynamic gateways might have settings, temporary store
if (empty($dynamic_gw[(string)$gateway->interface])) {
$dynamic_gw[(string)$gateway->interface] = array();
if (empty($this->cached_gateways)) {
// results are cached within this object
$definedIntf = $this->getDefinedInterfaces();
$dynamic_gw = array();
$gatewaySeq = 1;
$i=0; // sequence used in legacy edit form (item in the list)
// iterate configured gateways
if (!empty($this->configHandle->gateways)) {
foreach ($this->configHandle->gateways->children() as $tag => $gateway) {
if ($tag == "gateway_item") {
$gw_arr = array();
foreach ($gateway as $key => $value) {
$gw_arr[(string)$key] = (string)$value;
}
if (empty($gw_arr['priority'])) {
// default priority
$gw_arr['priority'] = 1;
}
$gw_arr["if"] = $definedIntf[$gw_arr["interface"]]['if'];
$gw_arr["attribute"] = $i++;
if (Util::isIpAddress($gateway->gateway)) {
if (empty($gw_arr['monitor_disable']) && empty($gw_arr['monitor'])) {
$gw_arr['monitor'] = $gw_arr['gateway'];
}
$this->cached_gateways[$this->newKey($gw_arr['priority'])] = $gw_arr;
} else {
// dynamic gateways might have settings, temporary store
if (empty($dynamic_gw[(string)$gateway->interface])) {
$dynamic_gw[(string)$gateway->interface] = array();
}
$dynamic_gw[(string)$gateway->interface][] = $gw_arr;
}
$dynamic_gw[(string)$gateway->interface][] = $gw_arr;
}
}
$gatewaySeq++;
}
}
// add dynamic gateways
foreach ($definedIntf as $ifname => $ifcfg) {
foreach (["inet", "inet6"] as $ipproto) {
// filename suffix and interface type as defined in the interface
$fsuffix = $ipproto == "inet6" ? "v6" : "";
$ctype = $ipproto == "inet" ? $ifcfg['ipaddr'] : $ifcfg['ipaddrv6'];
// default configuration, when not set in gateway_item
$thisconf = [
"priority" => 1,
"interface" => $ifname,
"weight" => 1,
"ipprotocol" => $ipproto,
"name" => strtoupper("{$ifname}_{$ctype}"),
"descr" => "Interface " . strtoupper("{$ifname}_{$ctype}") . " Gateway",
"if" => $ifcfg['if'],
"defaultgw" => file_exists("/tmp/{$ifcfg['if']}_defaultgw".$fsuffix)
];
// locate interface gateway settings
if (!empty($dynamic_gw[$ifname])) {
foreach ($dynamic_gw[$ifname] as $gw_arr) {
if ($gw_arr['ipprotocol'] == $ipproto) {
// dynamic gateway for this ip protocol found, use config
$thisconf = $gw_arr;
break;
}
// add dynamic gateways
foreach ($definedIntf as $ifname => $ifcfg) {
foreach (["inet", "inet6"] as $ipproto) {
// filename suffix and interface type as defined in the interface
$fsuffix = $ipproto == "inet6" ? "v6" : "";
$ctype = self::convertType($ipproto, $ifcfg);
$ctype = $ctype != null ? $ctype : "GW";
// default configuration, when not set in gateway_item
$thisconf = [
"priority" => 1,
"interface" => $ifname,
"weight" => 1,
"ipprotocol" => $ipproto,
"name" => strtoupper("{$ifname}_{$ctype}"),
"descr" => "Interface " . strtoupper("{$ifname}_{$ctype}") . " Gateway",
"if" => $ifcfg['if']
];
if (file_exists("/tmp/{$ifcfg['if']}_defaultgw".$fsuffix)) {
$thisconf["defaultgw"] = true;
}
// locate interface gateway settings
if (!empty($dynamic_gw[$ifname])) {
foreach ($dynamic_gw[$ifname] as $gw_arr) {
if ($gw_arr['ipprotocol'] == $ipproto) {
// dynamic gateway for this ip protocol found, use config
$thisconf = $gw_arr;
break;
}
}
}
$thisconf['dynamic'] = true;
// dynamic gateways dump their address in /tmp/[IF]_router[FSUFFIX]
if (file_exists("/tmp/{$ifcfg['if']}_router".$fsuffix)) {
$thisconf['gateway'] = trim(@file_get_contents("/tmp/{$ifcfg['if']}_router".$fsuffix));
if (empty($thisconf['monitor_disable']) && empty($thisconf['monitor'])) {
$thisconf['monitor'] = $thisconf['gateway'];
}
$this->cached_gateways[$this->newKey($gw_arr['priority'])] = $thisconf;
} elseif (!empty($this->ifconfig[$thisconf["if"]]["tunnel"]["dest_addr"])) {
// tunnel devices with a known endpoint
$thisconf['gateway'] = $this->ifconfig[$thisconf["if"]]["tunnel"]["dest_addr"];
$tunnel_ipproto = strpos($thisconf['gateway'], ":") != false ? "inet6" : "inet";
if ($tunnel_ipproto == $ipproto) {
if (empty($thisconf['monitor_disable']) && empty($thisconf['monitor'])) {
$thisconf['monitor'] = $thisconf['gateway'];
}
$this->cached_gateways[$this->newKey($gw_arr['priority'])] = $thisconf;
}
} elseif (self::convertType($ipproto, $ifcfg) != null) {
// other predefined types, only bound by interface (e.g. openvpn)
$this->cached_gateways[$this->newKey($gw_arr['priority'])] = $thisconf;
}
}
// dynamic gateways dump their addres in /tmp/[IF]_router[FSUFFIX]
if (file_exists("/tmp/{$ifcfg['if']}_router".$fsuffix)) {
$thisconf['gateway'] = trim(@file_get_contents("/tmp/{$ifcfg['if']}_router".$fsuffix));
$gwkey = sprintf("%d%010d", $gw_arr['priority'], $gatewaySeq);
$result[$gwkey] = $thisconf;
$gatewaySeq++;
}
// add loopback
$this->cached_gateways[$this->newKey(0)] = [
'name' => 'Null4',
'if' => 'lo0',
'interface' => 'loopback',
'ipprotocol' => 'inet',
'gateway' => '127.0.0.1',
'is_loopback' => true
];
$this->cached_gateways[$this->newKey(0)] = [
'name' => 'Null6',
'if' => 'lo0',
'interface' => 'loopback',
'ipprotocol' => 'inet6',
'gateway' => '::1',
'is_loopback' => true
];
// sort by priority
krsort($this->cached_gateways);
}
return $this->cached_gateways;
}
/**
* determine default gateway, exclude gateways in skip list
* @param array|null $skip list of gateways to ignore
* @param string $ipproto inet/inet6 type
* @return string type name
*/
public function getDefaultGW($skip=null, $ipproto='inet')
{
$othergw = null;
foreach ($this->getGateways() as $gateway) {
if ($gateway['ipprotocol'] == $ipproto) {
if (is_array($skip) && in_array($gateway['name'], $skip)) {
continue;
} elseif (!empty($gateway['disabled'])) {
continue;
}
if (!empty($gateway['gateway'])) {
if (!empty($gateway['defaultgw'])) {
return $gateway;
} elseif ($othergw == null) {
$othergw = $gateway;
}
}
}
}
// sort by priority
ksort($result);
return $othergw;
}
/**
* determine default gateway, exclude gateways in skip list
* @param bool $disabled return disabled gateways
* @param bool $localhost inet/inet6 type
* @return string type name
*/
public function gatewaysIndexedByName($disabled=false, $localhost=false, $inactive=false)
{
$result = array();
foreach ($this->getGateways() as $gateway) {
$intf = $gateway['interface'];
if (!empty($gateway['disabled']) && !$disabled) {
continue;
}
if (!empty($gateway['is_loopback']) && !$localhost) {
continue;
}
if (empty($gateway['is_loopback']) && empty($gateway['if']) && !$inactive){
continue;
}
$result[$gateway['name']] = $gateway;
}
return $result;
}