diff --git a/plugins/module_utils/network/vyos/argspec/nat/nat.py b/plugins/module_utils/network/vyos/argspec/nat/nat.py index 93cb4aa4..e6b7fe70 100644 --- a/plugins/module_utils/network/vyos/argspec/nat/nat.py +++ b/plugins/module_utils/network/vyos/argspec/nat/nat.py @@ -1,515 +1,554 @@ # -*- coding: utf-8 -*- # Copyright 2024 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 arg spec for the vyos_nat module """ class NatArgs(object): # pylint: disable=R0903 """The arg spec for the vyos_nat module""" argument_spec = { "config": { "type": "dict", "nat": { "type": "dict", "options": { "cgnat": { "type": "dict", "options": { "log_allocation": { "type": "bool", }, "pool": { "type": "dict", "options": { "external": { "type": "list", "elements": "dict", "options": { "name": { "type": "str", "required": True, }, "external_port_range": { "type": "str", }, "per_user_limit": { "type": "dict", "options": { "port": { - "type": "int", + "type": "str", }, }, }, "range": { "type": "list", "elements": "str", }, }, }, "internal": { "type": "list", "elements": "dict", "options": { "name": { "type": "str", "required": True, }, "range": { "type": "list", "elements": "str", }, }, }, }, }, "rule": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "source": { "type": "dict", "options": { "pool": { "type": "str", }, }, }, "translation": { "type": "dict", "options": { "pool": { "type": "str", }, }, }, }, }, }, }, "destination": { "type": "dict", "options": { "rule": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "description": { "type": "str", }, + "protocol": { + "type": "str", + }, + "exclude": { + "type": "bool", + }, + "log": { + "type": "bool", + }, + "disable": { + "type": "bool", + }, + "inbound_interface": { + "type": "dict", + "options": { + "name": { + "type": "str", + }, + "group": { + "type": "str", + }, + }, + }, "destination": { "type": "dict", "options": { "address": { "type": "str", }, "fqdn": { "type": "str", }, + "port": { + "type": "str", + }, "group": { "type": "dict", "options": { "address_group": { "type": "str", }, "domain_group": { "type": "str", }, "mac_group": { "type": "str", }, "network_group": { "type": "str", }, "port_group": { "type": "str", }, }, }, - "port": { + }, + }, + "translation": { + "type": "dict", + "options": { + "address": { "type": "str", }, - "protocol": { + "port": { "type": "str", }, - "exclude": { - "type": "bool", + "redirect_port": { + "type": "str", }, - "log": { - "type": "bool", + "address_mapping": { + "type": "str", + "choices": [ + "random", + "persistent", + ], }, - "disable": { - "type": "bool", + "port_mapping": { + "type": "str", + "choices": [ + "random", + "none", + ], }, }, }, }, }, }, }, "source": { "type": "dict", "options": { "rule": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "description": { "type": "str", }, + "protocol": { + "type": "str", + }, + "exclude": { + "type": "bool", + }, + "log": { + "type": "bool", + }, + "disable": { + "type": "bool", + }, "destination": { "type": "dict", "options": { "address": { "type": "str", }, "fqdn": { "type": "str", }, "group": { "type": "dict", "options": { "address_group": { "type": "str", }, "domain_group": { "type": "str", }, "mac_group": { "type": "str", }, "network_group": { "type": "str", }, "port_group": { "type": "str", }, }, }, "port": { "type": "str", }, - "protocol": { - "type": "str", - }, - "exclude": { - "type": "bool", - }, - "log": { - "type": "bool", - }, - "disable": { - "type": "bool", - }, }, }, }, }, }, }, "static": { "type": "dict", "options": { "rule": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "description": { "type": "str", }, "destination": { "type": "dict", "options": { "address": { "type": "str", }, }, }, "inbound_interface": { "type": "str", }, "log": { "type": "bool", }, "disable": { "type": "bool", }, "translation": { "type": "dict", "options": { "address": { "type": "str", }, }, }, }, }, }, }, }, }, "nat64": { "type": "dict", "options": { "source": { "type": "dict", "options": { "rule": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "description": { "type": "str", }, "disable": { "type": "bool", }, "match": { "type": "dict", "options": { "mark": { "type": "int", }, }, }, "source": { "type": "dict", "options": { "prefix": { "type": "str", }, }, }, "translation": { "type": "dict", "options": { "pool": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "address": { "type": "str", }, "description": { "type": "str", }, "disable": { "type": "bool", }, "port": { "type": "str", }, "protocol": { "type": "str", "choices": [ "icmp", "tcp", "udp", ], }, }, }, }, }, }, }, }, }, }, }, "nat66": { "type": "dict", "options": { "destination": { "type": "dict", "options": { "rule": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "description": { "type": "str", }, "destination": { "type": "dict", "options": { "address": { "type": "str", }, "port": { "type": "str", }, }, }, "disable": { "type": "bool", }, "exclude": { "type": "bool", }, "inbound_interface": { "type": "dict", "options": { "name": { "type": "str", }, }, }, "log": { "type": "bool", }, "protocol": { "type": "str", }, "source": { "type": "dict", "options": { "address": { "type": "str", }, "port": { "type": "str", }, }, }, "translation": { "type": "dict", "options": { "address": { "type": "str", }, "port": { "type": "str", }, }, }, }, }, }, }, "source": { "type": "dict", "options": { "rule": { "type": "list", "elements": "dict", "options": { "id": { "type": "int", "required": True, }, "description": { "type": "str", }, "destination": { "type": "dict", "options": { "port": { "type": "str", }, "prefix": { "type": "str", }, }, }, "disable": { "type": "bool", }, "exclude": { "type": "bool", }, "log": { "type": "bool", }, "outbound_interface": { "type": "dict", "options": { "name": { "type": "str", }, }, }, "protocol": { "type": "str", }, "source": { "type": "dict", "options": { "port": { "type": "str", }, "prefix": { "type": "str", }, }, }, "translation": { "type": "dict", "options": { "address": { "type": "str", }, "port": { "type": "str", }, }, }, }, }, }, }, }, }, }, "running_config": {"type": "str"}, "state": { "type": "str", "choices": [ "deleted", "merged", "overridden", "replaced", "gathered", "rendered", "parsed", ], "default": "merged", }, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/nat/nat.py b/plugins/module_utils/network/vyos/config/nat/nat.py index 7a099037..2d032009 100644 --- a/plugins/module_utils/network/vyos/config/nat/nat.py +++ b/plugins/module_utils/network/vyos/config/nat/nat.py @@ -1,193 +1,197 @@ # # -*- coding: utf-8 -*- # Copyright 2021 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 vyos_nat config file. It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to its desired end-state is created. """ from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( ResourceModule, ) 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.nat import ( NatTemplate, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine # from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( # dict_merge, # ) class Nat(ResourceModule): """ The vyos_nat config class """ def __init__(self, module): super(Nat, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="nat", tmplt=NatTemplate(), ) self.parsers = [] def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ if self.state not in ["parsed", "gathered"]: self.generate_commands() self.run_commands() return self.result def generate_commands(self): """Generate configuration commands to send based on want, have and desired state. """ wantd = {} haved = {} wantd = deepcopy(self.want) haved = deepcopy(self.have) - self._module.fail_json(msg={"want": wantd, " ******** have": haved}) + # wantd = self._ntp_list_to_dict(self.want) + # haved = self._ntp_list_to_dict(self.have) - # wantd = self._ntp_list_to_dict(self.want) - # haved = self._ntp_list_to_dict(self.have) + # if state is merged, merge want onto have and then compare + if self.state == "merged": + # wantd = dict_merge(haved, wantd) + wantd = combine(haved, wantd, recursive=True, list_merge="append_rp") - # # if state is merged, merge want onto have and then compare - # if self.state == "merged": - # wantd = dict_merge(haved, wantd) + # self._module.fail_json(msg={"want": wantd, " ******** have": haved, "******** original want": self.want}) + # self._module.fail_json(msg={"want": wantd, " ******** have": haved}) + self._module.fail_json(msg={"have": haved}) # # if state is deleted, empty out wantd and set haved to wantd # if self.state == "deleted": # haved = {k: v for k, v in haved.items() if k in wantd or not wantd} # wantd = {} # commandlist = self._commandlist(haved) # servernames = self._servernames(haved) # # removing the servername and commandlist from the list after deleting it from haved # # iterate through the top-level items to delete # for k, have in haved.items(): # if k not in wantd: # for hk, hval in have.items(): # if hk == "allow_clients" and hk in commandlist: # self.commands.append( # self._tmplt.render({"": hk}, "allow_clients_delete", True), # ) # commandlist.remove(hk) # elif hk == "listen_addresses" and hk in commandlist: # self.commands.append( # self._tmplt.render({"": hk}, "listen_addresses_delete", True), # ) # commandlist.remove(hk) # elif hk == "server" and have["server"] in servernames: # self._compareoverride(want={}, have=have) # servernames.remove(have["server"]) # # if everything is deleted add the delete command for {path} ntp # # this should be equiv: servernames == [] and commandlist == ["server"]: # if wantd == {} and haved != {}: # self.commands.append( # self._tmplt.render({}, "service_delete", True), # ) # # remove existing config for overridden and replaced # # Getting the list of the server names from haved # # to avoid the duplication of overridding/replacing the servers # if self.state in ["overridden", "replaced"]: # commandlist = self._commandlist(haved) # servernames = self._servernames(haved) # for k, have in haved.items(): # if k not in wantd: # if "server" not in have: # self._compareoverride(want={}, have=have) # # removing the servername from the list after deleting it from haved # elif have["server"] in servernames: # self._compareoverride(want={}, have=have) # servernames.remove(have["server"]) # for k, want in wantd.items(): # self._compare(want=want, have=haved.pop(k, {})) # def _compare(self, want, have): # """Leverages the base class `compare()` method and # populates the list of commands to be run by comparing # the `want` and `have` data with the `parsers` defined # for the Ntp network resource. # """ # if "options" in want: # self.compare(parsers="options", want=want, have=have) # else: # self.compare(parsers=self.parsers, want=want, have=have) # def _compareoverride(self, want, have): # # do not delete configuration with options level # for i, val in have.items(): # if i == "options": # pass # else: # self.compare(parsers=i, want={}, have=have) # def _ntp_list_to_dict(self, entry): # servers_dict = {} # for k, data in entry.items(): # if k == "servers": # for value in data: # if "options" in value: # result = self._serveroptions_list_to_dict(value) # for res, resvalue in result.items(): # servers_dict.update({res: resvalue}) # else: # servers_dict.update({value["server"]: value}) # else: # for value in data: # servers_dict.update({"ip_" + value: {k: value}}) # return servers_dict # def _serveroptions_list_to_dict(self, entry): # serveroptions_dict = {} # for Opk, Op in entry.items(): # if Opk == "options": # for val in Op: # dict = {} # dict.update({"server": entry["server"]}) # dict.update({Opk: val}) # serveroptions_dict.update({entry["server"] + "_" + val: dict}) # return serveroptions_dict # def _commandlist(self, haved): # commandlist = [] # for k, have in haved.items(): # for ck, cval in have.items(): # if ck != "options" and ck not in commandlist: # commandlist.append(ck) # return commandlist # def _servernames(self, haved): # servernames = [] # for k, have in haved.items(): # for sk, sval in have.items(): # if sk != "options" and sval not in servernames: # servernames.append(sval) # return servernames diff --git a/plugins/module_utils/network/vyos/facts/nat/nat.py b/plugins/module_utils/network/vyos/facts/nat/nat.py index 8169cfd7..03adf0a2 100644 --- a/plugins/module_utils/network/vyos/facts/nat/nat.py +++ b/plugins/module_utils/network/vyos/facts/nat/nat.py @@ -1,111 +1,145 @@ # -*- coding: utf-8 -*- # Copyright 2021 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 vyos ntp fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.nat.nat import ( NatArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.nat import ( NatTemplate, ) class NatFacts(object): """The vyos nat facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = NatArgs.argument_spec def get_config(self, connection): return connection.get("show configuration commands | match 'nat'") def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Ntp network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = [] config_lines = [] if not data: data = self.get_config(connection) for resource in data.splitlines(): config_lines.append(re.sub("'", "", resource)) nat_parser = NatTemplate(lines=config_lines, module=self._module) objs = nat_parser.parse() objs = self._normalise(objs) ansible_facts["ansible_network_resources"].pop("nat", None) params = utils.remove_empties( nat_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), ) if params.get("config"): facts["nat"] = params["config"] ansible_facts["ansible_network_resources"].update(facts) # self._module.fail_json(msg=ansible_facts) return ansible_facts def _merge_rule_list(self, rules): merged = {} for item in rules: rid = item["id"] if rid not in merged: merged[rid] = {"id": rid} for k, v in item.items(): if k == "id": continue if isinstance(v, dict): merged[rid].setdefault(k, {}) merged[rid][k].update(v) else: merged[rid][k] = v return list(merged.values()) + def _merge_pool_list(self, pools): + merged = {} + + for item in pools: + name = item["name"] + + if name not in merged: + merged[name] = {"name": name} + + for k, v in item.items(): + if k == "name": + continue + + if isinstance(v, dict): + merged[name].setdefault(k, {}) + merged[name][k].update(v) + + elif isinstance(v, list): + merged[name].setdefault(k, []) + for val in v: + if val not in merged[name][k]: + merged[name][k].append(val) + + else: + merged[name][k] = v + + return list(merged.values()) + def _normalise(self, objs): for nat_type in ["nat", "nat64", "nat66"]: nat = objs.get(nat_type) if not nat: continue for section in ["destination", "source", "static", "cgnat"]: if section in nat and "rule" in nat[section]: nat[section]["rule"] = self._merge_rule_list( nat[section]["rule"], ) + if "cgnat" in nat and "pool" in nat["cgnat"]: + pool = nat["cgnat"]["pool"] + + for ptype in ["external", "internal"]: + if ptype in pool: + pool[ptype] = self._merge_pool_list(pool[ptype]) return objs diff --git a/plugins/module_utils/network/vyos/rm_templates/nat.py b/plugins/module_utils/network/vyos/rm_templates/nat.py index be9637cb..305a6fa5 100644 --- a/plugins/module_utils/network/vyos/rm_templates/nat.py +++ b/plugins/module_utils/network/vyos/rm_templates/nat.py @@ -1,827 +1,1139 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function __metaclass__ = type import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( NetworkTemplate, ) class NatTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): prefix = {"set": "set", "remove": "delete"} super(NatTemplate, self).__init__(lines=lines, tmplt=self, prefix=prefix, module=module) # def parse(self): # data = super(NatTemplate, self).parse() # return self._normalize(data) # def _normalize(self, data): # def convert_rules(section): # if not section or "rule" not in section: # return section # rules = section["rule"] # if isinstance(rules, dict): # new_rules = [] # for rule_id, rule_data in rules.items(): # rule = rule_data.copy() # # normalize id # try: # rule["id"] = int(rule_id) # except (ValueError, TypeError): # rule["id"] = rule_id # new_rules.append(rule) # section["rule"] = sorted(new_rules, key=lambda x: x.get("id", 0)) # return section # if not data: # return data # for nat_type in ["nat", "nat64", "nat66"]: # if nat_type not in data: # continue # nat = data[nat_type] # for block in ["destination", "source", "static"]: # if block in nat: # nat[block] = convert_rules(nat[block]) # # CGNAT rules # if "cgnat" in nat and "rule" in nat["cgnat"]: # rules = nat["cgnat"]["rule"] # if isinstance(rules, list): # for r in rules: # if "id" in r: # r["id"] = int(r["id"]) # return data - def _normalize(self, data): - if not data: - return data - - def normalize_rules(rules): - """Convert rules dict → sorted list with int IDs, or cast IDs in existing list.""" - if isinstance(rules, dict): - result = [] - for rule_id, rule_data in rules.items(): - rule = rule_data.copy() - try: - rule["id"] = int(rule_id) - except (ValueError, TypeError): - rule["id"] = rule_id - result.append(rule) - return sorted(result, key=lambda x: x.get("id", 0)) - - if isinstance(rules, list): - for rule in rules: - if "id" in rule: - try: - rule["id"] = int(rule["id"]) - except (ValueError, TypeError): - pass - return rules - - return rules - - for nat_type in ["nat", "nat64", "nat66"]: - nat = data.get(nat_type) - if not nat: - continue - - for block in ["destination", "source", "static", "cgnat"]: - section = nat.get(block) - if section and "rule" in section: - section["rule"] = normalize_rules(section["rule"]) - - return data + # def _normalize(self, data): + # if not data: + # return data + + # def normalize_rules(rules): + # """Convert rules dict → sorted list with int IDs, or cast IDs in existing list.""" + # if isinstance(rules, dict): + # result = [] + # for rule_id, rule_data in rules.items(): + # rule = rule_data.copy() + # try: + # rule["id"] = int(rule_id) + # except (ValueError, TypeError): + # rule["id"] = rule_id + # result.append(rule) + # return sorted(result, key=lambda x: x.get("id", 0)) + + # if isinstance(rules, list): + # for rule in rules: + # if "id" in rule: + # try: + # rule["id"] = int(rule["id"]) + # except (ValueError, TypeError): + # pass + # return rules + + # return rules + + # for nat_type in ["nat", "nat64", "nat66"]: + # nat = data.get(nat_type) + # if not nat: + # continue + + # for block in ["destination", "source", "static", "cgnat"]: + # section = nat.get(block) + # if section and "rule" in section: + # section["rule"] = normalize_rules(section["rule"]) + + # return data # fmt: off PARSERS = [ # # ------------------------- # CGNAT (keep explicit) # ------------------------- # { "name": "cgnat_log_allocation", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+nat \s+cgnat \s+log-allocation $""", re.VERBOSE, ), - "setval": "{{ nat }} cgnat log-allocation", + "setval": "nat cgnat log-allocation", "result": { - "{{ nat }}": { + "nat": { "cgnat": { "log_allocation": True, }, }, }, }, { "name": "cgnat_pool_external_range", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+nat \s+cgnat \s+pool \s+external \s+(?P\S+) \s+range \s+(?P\S+)(?:\s+seq\s+(?P\d+))? $""", re.VERBOSE, ), - "setval": "{{ nat }} cgnat pool external {{ name }} range {{ range }}{% if seq is defined %} seq {{ seq }}{% endif %}", + "setval": "nat cgnat pool external {{ name }} range {{ range }}{% if seq is defined %} seq {{ seq }}{% endif %}", "result": { - "{{ nat }}": { + "nat": { "cgnat": { "pool": { "external": [ { "name": "{{ name }}", "range": ["{{ range }}"], "seq": "{{ seq }}", }, ], }, }, }, }, }, { "name": "cgnat_pool_external_port_range", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+nat \s+cgnat \s+pool \s+external \s+(?P\S+) \s+external-port-range \s+(?P\S+) $""", re.VERBOSE, ), - "setval": "{{ nat }} cgnat pool external {{ name }} external-port-range {{ range }}", + "setval": "nat cgnat pool external {{ name }} external-port-range {{ range }}", "result": { - "{{ nat }}": { + "nat": { "cgnat": { "pool": { "external": [ { "name": "{{ name }}", "external_port_range": "{{ range }}", }, ], }, }, }, }, }, { "name": "cgnat_pool_external_per_user", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+nat \s+cgnat \s+pool \s+external \s+(?P\S+) \s+per-user-limit \s+port \s+(?P\d+) $""", re.VERBOSE, ), - "setval": "{{ nat }} cgnat pool external {{ name }} per-user-limit port {{ limit }}", + "setval": "nat cgnat pool external {{ name }} per-user-limit port {{ limit }}", "result": { - "{{ nat }}": { + "nat": { "cgnat": { "pool": { "external": [ { "name": "{{ name }}", "per_user_limit": {"port": "{{ limit }}"}, }, ], }, }, }, }, }, { "name": "cgnat_pool_internal_range", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+nat \s+cgnat \s+pool \s+internal \s+(?P\S+) \s+range \s+(?P\S+) $""", re.VERBOSE, ), - "setval": "{{ nat }} cgnat pool internal {{ name }} range {{ range }}", + "setval": "nat cgnat pool internal {{ name }} range {{ range }}", "result": { - "{{ nat }}": { + "nat": { "cgnat": { "pool": { "internal": [ { "name": "{{ name }}", "range": ["{{ range }}"], }, ], }, }, }, }, }, { "name": "cgnat_rule_source_pool", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+nat \s+cgnat \s+rule \s+(?P\d+) \s+source \s+pool \s+(?P\S+) $""", re.VERBOSE, ), - "setval": "{{ nat }} cgnat rule {{ id }} source pool {{ pool }}", + "setval": "nat cgnat rule {{ id }} source pool {{ pool }}", "result": { - "{{ nat }}": { + "nat": { "cgnat": { "rule": [ { "id": "{{ id }}", "source": {"pool": "{{ pool }}"}, }, ], }, }, }, }, { "name": "cgnat_rule_translation_pool", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+nat \s+cgnat \s+rule \s+(?P\d+) \s+translation \s+pool \s+(?P\S+) $""", re.VERBOSE, ), - "setval": "{{ nat }} cgnat rule {{ id }} translation pool {{ pool }}", + "setval": "nat cgnat rule {{ id }} translation pool {{ pool }}", "result": { - "{{ nat }}": { + "nat": { "cgnat": { "rule": [ { "id": "{{ id }}", "translation": {"pool": "{{ pool }}"}, }, ], }, }, }, }, # # ------------------------- # GENERIC NAT (destination/source/static) # ------------------------- # # description { "name": "nat_type_description", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+description \s+(?P.+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} description {{ description }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "description": "{{ description }}", }, ], }, }, }, }, # protocol { "name": "nat_type_protocol", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+protocol \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} protocol {{ protocol }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "protocol": "{{ protocol }}", }, ], }, }, }, }, # flags { "name": "nat_type_disable", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+disable $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} disable", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "disable": True, }, ], }, }, }, }, { "name": "nat_type_exclude", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+exclude $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} exclude", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "exclude": True, }, ], }, }, }, }, { "name": "nat_type_log", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+log $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} log", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "log": True, }, ], }, }, }, }, # address (destination/source) { "name": "nat_type_address", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+address \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} address {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": {"address": "{{ value }}"}, }, ], }, }, }, }, + # prefix (destination/source) + { + "name": "nat_type_prefix", + "getval": re.compile( + r""" + ^set + \s+(?Pnat|nat64|nat66) + \s+(?Pdestination|source|static) + \s+rule + \s+(?P\S+) + \s+(?Pdestination|source) + \s+prefix + \s+(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} prefix {{ value }}", + "result": { + "{{ nat }}": { + "{{ type }}": { + "rule": [ + { + "id": "{{ id }}", + "{{ atype }}": {"prefix": "{{ value }}"}, + }, + ], + }, + }, + }, + }, + # fqdn { "name": "nat_type_fqdn", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+fqdn \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} fqdn {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": {"fqdn": "{{ value }}"}, }, ], }, }, }, }, # port { "name": "nat_type_port", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+(?Pdestination|source) \s+port \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} {{ atype }} port {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "{{ atype }}": {"port": "{{ value }}"}, }, ], }, }, }, }, # translation address { "name": "nat_type_translation_address", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+translation \s+address \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation address {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": {"address": "{{ value }}"}, }, ], }, }, }, }, # translation port { "name": "nat_type_translation_port", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+translation \s+port \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation port {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": {"port": "{{ value }}"}, }, ], }, }, }, }, + { + "name": "nat_inbound_interface_name", + "getval": re.compile( + r""" + ^set + \s+nat + \s+(?Pdestination|source|static) + \s+rule + \s+(?P\S+) + \s+inbound-interface + \s+name + \s+(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "nat {{ type }} rule {{ id }} inbound-interface name {{ value }}", + "result": { + "nat": { + "{{ type }}": { + "rule": [ + { + "id": "{{ id }}", + "inbound_interface": {"name": "{{ value }}"}, + }, + ], + }, + }, + }, + }, + { + "name": "nat_inbound_interface_group", + "getval": re.compile( + r""" + ^set + \s+nat + \s+(?Pdestination|source|static) + \s+rule + \s+(?P\S+) + \s+inbound-interface + \s+group + \s+(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "nat {{ type }} rule {{ id }} inbound-interface group {{ value }}", + "result": { + "nat": { + "{{ type }}": { + "rule": [ + { + "id": "{{ id }}", + "inbound_interface": {"group": "{{ value }}"}, + }, + ], + }, + }, + }, + }, - # inbound interface + # NAT6X inbound interface { - "name": "nat_type_inbound_interface", + "name": "nat6x_inbound_interface", "getval": re.compile( r""" ^set - \s+(?Pnat|nat64|nat66) + \s+(?Pnat64|nat66) \s+(?Pdestination|source|static) \s+rule \s+(?P\S+) \s+inbound-interface + \s+name \s+(?P\S+) $""", re.VERBOSE, ), - "setval": "{{ nat }} {{ type }} rule {{ id }} inbound-interface {{ value }}", + "setval": "{{ nat }} {{ type }} rule {{ id }} inbound-interface name {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "inbound_interface": "{{ value }}", }, ], }, }, }, }, + # outbound interface + { + "name": "nat_type_outbound_interface", + "getval": re.compile( + r""" + ^set + \s+(?Pnat64|nat66) + \s+(?Pdestination|source|static) + \s+rule + \s+(?P\S+) + \s+outbound-interface + \s+name + \s+(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "{{ nat }} {{ type }} rule {{ id }} outbound-interface name {{ value }}", + "result": { + "{{ nat }}": { + "{{ type }}": { + "rule": [ + { + "id": "{{ id }}", + "outbound_interface": "{{ value }}", + }, + ], + }, + }, + }, + }, + # packet type { "name": "nat_type_packet_type", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+packet-type \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} packet-type {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "packet_type": "{{ value }}", }, ], }, }, }, }, # load balance backend { "name": "nat_type_lb_backend", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+load-balance \s+backend \s+(?P\S+) \s+weight \s+(?P\d+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} load-balance backend {{ ip }} weight {{ weight }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "load_balance": { "backend": { "ip": "{{ ip }}", "weight": "{{ weight }}", }, }, }, ], }, }, }, }, # load balance hash { "name": "nat_type_lb_hash", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+load-balance \s+hash \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} load-balance hash {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "load_balance": {"hash": "{{ value }}"}, }, ], }, }, }, }, # translation options { "name": "nat_type_translation_options", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+translation \s+options \s+(?Paddress-mapping|port-mapping) \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation options {{ opt }} {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": { - "options": { - "{{ opt }}": "{{ value }}", - }, + "{{ opt | replace(\"-\", \"_\") }}": "{{ value }}", }, }, ], }, }, }, }, # redirect port { "name": "nat_type_translation_redirect", "getval": re.compile( r""" ^set \s+(?Pnat|nat64|nat66) \s+(?Pdestination|source) \s+rule \s+(?P\S+) \s+translation \s+redirect \s+port \s+(?P\S+) $""", re.VERBOSE, ), "setval": "{{ nat }} {{ type }} rule {{ id }} translation redirect port {{ value }}", "result": { "{{ nat }}": { "{{ type }}": { "rule": [ { "id": "{{ id }}", "translation": { "redirect_port": "{{ value }}", }, }, ], }, }, }, }, - + { + "name": "nat64_match_mark", + "getval": re.compile( + r""" + ^set + \s+nat64 + \s+source + \s+rule + \s+(?P\S+) + \s+match + \s+mark + \s+(?P\d+) + $""", + re.VERBOSE, + ), + "setval": "nat64 source rule {{ id }} match mark {{ mark }}", + "result": { + "nat64": { + "source": { + "rule": [ + { + "id": "{{ id }}", + "match": {"mark": "{{ mark }}"}, + }, + ], + }, + }, + }, + }, + { + "name": "nat64_translation_pool_address", + "getval": re.compile( + r""" + ^set + \s+nat64 + \s+source + \s+rule + \s+(?P\S+) + \s+translation + \s+pool + \s+(?P\d+) + \s+address + \s+(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} address {{ value }}", + "result": { + "nat64": { + "source": { + "rule": [ + { + "id": "{{ id }}", + "translation": { + "pool": [{"id": "{{ pool_id }}", "address": "{{ value }}"}], + }, + }, + ], + }, + }, + }, + }, + { + "name": "nat64_translation_pool_description", + "getval": re.compile( + r""" + ^set + \s+nat64 + \s+source + \s+rule + \s+(?P\S+) + \s+translation + \s+pool + \s+(?P\d+) + \s+description + \s+(?P.+) + $""", + re.VERBOSE, + ), + "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} description {{ value }}", + "result": { + "nat64": { + "source": { + "rule": [ + { + "id": "{{ id }}", + "translation": { + "pool": [{"id": "{{ pool_id }}", "description": "{{ value }}"}], + }, + }, + ], + }, + }, + }, + }, + { + "name": "nat64_translation_pool_disable", + "getval": re.compile( + r""" + ^set + \s+nat64 + \s+source + \s+rule + \s+(?P\S+) + \s+translation + \s+pool + \s+(?P\d+) + \s+disable + $""", + re.VERBOSE, + ), + "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} disable", + "result": { + "nat64": { + "source": { + "rule": [ + { + "id": "{{ id }}", + "translation": { + "pool": [{"id": "{{ pool_id }}", "disable": True}], + }, + }, + ], + }, + }, + }, + }, + { + "name": "nat64_translation_pool_port", + "getval": re.compile( + r""" + ^set + \s+nat64 + \s+source + \s+rule + \s+(?P\S+) + \s+translation + \s+pool + \s+(?P\d+) + \s+port + \s+(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} port {{ value }}", + "result": { + "nat64": { + "source": { + "rule": [ + { + "id": "{{ id }}", + "translation": { + "pool": [{"id": "{{ pool_id }}", "port": "{{ value }}"}], + }, + }, + ], + }, + }, + }, + }, + { + "name": "nat64_translation_pool_protocol", + "getval": re.compile( + r""" + ^set + \s+nat64 + \s+source + \s+rule + \s+(?P\S+) + \s+translation + \s+pool + \s+(?P\d+) + \s+protocol + \s+(?P\S+) + $""", + re.VERBOSE, + ), + "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} protocol {{ value }}", + "result": { + "nat64": { + "source": { + "rule": [ + { + "id": "{{ id }}", + "translation": { + "pool": [{"id": "{{ pool_id }}", "protocol": "{{ value }}"}], + }, + }, + ], + }, + }, + }, + }, ] + # fmt: on