From a7ac0fd5ceb993a8fbb256a3fd91d0d6c6531887 Mon Sep 17 00:00:00 2001
From: Gaige B Paulsen <gaige@cluetrust.net>
Date: Sat, 9 Nov 2024 21:43:44 -0500
Subject: T6815:Fix compatibility with 1.3-1.5 of VyOS (Step 1) (#352)

* omnibus update for 1.3-1.4 (with some support for 1.5)

(see contents in release fragments)
---------

Co-authored-by: Om Nom <omnom62@outlook.com>
---
 plugins/cliconf/vyos.py                            |   5 +
 .../argspec/firewall_global/firewall_global.py     |   3 +
 .../vyos/argspec/firewall_rules/firewall_rules.py  | 107 +++-
 .../vyos/config/firewall_global/firewall_global.py |  80 ++-
 .../vyos/config/firewall_rules/firewall_rules.py   | 443 ++++++++++----
 .../network/vyos/config/interfaces/interfaces.py   |   2 +-
 .../vyos/config/ospf_interfaces/ospf_interfaces.py |  28 +-
 .../vyos/facts/firewall_global/firewall_global.py  |  18 +-
 .../vyos/facts/firewall_rules/firewall_rules.py    | 180 +++++-
 .../network/vyos/facts/interfaces/interfaces.py    |  20 +-
 .../vyos/facts/l3_interfaces/l3_interfaces.py      |   2 +-
 .../vyos/facts/ospf_interfaces/ospf_interfaces.py  |  51 +-
 .../vyos/rm_templates/ospf_interfaces_14.py        | 650 +++++++++++++++++++++
 plugins/module_utils/network/vyos/utils/version.py |  13 +
 plugins/module_utils/network/vyos/vyos.py          |   8 +-
 plugins/modules/vyos_firewall_global.py            |  13 +
 plugins/modules/vyos_firewall_rules.py             | 180 +++++-
 plugins/modules/vyos_ospf_interfaces.py            |   2 +-
 18 files changed, 1592 insertions(+), 213 deletions(-)
 create mode 100644 plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py
 create mode 100644 plugins/module_utils/network/vyos/utils/version.py

(limited to 'plugins')

diff --git a/plugins/cliconf/vyos.py b/plugins/cliconf/vyos.py
index 7e6b0b17..5beffaa1 100644
--- a/plugins/cliconf/vyos.py
+++ b/plugins/cliconf/vyos.py
@@ -80,6 +80,11 @@ class Cliconf(CliconfBase):
             if match:
                 device_info["network_os_version"] = match.group(1)
 
+            if device_info["network_os_version"]:
+                match = re.search(r"VyOS\s*(\d+\.\d+)", device_info["network_os_version"])
+                if match:
+                    device_info["network_os_major_version"] = match.group(1)
+
             match = re.search(r"(?:HW|Hardware) model:\s*(\S+)", data)
             if match:
                 device_info["network_os_model"] = match.group(1)
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
index 2326bea1..f79454ed 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
@@ -134,6 +134,9 @@ class Firewall_globalArgs(object):  # pylint: disable=R0903
                             "type": "str",
                         },
                         "log": {"type": "bool"},
+                        "log_level": {
+                            "choices": ["emerg", "alert", "crit", "err", "warn", "notice", "info", "debug"]
+                        }
                     },
                     "type": "list",
                 },
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
index eb285cfd..4d0973e3 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
@@ -50,11 +50,16 @@ class Firewall_rulesArgs(object):  # pylint: disable=R0903
                     "elements": "dict",
                     "options": {
                         "default_action": {
-                            "choices": ["drop", "reject", "accept"],
+                            "choices": ["drop", "reject", "accept", "jump"],
                             "type": "str",
                         },
+                        "default_jump_target": {"type": "str"},
                         "description": {"type": "str"},
                         "enable_default_log": {"type": "bool"},
+                        "filter": {
+                            "choices": ["input", "output", "forward"],
+                            "type": "str"
+                        },
                         "name": {"type": "str"},
                         "rules": {
                             "elements": "dict",
@@ -65,6 +70,11 @@ class Firewall_rulesArgs(object):  # pylint: disable=R0903
                                         "reject",
                                         "accept",
                                         "inspect",
+                                        "continue",
+                                        "return",
+                                        "jump",
+                                        "queue",
+                                        "synproxy",
                                     ],
                                     "type": "str",
                                 },
@@ -147,9 +157,23 @@ class Firewall_rulesArgs(object):  # pylint: disable=R0903
                                     },
                                     "type": "dict",
                                 },
+                                "inbound_interface": {
+                                    "options": {
+                                        "group": {
+                                            "type": "str",
+                                        },
+                                        "name": {
+                                            "type": "str",
+                                        },
+                                    },
+                                    "type": "dict",
+                                },
                                 "ipsec": {
-                                    "choices": ["match-ipsec", "match-none"],
-                                    "type": "str",
+                                    "choices": ["match-ipsec", "match-none", "match-ipsec-in", "match-ipsec-out", "match-none-in", "match-none-out"],
+                                    "type": "str"
+                                },
+                                "jump_target": {
+                                    "type": "str"
                                 },
                                 "limit": {
                                     "options": {
@@ -169,6 +193,17 @@ class Firewall_rulesArgs(object):  # pylint: disable=R0903
                                     "choices": ["enable", "disable"],
                                 },
                                 "number": {"required": True, "type": "int"},
+                                "outbound_interface": {
+                                    "options": {
+                                        "group": {
+                                            "type": "str",
+                                        },
+                                        "name": {
+                                            "type": "str",
+                                        },
+                                    },
+                                    "type": "dict",
+                                },
                                 "p2p": {
                                     "elements": "dict",
                                     "options": {
@@ -185,19 +220,52 @@ class Firewall_rulesArgs(object):  # pylint: disable=R0903
                                             "type": "str",
                                         },
                                     },
+                                    "type": "list"
+                                },
+                                "packet_length": {
+                                    "elements": "dict",
+                                    "options": {
+                                        "length": {
+                                            "type": "str",
+                                        },
+                                    },
+                                    "type": "list"
+                                },
+                                "packet_length_exclude": {
+                                    "elements": "dict",
+                                    "options": {
+                                        "length": {
+                                            "type": "str",
+                                        }
+                                    },
                                     "type": "list",
                                 },
+                                "packet_type": {
+                                    "choices": [
+                                        "broadcast",
+                                        "multicast",
+                                        "host",
+                                        "other"
+                                    ],
+                                    "type": "str"
+                                },
                                 "protocol": {"type": "str"},
+                                "queue": {"type": "str"},
+                                "queue_options": {
+                                    "choices": ["bypass", "fanout"],
+                                    "type": "str"
+                                },
                                 "recent": {
                                     "options": {
                                         "count": {"type": "int"},
-                                        "time": {"type": "int"},
+                                        "time": {"type": "str"},
                                     },
                                     "type": "dict",
                                 },
                                 "source": {
                                     "options": {
                                         "address": {"type": "str"},
+                                        "fqdn": {"type": "str"},
                                         "group": {
                                             "options": {
                                                 "address_group": {"type": "str"},
@@ -220,8 +288,37 @@ class Firewall_rulesArgs(object):  # pylint: disable=R0903
                                     },
                                     "type": "dict",
                                 },
+                                "synproxy": {
+                                    "options": {
+                                        "mss": {"type": "int"},
+                                        "window_scale": {"type": "int"},
+                                    },
+                                    "type": "dict",
+                                },
                                 "tcp": {
-                                    "options": {"flags": {"type": "str"}},
+                                    "options": {
+                                        "flags": {
+                                            "elements": "dict",
+                                            "options": {
+                                                "flag": {
+                                                    "choices": [
+                                                        "ack",
+                                                        "cwr",
+                                                        "ecn",
+                                                        "fin",
+                                                        "psh",
+                                                        "rst",
+                                                        "syn",
+                                                        "urg",
+                                                        "all",
+                                                    ],
+                                                    "type": "str"
+                                                },
+                                                "invert": {"type": "bool"}
+                                            },
+                                            "type": "list"
+                                        }
+                                    },
                                     "type": "dict",
                                 },
                                 "time": {
diff --git a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
index 8694f11b..7e978ff9 100644
--- a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
@@ -31,6 +31,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils
     list_diff_want_only,
 )
 
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
 
 class Firewall_global(ConfigBase):
     """
@@ -255,7 +259,7 @@ class Firewall_global(ConfigBase):
                         continue
                     if (
                         key in l_set
-                        and not (h and self._in_target(h, key))
+                        and not self._in_target(h, key)
                         and not self._is_del(l_set, h)
                     ):
                         commands.append(
@@ -455,7 +459,10 @@ class Firewall_global(ConfigBase):
         """
         commands = []
         have = []
-        l_set = ("log", "action", "connection_type")
+        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            l_set = ("log", "action", "connection_type", "log_level")
+        else:
+            l_set = ("log", "action", "connection_type")
         if not opr and self._is_root_del(h, w, attr):
             commands.append(self._form_attr_cmd(attr=attr, opr=opr))
         else:
@@ -478,25 +485,23 @@ class Firewall_global(ConfigBase):
                                     ),
                                 )
                             elif not opr and key in l_set:
