diff --git a/net/relayd/Makefile b/net/relayd/Makefile index d16c606a3..c94f25e2a 100644 --- a/net/relayd/Makefile +++ b/net/relayd/Makefile @@ -1,6 +1,6 @@ PLUGIN_NAME= relayd -PLUGIN_VERSION= 2.5 -PLUGIN_REVISION= 2 +PLUGIN_VERSION= 2.6 +#PLUGIN_REVISION= 2 PLUGIN_DEPENDS= relayd PLUGIN_COMMENT= Relayd Load Balancer PLUGIN_MAINTAINER= frank.brendel@eurolog.com diff --git a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/ServiceController.php b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/ServiceController.php index d255473d4..83eda0132 100644 --- a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/ServiceController.php +++ b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/ServiceController.php @@ -2,6 +2,7 @@ /* * Copyright (C) 2018 EURO-LOG AG + * Copyright (c) 2021 Deciso B.V. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -98,10 +99,9 @@ class ServiceController extends ApiMutableServiceControllerBase $this->sessionClose(); $result['function'] = "reconfigure"; $result['status'] = 'failed'; - $mdlRelayd = new Relayd(); $backend = new Backend(); $status = $this->statusAction(); - if ($mdlRelayd->general->enabled->__toString() == 1) { + if (!empty((string)$this->getModel()->general->enabled)) { $result = $this->configtestAction(); if ($result['template'] == 'OK' && preg_match('/configuration OK$/', $result['result']) == 1) { if ($status['status'] != 'running') { @@ -118,8 +118,7 @@ class ServiceController extends ApiMutableServiceControllerBase } } $this->lock(1); - $mdlRelayd = new Relayd(); - if ($mdlRelayd->configClean()) { + if ($this->getModel()->configClean()) { $result['status'] = 'ok'; } return $result; diff --git a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/SettingsController.php b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/SettingsController.php index 67b0ff009..0f71cf62c 100644 --- a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/SettingsController.php +++ b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/SettingsController.php @@ -2,7 +2,7 @@ /** * Copyright (C) 2018 EURO-LOG AG - * + * Copyright (c) 2021 Deciso B.V. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,7 +30,7 @@ namespace OPNsense\Relayd\Api; -use OPNsense\Base\ApiControllerBase; +use OPNsense\Base\ApiMutableModelControllerBase; use OPNsense\Core\Config; use OPNsense\Relayd\Relayd; use OPNsense\Base\UIModelGrid; @@ -39,25 +39,17 @@ use OPNsense\Base\UIModelGrid; * Class SettingsController * @package OPNsense\Relayd */ -class SettingsController extends ApiControllerBase +class SettingsController extends ApiMutableModelControllerBase { protected static $internalModelName = 'relayd'; protected static $internalModelClass = '\OPNsense\Relayd\Relayd'; - public $mdlRelayd = null; /** * list with valid model node types */ private $nodeTypes = array('general', 'host', 'tablecheck', 'table', 'protocol', 'virtualserver'); - /** - * initialize object properties - */ - public function onConstruct() - { - $this->mdlRelayd = new Relayd(); - } /** * check if changes to the relayd settings were made @@ -66,7 +58,7 @@ class SettingsController extends ApiControllerBase public function dirtyAction() { $result = array('status' => 'ok'); - $result['relayd']['dirty'] = $this->mdlRelayd->configChanged(); + $result['relayd']['dirty'] = $this->getModel()->configChanged(); return $result; } @@ -82,12 +74,12 @@ class SettingsController extends ApiControllerBase if ($this->request->isGet() && $nodeType != null) { $this->validateNodeType($nodeType); if ($nodeType == 'general') { - $node = $this->mdlRelayd->getNodeByReference($nodeType); + $node = $this->getModel()->getNodeByReference($nodeType); } else { if ($uuid != null) { - $node = $this->mdlRelayd->getNodeByReference($nodeType . '.' . $uuid); + $node = $this->getModel()->getNodeByReference($nodeType . '.' . $uuid); } else { - $node = $this->mdlRelayd->$nodeType->Add(); + $node = $this->getModel()->$nodeType->Add(); } } if ($node != null) { @@ -111,12 +103,12 @@ class SettingsController extends ApiControllerBase if ($this->request->isPost() && $this->request->hasPost('relayd') && $nodeType != null) { $this->validateNodeType($nodeType); if ($nodeType == 'general') { - $node = $this->mdlRelayd->getNodeByReference($nodeType); + $node = $this->getModel()->getNodeByReference($nodeType); } else { if ($uuid != null) { - $node = $this->mdlRelayd->getNodeByReference($nodeType . '.' . $uuid); + $node = $this->getModel()->getNodeByReference($nodeType . '.' . $uuid); } else { - $node = $this->mdlRelayd->$nodeType->Add(); + $node = $this->getModel()->$nodeType->Add(); } } if ($node != null) { @@ -207,16 +199,16 @@ class SettingsController extends ApiControllerBase } $node->setNodes($relaydInfo[$nodeType]); - $valMsgs = $this->mdlRelayd->performValidation(); + $valMsgs = $this->getModel()->performValidation(); foreach ($valMsgs as $field => $msg) { $fieldnm = str_replace($node->__reference, "relayd." . $nodeType, $msg->getField()); $result["validations"][$fieldnm] = $msg->getMessage(); } if (empty($result["validations"])) { unset($result["validations"]); - $this->mdlRelayd->serializeToConfig(); + $this->getModel()->serializeToConfig(); $cfgRelayd = Config::getInstance()->save(); - if ($this->mdlRelayd->configDirty()) { + if ($this->getModel()->configDirty()) { $result['status'] = 'ok'; } } @@ -234,13 +226,14 @@ class SettingsController extends ApiControllerBase public function delAction($nodeType = null, $uuid = null) { $result = array("result" => "failed"); + Config::getInstance()->lock(); if ($nodeType != null) { $this->validateNodeType($nodeType); if ($uuid != null) { - $node = $this->mdlRelayd->getNodeByReference($nodeType . '.' . $uuid); + $node = $this->getModel()->getNodeByReference($nodeType . '.' . $uuid); if ($node != null) { - $nodeName = $this->mdlRelayd->getNodeByReference($nodeType . '.' . $uuid . '.name')->__toString(); - if ($this->mdlRelayd->$nodeType->del($uuid) == true) { + $nodeName = $this->getModel()->getNodeByReference($nodeType . '.' . $uuid . '.name')->__toString(); + if ($this->getModel()->$nodeType->del($uuid) == true) { // delete relations switch ($nodeType) { case 'host': @@ -250,7 +243,7 @@ class SettingsController extends ApiControllerBase $uuid, 'host', $nodeName, - $this->mdlRelayd + $this->getModel() ); break; case 'tablecheck': @@ -260,7 +253,7 @@ class SettingsController extends ApiControllerBase $uuid, 'tablecheck', $nodeName, - $this->mdlRelayd + $this->getModel() ); $this->deleteRelations( 'virtualserver', @@ -268,7 +261,7 @@ class SettingsController extends ApiControllerBase $uuid, 'tablecheck', $nodeName, - $this->mdlRelayd + $this->getModel() ); break; case 'table': @@ -278,7 +271,7 @@ class SettingsController extends ApiControllerBase $uuid, 'table', $nodeName, - $this->mdlRelayd + $this->getModel() ); $this->deleteRelations( 'virtualserver', @@ -286,7 +279,7 @@ class SettingsController extends ApiControllerBase $uuid, 'table', $nodeName, - $this->mdlRelayd + $this->getModel() ); break; case 'protocol': @@ -296,13 +289,13 @@ class SettingsController extends ApiControllerBase $uuid, 'protocol', $nodeName, - $this->mdlRelayd + $this->getModel() ); break; } - $this->mdlRelayd->serializeToConfig(); + $this->getModel()->serializeToConfig(); Config::getInstance()->save(); - if ($this->mdlRelayd->configDirty()) { + if ($this->getModel()->configDirty()) { $result['status'] = 'ok'; } } @@ -312,6 +305,21 @@ class SettingsController extends ApiControllerBase return $result; } + /** + * toggle status + * @param string $nodeType node type to address + * @param string $uuid id to toggled + * @param string|null $enabled set enabled by default + * @return array status + * @throws \Phalcon\Validation\Exception when field validations fail + * @throws \ReflectionException when not bound to model + */ + public function toggleAction($nodeType, $uuid, $enabled = null) + { + $this->getModel()->configDirty(); + return $this->toggleBase($nodeType, $uuid, $enabled); + } + /** * search relayd settings * @param $nodeType @@ -322,11 +330,11 @@ class SettingsController extends ApiControllerBase $this->sessionClose(); if ($this->request->isPost() && $nodeType != null) { $this->validateNodeType($nodeType); - $grid = new UIModelGrid($this->mdlRelayd->$nodeType); + $grid = new UIModelGrid($this->getModel()->$nodeType); $fields = array(); switch ($nodeType) { case 'host': - $fields = array('name', 'address'); + $fields = array('enabled', 'name', 'address'); break; case 'tablecheck': $fields = array('name', 'type'); @@ -342,7 +350,7 @@ class SettingsController extends ApiControllerBase break; } $result = $grid->fetchBindRequest($this->request, $fields); - $result['dirty'] = $this->mdlRelayd->configChanged(); + $result['dirty'] = $this->getModel()->configChanged(); return $result; } } @@ -374,7 +382,7 @@ class SettingsController extends ApiControllerBase $relNodeType = null, $relNodeName = null ) { - $nodes = $this->mdlRelayd->$nodeType->getNodes(); + $nodes = $this->getModel()->$nodeType->getNodes(); // get nodes with relations foreach ($nodes as $nodeUuid => $node) { // get relation uuids @@ -382,14 +390,14 @@ class SettingsController extends ApiControllerBase // remove uuid from field if ($fieldUuid == $relUuid) { $refField = $nodeType . '.' . $nodeUuid . '.' . $nodeField; - $relNode = $this->mdlRelayd->getNodeByReference($refField); + $relNode = $this->getModel()->getNodeByReference($refField); $nodeRels = str_replace($relUuid, '', $relNode->__toString()); $nodeRels = str_replace(',,', ',', $nodeRels); $nodeRels = rtrim($nodeRels, ','); $nodeRels = ltrim($nodeRels, ','); - $this->mdlRelayd->setNodeByReference($refField, $nodeRels); + $this->getModel()->setNodeByReference($refField, $nodeRels); if ($relNode->isEmptyAndRequired()) { - $nodeName = $this->mdlRelayd->getNodeByReference("{$nodeType}.{$nodeUuid}.name")->__toString(); + $nodeName = $this->getModel()->getNodeByReference("{$nodeType}.{$nodeUuid}.name")->__toString(); throw new \Exception("Cannot delete $relNodeType '$relNodeName' from $nodeType '$nodeName'"); } } diff --git a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/StatusController.php b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/StatusController.php index deb9a75e6..dfd721fcb 100644 --- a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/StatusController.php +++ b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/Api/StatusController.php @@ -2,6 +2,7 @@ /** * Copyright (C) 2018 EURO-LOG AG + * Copyright (c) 2021 Deciso B.V. * * All rights reserved. * @@ -32,6 +33,7 @@ namespace OPNsense\Relayd\Api; use OPNsense\Base\ApiControllerBase; use OPNsense\Core\Backend; +use OPNsense\Core\Config; use OPNsense\Relayd\Relayd; /** @@ -43,15 +45,32 @@ class StatusController extends ApiControllerBase /** * get relayd summary */ - public function sumAction() + public function sumAction($wait=0) { $result = array("result" => "failed"); $backend = new Backend(); + $relaydMdl = new Relayd(); + + // when $wait is set, try for max 10 seconds to receive a sensible status (wait for unknowns to resolve) + $max_tries = !empty($wait) ? 10 : 1; $output = array(); - $output = explode("\n", trim($backend->configdRun('relayd summary'))); + for ($i = 0; $i < $max_tries; $i++) { + $output = explode("\n", trim($backend->configdRun('relayd summary'))); + $unknowns = 0; + foreach ($output as $line) { + if (substr($line, -strlen("unknown")) == "unknown") { + $unknowns++; + } + } + if (!empty($output[0]) && $unknowns == 0) { + break; + } + sleep(1); + } if (empty($output[0])) { return $result; } + $output[] = "0\t****\t"; // end of data marker $result["result"] = 'ok'; $virtualServerId = 0; $virtualServerType = ''; @@ -59,40 +78,119 @@ class StatusController extends ApiControllerBase $virtualserver = array(); $rows = array(); foreach ($output as $line) { - $words = explode("\t", $line); - $id = trim($words[0]); - $type = trim($words[1]); - if ($type == 'redirect' || $type == 'relay') { + $words = array_map('trim', explode("\t", $line)); + $id = $words[0]; + $type = $words[1]; + if ($type == 'redirect' || $type == 'relay' || $type == '****') { // new virtual server id/type means new record if ( - ($id != $virtualServerId - && $virtualServerId > 0) - || ($type != $virtualServerType - && strlen($virtualServerType) > 5) + ($id != $virtualServerId && $virtualServerId > 0) || + ($type != $virtualServerType && strlen($virtualServerType) > 5) || + ($type == '****' && !empty($virtualserver)) ) { + // append backend hosts not found in the list, since relayd only supports disabled tables + // you might loose track of hosts that are disabled + if (!empty($virtualserver['tables'])) { + foreach ($virtualserver['tables'] as &$table) { + if (!empty($table['uuid'])) { + $tblnode = $relaydMdl->getNodeByReference("table.".$table['uuid']); + foreach (explode(",", (string)$tblnode->hosts) as $host_uuid) { + $found = false; + if (!empty($table['hosts'])) { + foreach ($table['hosts'] as $tblhost) { + foreach ($tblhost['properties'] as $hprops) { + if ($hprops['uuid'] == $host_uuid) { + $found = true; + } + } + } + } else { + $table['hosts'] = []; + } + if (!$found) { + $hostnode = $relaydMdl->getNodeByReference("host.".$host_uuid); + $table['hosts'][$host_uuid] = [ + "name" => (string)$hostnode->address, + "description" => (string)$hostnode->name, + "avlblty" => null, + "status" => empty((string)$hostnode->enabled) ? "disabled" : "-", + "properties" => [ + [ + "uuid" => $host_uuid, + "name" => (string)$hostnode->name, + "enabled" => (string)$hostnode->enabled + ] + ] + ]; + } + } + } + } + } $rows[] = $virtualserver; - $virtualserver = array(); + if ($type == '****') { + break; // end + } + $virtualserver = []; } $virtualServerId = $id; $virtualServerType = $type; $virtualserver['id'] = $id; $virtualserver['type'] = $type; - $virtualserver['name'] = trim($words[2]); - $virtualserver['status'] = trim($words[4]); - } - if ($type == 'table') { + $virtualserver['name'] = $words[2]; + $virtualserver['status'] = $words[4]; + $objs = $relaydMdl->getObjectsByAttribute("virtualserver", "name", $virtualserver['name']); + if (count($objs) > 0) { + $obj = $objs[0]; + $virtualserver['uuid'] = $obj->getAttribute('uuid'); + $virtualserver['listen_address'] = (string)$obj->listen_address; + $virtualserver['listen_startport'] = (string)$obj->listen_startport; + $virtualserver['listen_endport'] = (string)$obj->listen_endport; + } + } elseif ($type == 'table') { $tableId = $id; - $virtualserver['tables'][$tableId]['name'] = trim($words[2]); - $virtualserver['tables'][$tableId]['status'] = trim($words[4]); - } - if ($type == 'host') { + if (empty($virtualserver['tables'])) { + $virtualserver['tables'] = []; + } + $virtualserver['tables'][$tableId] = []; + $virtualserver['tables'][$tableId]['name'] = $words[2]; + $virtualserver['tables'][$tableId]['status'] = $words[4]; + $objs = $relaydMdl->getObjectsByAttribute("table", "name", explode(":", $words[2])[0]); + if (count($objs) > 0) { + $virtualserver['tables'][$tableId]['uuid'] = $objs[0]->getAttribute('uuid'); + } + } elseif ($type == 'host') { $hostId = trim($words[0]); - $virtualserver['tables'][$tableId]['hosts'][$hostId]['name'] = trim($words[2]); - $virtualserver['tables'][$tableId]['hosts'][$hostId]['avlblty'] = trim($words[3]); - $virtualserver['tables'][$tableId]['hosts'][$hostId]['status'] = trim($words[4]); + if (empty($virtualserver['tables'][$tableId]['hosts'])) { + $virtualserver['tables'][$tableId]['hosts'] = []; + } + $virtualserver['tables'][$tableId]['hosts'][$hostId] = ['properties' => []]; + $virtualserver['tables'][$tableId]['hosts'][$hostId]['name'] = $words[2]; + $virtualserver['tables'][$tableId]['hosts'][$hostId]['avlblty'] = $words[3]; + $status = $words[4] == 'disabled' ? 'stopped' : $words[4]; + $virtualserver['tables'][$tableId]['hosts'][$hostId]['status'] = $status; + // XXX: `relayctl show summary` name is actually the number, append name as description when found + $objs = $relaydMdl->getObjectsByAttribute("host", "address", $words[2]); + if (count($objs) > 0) { + $linked_hosts = []; + if (!empty($virtualserver['tables'][$tableId]['uuid'])) { + $tblnode = $relaydMdl->getNodeByReference("table.".$virtualserver['tables'][$tableId]['uuid']); + $linked_hosts = explode(",", (string)$tblnode->hosts); + } + // hosts aren't necessarily unique due to address matching + foreach ($objs as $obj) { + $this_uuid = $obj->getAttribute('uuid'); + if (empty($linked_hosts) || in_array($this_uuid, $linked_hosts)) { + $virtualserver['tables'][$tableId]['hosts'][$hostId]['properties'][] = [ + 'uuid' => $this_uuid, + 'name' => (string)$obj->name, + "enabled" => (string)$obj->enabled + ]; + } + } + } } } - $rows[] = $virtualserver; $result["rows"] = $rows; return $result; } @@ -102,29 +200,32 @@ class StatusController extends ApiControllerBase */ public function toggleAction($nodeType = null, $id = null, $action = null) { + $result = array("result" => "failed", "function" => "toggle"); if ($this->request->isPost()) { $this->sessionClose(); - } - $result = array("result" => "failed", "function" => "toggle"); - if ( - $nodeType != null && - ($nodeType == 'redirect' || - $nodeType == 'table' || - $nodeType == 'host') - ) { - if ( - $action != null && - ($action == 'enable' || - $action == 'disable') - ) { + $backend = new Backend(); + if (in_array($nodeType, ['redirect', 'table', 'host']) && in_array($action, ['enable', 'disable'])){ if ($id != null && $id > 0) { - $backend = new Backend(); - $result["output"] = $backend->configdRun("relayd toggle $nodeType $action $id"); + $result["output"] = $backend->configdpRun("relayd toggle",[$nodeType, $action, $id]); if (isset($result["output"])) { $result["result"] = 'ok'; } $result["output"] = trim($result["output"]); } + } elseif ($nodeType == 'host' && in_array($action, ['remove', 'add'])) { + Config::getInstance()->lock(); + $new_status = $action == "remove" ? "0" : "1"; + $relaydMdl = new Relayd(); + foreach (explode(",", $id) as $host_uuid) { + $obj = $relaydMdl->getNodeByReference("host.".$host_uuid); + if ($obj != null) { + $obj->enabled = $new_status; + } + } + $relaydMdl->serializeToConfig(); + Config::getInstance()->save(); + // invoke service controller + return (new ServiceController())->reconfigureAction(); } } return $result; diff --git a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/forms/host.xml b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/forms/host.xml index 1c6967ae0..09dcea4fa 100644 --- a/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/forms/host.xml +++ b/net/relayd/src/opnsense/mvc/app/controllers/OPNsense/Relayd/forms/host.xml @@ -1,4 +1,10 @@