summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorViacheslav Hletenko <v.gletenko@vyos.io>2025-04-25 14:52:02 +0300
committerGitHub <noreply@github.com>2025-04-25 14:52:02 +0300
commitf4e02fdff7a7fcb42a1c5fc313e317af63cd1dcd (patch)
tree046587e39d00c2959cd159583b6b4059de16de99
parent4d32bcddda72c1df944470f06b75908b2dc31667 (diff)
parentfa293316e679645e6397697428cee96e32e346fc (diff)
downloadvyos-1x-f4e02fdff7a7fcb42a1c5fc313e317af63cd1dcd.tar.gz
vyos-1x-f4e02fdff7a7fcb42a1c5fc313e317af63cd1dcd.zip
Merge pull request #29 from natali-rs1985/T7181-stati+dynamic
T7181: VPP Static and dynamic NAT
-rw-r--r--data/config-mode-dependencies/vyos-vpp.json17
-rw-r--r--interface-definitions/include/vpp/nat_address_range.xml.i20
-rw-r--r--interface-definitions/include/vpp/nat_interface.xml.i11
-rw-r--r--interface-definitions/include/vpp/nat_protocol.xml.i30
-rw-r--r--interface-definitions/vpp.xml.in293
-rw-r--r--op-mode-definitions/show_vpp_nat44.xml.in12
-rw-r--r--python/vyos/vpp/control_vpp.py50
-rw-r--r--python/vyos/vpp/nat/nat44.py210
-rwxr-xr-xsrc/conf_mode/vpp.py48
-rw-r--r--src/conf_mode/vpp_nat.py458
-rw-r--r--src/conf_mode/vpp_nat_source.py119
-rw-r--r--src/conf_mode/vpp_nat_static.py200
-rw-r--r--src/op_mode/show_vpp_nat44.py91
13 files changed, 1043 insertions, 516 deletions
diff --git a/data/config-mode-dependencies/vyos-vpp.json b/data/config-mode-dependencies/vyos-vpp.json
index 307e763b9..46c1ee9f2 100644
--- a/data/config-mode-dependencies/vyos-vpp.json
+++ b/data/config-mode-dependencies/vyos-vpp.json
@@ -10,43 +10,42 @@
"vpp_interfaces_loopback": ["vpp_interfaces_loopback"],
"vpp_interfaces_vxlan": ["vpp_interfaces_vxlan"],
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
- "vpp_nat_source": ["vpp_nat_source"],
- "vpp_nat_static": ["vpp_nat_static"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_bonding": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
- "vpp_nat_source": ["vpp_nat_source"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_ethernet": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
- "vpp_nat_source": ["vpp_nat_source"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_geneve": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
- "vpp_nat_source": ["vpp_nat_source"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_gre": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
- "vpp_nat_source": ["vpp_nat_source"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_ipip": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
- "vpp_nat_source": ["vpp_nat_source"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_loopback": {
- "vpp_nat_source": ["vpp_nat_source"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_vxlan": {
"vpp_interfaces_bridge": ["vpp_interfaces_bridge"],
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
- "vpp_nat_source": ["vpp_nat_source"],
+ "vpp_nat": ["vpp_nat"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
}
}
diff --git a/interface-definitions/include/vpp/nat_address_range.xml.i b/interface-definitions/include/vpp/nat_address_range.xml.i
new file mode 100644
index 000000000..48648f91a
--- /dev/null
+++ b/interface-definitions/include/vpp/nat_address_range.xml.i
@@ -0,0 +1,20 @@
+<!-- include start from vpp/nat_address_range.xml.i -->
+<leafNode name="address">
+ <properties>
+ <help>IP address or range</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4range</format>
+ <description>IPv4 address range</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv4-range"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/vpp/nat_interface.xml.i b/interface-definitions/include/vpp/nat_interface.xml.i
new file mode 100644
index 000000000..20a7356bf
--- /dev/null
+++ b/interface-definitions/include/vpp/nat_interface.xml.i
@@ -0,0 +1,11 @@
+<!-- include start from vpp/nat_interface.xml.i -->
+<leafNode name="interface">
+ <properties>
+ <help>Add IP address from an interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/vpp/nat_protocol.xml.i b/interface-definitions/include/vpp/nat_protocol.xml.i
new file mode 100644
index 000000000..b88fd2b62
--- /dev/null
+++ b/interface-definitions/include/vpp/nat_protocol.xml.i
@@ -0,0 +1,30 @@
+<!-- include start from vpp/nat_protocol.xml.i -->
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol</help>
+ <completionHelp>
+ <list>tcp udp icmp all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All protocols (TCP, UDP, and ICMP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>icmp</format>
+ <description>Internet Control Message Protocol (ICMP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>Transmission Control Protocol (TCP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>udp</format>
+ <description>User Datagram Protocol (UDP)</description>
+ </valueHelp>
+ <constraint>
+ <regex>(tcp|udp|icmp|all)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>all</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in
index 263f9cb91..b1ae348da 100644
--- a/interface-definitions/vpp.xml.in
+++ b/interface-definitions/vpp.xml.in
@@ -788,6 +788,98 @@
</leafNode>
</children>
</node>
+ <node name="nat44">
+ <properties>
+ <help>NAT settings</help>
+ </properties>
+ <children>
+ <node name="timeout">
+ <properties>
+ <help>NAT44 session timeouts</help>
+ </properties>
+ <children>
+ <leafNode name="icmp">
+ <properties>
+ <help>ICMP timeout</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Timeout in seconds (default: 60)</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ <leafNode name="tcp-established">
+ <properties>
+ <help>TCP established timeout</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>TCP established timeout in seconds (default: 7440)</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>7440</defaultValue>
+ </leafNode>
+ <leafNode name="tcp-transitory">
+ <properties>
+ <help>TCP transitory timeout</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Timeout in seconds (default: 240)</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>240</defaultValue>
+ </leafNode>
+ <leafNode name="udp">
+ <properties>
+ <help>UDP timeout</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Timeout in seconds (default: 60)</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="session-limit">
+ <properties>
+ <help>Maximum number of sessions per thread</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Number of sessions</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4294967295"/>
+ </constraint>
+ <constraintErrorMessage>Number of sessions must be between 1 and 4294967295</constraintErrorMessage>
+ </properties>
+ <defaultValue>64512</defaultValue>
+ </leafNode>
+ <leafNode name="workers">
+ <properties>
+ <help>List of NAT workers</help>
+ <valueHelp>
+ <format>&lt;id&gt;</format>
+ <description>Worker id</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;idN&gt;-&lt;idM&gt;</format>
+ <description>Worker id range (use '-' as delimiter)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--allow-range --range 0-512"/>
+ </constraint>
+ <constraintErrorMessage>Not a valid value or range</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="no-forwarding">
+ <properties>
+ <help>Do not forward packets which do not match existing NAT translations (static or dynamic)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<node name="physmem">
<properties>
<help>Physical memory settings</help>
@@ -842,73 +934,65 @@
</node>
</children>
</node>
- <node name="nat44">
+ <node name="nat44" owner="${vyos_conf_scripts_dir}/vpp_nat.py">
<properties>
<help>NAT44</help>
+ <priority>320</priority>
</properties>
<children>
- <node name="source" owner="${vyos_conf_scripts_dir}/vpp_nat_source.py">
+ <node name="interface">
<properties>
- <help>Source NAT setting</help>
- <priority>320</priority>
+ <help>NAT interface setting</help>
</properties>
<children>
- <leafNode name="inside-interface">
+ <leafNode name="inside">
<properties>
<help>NAT inside interface</help>
<completionHelp>
- <list>any</list>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
+ <multi/>
</properties>
</leafNode>
- <leafNode name="outside-interface">
+ <leafNode name="outside">
<properties>
<help>NAT outside interface</help>
<completionHelp>
- <list>any</list>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
+ <multi/>
</properties>
</leafNode>
+ </children>
+ </node>
+ <node name="address-pool">
+ <properties>
+ <help>NAT address pool</help>
+ </properties>
+ <children>
<node name="translation">
<properties>
- <help>Outside NAT IP (source NAT only)</help>
+ <help>NAT translation pool</help>
</properties>
<children>
- <leafNode name="address">
- <properties>
- <help>IP address, subnet, or range</help>
- <completionHelp>
- <list>masquerade</list>
- </completionHelp>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address to match</description>
- </valueHelp>
- <valueHelp>
- <format>ipv4range</format>
- <description>IPv4 address range to match</description>
- </valueHelp>
- <valueHelp>
- <format>masquerade</format>
- <description>NAT to the primary address of outbound-interface</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv4-range"/>
- <regex>(masquerade)</regex>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/vpp/nat_address_range.xml.i>
+ #include <include/vpp/nat_interface.xml.i>
+ </children>
+ </node>
+ <node name="twice-nat">
+ <properties>
+ <help>NAT twice-nat pool</help>
+ </properties>
+ <children>
+ #include <include/vpp/nat_address_range.xml.i>
+ #include <include/vpp/nat_interface.xml.i>
</children>
</node>
</children>
</node>
- <node name="static" owner="${vyos_conf_scripts_dir}/vpp_nat_static.py">
+ <node name="static">
<properties>
- <help>Static NAT settings</help>
- <priority>321</priority>
+ <help>Static NAT rules</help>
</properties>
<children>
<tagNode name="rule">
@@ -920,6 +1004,7 @@
</valueHelp>
</properties>
<children>
+ #include <include/generic-description.xml.i>
<node name="external">
<properties>
<help>NAT external parameters</help>
@@ -940,50 +1025,6 @@
#include <include/port-number.xml.i>
</children>
</node>
- <leafNode name="protocol">
- <properties>
- <help>Protocol to NAT</help>
- <completionHelp>
- <list>tcp udp icmp all</list>
- </completionHelp>
- <valueHelp>
- <format>all</format>
- <description>All protocols (TCP, UDP, and ICMP)</description>
- </valueHelp>
- <valueHelp>
- <format>icmp</format>
- <description>Internet control message protocol</description>
- </valueHelp>
- <valueHelp>
- <format>tcp</format>
- <description>Transmission control protocol</description>
- </valueHelp>
- <valueHelp>
- <format>udp</format>
- <description>user datagram protocol</description>
- </valueHelp>
- <constraint>
- <regex>(tcp|udp|icmp|all)</regex>
- </constraint>
- </properties>
- <defaultValue>all</defaultValue>
- </leafNode>
- <leafNode name="outside-interface">
- <properties>
- <help>NAT outside interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="inside-interface">
- <properties>
- <help>NAT inside interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- </properties>
- </leafNode>
<node name="local">
<properties>
<help>NAT local parameters</help>
@@ -1004,6 +1045,96 @@
#include <include/port-number.xml.i>
</children>
</node>
+ <node name="options">
+ <properties>
+ <help>NAT static mapping options</help>
+ </properties>
+ <children>
+ <leafNode name="twice-nat">
+ <properties>
+ <help>Rewrite source IP addresses on packets sent from outside to inside</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="self-twice-nat">
+ <properties>
+ <help>Rewrite source IP addresses on packets sent only from a local address to an external address</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="out-to-in-only">
+ <properties>
+ <help>Only apply rule for traffic from outside to inside interfaces</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="twice-nat-address">
+ <properties>
+ <help>Force use of specific IP address from twice-nat address pool</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/vpp/nat_protocol.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="exclude">
+ <properties>
+ <help>Exclude packets matching these rules from NAT</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Rule number</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Number of rule</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="local-address">
+ <properties>
+ <help>IP address of the internal (local) device</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="local-port">
+ <properties>
+ <help>Port number used by connection on internal device</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ #include <include/vpp/nat_protocol.xml.i>
+ <leafNode name="external-interface">
+ <properties>
+ <help>External interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
#include <include/generic-description.xml.i>
</children>
</tagNode>
diff --git a/op-mode-definitions/show_vpp_nat44.xml.in b/op-mode-definitions/show_vpp_nat44.xml.in
index f2e77267a..96d53fba9 100644
--- a/op-mode-definitions/show_vpp_nat44.xml.in
+++ b/op-mode-definitions/show_vpp_nat44.xml.in
@@ -27,6 +27,18 @@
</properties>
<command>sudo ${vyos_op_scripts_dir}/show_vpp_nat44.py show_summary</command>
</node>
+ <node name="addresses">
+ <properties>
+ <help>Show VPP NAT44 pool addresses</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/show_vpp_nat44.py show_addresses</command>
+ </node>
+ <node name="interfaces">
+ <properties>
+ <help>Show VPP NAT44 interfaces</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/show_vpp_nat44.py show_interfaces</command>
+ </node>
</children>
</node>
</children>
diff --git a/python/vyos/vpp/control_vpp.py b/python/vyos/vpp/control_vpp.py
index 9d45bbd91..5db0ea560 100644
--- a/python/vyos/vpp/control_vpp.py
+++ b/python/vyos/vpp/control_vpp.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2023 VyOS Inc.
+# Copyright (C) 2023-2025 VyOS Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -439,6 +439,54 @@ class VPPControl:
return iface.interface_dev_type
return None
+ @_Decorators.api_call
+ def enable_disable_nat44_forwarding(self, enable: bool) -> None:
+ """Enable/disable NAT44 forwarding
+
+ Args:
+ enable (bool): True if enable, False if disable
+ """
+ self.__vpp_api_client.api.nat44_forwarding_enable_disable(enable=enable)
+
+ @_Decorators.api_call
+ def set_nat_timeouts(
+ self, icmp: int, udp: int, tcp_established: int, tcp_transitory: int
+ ) -> None:
+ """Set NAT timeouts
+
+ Args:
+ tcp_established (int): TCP established timeout
+ tcp_transitory (int): TCP transitory timeout
+ udp (int): UDP timeout
+ icmp (int): ICMP timeout
+ """
+ self.__vpp_api_client.api.nat_set_timeouts(
+ icmp=icmp,
+ udp=udp,
+ tcp_established=tcp_established,
+ tcp_transitory=tcp_transitory,
+ )
+
+ @_Decorators.api_call
+ def set_nat44_session_limit(self, session_limit: int) -> None:
+ """Set NAT44 session limit
+
+ Args:
+ session_limit (int): Maximum number of sessions per thread
+ """
+ self.__vpp_api_client.api.nat44_set_session_limit(
+ session_limit=session_limit,
+ )
+
+ @_Decorators.api_call
+ def set_nat_workers(self, workers: int) -> None:
+ """Set NAT44 session limit
+
+ Args:
+ workers (int): Bitmask of workers list
+ """
+ self.__vpp_api_client.api.nat_set_workers(worker_mask=workers)
+
@property
def connected(self) -> bool:
"""Check if VPP API is connected
diff --git a/python/vyos/vpp/nat/nat44.py b/python/vyos/vpp/nat/nat44.py
index 97c4518e6..e01e05153 100644
--- a/python/vyos/vpp/nat/nat44.py
+++ b/python/vyos/vpp/nat/nat44.py
@@ -18,16 +18,20 @@
from vyos.vpp import VPPControl
+# NAT44 flags
+NAT_IS_NONE = 0x00
+NAT_IS_TWICE_NAT = 0x01
+NAT_IS_SELF_TWICE_NAT = 0x02
+NAT_IS_OUT2IN_ONLY = 0x04
+NAT_IS_ADDR_ONLY = 0x08
+NAT_IS_OUTSIDE = 0x10
+NAT_IS_INSIDE = 0x20
+
+NO_INTERFACE = 0xFFFFFFFF
+
+
class Nat44:
- def __init__(
- self,
- interface_in: str,
- interface_out: str,
- translation_pool: str,
- ):
- self.interface_in = interface_in
- self.interface_out = interface_out
- self.translation_pool = translation_pool
+ def __init__(self):
self.vpp = VPPControl()
def enable_nat44_ed(self):
@@ -40,6 +44,10 @@ class Nat44:
"""
self.vpp.api.nat44_ed_plugin_enable_disable(enable=True)
+ def disable_nat44_ed(self):
+ """Disable NAT44 endpoint dependent plugin"""
+ self.vpp.api.nat44_ed_plugin_enable_disable(enable=False)
+
def enable_nat44_ei(self):
"""Enable NAT44 endpoint independent plugin
Example:
@@ -49,148 +57,164 @@ class Nat44:
"""
self.vpp.api.nat44_ei_plugin_enable_disable(enable=True)
- def enable_nat44_forwarding(self):
- """Enable NAT44 forwarding
- Example:
- from vyos.vpp.nat import Nat44
- nat44 = Nat44()
- nat44.enable_nat44_forwarding()
- """
- self.vpp.api.nat44_forwarding_enable_disable(enable=True)
-
- def disable_nat44_forwarding(self):
- """Disable NAT44 forwarding
- Example:
- from vyos.vpp.nat import Nat44
- nat44 = Nat44()
- nat44.disable_nat44_forwarding()
- """
- self.vpp.api.nat44_forwarding_enable_disable(enable=False)
-
- def add_nat44_out_interface(self):
- """Add NAT44 output interface
- Example:
- from vyos.vpp.nat import Nat44
- nat44 = Nat44('eth0')
- nat44.add_nat44_out_interface()
- """
- self.vpp.api.nat44_ed_add_del_output_interface(
- sw_if_index=self.vpp.get_sw_if_index(self.interface_out),
- is_add=True,
- )
-
- def delete_nat44_out_interface(self):
- """Delete NAT44 output interface"""
- self.vpp.api.nat44_ed_add_del_output_interface(
- sw_if_index=self.vpp.get_sw_if_index(self.interface_out),
- is_add=False,
- )
-
- def add_nat44_interface_inside(self):
+ def add_nat44_interface_inside(self, interface_in):
"""Add NAT44 interface"""
self.vpp.api.nat44_interface_add_del_feature(
- flags=0x20,
- sw_if_index=self.vpp.get_sw_if_index(self.interface_in),
+ flags=NAT_IS_INSIDE,
+ sw_if_index=self.vpp.get_sw_if_index(interface_in),
is_add=True,
)
- def delete_nat44_interface_inside(self):
+ def delete_nat44_interface_inside(self, interface_in):
"""Delete NAT44 interface"""
self.vpp.api.nat44_interface_add_del_feature(
- flags=0x20,
- sw_if_index=self.vpp.get_sw_if_index(self.interface_in),
+ flags=NAT_IS_INSIDE,
+ sw_if_index=self.vpp.get_sw_if_index(interface_in),
is_add=False,
)
- def add_nat44_interface_outside(self):
+ def add_nat44_interface_outside(self, interface_out):
"""Add NAT44 interface"""
self.vpp.api.nat44_interface_add_del_feature(
- flags=0x10,
- sw_if_index=self.vpp.get_sw_if_index(self.interface_out),
+ flags=NAT_IS_OUTSIDE,
+ sw_if_index=self.vpp.get_sw_if_index(interface_out),
is_add=True,
)
- def delete_nat44_interface_outside(self):
+ def delete_nat44_interface_outside(self, interface_out):
"""Delete NAT44 interface"""
self.vpp.api.nat44_interface_add_del_feature(
- flags=0x10,
- sw_if_index=self.vpp.get_sw_if_index(self.interface_out),
+ flags=NAT_IS_OUTSIDE,
+ sw_if_index=self.vpp.get_sw_if_index(interface_out),
is_add=False,
)
- def add_nat44_address_range(self):
+ def add_nat44_address_range(self, addresses, twice_nat):
"""Add NAT44 address range"""
- if '-' not in self.translation_pool and self.translation_pool != 'masquerade':
- first_ip_address = last_ip_address = self.translation_pool
+ if '-' not in addresses:
+ first_ip_address = last_ip_address = addresses
else:
- first_ip_address, last_ip_address = self.translation_pool.split('-')
+ first_ip_address, last_ip_address = addresses.split('-')
self.vpp.api.nat44_add_del_address_range(
+ flags=NAT_IS_TWICE_NAT if twice_nat else NAT_IS_NONE,
first_ip_address=first_ip_address,
last_ip_address=last_ip_address,
is_add=True,
)
- def delete_nat44_address_range(self):
+ def delete_nat44_address_range(self, addresses, twice_nat):
"""Delete NAT44 address range"""
- if '-' not in self.translation_pool and self.translation_pool != 'masquerade':
- first_ip_address = last_ip_address = self.translation_pool
+ if '-' not in addresses:
+ first_ip_address = last_ip_address = addresses
else:
- first_ip_address, last_ip_address = self.translation_pool.split('-')
+ first_ip_address, last_ip_address = addresses.split('-')
self.vpp.api.nat44_add_del_address_range(
+ flags=NAT_IS_TWICE_NAT if twice_nat else NAT_IS_NONE,
first_ip_address=first_ip_address,
last_ip_address=last_ip_address,
is_add=False,
)
- def enable_ipfix(self):
- """Enable NAT44 IPFIX logging"""
- self.vpp.api.nat44_ei_ipfix_enable_disable(enable=True)
-
-
-class Nat44Static(Nat44):
- def __init__(self):
- self.vpp = VPPControl()
-
- def add_inside_interface(self, interface_in):
- self.interface_in = interface_in
- self.add_nat44_interface_inside()
-
- def delete_inside_interface(self, interface_in):
- self.interface_in = interface_in
- self.delete_nat44_interface_inside()
-
- def add_outside_interface(self, interface_out):
- self.interface_out = interface_out
- self.add_nat44_interface_outside()
+ def add_nat44_interface_address(self, interface, twice_nat):
+ """Add NAT44 interface address"""
+ self.vpp.api.nat44_add_del_interface_addr(
+ flags=NAT_IS_TWICE_NAT if twice_nat else NAT_IS_NONE,
+ sw_if_index=self.vpp.get_sw_if_index(interface),
+ is_add=True,
+ )
- def delete_outside_interface(self, interface_out):
- self.interface_out = interface_out
- self.delete_nat44_interface_outside()
+ def delete_nat44_interface_address(self, interface, twice_nat):
+ """Delete NAT44 interface address"""
+ self.vpp.api.nat44_add_del_interface_addr(
+ flags=NAT_IS_TWICE_NAT if twice_nat else NAT_IS_NONE,
+ sw_if_index=self.vpp.get_sw_if_index(interface),
+ is_add=False,
+ )
def add_nat44_static_mapping(
- self, local_ip, external_ip, local_port, external_port, protocol
+ self,
+ local_ip,
+ external_ip,
+ local_port,
+ external_port,
+ protocol,
+ twice_nat,
+ self_twice_nat,
+ out2in,
+ pool_ip,
):
"""Add NAT44 static mapping"""
+ flags = NAT_IS_ADDR_ONLY if not (protocol or local_port) else NAT_IS_NONE
+ flags |= NAT_IS_TWICE_NAT if twice_nat else 0
+ flags |= NAT_IS_SELF_TWICE_NAT if self_twice_nat else 0
+ flags |= NAT_IS_OUT2IN_ONLY if out2in else 0
self.vpp.api.nat44_add_del_static_mapping_v2(
local_ip_address=local_ip,
external_ip_address=external_ip,
protocol=protocol,
local_port=local_port,
external_port=external_port,
- flags=0x08 if not (protocol or local_port) else 0x00,
+ match_pool=True if pool_ip else False,
+ pool_ip_address=pool_ip if pool_ip else '',
+ flags=flags,
is_add=True,
)
def delete_nat44_static_mapping(
- self, local_ip, external_ip, local_port, external_port, protocol
+ self,
+ local_ip,
+ external_ip,
+ local_port,
+ external_port,
+ protocol,
+ twice_nat,
+ self_twice_nat,
+ out2in,
+ pool_ip,
):
"""Delete NAT44 static mapping"""
+ flags = NAT_IS_ADDR_ONLY if not (protocol or local_port) else NAT_IS_NONE
+ flags |= NAT_IS_TWICE_NAT if twice_nat else 0
+ flags |= NAT_IS_SELF_TWICE_NAT if self_twice_nat else 0
+ flags |= NAT_IS_OUT2IN_ONLY if out2in else 0
self.vpp.api.nat44_add_del_static_mapping_v2(
local_ip_address=local_ip,
external_ip_address=external_ip,
protocol=protocol,
local_port=local_port,
external_port=external_port,
- flags=0x08 if not (protocol or local_port) else 0x00,
+ match_pool=True if pool_ip else False,
+ pool_ip_address=pool_ip if pool_ip else '',
+ flags=flags,
is_add=False,
)
+
+ def add_nat44_identity_mapping(self, ip_address, protocol, port, interface):
+ """Add NAT44 identity mapping"""
+ self.vpp.api.nat44_add_del_identity_mapping(
+ ip_address=ip_address,
+ protocol=protocol,
+ port=port,
+ sw_if_index=(
+ self.vpp.get_sw_if_index(interface) if interface else NO_INTERFACE
+ ),
+ flags=NAT_IS_ADDR_ONLY if not (protocol or port) else NAT_IS_NONE,
+ is_add=True,
+ )
+
+ def delete_nat44_identity_mapping(self, ip_address, protocol, port, interface):
+ """Delete NAT44 identity mapping"""
+ self.vpp.api.nat44_add_del_identity_mapping(
+ ip_address=ip_address,
+ protocol=protocol,
+ port=port,
+ sw_if_index=(
+ self.vpp.get_sw_if_index(interface) if interface else NO_INTERFACE
+ ),
+ flags=NAT_IS_ADDR_ONLY if not (protocol or port) else NAT_IS_NONE,
+ is_add=False,
+ )
+
+ def enable_ipfix(self):
+ """Enable NAT44 IPFIX logging"""
+ self.vpp.api.nat44_ei_ipfix_enable_disable(enable=True)
diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py
index ca7d19160..7f9adc68b 100755
--- a/src/conf_mode/vpp.py
+++ b/src/conf_mode/vpp.py
@@ -136,10 +136,8 @@ def get_config(config=None):
set_dependents('ethernet', conf, removed_iface)
# NAT dependency
- if conf.exists(['vpp', 'nat44', 'source']):
- set_dependents('vpp_nat_source', conf)
- if conf.exists(['vpp', 'nat44', 'static']):
- set_dependents('vpp_nat_static', conf)
+ if conf.exists(['vpp', 'nat44']):
+ set_dependents('vpp_nat', conf)
if not conf.exists(base):
return {
@@ -424,6 +422,7 @@ def verify(config):
'Only one multipoint GRE tunnel is allowed from the same source address'
)
+ workers = 0
if 'cpu' in config['settings']:
if (
'corelist_workers' in config['settings']['cpu']
@@ -496,6 +495,22 @@ def verify(config):
if not all(el in cpus_available for el in all_core_numbers):
raise ConfigError('"cpu corelist-workers" is not correct')
+ workers = len(all_core_numbers)
+
+ if 'workers' in config['settings']['nat44']:
+ nat_workers = []
+ for worker_range in config['settings']['nat44']['workers']:
+ worker_numbers = worker_range.split('-')
+ if int(worker_numbers[0]) > int(worker_numbers[-1]):
+ raise ConfigError(
+ f'Range for "nat44 workers {worker_range}" is not correct'
+ )
+ nat_workers.extend(
+ range(int(worker_numbers[0]), int(worker_numbers[-1]) + 1)
+ )
+ if not all(el in list(range(workers)) for el in nat_workers):
+ raise ConfigError('"nat44 workers" is not correct')
+
verify_memory(config['settings'])
if 'host_resources' in config['settings']:
if (
@@ -716,6 +731,31 @@ def apply(config):
# Syncronize routes via LCP
vpp_control.lcp_resync()
+ # NAT44 settings
+ nat44_settings = config['settings'].get('nat44', {})
+
+ enable_forwarding = True
+ if 'no_forwarding' in nat44_settings:
+ enable_forwarding = False
+ vpp_control.enable_disable_nat44_forwarding(enable_forwarding)
+
+ vpp_control.set_nat_timeouts(
+ icmp=int(nat44_settings.get('timeout').get('icmp')),
+ udp=int(nat44_settings.get('timeout').get('udp')),
+ tcp_established=int(nat44_settings.get('timeout').get('tcp_established')),
+ tcp_transitory=int(nat44_settings.get('timeout').get('tcp_transitory')),
+ )
+
+ vpp_control.set_nat44_session_limit(int(nat44_settings.get('session_limit')))
+
+ if nat44_settings.get('workers'):
+ bitmask = 0
+ for worker_range in nat44_settings['workers']:
+ worker_numbers = worker_range.split('-')
+ for wid in range(int(worker_numbers[0]), int(worker_numbers[-1]) + 1):
+ bitmask |= 1 << wid
+ vpp_control.set_nat_workers(bitmask)
+
# Save persistent config
if 'persist_config' in config and config['persist_config']:
persist_config.write('eth_ifaces', config['persist_config'])
diff --git a/src/conf_mode/vpp_nat.py b/src/conf_mode/vpp_nat.py
new file mode 100644
index 000000000..d38599388
--- /dev/null
+++ b/src/conf_mode/vpp_nat.py
@@ -0,0 +1,458 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2025 VyOS Inc.
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import ipaddress
+
+from vyos import ConfigError
+
+from vyos.configdiff import Diff
+from vyos.configdict import node_changed
+from vyos.config import Config
+from vyos.utils.network import get_interface_address
+
+from vyos.vpp.utils import cli_ifaces_list
+from vyos.vpp.nat.nat44 import Nat44
+
+
+protocol_map = {
+ 'all': 0,
+ 'icmp': 1,
+ 'tcp': 6,
+ 'udp': 17,
+}
+
+
+def get_config(config=None) -> dict:
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['vpp', 'nat44']
+
+ # Get config_dict with default values
+ config = conf.get_config_dict(
+ base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_defaults=True,
+ with_recursive_defaults=True,
+ )
+
+ # Get effective config as we need full dictionary for deletion
+ effective_config = conf.get_config_dict(
+ base,
+ key_mangling=('-', '_'),
+ effective=True,
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ )
+
+ if not config:
+ config['remove'] = True
+ return config
+
+ config_changed = node_changed(
+ conf,
+ base,
+ key_mangling=('-', '_'),
+ recursive=True,
+ expand_nodes=Diff.DELETE | Diff.ADD,
+ )
+
+ changed_static_rules = node_changed(
+ conf,
+ base + ['static', 'rule'],
+ key_mangling=('-', '_'),
+ recursive=True,
+ expand_nodes=Diff.DELETE | Diff.ADD,
+ )
+
+ changed_exclude_rules = node_changed(
+ conf,
+ base + ['exclude', 'rule'],
+ key_mangling=('-', '_'),
+ recursive=True,
+ expand_nodes=Diff.DELETE | Diff.ADD,
+ )
+
+ if not config_changed:
+ changed_static_rules = list(config.get('static', {}).get('rule', {}).keys())
+ changed_exclude_rules = list(config.get('exclude', {}).get('rule', {}).keys())
+
+ config.update(
+ {
+ 'changed_static_rules': changed_static_rules,
+ 'changed_exclude_rules': changed_exclude_rules,
+ 'vpp_ifaces': cli_ifaces_list(conf),
+ }
+ )
+
+ if effective_config:
+ config.update({'effective': effective_config})
+
+ return config
+
+
+def convert_range_to_list_ips(address_range) -> list:
+ """Converts IP range to a list of IPs .
+
+ Example:
+ % ip = IPOperations('192.0.0.1-192.0.2.5')
+ % ip.convert_prefix_to_list_ips()
+ ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4', '192.0.2.5']
+ """
+ if '-' in address_range:
+ start_ip, end_ip = address_range.split('-')
+ start_ip = ipaddress.ip_address(start_ip)
+ end_ip = ipaddress.ip_address(end_ip)
+ return [
+ str(ipaddress.ip_address(ip))
+ for ip in range(int(start_ip), int(end_ip) + 1)
+ ]
+ else:
+ return [address_range]
+
+
+def verify(config):
+ if 'remove' in config:
+ return None
+
+ if 'interface' not in config:
+ raise ConfigError('Interfaces must be configured for NAT44')
+
+ required_keys = {'inside', 'outside'}
+ missing_keys = required_keys - set(config['interface'].keys())
+ if missing_keys:
+ raise ConfigError(
+ f'Both inside and outside interfaces must be configured. Please add: {", ".join(missing_keys)}'
+ )
+
+ for interface in config['interface']['inside']:
+ if interface not in config['vpp_ifaces']:
+ raise ConfigError(
+ f'{interface} must be a VPP interface for inside NAT interface'
+ )
+ for interface in config['interface']['outside']:
+ if interface not in config['vpp_ifaces']:
+ raise ConfigError(
+ f'{interface} must be a VPP interface for outside NAT interface'
+ )
+
+ if not config.get('address_pool', {}).get('translation') and not config.get(
+ 'static', {}
+ ).get('rule'):
+ raise ConfigError('"address-pool translation" or "static rule" is required')
+
+ addresses_translation = []
+ addresses_twice_nat = []
+ if 'address_pool' in config:
+ address_pool = config.get('address_pool')
+ if 'translation' in address_pool:
+ if not address_pool['translation'].get('address') and not address_pool[
+ 'translation'
+ ].get('interface'):
+ raise ConfigError(
+ '"address-pool translation" requires address or interface'
+ )
+
+ for address_range in address_pool['translation'].get('address', []):
+ addresses = convert_range_to_list_ips(address_range)
+ for address in addresses:
+ if address in addresses_translation:
+ raise ConfigError(
+ f'Address {address} is already in use in "address-pool translation address"'
+ )
+ addresses_translation.append(address)
+
+ for interface in address_pool['translation'].get('interface', []):
+ if interface not in config['vpp_ifaces']:
+ raise ConfigError(
+ f'{interface} must be a VPP interface for "address-pool translation interface"'
+ )
+ iface_address = get_interface_address(interface).get('addr_info', [])[0].get('local')
+ addresses_translation.append(iface_address)
+
+ if 'twice_nat' in address_pool:
+ if not address_pool['twice_nat'].get('address') and not address_pool[
+ 'twice_nat'
+ ].get('interface'):
+ raise ConfigError(
+ '"address-pool twice-nat" requires address or interface'
+ )
+
+ for address_range in address_pool['twice_nat'].get('address', []):
+ addresses = convert_range_to_list_ips(address_range)
+ for address in addresses:
+ if address in addresses_twice_nat:
+ raise ConfigError(
+ f'Address {address} is already in use in "address-pool twice-nat address"'
+ )
+ addresses_twice_nat.append(address)
+
+ for interface in address_pool['twice_nat'].get('interface', []):
+ if interface not in config['vpp_ifaces']:
+ raise ConfigError(
+ f'{interface} must be a VPP interface for "address-pool twice-nat interface"'
+ )
+ iface_address = get_interface_address(interface).get('addr_info', [])[0].get('local')
+ addresses_twice_nat.append(iface_address)
+
+ if 'static' in config:
+ addresses_with_ports = set()
+ addresses_without_ports = set()
+ local_addresses = set()
+
+ for rule, rule_config in config['static']['rule'].items():
+ error_msg = f'Configuration error in static rule {rule}:'
+
+ if not rule_config.get('local', {}).get('address'):
+ raise ConfigError(f'{error_msg} local settings require address')
+
+ if not rule_config.get('external', {}).get('address'):
+ raise ConfigError(f'{error_msg} external settings require address')
+
+ has_local_port = 'port' in rule_config.get('local', {})
+ has_external_port = 'port' in rule_config.get('external', {})
+
+ if not has_external_port == has_local_port:
+ raise ConfigError(
+ f'{error_msg} source and destination ports must either '
+ 'both be specified, or neither must be specified'
+ )
+
+ ext_address = rule_config['external']['address']
+ port = rule_config['external'].get('port')
+ local_address = rule_config['local']['address']
+
+ if port:
+ pair = (ext_address, port)
+ if (
+ pair in addresses_with_ports
+ or ext_address in addresses_without_ports
+ ):
+ raise ConfigError(
+ f'{error_msg} external address/port is already in use!'
+ )
+ addresses_with_ports.add(pair)
+ if ext_address not in addresses_translation:
+ raise ConfigError(
+ f'{error_msg} external address {ext_address} is not in "address-pool translation"'
+ )
+
+ else:
+ if ext_address in addresses_without_ports or any(
+ addr == ext_address for addr, _ in addresses_with_ports
+ ):
+ raise ConfigError(
+ f'{error_msg} external address is already in use!'
+ )
+ addresses_without_ports.add(ext_address)
+
+ if local_address in local_addresses:
+ raise ConfigError(
+ f'{error_msg} local address {local_address} is already in use'
+ )
+ local_addresses.add(local_address)
+
+ options = rule_config.get('options', {})
+ if all(key in options for key in ('twice_nat', 'self_twice_nat')):
+ raise ConfigError(
+ f'{error_msg} cannot set both options "twice-nat" and "self-twice-nat"'
+ )
+ if any(key in options for key in ('twice_nat', 'self_twice_nat')):
+ if not has_local_port or rule_config['protocol'] == 'all':
+ raise ConfigError(
+ f'{error_msg} twice-nat/self-twice-nat options require port and protocol to be set'
+ )
+ if not config.get('address_pool', {}).get('twice_nat'):
+ raise ConfigError(
+ f'{error_msg} twice-nat/self-twice-nat options require "address-pool twice-nat" to be set'
+ )
+ if 'twice_nat_address' in options:
+ if not any(key in options for key in ('twice_nat', 'self_twice_nat')):
+ raise ConfigError(
+ f'{error_msg} twice-nat/self-twice-nat option required when twice-nat-address is set'
+ )
+ tn_address = options['twice_nat_address']
+ if tn_address not in addresses_twice_nat:
+ raise ConfigError(
+ f'{error_msg} twice-nat-address {tn_address} is not in "address-pool twice-nat"'
+ )
+
+ if 'exclude' in config:
+ for rule, rule_config in config['exclude']['rule'].items():
+ keys = {'local_address', 'external_interface'}
+ if not any(key in rule_config for key in keys):
+ raise ConfigError(
+ f'Local-address or external-interface must be specified for exclude rule {rule}'
+ )
+ if all(key in rule_config for key in keys):
+ raise ConfigError(
+ f'Cannot set both address and interface for exclude rule {rule}'
+ )
+ if (
+ 'external_interface' in rule_config
+ and rule_config.get('external_interface') not in config['vpp_ifaces']
+ ):
+ raise ConfigError(
+ f'{rule_config["external_interface"]} must be a VPP interface for exclude rule {rule}'
+ )
+
+
+def generate(config):
+ pass
+
+
+def apply(config):
+ n = Nat44()
+
+ if 'remove' in config:
+ n.disable_nat44_ed()
+ return None
+
+ if 'effective' in config:
+ remove_config = config.get('effective')
+ # Delete inside interfaces
+ for interface in remove_config['interface']['inside']:
+ if interface not in config.get('interface', {}).get('inside', []):
+ n.delete_nat44_interface_inside(interface)
+ # Delete outside interfaces
+ for interface in remove_config['interface']['outside']:
+ if interface not in config.get('interface', {}).get('outside', []):
+ n.delete_nat44_interface_outside(interface)
+ # Delete address pool
+ address_pool = config.get('address_pool', {})
+ for address in (
+ remove_config.get('address_pool', {})
+ .get('translation', {})
+ .get('address', [])
+ ):
+ if address not in address_pool.get('translation', {}).get('address', []):
+ n.delete_nat44_address_range(address, twice_nat=False)
+ for interface in (
+ remove_config.get('address_pool', {})
+ .get('translation', {})
+ .get('interface', [])
+ ):
+ if interface not in address_pool.get('translation', {}).get(
+ 'interface', []
+ ):
+ n.delete_nat44_interface_address(interface, twice_nat=False)
+ for address in (
+ remove_config.get('address_pool', {})
+ .get('twice_nat', {})
+ .get('address', [])
+ ):
+ if address not in address_pool.get('twice_nat', {}).get('address', []):
+ n.delete_nat44_address_range(address, twice_nat=True)
+ for interface in (
+ remove_config.get('address_pool', {})
+ .get('twice_nat', {})
+ .get('interface', [])
+ ):
+ if interface not in address_pool.get('twice_nat', {}).get('interface', []):
+ n.delete_nat44_interface_address(interface, twice_nat=True)
+ # Delete NAT static mapping rules
+ for rule in config['changed_static_rules']:
+ if rule in remove_config.get('static', {}).get('rule', {}):
+ rule_config = remove_config['static']['rule'][rule]
+ n.delete_nat44_static_mapping(
+ local_ip=rule_config.get('local').get('address'),
+ external_ip=rule_config.get('external', {}).get('address', ''),
+ local_port=int(rule_config.get('local', {}).get('port', 0)),
+ external_port=int(rule_config.get('external', {}).get('port', 0)),
+ protocol=protocol_map[rule_config.get('protocol', 'all')],
+ twice_nat='twice_nat' in rule_config.get('options', {}),
+ self_twice_nat='self_twice_nat' in rule_config.get('options', {}),
+ out2in='out_to_in_only' in rule_config.get('options', {}),
+ pool_ip=rule_config.get('options', {}).get('twice_nat_address'),
+ )
+ # Delete NAT exclude rules
+ for rule in config['changed_exclude_rules']:
+ if rule in remove_config.get('exclude', {}).get('rule', {}):
+ rule_config = remove_config['exclude']['rule'][rule]
+ n.delete_nat44_identity_mapping(
+ ip_address=rule_config.get('local_address'),
+ protocol=protocol_map[rule_config.get('protocol', 'all')],
+ port=int(rule_config.get('local_port', 0)),
+ interface=rule_config.get('external_interface'),
+ )
+
+ # Add NAT44
+ n.enable_nat44_ed()
+ # Add inside interfaces
+ for interface in config['interface']['inside']:
+ n.add_nat44_interface_inside(interface)
+ # Add outside interfaces
+ for interface in config['interface']['outside']:
+ n.add_nat44_interface_outside(interface)
+ # Add translation pool
+ for address in (
+ config.get('address_pool', {}).get('translation', {}).get('address', [])
+ ):
+ n.add_nat44_address_range(address, twice_nat=False)
+ for interface in (
+ config.get('address_pool', {}).get('translation', {}).get('interface', [])
+ ):
+ n.add_nat44_interface_address(interface, twice_nat=False)
+ for address in (
+ config.get('address_pool', {}).get('twice_nat', {}).get('address', [])
+ ):
+ n.add_nat44_address_range(address, twice_nat=True)
+ for interface in (
+ config.get('address_pool', {}).get('twice_nat', {}).get('interface', [])
+ ):
+ n.add_nat44_interface_address(interface, twice_nat=True)
+ # Add NAT static mapping rules
+ for rule in config['changed_static_rules']:
+ if rule in config.get('static', {}).get('rule', {}):
+ rule_config = config['static']['rule'][rule]
+ n.add_nat44_static_mapping(
+ local_ip=rule_config.get('local').get('address'),
+ external_ip=rule_config.get('external', {}).get('address', ''),
+ local_port=int(rule_config.get('local', {}).get('port', 0)),
+ external_port=int(rule_config.get('external', {}).get('port', 0)),
+ protocol=protocol_map[rule_config.get('protocol', 'all')],
+ twice_nat='twice_nat' in rule_config.get('options', {}),
+ self_twice_nat='self_twice_nat' in rule_config.get('options', {}),
+ out2in='out_to_in_only' in rule_config.get('options', {}),
+ pool_ip=rule_config.get('options', {}).get('twice_nat_address'),
+ )
+ # Add NAT exclude rules
+ for rule in config['changed_exclude_rules']:
+ if rule in config.get('exclude', {}).get('rule', {}):
+ rule_config = config['exclude']['rule'][rule]
+ n.add_nat44_identity_mapping(
+ ip_address=rule_config.get('local_address'),
+ protocol=protocol_map[rule_config.get('protocol', 'all')],
+ port=int(rule_config.get('local_port', 0)),
+ interface=rule_config.get('external_interface'),
+ )
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/vpp_nat_source.py b/src/conf_mode/vpp_nat_source.py
deleted file mode 100644
index 40b16a3c8..000000000
--- a/src/conf_mode/vpp_nat_source.py
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2025 VyOS Inc.
-#
-# This program 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 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-from vyos.config import Config
-from vyos import ConfigError
-from vyos.vpp.nat.nat44 import Nat44
-
-
-def get_config(config=None) -> dict:
- if config:
- conf = config
- else:
- conf = Config()
-
- base = ['vpp', 'nat44', 'source']
-
- # Get config_dict with default values
- config = conf.get_config_dict(
- base,
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_defaults=True,
- with_recursive_defaults=True,
- )
-
- # Get effective config as we need full dicitonary per interface delete
- effective_config = conf.get_config_dict(
- base,
- key_mangling=('-', '_'),
- effective=True,
- get_first_key=True,
- no_tag_node_value_mangle=True,
- )
-
- if not config:
- config['remove'] = True
-
- if effective_config:
- config.update({'effective': effective_config})
-
- return config
-
-
-def verify(config):
- if 'remove' in config:
- return None
-
- required_keys = {'inside_interface', 'outside_interface'}
- if not all(key in config for key in required_keys):
- missing_keys = required_keys - set(config.keys())
- raise ConfigError(
- f"Required options are missing: {', '.join(missing_keys).replace('_', '-')}"
- )
-
- if not config.get('translation', {}).get('address'):
- raise ConfigError('Translation requires address')
-
- if config.get('translation', {}).get('address') == 'masquerade':
- raise ConfigError('Masquerade is not implemented')
-
-
-def generate(config):
- pass
-
-
-def apply(config):
- # Delete NAT source
- if 'effective' in config:
- remove_config = config.get('effective')
- interface_in = remove_config.get('inside_interface')
- interface_out = remove_config.get('outside_interface')
- translation_address = remove_config.get('translation', {}).get('address')
-
- n = Nat44(interface_in, interface_out, translation_address)
- n.delete_nat44_out_interface()
- n.delete_nat44_interface_inside()
- n.delete_nat44_address_range()
-
- if 'remove' in config:
- return None
-
- # Add NAT44
- interface_in = config.get('inside_interface')
- interface_out = config.get('outside_interface')
- translation_address = config.get('translation', {}).get('address')
-
- n = Nat44(interface_in, interface_out, translation_address)
- n.enable_nat44_ed()
- n.enable_nat44_forwarding()
- n.add_nat44_out_interface()
- # n.add_nat44_interface_inside()
- n.add_nat44_address_range()
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/vpp_nat_static.py b/src/conf_mode/vpp_nat_static.py
deleted file mode 100644
index b890ea150..000000000
--- a/src/conf_mode/vpp_nat_static.py
+++ /dev/null
@@ -1,200 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2025 VyOS Inc.
-#
-# This program 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 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-from vyos.configdiff import Diff
-from vyos.configdiff import get_config_diff
-from vyos.configdict import node_changed
-from vyos.config import Config
-from vyos import ConfigError
-from vyos.vpp.nat.nat44 import Nat44Static
-
-
-protocol_map = {
- 'all': 0,
- 'icmp': 1,
- 'tcp': 6,
- 'udp': 17,
-}
-
-
-def get_config(config=None) -> dict:
- if config:
- conf = config
- else:
- conf = Config()
-
- base = ['vpp', 'nat44', 'static']
-
- # Get config_dict with default values
- config = conf.get_config_dict(
- base,
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_defaults=True,
- with_recursive_defaults=True,
- )
-
- # Get effective config as we need full dictionary per interface delete
- effective_config = conf.get_config_dict(
- base,
- key_mangling=('-', '_'),
- effective=True,
- get_first_key=True,
- no_tag_node_value_mangle=True,
- )
-
- if not config:
- config['remove'] = True
-
- in_iface_add = []
- in_iface_del = []
- out_iface_add = []
- out_iface_del = []
-
- changed_rules = node_changed(
- conf,
- base + ['rule'],
- key_mangling=('-', '_'),
- recursive=True,
- expand_nodes=Diff.DELETE | Diff.ADD,
- )
- diff = get_config_diff(conf)
-
- for rule in changed_rules:
- base_rule = base + ['rule', rule]
- tmp = node_changed(
- conf,
- base_rule,
- key_mangling=('-', '_'),
- recursive=True,
- expand_nodes=Diff.DELETE | Diff.ADD,
- )
-
- if 'inside_interface' in tmp:
- new, old = diff.get_value_diff(base_rule + ['inside-interface'])
- in_iface_add.append(new) if new else None
- in_iface_del.append(old) if old else None
- if 'outside_interface' in tmp:
- new, old = diff.get_value_diff(base_rule + ['outside-interface'])
- out_iface_add.append(new) if new else None
- out_iface_del.append(old) if old else None
-
- final_in_iface_add = list(set(in_iface_add) - set(in_iface_del))
- final_in_iface_del = list(set(in_iface_del) - set(in_iface_add))
- final_out_iface_add = list(set(out_iface_add) - set(out_iface_del))
- final_out_iface_del = list(set(out_iface_del) - set(out_iface_add))
-
- config.update(
- {
- 'in_iface_add': final_in_iface_add,
- 'in_iface_del': final_in_iface_del,
- 'out_iface_add': final_out_iface_add,
- 'out_iface_del': final_out_iface_del,
- 'changed_rules': changed_rules,
- }
- )
-
- if effective_config:
- config.update({'effective': effective_config})
-
- return config
-
-
-def verify(config):
- if 'remove' in config:
- return None
-
- required_keys = {'inside_interface', 'outside_interface'}
- for rule, rule_config in config['rule'].items():
- missing_keys = required_keys - rule_config.keys()
- if missing_keys:
- raise ConfigError(
- f"Required options are missing: {', '.join(missing_keys).replace('_', '-')} in rule {rule}"
- )
-
- if not rule_config.get('local', {}).get('address'):
- raise ConfigError(f'Local settings require address in rule {rule}')
-
- if not rule_config.get('external', {}).get('address'):
- raise ConfigError(f'External settings require address in rule {rule}')
-
- has_local_port = 'port' in rule_config.get('local', {})
- has_external_port = 'port' in rule_config.get('external', {})
-
- if not has_external_port == has_local_port:
- raise ConfigError(
- 'Source and destination ports must either both be specified, or neither must be specified'
- )
-
-
-def generate(config):
- pass
-
-
-def apply(config):
- n = Nat44Static()
-
- # Delete inside interfaces
- for interface in config['in_iface_del']:
- n.delete_inside_interface(interface)
- # Delete outside interfaces
- for interface in config['out_iface_del']:
- n.delete_outside_interface(interface)
- # Delete NAT static mapping rules
- for rule in config['changed_rules']:
- if rule in config.get('effective', {}).get('rule', {}):
- rule_config = config['effective']['rule'][rule]
- n.delete_nat44_static_mapping(
- local_ip=rule_config.get('local').get('address'),
- external_ip=rule_config.get('external', {}).get('address', ''),
- local_port=int(rule_config.get('local', {}).get('port', 0)),
- external_port=int(rule_config.get('external', {}).get('port', 0)),
- protocol=protocol_map[rule_config.get('protocol', 'all')],
- )
-
- if 'remove' in config:
- return None
-
- # Add NAT44 static mapping rules
- n.enable_nat44_ed()
- for interface in config['in_iface_add']:
- n.add_inside_interface(interface)
- for interface in config['out_iface_add']:
- n.add_outside_interface(interface)
- for rule in config['changed_rules']:
- if rule in config.get('rule', {}):
- rule_config = config['rule'][rule]
- n.add_nat44_static_mapping(
- local_ip=rule_config.get('local').get('address'),
- external_ip=rule_config.get('external', {}).get('address', ''),
- local_port=int(rule_config.get('local', {}).get('port', 0)),
- external_port=int(rule_config.get('external', {}).get('port', 0)),
- protocol=protocol_map[rule_config.get('protocol', 'all')],
- )
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/op_mode/show_vpp_nat44.py b/src/op_mode/show_vpp_nat44.py
index fcf28f76a..d0569ac49 100644
--- a/src/op_mode/show_vpp_nat44.py
+++ b/src/op_mode/show_vpp_nat44.py
@@ -33,6 +33,15 @@ protocol_map = {
17: 'udp',
}
+# NAT flags
+flags_map = {
+ 'twice-nat': 0x01,
+ 'self-twice-nat': 0x02,
+ 'out2in-only': 0x04,
+ 'out': 0x10,
+ 'in': 0x20,
+}
+
def _verify(func):
"""Decorator checks if config for VPP NAT44 exists"""
@@ -50,6 +59,16 @@ def _verify(func):
return _wrapper
+def decode_bitmask(bitmask: int) -> list:
+ """Decode a bitmask into a list of flag names"""
+ return [name for name, value in flags_map.items() if bitmask & value]
+
+
+def _get_raw_output(data_dump):
+ data = [json.loads(json.dumps(d._asdict(), default=str)) for d in data_dump]
+ return data
+
+
def _get_raw_output_sessions(vpp_api):
users: list[dict] = vpp_api.nat44_user_dump()
sessions_list: list[dict] = []
@@ -108,12 +127,30 @@ def _get_formatted_output_sessions(sessions_list):
print('\n')
-def _get_raw_output_static_rules(vpp_api):
- nat_static_dump = vpp_api.nat44_static_mapping_dump()
- rules_list = [
- json.loads(json.dumps(rule._asdict(), default=str)) for rule in nat_static_dump
- ]
- return rules_list
+def _get_formatted_output_addresses(addresses):
+ twice_nat_address = []
+ translation_address = []
+ for address_info in addresses:
+ address = address_info.get('ip_address')
+ if address_info.get('flags') & flags_map['twice-nat']:
+ twice_nat_address.append(address)
+ else:
+ translation_address.append(address)
+
+ print('NAT44 pool addresses:')
+ for addr in translation_address:
+ print(f' {addr}')
+ print('NAT44 twice-nat pool addresses:')
+ for addr in twice_nat_address:
+ print(f' {addr}')
+
+
+def _get_formatted_output_interfaces(vpp, interfaces):
+ print('NAT44 interfaces:')
+ for interface in interfaces:
+ name = vpp.get_interface_name(interface['sw_if_index'])
+ iface_type = decode_bitmask(interface['flags'])
+ print(f' {name} {" ".join(iface_type)}')
def _get_formatted_output_rules(rules_list):
@@ -124,8 +161,16 @@ def _get_formatted_output_rules(rules_list):
local_address = rule.get('local_ip_address')
local_port = rule.get('local_port') or ''
protocol = protocol_map[rule.get('protocol', 0)]
-
- values = [external_address, external_port, local_address, local_port, protocol]
+ options = ' '.join(decode_bitmask(rule.get('flags')))
+
+ values = [
+ external_address,
+ external_port,
+ local_address,
+ local_port,
+ protocol,
+ options,
+ ]
data_entries.append(values)
headers = [
'External address',
@@ -133,6 +178,7 @@ def _get_formatted_output_rules(rules_list):
'Local address',
'Local port',
'Protocol',
+ 'Options',
]
out = sorted(data_entries, key=lambda x: x[2])
return tabulate(out, headers=headers, tablefmt='simple')
@@ -159,7 +205,8 @@ def show_summary(raw: bool):
@_verify
def show_static(raw: bool):
vpp = VPPControl()
- rules_list: list[dict] = _get_raw_output_static_rules(vpp.api)
+ nat_static_dump = vpp.api.nat44_static_mapping_dump()
+ rules_list: list[dict] = _get_raw_output(nat_static_dump)
if raw:
return rules_list
@@ -168,6 +215,32 @@ def show_static(raw: bool):
return _get_formatted_output_rules(rules_list)
+@_verify
+def show_addresses(raw: bool):
+ vpp = VPPControl()
+ addresses_dump = vpp.api.nat44_address_dump()
+ addresses: list[dict] = _get_raw_output(addresses_dump)
+
+ if raw:
+ return addresses
+
+ else:
+ return _get_formatted_output_addresses(addresses)
+
+
+@_verify
+def show_interfaces(raw: bool):
+ vpp = VPPControl()
+ interfaces_dump = vpp.api.nat44_interface_dump()
+ interfaces: list[dict] = _get_raw_output(interfaces_dump)
+
+ if raw:
+ return interfaces
+
+ else:
+ return _get_formatted_output_interfaces(vpp, interfaces)
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])