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

weizhouapache 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 c7667d8c1fb VR: fix passive ftp through the virtual router on newer 
system vm templates (#14330)
c7667d8c1fb is described below

commit c7667d8c1fb8ceb4ef5ee687fd85cc84a545c275
Author: Daman Arora <[email protected]>
AuthorDate: Fri Oct 16 02:24:07 2026 -0400

    VR: fix passive ftp through the virtual router on newer system vm templates 
(#14330)
    
    * fix passive ftp through the virtual router on newer system vm templates
    
    * attach the ftp helper when a port forward sends another public port to 
port 21
    
    * only attach the ftp helper on static nat and port forwarded public ips
---
 systemvm/debian/opt/cloud/bin/configure.py       | 17 +++++++
 systemvm/debian/opt/cloud/bin/setup/common.sh    |  4 +-
 systemvm/debian/opt/cloud/bin/setup/router.sh    |  2 +-
 systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh |  2 +-
 systemvm/test/TestCsForwardingRules.py           | 63 ++++++++++++++++++++++++
 5 files changed, 85 insertions(+), 3 deletions(-)

diff --git a/systemvm/debian/opt/cloud/bin/configure.py 
b/systemvm/debian/opt/cloud/bin/configure.py
index 24195a11bff..0575433ad00 100755
--- a/systemvm/debian/opt/cloud/bin/configure.py
+++ b/systemvm/debian/opt/cloud/bin/configure.py
@@ -1411,6 +1411,22 @@ class CsForwardingRules(CsDataBag):
             self.forward_vpc(rule)
         else:
             self.forward_vr(rule)
+        self.forward_ftp_helper(rule)
+
+    def forward_ftp_helper(self, rule):
+        # Linux 6.0 no longer attaches the ftp helper after DNAT, so attach it
+        # to the public port that is forwarded to port 21
+        if rule["protocol"] != "tcp" or "any" in [rule["public_ports"], 
rule["internal_ports"]]:
+            return
+        public_start = int(rule["public_ports"].split(":")[0])
+        internal_ports = rule["internal_ports"].split(":")
+        internal_start = int(internal_ports[0])
+        internal_end = int(internal_ports[-1])
+        if not internal_start <= 21 <= internal_end:
+            return
+        public_port = public_start + 21 - internal_start
+        self.fw.append(["raw", "", "-A PREROUTING -d %s/32 -p tcp -m tcp 
--dport %s -j CT --helper ftp" %
+                        (rule["public_ip"], public_port)])
 
     def forward_vr(self, rule):
         # Prefetch iptables variables
@@ -1555,6 +1571,7 @@ class CsForwardingRules(CsDataBag):
             self.fw.append(["mangle", "front", "-A %s -d %s -j RETURN" %
                             (chain_name, static_route['network'])])
 
+        self.fw.append(["raw", "", "-A PREROUTING -d %s/32 -p tcp -m tcp 
--dport 21 -j CT --helper ftp" % rule["public_ip"]])
         self.fw.append(["nat", "front",
                         "-A PREROUTING -d %s/32 -j DNAT --to-destination %s" % 
(rule["public_ip"], rule["internal_ip"])])
         self.fw.append(["nat", "front",
diff --git a/systemvm/debian/opt/cloud/bin/setup/common.sh 
b/systemvm/debian/opt/cloud/bin/setup/common.sh
index ef1576ab588..ef610c91407 100755
--- a/systemvm/debian/opt/cloud/bin/setup/common.sh
+++ b/systemvm/debian/opt/cloud/bin/setup/common.sh
@@ -164,7 +164,9 @@ enable_fwding() {
 
 enable_passive_ftp() {
   log_it "cloud: enabling passive FTP for guest VMs"
-  echo "$1" > /proc/sys/net/netfilter/nf_conntrack_helper
+  # Linux 6.0 removed the nf_conntrack_helper sysctl, the ftp helper is
+  # attached by raw table rules for static nat and port forwarding
+  modprobe nf_nat_ftp
 }
 
 disable_rpfilter() {
diff --git a/systemvm/debian/opt/cloud/bin/setup/router.sh 
b/systemvm/debian/opt/cloud/bin/setup/router.sh
index 5c72105f47a..dc4fb9d0a69 100755
--- a/systemvm/debian/opt/cloud/bin/setup/router.sh
+++ b/systemvm/debian/opt/cloud/bin/setup/router.sh
@@ -70,7 +70,7 @@ setup_router() {
   disable_rpfilter_domR
   enable_fwding 1
   enable_rpsrfs 1
-  enable_passive_ftp 1
+  enable_passive_ftp
   restore_ipv6
 
   # Only allow DNS service for current network
diff --git a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh 
b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh
index 767f87848dd..cf88e6cac48 100755
--- a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh
+++ b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh
@@ -82,7 +82,7 @@ setup_vpcrouter() {
   enable_vpc_rpsrfs 1
   disable_rpfilter
   enable_fwding 1
-  enable_passive_ftp 1
+  enable_passive_ftp
   cp /etc/iptables/iptables-vpcrouter /etc/iptables/rules.v4
   cp /etc/vpcdnsmasq.conf /etc/dnsmasq.conf
   cp /etc/cloud-nic.rules /etc/udev/rules.d/cloud-nic.rules
diff --git a/systemvm/test/TestCsForwardingRules.py 
b/systemvm/test/TestCsForwardingRules.py
new file mode 100644
index 00000000000..50ec370ea9d
--- /dev/null
+++ b/systemvm/test/TestCsForwardingRules.py
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import unittest
+import mock
+from configure import CsForwardingRules
+
+
+class TestCsForwardingRules(unittest.TestCase):
+
+    def ftp_helper_rules(self, protocol, public_ports, internal_ports):
+        forwarding = CsForwardingRules.__new__(CsForwardingRules)
+        forwarding.fw = []
+        forwarding.forward_ftp_helper({"public_ip": "10.0.0.2", "internal_ip": 
"10.1.1.10", "protocol": protocol,
+                                       "public_ports": public_ports, 
"internal_ports": internal_ports})
+        return forwarding.fw
+
+    def test_ftp_helper_on_other_public_port(self):
+        self.assertEqual(self.ftp_helper_rules("tcp", "2121:2121", "21:21"),
+                         [["raw", "", "-A PREROUTING -d 10.0.0.2/32 -p tcp -m 
tcp --dport 2121 -j CT --helper ftp"]])
+
+    def test_ftp_helper_on_port_range(self):
+        self.assertEqual(self.ftp_helper_rules("tcp", "1020:1030", "20:30"),
+                         [["raw", "", "-A PREROUTING -d 10.0.0.2/32 -p tcp -m 
tcp --dport 1021 -j CT --helper ftp"]])
+
+    def test_ftp_helper_on_port_21(self):
+        self.assertEqual(self.ftp_helper_rules("tcp", "21:21", "21:21"),
+                         [["raw", "", "-A PREROUTING -d 10.0.0.2/32 -p tcp -m 
tcp --dport 21 -j CT --helper ftp"]])
+
+    def test_no_ftp_helper(self):
+        self.assertEqual(self.ftp_helper_rules("tcp", "2121:2121", 
"2121:2121"), [])
+        self.assertEqual(self.ftp_helper_rules("udp", "2121:2121", "21:21"), 
[])
+        self.assertEqual(self.ftp_helper_rules("tcp", "any", "any"), [])
+
+    @mock.patch.object(CsForwardingRules, 'getStaticRoutes', return_value=[])
+    @mock.patch.object(CsForwardingRules, 'getPrivateGatewayNetworks', 
return_value=[])
+    @mock.patch.object(CsForwardingRules, 'getGuestIpByIp', 
return_value="10.1.1.1")
+    @mock.patch.object(CsForwardingRules, 'getNetworkByIp', 
return_value="10.1.1.0/24")
+    @mock.patch.object(CsForwardingRules, 'getDeviceByIp', return_value="eth2")
+    def test_ftp_helper_on_static_nat(self, *_):
+        forwarding = CsForwardingRules.__new__(CsForwardingRules)
+        forwarding.fw = []
+        forwarding.processStaticNatRule({"public_ip": "10.0.0.2", 
"internal_ip": "10.1.1.10"})
+        self.assertIn(["raw", "", "-A PREROUTING -d 10.0.0.2/32 -p tcp -m tcp 
--dport 21 -j CT --helper ftp"],
+                      forwarding.fw)
+
+
+if __name__ == '__main__':
+    unittest.main()

Reply via email to