This is an automated email from the ASF dual-hosted git repository.

sureshanaparti pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new 83f705ddc58 Static Routes with nexthop non-functional for private 
gateways (#12859)
83f705ddc58 is described below

commit 83f705ddc58841991e01436210d64bef55d98f52
Author: Brad House - Nexthop <[email protected]>
AuthorDate: Thu Apr 16 06:45:43 2026 -0400

    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
---
 systemvm/debian/opt/cloud/bin/cs/CsAddress.py      | 47 ++++++++++++++--------
 systemvm/debian/opt/cloud/bin/cs/CsHelper.py       | 30 ++++++++++++++
 systemvm/debian/opt/cloud/bin/cs/CsStaticRoutes.py | 40 ++++++++++++++++--
 3 files changed, 98 insertions(+), 19 deletions(-)

diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py 
b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py
index c7dac4df47e..bde4a976e31 100755
--- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py
+++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py
@@ -584,6 +584,37 @@ class CsIP:
                                 "-A PREROUTING -m state --state NEW -i %s -s 
%s ! -d %s/32 -j ACL_OUTBOUND_%s" %
                                 (self.dev, guestNetworkCidr, 
self.address['gateway'], self.dev)])
 
+        # Process static routes for this interface
+        static_routes = CsStaticRoutes("staticroutes", self.config)
+        if static_routes:
+            for item in static_routes.get_bag():
+                if item == "id":
+                    continue
+                static_route = static_routes.get_bag()[item]
+                if static_route['revoke']:
+                    continue
+
+                # Check if this static route applies to this interface
+                # Old style: ip_address field matches this interface's 
public_ip
+                # New style (nexthop): gateway is in this interface's subnet
+                applies_to_interface = False
+                if 'ip_address' in static_route and static_route['ip_address'] 
== self.address['public_ip']:
+                    applies_to_interface = True
+                elif 'gateway' in static_route:
+                    device = CsHelper.find_device_for_gateway(self.config, 
static_route['gateway'])
+                    if device == self.dev:
+                        applies_to_interface = True
+
+                if applies_to_interface:
+                    self.fw.append(["mangle", "",
+                                    "-A PREROUTING -m state --state NEW -i %s 
-s %s ! -d %s/32 -j ACL_OUTBOUND_%s" %
+                                    (self.dev, static_route['network'], 
self.address['public_ip'], self.dev)])
+                    self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s 
-j ACL_INBOUND_%s" %
+                                    (static_route['network'], self.dev, 
self.dev)])
+                    self.fw.append(["filter", "front",
+                                    "-A FORWARD -d %s -o %s -m state --state 
RELATED,ESTABLISHED -j ACCEPT" %
+                                    (static_route['network'], self.dev)])
+
         if self.is_private_gateway():
             self.fw.append(["filter", "front", "-A FORWARD -d %s -o %s -j 
ACL_INBOUND_%s" %
                             (self.address['network'], self.dev, self.dev)])
@@ -597,22 +628,6 @@ class CsIP:
                             "-A PREROUTING -s %s -d %s -m state --state NEW -j 
MARK --set-xmark %s/0xffffffff" %
                             (self.cl.get_vpccidr(), self.address['network'], 
hex(100 + int(self.dev[3:])))])
 
