diff options
-rw-r--r-- | data/templates/dhcp-client/override.conf.j2 | 2 | ||||
-rw-r--r-- | data/templates/frr/bgpd.frr.j2 | 2 | ||||
-rw-r--r-- | interface-definitions/include/bgp/protocol-common-config.xml.i | 33 | ||||
-rw-r--r-- | interface-definitions/include/firewall/firewall-hashing-parameters.xml.i | 35 | ||||
-rw-r--r-- | interface-definitions/include/firewall/nat-balance.xml.i | 28 | ||||
-rw-r--r-- | interface-definitions/include/nat-rule.xml.i | 9 | ||||
-rw-r--r-- | op-mode-definitions/rpki.xml.in (renamed from op-mode-definitions/show-rpki.xml.in) | 10 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 4 | ||||
-rw-r--r-- | python/vyos/nat.py | 33 | ||||
-rw-r--r-- | python/vyos/qos/base.py | 21 | ||||
-rw-r--r-- | smoketest/scripts/cli/base_interfaces_test.py | 2 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_bonding.py | 5 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_bridge.py | 5 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_nat.py | 36 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_vrf.py | 5 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-bonding.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/nat.py | 17 | ||||
-rw-r--r-- | src/systemd/dhclient@.service | 2 |
18 files changed, 217 insertions, 36 deletions
diff --git a/data/templates/dhcp-client/override.conf.j2 b/data/templates/dhcp-client/override.conf.j2 index d4d5eb49f..03fd71bf1 100644 --- a/data/templates/dhcp-client/override.conf.j2 +++ b/data/templates/dhcp-client/override.conf.j2 @@ -10,6 +10,6 @@ ConditionPathExists={{ isc_dhclient_dir }}/dhclient_%i.conf ExecStart= ExecStart={{ vrf_command }}/sbin/dhclient -4 {{ dhclient_options }} {{ ifname }} ExecStop= -ExecStop={{ vrf_command }}/sbin/dhclient -4 {{ dhclient_options }} {{ ifname }} -r +ExecStop=/sbin/dhclient -4 -r {{ dhclient_options }} {{ ifname }} WorkingDirectory={{ isc_dhclient_dir }} PIDFile={{ isc_dhclient_dir }}/dhclient_%i.pid diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index ddfba2306..4535758da 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -481,7 +481,7 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} bgp bestpath compare-routerid {% endif %} {% if parameters.bestpath.med is vyos_defined %} - bgp bestpath med {{ 'confed' if parameters.bestpath.med.confed is vyos_defined }} {{ 'missing-as-worst' if parameters.bestpath.med.missing_as_worst is vyos_defined }} + bgp bestpath med {{ parameters.bestpath.med | join(' ') | replace('_', '-') }} {% endif %} {% if parameters.bestpath.peer_type is vyos_defined %} bgp bestpath peer-type {{ 'multipath-relax' if parameters.bestpath.peer_type.multipath_relax is vyos_defined }} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 12024ed8b..504385b53 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1123,25 +1123,26 @@ <valueless/> </properties> </leafNode> - <node name="med"> + <leafNode name="med"> <properties> <help>MED attribute comparison parameters</help> + <completionHelp> + <list>confed missing-as-worst</list> + </completionHelp> + <valueHelp> + <format>confed</format> + <description>Compare MEDs among confederation paths</description> + </valueHelp> + <valueHelp> + <format>missing-as-worst</format> + <description>Treat missing route as a MED as the least preferred one</description> + </valueHelp> + <constraint> + <regex>(confed|missing-as-worst)</regex> + </constraint> + <multi/> </properties> - <children> - <leafNode name="confed"> - <properties> - <help>Compare MEDs among confederation paths</help> - <valueless/> - </properties> - </leafNode> - <leafNode name="missing-as-worst"> - <properties> - <help>Treat missing route as a MED as the least preferred one</help> - <valueless/> - </properties> - </leafNode> - </children> - </node> + </leafNode> <node name="peer-type"> <properties> <help>Peer type</help> diff --git a/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i b/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i new file mode 100644 index 000000000..7f34de3ba --- /dev/null +++ b/interface-definitions/include/firewall/firewall-hashing-parameters.xml.i @@ -0,0 +1,35 @@ +<!-- include start from firewall/firewall-hashing-parameters.xml.i --> +<leafNode name="hash"> + <properties> + <help>Define the parameters of the packet header to apply the hashing</help> + <completionHelp> + <list>source-address destination-address source-port destination-port random</list> + </completionHelp> + <valueHelp> + <format>source-address</format> + <description>Use source IP address for hashing</description> + </valueHelp> + <valueHelp> + <format>destination-address</format> + <description>Use destination IP address for hashing</description> + </valueHelp> + <valueHelp> + <format>source-port</format> + <description>Use source port for hashing</description> + </valueHelp> + <valueHelp> + <format>destination-port</format> + <description>Use destination port for hashing</description> + </valueHelp> + <valueHelp> + <format>random</format> + <description>Do not use information from ip header. Use random value.</description> + </valueHelp> + <constraint> + <regex>(source-address|destination-address|source-port|destination-port|random)</regex> + </constraint> + <multi/> + </properties> + <defaultValue>random</defaultValue> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/nat-balance.xml.i b/interface-definitions/include/firewall/nat-balance.xml.i new file mode 100644 index 000000000..01793f06b --- /dev/null +++ b/interface-definitions/include/firewall/nat-balance.xml.i @@ -0,0 +1,28 @@ +<!-- include start from firewall/nat-balance.xml.i --> +<tagNode name="backend"> + <properties> + <help>Translated IP address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + <leafNode name="weight"> + <properties> + <help>Set probability for this output value</help> + <valueHelp> + <format>u32:1-100</format> + <description>Set probability for this output value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--allow-range --range 1-100"/> + </constraint> + </properties> + </leafNode> + </children> +</tagNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i index 7b3b8804e..6234e6195 100644 --- a/interface-definitions/include/nat-rule.xml.i +++ b/interface-definitions/include/nat-rule.xml.i @@ -25,6 +25,15 @@ </node> #include <include/generic-disable-node.xml.i> #include <include/nat-exclude.xml.i> + <node name="load-balance"> + <properties> + <help>Apply NAT load balance</help> + </properties> + <children> + #include <include/firewall/firewall-hashing-parameters.xml.i> + #include <include/firewall/nat-balance.xml.i> + </children> + </node> <leafNode name="log"> <properties> <help>NAT rule logging</help> diff --git a/op-mode-definitions/show-rpki.xml.in b/op-mode-definitions/rpki.xml.in index c1902ccec..72d378b88 100644 --- a/op-mode-definitions/show-rpki.xml.in +++ b/op-mode-definitions/rpki.xml.in @@ -29,4 +29,14 @@ </node> </children> </node> + <node name="reset"> + <children> + <leafNode name="rpki"> + <properties> + <help>Reset RPKI</help> + </properties> + <command>vtysh -c "rpki reset"</command> + </leafNode> + </children> + </node> </interfaceDefinition> diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8ed5cb114..75c5f27a9 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1275,7 +1275,6 @@ class Interface(Control): # running, but it should be running (e.g. on system startup) if 'dhcp_options_changed' in self.config or not is_systemd_service_active(systemd_service): return self._cmd(f'systemctl restart {systemd_service}') - return None else: if is_systemd_service_active(systemd_service): self._cmd(f'systemctl stop {systemd_service}') @@ -1284,6 +1283,7 @@ class Interface(Control): if os.path.isfile(file): os.remove(file) + return None def set_dhcpv6(self, enable): """ @@ -1317,6 +1317,8 @@ class Interface(Control): if os.path.isfile(config_file): os.remove(config_file) + return None + def set_mirror_redirect(self): # Please refer to the document for details # - https://man7.org/linux/man-pages/man8/tc.8.html diff --git a/python/vyos/nat.py b/python/vyos/nat.py index 603fedb9b..418efe649 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -94,6 +94,39 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): if options: translation_str += f' {",".join(options)}' + if 'backend' in rule_conf['load_balance']: + hash_input_items = [] + current_prob = 0 + nat_map = [] + + 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) + element = hash_val + " : " + trans_addr + nat_map.append(element) + current_prob = current_prob + item_prob + + elements = ' , '.join(nat_map) + + 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['load_balance']['hash']: + if input_param == 'source-address': + param = 'ip saddr' + elif input_param == 'destination-address': + param = 'ip daddr' + elif input_param == 'source-port': + prot = rule_conf['protocol'] + param = f'{prot} sport' + elif input_param == 'destination-port': + prot = rule_conf['protocol'] + param = f'{prot} dport' + hash_input_items.append(param) + hash_input = ' . '.join(hash_input_items) + translation_str += f' jhash ' + f'{hash_input}' + ' mod 100 map ' + '{ ' + f'{elements}' + ' }' + for target in ['source', 'destination']: if target not in rule_conf: continue diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py index 1eac0d1ee..d8bbfe970 100644 --- a/python/vyos/qos/base.py +++ b/python/vyos/qos/base.py @@ -298,6 +298,27 @@ class QoSBase: filter_cmd += f' flowid {self._parent:x}:{cls:x}' self._cmd(filter_cmd) + if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config): + filter_cmd += f' action police' + + if 'exceed' in cls_config: + action = cls_config['exceed'] + filter_cmd += f' conform-exceed {action}' + if 'not_exceed' in cls_config: + action = cls_config['not_exceed'] + filter_cmd += f'/{action}' + + if 'bandwidth' in cls_config: + rate = self._rate_convert(cls_config['bandwidth']) + filter_cmd += f' rate {rate}' + + if 'burst' in cls_config: + burst = cls_config['burst'] + filter_cmd += f' burst {burst}' + cls = int(cls) + filter_cmd += f' flowid {self._parent:x}:{cls:x}' + self._cmd(filter_cmd) + else: filter_cmd += ' basic' diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 4244410bb..cef61e42b 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -146,8 +146,6 @@ class BasicInterfaceTest: for option in self._options.get(interface, []): self.cli_set(self._base_path + [interface] + option.split()) - self.cli_set(self._base_path + [interface, 'disable']) - # Also enable DHCP (ISC DHCP always places interface in admin up # state so we check that we do not start DHCP client. # https://vyos.dev/T2767 diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index d8e6bde5c..8867cb427 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -37,9 +37,8 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): if 'TEST_ETH' in os.environ: cls._members = os.environ['TEST_ETH'].split() else: - for tmp in Section.interfaces('ethernet'): - if not '.' in tmp: - cls._members.append(tmp) + for tmp in Section.interfaces('ethernet', vlan=False): + cls._members.append(tmp) cls._options = {'bond0' : []} for member in cls._members: diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index ce46643fb..cdff49f4b 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -41,9 +41,8 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): if 'TEST_ETH' in os.environ: cls._members = os.environ['TEST_ETH'].split() else: - for tmp in Section.interfaces('ethernet'): - if not '.' in tmp: - cls._members.append(tmp) + for tmp in Section.interfaces('ethernet', vlan=False): + cls._members.append(tmp) cls._options['br0'] = [] for member in cls._members: diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 28d566eba..e6eaedeff 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -252,5 +252,41 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_nat') + def test_nat_balance(self): + ifname = 'eth0' + member_1 = '198.51.100.1' + weight_1 = '10' + member_2 = '198.51.100.2' + weight_2 = '90' + member_3 = '192.0.2.1' + weight_3 = '35' + member_4 = '192.0.2.2' + weight_4 = '65' + dst_port = '443' + + 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', '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', '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() + + nftables_search = [ + [f'iifname "{ifname}"', f'tcp dport {dst_port}', f'dnat to jhash ip saddr . tcp sport . ip daddr . tcp dport mod 100 map', f'0-9 : {member_1}, 10-99 : {member_2}'], + [f'oifname "{ifname}"', f'snat to numgen random mod 100 map', f'0-34 : {member_3}, 35-99 : {member_4}'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index b4faac67d..5fb599a87 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -47,9 +47,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): tmp = os.environ['TEST_ETH'].split() cls._interfaces = tmp else: - for tmp in Section.interfaces('ethernet'): - if not '.' in tmp: - cls._interfaces.append(tmp) + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) # call base-classes classmethod super(VRFTest, cls).setUpClass() diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index b3b4e6342..0bd306ed0 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -195,11 +195,11 @@ def verify(bond): raise ConfigError(error_msg + 'it does not exist!') if 'is_bridge_member' in interface_config: - tmp = interface_config['is_bridge_member'] + tmp = next(iter(interface_config['is_bridge_member'])) raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') if 'is_bond_member' in interface_config: - tmp = interface_config['is_bond_member'] + tmp = next(iter(interface_config['is_bond_member'])) raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') if 'is_source_interface' in interface_config: diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 444a3fef6..f9d711b36 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -124,6 +124,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 'load_balance' in config: + for item in ['source-port', 'destination-port']: + 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 '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 load balance rule is not 100. You may get unexpected behaviour') + def get_config(config=None): if config: conf = config @@ -190,7 +202,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 'backend' not in config['load_balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') addr = dict_search('translation.address', config) @@ -202,7 +214,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}:' @@ -214,7 +225,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) and not dict_search('translation.redirect.port', config): - if 'exclude' not in 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 diff --git a/src/systemd/dhclient@.service b/src/systemd/dhclient@.service index 6d41de232..099f7ed52 100644 --- a/src/systemd/dhclient@.service +++ b/src/systemd/dhclient@.service @@ -7,7 +7,7 @@ After=vyos-router.service [Service] Type=exec ExecStart=/sbin/dhclient -4 -d $DHCLIENT_OPTS -ExecStop=/sbin/dhclient -4 -d $DHCLIENT_OPTS -r +ExecStop=/sbin/dhclient -4 -r $DHCLIENT_OPTS Restart=always RestartPreventExitStatus= RestartSec=10 |