Skip to content

gh-127478: ftplib: prefer EPSV over PASV on IPv4 connections#150236

Open
simonepelosi wants to merge 3 commits into
python:mainfrom
simonepelosi:ftplib-prefer-epsv
Open

gh-127478: ftplib: prefer EPSV over PASV on IPv4 connections#150236
simonepelosi wants to merge 3 commits into
python:mainfrom
simonepelosi:ftplib-prefer-epsv

Conversation

@simonepelosi
Copy link
Copy Markdown

@simonepelosi simonepelosi commented May 22, 2026

Summary

ftplib.FTP.makepasv() now prefers EPSV (RFC 2428) over PASV when connected over IPv4, with automatic fallback to PASV if the server does not support EPSV.

Motivation

PASV responses embed an IP address (227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)), which causes two classes of problems:

  1. Firewall FTP ALG interference: Next-Generation Firewalls with FTP Application Layer Gateways intercept PASV responses to parse/rewrite the embedded IP and set up data channel "pinholes". These ALGs frequently mangle or drop the response, causing 550 internal server error or connection timeouts.

  2. Interaction with bpo-43285 security fix: Since Python 3.9.3, trust_server_pasv_ipv4_address defaults to False, causing ftplib to ignore the PASV IP and connect the data channel to the control connection IP. When infrastructure uses load balancers or content caches in front of FTP servers, the control connection IP often has no listener on passive data ports, resulting in timeouts.

EPSV (229 Entering Extended Passive Mode (|||port|)) returns only a port number — no IP address. This makes it:

  • Firewall-transparent: no IP to parse/rewrite, ALGs pass it through
  • NAT-friendly: no embedded private IP that becomes unreachable externally
  • More secure: cannot redirect data connections to arbitrary third-party hosts (FTP bounce attack)
  • Protocol-agnostic: works on both IPv4 and IPv6

EPSV has been an RFC standard since 1998 (27 years) and is universally supported by FTP servers. ftplib already uses EPSV unconditionally on IPv6 — this change extends that preference to IPv4.

Changes

  • Lib/ftplib.py: Added FTP.prefer_epsv = True class attribute. makepasv() tries EPSV first on IPv4, falls back to PASV on error_perm.
  • Lib/test/test_ftplib.py:
    • Updated cmd_epsv handler to use self.socket.family instead of hardcoded AF_INET6 (enables EPSV testing on IPv4)
    • Added test_makepasv_prefer_epsv_disabled — verifies PASV is used when prefer_epsv=False
    • Added test_makepasv_prefer_epsv_fallback_to_pasv — verifies graceful fallback when server rejects EPSV
    • Updated existing PASV security tests to set prefer_epsv=False (they test PASV-specific behavior)
  • Misc/NEWS.d: Changelog entry

Backwards Compatibility

  • Default behavior change: IPv4 connections now send EPSV before PASV. Servers that support EPSV (virtually all modern FTP servers) will see no difference in functionality.
  • Opt-out: Set ftp.prefer_epsv = False to restore the old PASV-first behavior.
  • Fallback: If a server responds with an error to EPSV, ftplib automatically falls back to PASV with all existing behavior (including trust_server_pasv_ipv4_address handling).

Testing

  • All 99 ftplib tests pass (1 skipped — TLS-related, pre-existing)
  • Validated against a real-world FTP server (ppa.launchpad.net) where PASV fails through firewall ALG but EPSV succeeds

Real-World Impact

This fixes FTP uploads for all Python FTP clients connecting through firewalls with FTP ALG enabled — a common enterprise and ISP configuration.

Closes #127478

makepasv() now tries EPSV (RFC 2428) before PASV when connected over
IPv4. EPSV returns only a port number without an IP address, making it
transparent to firewall FTP Application Layer Gateways (ALGs) that
intercept and often mangle PASV responses containing embedded IPs.

Falls back to PASV if the server responds with an error to EPSV.
A new class attribute FTP.prefer_epsv (default True) allows reverting
to the old PASV-first behavior when set to False.

This also fixes connectivity issues caused by the trust_server_pasv_ipv4_address
security fix (bpo-43285): when firewalls rewrite PASV responses, clients
connecting to the control channel IP on the data port often fail because
nothing is listening there. EPSV avoids this entirely since the client
always connects back to the same IP.
@simonepelosi simonepelosi requested a review from giampaolo as a code owner May 22, 2026 11:11
@python-cla-bot
Copy link
Copy Markdown

python-cla-bot Bot commented May 22, 2026

All commit authors signed the Contributor License Agreement.

CLA signed

@read-the-docs-community
Copy link
Copy Markdown

read-the-docs-community Bot commented May 22, 2026

Documentation build overview

📚 cpython-previews | 🛠️ Build #32809903 | 📁 Comparing 208011c against main (28eac9a)

  🔍 Preview build  

5 files changed · ± 5 modified

± Modified

Replace :meth:`makepasv` references (undocumented method) with plain
prose to avoid unresolved cross-reference warnings in nitpicky mode.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement to support FTP connection with NAT scenario

1 participant