Skip to content

Commit

Permalink
Merge pull request #1466 from sever-sever/T538
Browse files Browse the repository at this point in the history
nat: T538: Add static NAT one-to-one
  • Loading branch information
c-po authored Sep 1, 2022
2 parents 5c20eac + 3489089 commit 735767f
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 10 deletions.
115 changes: 115 additions & 0 deletions data/templates/firewall/nftables-static-nat.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/sbin/nft -f

{% macro nat_rule(rule, config, chain) %}
{% set comment = '' %}
{% set base_log = '' %}

{% if chain is vyos_defined('PREROUTING') %}
{% set comment = 'STATIC-NAT-' ~ rule %}
{% set base_log = '[NAT-DST-' ~ rule %}
{% set interface = ' iifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %}
{% if config.translation.address is vyos_defined %}
{# support 1:1 network translation #}
{% if config.translation.address | is_ip_network %}
{% set trns_addr = 'dnat ip prefix to ip daddr map { ' ~ config.destination.address ~ ' : ' ~ config.translation.address ~ ' }' %}
{# we can now clear out the dst_addr part as it's already covered in aboves map #}
{% else %}
{% set dst_addr = 'ip daddr ' ~ config.destination.address if config.destination.address is vyos_defined %}
{% set trns_addr = 'dnat to ' ~ config.translation.address %}
{% endif %}
{% endif %}
{% elif chain is vyos_defined('POSTROUTING') %}
{% set comment = 'STATIC-NAT-' ~ rule %}
{% set base_log = '[NAT-SRC-' ~ rule %}
{% set interface = ' oifname "' ~ config.inbound_interface ~ '"' if config.inbound_interface is vyos_defined and config.inbound_interface is not vyos_defined('any') else '' %}
{% if config.translation.address is vyos_defined %}
{# support 1:1 network translation #}
{% if config.translation.address | is_ip_network %}
{% set trns_addr = 'snat ip prefix to ip saddr map { ' ~ config.translation.address ~ ' : ' ~ config.destination.address ~ ' }' %}
{# we can now clear out the src_addr part as it's already covered in aboves map #}
{% else %}
{% set src_addr = 'ip saddr ' ~ config.translation.address if config.translation.address is vyos_defined %}
{% set trns_addr = 'snat to ' ~ config.destination.address %}
{% endif %}
{% endif %}
{% endif %}

{% if config.exclude is vyos_defined %}
{# rule has been marked as 'exclude' thus we simply return here #}
{% set trns_addr = 'return' %}
{% set trns_port = '' %}
{% endif %}

{% if config.translation.options is vyos_defined %}
{% if config.translation.options.address_mapping is vyos_defined('persistent') %}
{% set trns_opts_addr = 'persistent' %}
{% endif %}
{% if config.translation.options.port_mapping is vyos_defined('random') %}
{% set trns_opts_port = 'random' %}
{% elif config.translation.options.port_mapping is vyos_defined('fully-random') %}
{% set trns_opts_port = 'fully-random' %}
{% endif %}
{% endif %}

{% if trns_opts_addr is vyos_defined and trns_opts_port is vyos_defined %}
{% set trns_opts = trns_opts_addr ~ ',' ~ trns_opts_port %}
{% elif trns_opts_addr is vyos_defined %}
{% set trns_opts = trns_opts_addr %}
{% elif trns_opts_port is vyos_defined %}
{% set trns_opts = trns_opts_port %}
{% endif %}

{% set output = 'add rule ip vyos_static_nat ' ~ chain ~ interface %}

{% if dst_addr is vyos_defined %}
{% set output = output ~ ' ' ~ dst_addr %}
{% endif %}
{% if src_addr is vyos_defined %}
{% set output = output ~ ' ' ~ src_addr %}
{% endif %}

{# Count packets #}
{% set output = output ~ ' counter' %}
{# Special handling of log option, we must repeat the entire rule before the #}
{# NAT translation options are added, this is essential #}
{% if log is vyos_defined %}
{% set log_output = output ~ ' log prefix "' ~ log ~ '" comment "' ~ comment ~ '"' %}
{% endif %}
{% if trns_addr is vyos_defined %}
{% set output = output ~ ' ' ~ trns_addr %}
{% endif %}

{% if trns_opts is vyos_defined %}
{% set output = output ~ ' ' ~ trns_opts %}
{% endif %}
{% if comment is vyos_defined %}
{% set output = output ~ ' comment "' ~ comment ~ '"' %}
{% endif %}
{{ log_output if log_output is vyos_defined }}
{{ output }}
{% endmacro %}

# Start with clean STATIC NAT chains
flush chain ip vyos_static_nat PREROUTING
flush chain ip vyos_static_nat POSTROUTING

{# NAT if enabled - add targets to nftables #}

#
# Destination NAT rules build up here
#
add rule ip vyos_static_nat PREROUTING counter jump VYOS_PRE_DNAT_HOOK
{% if static.rule is vyos_defined %}
{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
{{ nat_rule(rule, config, 'PREROUTING') }}
{% endfor %}
{% endif %}
#
# Source NAT rules build up here
#
add rule ip vyos_static_nat POSTROUTING counter jump VYOS_PRE_SNAT_HOOK
{% if static.rule is vyos_defined %}
{% for rule, config in static.rule.items() if config.disable is not vyos_defined %}
{{ nat_rule(rule, config, 'POSTROUTING') }}
{% endfor %}
{% endif %}
20 changes: 20 additions & 0 deletions data/templates/firewall/nftables.j2
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,26 @@ table ip nat {
}
}

table ip vyos_static_nat {
chain PREROUTING {
type nat hook prerouting priority -100; policy accept;
counter jump VYOS_PRE_DNAT_HOOK
}

chain POSTROUTING {
type nat hook postrouting priority 100; policy accept;
counter jump VYOS_PRE_SNAT_HOOK
}

chain VYOS_PRE_DNAT_HOOK {
return
}

chain VYOS_PRE_SNAT_HOOK {
return
}
}

table ip6 nat {
chain PREROUTING {
type nat hook prerouting priority -100; policy accept;
Expand Down
11 changes: 11 additions & 0 deletions interface-definitions/include/inbound-interface.xml.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- include start from inbound-interface.xml.i -->
<leafNode name="inbound-interface">
<properties>
<help>Inbound interface of NAT traffic</help>
<completionHelp>
<list>any</list>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
</properties>
</leafNode>
<!-- include end -->
19 changes: 19 additions & 0 deletions interface-definitions/include/ipv4-address-prefix.xml.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- include start from ipv4-address-prefix.xml.i -->
<leafNode name="address">
<properties>
<help>IP address, prefix</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address to match</description>
</valueHelp>
<valueHelp>
<format>ipv4net</format>
<description>IPv4 prefix to match</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv4-prefix"/>
</constraint>
</properties>
</leafNode>
<!-- include end -->
53 changes: 44 additions & 9 deletions interface-definitions/nat.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,7 @@
#include <include/nat-rule.xml.i>
<tagNode name="rule">
<children>
<leafNode name="inbound-interface">
<properties>
<help>Inbound interface of NAT traffic</help>
<completionHelp>
<list>any</list>
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
</properties>
</leafNode>
#include <include/inbound-interface.xml.i>
<node name="translation">
<properties>
<help>Inside NAT IP (destination NAT only)</help>
Expand Down Expand Up @@ -65,6 +57,17 @@
<children>
#include <include/nat-rule.xml.i>
<tagNode name="rule">
<properties>
<help>Rule number for NAT</help>
<valueHelp>
<format>u32:1-999999</format>
<description>Number of NAT rule</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-999999"/>
</constraint>
<constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
#include <include/nat-interface.xml.i>
<node name="translation">
Expand Down Expand Up @@ -110,6 +113,38 @@
</tagNode>
</children>
</node>
<node name="static">
<properties>
<help>Static NAT (one-to-one)</help>
</properties>
<children>
<tagNode name="rule">
<properties>
<help>Rule number for NAT</help>
</properties>
<children>
#include <include/generic-description.xml.i>
<node name="destination">
<properties>
<help>NAT destination parameters</help>
</properties>
<children>
#include <include/ipv4-address-prefix.xml.i>
</children>
</node>
#include <include/inbound-interface.xml.i>
<node name="translation">
<properties>
<help>Translation address or prefix</help>
</properties>
<children>
#include <include/ipv4-address-prefix.xml.i>
</children>
</node>
</children>
</tagNode>
</children>
</node>
</children>
</node>
</interfaceDefinition>
18 changes: 17 additions & 1 deletion src/conf_mode/nat.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
k_mod = ['nft_nat', 'nft_chain_nat_ipv4']

nftables_nat_config = '/run/nftables_nat.conf'
nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'

def get_handler(json, chain, target):
""" Get nftable rule handler number of given chain/target combination.
Expand Down Expand Up @@ -88,7 +89,7 @@ def get_config(config=None):

# T2665: we must add the tagNode defaults individually until this is
# moved to the base class
for direction in ['source', 'destination']:
for direction in ['source', 'destination', 'static']:
if direction in nat:
default_values = defaults(base + [direction, 'rule'])
for rule in dict_search(f'{direction}.rule', nat) or []:
Expand Down Expand Up @@ -178,20 +179,35 @@ def verify(nat):
# common rule verification
verify_rule(config, err_msg)

if dict_search('static.rule', nat):
for rule, config in dict_search('static.rule', nat).items():
err_msg = f'Static NAT configuration error in rule {rule}:'

if 'inbound_interface' not in config:
raise ConfigError(f'{err_msg}\n' \
'inbound-interface not specified')

# common rule verification
verify_rule(config, err_msg)

return None

def generate(nat):
render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)
render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat)

# dry-run newly generated configuration
tmp = run(f'nft -c -f {nftables_nat_config}')
if tmp > 0:
raise ConfigError('Configuration file errors encountered!')

tmp = run(f'nft -c -f {nftables_nat_config}')

return None

def apply(nat):
cmd(f'nft -f {nftables_nat_config}')
cmd(f'nft -f {nftables_static_nat_conf}')

return None

Expand Down

0 comments on commit 735767f

Please sign in to comment.