From 8a17966ed7ed13a5da8a17e4d67b6f163d260088 Mon Sep 17 00:00:00 2001
From: Viacheslav Hletenko <v.gletenko@vyos.io>
Date: Wed, 13 Dec 2023 16:59:01 +0000
Subject: T4163: Add BGP Monitoring Protocol BMP feature

Add BMP feature.
BMP (BGP Monitoring Protocol, RFC 7854) is used to send monitoring
data from BGP routers to network management entities

https://docs.frrouting.org/en/latest/bmp.html

Example:

set system frr bmp
commit
run restart bgp

set protocols bgp system-as '65001'
set protocols bgp neighbor 192.0.2.11 address-family ipv4-unicast
set protocols bgp neighbor 192.0.2.11 remote-as '65001'

set protocols bgp bmp mirror-buffer-limit '256000000'
set protocols bgp bmp target foo address '127.0.0.1'
set protocols bgp bmp target foo port '5000'
set protocols bgp bmp target foo min-retry '1000'
set protocols bgp bmp target foo max-retry '2000'
set protocols bgp bmp target foo mirror
set protocols bgp bmp target foo monitor ipv4-unicast post-policy
set protocols bgp bmp target foo monitor ipv4-unicast pre-policy
set protocols bgp bmp target foo monitor ipv6-unicast post-policy
set protocols bgp bmp target foo monitor ipv6-unicast pre-policy

(cherry picked from commit 5523fccf4f7d05444c36c568128e94cd7b08c34f)
---
 data/templates/frr/bgpd.frr.j2                     | 32 ++++++++
 .../include/bgp/bmp-monitor-afi-policy.xml.i       | 14 ++++
 .../include/bgp/protocol-common-config.xml.i       | 86 ++++++++++++++++++++++
 smoketest/scripts/cli/test_protocols_bgp.py        | 56 ++++++++++++++
 src/conf_mode/protocols_bgp.py                     | 14 ++++
 5 files changed, 202 insertions(+)
 create mode 100644 interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i

diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 641dac44a..679ba8b04 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -470,6 +470,38 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
 {%     endfor %}
 {% endif %}
  !
+{% if bmp is vyos_defined %}
+{%     if bmp.mirror_buffer_limit is vyos_defined %}
+ bmp mirror buffer-limit {{ bmp.mirror_buffer_limit }}
+ !
+{%     endif %}
+{%     if bmp.target is vyos_defined %}
+{%         for bmp, bmp_config in bmp.target.items() %}
+ bmp targets {{ bmp }}
+{%             if bmp_config.mirror is vyos_defined %}
+  bmp mirror
+{%             endif %}
+{%             if bmp_config.monitor is vyos_defined %}
+{%                 if bmp_config.monitor.ipv4_unicast.pre_policy is vyos_defined %}
+  bmp monitor ipv4 unicast pre-policy
+{%                 endif %}
+{%                 if bmp_config.monitor.ipv4_unicast.post_policy is vyos_defined %}
+  bmp monitor ipv4 unicast post-policy
+{%                 endif %}
+{%                 if bmp_config.monitor.ipv6_unicast.pre_policy is vyos_defined %}
+  bmp monitor ipv6 unicast pre-policy
+{%                 endif %}
+{%                 if bmp_config.monitor.ipv6_unicast.post_policy is vyos_defined %}
+  bmp monitor ipv6 unicast post-policy
+{%                 endif %}
+{%             endif %}
+{%             if bmp_config.address is vyos_defined %}
+  bmp connect {{ bmp_config.address }} port {{ bmp_config.port }} min-retry {{ bmp_config.min_retry }} max-retry {{ bmp_config.max_retry }}
+{%             endif %}
+{%         endfor %}
+ exit
+{%     endif %}
+{% endif %}
 {% if peer_group is vyos_defined %}
 {%     for peer, config in peer_group.items() %}
 {{ bgp_neighbor(peer, config, true) }}
diff --git a/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i
new file mode 100644
index 000000000..261d60232
--- /dev/null
+++ b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from bgp/bmp-monitor-afi-policy.xml.i -->
+<leafNode name="pre-policy">
+  <properties>
+    <help>Send state before policy and filter processing</help>
+    <valueless/>
+  </properties>
+</leafNode>
+<leafNode name="post-policy">
+  <properties>
+    <help>Send state with policy and filters applied</help>
+    <valueless/>
+  </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index c379ba95c..dce61ee77 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -909,6 +909,92 @@
     </node>
   </children>
 </node>
