From 8637dc4e2e10fad570ca7476a30feafac80e0ad9 Mon Sep 17 00:00:00 2001 From: hulucc <4343900+hulucc@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:22:57 +0800 Subject: [PATCH] Routed VR: accept VRRP and conntrackd sync on redundant routers In ROUTED mode the VR replaces iptables with nftables (table ip4_firewall for isolated networks, ip4_acl for VPCs) whose INPUT chain has a drop policy. The INPUT whitelist does not accept VRRP (224.0.0.18) and conntrackd (225.0.0.50) multicast, which the iptables path allows via "-A INPUT -d 224.0.0.18/32 -j ACCEPT" and "-d 225.0.0.50/32". With a redundant router offering both VRs drop each other's advertisements and both become PRIMARY, holding the guest gateway and public IPs at the same time. Accept both on the guest interface in fw_router_routing and fw_vpcrouter_routing, and add unit tests for them. Co-Authored-By: Claude Opus 5.5 --- systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 12 +++++++ systemvm/test/TestCsAddress.py | 34 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py index 1c4695df8064..66ccde91f108 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py @@ -660,6 +660,12 @@ def fw_router_routing(self): 'rule': "iifname %s ct state related,established counter accept" % self.dev}) self.nft_ipv4_fw.append({'type': "", 'chain': 'INPUT', 'rule': "iifname %s udp dport 67 counter accept" % self.dev}) + # VRRP (keepalived) and conntrackd sync between redundant routers; the iptables + # path allows these via "-A INPUT -d 224.0.0.18/32" and "-d 225.0.0.50/32". + self.nft_ipv4_fw.append({'type': "", 'chain': 'INPUT', + 'rule': "iifname %s ip daddr 224.0.0.18 counter accept" % self.dev}) + self.nft_ipv4_fw.append({'type': "", 'chain': 'INPUT', + 'rule': "iifname %s ip daddr 225.0.0.50 counter accept" % self.dev}) self.nft_ipv4_fw.append({'type': "", 'chain': 'INPUT', 'rule': "iifname %s ip saddr %s tcp dport 53 counter accept" % (self.dev, guestNetworkCidr)}) self.nft_ipv4_fw.append({'type': "", 'chain': 'INPUT', @@ -687,6 +693,12 @@ def fw_vpcrouter_routing(self): 'rule': "iifname %s ct state related,established counter accept" % self.dev}) self.nft_ipv4_acl.append({'type': "", 'chain': 'INPUT', 'rule': "iifname %s udp dport 67 counter accept" % self.dev}) + # VRRP (keepalived) and conntrackd sync between redundant routers; the iptables + # path allows these via "-A INPUT -d 224.0.0.18/32" and "-d 225.0.0.50/32". + self.nft_ipv4_acl.append({'type': "", 'chain': 'INPUT', + 'rule': "iifname %s ip daddr 224.0.0.18 counter accept" % self.dev}) + self.nft_ipv4_acl.append({'type': "", 'chain': 'INPUT', + 'rule': "iifname %s ip daddr 225.0.0.50 counter accept" % self.dev}) self.nft_ipv4_acl.append({'type': "", 'chain': 'INPUT', 'rule': "iifname %s ip saddr %s tcp dport 53 counter accept" % (self.dev, guestNetworkCidr)}) self.nft_ipv4_acl.append({'type': "", 'chain': 'INPUT', diff --git a/systemvm/test/TestCsAddress.py b/systemvm/test/TestCsAddress.py index 0ad9ae861b91..6c75a102198a 100644 --- a/systemvm/test/TestCsAddress.py +++ b/systemvm/test/TestCsAddress.py @@ -16,7 +16,8 @@ # under the License. import unittest -from cs.CsAddress import CsAddress +import mock +from cs.CsAddress import CsAddress, CsIP import merge @@ -39,5 +40,36 @@ def test_get_guest_netmask(self): self.assertTrue(self.csaddress.get_guest_netmask() == "255.255.255.0") +class TestCsIPRouted(unittest.TestCase): + + def routed_guest_ip(self, dev, is_vpc): + config = mock.MagicMock() + config.is_vpc.return_value = is_vpc + config.is_routed.return_value = True + config.get_nft_ipv4_fw.return_value = [] + config.get_nft_ipv4_acl.return_value = [] + with mock.patch.object(CsIP, 'list'): + ip = CsIP(dev, config) + ip.setAddress({"nw_type": "guest", "network": "10.1.1.0/24"}) + return ip + + def input_rules(self, rules): + return [rule['rule'] for rule in rules if rule['chain'] == 'INPUT'] + + def test_fw_router_routing_accepts_vrrp_and_conntrackd(self): + ip = self.routed_guest_ip("eth0", False) + ip.fw_router_routing() + rules = self.input_rules(ip.nft_ipv4_fw) + self.assertIn("iifname eth0 ip daddr 224.0.0.18 counter accept", rules) + self.assertIn("iifname eth0 ip daddr 225.0.0.50 counter accept", rules) + + def test_fw_vpcrouter_routing_accepts_vrrp_and_conntrackd(self): + ip = self.routed_guest_ip("eth2", True) + ip.fw_vpcrouter_routing() + rules = self.input_rules(ip.nft_ipv4_acl) + self.assertIn("iifname eth2 ip daddr 224.0.0.18 counter accept", rules) + self.assertIn("iifname eth2 ip daddr 225.0.0.50 counter accept", rules) + + if __name__ == '__main__': unittest.main()