Skip to content

Commit 83f705d

Browse files
Static Routes with nexthop non-functional for private gateways (#12859)
* Fix static routes to be added to PBR tables in VPC routers Static routes were only being added to the main routing table, but policy-based routing (PBR) is active on VPC routers. This caused traffic coming in from specific interfaces to not find the static routes, as they use interface-specific routing tables (Table_ethX). This fix: - Adds a helper method to find which interface a gateway belongs to by matching the gateway IP against configured interface subnets - Modifies route add/delete operations to update both the main table and the appropriate interface-specific PBR table - Uses existing CsAddress databag metadata to avoid OS queries - Handles both add and revoke operations for proper cleanup - Adds comprehensive logging for troubleshooting Fixes #12857 * Add iptables FORWARD rules for nexthop-based static routes When static routes use nexthop (gateway) instead of referencing a private gateway's public IP, the iptables FORWARD rules were not being generated. This caused traffic to be dropped by ACLs. This fix: - Adds a shared helper CsHelper.find_device_for_gateway() to determine which interface a gateway belongs to by checking subnet membership - Updates CsStaticRoutes to use the shared helper instead of duplicating the device-finding logic - Modifies CsAddress firewall rule generation to handle both old-style (ip_address-based) and new-style (nexthop-based) static routes - Generates the required FORWARD and PREROUTING rules for nexthop routes: * -A PREROUTING -s <network> ! -d <interface_ip>/32 -i <dev> -j ACL_OUTBOUND_<dev> * -A FORWARD -d <network> -o <dev> -j ACL_INBOUND_<dev> * -A FORWARD -d <network> -o <dev> -m state --state RELATED,ESTABLISHED -j ACCEPT Fixes the second part of #12857 * network matching grep fix, don't let 1.2.3.4/32 match 11.2.3.4/32
1 parent 6e81098 commit 83f705d

File tree

3 files changed

+98
-19
lines changed

3 files changed

+98
-19
lines changed

systemvm/debian/opt/cloud/bin/cs/CsAddress.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,37 @@ def fw_vpcrouter(self):
584584
"-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" %
585585
(self.dev, guestNetworkCidr, self.address['gateway'], self.dev)])
586586

587+
# Process static routes for this interface
588+
static_routes = CsStaticRoutes("staticroutes", self.config)
589+
if static_routes:
590+
for item in static_routes.get_bag():
591+
if item == "id":
592+
continue
593+
static_route = static_routes.get_bag()[item]
594+
if static_route['revoke']:
595+
continue
596+
597+
# Check if this static route applies to this interface
598+
# Old style: ip_address field matches this interface's public_ip
599+
# New style (nexthop): gateway is in this interface's subnet
600+
applies_to_interface = False
601+
if 'ip_address' in static_route and static_route['ip_address'] == self.address['public_ip']:
602+
applies_to_interface = True
603+
elif 'gateway' in static_route:
604+
device = CsHelper.find_device_for_gateway(self.config, static_route['gateway'])
605+
if device == self.dev:
606+
applies_to_interface = True
607+
608+
if applies_to_interface:
609+
self.fw.append(["mangle", "",
610+
"-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" %
611+
(self.dev, static_route['network'], self.address['public_ip'], self.dev)])
612+
self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" %
613+
(static_route['network'], self.dev, self.dev)])
614+
self.fw.append(["filter", "front",
615+
"-A FORWARD -d %s -o %s -m state --state RELATED,ESTABLISHED -j ACCEPT" %
616+
(static_route['network'], self.dev)])
617+
587618
if self.is_private_gateway():
588619
self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" %
589620
(self.address['network'], self.dev, self.dev)])
@@ -597,22 +628,6 @@ def fw_vpcrouter(self):
597628
"-A PREROUTING -s %s -d %s -m state --state NEW -j MARK --set-xmark %s/0xffffffff" %
598629
(self.cl.get_vpccidr(), self.address['network'], hex(100 + int(self.dev[3:])))])
599630

