summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/firewall/nftables-nat66.j241
-rw-r--r--interface-definitions/include/nat/protocol.xml.i34
-rw-r--r--interface-definitions/nat66.xml.in8
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py48
4 files changed, 131 insertions, 0 deletions
diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2
index 2fe04b4ff..28714c7a7 100644
--- a/data/templates/firewall/nftables-nat66.j2
+++ b/data/templates/firewall/nftables-nat66.j2
@@ -7,6 +7,17 @@
{% set src_prefix = 'ip6 saddr ' ~ config.source.prefix.replace('!','!= ') if config.source.prefix is vyos_defined %}
{% set source_address = 'ip6 saddr ' ~ config.source.address.replace('!','!= ') if config.source.address is vyos_defined %}
{% set dest_address = 'ip6 daddr ' ~ config.destination.address.replace('!','!= ') if config.destination.address is vyos_defined %}
+{# Port #}
+{% if config.source.port is vyos_defined and config.source.port.startswith('!') %}
+{% set src_port = 'sport != { ' ~ config.source.port.replace('!','') ~ ' }' %}
+{% else %}
+{% set src_port = 'sport { ' ~ config.source.port ~ ' }' if config.source.port is vyos_defined %}
+{% endif %}
+{% if config.destination.port is vyos_defined and config.destination.port.startswith('!') %}
+{% set dst_port = 'dport != { ' ~ config.destination.port.replace('!','') ~ ' }' %}
+{% else %}
+{% set dst_port = 'dport { ' ~ config.destination.port ~ ' }' if config.destination.port is vyos_defined %}
+{% endif %}
{% if chain is vyos_defined('PREROUTING') %}
{% set comment = 'DST-NAT66-' ~ rule %}
{% set base_log = '[NAT66-DST-' ~ rule %}
@@ -36,6 +47,14 @@
{% endif %}
{% set interface = ' oifname "' ~ config.outbound_interface ~ '"' if config.outbound_interface is vyos_defined else '' %}
{% endif %}
+{% set trns_port = ':' ~ config.translation.port if config.translation.port is vyos_defined %}
+{# protocol has a default value thus it is always present #}
+{% if config.protocol is vyos_defined('tcp_udp') %}
+{% set protocol = 'tcp' %}
+{% set comment = comment ~ ' tcp_udp' %}
+{% else %}
+{% set protocol = config.protocol %}
+{% endif %}
{% if config.log is vyos_defined %}
{% if config.translation.address is vyos_defined('masquerade') %}
{% set log = base_log ~ '-MASQ]' %}
@@ -43,6 +62,11 @@
{% set log = base_log ~ ']' %}
{% 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 %}
{% set output = 'add rule ip6 nat ' ~ chain ~ interface %}
{# Count packets #}
{% set output = output ~ ' counter' %}
@@ -54,12 +78,18 @@
{% if src_prefix is vyos_defined %}
{% set output = output ~ ' ' ~ src_prefix %}
{% endif %}
+{% if dst_port is vyos_defined %}
+{% set output = output ~ ' ' ~ protocol ~ ' ' ~ dst_port %}
+{% endif %}
{% if dst_prefix is vyos_defined %}
{% set output = output ~ ' ' ~ dst_prefix %}
{% endif %}
{% if source_address is vyos_defined %}
{% set output = output ~ ' ' ~ source_address %}
{% endif %}
+{% if src_port is vyos_defined %}
+{% set output = output ~ ' ' ~ protocol ~ ' ' ~ src_port %}
+{% endif %}
{% if dest_address is vyos_defined %}
{% set output = output ~ ' ' ~ dest_address %}
{% endif %}
@@ -70,11 +100,22 @@
{% if trns_address is vyos_defined %}
{% set output = output ~ ' ' ~ trns_address %}
{% endif %}
+{% if trns_port is vyos_defined %}
+{# Do not add a whitespace here, translation port must be directly added after IP address #}
+{# e.g. 2001:db8::1:3389 #}
+{% set output = output ~ trns_port %}
+{% endif %}
{% if comment is vyos_defined %}
{% set output = output ~ ' comment "' ~ comment ~ '"' %}
{% endif %}
{{ log_output if log_output is vyos_defined }}
{{ output }}
+{# Special handling if protocol is tcp_udp, we must repeat the entire rule with udp as protocol #}
+{% if config.protocol is vyos_defined('tcp_udp') %}
+{# Beware of trailing whitespace, without it the comment tcp_udp will be changed to udp_udp #}
+{{ log_output | replace('tcp ', 'udp ') if log_output is vyos_defined }}
+{{ output | replace('tcp ', 'udp ') }}
+{% endif %}
{% endmacro %}
# Start with clean NAT table
diff --git a/interface-definitions/include/nat/protocol.xml.i b/interface-definitions/include/nat/protocol.xml.i
new file mode 100644
index 000000000..54e7ff00d
--- /dev/null
+++ b/interface-definitions/include/nat/protocol.xml.i
@@ -0,0 +1,34 @@
+<!-- include start from nat/protocol.xml.i -->
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol to match (protocol name, number, or "all")</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_protocols.sh</script>
+ <list>all tcp_udp</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All IP protocols</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp_udp</format>
+ <description>Both TCP and UDP</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:0-255</format>
+ <description>IP protocol number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!&lt;protocol&gt;</format>
+ <description>IP protocol name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-protocol"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
index bde1a6f8d..dab4543e0 100644
--- a/interface-definitions/nat66.xml.in
+++ b/interface-definitions/nat66.xml.in
@@ -50,6 +50,7 @@
</completionHelp>
</properties>
</leafNode>
+ #include <include/nat/protocol.xml.i>
<node name="destination">
<properties>
<help>IPv6 destination prefix options</help>
@@ -72,6 +73,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="source">
@@ -96,6 +98,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="translation">
@@ -128,6 +131,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-translation-port.xml.i>
</children>
</node>
</children>
@@ -179,6 +183,7 @@
</completionHelp>
</properties>
</leafNode>
+ #include <include/nat/protocol.xml.i>
<node name="destination">
<properties>
<help>IPv6 destination prefix options</help>
@@ -211,6 +216,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="source">
@@ -245,6 +251,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-port.xml.i>
</children>
</node>
<node name="translation">
@@ -269,6 +276,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/nat-translation-port.xml.i>
</children>
</node>
</children>
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index 4b5625569..c5db066db 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -131,6 +131,30 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 nat')
+ def test_destination_nat66_protocol(self):
+ translation_address = '2001:db8:1111::1'
+ source_prefix = '2001:db8:2222::/64'
+ dport = '4545'
+ sport = '8080'
+ tport = '5555'
+ proto = 'tcp'
+ self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1'])
+ self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dport])
+ self.cli_set(dst_path + ['rule', '1', 'source', 'address', source_prefix])
+ self.cli_set(dst_path + ['rule', '1', 'source', 'port', sport])
+ self.cli_set(dst_path + ['rule', '1', 'protocol', proto])
+ self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_address])
+ self.cli_set(dst_path + ['rule', '1', 'translation', 'port', tport])
+
+ # check validate() - outbound-interface must be defined
+ self.cli_commit()
+
+ nftables_search = [
+ ['iifname "eth1"', 'tcp dport { 4545 } ip6 saddr 2001:db8:2222::/64 tcp sport { 8080 } dnat to 2001:db8:1111::1:5555']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 nat')
+
def test_destination_nat66_prefix(self):
destination_prefix = 'fc00::/64'
translation_prefix = 'fc01::/64'
@@ -176,6 +200,30 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
self.cli_commit()
+ def test_source_nat66_protocol(self):
+ translation_address = '2001:db8:1111::1'
+ source_prefix = '2001:db8:2222::/64'
+ dport = '9999'
+ sport = '8080'
+ tport = '80'
+ proto = 'tcp'
+ self.cli_set(src_path + ['rule', '1', 'outbound-interface', 'eth1'])
+ self.cli_set(src_path + ['rule', '1', 'destination', 'port', dport])
+ self.cli_set(src_path + ['rule', '1', 'source', 'prefix', source_prefix])
+ self.cli_set(src_path + ['rule', '1', 'source', 'port', sport])
+ self.cli_set(src_path + ['rule', '1', 'protocol', proto])
+ self.cli_set(src_path + ['rule', '1', 'translation', 'address', translation_address])
+ self.cli_set(src_path + ['rule', '1', 'translation', 'port', tport])
+
+ # check validate() - outbound-interface must be defined
+ self.cli_commit()
+
+ nftables_search = [
+ ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64 tcp dport { 9999 } tcp sport { 8080 } snat to 2001:db8:1111::1:80']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 nat')
+
def test_nat66_no_rules(self):
# T3206: deleting all rules but keep the direction 'destination' or
# 'source' resulteds in KeyError: 'rule'.