From 402d685a3cc98a09726c99aecbb42a427624e806 Mon Sep 17 00:00:00 2001 From: philo Date: Wed, 10 Apr 2024 15:45:53 +0800 Subject: [PATCH 01/10] Supports FRR-VRRP configuration Signed-off-by: philo --- .../frr/supervisord/critical_processes.j2 | 2 + .../frr/supervisord/supervisord.conf.j2 | 33 +- .../docker-orchagent/critical_processes.j2 | 1 + dockers/docker-orchagent/supervisord.conf.j2 | 15 + files/image_config/copp/copp_cfg.j2 | 18 +- rules/docker-fpm-frr.mk | 2 +- rules/sonic_vrrpcfgd.dep | 8 + rules/sonic_vrrpcfgd.mk | 13 + .../frrcfgd/frrcfgd.py | 242 +- ...rrp6-commands-and-tracking-interface.patch | 3091 +++++++++++++++++ src/sonic-frr/patch/series | 2 + src/sonic-vrrpcfgd/.gitignore | 12 + src/sonic-vrrpcfgd/setup.cfg | 5 + src/sonic-vrrpcfgd/setup.py | 29 + src/sonic-vrrpcfgd/tests/__init__.py | 0 src/sonic-vrrpcfgd/tests/test_config.py | 148 + src/sonic-vrrpcfgd/tests/test_constructor.py | 19 + src/sonic-vrrpcfgd/vrrpcfgd/__init__.py | 0 src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py | 735 ++++ 19 files changed, 4370 insertions(+), 5 deletions(-) create mode 100644 rules/sonic_vrrpcfgd.dep create mode 100644 rules/sonic_vrrpcfgd.mk create mode 100644 src/sonic-frr/patch/0034-support-vrrp6-commands-and-tracking-interface.patch create mode 100644 src/sonic-vrrpcfgd/.gitignore create mode 100644 src/sonic-vrrpcfgd/setup.cfg create mode 100644 src/sonic-vrrpcfgd/setup.py create mode 100644 src/sonic-vrrpcfgd/tests/__init__.py create mode 100644 src/sonic-vrrpcfgd/tests/test_config.py create mode 100644 src/sonic-vrrpcfgd/tests/test_constructor.py create mode 100644 src/sonic-vrrpcfgd/vrrpcfgd/__init__.py create mode 100644 src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py diff --git a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 index 69f4e8e6931e..f7984dfa9b51 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 @@ -2,6 +2,7 @@ program:zebra program:staticd program:bgpd program:fpmsyncd +program:vrrpd {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} program:bfdd program:ospfd @@ -9,4 +10,5 @@ program:pimd program:frrcfgd {%- else %} program:bgpcfgd +program:vrrpcfgd {%- endif %} diff --git a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 index 0b26be8d3c45..18edd2d1e9e3 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 @@ -76,6 +76,18 @@ dependent_startup=true dependent_startup_wait_for=zebra:running {% endif %} +[program:vrrpd] +command=/usr/lib/frr/vrrpd -A 127.0.0.1 +priority=4 +stopsignal=KILL +autostart=false +autorestart=false +startsecs=0 +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true +dependent_startup_wait_for=zebra:running + [program:bgpd] command=/usr/lib/frr/bgpd -A 127.0.0.1 -M snmp priority=5 @@ -128,10 +140,17 @@ dependent_startup_wait_for=bgpd:running {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} [program:frrcfgd] command=/usr/local/bin/frrcfgd +priority=6 +autostart=false +autorestart=false +startsecs=0 +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true +dependent_startup_wait_for=bgpd:running {% else %} [program:bgpcfgd] command=/usr/local/bin/bgpcfgd -{% endif %} priority=6 autostart=false autorestart=false @@ -141,6 +160,18 @@ stderr_logfile=syslog dependent_startup=true dependent_startup_wait_for=bgpd:running +[program:vrrpcfgd] +command=/usr/local/bin/vrrpcfgd +priority=6 +autostart=false +autorestart=true +startsecs=0 +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true +dependent_startup_wait_for=bgpd:running +{% endif %} + {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} {% else %} [program:staticroutebfd] diff --git a/dockers/docker-orchagent/critical_processes.j2 b/dockers/docker-orchagent/critical_processes.j2 index b9bad74089b6..3cc9f68b53ce 100644 --- a/dockers/docker-orchagent/critical_processes.j2 +++ b/dockers/docker-orchagent/critical_processes.j2 @@ -19,4 +19,5 @@ program:nbrmgrd program:vxlanmgrd program:coppmgrd program:tunnelmgrd +program:vrrpmgrd {%- endif %} diff --git a/dockers/docker-orchagent/supervisord.conf.j2 b/dockers/docker-orchagent/supervisord.conf.j2 index 026958197fb3..8d558cc7d1b9 100644 --- a/dockers/docker-orchagent/supervisord.conf.j2 +++ b/dockers/docker-orchagent/supervisord.conf.j2 @@ -302,3 +302,18 @@ dependent_startup_wait_for=swssconfig:exited environment=ASAN_OPTIONS="log_path=/var/log/asan/fdbsyncd-asan.log{{ asan_extra_options }}" {% endif %} {%- endif %} + +{% if is_fabric_asic == 0 %} +[program:vrrpmgrd] +command=/usr/bin/vrrpmgrd +priority=18 +autostart=false +autorestart=false +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true +dependent_startup_wait_for=swssconfig:exited +{% if ENABLE_ASAN == "y" %} +environment=ASAN_OPTIONS="log_path=/var/log/asan/fdbsyncd-asan.log{{ asan_extra_options }}" +{% endif %} +{%- endif %} diff --git a/files/image_config/copp/copp_cfg.j2 b/files/image_config/copp/copp_cfg.j2 index dcae06fc0bbc..374e281175e5 100755 --- a/files/image_config/copp/copp_cfg.j2 +++ b/files/image_config/copp/copp_cfg.j2 @@ -74,8 +74,17 @@ "red_action": "drop", "trap_action": "trap", "trap_priority": "1" - - } + }, + "queue2_group2": { + "cir":"300", + "cbs":"300", + "meter_type":"packets", + "mode":"sr_tcm", + "red_action":"drop", + "trap_action":"trap", + "trap_priority":"7", + "queue": "7" + } }, "COPP_TRAP": { "bgp": { @@ -118,6 +127,11 @@ "trap_ids": "src_nat_miss,dest_nat_miss", "trap_group": "queue1_group2" }, + "vrrp": { + "trap_ids": "vrrp,vrrpv6", + "trap_group": "queue2_group2", + "always_enabled": "true" + }, "sflow": { "trap_group": "queue2_group1", "trap_ids": "sample_packet" diff --git a/rules/docker-fpm-frr.mk b/rules/docker-fpm-frr.mk index bcc293e0257f..4861abb351ee 100644 --- a/rules/docker-fpm-frr.mk +++ b/rules/docker-fpm-frr.mk @@ -5,7 +5,7 @@ DOCKER_FPM_FRR = $(DOCKER_FPM_FRR_STEM).gz DOCKER_FPM_FRR_DBG = $(DOCKER_FPM_FRR_STEM)-$(DBG_IMAGE_MARK).gz $(DOCKER_FPM_FRR)_PATH = $(DOCKERS_PATH)/$(DOCKER_FPM_FRR_STEM) -$(DOCKER_FPM_FRR)_PYTHON_WHEELS += $(SONIC_BGPCFGD) $(SONIC_FRR_MGMT_FRAMEWORK) +$(DOCKER_FPM_FRR)_PYTHON_WHEELS += $(SONIC_BGPCFGD) $(SONIC_FRR_MGMT_FRAMEWORK) $(SONIC_VRRPCFGD) $(DOCKER_FPM_FRR)_DEPENDS += $(FRR) $(FRR_SNMP) $(SWSS) $(LIBYANG2) $(SONIC_RSYSLOG_PLUGIN) $(DOCKER_FPM_FRR)_DBG_DEPENDS = $($(DOCKER_SWSS_LAYER_BULLSEYE)_DBG_DEPENDS) diff --git a/rules/sonic_vrrpcfgd.dep b/rules/sonic_vrrpcfgd.dep new file mode 100644 index 000000000000..bb2d75b61529 --- /dev/null +++ b/rules/sonic_vrrpcfgd.dep @@ -0,0 +1,8 @@ +SPATH := $($(SONIC_VRRPCFGD)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/sonic_vrrpcfgd.mk rules/sonic_vrrpcfgd.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(SPATH)) + +$(SONIC_VRRPCFGD)_CACHE_MODE := GIT_CONTENT_SHA +$(SONIC_VRRPCFGD)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(SONIC_VRRPCFGD)_DEP_FILES := $(DEP_FILES) diff --git a/rules/sonic_vrrpcfgd.mk b/rules/sonic_vrrpcfgd.mk new file mode 100644 index 000000000000..34dcfee842d0 --- /dev/null +++ b/rules/sonic_vrrpcfgd.mk @@ -0,0 +1,13 @@ +# sonic-vrrpcfgd package + +SONIC_VRRPCFGD = sonic_vrrpcfgd-1.0-py3-none-any.whl +$(SONIC_VRRPCFGD)_SRC_PATH = $(SRC_PATH)/sonic-vrrpcfgd +# These dependencies are only needed because they are dependencies +# of sonic-config-engine and vrrpcfgd explicitly calls sonic-cfggen +# as part of its unit tests. +# TODO: Refactor unit tests so that these dependencies are not needed + +$(SONIC_VRRPCFGD)_DEPENDS += $(SONIC_CONFIG_ENGINE_PY3) +$(SONIC_VRRPCFGD)_DEBS_DEPENDS += $(PYTHON_SWSSCOMMON) +$(SONIC_VRRPCFGD)_PYTHON_VERSION = 3 +SONIC_PYTHON_WHEELS += $(SONIC_VRRPCFGD) \ No newline at end of file diff --git a/src/sonic-frr-mgmt-framework/frrcfgd/frrcfgd.py b/src/sonic-frr-mgmt-framework/frrcfgd/frrcfgd.py index 8db49bf327f5..851e94821e72 100755 --- a/src/sonic-frr-mgmt-framework/frrcfgd/frrcfgd.py +++ b/src/sonic-frr-mgmt-framework/frrcfgd/frrcfgd.py @@ -75,7 +75,7 @@ def extract_cmd_daemons(cmd_str): class BgpdClientMgr(threading.Thread): VTYSH_MARK = 'vtysh ' PROXY_SERVER_ADDR = '/etc/frr/bgpd_client_sock' - ALL_DAEMONS = ['bgpd', 'zebra', 'staticd', 'bfdd', 'ospfd', 'pimd'] + ALL_DAEMONS = ['bgpd', 'zebra', 'staticd', 'bfdd', 'ospfd', 'pimd', 'vrrpd'] TABLE_DAEMON = { 'DEVICE_METADATA': ['bgpd'], 'BGP_GLOBALS': ['bgpd'], @@ -118,6 +118,10 @@ class BgpdClientMgr(threading.Thread): 'PIM_INTERFACE': ['pimd'], 'IGMP_INTERFACE': ['pimd'], 'IGMP_INTERFACE_QUERY': ['pimd'], + 'VRRP': ['vrrpd'], + 'VRRP6': ['vrrpd'], + 'VRRP_TRACK': ['vrrpd'], + 'VRRP6_TRACK': ['vrrpd'], } VTYSH_CMD_DAEMON = [(r'show (ip|ipv6) route($|\s+\S+)', ['zebra']), (r'show ip mroute($|\s+\S+)', ['pimd']), @@ -132,6 +136,7 @@ class BgpdClientMgr(threading.Thread): (r'show ip sla($|\s+\S+)', ['iptrackd']), (r'clear ip sla($|\s+\S+)', ['iptrackd']), (r'clear ip igmp($|\s+\S+)', ['pimd']), + (r'show vrrp($|\s+\S+)', ['vrrpd']), (r'.*', ['bgpd'])] @staticmethod def __create_proxy_socket(): @@ -2229,6 +2234,10 @@ def __init__(self): ('BGP_GLOBALS_EVPN_RT', self.bgp_table_handler_common), ('BGP_GLOBALS_EVPN_VNI_RT', self.bgp_table_handler_common), ('BFD_PEER', self.bfd_handler), + ('VRRP', self.vrrp_handler), + ('VRRP6', self.vrrp6_handler), + ('VRRP_TRACK', self.vrrp_track_handler), + ('VRRP6_TRACK', self.vrrp6_track_handler), ('NEIGHBOR_SET', self.bgp_table_handler_common), ('NEXTHOP_SET', self.bgp_table_handler_common), ('TAG_SET', self.bgp_table_handler_common), @@ -2327,6 +2336,237 @@ def bfd_handler(self, table, key, data): command = command + " -c 'shutdown'" self.__run_command(table, command) + def vrrp_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[vrrp cfgd](vrrp) value for {} changed to {}'.format(key, data)) + #get frr vrrp session key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp {}'.format(key_params[1]) + table_key = ExtConfigDBConnector.get_table_key(table, key) + comb_attr_list = ['vip'] + if not data: + #VRRP instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + #del cache data + del(self.table_data_cache[table_key]) + else: + #create/update case + command = "vtysh -c 'configure terminal' -c '{}'".format(intf_cmd) + self.__add_op_to_data(table_key, data, comb_attr_list) + cached_data = self.table_data_cache.setdefault(table_key, {}) + for param in data: + if param == 'vid': + if param in cached_data and data[param].data == cached_data[param]: + continue + elif 'vip' not in data: + command = command + " -c '{}'".format(cmd) + elif param == 'vip': + if 'vip' in cached_data: + cache_address = cached_data[param] + data_address = data[param].data + # add vip + for d_address in data_address: + if d_address in cache_address: + continue + elif d_address != "": + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) + if ip_address.version == 4: + command = command + " -c '{} ip {}'".format(cmd, d_addr[0]) + # del vip + for c_address in cache_address: + if c_address in data_address: + continue + elif c_address != "": + c_addr = c_address.split('/') + try: + ip_address = ipaddress.ip_interface(c_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) + if ip_address.version == 4: + command = command + " -c 'no {} ip {}'".format(cmd, c_addr[0]) + else: + # first time to config + data_address = data[param].data + for d_address in data_address: + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) + if ip_address.version == 4: + command = command + " -c '{} ip {}'".format(cmd, d_addr[0]) + elif param == 'priority': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} priority {}'".format(cmd, data[param].data) + elif param == 'adv_interval': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} advertisement-interval {}'".format(cmd, data[param].data) + elif param == 'version': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} version {}'".format(cmd, data[param].data) + elif param == 'admin_status': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'down': + command = command + " -c '{} shutdown'".format(cmd) + elif data[param].data == 'up' or data[param].data == '': + command = command + " -c 'no {} shutdown'".format(cmd) + elif param == 'preempt': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'enabled': + command = command + " -c '{} preempt'".format(cmd) + elif data[param].data == 'disabled': + command = command + " -c 'no {} preempt'".format(cmd) + data[param].status = CachedDataWithOp.STAT_SUCC + self.__update_cache_data(table_key, data) + self.__run_command(table, command) + + def vrrp6_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp6) value for {} changed to {}'.format(key, data)) + #get frr vrrp6 session key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp6 {}'.format(key_params[1]) + table_key = ExtConfigDBConnector.get_table_key(table, key) + comb_attr_list = ['vip'] + if not data: + #VRRP instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + #del cache data + del(self.table_data_cache[table_key]) + else: + #create/update case + command = "vtysh -c 'configure terminal' -c '{}'".format(intf_cmd) + self.__add_op_to_data(table_key, data, comb_attr_list) + cached_data = self.table_data_cache.setdefault(table_key, {}) + for param in data: + if param == 'vid': + if param in cached_data and data[param].data == cached_data[param]: + continue + elif 'vip' not in data: + command = command + " -c '{}'".format(cmd) + elif param == 'vip': + if 'vip' in cached_data: + cache_address = cached_data[param] + data_address = data[param].data + for d_address in data_address: + if d_address in cache_address: + continue + elif d_address != "": + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) + if ip_address.version == 6: + command = command + " -c '{} ipv6 {}'".format(cmd, d_addr[0]) + + for c_address in cache_address: + if c_address in data_address: + continue + elif c_address != "": + c_addr = c_address.split('/') + try: + ip_address = ipaddress.ip_interface(c_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) + if ip_address.version == 6: + command = command + " -c 'no {} ipv6 {}'".format(cmd, c_addr[0]) + else: + # first time to config + data_address = data[param].data + for d_address in data_address: + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) + if ip_address.version == 6: + command = command + " -c '{} ipv6 {}'".format(cmd, d_addr[0]) + elif param == 'priority': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} priority {}'".format(cmd, data[param].data) + elif param == 'adv_interval': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} advertisement-interval {}'".format(cmd, data[param].data) + elif param == 'version': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} version {}'".format(cmd, data[param].data) + elif param == 'admin_status': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'down': + command = command + " -c '{} shutdown'".format(cmd) + elif data[param].data == 'up' or data[param].data == '': + command = command + " -c 'no {} shutdown'".format(cmd) + elif param == 'preempt': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'enabled': + command = command + " -c '{} preempt'".format(cmd) + elif data[param].data == 'disabled': + command = command + " -c 'no {} preempt'".format(cmd) + data[param].status = CachedDataWithOp.STAT_SUCC + self.__update_cache_data(table_key, data) + self.__run_command(table, command) + + def vrrp_track_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp track) value for {} changed to {}'.format(key, data)) + #get frr vrrp track key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp {} track-interface {}'.format(key_params[1], key_params[2]) + + if not data: + #VRRP track instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + else: + #create/update case + if 'priority_increment' in data: + command = "vtysh -c 'configure terminal' -c '{}' -c '{} priority-dec {}'".format(intf_cmd, cmd, data['priority_increment']) + self.__run_command(table, command) + + def vrrp6_track_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp6 track) value for {} changed to {}'.format(key, data)) + #get frr vrrp6 track key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp6 {} track-interface {}'.format(key_params[1], key_params[2]) + + if not data: + #VRRP track instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + else: + #create/update case + if 'priority_increment' in data: + command = "vtysh -c 'configure terminal' -c '{}' -c '{} priority-dec {}'".format(intf_cmd, cmd, data['priority_increment']) + self.__run_command(table, command) + def vrf_handler(self, table, key, data): syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrf) value for {} changed to {}'.format(key, data)) #get vrf key diff --git a/src/sonic-frr/patch/0034-support-vrrp6-commands-and-tracking-interface.patch b/src/sonic-frr/patch/0034-support-vrrp6-commands-and-tracking-interface.patch new file mode 100644 index 000000000000..066c0928bd1c --- /dev/null +++ b/src/sonic-frr/patch/0034-support-vrrp6-commands-and-tracking-interface.patch @@ -0,0 +1,3091 @@ +From 004320661a0d6c0a8705b0114b8093856f50084d Mon Sep 17 00:00:00 2001 +From: philo +Date: Mon, 8 Apr 2024 03:53:58 -0500 +Subject: [PATCH] support vrrp6 commands and tracking interface + +Signed-off-by: philo +--- + lib/command.h | 1 + + vrrpd/subdir.am | 3 + + vrrpd/vrrp.c | 602 +++++++++++++++++++++++++----------- + vrrpd/vrrp.h | 94 +++++- + vrrpd/vrrp6_vty.c | 657 ++++++++++++++++++++++++++++++++++++++++ + vrrpd/vrrp6_vty.h | 28 ++ + vrrpd/vrrp_main.c | 2 + + vrrpd/vrrp_northbound.c | 296 ++++++++++++++---- + vrrpd/vrrp_vty.c | 246 +++++++-------- + vrrpd/vrrp_vty.h | 4 +- + yang/frr-vrrpd.yang | 60 +++- + 11 files changed, 1603 insertions(+), 390 deletions(-) + create mode 100644 vrrpd/vrrp6_vty.c + create mode 100644 vrrpd/vrrp6_vty.h + +diff --git a/lib/command.h b/lib/command.h +index 8f5d96053..18f89066a 100644 +--- a/lib/command.h ++++ b/lib/command.h +@@ -183,6 +183,7 @@ enum node_type { + BFD_PROFILE_NODE, /* BFD profile configuration mode. */ + OPENFABRIC_NODE, /* OpenFabric router configuration node */ + VRRP_NODE, /* VRRP node */ ++ VRRP6_NODE, /* VRRP6 node */ + BMP_NODE, /* BMP config under router bgp */ + NODE_TYPE_MAX, /* maximum */ + }; +diff --git a/vrrpd/subdir.am b/vrrpd/subdir.am +index 03b404261..4cf6f3ba7 100644 +--- a/vrrpd/subdir.am ++++ b/vrrpd/subdir.am +@@ -17,6 +17,7 @@ vrrpd_vrrpd_SOURCES = \ + vrrpd/vrrp_northbound.c \ + vrrpd/vrrp_packet.c \ + vrrpd/vrrp_vty.c \ ++ vrrpd/vrrp6_vty.c \ + vrrpd/vrrp_zebra.c \ + # end + +@@ -27,11 +28,13 @@ noinst_HEADERS += \ + vrrpd/vrrp_ndisc.h \ + vrrpd/vrrp_packet.h \ + vrrpd/vrrp_vty.h \ ++ vrrpd/vrrp6_vty.h \ + vrrpd/vrrp_zebra.h \ + # end + + clippy_scan += \ + vrrpd/vrrp_vty.c \ ++ vrrpd/vrrp6_vty.c \ + # end + + vrrpd_vrrpd_LDADD = lib/libfrr.la @LIBCAP@ +diff --git a/vrrpd/vrrp.c b/vrrpd/vrrp.c +index c3ab1abbd..0e689edc2 100644 +--- a/vrrpd/vrrp.c ++++ b/vrrpd/vrrp.c +@@ -42,11 +42,14 @@ + + DEFINE_MTYPE_STATIC(VRRPD, VRRP_IP, "VRRP IP address"); + DEFINE_MTYPE_STATIC(VRRPD, VRRP_RTR, "VRRP Router"); ++DEFINE_MTYPE_STATIC(VRRPD, VRRP_TRACK_INTF, "VRRP Track Interface"); + + /* statics */ + struct hash *vrrp_vrouters_hash; + bool vrrp_autoconfig_is_on; + int vrrp_autoconfig_version; ++bool vrrp6_autoconfig_is_on; ++int vrrp6_autoconfig_version; + + struct vrrp_defaults vd; + +@@ -228,7 +231,9 @@ static struct vrrp_vrouter *vrrp_lookup_by_if_mvl(struct interface *mvl_ifp) + + uint8_t vrid = mvl_ifp->hw_addr[5]; + +- return vrrp_lookup(p, vrid); ++ int family = mvl_ifp->hw_addr[4] == 0x02 ? AF_INET6 : AF_INET; ++ ++ return vrrp_lookup(p, vrid, family); + } + + /* +@@ -300,91 +305,128 @@ void vrrp_check_start(struct vrrp_vrouter *vr) + if (vr->shutdown || vr->ifp == NULL) + return; + +- r = vr->v4; +- /* Must not already be started */ +- start = r->fsm.state == VRRP_STATE_INITIALIZE; +- whynot = (!start && !whynot) ? "Already running" : whynot; +- /* Must have a parent interface */ +- start = start && (vr->ifp != NULL); +- whynot = (!start && !whynot) ? "No base interface" : whynot; ++ if (vr->family == AF_INET){ ++ r = vr->vrrp; ++ /* Must not already be started */ ++ start = r->fsm.state == VRRP_STATE_INITIALIZE; ++ whynot = (!start && !whynot) ? "Already running" : whynot; ++ /* Must have a parent interface */ ++ start = start && (vr->ifp != NULL); ++ whynot = (!start && !whynot) ? "No base interface" : whynot; ++ ++ /* Parent interface must be up */ ++ start = start && if_is_operative(vr->ifp); ++ whynot = (!start && !whynot) ? "Base interface inoperative" : whynot; ++ ++ /* Parent interface must have at least one v4 */ ++ start = start && connected_count_by_family(vr->ifp, AF_INET) > 0; ++ whynot = (!start && !whynot) ? "No primary IPv4 address" : whynot; ++ /* Must have a macvlan interface */ ++ start = start && (r->mvl_ifp != NULL); ++ whynot = (!start && !whynot) ? "No VRRP interface" : whynot; + #if 0 +- /* Parent interface must be up */ +- start = start && if_is_operative(vr->ifp); +- start = (!start && !whynot) ? "Base interface inoperative" : whynot; ++ /* Macvlan interface must be admin up */ ++ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP); ++ start = (!start && !whynot) ? "Macvlan device admin down" : whynot; + #endif +- /* Parent interface must have at least one v4 */ +- start = start && connected_count_by_family(vr->ifp, AF_INET) > 0; +- whynot = (!start && !whynot) ? "No primary IPv4 address" : whynot; +- /* Must have a macvlan interface */ +- start = start && (r->mvl_ifp != NULL); +- whynot = (!start && !whynot) ? "No VRRP interface" : whynot; +-#if 0 +- /* Macvlan interface must be admin up */ +- start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP); +- start = (!start && !whynot) ? "Macvlan device admin down" : whynot; +-#endif +- /* Must have at least one VIP configured */ +- start = start && r->addrs->count > 0; +- whynot = (!start && !whynot) ? "No Virtual IP address configured" +- : whynot; +- if (start) +- vrrp_event(r, VRRP_EVENT_STARTUP); +- else if (whynot) +- zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM +- "Refusing to start Virtual Router: %s", +- vr->vrid, family2str(r->family), whynot); +- +- whynot = NULL; +- +- r = vr->v6; +- /* Must not already be started */ +- start = r->fsm.state == VRRP_STATE_INITIALIZE; +- whynot = (!start && !whynot) ? "Already running" : whynot; +- /* Must not be v2 */ +- start = start && vr->version != 2; +- whynot = (!start && !whynot) ? "VRRPv2 does not support v6" : whynot; +- /* Must have a parent interface */ +- start = start && (vr->ifp != NULL); +- whynot = (!start && !whynot) ? "No base interface" : whynot; +-#if 0 +- /* Parent interface must be up */ +- start = start && if_is_operative(vr->ifp); +- start = (!start && !whynot) ? "Base interface inoperative" : whynot; +-#endif +- /* Must have a macvlan interface */ +- start = start && (r->mvl_ifp != NULL); +- whynot = (!start && !whynot) ? "No VRRP interface" : whynot; ++ /* Must have at least one VIP configured */ ++ start = start && r->addrs->count > 0; ++ whynot = (!start && !whynot) ? "No Virtual IP address configured" ++ : whynot; ++ if (start) ++ vrrp_event(r, VRRP_EVENT_STARTUP); ++ else if (whynot) ++ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM ++ "Refusing to start Virtual Router: %s", ++ vr->vrid, family2str(r->family), whynot); ++ } else if (vr->family == AF_INET6) { ++ //whynot = NULL; ++ ++ r = vr->vrrp; ++ /* Must not already be started */ ++ start = r->fsm.state == VRRP_STATE_INITIALIZE; ++ whynot = (!start && !whynot) ? "Already running" : whynot; ++ /* Must not be v2 */ ++ start = start && vr->version != 2; ++ whynot = (!start && !whynot) ? "VRRPv2 does not support v6" : whynot; ++ /* Must have a parent interface */ ++ start = start && (vr->ifp != NULL); ++ whynot = (!start && !whynot) ? "No base interface" : whynot; ++ ++ /* Parent interface must be up */ ++ start = start && if_is_operative(vr->ifp); ++ whynot = (!start && !whynot) ? "Base interface inoperative" : whynot; ++ ++ /* Must have a macvlan interface */ ++ start = start && (r->mvl_ifp != NULL); ++ whynot = (!start && !whynot) ? "No VRRP interface" : whynot; + #if 0 +- /* Macvlan interface must be admin up */ +- start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP); +- start = (!start && !whynot) ? "Macvlan device admin down" : whynot; +- /* Macvlan interface must have a link local */ +- start = start && connected_get_linklocal(r->mvl_ifp); +- whynot = +- (!start && !whynot) ? "No link local address configured" : whynot; +- /* Macvlan interface must have a v6 IP besides the link local */ +- start = start && (connected_count_by_family(r->mvl_ifp, AF_INET6) > 1); +- whynot = (!start && !whynot) +- ? "No Virtual IPv6 address configured on macvlan device" +- : whynot; ++ /* Macvlan interface must be admin up */ ++ start = start && CHECK_FLAG(r->mvl_ifp->flags, IFF_UP); ++ start = (!start && !whynot) ? "Macvlan device admin down" : whynot; ++ /* Macvlan interface must have a link local */ ++ start = start && connected_get_linklocal(r->mvl_ifp); ++ whynot = ++ (!start && !whynot) ? "No link local address configured" : whynot; ++ /* Macvlan interface must have a v6 IP besides the link local */ ++ start = start && (connected_count_by_family(r->mvl_ifp, AF_INET6) > 1); ++ whynot = (!start && !whynot) ++ ? "No Virtual IPv6 address configured on macvlan device" ++ : whynot; + #endif +- /* Must have at least one VIP configured */ +- start = start && r->addrs->count > 0; +- whynot = +- (!start && !whynot) ? "No Virtual IP address configured" : whynot; +- if (start) +- vrrp_event(r, VRRP_EVENT_STARTUP); +- else if (whynot) +- zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM +- "Refusing to start Virtual Router: %s", +- vr->vrid, family2str(r->family), whynot); ++ /* Must have at least one VIP configured */ ++ start = start && r->addrs->count > 0; ++ whynot = ++ (!start && !whynot) ? "No Virtual IP address configured" : whynot; ++ if (start) ++ vrrp_event(r, VRRP_EVENT_STARTUP); ++ else if (whynot) ++ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM ++ "Refusing to start Virtual Router: %s", ++ vr->vrid, family2str(r->family), whynot); ++ } ++} ++ ++/* Is the interface operative ++ 0 means down ++ 1 means up ++*/ ++static int track_intf_is_operative(const struct track_intf *ifp) ++{ ++ return ifp->status; ++} ++ ++static void vrrp_recalculate_priority(struct vrrp_vrouter *vr) ++{ ++ struct listnode *l_n, *n_n; ++ struct track_intf *iter; ++ int priority_dec = 0; ++ ++ for (ALL_LIST_ELEMENTS(vr->track_intf, l_n, n_n, iter)){ ++ if (!track_intf_is_operative(iter)){ ++ priority_dec += iter->priority_dec; ++ } ++ } ++ ++ if (vr->priority > priority_dec) { ++ vr->vrrp->priority = vr->priority - priority_dec; ++ } else { ++ vr->vrrp->priority = VRRP_PRIORITY_MINIMUM; ++ } ++ ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "vrrp recalculate priority, vrrp family: %d, Effectiv priority: %d", ++ vr->vrid, vr->family, vr->vrrp->priority); + } + + void vrrp_set_priority(struct vrrp_vrouter *vr, uint8_t priority) + { + vr->priority = priority; +- vr->v4->priority = priority; +- vr->v6->priority = priority; ++ if(vr->track_intf->count == 0){ ++ vr->vrrp->priority = priority; ++ } else { ++ vrrp_recalculate_priority(vr); ++ } + } + + void vrrp_set_advertisement_interval(struct vrrp_vrouter *vr, +@@ -394,13 +436,12 @@ void vrrp_set_advertisement_interval(struct vrrp_vrouter *vr, + return; + + vr->advertisement_interval = advertisement_interval; +- vrrp_recalculate_timers(vr->v4); +- vrrp_recalculate_timers(vr->v6); ++ vrrp_recalculate_timers(vr->vrrp); + } + + static bool vrrp_has_ip(struct vrrp_vrouter *vr, struct ipaddr *ip) + { +- struct vrrp_router *r = ip->ipa_type == IPADDR_V4 ? vr->v4 : vr->v6; ++ struct vrrp_router *r = vr->vrrp; + struct listnode *ln; + struct ipaddr *iter; + +@@ -411,9 +452,169 @@ static bool vrrp_has_ip(struct vrrp_vrouter *vr, struct ipaddr *ip) + return false; + } + ++static int vrrp_priority_adjusting(struct vrrp_vrouter *vr, struct track_intf *trac_intf, bool dec_flag) ++{ ++ if (dec_flag) { ++ if (vr->vrrp->priority <= trac_intf->priority_dec){ ++ vr->vrrp->priority = VRRP_PRIORITY_MINIMUM; ++ } else { ++ vr->vrrp->priority -= trac_intf->priority_dec; ++ } ++ } else { ++ if (vr->vrrp->priority == VRRP_PRIORITY_MINIMUM) { ++ vr->vrrp->priority = trac_intf->priority_dec; ++ } else if ((vr->vrrp->priority + trac_intf->priority_dec) > vr->priority) ++ { ++ vr->vrrp->priority = vr->priority; ++ } else { ++ vr->vrrp->priority += trac_intf->priority_dec; ++ } ++ } ++ ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "vrrp instance priority adjusting, vr->family: %d, priority: %d", ++ vr->vrid, vr->family, vr->vrrp->priority); ++ ++ return 1; ++} ++ ++static bool vrrp_has_track_intf(struct vrrp_vrouter *vr, struct interface *intf) ++{ ++ struct listnode *ln; ++ struct track_intf *iter; ++ ++ for (ALL_LIST_ELEMENTS_RO(vr->track_intf, ln, iter)) ++ if (!memcmp(&iter->name, &intf->name, sizeof(intf->name))) ++ return true; ++ ++ return false; ++} ++ ++static bool vrrp_update_track_intf_priority_dec(struct vrrp_vrouter *vr, struct track_intf *intf, const uint8_t priority_dec) ++{ ++ struct listnode *ln; ++ struct track_intf *iter; ++ ++ for (ALL_LIST_ELEMENTS_RO(vr->track_intf, ln, iter)){ ++ if (strcmp(iter->name, intf->name) == 0){ ++ iter->priority_dec = priority_dec; ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "has update the tracking interface priority-dec, interface name: %s, dec-value:%ld", ++ vr->vrid, iter->name, iter->priority_dec); ++ return true; ++ } ++ } ++ return false; ++} ++ ++static struct interface *if_lookup_by_name_all_vrf(const char *name) ++{ ++ struct vrf *vrf; ++ struct interface *ifp; ++ ++ if (!name || strnlen(name, INTERFACE_NAMSIZ) == INTERFACE_NAMSIZ) ++ return NULL; ++ ++ RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { ++ ifp = if_lookup_by_name_vrf(name, vrf); ++ if (ifp) ++ return ifp; ++ } ++ ++ return NULL; ++} ++ ++int vrrp_add_track_interface(struct vrrp_vrouter *vr, const char *intf_name, const uint8_t priority_dec){ ++ struct interface *tack_intf = if_lookup_by_name_all_vrf(intf_name); ++ int intf_status; ++ ++ if (tack_intf == NULL){ ++ intf_status = 0; ++ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "tracking interface name is NULL.", ++ vr->vrid); ++ } else { ++ /* get the interface status info */ ++ intf_status = if_is_operative(tack_intf); ++ } ++ ++ struct track_intf *new = XCALLOC(MTYPE_VRRP_TRACK_INTF, sizeof(struct track_intf)); ++ /* get track interface info */ ++ if (tack_intf == NULL) { ++ strcpy(new->name, intf_name); ++ } else { ++ strcpy(new->name, tack_intf->name); ++ } ++ new->status = intf_status; ++ new->priority_dec = priority_dec; ++ ++ /* if the track interface has configured, updates the priority-dec */ ++ if (vrrp_update_track_intf_priority_dec(vr, new, priority_dec)){ ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "has configured the tracking interface, interface name: %s", ++ vr->vrid, new->name); ++ XFREE(MTYPE_VRRP_TRACK_INTF, new); ++ return 0; ++ } ++ ++ listnode_add(vr->track_intf, new); ++ ++ /* if interface status is down, need to adjust priority */ ++ if(!new->status){ ++ vrrp_priority_adjusting(vr, new, true); ++ } ++ ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "add tracking interface, interface name: %s, state: %d", ++ vr->vrid, new->name, new->status); ++ ++ return 1; ++} ++ ++int vrrp_del_track_interface(struct vrrp_vrouter *vr, const char *intf_name){ ++ struct listnode *ln, *nn; ++ struct track_intf *iter; ++ int intf_status; ++ ++ struct interface *tack_intf = if_lookup_by_name_all_vrf(intf_name); ++ ++ if (tack_intf == NULL){ ++ intf_status = 0; ++ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "tracking interface name is NULL.", ++ vr->vrid); ++ } else { ++ /* get the interface status info */ ++ intf_status = if_is_operative(tack_intf); ++ if (!vrrp_has_track_intf(vr, tack_intf)){ ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "has not configured the tracking interface, interface name: %s", ++ vr->vrid, tack_intf->name); ++ return 0; ++ } ++ } ++ ++ //struct interface *new = XCALLOC(MTYPE_VRRP_TRACK_INTF, sizeof(struct interface)); ++ ++ for (ALL_LIST_ELEMENTS(vr->track_intf, ln, nn, iter)){ ++ if (!strcmp(iter->name, intf_name)){ ++ if(!intf_status){ ++ vrrp_priority_adjusting(vr, iter, false); ++ } ++ list_delete_node(vr->track_intf, ln); ++ } ++ } ++ ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "del tracking interface, interface name: %s, state: %d", ++ vr->vrid, intf_name, intf_status); ++ ++ return 1; ++} ++ + int vrrp_add_ip(struct vrrp_vrouter *vr, struct ipaddr *ip) + { +- struct vrrp_router *r = IS_IPADDR_V4(ip) ? vr->v4 : vr->v6; ++ struct vrrp_router *r = vr->vrrp; + int af = r->family; + + assert(r->family == af); +@@ -478,7 +679,7 @@ int vrrp_del_ip(struct vrrp_vrouter *vr, struct ipaddr *ip) + struct ipaddr *iter; + int ret = 0; + +- struct vrrp_router *r = IS_IPADDR_V4(ip) ? vr->v4 : vr->v6; ++ struct vrrp_router *r = vr->vrrp; + + if (!vrrp_has_ip(r->vr, ip)) + return 0; +@@ -527,6 +728,14 @@ static void vrrp_router_addr_list_del_cb(void *val) + XFREE(MTYPE_VRRP_IP, ip); + } + ++static void vrrp_vrouter_track_intf_list_del_cb(void *val) ++{ ++ struct track_intf *intf = val; ++ ++ XFREE(MTYPE_VRRP_TRACK_INTF, intf); ++} ++ ++ + /* + * Search for a suitable macvlan subinterface we can attach to, and if found, + * attach to it. +@@ -629,9 +838,9 @@ static void vrrp_router_destroy(struct vrrp_router *r) + } + + struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid, +- uint8_t version) ++ uint8_t version, int family) + { +- struct vrrp_vrouter *vr = vrrp_lookup(ifp, vrid); ++ struct vrrp_vrouter *vr = vrrp_lookup(ifp, vrid, family); + + if (vr) + return vr; +@@ -644,15 +853,21 @@ struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid, + vr->ifp = ifp; + vr->version = version; + vr->vrid = vrid; ++ vr->family = family; + vr->priority = vd.priority; + vr->preempt_mode = vd.preempt_mode; + vr->accept_mode = vd.accept_mode; + vr->checksum_with_ipv4_pseudoheader = + vd.checksum_with_ipv4_pseudoheader; + vr->shutdown = vd.shutdown; ++ vr->track_intf = list_new(); ++ vr->track_intf->del = vrrp_vrouter_track_intf_list_del_cb; + +- vr->v4 = vrrp_router_create(vr, AF_INET); +- vr->v6 = vrrp_router_create(vr, AF_INET6); ++ if (vr->family == AF_INET){ ++ vr->vrrp = vrrp_router_create(vr, AF_INET); ++ } else { ++ vr->vrrp = vrrp_router_create(vr, AF_INET6); ++ } + + vrrp_set_advertisement_interval(vr, vd.advertisement_interval); + +@@ -663,13 +878,13 @@ struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid, + + void vrrp_vrouter_destroy(struct vrrp_vrouter *vr) + { +- vrrp_router_destroy(vr->v4); +- vrrp_router_destroy(vr->v6); ++ vrrp_router_destroy(vr->vrrp); ++ list_delete(&vr->track_intf); + hash_release(vrrp_vrouters_hash, vr); + XFREE(MTYPE_VRRP_RTR, vr); + } + +-struct vrrp_vrouter *vrrp_lookup(const struct interface *ifp, uint8_t vrid) ++struct vrrp_vrouter *vrrp_lookup(const struct interface *ifp, uint8_t vrid, int family) + { + if (!ifp) + return NULL; +@@ -678,6 +893,7 @@ struct vrrp_vrouter *vrrp_lookup(const struct interface *ifp, uint8_t vrid) + + vr.vrid = vrid; + vr.ifp = (struct interface *)ifp; ++ vr.family = family; + + return hash_lookup(vrrp_vrouters_hash, &vr); + } +@@ -1766,7 +1982,18 @@ vrrp_autoconfig_autocreate(struct interface *mvl_ifp) + "Autoconfiguring VRRP on %s", + vrid, family2str(fam), p->name); + +- vr = vrrp_vrouter_create(p, vrid, vrrp_autoconfig_version); ++ if (vrrp_autoconfig_is_on && fam == AF_INET) ++ { ++ vr = vrrp_vrouter_create(p, vrid, vrrp_autoconfig_version, fam); ++ } else if (vrrp6_autoconfig_is_on && fam == AF_INET6) ++ { ++ vr = vrrp_vrouter_create(p, vrid, vrrp6_autoconfig_version, fam); ++ } else { ++ zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM ++ "Failed to autoconfigure VRRP on %s, due to the vrrp family mismatch.", ++ vrid, family2str(fam), p->name); ++ return NULL; ++ } + + if (!vr) { + zlog_warn(VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM +@@ -1782,14 +2009,11 @@ vrrp_autoconfig_autocreate(struct interface *mvl_ifp) + * in order to get Zebra to send us their addresses so we can + * autoconfigure them. + */ +- if (vr->v4->mvl_ifp) +- vrrp_zclient_send_interface_protodown(vr->v4->mvl_ifp, false); +- if (vr->v6->mvl_ifp) +- vrrp_zclient_send_interface_protodown(vr->v6->mvl_ifp, false); ++ if (vr->vrrp->mvl_ifp) ++ vrrp_zclient_send_interface_protodown(vr->vrrp->mvl_ifp, false); + + /* If they're not, we can go ahead and add the addresses we have */ +- vrrp_autoconfig_autoaddrupdate(vr->v4); +- vrrp_autoconfig_autoaddrupdate(vr->v6); ++ vrrp_autoconfig_autoaddrupdate(vr->vrrp); + + return vr; + } +@@ -1813,7 +2037,7 @@ static int vrrp_autoconfig_if_add(struct interface *ifp) + bool created = false; + struct vrrp_vrouter *vr; + +- if (!vrrp_autoconfig_is_on) ++ if (!vrrp_autoconfig_is_on && !vrrp6_autoconfig_is_on) + return 0; + + if (!ifp || !ifp->link_ifindex || !vrrp_ifp_has_vrrp_mac(ifp)) +@@ -1823,7 +2047,10 @@ static int vrrp_autoconfig_if_add(struct interface *ifp) + + if (!vr) { + vr = vrrp_autoconfig_autocreate(ifp); +- created = true; ++ if (vr != NULL) ++ { ++ created = true; ++ } + } + + if (!vr || !vr->autoconf) +@@ -1834,30 +2061,17 @@ static int vrrp_autoconfig_if_add(struct interface *ifp) + * We didn't create it, but it has already been autoconfigured. + * Try to attach this interface to the existing instance. + */ +- if (!vr->v4->mvl_ifp) { +- vrrp_attach_interface(vr->v4); ++ if (!vr->vrrp->mvl_ifp) { ++ vrrp_attach_interface(vr->vrrp); + /* If we just attached it, make sure it's turned on */ +- if (vr->v4->mvl_ifp) { ++ if (vr->vrrp->mvl_ifp) { + vrrp_zclient_send_interface_protodown( +- vr->v4->mvl_ifp, false); ++ vr->vrrp->mvl_ifp, false); + /* + * If it's already up, we can go ahead and add + * the addresses we have + */ +- vrrp_autoconfig_autoaddrupdate(vr->v4); +- } +- } +- if (!vr->v6->mvl_ifp) { +- vrrp_attach_interface(vr->v6); +- /* If we just attached it, make sure it's turned on */ +- if (vr->v6->mvl_ifp) { +- vrrp_zclient_send_interface_protodown( +- vr->v6->mvl_ifp, false); +- /* +- * If it's already up, we can go ahead and add +- * the addresses we have +- */ +- vrrp_autoconfig_autoaddrupdate(vr->v6); ++ vrrp_autoconfig_autoaddrupdate(vr->vrrp); + } + } + } +@@ -1883,7 +2097,7 @@ static int vrrp_autoconfig_if_add(struct interface *ifp) + */ + static int vrrp_autoconfig_if_del(struct interface *ifp) + { +- if (!vrrp_autoconfig_is_on) ++ if (!vrrp_autoconfig_is_on && !vrrp6_autoconfig_is_on) + return 0; + + struct vrrp_vrouter *vr; +@@ -1894,7 +2108,7 @@ static int vrrp_autoconfig_if_del(struct interface *ifp) + + for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) + if (vr->autoconf +- && (!vr->ifp || (!vr->v4->mvl_ifp && !vr->v6->mvl_ifp))) { ++ && (!vr->ifp || (!vr->vrrp->mvl_ifp))) { + DEBUGD(&vrrp_dbg_auto, + VRRP_LOGPFX VRRP_LOGPFX_VRID + "All VRRP interfaces for instance deleted; destroying autoconfigured VRRP router", +@@ -1922,7 +2136,7 @@ static int vrrp_autoconfig_if_del(struct interface *ifp) + */ + static int vrrp_autoconfig_if_up(struct interface *ifp) + { +- if (!vrrp_autoconfig_is_on) ++ if (!vrrp_autoconfig_is_on && !vrrp6_autoconfig_is_on) + return 0; + + struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp); +@@ -1955,7 +2169,7 @@ static int vrrp_autoconfig_if_up(struct interface *ifp) + */ + static int vrrp_autoconfig_if_down(struct interface *ifp) + { +- if (!vrrp_autoconfig_is_on) ++ if (!vrrp_autoconfig_is_on && !vrrp6_autoconfig_is_on) + return 0; + + return 0; +@@ -1978,16 +2192,14 @@ static int vrrp_autoconfig_if_down(struct interface *ifp) + */ + static int vrrp_autoconfig_if_address_add(struct interface *ifp) + { +- if (!vrrp_autoconfig_is_on) ++ if (!vrrp_autoconfig_is_on && !vrrp6_autoconfig_is_on) + return 0; + + struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp); + + if (vr && vr->autoconf) { +- if (vr->v4->mvl_ifp == ifp) +- vrrp_autoconfig_autoaddrupdate(vr->v4); +- else if (vr->v6->mvl_ifp == ifp) +- vrrp_autoconfig_autoaddrupdate(vr->v6); ++ if (vr->vrrp->mvl_ifp == ifp) ++ vrrp_autoconfig_autoaddrupdate(vr->vrrp); + } + + return 0; +@@ -2010,16 +2222,14 @@ static int vrrp_autoconfig_if_address_add(struct interface *ifp) + */ + static int vrrp_autoconfig_if_address_del(struct interface *ifp) + { +- if (!vrrp_autoconfig_is_on) ++ if (!vrrp_autoconfig_is_on && !vrrp6_autoconfig_is_on) + return 0; + + struct vrrp_vrouter *vr = vrrp_lookup_by_if_mvl(ifp); + + if (vr && vr->autoconf) { +- if (vr->v4->mvl_ifp == ifp) +- vrrp_autoconfig_autoaddrupdate(vr->v4); +- else if (vr->v6->mvl_ifp == ifp) +- vrrp_autoconfig_autoaddrupdate(vr->v6); ++ if (vr->vrrp->mvl_ifp == ifp) ++ vrrp_autoconfig_autoaddrupdate(vr->vrrp); + } + + return 0; +@@ -2027,7 +2237,7 @@ static int vrrp_autoconfig_if_address_del(struct interface *ifp) + + int vrrp_autoconfig(void) + { +- if (!vrrp_autoconfig_is_on) ++ if (!vrrp_autoconfig_is_on && !vrrp6_autoconfig_is_on) + return 0; + + struct vrf *vrf; +@@ -2049,6 +2259,13 @@ void vrrp_autoconfig_on(int version) + vrrp_autoconfig(); + } + ++void vrrp6_autoconfig_on(void) ++{ ++ vrrp6_autoconfig_is_on = true; ++ ++ vrrp_autoconfig(); ++} ++ + void vrrp_autoconfig_off(void) + { + vrrp_autoconfig_is_on = false; +@@ -2059,12 +2276,28 @@ void vrrp_autoconfig_off(void) + struct vrrp_vrouter *vr; + + for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) +- if (vr->autoconf) ++ if (vr->autoconf && vr->family == AF_INET) + vrrp_vrouter_destroy(vr); + + list_delete(&ll); + } + ++void vrrp6_autoconfig_off(void) ++{ ++ vrrp6_autoconfig_is_on = false; ++ ++ struct list *ll = hash_to_list(vrrp_vrouters_hash); ++ ++ struct listnode *ln; ++ struct vrrp_vrouter *vr; ++ ++ for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) ++ if (vr->autoconf && vr->family == AF_INET6) ++ vrrp_vrouter_destroy(vr); ++ ++ list_delete(&ll); ++} ++ + /* Interface tracking ------------------------------------------------------ */ + + /* +@@ -2090,10 +2323,37 @@ static void vrrp_bind_pending(struct interface *mvl_ifp) + "<-- This instance can probably use interface %s", + vr->vrid, mvl_ifp->name); + +- if (mvl_ifp->hw_addr[4] == 0x01 && !vr->v4->mvl_ifp) +- vrrp_attach_interface(vr->v4); +- else if (mvl_ifp->hw_addr[4] == 0x02 && !vr->v6->mvl_ifp) +- vrrp_attach_interface(vr->v6); ++ if ((mvl_ifp->hw_addr[4] == 0x01 || mvl_ifp->hw_addr[4] == 0x02) && !vr->vrrp->mvl_ifp) ++ vrrp_attach_interface(vr->vrrp); ++ } ++} ++ ++void vrrp_track_intf_state_change(struct interface *ifp, bool is_down) ++{ ++ struct listnode *ln, *l_n, *nn, *n_n; ++ struct vrrp_vrouter *vr; ++ struct track_intf *iter; ++ struct list *vrs = hash_to_list(vrrp_vrouters_hash); ++ ++ zlog_info(VRRP_LOGPFX ++ "vrrp_track_intf_state_change, interface name: %s, status: %d", ++ ifp->name, ifp->status); ++ // match vrrp instance tracking interface, and reduce priority ++ for (ALL_LIST_ELEMENTS(vrs, ln, nn, vr)){ ++ for (ALL_LIST_ELEMENTS(vr->track_intf, l_n, n_n, iter)){ ++ if (!memcmp(&iter->name, &ifp->name, sizeof(ifp->name))){ ++ if (if_is_operative(ifp) != track_intf_is_operative(iter)){ ++ vrrp_priority_adjusting(vr, iter, is_down); ++ } ++ //update track intf state ++ iter->status = if_is_operative(ifp); ++ ++ zlog_info(VRRP_LOGPFX VRRP_LOGPFX_VRID ++ "update tracking interface state, interface name: %s, status: %d", ++ vr->vrid, iter->name, iter->status); ++ break; ++ } ++ } + } + } + +@@ -2118,47 +2378,37 @@ void vrrp_if_up(struct interface *ifp) + * transition on this VRRP router but needed to wait for the + * macvlan interface to come up to perform some actions + */ +- if (ifp == vr->v4->mvl_ifp) { +- if (vr->v4->advert_pending) { ++ if (ifp == vr->vrrp->mvl_ifp) { ++ if (vr->vrrp->advert_pending) { + DEBUGD(&vrrp_dbg_proto, + VRRP_LOGPFX VRRP_LOGPFX_VRID + VRRP_LOGPFX_FAM + "Interface up; sending pending advertisement", +- vr->vrid, family2str(vr->v4->family)); +- vrrp_send_advertisement(vr->v4); +- vr->v4->advert_pending = false; ++ vr->vrid, family2str(vr->vrrp->family)); ++ vrrp_send_advertisement(vr->vrrp); ++ vr->vrrp->advert_pending = false; + } +- if (vr->v4->garp_pending) { ++ if (vr->family == AF_INET && vr->vrrp->garp_pending) { + DEBUGD(&vrrp_dbg_proto, + VRRP_LOGPFX VRRP_LOGPFX_VRID + VRRP_LOGPFX_FAM + "Interface up; sending pending gratuitous ARP", +- vr->vrid, family2str(vr->v4->family)); +- vrrp_garp_send_all(vr->v4); +- vr->v4->garp_pending = false; +- } +- } +- if (ifp == vr->v6->mvl_ifp) { +- if (vr->v6->advert_pending) { +- DEBUGD(&vrrp_dbg_proto, +- VRRP_LOGPFX VRRP_LOGPFX_VRID +- VRRP_LOGPFX_FAM +- "Interface up; sending pending advertisement", +- vr->vrid, family2str(vr->v6->family)); +- vrrp_send_advertisement(vr->v6); +- vr->v6->advert_pending = false; +- } +- if (vr->v6->ndisc_pending) { ++ vr->vrid, family2str(vr->vrrp->family)); ++ vrrp_garp_send_all(vr->vrrp); ++ vr->vrrp->garp_pending = false; ++ } else if (vr->family == AF_INET6 && vr->vrrp->ndisc_pending) { + DEBUGD(&vrrp_dbg_proto, + VRRP_LOGPFX VRRP_LOGPFX_VRID + VRRP_LOGPFX_FAM + "Interface up; sending pending Unsolicited Neighbor Advertisement", +- vr->vrid, family2str(vr->v6->family)); +- vrrp_ndisc_una_send_all(vr->v6); +- vr->v6->ndisc_pending = false; ++ vr->vrid, family2str(vr->vrrp->family)); ++ vrrp_ndisc_una_send_all(vr->vrrp); ++ vr->vrrp->ndisc_pending = false; + } + } + } ++ //call track intf state change func, false means intf state is not down. ++ vrrp_track_intf_state_change(ifp, false); + + list_delete(&vrs); + +@@ -2178,13 +2428,14 @@ void vrrp_if_down(struct interface *ifp) + for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) { + vrrp_check_start(vr); + +- if (vr->ifp == ifp || vr->v4->mvl_ifp == ifp +- || vr->v6->mvl_ifp == ifp) { ++ if (vr->ifp == ifp || vr->vrrp->mvl_ifp == ifp) { + DEBUGD(&vrrp_dbg_auto, + VRRP_LOGPFX VRRP_LOGPFX_VRID "Interface %s down", + vr->vrid, ifp->name); + } + } ++ //call track intf state change func, true means intf state is down. ++ vrrp_track_intf_state_change(ifp, true); + + list_delete(&vrs); + +@@ -2227,14 +2478,12 @@ void vrrp_if_del(struct interface *ifp) + + for (ALL_LIST_ELEMENTS_RO(vrs, ln, vr)) { + if (ifp == vr->ifp) { +- vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN); +- vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN); ++ vrrp_event(vr->vrrp, VRRP_EVENT_SHUTDOWN); + /* + * Stands to reason if the base was deleted, so were + * (or will be) its children + */ +- vr->v4->mvl_ifp = NULL; +- vr->v6->mvl_ifp = NULL; ++ vr->vrrp->mvl_ifp = NULL; + /* + * We shouldn't need to lose the reference if it's the + * primary interface, because that was configured +@@ -2242,22 +2491,14 @@ void vrrp_if_del(struct interface *ifp) + * stub; to avoid stupid bugs, double check that + */ + assert(ifp->configured); +- } else if (ifp == vr->v4->mvl_ifp) { +- vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN); ++ } else if (ifp == vr->vrrp->mvl_ifp) { ++ vrrp_event(vr->vrrp, VRRP_EVENT_SHUTDOWN); + /* + * If this is a macvlan, then it wasn't explicitly + * configured and will be deleted when we return from + * this function, so we need to lose the reference + */ +- vr->v4->mvl_ifp = NULL; +- } else if (ifp == vr->v6->mvl_ifp) { +- vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN); +- /* +- * If this is a macvlan, then it wasn't explicitly +- * configured and will be deleted when we return from +- * this function, so we need to lose the reference +- */ +- vr->v6->mvl_ifp = NULL; ++ vr->vrrp->mvl_ifp = NULL; + } + } + +@@ -2373,7 +2614,7 @@ static unsigned int vrrp_hash_key(const void *arg) + const struct vrrp_vrouter *vr = arg; + char key[IFNAMSIZ + 64]; + +- snprintf(key, sizeof(key), "%s@%u", vr->ifp->name, vr->vrid); ++ snprintf(key, sizeof(key), "%s@%u@%d", vr->ifp->name, vr->vrid, vr->family); + + return string_hash_make(key); + } +@@ -2387,6 +2628,8 @@ static bool vrrp_hash_cmp(const void *arg1, const void *arg2) + return false; + if (vr1->vrid != vr2->vrid) + return false; ++ if (vr1->family != vr2->family) ++ return false; + + return true; + } +@@ -2406,6 +2649,7 @@ void vrrp_init(void) + vd.shutdown = VRRP_DEFAULT_SHUTDOWN; + + vrrp_autoconfig_version = 3; ++ vrrp6_autoconfig_version = 3; + vrrp_vrouters_hash = hash_create(&vrrp_hash_key, vrrp_hash_cmp, + "VRRP virtual router hash"); + vrf_init(NULL, NULL, NULL, NULL); +diff --git a/vrrpd/vrrp.h b/vrrpd/vrrp.h +index b3141ef31..0711de3f3 100644 +--- a/vrrpd/vrrp.h ++++ b/vrrpd/vrrp.h +@@ -49,12 +49,14 @@ + /* Default defaults */ + #define VRRP_XPATH_FULL "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group" + #define VRRP_XPATH "./frr-vrrpd:vrrp/vrrp-group" ++#define VRRP6_XPATH "./frr-vrrpd:vrrp/vrrp6-group" + #define VRRP_DEFAULT_PRIORITY 100 + #define VRRP_DEFAULT_ADVINT 100 + #define VRRP_DEFAULT_PREEMPT true + #define VRRP_DEFAULT_ACCEPT true + #define VRRP_DEFAULT_CHECKSUM_WITH_IPV4_PSEUDOHEADER true + #define VRRP_DEFAULT_SHUTDOWN false ++#define VRRP_PRIORITY_MINIMUM 1 + + /* User compatibility constant */ + #define CS2MS 10 +@@ -245,9 +247,22 @@ struct vrrp_vrouter { + /* Virtual Router Identifier */ + uint32_t vrid; + ++ /* ++ * Address family of this Virtual Router. ++ * Either AF_INET or AF_INET6. ++ */ ++ int family; ++ + /* Configured priority */ + uint8_t priority; + ++ /* ++ * VRRP instance tracking interfaces list ++ * ++ * Type: struct track_intf * ++ */ ++ struct list *track_intf; ++ + /* + * Time interval between ADVERTISEMENTS (centiseconds). Default is 100 + * centiseconds (1 second). +@@ -276,10 +291,31 @@ struct vrrp_vrouter { + */ + bool checksum_with_ipv4_pseudoheader; + +- struct vrrp_router *v4; +- struct vrrp_router *v6; ++ struct vrrp_router *vrrp; + }; + ++/* ++ * Track interface. ++ * ++ * This struct contains interface name ande priority dec. ++ */ ++struct track_intf { ++ /* Interface name. */ ++ char name[INTERFACE_NAMSIZ]; ++ ++ /* interface status ++ 0 means down ++ 1 means up ++ */ ++ int status; ++ ++ /* Specifies how much to decrement the priority of the VRRP instance ++ if the tracking interface goes down. ++ */ ++ uint8_t priority_dec; ++}; ++ ++ + /* + * Initialize VRRP global datastructures. + */ +@@ -306,7 +342,7 @@ void vrrp_fini(void); + * Virtual Router Identifier + */ + struct vrrp_vrouter *vrrp_vrouter_create(struct interface *ifp, uint8_t vrid, +- uint8_t version); ++ uint8_t version, int family); + + /* + * Destroy a VRRP Virtual Router, freeing all its resources. +@@ -474,6 +510,38 @@ int vrrp_del_ipv4(struct vrrp_vrouter *vr, struct in_addr v4); + */ + int vrrp_del_ipv6(struct vrrp_vrouter *vr, struct in6_addr v6); + ++/* ++ * Add an Tracking interface to a VRRP Virtual Router. ++ * ++ * vr ++ * Virtual Router to add IPvx address to ++ * ++ * track_intf ++ * interface to add ++ * ++ * ++ * Returns: ++ * -1 on error ++ * 0 otherwise ++ */ ++int vrrp_add_track_interface(struct vrrp_vrouter *vr, const char *track_intf, const uint8_t priority_dec); ++ ++/* ++ * Del an Tracking interface to a VRRP Virtual Router. ++ * ++ * vr ++ * Virtual Router to add IPvx address to ++ * ++ * track_intf ++ * interface to del ++ * ++ * ++ * Returns: ++ * -1 on error ++ * 0 otherwise ++ */ ++int vrrp_del_track_interface(struct vrrp_vrouter *vr, const char *track_intf); ++ + /* State machine ----------------------------------------------------------- */ + + #define VRRP_STATE_INITIALIZE 0 +@@ -541,6 +609,17 @@ int vrrp_autoconfig(void); + */ + void vrrp_autoconfig_on(int version); + ++/* ++ * Enable autoconfiguration. ++ * ++ * Calling this function will cause vrrpd to automatically configure VRRPv6 ++ * instances on existing compatible macvlan interfaces. These instances will ++ * react to interface up/down and address add/delete events to keep themselves ++ * in sync with the available interfaces. ++ * ++ */ ++void vrrp6_autoconfig_on(void); ++ + /* + * Disable autoconfiguration. + * +@@ -548,6 +627,13 @@ void vrrp_autoconfig_on(int version); + */ + void vrrp_autoconfig_off(void); + ++/* ++ * Disable autoconfiguration. ++ * ++ * Calling this function will delete all existing autoconfigured VRRPv6 instances. ++ */ ++void vrrp6_autoconfig_off(void); ++ + /* Interface Tracking ------------------------------------------------------ */ + + void vrrp_if_add(struct interface *ifp); +@@ -573,6 +659,6 @@ int vrrp_config_write_global(struct vty *vty); + /* + * Find VRRP Virtual Router by Virtual Router ID + */ +-struct vrrp_vrouter *vrrp_lookup(const struct interface *ifp, uint8_t vrid); ++struct vrrp_vrouter *vrrp_lookup(const struct interface *ifp, uint8_t vrid, int family); + + #endif /* __VRRP_H__ */ +diff --git a/vrrpd/vrrp6_vty.c b/vrrpd/vrrp6_vty.c +new file mode 100644 +index 000000000..2511876c2 +--- /dev/null ++++ b/vrrpd/vrrp6_vty.c +@@ -0,0 +1,657 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * VRRP6 CLI commands. ++ * Copyright (C) 2022-2024 Micas Networks, Inc. ++ * Leo ++ */ ++#include ++ ++#include "lib/command.h" ++#include "lib/if.h" ++#include "lib/ipaddr.h" ++#include "lib/json.h" ++#include "lib/northbound_cli.h" ++#include "lib/prefix.h" ++#include "lib/termtable.h" ++#include "lib/vty.h" ++#include "lib/vrf.h" ++ ++#include "vrrp.h" ++#include "vrrp_debug.h" ++#include "vrrp6_vty.h" ++#include "vrrp_zebra.h" ++#ifndef VTYSH_EXTRACT_PL ++#include "vrrpd/vrrp6_vty_clippy.c" ++#endif ++ ++ ++#define VRRP6_STR "Virtual Router Redundancy Protocol for IPv6\n" ++#define VRRP6_VRID_STR "Virtual Router ID\n" ++#define VRRP6_PRIORITY_STR "Virtual Router Priority\n" ++#define VRRP6_ADVINT_STR "Virtual Router Advertisement Interval\n" ++#define VRRP6_IP_STR "Virtual Router IPv6 address\n" ++#define VRRP6_VERSION_STR "VRRPv6 protocol version\n" ++#define VRRP6_TRACK_INTF_STR "VRRPv6 Track Interface name\n" ++#define VRRP6_PRIORITY_DECREMENT_STR "VRRPv6 Track Interface Decrement Priority\n" ++ ++#define VRRP6_XPATH_ENTRY VRRP6_XPATH "[virtual-router-id='%ld'][family='%s']" ++#define VRRP6_FAMILY "VRRPv6" ++ ++/* clang-format off */ ++ ++/* ++ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group ++ */ ++DEFPY_YANG(vrrp6_vrid, ++ vrrp6_vrid_cmd, ++ "[no] vrrp6 (1-255)$vrid [version (2-3)]", ++ NO_STR ++ VRRP6_STR ++ VRRP6_VRID_STR ++ VRRP6_VERSION_STR ++ VRRP6_VERSION_STR) ++{ ++ char valbuf[20]; ++ ++ snprintf(valbuf, sizeof(valbuf), "%ld", version ? version : vd.version); ++ ++ if (no) ++ nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); ++ else { ++ nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); ++ nb_cli_enqueue_change(vty, "./version", NB_OP_MODIFY, valbuf); ++ } ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++void cli_show_vrrp6(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) ++{ ++ const char *vrid = yang_dnode_get_string(dnode, "./virtual-router-id"); ++ const char *ver = yang_dnode_get_string(dnode, "./version"); ++ ++ vty_out(vty, " vrrp6 %s", vrid); ++ if (show_defaults || !yang_dnode_is_default(dnode, "./version")) ++ vty_out(vty, " version %s", ver); ++ vty_out(vty, "\n"); ++} ++ ++/* ++ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/shutdown ++ */ ++DEFPY_YANG(vrrp6_shutdown, ++ vrrp6_shutdown_cmd, ++ "[no] vrrp6 (1-255)$vrid shutdown", ++ NO_STR ++ VRRP6_STR ++ VRRP6_VRID_STR ++ "Force VRRPv6 router into administrative shutdown\n") ++{ ++ nb_cli_enqueue_change(vty, "./shutdown", NB_OP_MODIFY, ++ no ? "false" : "true"); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++void cli_show_shutdown_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults) ++{ ++ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id"); ++ const bool shut = yang_dnode_get_bool(dnode, NULL); ++ ++ vty_out(vty, " %svrrp6 %s shutdown\n", shut ? "" : "no ", vrid); ++} ++ ++/* ++ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/priority ++ */ ++DEFPY_YANG(vrrp6_priority, ++ vrrp6_priority_cmd, ++ "vrrp6 (1-255)$vrid priority (1-254)", ++ VRRP6_STR ++ VRRP6_VRID_STR ++ VRRP6_PRIORITY_STR ++ "Priority value") ++{ ++ nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, priority_str); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++/* ++ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/priority ++ */ ++DEFPY_YANG(no_vrrp6_priority, ++ no_vrrp6_priority_cmd, ++ "no vrrp6 (1-255)$vrid priority [(1-254)]", ++ NO_STR ++ VRRP6_STR ++ VRRP6_VRID_STR ++ VRRP6_PRIORITY_STR ++ "Priority value") ++{ ++ nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, NULL); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++void cli_show_priority_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults) ++{ ++ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id"); ++ const char *prio = yang_dnode_get_string(dnode, NULL); ++ ++ vty_out(vty, " vrrp6 %s priority %s\n", vrid, prio); ++} ++ ++/* ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/advertisement-interval ++ */ ++DEFPY_YANG(vrrp6_advertisement_interval, ++ vrrp6_advertisement_interval_cmd, ++ "vrrp6 (1-255)$vrid advertisement-interval (10-40950)", ++ VRRP6_STR VRRP6_VRID_STR VRRP6_ADVINT_STR ++ "Advertisement interval in milliseconds; must be multiple of 10") ++{ ++ char val[20]; ++ ++ /* all internal computations are in centiseconds */ ++ advertisement_interval /= CS2MS; ++ snprintf(val, sizeof(val), "%ld", advertisement_interval); ++ nb_cli_enqueue_change(vty, "./advertisement-interval", NB_OP_MODIFY, ++ val); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++/* ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/advertisement-interval ++ */ ++DEFPY_YANG(no_vrrp6_advertisement_interval, ++ no_vrrp6_advertisement_interval_cmd, ++ "no vrrp6 (1-255)$vrid advertisement-interval [(10-40950)]", ++ NO_STR VRRP6_STR VRRP6_VRID_STR VRRP6_ADVINT_STR ++ "Advertisement interval in milliseconds; must be multiple of 10") ++{ ++ nb_cli_enqueue_change(vty, "./advertisement-interval", NB_OP_MODIFY, ++ NULL); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++void cli_show_advertisement_interval_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults) ++{ ++ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id"); ++ uint16_t advint = yang_dnode_get_uint16(dnode, NULL); ++ ++ vty_out(vty, " vrrp6 %s advertisement-interval %u\n", vrid, ++ advint * CS2MS); ++} ++ ++/* ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/virtual-address ++ */ ++DEFPY_YANG(vrrp6_ip6, ++ vrrp6_ip6_cmd, ++ "[no] vrrp6 (1-255)$vrid ipv6 X:X::X:X", ++ NO_STR ++ VRRP6_STR ++ VRRP6_VRID_STR ++ "Add IPv6 address\n" ++ VRRP6_IP_STR) ++{ ++ int op = no ? NB_OP_DESTROY : NB_OP_CREATE; ++ nb_cli_enqueue_change(vty, "./v6/virtual-address", op, ipv6_str); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++void cli_show_ipv6_vrrp6(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) ++{ ++ const char *vrid = ++ yang_dnode_get_string(dnode, "../../virtual-router-id"); ++ const char *ipv6 = yang_dnode_get_string(dnode, NULL); ++ ++ vty_out(vty, " vrrp6 %s ipv6 %s\n", vrid, ipv6); ++} ++ ++/* ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/track-interface ++ */ ++DEFPY_YANG(vrrp6_track_interface, ++ vrrp6_track_interface_cmd, ++ "vrrp6 (1-255)$vrid track-interface IFNAME$intf_name priority-dec (10-50)", ++ VRRP6_STR ++ VRRP6_VRID_STR ++ "Add tracking interface\n" ++ VRRP6_TRACK_INTF_STR ++ VRRP6_PRIORITY_DECREMENT_STR ++ "Priority decrement value\n") ++{ ++ char xpath[XPATH_MAXLEN]; ++ char xpath_track[XPATH_MAXLEN + 32]; ++ ++ snprintf(xpath, sizeof(xpath), ++ "./track-interface[interface='%s']", ++ intf_name); ++ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); ++ ++ snprintf(xpath_track, sizeof(xpath_track), ++ "%s/priority-decrement", xpath); ++ nb_cli_enqueue_change(vty, xpath_track, NB_OP_MODIFY, priority_dec_str); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++DEFPY_YANG(no_vrrp6_track_interface, ++ no_vrrp6_track_interface_cmd, ++ "no vrrp6 (1-255)$vrid track-interface IFNAME$intf_name", ++ NO_STR ++ VRRP6_STR ++ VRRP6_VRID_STR ++ "Add tracking interface\n" ++ VRRP6_TRACK_INTF_STR ++ VRRP6_PRIORITY_DECREMENT_STR ++ "Priority decrement value\n") ++{ ++ char xpath[XPATH_MAXLEN]; ++ ++ snprintf(xpath, sizeof(xpath), ++ "./track-interface[interface='%s']", ++ intf_name); ++ ++ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++void cli_show_track_interface_vrrp6(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) ++{ ++ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id"); ++ const char *track_intf = yang_dnode_get_string(dnode, "./interface"); ++ ++ const char *priority_dec = yang_dnode_get_string(dnode, "./priority-decrement"); ++ ++ vty_out(vty, " vrrp6 %s track-interface %s priority-dec %s\n", vrid, track_intf, priority_dec); ++} ++ ++/* ++ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/preempt ++ */ ++DEFPY_YANG(vrrp6_preempt, ++ vrrp6_preempt_cmd, ++ "[no] vrrp6 (1-255)$vrid preempt", ++ NO_STR ++ VRRP6_STR ++ VRRP6_VRID_STR ++ "Preempt mode\n") ++{ ++ nb_cli_enqueue_change(vty, "./preempt", NB_OP_MODIFY, ++ no ? "false" : "true"); ++ ++ return nb_cli_apply_changes(vty, VRRP6_XPATH_ENTRY, vrid, VRRP6_FAMILY); ++} ++ ++void cli_show_preempt_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults) ++{ ++ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id"); ++ const bool pre = yang_dnode_get_bool(dnode, NULL); ++ ++ vty_out(vty, " %svrrp6 %s preempt\n", pre ? "" : "no ", vrid); ++} ++ ++/* XXX: yang conversion */ ++DEFPY_YANG(vrrp6_autoconfigure, ++ vrrp6_autoconfigure_cmd, ++ "[no] vrrp6 autoconfigure", ++ NO_STR ++ VRRP6_STR ++ "Automatically set up VRRP6 instances on VRRP-compatible interfaces\n") ++{ ++ if (!no) ++ vrrp6_autoconfig_on(); ++ else ++ vrrp6_autoconfig_off(); ++ ++ return CMD_SUCCESS; ++} ++ ++/* XXX: yang conversion */ ++DEFPY_YANG(vrrp6_default, ++ vrrp6_default_cmd, ++ "[no] vrrp6 default ", ++ NO_STR ++ VRRP6_STR ++ "Configure defaults for new VRRPv6 instances\n" ++ VRRP6_ADVINT_STR ++ "Advertisement interval in milliseconds\n" ++ "Preempt mode\n" ++ VRRP6_PRIORITY_STR ++ "Priority value\n" ++ "Force VRRPv6 router into administrative shutdown\n") ++{ ++ if (adv) { ++ if (advint % CS2MS != 0) { ++ vty_out(vty, "%% Value must be a multiple of %u\n", ++ (unsigned int)CS2MS); ++ return CMD_WARNING_CONFIG_FAILED; ++ } ++ /* all internal computations are in centiseconds */ ++ advint /= CS2MS; ++ vd.advertisement_interval = no ? VRRP_DEFAULT_ADVINT : advint; ++ } ++ if (p) ++ vd.preempt_mode = !no; ++ if (prio) ++ vd.priority = no ? VRRP_DEFAULT_PRIORITY : prioval; ++ if (s) ++ vd.shutdown = !no; ++ ++ return CMD_SUCCESS; ++} ++ ++/* clang-format on */ ++ ++/* ++ * Build JSON representation of VRRP6 instance. ++ * ++ * vr ++ * VRRP6 router to build json object from ++ * ++ * Returns: ++ * JSON representation of VRRP6 instance. Must be freed by caller. ++ */ ++static struct json_object *vrrp6_build_json(struct vrrp_vrouter *vr) ++{ ++ char ethstr6[ETHER_ADDR_STRLEN]; ++ char ipstr[INET6_ADDRSTRLEN]; ++ const char *stastr6 = vrrp_state_names[vr->vrrp->fsm.state]; ++ char sipstr6[INET6_ADDRSTRLEN] = {}; ++ struct listnode *ln; ++ struct ipaddr *ip; ++ struct json_object *j = json_object_new_object(); ++ struct json_object *v6 = json_object_new_object(); ++ struct json_object *v6_stats = json_object_new_object(); ++ struct json_object *v6_addrs = json_object_new_array(); ++ ++ prefix_mac2str(&vr->vrrp->vmac, ethstr6, sizeof(ethstr6)); ++ ++ json_object_int_add(j, "vrid", vr->vrid); ++ json_object_int_add(j, "version", vr->version); ++ json_object_boolean_add(j, "autoconfigured", vr->autoconf); ++ json_object_boolean_add(j, "shutdown", vr->shutdown); ++ json_object_boolean_add(j, "preemptMode", vr->preempt_mode); ++ json_object_boolean_add(j, "acceptMode", vr->accept_mode); ++ json_object_string_add(j, "interface", vr->ifp->name); ++ json_object_int_add(j, "advertisementInterval", ++ vr->advertisement_interval * CS2MS); ++ ++ /* v6 */ ++ json_object_string_add(v6, "interface", ++ vr->vrrp->mvl_ifp ? vr->vrrp->mvl_ifp->name : ""); ++ json_object_string_add(v6, "vmac", ethstr6); ++ ipaddr2str(&vr->vrrp->src, sipstr6, sizeof(sipstr6)); ++ if (strlen(sipstr6) == 0 && vr->vrrp->src.ip.addr == 0x00) ++ strlcat(sipstr6, "::", sizeof(sipstr6)); ++ json_object_string_add(v6, "primaryAddress", sipstr6); ++ json_object_string_add(v6, "status", stastr6); ++ json_object_int_add(v6, "effectivePriority", vr->vrrp->priority); ++ json_object_int_add(v6, "masterAdverInterval", ++ vr->vrrp->master_adver_interval * CS2MS); ++ json_object_int_add(v6, "skewTime", vr->vrrp->skew_time * CS2MS); ++ json_object_int_add(v6, "masterDownInterval", ++ vr->vrrp->master_down_interval * CS2MS); ++ /* v6 stats */ ++ json_object_int_add(v6_stats, "adverTx", vr->vrrp->stats.adver_tx_cnt); ++ json_object_int_add(v6_stats, "adverRx", vr->vrrp->stats.adver_rx_cnt); ++ json_object_int_add(v6_stats, "neighborAdverTx", ++ vr->vrrp->stats.una_tx_cnt); ++ json_object_int_add(v6_stats, "transitions", vr->vrrp->stats.trans_cnt); ++ json_object_object_add(v6, "stats", v6_stats); ++ /* v6 addrs */ ++ if (vr->vrrp->addrs->count) { ++ for (ALL_LIST_ELEMENTS_RO(vr->vrrp->addrs, ln, ip)) { ++ inet_ntop(vr->vrrp->family, &ip->ipaddr_v6, ipstr, ++ sizeof(ipstr)); ++ json_object_array_add(v6_addrs, ++ json_object_new_string(ipstr)); ++ } ++ } ++ json_object_object_add(v6, "addresses", v6_addrs); ++ json_object_object_add(j, "v6", v6); ++ ++ return j; ++} ++ ++/* ++ * Dump VRRP6 instance status to VTY. ++ * ++ * vty ++ * vty to dump to ++ * ++ * vr ++ * VRRP6 router to dump ++ */ ++static void vrrp6_show(struct vty *vty, struct vrrp_vrouter *vr) ++{ ++ char ethstr6[ETHER_ADDR_STRLEN]; ++ char ipstr[INET6_ADDRSTRLEN]; ++ const char *stastr6 = vrrp_state_names[vr->vrrp->fsm.state]; ++ char sipstr6[INET6_ADDRSTRLEN] = {}; ++ struct listnode *ln; ++ struct ipaddr *ip; ++ struct track_intf *track_intf; ++ ++ struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ++ ++ ttable_add_row(tt, "%s|%u", "Virtual Router ID", vr->vrid); ++ ttable_add_row(tt, "%s|%hhu", "Protocol Version", vr->version); ++ ttable_add_row(tt, "%s|%s", "Autoconfigured", ++ vr->autoconf ? "Yes" : "No"); ++ ttable_add_row(tt, "%s|%s", "Shutdown", vr->shutdown ? "Yes" : "No"); ++ ttable_add_row(tt, "%s|%s", "Interface", vr->ifp->name); ++ prefix_mac2str(&vr->vrrp->vmac, ethstr6, sizeof(ethstr6)); ++ ttable_add_row(tt, "%s|%s", "VRRP interface (v6)", ++ vr->vrrp->mvl_ifp ? vr->vrrp->mvl_ifp->name : "None"); ++ ipaddr2str(&vr->vrrp->src, sipstr6, sizeof(sipstr6)); ++ if (strlen(sipstr6) == 0 && vr->vrrp->src.ip.addr == 0x00) ++ strlcat(sipstr6, "::", sizeof(sipstr6)); ++ ttable_add_row(tt, "%s|%s", "Primary IP (v6)", sipstr6); ++ ttable_add_row(tt, "%s|%s", "Virtual MAC (v6)", ethstr6); ++ ttable_add_row(tt, "%s|%s", "Status (v6)", stastr6); ++ ttable_add_row(tt, "%s|%hhu", "Priority", vr->priority); ++ ttable_add_row(tt, "%s|%hhu", "Effective Priority (v6)", ++ vr->vrrp->priority); ++ ttable_add_row(tt, "%s|%s", "Preempt Mode", ++ vr->preempt_mode ? "Yes" : "No"); ++ ttable_add_row(tt, "%s|%s", "Accept Mode", ++ vr->accept_mode ? "Yes" : "No"); ++ ttable_add_row(tt, "%s|%d ms", "Advertisement Interval", ++ vr->advertisement_interval * CS2MS); ++ ttable_add_row(tt, "%s|%d ms", ++ "Master Advertisement Interval (v6)", ++ vr->vrrp->master_adver_interval * CS2MS); ++ ttable_add_row(tt, "%s|%u", "Advertisements Tx (v6)", ++ vr->vrrp->stats.adver_tx_cnt); ++ ttable_add_row(tt, "%s|%u", "Advertisements Rx (v6)", ++ vr->vrrp->stats.adver_rx_cnt); ++ ttable_add_row(tt, "%s|%u", "Neigh. Adverts Tx (v6)", ++ vr->vrrp->stats.una_tx_cnt); ++ ttable_add_row(tt, "%s|%u", "State transitions (v6)", ++ vr->vrrp->stats.trans_cnt); ++ ttable_add_row(tt, "%s|%d ms", "Skew Time (v6)", ++ vr->vrrp->skew_time * CS2MS); ++ ttable_add_row(tt, "%s|%d ms", "Master Down Interval (v6)", ++ vr->vrrp->master_down_interval * CS2MS); ++ ttable_add_row(tt, "%s|%u", "Tracking interface", vr->track_intf->count); ++ ++ char fill[35]; ++ ++ memset(fill, '.', sizeof(fill)); ++ fill[sizeof(fill) - 1] = 0x00; ++ ++ if (vr->track_intf->count) { ++ for (ALL_LIST_ELEMENTS_RO(vr->track_intf, ln, track_intf)) { ++ ttable_add_row(tt, "%s|%s (weight:%d)", fill, track_intf->name, track_intf->priority_dec); ++ } ++ } ++ ++ ttable_add_row(tt, "%s|%u", "IPv6 Addresses", vr->vrrp->addrs->count); ++ ++ if (vr->vrrp->addrs->count) { ++ for (ALL_LIST_ELEMENTS_RO(vr->vrrp->addrs, ln, ip)) { ++ inet_ntop(vr->vrrp->family, &ip->ipaddr_v6, ipstr, ++ sizeof(ipstr)); ++ ttable_add_row(tt, "%s|%s", fill, ipstr); ++ } ++ } ++ ++ char *table = ttable_dump(tt, "\n"); ++ ++ vty_out(vty, "\n%s\n", table); ++ XFREE(MTYPE_TMP, table); ++ ttable_del(tt); ++} ++ ++/* ++ * Sort comparator, used when sorting VRRP instances for display purposes. ++ * ++ * Sorts by interface name first, then by VRID ascending. ++ */ ++static int vrrp6_instance_display_sort_cmp(const void **d1, const void **d2) ++{ ++ const struct vrrp_vrouter *vr1 = *d1; ++ const struct vrrp_vrouter *vr2 = *d2; ++ int result; ++ ++ result = strcmp(vr1->ifp->name, vr2->ifp->name); ++ result += !result * (vr1->vrid - vr2->vrid); ++ ++ return result; ++} ++ ++/* clang-format off */ ++ ++DEFPY_YANG(vrrp6_vrid_show, ++ vrrp6_vrid_show_cmd, ++ "show vrrp6 [interface INTERFACE$ifn] [(1-255)$vrid] [json$json]", ++ SHOW_STR ++ VRRP6_STR ++ INTERFACE_STR ++ "Only show VRRP instances on this interface\n" ++ VRRP6_VRID_STR ++ JSON_STR) ++{ ++ struct vrrp_vrouter *vr; ++ struct listnode *ln; ++ struct list *ll = hash_to_list(vrrp_vrouters_hash); ++ struct json_object *j = json_object_new_array(); ++ ++ list_sort(ll, vrrp6_instance_display_sort_cmp); ++ ++ for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) { ++ if (ifn && !strmatch(ifn, vr->ifp->name)) ++ continue; ++ if (vrid && ((uint8_t) vrid) != vr->vrid) ++ continue; ++ if (vr->family != AF_INET6) ++ continue; ++ ++ if (!json) ++ vrrp6_show(vty, vr); ++ else ++ json_object_array_add(j, vrrp6_build_json(vr)); ++ } ++ ++ if (json) ++ vty_out(vty, "%s\n", ++ json_object_to_json_string_ext( ++ j, JSON_C_TO_STRING_PRETTY)); ++ ++ json_object_free(j); ++ ++ list_delete(&ll); ++ ++ return CMD_SUCCESS; ++} ++ ++DEFPY_YANG(vrrp6_vrid_show_summary, ++ vrrp6_vrid_show_summary_cmd, ++ "show vrrp6 [interface INTERFACE$ifn] [(1-255)$vrid] summary", ++ SHOW_STR ++ VRRP6_STR ++ INTERFACE_STR ++ "Only show VRRP instances on this interface\n" ++ VRRP6_VRID_STR ++ "Summarize all VRRPv6 instances\n") ++{ ++ struct vrrp_vrouter *vr; ++ struct listnode *ln; ++ struct list *ll = hash_to_list(vrrp_vrouters_hash); ++ ++ list_sort(ll, vrrp6_instance_display_sort_cmp); ++ ++ struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ++ ++ ttable_add_row( ++ tt, "Interface|VRID|Priority|IPv6|State (v6)"); ++ ttable_rowseps(tt, 0, BOTTOM, true, '-'); ++ ++ for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) { ++ if (ifn && !strmatch(ifn, vr->ifp->name)) ++ continue; ++ if (vrid && ((uint8_t)vrid) != vr->vrid) ++ continue; ++ if (vr->family != AF_INET6) ++ continue; ++ ++ ttable_add_row( ++ tt, "%s|%u|%hhu|%d|%s", ++ vr->ifp->name, vr->vrid, vr->priority, ++ vr->vrrp->addrs->count, ++ vrrp_state_names[vr->vrrp->fsm.state]); ++ } ++ ++ char *table = ttable_dump(tt, "\n"); ++ ++ vty_out(vty, "\n%s\n", table); ++ XFREE(MTYPE_TMP, table); ++ ttable_del(tt); ++ ++ list_delete(&ll); ++ ++ return CMD_SUCCESS; ++} ++ ++static struct cmd_node vrrp6_node = { ++ .name = "vrrp6", ++ .node = VRRP6_NODE, ++ .prompt = "", ++ .config_write = vrrp_config_write_global, ++}; ++ ++void vrrp6_vty_init(void) ++{ ++ install_node(&vrrp6_node); ++ ++ install_element(VIEW_NODE, &vrrp6_vrid_show_cmd); ++ install_element(VIEW_NODE, &vrrp6_vrid_show_summary_cmd); ++ install_element(CONFIG_NODE, &vrrp6_autoconfigure_cmd); ++ install_element(CONFIG_NODE, &vrrp6_default_cmd); ++ install_element(INTERFACE_NODE, &vrrp6_vrid_cmd); ++ install_element(INTERFACE_NODE, &vrrp6_shutdown_cmd); ++ install_element(INTERFACE_NODE, &vrrp6_priority_cmd); ++ install_element(INTERFACE_NODE, &no_vrrp6_priority_cmd); ++ install_element(INTERFACE_NODE, &vrrp6_advertisement_interval_cmd); ++ install_element(INTERFACE_NODE, &no_vrrp6_advertisement_interval_cmd); ++ install_element(INTERFACE_NODE, &vrrp6_ip6_cmd); ++ install_element(INTERFACE_NODE, &vrrp6_preempt_cmd); ++ install_element(INTERFACE_NODE, &vrrp6_track_interface_cmd); ++ install_element(INTERFACE_NODE, &no_vrrp6_track_interface_cmd); ++} +diff --git a/vrrpd/vrrp6_vty.h b/vrrpd/vrrp6_vty.h +new file mode 100644 +index 000000000..1668ba0f6 +--- /dev/null ++++ b/vrrpd/vrrp6_vty.h +@@ -0,0 +1,28 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * VRRP6 CLI commands. ++ * Copyright (C) 2022-2024 Micas Networks, Inc. ++ * Leo ++ */ ++#ifndef __VRRP6_VTY_H__ ++#define __VRRP6_VTY_H__ ++ ++#include "lib/northbound.h" ++ ++void vrrp6_vty_init(void); ++ ++/* Northbound callbacks */ ++void cli_show_vrrp6(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); ++void cli_show_shutdown_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults); ++void cli_show_priority_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults); ++void cli_show_track_interface_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults); ++void cli_show_advertisement_interval_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults); ++void cli_show_ipv6_vrrp6(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); ++void cli_show_preempt_vrrp6(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults); ++ ++#endif /* __VRRP6_VTY_H__ */ +diff --git a/vrrpd/vrrp_main.c b/vrrpd/vrrp_main.c +index 59769788e..4d0918ed3 100644 +--- a/vrrpd/vrrp_main.c ++++ b/vrrpd/vrrp_main.c +@@ -38,6 +38,7 @@ + #include "vrrp.h" + #include "vrrp_debug.h" + #include "vrrp_vty.h" ++#include "vrrp6_vty.h" + #include "vrrp_zebra.h" + + DEFINE_MGROUP(VRRPD, "vrrpd"); +@@ -157,6 +158,7 @@ int main(int argc, char **argv, char **envp) + vrrp_debug_init(); + vrrp_zebra_init(); + vrrp_vty_init(); ++ vrrp6_vty_init(); + vrrp_init(); + + snprintf(backup_config_file, sizeof(backup_config_file), +diff --git a/vrrpd/vrrp_northbound.c b/vrrpd/vrrp_northbound.c +index 76d0ad3b1..e5cd38bf4 100644 +--- a/vrrpd/vrrp_northbound.c ++++ b/vrrpd/vrrp_northbound.c +@@ -29,25 +29,39 @@ + #include "libfrr.h" + #include "vrrp.h" + #include "vrrp_vty.h" ++#include "vrrp6_vty.h" ++ ++#define VRRP6_FAMILY "VRRPv6" + + /* + * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group + */ + static int lib_interface_vrrp_vrrp_group_create(struct nb_cb_create_args *args) + { + struct interface *ifp; + uint8_t vrid; + uint8_t version = 3; ++ int family; + struct vrrp_vrouter *vr; + + vrid = yang_dnode_get_uint8(args->dnode, "./virtual-router-id"); + version = yang_dnode_get_enum(args->dnode, "./version"); ++ const char *family_s = yang_dnode_get_string(args->dnode, "./family"); ++ ++ char *ptr = strstr(family_s, VRRP6_FAMILY); ++ ++ if (ptr) { ++ family = AF_INET6; ++ } else { ++ family = AF_INET; ++ } + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, false); + if (ifp) { +- vr = vrrp_lookup(ifp, vrid); ++ vr = vrrp_lookup(ifp, vrid, family); + if (vr && vr->autoconf) { + snprintf( + args->errmsg, args->errmsg_len, +@@ -65,7 +79,7 @@ static int lib_interface_vrrp_vrrp_group_create(struct nb_cb_create_args *args) + } + + ifp = nb_running_get_entry(args->dnode, NULL, true); +- vr = vrrp_vrouter_create(ifp, vrid, version); ++ vr = vrrp_vrouter_create(ifp, vrid, version, family); + nb_running_set_entry(args->dnode, vr); + + return NB_OK; +@@ -121,9 +135,12 @@ lib_interface_vrrp_vrrp_group_get_keys(struct nb_cb_get_keys_args *args) + { + const struct vrrp_vrouter *vr = args->list_entry; + +- args->keys->num = 1; ++ args->keys->num = 2; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u", + vr->vrid); ++ snprintf(args->keys->key[1], sizeof(args->keys->key[1]), "%d", ++ vr->family); ++ + + return NB_OK; + } +@@ -134,11 +151,21 @@ lib_interface_vrrp_vrrp_group_lookup_entry(struct nb_cb_lookup_entry_args *args) + uint32_t vrid = strtoul(args->keys->key[0], NULL, 10); + const struct interface *ifp = args->parent_list_entry; + +- return vrrp_lookup(ifp, vrid); ++ char *ptr = strstr(args->keys->key[1], VRRP6_FAMILY); ++ int family; ++ ++ if (ptr) { ++ family = AF_INET; ++ } else { ++ family = AF_INET6; ++ } ++ ++ return vrrp_lookup(ifp, vrid, family); + } + + /* + * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/version ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/version + */ + static int + lib_interface_vrrp_vrrp_group_version_modify(struct nb_cb_modify_args *args) +@@ -150,8 +177,7 @@ lib_interface_vrrp_vrrp_group_version_modify(struct nb_cb_modify_args *args) + uint8_t version; + + vr = nb_running_get_entry(args->dnode, NULL, true); +- vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN); +- vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN); ++ vrrp_event(vr->vrrp, VRRP_EVENT_SHUTDOWN); + version = yang_dnode_get_enum(args->dnode, NULL); + vr->version = version; + +@@ -205,6 +231,75 @@ static int lib_interface_vrrp_vrrp_group_v4_virtual_address_destroy( + return NB_OK; + } + ++/* ++ * Helper function for track_interface list OP_MODIFY callbacks. ++ */ ++static void vrrp_yang_add_del_track_interface(const struct lyd_node *dnode, ++ bool add) ++{ ++ struct vrrp_vrouter *vr; ++ ++ vr = nb_running_get_entry(dnode, NULL, true); ++ const char *track_intf = yang_dnode_get_string(dnode, "./interface"); ++ if (add) { ++ const priority_dec = yang_dnode_get_uint8(dnode, "./priority-decrement"); ++ vrrp_add_track_interface(vr, track_intf, priority_dec); ++ } else { ++ vrrp_del_track_interface(vr, track_intf); ++ } ++ ++ vrrp_check_start(vr); ++} ++ ++/* ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/track-interface ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/track-interface ++ */ ++static int lib_interface_vrrp_vrrp_track_interface_create( ++ struct nb_cb_create_args *args) ++{ ++ if (args->event != NB_EV_APPLY) ++ return NB_OK; ++ ++ vrrp_yang_add_del_track_interface(args->dnode, true); ++ ++ return NB_OK; ++} ++ ++static int lib_interface_vrrp_vrrp_track_interface_destroy( ++ struct nb_cb_destroy_args *args) ++{ ++ if (args->event != NB_EV_APPLY) ++ return NB_OK; ++ ++ vrrp_yang_add_del_track_interface(args->dnode, false); ++ ++ return NB_OK; ++} ++ ++/* ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/track-interface/priority-decrement ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/track-interface/priority-decrement ++ */ ++static int lib_interface_vrrp_vrrp_track_interface_priority_modify( ++ struct nb_cb_modify_args *args) ++{ ++ if (args->event != NB_EV_APPLY) ++ return NB_OK; ++ ++ struct vrrp_vrouter *vr; ++ ++ vr = nb_running_get_entry(args->dnode, NULL, true); ++ const char *track_intf = yang_dnode_get_string(args->dnode, "../interface"); ++ const uint8_t priority_dec = yang_dnode_get_uint8(args->dnode, NULL); ++ ++ vrrp_add_track_interface(vr, track_intf, priority_dec); ++ ++ vrrp_check_start(vr); ++} ++ + /* + * XPath: + * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/current-priority +@@ -215,7 +310,7 @@ lib_interface_vrrp_vrrp_group_v4_current_priority_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint8(args->xpath, vr->v4->priority); ++ return yang_data_new_uint8(args->xpath, vr->vrrp->priority); + } + + /* +@@ -230,8 +325,8 @@ lib_interface_vrrp_vrrp_group_v4_vrrp_interface_get_elem( + + struct yang_data *val = NULL; + +- if (vr->v4->mvl_ifp) +- val = yang_data_new_string(args->xpath, vr->v4->mvl_ifp->name); ++ if (vr->vrrp->mvl_ifp) ++ val = yang_data_new_string(args->xpath, vr->vrrp->mvl_ifp->name); + + return val; + } +@@ -247,8 +342,8 @@ lib_interface_vrrp_vrrp_group_v4_source_address_get_elem( + const struct vrrp_vrouter *vr = args->list_entry; + struct yang_data *val = NULL; + +- if (!ipaddr_is_zero(&vr->v4->src)) +- val = yang_data_new_ip(args->xpath, &vr->v4->src); ++ if (!ipaddr_is_zero(&vr->vrrp->src)) ++ val = yang_data_new_ip(args->xpath, &vr->vrrp->src); + + return val; + } +@@ -261,7 +356,7 @@ static struct yang_data *lib_interface_vrrp_vrrp_group_v4_state_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_enum(args->xpath, vr->v4->fsm.state); ++ return yang_data_new_enum(args->xpath, vr->vrrp->fsm.state); + } + + /* +@@ -274,7 +369,7 @@ lib_interface_vrrp_vrrp_group_v4_master_advertisement_interval_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint16(args->xpath, vr->v4->master_adver_interval); ++ return yang_data_new_uint16(args->xpath, vr->vrrp->master_adver_interval); + } + + /* +@@ -285,7 +380,7 @@ static struct yang_data *lib_interface_vrrp_vrrp_group_v4_skew_time_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint16(args->xpath, vr->v4->skew_time); ++ return yang_data_new_uint16(args->xpath, vr->vrrp->skew_time); + } + + /* +@@ -298,7 +393,7 @@ lib_interface_vrrp_vrrp_group_v4_counter_state_transition_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint32(args->xpath, vr->v4->stats.trans_cnt); ++ return yang_data_new_uint32(args->xpath, vr->vrrp->stats.trans_cnt); + } + + /* +@@ -311,7 +406,7 @@ lib_interface_vrrp_vrrp_group_v4_counter_tx_advertisement_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint32(args->xpath, vr->v4->stats.adver_tx_cnt); ++ return yang_data_new_uint32(args->xpath, vr->vrrp->stats.adver_tx_cnt); + } + + /* +@@ -324,7 +419,7 @@ lib_interface_vrrp_vrrp_group_v4_counter_tx_gratuitous_arp_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint32(args->xpath, vr->v4->stats.garp_tx_cnt); ++ return yang_data_new_uint32(args->xpath, vr->vrrp->stats.garp_tx_cnt); + } + + /* +@@ -337,12 +432,12 @@ lib_interface_vrrp_vrrp_group_v4_counter_rx_advertisement_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint32(args->xpath, vr->v4->stats.adver_rx_cnt); ++ return yang_data_new_uint32(args->xpath, vr->vrrp->stats.adver_rx_cnt); + } + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/virtual-address ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/virtual-address + */ + static int lib_interface_vrrp_vrrp_group_v6_virtual_address_create( + struct nb_cb_create_args *args) +@@ -368,7 +463,7 @@ static int lib_interface_vrrp_vrrp_group_v6_virtual_address_destroy( + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/current-priority ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/current-priority + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_current_priority_get_elem( +@@ -376,12 +471,12 @@ lib_interface_vrrp_vrrp_group_v6_current_priority_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint8(args->xpath, vr->v6->priority); ++ return yang_data_new_uint8(args->xpath, vr->vrrp->priority); + } + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/vrrp-interface ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/vrrp-interface + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_vrrp_interface_get_elem( +@@ -390,15 +485,15 @@ lib_interface_vrrp_vrrp_group_v6_vrrp_interface_get_elem( + const struct vrrp_vrouter *vr = args->list_entry; + struct yang_data *val = NULL; + +- if (vr->v6->mvl_ifp) +- val = yang_data_new_string(args->xpath, vr->v6->mvl_ifp->name); ++ if (vr->vrrp->mvl_ifp) ++ val = yang_data_new_string(args->xpath, vr->vrrp->mvl_ifp->name); + + return val; + } + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/source-address ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/source-address + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_source_address_get_elem( +@@ -407,26 +502,26 @@ lib_interface_vrrp_vrrp_group_v6_source_address_get_elem( + const struct vrrp_vrouter *vr = args->list_entry; + struct yang_data *val = NULL; + +- if (!ipaddr_is_zero(&vr->v6->src)) +- val = yang_data_new_ip(args->xpath, &vr->v6->src); ++ if (!ipaddr_is_zero(&vr->vrrp->src)) ++ val = yang_data_new_ip(args->xpath, &vr->vrrp->src); + + return val; + } + + /* +- * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/state ++ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/state + */ + static struct yang_data *lib_interface_vrrp_vrrp_group_v6_state_get_elem( + struct nb_cb_get_elem_args *args) + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_enum(args->xpath, vr->v6->fsm.state); ++ return yang_data_new_enum(args->xpath, vr->vrrp->fsm.state); + } + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/master-advertisement-interval ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/master-advertisement-interval + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_master_advertisement_interval_get_elem( +@@ -434,23 +529,23 @@ lib_interface_vrrp_vrrp_group_v6_master_advertisement_interval_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint16(args->xpath, vr->v6->master_adver_interval); ++ return yang_data_new_uint16(args->xpath, vr->vrrp->master_adver_interval); + } + + /* +- * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/skew-time ++ * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/skew-time + */ + static struct yang_data *lib_interface_vrrp_vrrp_group_v6_skew_time_get_elem( + struct nb_cb_get_elem_args *args) + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint16(args->xpath, vr->v6->skew_time); ++ return yang_data_new_uint16(args->xpath, vr->vrrp->skew_time); + } + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/state-transition ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/state-transition + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_counter_state_transition_get_elem( +@@ -458,12 +553,12 @@ lib_interface_vrrp_vrrp_group_v6_counter_state_transition_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint32(args->xpath, vr->v6->stats.trans_cnt); ++ return yang_data_new_uint32(args->xpath, vr->vrrp->stats.trans_cnt); + } + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/advertisement ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/tx/advertisement + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_counter_tx_advertisement_get_elem( +@@ -471,12 +566,12 @@ lib_interface_vrrp_vrrp_group_v6_counter_tx_advertisement_get_elem( + { + const struct vrrp_vrouter *vr = args->list_entry; + +- return yang_data_new_uint32(args->xpath, vr->v6->stats.adver_tx_cnt); ++ return yang_data_new_uint32(args->xpath, vr->vrrp->stats.adver_tx_cnt); + } + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/neighbor-advertisement ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/tx/neighbor-advertisement + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_counter_tx_neighbor_advertisement_get_elem( +@@ -488,7 +583,7 @@ lib_interface_vrrp_vrrp_group_v6_counter_tx_neighbor_advertisement_get_elem( + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/rx/advertisement ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/rx/advertisement + */ + static struct yang_data * + lib_interface_vrrp_vrrp_group_v6_counter_rx_advertisement_get_elem( +@@ -499,7 +594,9 @@ lib_interface_vrrp_vrrp_group_v6_counter_rx_advertisement_get_elem( + } + + /* +- * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/priority ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/priority ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/priority + */ + static int + lib_interface_vrrp_vrrp_group_priority_modify(struct nb_cb_modify_args *args) +@@ -518,7 +615,9 @@ lib_interface_vrrp_vrrp_group_priority_modify(struct nb_cb_modify_args *args) + } + + /* +- * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/preempt ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/preempt ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/preempt + */ + static int + lib_interface_vrrp_vrrp_group_preempt_modify(struct nb_cb_modify_args *args) +@@ -537,7 +636,9 @@ lib_interface_vrrp_vrrp_group_preempt_modify(struct nb_cb_modify_args *args) + } + + /* +- * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/accept-mode ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/accept-mode ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/accept-mode + */ + static int + lib_interface_vrrp_vrrp_group_accept_mode_modify(struct nb_cb_modify_args *args) +@@ -558,6 +659,7 @@ lib_interface_vrrp_vrrp_group_accept_mode_modify(struct nb_cb_modify_args *args) + /* + * XPath: + * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/advertisement-interval ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/advertisement-interval + */ + static int lib_interface_vrrp_vrrp_group_advertisement_interval_modify( + struct nb_cb_modify_args *args) +@@ -576,7 +678,9 @@ static int lib_interface_vrrp_vrrp_group_advertisement_interval_modify( + } + + /* +- * XPath: /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/shutdown ++ * XPath: ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/shutdown ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/shutdown + */ + static int + lib_interface_vrrp_vrrp_group_shutdown_modify(struct nb_cb_modify_args *args) +@@ -593,8 +697,7 @@ lib_interface_vrrp_vrrp_group_shutdown_modify(struct nb_cb_modify_args *args) + vr->shutdown = shutdown; + + if (shutdown) { +- vrrp_event(vr->v4, VRRP_EVENT_SHUTDOWN); +- vrrp_event(vr->v6, VRRP_EVENT_SHUTDOWN); ++ vrrp_event(vr->vrrp, VRRP_EVENT_SHUTDOWN); + } else { + vrrp_check_start(vr); + } +@@ -684,6 +787,26 @@ const struct frr_yang_module_info frr_vrrpd_info = { + .cli_show = cli_show_shutdown, + } + }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/track-interface", ++ .cbs = { ++ .create = lib_interface_vrrp_vrrp_track_interface_create, ++ .destroy = lib_interface_vrrp_vrrp_track_interface_destroy, ++ .cli_show = cli_show_track_interface, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/track-interface/priority-decrement", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_track_interface_priority_modify, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/track-interface/priority-decrement", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_track_interface_priority_modify, ++ } ++ }, + { + .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v4/virtual-address", + .cbs = { +@@ -753,69 +876,128 @@ const struct frr_yang_module_info frr_vrrpd_info = { + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/virtual-address", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group", ++ .cbs = { ++ .create = lib_interface_vrrp_vrrp_group_create, ++ .destroy = lib_interface_vrrp_vrrp_group_destroy, ++ .get_next = lib_interface_vrrp_vrrp_group_get_next, ++ .get_keys = lib_interface_vrrp_vrrp_group_get_keys, ++ .lookup_entry = lib_interface_vrrp_vrrp_group_lookup_entry, ++ .cli_show = cli_show_vrrp6, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/version", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_group_version_modify, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/priority", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_group_priority_modify, ++ .cli_show = cli_show_priority_vrrp6, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/preempt", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_group_preempt_modify, ++ .cli_show = cli_show_preempt_vrrp6, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/accept-mode", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_group_accept_mode_modify, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/advertisement-interval", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_group_advertisement_interval_modify, ++ .cli_show = cli_show_advertisement_interval_vrrp6, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/shutdown", ++ .cbs = { ++ .modify = lib_interface_vrrp_vrrp_group_shutdown_modify, ++ .cli_show = cli_show_shutdown_vrrp6, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/track-interface", ++ .cbs = { ++ .create = lib_interface_vrrp_vrrp_track_interface_create, ++ .destroy = lib_interface_vrrp_vrrp_track_interface_destroy, ++ .cli_show = cli_show_track_interface_vrrp6, ++ } ++ }, ++ { ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/virtual-address", + .cbs = { + .create = lib_interface_vrrp_vrrp_group_v6_virtual_address_create, + .destroy = lib_interface_vrrp_vrrp_group_v6_virtual_address_destroy, +- .cli_show = cli_show_ipv6, ++ .cli_show = cli_show_ipv6_vrrp6, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/current-priority", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/current-priority", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_current_priority_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/vrrp-interface", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/vrrp-interface", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_vrrp_interface_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/source-address", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/source-address", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_source_address_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/state", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/state", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_state_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/master-advertisement-interval", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/master-advertisement-interval", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_master_advertisement_interval_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/skew-time", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/skew-time", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_skew_time_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/state-transition", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/state-transition", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_state_transition_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/advertisement", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/tx/advertisement", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_tx_advertisement_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/tx/neighbor-advertisement", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/tx/neighbor-advertisement", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_tx_neighbor_advertisement_get_elem, + } + }, + { +- .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/counter/rx/advertisement", ++ .xpath = "/frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp6-group/v6/counter/rx/advertisement", + .cbs = { + .get_elem = lib_interface_vrrp_vrrp_group_v6_counter_rx_advertisement_get_elem, + } +diff --git a/vrrpd/vrrp_vty.c b/vrrpd/vrrp_vty.c +index 1e1edb821..54d97b9cf 100644 +--- a/vrrpd/vrrp_vty.c ++++ b/vrrpd/vrrp_vty.c +@@ -42,8 +42,11 @@ + #define VRRP_ADVINT_STR "Virtual Router Advertisement Interval\n" + #define VRRP_IP_STR "Virtual Router IP address\n" + #define VRRP_VERSION_STR "VRRP protocol version\n" ++#define VRRP_TRACK_INTF_STR "VRRP Track Interface name\n" ++#define VRRP_PRIORITY_DECREMENT_STR "VRRP Track Interface Decrement Priority\n" + +-#define VRRP_XPATH_ENTRY VRRP_XPATH "[virtual-router-id='%ld']" ++#define VRRP_XPATH_ENTRY VRRP_XPATH "[virtual-router-id='%ld'][family='%s']" ++#define VRRP_FAMILY "VRRPv4" + + /* clang-format off */ + +@@ -70,7 +73,7 @@ DEFPY_YANG(vrrp_vrid, + nb_cli_enqueue_change(vty, "./version", NB_OP_MODIFY, valbuf); + } + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + void cli_show_vrrp(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +@@ -98,7 +101,7 @@ DEFPY_YANG(vrrp_shutdown, + nb_cli_enqueue_change(vty, "./shutdown", NB_OP_MODIFY, + no ? "false" : "true"); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + void cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode, +@@ -123,7 +126,7 @@ DEFPY_YANG(vrrp_priority, + { + nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, priority_str); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + /* +@@ -140,7 +143,7 @@ DEFPY_YANG(no_vrrp_priority, + { + nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, NULL); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + void cli_show_priority(struct vty *vty, const struct lyd_node *dnode, +@@ -170,7 +173,7 @@ DEFPY_YANG(vrrp_advertisement_interval, + nb_cli_enqueue_change(vty, "./advertisement-interval", NB_OP_MODIFY, + val); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + /* +@@ -186,7 +189,7 @@ DEFPY_YANG(no_vrrp_advertisement_interval, + nb_cli_enqueue_change(vty, "./advertisement-interval", NB_OP_MODIFY, + NULL); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + void cli_show_advertisement_interval(struct vty *vty, const struct lyd_node *dnode, +@@ -215,7 +218,7 @@ DEFPY_YANG(vrrp_ip, + int op = no ? NB_OP_DESTROY : NB_OP_CREATE; + nb_cli_enqueue_change(vty, "./v4/virtual-address", op, ip_str); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + void cli_show_ip(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +@@ -229,30 +232,62 @@ void cli_show_ip(struct vty *vty, const struct lyd_node *dnode, bool show_defaul + + /* + * XPath: +- * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/v6/virtual-address ++ * /frr-interface:lib/interface/frr-vrrpd:vrrp/vrrp-group/track-interface + */ +-DEFPY_YANG(vrrp_ip6, +- vrrp_ip6_cmd, +- "[no] vrrp (1-255)$vrid ipv6 X:X::X:X", ++DEFPY_YANG(vrrp_track_interface, ++ vrrp_track_interface_cmd, ++ "vrrp (1-255)$vrid track-interface IFNAME$intf_name priority-dec (10-50)", ++ VRRP_STR ++ VRRP_VRID_STR ++ "Add tracking interface\n" ++ VRRP_TRACK_INTF_STR ++ VRRP_PRIORITY_DECREMENT_STR ++ "Priority decrement value\n") ++{ ++ char xpath[XPATH_MAXLEN]; ++ char xpath_track[XPATH_MAXLEN + 32]; ++ ++ snprintf(xpath, sizeof(xpath), ++ "./track-interface[interface='%s']", ++ intf_name); ++ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); ++ ++ snprintf(xpath_track, sizeof(xpath_track), ++ "%s/priority-decrement", xpath); ++ nb_cli_enqueue_change(vty, xpath_track, NB_OP_MODIFY, priority_dec_str); ++ ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); ++} ++ ++DEFPY_YANG(no_vrrp_track_interface, ++ no_vrrp_track_interface_cmd, ++ "no vrrp (1-255)$vrid track-interface IFNAME$intf_name", + NO_STR + VRRP_STR + VRRP_VRID_STR +- "Add IPv6 address\n" +- VRRP_IP_STR) ++ "Add tracking interface\n" ++ VRRP_TRACK_INTF_STR ++ VRRP_PRIORITY_DECREMENT_STR ++ "Priority decrement value\n") + { +- int op = no ? NB_OP_DESTROY : NB_OP_CREATE; +- nb_cli_enqueue_change(vty, "./v6/virtual-address", op, ipv6_str); ++ char xpath[XPATH_MAXLEN]; ++ snprintf(xpath, sizeof(xpath), ++ "./track-interface[interface='%s']", ++ intf_name); ++ ++ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + +-void cli_show_ipv6(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) ++void cli_show_track_interface(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) + { +- const char *vrid = +- yang_dnode_get_string(dnode, "../../virtual-router-id"); +- const char *ipv6 = yang_dnode_get_string(dnode, NULL); ++ const char *vrid = yang_dnode_get_string(dnode, "../virtual-router-id"); ++ const char *track_intf = yang_dnode_get_string(dnode, "./interface"); + +- vty_out(vty, " vrrp %s ipv6 %s\n", vrid, ipv6); ++ const char *priority_dec = yang_dnode_get_string(dnode, "./priority-decrement"); ++ ++ vty_out(vty, " vrrp %s track-interface %s priority-dec %s\n", vrid, track_intf, priority_dec); + } + + /* +@@ -269,7 +304,7 @@ DEFPY_YANG(vrrp_preempt, + nb_cli_enqueue_change(vty, "./preempt", NB_OP_MODIFY, + no ? "false" : "true"); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + void cli_show_preempt(struct vty *vty, const struct lyd_node *dnode, +@@ -296,7 +331,7 @@ DEFPY_YANG(vrrp_checksum_with_ipv4_pseudoheader, + nb_cli_enqueue_change(vty, "./checksum-with-ipv4-pseudoheader", + NB_OP_MODIFY, no ? "false" : "true"); + +- return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid); ++ return nb_cli_apply_changes(vty, VRRP_XPATH_ENTRY, vrid, VRRP_FAMILY); + } + + void cli_show_checksum_with_ipv4_pseudoheader(struct vty *vty, +@@ -381,24 +416,17 @@ DEFPY_YANG(vrrp_default, + static struct json_object *vrrp_build_json(struct vrrp_vrouter *vr) + { + char ethstr4[ETHER_ADDR_STRLEN]; +- char ethstr6[ETHER_ADDR_STRLEN]; + char ipstr[INET6_ADDRSTRLEN]; +- const char *stastr4 = vrrp_state_names[vr->v4->fsm.state]; +- const char *stastr6 = vrrp_state_names[vr->v6->fsm.state]; ++ const char *stastr4 = vrrp_state_names[vr->vrrp->fsm.state]; + char sipstr4[INET6_ADDRSTRLEN] = {}; +- char sipstr6[INET6_ADDRSTRLEN] = {}; + struct listnode *ln; + struct ipaddr *ip; + struct json_object *j = json_object_new_object(); + struct json_object *v4 = json_object_new_object(); + struct json_object *v4_stats = json_object_new_object(); + struct json_object *v4_addrs = json_object_new_array(); +- struct json_object *v6 = json_object_new_object(); +- struct json_object *v6_stats = json_object_new_object(); +- struct json_object *v6_addrs = json_object_new_array(); + +- prefix_mac2str(&vr->v4->vmac, ethstr4, sizeof(ethstr4)); +- prefix_mac2str(&vr->v6->vmac, ethstr6, sizeof(ethstr6)); ++ prefix_mac2str(&vr->vrrp->vmac, ethstr4, sizeof(ethstr4)); + + json_object_int_add(j, "vrid", vr->vrid); + json_object_int_add(j, "version", vr->version); +@@ -413,27 +441,27 @@ static struct json_object *vrrp_build_json(struct vrrp_vrouter *vr) + vr->advertisement_interval * CS2MS); + /* v4 */ + json_object_string_add(v4, "interface", +- vr->v4->mvl_ifp ? vr->v4->mvl_ifp->name : ""); ++ vr->vrrp->mvl_ifp ? vr->vrrp->mvl_ifp->name : ""); + json_object_string_add(v4, "vmac", ethstr4); +- ipaddr2str(&vr->v4->src, sipstr4, sizeof(sipstr4)); ++ ipaddr2str(&vr->vrrp->src, sipstr4, sizeof(sipstr4)); + json_object_string_add(v4, "primaryAddress", sipstr4); + json_object_string_add(v4, "status", stastr4); +- json_object_int_add(v4, "effectivePriority", vr->v4->priority); ++ json_object_int_add(v4, "effectivePriority", vr->vrrp->priority); + json_object_int_add(v4, "masterAdverInterval", +- vr->v4->master_adver_interval * CS2MS); +- json_object_int_add(v4, "skewTime", vr->v4->skew_time * CS2MS); ++ vr->vrrp->master_adver_interval * CS2MS); ++ json_object_int_add(v4, "skewTime", vr->vrrp->skew_time * CS2MS); + json_object_int_add(v4, "masterDownInterval", +- vr->v4->master_down_interval * CS2MS); ++ vr->vrrp->master_down_interval * CS2MS); + /* v4 stats */ +- json_object_int_add(v4_stats, "adverTx", vr->v4->stats.adver_tx_cnt); +- json_object_int_add(v4_stats, "adverRx", vr->v4->stats.adver_rx_cnt); +- json_object_int_add(v4_stats, "garpTx", vr->v4->stats.garp_tx_cnt); +- json_object_int_add(v4_stats, "transitions", vr->v4->stats.trans_cnt); ++ json_object_int_add(v4_stats, "adverTx", vr->vrrp->stats.adver_tx_cnt); ++ json_object_int_add(v4_stats, "adverRx", vr->vrrp->stats.adver_rx_cnt); ++ json_object_int_add(v4_stats, "garpTx", vr->vrrp->stats.garp_tx_cnt); ++ json_object_int_add(v4_stats, "transitions", vr->vrrp->stats.trans_cnt); + json_object_object_add(v4, "stats", v4_stats); + /* v4 addrs */ +- if (vr->v4->addrs->count) { +- for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ln, ip)) { +- inet_ntop(vr->v4->family, &ip->ipaddr_v4, ipstr, ++ if (vr->vrrp->addrs->count) { ++ for (ALL_LIST_ELEMENTS_RO(vr->vrrp->addrs, ln, ip)) { ++ inet_ntop(vr->vrrp->family, &ip->ipaddr_v4, ipstr, + sizeof(ipstr)); + json_object_array_add(v4_addrs, + json_object_new_string(ipstr)); +@@ -442,40 +470,6 @@ static struct json_object *vrrp_build_json(struct vrrp_vrouter *vr) + json_object_object_add(v4, "addresses", v4_addrs); + json_object_object_add(j, "v4", v4); + +- /* v6 */ +- json_object_string_add(v6, "interface", +- vr->v6->mvl_ifp ? vr->v6->mvl_ifp->name : ""); +- json_object_string_add(v6, "vmac", ethstr6); +- ipaddr2str(&vr->v6->src, sipstr6, sizeof(sipstr6)); +- if (strlen(sipstr6) == 0 && vr->v6->src.ip.addr == 0x00) +- strlcat(sipstr6, "::", sizeof(sipstr6)); +- json_object_string_add(v6, "primaryAddress", sipstr6); +- json_object_string_add(v6, "status", stastr6); +- json_object_int_add(v6, "effectivePriority", vr->v6->priority); +- json_object_int_add(v6, "masterAdverInterval", +- vr->v6->master_adver_interval * CS2MS); +- json_object_int_add(v6, "skewTime", vr->v6->skew_time * CS2MS); +- json_object_int_add(v6, "masterDownInterval", +- vr->v6->master_down_interval * CS2MS); +- /* v6 stats */ +- json_object_int_add(v6_stats, "adverTx", vr->v6->stats.adver_tx_cnt); +- json_object_int_add(v6_stats, "adverRx", vr->v6->stats.adver_rx_cnt); +- json_object_int_add(v6_stats, "neighborAdverTx", +- vr->v6->stats.una_tx_cnt); +- json_object_int_add(v6_stats, "transitions", vr->v6->stats.trans_cnt); +- json_object_object_add(v6, "stats", v6_stats); +- /* v6 addrs */ +- if (vr->v6->addrs->count) { +- for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ln, ip)) { +- inet_ntop(vr->v6->family, &ip->ipaddr_v6, ipstr, +- sizeof(ipstr)); +- json_object_array_add(v6_addrs, +- json_object_new_string(ipstr)); +- } +- } +- json_object_object_add(v6, "addresses", v6_addrs); +- json_object_object_add(j, "v6", v6); +- + return j; + } + +@@ -491,14 +485,12 @@ static struct json_object *vrrp_build_json(struct vrrp_vrouter *vr) + static void vrrp_show(struct vty *vty, struct vrrp_vrouter *vr) + { + char ethstr4[ETHER_ADDR_STRLEN]; +- char ethstr6[ETHER_ADDR_STRLEN]; + char ipstr[INET6_ADDRSTRLEN]; +- const char *stastr4 = vrrp_state_names[vr->v4->fsm.state]; +- const char *stastr6 = vrrp_state_names[vr->v6->fsm.state]; ++ const char *stastr4 = vrrp_state_names[vr->vrrp->fsm.state]; + char sipstr4[INET6_ADDRSTRLEN] = {}; +- char sipstr6[INET6_ADDRSTRLEN] = {}; + struct listnode *ln; + struct ipaddr *ip; ++ struct track_intf *track_intf; + + struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + +@@ -508,27 +500,16 @@ static void vrrp_show(struct vty *vty, struct vrrp_vrouter *vr) + vr->autoconf ? "Yes" : "No"); + ttable_add_row(tt, "%s|%s", "Shutdown", vr->shutdown ? "Yes" : "No"); + ttable_add_row(tt, "%s|%s", "Interface", vr->ifp->name); +- prefix_mac2str(&vr->v4->vmac, ethstr4, sizeof(ethstr4)); +- prefix_mac2str(&vr->v6->vmac, ethstr6, sizeof(ethstr6)); ++ prefix_mac2str(&vr->vrrp->vmac, ethstr4, sizeof(ethstr4)); + ttable_add_row(tt, "%s|%s", "VRRP interface (v4)", +- vr->v4->mvl_ifp ? vr->v4->mvl_ifp->name : "None"); +- ttable_add_row(tt, "%s|%s", "VRRP interface (v6)", +- vr->v6->mvl_ifp ? vr->v6->mvl_ifp->name : "None"); +- ipaddr2str(&vr->v4->src, sipstr4, sizeof(sipstr4)); +- ipaddr2str(&vr->v6->src, sipstr6, sizeof(sipstr6)); +- if (strlen(sipstr6) == 0 && vr->v6->src.ip.addr == 0x00) +- strlcat(sipstr6, "::", sizeof(sipstr6)); ++ vr->vrrp->mvl_ifp ? vr->vrrp->mvl_ifp->name : "None"); ++ ipaddr2str(&vr->vrrp->src, sipstr4, sizeof(sipstr4)); + ttable_add_row(tt, "%s|%s", "Primary IP (v4)", sipstr4); +- ttable_add_row(tt, "%s|%s", "Primary IP (v6)", sipstr6); + ttable_add_row(tt, "%s|%s", "Virtual MAC (v4)", ethstr4); +- ttable_add_row(tt, "%s|%s", "Virtual MAC (v6)", ethstr6); + ttable_add_row(tt, "%s|%s", "Status (v4)", stastr4); +- ttable_add_row(tt, "%s|%s", "Status (v6)", stastr6); + ttable_add_row(tt, "%s|%hhu", "Priority", vr->priority); + ttable_add_row(tt, "%s|%hhu", "Effective Priority (v4)", +- vr->v4->priority); +- ttable_add_row(tt, "%s|%hhu", "Effective Priority (v6)", +- vr->v6->priority); ++ vr->vrrp->priority); + ttable_add_row(tt, "%s|%s", "Preempt Mode", + vr->preempt_mode ? "Yes" : "No"); + ttable_add_row(tt, "%s|%s", "Accept Mode", +@@ -539,53 +520,36 @@ static void vrrp_show(struct vty *vty, struct vrrp_vrouter *vr) + vr->advertisement_interval * CS2MS); + ttable_add_row(tt, "%s|%d ms (stale)", + "Master Advertisement Interval (v4) Rx", +- vr->v4->master_adver_interval * CS2MS); +- ttable_add_row(tt, "%s|%d ms (stale)", +- "Master Advertisement Interval (v6) Rx", +- vr->v6->master_adver_interval * CS2MS); ++ vr->vrrp->master_adver_interval * CS2MS); + ttable_add_row(tt, "%s|%u", "Advertisements Tx (v4)", +- vr->v4->stats.adver_tx_cnt); +- ttable_add_row(tt, "%s|%u", "Advertisements Tx (v6)", +- vr->v6->stats.adver_tx_cnt); ++ vr->vrrp->stats.adver_tx_cnt); + ttable_add_row(tt, "%s|%u", "Advertisements Rx (v4)", +- vr->v4->stats.adver_rx_cnt); +- ttable_add_row(tt, "%s|%u", "Advertisements Rx (v6)", +- vr->v6->stats.adver_rx_cnt); ++ vr->vrrp->stats.adver_rx_cnt); + ttable_add_row(tt, "%s|%u", "Gratuitous ARP Tx (v4)", +- vr->v4->stats.garp_tx_cnt); +- ttable_add_row(tt, "%s|%u", "Neigh. Adverts Tx (v6)", +- vr->v6->stats.una_tx_cnt); ++ vr->vrrp->stats.garp_tx_cnt); + ttable_add_row(tt, "%s|%u", "State transitions (v4)", +- vr->v4->stats.trans_cnt); +- ttable_add_row(tt, "%s|%u", "State transitions (v6)", +- vr->v6->stats.trans_cnt); ++ vr->vrrp->stats.trans_cnt); + ttable_add_row(tt, "%s|%d ms", "Skew Time (v4)", +- vr->v4->skew_time * CS2MS); +- ttable_add_row(tt, "%s|%d ms", "Skew Time (v6)", +- vr->v6->skew_time * CS2MS); ++ vr->vrrp->skew_time * CS2MS); + ttable_add_row(tt, "%s|%d ms", "Master Down Interval (v4)", +- vr->v4->master_down_interval * CS2MS); +- ttable_add_row(tt, "%s|%d ms", "Master Down Interval (v6)", +- vr->v6->master_down_interval * CS2MS); +- ttable_add_row(tt, "%s|%u", "IPv4 Addresses", vr->v4->addrs->count); ++ vr->vrrp->master_down_interval * CS2MS); ++ ttable_add_row(tt, "%s|%u", "Tracking interface", vr->track_intf->count); + + char fill[35]; + + memset(fill, '.', sizeof(fill)); + fill[sizeof(fill) - 1] = 0x00; +- if (vr->v4->addrs->count) { +- for (ALL_LIST_ELEMENTS_RO(vr->v4->addrs, ln, ip)) { +- inet_ntop(vr->v4->family, &ip->ipaddr_v4, ipstr, +- sizeof(ipstr)); +- ttable_add_row(tt, "%s|%s", fill, ipstr); ++ ++ if (vr->track_intf->count) { ++ for (ALL_LIST_ELEMENTS_RO(vr->track_intf, ln, track_intf)) { ++ ttable_add_row(tt, "%s|%s (weight:%d)", fill, track_intf->name, track_intf->priority_dec); + } + } + +- ttable_add_row(tt, "%s|%u", "IPv6 Addresses", vr->v6->addrs->count); +- +- if (vr->v6->addrs->count) { +- for (ALL_LIST_ELEMENTS_RO(vr->v6->addrs, ln, ip)) { +- inet_ntop(vr->v6->family, &ip->ipaddr_v6, ipstr, ++ ttable_add_row(tt, "%s|%u", "IPv4 Addresses", vr->vrrp->addrs->count); ++ if (vr->vrrp->addrs->count) { ++ for (ALL_LIST_ELEMENTS_RO(vr->vrrp->addrs, ln, ip)) { ++ inet_ntop(vr->vrrp->family, &ip->ipaddr_v4, ipstr, + sizeof(ipstr)); + ttable_add_row(tt, "%s|%s", fill, ipstr); + } +@@ -639,6 +603,8 @@ DEFPY_YANG(vrrp_vrid_show, + continue; + if (vrid && ((uint8_t) vrid) != vr->vrid) + continue; ++ if (vr->family != AF_INET) ++ continue; + + if (!json) + vrrp_show(vty, vr); +@@ -677,7 +643,7 @@ DEFPY_YANG(vrrp_vrid_show_summary, + struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + + ttable_add_row( +- tt, "Interface|VRID|Priority|IPv4|IPv6|State (v4)|State (v6)"); ++ tt, "Interface|VRID|Priority|IPv4|State (v4)"); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + for (ALL_LIST_ELEMENTS_RO(ll, ln, vr)) { +@@ -685,15 +651,14 @@ DEFPY_YANG(vrrp_vrid_show_summary, + continue; + if (vrid && ((uint8_t)vrid) != vr->vrid) + continue; ++ if (vr->family != AF_INET) ++ continue; + + ttable_add_row( +- tt, "%s|%u|%hhu|%d|%d|%s|%s", ++ tt, "%s|%u|%hhu|%d|%s", + vr->ifp->name, vr->vrid, vr->priority, +- vr->v4->addrs->count, vr->v6->addrs->count, +- vr->v4->fsm.state == VRRP_STATE_MASTER ? "Master" +- : "Backup", +- vr->v6->fsm.state == VRRP_STATE_MASTER ? "Master" +- : "Backup"); ++ vr->vrrp->addrs->count, ++ vrrp_state_names[vr->vrrp->fsm.state]); + } + + char *table = ttable_dump(tt, "\n"); +@@ -786,8 +751,9 @@ void vrrp_vty_init(void) + install_element(INTERFACE_NODE, &vrrp_advertisement_interval_cmd); + install_element(INTERFACE_NODE, &no_vrrp_advertisement_interval_cmd); + install_element(INTERFACE_NODE, &vrrp_ip_cmd); +- install_element(INTERFACE_NODE, &vrrp_ip6_cmd); + install_element(INTERFACE_NODE, &vrrp_preempt_cmd); + install_element(INTERFACE_NODE, + &vrrp_checksum_with_ipv4_pseudoheader_cmd); ++ install_element(INTERFACE_NODE, &vrrp_track_interface_cmd); ++ install_element(INTERFACE_NODE, &no_vrrp_track_interface_cmd); + } +diff --git a/vrrpd/vrrp_vty.h b/vrrpd/vrrp_vty.h +index be88349e7..de71f40a7 100644 +--- a/vrrpd/vrrp_vty.h ++++ b/vrrpd/vrrp_vty.h +@@ -31,13 +31,13 @@ void cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); + void cli_show_priority(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); ++void cli_show_track_interface(struct vty *vty, const struct lyd_node *dnode, ++ bool show_defaults); + void cli_show_advertisement_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); + void cli_show_ip(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +-void cli_show_ipv6(struct vty *vty, const struct lyd_node *dnode, +- bool show_defaults); + void cli_show_preempt(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); + void cli_show_checksum_with_ipv4_pseudoheader(struct vty *vty, +diff --git a/yang/frr-vrrpd.yang b/yang/frr-vrrpd.yang +index cd04df267..c54474597 100644 +--- a/yang/frr-vrrpd.yang ++++ b/yang/frr-vrrpd.yang +@@ -66,6 +66,19 @@ module frr-vrrpd { + generated for the VRRP group"; + } + ++ leaf family { ++ type enumeration { ++ enum "VRRPv4" { ++ description ++ "Configures VRRP version 2 or 3 for IPv4."; ++ } ++ enum "VRRPv6" { ++ description ++ "Configures VRRP for IPv6."; ++ } ++ } ++ } ++ + leaf version { + type enumeration { + enum "2" { +@@ -93,6 +106,30 @@ module frr-vrrpd { + priority"; + } + ++ list track-interface { ++ key "interface"; ++ description ++ "tracking interface."; ++ ++ leaf interface { ++ type frr-interface:interface-ref; ++ description ++ "Configure one or more tracking interfaces for the ++ VRRP group"; ++ } ++ ++ leaf priority-decrement { ++ type uint8 { ++ range "10..50"; ++ } ++ default "20"; ++ description ++ "Specifies how much to decrement the priority ++ of the VRRP instance if the tracking interface ++ goes down"; ++ } ++ } ++ + leaf preempt { + type boolean; + default "true"; +@@ -110,13 +147,6 @@ module frr-vrrpd { + address is not owned by the router interface"; + } + +- leaf checksum-with-ipv4-pseudoheader { +- type boolean; +- default "true"; +- description +- "Enabled if VRRPv3 checksum for IPv4 involves pseudoheader"; +- } +- + leaf advertisement-interval { + type uint16 { + range "1..4095"; +@@ -239,11 +269,18 @@ module frr-vrrpd { + "RFC 5798 - Virtual Router Redundancy Protocol + (VRRP) Version 3 for IPv4 and IPv6"; + list vrrp-group { +- key "virtual-router-id"; ++ key "virtual-router-id family"; + description + "List of VRRP groups, keyed by virtual router id"; + uses ip-vrrp-config; + ++ leaf checksum-with-ipv4-pseudoheader { ++ type boolean; ++ default "true"; ++ description ++ "Enabled if VRRPv3 checksum for IPv4 involves pseudoheader"; ++ } ++ + container v4 { + leaf-list virtual-address { + type inet:ipv4-address; +@@ -262,6 +299,13 @@ module frr-vrrpd { + } + } + } ++ } ++ ++ list vrrp6-group { ++ key "virtual-router-id family"; ++ description ++ "List of VRRP groups, keyed by virtual router id"; ++ uses ip-vrrp-config; + + container v6 { + when "../version = 3"; +-- +2.25.1 + diff --git a/src/sonic-frr/patch/series b/src/sonic-frr/patch/series index bcb29008824f..c4b5c30a96e6 100644 --- a/src/sonic-frr/patch/series +++ b/src/sonic-frr/patch/series @@ -33,3 +33,5 @@ cross-compile-changes.patch 0031-bgpd-Ignore-handling-NLRIs-if-we-received-MP_UNREACH.patch 0032-zebra-Fix-fpm-multipath-encap-addition.patch 0033-zebra-The-dplane_fpm_nl-return-path-leaks-memory.patch +0034-support-vrrp6-commands-and-tracking-interface.patch + diff --git a/src/sonic-vrrpcfgd/.gitignore b/src/sonic-vrrpcfgd/.gitignore new file mode 100644 index 000000000000..10fb4e57b77e --- /dev/null +++ b/src/sonic-vrrpcfgd/.gitignore @@ -0,0 +1,12 @@ +.eggs/ +build/ +dist/ +*.egg-info/ +vrrpcfgd/*.pyc +tests/*.pyc +tests/__pycache__/ +.idea +.coverage +vrrpcfgd/__pycache__/ +venv +tests/.coverage* \ No newline at end of file diff --git a/src/sonic-vrrpcfgd/setup.cfg b/src/sonic-vrrpcfgd/setup.cfg new file mode 100644 index 000000000000..00ed5efbcbad --- /dev/null +++ b/src/sonic-vrrpcfgd/setup.cfg @@ -0,0 +1,5 @@ +[aliases] +test=pytest +[tool:pytest] +addopts = --verbose +python_files = tests/*.py diff --git a/src/sonic-vrrpcfgd/setup.py b/src/sonic-vrrpcfgd/setup.py new file mode 100644 index 000000000000..a47ea7fc1829 --- /dev/null +++ b/src/sonic-vrrpcfgd/setup.py @@ -0,0 +1,29 @@ +import setuptools + +setuptools.setup( + name = 'sonic-vrrpcfgd', + version = '1.0', + description = 'Utility to dynamically generate VRRP configuration for FRR', + url = 'https://github.com/Azure/sonic-buildimage', + packages = setuptools.find_packages(), + entry_points = { + 'console_scripts': [ + 'vrrpcfgd = vrrpcfgd.vrrpcfgd:main', + ] + }, + install_requires = [ + 'jinja2>=2.10', + 'netaddr==0.8.0', + 'pyyaml==6.0.1', + 'ipaddress==1.0.23' + ], + setup_requires = [ + 'pytest-runner', + 'wheel' + ], + tests_require = [ + 'pytest', + 'pytest-cov', + 'sonic-config-engine' + ] +) diff --git a/src/sonic-vrrpcfgd/tests/__init__.py b/src/sonic-vrrpcfgd/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/sonic-vrrpcfgd/tests/test_config.py b/src/sonic-vrrpcfgd/tests/test_config.py new file mode 100644 index 000000000000..63f03bc67054 --- /dev/null +++ b/src/sonic-vrrpcfgd/tests/test_config.py @@ -0,0 +1,148 @@ +import copy +import re +from unittest.mock import MagicMock, NonCallableMagicMock, patch + +swsscommon_module_mock = MagicMock(ConfigDBConnector = NonCallableMagicMock) +# because can’t use dotted names directly in a call, have to create a dictionary and unpack it using **: +mockmapping = {'swsscommon.swsscommon': swsscommon_module_mock} + +@patch.dict('sys.modules', **mockmapping) +def test_contructor(): + from vrrpcfgd.vrrpcfgd import VRRPConfigDaemon + daemon = VRRPConfigDaemon() + daemon.start() + for table, hdlr in daemon.table_handler_list: + daemon.config_db.subscribe.assert_any_call(table, hdlr) + daemon.config_db.pubsub.psubscribe.assert_called_once() + assert(daemon.config_db.sub_thread.is_alive() == True) + daemon.stop() + daemon.config_db.pubsub.punsubscribe.assert_called_once() + assert(daemon.config_db.sub_thread.is_alive() == False) + +class CmdMapTestInfo: + data_buf = {} + def __init__(self, table, key, data, exp_cmd, no_del = False, neg_cmd = None, + chk_data = None, daemons = None, ignore_tail = False): + self.table_name = table + self.key = key + self.data = data + self.vtysh_cmd = exp_cmd + self.no_del = no_del + self.vtysh_neg_cmd = neg_cmd + self.chk_data = chk_data + self.daemons = daemons + self.ignore_tail = ignore_tail + @classmethod + def add_test_data(cls, test): + assert(isinstance(test.data, dict)) + cls.data_buf.setdefault( + test.table_name, {}).setdefault(test.key, {}).update(test.data) + @classmethod + def set_default_test_data(cls, test): + assert(test.table_name in cls.data_buf and + test.key in cls.data_buf[test.table_name]) + cache_data = cls.data_buf[test.table_name][test.key] + assert(isinstance(test.data, dict)) + for k, v in test.data.items(): + assert(k in cache_data and cache_data[k] == v) + if k == 'preempt': + test.data[k] = 'disabled' + if k == 'admin_status': + test.data[k] = 'up' + cls.data_buf.setdefault( + test.table_name, {}).setdefault(test.key, {}).update(test.data) + + @classmethod + def get_test_data(cls, test): + assert(test.table_name in cls.data_buf and + test.key in cls.data_buf[test.table_name]) + return copy.deepcopy(cls.data_buf[test.table_name][test.key]) + @staticmethod + def compose_vtysh_cmd(cmd_list, negtive = False): + cmdline = 'vtysh' + for cmd in cmd_list: + cmd = cmd.format('no ' if negtive else '') + cmdline += " -c '%s'" % cmd + return cmdline + def check_running_cmd(self, mock, is_del): + if is_del: + vtysh_cmd = self.vtysh_cmd if self.vtysh_neg_cmd is None else self.vtysh_neg_cmd + else: + vtysh_cmd = self.vtysh_cmd + if callable(vtysh_cmd): + cmds = [] + for call in mock.call_args_list: + assert(call[0][0] == self.table_name) + cmds.append(call[0][1]) + vtysh_cmd(is_del, cmds, self.chk_data) + else: + if self.ignore_tail is False: + mock.assert_called_with(self.table_name, self.compose_vtysh_cmd(vtysh_cmd, is_del), + True, self.daemons) + else: + mock.assert_called_with(self.table_name, self.compose_vtysh_cmd(vtysh_cmd, is_del), + True, self.daemons, self.ignore_tail) + +conf_cmd = 'configure terminal' +conf_intf_cmd = lambda intf_name: [conf_cmd, 'interface %s' % intf_name] + +vrrp_config_data = [ + CmdMapTestInfo('VRRP', 'Ethernet8|8', {'vid': '8'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp 8'], True), + CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'vid': '8'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp6 8'], True), + CmdMapTestInfo('VRRP', 'Ethernet1|8', {'vip': ['7.7.7.7/24']}, + conf_intf_cmd('Ethernet1') + ['{}vrrp 8 ip 7.7.7.7'], True), + CmdMapTestInfo('VRRP', 'Ethernet8|8', {'admin_status': 'down'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp 8 shutdown']), + CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'admin_status': 'down'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 shutdown']), + CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'vip': ['2000::1/64']}, + conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 ipv6 2000::1'], True), + CmdMapTestInfo('VRRP_TRACK', 'Ethernet8|8|Ethernet5', {'priority_increment': '20'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp 8 track-interface Ethernet5 priority-dec 20'], True), + CmdMapTestInfo('VRRP6_TRACK', 'Ethernet8|8|Ethernet5', {'priority_increment': '20'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 track-interface Ethernet5 priority-dec 20'], True), + CmdMapTestInfo('VRRP', 'Ethernet8|8', {'preempt': 'enabled'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp 8 preempt']), + CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'preempt': 'enabled'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 preempt']), + CmdMapTestInfo('VRRP', 'Ethernet8|8', {'version': '2'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp 8 version 2'], True), + CmdMapTestInfo('VRRP', 'Ethernet8|8', {'adv_interval': '2000'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp 8 advertisement-interval 2000'], True), + CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'adv_interval': '2000'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 advertisement-interval 2000'], True), + CmdMapTestInfo('VRRP', 'Ethernet8|8', {'priority': '150'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp 8 priority 150'], True), + CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'priority': '150'}, + conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 priority 150'], True), +] + +@patch.dict('sys.modules', **mockmapping) +@patch('vrrpcfgd.vrrpcfgd.g_run_command') +def data_set_del_test(test_data, run_cmd): + from vrrpcfgd.vrrpcfgd import VRRPConfigDaemon + daemon = VRRPConfigDaemon() + data_buf = {} + # add data in list + for test in test_data: + run_cmd.reset_mock() + hdlr = [h for t, h in daemon.table_handler_list if t == test.table_name] + assert(len(hdlr) == 1) + CmdMapTestInfo.add_test_data(test) + hdlr[0](test.table_name, test.key, CmdMapTestInfo.get_test_data(test)) + test.check_running_cmd(run_cmd, False) + # delete data in reverse direction + for test in reversed(test_data): + if test.no_del: + continue + run_cmd.reset_mock() + hdlr = [h for t, h in daemon.table_handler_list if t == test.table_name] + assert(len(hdlr) == 1) + CmdMapTestInfo.set_default_test_data(test) + hdlr[0](test.table_name, test.key, CmdMapTestInfo.get_test_data(test)) + test.check_running_cmd(run_cmd, True) + +def test_vrrp_config(): + data_set_del_test(vrrp_config_data) diff --git a/src/sonic-vrrpcfgd/tests/test_constructor.py b/src/sonic-vrrpcfgd/tests/test_constructor.py new file mode 100644 index 000000000000..9b3dbaffb837 --- /dev/null +++ b/src/sonic-vrrpcfgd/tests/test_constructor.py @@ -0,0 +1,19 @@ +import socket +import pytest +from unittest.mock import MagicMock, NonCallableMagicMock, patch + +swsscommon_module_mock = MagicMock(ConfigDBConnector = NonCallableMagicMock) +# because can¡¯t use dotted names directly in a call, have to create a dictionary and unpack it using **: +mockmapping = {'swsscommon.swsscommon': swsscommon_module_mock} + +with patch.dict('sys.modules', **mockmapping): + from vrrpcfgd.vrrpcfgd import CachedDataWithOp + +def test_data_with_op(): + data = CachedDataWithOp() + assert(data.data is None) + assert(data.op == CachedDataWithOp.OP_NONE) + data = CachedDataWithOp(10, CachedDataWithOp.OP_ADD) + assert(data.data == 10) + assert(data.op == CachedDataWithOp.OP_ADD) + diff --git a/src/sonic-vrrpcfgd/vrrpcfgd/__init__.py b/src/sonic-vrrpcfgd/vrrpcfgd/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py b/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py new file mode 100644 index 000000000000..effd8df9458e --- /dev/null +++ b/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py @@ -0,0 +1,735 @@ +#!/usr/bin/env python + +import copy +import subprocess +import time +import syslog +import os +from swsscommon.swsscommon import ConfigDBConnector +import socket +import threading +import queue +import signal +import re +import logging +import netaddr +import ipaddress +import io +import struct + +class CachedDataWithOp: + OP_NONE = 0 + OP_ADD = 1 + OP_DELETE = 2 + OP_UPDATE = 3 + + STAT_SUCC = 0 + STAT_FAIL = 1 + + def __init__(self, data = None, op = OP_NONE): + self.data = data + self.op = op + self.status = self.STAT_FAIL + + def __repr__(self): + op_str = '' + if self.op == self.OP_NONE: + op_str = 'NONE' + elif self.op == self.OP_ADD: + op_str = 'ADD' + elif self.op == self.OP_DELETE: + op_str = 'DELETE' + elif self.op == self.OP_UPDATE: + op_str = 'UPDATE' + return '(%s, %s)' % (self.data, op_str) + +vrrpd_client = None + +def g_run_command(table, command, use_bgpd_client, daemons, ignore_fail = False): + syslog.syslog(syslog.LOG_DEBUG, "execute command {} for table {}.".format(command, table)) + if not command.startswith('vtysh '): + use_bgpd_client = False + if use_bgpd_client: + if not vrrpd_client.run_vtysh_command(table, command, daemons) and not ignore_fail: + syslog.syslog(syslog.LOG_ERR, 'command execution failure. Command: "{}"'.format(command)) + return False + else: + p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + stdout = p.communicate()[0] + p.wait() + if p.returncode != 0 and not ignore_fail: + syslog.syslog(syslog.LOG_ERR, '[vrrp cfgd] command execution returned {}. Command: "{}", stdout: "{}"'.\ + format(p.returncode, command, stdout)) + return False + return True + +def extract_cmd_daemons(cmd_str): + # daemon list could be given within brackets at head of input lines + dm_mark = re.match(r'\[(?P.+)\]', cmd_str) + if dm_mark is not None and 'daemons' in dm_mark.groupdict(): + cmd_str = cmd_str[dm_mark.end():] + daemons = dm_mark.groupdict()['daemons'].split(',') + else: + daemons = None + return (daemons, cmd_str) + +class VrrpdClientMgr(threading.Thread): + VTYSH_MARK = 'vtysh ' + PROXY_SERVER_ADDR = '/etc/frr/vrrpd_client_sock' + ALL_DAEMONS = ['vrrpd'] + TABLE_DAEMON = { + 'VRRP': ['vrrpd'], + 'VRRP6': ['vrrpd'], + 'VRRP_TRACK': ['vrrpd'], + 'VRRP6_TRACK': ['vrrpd'], + } + VTYSH_CMD_DAEMON = [(r'show vrrp($|\s+\S+)', ['vrrpd'])] + @staticmethod + def __create_proxy_socket(): + try: + os.unlink(VrrpdClientMgr.PROXY_SERVER_ADDR) + except OSError: + if os.path.exists(VrrpdClientMgr.PROXY_SERVER_ADDR): + raise + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(VrrpdClientMgr.PROXY_SERVER_ADDR) + sock.listen(1) + return sock + @staticmethod + def __get_reply(sock): + reply_msg = None + ret_code = None + msg_buf = io.StringIO() + while True: + try: + rd_msg = sock.recv(16384) + msg_buf.write(rd_msg.decode()) + except socket.timeout: + syslog.syslog(syslog.LOG_ERR, 'socket reading timeout') + break + if len(rd_msg) < 4: + rd_msg = msg_buf.getvalue() + if len(rd_msg) < 4: + continue + msg_tail = rd_msg[-4:] + if isinstance(msg_tail, str): + msg_tail = bytes(msg_tail, 'utf-8') + if msg_tail[0] == 0 and msg_tail[1] == 0 and msg_tail[2] == 0: + ret_code = msg_tail[3] + reply_msg = msg_buf.getvalue()[:-4] + break + msg_buf.close() + return (ret_code, reply_msg) + @staticmethod + def __send_data(sock, data): + if isinstance(data, str): + data = bytes(data, 'utf-8') + sock.sendall(data) + def __create_frr_client(self): + self.client_socks = {} + for daemon in self.ALL_DAEMONS: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + serv_addr = '/run/frr/%s.vty' % daemon + retry_cnt = 0 + while True: + try: + sock.connect(serv_addr) + break + except socket.error as msg: + syslog.syslog(syslog.LOG_ERR, 'failed to connect to frr daemon %s: %s' % (daemon, msg)) + retry_cnt += 1 + if retry_cnt > 100 or not main_loop: + syslog.syslog(syslog.LOG_ERR, 're-tried too many times, give up') + for _, sock in self.client_socks.items(): + sock.close() + return False + time.sleep(2) + continue + sock.settimeout(120) + self.client_socks[daemon] = sock + for daemon, sock in self.client_socks.items(): + syslog.syslog(syslog.LOG_DEBUG, 'send initial enable command to %s' % daemon) + try: + self.__send_data(sock, 'enable\0') + except socket.error as msg: + syslog.syslog(syslog.LOG_ERR, 'failed to send initial enable command to %s' % daemon) + return False + ret_code, reply = self.__get_reply(sock) + if ret_code is None: + syslog.syslog(syslog.LOG_ERR, 'failed to get command response for enable command from %s' % daemon) + return False + if ret_code != 0: + syslog.syslog(syslog.LOG_ERR, 'enable command failed: ret_code=%d' % ret_code) + syslog.syslog(syslog.LOG_ERR, reply) + return False + return True + def __init__(self): + super(VrrpdClientMgr, self).__init__(name = 'VTYSH sub-process manager') + if not self.__create_frr_client(): + syslog.syslog(syslog.LOG_ERR, 'failed to create socket to FRR daemon') + raise RuntimeError('connect to FRR daemon failed') + self.proxy_running = True + self.lock = threading.Lock() + self.proxy_sock = self.__create_proxy_socket() + self.cmd_to_daemon = [] + for pat, daemons in self.VTYSH_CMD_DAEMON: + try: + self.cmd_to_daemon.append((re.compile(pat), daemons)) + except Exception: + syslog.syslog(syslog.LOG_ERR, 'invalid regex format: %s' % pat) + continue + def __get_cmd_daemons(self, cmd_list): + cmn_daemons = None + for cmd in cmd_list: + found = False + for re_comp, daemons in self.cmd_to_daemon: + if re_comp.match(cmd.strip()) is not None: + found = True + break + if not found: + syslog.syslog(syslog.LOG_ERR, 'no matched daemons found for command %s' % cmd) + return None + if cmn_daemons is None: + cmn_daemons = set(daemons) + else: + cmn_daemons = cmn_daemons.intersection(set(daemons)) + if len(cmn_daemons) == 0: + return [] + return list(cmn_daemons) + def __proc_command(self, command, daemons): + syslog.syslog(syslog.LOG_DEBUG, 'VTYSH CMD: %s daemons: %s' % (command, daemons)) + resp = '' + ret_val = False + for daemon in daemons: + sock = self.client_socks.get(daemon, None) + if sock is None: + syslog.syslog(syslog.LOG_ERR, 'daemon %s is not connected' % daemon) + continue + try: + self.__send_data(sock, command + '\0') + except socket.error as msg: + syslog.syslog(syslog.LOG_ERR, 'failed to send command to frr daemon: %s' % msg) + return (False, None) + ret_code, reply = self.__get_reply(sock) + if ret_code is None or ret_code != 0: + if ret_code is None: + syslog.syslog(syslog.LOG_ERR, 'failed to get reply from frr daemon') + continue + else: + syslog.syslog(syslog.LOG_DEBUG, '[%s] command return code: %d' % (daemon, ret_code)) + syslog.syslog(syslog.LOG_DEBUG, reply) + else: + # command is running successfully by at least one daemon + ret_val = True + resp += reply + return (ret_val, resp) + def run_vtysh_command(self, table, command, daemons): + if not command.startswith(self.VTYSH_MARK): + syslog.syslog(syslog.LOG_ERR, 'command %s is not for vtysh config' % command) + return False + cmd_line = command[len(self.VTYSH_MARK):] + cmd_list = re.findall(r"-c\s+'([^']+)'\s*", cmd_line) + cmd_list.append('end') + if daemons is None: + daemons = self.TABLE_DAEMON.get(table, None) + if daemons is None: + daemons = self.__get_cmd_daemons(cmd_list) + if daemons is None or len(daemons) == 0: + syslog.syslog(syslog.LOG_ERR, 'no common daemon list found for given commands') + return False + ret_val = True + with self.lock: + for cmd in cmd_list: + succ, _ = self.__proc_command(cmd.strip(), daemons) + if not succ: + ret_val = False + return ret_val + @staticmethod + def __read_all(sock, data_len): + in_buf = io.StringIO() + left_len = data_len + while left_len > 0: + data = sock.recv(left_len) + if data is None: + break + in_buf.write(data) + left_len -= len(data) + return in_buf.getvalue() + def shutdown(self): + syslog.syslog(syslog.LOG_DEBUG, 'terminate vrrpcfgd client manager') + if self.is_alive(): + self.proxy_running = False + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.connect(self.PROXY_SERVER_ADDR) + finally: + sock.close() + self.join() + for _, sock in self.client_socks.items(): + sock.close() + def run(self): + syslog.syslog(syslog.LOG_DEBUG, 'entering VTYSH proxy thread') + while self.proxy_running: + syslog.syslog(syslog.LOG_DEBUG, 'waiting for client connection ...') + conn_sock, clnt_addr = self.proxy_sock.accept() + if not self.proxy_running: + conn_sock.close() + break + try: + syslog.syslog(syslog.LOG_DEBUG, 'client connection from %s' % clnt_addr) + data = self.__read_all(conn_sock, 4) + if len(data) == 4: + data_len = struct.unpack('>I', data)[0] + in_cmd = self.__read_all(conn_sock, data_len) + if len(in_cmd) == data_len: + daemons, in_cmd = extract_cmd_daemons(in_cmd) + in_lines = in_cmd.splitlines() + if daemons is None: + daemons = self.__get_cmd_daemons(in_lines) + if daemons is not None and len(daemons) > 0: + with self.lock: + for line in in_lines: + _, reply = self.__proc_command(line.strip(), daemons) + if reply is not None: + self.__send_data(conn_sock, reply) + else: + syslog.syslog(syslog.LOG_ERR, 'failed running VTYSH command') + else: + syslog.syslog(syslog.LOG_ERR, 'could not find common daemons for input commands') + else: + syslog.syslog(syslog.LOG_ERR, 'read data of length %d is not expected length %d' % (data_len, len(in_cmd))) + else: + syslog.syslog(syslog.LOG_ERR, 'invalid data length %d' % len(data)) + except socket.error as msg: + syslog.syslog(syslog.LOG_ERR, 'socket writing failed: %s' % msg) + finally: + syslog.syslog(syslog.LOG_DEBUG, 'closing data socket from client') + conn_sock.close() + syslog.syslog(syslog.LOG_DEBUG, 'leaving VTYSH proxy thread') + +class ExtConfigDBConnector(ConfigDBConnector): + def __init__(self, ns_attrs = None): + super(ExtConfigDBConnector, self).__init__() + self.nosort_attrs = ns_attrs if ns_attrs is not None else {} + self.__listen_thread_running = False + def raw_to_typed(self, raw_data, table = ''): + if len(raw_data) == 0: + raw_data = None + data = super(ExtConfigDBConnector, self).raw_to_typed(raw_data) + if data is None: + return None + for key, val in data.items(): + if type(val) is list and key not in self.nosort_attrs.get(table, set()): + val.sort() + return data + def sub_msg_handler(self, msg_item): + if msg_item['type'] == 'pmessage': + key = msg_item['channel'].split(':', 1)[1] + try: + (table, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) + if table in self.handlers: + client = self.get_redis_client(self.db_name) + data = self.raw_to_typed(client.hgetall(key), table) + super(ExtConfigDBConnector, self)._ConfigDBConnector__fire(table, row, data) + except ValueError: + pass #Ignore non table-formated redis entries + except Exception as e: + syslog.syslog(syslog.LOG_ERR, '[vrrp cfgd] Failed handling config DB update with exception:' + str(e)) + logging.exception(e) + + def listen_thread(self, timeout): + self.__listen_thread_running = True + sub_key_space = "__keyspace@{}__:*".format(self.get_dbid(self.db_name)) + self.pubsub.psubscribe(sub_key_space) + while self.__listen_thread_running: + msg = self.pubsub.get_message(timeout, True) + if msg: + self.sub_msg_handler(msg) + + self.pubsub.punsubscribe(sub_key_space) + + def listen(self): + """Start listen Redis keyspace events and will trigger corresponding handlers when content of a table changes. + """ + self.pubsub = self.get_redis_client(self.db_name).pubsub() + self.sub_thread = threading.Thread(target=self.listen_thread, args=(0.01,)) + self.sub_thread.start() + + def stop_listen(self): + self.__listen_thread_running = False + + @staticmethod + def get_table_key(table, key): + return table + '&&' + key + def get_table_data(self, table_list): + ret_data = {} + for table in table_list: + table_data = self.get_table(table) + for key, data in table_data.items(): + table_key = self.get_table_key(table, self.serialize_key(key)) + ret_data[table_key] = data + return ret_data + +class VRRPConfigDaemon: + DEFAULT_VRF = 'default' + + def __init__(self): + self.config_db = ExtConfigDBConnector() + try: + self.config_db.connect() + except Exception as e: + syslog.syslog(syslog.LOG_ERR, '[vrrp cfgd] Failed connecting to config DB with exception:' + str(e)) + db_entry = self.config_db.get_entry('DEVICE_METADATA', 'localhost') + if 'docker_routing_config_mode' in db_entry: + self.config_mode = db_entry['docker_routing_config_mode'] + else: + self.config_mode = "separated" + + self.table_handler_list = [ + ('VRRP', self.vrrp_handler), + ('VRRP6', self.vrrp6_handler), + ('VRRP_TRACK', self.vrrp_track_handler), + ('VRRP6_TRACK', self.vrrp6_track_handler), + ] + self.table_data_cache = self.config_db.get_table_data([tbl for tbl, _ in self.table_handler_list]) + syslog.syslog(syslog.LOG_DEBUG, 'Init Cached DB data') + for key, entry in self.table_data_cache.items(): + syslog.syslog(syslog.LOG_DEBUG, ' %-20s : %s' % (key, entry)) + if self.config_mode == "unified": + for table, _ in self.table_handler_list: + table_list = self.config_db.get_table(table) + for key, data in table_list.items(): + syslog.syslog(syslog.LOG_DEBUG, 'config replay for table {} key {}'.format(table, key)) + upd_data = {} + for upd_key, upd_val in data.items(): + upd_data[upd_key] = CachedDataWithOp(upd_val, CachedDataWithOp.OP_ADD) + upd_data_list = [] + self.__update_bgp(upd_data_list) + for table1, key1, data1 in upd_data_list: + table_key = ExtConfigDBConnector.get_table_key(table1, key1) + self.__update_cache_data(table_key, data1) + + def subscribe_all(self): + for table, hdlr in self.table_handler_list: + self.config_db.subscribe(table, hdlr) + + @staticmethod + def __run_command(table, command, daemons = None): + return g_run_command(table, command, True, daemons) + + def vrrp_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[vrrp cfgd](vrrp) value for {} changed to {}'.format(key, data)) + #get frr vrrp session key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp {}'.format(key_params[1]) + table_key = ExtConfigDBConnector.get_table_key(table, key) + comb_attr_list = ['vip'] + if not data: + #VRRP instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + #del cache data + del(self.table_data_cache[table_key]) + else: + #create/update case + command = "vtysh -c 'configure terminal' -c '{}'".format(intf_cmd) + self.__add_op_to_data(table_key, data, comb_attr_list) + cached_data = self.table_data_cache.setdefault(table_key, {}) + for param in data: + if param == 'vid': + if param in cached_data and data[param].data == cached_data[param]: + continue + elif 'vip' not in data: + command = command + " -c '{}'".format(cmd) + elif param == 'vip': + if 'vip' in cached_data: + cache_address = cached_data[param] + data_address = data[param].data + # add vip + for d_address in data_address: + if d_address in cache_address: + continue + elif d_address != "": + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) + if ip_address.version == 4: + command = command + " -c '{} ip {}'".format(cmd, d_addr[0]) + # del vip + for c_address in cache_address: + if c_address in data_address: + continue + elif c_address != "": + c_addr = c_address.split('/') + try: + ip_address = ipaddress.ip_interface(c_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) + if ip_address.version == 4: + command = command + " -c 'no {} ip {}'".format(cmd, c_addr[0]) + else: + # first time to config + data_address = data[param].data + for d_address in data_address: + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) + if ip_address.version == 4: + command = command + " -c '{} ip {}'".format(cmd, d_addr[0]) + elif param == 'priority': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} priority {}'".format(cmd, data[param].data) + elif param == 'adv_interval': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} advertisement-interval {}'".format(cmd, data[param].data) + elif param == 'version': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} version {}'".format(cmd, data[param].data) + elif param == 'admin_status': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'down': + command = command + " -c '{} shutdown'".format(cmd) + elif data[param].data == 'up' or data[param].data == '': + command = command + " -c 'no {} shutdown'".format(cmd) + elif param == 'preempt': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'enabled': + command = command + " -c '{} preempt'".format(cmd) + elif data[param].data == 'disabled': + command = command + " -c 'no {} preempt'".format(cmd) + data[param].status = CachedDataWithOp.STAT_SUCC + self.__update_cache_data(table_key, data) + self.__run_command(table, command) + + def vrrp6_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp6) value for {} changed to {}'.format(key, data)) + #get frr vrrp6 session key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp6 {}'.format(key_params[1]) + table_key = ExtConfigDBConnector.get_table_key(table, key) + comb_attr_list = ['vip'] + if not data: + #VRRP instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + #del cache data + del(self.table_data_cache[table_key]) + else: + #create/update case + command = "vtysh -c 'configure terminal' -c '{}'".format(intf_cmd) + self.__add_op_to_data(table_key, data, comb_attr_list) + cached_data = self.table_data_cache.setdefault(table_key, {}) + for param in data: + if param == 'vid': + if param in cached_data and data[param].data == cached_data[param]: + continue + elif 'vip' not in data: + command = command + " -c '{}'".format(cmd) + elif param == 'vip': + if 'vip' in cached_data: + cache_address = cached_data[param] + data_address = data[param].data + for d_address in data_address: + if d_address in cache_address: + continue + elif d_address != "": + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) + if ip_address.version == 6: + command = command + " -c '{} ipv6 {}'".format(cmd, d_addr[0]) + + for c_address in cache_address: + if c_address in data_address: + continue + elif c_address != "": + c_addr = c_address.split('/') + try: + ip_address = ipaddress.ip_interface(c_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) + if ip_address.version == 6: + command = command + " -c 'no {} ipv6 {}'".format(cmd, c_addr[0]) + else: + # first time to config + data_address = data[param].data + for d_address in data_address: + d_addr = d_address.split('/') + try: + ip_address = ipaddress.ip_interface(d_addr[0]) + except ValueError as err: + syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) + if ip_address.version == 6: + command = command + " -c '{} ipv6 {}'".format(cmd, d_addr[0]) + elif param == 'priority': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} priority {}'".format(cmd, data[param].data) + elif param == 'adv_interval': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} advertisement-interval {}'".format(cmd, data[param].data) + elif param == 'version': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + command = command + " -c '{} version {}'".format(cmd, data[param].data) + elif param == 'admin_status': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'down': + command = command + " -c '{} shutdown'".format(cmd) + elif data[param].data == 'up' or data[param].data == '': + command = command + " -c 'no {} shutdown'".format(cmd) + elif param == 'preempt': + if param in cached_data and data[param].data == cached_data[param]: + continue + else: + if data[param].data == 'enabled': + command = command + " -c '{} preempt'".format(cmd) + elif data[param].data == 'disabled': + command = command + " -c 'no {} preempt'".format(cmd) + data[param].status = CachedDataWithOp.STAT_SUCC + self.__update_cache_data(table_key, data) + self.__run_command(table, command) + + def vrrp_track_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp track) value for {} changed to {}'.format(key, data)) + #get frr vrrp track key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp {} track-interface {}'.format(key_params[1], key_params[2]) + + if not data: + #VRRP track instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + else: + #create/update case + if 'priority_increment' in data: + command = "vtysh -c 'configure terminal' -c '{}' -c '{} priority-dec {}'".format(intf_cmd, cmd, data['priority_increment']) + self.__run_command(table, command) + + def vrrp6_track_handler(self, table, key, data): + syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp6 track) value for {} changed to {}'.format(key, data)) + #get frr vrrp6 track key + key_params = key.split('|') + intf_cmd = 'interface {}'.format(key_params[0]) + cmd = 'vrrp6 {} track-interface {}'.format(key_params[1], key_params[2]) + + if not data: + #VRRP track instance is deleted + command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) + self.__run_command(table, command) + else: + #create/update case + if 'priority_increment' in data: + command = "vtysh -c 'configure terminal' -c '{}' -c '{} priority-dec {}'".format(intf_cmd, cmd, data['priority_increment']) + self.__run_command(table, command) + + def __add_op_to_data(self, table_key, data, comb_attr_list): + cached_data = self.table_data_cache.setdefault(table_key, {}) + for key in cached_data: + if key in data: + # both in cache and data, update/none + data[key] = (CachedDataWithOp(data[key], CachedDataWithOp.OP_NONE) if data[key] == cached_data[key] else + CachedDataWithOp(data[key], CachedDataWithOp.OP_UPDATE)) + else: + # in cache but not in data, delete + data[key] = CachedDataWithOp(cached_data[key], CachedDataWithOp.OP_DELETE) + for key in data: + if not isinstance(data[key], CachedDataWithOp): + # in data but not in cache, add + data[key] = CachedDataWithOp(data[key], CachedDataWithOp.OP_ADD) + # combo attributes handling + op_list = [CachedDataWithOp.OP_DELETE, CachedDataWithOp.OP_ADD, CachedDataWithOp.OP_UPDATE, CachedDataWithOp.OP_NONE] + for key_set in comb_attr_list: + all_in = True + op_idx = len(op_list) - 1 + for key in key_set: + if key not in data: + all_in = False + break + idx = op_list.index(data[key].op) + if idx >= 0 and idx < op_idx: + op_idx = idx + if all_in: + for key in key_set: + data[key].op = op_list[op_idx] + else: + # if one key doesn't exist, clean the whole key set + for key in key_set: + data.pop(key, None) + + def __update_cache_data(self, table_key, data): + cached_data = self.table_data_cache.setdefault(table_key, {}) + for key, val in data.items(): + if not isinstance(val, CachedDataWithOp) or val.op == CachedDataWithOp.OP_NONE or val.status == CachedDataWithOp.STAT_FAIL: + syslog.syslog(syslog.LOG_DEBUG, 'ignore cache update for %s because of %s%s%s' % + (key, ('' if isinstance(val, CachedDataWithOp) else 'INV_DATA '), + ('NO_OP ' if isinstance(val, CachedDataWithOp) and val.op == CachedDataWithOp.OP_NONE else ''), + ('STAT_FAIL ' if isinstance(val, CachedDataWithOp) and val.status == CachedDataWithOp.STAT_FAIL else ''))) + continue + if val.op == CachedDataWithOp.OP_ADD or val.op == CachedDataWithOp.OP_UPDATE: + cached_data[key] = val.data + syslog.syslog(syslog.LOG_INFO, 'Add {} data {} to cache'.format(key, cached_data[key])) + elif val.op == CachedDataWithOp.OP_DELETE: + syslog.syslog(syslog.LOG_INFO, 'delete {} data {} from cache'.format(key, cached_data.get(key, ''))) + cached_data.pop(key, None) + if len(cached_data) == 0: + syslog.syslog(syslog.LOG_INFO, 'delete table row {} from cache'.format(table_key)) + del(self.table_data_cache[table_key]) + + def start(self): + self.subscribe_all() + self.config_db.listen() + def stop(self): + self.config_db.stop_listen() + if self.config_db.sub_thread.is_alive(): + self.config_db.sub_thread.join() + +main_loop = True + +def sig_handler(signum, frame): + global main_loop + syslog.syslog(syslog.LOG_DEBUG, 'entering signal handler') + main_loop = False + +def main(): + global vrrpd_client + for sig_num in [signal.SIGTERM, signal.SIGINT]: + signal.signal(sig_num, sig_handler) + syslog.syslog(syslog.LOG_DEBUG, 'entering VRRP configuration daemon') + vrrpd_client = VrrpdClientMgr() + vrrpd_client.start() + daemon = VRRPConfigDaemon() + daemon.start() + while main_loop: + signal.pause() + syslog.syslog(syslog.LOG_DEBUG, 'leaving VRRP configuration daemon') + vrrpd_client.shutdown() + daemon.stop() + +if __name__ == "__main__": + main() From de8463948cc91845c9c8cbdccb2684a72910f562 Mon Sep 17 00:00:00 2001 From: philo Date: Wed, 17 Apr 2024 15:53:00 +0800 Subject: [PATCH 02/10] update src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py Signed-off-by: philo --- src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) mode change 100644 => 100755 src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py diff --git a/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py b/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py old mode 100644 new mode 100755 index effd8df9458e..15d813316fd8 --- a/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py +++ b/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py @@ -45,22 +45,14 @@ def __repr__(self): vrrpd_client = None -def g_run_command(table, command, use_bgpd_client, daemons, ignore_fail = False): +def g_run_command(table, command, daemons, ignore_fail = False): syslog.syslog(syslog.LOG_DEBUG, "execute command {} for table {}.".format(command, table)) if not command.startswith('vtysh '): - use_bgpd_client = False - if use_bgpd_client: + syslog.syslog(syslog.LOG_ERR, 'Command no start with "vtysh". Command: "{}"'.format(command)) + else: if not vrrpd_client.run_vtysh_command(table, command, daemons) and not ignore_fail: syslog.syslog(syslog.LOG_ERR, 'command execution failure. Command: "{}"'.format(command)) return False - else: - p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - stdout = p.communicate()[0] - p.wait() - if p.returncode != 0 and not ignore_fail: - syslog.syslog(syslog.LOG_ERR, '[vrrp cfgd] command execution returned {}. Command: "{}", stdout: "{}"'.\ - format(p.returncode, command, stdout)) - return False return True def extract_cmd_daemons(cmd_str): @@ -415,7 +407,7 @@ def subscribe_all(self): @staticmethod def __run_command(table, command, daemons = None): - return g_run_command(table, command, True, daemons) + return g_run_command(table, command, daemons) def vrrp_handler(self, table, key, data): syslog.syslog(syslog.LOG_INFO, '[vrrp cfgd](vrrp) value for {} changed to {}'.format(key, data)) From c0c924e9f6b9c55bd6ec3250bc2bd332708de9c3 Mon Sep 17 00:00:00 2001 From: Philo <135693886+philo-micas@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:02:24 +0800 Subject: [PATCH 03/10] Update test_config.py --- src/sonic-vrrpcfgd/tests/test_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sonic-vrrpcfgd/tests/test_config.py b/src/sonic-vrrpcfgd/tests/test_config.py index 63f03bc67054..1f70fe4c76b1 100644 --- a/src/sonic-vrrpcfgd/tests/test_config.py +++ b/src/sonic-vrrpcfgd/tests/test_config.py @@ -78,10 +78,10 @@ def check_running_cmd(self, mock, is_del): else: if self.ignore_tail is False: mock.assert_called_with(self.table_name, self.compose_vtysh_cmd(vtysh_cmd, is_del), - True, self.daemons) + self.daemons) else: mock.assert_called_with(self.table_name, self.compose_vtysh_cmd(vtysh_cmd, is_del), - True, self.daemons, self.ignore_tail) + self.daemons, self.ignore_tail) conf_cmd = 'configure terminal' conf_intf_cmd = lambda intf_name: [conf_cmd, 'interface %s' % intf_name] From b3f4e6c4982613bd3ddb13c5f07a9a6eadf235e4 Mon Sep 17 00:00:00 2001 From: philo Date: Mon, 29 Apr 2024 10:32:30 +0800 Subject: [PATCH 04/10] run vrrpmgrd on frr container Signed-off-by: philo --- .../frr/supervisord/critical_processes.j2 | 1 + .../frr/supervisord/supervisord.conf.j2 | 11 +++++++++++ dockers/docker-orchagent/critical_processes.j2 | 1 - dockers/docker-orchagent/supervisord.conf.j2 | 14 -------------- rules/docker-fpm-frr.mk | 2 +- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 index f7984dfa9b51..6e798b0fe2b3 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 @@ -3,6 +3,7 @@ program:staticd program:bgpd program:fpmsyncd program:vrrpd +program:vrrmgrd {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} program:bfdd program:ospfd diff --git a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 index 18edd2d1e9e3..db09cc9affd4 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 @@ -137,6 +137,17 @@ stderr_logfile=syslog dependent_startup=true dependent_startup_wait_for=bgpd:running +[program:vrrpmgrd] +command=vrrpmgrd +priority=6 +autostart=false +autorestart=false +startsecs=0 +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true +dependent_startup_wait_for=vrrpd:running + {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} [program:frrcfgd] command=/usr/local/bin/frrcfgd diff --git a/dockers/docker-orchagent/critical_processes.j2 b/dockers/docker-orchagent/critical_processes.j2 index 3cc9f68b53ce..b9bad74089b6 100644 --- a/dockers/docker-orchagent/critical_processes.j2 +++ b/dockers/docker-orchagent/critical_processes.j2 @@ -19,5 +19,4 @@ program:nbrmgrd program:vxlanmgrd program:coppmgrd program:tunnelmgrd -program:vrrpmgrd {%- endif %} diff --git a/dockers/docker-orchagent/supervisord.conf.j2 b/dockers/docker-orchagent/supervisord.conf.j2 index 8d558cc7d1b9..38a55a4be73f 100644 --- a/dockers/docker-orchagent/supervisord.conf.j2 +++ b/dockers/docker-orchagent/supervisord.conf.j2 @@ -303,17 +303,3 @@ environment=ASAN_OPTIONS="log_path=/var/log/asan/fdbsyncd-asan.log{{ asan_extra_ {% endif %} {%- endif %} -{% if is_fabric_asic == 0 %} -[program:vrrpmgrd] -command=/usr/bin/vrrpmgrd -priority=18 -autostart=false -autorestart=false -stdout_logfile=syslog -stderr_logfile=syslog -dependent_startup=true -dependent_startup_wait_for=swssconfig:exited -{% if ENABLE_ASAN == "y" %} -environment=ASAN_OPTIONS="log_path=/var/log/asan/fdbsyncd-asan.log{{ asan_extra_options }}" -{% endif %} -{%- endif %} diff --git a/rules/docker-fpm-frr.mk b/rules/docker-fpm-frr.mk index 4861abb351ee..5c5988ea28f3 100644 --- a/rules/docker-fpm-frr.mk +++ b/rules/docker-fpm-frr.mk @@ -28,7 +28,7 @@ SONIC_DOCKER_IMAGES += $(DOCKER_FPM_FRR) SONIC_DOCKER_DBG_IMAGES += $(DOCKER_FPM_FRR_DBG) $(DOCKER_FPM_FRR)_CONTAINER_NAME = bgp -$(DOCKER_FPM_FRR)_RUN_OPT += -t --cap-add=NET_ADMIN --cap-add=SYS_ADMIN +$(DOCKER_FPM_FRR)_RUN_OPT += -t --cap-add=NET_ADMIN --cap-add=SYS_ADMIN --cap-add=CAP_NET_ADMIN $(DOCKER_FPM_FRR)_RUN_OPT += -v /etc/sonic:/etc/sonic:ro $(DOCKER_FPM_FRR)_RUN_OPT += -v /etc/timezone:/etc/timezone:ro From 8e903327362ededdf45798dc87363a5c996371af Mon Sep 17 00:00:00 2001 From: Philo <135693886+philo-micas@users.noreply.github.com> Date: Mon, 6 May 2024 11:39:39 +0800 Subject: [PATCH 05/10] Rename 0034-support-vrrp6-commands-and-tracking-interface.patch to 0028-support-vrrp6-commands-and-tracking-interface.patch --- ...h => 0028-support-vrrp6-commands-and-tracking-interface.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/sonic-frr/patch/{0034-support-vrrp6-commands-and-tracking-interface.patch => 0028-support-vrrp6-commands-and-tracking-interface.patch} (100%) diff --git a/src/sonic-frr/patch/0034-support-vrrp6-commands-and-tracking-interface.patch b/src/sonic-frr/patch/0028-support-vrrp6-commands-and-tracking-interface.patch similarity index 100% rename from src/sonic-frr/patch/0034-support-vrrp6-commands-and-tracking-interface.patch rename to src/sonic-frr/patch/0028-support-vrrp6-commands-and-tracking-interface.patch From 212f0a442f1444cec81bea0f252e59ccd827d283 Mon Sep 17 00:00:00 2001 From: Philo <135693886+philo-micas@users.noreply.github.com> Date: Fri, 26 Jul 2024 09:46:41 +0800 Subject: [PATCH 06/10] Rename 0028-support-vrrp6-commands-and-tracking-interface.patch to 0030-support-vrrp6-commands-and-tracking-interface.patch --- ...h => 0030-support-vrrp6-commands-and-tracking-interface.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/sonic-frr/patch/{0028-support-vrrp6-commands-and-tracking-interface.patch => 0030-support-vrrp6-commands-and-tracking-interface.patch} (100%) diff --git a/src/sonic-frr/patch/0028-support-vrrp6-commands-and-tracking-interface.patch b/src/sonic-frr/patch/0030-support-vrrp6-commands-and-tracking-interface.patch similarity index 100% rename from src/sonic-frr/patch/0028-support-vrrp6-commands-and-tracking-interface.patch rename to src/sonic-frr/patch/0030-support-vrrp6-commands-and-tracking-interface.patch From 38c35632f41b15914c6b3bff995d150906ae1da9 Mon Sep 17 00:00:00 2001 From: philo Date: Tue, 30 Jul 2024 16:23:06 +0800 Subject: [PATCH 07/10] Change the name of vrrpmgrd to macvlanmgrd Signed-off-by: philo --- dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 | 2 +- dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 index 6e798b0fe2b3..11b8eac6687e 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 @@ -3,7 +3,7 @@ program:staticd program:bgpd program:fpmsyncd program:vrrpd -program:vrrmgrd +program:macvlanmgrd {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} program:bfdd program:ospfd diff --git a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 index 1ffbd295fee2..752dbace7e3e 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 @@ -137,8 +137,8 @@ stderr_logfile=syslog dependent_startup=true dependent_startup_wait_for=bgpd:running -[program:vrrpmgrd] -command=vrrpmgrd +[program:macvlanmgrd] +command=macvlanmgrd priority=6 autostart=false autorestart=false From 903470dad680a8f6f0be6e1c75f02ce25850155b Mon Sep 17 00:00:00 2001 From: philo Date: Mon, 12 Aug 2024 16:00:34 +0800 Subject: [PATCH 08/10] update vrrp patch Signed-off-by: philo --- ...h => 0053-support-vrrp6-commands-and-tracking-interface.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/sonic-frr/patch/{0030-support-vrrp6-commands-and-tracking-interface.patch => 0053-support-vrrp6-commands-and-tracking-interface.patch} (100%) diff --git a/src/sonic-frr/patch/0030-support-vrrp6-commands-and-tracking-interface.patch b/src/sonic-frr/patch/0053-support-vrrp6-commands-and-tracking-interface.patch similarity index 100% rename from src/sonic-frr/patch/0030-support-vrrp6-commands-and-tracking-interface.patch rename to src/sonic-frr/patch/0053-support-vrrp6-commands-and-tracking-interface.patch From 2485aabb566961c50803b9a175b03fd964aca249 Mon Sep 17 00:00:00 2001 From: Philo <135693886+philo-micas@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:02:50 +0800 Subject: [PATCH 09/10] Rename 0053-support-vrrp6-commands-and-tracking-interface.patch to 0054-support-vrrp6-commands-and-tracking-interface.patch --- ...h => 0054-support-vrrp6-commands-and-tracking-interface.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/sonic-frr/patch/{0053-support-vrrp6-commands-and-tracking-interface.patch => 0054-support-vrrp6-commands-and-tracking-interface.patch} (100%) diff --git a/src/sonic-frr/patch/0053-support-vrrp6-commands-and-tracking-interface.patch b/src/sonic-frr/patch/0054-support-vrrp6-commands-and-tracking-interface.patch similarity index 100% rename from src/sonic-frr/patch/0053-support-vrrp6-commands-and-tracking-interface.patch rename to src/sonic-frr/patch/0054-support-vrrp6-commands-and-tracking-interface.patch From 3d4fff0b2f97cf0795c32c230f8341f845761c0e Mon Sep 17 00:00:00 2001 From: philo Date: Fri, 27 Sep 2024 15:25:53 +0800 Subject: [PATCH 10/10] del vrrpcfgd Signed-off-by: philo --- .../frr/supervisord/critical_processes.j2 | 1 - .../frr/supervisord/supervisord.conf.j2 | 21 +- dockers/docker-orchagent/supervisord.conf.j2 | 1 - rules/docker-fpm-frr.mk | 2 +- rules/sonic_vrrpcfgd.dep | 8 - rules/sonic_vrrpcfgd.mk | 13 - src/sonic-vrrpcfgd/.gitignore | 12 - src/sonic-vrrpcfgd/setup.cfg | 5 - src/sonic-vrrpcfgd/setup.py | 29 - src/sonic-vrrpcfgd/tests/__init__.py | 0 src/sonic-vrrpcfgd/tests/test_config.py | 148 ---- src/sonic-vrrpcfgd/tests/test_constructor.py | 19 - src/sonic-vrrpcfgd/vrrpcfgd/__init__.py | 0 src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py | 727 ------------------ 14 files changed, 2 insertions(+), 984 deletions(-) delete mode 100644 rules/sonic_vrrpcfgd.dep delete mode 100644 rules/sonic_vrrpcfgd.mk delete mode 100644 src/sonic-vrrpcfgd/.gitignore delete mode 100644 src/sonic-vrrpcfgd/setup.cfg delete mode 100644 src/sonic-vrrpcfgd/setup.py delete mode 100644 src/sonic-vrrpcfgd/tests/__init__.py delete mode 100644 src/sonic-vrrpcfgd/tests/test_config.py delete mode 100644 src/sonic-vrrpcfgd/tests/test_constructor.py delete mode 100644 src/sonic-vrrpcfgd/vrrpcfgd/__init__.py delete mode 100755 src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py diff --git a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 index 11b8eac6687e..d3af755f2012 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/critical_processes.j2 @@ -11,5 +11,4 @@ program:pimd program:frrcfgd {%- else %} program:bgpcfgd -program:vrrpcfgd {%- endif %} diff --git a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 index c62009976006..91df086717a7 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.j2 @@ -151,17 +151,10 @@ dependent_startup_wait_for=vrrpd:running {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} [program:frrcfgd] command=/usr/local/bin/frrcfgd -priority=6 -autostart=false -autorestart=false -startsecs=0 -stdout_logfile=syslog -stderr_logfile=syslog -dependent_startup=true -dependent_startup_wait_for=bgpd:running {% else %} [program:bgpcfgd] command=/usr/local/bin/bgpcfgd +{% endif %} priority=6 autostart=false autorestart=false @@ -171,18 +164,6 @@ stderr_logfile=syslog dependent_startup=true dependent_startup_wait_for=bgpd:running -[program:vrrpcfgd] -command=/usr/local/bin/vrrpcfgd -priority=6 -autostart=false -autorestart=true -startsecs=0 -stdout_logfile=syslog -stderr_logfile=syslog -dependent_startup=true -dependent_startup_wait_for=bgpd:running -{% endif %} - {% if DEVICE_METADATA.localhost.frr_mgmt_framework_config is defined and DEVICE_METADATA.localhost.frr_mgmt_framework_config == "true" %} {% else %} [program:staticroutebfd] diff --git a/dockers/docker-orchagent/supervisord.conf.j2 b/dockers/docker-orchagent/supervisord.conf.j2 index 38a55a4be73f..026958197fb3 100644 --- a/dockers/docker-orchagent/supervisord.conf.j2 +++ b/dockers/docker-orchagent/supervisord.conf.j2 @@ -302,4 +302,3 @@ dependent_startup_wait_for=swssconfig:exited environment=ASAN_OPTIONS="log_path=/var/log/asan/fdbsyncd-asan.log{{ asan_extra_options }}" {% endif %} {%- endif %} - diff --git a/rules/docker-fpm-frr.mk b/rules/docker-fpm-frr.mk index 6f8ab1e95b5a..a611cdd305e7 100644 --- a/rules/docker-fpm-frr.mk +++ b/rules/docker-fpm-frr.mk @@ -5,7 +5,7 @@ DOCKER_FPM_FRR = $(DOCKER_FPM_FRR_STEM).gz DOCKER_FPM_FRR_DBG = $(DOCKER_FPM_FRR_STEM)-$(DBG_IMAGE_MARK).gz $(DOCKER_FPM_FRR)_PATH = $(DOCKERS_PATH)/$(DOCKER_FPM_FRR_STEM) -$(DOCKER_FPM_FRR)_PYTHON_WHEELS += $(SONIC_BGPCFGD) $(SONIC_FRR_MGMT_FRAMEWORK) $(SONIC_VRRPCFGD) +$(DOCKER_FPM_FRR)_PYTHON_WHEELS += $(SONIC_BGPCFGD) $(SONIC_FRR_MGMT_FRAMEWORK) $(DOCKER_FPM_FRR)_DEPENDS += $(FRR) $(FRR_SNMP) $(SWSS) $(LIBYANG2) $(SONIC_RSYSLOG_PLUGIN) $(DOCKER_FPM_FRR)_DBG_DEPENDS = $($(DOCKER_SWSS_LAYER_BOOKWORM)_DBG_DEPENDS) diff --git a/rules/sonic_vrrpcfgd.dep b/rules/sonic_vrrpcfgd.dep deleted file mode 100644 index bb2d75b61529..000000000000 --- a/rules/sonic_vrrpcfgd.dep +++ /dev/null @@ -1,8 +0,0 @@ -SPATH := $($(SONIC_VRRPCFGD)_SRC_PATH) -DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/sonic_vrrpcfgd.mk rules/sonic_vrrpcfgd.dep -DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) -DEP_FILES += $(shell git ls-files $(SPATH)) - -$(SONIC_VRRPCFGD)_CACHE_MODE := GIT_CONTENT_SHA -$(SONIC_VRRPCFGD)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) -$(SONIC_VRRPCFGD)_DEP_FILES := $(DEP_FILES) diff --git a/rules/sonic_vrrpcfgd.mk b/rules/sonic_vrrpcfgd.mk deleted file mode 100644 index 34dcfee842d0..000000000000 --- a/rules/sonic_vrrpcfgd.mk +++ /dev/null @@ -1,13 +0,0 @@ -# sonic-vrrpcfgd package - -SONIC_VRRPCFGD = sonic_vrrpcfgd-1.0-py3-none-any.whl -$(SONIC_VRRPCFGD)_SRC_PATH = $(SRC_PATH)/sonic-vrrpcfgd -# These dependencies are only needed because they are dependencies -# of sonic-config-engine and vrrpcfgd explicitly calls sonic-cfggen -# as part of its unit tests. -# TODO: Refactor unit tests so that these dependencies are not needed - -$(SONIC_VRRPCFGD)_DEPENDS += $(SONIC_CONFIG_ENGINE_PY3) -$(SONIC_VRRPCFGD)_DEBS_DEPENDS += $(PYTHON_SWSSCOMMON) -$(SONIC_VRRPCFGD)_PYTHON_VERSION = 3 -SONIC_PYTHON_WHEELS += $(SONIC_VRRPCFGD) \ No newline at end of file diff --git a/src/sonic-vrrpcfgd/.gitignore b/src/sonic-vrrpcfgd/.gitignore deleted file mode 100644 index 10fb4e57b77e..000000000000 --- a/src/sonic-vrrpcfgd/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -.eggs/ -build/ -dist/ -*.egg-info/ -vrrpcfgd/*.pyc -tests/*.pyc -tests/__pycache__/ -.idea -.coverage -vrrpcfgd/__pycache__/ -venv -tests/.coverage* \ No newline at end of file diff --git a/src/sonic-vrrpcfgd/setup.cfg b/src/sonic-vrrpcfgd/setup.cfg deleted file mode 100644 index 00ed5efbcbad..000000000000 --- a/src/sonic-vrrpcfgd/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[aliases] -test=pytest -[tool:pytest] -addopts = --verbose -python_files = tests/*.py diff --git a/src/sonic-vrrpcfgd/setup.py b/src/sonic-vrrpcfgd/setup.py deleted file mode 100644 index a47ea7fc1829..000000000000 --- a/src/sonic-vrrpcfgd/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -import setuptools - -setuptools.setup( - name = 'sonic-vrrpcfgd', - version = '1.0', - description = 'Utility to dynamically generate VRRP configuration for FRR', - url = 'https://github.com/Azure/sonic-buildimage', - packages = setuptools.find_packages(), - entry_points = { - 'console_scripts': [ - 'vrrpcfgd = vrrpcfgd.vrrpcfgd:main', - ] - }, - install_requires = [ - 'jinja2>=2.10', - 'netaddr==0.8.0', - 'pyyaml==6.0.1', - 'ipaddress==1.0.23' - ], - setup_requires = [ - 'pytest-runner', - 'wheel' - ], - tests_require = [ - 'pytest', - 'pytest-cov', - 'sonic-config-engine' - ] -) diff --git a/src/sonic-vrrpcfgd/tests/__init__.py b/src/sonic-vrrpcfgd/tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/sonic-vrrpcfgd/tests/test_config.py b/src/sonic-vrrpcfgd/tests/test_config.py deleted file mode 100644 index 1f70fe4c76b1..000000000000 --- a/src/sonic-vrrpcfgd/tests/test_config.py +++ /dev/null @@ -1,148 +0,0 @@ -import copy -import re -from unittest.mock import MagicMock, NonCallableMagicMock, patch - -swsscommon_module_mock = MagicMock(ConfigDBConnector = NonCallableMagicMock) -# because can’t use dotted names directly in a call, have to create a dictionary and unpack it using **: -mockmapping = {'swsscommon.swsscommon': swsscommon_module_mock} - -@patch.dict('sys.modules', **mockmapping) -def test_contructor(): - from vrrpcfgd.vrrpcfgd import VRRPConfigDaemon - daemon = VRRPConfigDaemon() - daemon.start() - for table, hdlr in daemon.table_handler_list: - daemon.config_db.subscribe.assert_any_call(table, hdlr) - daemon.config_db.pubsub.psubscribe.assert_called_once() - assert(daemon.config_db.sub_thread.is_alive() == True) - daemon.stop() - daemon.config_db.pubsub.punsubscribe.assert_called_once() - assert(daemon.config_db.sub_thread.is_alive() == False) - -class CmdMapTestInfo: - data_buf = {} - def __init__(self, table, key, data, exp_cmd, no_del = False, neg_cmd = None, - chk_data = None, daemons = None, ignore_tail = False): - self.table_name = table - self.key = key - self.data = data - self.vtysh_cmd = exp_cmd - self.no_del = no_del - self.vtysh_neg_cmd = neg_cmd - self.chk_data = chk_data - self.daemons = daemons - self.ignore_tail = ignore_tail - @classmethod - def add_test_data(cls, test): - assert(isinstance(test.data, dict)) - cls.data_buf.setdefault( - test.table_name, {}).setdefault(test.key, {}).update(test.data) - @classmethod - def set_default_test_data(cls, test): - assert(test.table_name in cls.data_buf and - test.key in cls.data_buf[test.table_name]) - cache_data = cls.data_buf[test.table_name][test.key] - assert(isinstance(test.data, dict)) - for k, v in test.data.items(): - assert(k in cache_data and cache_data[k] == v) - if k == 'preempt': - test.data[k] = 'disabled' - if k == 'admin_status': - test.data[k] = 'up' - cls.data_buf.setdefault( - test.table_name, {}).setdefault(test.key, {}).update(test.data) - - @classmethod - def get_test_data(cls, test): - assert(test.table_name in cls.data_buf and - test.key in cls.data_buf[test.table_name]) - return copy.deepcopy(cls.data_buf[test.table_name][test.key]) - @staticmethod - def compose_vtysh_cmd(cmd_list, negtive = False): - cmdline = 'vtysh' - for cmd in cmd_list: - cmd = cmd.format('no ' if negtive else '') - cmdline += " -c '%s'" % cmd - return cmdline - def check_running_cmd(self, mock, is_del): - if is_del: - vtysh_cmd = self.vtysh_cmd if self.vtysh_neg_cmd is None else self.vtysh_neg_cmd - else: - vtysh_cmd = self.vtysh_cmd - if callable(vtysh_cmd): - cmds = [] - for call in mock.call_args_list: - assert(call[0][0] == self.table_name) - cmds.append(call[0][1]) - vtysh_cmd(is_del, cmds, self.chk_data) - else: - if self.ignore_tail is False: - mock.assert_called_with(self.table_name, self.compose_vtysh_cmd(vtysh_cmd, is_del), - self.daemons) - else: - mock.assert_called_with(self.table_name, self.compose_vtysh_cmd(vtysh_cmd, is_del), - self.daemons, self.ignore_tail) - -conf_cmd = 'configure terminal' -conf_intf_cmd = lambda intf_name: [conf_cmd, 'interface %s' % intf_name] - -vrrp_config_data = [ - CmdMapTestInfo('VRRP', 'Ethernet8|8', {'vid': '8'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp 8'], True), - CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'vid': '8'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp6 8'], True), - CmdMapTestInfo('VRRP', 'Ethernet1|8', {'vip': ['7.7.7.7/24']}, - conf_intf_cmd('Ethernet1') + ['{}vrrp 8 ip 7.7.7.7'], True), - CmdMapTestInfo('VRRP', 'Ethernet8|8', {'admin_status': 'down'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp 8 shutdown']), - CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'admin_status': 'down'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 shutdown']), - CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'vip': ['2000::1/64']}, - conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 ipv6 2000::1'], True), - CmdMapTestInfo('VRRP_TRACK', 'Ethernet8|8|Ethernet5', {'priority_increment': '20'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp 8 track-interface Ethernet5 priority-dec 20'], True), - CmdMapTestInfo('VRRP6_TRACK', 'Ethernet8|8|Ethernet5', {'priority_increment': '20'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 track-interface Ethernet5 priority-dec 20'], True), - CmdMapTestInfo('VRRP', 'Ethernet8|8', {'preempt': 'enabled'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp 8 preempt']), - CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'preempt': 'enabled'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 preempt']), - CmdMapTestInfo('VRRP', 'Ethernet8|8', {'version': '2'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp 8 version 2'], True), - CmdMapTestInfo('VRRP', 'Ethernet8|8', {'adv_interval': '2000'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp 8 advertisement-interval 2000'], True), - CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'adv_interval': '2000'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 advertisement-interval 2000'], True), - CmdMapTestInfo('VRRP', 'Ethernet8|8', {'priority': '150'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp 8 priority 150'], True), - CmdMapTestInfo('VRRP6', 'Ethernet8|8', {'priority': '150'}, - conf_intf_cmd('Ethernet8') + ['{}vrrp6 8 priority 150'], True), -] - -@patch.dict('sys.modules', **mockmapping) -@patch('vrrpcfgd.vrrpcfgd.g_run_command') -def data_set_del_test(test_data, run_cmd): - from vrrpcfgd.vrrpcfgd import VRRPConfigDaemon - daemon = VRRPConfigDaemon() - data_buf = {} - # add data in list - for test in test_data: - run_cmd.reset_mock() - hdlr = [h for t, h in daemon.table_handler_list if t == test.table_name] - assert(len(hdlr) == 1) - CmdMapTestInfo.add_test_data(test) - hdlr[0](test.table_name, test.key, CmdMapTestInfo.get_test_data(test)) - test.check_running_cmd(run_cmd, False) - # delete data in reverse direction - for test in reversed(test_data): - if test.no_del: - continue - run_cmd.reset_mock() - hdlr = [h for t, h in daemon.table_handler_list if t == test.table_name] - assert(len(hdlr) == 1) - CmdMapTestInfo.set_default_test_data(test) - hdlr[0](test.table_name, test.key, CmdMapTestInfo.get_test_data(test)) - test.check_running_cmd(run_cmd, True) - -def test_vrrp_config(): - data_set_del_test(vrrp_config_data) diff --git a/src/sonic-vrrpcfgd/tests/test_constructor.py b/src/sonic-vrrpcfgd/tests/test_constructor.py deleted file mode 100644 index 9b3dbaffb837..000000000000 --- a/src/sonic-vrrpcfgd/tests/test_constructor.py +++ /dev/null @@ -1,19 +0,0 @@ -import socket -import pytest -from unittest.mock import MagicMock, NonCallableMagicMock, patch - -swsscommon_module_mock = MagicMock(ConfigDBConnector = NonCallableMagicMock) -# because can¡¯t use dotted names directly in a call, have to create a dictionary and unpack it using **: -mockmapping = {'swsscommon.swsscommon': swsscommon_module_mock} - -with patch.dict('sys.modules', **mockmapping): - from vrrpcfgd.vrrpcfgd import CachedDataWithOp - -def test_data_with_op(): - data = CachedDataWithOp() - assert(data.data is None) - assert(data.op == CachedDataWithOp.OP_NONE) - data = CachedDataWithOp(10, CachedDataWithOp.OP_ADD) - assert(data.data == 10) - assert(data.op == CachedDataWithOp.OP_ADD) - diff --git a/src/sonic-vrrpcfgd/vrrpcfgd/__init__.py b/src/sonic-vrrpcfgd/vrrpcfgd/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py b/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py deleted file mode 100755 index 15d813316fd8..000000000000 --- a/src/sonic-vrrpcfgd/vrrpcfgd/vrrpcfgd.py +++ /dev/null @@ -1,727 +0,0 @@ -#!/usr/bin/env python - -import copy -import subprocess -import time -import syslog -import os -from swsscommon.swsscommon import ConfigDBConnector -import socket -import threading -import queue -import signal -import re -import logging -import netaddr -import ipaddress -import io -import struct - -class CachedDataWithOp: - OP_NONE = 0 - OP_ADD = 1 - OP_DELETE = 2 - OP_UPDATE = 3 - - STAT_SUCC = 0 - STAT_FAIL = 1 - - def __init__(self, data = None, op = OP_NONE): - self.data = data - self.op = op - self.status = self.STAT_FAIL - - def __repr__(self): - op_str = '' - if self.op == self.OP_NONE: - op_str = 'NONE' - elif self.op == self.OP_ADD: - op_str = 'ADD' - elif self.op == self.OP_DELETE: - op_str = 'DELETE' - elif self.op == self.OP_UPDATE: - op_str = 'UPDATE' - return '(%s, %s)' % (self.data, op_str) - -vrrpd_client = None - -def g_run_command(table, command, daemons, ignore_fail = False): - syslog.syslog(syslog.LOG_DEBUG, "execute command {} for table {}.".format(command, table)) - if not command.startswith('vtysh '): - syslog.syslog(syslog.LOG_ERR, 'Command no start with "vtysh". Command: "{}"'.format(command)) - else: - if not vrrpd_client.run_vtysh_command(table, command, daemons) and not ignore_fail: - syslog.syslog(syslog.LOG_ERR, 'command execution failure. Command: "{}"'.format(command)) - return False - return True - -def extract_cmd_daemons(cmd_str): - # daemon list could be given within brackets at head of input lines - dm_mark = re.match(r'\[(?P.+)\]', cmd_str) - if dm_mark is not None and 'daemons' in dm_mark.groupdict(): - cmd_str = cmd_str[dm_mark.end():] - daemons = dm_mark.groupdict()['daemons'].split(',') - else: - daemons = None - return (daemons, cmd_str) - -class VrrpdClientMgr(threading.Thread): - VTYSH_MARK = 'vtysh ' - PROXY_SERVER_ADDR = '/etc/frr/vrrpd_client_sock' - ALL_DAEMONS = ['vrrpd'] - TABLE_DAEMON = { - 'VRRP': ['vrrpd'], - 'VRRP6': ['vrrpd'], - 'VRRP_TRACK': ['vrrpd'], - 'VRRP6_TRACK': ['vrrpd'], - } - VTYSH_CMD_DAEMON = [(r'show vrrp($|\s+\S+)', ['vrrpd'])] - @staticmethod - def __create_proxy_socket(): - try: - os.unlink(VrrpdClientMgr.PROXY_SERVER_ADDR) - except OSError: - if os.path.exists(VrrpdClientMgr.PROXY_SERVER_ADDR): - raise - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.bind(VrrpdClientMgr.PROXY_SERVER_ADDR) - sock.listen(1) - return sock - @staticmethod - def __get_reply(sock): - reply_msg = None - ret_code = None - msg_buf = io.StringIO() - while True: - try: - rd_msg = sock.recv(16384) - msg_buf.write(rd_msg.decode()) - except socket.timeout: - syslog.syslog(syslog.LOG_ERR, 'socket reading timeout') - break - if len(rd_msg) < 4: - rd_msg = msg_buf.getvalue() - if len(rd_msg) < 4: - continue - msg_tail = rd_msg[-4:] - if isinstance(msg_tail, str): - msg_tail = bytes(msg_tail, 'utf-8') - if msg_tail[0] == 0 and msg_tail[1] == 0 and msg_tail[2] == 0: - ret_code = msg_tail[3] - reply_msg = msg_buf.getvalue()[:-4] - break - msg_buf.close() - return (ret_code, reply_msg) - @staticmethod - def __send_data(sock, data): - if isinstance(data, str): - data = bytes(data, 'utf-8') - sock.sendall(data) - def __create_frr_client(self): - self.client_socks = {} - for daemon in self.ALL_DAEMONS: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - serv_addr = '/run/frr/%s.vty' % daemon - retry_cnt = 0 - while True: - try: - sock.connect(serv_addr) - break - except socket.error as msg: - syslog.syslog(syslog.LOG_ERR, 'failed to connect to frr daemon %s: %s' % (daemon, msg)) - retry_cnt += 1 - if retry_cnt > 100 or not main_loop: - syslog.syslog(syslog.LOG_ERR, 're-tried too many times, give up') - for _, sock in self.client_socks.items(): - sock.close() - return False - time.sleep(2) - continue - sock.settimeout(120) - self.client_socks[daemon] = sock - for daemon, sock in self.client_socks.items(): - syslog.syslog(syslog.LOG_DEBUG, 'send initial enable command to %s' % daemon) - try: - self.__send_data(sock, 'enable\0') - except socket.error as msg: - syslog.syslog(syslog.LOG_ERR, 'failed to send initial enable command to %s' % daemon) - return False - ret_code, reply = self.__get_reply(sock) - if ret_code is None: - syslog.syslog(syslog.LOG_ERR, 'failed to get command response for enable command from %s' % daemon) - return False - if ret_code != 0: - syslog.syslog(syslog.LOG_ERR, 'enable command failed: ret_code=%d' % ret_code) - syslog.syslog(syslog.LOG_ERR, reply) - return False - return True - def __init__(self): - super(VrrpdClientMgr, self).__init__(name = 'VTYSH sub-process manager') - if not self.__create_frr_client(): - syslog.syslog(syslog.LOG_ERR, 'failed to create socket to FRR daemon') - raise RuntimeError('connect to FRR daemon failed') - self.proxy_running = True - self.lock = threading.Lock() - self.proxy_sock = self.__create_proxy_socket() - self.cmd_to_daemon = [] - for pat, daemons in self.VTYSH_CMD_DAEMON: - try: - self.cmd_to_daemon.append((re.compile(pat), daemons)) - except Exception: - syslog.syslog(syslog.LOG_ERR, 'invalid regex format: %s' % pat) - continue - def __get_cmd_daemons(self, cmd_list): - cmn_daemons = None - for cmd in cmd_list: - found = False - for re_comp, daemons in self.cmd_to_daemon: - if re_comp.match(cmd.strip()) is not None: - found = True - break - if not found: - syslog.syslog(syslog.LOG_ERR, 'no matched daemons found for command %s' % cmd) - return None - if cmn_daemons is None: - cmn_daemons = set(daemons) - else: - cmn_daemons = cmn_daemons.intersection(set(daemons)) - if len(cmn_daemons) == 0: - return [] - return list(cmn_daemons) - def __proc_command(self, command, daemons): - syslog.syslog(syslog.LOG_DEBUG, 'VTYSH CMD: %s daemons: %s' % (command, daemons)) - resp = '' - ret_val = False - for daemon in daemons: - sock = self.client_socks.get(daemon, None) - if sock is None: - syslog.syslog(syslog.LOG_ERR, 'daemon %s is not connected' % daemon) - continue - try: - self.__send_data(sock, command + '\0') - except socket.error as msg: - syslog.syslog(syslog.LOG_ERR, 'failed to send command to frr daemon: %s' % msg) - return (False, None) - ret_code, reply = self.__get_reply(sock) - if ret_code is None or ret_code != 0: - if ret_code is None: - syslog.syslog(syslog.LOG_ERR, 'failed to get reply from frr daemon') - continue - else: - syslog.syslog(syslog.LOG_DEBUG, '[%s] command return code: %d' % (daemon, ret_code)) - syslog.syslog(syslog.LOG_DEBUG, reply) - else: - # command is running successfully by at least one daemon - ret_val = True - resp += reply - return (ret_val, resp) - def run_vtysh_command(self, table, command, daemons): - if not command.startswith(self.VTYSH_MARK): - syslog.syslog(syslog.LOG_ERR, 'command %s is not for vtysh config' % command) - return False - cmd_line = command[len(self.VTYSH_MARK):] - cmd_list = re.findall(r"-c\s+'([^']+)'\s*", cmd_line) - cmd_list.append('end') - if daemons is None: - daemons = self.TABLE_DAEMON.get(table, None) - if daemons is None: - daemons = self.__get_cmd_daemons(cmd_list) - if daemons is None or len(daemons) == 0: - syslog.syslog(syslog.LOG_ERR, 'no common daemon list found for given commands') - return False - ret_val = True - with self.lock: - for cmd in cmd_list: - succ, _ = self.__proc_command(cmd.strip(), daemons) - if not succ: - ret_val = False - return ret_val - @staticmethod - def __read_all(sock, data_len): - in_buf = io.StringIO() - left_len = data_len - while left_len > 0: - data = sock.recv(left_len) - if data is None: - break - in_buf.write(data) - left_len -= len(data) - return in_buf.getvalue() - def shutdown(self): - syslog.syslog(syslog.LOG_DEBUG, 'terminate vrrpcfgd client manager') - if self.is_alive(): - self.proxy_running = False - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - try: - sock.connect(self.PROXY_SERVER_ADDR) - finally: - sock.close() - self.join() - for _, sock in self.client_socks.items(): - sock.close() - def run(self): - syslog.syslog(syslog.LOG_DEBUG, 'entering VTYSH proxy thread') - while self.proxy_running: - syslog.syslog(syslog.LOG_DEBUG, 'waiting for client connection ...') - conn_sock, clnt_addr = self.proxy_sock.accept() - if not self.proxy_running: - conn_sock.close() - break - try: - syslog.syslog(syslog.LOG_DEBUG, 'client connection from %s' % clnt_addr) - data = self.__read_all(conn_sock, 4) - if len(data) == 4: - data_len = struct.unpack('>I', data)[0] - in_cmd = self.__read_all(conn_sock, data_len) - if len(in_cmd) == data_len: - daemons, in_cmd = extract_cmd_daemons(in_cmd) - in_lines = in_cmd.splitlines() - if daemons is None: - daemons = self.__get_cmd_daemons(in_lines) - if daemons is not None and len(daemons) > 0: - with self.lock: - for line in in_lines: - _, reply = self.__proc_command(line.strip(), daemons) - if reply is not None: - self.__send_data(conn_sock, reply) - else: - syslog.syslog(syslog.LOG_ERR, 'failed running VTYSH command') - else: - syslog.syslog(syslog.LOG_ERR, 'could not find common daemons for input commands') - else: - syslog.syslog(syslog.LOG_ERR, 'read data of length %d is not expected length %d' % (data_len, len(in_cmd))) - else: - syslog.syslog(syslog.LOG_ERR, 'invalid data length %d' % len(data)) - except socket.error as msg: - syslog.syslog(syslog.LOG_ERR, 'socket writing failed: %s' % msg) - finally: - syslog.syslog(syslog.LOG_DEBUG, 'closing data socket from client') - conn_sock.close() - syslog.syslog(syslog.LOG_DEBUG, 'leaving VTYSH proxy thread') - -class ExtConfigDBConnector(ConfigDBConnector): - def __init__(self, ns_attrs = None): - super(ExtConfigDBConnector, self).__init__() - self.nosort_attrs = ns_attrs if ns_attrs is not None else {} - self.__listen_thread_running = False - def raw_to_typed(self, raw_data, table = ''): - if len(raw_data) == 0: - raw_data = None - data = super(ExtConfigDBConnector, self).raw_to_typed(raw_data) - if data is None: - return None - for key, val in data.items(): - if type(val) is list and key not in self.nosort_attrs.get(table, set()): - val.sort() - return data - def sub_msg_handler(self, msg_item): - if msg_item['type'] == 'pmessage': - key = msg_item['channel'].split(':', 1)[1] - try: - (table, row) = key.split(self.TABLE_NAME_SEPARATOR, 1) - if table in self.handlers: - client = self.get_redis_client(self.db_name) - data = self.raw_to_typed(client.hgetall(key), table) - super(ExtConfigDBConnector, self)._ConfigDBConnector__fire(table, row, data) - except ValueError: - pass #Ignore non table-formated redis entries - except Exception as e: - syslog.syslog(syslog.LOG_ERR, '[vrrp cfgd] Failed handling config DB update with exception:' + str(e)) - logging.exception(e) - - def listen_thread(self, timeout): - self.__listen_thread_running = True - sub_key_space = "__keyspace@{}__:*".format(self.get_dbid(self.db_name)) - self.pubsub.psubscribe(sub_key_space) - while self.__listen_thread_running: - msg = self.pubsub.get_message(timeout, True) - if msg: - self.sub_msg_handler(msg) - - self.pubsub.punsubscribe(sub_key_space) - - def listen(self): - """Start listen Redis keyspace events and will trigger corresponding handlers when content of a table changes. - """ - self.pubsub = self.get_redis_client(self.db_name).pubsub() - self.sub_thread = threading.Thread(target=self.listen_thread, args=(0.01,)) - self.sub_thread.start() - - def stop_listen(self): - self.__listen_thread_running = False - - @staticmethod - def get_table_key(table, key): - return table + '&&' + key - def get_table_data(self, table_list): - ret_data = {} - for table in table_list: - table_data = self.get_table(table) - for key, data in table_data.items(): - table_key = self.get_table_key(table, self.serialize_key(key)) - ret_data[table_key] = data - return ret_data - -class VRRPConfigDaemon: - DEFAULT_VRF = 'default' - - def __init__(self): - self.config_db = ExtConfigDBConnector() - try: - self.config_db.connect() - except Exception as e: - syslog.syslog(syslog.LOG_ERR, '[vrrp cfgd] Failed connecting to config DB with exception:' + str(e)) - db_entry = self.config_db.get_entry('DEVICE_METADATA', 'localhost') - if 'docker_routing_config_mode' in db_entry: - self.config_mode = db_entry['docker_routing_config_mode'] - else: - self.config_mode = "separated" - - self.table_handler_list = [ - ('VRRP', self.vrrp_handler), - ('VRRP6', self.vrrp6_handler), - ('VRRP_TRACK', self.vrrp_track_handler), - ('VRRP6_TRACK', self.vrrp6_track_handler), - ] - self.table_data_cache = self.config_db.get_table_data([tbl for tbl, _ in self.table_handler_list]) - syslog.syslog(syslog.LOG_DEBUG, 'Init Cached DB data') - for key, entry in self.table_data_cache.items(): - syslog.syslog(syslog.LOG_DEBUG, ' %-20s : %s' % (key, entry)) - if self.config_mode == "unified": - for table, _ in self.table_handler_list: - table_list = self.config_db.get_table(table) - for key, data in table_list.items(): - syslog.syslog(syslog.LOG_DEBUG, 'config replay for table {} key {}'.format(table, key)) - upd_data = {} - for upd_key, upd_val in data.items(): - upd_data[upd_key] = CachedDataWithOp(upd_val, CachedDataWithOp.OP_ADD) - upd_data_list = [] - self.__update_bgp(upd_data_list) - for table1, key1, data1 in upd_data_list: - table_key = ExtConfigDBConnector.get_table_key(table1, key1) - self.__update_cache_data(table_key, data1) - - def subscribe_all(self): - for table, hdlr in self.table_handler_list: - self.config_db.subscribe(table, hdlr) - - @staticmethod - def __run_command(table, command, daemons = None): - return g_run_command(table, command, daemons) - - def vrrp_handler(self, table, key, data): - syslog.syslog(syslog.LOG_INFO, '[vrrp cfgd](vrrp) value for {} changed to {}'.format(key, data)) - #get frr vrrp session key - key_params = key.split('|') - intf_cmd = 'interface {}'.format(key_params[0]) - cmd = 'vrrp {}'.format(key_params[1]) - table_key = ExtConfigDBConnector.get_table_key(table, key) - comb_attr_list = ['vip'] - if not data: - #VRRP instance is deleted - command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) - self.__run_command(table, command) - #del cache data - del(self.table_data_cache[table_key]) - else: - #create/update case - command = "vtysh -c 'configure terminal' -c '{}'".format(intf_cmd) - self.__add_op_to_data(table_key, data, comb_attr_list) - cached_data = self.table_data_cache.setdefault(table_key, {}) - for param in data: - if param == 'vid': - if param in cached_data and data[param].data == cached_data[param]: - continue - elif 'vip' not in data: - command = command + " -c '{}'".format(cmd) - elif param == 'vip': - if 'vip' in cached_data: - cache_address = cached_data[param] - data_address = data[param].data - # add vip - for d_address in data_address: - if d_address in cache_address: - continue - elif d_address != "": - d_addr = d_address.split('/') - try: - ip_address = ipaddress.ip_interface(d_addr[0]) - except ValueError as err: - syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) - if ip_address.version == 4: - command = command + " -c '{} ip {}'".format(cmd, d_addr[0]) - # del vip - for c_address in cache_address: - if c_address in data_address: - continue - elif c_address != "": - c_addr = c_address.split('/') - try: - ip_address = ipaddress.ip_interface(c_addr[0]) - except ValueError as err: - syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) - if ip_address.version == 4: - command = command + " -c 'no {} ip {}'".format(cmd, c_addr[0]) - else: - # first time to config - data_address = data[param].data - for d_address in data_address: - d_addr = d_address.split('/') - try: - ip_address = ipaddress.ip_interface(d_addr[0]) - except ValueError as err: - syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IP address is not valid:{}'.format(err)) - if ip_address.version == 4: - command = command + " -c '{} ip {}'".format(cmd, d_addr[0]) - elif param == 'priority': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - command = command + " -c '{} priority {}'".format(cmd, data[param].data) - elif param == 'adv_interval': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - command = command + " -c '{} advertisement-interval {}'".format(cmd, data[param].data) - elif param == 'version': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - command = command + " -c '{} version {}'".format(cmd, data[param].data) - elif param == 'admin_status': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - if data[param].data == 'down': - command = command + " -c '{} shutdown'".format(cmd) - elif data[param].data == 'up' or data[param].data == '': - command = command + " -c 'no {} shutdown'".format(cmd) - elif param == 'preempt': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - if data[param].data == 'enabled': - command = command + " -c '{} preempt'".format(cmd) - elif data[param].data == 'disabled': - command = command + " -c 'no {} preempt'".format(cmd) - data[param].status = CachedDataWithOp.STAT_SUCC - self.__update_cache_data(table_key, data) - self.__run_command(table, command) - - def vrrp6_handler(self, table, key, data): - syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp6) value for {} changed to {}'.format(key, data)) - #get frr vrrp6 session key - key_params = key.split('|') - intf_cmd = 'interface {}'.format(key_params[0]) - cmd = 'vrrp6 {}'.format(key_params[1]) - table_key = ExtConfigDBConnector.get_table_key(table, key) - comb_attr_list = ['vip'] - if not data: - #VRRP instance is deleted - command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) - self.__run_command(table, command) - #del cache data - del(self.table_data_cache[table_key]) - else: - #create/update case - command = "vtysh -c 'configure terminal' -c '{}'".format(intf_cmd) - self.__add_op_to_data(table_key, data, comb_attr_list) - cached_data = self.table_data_cache.setdefault(table_key, {}) - for param in data: - if param == 'vid': - if param in cached_data and data[param].data == cached_data[param]: - continue - elif 'vip' not in data: - command = command + " -c '{}'".format(cmd) - elif param == 'vip': - if 'vip' in cached_data: - cache_address = cached_data[param] - data_address = data[param].data - for d_address in data_address: - if d_address in cache_address: - continue - elif d_address != "": - d_addr = d_address.split('/') - try: - ip_address = ipaddress.ip_interface(d_addr[0]) - except ValueError as err: - syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) - if ip_address.version == 6: - command = command + " -c '{} ipv6 {}'".format(cmd, d_addr[0]) - - for c_address in cache_address: - if c_address in data_address: - continue - elif c_address != "": - c_addr = c_address.split('/') - try: - ip_address = ipaddress.ip_interface(c_addr[0]) - except ValueError as err: - syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) - if ip_address.version == 6: - command = command + " -c 'no {} ipv6 {}'".format(cmd, c_addr[0]) - else: - # first time to config - data_address = data[param].data - for d_address in data_address: - d_addr = d_address.split('/') - try: - ip_address = ipaddress.ip_interface(d_addr[0]) - except ValueError as err: - syslog.syslog(syslog.LOG_ERR, '[bgp vrrpd] IPv6 address is not valid:{}'.format(err)) - if ip_address.version == 6: - command = command + " -c '{} ipv6 {}'".format(cmd, d_addr[0]) - elif param == 'priority': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - command = command + " -c '{} priority {}'".format(cmd, data[param].data) - elif param == 'adv_interval': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - command = command + " -c '{} advertisement-interval {}'".format(cmd, data[param].data) - elif param == 'version': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - command = command + " -c '{} version {}'".format(cmd, data[param].data) - elif param == 'admin_status': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - if data[param].data == 'down': - command = command + " -c '{} shutdown'".format(cmd) - elif data[param].data == 'up' or data[param].data == '': - command = command + " -c 'no {} shutdown'".format(cmd) - elif param == 'preempt': - if param in cached_data and data[param].data == cached_data[param]: - continue - else: - if data[param].data == 'enabled': - command = command + " -c '{} preempt'".format(cmd) - elif data[param].data == 'disabled': - command = command + " -c 'no {} preempt'".format(cmd) - data[param].status = CachedDataWithOp.STAT_SUCC - self.__update_cache_data(table_key, data) - self.__run_command(table, command) - - def vrrp_track_handler(self, table, key, data): - syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp track) value for {} changed to {}'.format(key, data)) - #get frr vrrp track key - key_params = key.split('|') - intf_cmd = 'interface {}'.format(key_params[0]) - cmd = 'vrrp {} track-interface {}'.format(key_params[1], key_params[2]) - - if not data: - #VRRP track instance is deleted - command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) - self.__run_command(table, command) - else: - #create/update case - if 'priority_increment' in data: - command = "vtysh -c 'configure terminal' -c '{}' -c '{} priority-dec {}'".format(intf_cmd, cmd, data['priority_increment']) - self.__run_command(table, command) - - def vrrp6_track_handler(self, table, key, data): - syslog.syslog(syslog.LOG_INFO, '[bgp cfgd](vrrp6 track) value for {} changed to {}'.format(key, data)) - #get frr vrrp6 track key - key_params = key.split('|') - intf_cmd = 'interface {}'.format(key_params[0]) - cmd = 'vrrp6 {} track-interface {}'.format(key_params[1], key_params[2]) - - if not data: - #VRRP track instance is deleted - command = "vtysh -c 'configure terminal' -c '{}' -c 'no {}'".format(intf_cmd, cmd) - self.__run_command(table, command) - else: - #create/update case - if 'priority_increment' in data: - command = "vtysh -c 'configure terminal' -c '{}' -c '{} priority-dec {}'".format(intf_cmd, cmd, data['priority_increment']) - self.__run_command(table, command) - - def __add_op_to_data(self, table_key, data, comb_attr_list): - cached_data = self.table_data_cache.setdefault(table_key, {}) - for key in cached_data: - if key in data: - # both in cache and data, update/none - data[key] = (CachedDataWithOp(data[key], CachedDataWithOp.OP_NONE) if data[key] == cached_data[key] else - CachedDataWithOp(data[key], CachedDataWithOp.OP_UPDATE)) - else: - # in cache but not in data, delete - data[key] = CachedDataWithOp(cached_data[key], CachedDataWithOp.OP_DELETE) - for key in data: - if not isinstance(data[key], CachedDataWithOp): - # in data but not in cache, add - data[key] = CachedDataWithOp(data[key], CachedDataWithOp.OP_ADD) - # combo attributes handling - op_list = [CachedDataWithOp.OP_DELETE, CachedDataWithOp.OP_ADD, CachedDataWithOp.OP_UPDATE, CachedDataWithOp.OP_NONE] - for key_set in comb_attr_list: - all_in = True - op_idx = len(op_list) - 1 - for key in key_set: - if key not in data: - all_in = False - break - idx = op_list.index(data[key].op) - if idx >= 0 and idx < op_idx: - op_idx = idx - if all_in: - for key in key_set: - data[key].op = op_list[op_idx] - else: - # if one key doesn't exist, clean the whole key set - for key in key_set: - data.pop(key, None) - - def __update_cache_data(self, table_key, data): - cached_data = self.table_data_cache.setdefault(table_key, {}) - for key, val in data.items(): - if not isinstance(val, CachedDataWithOp) or val.op == CachedDataWithOp.OP_NONE or val.status == CachedDataWithOp.STAT_FAIL: - syslog.syslog(syslog.LOG_DEBUG, 'ignore cache update for %s because of %s%s%s' % - (key, ('' if isinstance(val, CachedDataWithOp) else 'INV_DATA '), - ('NO_OP ' if isinstance(val, CachedDataWithOp) and val.op == CachedDataWithOp.OP_NONE else ''), - ('STAT_FAIL ' if isinstance(val, CachedDataWithOp) and val.status == CachedDataWithOp.STAT_FAIL else ''))) - continue - if val.op == CachedDataWithOp.OP_ADD or val.op == CachedDataWithOp.OP_UPDATE: - cached_data[key] = val.data - syslog.syslog(syslog.LOG_INFO, 'Add {} data {} to cache'.format(key, cached_data[key])) - elif val.op == CachedDataWithOp.OP_DELETE: - syslog.syslog(syslog.LOG_INFO, 'delete {} data {} from cache'.format(key, cached_data.get(key, ''))) - cached_data.pop(key, None) - if len(cached_data) == 0: - syslog.syslog(syslog.LOG_INFO, 'delete table row {} from cache'.format(table_key)) - del(self.table_data_cache[table_key]) - - def start(self): - self.subscribe_all() - self.config_db.listen() - def stop(self): - self.config_db.stop_listen() - if self.config_db.sub_thread.is_alive(): - self.config_db.sub_thread.join() - -main_loop = True - -def sig_handler(signum, frame): - global main_loop - syslog.syslog(syslog.LOG_DEBUG, 'entering signal handler') - main_loop = False - -def main(): - global vrrpd_client - for sig_num in [signal.SIGTERM, signal.SIGINT]: - signal.signal(sig_num, sig_handler) - syslog.syslog(syslog.LOG_DEBUG, 'entering VRRP configuration daemon') - vrrpd_client = VrrpdClientMgr() - vrrpd_client.start() - daemon = VRRPConfigDaemon() - daemon.start() - while main_loop: - signal.pause() - syslog.syslog(syslog.LOG_DEBUG, 'leaving VRRP configuration daemon') - vrrpd_client.shutdown() - daemon.stop() - -if __name__ == "__main__": - main()