600-
static_routes = CsStaticRoutes("staticroutes", self.config)
601-
if static_routes:
602-
for item in static_routes.get_bag():
603-
if item == "id":
604-
continue
605-
static_route = static_routes.get_bag()[item]
606-
if 'ip_address' in static_route and static_route['ip_address'] == self.address['public_ip'] and not static_route['revoke']:
607-
self.fw.append(["mangle", "",
608-
"-A PREROUTING -m state --state NEW -i %s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" %
609-
(self.dev, static_route['network'], static_route['ip_address'], self.dev)])
610-
self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s -j ACL_INBOUND_%s" %
611-
(static_route['network'], self.dev, self.dev)])
612-
self.fw.append(["filter", "front",
613-
"-A FORWARD -d %s -o %s -m state --state RELATED,ESTABLISHED -j ACCEPT" %
614-
(static_route['network'], self.dev)])
615-
616631
if self.address["source_nat"]:
617632
self.fw.append(["nat", "front",
618633
"-A POSTROUTING -o %s -j SNAT --to-source %s" %

systemvm/debian/opt/cloud/bin/cs/CsHelper.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@
2525
import os.path
2626
import re
2727
import shutil
28+
from typing import Optional, TYPE_CHECKING
2829
from netaddr import *
2930

31+
if TYPE_CHECKING:
32+
from .CsConfig import CsConfig
33+
3034
PUBLIC_INTERFACES = {"router": "eth2", "vpcrouter": "eth1"}
3135

3236
STATE_COMMANDS = {"router": "ip addr show dev eth0 | grep inet | wc -l | xargs bash -c 'if [ $0 == 2 ]; then echo \"PRIMARY\"; else echo \"BACKUP\"; fi'",
@@ -270,3 +274,29 @@ def copy(src, dest):
270274
logging.error("Could not copy %s to %s" % (src, dest))
271275
else:
272276
logging.info("Copied %s to %s" % (src, dest))
277+
278+
279+
def find_device_for_gateway(config: 'CsConfig', gateway_ip: str) -> Optional[str]:
280+
"""
281+
Find which ethernet device the gateway IP belongs to by checking
282+
if the gateway is in any of the configured interface subnets.
283+
284+
Args:
285+
config: CsConfig instance containing network configuration
286+
gateway_ip: IP address of the gateway to locate
287+
288+
Returns:
289+
Device name (e.g., 'eth2') or None if not found
290+
"""
291+
try:
292+
interfaces = config.address().get_interfaces()
293+
for interface in interfaces:
294+
if not interface.is_added():
295+
continue
296+
if interface.ip_in_subnet(gateway_ip):
297+
return interface.get_device()
298+
logging.debug("No matching device found for gateway %s" % gateway_ip)
299+
return None
300+
except Exception as e:
301+
logging.error("Error finding device for gateway %s: %s" % (gateway_ip, e))
302+
return None

systemvm/debian/opt/cloud/bin/cs/CsStaticRoutes.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import logging
2121
from . import CsHelper
2222
from .CsDatabag import CsDataBag
23+
from .CsRoute import CsRoute
2324

2425

2526
class CsStaticRoutes(CsDataBag):
@@ -31,13 +32,46 @@ def process(self):
3132
continue
3233
self.__update(self.dbag[item])
3334

35+
36+
3437
def __update(self, route):
38+
network = route['network']
39+
gateway = route['gateway']
40+
3541
if route['revoke']:
36-
command = "ip route del %s via %s" % (route['network'], route['gateway'])
42+
# Delete from main table
43+
command = "ip route del %s via %s" % (network, gateway)
3744
CsHelper.execute(command)
45+
46+
# Delete from PBR table if applicable
47+
device = CsHelper.find_device_for_gateway(self.config, gateway)
48+
if device:
49+
cs_route = CsRoute()
50+
table_name = cs_route.get_tablename(device)
51+
command = "ip route del %s via %s table %s" % (network, gateway, table_name)
52+
CsHelper.execute(command)
53+
logging.info("Deleted static route %s via %s from PBR table %s" % (network, gateway, table_name))
3854
else:
39-
command = "ip route show | grep %s | awk '{print $1, $3}'" % route['network']
55+
# Add to main table (existing logic)
56+
command = "ip route show | grep '^%s' | awk '{print $1, $3}'" % network
4057
result = CsHelper.execute(command)
4158
if not result:
42-
route_command = "ip route add %s via %s" % (route['network'], route['gateway'])
59+
route_command = "ip route add %s via %s" % (network, gateway)
4360
CsHelper.execute(route_command)
61+
logging.info("Added static route %s via %s to main table" % (network, gateway))
62+
63+
# Add to PBR table if applicable
64+
device = CsHelper.find_device_for_gateway(self.config, gateway)
65+
if device:
66+
cs_route = CsRoute()
67+
table_name = cs_route.get_tablename(device)
68+
# Check if route already exists in the PBR table
69+
check_command = "ip route show table %s | grep '^%s' | awk '{print $1, $3}'" % (table_name, network)
70+
result = CsHelper.execute(check_command)
71+
if not result:
72+
# Add route to the interface-specific table
73+
route_command = "ip route add %s via %s dev %s table %s" % (network, gateway, device, table_name)
74+
CsHelper.execute(route_command)
75+
logging.info("Added static route %s via %s to PBR table %s" % (network, gateway, table_name))
76+
else:
77+
logging.info("Static route %s via %s added to main table only (no matching interface found for PBR table)" % (network, gateway))

0 commit comments

Comments
 (0)