+<node name="bmp">
+  <properties>
+    <help>BGP Monitoring Protocol (BMP)</help>
+  </properties>
+  <children>
+    <leafNode name="mirror-buffer-limit">
+      <properties>
+        <help>Maximum memory used for buffered mirroring messages (in bytes)</help>
+        <valueHelp>
+          <format>u32:0-4294967294</format>
+          <description>Limit in bytes</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 0-4294967294"/>
+        </constraint>
+      </properties>
+    </leafNode>
+    <tagNode name="target">
+      <properties>
+        <help>BMP target</help>
+      </properties>
+      <children>
+        #include <include/address-ipv4-ipv6-single.xml.i>
+        #include <include/port-number.xml.i>
+        <leafNode name="port">
+          <defaultValue>5000</defaultValue>
+        </leafNode>
+        <leafNode name="min-retry">
+          <properties>
+            <help>Minimum connection retry interval (in milliseconds)</help>
+            <valueHelp>
+              <format>u32:100-86400000</format>
+              <description>Minimum connection retry interval</description>
+            </valueHelp>
+            <constraint>
+              <validator name="numeric" argument="--range 100-86400000"/>
+            </constraint>
+          </properties>
+          <defaultValue>1000</defaultValue>
+        </leafNode>
+        <leafNode name="max-retry">
+          <properties>
+            <help>Maximum connection retry interval</help>
+            <valueHelp>
+              <format>u32:100-4294967295</format>
+              <description>Maximum connection retry interval</description>
+            </valueHelp>
+            <constraint>
+              <validator name="numeric" argument="--range 100-86400000"/>
+            </constraint>
+          </properties>
+          <defaultValue>2000</defaultValue>
+        </leafNode>
+        <leafNode name="mirror">
+          <properties>
+            <help>Send BMP route mirroring messages</help>
+            <valueless/>
+          </properties>
+        </leafNode>
+        <node name="monitor">
+          <properties>
+            <help>Send BMP route monitoring messages</help>
+          </properties>
+          <children>
+            <node name="ipv4-unicast">
+              <properties>
+                <help>Address family IPv4 unicast</help>
+              </properties>
+              <children>
+                #include <include/bgp/bmp-monitor-afi-policy.xml.i>
+              </children>
+            </node>
+            <node name="ipv6-unicast">
+              <properties>
+                <help>Address family IPv6 unicast</help>
+              </properties>
+              <children>
+                #include <include/bgp/bmp-monitor-afi-policy.xml.i>
+              </children>
+            </node>
+          </children>
+        </node>
+      </children>
+    </tagNode>
+  </children>
+</node>
 <tagNode name="interface">
   <properties>
     <help>Configure interface related parameters, e.g. MPLS</help>
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 97dab255e..8102a3153 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -15,6 +15,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import unittest
+from subprocess import run
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
@@ -1148,5 +1149,60 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
         self.assertIn(f'  locator {locator_name}', frrconfig)
         self.assertIn(f' sid vpn per-vrf export {sid}', frrconfig)
 
+    def test_bgp_25_bmp(self):
+        target_name = 'instance-bmp'
+        target_address = '127.0.0.1'
+        target_port = '5000'
+        min_retry = '1024'
+        max_retry = '2048'
+        monitor_ipv4 = 'pre-policy'
+        monitor_ipv6 = 'pre-policy'
+        mirror_buffer = '32000000'
+        bmp_path = base_path + ['bmp']
+        target_path = bmp_path + ['target', target_name]
+        bgpd_bmp_pid = process_named_running('bgpd', 'bmp')
+        command = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'restart', 'bgp']
+
+        self.cli_set(bmp_path)
+        # by default the 'bmp' module not loaded for the bgpd
+        # expect Error
+        if not bgpd_bmp_pid:
+            with self.assertRaises(ConfigSessionError):
+                self.cli_commit()
+
+        # add required 'bmp' module to bgpd and restart bgpd
+        self.cli_delete(bmp_path)
+        self.cli_set(['system', 'frr', 'bmp'])
+        self.cli_commit()
+        # restart bgpd to apply "-M bmp" and update PID
+        run(command, input='Y', text=True)
+        self.daemon_pid = process_named_running(PROCESS_NAME)
+
+        # set bmp config but not set address
+        self.cli_set(target_path + ['port', target_port])
+        # address is not set, expect Error
+        with self.assertRaises(ConfigSessionError):
+            self.cli_commit()
+
+        # config other bmp options
+        self.cli_set(target_path + ['address', target_address])
+        self.cli_set(bmp_path + ['mirror-buffer-limit', mirror_buffer])
+        self.cli_set(target_path + ['port', target_port])
+        self.cli_set(target_path + ['min-retry', min_retry])
+        self.cli_set(target_path + ['max-retry', max_retry])
+        self.cli_set(target_path + ['mirror'])
+        self.cli_set(target_path + ['monitor', 'ipv4-unicast', monitor_ipv4])
+        self.cli_set(target_path + ['monitor', 'ipv6-unicast', monitor_ipv6])
+        self.cli_commit()
+
+        # Verify bgpd bmp configuration
+        frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+        self.assertIn(f'bmp mirror buffer-limit {mirror_buffer}', frrconfig)
+        self.assertIn(f'bmp targets {target_name}', frrconfig)
+        self.assertIn(f'bmp mirror', frrconfig)
+        self.assertIn(f'bmp monitor ipv4 unicast {monitor_ipv4}', frrconfig)
+        self.assertIn(f'bmp monitor ipv6 unicast {monitor_ipv6}', frrconfig)
+        self.assertIn(f'bmp connect {target_address} port {target_port} min-retry {min_retry} max-retry {max_retry}', frrconfig)
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 557f0a9e9..5a503a9b7 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -30,6 +30,7 @@ from vyos.template import render_to_string
 from vyos.utils.dict import dict_search
 from vyos.utils.network import get_interface_vrf
 from vyos.utils.network import is_addr_assigned
+from vyos.utils.process import process_named_running
 from vyos import ConfigError
 from vyos import frr
 from vyos import airbag
@@ -247,6 +248,19 @@ def verify(bgp):
     if 'system_as' not in bgp:
         raise ConfigError('BGP system-as number must be defined!')
 
+    # Verify BMP
+    if 'bmp' in bgp:
+        # check bmp flag "bgpd -d -F traditional --daemon -A 127.0.0.1 -M rpki -M bmp"
+        if not process_named_running('bgpd', 'bmp'):
+            raise ConfigError(
+                f'"bmp" flag is not found in bgpd. Configure "set system frr bmp" and restart bgp process'
+            )
+        # check bmp target
+        if 'target' in bgp['bmp']:
+            for target, target_config in bgp['bmp']['target'].items():
+                if 'address' not in target_config:
+                    raise ConfigError(f'BMP target "{target}" address must be defined!')
+
     # Verify vrf on interface and bgp section
     if 'interface' in bgp:
         for interface in bgp['interface']:
-- 
cgit v1.2.3