-                                if not (h and self._in_target(h, key)) and not self._is_del(
-                                    l_set,
-                                    h,
-                                ):
-                                    if key == "action":
-                                        commands.append(
-                                            self._form_attr_cmd(
-                                                attr=attr + " " + w["connection_type"],
-                                                opr=opr,
-                                            ),
-                                        )
-                                    else:
-                                        commands.append(
-                                            self._form_attr_cmd(
-                                                attr=attr + " " + w["connection_type"],
-                                                val=self._bool_to_str(val),
-                                                opr=opr,
-                                            ),
-                                        )
+                                if not h:
+                                    commands.append(
+                                        self._form_attr_cmd(
+                                            attr=attr + " " + w["connection_type"],
+                                            opr=opr,
+                                        ),
+                                    )
+                                    break  # delete the whole thing and move on
+                                if (not self._in_target(h, key) or h[key] is None) and (self._in_target(w, key) and w[key]):
+                                    # delete if not being replaced and value currently exists
+                                    commands.append(
+                                        self._form_attr_cmd(
+                                            attr=attr + " " + w["connection_type"] + " " + key,
+                                            val=self._bool_to_str(val),
+                                            opr=opr,
+                                        ),
+                                    )
         return commands
 
     def _render_route_redirects(self, attr, w, h, opr):
@@ -520,6 +525,14 @@ class Firewall_global(ConfigBase):
         if want:
             for w in want:
                 h = self.search_attrib_in_have(have, w, "afi")
+                if 'afi' in w:
+                    afi = w['afi']
+                else:
+                    if h and 'afi' in h:
+                        afi = h['afi']
+                    else:
+                        afi = None
+                    afi = None
                 for key, val in iteritems(w):
                     if val and key != "afi":
                         if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
@@ -528,6 +541,7 @@ class Firewall_global(ConfigBase):
                                     attr=key,
                                     val=self._bool_to_str(val),
                                     opr=opr,
+                                    type=afi
                                 ),
                             )
                         elif not opr and key in l_set:
@@ -537,6 +551,7 @@ class Firewall_global(ConfigBase):
                                         attr=key,
                                         val=self._bool_to_str(val),
                                         opr=opr,
+                                        type=afi
                                     ),
                                 )
                                 continue
@@ -546,6 +561,7 @@ class Firewall_global(ConfigBase):
                                         attr=key,
                                         val=self._bool_to_str(val),
                                         opr=opr,
+                                        type=afi
                                     ),
                                 )
                         elif key == "icmp_redirects":
@@ -565,20 +581,27 @@ class Firewall_global(ConfigBase):
         commands = []
         h_red = {}
         l_set = ("send", "receive")
+        if w and 'afi' in w:
+            afi = w['afi']
+        else:
+            if h and 'afi' in h:
+                afi = h['afi']
+            else:
+                afi = None
         if w[attr]:
             if h and attr in h.keys():
                 h_red = h.get(attr) or {}
             for item, value in iteritems(w[attr]):
                 if opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)):
                     commands.append(
-                        self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr),
+                        self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr, type=afi)
                     )
                 elif (
                     not opr
                     and item in l_set
                     and not (h_red and self._is_w_same(w[attr], h_red, item))
                 ):
-                    commands.append(self._form_attr_cmd(attr=item, opr=opr))
+                    commands.append(self._form_attr_cmd(attr=item, opr=opr, type=afi))
         return commands
 
     def search_attrib_in_have(self, have, want, attr):
@@ -595,16 +618,17 @@ class Firewall_global(ConfigBase):
                     return h
         return None
 
-    def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True):
+    def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True, type=None):
         """
         This function forms the command for leaf attribute.
         :param key: parent key.
         :param attr: attribute name
         :param value: value
         :param opr: True/False.
+        :param type: AF type of attribute.
         :return: generated command.
         """
-        command = self._compute_command(key=key, attr=self._map_attrib(attr), val=val, opr=opr)
+        command = self._compute_command(key=key, attr=self._map_attrib(attr, type=type), val=val, opr=opr)
         return command
 
     def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True):
@@ -621,13 +645,15 @@ class Firewall_global(ConfigBase):
             cmd = "delete firewall "
         else:
             cmd = "set firewall "
+        if key != "group" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            cmd += "global-options "
         if key:
             cmd += key.replace("_", "-") + " "
         if attr:
             cmd += attr.replace("_", "-")
         if val and opr:
             cmd += " '" + str(val) + "'"
-        return cmd
+        return cmd.strip()
 
     def _bool_to_str(self, val):
         """
@@ -698,7 +724,7 @@ class Firewall_global(ConfigBase):
         :param key: number.
         :return: True/False.
         """
-        return key in b_set and not (h and self._in_target(h, key))
+        return key in b_set and not self._in_target(h, key)
 
     def _map_attrib(self, attrib, type=None):
         """
diff --git a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
index 09e19d70..106b2b8b 100644
--- a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
@@ -15,8 +15,6 @@ from __future__ import absolute_import, division, print_function
 
 __metaclass__ = type
 
-import re
-
 from copy import deepcopy
 
 from ansible.module_utils.six import iteritems
@@ -33,6 +31,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils
     list_diff_want_only,
 )
 
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
 
 class Firewall_rules(ConfigBase):
     """
@@ -171,12 +173,8 @@ class Firewall_rules(ConfigBase):
                     # In the desired configuration, search for the rule set we
                     # already have (to be replaced by our desired
                     # configuration's rule set).
-                    wanted_rule_set = self.search_r_sets_in_have(
-                        want,
-                        rs["name"],
-                        "r_list",
-                        h["afi"],
-                    )
+                    rs_id = self._rs_id(rs, h["afi"])
+                    wanted_rule_set = self.search_r_sets_in_have(want, rs_id, "r_list")
                     if wanted_rule_set is not None:
                         # Remove the rules that we already have if the wanted
                         # rules exist under the same name.
@@ -202,11 +200,12 @@ class Firewall_rules(ConfigBase):
         commands = []
         if have:
             for h in have:
-                r_sets = self._get_r_sets(h)
-                for rs in r_sets:
-                    w = self.search_r_sets_in_have(want, rs["name"], "r_list", h["afi"])
+                have_r_sets = self._get_r_sets(h)
+                for rs in have_r_sets:
+                    rs_id = self._rs_id(rs, h["afi"])
+                    w = self.search_r_sets_in_have(want, rs_id, "r_list")
                     if not w:
-                        commands.append(self._compute_command(h["afi"], rs["name"], remove=True))
+                        commands.append(self._compute_command(rs_id, remove=True))
                     else:
                         commands.extend(self._add_r_sets(h["afi"], rs, w, opr=False))
         commands.extend(self._state_merged(want, have))
@@ -223,7 +222,8 @@ class Firewall_rules(ConfigBase):
         for w in want:
             r_sets = self._get_r_sets(w)
             for rs in r_sets:
-                h = self.search_r_sets_in_have(have, rs["name"], "r_list", w["afi"])
+                rs_id = self._rs_id(rs, w["afi"])
+                h = self.search_r_sets_in_have(have, rs_id, "r_list")
                 commands.extend(self._add_r_sets(w["afi"], rs, h))
         return commands
 
@@ -240,18 +240,21 @@ class Firewall_rules(ConfigBase):
                 r_sets = self._get_r_sets(w)
                 if r_sets:
                     for rs in r_sets:
-                        h = self.search_r_sets_in_have(have, rs["name"], "r_list", w["afi"])
+                        rs_id = self._rs_id(rs, w["afi"])
+                        h = self.search_r_sets_in_have(have, rs_id, "r_list")
                         if h:
-                            commands.append(self._compute_command(w["afi"], h["name"], remove=True))
+                            commands.append(self._compute_command(rs_id, remove=True))
                 elif have:
                     for h in have:
                         if h["afi"] == w["afi"]:
-                            commands.append(self._compute_command(w["afi"], remove=True))
+                            commands.append(
+                                self._compute_command(self._rs_id(None, w["afi"]), remove=True)
+                            )
         elif have:
             for h in have:
                 r_sets = self._get_r_sets(h)
                 if r_sets:
-                    commands.append(self._compute_command(afi=h["afi"], remove=True))
+                    commands.append(self._compute_command(self._rs_id(None, h["afi"]), remove=True))
         return commands
 
     def _add_r_sets(self, afi, want, have, opr=True):
