weizhouapache opened a new issue, #14319:
URL: https://github.com/apache/cloudstack/issues/14319

   ### problem
   
   The VR tries to turn on automatic conntrack helper assignment at boot:
   
   - `systemvm/debian/opt/cloud/bin/setup/common.sh` → `enable_passive_ftp()`:
     `echo "$1" > /proc/sys/net/netfilter/nf_conntrack_helper`
   - called as `enable_passive_ftp 1` from `router.sh` and `vpcrouter.sh`
   
   Linux 6.0 removed both the `nf_conntrack_helper` sysctl and the 
`nf_conntrack.nf_conntrack_helper` module parameter.
   On the Debian 12 based VR (kernel 6.1):
   
   - `/proc/sys/net/netfilter/nf_conntrack_helper` does not exist, so the 
`echo` fails on every boot
   - the FTP helper modules (`nf_conntrack_ftp` / `nf_nat_ftp`) are not loaded
   - nothing else attaches the helper: there are no `-j CT --helper ftp` rules 
in the raw table
   
   So the FTP helper is never attached to FTP control connections that go 
through the VR.
   As a result:
   
   1. The PASV reply (`227 Entering Passive Mode (a,b,c,d,p1,p2)`) is not 
rewritten.
      The guest VM's private IP is sent to the client.
   2. The passive data connection is not tracked as `RELATED`.
      It is not DNATed to the VM (port forwarding or static nat), and the 
`RELATED,ESTABLISHED` rule does not let it through the firewall.
      Only the control port (21) is reachable.
   
   This worked on older systemVM templates (kernel < 6.0), where the sysctl 
still existed.
   
   ### versions
   
   4.22.x (Debian 12 systemVM template, kernel 6.1), also present on main
   
   ### The steps to reproduce the bug
   
   1. Create an isolated network and deploy a guest VM.
   2. Inside the VM, install vsftpd:
      - enable anonymous login and anonymous upload
        (`anonymous_enable=YES`, `write_enable=YES`, `anon_upload_enable=YES`)
      - create an `upload` folder writable by the anonymous user
   3. Acquire a public IP and enable static NAT to the VM.
   4. Add a firewall rule on the public IP allowing TCP port 21 only.
   5. From an external Ubuntu 24.04 VM, connect with the stock `ftp` client.
      This **works**, because the client uses EPSV:
   
      ```
      # ftp 10.0.57.6
      Connected to 10.0.57.6.
      220 (vsFTPd 3.0.5)
      Name (10.0.57.6:user): anonymous
      331 Please specify the password.
      Password:
      230 Login successful.
      Remote system type is UNIX.
      Using binary mode to transfer files.
      ftp> cd upload
      250 Directory successfully changed.
      ftp> put README.md
      local: README.md remote: README.md
      229 Entering Extended Passive Mode (|||26040|)
      150 Ok to send data.
      100% |*************************************************| 12357      
206.74 MiB/s    00:00 ETA
      226 Transfer complete.
      12357 bytes sent in 00:00 (105.30 KiB/s)
      ```
   
   6. Simulate a PASV client that uses the IP from the 227 reply:
   
      ```
      curl -v --disable-epsv --no-ftp-skip-pasv-ip --list-only 
ftp://anonymous:[email protected]:21
      ```
   
   ### EXPECTED RESULTS
   
   The 227 reply contains the public IP, and the directory listing is returned.
   
   ### ACTUAL RESULTS
   
   The 227 reply contains the guest VM's private IP. The data connection fails 
and the listing is never returned.
   
   On the VR:
   
   ```
   # ls /proc/sys/net/netfilter/nf_conntrack_helper
   ls: cannot access '/proc/sys/net/netfilter/nf_conntrack_helper': No such 
file or directory
   # lsmod | grep -E 'nf_(conntrack|nat)_ftp'
   (no output)
   # iptables -t raw -S
   -P PREROUTING ACCEPT
   -P OUTPUT ACCEPT
   ```
   
   ### What to do about it?
   
   ### WORKAROUNDS
   
   #### Workaround 1: attach the FTP helper explicitly on the VR (verified)
   
   On the VR, add a raw-table rule for the public IP and the public FTP port:
   
   ```
   iptables -t raw -A PREROUTING -d <public-ip> -p tcp --dport 21 -j CT 
--helper ftp
   ```
   
   With this rule in place, the curl command from step 6 succeeds.
   The 227 reply is rewritten to the public IP, and the data connection is 
allowed as `RELATED`.
   No passive port range needs to be opened in the firewall.
   
   Notes:
   - The raw table is processed **before** DNAT, so `--dport` must be the 
**public** port.
     With static NAT, or port forwarding from public 21 to private 21, this is 
`21`.
     With port forwarding from a different public port (e.g. public 2121 → 
private 21), use `--dport 2121`.
   - The rule is **not persistent**. It is lost when the VR is restarted or 
recreated, and must be re-applied after that.
   - To remove it:
     `iptables -t raw -D PREROUTING -d <public-ip> -p tcp --dport 21 -j CT 
--helper ftp`
   
   #### Workaround 2: advertise the public IP and a fixed passive port range 
from the FTP server (verified, no VR change)
   
   In the guest VM, set the following in `/etc/vsftpd.conf`:
   
   ```
   listen=YES
   listen_ipv6=NO
   pasv_enable=YES
   pasv_address=<public-ip>
   pasv_min_port=60001
   pasv_max_port=60020
   ```
   
   Restart vsftpd and confirm it is listening on IPv4 (`0.0.0.0:21`):
   
   ```
   systemctl restart vsftpd && ss -ltnp | grep ':21 '
   ```
   
   Then, in CloudStack, add a firewall rule on the public IP for TCP ports 
60001–60020, in addition to port 21.
   With port forwarding instead of static NAT, also add a port-forwarding rule 
for 60001–60020 to the same private ports.
   
   With this, the curl command from step 6 succeeds.
   The 227 reply contains the public IP and a port in 60001–60020.
   
   Notes:
   - `listen=YES` / `listen_ipv6=NO` is required.
     With Ubuntu's default (`listen=NO`, `listen_ipv6=YES`), vsftpd ignores 
`pasv_address` and sends `227 Entering Passive Mode (0,0,0,0,...)`.
   - This workaround is persistent and does not depend on the VR.
     However, the passive port range must be exposed in the firewall, and the 
public IP is hard-coded in the server config.
   - Clients inside the guest network also receive the public IP in the 227 
reply, so they rely on hairpin NAT.
   
   
   ### SUGGESTED FIX
   
   Replace the removed sysctl with explicit helper assignment (the approach the 
kernel developers recommend), generated by the VR scripts:
   
   - **Static NAT / port forwarding with private port 21:**
     add `-t raw -A PREROUTING -d <public-ip> -p tcp --dport <public-port> -j 
CT --helper ftp`.
     Since the raw table runs before DNAT, port forwarding needs the **public** 
port.
   - **Outbound active FTP from guest VMs through source NAT** relied on the 
same automatic assignment and is likely affected too.
     This would need e.g. `-t raw -A PREROUTING -i <guest-if> -p tcp --dport 21 
-j CT --helper ftp`.
   - Remove or replace the no-op `enable_passive_ftp()` in `common.sh`.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to