Page MenuHomeVyOS Platform

No OneTemporary

Size
483 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/README.md b/README.md
index bf6a7701..5e7fbc82 100644
--- a/README.md
+++ b/README.md
@@ -1,269 +1,270 @@
# VyOS Collection
[![codecov](https://codecov.io/gh/vyos/vyos.vyos/graph/badge.svg?token=J217GFD69W)](https://codecov.io/gh/vyos/vyos.vyos)
[![CI](https://github.com/vyos/vyos.vyos/actions/workflows/tests.yml/badge.svg?branch=main&event=schedule)](https://github.com/vyos/vyos.vyos/actions/workflows/tests.yml)
The Ansible VyOS collection includes a variety of Ansible content to help automate the management of VyOS network appliances.
This collection has been tested against VyOS 1.3.8, 1.4.1 and the current rolling release for 1.5. Where possible,
compatibility with older versions of VyOS are maintained but not guaranteed.
## Communication
* Join the VyOS forum:
* [FAQ](https://forum.vyos.io/faq): find answers to frequently asked questions.
* [Guides and How To](https://forum.vyos.io/c/howto-guies/27): find guides and how-to articles.
* [News & Announcements](https://forum.vyos.io/c/announcements/6): track project-wide announcements .
<!--start requires_ansible-->
## Ansible version compatibility
This collection has been tested against the following Ansible versions: **>=2.15.0**.
Plugins and modules within a collection may be tested with only specific Ansible versions.
A collection may contain metadata that identifies these versions.
PEP440 is the schema used to describe the versions of Ansible.
<!--end requires_ansible-->
### Supported connections
The VyOS collection supports ``network_cli`` connections.
## Included content
<!--start collection content-->
### Cliconf plugins
Name | Description
--- | ---
[vyos.vyos.vyos](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_cliconf.rst)|Use vyos cliconf to run command on VyOS platform
### Modules
Name | Description
--- | ---
[vyos.vyos.vyos_banner](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_banner_module.rst)|Manage multiline banners on VyOS devices
[vyos.vyos.vyos_bgp_address_family](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_bgp_address_family_module.rst)|BGP Address Family resource module
[vyos.vyos.vyos_bgp_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_bgp_global_module.rst)|BGP global resource module
[vyos.vyos.vyos_command](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_command_module.rst)|Run one or more commands on VyOS devices
[vyos.vyos.vyos_config](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_config_module.rst)|Manage VyOS configuration on remote device
[vyos.vyos.vyos_facts](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_facts_module.rst)|Get facts about vyos devices.
[vyos.vyos.vyos_file](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_file_module.rst)|Manage files, directories, and their ownership on VyOS devices
[vyos.vyos.vyos_firewall_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_global_module.rst)|Firewall global resource module
[vyos.vyos.vyos_firewall_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_interfaces_module.rst)|Firewall interfaces resource module
[vyos.vyos.vyos_firewall_rules](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_rules_module.rst)|Firewall rules resource module
[vyos.vyos.vyos_ha](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ha_module.rst)|Manage VRRP and load balancer configuration on VyOS
[vyos.vyos.vyos_hostname](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_hostname_module.rst)|Manages hostname resource module
[vyos.vyos.vyos_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_interfaces_module.rst)|Manages interface attributes of VyOS network devices.
[vyos.vyos.vyos_l3_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_l3_interfaces_module.rst)|Layer 3 interfaces resource module.
[vyos.vyos.vyos_lag_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lag_interfaces_module.rst)|LAG interfaces resource module
[vyos.vyos.vyos_lldp_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lldp_global_module.rst)|LLDP global resource module
[vyos.vyos.vyos_lldp_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lldp_interfaces_module.rst)|LLDP interfaces resource module
[vyos.vyos.vyos_logging_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_logging_global_module.rst)|Logging resource module
+[vyos.vyos.vyos_nat](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_nat_module.rst)|NAT resource module
[vyos.vyos.vyos_ntp_global](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ntp_global_module.rst)|NTP global resource module
[vyos.vyos.vyos_ospf_interfaces](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospf_interfaces_module.rst)|OSPF Interfaces Resource Module.
[vyos.vyos.vyos_ospfv2](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospfv2_module.rst)|OSPFv2 resource module
[vyos.vyos.vyos_ospfv3](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospfv3_module.rst)|OSPFv3 resource module
[vyos.vyos.vyos_ping](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ping_module.rst)|Tests reachability using ping from VyOS network devices
[vyos.vyos.vyos_prefix_lists](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_prefix_lists_module.rst)|Prefix-Lists resource module for VyOS
[vyos.vyos.vyos_route_maps](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_route_maps_module.rst)|Route Map resource module
[vyos.vyos.vyos_snmp_server](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_snmp_server_module.rst)|Manages snmp_server resource module
[vyos.vyos.vyos_static_routes](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_static_routes_module.rst)|Static routes resource module
[vyos.vyos.vyos_system](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_system_module.rst)|Run `set system` commands on VyOS devices
[vyos.vyos.vyos_user](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_user_module.rst)|Manage the collection of local users on VyOS device
[vyos.vyos.vyos_vlan](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_vlan_module.rst)|Manage VLANs on VyOS network devices
[vyos.vyos.vyos_vpn_ipsec](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_vpn_ipsec_module.rst)|Manages global IPsec (ike-group, esp-group, profile, authentication, options) attributes of VyOS network devices.
[vyos.vyos.vyos_vpn_ipsec_s2s](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_vpn_ipsec_s2s_module.rst)|Manages IPsec site-to-site VPN peers on VyOS network devices.
[vyos.vyos.vyos_vrf](https://github.com/vyos/vyos.vyos/blob/main/docs/vyos.vyos.vyos_vrf_module.rst)|VRF resource module
<!--end collection content-->
Click the ``Content`` button to see the list of content included in this collection.
## Installing this collection
You can install the VyOS collection with the Ansible Galaxy CLI:
ansible-galaxy collection install vyos.vyos
You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format:
```yaml
---
collections:
- name: vyos.vyos
```
## Using this collection
This collection includes [network resource modules](https://docs.ansible.com/ansible/latest/network/user_guide/network_resource_modules.html).
### Using modules from the VyOS collection in your playbooks
You can call modules by their Fully Qualified Collection Namespace (FQCN), such as `vyos.vyos.vyos_static_routes`.
The following example task replaces configuration changes in the existing configuration on a VyOS network device, using the FQCN:
```yaml
---
- name: Replace device configurations of listed static routes with provided
configurations
register: result
vyos.vyos.vyos_static_routes: &id001
config:
- address_families:
- afi: ipv4
routes:
- dest: 192.0.2.32/28
blackhole_config:
distance: 2
next_hops:
- forward_router_address: 192.0.2.7
- forward_router_address: 192.0.2.8
- forward_router_address: 192.0.2.9
state: replaced
```
**NOTE**: For Ansible 2.9, you may not see deprecation warnings when you run your playbooks with this collection. Use this documentation to track when a module is deprecated.
### See Also:
* [VyOS Platform Options](https://docs.ansible.com/ansible/latest/network/user_guide/platform_vyos.html)
* [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
## Contributing to this collection
We welcome community contributions to this collection. If you find problems, please open an issue or create a PR against the [VyOS collection repository](https://github.com/vyos/vyos.vyos). See [Contributing to VyOS](https://vyos.net/contribute/) for complete details.
You can also join us on:
- Forum - https://forum.vyos.io
See the [Contributing to VyOS](https://vyos.net/contribute/) for details on contributing to Ansible.
### Code of Conduct
This collection follows the Ansible project's
[Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html).
Please read and familiarize yourself with this document.
### Updating from resource module models
Some of our modules were templated using `resource_module_builder`, but some use
the newer [`cli_rm_builder`](https://github.com/ansible-network/cli_rm_builder)
which tempaltes baed on in-place device information, but also uses a new network
parsing engine designed to simplify and standardize the parsing of network
configuration.
#### Using older *resource_module_builder* modules
Last build was with a slightly-modified version of resource_module_builder.
This changes the calling parameters for the resources.
To update the collection from the resource module models, run the following command:
```bash
ansible-playbook -e rm_dest=`pwd` \
-e structure=collection \
-e collection_org=vyos \
-e collection_name=vyos \
-e model=../../../resource_module_models/models/vyos/firewall_rules/vyos_firewall_rules.yaml \
../../../resource_module_builder/site.yml
```
#### Using *cli_rm_builder* modules
The newer `cli_rm_builder` works similarly to the older `resource_module_builder`, but
pulls the information directly from the `DOCUMENTATION`, `EXAMPLES` and `RETURN`
blocks in the module itself.
To update the collection from the `cli_rm_builder` models, run the following command:
```bash
ansible-playbook -e rm_dest=`pwd` \
-e collection_org=vyos \
-e collection_name=vyos \
-e resource=bgp_address_family \
../../../cli_rm_builder/run.yml
```
Unlike the `resource_module_builder`, the `cli_rm_builder` does not require the `model` parameter. Instead, it uses the `resource` parameter to specify the resource to build.
### Testing playbooks
You can use `ANSIBLE_COLLECTIONS_PATH` to test the collection locally. For example:
```
ANSIBLE_COLLECTIONS_PATHS=~/my_dev_path ansible-playbook -i inventory.network test.yml
```
### Integration Tests
Integration tests are run using `ansible-test` and require that there be an inventory
defined (you can pass this in with `--inventory <filename>`) and that the system be
configured for access (recommended to use SSH keys).
Additionally:
- eth0 should be configured for `address dhcp` and should have an assigned address
on the local network
- eth1 and eth2 should be defined and uncofirgured (they'll be overwritten by the tests)
- eth3 and beyond should not be present or interface-related tests will fail
- when using VMs for testing, ensure that the interfaces don't use `virtio`, as it will supress
some interface configurations. `e1000e` is a good choice for testing.
- eth0 is also expected to show `duplex auto` and `speed auto` in the output of `show interfaces`,
however others are not due to the fact that they are repeatedly deleted and recreated
which causes the default values to be hidden.
## Changelogs
Change logs are available [here](https://github.com/vyos/vyos.vyos/blob/main/CHANGELOG.rst).
## Release notes
Release notes are available [here](https://github.com/vyos/vyos.vyos/blob/main/CHANGELOG.rst).
## Roadmap
<!-- Optional. Include the roadmap for this collection, and the proposed release/versioning strategy so users can anticipate the upgrade/update cycle. -->
Major Version | Ansible Support | VyOS Support | Details
--- | --- | --- | ---
4.1.0 | 2.15 | 1.1.2 | Final release for the 4.x series
5.0.0 | 2.16 | 1.1.2 | First relase under VyOS control as a separate collection
6.0.0 | 2.18 | 1.3.8 | *Planned* release for supporting VyOS 1.3.8+
7.0.0 | x.xx | 1.4.x | *Prospective* release deprecating incompatible 1.3.x modules
Note:
- Unreleased versions are not guaranteed to be released as described.
- Some modules may support a wider variety of versions depending upon the compatibility
with prior versions of VyOS.
- The roadmap is subject to change based on community feedback and contributions.
## More information
VyOS resources
- [Contributing to VyOS](https://vyos.net/contribute)
- [VyOS documentation](https://docs.vyos.io/en/latest/)
- [VyOS forum](https://forum.vyos.io)
Ansible Resources
- [Ansible network resources](https://docs.ansible.com/ansible/latest/network/getting_started/network_resources.html)
- [Ansible Collection overview](https://github.com/ansible-collections/overview)
- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html)
- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html)
- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html)
## Licensing
GNU General Public License v3.0 or later.
See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.
diff --git a/changelogs/fragments/t8323-vyos_nat.yml b/changelogs/fragments/t8323-vyos_nat.yml
new file mode 100644
index 00000000..ae71d4a9
--- /dev/null
+++ b/changelogs/fragments/t8323-vyos_nat.yml
@@ -0,0 +1,3 @@
+---
+minor_changes:
+ - vyos_nat - Add new module to support NAT configuration.
diff --git a/docs/vyos.vyos.vyos_nat_module.rst b/docs/vyos.vyos.vyos_nat_module.rst
new file mode 100644
index 00000000..7a8045ee
--- /dev/null
+++ b/docs/vyos.vyos.vyos_nat_module.rst
@@ -0,0 +1,3723 @@
+.. _vyos.vyos.vyos_nat_module:
+
+
+******************
+vyos.vyos.vyos_nat
+******************
+
+**NAT resource module**
+
+
+Version added: 6.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- This module manages NAT configuration on devices running VyOS.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="7">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="7">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>The desired configuration for the NAT resource represented as a dictionary.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="6">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>nat</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for NAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>cgnat</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for Carrier Grade NAT (CGNAT).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>log_allocation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Log CGNAT address allocations.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>pool</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for CGNAT pools.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>external</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of external NAT pools for CGNAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>external_port_range</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port range to use for NAT translations in this external pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the external NAT pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>per_user_limit</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Per-user limit configuration for the external pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Maximum number of ports allocated per user.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>range</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of external IP address ranges in the pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>seq</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Optional sequence number for this range entry.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>value</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address, prefix, or range (e.g. 203.0.113.0/24 or 203.0.113.1-203.0.113.60).</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>internal</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of internal NAT pools for CGNAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the internal NAT pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>range</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of internal IP addresses or prefixes in the pool.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rule</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of CGNAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Rule number for CGNAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Source pool configuration for CGNAT translation.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>pool</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Source pool name to use for CGNAT translation.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation pool configuration for CGNAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>pool</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation pool name to use for CGNAT translation.</div>
+ </td>
+ </tr>
+
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for destination NAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rule</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of destination NAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>User-friendly description of the destination NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Match criteria for destination NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address, subnet, or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Address group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>domain_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Domain group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>fqdn</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Fully qualified domain name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mac_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>MAC address group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>network_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Network group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port group name to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable this destination NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>exclude</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Exclude packets matching this rule from NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Rule number for destination NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>inbound_interface</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Match inbound interface.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface group to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface name to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>load_balance</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Load balancing configuration for this NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>backend</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of backends to load-balance across. Weights should sum to 100.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ip</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address of the backend translation target.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>weight</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Relative weight (1-100) for this backend&#x27;s share of load-balanced traffic.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>hash</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>source-address</li>
+ <li>destination-address</li>
+ <li>source-port</li>
+ <li>destination-port</li>
+ <li>random</li>
+ </ul>
+ </td>
+ <td>
+ <div>Fields to hash on for load balancing. Mutually exclusive with <em>translation.address</em>.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>log</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Log packets hitting this rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>packet_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Packet type to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>protocol</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Protocol to NAT (default all).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation configuration for destination NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address or prefix to translate destination to.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address_mapping</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>random</li>
+ <li>persistent</li>
+ </ul>
+ </td>
+ <td>
+ <div>Address mapping mode for translation.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to translate destination port to.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port_mapping</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>random</li>
+ <li>none</li>
+ </ul>
+ </td>
+ <td>
+ <div>Port mapping mode for translation.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>redirect_port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Redirect to local port number.</div>
+ </td>
+ </tr>
+
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for source NAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rule</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of source NAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>User-friendly description of the source NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Destination match criteria for source NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address, subnet, or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Address group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>domain_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Domain group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>fqdn</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Fully qualified domain name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mac_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>MAC address group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>network_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Network group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port group name to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable this source NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>exclude</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Exclude packets matching this rule from NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Rule number for source NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>load_balance</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Load balancing configuration for this NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>backend</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of backends to load-balance across. Weights should sum to 100.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ip</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address of the backend translation target.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>weight</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Relative weight (1-100) for this backend&#x27;s share of load-balanced traffic.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>hash</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>source-address</li>
+ <li>destination-address</li>
+ <li>source-port</li>
+ <li>destination-port</li>
+ <li>random</li>
+ </ul>
+ </td>
+ <td>
+ <div>Fields to hash on for load balancing. Mutually exclusive with <em>translation.address</em>.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>log</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Log packets hitting this rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>outbound_interface</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Match outbound interface.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface group to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface name to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>packet_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Packet type to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>protocol</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Protocol to NAT (default all).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Source match criteria for source NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address, subnet, or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Address group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>domain_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Domain group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>fqdn</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Fully qualified domain name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mac_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>MAC address group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>network_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Network group name to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port_group</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port group name to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation configuration for source NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address or prefix to translate source to. Use masquerade to masquerade as the outbound interface address.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address_mapping</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>random</li>
+ <li>persistent</li>
+ </ul>
+ </td>
+ <td>
+ <div>Address mapping mode for translation.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to translate source port to.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port_mapping</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>random</li>
+ <li>none</li>
+ </ul>
+ </td>
+ <td>
+ <div>Port mapping mode for translation.</div>
+ </td>
+ </tr>
+
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>static</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for static one-to-one NAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rule</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of static NAT rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>User-friendly description of the static NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Match criteria for static NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address, subnet, or range to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Rule number for static NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>inbound_interface</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Inbound interface that this static NAT rule applies to.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>log</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Log packets hitting this static NAT rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation configuration for static NAT.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP address or prefix to translate to.</div>
+ </td>
+ </tr>
+
+
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="6">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>nat64</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for NAT64 (IPv6-to-IPv4) rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for NAT64 source rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rule</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of NAT64 source rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>User-friendly description of the NAT64 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable this NAT64 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Rule number for NAT64 source rule (1-999999).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>match</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Match criteria for NAT64 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mark</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Match on firewall mark value (1-2147483647).</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 source prefix to match for NAT64 translation.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>prefix</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 source prefix to match (h:h:h:h:h:h:h:h/x).</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation configuration for NAT64 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>pool</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of translation pools for NAT64.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv4 address or prefix for translation pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>User-friendly description of the translation pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable this translation pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Pool number (1-999999).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range for translation pool.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>protocol</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>icmp</li>
+ <li>tcp</li>
+ <li>udp</li>
+ </ul>
+ </td>
+ <td>
+ <div>Protocol for this translation pool entry.</div>
+ </td>
+ </tr>
+
+
+
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="6">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>nat66</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for NAT66 (IPv6-to-IPv6) rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for NAT66 destination rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rule</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of NAT66 destination rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>User-friendly description of the NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Match criteria for NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 address or prefix to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable this NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>exclude</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Exclude packets matching this rule from NAT66.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Rule number for NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>inbound_interface</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Inbound interface to match for NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface name to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>log</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Log packets hitting this NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>protocol</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Protocol to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Source match criteria for NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 source address or prefix to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Source port number or range to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation configuration for NAT66 destination rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 address or prefix to translate destination to.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to translate destination port to.</div>
+ </td>
+ </tr>
+
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Configuration for NAT66 source rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rule</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of NAT66 source rules.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>User-friendly description of the NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>destination</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Destination match criteria for NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Destination port number or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>prefix</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 destination prefix to match (h:h:h:h:h:h:h:h/x).</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>disable</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable this NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>exclude</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Exclude packets matching this rule from NAT66.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Rule number for NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>log</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Log packets hitting this NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>outbound_interface</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Outbound interface to match for NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface name to match.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>protocol</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Protocol to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>source</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Source match criteria for NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Source port number or range to match.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>prefix</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 source prefix to match (h:h:h:h:h:h:h:h/x).</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translation</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Translation configuration for NAT66 source rule.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 address or prefix to translate source to. Use masquerade to masquerade as the outbound interface address.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>port</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Port number or range to translate source port to.</div>
+ </td>
+ </tr>
+
+
+
+
+
+ <tr>
+ <td colspan="7">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>running_config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>This option is used only with state <em>parsed</em>.</div>
+ <div>The value of this option should be the output received from the VyOS device by executing the command <b>show configuration commands | match &#x27;nat&#x27;</b>.</div>
+ <div>The state <em>parsed</em> reads the configuration from <code>show configuration commands | match &#x27;nat&#x27;</code> and transforms it into Ansible structured data as per the module argspec. The value is then returned in the <em>parsed</em> key within the result.</div>
+ <div>The state <em>replaced</em> replaces only the provided configuration, while <em>overridden</em> removes any existing NAT configuration not specified in <em>config</em>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="7">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>deleted</li>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>overridden</li>
+ <li>replaced</li>
+ <li>gathered</li>
+ <li>rendered</li>
+ <li>parsed</li>
+ </ul>
+ </td>
+ <td>
+ <div>The state the configuration should be left in.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+Notes
+-----
+
+.. note::
+ - Tested against VyOS 1.4.3 and 1.5.0.
+ - This module works with connection ``network_cli``.
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ # Using merged - configure CGNAT
+ - name: Merge CGNAT configuration
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ cgnat:
+ log_allocation: true
+ pool:
+ external:
+ - name: ext-pool-1
+ external_port_range: "10000-20000"
+ per_user_limit:
+ port: "200"
+ range:
+ - value: 203.0.113.0/24
+ internal:
+ - name: int-pool-1
+ range:
+ - 10.0.0.0/24
+ rule:
+ - id: 1
+ source:
+ pool: int-pool-1
+ translation:
+ pool: ext-pool-1
+ state: merged
+
+ # Using merged - configure destination NAT
+ - name: Merge destination NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Web server NAT"
+ protocol: tcp
+ log: true
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: merged
+
+ # Using merged - configure source NAT
+ - name: Merge source NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ source:
+ rule:
+ - id: 200
+ description: "Outbound masquerade"
+ protocol: tcp
+ log: true
+ outbound_interface:
+ name: eth0
+ translation:
+ address: masquerade
+ state: merged
+
+ # Using merged - configure static NAT
+ - name: Merge static NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ static:
+ rule:
+ - id: 300
+ description: "Static mapping"
+ inbound_interface: eth2
+ destination:
+ address: 198.51.100.20
+ translation:
+ address: 192.168.1.20
+ log: true
+ state: merged
+
+ # Using merged - configure NAT64
+ - name: Merge NAT64 source rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat64:
+ source:
+ rule:
+ - id: 10
+ description: "NAT64 example"
+ source:
+ prefix: 2001:db8::/96
+ match:
+ mark: 100
+ translation:
+ pool:
+ - id: 1
+ address: 192.168.100.10
+ port: "1-65535"
+ protocol: udp
+ state: merged
+
+ # Using merged - configure NAT66
+ - name: Merge NAT66 destination rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat66:
+ destination:
+ rule:
+ - id: 20
+ description: "NAT66 DNAT"
+ protocol: tcp
+ inbound_interface:
+ name: eth1
+ destination:
+ address: 2001:db8::1
+ translation:
+ address: 2001:db8:1::10
+ port: "8443"
+ state: merged
+
+ # Using replaced - replace specific NAT rules
+ - name: Replace destination NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Replaced web server NAT"
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "443"
+ translation:
+ address: 192.168.1.10
+ port: "8443"
+ state: replaced
+
+ # Using overridden - override entire NAT configuration
+ - name: Override entire NAT configuration
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Only rule after override"
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: overridden
+
+ # Using deleted - delete all NAT configuration
+ - name: Delete all NAT configuration
+ vyos.vyos.vyos_nat:
+ state: deleted
+
+ # Using deleted - delete specific NAT rules
+ - name: Delete specific NAT rules
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ source:
+ rule:
+ - id: 200
+ nat64:
+ source:
+ rule:
+ - id: 10
+ state: deleted
+
+ # Using gathered
+ - name: Gather NAT configuration from device
+ vyos.vyos.vyos_nat:
+ state: gathered
+
+ # Using rendered
+ - name: Render NAT configuration offline
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Rendered rule"
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: rendered
+
+ # Using parsed
+ - name: Parse NAT configuration from file
+ vyos.vyos.vyos_nat:
+ running_config: "{{ lookup('file', './nat_config.cfg') }}"
+ state: parsed
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>The resulting configuration after module execution.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when <em>state</em> is <code>merged</code>, <code>replaced</code>, <code>overridden</code> or <code>deleted</code></td>
+ <td>
+ <div>The configuration prior to the module execution.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when <em>state</em> is <code>merged</code>, <code>replaced</code>, <code>overridden</code> or <code>deleted</code></td>
+ <td>
+ <div>The set of commands pushed to the remote device.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">[&quot;set nat destination rule 100 description &#x27;Web server NAT&#x27;&quot;, &#x27;set nat destination rule 100 protocol tcp&#x27;, &#x27;set nat destination rule 100 inbound-interface name eth2&#x27;, &#x27;set nat destination rule 100 destination address 198.51.100.10&#x27;, &#x27;set nat destination rule 100 translation address 192.168.1.10&#x27;, &#x27;delete nat source rule 200&#x27;]</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when <em>state</em> is <code>gathered</code></td>
+ <td>
+ <div>Facts about the network resource gathered from the remote device as structured data.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>parsed</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when <em>state</em> is <code>parsed</code></td>
+ <td>
+ <div>The device native config provided in <em>running_config</em> option parsed into structured data as per module argspec.</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">This output will always be in the same format as the module argspec.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rendered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when <em>state</em> is <code>rendered</code></td>
+ <td>
+ <div>The provided configuration in the task rendered in device-native format (offline).</div>
+ <br/>
+ <div style="font-size: smaller"><b>Sample:</b></div>
+ <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">[&quot;set nat destination rule 100 description &#x27;Web server NAT&#x27;&quot;, &#x27;set nat destination rule 100 protocol tcp&#x27;, &#x27;set nat destination rule 100 inbound-interface name eth2&#x27;, &#x27;set nat destination rule 100 destination address 198.51.100.10&#x27;, &#x27;set nat destination rule 100 translation address 192.168.1.10&#x27;]</div>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- Evgeny Molotkov (@omnom62)
diff --git a/meta/runtime.yml b/meta/runtime.yml
index c1065dbd..093e129d 100644
--- a/meta/runtime.yml
+++ b/meta/runtime.yml
@@ -1,76 +1,78 @@
---
requires_ansible: ">=2.15.0"
plugin_routing:
modules:
banner:
redirect: vyos.vyos.vyos_banner
bgp_global:
redirect: vyos.vyos.vyos_bgp_global
bgp_address_family:
redirect: vyos.vyos.vyos_bgp_address_family
command:
redirect: vyos.vyos.vyos_command
config:
redirect: vyos.vyos.vyos_config
facts:
redirect: vyos.vyos.vyos_facts
file:
redirect: vyos.vyos.vyos_file
firewall_global:
redirect: vyos.vyos.vyos_firewall_global
firewall_interfaces:
redirect: vyos.vyos.vyos_firewall_interfaces
firewall_rules:
redirect: vyos.vyos.vyos_firewall_rules
hostname:
redirect: vyos.vyos.vyos_hostname
interfaces:
redirect: vyos.vyos.vyos_interfaces
l3_interfaces:
redirect: vyos.vyos.vyos_l3_interfaces
lag_interfaces:
redirect: vyos.vyos.vyos_lag_interfaces
lldp_global:
redirect: vyos.vyos.vyos_lldp_global
lldp_interfaces:
redirect: vyos.vyos.vyos_lldp_interfaces
logging:
tombstone:
removal_version: 6.0.0
warning_text: use vyos_logging_global instead
vyos_logging:
tombstone:
removal_version: 6.0.0
warning_text: use vyos_logging_global instead
logging_global:
redirect: vyos.vyos.vyos_logging_global
ntp_global:
redirect: vyos.vyos.vyos_ntp_global
+ nat:
+ redirect: vyos.vyos.vyos_nat
ospfv2:
redirect: vyos.vyos.vyos_ospfv2
ospfv3:
redirect: vyos.vyos.vyos_ospfv3
ospf_interfaces:
redirect: vyos.vyos.vyos_ospf_interfaces
ping:
redirect: vyos.vyos.vyos_ping
prefix_lists:
redirect: vyos.vyos.vyos_prefix_lists
snmp_server:
redirect: vyos.vyos.vyos_snmp_server
static_routes:
redirect: vyos.vyos.vyos_static_routes
system:
redirect: vyos.vyos.vyos_system
user:
redirect: vyos.vyos.vyos_user
vlan:
redirect: vyos.vyos.vyos_vlan
vrf:
redirect: vyos.vyos.vyos_vrf
vpn_ipsec:
redirect: vyos.vyos.vyos_vpn_ipsec
vpn_ipsec_s2s:
redirect: vyos.vyos.vyos_vpn_ipsec_s2s
ha:
redirect: vyos.vyos.vyos_ha
diff --git a/plugins/action/nat.py b/plugins/action/nat.py
new file mode 120000
index 00000000..331a791f
--- /dev/null
+++ b/plugins/action/nat.py
@@ -0,0 +1 @@
+vyos.py
\ No newline at end of file
diff --git a/plugins/module_utils/network/vyos/argspec/nat/__init__.py b/plugins/module_utils/network/vyos/argspec/nat/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/plugins/module_utils/network/vyos/argspec/nat/nat.py b/plugins/module_utils/network/vyos/argspec/nat/nat.py
new file mode 100644
index 00000000..6f81c53c
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/nat/nat.py
@@ -0,0 +1,652 @@
+# -*- coding: utf-8 -*-
+# 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",
+ "options": {
+ "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": "str",
+ },
+ },
+ },
+ "range": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "value": {
+ "type": "str",
+ "required": True,
+ },
+ "seq": {
+ "type": "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",
+ },
+ "packet_type": {
+ "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",
+ },
+ "address_group": {
+ "type": "str",
+ },
+ "domain_group": {
+ "type": "str",
+ },
+ "mac_group": {
+ "type": "str",
+ },
+ "network_group": {
+ "type": "str",
+ },
+ "port_group": {
+ "type": "str",
+ },
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ "redirect_port": {
+ "type": "str",
+ },
+ "address_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "persistent",
+ ],
+ },
+ "port_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "none",
+ ],
+ },
+ },
+ },
+ "load_balance": {
+ "type": "dict",
+ "options": {
+ "backend": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "ip": {"type": "str"},
+ "weight": {"type": "int"},
+ },
+ },
+ "hash": {
+ "type": "list",
+ "elements": "str",
+ "choices": [
+ "source-address",
+ "destination-address",
+ "source-port",
+ "destination-port",
+ "random",
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "description": {
+ "type": "str",
+ },
+ "protocol": {
+ "type": "str",
+ },
+ "packet_type": {
+ "type": "str",
+ },
+ "exclude": {
+ "type": "bool",
+ },
+ "log": {
+ "type": "bool",
+ },
+ "disable": {
+ "type": "bool",
+ },
+ "outbound_interface": {
+ "type": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ },
+ "group": {
+ "type": "str",
+ },
+ },
+ },
+ "destination": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "fqdn": {
+ "type": "str",
+ },
+ "address_group": {
+ "type": "str",
+ },
+ "domain_group": {
+ "type": "str",
+ },
+ "mac_group": {
+ "type": "str",
+ },
+ "network_group": {
+ "type": "str",
+ },
+ "port_group": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ },
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "address": {"type": "str"},
+ "fqdn": {"type": "str"},
+ "port": {"type": "str"},
+ "address_group": {"type": "str"},
+ "domain_group": {"type": "str"},
+ "mac_group": {"type": "str"},
+ "network_group": {"type": "str"},
+ "port_group": {"type": "str"},
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ "address_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "persistent",
+ ],
+ },
+ "port_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "none",
+ ],
+ },
+ },
+ },
+ "load_balance": {
+ "type": "dict",
+ "options": {
+ "backend": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "ip": {"type": "str"},
+ "weight": {"type": "int"},
+ },
+ },
+ "hash": {
+ "type": "list",
+ "elements": "str",
+ "choices": [
+ "source-address",
+ "destination-address",
+ "source-port",
+ "destination-port",
+ "random",
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "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",
+ },
+ "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/__init__.py b/plugins/module_utils/network/vyos/config/nat/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/plugins/module_utils/network/vyos/config/nat/nat.py b/plugins/module_utils/network/vyos/config/nat/nat.py
new file mode 100644
index 00000000..a2800914
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/nat/nat.py
@@ -0,0 +1,577 @@
+# -*- coding: utf-8 -*-
+# 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
+
+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
+
+
+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):
+ if self.state not in ["parsed", "gathered"]:
+ self.generate_commands()
+ self.run_commands()
+ return self.result
+
+ def generate_commands(self):
+ wantd = deepcopy(self.want)
+ haved = deepcopy(self.have)
+
+ if self.state == "merged":
+ wantd = combine(haved, wantd, recursive=True)
+
+ if self.state == "deleted":
+ if not wantd:
+ for nat_type in haved:
+ self.commands.append(f"delete {nat_type}")
+ return
+ self._list_to_dict(wantd)
+ self._list_to_dict(haved)
+ self._delete_nat_objects(wantd, haved, only_missing=False)
+ return
+
+ self._list_to_dict(wantd)
+ self._list_to_dict(haved)
+
+ if self.state == "replaced":
+ self._delete_nat_objects(wantd, haved, only_missing=False)
+ self._set_commands(wantd, haved)
+ elif self.state == "overridden":
+ self._delete_nat_objects(wantd, haved, only_missing=True)
+ self._delete_nat_objects(wantd, haved, only_missing=False)
+ self._set_commands(wantd, haved)
+ else:
+ self._set_commands(wantd, haved)
+
+ self.commands = list(dict.fromkeys(self.commands))
+
+ # -------------------------------------------------------------------------
+ # List → keyed dict conversion
+ # -------------------------------------------------------------------------
+
+ def _list_to_dict(self, config):
+ nat = config.get("nat", {})
+ cgnat = nat.get("cgnat", {})
+
+ pool = cgnat.get("pool", {})
+ for ptype in ("external", "internal"):
+ entries = pool.get(ptype)
+ if isinstance(entries, list):
+ pool[ptype] = {item["name"]: item for item in entries}
+
+ rules = cgnat.get("rule")
+ if isinstance(rules, list):
+ cgnat["rule"] = {r["id"]: r for r in rules}
+
+ for section in ("destination", "source", "static"):
+ rules = nat.get(section, {}).get("rule")
+ if isinstance(rules, list):
+ nat[section]["rule"] = {r["id"]: r for r in rules}
+
+ nat64 = config.get("nat64", {})
+ rules = nat64.get("source", {}).get("rule")
+ if isinstance(rules, list):
+ nat64["source"]["rule"] = {r["id"]: r for r in rules}
+ for rule in nat64["source"]["rule"].values():
+ pools = rule.get("translation", {}).get("pool")
+ if isinstance(pools, list):
+ rule["translation"]["pool"] = {p["id"]: p for p in pools}
+
+ nat66 = config.get("nat66", {})
+ for section in ("destination", "source"):
+ rules = nat66.get(section, {}).get("rule")
+ if isinstance(rules, list):
+ nat66[section]["rule"] = {r["id"]: r for r in rules}
+
+ # -------------------------------------------------------------------------
+ # Top-level dispatch
+ # -------------------------------------------------------------------------
+
+ def _set_commands(self, wantd, haved):
+ self._compare_cgnat_global(wantd, haved)
+ self._compare_cgnat_pools(wantd, haved)
+ self._compare_cgnat_rules(wantd, haved)
+
+ for section in ("destination", "source", "static"):
+ self._compare_nat_rules("nat", section, wantd, haved)
+
+ self._compare_nat_rules("nat64", "source", wantd, haved)
+
+ for section in ("destination", "source"):
+ self._compare_nat_rules("nat66", section, wantd, haved)
+
+ self.commands = list(dict.fromkeys(self.commands))
+
+ # -------------------------------------------------------------------------
+ # Delete helpers
+ # -------------------------------------------------------------------------
+
+ def _delete_nat_objects(self, wantd, haved, only_missing=False):
+ """
+ Generate delete commands for NAT objects.
+ only_missing=False: delete objects present in both want and have (when different)
+ only_missing=True: delete objects present in have but absent from want
+ """
+ for nat_type in haved:
+ want_nat = wantd.get(nat_type, {})
+ have_nat = haved[nat_type]
+
+ if only_missing and nat_type not in wantd:
+ self.commands.append(f"delete {nat_type}")
+ continue
+
+ for section in have_nat:
+ want_section = want_nat.get(section, {})
+ have_section = have_nat[section]
+
+ if only_missing and section not in want_nat:
+ self.commands.append(
+ f"delete {nat_type} {section.replace('_', '-')}",
+ )
+ continue
+
+ if section == "cgnat":
+ for pool_type in ("external", "internal"):
+ want_pools = want_section.get("pool", {}).get(pool_type, {})
+ have_pools = have_section.get("pool", {}).get(pool_type, {})
+ for name in have_pools:
+ if only_missing and name not in want_pools:
+ self.commands.append(
+ f"delete {nat_type} cgnat pool {pool_type} {name}",
+ )
+
+ elif not only_missing and name in want_pools:
+ if self.state == "deleted" or want_pools[name] != have_pools[name]:
+ self.commands.append(
+ f"delete {nat_type} cgnat pool {pool_type} {name}",
+ )
+ want_rules = want_section.get("rule", {})
+ have_rules = have_section.get("rule", {})
+ for rid in have_rules:
+ if only_missing and rid not in want_rules:
+ self.commands.append(f"delete {nat_type} cgnat rule {rid}")
+
+ elif not only_missing and rid in want_rules:
+ if self.state == "deleted" or want_rules[rid] != have_rules[rid]:
+ self.commands.append(f"delete {nat_type} cgnat rule {rid}")
+ else:
+ want_rules = want_section.get("rule", {})
+ have_rules = have_section.get("rule", {})
+ cli_section = section.replace("_", "-")
+ for rid in have_rules:
+ if only_missing and rid not in want_rules:
+ self.commands.append(
+ f"delete {nat_type} {cli_section} rule {rid}",
+ )
+
+ elif not only_missing and rid in want_rules:
+ if self.state == "deleted" or want_rules[rid] != have_rules[rid]:
+ self.commands.append(
+ f"delete {nat_type} {cli_section} rule {rid}",
+ )
+
+ # -------------------------------------------------------------------------
+ # CGNAT
+ # -------------------------------------------------------------------------
+
+ def _compare_cgnat_global(self, wantd, haved):
+ if self.state in ("replaced", "overridden") and not wantd.get("nat", {}).get("cgnat"):
+ return
+ w = wantd.get("nat", {}).get("cgnat", {}).get("log_allocation")
+ h = haved.get("nat", {}).get("cgnat", {}).get("log_allocation")
+ if bool(w) != bool(h):
+ self.addcmd(
+ {"nat": {"cgnat": {"log_allocation": True}}},
+ "cgnat_log_allocation",
+ not bool(w),
+ )
+
+ def _compare_cgnat_pools(self, wantd, haved):
+ want_ext = wantd.get("nat", {}).get("cgnat", {}).get("pool", {}).get("external", {})
+ have_ext = haved.get("nat", {}).get("cgnat", {}).get("pool", {}).get("external", {})
+ want_int = wantd.get("nat", {}).get("cgnat", {}).get("pool", {}).get("internal", {})
+ have_int = haved.get("nat", {}).get("cgnat", {}).get("pool", {}).get("internal", {})
+
+ scope = self.state in ("replaced", "overridden")
+ ext_names = set(want_ext) if scope else set(want_ext) | set(have_ext)
+ int_names = set(want_int) if scope else set(want_int) | set(have_int)
+
+ for name in ext_names:
+ w = want_ext.get(name, {})
+ h = have_ext.get(name, {})
+ if scope and w != h:
+ h = {}
+ self._compare_external_pool(name, w, h)
+
+ for name in int_names:
+ w = want_int.get(name, {})
+ h = have_int.get(name, {})
+ if scope and w != h:
+ h = {}
+ self._compare_internal_pool(name, w, h)
+
+ def _compare_external_pool(self, name, want, have):
+ w = want.get("external_port_range")
+ h = have.get("external_port_range")
+ if w != h:
+ if w:
+ self.addcmd({"name": name, "range": w}, "cgnat_pool_external_port_range", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"name": name, "range": h}, "cgnat_pool_external_port_range", True)
+
+ w = want.get("per_user_limit", {}).get("port")
+ h = have.get("per_user_limit", {}).get("port")
+ if w != h:
+ if w:
+ self.addcmd({"name": name, "limit": w}, "cgnat_pool_external_per_user", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"name": name, "limit": h}, "cgnat_pool_external_per_user", True)
+
+ want_ranges = {(r["value"] if isinstance(r, dict) else r): r for r in want.get("range", [])}
+ have_ranges = {(r["value"] if isinstance(r, dict) else r): r for r in have.get("range", [])}
+ for val, rng in want_ranges.items():
+ existing = have_ranges.get(val)
+ if existing is None or existing != rng:
+ seq = rng.get("seq") if isinstance(rng, dict) else None
+ self.addcmd(
+ {"name": name, "range": val, "seq": seq},
+ "cgnat_pool_external_range",
+ False,
+ )
+
+ if self.state in ("replaced", "overridden"):
+ for val in have_ranges:
+ if val not in want_ranges:
+ self.addcmd({"name": name, "range": val}, "cgnat_pool_external_range", True)
+
+ def _compare_internal_pool(self, name, want, have):
+ want_ranges = set(want.get("range", []))
+ have_ranges = set(have.get("range", []))
+
+ for rng in want_ranges - have_ranges:
+ self.addcmd({"name": name, "range": rng}, "cgnat_pool_internal_range", False)
+
+ if self.state in ("replaced", "overridden"):
+ for rng in have_ranges - want_ranges:
+ self.addcmd({"name": name, "range": rng}, "cgnat_pool_internal_range", True)
+
+ def _compare_cgnat_rules(self, wantd, haved):
+ want_rules = wantd.get("nat", {}).get("cgnat", {}).get("rule", {})
+ have_rules = haved.get("nat", {}).get("cgnat", {}).get("rule", {})
+
+ rids = (
+ set(want_rules)
+ if self.state in ("replaced", "overridden")
+ else set(want_rules) | set(have_rules)
+ )
+
+ for rid in rids:
+ w = want_rules.get(rid, {})
+ h = have_rules.get(rid, {})
+
+ if self.state in ("replaced", "overridden") and w != h:
+ h = {}
+
+ w_src = w.get("source", {}).get("pool")
+ h_src = h.get("source", {}).get("pool")
+ if w_src != h_src:
+ if w_src:
+ self.addcmd({"id": rid, "pool": w_src}, "cgnat_rule_source_pool", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"id": rid, "pool": h_src}, "cgnat_rule_source_pool", True)
+
+ w_tr = w.get("translation", {}).get("pool")
+ h_tr = h.get("translation", {}).get("pool")
+ if w_tr != h_tr:
+ if w_tr:
+ self.addcmd({"id": rid, "pool": w_tr}, "cgnat_rule_translation_pool", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"id": rid, "pool": h_tr}, "cgnat_rule_translation_pool", True)
+
+ # -------------------------------------------------------------------------
+ # NAT / NAT64 / NAT66 rules
+ # -------------------------------------------------------------------------
+
+ def _compare_nat_rules(self, nat_type, section, wantd, haved):
+ want_rules = wantd.get(nat_type, {}).get(section, {}).get("rule", {})
+ have_rules = haved.get(nat_type, {}).get(section, {}).get("rule", {})
+
+ rids = (
+ set(want_rules)
+ if self.state in ("replaced", "overridden")
+ else set(want_rules) | set(have_rules)
+ )
+
+ for rid in rids:
+ w = want_rules.get(rid, {})
+ h = have_rules.get(rid, {})
+ if self.state in ("replaced", "overridden") and w != h:
+ h = {}
+ if w == h and self.state != "rendered":
+ continue
+ self._compare_rule(nat_type, section, rid, w, h)
+
+ def _compare_rule(self, nat_type, section, rid, want, have):
+ ctx = {"nat": nat_type, "type": section, "id": rid}
+
+ want_lb = want.get("load_balance") or {}
+ have_lb = have.get("load_balance") or {}
+ want_trans_addr = (want.get("translation") or {}).get("address")
+ have_trans_addr = (have.get("translation") or {}).get("address")
+ if want_lb and want_trans_addr is not None:
+ self._module.fail_json(
+ msg="translation.address and load_balance are mutually exclusive",
+ )
+ if self.state == "merged":
+ if want_lb and have_trans_addr is not None:
+ self._module.fail_json(
+ msg=(
+ "Cannot add load_balance to a rule that already has translation.address with "
+ "state=merged; use state=replaced or state=overridden"
+ ),
+ )
+ if want_trans_addr is not None and have_lb:
+ self._module.fail_json(
+ msg=(
+ "Cannot add translation.address to a rule that already has load_balance with "
+ "state=merged; use state=replaced or state=overridden"
+ ),
+ )
+
+ for field in set(want) | set(have):
+ if field == "inbound_interface":
+ continue
+ val = want.get(field) if field in want else have.get(field)
+ if isinstance(val, bool):
+ self._cmp_bool(want, have, field, ctx, f"nat_type_{field}")
+ elif isinstance(val, str):
+ self._cmp_scalar(want, have, field, ctx, f"nat_type_{field}")
+
+ self._cmp_interface(want, have, ctx, nat_type, section)
+ self._cmp_outbound_interface(want, have, ctx)
+ for atype in ("destination", "source"):
+ self._cmp_addr_sub(want, have, atype, ctx)
+ self._cmp_translation(want, have, ctx)
+ self._cmp_match_mark(want, have, ctx)
+ self._cmp_nat64_pools(want, have, ctx)
+ self._cmp_load_balance(want, have, ctx)
+
+ # -------------------------------------------------------------------------
+ # Field-level helpers
+ # -------------------------------------------------------------------------
+
+ def _cmp_scalar(self, want, have, field, ctx, parser):
+ w = want.get(field)
+ h = have.get(field)
+ if w != h:
+ if w is not None:
+ self.addcmd(dict(ctx, **{field: w}), parser, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, **{field: h}), parser, True)
+
+ def _cmp_bool(self, want, have, field, ctx, parser):
+ w = bool(want.get(field))
+ h = bool(have.get(field))
+ if w != h:
+ if w:
+ self.addcmd(dict(ctx), parser, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx), parser, True)
+
+ def _cmp_interface(self, want, have, ctx, nat_type, section):
+ iface_w = want.get("inbound_interface")
+ iface_h = have.get("inbound_interface")
+ if iface_w == iface_h:
+ return
+
+ if nat_type == "nat" and section == "static":
+ if iface_w:
+ self.addcmd(dict(ctx, value=iface_w), "nat_static_inbound_interface", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h), "nat_static_inbound_interface", True)
+ return
+
+ iface_w = iface_w or {}
+ iface_h = iface_h or {}
+
+ if nat_type == "nat":
+ parser_name = "nat_inbound_interface_name"
+ parser_group = "nat_inbound_interface_group"
+ else:
+ parser_name = "nat6x_inbound_interface"
+ parser_group = "nat6x_inbound_interface"
+
+ if iface_w.get("name") != iface_h.get("name"):
+ if iface_w.get("name"):
+ self.addcmd(dict(ctx, value=iface_w["name"]), parser_name, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h["name"]), parser_name, True)
+
+ if nat_type == "nat" and iface_w.get("group") != iface_h.get("group"):
+ if iface_w.get("group"):
+ self.addcmd(dict(ctx, value=iface_w["group"]), parser_group, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h["group"]), parser_group, True)
+
+ def _cmp_outbound_interface(self, want, have, ctx):
+ iface_w = want.get("outbound_interface") or {}
+ iface_h = have.get("outbound_interface") or {}
+
+ if iface_w.get("name") != iface_h.get("name"):
+ if iface_w.get("name"):
+ self.addcmd(dict(ctx, value=iface_w["name"]), "nat_type_outbound_interface", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h["name"]), "nat_type_outbound_interface", True)
+
+ if iface_w.get("group") != iface_h.get("group"):
+ if iface_w.get("group"):
+ self.addcmd(
+ dict(ctx, value=iface_w["group"]),
+ "nat_type_outbound_interface_group",
+ False,
+ )
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(
+ dict(ctx, value=iface_h["group"]),
+ "nat_type_outbound_interface_group",
+ True,
+ )
+
+ def _cmp_addr_sub(self, want, have, atype, ctx):
+ sub_w = want.get(atype) or {}
+ sub_h = have.get(atype) or {}
+ if sub_w == sub_h:
+ return
+
+ changed = {k: v for k, v in sub_w.items() if sub_h.get(k) != v}
+ removed = {
+ k: v
+ for k, v in sub_h.items()
+ if k not in sub_w and self.state in ("replaced", "overridden")
+ }
+
+ if changed:
+ self.addcmd(dict(ctx, atype=atype, sub=changed), "nat_type_address", False)
+ if removed:
+ self.addcmd(dict(ctx, atype=atype, sub=removed), "nat_type_address", True)
+
+ def _cmp_translation(self, want, have, ctx):
+ trans_w = want.get("translation") or {}
+ trans_h = have.get("translation") or {}
+ if trans_w == trans_h:
+ return
+
+ changed = {k: v for k, v in trans_w.items() if k != "pool" and trans_h.get(k) != v}
+ removed = {
+ k: v
+ for k, v in trans_h.items()
+ if k != "pool" and k not in trans_w and self.state in ("replaced", "overridden")
+ }
+
+ if changed:
+ self.addcmd(dict(ctx, translation=changed), "nat_type_translation_address", False)
+ if removed:
+ self.addcmd(dict(ctx, translation=removed), "nat_type_translation_address", True)
+
+ def _cmp_match_mark(self, want, have, ctx):
+ w = want.get("match", {}).get("mark")
+ h = have.get("match", {}).get("mark")
+ if w != h:
+ if w is not None:
+ self.addcmd(dict(ctx, mark=w), "nat64_match_mark", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, mark=h), "nat64_match_mark", True)
+
+ def _cmp_nat64_pools(self, want, have, ctx):
+ want_pools = want.get("translation", {}).get("pool", {})
+ have_pools = have.get("translation", {}).get("pool", {})
+
+ if isinstance(want_pools, list):
+ want_pools = {p["id"]: p for p in want_pools}
+ if isinstance(have_pools, list):
+ have_pools = {p["id"]: p for p in have_pools}
+
+ for pid in set(want_pools) | set(have_pools):
+ wp = want_pools.get(pid, {})
+ hp = have_pools.get(pid, {})
+
+ if wp == hp:
+ continue
+
+ changed = {k: v for k, v in wp.items() if k != "id" and hp.get(k) != v}
+ removed = {
+ k: v
+ for k, v in hp.items()
+ if k != "id" and k not in wp and self.state in ("replaced", "overridden")
+ }
+
+ if changed:
+ self.addcmd(
+ dict(ctx, pool_id=pid, pool=changed),
+ "nat64_translation_pool",
+ False,
+ )
+ if removed:
+ self.addcmd(
+ dict(ctx, pool_id=pid, pool=removed),
+ "nat64_translation_pool",
+ True,
+ )
+
+ def _cmp_load_balance(self, want, have, ctx):
+ lb_w = want.get("load_balance") or {}
+ lb_h = have.get("load_balance") or {}
+
+ want_hash = set(lb_w.get("hash") or [])
+ have_hash = set(lb_h.get("hash") or [])
+
+ for h in want_hash - have_hash:
+ self.addcmd(dict(ctx, value=h), "nat_type_lb_hash", False)
+ if self.state in ("replaced", "overridden"):
+ for h in have_hash - want_hash:
+ self.addcmd(dict(ctx, value=h), "nat_type_lb_hash", True)
+
+ want_backends = lb_w.get("backend", [])
+ have_backends = lb_h.get("backend", [])
+ if isinstance(want_backends, list):
+ want_backends = {b["ip"]: b for b in want_backends}
+ if isinstance(have_backends, list):
+ have_backends = {b["ip"]: b for b in have_backends}
+
+ for ip in set(want_backends) | set(have_backends):
+ wb = want_backends.get(ip, {})
+ hb = have_backends.get(ip, {})
+ if wb == hb:
+ continue
+ if wb:
+ weight = wb.get("weight")
+ if weight is None:
+ self._module.fail_json(msg="load_balance.backend entries require 'weight'")
+ self.addcmd(dict(ctx, ip=ip, weight=weight), "nat_type_lb_backend", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, ip=ip, weight=hb.get("weight")), "nat_type_lb_backend", True)
diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py
index 69cb0d0b..92c0f97a 100644
--- a/plugins/module_utils/network/vyos/facts/facts.py
+++ b/plugins/module_utils/network/vyos/facts/facts.py
@@ -1,144 +1,148 @@
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The facts class for vyos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import (
FactsBase,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_address_family.bgp_address_family import (
Bgp_address_familyFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import (
Bgp_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global import (
Firewall_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_interfaces.firewall_interfaces import (
Firewall_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import (
Firewall_rulesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ha.ha import (
HaFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.hostname.hostname import (
HostnameFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.interfaces.interfaces import (
InterfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.l3_interfaces.l3_interfaces import (
L3_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces import (
Lag_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import (
Config,
Default,
Neighbors,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_global.lldp_global import (
Lldp_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import (
Lldp_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.logging_global.logging_global import (
Logging_globalFacts,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.nat.nat import (
+ NatFacts,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ntp_global.ntp_global import (
Ntp_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces import (
Ospf_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import (
Ospfv2Facts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import (
Ospfv3Facts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.prefix_lists.prefix_lists import (
Prefix_listsFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps import (
Route_mapsFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.snmp_server.snmp_server import (
Snmp_serverFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import (
Static_routesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vpn_ipsec.vpn_ipsec import (
Vpn_ipsecFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vpn_ipsec_s2s.vpn_ipsec_s2s import (
Vpn_ipsec_s2sFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vrf.vrf import VrfFacts
FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config)
FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts,
l3_interfaces=L3_interfacesFacts,
lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts,
static_routes=Static_routesFacts,
firewall_rules=Firewall_rulesFacts,
firewall_global=Firewall_globalFacts,
firewall_interfaces=Firewall_interfacesFacts,
ha=HaFacts,
ospfv3=Ospfv3Facts,
ospfv2=Ospfv2Facts,
ospf_interfaces=Ospf_interfacesFacts,
bgp_global=Bgp_globalFacts,
bgp_address_family=Bgp_address_familyFacts,
route_maps=Route_mapsFacts,
prefix_lists=Prefix_listsFacts,
logging_global=Logging_globalFacts,
ntp_global=Ntp_globalFacts,
+ nat=NatFacts,
snmp_server=Snmp_serverFacts,
hostname=HostnameFacts,
vrf=VrfFacts,
vpn_ipsec=Vpn_ipsecFacts,
vpn_ipsec_s2s=Vpn_ipsec_s2sFacts,
)
class Facts(FactsBase):
"""The fact class for vyos"""
VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())
def __init__(self, module):
super(Facts, self).__init__(module)
def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
"""Collect the facts for vyos
:param legacy_facts_type: List of legacy facts types
:param resource_facts_type: List of resource fact types
:param data: previously collected conf
:rtype: dict
:return: the facts gathered
"""
if self.VALID_RESOURCE_SUBSETS:
self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data)
if self.VALID_LEGACY_GATHER_SUBSETS:
self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)
return self.ansible_facts, self._warnings
diff --git a/plugins/module_utils/network/vyos/facts/nat/__init__.py b/plugins/module_utils/network/vyos/facts/nat/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/plugins/module_utils/network/vyos/facts/nat/nat.py b/plugins/module_utils/network/vyos/facts/nat/nat.py
new file mode 100644
index 00000000..73d6238f
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/nat/nat.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+# 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
+
+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 'set nat'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ facts = {}
+ config_lines = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ for resource in data.splitlines():
+ config_lines.append(re.sub(r"'([^']*)'", r"\1", 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)
+
+ return ansible_facts
+
+ def _deep_merge(self, base, override):
+ for k, v in override.items():
+ if k in base and isinstance(base[k], dict) and isinstance(v, dict):
+ self._deep_merge(base[k], v)
+ elif k in base and isinstance(base[k], list) and isinstance(v, list):
+ for entry in v:
+ if entry not in base[k]:
+ base[k].append(entry)
+ else:
+ base[k] = v
+ return base
+
+ 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, list):
+ existing = merged[rid].setdefault(k, [])
+ for entry in v:
+ if entry not in existing:
+ existing.append(entry)
+ elif isinstance(v, dict):
+ merged[rid].setdefault(k, {})
+ self._deep_merge(merged[rid][k], 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 k == "range" and isinstance(v, list):
+ existing = merged[name].setdefault(k, [])
+ existing.extend(v)
+ if v and isinstance(v[0], dict):
+ merged[name][k] = self._merge_range_list(existing)
+ else:
+ merged[name][k] = list(dict.fromkeys(existing))
+ elif isinstance(v, list):
+ merged[name].setdefault(k, [])
+ for val in v:
+ if val not in merged[name][k]:
+ merged[name][k].append(val)
+ elif isinstance(v, dict):
+ merged[name].setdefault(k, {})
+ self._deep_merge(merged[name][k], v)
+ else:
+ merged[name][k] = v
+ return list(merged.values())
+
+ def _merge_range_list(self, ranges):
+ """Merge external pool range entries by value, preserving seq."""
+ merged = {}
+ for entry in ranges:
+ if isinstance(entry, dict):
+ key = entry.get("value") or entry.get("address", "")
+ if not key:
+ continue
+ if key not in merged:
+ merged[key] = {"value": key}
+ if entry.get("seq"):
+ merged[key]["seq"] = entry["seq"]
+ else:
+ if entry not in merged:
+ merged[entry] = {"value": entry}
+ 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 not in nat:
+ continue
+ rules = nat[section].get("rule")
+ if isinstance(rules, list):
+ nat[section]["rule"] = self._merge_rule_list(rules)
+ nat[section]["rule"].sort(key=lambda x: x.get("id", 0))
+
+ if "cgnat" in nat and "pool" in nat["cgnat"]:
+ pool = nat["cgnat"]["pool"]
+ for ptype in ["external", "internal"]:
+ if ptype in pool and isinstance(pool[ptype], list):
+ pool[ptype] = self._merge_pool_list(pool[ptype])
+
+ if nat_type == "nat64":
+ for rule in nat.get("source", {}).get("rule", []):
+ pools = rule.get("translation", {}).get("pool")
+ if pools and isinstance(pools, list):
+ rule["translation"]["pool"] = self._merge_rule_list(pools)
+ rule["translation"]["pool"].sort(key=lambda x: x.get("id", 0))
+
+ self._cast_ports(objs)
+ return objs
+
+ def _cast_ports(self, obj):
+ """Recursively cast known integer port/seq fields to str."""
+ if isinstance(obj, dict):
+ for k, v in obj.items():
+ if k in ("port", "seq") and isinstance(v, int):
+ obj[k] = str(v)
+ else:
+ self._cast_ports(v)
+ elif isinstance(obj, list):
+ for item in obj:
+ self._cast_ports(item)
diff --git a/plugins/module_utils/network/vyos/rm_templates/nat.py b/plugins/module_utils/network/vyos/rm_templates/nat.py
new file mode 100644
index 00000000..94af8696
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/nat.py
@@ -0,0 +1,1186 @@
+# -*- coding: utf-8 -*-
+# 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
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_nat_rule_addr_sub(config_data):
+ """Generate address/fqdn/prefix/port/group commands for destination or source sub-dict."""
+ nat = config_data["nat"]
+ type_ = config_data["type"]
+ rid = config_data["id"]
+ atype = config_data["atype"]
+ sub = config_data["sub"]
+
+ base = f"{nat} {type_} rule {rid} {atype}"
+ commands = []
+
+ for field in ("address", "fqdn", "prefix", "port"):
+ if sub.get(field) is not None:
+ commands.append(f"{base} {field} {sub[field]}")
+
+ for gtype in ("address_group", "domain_group", "mac_group", "network_group", "port_group"):
+ if sub.get(gtype) is not None:
+ commands.append(f"{base} group {gtype.replace('_', '-')} {sub[gtype]}")
+
+ return commands
+
+
+def _tmplt_nat_rule_translation(config_data):
+ """Generate translation commands."""
+ nat = config_data["nat"]
+ type_ = config_data["type"]
+ rid = config_data["id"]
+ trans = config_data["translation"]
+
+ base = f"{nat} {type_} rule {rid} translation"
+ commands = []
+
+ if trans.get("address") is not None:
+ commands.append(f"{base} address {trans['address']}")
+
+ if trans.get("port") is not None:
+ commands.append(f"{base} port {trans['port']}")
+
+ if trans.get("redirect_port") is not None:
+ commands.append(f"{base} redirect port {trans['redirect_port']}")
+
+ if trans.get("address_mapping") is not None:
+ commands.append(f"{base} options address-mapping {trans['address_mapping']}")
+
+ if trans.get("port_mapping") is not None:
+ commands.append(f"{base} options port-mapping {trans['port_mapping']}")
+
+ return commands
+
+
+def _tmplt_nat64_translation_pool(config_data):
+ """Generate all nat64 translation pool commands from a single call."""
+ nat = config_data["nat"]
+ type_ = config_data["type"]
+ rid = config_data["id"]
+ pool_id = config_data["pool_id"]
+ pool = config_data["pool"]
+
+ base = f"{nat} {type_} rule {rid} translation pool {pool_id}"
+ commands = []
+
+ if pool.get("address") is not None:
+ commands.append(f"{base} address {pool['address']}")
+ if pool.get("description") is not None:
+ commands.append(f"{base} description '{pool['description']}'")
+ if pool.get("port") is not None:
+ commands.append(f"{base} port {pool['port']}")
+ if pool.get("protocol") is not None:
+ commands.append(f"{base} protocol {pool['protocol']}")
+ if pool.get("disable"):
+ commands.append(f"{base} disable")
+
+ return commands
+
+
+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)
+
+ # fmt: off
+ PARSERS = [
+
+ # -------------------------
+ # CGNAT
+ # -------------------------
+ {
+ "name": "cgnat_log_allocation",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+log-allocation
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat log-allocation",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "log_allocation": True,
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_external_range",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+external
+ \s+(?P<name>\S+)
+ \s+range
+ \s+(?P<range>\S+)(?:\s+seq\s+(?P<seq>\d+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool external {{ name }} range {{ range }}{% if seq is defined and seq %} seq {{ seq }}{% endif %}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "external": [
+ {
+ "name": "{{ name }}",
+ "range": [
+ {
+ "value": "{{ range }}",
+ "seq": "{{ seq }}",
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_external_port_range",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+external
+ \s+(?P<name>\S+)
+ \s+external-port-range
+ \s+(?P<range>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool external {{ name }} external-port-range {{ range }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "external": [
+ {
+ "name": "{{ name }}",
+ "external_port_range": "{{ range }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_external_per_user",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+external
+ \s+(?P<name>\S+)
+ \s+per-user-limit
+ \s+port
+ \s+(?P<limit>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool external {{ name }} per-user-limit port {{ limit }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "external": [
+ {
+ "name": "{{ name }}",
+ "per_user_limit": {"port": "{{ limit }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_internal_range",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+internal
+ \s+(?P<name>\S+)
+ \s+range
+ \s+(?P<range>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool internal {{ name }} range {{ range }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "internal": [
+ {
+ "name": "{{ name }}",
+ "range": ["{{ range }}"],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_rule_source_pool",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+rule
+ \s+(?P<id>\d+)
+ \s+source
+ \s+pool
+ \s+(?P<pool>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat rule {{ id }} source pool {{ pool }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "source": {"pool": "{{ pool }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_rule_translation_pool",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+rule
+ \s+(?P<id>\d+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat rule {{ id }} translation pool {{ pool }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {"pool": "{{ pool }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+
+ # -------------------------
+ # GENERIC NAT
+ # -------------------------
+ {
+ "name": "nat_type_description",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+description
+ \s+(?P<description>.+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} description '{{ description }}'",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "description": "{{ description }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_protocol",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+protocol
+ \s+(?P<protocol>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} protocol {{ protocol }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "protocol": "{{ protocol }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_disable",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\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+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\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+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+log
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} log",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "log": True,
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_address",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+address
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"address": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_prefix",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+prefix
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"prefix": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_fqdn",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+fqdn
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"fqdn": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_port",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+port
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"port": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_address_group",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+group
+ \s+(?P<gtype>address-group|domain-group|mac-group|network-group|port-group)
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {
+ "{{ gtype | replace('-', '_') }}": "{{ value }}",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_address",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+address
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {"address": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_port",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+port
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {"port": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_options",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+options
+ \s+(?P<opt>address-mapping|port-mapping)
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "{{ opt | replace('-', '_') }}": "{{ value }}",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_redirect",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+redirect
+ \s+port
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "redirect_port": "{{ value }}",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_inbound_interface_name",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+name
+ \s+(?P<value>\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+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+group
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat {{ type }} rule {{ id }} inbound-interface group {{ value }}",
+ "result": {
+ "nat": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "inbound_interface": {"group": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_static_inbound_interface",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+static
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat static rule {{ id }} inbound-interface {{ value }}",
+ "result": {
+ "nat": {
+ "static": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "inbound_interface": "{{ value }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat6x_inbound_interface",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+name
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} inbound-interface name {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "inbound_interface": {"name": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_outbound_interface",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+outbound-interface
+ \s+name
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} outbound-interface name {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "outbound_interface": {"name": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_outbound_interface_group",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+outbound-interface
+ \s+group
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} outbound-interface group {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "outbound_interface": {"group": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_packet_type",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+packet-type
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} packet-type {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "packet_type": "{{ value }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_lb_backend",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+load-balance
+ \s+backend
+ \s+(?P<ip>\S+)
+ \s+weight
+ \s+(?P<weight>\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 }}"},
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_lb_hash",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+load-balance
+ \s+hash
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} load-balance hash {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "load_balance": {"hash": ["{{ value }}"]},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat64_match_mark",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+match
+ \s+mark
+ \s+(?P<mark>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat64 source rule {{ id }} match mark {{ mark }}",
+ "result": {
+ "nat64": {
+ "source": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "match": {"mark": "{{ mark }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat64_translation_pool",
+ "getval": re.compile(r"^$"), # never matches — setval only
+ "setval": _tmplt_nat64_translation_pool,
+ "result": {},
+ },
+ {
+ "name": "nat64_translation_pool_address",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+address
+ \s+(?P<value>\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<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+description
+ \s+(?P<value>.+)
+ $""",
+ 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<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\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<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+port
+ \s+(?P<value>\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<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+protocol
+ \s+(?P<value>\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
diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py
index e95e8dea..89f12773 100644
--- a/plugins/module_utils/network/vyos/utils/utils.py
+++ b/plugins/module_utils/network/vyos/utils/utils.py
@@ -1,345 +1,346 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# utils
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.basic import missing_required_lib
try:
import ipaddress
HAS_IPADDRESS = True
except ImportError:
HAS_IPADDRESS = False
def search_obj_in_list(name, lst, key="name"):
if lst:
for item in lst:
if item[key] == name:
return item
return None
def get_interface_type(interface):
"""Gets the type of interface"""
if interface.startswith("eth"):
return "ethernet"
elif interface.startswith("bond"):
return "bonding"
elif interface.startswith("vti"):
return "vti"
elif interface.startswith("lo"):
return "loopback"
elif interface.startswith("vtun"):
return "openvpn"
elif interface.startswith("wg"):
return "wireguard"
elif interface.startswith("tun"):
return "tunnel"
elif interface.startswith("br"):
return "bridge"
elif interface.startswith("dum"):
return "dummy"
def get_interface_with_vif(interface):
"""Gets virtual interface if any or return as is"""
vlan = None
interface_real = interface
if "." in interface:
interface_real, vlan = interface.split(".")
if vlan is not None:
interface_real = interface_real + " vif " + vlan
return interface_real
def dict_delete(base, comparable):
"""
This function generates a dict containing key, value pairs for keys
that are present in the `base` dict but not present in the `comparable`
dict.
:param base: dict object to base the diff on
:param comparable: dict object to compare against base
:returns: new dict object with key, value pairs that needs to be deleted.
"""
to_delete = dict()
for key in base:
if isinstance(base[key], dict):
sub_diff = dict_delete(base[key], comparable.get(key, {}))
if sub_diff:
to_delete[key] = sub_diff
else:
if key not in comparable:
to_delete[key] = base[key]
return to_delete
def diff_list_of_dicts(want, have):
diff = []
set_w = set(tuple(d.items()) for d in want)
set_h = set(tuple(d.items()) for d in have)
difference = set_w.difference(set_h)
for element in difference:
diff.append(dict((x, y) for x, y in element))
return diff
def get_lst_diff_for_dicts(want, have, lst):
"""
This function generates a list containing values
that are only in want and not in list in have dict
:param want: dict object to want
:param have: dict object to have
:param lst: list the diff on
:return: new list object with values which are only in want.
"""
if not have:
diff = want.get(lst) or []
else:
want_elements = want.get(lst) or {}
have_elements = have.get(lst) or {}
diff = list_diff_want_only(want_elements, have_elements)
return diff
def get_lst_same_for_dicts(want, have, lst):
"""
This function generates a list containing values
that are common for list in want and list in have dict
:param want: dict object to want
:param have: dict object to have
:param lst: list the comparison on
:return: new list object with values which are common in want and have.
"""
diff = None
if want and have:
want_list = want.get(lst) or {}
have_list = have.get(lst) or {}
diff = [i for i in want_list and have_list if i in have_list and i in want_list]
return diff
def list_diff_have_only(want_list, have_list):
"""
This function generated the list containing values
that are only in have list.
:param want_list:
:param have_list:
:return: new list with values which are only in have list
"""
if have_list and not want_list:
diff = have_list
elif not have_list:
diff = None
else:
diff = [i for i in have_list + want_list if i in have_list and i not in want_list]
return diff
def list_diff_want_only(want_list, have_list):
"""
This function generated the list containing values
that are only in want list.
:param want_list:
:param have_list:
:return: new list with values which are only in want list
"""
if have_list and not want_list:
diff = None
elif not have_list:
diff = want_list
else:
diff = [i for i in have_list + want_list if i in want_list and i not in have_list]
return diff
def search_dict_tv_in_list(d_val1, d_val2, lst, key1, key2):
"""
This function return the dict object if it exist in list.
:param d_val1:
:param d_val2:
:param lst:
:param key1:
:param key2:
:return:
"""
obj = next(
(item for item in lst if item[key1] == d_val1 and item[key2] == d_val2),
None,
)
if obj:
return obj
else:
return None
def key_value_in_dict(have_key, have_value, want_dict):
"""
This function checks whether the key and values exist in dict
:param have_key:
:param have_value:
:param want_dict:
:return:
"""
for key, value in want_dict.items():
if key == have_key and value == have_value:
return True
return False
def is_dict_element_present(dict, key):
"""
This function checks whether the key is present in dict.
:param dict:
:param key:
:return:
"""
for item in dict:
if item == key:
return True
return False
def get_ip_address_version(address):
"""
This function returns the version of IP address
:param address: IP address
:return:
"""
if not HAS_IPADDRESS:
raise Exception(missing_required_lib("ipaddress"))
try:
address = unicode(address)
except NameError:
address = str(address)
version = ipaddress.ip_address(address.split("/")[0]).version
return version
def get_route_type(address):
"""
This function returns the route type based on IP address
:param address:
:return:
"""
version = get_ip_address_version(address)
if version == 6:
return "route6"
elif version == 4:
return "route"
def _bool_to_str(val):
"""
This function converts the bool value into string.
:param val: bool value.
:return: enable/disable.
"""
return "enable" if str(val) == "True" else "disable" if str(val) == "False" else val
def _is_w_same(w, h, key):
"""
This function checks whether the key value is same in desired and
target config dictionary.
:param w: base config.
:param h: target config.
:param key:attribute name.
:return: True/False.
"""
return True if h and key in h and h[key] == w[key] else False
def _in_target(h, key):
"""
This functi checks whether the target exist 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 in_target_not_none(h, key):
"""
This function checks whether the target exist,key present in target config, and the value is not None.
:param h: target config.
:param key: attribute name.
:return: True/False.
"""
return True if h and key in h and h[key] is not None else False
def combine(a, b, recursive=False, list_merge="replace"):
"""Merge dict ``b`` into dict ``a``, returning a new dict.
:param a: Base dictionary.
:param b: Dictionary whose values take precedence over ``a``.
:param recursive: When True, nested dicts are merged recursively rather
than replaced wholesale.
:param list_merge: Controls how list values are combined when the same key
exists in both dicts. Supported modes:
- ``"replace"`` *(default)* — ``b``'s list replaces ``a``'s list.
- ``"append"`` — ``b``'s list is appended to ``a``'s list (duplicates
kept).
- ``"prepend"`` — ``b``'s list is prepended to ``a``'s list
(duplicates kept).
- ``"append_rp"`` — like ``"append"`` but duplicates are removed,
preserving the first occurrence (rp = remove-preserve).
- ``"prepend_rp"`` — like ``"prepend"`` but duplicates are removed,
preserving the first occurrence.
Passing any other value raises ``ValueError``.
:returns: New merged dict.
:raises ValueError: If either argument is not a dict, or if an
unsupported ``list_merge`` mode is given.
"""
+
if not isinstance(a, dict) or not isinstance(b, dict):
raise ValueError("combine expects two dictionaries")
result = a.copy()
for k, v in b.items():
if k in result:
# dict merge
if recursive and isinstance(result[k], dict) and isinstance(v, dict):
result[k] = combine(result[k], v, recursive=True, list_merge=list_merge)
# list merge
elif isinstance(result[k], list) and isinstance(v, list):
if list_merge == "replace":
result[k] = v
elif list_merge == "append":
result[k] = result[k] + v
elif list_merge == "prepend":
result[k] = v + result[k]
elif list_merge == "append_rp":
result[k] = list(dict.fromkeys(result[k] + v))
elif list_merge == "prepend_rp":
result[k] = list(dict.fromkeys(v + result[k]))
else:
raise ValueError(f"Unsupported list_merge mode: {list_merge}")
# everything else
else:
result[k] = v
else:
result[k] = v
return result
diff --git a/plugins/modules/vyos_nat.py b/plugins/modules/vyos_nat.py
new file mode 100644
index 00000000..7715cb76
--- /dev/null
+++ b/plugins/modules/vyos_nat.py
@@ -0,0 +1,934 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for vyos_nat
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: vyos_nat
+version_added: 6.0.0
+short_description: NAT resource module
+description:
+- This module manages NAT configuration on devices running VyOS.
+author:
+- Evgeny Molotkov (@omnom62)
+notes:
+- Tested against VyOS 1.4.3 and 1.5.0.
+- This module works with connection C(network_cli).
+options:
+ config:
+ description:
+ - The desired configuration for the NAT resource represented as a dictionary.
+ type: dict
+ suboptions:
+ nat:
+ type: dict
+ description: Configuration for NAT rules.
+ suboptions:
+ cgnat:
+ type: dict
+ description: Configuration for Carrier Grade NAT (CGNAT).
+ suboptions:
+ log_allocation:
+ type: bool
+ description: Log CGNAT address allocations.
+ pool:
+ type: dict
+ description: Configuration for CGNAT pools.
+ suboptions:
+ external:
+ type: list
+ elements: dict
+ description: List of external NAT pools for CGNAT.
+ suboptions:
+ name:
+ type: str
+ required: true
+ description: Name of the external NAT pool.
+ external_port_range:
+ type: str
+ description: Port range to use for NAT translations in this external pool.
+ per_user_limit:
+ type: dict
+ description: Per-user limit configuration for the external pool.
+ suboptions:
+ port:
+ type: str
+ description: Maximum number of ports allocated per user.
+ range:
+ type: list
+ elements: dict
+ description: List of external IP address ranges in the pool.
+ suboptions:
+ value:
+ type: str
+ required: true
+ description: IP address, prefix, or range (e.g. 203.0.113.0/24 or 203.0.113.1-203.0.113.60).
+ seq:
+ type: str
+ description: Optional sequence number for this range entry.
+ internal:
+ type: list
+ elements: dict
+ description: List of internal NAT pools for CGNAT.
+ suboptions:
+ name:
+ type: str
+ required: true
+ description: Name of the internal NAT pool.
+ range:
+ type: list
+ elements: str
+ description: List of internal IP addresses or prefixes in the pool.
+ rule:
+ type: list
+ elements: dict
+ description: List of CGNAT rules.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Rule number for CGNAT.
+ source:
+ type: dict
+ description: Source pool configuration for CGNAT translation.
+ suboptions:
+ pool:
+ type: str
+ description: Source pool name to use for CGNAT translation.
+ translation:
+ type: dict
+ description: Translation pool configuration for CGNAT.
+ suboptions:
+ pool:
+ type: str
+ description: Translation pool name to use for CGNAT translation.
+ destination:
+ type: dict
+ description: Configuration for destination NAT rules.
+ suboptions:
+ rule:
+ type: list
+ elements: dict
+ description: List of destination NAT rules.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Rule number for destination NAT.
+ description:
+ type: str
+ description: User-friendly description of the destination NAT rule.
+ protocol:
+ type: str
+ description: Protocol to NAT (default all).
+ packet_type:
+ type: str
+ description: Packet type to match.
+ exclude:
+ type: bool
+ description: Exclude packets matching this rule from NAT.
+ log:
+ type: bool
+ description: Log packets hitting this rule.
+ disable:
+ type: bool
+ description: Disable this destination NAT rule.
+ inbound_interface:
+ type: dict
+ description: Match inbound interface.
+ suboptions:
+ name:
+ type: str
+ description: Interface name to match.
+ group:
+ type: str
+ description: Interface group to match.
+ destination:
+ type: dict
+ description: Match criteria for destination NAT.
+ suboptions:
+ address:
+ type: str
+ description: IP address, subnet, or range to match.
+ fqdn:
+ type: str
+ description: Fully qualified domain name to match.
+ port:
+ type: str
+ description: Port number or range to match.
+ address_group:
+ type: str
+ description: Address group name to match.
+ domain_group:
+ type: str
+ description: Domain group name to match.
+ mac_group:
+ type: str
+ description: MAC address group name to match.
+ network_group:
+ type: str
+ description: Network group name to match.
+ port_group:
+ type: str
+ description: Port group name to match.
+ translation:
+ type: dict
+ description: Translation configuration for destination NAT.
+ suboptions:
+ address:
+ type: str
+ description: IP address or prefix to translate destination to.
+ port:
+ type: str
+ description: Port number or range to translate destination port to.
+ redirect_port:
+ type: str
+ description: Redirect to local port number.
+ address_mapping:
+ type: str
+ choices:
+ - random
+ - persistent
+ description: Address mapping mode for translation.
+ port_mapping:
+ type: str
+ choices:
+ - random
+ - none
+ description: Port mapping mode for translation.
+ load_balance:
+ type: dict
+ description: Load balancing configuration for this NAT rule.
+ suboptions:
+ hash:
+ type: list
+ elements: str
+ description: Fields to hash on for load balancing. Mutually exclusive with I(translation.address).
+ choices:
+ - source-address
+ - destination-address
+ - source-port
+ - destination-port
+ - random
+ backend:
+ type: list
+ elements: dict
+ description: List of backends to load-balance across. Weights should sum to 100.
+ suboptions:
+ ip:
+ type: str
+ description: IP address of the backend translation target.
+ weight:
+ type: int
+ description: Relative weight (1-100) for this backend's share of load-balanced traffic.
+ source:
+ type: dict
+ description: Configuration for source NAT rules.
+ suboptions:
+ rule:
+ type: list
+ elements: dict
+ description: List of source NAT rules.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Rule number for source NAT.
+ description:
+ type: str
+ description: User-friendly description of the source NAT rule.
+ protocol:
+ type: str
+ description: Protocol to NAT (default all).
+ packet_type:
+ type: str
+ description: Packet type to match.
+ exclude:
+ type: bool
+ description: Exclude packets matching this rule from NAT.
+ log:
+ type: bool
+ description: Log packets hitting this rule.
+ disable:
+ type: bool
+ description: Disable this source NAT rule.
+ outbound_interface:
+ type: dict
+ description: Match outbound interface.
+ suboptions:
+ name:
+ type: str
+ description: Interface name to match.
+ group:
+ type: str
+ description: Interface group to match.
+ destination:
+ type: dict
+ description: Destination match criteria for source NAT.
+ suboptions:
+ address:
+ type: str
+ description: IP address, subnet, or range to match.
+ fqdn:
+ type: str
+ description: Fully qualified domain name to match.
+ port:
+ type: str
+ description: Port number or range to match.
+ address_group:
+ type: str
+ description: Address group name to match.
+ domain_group:
+ type: str
+ description: Domain group name to match.
+ mac_group:
+ type: str
+ description: MAC address group name to match.
+ network_group:
+ type: str
+ description: Network group name to match.
+ port_group:
+ type: str
+ description: Port group name to match.
+ source:
+ type: dict
+ description: Source match criteria for source NAT.
+ suboptions:
+ address:
+ type: str
+ description: IP address, subnet, or range to match.
+ fqdn:
+ type: str
+ description: Fully qualified domain name to match.
+ port:
+ type: str
+ description: Port number or range to match.
+ address_group:
+ type: str
+ description: Address group name to match.
+ domain_group:
+ type: str
+ description: Domain group name to match.
+ mac_group:
+ type: str
+ description: MAC address group name to match.
+ network_group:
+ type: str
+ description: Network group name to match.
+ port_group:
+ type: str
+ description: Port group name to match.
+ translation:
+ type: dict
+ description: Translation configuration for source NAT.
+ suboptions:
+ address:
+ type: str
+ description: IP address or prefix to translate source to. Use masquerade to masquerade as the outbound interface address.
+ port:
+ type: str
+ description: Port number or range to translate source port to.
+ address_mapping:
+ type: str
+ choices:
+ - random
+ - persistent
+ description: Address mapping mode for translation.
+ port_mapping:
+ type: str
+ choices:
+ - random
+ - none
+ description: Port mapping mode for translation.
+ load_balance:
+ type: dict
+ description: Load balancing configuration for this NAT rule.
+ suboptions:
+ hash:
+ type: list
+ elements: str
+ description: Fields to hash on for load balancing. Mutually exclusive with I(translation.address).
+ choices:
+ - source-address
+ - destination-address
+ - source-port
+ - destination-port
+ - random
+ backend:
+ type: list
+ elements: dict
+ description: List of backends to load-balance across. Weights should sum to 100.
+ suboptions:
+ ip:
+ type: str
+ description: IP address of the backend translation target.
+ weight:
+ type: int
+ description: Relative weight (1-100) for this backend's share of load-balanced traffic.
+ static:
+ type: dict
+ description: Configuration for static one-to-one NAT rules.
+ suboptions:
+ rule:
+ type: list
+ elements: dict
+ description: List of static NAT rules.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Rule number for static NAT.
+ description:
+ type: str
+ description: User-friendly description of the static NAT rule.
+ destination:
+ type: dict
+ description: Match criteria for static NAT.
+ suboptions:
+ address:
+ type: str
+ description: IP address, subnet, or range to match.
+ inbound_interface:
+ type: str
+ description: Inbound interface that this static NAT rule applies to.
+ log:
+ type: bool
+ description: Log packets hitting this static NAT rule.
+ translation:
+ type: dict
+ description: Translation configuration for static NAT.
+ suboptions:
+ address:
+ type: str
+ description: IP address or prefix to translate to.
+ nat64:
+ type: dict
+ description: Configuration for NAT64 (IPv6-to-IPv4) rules.
+ suboptions:
+ source:
+ type: dict
+ description: Configuration for NAT64 source rules.
+ suboptions:
+ rule:
+ type: list
+ elements: dict
+ description: List of NAT64 source rules.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Rule number for NAT64 source rule (1-999999).
+ description:
+ type: str
+ description: User-friendly description of the NAT64 source rule.
+ disable:
+ type: bool
+ description: Disable this NAT64 source rule.
+ match:
+ type: dict
+ description: Match criteria for NAT64 source rule.
+ suboptions:
+ mark:
+ type: int
+ description: Match on firewall mark value (1-2147483647).
+ source:
+ type: dict
+ description: IPv6 source prefix to match for NAT64 translation.
+ suboptions:
+ prefix:
+ type: str
+ description: IPv6 source prefix to match (h:h:h:h:h:h:h:h/x).
+ translation:
+ type: dict
+ description: Translation configuration for NAT64 source rule.
+ suboptions:
+ pool:
+ type: list
+ elements: dict
+ description: List of translation pools for NAT64.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Pool number (1-999999).
+ address:
+ type: str
+ description: IPv4 address or prefix for translation pool.
+ description:
+ type: str
+ description: User-friendly description of the translation pool.
+ disable:
+ type: bool
+ description: Disable this translation pool.
+ port:
+ type: str
+ description: Port number or range for translation pool.
+ protocol:
+ type: str
+ choices:
+ - icmp
+ - tcp
+ - udp
+ description: Protocol for this translation pool entry.
+ nat66:
+ type: dict
+ description: Configuration for NAT66 (IPv6-to-IPv6) rules.
+ suboptions:
+ destination:
+ type: dict
+ description: Configuration for NAT66 destination rules.
+ suboptions:
+ rule:
+ type: list
+ elements: dict
+ description: List of NAT66 destination rules.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Rule number for NAT66 destination rule.
+ description:
+ type: str
+ description: User-friendly description of the NAT66 destination rule.
+ destination:
+ type: dict
+ description: Match criteria for NAT66 destination rule.
+ suboptions:
+ address:
+ type: str
+ description: IPv6 address or prefix to match.
+ port:
+ type: str
+ description: Port number or range to match.
+ disable:
+ type: bool
+ description: Disable this NAT66 destination rule.
+ exclude:
+ type: bool
+ description: Exclude packets matching this rule from NAT66.
+ inbound_interface:
+ type: dict
+ description: Inbound interface to match for NAT66 destination rule.
+ suboptions:
+ name:
+ type: str
+ description: Interface name to match.
+ log:
+ type: bool
+ description: Log packets hitting this NAT66 destination rule.
+ protocol:
+ type: str
+ description: Protocol to match.
+ source:
+ type: dict
+ description: Source match criteria for NAT66 destination rule.
+ suboptions:
+ address:
+ type: str
+ description: IPv6 source address or prefix to match.
+ port:
+ type: str
+ description: Source port number or range to match.
+ translation:
+ type: dict
+ description: Translation configuration for NAT66 destination rule.
+ suboptions:
+ address:
+ type: str
+ description: IPv6 address or prefix to translate destination to.
+ port:
+ type: str
+ description: Port number or range to translate destination port to.
+ source:
+ type: dict
+ description: Configuration for NAT66 source rules.
+ suboptions:
+ rule:
+ type: list
+ elements: dict
+ description: List of NAT66 source rules.
+ suboptions:
+ id:
+ type: int
+ required: true
+ description: Rule number for NAT66 source rule.
+ description:
+ type: str
+ description: User-friendly description of the NAT66 source rule.
+ destination:
+ type: dict
+ description: Destination match criteria for NAT66 source rule.
+ suboptions:
+ port:
+ type: str
+ description: Destination port number or range to match.
+ prefix:
+ type: str
+ description: IPv6 destination prefix to match (h:h:h:h:h:h:h:h/x).
+ disable:
+ type: bool
+ description: Disable this NAT66 source rule.
+ exclude:
+ type: bool
+ description: Exclude packets matching this rule from NAT66.
+ log:
+ type: bool
+ description: Log packets hitting this NAT66 source rule.
+ outbound_interface:
+ type: dict
+ description: Outbound interface to match for NAT66 source rule.
+ suboptions:
+ name:
+ type: str
+ description: Interface name to match.
+ protocol:
+ type: str
+ description: Protocol to match.
+ source:
+ type: dict
+ description: Source match criteria for NAT66 source rule.
+ suboptions:
+ port:
+ type: str
+ description: Source port number or range to match.
+ prefix:
+ type: str
+ description: IPv6 source prefix to match (h:h:h:h:h:h:h:h/x).
+ translation:
+ type: dict
+ description: Translation configuration for NAT66 source rule.
+ suboptions:
+ address:
+ type: str
+ description: IPv6 address or prefix to translate source to. Use masquerade to masquerade as the outbound interface address.
+ port:
+ type: str
+ description: Port number or range to translate source port to.
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the VyOS device by
+ executing the command B(show configuration commands | match 'nat').
+ - The state I(parsed) reads the configuration from C(show configuration commands | match 'nat')
+ and transforms it into Ansible structured data as per the module argspec.
+ The value is then returned in the I(parsed) key within the result.
+ - The state I(replaced) replaces only the provided configuration, while I(overridden) removes any
+ existing NAT configuration not specified in I(config).
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+"""
+EXAMPLES = """
+# Using merged - configure CGNAT
+- name: Merge CGNAT configuration
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ cgnat:
+ log_allocation: true
+ pool:
+ external:
+ - name: ext-pool-1
+ external_port_range: "10000-20000"
+ per_user_limit:
+ port: "200"
+ range:
+ - value: 203.0.113.0/24
+ internal:
+ - name: int-pool-1
+ range:
+ - 10.0.0.0/24
+ rule:
+ - id: 1
+ source:
+ pool: int-pool-1
+ translation:
+ pool: ext-pool-1
+ state: merged
+
+# Using merged - configure destination NAT
+- name: Merge destination NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Web server NAT"
+ protocol: tcp
+ log: true
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: merged
+
+# Using merged - configure source NAT
+- name: Merge source NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ source:
+ rule:
+ - id: 200
+ description: "Outbound masquerade"
+ protocol: tcp
+ log: true
+ outbound_interface:
+ name: eth0
+ translation:
+ address: masquerade
+ state: merged
+
+# Using merged - configure static NAT
+- name: Merge static NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ static:
+ rule:
+ - id: 300
+ description: "Static mapping"
+ inbound_interface: eth2
+ destination:
+ address: 198.51.100.20
+ translation:
+ address: 192.168.1.20
+ log: true
+ state: merged
+
+# Using merged - configure NAT64
+- name: Merge NAT64 source rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat64:
+ source:
+ rule:
+ - id: 10
+ description: "NAT64 example"
+ source:
+ prefix: 2001:db8::/96
+ match:
+ mark: 100
+ translation:
+ pool:
+ - id: 1
+ address: 192.168.100.10
+ port: "1-65535"
+ protocol: udp
+ state: merged
+
+# Using merged - configure NAT66
+- name: Merge NAT66 destination rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat66:
+ destination:
+ rule:
+ - id: 20
+ description: "NAT66 DNAT"
+ protocol: tcp
+ inbound_interface:
+ name: eth1
+ destination:
+ address: 2001:db8::1
+ translation:
+ address: 2001:db8:1::10
+ port: "8443"
+ state: merged
+
+# Using replaced - replace specific NAT rules
+- name: Replace destination NAT rule
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Replaced web server NAT"
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "443"
+ translation:
+ address: 192.168.1.10
+ port: "8443"
+ state: replaced
+
+# Using overridden - override entire NAT configuration
+- name: Override entire NAT configuration
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Only rule after override"
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: overridden
+
+# Using deleted - delete all NAT configuration
+- name: Delete all NAT configuration
+ vyos.vyos.vyos_nat:
+ state: deleted
+
+# Using deleted - delete specific NAT rules
+- name: Delete specific NAT rules
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ source:
+ rule:
+ - id: 200
+ nat64:
+ source:
+ rule:
+ - id: 10
+ state: deleted
+
+# Using gathered
+- name: Gather NAT configuration from device
+ vyos.vyos.vyos_nat:
+ state: gathered
+
+# Using rendered
+- name: Render NAT configuration offline
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: "Rendered rule"
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: rendered
+
+# Using parsed
+- name: Parse NAT configuration from file
+ vyos.vyos.vyos_nat:
+ running_config: "{{ lookup('file', './nat_config.cfg') }}"
+ state: parsed
+"""
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden) or C(deleted)
+ type: list
+ sample:
+ - set nat destination rule 100 description 'Web server NAT'
+ - set nat destination rule 100 protocol tcp
+ - set nat destination rule 100 inbound-interface name eth2
+ - set nat destination rule 100 destination address 198.51.100.10
+ - set nat destination rule 100 translation address 192.168.1.10
+ - delete nat source rule 200
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - set nat destination rule 100 description 'Web server NAT'
+ - set nat destination rule 100 protocol tcp
+ - set nat destination rule 100 inbound-interface name eth2
+ - set nat destination rule 100 destination address 198.51.100.10
+ - set nat destination rule 100 translation address 192.168.1.10
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+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.config.nat.nat import (
+ Nat,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=NatArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Nat(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_vrf.py b/plugins/modules/vyos_vrf.py
index defe24bb..3f7ae10e 100644
--- a/plugins/modules/vyos_vrf.py
+++ b/plugins/modules/vyos_vrf.py
@@ -1,1544 +1,1542 @@
#!/usr/bin/python
# -*- 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)
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The module file for vyos_vrf
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: vyos_vrf
version_added: 1.0.0
short_description: VRF resource module
description:
- This module manages vrf configuration on devices running Vyos
author:
- Evgeny Molotkov (@omnom62)
notes:
- Tested against vyos 1.4.2 and 1.5-stream-2025-Q1
- This module works with connection C(network_cli).
options:
config:
description: List of vrf configuration.
type: dict
suboptions:
bind_to_all:
default: false
description: Enable binding services to all VRFs
type: bool
instances:
description: Virtual Routing and Forwarding instance
type: list
elements: dict
suboptions:
name:
description: VRF instance name
required: true
type: str
description:
description: Description
type: str
disable:
default: false
description: Administratively disable interface
type: bool
aliases: ['disabled']
table_id:
description: Routing table associated with this instance
type: int
vni:
description: Virtual Network Identifier
type: int
address_family:
type: list
elements: dict
description: Address family configuration
suboptions:
afi:
description: Address family identifier
type: str
choices: ['ipv4', 'ipv6']
disable_forwarding:
default: false
description: Disable forwarding for this address family
type: bool
nht_no_resolve_via_default:
default: false
description: Disable next-hop resolution via default route
type: bool
route_maps:
description: List of route maps for this address family
type: list
elements: dict
suboptions:
rm_name:
description: Route map name
type: str
required: true
protocol:
description: Protocol to which the route map applies
type: str
choices:
- any
- babel
- bgp
- eigrp
- isis
- ospf
- rip
- static
protocols:
# type: list # sanity
# elements: dict
type: dict
description: Protocol configuration
suboptions:
bgp:
type: dict
description: BGP configuration
suboptions:
as_number:
description:
- AS number.
type: int
#maximum_paths: --> moved to address-family before 1.3
neighbor:
description: BGP neighbor
type: list
elements: dict
suboptions:
address:
description:
- BGP neighbor address (v4/v6).
type: str
advertisement_interval:
description:
- Minimum interval for sending routing updates.
type: int
capability:
description:
- Advertise capabilities to this neighbor.
type: dict
suboptions:
dynamic:
description:
- Advertise dynamic capability to this neighbor.
type: bool
extended_nexthop:
description:
- Advertise extended nexthop capability to this neighbor.
type: bool
default_originate:
description:
- Send default route to this neighbor
type: str
description:
description:
- Description of the neighbor
type: str
disable_capability_negotiation:
description:
- Disbale capability negotiation with the neighbor
type: bool
disable_connected_check:
description:
- Disable check to see if EBGP peer's address is a connected route.
type: bool
disable_send_community:
description:
- Disable sending community attributes to this neighbor.
type: str
choices: ['extended', 'standard']
ebgp_multihop:
description:
- Allow this EBGP neighbor to not be on a directly connected network. Specify
the number hops.
type: int
local_as:
description: local as number not to be prepended to updates from EBGP peers
type: int
override_capability:
description: Ignore capability negotiation with specified neighbor.
type: bool
passive:
description: Do not initiate a session with this neighbor
type: bool
password:
description: BGP MD5 password
type: str
peer_group_name:
description: IPv4 peer group for this peer
type: str
peer_group:
description: True if all the configs under this neighbor key is for peer group template.
type: bool
port:
description: Neighbor's BGP port
type: int
remote_as:
description: Neighbor BGP AS number
type: int
shutdown:
description: Administratively shut down neighbor
type: bool
solo: # <-- added in 1.3
description: Do not send back prefixes learned from the neighbor
type: bool
strict_capability_match:
description: Enable strict capability negotiation
type: bool
timers:
description: Neighbor timers
type: dict
suboptions:
connect:
description: BGP connect timer for this neighbor.
type: int
holdtime:
description: BGP hold timer for this neighbor
type: int
keepalive:
description: BGP keepalive interval for this neighbor
type: int
ttl_security:
description: Number of the maximum number of hops to the BGP peer
type: int
update_source:
description: Source IP of routing updates
type: str
timers:
description: BGP protocol timers
type: dict
suboptions:
keepalive:
description: Keepalive interval
type: int
holdtime:
description: Hold time interval
type: int
bgp_params:
description: BGP parameters
type: dict
suboptions:
always_compare_med:
description: Always compare MEDs from different neighbors
type: bool
bestpath:
description: Default bestpath selection mechanism
type: dict
suboptions:
as_path:
description: AS-path attribute comparison parameters
type: str
choices: ['confed', 'ignore']
compare_routerid:
description: Compare the router-id for identical EBGP paths
type: bool
med:
description: MED attribute comparison parameters
type: str
choices: ['confed', 'missing-as-worst']
cluster_id:
description: Route-reflector cluster-id
type: str
confederation:
description: AS confederation parameters
type: list
elements: dict
suboptions:
identifier:
description: Confederation AS identifier
type: int
peers:
description: Peer ASs in the BGP confederation
type: int
dampening:
description: Enable route-flap dampening
type: dict
suboptions:
half_life:
description: Half-life penalty in seconds
type: int
max_suppress_time:
description: Maximum duration to suppress a stable route
type: int
re_use:
description: Time to start reusing a route
type: int
start_suppress_time:
description: When to start suppressing a route
type: int
default:
description: BGP defaults
type: dict
suboptions:
local_pref:
description: Default local preference
type: int
no_ipv4_unicast:
description: |
Deactivate IPv4 unicast for a peer by default
Deprecated: Unavailable after 1.4
type: bool
deterministic_med:
description: Compare MEDs between different peers in the same AS
type: bool
disable_network_import_check:
description: Disable IGP route check for network statements
type: bool
distance:
description: Administrative distances for BGP routes
type: list
elements: dict
suboptions:
type:
description: Type of route
type: str
choices: ['external', 'internal', 'local']
value:
description: distance
type: int
prefix:
description: Administrative distance for a specific BGP prefix
type: int
enforce_first_as:
description: Require first AS in the path to match peer's AS
type: bool
graceful_restart:
description: Maximum time to hold onto restarting peer's stale paths
type: int
log_neighbor_changes:
description: Log neighbor up/down changes and reset reason
type: bool
no_client_to_client_reflection:
description: Disable client to client route reflection
type: bool
no_fast_external_failover:
description: Disable immediate session reset if peer's connected link goes down
type: bool
router_id:
description: BGP router-id
type: str
scan_time:
description: BGP route scanner interval
type: int
ospf:
type: dict
description: OSPFv2 configuration
suboptions:
areas:
description: OSPFv2 area.
type: list
elements: dict
suboptions:
area_id:
description: OSPFv2 area identity.
type: str
area_type:
description: Area type.
type: dict
suboptions:
normal:
description: Normal OSPFv2 area.
type: bool
nssa:
description: NSSA OSPFv2 area.
type: dict
suboptions:
set:
description: Enabling NSSA.
type: bool
default_cost:
description: Summary-default cost of NSSA area.
type: int
no_summary:
description: Do not inject inter-area routes into stub.
type: bool
translate:
description: NSSA-ABR.
type: str
choices: [always, candidate, never]
stub:
description: Stub OSPFv2 area.
type: dict
suboptions:
set:
description: Enabling stub.
type: bool
default_cost:
description: Summary-default cost of stub area.
type: int
no_summary:
description: Do not inject inter-area routes into stub.
type: bool
authentication:
description: OSPFv2 area authentication type.
type: str
choices: [plaintext-password, md5]
network:
description: OSPFv2 network.
type: list
elements: dict
suboptions:
address:
required: true
description: OSPFv2 IPv4 network address.
type: str
range:
description: Summarize routes matching prefix (border routers only).
type: list
elements: dict
suboptions:
address:
description: border router IPv4 address.
type: str
cost:
description: Metric for this range.
type: int
not_advertise:
description: Don't advertise this range.
type: bool
substitute:
description: Announce area range (IPv4 address) as another prefix.
type: str
shortcut:
description: Area's shortcut mode.
type: str
choices: [default, disable, enable]
virtual_link:
description: Virtual link address.
type: list
elements: dict
suboptions:
address:
description: virtual link address.
type: str
authentication:
description: OSPFv2 area authentication type.
type: dict
suboptions:
md5:
description: MD5 key id based authentication.
type: list
elements: dict
suboptions:
key_id:
description: MD5 key id.
type: int
md5_key:
description: MD5 key.
type: str
plaintext_password:
description: Plain text password.
type: str
dead_interval:
description: Interval after which a neighbor is declared dead.
type: int
hello_interval:
description: Interval between hello packets.
type: int
retransmit_interval:
description: Interval between retransmitting lost link state advertisements.
type: int
transmit_delay:
description: Link state transmit delay.
type: int
log_adjacency_changes:
description: Log changes in adjacency state.
type: str
choices: [detail]
max_metric:
description: OSPFv2 maximum/infinite-distance metric.
type: dict
suboptions:
router_lsa:
description: Advertise own Router-LSA with infinite distance (stub router).
type: dict
suboptions:
administrative:
description: Administratively apply, for an indefinite period.
type: bool
on_shutdown:
description: Time to advertise self as stub-router.
type: int
on_startup:
description: Time to advertise self as stub-router
type: int
auto_cost:
description: Calculate OSPFv2 interface cost according to bandwidth.
type: dict
suboptions:
reference_bandwidth:
description: Reference bandwidth cost in Mbits/sec.
type: int
default_information:
description: Control distribution of default information.
type: dict
suboptions:
originate:
description: Distribute a default route.
type: dict
suboptions:
always:
description: Always advertise default route.
type: bool
metric:
description: OSPFv2 default metric.
type: int
metric_type:
description: OSPFv2 Metric types for default routes.
type: int
route_map:
description: Route map references.
type: str
default_metric:
description: Metric of redistributed routes
type: int
distance:
description: Administrative distance.
type: dict
suboptions:
global:
description: Global OSPFv2 administrative distance.
type: int
ospf:
description: OSPFv2 administrative distance.
type: dict
suboptions:
external:
description: Distance for external routes.
type: int
inter_area:
description: Distance for inter-area routes.
type: int
intra_area:
description: Distance for intra-area routes.
type: int
mpls_te:
description: MultiProtocol Label Switching-Traffic Engineering (MPLS-TE) parameters.
type: dict
suboptions:
enabled:
description: Enable MPLS-TE functionality.
type: bool
router_address:
description: Stable IP address of the advertising router.
type: str
neighbor:
description: Neighbor IP address.
type: list
elements: dict
suboptions:
neighbor_id:
description: Identity (number/IP address) of neighbor.
type: str
poll_interval:
description: Seconds between dead neighbor polling interval.
type: int
priority:
description: Neighbor priority.
type: int
parameters:
description: OSPFv2 specific parameters.
type: dict
suboptions:
abr_type:
description: OSPFv2 ABR Type.
type: str
choices: [cisco, ibm, shortcut, standard]
opaque_lsa:
description: Enable the Opaque-LSA capability (rfc2370).
type: bool
rfc1583_compatibility:
description: Enable rfc1583 criteria for handling AS external routes.
type: bool
router_id:
description: Override the default router identifier.
type: str
passive_interface:
description: Suppress routing updates on an interface.
type: list
elements: str
passive_interface_exclude:
description: Interface to exclude when using passive-interface default.
type: list
elements: str
redistribute:
description: Redistribute information from another routing protocol.
type: list
elements: dict
suboptions:
route_type:
description: Route type to redistribute.
type: str
choices: [bgp, connected, kernel, rip, static]
metric:
description: Metric for redistribution routes.
type: int
metric_type:
description: OSPFv2 Metric types.
type: int
route_map:
description: Route map references.
type: str
route_map:
description: Filter routes installed in local route map.
type: list
elements: str
timers:
description: Adjust routing timers.
type: dict
suboptions:
refresh:
description: Adjust refresh parameters.
type: dict
suboptions:
timers:
description: refresh timer.
type: int
throttle:
description: Throttling adaptive timers.
type: dict
suboptions:
spf:
description: OSPFv2 SPF timers.
type: dict
suboptions:
delay:
description: Delay (msec) from first change received till SPF
calculation.
type: int
initial_holdtime:
description: Initial hold time(msec) between consecutive SPF calculations.
type: int
max_holdtime:
description: maximum hold time (sec).
type: int
ospfv3:
type: dict
description: OSPFv3 configuration
suboptions:
areas:
description: OSPFv3 area.
type: list
elements: dict
suboptions:
area_id:
description: OSPFv3 Area name/identity.
type: str
export_list:
description: Name of export-list.
type: str
import_list:
description: Name of import-list.
type: str
interface:
description: Enable OSPVv3 on an interface for this area.
aliases: ['interfaces']
type: list
elements: dict
suboptions:
name:
description: Interface name.
type: str
range:
description: Summarize routes matching prefix (border routers only).
type: list
elements: dict
suboptions:
address:
description: border router IPv4 address.
type: str
advertise:
description: Advertise this range.
type: bool
not_advertise:
description: Don't advertise this range.
type: bool
parameters:
description: OSPFv3 specific parameters.
type: dict
suboptions:
router_id:
description: Override the default router identifier.
type: str
redistribute:
description: Redistribute information from another routing protocol.
type: list
elements: dict
suboptions:
route_type:
description: Route type to redistribute.
type: str
choices:
- bgp
- connected
- kernel
- ripng
- static
route_map:
description: Route map references.
type: str
static:
type: list
description: Static routes configuration
elements: dict
suboptions:
address_families:
description: A dictionary specifying the address family to which the static
route(s) belong.
type: list
elements: dict
suboptions:
afi:
description:
- Specifies the type of route.
type: str
choices:
- ipv4
- ipv6
required: true
routes:
description: A dictionary that specify the static route configurations.
type: list
elements: dict
suboptions:
dest:
description:
- An IPv4/v6 address in CIDR notation that specifies the destination
network for the static route.
type: str
required: true
blackhole_config:
description:
- Configured to silently discard packets.
type: dict
suboptions:
type:
description:
- This is to configure only blackhole.
type: str
distance:
description:
- Distance for the route.
type: int
next_hops:
description:
- Next hops to the specified destination.
type: list
elements: dict
suboptions:
forward_router_address:
description:
- The IP address of the next hop that can be used to reach the
destination network.
type: str
enabled:
description:
- Disable IPv4/v6 next-hop static route.
type: bool
admin_distance:
description:
- Distance value for the route.
type: int
interface:
description:
- Name of the outgoing interface.
type: str
running_config:
description:
- This option is used only with state I(parsed).
- The value of this option should be the output received from the VYOS device by
executing the command B(show configuration commands | match "set vrf").
- The states I(replaced) and I(overridden) have identical
behaviour for this module.
- The state I(parsed) reads the configuration from C(show configuration commands | match "set vrf") option and
transforms it into Ansible structured data as per the resource module's argspec
and the value is then returned in the I(parsed) key within the result.
type: str
state:
description:
- The state the configuration should be left in.
type: str
choices:
- deleted
- merged
- overridden
- replaced
- gathered
- rendered
- parsed
default: merged
"""
EXAMPLES = """
# # -------------------
# # 1. Using merged
# # -------------------
# # Before state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue disable
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# vyos@vyos:~$
# # Task
# # -------------
# - name: Merge provided configuration with device configuration
# vyos.vyos.vyos_vrf:
# config:
# instances:
# - name: "vrf-green"
# description: "green-vrf"
# table_id: 110
# vni: 1010
# Task output:
# -------------
# "after": {
# "bind_to_all": false,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": true,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1000
# },
# {
# "description": "green-vrf",
# "disable": false,
# "name": "vrf-green",
# "table_id": 110,
# "vni": 1010
# }
# ]
# },
# "before": {
# "bind_to_all": false,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": true,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1000
# }
# ]
# },
# "changed": true,
# "commands": [
# "set vrf name vrf-green table 110",
# "set vrf name vrf-green vni 1010",
# "set vrf name vrf-green description green-vrf"
# ]
# After state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue disable
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# set vrf name vrf-green description 'green-vrf'
# set vrf name vrf-green table '110'
# set vrf name vrf-green vni '1010'
# vyos@vyos:~$
# # -------------------
# # 2. Using replaced
# # -------------------
# # Before state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf bind-to-all
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip disable-forwarding
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# # Task
# # -------------
# - name: Merge provided configuration with device configuration
# vyos.vyos.vyos_vrf:
# config:
# bind_to_all: true
# instances:
# - name: "vrf-blue"
# description: "blue-vrf"
# disable: false
# table_id: 100
# vni: 1002
# - name: "vrf-red"
# description: "red-vrf"
# disable: false
# table_id: 101
# vni: 1001
# address_family:
# - afi: "ipv4"
# disable_forwarding: false
# route_maps:
# - rm_name: "rm1"
# protocol: "ospf"
# - afi: "ipv6"
# nht_no_resolve_via_default: true
# state: replaced
# # Task output:
# # -------------
# "after": {
# "bind_to_all": true,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": false,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1002
# },
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": false,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "ospf",
# "rm_name": "rm1"
# },
# {
# "protocol": "rip",
# "rm_name": "rm1"
# }
# ]
# },
# {
# "afi": "ipv6",
# "disable_forwarding": false,
# "nht_no_resolve_via_default": true
# }
# ],
# "description": "red-vrf",
# "disable": false,
# "name": "vrf-red",
# "table_id": 101,
# "vni": 1001
# }
# ]
# },
# "before": {
# "bind_to_all": true,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": false,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1000
# },
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": true,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "rip",
# "rm_name": "rm1"
# }
# ]
# }
# ],
# "description": "red-vrf",
# "disable": true,
# "name": "vrf-red",
# "table_id": 101,
# "vni": 1001
# }
# ]
# },
# "changed": true,
# "commands": [
# "set vrf name vrf-blue vni 1002",
# "delete vrf name vrf-red disable",
# "set vrf name vrf-red ip protocol ospf route-map rm1",
# "delete vrf name vrf-red ip disable-forwarding",
# "set vrf name vrf-red ipv6 nht no-resolve-via-default"
# ]
# After state:
# # -------------
# vyos@vyos:~$
# set vrf bind-to-all
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1002'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red ip protocol ospf route-map 'rm1'
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red ipv6 nht no-resolve-via-default
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# # -------------------
# # 3. Using overridden
# # -------------------
# # Before state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf bind-to-all
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip disable-forwarding
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# Task
# -------------
# - name: Overridden provided configuration with device configuration
# vyos.vyos.vyos_vrf:
# config:
# bind_to_all: true
# instances:
# - name: "vrf-blue"
# description: "blue-vrf"
# disable: true
# table_id: 100
# vni: 1000
# - name: "vrf-red"
# description: "red-vrf"
# disable: true
# table_id: 101
# vni: 1001
# address_family:
# - afi: "ipv4"
# disable_forwarding: false
# route_maps:
# - rm_name: "rm1"
# protocol: "rip"
# - afi: "ipv6"
# nht_no_resolve_via_default: false
# state: overridden
# # Task output:
# # -------------
# "after": {
# "bind_to_all": true,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": true,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1000
# },
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": false,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "rip",
# "rm_name": "rm1"
# }
# ]
# }
# ],
# "description": "red-vrf",
# "disable": true,
# "name": "vrf-red",
# "table_id": 101,
# "vni": 1001
# }
# ]
# },
# "before": {
# "bind_to_all": true,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": false,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1000
# },
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": true,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "rip",
# "rm_name": "rm1"
# }
# ]
# }
# ],
# "description": "red-vrf",
# "disable": true,
# "name": "vrf-red",
# "table_id": 101,
# "vni": 1001
# }
# ]
# },
# "changed": true,
# "commands": [
# "delete vrf name vrf-blue",
# "commit",
# "delete vrf name vrf-red",
# "commit",
# "set vrf name vrf-blue table 100",
# "set vrf name vrf-blue vni 1000",
# "set vrf name vrf-blue description blue-vrf",
# "set vrf name vrf-blue disable",
# "set vrf name vrf-red table 101",
# "set vrf name vrf-red vni 1001",
# "set vrf name vrf-red description red-vrf",
# "set vrf name vrf-red disable",
# "set vrf name vrf-red ip protocol rip route-map rm1"
# ]
# After state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf bind-to-all
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue disable
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# 4. Using gathered
# -------------------
# # Before state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf bind-to-all
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip disable-forwarding
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# Task
# -------------
# - name: Gather provided configuration with device configuration
# vyos.vyos.vyos_vrf:
# config:
# state: gathered
# # Task output:
# # -------------
# "gathered": {
# "bind_to_all": true,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": false,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1000
# },
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": true,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "rip",
# "rm_name": "rm1"
# }
# ]
# }
# ],
# "description": "red-vrf",
# "disable": true,
# "name": "vrf-red",
# "table_id": 101,
# "vni": 1001
# }
# ]
# }
# After state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf bind-to-all
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip disable-forwarding
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# # -------------------
# # 5. Using deleted
# # -------------------
# # Before state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf bind-to-all
# set vrf name vrf-blue description 'blue-vrf'
# set vrf name vrf-blue table '100'
# set vrf name vrf-blue vni '1000'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip disable-forwarding
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# # Task
# # -------------
# - name: Replace provided configuration with device configuration
# vyos.vyos.vyos_vrf:
# config:
# bind_to_all: false
# instances:
# - name: "vrf-blue"
# state: deleted
# # Task output:
# # -------------
# "after": {
# "bind_to_all": false,
# "instances": [
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": true,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "rip",
# "rm_name": "rm1"
# }
# ]
# }
# ],
# "description": "red-vrf",
# "disable": true,
# "name": "vrf-red",
# "table_id": 101,
# "vni": 1001
# }
# ]
# },
# "before": {
# "bind_to_all": true,
# "instances": [
# {
# "description": "blue-vrf",
# "disable": false,
# "name": "vrf-blue",
# "table_id": 100,
# "vni": 1000
# },
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": true,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "rip",
# "rm_name": "rm1"
# }
# ]
# }
# ],
# "description": "red-vrf",
# "disable": true,
# "name": "vrf-red",
# "table_id": 101,
# "vni": 1001
# }
# ]
# },
# "changed": true,
# "commands": [
# "delete vrf bind-to-all",
# "delete vrf name vrf-blue"
# ]
# After state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip disable-forwarding
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# # -------------------
# # 6. Using rendered
# # -------------------
# # Before state:
# # -------------
# vyos@vyos:~$ show configuration commands | match 'set vrf'
# set vrf name vrf-red description 'red-vrf'
# set vrf name vrf-red disable
# set vrf name vrf-red ip disable-forwarding
# set vrf name vrf-red ip protocol rip route-map 'rm1'
# set vrf name vrf-red table '101'
# set vrf name vrf-red vni '1001'
# vyos@vyos:~$
# Task
# -------------
# - name: Render provided configuration with device configuration
# vyos.vyos.vyos_vrf:
# config:
# bind_to_all: true
# instances:
# - name: "vrf-green"
# description: "green-vrf"
# disabled: true
# table_id: 105
# vni: 1000
# - name: "vrf-amber"
# description: "amber-vrf"
# disable: false
# table_id: 111
# vni: 1001
# address_family:
# - afi: "ipv4"
# disable_forwarding: true
# route_maps:
# - rm_name: "rm1"
# protocol: "ospf"
# - afi: "ipv6"
# nht_no_resolve_via_default: false
# state: rendered
# # Task output:
# # -------------
# "rendered": [
# "set vrf bind-to-all",
# "set vrf name vrf-green table 105",
# "set vrf name vrf-green vni 1000",
# "set vrf name vrf-green description green-vrf",
# "set vrf name vrf-green disable",
# "set vrf name vrf-amber table 111",
# "set vrf name vrf-amber vni 1001",
# "set vrf name vrf-amber description amber-vrf",
# "set vrf name vrf-amber ip protocol ospf route-map rm1",
# "set vrf name vrf-amber ip disable-forwarding"
# ]
# # -------------------
# # 7. Using parsed
# # -------------------
# # vrf_parsed.cfg:
# # -------------
# set vrf bind-to-all
# set vrf name vrf1 description 'red'
# set vrf name vrf1 disable
# set vrf name vrf1 table 101
# set vrf name vrf1 vni 501
# set vrf name vrf2 description 'blah2'
# set vrf name vrf2 disable
# set vrf name vrf2 table 102
# set vrf name vrf2 vni 102
# set vrf name vrf1 ip disable-forwarding
# set vrf name vrf1 ip nht no-resolve-via-default
# set vrf name vrf-red ip protocol ospf route-map 'rm1'
# set vrf name vrf-red ipv6 nht no-resolve-via-default
# Task:
# -------------
# - name: Parse provided configuration with device configuration
# vyos.vyos.vyos_vrf:
# running_config: "{{ lookup('file', './vrf_parsed.cfg') }}"
# state: parsed
# # Task output:
# # -------------
# "parsed": {
# "bind_to_all": true,
# "instances": [
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": true,
# "nht_no_resolve_via_default": true
# }
# ],
# "description": "red",
# "disable": true,
# "name": "vrf1"
# },
# {
# "description": "blah2",
# "disable": true,
# "name": "vrf2"
# },
# {
# "address_family": [
# {
# "afi": "ipv4",
# "disable_forwarding": false,
# "nht_no_resolve_via_default": false,
# "route_maps": [
# {
# "protocol": "ospf",
# "rm_name": "rm1"
# }
# ]
# },
# {
# "afi": "ipv6",
# "disable_forwarding": false,
# "nht_no_resolve_via_default": true
# }
# ],
# "disable": false,
# "name": "vrf-red"
# }
# ]
# }
"""
RETURN = """
before:
description: The configuration prior to the module execution.
returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
type: dict
sample: >
This output will always be in the same format as the
module argspec.
after:
description: The resulting configuration after module execution.
returned: when changed
type: dict
sample: >
This output will always be in the same format as the
module argspec.
commands:
description: The set of commands pushed to the remote device.
returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
type: list
sample:
- set system ntp server server1 dynamic
- set system ntp server server1 prefer
- set system ntp server server2 noselect
- set system ntp server server2 preempt
- set system ntp server server_add preempt
rendered:
description: The provided configuration in the task rendered in device-native format (offline).
returned: when I(state) is C(rendered)
type: list
sample:
- set system ntp server server1 dynamic
- set system ntp server server1 prefer
- set system ntp server server2 noselect
- set system ntp server server2 preempt
- set system ntp server server_add preempt
gathered:
description: Facts about the network resource gathered from the remote device as structured data.
returned: when I(state) is C(gathered)
type: list
sample: >
This output will always be in the same format as the
module argspec.
parsed:
description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
returned: when I(state) is C(parsed)
type: list
sample: >
This output will always be in the same format as the
module argspec.
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrf.vrf import VrfArgs
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.vrf.vrf import Vrf
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
module = AnsibleModule(
argument_spec=VrfArgs.argument_spec,
mutually_exclusive=[["config", "running_config"]],
required_if=[
["state", "merged", ["config"]],
["state", "replaced", ["config"]],
["state", "overridden", ["config"]],
["state", "rendered", ["config"]],
["state", "parsed", ["running_config"]],
],
supports_check_mode=True,
)
result = Vrf(module).execute_module()
module.exit_json(**result)
if __name__ == "__main__":
main()
diff --git a/tests/integration/targets/vyos_nat/defaults/main.yaml b/tests/integration/targets/vyos_nat/defaults/main.yaml
new file mode 100644
index 00000000..164afead
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_nat/tasks/cli.yaml b/tests/integration/targets/vyos_nat/tasks/cli.yaml
new file mode 100644
index 00000000..a71206c0
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tasks/cli.yaml
@@ -0,0 +1,20 @@
+---
+- name: Collect all cli test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}\\.yaml$"
+ use_regex: true
+ delegate_to: localhost
+ register: test_cases
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=ansible.netcommon.network_cli)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_nat/tasks/main.yaml b/tests/integration/targets/vyos_nat/tasks/main.yaml
new file mode 100644
index 00000000..e6378581
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run CLI tests
+ ansible.builtin.include_tasks: cli.yaml
+ tags:
+ - network_cli
diff --git a/tests/integration/targets/vyos_nat/tests/cli/_parsed.cfg b/tests/integration/targets/vyos_nat/tests/cli/_parsed.cfg
new file mode 100644
index 00000000..68375366
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/_parsed.cfg
@@ -0,0 +1,17 @@
+set nat destination rule 100 description 'Web server NAT'
+set nat destination rule 100 destination address '198.51.100.10'
+set nat destination rule 100 destination port '80'
+set nat destination rule 100 inbound-interface name 'eth2'
+set nat destination rule 100 log
+set nat destination rule 100 protocol 'tcp'
+set nat destination rule 100 translation address '192.168.1.10'
+set nat destination rule 100 translation port '8080'
+set nat source rule 200 description 'Outbound NAT'
+set nat source rule 200 destination address '192.168.10.100'
+set nat source rule 200 destination port '8083'
+set nat source rule 200 disable
+set nat source rule 200 exclude
+set nat source rule 200 log
+set nat source rule 200 protocol 'tcp'
+set nat source rule 200 translation address 'masquerade'
+set nat source rule 200 translation port '443'
diff --git a/tests/integration/targets/vyos_nat/tests/cli/_populate.yaml b/tests/integration/targets/vyos_nat/tests/cli/_populate.yaml
new file mode 100644
index 00000000..a3715be9
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/_populate.yaml
@@ -0,0 +1,8 @@
+---
+- ansible.builtin.include_tasks: _remove_config.yaml
+
+- name: Setup NAT configuration
+ vyos.vyos.vyos_config:
+ lines: "{{ populate_config }}"
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
diff --git a/tests/integration/targets/vyos_nat/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_nat/tests/cli/_remove_config.yaml
new file mode 100644
index 00000000..3618bf40
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/_remove_config.yaml
@@ -0,0 +1,10 @@
+---
+- name: Remove NAT configuration
+ vyos.vyos.vyos_config:
+ lines:
+ - delete nat
+ - delete nat64
+ - delete nat66
+ ignore_errors: true
+ vars:
+ ansible_connection: ansible.netcommon.network_cli
diff --git a/tests/integration/targets/vyos_nat/tests/cli/deleted.yaml b/tests/integration/targets/vyos_nat/tests/cli/deleted.yaml
new file mode 100644
index 00000000..c08d6b1c
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/deleted.yaml
@@ -0,0 +1,34 @@
+---
+- debug:
+ msg: START vyos_nat deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Delete all NAT configuration
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(deleted.commands) == []
+
+ - name: Assert that the after dicts were correctly generated
+ assert:
+ that:
+ - result.after|symmetric_difference(deleted.after) == []
+
+ - name: Delete the existing configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/deleted_full_match.yaml b/tests/integration/targets/vyos_nat/tests/cli/deleted_full_match.yaml
new file mode 100644
index 00000000..43a8124f
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/deleted_full_match.yaml
@@ -0,0 +1,70 @@
+---
+- debug:
+ msg: START vyos_nat deleted full-match integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Delete destination rule 100 with full matching config
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands == ["delete nat destination rule 100"]
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Assert rule 100 is actually gone from the device
+ assert:
+ that:
+ - ansible_facts['network_resources']['nat'].nat.destination is not defined or
+ ansible_facts['network_resources']['nat'].nat.destination.rule | default([]) | selectattr('id', 'equalto', 100) | list | length == 0
+
+ - name: Delete the same configuration again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: deleted
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_nat/tests/cli/empty_config.yaml
new file mode 100644
index 00000000..39b57fee
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/empty_config.yaml
@@ -0,0 +1,60 @@
+---
+- debug:
+ msg: START vyos_nat empty_config integration tests on connection={{ ansible_connection }}
+
+- name: Merged with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: merged
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state merged'
+
+- name: Replaced with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: replaced
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state replaced'
+
+- name: Overridden with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: overridden
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state overridden'
+
+- name: Parsed with empty running_config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ running_config:
+ state: parsed
+
+- assert:
+ that:
+ - result.msg == 'value of running_config parameter must not be empty for state parsed'
+
+- name: Rendered with empty config should give appropriate error message
+ register: result
+ ignore_errors: true
+ vyos.vyos.vyos_nat:
+ config:
+ state: rendered
+
+- assert:
+ that:
+ - result.msg == 'value of config parameter must not be empty for state rendered'
+
+- include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/gathered.yaml b/tests/integration/targets/vyos_nat/tests/cli/gathered.yaml
new file mode 100644
index 00000000..a91410b8
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/gathered.yaml
@@ -0,0 +1,22 @@
+---
+- debug:
+ msg: START vyos_nat gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Gather NAT config from the device
+ register: result
+ vyos.vyos.vyos_nat:
+ state: gathered
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.gathered|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/merged.yaml b/tests/integration/targets/vyos_nat/tests/cli/merged.yaml
new file mode 100644
index 00000000..73184fb6
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/merged.yaml
@@ -0,0 +1,110 @@
+---
+- debug:
+ msg: START vyos_nat merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Merge new NAT rule with existing configuration
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 101
+ description: New DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ state: merged
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(merged.commands) == []
+ - result.after|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ - name: Assert that before dicts were correctly generated
+ assert:
+ that:
+ - merged['before'] | symmetric_difference(result['before']) | length == 0
+
+ - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ - name: Add static rule with plain inbound_interface string
+ register: result
+ vyos.vyos.vyos_nat: &id002
+ config:
+ nat:
+ static:
+ rule:
+ - id: 300
+ description: Static mapping
+ inbound_interface: eth2
+ destination:
+ address: 192.168.100.20
+ translation:
+ address: 192.168.1.20
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - "'set nat static rule 300 inbound-interface eth2' in result.commands"
+
+ - name: Add static rule again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id002
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ - name: Change static rule inbound_interface
+ register: result
+ vyos.vyos.vyos_nat: &id003
+ config:
+ nat:
+ static:
+ rule:
+ - id: 300
+ description: Static mapping
+ inbound_interface: eth3
+ destination:
+ address: 192.168.100.20
+ translation:
+ address: 192.168.1.20
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands == ["set nat static rule 300 inbound-interface eth3"]
+
+ - name: Change static rule inbound_interface again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id003
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/merged_load_balance.yaml b/tests/integration/targets/vyos_nat/tests/cli/merged_load_balance.yaml
new file mode 100644
index 00000000..04ee5816
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/merged_load_balance.yaml
@@ -0,0 +1,107 @@
+---
+- debug:
+ msg: START vyos_nat load balance integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Add destination rule with load-balance hash and backends
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 105
+ protocol: tcp
+ destination:
+ port: "80"
+ inbound_interface:
+ name: eth0
+ load_balance:
+ hash:
+ - source-address
+ - destination-address
+ backend:
+ - ip: 10.10.10.1
+ weight: 60
+ - ip: 10.10.10.2
+ weight: 40
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+ - "'set nat destination rule 105 load-balance hash source-address' in result.commands"
+ - "'set nat destination rule 105 load-balance hash destination-address' in result.commands"
+ - "'set nat destination rule 105 load-balance backend 10.10.10.1 weight 60' in result.commands"
+ - "'set nat destination rule 105 load-balance backend 10.10.10.2 weight 40' in result.commands"
+
+ - name: Re-apply same load-balance config (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ - name: Change backend weights only (in-place overwrite check)
+ register: result
+ vyos.vyos.vyos_nat: &id002
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 105
+ protocol: tcp
+ destination:
+ port: "80"
+ inbound_interface:
+ name: eth0
+ load_balance:
+ hash:
+ - source-address
+ - destination-address
+ backend:
+ - ip: 10.10.10.1
+ weight: 70
+ - ip: 10.10.10.2
+ weight: 30
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Extract rule 105 from gathered facts
+ set_fact:
+ rule_105: "{{ ansible_facts['network_resources']['nat'].nat.destination.rule | selectattr('id', 'equalto', 105) | first }}"
+
+ - name: Extract backend weights from rule 105
+ set_fact:
+ backend_105: "{{ rule_105.load_balance.backend }}"
+ weight_101: "{{ (rule_105.load_balance.backend | selectattr('ip', 'equalto', '10.10.10.1') | first).weight }}"
+ weight_102: "{{ (rule_105.load_balance.backend | selectattr('ip', 'equalto', '10.10.10.2') | first).weight }}"
+
+ - name: Assert weights updated and no duplicate/stale backend entries
+ assert:
+ that:
+ - backend_105 | length == 2
+ - weight_101 == 70
+ - weight_102 == 30
+
+ - name: Re-apply changed weights again (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id002
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/overridden.yaml b/tests/integration/targets/vyos_nat/tests/cli/overridden.yaml
new file mode 100644
index 00000000..fbb87d72
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/overridden.yaml
@@ -0,0 +1,45 @@
+---
+- debug:
+ msg: START vyos_nat overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Override NAT configuration
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Overridden DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.20
+ port: "443"
+ translation:
+ address: 192.168.1.20
+ port: "8443"
+ state: overridden
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(overridden.commands) == []
+ - result.after|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ - name: Override the existing configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/parsed.yaml b/tests/integration/targets/vyos_nat/tests/cli/parsed.yaml
new file mode 100644
index 00000000..13a54f42
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/parsed.yaml
@@ -0,0 +1,16 @@
+---
+- debug:
+ msg: START vyos_nat parsed integration tests on connection={{ ansible_connection }}
+
+- name: Parse NAT configuration from file
+ register: result
+ vyos.vyos.vyos_nat:
+ running_config: "{{ lookup('file', '_parsed.cfg') }}"
+ state: parsed
+
+- assert:
+ that:
+ - result.changed == false
+ - result.parsed|symmetric_difference(merged.before) == []
+
+- include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/rendered.yaml b/tests/integration/targets/vyos_nat/tests/cli/rendered.yaml
new file mode 100644
index 00000000..9c7a8ccb
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/rendered.yaml
@@ -0,0 +1,32 @@
+---
+- debug:
+ msg: START vyos_nat rendered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Render NAT configuration without applying to device
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Rendered rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ state: rendered
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.rendered|symmetric_difference(rendered.commands) == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/replaced.yaml b/tests/integration/targets/vyos_nat/tests/cli/replaced.yaml
new file mode 100644
index 00000000..a327c623
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/replaced.yaml
@@ -0,0 +1,45 @@
+---
+- debug:
+ msg: START vyos_nat replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Replace NAT destination rule 100
+ register: result
+ vyos.vyos.vyos_nat: &id001
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Replaced DNAT rule
+ protocol: udp
+ destination:
+ address: 198.51.100.10
+ port: "53"
+ translation:
+ address: 192.168.1.53
+ port: "53"
+ state: replaced
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - assert:
+ that:
+ - result.changed == true
+ - result.commands|symmetric_difference(replaced.commands) == []
+ - result.after|symmetric_difference(ansible_facts['network_resources']['nat']) == []
+
+ - name: Replace the provided configuration (IDEMPOTENT)
+ register: result
+ vyos.vyos.vyos_nat: *id001
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - result['changed'] == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/replaced_cgnat_pool.yaml b/tests/integration/targets/vyos_nat/tests/cli/replaced_cgnat_pool.yaml
new file mode 100644
index 00000000..77fc295b
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/replaced_cgnat_pool.yaml
@@ -0,0 +1,77 @@
+---
+- debug:
+ msg: START vyos_nat replaced cgnat pool integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Establish baseline CGNAT external pool
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ cgnat:
+ log_allocation: true
+ pool:
+ external:
+ - name: ext-pool-1
+ external_port_range: "10000-20000"
+ per_user_limit:
+ port: "200"
+ range:
+ - value: 203.0.113.0/24
+ - value: 203.1.113.1-203.1.113.60
+ seq: "10"
+ internal:
+ - name: int-pool-1
+ range:
+ - 10.0.0.0/24
+ rule:
+ - id: 1
+ source:
+ pool: int-pool-1
+ translation:
+ pool: ext-pool-1
+ state: merged
+
+ - name: Replace CGNAT pool changing only external_port_range
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ cgnat:
+ log_allocation: true
+ pool:
+ external:
+ - name: ext-pool-1
+ external_port_range: "30000-40000"
+ per_user_limit:
+ port: "200"
+ range:
+ - value: 203.0.113.0/24
+ - value: 203.1.113.1-203.1.113.60
+ seq: "10"
+ internal:
+ - name: int-pool-1
+ range:
+ - 10.0.0.0/24
+ rule:
+ - id: 1
+ source:
+ pool: int-pool-1
+ translation:
+ pool: ext-pool-1
+ state: replaced
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Assert all pool fields survived the replace, not just the changed one
+ assert:
+ that:
+ - result.changed == true
+ - ansible_facts['network_resources']['nat'].nat.cgnat.pool.external[0].external_port_range == "30000-40000"
+ - ansible_facts['network_resources']['nat'].nat.cgnat.pool.external[0].per_user_limit.port == "200"
+ - ansible_facts['network_resources']['nat'].nat.cgnat.pool.external[0].range | length == 2
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/tests/cli/rtt.yaml b/tests/integration/targets/vyos_nat/tests/cli/rtt.yaml
new file mode 100644
index 00000000..8ec9397e
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/tests/cli/rtt.yaml
@@ -0,0 +1,70 @@
+---
+- debug:
+ msg: START vyos_nat round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Apply additional NAT rule to establish base config
+ register: baseconfig
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ - id: 101
+ description: RTT base rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ state: merged
+
+ - vyos.vyos.vyos_facts:
+ gather_network_resources: nat
+
+ - name: Modify rule 101 description
+ register: result
+ vyos.vyos.vyos_nat:
+ config:
+ nat:
+ destination:
+ rule:
+ - id: 101
+ description: RTT modified rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ state: merged
+
+ - name: Revert back to base config using facts round trip
+ register: revert
+ vyos.vyos.vyos_nat:
+ config: "{{ ansible_facts['network_resources']['nat'] }}"
+ state: overridden
+
+ - name: Assert that config was reverted
+ assert:
+ that: baseconfig.after == revert.after
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_nat/vars/main.yaml b/tests/integration/targets/vyos_nat/vars/main.yaml
new file mode 100644
index 00000000..0bae2c03
--- /dev/null
+++ b/tests/integration/targets/vyos_nat/vars/main.yaml
@@ -0,0 +1,172 @@
+---
+populate_config:
+ - set nat destination rule 100 description 'Web server NAT'
+ - set nat destination rule 100 destination address '198.51.100.10'
+ - set nat destination rule 100 destination port '80'
+ - set nat destination rule 100 inbound-interface name 'eth2'
+ - set nat destination rule 100 log
+ - set nat destination rule 100 protocol 'tcp'
+ - set nat destination rule 100 translation address '192.168.1.10'
+ - set nat destination rule 100 translation port '8080'
+ - set nat source rule 200 description 'Outbound NAT'
+ - set nat source rule 200 destination address '192.168.10.100'
+ - set nat source rule 200 destination port '8083'
+ - set nat source rule 200 disable
+ - set nat source rule 200 exclude
+ - set nat source rule 200 log
+ - set nat source rule 200 protocol 'tcp'
+ - set nat source rule 200 translation address 'masquerade'
+ - set nat source rule 200 translation port '443'
+
+merged:
+ before:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ source:
+ rule:
+ - id: 200
+ description: Outbound NAT
+ protocol: tcp
+ log: true
+ disable: true
+ exclude: true
+ destination:
+ address: 192.168.10.100
+ port: "8083"
+ translation:
+ address: masquerade
+ port: "443"
+ commands:
+ - set nat destination rule 101 description 'New DNAT rule'
+ - set nat destination rule 101 protocol tcp
+ - set nat destination rule 101 destination address 198.51.100.11
+ - set nat destination rule 101 destination port 443
+ - set nat destination rule 101 translation address 192.168.1.11
+ - set nat destination rule 101 translation port 8443
+ after:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Web server NAT
+ protocol: tcp
+ log: true
+ inbound_interface:
+ name: eth2
+ destination:
+ address: 198.51.100.10
+ port: "80"
+ translation:
+ address: 192.168.1.10
+ port: "8080"
+ - id: 101
+ description: New DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.11
+ port: "443"
+ translation:
+ address: 192.168.1.11
+ port: "8443"
+ source:
+ rule:
+ - id: 200
+ description: Outbound NAT
+ protocol: tcp
+ log: true
+ disable: true
+ exclude: true
+ destination:
+ address: 192.168.10.100
+ port: "8083"
+ translation:
+ address: masquerade
+ port: "443"
+
+replaced:
+ commands:
+ - delete nat destination rule 100
+ - set nat destination rule 100 description 'Replaced DNAT rule'
+ - set nat destination rule 100 protocol udp
+ - set nat destination rule 100 destination address 198.51.100.10
+ - set nat destination rule 100 destination port 53
+ - set nat destination rule 100 translation address 192.168.1.53
+ - set nat destination rule 100 translation port 53
+ after:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Replaced DNAT rule
+ protocol: udp
+ destination:
+ address: 198.51.100.10
+ port: "53"
+ translation:
+ address: 192.168.1.53
+ port: "53"
+ source:
+ rule:
+ - id: 200
+ description: Outbound NAT
+ protocol: tcp
+ log: true
+ disable: true
+ exclude: true
+ destination:
+ address: 192.168.10.100
+ port: "8083"
+ translation:
+ address: masquerade
+ port: "443"
+
+overridden:
+ commands:
+ - delete nat source
+ - delete nat destination rule 100
+ - set nat destination rule 100 description 'Overridden DNAT rule'
+ - set nat destination rule 100 protocol tcp
+ - set nat destination rule 100 destination address 198.51.100.20
+ - set nat destination rule 100 destination port 443
+ - set nat destination rule 100 translation address 192.168.1.20
+ - set nat destination rule 100 translation port 8443
+ after:
+ nat:
+ destination:
+ rule:
+ - id: 100
+ description: Overridden DNAT rule
+ protocol: tcp
+ destination:
+ address: 198.51.100.20
+ port: "443"
+ translation:
+ address: 192.168.1.20
+ port: "8443"
+
+deleted:
+ commands:
+ - delete nat
+ after: {}
+
+rendered:
+ commands:
+ - set nat destination rule 100 description 'Rendered rule'
+ - set nat destination rule 100 protocol tcp
+ - set nat destination rule 100 destination address 198.51.100.10
+ - set nat destination rule 100 destination port 80
+ - set nat destination rule 100 translation address 192.168.1.10
+ - set nat destination rule 100 translation port 8080
diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.15.txt
+++ b/tests/sanity/ignore-2.15.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.16.txt
+++ b/tests/sanity/ignore-2.16.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.17.txt
+++ b/tests/sanity/ignore-2.17.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.18.txt
+++ b/tests/sanity/ignore-2.18.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.19.txt
+++ b/tests/sanity/ignore-2.19.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.20.txt b/tests/sanity/ignore-2.20.txt
index 255b0a87..a96f7e91 100644
--- a/tests/sanity/ignore-2.20.txt
+++ b/tests/sanity/ignore-2.20.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.21.txt b/tests/sanity/ignore-2.21.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.21.txt
+++ b/tests/sanity/ignore-2.21.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/sanity/ignore-2.22.txt b/tests/sanity/ignore-2.22.txt
index 255b0a87..ee3d2aac 100644
--- a/tests/sanity/ignore-2.22.txt
+++ b/tests/sanity/ignore-2.22.txt
@@ -1,38 +1,39 @@
plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
plugins/action/banner.py action-plugin-docs
plugins/action/bgp_address_family.py action-plugin-docs
plugins/action/bgp_global.py action-plugin-docs
plugins/action/command.py action-plugin-docs
plugins/action/config.py action-plugin-docs
plugins/action/facts.py action-plugin-docs
plugins/action/firewall_global.py action-plugin-docs
plugins/action/firewall_interfaces.py action-plugin-docs
plugins/action/firewall_rules.py action-plugin-docs
plugins/action/ha.py action-plugin-docs
plugins/action/hostname.py action-plugin-docs
plugins/action/interface.py action-plugin-docs
plugins/action/interfaces.py action-plugin-docs
plugins/action/l3_interface.py action-plugin-docs
plugins/action/l3_interfaces.py action-plugin-docs
plugins/action/lag_interfaces.py action-plugin-docs
plugins/action/linkagg.py action-plugin-docs
plugins/action/lldp.py action-plugin-docs
plugins/action/lldp_global.py action-plugin-docs
plugins/action/lldp_interface.py action-plugin-docs
plugins/action/lldp_interfaces.py action-plugin-docs
plugins/action/logging_global.py action-plugin-docs
+plugins/action/nat.py action-plugin-docs
plugins/action/ntp_global.py action-plugin-docs
plugins/action/ospf_interfaces.py action-plugin-docs
plugins/action/ospfv2.py action-plugin-docs
plugins/action/ospfv3.py action-plugin-docs
plugins/action/ping.py action-plugin-docs
plugins/action/prefix_lists.py action-plugin-docs
plugins/action/route_maps.py action-plugin-docs
plugins/action/snmp_server.py action-plugin-docs
plugins/action/static_route.py action-plugin-docs
plugins/action/static_routes.py action-plugin-docs
plugins/action/system.py action-plugin-docs
plugins/action/user.py action-plugin-docs
plugins/action/vlan.py action-plugin-docs
plugins/action/vrf.py action-plugin-docs
plugins/action/file.py action-plugin-docs
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_nat_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_nat_config.cfg
new file mode 100644
index 00000000..505f5e57
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_nat_config.cfg
@@ -0,0 +1,61 @@
+set nat cgnat log-allocation
+set nat cgnat pool external ext-pool-1 external-port-range '10000-20000'
+set nat cgnat pool external ext-pool-1 per-user-limit port '200'
+set nat cgnat pool external ext-pool-1 range 203.0.113.0/24
+set nat cgnat pool external ext-pool-1 range 203.1.113.1-203.1.113.60 seq '10'
+set nat cgnat pool internal int-pool-1 range '10.0.0.0/24'
+set nat cgnat pool internal int-pool-1 range '10.1.0.0/24'
+set nat cgnat rule 1 source pool 'int-pool-1'
+set nat cgnat rule 1 translation pool 'ext-pool-1'
+set nat destination rule 100 description 'Web server NAT'
+set nat destination rule 100 destination address '198.51.100.10'
+set nat destination rule 100 destination port '80'
+set nat destination rule 100 inbound-interface name 'eth2'
+set nat destination rule 100 log
+set nat destination rule 100 protocol 'tcp'
+set nat destination rule 100 translation address '192.168.1.10'
+set nat destination rule 100 translation options address-mapping 'persistent'
+set nat destination rule 100 translation options port-mapping 'random'
+set nat destination rule 100 translation port '8080'
+set nat destination rule 105 protocol tcp
+set nat destination rule 105 destination port '80'
+set nat destination rule 105 inbound-interface name 'eth0'
+set nat destination rule 105 load-balance hash 'source-address'
+set nat destination rule 105 load-balance hash 'destination-address'
+set nat destination rule 105 load-balance backend 10.10.10.1 weight '60'
+set nat destination rule 105 load-balance backend 10.10.10.2 weight '40'
+set nat source rule 200 description 'Outbound NAT'
+set nat source rule 200 destination address '192.168.10.100'
+set nat source rule 200 destination port '8083'
+set nat source rule 200 disable
+set nat source rule 200 exclude
+set nat source rule 200 log
+set nat source rule 200 protocol 'tcp'
+set nat source rule 200 translation address 'masquerade'
+set nat source rule 200 translation port '443'
+set nat static rule 300 description 'Static mapping'
+set nat static rule 300 destination address '192.168.100.20'
+set nat static rule 300 inbound-interface 'eth2'
+set nat static rule 300 log
+set nat static rule 300 translation address '192.168.1.20'
+set nat64 source rule 10 description 'NAT64 example'
+set nat64 source rule 10 disable
+set nat64 source rule 10 match mark '100'
+set nat64 source rule 10 source prefix '2001:db8::/96'
+set nat64 source rule 10 translation pool 1 address '192.168.100.10'
+set nat64 source rule 10 translation pool 1 description 'NAT64 translation pool'
+set nat64 source rule 10 translation pool 1 disable
+set nat64 source rule 10 translation pool 1 port '1-65535'
+set nat64 source rule 10 translation pool 1 protocol udp
+set nat66 destination rule 20 description 'NAT66 DNAT'
+set nat66 destination rule 20 destination address '2001:db8::1'
+set nat66 destination rule 20 inbound-interface name 'eth1'
+set nat66 destination rule 20 protocol 'tcp'
+set nat66 destination rule 20 translation address '2001:db8:1::10'
+set nat66 destination rule 20 translation port '8443'
+set nat66 source rule 30 description 'NAT66 SNAT'
+set nat66 source rule 30 destination prefix '2001:db8::/96'
+set nat66 source rule 30 outbound-interface name 'eth2'
+set nat66 source rule 30 protocol 'tcp'
+set nat66 source rule 30 source prefix '2001:db8:2::/64'
+set nat66 source rule 30 translation address 'masquerade'
diff --git a/tests/unit/modules/network/vyos/test_vyos_nat.py b/tests/unit/modules/network/vyos/test_vyos_nat.py
new file mode 100644
index 00000000..4ef76305
--- /dev/null
+++ b/tests/unit/modules/network/vyos/test_vyos_nat.py
@@ -0,0 +1,839 @@
+# (c) 2024 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from unittest.mock import patch
+
+from ansible_collections.vyos.vyos.plugins.modules import vyos_nat
+from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
+
+from .vyos_module import TestVyosModule, load_fixture
+
+
+class TestVyosNatModule(TestVyosModule):
+ module = vyos_nat
+
+ def setUp(self):
+ super(TestVyosNatModule, self).setUp()
+
+ self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection",
+ )
+ self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
+
+ self.mock_get_resource_connection_facts = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection",
+ )
+ self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
+
+ self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.nat.nat.NatFacts.get_config",
+ )
+ self.execute_show_command = self.mock_execute_show_command.start()
+ self.maxDiff = None
+
+ def tearDown(self):
+ super(TestVyosNatModule, self).tearDown()
+ self.mock_get_resource_connection_config.stop()
+ self.mock_get_resource_connection_facts.stop()
+ self.mock_execute_show_command.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ if filename is None:
+ filename = "vyos_nat_config.cfg"
+
+ def load_from_file(*args, **kwargs):
+ return load_fixture(filename)
+
+ self.execute_show_command.side_effect = load_from_file
+
+ # -------------------------------------------------------------------------
+ # merged
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_merged_idempotent(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ log_allocation=True,
+ pool=dict(
+ external=[
+ dict(
+ name="ext-pool-1",
+ external_port_range="10000-20000",
+ per_user_limit=dict(port="200"),
+ range=[
+ dict(value="203.0.113.0/24"),
+ dict(value="203.1.113.1-203.1.113.60", seq="10"),
+ ],
+ ),
+ ],
+ internal=[
+ dict(
+ name="int-pool-1",
+ range=["10.0.0.0/24", "10.1.0.0/24"],
+ ),
+ ],
+ ),
+ rule=[
+ dict(
+ id=1,
+ source=dict(pool="int-pool-1"),
+ translation=dict(pool="ext-pool-1"),
+ ),
+ ],
+ ),
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Web server NAT",
+ protocol="tcp",
+ log=True,
+ inbound_interface=dict(name="eth2"),
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ address_mapping="persistent",
+ port_mapping="random",
+ ),
+ ),
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=["source-address", "destination-address"],
+ backend=[
+ dict(ip="10.10.10.1", weight=60),
+ dict(ip="10.10.10.2", weight=40),
+ ],
+ ),
+ ),
+ ],
+ ),
+ source=dict(
+ rule=[
+ dict(
+ id=200,
+ description="Outbound NAT",
+ protocol="tcp",
+ log=True,
+ exclude=True,
+ disable=True,
+ destination=dict(address="192.168.10.100", port="8083"),
+ translation=dict(address="masquerade", port="443"),
+ ),
+ ],
+ ),
+ static=dict(
+ rule=[
+ dict(
+ id=300,
+ description="Static mapping",
+ inbound_interface="eth2",
+ destination=dict(address="192.168.100.20"),
+ translation=dict(address="192.168.1.20"),
+ log=True,
+ ),
+ ],
+ ),
+ ),
+ nat64=dict(
+ source=dict(
+ rule=[
+ dict(
+ id=10,
+ description="NAT64 example",
+ disable=True,
+ match=dict(mark=100),
+ source=dict(prefix="2001:db8::/96"),
+ translation=dict(
+ pool=[
+ dict(
+ id=1,
+ address="192.168.100.10",
+ description="NAT64 translation pool",
+ disable=True,
+ port="1-65535",
+ protocol="udp",
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ nat66=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=20,
+ description="NAT66 DNAT",
+ protocol="tcp",
+ inbound_interface=dict(name="eth1"),
+ destination=dict(address="2001:db8::1"),
+ translation=dict(address="2001:db8:1::10", port="8443"),
+ ),
+ ],
+ ),
+ source=dict(
+ rule=[
+ dict(
+ id=30,
+ description="NAT66 SNAT",
+ protocol="tcp",
+ destination=dict(prefix="2001:db8::/96"),
+ outbound_interface=dict(name="eth2"),
+ source=dict(prefix="2001:db8:2::/64"),
+ translation=dict(address="masquerade"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_nat_merged_new_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=101,
+ description="New DNAT rule",
+ protocol="tcp",
+ destination=dict(address="198.51.100.11", port="443"),
+ translation=dict(address="192.168.1.11", port="8443"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 101 description 'New DNAT rule'",
+ "set nat destination rule 101 protocol tcp",
+ "set nat destination rule 101 destination address 198.51.100.11",
+ "set nat destination rule 101 destination port 443",
+ "set nat destination rule 101 translation address 192.168.1.11",
+ "set nat destination rule 101 translation port 8443",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_update_existing_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Updated DNAT rule",
+ protocol="tcp",
+ inbound_interface=dict(name="eth2"),
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ address_mapping="persistent",
+ port_mapping="random",
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 100 description 'Updated DNAT rule'",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_cgnat_new_pool(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ pool=dict(
+ external=[
+ dict(
+ name="ext-pool-2",
+ external_port_range="30000-40000",
+ range=[dict(value="203.0.114.0/24")],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat cgnat pool external ext-pool-2 external-port-range 30000-40000",
+ "set nat cgnat pool external ext-pool-2 range 203.0.114.0/24",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_nat66_new_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat66=dict(
+ source=dict(
+ rule=[
+ dict(
+ id=31,
+ description="New NAT66 SNAT",
+ protocol="udp",
+ outbound_interface=dict(name="eth3"),
+ source=dict(prefix="2001:db8:3::/64"),
+ translation=dict(address="masquerade"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat66 source rule 31 description 'New NAT66 SNAT'",
+ "set nat66 source rule 31 protocol udp",
+ "set nat66 source rule 31 outbound-interface name eth3",
+ "set nat66 source rule 31 source prefix 2001:db8:3::/64",
+ "set nat66 source rule 31 translation address masquerade",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_static_inbound_interface_change(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ static=dict(
+ rule=[
+ dict(
+ id=300,
+ description="Static mapping",
+ inbound_interface="eth3",
+ destination=dict(address="192.168.100.20"),
+ translation=dict(address="192.168.1.20"),
+ log=True,
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = ["set nat static rule 300 inbound-interface eth3"]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # deleted
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_deleted_all(self):
+ set_module_args(dict(state="deleted"))
+ commands = [
+ "delete nat",
+ "delete nat64",
+ "delete nat66",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_specific_rules(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(rule=[dict(id=100)]),
+ source=dict(rule=[dict(id=200)]),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = [
+ "delete nat destination rule 100",
+ "delete nat source rule 200",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_cgnat_pool(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ pool=dict(
+ external=[dict(name="ext-pool-1")],
+ internal=[dict(name="int-pool-1")],
+ ),
+ ),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = [
+ "delete nat cgnat pool external ext-pool-1",
+ "delete nat cgnat pool internal int-pool-1",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_nat64_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat64=dict(
+ source=dict(rule=[dict(id=10)]),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = ["delete nat64 source rule 10"]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_deleted_nonexistent_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(rule=[dict(id=999)]),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_nat_deleted_cgnat_rule_full_match(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ rule=[
+ dict(
+ id=1,
+ source=dict(pool="int-pool-1"),
+ translation=dict(pool="ext-pool-1"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="deleted",
+ ),
+ )
+ commands = ["delete nat cgnat rule 1"]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # replaced
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_replaced_idempotent(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Web server NAT",
+ protocol="tcp",
+ log=True,
+ inbound_interface=dict(name="eth2"),
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ address_mapping="persistent",
+ port_mapping="random",
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_nat_replaced_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Replaced DNAT rule",
+ protocol="udp",
+ destination=dict(address="198.51.100.10", port="53"),
+ translation=dict(address="192.168.1.53", port="53"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete nat destination rule 100",
+ "set nat destination rule 100 description 'Replaced DNAT rule'",
+ "set nat destination rule 100 protocol udp",
+ "set nat destination rule 100 destination address 198.51.100.10",
+ "set nat destination rule 100 destination port 53",
+ "set nat destination rule 100 translation address 192.168.1.53",
+ "set nat destination rule 100 translation port 53",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_replaced_cgnat_pool_partial_field_change(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ cgnat=dict(
+ pool=dict(
+ external=[
+ dict(
+ name="ext-pool-1",
+ external_port_range="30000-40000",
+ per_user_limit=dict(port="200"),
+ range=[
+ dict(value="203.0.113.0/24"),
+ dict(value="203.1.113.1-203.1.113.60", seq="10"),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete nat cgnat pool external ext-pool-1",
+ "delete nat cgnat log-allocation",
+ "set nat cgnat pool external ext-pool-1 external-port-range 30000-40000",
+ "set nat cgnat pool external ext-pool-1 per-user-limit port 200",
+ "set nat cgnat pool external ext-pool-1 range 203.0.113.0/24",
+ "set nat cgnat pool external ext-pool-1 range 203.1.113.1-203.1.113.60 seq 10",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # overridden
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_overridden_remove_sections(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Overridden web server NAT", # changed
+ protocol="tcp",
+ inbound_interface=dict(name="eth3"), # changed
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(
+ address="192.168.1.10",
+ port="8080",
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="overridden",
+ ),
+ )
+ commands = [
+ "delete nat cgnat",
+ "delete nat source",
+ "delete nat static",
+ "delete nat64",
+ "delete nat66",
+ "delete nat destination rule 100",
+ "delete nat destination rule 105",
+ "set nat destination rule 100 description 'Overridden web server NAT'",
+ "set nat destination rule 100 protocol tcp",
+ "set nat destination rule 100 inbound-interface name eth3",
+ "set nat destination rule 100 destination address 198.51.100.10",
+ "set nat destination rule 100 destination port 80",
+ "set nat destination rule 100 translation address 192.168.1.10",
+ "set nat destination rule 100 translation port 8080",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ # -------------------------------------------------------------------------
+ # rendered
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_rendered(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=100,
+ description="Rendered rule",
+ protocol="tcp",
+ destination=dict(address="198.51.100.10", port="80"),
+ translation=dict(address="192.168.1.10", port="8080"),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="rendered",
+ ),
+ )
+ rendered_cmds = [
+ "set nat destination rule 100 description 'Rendered rule'",
+ "set nat destination rule 100 protocol tcp",
+ "set nat destination rule 100 destination address 198.51.100.10",
+ "set nat destination rule 100 destination port 80",
+ "set nat destination rule 100 translation address 192.168.1.10",
+ "set nat destination rule 100 translation port 8080",
+ ]
+ result = self.execute_module(changed=False)
+ self.assertEqual(sorted(result["rendered"]), sorted(rendered_cmds), result["rendered"])
+
+ # -------------------------------------------------------------------------
+ # parsed
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_parsed(self):
+ parsed_str = (
+ "set nat destination rule 100 description 'Web server NAT'\n"
+ "set nat destination rule 100 destination address '198.51.100.10'\n"
+ "set nat destination rule 100 destination port '80'\n"
+ "set nat destination rule 100 inbound-interface name 'eth2'\n"
+ "set nat destination rule 100 log\n"
+ "set nat destination rule 100 protocol 'tcp'\n"
+ "set nat destination rule 100 translation address '192.168.1.10'\n"
+ "set nat destination rule 100 translation port '8080'"
+ )
+ set_module_args(dict(running_config=parsed_str, state="parsed"))
+ result = self.execute_module(changed=False)
+ parsed_list = {
+ "nat": {
+ "destination": {
+ "rule": [
+ {
+ "id": 100,
+ "description": "Web server NAT",
+ "protocol": "tcp",
+ "log": True,
+ "inbound_interface": {"name": "eth2"},
+ "destination": {"address": "198.51.100.10", "port": "80"},
+ "translation": {"address": "192.168.1.10", "port": "8080"},
+ },
+ ],
+ },
+ },
+ }
+ self.assertEqual(parsed_list, result["parsed"])
+
+ # -------------------------------------------------------------------------
+ # gathered
+ # -------------------------------------------------------------------------
+
+ def test_vyos_nat_gathered(self):
+ set_module_args(dict(state="gathered"))
+ result = self.execute_module(changed=False)
+ gathered = result["gathered"]
+ self.assertIn("nat", gathered)
+ self.assertIn("nat64", gathered)
+ self.assertIn("nat66", gathered)
+ self.assertEqual(gathered["nat"]["destination"]["rule"][0]["id"], 100)
+ self.assertEqual(gathered["nat64"]["source"]["rule"][0]["id"], 10)
+ self.assertEqual(gathered["nat66"]["destination"]["rule"][0]["id"], 20)
+
+ def test_vyos_nat_merged_load_balance_new_rule(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=110,
+ protocol="tcp",
+ destination=dict(port="443"),
+ load_balance=dict(
+ hash=["random"],
+ backend=[
+ dict(ip="10.20.20.1", weight=100),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 110 protocol tcp",
+ "set nat destination rule 110 destination port 443",
+ "set nat destination rule 110 load-balance hash random",
+ "set nat destination rule 110 load-balance backend 10.20.20.1 weight 100",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_load_balance_hash_add(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=[
+ "source-address",
+ "destination-address",
+ "source-port",
+ ],
+ backend=[
+ dict(ip="10.10.10.1", weight=60),
+ dict(ip="10.10.10.2", weight=40),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = ["set nat destination rule 105 load-balance hash source-port"]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_merged_load_balance_backend_weight_change(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=["source-address", "destination-address"],
+ backend=[
+ dict(ip="10.10.10.1", weight=70),
+ dict(ip="10.10.10.2", weight=30),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ commands = [
+ "set nat destination rule 105 load-balance backend 10.10.10.1 weight 70",
+ "set nat destination rule 105 load-balance backend 10.10.10.2 weight 30",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_nat_replaced_load_balance_removes_omitted(self):
+ set_module_args(
+ dict(
+ config=dict(
+ nat=dict(
+ destination=dict(
+ rule=[
+ dict(
+ id=105,
+ protocol="tcp",
+ destination=dict(port="80"),
+ inbound_interface=dict(name="eth0"),
+ load_balance=dict(
+ hash=["source-address"],
+ backend=[
+ dict(ip="10.10.10.1", weight=100),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete nat destination rule 105",
+ "set nat destination rule 105 protocol tcp",
+ "set nat destination rule 105 destination port 80",
+ "set nat destination rule 105 inbound-interface name eth0",
+ "set nat destination rule 105 load-balance hash source-address",
+ "set nat destination rule 105 load-balance backend 10.10.10.1 weight 100",
+ ]
+ self.execute_module(changed=True, commands=commands)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 6, 10:11 AM (1 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4654167
Default Alt Text
(483 KB)

Event Timeline