@@ -265,11 +268,12 @@ class Firewall_rules(ConfigBase):
         :return: generated commands list.
         """
         commands = []
-        l_set = ("description", "default_action", "enable_default_log")
+        l_set = ("description", "default_action", "default_jump_target", "enable_default_log")
         h_rs = {}
         h_rules = {}
         w_rs = deepcopy(remove_empties(want))
         w_rules = w_rs.pop("rules", None)
+        rs_id = self._rs_id(want, afi=afi)
         if have:
             h_rs = deepcopy(remove_empties(have))
             h_rules = h_rs.pop("rules", None)
@@ -278,9 +282,9 @@ class Firewall_rules(ConfigBase):
                 if opr and key in l_set and not (h_rs and self._is_w_same(w_rs, h_rs, key)):
                     if key == "enable_default_log":
                         if val and (not h_rs or key not in h_rs or not h_rs[key]):
-                            commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs))
+                            commands.append(self._add_rs_base_attrib(rs_id, key, w_rs))
                     else:
-                        commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs))
+                        commands.append(self._add_rs_base_attrib(rs_id, key, w_rs))
                 elif not opr and key in l_set:
                     if (
                         key == "enable_default_log"
@@ -288,22 +292,24 @@ class Firewall_rules(ConfigBase):
                         and h_rs
                         and (key not in h_rs or not h_rs[key])
                     ):
-                        commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs, opr))
+                        commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr))
                     elif not (h_rs and self._in_target(h_rs, key)):
-                        commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs, opr))
-            commands.extend(self._add_rules(afi, want["name"], w_rules, h_rules, opr))
+                        commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr))
+            commands.extend(self._add_rules(rs_id, w_rules, h_rules, opr))
         if h_rules:
             have["rules"] = h_rules
         if w_rules:
             want["rules"] = w_rules
         return commands
 
-    def _add_rules(self, afi, name, w_rules, h_rules, opr=True):
+    def _add_rules(self, rs_id, w_rules, h_rules, opr=True):
         """
         This function forms the set/delete commands based on the 'opr' type
         for rules attributes.
-        :param want: desired config.
-        :param have: target config.
+        :param rs_id: rule-set identifier.
+        :param w_rules: desired config.
+        :param h_rules: target config.
+        :param opr: True/False.
         :return: generated commands list.
         """
         commands = []
@@ -316,31 +322,70 @@ class Firewall_rules(ConfigBase):
             "disable",
             "description",
             "log",
+            "jump_target",
         )
         if w_rules:
             for w in w_rules:
-                cmd = self._compute_command(afi, name, w["number"], opr=opr)
-                h = self.search_r_sets_in_have(h_rules, w["number"], type="rules")
+                cmd = self._compute_command(rs_id, w["number"], opr=opr)
+                h = self.search_rules_in_have_rs(h_rules, w["number"])
                 for key, val in iteritems(w):
                     if val:
                         if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
                             if key == "disable":
                                 if not (not val and (not h or key not in h or not h[key])):
-                                    commands.append(self._add_r_base_attrib(afi, name, key, w))
+                                    commands.append(self._add_r_base_attrib(rs_id, key, w))
                             else:
-                                commands.append(self._add_r_base_attrib(afi, name, key, w))
+                                commands.append(self._add_r_base_attrib(rs_id, key, w))
                         elif not opr:
+                            # Note: if you are experiencing sticky configuration on replace
+                            # you may need to add an explicit check for the key here. Anything that
+                            # doesn't have a custom operation is taken care of by the `l_set` check
+                            # below, but I'm not sure how any of the others work.
+                            # It's possible that historically the delete was forced (but now it's
+                            # checked).
                             if key == "number" and self._is_del(l_set, h):
-                                commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr))
+                                commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
                                 continue
-                            if key == "disable" and val and h and (key not in h or not h[key]):
-                                commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr))
+                            if (
+                                key == "tcp"
+                                and val
+                                and h
+                                and (key not in h or not h[key] or h[key] != w[key])
+                            ):
+                                commands.extend(self._add_tcp(key, w, h, cmd, opr))
+                            if (
+                                key == "state"
+                                and val
+                                and h
+                                and (key not in h or not h[key] or h[key] != w[key])
+                            ):
+                                commands.extend(self._add_state(key, w, h, cmd, opr))
+                            if (
+                                key == "icmp"
+                                and val
+                                and h
+                                and (key not in h or not h[key] or h[key] != w[key])
+                            ):
+                                commands.extend(self._add_icmp(key, w, h, cmd, opr))
+                            if (
+                                key in ("packet_length", "packet_length_exclude")
+                                and val
+                                and h
+                                and (key not in h or not h[key] or h[key] != w[key])
+                            ):
+                                commands.extend(self._add_packet_length(key, w, h, cmd, opr))
+                            elif key == "disable" and val and h and (key not in h or not h[key]):
+                                commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
+                            elif key in ("inbound_interface", "outbound_interface") and not (
+                                h and self._is_w_same(w, h, key)
+                            ):
+                                commands.extend(self._add_interface(key, w, h, cmd, opr))
                             elif (
                                 key in l_set
                                 and not (h and self._in_target(h, key))
                                 and not self._is_del(l_set, h)
                             ):
-                                commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr))
+                                commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
                         elif key == "p2p":
                             commands.extend(self._add_p2p(key, w, h, cmd, opr))
                         elif key == "tcp":
@@ -357,6 +402,10 @@ class Firewall_rules(ConfigBase):
                             commands.extend(self._add_recent(key, w, h, cmd, opr))
                         elif key == "destination" or key == "source":
                             commands.extend(self._add_src_or_dest(key, w, h, cmd, opr))
+                        elif key in ("packet_length", "packet_length_exclude"):
+                            commands.extend(self._add_packet_length(key, w, h, cmd, opr))
+                        elif key in ("inbound_interface", "outbound_interface"):
+                            commands.extend(self._add_interface(key, w, h, cmd, opr))
         return commands
 
     def _add_p2p(self, attr, w, h, cmd, opr):
@@ -405,8 +454,11 @@ class Firewall_rules(ConfigBase):
                     and item in l_set
                     and not (h_state and self._is_w_same(w[attr], h_state, item))
                 ):
-                    commands.append(cmd + (" " + attr + " " + item + " " + self._bool_to_str(val)))
-                elif not opr and item in l_set and not (h_state and self._in_target(h_state, item)):
+                    if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+                        commands.append(cmd + (" " + attr + " " + item))
+                    else:
+                        commands.append(cmd + (" " + attr + " " + item + " " + self._bool_to_str(val)))
+                elif not opr and item in l_set and not self._in_target(h_state, item):
                     commands.append(cmd + (" " + attr + " " + item))
         return commands
 
@@ -460,26 +512,50 @@ class Firewall_rules(ConfigBase):
                     and not (h_icmp and self._is_w_same(w[attr], h_icmp, item))
                 ):
                     if item == "type_name":
-                        os_version = self._get_os_version()
-                        ver = re.search(
-                            "vyos ([\\d\\.]+)-?.*",  # noqa: W605
-                            os_version,
-                            re.IGNORECASE,
-                        )
-                        if ver.group(1) >= "1.4":
+                        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.3"):
                             param_name = "type-name"
                         else:
                             param_name = "type"
-                        if "ipv6-name" in cmd:
+                        if "ipv6" in cmd:  # ipv6-name or ipv6
                             commands.append(cmd + (" " + "icmpv6" + " " + param_name + " " + val))
                         else:
                             commands.append(
                                 cmd + (" " + attr + " " + item.replace("_", "-") + " " + val),
                             )
                     else:
-                        commands.append(cmd + (" " + attr + " " + item + " " + str(val)))
-                elif not opr and item in l_set and not (h_icmp and self._in_target(h_icmp, item)):
-                    commands.append(cmd + (" " + attr + " " + item))
+                        if "ipv6" in cmd:  # ipv6-name or ipv6
+                            commands.append(cmd + (" " + "icmpv6" + " " + item + " " + str(val)))
+                        else:
+                            commands.append(cmd + (" " + attr + " " + item + " " + str(val)))
+                elif not opr and item in l_set and not self._in_target(h_icmp, item):
+                    commands.append(cmd + (" " + attr + " " + item.replace("_", "-") + " " + str(val)))
+        return commands
+
+    def _add_interface(self, attr, w, h, cmd, opr):
+        """
+        This function forms the commands for 'interface' attributes based on the 'opr'.
+        :param attr: attribute name.
+        :param w: base config.
+        :param h: target config.
+        :param cmd: commands to be prepend.
+        :return: generated list of commands.
+        """
+        commands = []
+        h_if = {}
+        l_set = ("name", "group")
+        if w[attr]:
+            if h and attr in h.keys():
+                h_if = h.get(attr) or {}
+            for item, val in iteritems(w[attr]):
+                if opr and item in l_set and not (h_if and self._is_w_same(w[attr], h_if, item)):
+                    commands.append(
+                        cmd
+                        + (" " + attr.replace("_", "-") + " " + item.replace("_", "-") + " " + val)
+                    )
+                elif not opr and item in l_set and not (h_if and self._in_target(h_if, item)):
+                    commands.append(
+                        cmd + (" " + attr.replace("_", "-") + " " + item.replace("_", "-"))
+                    )
         return commands
 
     def _add_time(self, attr, w, h, cmd, opr):
@@ -524,15 +600,107 @@ class Firewall_rules(ConfigBase):
                     commands.append(cmd + (" " + attr + " " + item))
         return commands
 
+    def _add_tcp_1_4(self, attr, w, h, cmd, opr):
+        """
+        This function forms the commands for 'tcp' attributes based on the 'opr'.
+        Version 1.4+
+        :param attr: attribute name.
+        :param w: base config.
+        :param h: target config.
+        :param cmd: commands to be prepend.
+        :return: generated list of commands.
+        """
+        commands = []
+        have = []
+        key = "flags"
+        want = []
+
+        if w:
+            if w.get(attr):
+                want = w.get(attr).get(key) or []
+        if h:
+            if h.get(attr):
+                have = h.get(attr).get(key) or []
+        if want:
+            if opr:
+                flags = list_diff_want_only(want, have)
+                for flag in flags:
+                    invert = flag.get("invert", False)
+                    commands.append(
+                        cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"])
+                    )
+            elif not opr:
+                flags = list_diff_want_only(want, have)
+                for flag in flags:
+                    invert = flag.get("invert", False)
+                    commands.append(
+                        cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"])
+                    )
+        return commands
+
+    def _add_packet_length(self, attr, w, h, cmd, opr):
+        """
+        This function forms the commands for 'packet_length[_exclude]' attributes based on the 'opr'.
+        If < 1.4, handle tcp attributes.
+        :param attr: attribute name.
+        :param w: base config.
+        :param h: target config.
+        :param cmd: commands to be prepend.
+        :return: generated list of commands.
+        """
+        commands = []
+        have = []
+        want = []
+
+        if w:
+            if w.get(attr):
+                want = w.get(attr) or []
+        if h:
+            if h.get(attr):
+                have = h.get(attr) or []
+        attr = attr.replace("_", "-")
+        if want:
+            if opr:
+                lengths = list_diff_want_only(want, have)
+                for l_rec in lengths:
+                    commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
+            elif not opr:
+                lengths = list_diff_want_only(want, have)
+                for l_rec in lengths:
+                    commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
+        return commands
+
+    def _tcp_flags_string(self, flags):
+        """
+        This function forms the tcp flags string.
+        :param flags: flags list.
+        :return: flags string or None.
+        """
+        if not flags:
+            return ""
+        flag_str = ""
+        for flag in flags:
+            this_flag = flag["flag"].upper()
+            if flag.get("invert", False):
+                this_flag = "!" + this_flag
+            if len(flag_str) > 0:
+                flag_str = ",".join([flag_str, this_flag])
+            else:
+                flag_str = this_flag
+        return flag_str
+
     def _add_tcp(self, attr, w, h, cmd, opr):
         """
         This function forms the commands for 'tcp' attributes based on the 'opr'.
