diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
new file mode 100644
index 000000000..ab7b14d6b
--- /dev/null
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -0,0 +1,54 @@
+zebra=yes
+bgpd=yes
+ospfd=yes
+ospf6d=yes
+ripd=yes
+ripngd=yes
+isisd=yes
+pimd=no
+ldpd=yes
+nhrpd=no
+eigrpd=no
+babeld=no
+sharpd=no
+pbrd=no
+bfdd=yes
+staticd=yes
+
+vtysh_enable=yes
+zebra_options="  -s 90000000 --daemon -A 127.0.0.1
+{%- if irdp is defined %} -M irdp{% endif -%}
+{%- if snmp is defined and snmp.zebra is defined %} -M snmp{% endif -%}
+"
+bgpd_options="   --daemon -A 127.0.0.1
+{%- if bmp is defined %} -M bmp{% endif -%}
+{%- if snmp is defined and snmp.bgpd is defined %} -M snmp{% endif -%}
+"
+ospfd_options="  --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ospfd is defined %} -M snmp{% endif -%}
+"
+ospf6d_options=" --daemon -A ::1
+{%- if snmp is defined and snmp.ospf6d is defined %} -M snmp{% endif -%}
+"
+ripd_options="   --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ripd is defined %} -M snmp{% endif -%}
+"
+ripngd_options=" --daemon -A ::1"
+isisd_options="  --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.isisd is defined %} -M snmp{% endif -%}
+"
+pimd_options="  --daemon -A 127.0.0.1"
+ldpd_options="  --daemon -A 127.0.0.1
+{%- if snmp is defined and snmp.ldpd is defined %} -M snmp{% endif -%}
+"
+nhrpd_options="  --daemon -A 127.0.0.1"
+eigrpd_options="  --daemon -A 127.0.0.1"
+babeld_options="  --daemon -A 127.0.0.1"
+sharpd_options="  --daemon -A 127.0.0.1"
+pbrd_options="  --daemon -A 127.0.0.1"
+staticd_options="  --daemon -A 127.0.0.1"
+bfdd_options="  --daemon -A 127.0.0.1"
+
+watchfrr_enable=no
+valgrind_enable=no
+
diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in
new file mode 100644
index 000000000..9fe23ed75
--- /dev/null
+++ b/interface-definitions/system-frr.xml.in
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+  <node name="system">
+    <children>
+      <node name="frr" owner="${vyos_conf_scripts_dir}/system_frr.py">
+        <properties>
+          <help>Configure FRR parameters</help>
+          <!-- Before components that use FRR -->
+          <priority>150</priority>
+        </properties>
+        <children>
+          <leafNode name="bmp">
+            <properties>
+              <help>Enable BGP Monitoring Protocol support</help>
+              <valueless/>
+            </properties>
+          </leafNode>
+          <leafNode name="irdp">
+            <properties>
+              <help>Enable ICMP Router Discovery Protocol support</help>
+              <valueless/>
+            </properties>
+          </leafNode>
+          <node name="snmp">
+            <properties>
+              <help>Enable SNMP integration for next daemons</help>
+            </properties>
+            <children>
+              <leafNode name="bgpd">
+                <properties>
+                  <help>BGP</help>
+                  <valueless/>
+                </properties>
+              </leafNode>
+              <leafNode name="isisd">
+                <properties>
+                  <help>IS-IS</help>
+                  <valueless/>
+                </properties>
+              </leafNode>
+              <leafNode name="ldpd">
+                <properties>
+                  <help>LDP</help>
+                  <valueless/>
+                </properties>
+              </leafNode>
+              <leafNode name="ospf6d">
+                <properties>
+                  <help>OSPFv3</help>
+                  <valueless/>
+                </properties>
+              </leafNode>
+              <leafNode name="ospfd">
+                <properties>
+                  <help>OSPFv2</help>
+                  <valueless/>
+                </properties>
+              </leafNode>
+              <leafNode name="ripd">
+                <properties>
+                  <help>RIP</help>
+                  <valueless/>
+                </properties>
+              </leafNode>
+              <leafNode name="zebra">
+                <properties>
+                  <help>Zebra (IP routing manager)</help>
+                  <valueless/>
+                </properties>
+              </leafNode>
+            </children>
+          </node>
+        </children>
+      </node>
+    </children>
+  </node>
+</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py
new file mode 100755
index 000000000..331133ed4
--- /dev/null
+++ b/smoketest/scripts/cli/test_system_frr.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import unittest
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.util import read_file
+
+config_file = '/etc/frr/daemons'
+base_path = ['system', 'frr']
+
+
+def daemons_config_parse(daemons_config):
+    # create regex for parsing daemons options
+    regex_daemon_config = re.compile(
+        r'^(?P<daemon_name>\w+)_options="(?P<daemon_options>.*)"$', re.M)
+    # create empty dict for config
+    daemons_config_dict = {}
+    # fill dictionary with actual config
+    for daemon in regex_daemon_config.finditer(daemons_config):
+        daemon_name = daemon.group('daemon_name')
+        daemon_options = daemon.group('daemon_options')
+        daemons_config_dict[daemon_name] = daemon_options
+
+    # return daemons config
+    return (daemons_config_dict)
+
+
+class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
+
+    def tearDown(self):
+        self.cli_delete(base_path)
+        self.cli_commit()
+
+    def test_frr_snmp_multipledaemons(self):
+        # test SNMP integration for multiple daemons
+        test_daemon_names = ['ospfd', 'bgpd']
+        for test_daemon_name in test_daemon_names:
+            self.cli_set(base_path + ['snmp', test_daemon_name])
+        self.cli_commit()
+
+        # read the config file and check content
+        daemons_config = read_file(config_file)
+        daemons_config_dict = daemons_config_parse(daemons_config)
+        # prepare regex for matching SNMP integration
+        regex_snmp = re.compile(r'^.* -M snmp.*$')
+        for (daemon_name, daemon_options) in daemons_config_dict.items():
+            snmp_enabled = regex_snmp.match(daemon_options)
+            if daemon_name in test_daemon_names:
+                self.assertTrue(snmp_enabled)
+            else:
+                self.assertFalse(snmp_enabled)
+
+    def test_frr_snmp_addandremove(self):
+        # test enabling and disabling of SNMP integration
+        test_daemon_names = ['ospfd', 'bgpd']
+        for test_daemon_name in test_daemon_names:
+            self.cli_set(base_path + ['snmp', test_daemon_name])
+        self.cli_commit()
+
+        self.cli_delete(base_path)
+        self.cli_commit()
+
+        # read the config file and check content
+        daemons_config = read_file(config_file)
+        daemons_config_dict = daemons_config_parse(daemons_config)
+        # prepare regex for matching SNMP integration
+        regex_snmp = re.compile(r'^.* -M snmp.*$')
+        for test_daemon_name in test_daemon_names:
+            snmp_enabled = regex_snmp.match(
+                daemons_config_dict[test_daemon_name])
+            self.assertFalse(snmp_enabled)
+
+    def test_frr_snmp_empty(self):
+        # test empty config section
+        self.cli_set(base_path + ['snmp'])
+        self.cli_commit()
+
+        # read the config file and check content
+        daemons_config = read_file(config_file)
+        daemons_config_dict = daemons_config_parse(daemons_config)
+        # prepare regex for matching SNMP integration
+        regex_snmp = re.compile(r'^.* -M snmp.*$')
+        for daemon_options in daemons_config_dict.values():
+            snmp_enabled = regex_snmp.match(daemon_options)
+            self.assertFalse(snmp_enabled)
+
+    def test_frr_bmp(self):
+        # test BMP
+        self.cli_set(base_path + ['bmp'])
+        self.cli_commit()
+
+        # read the config file and check content
+        daemons_config = read_file(config_file)
+        daemons_config_dict = daemons_config_parse(daemons_config)
+        # prepare regex
+        regex_bmp = re.compile(r'^.* -M bmp.*$')
+        bmp_enabled = regex_bmp.match(daemons_config_dict['bgpd'])
+        self.assertTrue(bmp_enabled)
+
+    def test_frr_irdp(self):
+        # test IRDP
+        self.cli_set(base_path + ['irdp'])
+        self.cli_commit()
+
+        # read the config file and check content
+        daemons_config = read_file(config_file)
+        daemons_config_dict = daemons_config_parse(daemons_config)
+        # prepare regex
+        regex_irdp = re.compile(r'^.* -M irdp.*$')
+        irdp_enabled = regex_irdp.match(daemons_config_dict['zebra'])
+        self.assertTrue(irdp_enabled)
+
+    def test_frr_bmpandsnmp(self):
+        # test empty config section
+        self.cli_set(base_path + ['bmp'])
+        self.cli_set(base_path + ['snmp', 'bgpd'])
+        self.cli_commit()
+
+        # read the config file and check content
+        daemons_config = read_file(config_file)
+        daemons_config_dict = daemons_config_parse(daemons_config)
+        # prepare regex
+        regex_snmp = re.compile(r'^.* -M bmp.*$')
+        regex_snmp = re.compile(r'^.* -M snmp.*$')
+        bmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
+        snmp_enabled = regex_snmp.match(daemons_config_dict['bgpd'])
+        self.assertTrue(bmp_enabled)
+        self.assertTrue(snmp_enabled)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2, failfast=True)
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index ae060580d..5cd24db32 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -1,308 +1,316 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018-2021 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import exit
 
 from vyos.base import Warning
 from vyos.config import Config
 from vyos.configdict import dict_merge
 from vyos.configverify import verify_vrf
 from vyos.snmpv3_hashgen import plaintext_to_md5
 from vyos.snmpv3_hashgen import plaintext_to_sha1
 from vyos.snmpv3_hashgen import random
 from vyos.template import render
 from vyos.util import call
 from vyos.util import chmod_755
 from vyos.util import dict_search
 from vyos.validate import is_addr_assigned
 from vyos.version import get_version_data
 from vyos.xml import defaults
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 config_file_client  = r'/etc/snmp/snmp.conf'
 config_file_daemon  = r'/etc/snmp/snmpd.conf'
 config_file_access  = r'/usr/share/snmp/snmpd.conf'
 config_file_user    = r'/var/lib/snmp/snmpd.conf'
 systemd_override    = r'/etc/systemd/system/snmpd.service.d/override.conf'
 systemd_service     = 'snmpd.service'
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['service', 'snmp']
 
     snmp = conf.get_config_dict(base, key_mangling=('-', '_'),
                                 get_first_key=True, no_tag_node_value_mangle=True)
     if not conf.exists(base):
         snmp.update({'deleted' : ''})
 
     if conf.exists(['service', 'lldp', 'snmp', 'enable']):
         snmp.update({'lldp_snmp' : ''})
 
     if 'deleted' in snmp:
         return snmp
 
     version_data = get_version_data()
     snmp['version'] = version_data['version']
 
     # create an internal snmpv3 user of the form 'vyosxxxxxxxxxxxxxxxx'
     snmp['vyos_user'] = 'vyos' + random(8)
     snmp['vyos_user_pass'] = random(16)
 
     # We have gathered the dict representation of the CLI, but there are default
     # options which we need to update into the dictionary retrived.
     default_values = defaults(base)
 
     # We can not merge defaults for tagNodes - those need to be blended in
     # per tagNode instance
     if 'listen_address' in default_values:
         del default_values['listen_address']
     if 'community' in default_values:
         del default_values['community']
     if 'trap_target' in default_values:
         del default_values['trap_target']
     if 'v3' in default_values:
         del default_values['v3']
     snmp = dict_merge(default_values, snmp)
 
     if 'listen_address' in snmp:
         default_values = defaults(base + ['listen-address'])
         for address in snmp['listen_address']:
             snmp['listen_address'][address] = dict_merge(
                 default_values, snmp['listen_address'][address])
 
         # Always listen on localhost if an explicit address has been configured
         # This is a safety measure to not end up with invalid listen addresses
         # that are not configured on this system. See https://phabricator.vyos.net/T850
         if '127.0.0.1' not in snmp['listen_address']:
             tmp = {'127.0.0.1': {'port': '161'}}
             snmp['listen_address'] = dict_merge(tmp, snmp['listen_address'])
 
         if '::1' not in snmp['listen_address']:
             tmp = {'::1': {'port': '161'}}
             snmp['listen_address'] = dict_merge(tmp, snmp['listen_address'])
 
     if 'community' in snmp:
         default_values = defaults(base + ['community'])
         for community in snmp['community']:
             snmp['community'][community] = dict_merge(
                 default_values, snmp['community'][community])
 
     if 'trap_target' in snmp:
         default_values = defaults(base + ['trap-target'])
         for trap in snmp['trap_target']:
             snmp['trap_target'][trap] = dict_merge(
                 default_values, snmp['trap_target'][trap])
 
     if 'v3' in snmp:
         default_values = defaults(base + ['v3'])
         # tagNodes need to be merged in individually later on
         for tmp in ['user', 'group', 'trap_target']:
             del default_values[tmp]
         snmp['v3'] = dict_merge(default_values, snmp['v3'])
 
         for user_group in ['user', 'group']:
             if user_group in snmp['v3']:
                 default_values = defaults(base + ['v3', user_group])
                 for tmp in snmp['v3'][user_group]:
                     snmp['v3'][user_group][tmp] = dict_merge(
                         default_values, snmp['v3'][user_group][tmp])
 
             if 'trap_target' in snmp['v3']:
                 default_values = defaults(base + ['v3', 'trap-target'])
                 for trap in snmp['v3']['trap_target']:
                     snmp['v3']['trap_target'][trap] = dict_merge(
                         default_values, snmp['v3']['trap_target'][trap])
 
     return snmp
 
 def verify(snmp):
     if not snmp:
         return None
 
     if {'deleted', 'lldp_snmp'} <= set(snmp):
         raise ConfigError('Can not delete SNMP service, as LLDP still uses SNMP!')
 
     ### check if the configured script actually exist
     if 'script_extensions' in snmp and 'extension_name' in snmp['script_extensions']:
         for extension, extension_opt in snmp['script_extensions']['extension_name'].items():
             if 'script' not in extension_opt:
                 raise ConfigError(f'Script extension "{extension}" requires an actual script to be configured!')
 
             tmp = extension_opt['script']
             if not os.path.isfile(tmp):
                 Warning(f'script "{tmp}" does not exist!')
             else:
                 chmod_755(extension_opt['script'])
 
     if 'listen_address' in snmp:
         for address in snmp['listen_address']:
             # We only wan't to configure addresses that exist on the system.
             # Hint the user if they don't exist
             if not is_addr_assigned(address):
                 Warning(f'SNMP listen address "{address}" not configured!')
 
     if 'trap_target' in snmp:
         for trap, trap_config in snmp['trap_target'].items():
             if 'community' not in trap_config:
                 raise ConfigError(f'Trap target "{trap}" requires a community to be set!')
 
     verify_vrf(snmp)
 
     # bail out early if SNMP v3 is not configured
     if 'v3' not in snmp:
         return None
 
     if 'user' in snmp['v3']:
         for user, user_config in snmp['v3']['user'].items():
             if 'group' not in user_config:
                 raise ConfigError(f'Group membership required for user "{user}"!')
 
             if 'plaintext_password' not in user_config['auth'] and 'encrypted_password' not in user_config['auth']:
                 raise ConfigError(f'Must specify authentication encrypted-password or plaintext-password for user "{user}"!')
 
             if 'plaintext_password' not in user_config['privacy'] and 'encrypted_password' not in user_config['privacy']:
                 raise ConfigError(f'Must specify privacy encrypted-password or plaintext-password for user "{user}"!')
 
     if 'group' in snmp['v3']:
         for group, group_config in snmp['v3']['group'].items():
             if 'seclevel' not in group_config:
                 raise ConfigError(f'Must configure "seclevel" for group "{group}"!')
             if 'view' not in group_config:
                 raise ConfigError(f'Must configure "view" for group "{group}"!')
 
             # Check if 'view' exists
             view = group_config['view']
             if 'view' not in snmp['v3'] or view not in snmp['v3']['view']:
                 raise ConfigError(f'You must create view "{view}" first!')
 
     if 'view' in snmp['v3']:
         for view, view_config in snmp['v3']['view'].items():
             if 'oid' not in view_config:
                 raise ConfigError(f'Must configure an "oid" for view "{view}"!')
 
     if 'trap_target' in snmp['v3']:
         for trap, trap_config in snmp['v3']['trap_target'].items():
             if 'plaintext_password' not in trap_config['auth'] and 'encrypted_password' not in trap_config['auth']:
                 raise ConfigError(f'Must specify one of authentication encrypted-password or plaintext-password for trap "{trap}"!')
 
             if {'plaintext_password', 'encrypted_password'} <= set(trap_config['auth']):
                 raise ConfigError(f'Can not specify both authentication encrypted-password and plaintext-password for trap "{trap}"!')
 
             if 'plaintext_password' not in trap_config['privacy'] and 'encrypted_password' not in trap_config['privacy']:
                 raise ConfigError(f'Must specify one of privacy encrypted-password or plaintext-password for trap "{trap}"!')
 
             if {'plaintext_password', 'encrypted_password'} <= set(trap_config['privacy']):
                 raise ConfigError(f'Can not specify both privacy encrypted-password and plaintext-password for trap "{trap}"!')
 
             if 'type' not in trap_config:
                 raise ConfigError('SNMP v3 trap "type" must be specified!')
 
     return None
 
 def generate(snmp):
 
     #
     # As we are manipulating the snmpd user database we have to stop it first!
     # This is even save if service is going to be removed
     call(f'systemctl stop {systemd_service}')
     # Clean config files
     config_files = [config_file_client, config_file_daemon,
                     config_file_access, config_file_user, systemd_override]
     for file in config_files:
         if os.path.isfile(file):
             os.unlink(file)
 
     if not snmp:
         return None
 
     if 'v3' in snmp:
         # net-snmp is now regenerating the configuration file in the background
         # thus we need to re-open and re-read the file as the content changed.
         # After that we can no read the encrypted password from the config and
         # replace the CLI plaintext password with its encrypted version.
         os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos'
 
         if 'user' in snmp['v3']:
             for user, user_config in snmp['v3']['user'].items():
                 if dict_search('auth.type', user_config)  == 'sha':
                     hash = plaintext_to_sha1
                 else:
                     hash = plaintext_to_md5
 
                 if dict_search('auth.plaintext_password', user_config) is not None:
                     tmp = hash(dict_search('auth.plaintext_password', user_config),
                         dict_search('v3.engineid', snmp))
 
                     snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp
                     del snmp['v3']['user'][user]['auth']['plaintext_password']
 
                     call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" auth encrypted-password "{tmp}" > /dev/null')
                     call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" auth plaintext-password > /dev/null')
 
                 if dict_search('privacy.plaintext_password', user_config) is not None:
                     tmp = hash(dict_search('privacy.plaintext_password', user_config),
                         dict_search('v3.engineid', snmp))
 
                     snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp
                     del snmp['v3']['user'][user]['privacy']['plaintext_password']
 
                     call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" privacy encrypted-password "{tmp}" > /dev/null')
                     call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null')
 
     # Write client config file
     render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp)
     # Write server config file
     render(config_file_daemon, 'snmp/etc.snmpd.conf.j2', snmp)
     # Write access rights config file
     render(config_file_access, 'snmp/usr.snmpd.conf.j2', snmp)
     # Write access rights config file
     render(config_file_user, 'snmp/var.snmpd.conf.j2', snmp)
     # Write daemon configuration file
     render(systemd_override, 'snmp/override.conf.j2', snmp)
 
     return None
 
 def apply(snmp):
     # Always reload systemd manager configuration
     call('systemctl daemon-reload')
 
     if not snmp:
         return None
 
     # start SNMP daemon
     call(f'systemctl restart {systemd_service}')
 
     # Enable AgentX in FRR