-            static_routes = CsStaticRoutes("staticroutes", self.config)
-            if static_routes:
-                for item in static_routes.get_bag():
-                    if item == "id":
-                        continue
-                    static_route = static_routes.get_bag()[item]
-                    if 'ip_address' in static_route and 
static_route['ip_address'] == self.address['public_ip'] and not 
static_route['revoke']:
-                        self.fw.append(["mangle", "",
-                                        "-A PREROUTING -m state --state NEW -i 
%s -s %s ! -d %s/32 -j ACL_OUTBOUND_%s" %
-                                        (self.dev, static_route['network'], 
static_route['ip_address'], self.dev)])
-                        self.fw.append(["filter", "front", "-A FORWARD -d %s 
-o %s -j ACL_INBOUND_%s" %
-                                        (static_route['network'], self.dev, 
self.dev)])
-                        self.fw.append(["filter", "front",
-                                        "-A FORWARD -d %s -o %s -m state 
--state RELATED,ESTABLISHED -j ACCEPT" %
-                                        (static_route['network'], self.dev)])
-
             if self.address["source_nat"]:
                 self.fw.append(["nat", "front",
                                 "-A POSTROUTING -o %s -j SNAT --to-source %s" %
diff --git a/systemvm/debian/opt/cloud/bin/cs/CsHelper.py 
b/systemvm/debian/opt/cloud/bin/cs/CsHelper.py
index c71bcdac4f4..28bed0ae5d6 100755
--- a/systemvm/debian/opt/cloud/bin/cs/CsHelper.py
+++ b/systemvm/debian/opt/cloud/bin/cs/CsHelper.py
@@ -25,8 +25,12 @@ import sys
 import os.path
 import re
 import shutil
+from typing import Optional, TYPE_CHECKING
 from netaddr import *
 
+if TYPE_CHECKING:
+    from .CsConfig import CsConfig
+
 PUBLIC_INTERFACES = {"router": "eth2", "vpcrouter": "eth1"}
 
 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):
         logging.error("Could not copy %s to %s" % (src, dest))
     else:
         logging.info("Copied %s to %s" % (src, dest))
+
+
+def find_device_for_gateway(config: 'CsConfig', gateway_ip: str) -> 
Optional[str]:
+    """
+    Find which ethernet device the gateway IP belongs to by checking
+    if the gateway is in any of the configured interface subnets.
+
+    Args:
+        config: CsConfig instance containing network configuration
+        gateway_ip: IP address of the gateway to locate
+
+    Returns:
+        Device name (e.g., 'eth2') or None if not found
+    """
+    try:
+        interfaces = config.address().get_interfaces()
+        for interface in interfaces:
+            if not interface.is_added():
+                continue
+            if interface.ip_in_subnet(gateway_ip):
+                return interface.get_device()
+        logging.debug("No matching device found for gateway %s" % gateway_ip)
+        return None
+    except Exception as e:
+        logging.error("Error finding device for gateway %s: %s" % (gateway_ip, 
e))
+        return None
diff --git a/systemvm/debian/opt/cloud/bin/cs/CsStaticRoutes.py 
b/systemvm/debian/opt/cloud/bin/cs/CsStaticRoutes.py
index bcd669b6d45..d15ea66e164 100755
--- a/systemvm/debian/opt/cloud/bin/cs/CsStaticRoutes.py
+++ b/systemvm/debian/opt/cloud/bin/cs/CsStaticRoutes.py
@@ -20,6 +20,7 @@
 import logging
 from . import CsHelper
 from .CsDatabag import CsDataBag
+from .CsRoute import CsRoute
 
 
 class CsStaticRoutes(CsDataBag):
@@ -31,13 +32,46 @@ class CsStaticRoutes(CsDataBag):
                 continue
             self.__update(self.dbag[item])
 
+
+
     def __update(self, route):
+        network = route['network']
+        gateway = route['gateway']
+
         if route['revoke']:
-            command = "ip route del %s via %s" % (route['network'], 
route['gateway'])
+            # Delete from main table
+            command = "ip route del %s via %s" % (network, gateway)
             CsHelper.execute(command)
+
+            # Delete from PBR table if applicable
+            device = CsHelper.find_device_for_gateway(self.config, gateway)
+            if device:
+                cs_route = CsRoute()
+                table_name = cs_route.get_tablename(device)
+                command = "ip route del %s via %s table %s" % (network, 
gateway, table_name)
+                CsHelper.execute(command)
+                logging.info("Deleted static route %s via %s from PBR table 
%s" % (network, gateway, table_name))
         else:
-            command = "ip route show | grep %s | awk '{print $1, $3}'" % 
route['network']
+            # Add to main table (existing logic)
+            command = "ip route show | grep '^%s' | awk '{print $1, $3}'" % 
network
             result = CsHelper.execute(command)
             if not result:
-                route_command = "ip route add %s via %s" % (route['network'], 
route['gateway'])
+                route_command = "ip route add %s via %s" % (network, gateway)
                 CsHelper.execute(route_command)
+                logging.info("Added static route %s via %s to main table" % 
(network, gateway))
+
+            # Add to PBR table if applicable
+            device = CsHelper.find_device_for_gateway(self.config, gateway)
+            if device:
+                cs_route = CsRoute()
+                table_name = cs_route.get_tablename(device)
+                # Check if route already exists in the PBR table
+                check_command = "ip route show table %s | grep '^%s' | awk 
'{print $1, $3}'" % (table_name, network)
+                result = CsHelper.execute(check_command)
+                if not result:
+                    # Add route to the interface-specific table
+                    route_command = "ip route add %s via %s dev %s table %s" % 
(network, gateway, device, table_name)
+                    CsHelper.execute(route_command)
+                    logging.info("Added static route %s via %s to PBR table 
%s" % (network, gateway, table_name))
+            else:
+                logging.info("Static route %s via %s added to main table only 
(no matching interface found for PBR table)" % (network, gateway))

Reply via email to