+        If < 1.4, handle tcp attributes.
         :param attr: attribute name.
         :param w: base config.
         :param h: target config.
         :param cmd: commands to be prepend.
         :return: generated list of commands.
         """
+        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            return self._add_tcp_1_4(attr, w, h, cmd, opr)
         h_tcp = {}
         commands = []
         if w[attr]:
@@ -542,10 +710,11 @@ class Firewall_rules(ConfigBase):
                 if h and key in h[attr].keys():
                     h_tcp = h[attr].get(key) or {}
                 if flags:
-                    if opr and not (h_tcp and self._is_w_same(w[attr], h[attr], key)):
-                        commands.append(cmd + (" " + attr + " " + key + " " + flags))
-                    if not opr and not (h_tcp and self._is_w_same(w[attr], h[attr], key)):
-                        commands.append(cmd + (" " + attr + " " + key + " " + flags))
+                    flag_str = self._tcp_flags_string(flags)
+                    if opr and not (h_tcp and flags == h_tcp):
+                        commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
+                    if not opr and not (h_tcp and flags == h_tcp):
+                        commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
         return commands
 
     def _add_limit(self, attr, w, h, cmd, opr):
@@ -671,43 +840,68 @@ class Firewall_rules(ConfigBase):
                             )
         return commands
 
-    def search_r_sets_in_have(self, have, w_name, type="rule_sets", afi=None):
+    def search_rules_in_have_rs(self, have_rules, r_number):
+        """
+        This function returns the rule if it is present in target config.
+        :param have: target config.
+        :param rs_id: rule-set identifier.
+        :param r_number: rule-number.
+        :return: rule.
+        """
+        if have_rules:
+            for h in have_rules:
+                key = "number"
+                for r in have_rules:
+                    if key in r and r[key] == r_number:
+                        return r
+        return None
+
+    def search_r_sets_in_have(self, have, rs_id, type="rule_sets"):
         """
         This function  returns the rule-set/rule if it is present in target config.
         :param have: target config.
-        :param w_name: rule-set name.
-        :param type: rule_sets/rule/r_list.
-        :param afi: address family (when type is r_list).
+        :param rs_id: rule-identifier.
+        :param type: rule_sets if searching a rule_set and r_list if searching from a rule_list.
         :return: rule-set/rule.
         """
-        if have:
+        if "afi" in rs_id:
+            afi = rs_id["afi"]
+        else:
+            afi = None
+        if rs_id["filter"]:
+            key = "filter"
+            w_value = rs_id["filter"]
+        elif rs_id["name"]:
             key = "name"
-            if type == "rules":
-                key = "number"
-                for r in have:
-                    if r[key] == w_name:
-                        return r
-            elif type == "r_list":
+            w_value = rs_id["name"]
+        else:
+            raise ValueError("id must be specific to name or filter")
+
+        if type not in ("r_list", "rule_sets"):
+            raise ValueError("type must be rule_sets or r_list")
+        if have:
+            if type == "r_list":
                 for h in have:
                     if h["afi"] == afi:
                         r_sets = self._get_r_sets(h)
                         for rs in r_sets:
-                            if rs[key] == w_name:
+                            if key in rs and rs[key] == w_value:
                                 return rs
             else:
+                # searching a ruleset
                 for rs in have:
-                    if rs[key] == w_name:
+                    if key in rs and rs[key] == w_value:
                         return rs
         return None
 
-    def _get_r_sets(self, item, type="rule_sets"):
+    def _get_r_sets(self, item):
         """
-        This function returns the list of rule-sets/rules.
+        This function returns the list of rule-sets.
         :param item: config dictionary.
-        :param type: rule_sets/rule/r_list.
         :return: list of rule-sets/rules.
         """
         rs_list = []
+        type = "rule_sets"
         r_sets = item[type]
         if r_sets:
             for rs in r_sets:
@@ -716,8 +910,7 @@ class Firewall_rules(ConfigBase):
 
     def _compute_command(
         self,
-        afi,
-        name=None,
+        rs_id,
         number=None,
         attrib=None,
         value=None,
@@ -726,46 +919,53 @@ class Firewall_rules(ConfigBase):
     ):
         """
         This function construct the add/delete command based on passed attributes.
-        :param afi:  address type.
-        :param name:  rule-set name.
+        :param rs_id: rule-set identifier.
         :param number: rule-number.
         :param attrib: attribute name.
         :param value: value.
         :param remove: True if delete command needed to be construct.
-        :param opr: opeeration flag.
+        :param opr: operation flag.
         :return: generated command.
         """
+        if rs_id["name"] and rs_id["filter"]:
+            raise ValueError("name and filter cannot be used together")
         if remove or not opr:
-            cmd = "delete firewall " + self._get_fw_type(afi)
+            cmd = "delete firewall " + self._get_fw_type(rs_id["afi"])
         else:
-            cmd = "set firewall " + self._get_fw_type(afi)
-        if name:
-            cmd += " " + name
+            cmd = "set firewall " + self._get_fw_type(rs_id["afi"])
+        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            if rs_id["name"]:
+                cmd += " name " + rs_id["name"]
+            elif rs_id["filter"]:
+                cmd += " " + rs_id["filter"] + " filter"
+        elif rs_id["name"]:
+            cmd += " " + rs_id["name"]
         if number:
             cmd += " rule " + str(number)
         if attrib:
-            cmd += " " + attrib.replace("_", "-")
+            if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4") and attrib == "enable_default_log":
+                cmd += " " + "default-log"
+            else:
+                cmd += " " + attrib.replace("_", "-")
         if value and opr and attrib != "enable_default_log" and attrib != "disable":
             cmd += " '" + str(value) + "'"
         return cmd
 
-    def _add_r_base_attrib(self, afi, name, attr, rule, opr=True):
+    def _add_r_base_attrib(self, rs_id, attr, rule, opr=True):
         """
         This function forms the command for 'rules' attributes which doesn't
         have further sub attributes.
-        :param afi: address type.
-        :param name: rule-set name
+        :param rs_id: rule-set identifier.
         :param attrib: attribute name
         :param rule: rule config dictionary.
         :param opr: True/False.
         :return: generated command.
         """
         if attr == "number":
-            command = self._compute_command(afi=afi, name=name, number=rule["number"], opr=opr)
+            command = self._compute_command(rs_id, number=rule["number"], opr=opr)
         else:
             command = self._compute_command(
-                afi=afi,
-                name=name,
+                rs_id=rs_id,
                 number=rule["number"],
                 attrib=attr,
                 value=rule[attr],
@@ -773,21 +973,54 @@ class Firewall_rules(ConfigBase):
             )
         return command
 
-    def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True):
+    def _rs_id(self, have, afi, name=None, filter=None):
         """
+        This function returns the rule-set identifier based on
+        the example rule, overriding the components as specified.
 
-        This function forms the command for 'rule-sets' attributes which doesn't
-        have further sub attributes.
+        :param have: example rule.
         :param afi: address type.
