summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/firewall/nftables.j225
-rw-r--r--data/vyos-firewall-init.conf14
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i2
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i32
-rw-r--r--python/vyos/ifconfig/interface.py61
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py8
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py8
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py38
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py7
-rwxr-xr-xsrc/conf_mode/service_dhcp-server.py6
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py2
-rwxr-xr-xsrc/op_mode/image_manager.py25
12 files changed, 170 insertions, 58 deletions
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 3f7906628..4851e3a05 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -8,29 +8,36 @@
flush chain raw FW_CONNTRACK
flush chain ip6 raw FW_CONNTRACK
+flush chain raw vyos_global_rpfilter
+flush chain ip6 raw vyos_global_rpfilter
+
table raw {
chain FW_CONNTRACK {
{{ ipv4_conntrack_action }}
}
+
+ chain vyos_global_rpfilter {
+{% if global_options.source_validation is vyos_defined('loose') %}
+ fib saddr oif 0 counter drop
+{% elif global_options.source_validation is vyos_defined('strict') %}
+ fib saddr . iif oif 0 counter drop
+{% endif %}
+ return
+ }
}
table ip6 raw {
chain FW_CONNTRACK {
{{ ipv6_conntrack_action }}
}
-}
-{% if first_install is not vyos_defined %}
-delete table inet vyos_global_rpfilter
-{% endif %}
-table inet vyos_global_rpfilter {
- chain PREROUTING {
- type filter hook prerouting priority -300; policy accept;
-{% if global_options.source_validation is vyos_defined('loose') %}
+ chain vyos_global_rpfilter {
+{% if global_options.ipv6_source_validation is vyos_defined('loose') %}
fib saddr oif 0 counter drop
-{% elif global_options.source_validation is vyos_defined('strict') %}
+{% elif global_options.ipv6_source_validation is vyos_defined('strict') %}
fib saddr . iif oif 0 counter drop
{% endif %}
+ return
}
}
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 41e7627f5..b0026fdf3 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -19,6 +19,15 @@ table raw {
type filter hook forward priority -300; policy accept;
}
+ chain vyos_global_rpfilter {
+ return
+ }
+
+ chain vyos_rpfilter {
+ type filter hook prerouting priority -300; policy accept;
+ counter jump vyos_global_rpfilter
+ }
+
chain PREROUTING {
type filter hook prerouting priority -300; policy accept;
counter jump VYOS_CT_IGNORE
@@ -82,8 +91,13 @@ table ip6 raw {
type filter hook forward priority -300; policy accept;
}
+ chain vyos_global_rpfilter {
+ return
+ }
+
chain vyos_rpfilter {
type filter hook prerouting priority -300; policy accept;
+ counter jump vyos_global_rpfilter
}
chain PREROUTING {
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index dce61ee77..bb35efe94 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -1698,8 +1698,10 @@
</properties>
<children>
#include <include/bgp/neighbor-afi-ipv4-unicast.xml.i>
+ #include <include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i>
#include <include/bgp/neighbor-afi-ipv4-vpn.xml.i>
#include <include/bgp/neighbor-afi-ipv6-unicast.xml.i>
+ #include <include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i>
#include <include/bgp/neighbor-afi-ipv6-vpn.xml.i>
#include <include/bgp/neighbor-afi-l2vpn-evpn.xml.i>
</children>
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index 3026b54ab..415d85f05 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -145,21 +145,21 @@
</leafNode>
<leafNode name="source-validation">
<properties>
- <help>Policy for source validation by reversed path, as specified in RFC3704</help>
+ <help>Policy for IPv4 source validation by reversed path, as specified in RFC3704</help>
<completionHelp>
<list>strict loose disable</list>
</completionHelp>
<valueHelp>
<format>strict</format>
- <description>Enable Strict Reverse Path Forwarding as defined in RFC3704</description>
+ <description>Enable IPv4 Strict Reverse Path Forwarding as defined in RFC3704</description>
</valueHelp>
<valueHelp>
<format>loose</format>
- <description>Enable Loose Reverse Path Forwarding as defined in RFC3704</description>
+ <description>Enable IPv4 Loose Reverse Path Forwarding as defined in RFC3704</description>
</valueHelp>
<valueHelp>
<format>disable</format>
- <description>No source validation</description>
+ <description>No IPv4 source validation</description>
</valueHelp>
<constraint>
<regex>(strict|loose|disable)</regex>
@@ -264,6 +264,30 @@
</properties>
<defaultValue>disable</defaultValue>
</leafNode>
+ <leafNode name="ipv6-source-validation">
+ <properties>
+ <help>Policy for IPv6 source validation by reversed path, as specified in RFC3704</help>
+ <completionHelp>
+ <list>strict loose disable</list>
+ </completionHelp>
+ <valueHelp>
+ <format>strict</format>
+ <description>Enable IPv6 Strict Reverse Path Forwarding as defined in RFC3704</description>
+ </valueHelp>
+ <valueHelp>
+ <format>loose</format>
+ <description>Enable IPv6 Loose Reverse Path Forwarding as defined in RFC3704</description>
+ </valueHelp>
+ <valueHelp>
+ <format>disable</format>
+ <description>No IPv6 source validation</description>
+ </valueHelp>
+ <constraint>
+ <regex>(strict|loose|disable)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>disable</defaultValue>
+ </leafNode>
<leafNode name="ipv6-src-route">
<properties>
<help>Policy for handling IPv6 packets with routing extension header</help>
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index a038ef28d..c793f6199 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -175,10 +175,6 @@ class Interface(Control):
'validate': assert_boolean,
'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding',
},
- 'rp_filter': {
- 'validate': lambda flt: assert_range(flt,0,3),
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter',
- },
'ipv6_accept_ra': {
'validate': lambda ara: assert_range(ara,0,3),
'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
@@ -252,9 +248,6 @@ class Interface(Control):
'ipv4_directed_broadcast': {
'location': '/proc/sys/net/ipv4/conf/{ifname}/bc_forwarding',
},
- 'rp_filter': {
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/rp_filter',
- },
'ipv6_accept_ra': {
'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
},
@@ -755,40 +748,32 @@ class Interface(Control):
return None
return self.set_interface('ipv4_directed_broadcast', forwarding)
- def set_ipv4_source_validation(self, value):
- """
- Help prevent attacks used by Spoofing IP Addresses. Reverse path
- filtering is a Kernel feature that, when enabled, is designed to ensure
- packets that are not routable to be dropped. The easiest example of this
- would be and IP Address of the range 10.0.0.0/8, a private IP Address,
- being received on the Internet facing interface of the router.
+ def _cleanup_ipv4_source_validation_rules(self, ifname):
+ results = self._cmd(f'nft -a list chain ip raw vyos_rpfilter').split("\n")
+ for line in results:
+ if f'iifname "{ifname}"' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ self._cmd(f'nft delete rule ip raw vyos_rpfilter handle {handle_search[1]}')
- As per RFC3074.
+ def set_ipv4_source_validation(self, mode):
"""
- if value == 'strict':
- value = 1
- elif value == 'loose':
- value = 2
- else:
- value = 0
-
- all_rp_filter = int(read_file('/proc/sys/net/ipv4/conf/all/rp_filter'))
- if all_rp_filter > value:
- global_setting = 'disable'
- if all_rp_filter == 1: global_setting = 'strict'
- elif all_rp_filter == 2: global_setting = 'loose'
-
- from vyos.base import Warning
- Warning(f'Global source-validation is set to "{global_setting}", this '\
- f'overrides per interface setting on "{self.ifname}"!')
+ Set IPv4 reverse path validation
- tmp = self.get_interface('rp_filter')
- if int(tmp) == value:
- return None
- return self.set_interface('rp_filter', value)
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_ipv4_source_validation('strict')
+ """
+ self._cleanup_ipv4_source_validation_rules(self.ifname)
+ nft_prefix = f'nft insert rule ip raw vyos_rpfilter iifname "{self.ifname}"'
+ if mode in ['strict', 'loose']:
+ self._cmd(f"{nft_prefix} counter return")
+ if mode == 'strict':
+ self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop")
+ elif mode == 'loose':
+ self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop")
def _cleanup_ipv6_source_validation_rules(self, ifname):
- commands = []
results = self._cmd(f'nft -a list chain ip6 raw vyos_rpfilter').split("\n")
for line in results:
if f'iifname "{ifname}"' in line:
@@ -805,7 +790,9 @@ class Interface(Control):
>>> Interface('eth0').set_ipv6_source_validation('strict')
"""
self._cleanup_ipv6_source_validation_rules(self.ifname)
- nft_prefix = f'nft add rule ip6 raw vyos_rpfilter iifname "{self.ifname}"'
+ nft_prefix = f'nft insert rule ip6 raw vyos_rpfilter iifname "{self.ifname}"'
+ if mode in ['strict', 'loose']:
+ self._cmd(f"{nft_prefix} counter return")
if mode == 'strict':
self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop")
elif mode == 'loose':
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 366d71abd..a40b762a8 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -841,8 +841,12 @@ class BasicInterfaceTest:
self.assertEqual('1', tmp)
if cli_defined(self._base_path + ['ip'], 'source-validation'):
- tmp = read_file(f'{proc_base}/rp_filter')
- self.assertEqual('2', tmp)
+ base_options = f'iifname "{interface}"'
+ out = cmd('sudo nft list chain ip raw vyos_rpfilter')
+ for line in out.splitlines():
+ if line.startswith(base_options):
+ self.assertIn('fib saddr oif 0', line)
+ self.assertIn('drop', line)
def test_interface_ipv6_options(self):
if not self._test_ipv6:
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index a3885158b..f74a33566 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -600,23 +600,27 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
def test_source_validation(self):
# Strict
self.cli_set(['firewall', 'global-options', 'source-validation', 'strict'])
+ self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict'])
self.cli_commit()
nftables_strict_search = [
['fib saddr . iif oif 0', 'drop']
]
- self.verify_nftables(nftables_strict_search, 'inet vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter')
# Loose
self.cli_set(['firewall', 'global-options', 'source-validation', 'loose'])
+ self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose'])
self.cli_commit()
nftables_loose_search = [
['fib saddr oif 0', 'drop']
]
- self.verify_nftables(nftables_loose_search, 'inet vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter')
+ self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter')
def test_sysfs(self):
for name, conf in sysfs_config.items():
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index ebc9eeaaa..d5efae12c 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -1151,7 +1151,43 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' locator {locator_name}', frrconfig)
self.assertIn(f' sid vpn per-vrf export {sid}', frrconfig)
- def test_bgp_25_bmp(self):
+ def test_bgp_25_ipv4_ipv6_labeled_unicast_peer_group(self):
+ pg_ipv4 = 'foo4'
+ pg_ipv6 = 'foo6'
+
+ ipv4_max_prefix = '20'
+ ipv6_max_prefix = '200'
+ ipv4_prefix = '192.0.2.0/24'
+ ipv6_prefix = '2001:db8:1000::/64'
+
+ self.cli_set(base_path + ['listen', 'range', ipv4_prefix, 'peer-group', pg_ipv4])
+ self.cli_set(base_path + ['listen', 'range', ipv6_prefix, 'peer-group', pg_ipv6])
+
+ self.cli_set(base_path + ['peer-group', pg_ipv4, 'address-family', 'ipv4-labeled-unicast', 'maximum-prefix', ipv4_max_prefix])
+ self.cli_set(base_path + ['peer-group', pg_ipv4, 'remote-as', 'external'])
+ self.cli_set(base_path + ['peer-group', pg_ipv6, 'address-family', 'ipv6-labeled-unicast', 'maximum-prefix', ipv6_max_prefix])
+ self.cli_set(base_path + ['peer-group', pg_ipv6, 'remote-as', 'external'])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ self.assertIn(f'router bgp {ASN}', frrconfig)
+ self.assertIn(f' neighbor {pg_ipv4} peer-group', frrconfig)
+ self.assertIn(f' neighbor {pg_ipv4} remote-as external', frrconfig)
+ self.assertIn(f' neighbor {pg_ipv6} peer-group', frrconfig)
+ self.assertIn(f' neighbor {pg_ipv6} remote-as external', frrconfig)
+ self.assertIn(f' bgp listen range {ipv4_prefix} peer-group {pg_ipv4}', frrconfig)
+ self.assertIn(f' bgp listen range {ipv6_prefix} peer-group {pg_ipv6}', frrconfig)
+
+ afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast')
+ self.assertIn(f' neighbor {pg_ipv4} activate', afiv4_config)
+ self.assertIn(f' neighbor {pg_ipv4} maximum-prefix {ipv4_max_prefix}', afiv4_config)
+
+ afiv6_config = self.getFRRconfig(' address-family ipv6 labeled-unicast')
+ self.assertIn(f' neighbor {pg_ipv6} activate', afiv6_config)
+ self.assertIn(f' neighbor {pg_ipv6} maximum-prefix {ipv6_max_prefix}', afiv6_config)
+
+ def test_bgp_99_bmp(self):
target_name = 'instance-bmp'
target_address = '127.0.0.1'
target_port = '5000'
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 093e43494..91ae901cd 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -202,6 +202,13 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(pool + ['static-mapping', client, 'ip-address', inc_ip(subnet, client_base)])
client_base += 1
+ # cannot have mappings with duplicate IP addresses
+ with self.assertRaises(ConfigSessionError):
+ self.cli_set(pool + ['static-mapping', 'dupe1', 'mac', '00:50:00:00:00:01'])
+ self.cli_set(pool + ['static-mapping', 'dupe1', 'ip-address', inc_ip(subnet, 10)])
+ self.cli_commit()
+ self.cli_delete(pool + ['static-mapping', 'dupe1'])
+
# commit changes
self.cli_commit()
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index ac7d95632..8d849b1e6 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -214,6 +214,7 @@ def verify(dhcp):
if 'static_mapping' in subnet_config:
# Static mappings require just a MAC address (will use an IP from the dynamic pool if IP is not set)
+ used_ips = []
for mapping, mapping_config in subnet_config['static_mapping'].items():
if 'ip_address' in mapping_config:
if ip_address(mapping_config['ip_address']) not in ip_network(subnet):
@@ -224,6 +225,11 @@ def verify(dhcp):
raise ConfigError(f'MAC address required for static mapping "{mapping}"\n' \
f'within shared-network "{network}, {subnet}"!')
+ if mapping_config['ip_address'] in used_ips:
+ raise ConfigError(f'Configured IP address for static mapping "{mapping}" exists on another static mapping')
+
+ used_ips.append(mapping_config['ip_address'])
+
# There must be one subnet connected to a listen interface.
# This only counts if the network itself is not disabled!
if 'disable' not in network_config:
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index 5bdcf2fa1..adbac0405 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -159,7 +159,7 @@ def verify(ipsec):
if 'id' not in psk_config or 'secret' not in psk_config:
raise ConfigError(f'Authentication psk "{psk}" missing "id" or "secret"')
- if 'interfaces' in ipsec :
+ if 'interface' in ipsec:
for ifname in ipsec['interface']:
verify_interface_exists(ifname)
diff --git a/src/op_mode/image_manager.py b/src/op_mode/image_manager.py
index e75485f9f..e64a85b95 100755
--- a/src/op_mode/image_manager.py
+++ b/src/op_mode/image_manager.py
@@ -33,6 +33,27 @@ DELETE_IMAGE_PROMPT_MSG: str = 'Select an image to delete:'
MSG_DELETE_IMAGE_RUNNING: str = 'Currently running image cannot be deleted; reboot into another image first'
MSG_DELETE_IMAGE_DEFAULT: str = 'Default image cannot be deleted; set another image as default first'
+def annotated_list(images_list: list[str]) -> list[str]:
+ """Annotate list of images with additional info
+
+ Args:
+ images_list (list[str]): a list of image names
+
+ Returns:
+ list[str]: a list of image names with additional info
+ """
+ index_running: int = None
+ index_default: int = None
+ try:
+ index_running = images_list.index(image.get_running_image())
+ index_default = images_list.index(image.get_default_image())
+ except ValueError:
+ pass
+ if index_running is not None:
+ images_list[index_running] += ' (running)'
+ if index_default is not None:
+ images_list[index_default] += ' (default boot)'
+ return images_list
@compat.grub_cfg_update
def delete_image(image_name: Optional[str] = None,
@@ -42,7 +63,7 @@ def delete_image(image_name: Optional[str] = None,
Args:
image_name (str): a name of image to delete
"""
- available_images: list[str] = grub.version_list()
+ available_images: list[str] = annotated_list(grub.version_list())
if image_name is None:
if no_prompt:
exit('An image name is required for delete action')
@@ -83,7 +104,7 @@ def set_image(image_name: Optional[str] = None,
Args:
image_name (str): an image name
"""
- available_images: list[str] = grub.version_list()
+ available_images: list[str] = annotated_list(grub.version_list())
if image_name is None:
if not prompt:
exit('An image name is required for set action')