-    call('vtysh -c "configure terminal" -c "agentx" >/dev/null')
+    # This should be done for each daemon individually because common command
+    # works only if all the daemons started with SNMP support
+    frr_daemons_list = [
+        'bgpd', 'ospf6d', 'ospfd', 'ripd', 'ripngd', 'isisd', 'ldpd', 'zebra'
+    ]
+    for frr_daemon in frr_daemons_list:
+        call(
+            f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null'
+        )
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py
new file mode 100755
index 000000000..1af0055f6
--- /dev/null
+++ b/src/conf_mode/system_frr.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from pathlib import Path
+from sys import exit
+
+from vyos import ConfigError
+from vyos import airbag
+from vyos.config import Config
+from vyos.logger import syslog
+from vyos.template import render_to_string
+from vyos.util import read_file, write_file, run
+airbag.enable()
+
+# path to daemons config and config status files
+config_file = '/etc/frr/daemons'
+vyos_status_file = '/tmp/vyos-config-status'
+# path to watchfrr for FRR control
+watchfrr = '/usr/lib/frr/watchfrr.sh'
+
+
+def get_config(config=None):
+    if config:
+        conf = config
+    else:
+        conf = Config()
+
+    base = ['system', 'frr']
+    frr_config = conf.get_config_dict(base, get_first_key=True)
+
+    return frr_config
+
+
+def verify(frr_config):
+    # Nothing to verify here
+    pass
+
+
+def generate(frr_config):
+    # read daemons config file
+    daemons_config_current = read_file(config_file)
+    # generate new config file
+    daemons_config_new = render_to_string('frr/daemons.frr.tmpl', frr_config)
+    # update configuration file if this is necessary
+    if daemons_config_new != daemons_config_current:
+        syslog.warning('FRR daemons configuration file need to be changed')
+        write_file(config_file, daemons_config_new)
+        frr_config['config_file_changed'] = True
+
+
+def apply(frr_config):
+    # check if this is initial commit during boot or intiated by CLI
+    # if the file exists, this must be CLI commit
+    commit_type_cli = Path(vyos_status_file).exists()
+    # display warning to user
+    if commit_type_cli and frr_config.get('config_file_changed'):
+        # Since FRR restart is not safe thing, better to give
+        # control over this to users
+        print('''
+        You need to reboot a router (preferred) or restart FRR
+        to apply changes in modules settings
+        ''')
+    # restart FRR automatically. DUring the initial boot this should be
+    # safe in most cases
+    if not commit_type_cli and frr_config.get('config_file_changed'):
+        syslog.warning('Restarting FRR to apply changes in modules')
+        run(f'{watchfrr} restart')
+
+
+if __name__ == '__main__':
+    try:
+        c = get_config()
+        verify(c)
+        generate(c)
+        apply(c)
+    except ConfigError as e:
+        print(e)
+        exit(1)