-        :param name: rule-set name
+        :param name: rule-set name.
+        :param filter: filter name.
+        :return: rule-set identifier.
+        """
+        identifier = {"name": None, "filter": None}
+        if afi:
+            identifier["afi"] = afi
+        else:
+            raise ValueError("afi must be provided")
+
+        if name:
+            identifier["name"] = name
+            return identifier
+        elif filter:
+            identifier["filter"] = filter
+            return identifier
+        if have:
+            if "name" in have and have["name"]:
+                identifier["name"] = have["name"]
+                return identifier
+            if "filter" in have and have["filter"]:
+                identifier["filter"] = have["filter"]
+                return identifier
+        # raise ValueError("name or filter must be provided or present in have")
+        # unless we want a wildcard
+        return identifier
+
+    def _add_rs_base_attrib(self, rs_id, attrib, rule, opr=True):
+        """
+
+        This function forms the command for 'rule-sets' attributes which don't
+        have further sub attributes.
+
+        :param rs_id: rule-set identifier.
         :param attrib: attribute name
         :param rule: rule config dictionary.
         :param opr: True/False.
         :return: generated command.
         """
         command = self._compute_command(
-            afi=afi,
-            name=name,
+            rs_id=rs_id,
             attrib=attrib,
             value=rule[attrib],
             opr=opr,
@@ -808,6 +1041,8 @@ class Firewall_rules(ConfigBase):
         :param afi: address type
         :return: rule-set type.
         """
+        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            return "ipv6" if afi == "ipv6" else "ipv4"
         return "ipv6-name" if afi == "ipv6" else "name"
 
     def _is_del(self, l_set, h, key="number"):
@@ -834,37 +1069,9 @@ class Firewall_rules(ConfigBase):
 
     def _in_target(self, h, key):
         """
-        This function checks whether the target nexist and key present in target config.
+        This function checks whether the target exists and key present in target config.
         :param h: target config.
         :param key: attribute name.
         :return: True/False.
         """
         return True if h and key in h else False
-
-    def _is_base_attrib(self, key):
-        """
-        This function checks whether key is present in predefined
-        based attribute set.
-        :param key:
-        :return: True/False.
-        """
-        r_set = (
-            "p2p",
-            "ipsec",
-            "log",
-            "action",
-            "fragment",
-            "protocol",
-            "disable",
-            "description",
-            "mac_address",
-            "default_action",
-            "enable_default_log",
-        )
-        return True if key in r_set else False
-
-    def _get_os_version(self):
-        os_version = "1.2"
-        if self._connection:
-            os_version = self._connection.get_device_info()["network_os_version"]
-        return os_version
diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
index 731014cd..62e4f922 100644
--- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
@@ -275,7 +275,7 @@ class Interfaces(ConfigBase):
             commands.append(
                 self._compute_commands(key=key, interface=want_copy["name"], remove=True),
             )
-        if have_copy["enabled"] is False:
+        if have_copy["enabled"] is False and not ('enabled' in want_copy and want_copy["enabled"] is False):
             commands.append(
                 self._compute_commands(key="enabled", value=True, interface=want_copy["name"]),
             )
