summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interface-definitions/nat.xml.in8
-rw-r--r--python/vyos/nat.py42
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py21
-rwxr-xr-xsrc/conf_mode/nat.py3
4 files changed, 54 insertions, 20 deletions
diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in
index 501ff05d3..a06ceefb6 100644
--- a/interface-definitions/nat.xml.in
+++ b/interface-definitions/nat.xml.in
@@ -44,6 +44,14 @@
</leafNode>
#include <include/nat-translation-port.xml.i>
#include <include/nat-translation-options.xml.i>
+ <node name="redirect">
+ <properties>
+ <help>Redirect to local host</help>
+ </properties>
+ <children>
+ #include <include/nat-translation-port.xml.i>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 5b8d5d1a3..603fedb9b 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -54,28 +54,32 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
translation_str = 'return'
log_suffix = '-EXCL'
elif 'translation' in rule_conf:
- translation_prefix = nat_type[:1]
- translation_output = [f'{translation_prefix}nat']
addr = dict_search_args(rule_conf, 'translation', 'address')
port = dict_search_args(rule_conf, 'translation', 'port')
-
- if addr and is_ip_network(addr):
- if not ipv6:
- map_addr = dict_search_args(rule_conf, nat_type, 'address')
- translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}')
- ignore_type_addr = True
- else:
- translation_output.append(f'prefix to {addr}')
- elif addr == 'masquerade':
- if port:
- addr = f'{addr} to '
- translation_output = [addr]
- log_suffix = '-MASQ'
+ redirect_port = dict_search_args(rule_conf, 'translation', 'redirect', 'port')
+ if redirect_port:
+ translation_output = [f'redirect to {redirect_port}']
else:
- translation_output.append('to')
- if addr:
- addr = bracketize_ipv6(addr)
- translation_output.append(addr)
+ translation_prefix = nat_type[:1]
+ translation_output = [f'{translation_prefix}nat']
+
+ if addr and is_ip_network(addr):
+ if not ipv6:
+ map_addr = dict_search_args(rule_conf, nat_type, 'address')
+ translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}')
+ ignore_type_addr = True
+ else:
+ translation_output.append(f'prefix to {addr}')
+ elif addr == 'masquerade':
+ if port:
+ addr = f'{addr} to '
+ translation_output = [addr]
+ log_suffix = '-MASQ'
+ else:
+ translation_output.append('to')
+ if addr:
+ addr = bracketize_ipv6(addr)
+ translation_output.append(addr)
options = []
addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping')
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 02fa03f7b..28d566eba 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -231,5 +231,26 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_static_nat')
+ def test_dnat_redirect(self):
+ dst_addr_1 = '10.0.1.1'
+ dest_port = '5122'
+ protocol = 'tcp'
+ redirected_port = '22'
+ ifname = 'eth0'
+
+ self.cli_set(dst_path + ['rule', '10', 'destination', 'address', dst_addr_1])
+ self.cli_set(dst_path + ['rule', '10', 'destination', 'port', dest_port])
+ self.cli_set(dst_path + ['rule', '10', 'protocol', protocol])
+ self.cli_set(dst_path + ['rule', '10', 'inbound-interface', ifname])
+ self.cli_set(dst_path + ['rule', '10', 'translation', 'redirect', 'port', redirected_port])
+
+ self.cli_commit()
+
+ nftables_search = [
+ [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect to :{redirected_port}']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_nat')
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 5f4b658f8..e19b12937 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -72,6 +72,7 @@ def verify_rule(config, err_msg, groups_dict):
""" Common verify steps used for both source and destination NAT """
if (dict_search('translation.port', config) != None or
+ dict_search('translation.redirect.port', config) != None or
dict_search('destination.port', config) != None or
dict_search('source.port', config)):
@@ -221,7 +222,7 @@ def verify(nat):
elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
- if not dict_search('translation.address', config) and not dict_search('translation.port', config):
+ if not dict_search('translation.address', config) and not dict_search('translation.port', config) and not dict_search('translation.redirect.port', config):
if 'exclude' not in config:
raise ConfigError(f'{err_msg} translation requires address and/or port')