Skip to content

Commit e102b98

Browse files
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
1 parent 8a60b80 commit e102b98

File tree

3 files changed

+63
-40
lines changed

3 files changed

+63
-40
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: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,7 @@ def process(self):
3232
continue
3333
self.__update(self.dbag[item])
3434

35-
def __find_device_for_gateway(self, gateway_ip):
36-
"""
37-
Find which ethernet device the gateway IP belongs to by checking
38-
if the gateway is in any of the configured interface subnets.
39-
Returns device name (e.g., 'eth2') or None if not found.
40-
"""
41-
try:
42-
# Get all configured interfaces from the address databag
43-
interfaces = self.config.address().get_interfaces()
4435

45-
for interface in interfaces:
46-
if not interface.is_added():
47-
continue
48-
49-
# Check if gateway IP is in this interface's subnet
50-
if interface.ip_in_subnet(gateway_ip):
51-
return interface.get_device()
52-
53-
logging.debug("No matching device found for gateway %s" % gateway_ip)
54-
return None
55-
except Exception as e:
56-
logging.error("Error finding device for gateway %s: %s" % (gateway_ip, e))
57-
return None
5836

5937
def __update(self, route):
6038
network = route['network']
@@ -66,7 +44,7 @@ def __update(self, route):
6644
CsHelper.execute(command)
6745

6846
# Delete from PBR table if applicable
69-
device = self.__find_device_for_gateway(gateway)
47+
device = CsHelper.find_device_for_gateway(self.config, gateway)
7048
if device:
7149
cs_route = CsRoute()
7250
table_name = cs_route.get_tablename(device)
@@ -83,7 +61,7 @@ def __update(self, route):
8361
logging.info("Added static route %s via %s to main table" % (network, gateway))
8462

8563
# Add to PBR table if applicable
86-
device = self.__find_device_for_gateway(gateway)
64+
device = CsHelper.find_device_for_gateway(self.config, gateway)
8765
if device:
8866
cs_route = CsRoute()
8967
table_name = cs_route.get_tablename(device)

0 commit comments

Comments
 (0)