diff --git a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
index a7652a6b..51b47494 100644
--- a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
@@ -27,9 +27,17 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.u
 
 from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
 from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import (
-    Ospf_interfacesTemplate,
+    Ospf_interfacesTemplate
 )
 
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import (
+    Ospf_interfacesTemplate14
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
 
 class Ospf_interfaces(ResourceModule):
     """
@@ -61,12 +69,30 @@ class Ospf_interfaces(ResourceModule):
             "passive",
         ]
 
+    def _validate_template(self):
+        version = get_os_version(self._module)
+        if LooseVersion(version) >= LooseVersion("1.4"):
+            self._tmplt = Ospf_interfacesTemplate14()
+        else:
+            self._tmplt = Ospf_interfacesTemplate()
+
+    def parse(self):
+        """ override parse to check template """
+        self._validate_template()
+        return super().parse()
+
+    def get_parser(self, name):
+        """get_parsers"""
+        self._validate_template()
+        return super().get_parser(name)
+
     def execute_module(self):
         """Execute the module
 
         :rtype: A dictionary
         :returns: The result from module execution
         """
+        self._validate_template()
         if self.state not in ["parsed", "gathered"]:
             self.generate_commands()
             self.run_commands()
diff --git a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
index 5b472222..3f4da3ea 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
@@ -174,9 +174,8 @@ class Firewall_globalFacts(object):
         :return: generated rule list configuration.
         """
         sp_lst = []
-        attrib = "state-policy"
-        policies = findall(r"^set firewall " + attrib + " (\\S+)", conf, M)
-
+        policies = findall(r"^set firewall (?:global-options )state-policy (\S+)", conf, M)
+        policies = list(set(policies))  # remove redundancies
         if policies:
             rules_lst = []
             for sp in set(policies):
@@ -197,7 +196,7 @@ class Firewall_globalFacts(object):
         :param attrib: connection type.
         :return: generated rule configuration dictionary.
         """
-        a_lst = ["action", "log"]
+        a_lst = ["action", "log", "log_level"]
         cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
         return cfg_dict
 
@@ -304,16 +303,15 @@ class Firewall_globalFacts(object):
                 regex = match + " " + regex
             if conf:
                 if self.is_bool(attrib):
-                    attr = self.map_regex(attrib, type)
-                    out = conf.find(attr.replace("_", "-"))
-                    dis = conf.find(attr.replace("_", "-") + " 'disable'")
-                    if out >= 1:
-                        if dis >= 1:
+                    # fancy regex to make sure we don't get a substring
+                    out = search(r"^.*" + regex + r"( 'disable')?(?=\s|$)", conf, M)
+                    if out:
+                        if out.group(1):
                             config[attrib] = False
                         else:
                             config[attrib] = True
                 else:
-                    out = search(r"^.*" + regex + " (.+)", conf, M)
+                    out = search(r"^.*" + regex + r" (.+)", conf, M)
                     if out:
                         val = out.group(1).strip("'")
                         if self.is_num(attrib):
diff --git a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
index ead038a5..1fc70255 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
@@ -14,8 +14,6 @@ from __future__ import absolute_import, division, print_function
 
 __metaclass__ = type
 
-import re
-
 from copy import deepcopy
 from re import M, findall, search
 
@@ -61,15 +59,31 @@ class Firewall_rulesFacts(object):
             data = self.get_device_data(connection)
         # split the config into instances of the resource
         objs = []
-        v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M)
-        v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M)
+        # check 1.4+ first
+        new_rules = True
+        v6_rules = findall(r"^set firewall ipv6 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M)
+        if not v6_rules:
+            v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M)
+            if v6_rules:
+                new_rules = False
+        v4_rules = findall(r"^set firewall ipv4 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M)
+        if not v4_rules:
+            v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M)
+            if v4_rules:
+                new_rules = False
         if v6_rules:
-            config = self.get_rules(data, v6_rules, type="ipv6")
+            if new_rules:
+                config = self.get_rules_post_1_4(data, v6_rules, type="ipv6")
+            else:
+                config = self.get_rules(data, v6_rules, type="ipv6")
             if config:
                 config = utils.remove_empties(config)
                 objs.append(config)
         if v4_rules:
-            config = self.get_rules(data, v4_rules, type="ipv4")
+            if new_rules:
+                config = self.get_rules_post_1_4(data, v4_rules, type="ipv4")
+            else:
+                config = self.get_rules(data, v4_rules, type="ipv4")
             if config:
                 config = utils.remove_empties(config)
                 objs.append(config)
@@ -113,6 +127,39 @@ class Firewall_rulesFacts(object):
             config = {"afi": "ipv6", "rule_sets": r_v6}
         return config
 
+    def get_rules_post_1_4(self, data, rules, type):
+        """
+        This function performs following:
+        - Form regex to fetch 'rule-sets' specific config from data.
+        - Form the rule-set list based on ip address.
+        Specifically for v1.4+ version.
+        :param data: configuration.
+        :param rules: list of rule-sets.
+        :param type: ip address type.
+        :return: generated rule-sets configuration.
+        """
+        r_v4 = []
+        r_v6 = []
+        for kind, name in set(rules):
+            rule_regex = r" %s %s %s .+$" % (type, kind, name.strip("'"))
+            cfg = findall(rule_regex, data, M)
+            fr = self.render_config(cfg, name.strip("'"))
+            if kind == "name":
+                fr["name"] = name.strip("'")
+            elif kind in ("forward", "input", "output"):
+                fr["filter"] = kind
+            else:
+                raise ValueError("Unknown rule kind: %s %s" % kind, name)
+            if type == "ipv6":
+                r_v6.append(fr)
+            else:
+                r_v4.append(fr)
+        if r_v4:
+            config = {"afi": "ipv4", "rule_sets": r_v4}
+        if r_v6:
+            config = {"afi": "ipv6", "rule_sets": r_v6}
+        return config
+
     def render_config(self, conf, match):
         """
         Render config as dictionary structure and delete keys
@@ -124,10 +171,12 @@ class Firewall_rulesFacts(object):
         :returns: The generated config
         """
         conf = "\n".join(filter(lambda x: x, conf))
-        a_lst = ["description", "default_action", "enable_default_log"]
+        a_lst = ["description", "default_action", "default_jump_target", "enable_default_log", "default_log"]
         config = self.parse_attr(conf, a_lst, match)
         if not config:
             config = {}
+        if 'default_log' in config:
+            config['enable_default_log'] = config.pop('default_log')
         config["rules"] = self.parse_rules_lst(conf)
         return config
 
@@ -169,11 +218,14 @@ class Firewall_rulesFacts(object):
             "disable",
             "description",
             "icmp",
+            "jump_target",
+            "queue",
+            "queue_options",
         ]
         rule = self.parse_attr(conf, a_lst)
         r_sub = {
             "p2p": self.parse_p2p(conf),
-            "tcp": self.parse_tcp(conf, "tcp"),
+            "tcp": self.parse_tcp(conf),
             "icmp": self.parse_icmp(conf, "icmp"),
             "time": self.parse_time(conf, "time"),
             "limit": self.parse_limit(conf, "limit"),
@@ -181,10 +233,42 @@ class Firewall_rulesFacts(object):
             "recent": self.parse_recent(conf, "recent"),
             "source": self.parse_src_or_dest(conf, "source"),
             "destination": self.parse_src_or_dest(conf, "destination"),
+            "inbound_interface": self.parse_interface(conf, "inbound-interface"),
+            "outbound_interface": self.parse_interface(conf, "outbound-interface"),
+            "packet_length": self.parse_packet_length(conf, "packet-length"),
+            "packet_length_exclude": self.parse_packet_length(conf, "packet-length-exclude"),
         }
         rule.update(r_sub)
         return rule
 
+    def parse_interface(self, conf, attrib):
+        """
+        This function triggers the parsing of 'interface' attributes.
+        :param conf: configuration.
+        :param attrib: 'interface'.
+        :return: generated config dictionary.
+        """
+        a_lst = ["name", "group"]
+        cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
+        return cfg_dict
+
+    def parse_packet_length(self, conf, attrib=None):
+        """
+        This function triggers the parsing of 'packet-length' attributes.
+        :param conf: configuration.
+        :param attrib: 'packet-length'.
+        :return: generated config dictionary.
+        """
+        lengths = []
+        rule_regex = r"%s (\d+)" % attrib
+        found_lengths = findall(rule_regex, conf, M)
+        if found_lengths:
+            lengths = []
+            for l in set(found_lengths):
+                obj = {"length": l.strip("'")}
+                lengths.append(obj)
+        return lengths
+
     def parse_p2p(self, conf):
         """
         This function forms the regex to fetch the 'p2p' with in
@@ -226,15 +310,41 @@ class Firewall_rulesFacts(object):
         cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
         return cfg_dict
 
-    def parse_tcp(self, conf, attrib=None):
+    def parse_tcp(self, conf):
         """
         This function triggers the parsing of 'tcp' attributes.
         :param conf: configuration.
         :param attrib: 'tcp'.
         :return: generated config dictionary.
         """
-        cfg_dict = self.parse_attr(conf, ["flags"], match=attrib)
-        return cfg_dict
+        f_lst = []
+        flags = findall(r"tcp flags (not )?(?:\'*)([\w!,]+)(?:\'*)", conf, M)
+        # for pre 1.4, this is a string including possible commas
+        # and ! as an inverter. For 1.4+ this is a single flag per
+        # command and 'not' as the inverter
+        if flags:
+            flag_lst = []
+            for n, f in set(flags):
+                f = f.strip("'").lower()
+                if "," in f:
+                    # pre 1.4 version with multiple flags
+                    fs = f.split(",")
+                    for f in fs:
+                        if "!" in f:
+                            obj = {"flag": f.strip("'!"), "invert": True}
+                        else:
+                            obj = {"flag": f.strip("'")}
+                        flag_lst.append(obj)
+                elif "!" in f:
+                    obj = {"flag": f.strip("'!"), "invert": True}
+                    flag_lst.append(obj)
+                else:
+                    obj = {"flag": f.strip("'")}
+                    if n:
+                        obj["invert"] = True
+                    flag_lst.append(obj)
+            f_lst = sorted(flag_lst, key=lambda i: i["flag"])
+        return {"flags": f_lst}
 
     def parse_time(self, conf, attrib=None):
         """
@@ -276,6 +386,44 @@ class Firewall_rulesFacts(object):
         cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
         return cfg_dict
 
+    def parse_icmp_attr(self, conf, match):
+        """
+        This function peforms the following:
+        - parse ICMP arguemnts for firewall rules
+        - consider that older versions may need numbers or letters
+          in type, newer ones are more specific
+        :param conf: configuration.
+        :param match: parent node/attribute name.
+        :return: generated config dictionary.
+        """
+        config = {}
+        if not conf:
+            return config
+
+        for attrib in ("code", "type", "type-name"):
+            regex = self.map_regex(attrib)
+            if match:
+                regex = match + " " + regex
+            out = search(r"^.*" + regex + " (.+)", conf, M)
+            if out:
+                val = out.group(1).strip("'")
+                if attrib == 'type-name':
+                    config['type_name'] = val
+                if attrib == 'code':
+                    config['code'] = int(val)
+                if attrib == 'type':
+                    # <1.3 could be # (type), #/# (type/code) or 'type' (type_name)
+                    # recent this is only for strings
+                    if "/" in val:  # type/code
+                        (type_no, code) = val.split(".")
+                        config['type'] = type_no
+                        config['code'] = code
+                    elif val.isnumeric():
+                        config['type'] = type_no
+                    else:
+                        config['type_name'] = val
+        return config
+
     def parse_icmp(self, conf, attrib=None):
         """
         This function triggers the parsing of 'icmp' attributes.
@@ -283,11 +431,9 @@ class Firewall_rulesFacts(object):
         :param attrib: 'icmp'.
         :return: generated config dictionary.
         """
-        a_lst = ["code", "type", "type_name"]
-        if attrib == "icmp":
-            attrib = "icmpv6"
-        conf = re.sub("icmpv6 type", "icmpv6 type-name", conf)
-        cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
+        cfg_dict = self.parse_icmp_attr(conf, "icmp")
+        if (len(cfg_dict) == 0):
+            cfg_dict = self.parse_icmp_attr(conf, "icmpv6")
         return cfg_dict
 
     def parse_limit(self, conf, attrib=None):
@@ -330,7 +476,6 @@ class Firewall_rulesFacts(object):
             if conf:
                 if self.is_bool(attrib):
                     out = conf.find(attrib.replace("_", "-"))
-
                     dis = conf.find(attrib.replace("_", "-") + " 'disable'")
                     if out >= 1:
                         if dis >= 1:
@@ -375,6 +520,7 @@ class Firewall_rulesFacts(object):
             "disabled",
             "established",
             "enable_default_log",
+            "default_log",
         )
         return True if attrib in bool_set else False
 
diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
index 995be911..cd8008c6 100644
--- a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
@@ -17,7 +17,7 @@ __metaclass__ = type
 
 
 from copy import deepcopy
-from re import M, findall
+from re import M, findall, search
 
 from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
 
@@ -66,7 +66,7 @@ class InterfacesFacts(object):
         )
         if interface_names:
             for interface in set(interface_names):
-                intf_regex = r" %s .+$" % interface.strip("'")
+                intf_regex = r" %s (.+$)" % interface.strip("'")
                 cfg = findall(intf_regex, data, M)
                 obj = self.render_config(cfg)
                 obj["name"] = interface.strip("'")
@@ -106,7 +106,7 @@ class InterfacesFacts(object):
         if vif_names:
             vifs_list = []
             for vif in set(vif_names):
-                vif_regex = r" %s .+$" % vif
+                vif_regex = r"%s (.+$)" % vif
                 cfg = "\n".join(findall(vif_regex, conf, M))
                 obj = self.parse_attribs(["description", "mtu"], cfg)
                 obj["vlan_id"] = int(vif)
@@ -117,6 +117,14 @@ class InterfacesFacts(object):
         return vifs_list
 
     def parse_attribs(self, attribs, conf):
+        """
+        Parse the attributes of a network interface.
+
+        :param attribs: List of attribute names.
+        :param conf: Configuration string.
+        :rtype: dict
+        :returns: Parsed configuration dictionary.
+        """
         config = {}
         for item in attribs:
             value = utils.parse_conf_arg(conf, item)
@@ -126,7 +134,11 @@ class InterfacesFacts(object):
                 config[item] = value.strip("'")
             else:
                 config[item] = None
-        if "disable" in conf:
+
+        # match only on disable next to the interface name
+        # there are other sub-commands that can be disabled
+        match = search(r"^ *disable", conf, M)
+        if match:
             config["enabled"] = False
         else:
             config["enabled"] = True
diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
index be467a0a..7d4d1a08 100644
--- a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
@@ -62,7 +62,7 @@ class L3_interfacesFacts(object):
         # operate on a collection of resource x
         objs = []
         interface_names = re.findall(
-            r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|vxlan) (?:\'*)(\S+)(?:\'*)",
+            r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|vxlan) (?:\'*)(\S+)(?:\'*)",
             data,
             re.M,
         )
diff --git a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
index af6c5770..c0e74895 100644
--- a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
@@ -23,9 +23,17 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.osp
     Ospf_interfacesArgs,
 )
 from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import (
-    Ospf_interfacesTemplate,
+    Ospf_interfacesTemplate
 )
 
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import (
+    Ospf_interfacesTemplate14
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
 
 class Ospf_interfacesFacts(object):
     """The vyos ospf_interfaces facts class"""
@@ -35,9 +43,30 @@ class Ospf_interfacesFacts(object):
         self.argument_spec = Ospf_interfacesArgs.argument_spec
 
     def get_device_data(self, connection):
+        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            # use set protocols ospf in order to get both ospf and ospfv3
+            return connection.get("show configuration commands |  match 'set protocols ospf'")
         return connection.get('show configuration commands |  match "set interfaces"')
 
-    def get_config_set(self, data):
+    def get_config_set_1_4(self, data):
+        """To classify the configurations beased on interface"""
+        interface_list = []
+        config_set = []
+        int_string = ""
+        for config_line in data.splitlines():
+            ospf_int = re.search(r"set protocols (?:ospf|ospfv3) interface (\S+) .*", config_line)
+            if ospf_int:
+                if ospf_int.group(1) not in interface_list:
+                    if int_string:
+                        config_set.append(int_string)
+                    interface_list.append(ospf_int.group(1))
+                    int_string = ""
+                int_string = int_string + config_line + "\n"
+        if int_string:
+            config_set.append(int_string)
+        return config_set
+
+    def get_config_set_1_2(self, data):
         """To classify the configurations beased on interface"""
         interface_list = []
         config_set = []
@@ -55,6 +84,12 @@ class Ospf_interfacesFacts(object):
             config_set.append(int_string)
         return config_set
 
+    def get_config_set(self, data, connection):
+        """To classify the configurations beased on interface"""
+        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            return self.get_config_set_1_4(data)
+        return self.get_config_set_1_2(data)
+
     def populate_facts(self, connection, ansible_facts, data=None):
         """Populate the facts for Ospf_interfaces network resource
 
@@ -67,16 +102,20 @@ class Ospf_interfacesFacts(object):
         """
         facts = {}
         objs = []
-        ospf_interfaces_parser = Ospf_interfacesTemplate(lines=[], module=self._module)
+        if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+            ospf_interface_class = Ospf_interfacesTemplate14
+        else:
+            ospf_interface_class = Ospf_interfacesTemplate
+        ospf_interfaces_parser = ospf_interface_class(lines=[], module=self._module)
 
         if not data:
             data = self.get_device_data(connection)
 
         # parse native config using the Ospf_interfaces template
         ospf_interfaces_facts = []
-        resources = self.get_config_set(data)
+        resources = self.get_config_set(data, connection)
         for resource in resources:
-            ospf_interfaces_parser = Ospf_interfacesTemplate(
+            ospf_interfaces_parser = ospf_interface_class(
                 lines=resource.split("\n"),
                 module=self._module,
             )
@@ -93,7 +132,7 @@ class Ospf_interfacesFacts(object):
                 self.argument_spec,
                 {"config": ospf_interfaces_facts},
                 redact=True,
-            ),
+            )
         )
         if params.get("config"):
             for cfg in params["config"]:
diff --git a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py
new file mode 100644
index 00000000..7f49d47a
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py
@@ -0,0 +1,650 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+"""
+The Ospf_interfaces parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+    NetworkTemplate,
+)
+
+
+def _get_parameters(data):
+    if data["afi"] == "ipv6":
+        val = ["ospfv3", "ipv6"]
+    else:
+        val = ["ospf", "ip"]
+    return val
+
+
+def _tmplt_ospf_int_delete(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols " + params[0] + " interface {name}".format(**config_data)
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_cost(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " cost {cost}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_auth_password(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " authentication plaintext-password {plaintext_password}".format(
+            **config_data["address_family"]["authentication"]
+        )
+    )
+    return command
+
+
+def _tmplt_ospf_int_auth_md5(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " authentication md5 key-id {key_id} ".format(
+            **config_data["address_family"]["authentication"]["md5_key"]
+        )
+        + "md5-key {key}".format(**config_data["address_family"]["authentication"]["md5_key"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_auth_md5_delete(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " authentication"
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_bw(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " bandwidth {bandwidth}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_hello_interval(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " hello-interval {hello_interval}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_dead_interval(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " dead-interval {dead_interval}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_mtu_ignore(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " mtu-ignore"
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_network(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " network {network}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_priority(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " priority {priority}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_retransmit_interval(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " retransmit-interval {retransmit_interval}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_transmit_delay(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " transmit-delay {transmit_delay}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_ifmtu(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " ifmtu {ifmtu}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_instance(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " instance-id {instance}".format(**config_data["address_family"])
+    )
+
+    return command
+
+
+def _tmplt_ospf_int_passive(config_data):
+    params = _get_parameters(config_data["address_family"])
+    command = (
+        "protocols "
+        + params[0]
+        + " interface {name}".format(**config_data)
+        + " passive"
+    )
+
+    return command
+
+
+class Ospf_interfacesTemplate14(NetworkTemplate):
+    def __init__(self, lines=None, module=None):
+        prefix = {"set": "set", "remove": "delete"}
+        super(Ospf_interfacesTemplate14, self).__init__(
+            lines=lines, tmplt=self, prefix=prefix, module=module
+        )
+
+    # fmt: off
+    PARSERS = [
+        {
+            "name": "ip_ospf",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                *$""",
+                re.VERBOSE,
+            ),
+            "remval": _tmplt_ospf_int_delete,
+            "compval": "address_family",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                    }
+                }
+            }
+        },
+        {
+            "name": "authentication_password",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+authentication
+                \s+plaintext-password
+                \s+(?P<text>\S+)
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_auth_password,
+            "compval": "address_family.authentication",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "authentication": {
+                            "plaintext_password": "{{ text }}"
+                        }
+                    }
+                }
+            }
+        },
+        {
+            "name": "authentication_md5",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+authentication
+                \s+md5
+                \s+key-id
+                \s+(?P<id>\d+)
+                \s+md5-key
+                \s+(?P<text>\S+)
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_auth_md5,
+            "remval": _tmplt_ospf_int_auth_md5_delete,
+            "compval": "address_family.authentication",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "authentication": {
+                            "md5_key": {
+                                "key_id": "{{ id }}",
+                                "key": "{{ text }}"
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        {
+            "name": "bandwidth",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+bandwidth
+                \s+(?P<bw>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_bw,
+            "compval": "address_family.bandwidth",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "bandwidth": "{{ bw }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "cost",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+cost
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_cost,
+            "compval": "address_family.cost",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "cost": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "hello_interval",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+hello-interval
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_hello_interval,
+            "compval": "address_family.hello_interval",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "hello_interval": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "dead_interval",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+dead-interval
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_dead_interval,
+            "compval": "address_family.dead_interval",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "dead_interval": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "mtu_ignore",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+(?P<mtu>\'mtu-ignore\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_mtu_ignore,
+            "compval": "address_family.mtu_ignore",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "mtu_ignore": "{{ True if mtu is defined }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "network",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+network
+                \s+(?P<val>\S+)
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_network,
+            "compval": "address_family.network",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "network": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "priority",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+priority
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_priority,
+            "compval": "address_family.priority",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "priority": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "retransmit_interval",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+retransmit-interval
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_retransmit_interval,
+            "compval": "address_family.retransmit_interval",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "retransmit_interval": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "transmit_delay",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+transmit-delay
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_transmit_delay,
+            "compval": "address_family.transmit_delay",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "transmit_delay": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "ifmtu",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+ifmtu
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_ifmtu,
+            "compval": "address_family.ifmtu",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "ifmtu": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "instance",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+instance-id
+                \s+(?P<val>\'\d+\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_instance,
+            "compval": "address_family.instance",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "instance": "{{ val }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "passive",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                \s+(?P<pass>\'passive\')
+                *$""",
+                re.VERBOSE,
+            ),
+            "setval": _tmplt_ospf_int_passive,
+            "compval": "address_family.passive",
+            "result": {
+                "name": "{{ name }}",
+                "address_family": {
+                    '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+                        "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+                        "passive": "{{ True if pass is defined }}"
+                    }
+                }
+            }
+        },
+        {
+            "name": "interface_name",
+            "getval": re.compile(
+                r"""
+                ^set
+                \s+protocols
+                \s+(?P<proto>ospf|ospfv3)
+                \s+interface
+                \s+(?P<name>\S+)
+                .*$""",
+                re.VERBOSE,
+            ),
+            "setval": "set protocols {{ proto }} interface {{ name }}",
+            "result": {
+                "name": "{{ name }}",
+            }
+        },
+    ]
+    # fmt: on
diff --git a/plugins/module_utils/network/vyos/utils/version.py b/plugins/module_utils/network/vyos/utils/version.py
new file mode 100644
index 00000000..cc3028c3
--- /dev/null
+++ b/plugins/module_utils/network/vyos/utils/version.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2021, Felix Fontein <felix@fontein.de>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+"""Provide version object to compare version numbers."""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+from ansible.module_utils.compat.version import LooseVersion  # pylint: disable=unused-import
diff --git a/plugins/module_utils/network/vyos/vyos.py b/plugins/module_utils/network/vyos/vyos.py
index 4fcb3317..1430b1b1 100644
--- a/plugins/module_utils/network/vyos/vyos.py
+++ b/plugins/module_utils/network/vyos/vyos.py
@@ -34,7 +34,6 @@ import json
 from ansible.module_utils._text import to_text
 from ansible.module_utils.connection import Connection, ConnectionError
 
-
 _DEVICE_CONFIGS = {}
 
 
@@ -100,3 +99,10 @@ def load_config(module, commands, commit=False, comment=None):
         module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
 
     return response.get("diff")
+
+
+def get_os_version(module):
+    connection = get_connection(module)
+    if connection.get_device_info():
+        os_version = connection.get_device_info()["network_os_major_version"]
+    return os_version
diff --git a/plugins/modules/vyos_firewall_global.py b/plugins/modules/vyos_firewall_global.py
index 205ef136..befe5e73 100644
--- a/plugins/modules/vyos_firewall_global.py
+++ b/plugins/modules/vyos_firewall_global.py
@@ -253,6 +253,19 @@ options:
             description:
             - Enable logging of packets part of an established connection.
             type: bool
+          log_level:
+            description:
+            - Only available in 1.4+
+            type: str
+            choices:
+            - emerg
+            - alert
+            - crit
+            - err
+            - warn
+            - notice
+            - info
+            - debug
   running_config:
     description:
     - The module, by default, will connect to the remote device and retrieve the current
diff --git a/plugins/modules/vyos_firewall_rules.py b/plugins/modules/vyos_firewall_rules.py
index 06a300f5..fd2e7d55 100644
--- a/plugins/modules/vyos_firewall_rules.py
+++ b/plugins/modules/vyos_firewall_rules.py
@@ -31,6 +31,11 @@ from __future__ import absolute_import, division, print_function
 
 __metaclass__ = type
 
+ANSIBLE_METADATA = {
+    'metadata_version': '1.1',
+    'status': ['preview'],
+    'supported_by': 'network'
+}
 
 DOCUMENTATION = """
 module: vyos_firewall_rules
@@ -62,9 +67,16 @@ options:
         type: list
         elements: dict
         suboptions:
+          filter:
+            description:
+              - Filter type (exclusive to "name").
+              - Supported in 1.4 and later.
+            type: str
+            choices: ['input', 'output', 'forward']
           name:
             description:
             - Firewall rule set name.
+            - Required for 1.3- and optional for 1.4+.
             type: str
           default_action:
             description:
@@ -72,11 +84,15 @@ options:
             - drop (Drop if no prior rules are hit (default))
             - reject (Drop and notify source if no prior rules are hit)
             - accept (Accept if no prior rules are hit)
+              - jump (Jump to another rule-set, 1.4+)
+            type: str
+            choices: ['drop', 'reject', 'accept', 'jump']
+          default_jump_target:
+            description:
+              - Default jump target if the default action is jump.
+              - Only valid in 1.4 and later.
+              - Only valid when default_action = jump.
             type: str
-            choices:
-            - drop
-            - reject
-            - accept
           description:
             description:
             - Rule set description.
@@ -103,12 +119,19 @@ options:
               action:
                 description:
                 - Specifying the action.
+                - inspect is available  < 1.4
+                - continue, return, jump, queue, synproxy are available >= 1.4
                 type: str
                 choices:
                 - drop
                 - reject
                 - accept
                 - inspect
+                - continue
+                - return
+                - jump
+                - queue
+                - synproxy
               destination:
                 description:
                 - Specifying the destination parameters.
@@ -148,6 +171,7 @@ options:
               disable:
                 description:
                 - Option to disable firewall rule.
+                - aliased to disabled
                 type: bool
                 aliases: ["disabled"]
               fragment:
@@ -215,6 +239,21 @@ options:
                     description:
                     - ICMP type.
                     type: int
+              inbound_interface:
+                description:
+                  - Inbound interface.
+                  - Only valid in 1.4 and later.
+                type: dict
+                suboptions:
+                  name:
+                    description:
+                      - Interface name.
+                      - Can have wildcards
+                    type: str
+                  group:
+                    description:
+                      - Interface group.
+                    type: str
               ipsec:
                 description:
                 - Inbound ip sec packets.
@@ -222,13 +261,16 @@ options:
                 choices:
                 - match-ipsec
                 - match-none
-              log:
+                - match-ipsec-in
+                - match-ipsec-out
+                - match-none-in
+                - match-none-out
+              jump_target:
                 description:
-                - Option to log packets matching rule
+                  - Jump target if the action is jump.
+                  - Only valid in 1.4 and later.
+                  - Only valid when action = jump.
                 type: str
-                choices:
-                - disable
-                - enable
               limit:
                 description:
                 - Rate limit using a token bucket filter.
@@ -255,6 +297,55 @@ options:
                         description:
                         - This is the time unit.
                         type: str
+              log:
+                description:
+                  - Log matching packets.
+                type: str
+                choices: ['disable', 'enable']
+              outbound_interface:
+                description:
+                  - Match outbound interface.
+                  - Only valid in 1.4 and later.
+                type: dict
+                suboptions:
+                  name:
+                    description:
+                      - Interface name.
+                      - Can have wildcards
+                    type: str
+                  group:
+                    description:
+                      - Interface group.
+                    type: str
+              packet_length:
+                description:
+                  - Packet length match.
+                  - Only valid in 1.4 and later.
+                  - Multiple values from 1 to 65535 and ranges are supported
+                type: list
+                elements: dict
+                suboptions:
+                  length:
+                    description:
+                      - Packet length or range.
+                    type: str
+              packet_length_exclude:
+                description:
+                  - Packet length match.
+                  - Only valid in 1.4 and later.
+                  - Multiple values from 1 to 65535 and ranges are supported
+                type: list
+                elements: dict
+                suboptions:
+                  length:
+                    description:
+                      - Packet length or range.
+                    type: str
+              packet_type:
+                description:
+                  - Packet type match.
+                type: str
+                choices: ['broadcast', 'multicast', 'host', 'other']
               p2p:
                 description:
                 - P2P application packets.
@@ -283,6 +374,20 @@ options:
                 - all All IP protocols.
                 - (!)All IP protocols except for the specified name or number.
                 type: str
+              queue:
+                description:
+                  - Queue options.
+                  - Only valid in 1.4 and later.
+                  - Only valid when action = queue.
+                  - Can be a queue number or range.
+                type: str
+              queue_options:
+                description:
+                  - Queue options.
+                  - Only valid in 1.4 and later.
+                  - Only valid when action = queue.
+                type: str
+                choices: ['bypass', 'fanout']
               recent:
                 description:
                 - Parameters for matching recently seen sources.
@@ -295,7 +400,8 @@ options:
                   time:
                     description:
                     - Source addresses seen in the last N seconds.
-                    type: int
+                    - Since 1.4, this is a string of second/minute/hour
+                    type: str
               source:
                 description:
                 - Source parameters.
@@ -337,6 +443,12 @@ options:
                     - <MAC address> MAC address to match.
                     - <!MAC address> Match everything except the specified MAC address.
                     type: str
+                  fqdn:
+                    description:
+                      - Fully qualified domain name.
+                      - Available in 1.4 and later.
+                    type: str
+
               state:
                 description:
                 - Session state.
@@ -358,6 +470,21 @@ options:
                     description:
                     - Related state.
                     type: bool
+              synproxy:
+                description:
+                  - SYN proxy options.
+                  - Only valid in 1.4 and later.
+                  - Only valid when action = synproxy.
+                type: dict
+                suboptions:
+                  mss:
+                    description:
+                      - Adjust MSS (501-65535)
+                    type: int
+                  window_scale:
+                    description:
+                      - Window scale (1-14).
+                    type: int
               tcp:
                 description:
                 - TCP flags to match.
@@ -365,8 +492,22 @@ options:
                 suboptions:
                   flags:
                     description:
-                    - TCP flags to be matched.
-                    type: str
+                      - list of tcp flags to be matched
+                      - 5.0 breaking change to support 1.4+ and 1.3-
+                    type: list
+                    elements: dict
+                    suboptions:
+                      flag:
+                        description:
+                          - TCP flag to be matched.
+                          - syn, ack, fin, rst, urg, psh, all (1.3-)
+                          - syn, ack, fin, rst, urg, psh, cwr, ecn (1.4+)
+                        type: str
+                        choices: ['ack', 'cwr', 'ecn', 'fin', 'psh', 'rst', 'syn', 'urg', 'all']
+                      invert:
+                        description:
+                          - Invert the match.
+                        type: bool
               time:
                 description:
                 - Time to match rule.
@@ -1460,14 +1601,14 @@ RETURN = """
 before:
   description: The configuration prior to the model invocation.
   returned: always
-  type: list
+  type: dict
   sample: >
     The configuration returned will always be in the same format
      of the parameters above.
 after:
   description: The resulting configuration model invocation.
   returned: when changed
-  type: list
+  type: dict
   sample: >
     The configuration returned will always be in the same format
      of the parameters above.
@@ -1486,17 +1627,14 @@ commands:
 
 from ansible.module_utils.basic import AnsibleModule
 
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import (
-    Firewall_rulesArgs,
-)
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules import (
-    Firewall_rules,
-)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import Firewall_rulesArgs
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules import Firewall_rules
 
 
 def main():
     """
     Main entry point for module execution
+
     :returns: the result form module invocation
     """
     required_if = [
@@ -1518,5 +1656,5 @@ def main():
     module.exit_json(**result)
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     main()
diff --git a/plugins/modules/vyos_ospf_interfaces.py b/plugins/modules/vyos_ospf_interfaces.py
index c2326895..33290581 100644
--- a/plugins/modules/vyos_ospf_interfaces.py
+++ b/plugins/modules/vyos_ospf_interfaces.py
@@ -901,7 +901,7 @@ def main():
         argument_spec=Ospf_interfacesArgs.argument_spec,
         mutually_exclusive=[],
         required_if=[],
-        supports_check_mode=False,
+        supports_check_mode=True,
     )
 
     result = Ospf_interfaces(module).execute_module()
-- 
cgit v1.2.3