From d8cde4655813808f1e958f2a1928d58e9ee8cd52 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 14 May 2017 17:34:44 +0200 Subject: [PATCH] Quagga: Add AS-Path Lists and Route-Maps to BGP (#157) * Create dialogEditBGPASPath.xml * Create dialogEditBGPRoutemaps.xml * Rename dialogEditBGPRoutemaps.xml to dialogEditBGPRouteMaps.xml * Update BgpController.php * Update bgpd.conf * Update bgp.volt * Update bgp.xml * Update BGP.xml * Update dialogEditBGPASPath.xml * Update dialogEditBGPNeighbor.xml * Update dialogEditBGPRouteMaps.xml Comment from @fabianfrz: Deduplicated commit message lines here --- .../OPNsense/Quagga/Api/BgpController.php | 207 +++++++++++++++++- .../OPNsense/Quagga/BgpController.php | 2 + .../controllers/OPNsense/Quagga/forms/bgp.xml | 2 +- .../Quagga/forms/dialogEditBGPASPath.xml | 26 +++ .../Quagga/forms/dialogEditBGPNeighbor.xml | 12 + .../Quagga/forms/dialogEditBGPRouteMaps.xml | 40 ++++ .../mvc/app/models/OPNsense/Quagga/BGP.xml | 98 ++++++++- .../mvc/app/views/OPNsense/Quagga/bgp.volt | 82 ++++++- .../templates/OPNsense/Quagga/bgpd.conf | 44 ++++ 9 files changed, 507 insertions(+), 6 deletions(-) create mode 100644 net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPASPath.xml create mode 100644 net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPRouteMaps.xml diff --git a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php index 488cad051..c71eb3cb2 100644 --- a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php +++ b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/Api/BgpController.php @@ -81,7 +81,7 @@ class BgpController extends ApiMutableModelControllerBase $grid = new UIModelGrid($mdlBGP->neighbors->neighbor); return $grid->fetchBindRequest( $this->request, - array("enabled", "address", "remoteas", "updatesource", "nexthopself", "defaultoriginate" ) + array("enabled", "address", "remoteas", "updatesource", "nexthopself", "defaultoriginate", "linkedRoutemapIn", "linkedRoutemapOut" ) ); } @@ -119,6 +119,7 @@ class BgpController extends ApiMutableModelControllerBase // save config if validated correctly $mdlBGP->serializeToConfig(); Config::getInstance()->save(); + unset($result['validations']); $result["result"] = "saved"; } } @@ -171,6 +172,200 @@ class BgpController extends ApiMutableModelControllerBase return array("result" => "failed"); } + public function searchAspathAction() + { + $this->sessionClose(); + $mdlBGP = $this->getModel(); + $grid = new UIModelGrid($mdlBGP->aspaths->aspath); + return $grid->fetchBindRequest( + $this->request, + array("enabled", "number", "action", "as" ) + ); + } + + public function getAspathAction($uuid = null) + { + $mdlBGP = $this->getModel(); + if ($uuid != null) { + $node = $mdlBGP->getNodeByReference('aspaths.aspath.' . $uuid); + if ($node != null) { + // return node + return array("aspath" => $node->getNodes()); + } + } else { + $node = $mdlBGP->aspaths->aspath->add(); + return array("aspath" => $node->getNodes()); + } + return array(); + } + + public function addAspathAction() + { + $result = array("result" => "failed"); + if ($this->request->isPost() && $this->request->hasPost("aspath")) { + $result = array("result" => "failed", "validations" => array()); + $mdlBGP = $this->getModel(); + $node = $mdlBGP->aspaths->aspath->Add(); + $node->setNodes($this->request->getPost("aspath")); + $valMsgs = $mdlBGP->performValidation(); + foreach ($valMsgs as $field => $msg) { + $fieldnm = str_replace($node->__reference, "aspath", $msg->getField()); + $result["validations"][$fieldnm] = $msg->getMessage(); + } + if (count($result['validations']) == 0) { + // save config if validated correctly + $mdlBGP->serializeToConfig(); + Config::getInstance()->save(); + unset($result['validations']); + $result["result"] = "saved"; + } + } + return $result; + } + + public function delAspathAction($uuid) + { + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdlBGP = $this->getModel(); + if ($uuid != null) { + if ($mdlBGP->aspaths->aspath->del($uuid)) { + $mdlBGP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + public function setAspathAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("aspath")) { + $mdlNeighbor = $this->getModel(); + if ($uuid != null) { + $node = $mdlNeighbor->getNodeByReference('aspaths.aspath.' . $uuid); + if ($node != null) { + $result = array("result" => "failed", "validations" => array()); + $aspathInfo = $this->request->getPost("aspath"); + $node->setNodes($aspathInfo); + $valMsgs = $mdlNeighbor->performValidation(); + foreach ($valMsgs as $field => $msg) { + $fieldnm = str_replace($node->__reference, "aspath", $msg->getField()); + $result["validations"][$fieldnm] = $msg->getMessage(); + } + if (count($result['validations']) == 0) { + // save config if validated correctly + $mdlNeighbor->serializeToConfig(); + Config::getInstance()->save(); + $result = array("result" => "saved"); + } + return $result; + } + } + } + return array("result" => "failed"); + } + + public function searchRoutemapAction() + { + $this->sessionClose(); + $mdlBGP = $this->getModel(); + $grid = new UIModelGrid($mdlBGP->routemaps->routemap); + return $grid->fetchBindRequest( + $this->request, + array("enabled", "name", "action", "id", "match", "set" ) + ); + } + + public function getRoutemapAction($uuid = null) + { + $mdlBGP = $this->getModel(); + if ($uuid != null) { + $node = $mdlBGP->getNodeByReference('routemaps.routemap.' . $uuid); + if ($node != null) { + // return node + return array("routemap" => $node->getNodes()); + } + } else { + $node = $mdlBGP->routemaps->routemap->add(); + return array("routemap" => $node->getNodes()); + } + return array(); + } + + public function addRoutemapAction() + { + $result = array("result" => "failed"); + if ($this->request->isPost() && $this->request->hasPost("routemap")) { + $result = array("result" => "failed", "validations" => array()); + $mdlBGP = $this->getModel(); + $node = $mdlBGP->routemaps->routemap->Add(); + $node->setNodes($this->request->getPost("routemap")); + $valMsgs = $mdlBGP->performValidation(); + foreach ($valMsgs as $field => $msg) { + $fieldnm = str_replace($node->__reference, "routemap", $msg->getField()); + $result["validations"][$fieldnm] = $msg->getMessage(); + } + if (count($result['validations']) == 0) { + // save config if validated correctly + $mdlBGP->serializeToConfig(); + Config::getInstance()->save(); + unset($result['validations']); + $result["result"] = "saved"; + } + } + return $result; + } + + public function delRoutemapAction($uuid) + { + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdlBGP = $this->getModel(); + if ($uuid != null) { + if ($mdlBGP->routemaps->routemap->del($uuid)) { + $mdlBGP->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + public function setRoutemapAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost("routemap")) { + $mdlNeighbor = $this->getModel(); + if ($uuid != null) { + $node = $mdlNeighbor->getNodeByReference('routemaps.routemap.' . $uuid); + if ($node != null) { + $result = array("result" => "failed", "validations" => array()); + $routemapInfo = $this->request->getPost("routemap"); + $node->setNodes($routemapInfo); + $valMsgs = $mdlNeighbor->performValidation(); + foreach ($valMsgs as $field => $msg) { + $fieldnm = str_replace($node->__reference, "routemap", $msg->getField()); + $result["validations"][$fieldnm] = $msg->getMessage(); + } + if (count($result['validations']) == 0) { + // save config if validated correctly + $mdlNeighbor->serializeToConfig(); + Config::getInstance()->save(); + $result = array("result" => "saved"); + } + return $result; + } + } + } + return array("result" => "failed"); + } + public function toggle_handler($uuid, $elements, $element) { $result = array("result" => "failed"); @@ -199,4 +394,14 @@ class BgpController extends ApiMutableModelControllerBase { return $this->toggle_handler($uuid, 'neighbors', 'neighbor'); } + + public function toggleAspathAction($uuid) + { + return $this->toggle_handler($uuid, 'aspaths', 'aspath'); + } + + public function toggleRoutemapAction($uuid) + { + return $this->toggle_handler($uuid, 'routemaps', 'routemap'); + } } diff --git a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/BgpController.php b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/BgpController.php index 127e2d3de..a1dc14457 100644 --- a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/BgpController.php +++ b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/BgpController.php @@ -31,6 +31,8 @@ class BgpController extends \OPNsense\Base\IndexController $this->view->title = gettext("BGP Settings"); $this->view->bgpForm = $this->getForm("bgp"); $this->view->formDialogEditBGPNeighbor = $this->getForm("dialogEditBGPNeighbor"); + $this->view->formDialogEditBGPASPaths = $this->getForm("dialogEditBGPASPath"); + $this->view->formDialogEditBGPRouteMaps = $this->getForm("dialogEditBGPRouteMaps"); $this->view->pick('OPNsense/Quagga/bgp'); } } diff --git a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/bgp.xml b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/bgp.xml index a6ee35c6d..5acd5fbcc 100644 --- a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/bgp.xml +++ b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/bgp.xml @@ -9,7 +9,7 @@ bgp.asnumber text - Your AS Number here + Your AS Number here bgp.networks diff --git a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPASPath.xml b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPASPath.xml new file mode 100644 index 000000000..1a3f42f7d --- /dev/null +++ b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPASPath.xml @@ -0,0 +1,26 @@ +
+ + aspath.enabled + + checkbox + Enable / Disable + + + aspath.number + + text + The ACL rule number (10-99); keep in mind that there are no sequence numbers with AS-Path lists. When you want to add a new line between you have to completely remove the ACL! + + + aspath.action + + select_multiple + Set permit for match or deny to negate the rule. + + + aspath.as + + text + The AS pattern you want to match, regexp allowed (e.g. *$ or _1$). It's not validated so please be careful! + +
diff --git a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml index 019d3a5b8..15a093d02 100644 --- a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml +++ b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPNeighbor.xml @@ -32,4 +32,16 @@ checkbox
+ + neighbor.linkedRoutemapIn + + dropdown + Route-Map for inbound direction + + + neighbor.linkedRoutemapOut + + dropdown + Route-Map for outbound direction + diff --git a/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPRouteMaps.xml b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPRouteMaps.xml new file mode 100644 index 000000000..22b8eac93 --- /dev/null +++ b/net/quagga/src/opnsense/mvc/app/controllers/OPNsense/Quagga/forms/dialogEditBGPRouteMaps.xml @@ -0,0 +1,40 @@ +
+ + routemap.enabled + + checkbox + Enable / Disable + + + routemap.name + + text + Route-map name to match and set your patterns, it will be enabled via the neigbor configuration. + + + routemap.action + + select_multiple + Set permit for match or deny to negate the rule. + + + routemap.id + + text + Route-map ID between 10 and 99. Be aware that the sorting will be done under the hood, so when you add an entry between it get's to the right position + + + routemap.match + + select_multiple + + true + Select the AS-Path list + + + routemap.set + + text + Free text field for your set, please be careful! You can set e.g. "local-prefernce 300" or "community 1:1" (http://www.nongnu.org/quagga/docs/docs-multi/Route-Map-Set-Command.html#Route-Map-Set-Command) + +
diff --git a/net/quagga/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml b/net/quagga/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml index 54752df57..e380a1a81 100644 --- a/net/quagga/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml +++ b/net/quagga/src/opnsense/mvc/app/models/OPNsense/Quagga/BGP.xml @@ -57,15 +57,107 @@ /^(?!0).*$/ - + 0 N - + 0 N - + + + + + Related Route-Map item not found + N + N + + + + + + Related Route-Map item not found + N + N + + + + + + 1 + Y + + + + Y + 10 + 99 + + + + Y + + Permit + Deny + + + + + Y + + + + + + + 1 + Y + + + + Y + + + + Y + + Permit + Deny + + + + + Y + 10 + 99 + + + + + + Related item not found + Y + N + + + + Y + + + diff --git a/net/quagga/src/opnsense/mvc/app/views/OPNsense/Quagga/bgp.volt b/net/quagga/src/opnsense/mvc/app/views/OPNsense/Quagga/bgp.volt index 7b4e12829..f36fe29c1 100644 --- a/net/quagga/src/opnsense/mvc/app/views/OPNsense/Quagga/bgp.volt +++ b/net/quagga/src/opnsense/mvc/app/views/OPNsense/Quagga/bgp.volt @@ -31,6 +31,8 @@ POSSIBILITY OF SUCH DAMAGE.
@@ -55,6 +57,8 @@ POSSIBILITY OF SUCH DAMAGE. {{ lang._('Update Source Address') }} {{ lang._('Next Hop Self') }} {{ lang._('Default Originate') }} + {{ lang._('Route-Map Inbound') }} + {{ lang._('Route-Map Outbound') }} {{ lang._('ID') }} {{ lang._('Commands') }} @@ -72,6 +76,61 @@ POSSIBILITY OF SUCH DAMAGE.
+ +
+ + + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('Number') }}{{ lang._('Action') }}{{ lang._('AS Number') }}{{ lang._('ID') }}{{ lang._('Commands') }}
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
{{ lang._('Enabled') }}{{ lang._('Name') }}{{ lang._('Action') }}{{ lang._('ID') }}{{ lang._('AS Path List') }}{{ lang._('Set') }}{{ lang._('ID') }}{{ lang._('Commands') }}
+ + +
+
+
{{ partial("layout_partials/base_dialog",['fields':formDialogEditBGPNeighbor,'id':'DialogEditBGPNeighbor','label':lang._('Edit Neighbor')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditBGPASPaths,'id':'DialogEditBGPASPaths','label':lang._('Edit AS-Paths')])}} +{{ partial("layout_partials/base_dialog",['fields':formDialogEditBGPRouteMaps,'id':'DialogEditBGPRouteMaps','label':lang._('Edit Route-Maps')])}} diff --git a/net/quagga/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf b/net/quagga/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf index a7fbc72c1..e32b4f9df 100644 --- a/net/quagga/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf +++ b/net/quagga/src/opnsense/service/templates/OPNsense/Quagga/bgpd.conf @@ -27,9 +27,53 @@ router bgp {{ OPNsense.quagga.bgp.asnumber }} {% if 'defaultoriginate' in neighbor and neighbor.defaultoriginate == '1' %} neighbor {{ neighbor.address }} default-originate {% endif %} +{% if neighbor.linkedRoutemapIn|default("") != "" %} +{% for aspath in neighbor.linkedRoutemapIn.split(",") %} +{% set routemap2_data = helpers.getUUID(aspath) %} +{% if routemap2_data != '' %} + neighbor {{ neighbor.address }} route-map {{ routemap2_data.name }} in +{% endif %} +{% endfor %} +{% endif %} +{% if neighbor.linkedRoutemapOut|default("") != "" %} +{% for aspath in neighbor.linkedRoutemapOut.split(",") %} +{% set routemap_data = helpers.getUUID(aspath) %} +{% if routemap_data != '' %} + neighbor {{ neighbor.address }} route-map {{ routemap_data.name }} out +{% endif %} +{% endfor %} +{% endif %} {% endif %} {% endfor %} {% endif %} +! +{% if helpers.exists('OPNsense.quagga.bgp.aspaths.aspath') %} +{% for aspath in helpers.sortDictList(OPNsense.quagga.bgp.aspaths.aspath, 'number' ) %} +{% if aspath.enabled == '1' %} +ip as-path access-list {{ aspath.number }} {{ aspath.action }} {{ aspath.as }} +{% endif %} +{% endfor %} +{% endif %} +! +{% if helpers.exists('OPNsense.quagga.bgp.routemaps.routemap') %} +{% for routemap in helpers.sortDictList(OPNsense.quagga.bgp.routemaps.routemap, 'name', 'id' ) %} +{% if routemap.enabled == '1' %} +route-map {{ routemap.name }} {{ routemap.action }} {{ routemap.id }} +{% if routemap.match|default("") != "" %} +{% for aspath in routemap.match.split(",") %} +{% set aspath_data = helpers.getUUID(aspath) %} +{% if 'match' in routemap and routemap.match != '' %} + match as-path {{ aspath_data.number }} +{% endif %} +{% endfor %} +{% endif %} +{% if routemap.set != '' %} + set {{ routemap.set }} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +! {% endif %} ! line vty