Skip to content

Commit

Permalink
T7181: VPP add initial source NAT implentation
Browse files Browse the repository at this point in the history
Add initial source NAT implementation

```
set vpp nat source inbound-interface 'eth2'
set vpp nat source outbound-interface 'eth1'
set vpp nat source translation-pool '192.0.2.1-192.0.2.2'
```

Add initial simple implementation of the source NAT
In the future, we'll extend it to the rules if it is possible to do
via VPP API
  • Loading branch information
sever-sever committed Feb 19, 2025
1 parent 752b400 commit c4f5e7e
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 2 deletions.
8 changes: 8 additions & 0 deletions data/config-mode-dependencies/vyos-vpp.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +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_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_bonding": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
"vpp_nat_source": ["vpp_nat_source"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_ethernet": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
"vpp_nat_source": ["vpp_nat_source"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_geneve": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
"vpp_nat_source": ["vpp_nat_source"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_gre": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
"vpp_nat_source": ["vpp_nat_source"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_ipip": {
"vpp_interfaces_xconnect": ["vpp_interfaces_xconnect"],
"vpp_nat_source": ["vpp_nat_source"],
"vpp_kernel_interface": ["vpp_kernel-interfaces"]
},
"vpp_interfaces_loopback": {
"vpp_nat_source": ["vpp_nat_source"],
"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_kernel_interface": ["vpp_kernel-interfaces"]
}
}
4 changes: 2 additions & 2 deletions data/templates/vpp/startup.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ plugins {
plugin pppoe_plugin.so { enable }
# NAT uncomment if needed
# plugin cnat_plugin.so { enable }
# plugin nat_plugin.so { enable }
# plugin nat44_plugin.so { enable }
plugin nat_plugin.so { enable }
plugin nat44_ei_plugin.so { enable }
# plugin nat44_ei_plugin.so { enable }
# plugin nat64_plugin.so { enable }
# plugin nat66_plugin.so { enable }
Expand Down
45 changes: 45 additions & 0 deletions interface-definitions/vpp.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,51 @@
</node>
</children>
</node>
<node name="nat">
<properties>
<help>NAT</help>
</properties>
<children>
<node name="source" owner="${vyos_conf_scripts_dir}/vpp/vpp_nat_source.py">
<properties>
<help>Source NAT setting</help>
<priority>320</priority>
</properties>
<children>
<leafNode name="inbound-interface">
<properties>
<help>Inbound interface of NAT traffic</help>
<completionHelp>
<list>any</list>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
<leafNode name="outbound-interface">
<properties>
<help>Outbound interface of NAT traffic</help>
<completionHelp>
<list>any</list>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
<leafNode name="translation-pool">
<properties>
<help>IPv4 range</help>
<valueHelp>
<format>ipv4range</format>
<description>IPv4 address range to match</description>
</valueHelp>
<constraint>
<validator name="ipv4-range" />
</constraint>
</properties>
</leafNode>
</children>
</node>
</children>
</node>
<tagNode name="kernel-interfaces" owner="${vyos_conf_scripts_dir}/vpp_kernel-interfaces.py">
<properties>
<help>VPP kernel interface settings</help>
Expand Down
3 changes: 3 additions & 0 deletions python/vyos/vpp/nat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .nat44 import Nat44

__all__ = ['Nat44']
89 changes: 89 additions & 0 deletions python/vyos/vpp/nat/nat44.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# 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.vpp import VPPControl


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
self.vpp = VPPControl()

def enable_nat44_ed(self):
"""Enable NAT44 endpoint dependent plugin
Example:
from vyos.vpp.nat import Nat44
nat44 = Nat44()
nat44.enable_nat44_ed()
https://github.com/FDio/vpp/blob/stable/2410/src/plugins/nat/nat44-ed/nat44_ed.api
"""
self.vpp.api.nat44_ed_plugin_enable_disable(enable=True)

def enable_nat44_ei(self):
"""Enable NAT44 endpoint independent plugin
Example:
from vyos.vpp.nat import Nat44
nat44 = Nat44()
nat44.enable_nat44_ei()
"""
self.vpp.api.nat44_ei_plugin_enable_disable(enable=True)

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_address_range(self):
"""Add NAT44 address range"""
self.vpp.api.nat44_add_del_address_range(
first_ip_address=self.translation_pool.split('-')[0],
last_ip_address=self.translation_pool.split('-')[1],
is_add=True,
)

def delete_nat44_address_range(self):
"""Delete NAT44 address range"""
self.vpp.api.nat44_add_del_address_range(
first_ip_address=self.translation_pool.split('-')[0],
last_ip_address=self.translation_pool.split('-')[1],
is_add=False,
)

def enable_ipfix(self):
"""Enable NAT44 IPFIX logging"""
self.vpp.api.nat44_ei_ipfix_enable_disable(enable=True)
4 changes: 4 additions & 0 deletions src/conf_mode/vpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ def get_config(config=None):
# to be reinitialized after the commit
set_dependents('ethernet', conf, removed_iface)

# NAT dependency
if conf.exists(['vpp', 'nat', 'source']):
set_dependents('vpp_nat_source', conf)

if not conf.exists(base):
return {
'removed_ifaces': removed_ifaces,
Expand Down
110 changes: 110 additions & 0 deletions src/conf_mode/vpp_nat_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/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 import Nat44


def get_config(config=None) -> dict:
if config:
conf = config
else:
conf = Config()

base = ['vpp', 'nat', '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 = {'inbound_interface', 'outbound_interface', 'translation_pool'}
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('_', '-')}"
)


def generate(config):
pass


def apply(config):
# Delete NAT source
if 'effective' in config:
remove_config = config.get('effective')
interface_in = remove_config.get('inbound_interface')
interface_out = remove_config.get('outbound_interface')
translation_pool = remove_config.get('translation_pool')

n = Nat44(interface_in, interface_out, translation_pool)
n.delete_nat44_out_interface()
n.delete_nat44_out_interface()

if 'remove' in config:
return None

# Add NAT64
interface_in = config.get('inbound_interface')
interface_out = config.get('outbound_interface')
translation_pool = config.get('translation_pool')

n = Nat44(interface_in, interface_out, translation_pool)
n.enable_nat44_ed()
n.add_nat44_out_interface()
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)

0 comments on commit c4f5e7e

Please sign in to comment.