From b7825f1f2b9b3ff7d25e8e072d60db7b70fa250a Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Fri, 28 Jul 2023 20:29:01 +0000 Subject: T5014: nat: add source and destination nat options for configuring load balance within a single rule. --- src/conf_mode/nat.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 5f4b658f8..dea833cf1 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -125,6 +125,18 @@ def verify_rule(config, err_msg, groups_dict): if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group') + if 'balance' in config: + for item in ['source-port', 'destination-port']: + if item in config['balance']['hash'] and config['protocol'] not in ['tcp', 'udp']: + raise ConfigError('Protocol must be tcp or udp when specifying hash ports') + count = 0 + if 'member' in config['balance']: + for member in config['balance']['member']: + weight = config['balance']['member'][member]['weight'] + count = count + int(weight) + if count != 100: + Warning(f'Sum of weight for nat balance rule is not 100. You may get unexpected behaviour') + def get_config(config=None): if config: conf = config @@ -198,7 +210,7 @@ def verify(nat): Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') if not dict_search('translation.address', config) and not dict_search('translation.port', config): - if 'exclude' not in config: + if 'exclude' not in config and 'member' not in config['balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') addr = dict_search('translation.address', config) @@ -222,7 +234,7 @@ def verify(nat): 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 'exclude' not in config: + if 'exclude' not in config and 'member' not in config['balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') # common rule verification -- cgit v1.2.3 From 7ae9d8953ddc9ba38d62400187ce1ec44abb5a6e Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Mon, 31 Jul 2023 13:09:31 +0000 Subject: T5014: fix conflicts. Add code for redirection, which is causing conflicts. Change code for new syntax --- .../include/firewall/nat-balance.xml.i | 2 +- interface-definitions/include/nat-rule.xml.i | 4 +-- python/vyos/nat.py | 8 ++--- smoketest/scripts/cli/test_nat.py | 39 +++++++++++++++++----- src/conf_mode/nat.py | 19 +++++------ 5 files changed, 46 insertions(+), 26 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/include/firewall/nat-balance.xml.i b/interface-definitions/include/firewall/nat-balance.xml.i index ac60a2545..01793f06b 100644 --- a/interface-definitions/include/firewall/nat-balance.xml.i +++ b/interface-definitions/include/firewall/nat-balance.xml.i @@ -1,5 +1,5 @@ - + Translated IP address diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i index fa7625c7d..6234e6195 100644 --- a/interface-definitions/include/nat-rule.xml.i +++ b/interface-definitions/include/nat-rule.xml.i @@ -25,9 +25,9 @@ #include #include - + - Apply NAT balance + Apply NAT load balance #include diff --git a/python/vyos/nat.py b/python/vyos/nat.py index 9978993a7..a56ca1ff3 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -90,12 +90,12 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): if options: translation_str += f' {",".join(options)}' - if 'member' in rule_conf['balance']: + if 'backend' in rule_conf['load_balance']: hash_input_items = [] current_prob = 0 nat_map = [] - for trans_addr, addr in rule_conf['balance']['member'].items(): + for trans_addr, addr in rule_conf['load_balance']['backend'].items(): item_prob = int(addr['weight']) upper_limit = current_prob + item_prob - 1 hash_val = str(current_prob) + '-' + str(upper_limit) @@ -105,10 +105,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): elements = ' , '.join(nat_map) - if 'hash' in rule_conf['balance'] and 'random' in rule_conf['balance']['hash']: + if 'hash' in rule_conf['load_balance'] and 'random' in rule_conf['load_balance']['hash']: translation_str += ' numgen random mod 100 map ' + '{ ' + f'{elements}' + ' }' else: - for input_param in rule_conf['balance']['hash']: + for input_param in rule_conf['load_balance']['hash']: if input_param == 'source-address': param = 'ip saddr' elif input_param == 'destination-address': diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 673be9905..f0c4fef2e 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -231,6 +231,27 @@ 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') + def test_nat_balance(self): ifname = 'eth0' member_1 = '198.51.100.1' @@ -246,17 +267,17 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.cli_set(dst_path + ['rule', '1', 'inbound-interface', ifname]) self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dst_port]) - self.cli_set(dst_path + ['rule', '1', 'balance', 'hash', 'source-address']) - self.cli_set(dst_path + ['rule', '1', 'balance', 'hash', 'source-port']) - self.cli_set(dst_path + ['rule', '1', 'balance', 'hash', 'destination-address']) - self.cli_set(dst_path + ['rule', '1', 'balance', 'hash', 'destination-port']) - self.cli_set(dst_path + ['rule', '1', 'balance', 'member', member_1, 'weight', weight_1]) - self.cli_set(dst_path + ['rule', '1', 'balance', 'member', member_2, 'weight', weight_2]) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-address']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-port']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-address']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-port']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_1, 'weight', weight_1]) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_2, 'weight', weight_2]) self.cli_set(src_path + ['rule', '1', 'outbound-interface', ifname]) - self.cli_set(src_path + ['rule', '1', 'balance', 'hash', 'random']) - self.cli_set(src_path + ['rule', '1', 'balance', 'member', member_3, 'weight', weight_3]) - self.cli_set(src_path + ['rule', '1', 'balance', 'member', member_4, 'weight', weight_4]) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'hash', 'random']) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_3, 'weight', weight_3]) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_4, 'weight', weight_4]) self.cli_commit() diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index dea833cf1..fa6fe9bb6 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -125,17 +125,17 @@ def verify_rule(config, err_msg, groups_dict): if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group') - if 'balance' in config: + if 'load_balance' in config: for item in ['source-port', 'destination-port']: - if item in config['balance']['hash'] and config['protocol'] not in ['tcp', 'udp']: + if item in config['load_balance']['hash'] and config['protocol'] not in ['tcp', 'udp']: raise ConfigError('Protocol must be tcp or udp when specifying hash ports') count = 0 - if 'member' in config['balance']: - for member in config['balance']['member']: - weight = config['balance']['member'][member]['weight'] + if 'backend' in config['load_balance']: + for member in config['load_balance']['backend']: + weight = config['load_balance']['backend'][member]['weight'] count = count + int(weight) if count != 100: - Warning(f'Sum of weight for nat balance rule is not 100. You may get unexpected behaviour') + Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour') def get_config(config=None): if config: @@ -210,7 +210,7 @@ def verify(nat): Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') if not dict_search('translation.address', config) and not dict_search('translation.port', config): - if 'exclude' not in config and 'member' not in config['balance']: + if 'exclude' not in config and 'backend' not in config['load_balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') addr = dict_search('translation.address', config) @@ -222,7 +222,6 @@ def verify(nat): # common rule verification verify_rule(config, err_msg, nat['firewall_group']) - if dict_search('destination.rule', nat): for rule, config in dict_search('destination.rule', nat).items(): err_msg = f'Destination NAT configuration error in rule {rule}:' @@ -233,8 +232,8 @@ 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 'exclude' not in config and 'member' not in config['balance']: + 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 and 'backend' not in config['load_balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') # common rule verification -- cgit v1.2.3