On Wed, Aug 13, 2025 at 4:23 PM Aaron Conole <acon...@redhat.com> wrote: > > Mike Pattrick via dev <ovs-dev@openvswitch.org> writes: > > > Recent versions of Curl combine the EPSV and EPRT commands with IPv4 > > connections instead of the PASV and PORT commands. EPSV and EPRT were > > added to FTP for IPv6 support but these commands also support IPv4. Most > > software still uses the old PASV and PORT commands in IPv4 connections > > but recent versions of curl will default to EPSV and EPRT for all > > connections. This patch adds support for these extended commands, and > > added tests for both with and without them. > > > > Reported-at: https://issues.redhat.com/browse/FDP-907 > > Reported-by: Eelco Chaudron <echau...@redhat.com> > > Signed-off-by: Mike Pattrick <m...@redhat.com> > > --- > > NEWS | 3 + > > lib/conntrack.c | 189 +++++++++++++++++++++++----------- > > tests/system-common-macros.at | 4 +- > > tests/system-traffic.at | 10 +- > > 4 files changed, 142 insertions(+), 64 deletions(-) > > > > diff --git a/NEWS b/NEWS > > index b5d565ecc..72782f9cf 100644 > > --- a/NEWS > > +++ b/NEWS > > @@ -48,6 +48,9 @@ v3.6.0 - xx xxx xxxx > > the OVS distribution in the 3.0 release and is no longer present in > > any supported versions of OVS. The remaining documentation of this > > kernel module relates to topics for older releases of OVS. > > + - Userspace datapath: > > + * Conntrack now supports the FTP commands EPSV and EPRT with IPv4 > > + connections, instead of limiting these commands to IPv6 only. > > > > > > v3.5.0 - 17 Feb 2025 > > diff --git a/lib/conntrack.c b/lib/conntrack.c > > index 00346a0be..9234cace1 100644 > > --- a/lib/conntrack.c > > +++ b/lib/conntrack.c > > @@ -192,6 +192,8 @@ static alg_helper alg_helpers[] = { > > #define FTP_PORT_CMD "PORT" > > /* FTP pasv string used in passive mode. */ > > #define FTP_PASV_REPLY_CODE "227" > > +/* FTP epsv string used in passive mode. */ > > +#define FTP_EPSV_REPLY_CODE "229" > > /* Maximum decimal digits for port in FTP command. > > * The port is represented as two 3 digit numbers with the > > * high part a multiple of 256. */ > > @@ -203,6 +205,8 @@ static alg_helper alg_helpers[] = { > > #define FTP_EPSV_REPLY "EXTENDED PASSIVE" > > /* Maximum decimal digits for port in FTP extended command. */ > > #define MAX_EXT_FTP_PORT_DGTS 5 > > +/* FTP extended command code for IPv4. */ > > +#define FTP_AF_V4 '1' > > /* FTP extended command code for IPv6. */ > > #define FTP_AF_V6 '2' > > /* Used to indicate a wildcard L4 source port number for ALGs. > > @@ -3269,10 +3273,16 @@ static int > > repl_ftp_v4_addr(struct dp_packet *pkt, ovs_be32 v4_addr_rep, > > char *ftp_data_start, > > size_t addr_offset_from_ftp_data_start, > > - size_t addr_size OVS_UNUSED) > > + size_t addr_size) > > { > > enum { MAX_FTP_V4_NAT_DELTA = 8 }; > > > > + /* EPSV mode. */ > > + if (addr_offset_from_ftp_data_start == 0 && > > + addr_size == 0) { > > + return 0; > > + } > > + > > /* Do conservative check for pathological MTU usage. */ > > uint32_t orig_used_size = dp_packet_size(pkt); > > if (orig_used_size + MAX_FTP_V4_NAT_DELTA > > > @@ -3345,8 +3355,11 @@ detect_ftp_ctl_type(const struct conn_lookup_ctx > > *ctx, > > } > > } else { > > if (strncasecmp(ftp_msg, FTP_PORT_CMD, strlen(FTP_PORT_CMD)) && > > + strncasecmp(ftp_msg, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD)) && > > strncasecmp(ftp_msg, FTP_PASV_REPLY_CODE, > > - strlen(FTP_PASV_REPLY_CODE))) { > > + strlen(FTP_PASV_REPLY_CODE)) && > > + strncasecmp(ftp_msg, FTP_EPSV_REPLY_CODE, > > + strlen(FTP_EPSV_REPLY_CODE))) { > > return CT_FTP_CTL_OTHER; > > } > > } > > @@ -3370,11 +3383,23 @@ process_ftp_ctl_v4(struct conntrack *ct, > > char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0}; > > get_ftp_ctl_msg(pkt, ftp_msg); > > char *ftp = ftp_msg; > > + struct in_addr ip_addr; > > enum ct_alg_mode mode; > > + bool extended = false; > > + char delim = 0; > > > > if (!strncasecmp(ftp, FTP_PORT_CMD, strlen(FTP_PORT_CMD))) { > > ftp = ftp_msg + strlen(FTP_PORT_CMD); > > mode = CT_FTP_MODE_ACTIVE; > > + } else if (!strncasecmp(ftp, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD))) { > > + ftp = ftp_msg + strlen(FTP_EPRT_CMD); > > + mode = CT_FTP_MODE_ACTIVE; > > + extended = true; > > + } else if (!strncasecmp(ftp, FTP_EPSV_REPLY_CODE, > > + strlen(FTP_EPSV_REPLY_CODE))) { > > + ftp = ftp_msg + strlen(FTP_EPSV_REPLY_CODE); > > + mode = CT_FTP_MODE_PASSIVE; > > + extended = true; > > } else { > > ftp = ftp_msg + strlen(FTP_PASV_REPLY_CODE); > > mode = CT_FTP_MODE_PASSIVE; > > @@ -3392,71 +3417,117 @@ process_ftp_ctl_v4(struct conntrack *ct, > > return CT_FTP_CTL_INVALID; > > } > > > > - char *ip_addr_start = ftp; > > - *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg; > > + /* EPRT, skip address family. */ > > We're not exactly just blindly skipping - maybe 'verify'. > > > + if (extended && mode == CT_FTP_MODE_ACTIVE) { > > + if (*ftp != FTP_AF_V4) { > > I think we should also add check for '|| isdigit(ftp[1])' > > RFC 2428 only specifies [1, 2] as values for now. But a future revision > that somehow adds '10+' would pass this check. > > > + return CT_FTP_CTL_INVALID; > > + } > > + delim = ftp[1]; > > + ftp = skip_non_digits(ftp + 1); > > + if (*ftp == 0) { > > + return CT_FTP_CTL_INVALID; > > + } > > + } > > + > > + if (!extended || mode == CT_FTP_MODE_ACTIVE) { > > + char *ip_addr_start = ftp; > > + *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg; > > > > - uint8_t comma_count = 0; > > - while (comma_count < 4 && *ftp) { > > - if (*ftp == ',') { > > - comma_count++; > > - if (comma_count == 4) { > > - *ftp = 0; > > + uint8_t delim_count = 0; > > + while (delim_count < 4 && *ftp) { > > + if (extended) { > > + if (*ftp == '.') { > > Maybe a nit - feel free to not do it, but this use of '.' and ',' to > mean the delimiter open coded are a bit difficult to understand on first > glance. Especially because this seems like a re-implementation of > repl_bytes and we just want the count. Maybe a useful preparation patch > would be to update repl_bytes to support this to reduce the amount of > open coding here. > > But that would need an additional patch in front of this. > > > + delim_count++; > > + } else if (*ftp == delim) { > > + delim_count++; > > + *ftp = 0; > > + ftp++; > > + break; > > + } > > } else { > > - *ftp = '.'; > > + if (*ftp == ',') { > > + delim_count++; > > + if (delim_count == 4) { > > + *ftp = 0; > > + } else { > > + *ftp = '.'; > > + } > > + } > > } > > + ftp++; > > + } > > + if (delim_count != 4) { > > + return CT_FTP_CTL_INVALID; > > } > > - ftp++; > > - } > > - if (comma_count != 4) { > > - return CT_FTP_CTL_INVALID; > > - } > > > > - struct in_addr ip_addr; > > - int rc2 = inet_pton(AF_INET, ip_addr_start, &ip_addr); > > - if (rc2 != 1) { > > - return CT_FTP_CTL_INVALID; > > + int rc2 = inet_pton(AF_INET, ip_addr_start, &ip_addr); > > + if (rc2 != 1) { > > + return CT_FTP_CTL_INVALID; > > + } > > + > > + *addr_size = ftp - ip_addr_start - 1; > > + } else { > > + *addr_size = 0; > > + *addr_offset_from_ftp_data_start = 0; > > } > > > > - *addr_size = ftp - ip_addr_start - 1; > > char *save_ftp = ftp; > > - ftp = terminate_number_str(ftp, MAX_FTP_PORT_DGTS); > > - if (!ftp) { > > - return CT_FTP_CTL_INVALID; > > - } > > - int value; > > - if (!str_to_int(save_ftp, 10, &value)) { > > - return CT_FTP_CTL_INVALID; > > - } > > + uint16_t port_hs; > > > > - /* This is derived from the L4 port maximum is 65535. */ > > - if (value > 255) { > > - return CT_FTP_CTL_INVALID; > > - } > > + if (!extended) { > > + ftp = terminate_number_str(ftp, MAX_FTP_PORT_DGTS); > > + if (!ftp) { > > + return CT_FTP_CTL_INVALID; > > + } > > + int value; > > + if (!str_to_int(save_ftp, 10, &value)) { > > + return CT_FTP_CTL_INVALID; > > + } > > > > - uint16_t port_hs = value; > > - port_hs <<= 8; > > + /* This is derived from the L4 port maximum is 65535. */ > > + if (value > 255) { > > + return CT_FTP_CTL_INVALID; > > + } > > > > - /* Skip over comma. */ > > - ftp++; > > - save_ftp = ftp; > > - bool digit_found = false; > > - while (isdigit(*ftp)) { > > + port_hs = value; > > + port_hs <<= 8; > > + > > + /* Skip over comma. */ > > ftp++; > > - digit_found = true; > > - } > > - if (!digit_found) { > > - return CT_FTP_CTL_INVALID; > > - } > > - *ftp = 0; > > - if (!str_to_int(save_ftp, 10, &value)) { > > - return CT_FTP_CTL_INVALID; > > - } > > + save_ftp = ftp; > > + bool digit_found = false; > > + while (isdigit(*ftp)) { > > + ftp++; > > + digit_found = true; > > + } > > + if (!digit_found) { > > + return CT_FTP_CTL_INVALID; > > + } > > + *ftp = 0; > > + if (!str_to_int(save_ftp, 10, &value)) { > > + return CT_FTP_CTL_INVALID; > > + } > > > > - if (value > 255) { > > - return CT_FTP_CTL_INVALID; > > + if (value > 255) { > > + return CT_FTP_CTL_INVALID; > > + } > > + > > + port_hs |= value; > > + } else { > > + ftp = terminate_number_str(ftp, MAX_EXT_FTP_PORT_DGTS); > > + if (!ftp) { > > + return CT_FTP_CTL_INVALID; > > + } > > + int value; > > + if (!str_to_int(save_ftp, 10, &value)) { > > + return CT_FTP_CTL_INVALID; > > + } > > + if (value > UINT16_MAX) { > > + return CT_FTP_CTL_INVALID; > > + } > > + port_hs = (uint16_t) value; > > } > > > > - port_hs |= value; > > ovs_be16 port = htons(port_hs); > > ovs_be32 conn_ipv4_addr; > > > > @@ -3478,12 +3549,14 @@ process_ftp_ctl_v4(struct conntrack *ct, > > OVS_NOT_REACHED(); > > } > > > > - ovs_be32 ftp_ipv4_addr; > > - ftp_ipv4_addr = ip_addr.s_addr; > > - /* Although most servers will block this exploit, there may be some > > - * less well managed. */ > > - if (ftp_ipv4_addr != conn_ipv4_addr && ftp_ipv4_addr != *v4_addr_rep) { > > - return CT_FTP_CTL_INVALID; > > + if (!extended || mode == CT_FTP_MODE_ACTIVE) { > > Does the 'extended' check matter here? If I remember right, PASV / > EPASV commands don't provide an address for the server to connect - only > PORT/EPORT (or active mode connections). Also, this previously was > being checked for all connection modes (active / passive), and now we > will isolate only EPASV connections. But maybe we should just not > change this part for now.
A PASV response looks like: Entering Passive Mode (h1,h2,h3,h4,p1,p2). An EPSV response looks like: Entering Extended Passive Mode (|||port|) So they really aren't compatible, epsv is the only command in the series that doesn't include the address. So the different handling is required, but I could invert the logic to make that more clear and leave a comment in the next version. The other requests sound fine, will send a new revision. -M > > WDYT? > > > + ovs_be32 ftp_ipv4_addr; > > + ftp_ipv4_addr = ip_addr.s_addr; > > + /* Although most servers will block this exploit, there may be some > > + * less well managed. */ > > + if (ftp_ipv4_addr != conn_ipv4_addr && ftp_ipv4_addr != > > *v4_addr_rep) { > > + return CT_FTP_CTL_INVALID; > > + } > > } > > > > expectation_create(ct, port, conn_for_expectation, > > diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at > > index 7f029dbb0..e4862231a 100644 > > --- a/tests/system-common-macros.at > > +++ b/tests/system-common-macros.at > > @@ -280,7 +280,7 @@ m4_define([OVS_GET_HTTP], > > # > > m4_define([OVS_GET_FTP], > > [curl ftp://$1 --retry 3 --max-time 1 --retry-connrefused \ > > - --disable-epsv -v $2] > > + -v $2] > > ) > > > > # OVS_GET_FTP_ACTIVE([url], [optional_curl_arguments]) > > @@ -289,7 +289,7 @@ m4_define([OVS_GET_FTP], > > # > > m4_define([OVS_GET_FTP_ACTIVE], > > [curl ftp://$1 --retry 3 --max-time 1 --retry-connrefused -v \ > > - --ftp-port - --disable-eprt $2] > > + --ftp-port - $2] > > ) > > > > # OVS_CHECK_FIREWALL() > > diff --git a/tests/system-traffic.at b/tests/system-traffic.at > > index 0f3acef3f..e5a1131e6 100644 > > --- a/tests/system-traffic.at > > +++ b/tests/system-traffic.at > > @@ -8204,8 +8204,9 @@ OVS_WAIT_UNTIL([ip netns exec at_ns0 ping -c 1 > > 10.1.1.2 >/dev/null]) > > > > OVS_START_L7([at_ns1], [ftp]) > > > > -dnl FTP requests from p0->p1 should work fine. > > -NS_CHECK_EXEC([at_ns0], OVS_GET_FTP([10.1.1.2]), [0], [ignore], [ignore]) > > +dnl FTP requests from p0->p1 should work fine, with and without EPSV. > > +NS_CHECK_EXEC([at_ns0], OVS_GET_FTP([10.1.1.2], [--epsv]), [0], [ignore], > > [ignore]) > > +NS_CHECK_EXEC([at_ns0], OVS_GET_FTP([10.1.1.2], [--disable-epsv]), [0], > > [ignore], [ignore]) > > > > dnl Discards CLOSE_WAIT and CLOSING > > AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl > > @@ -8384,8 +8385,9 @@ OVS_WAIT_UNTIL([ip netns exec at_ns0 ping -c 1 > > 10.1.1.240 >/dev/null]) > > > > OVS_START_L7([at_ns1], [ftp]) > > > > -dnl FTP requests from p0->p1 should work fine. > > -NS_CHECK_EXEC([at_ns0], OVS_GET_FTP_ACTIVE([10.1.1.2]), [0], [ignore], > > [ignore]) > > +dnl FTP requests from p0->p1 should work fine with and without EPRT. > > +NS_CHECK_EXEC([at_ns0], OVS_GET_FTP_ACTIVE([10.1.1.2], [--eprt]), [0], > > [ignore], [ignore]) > > +NS_CHECK_EXEC([at_ns0], OVS_GET_FTP_ACTIVE([10.1.1.2], [--disable-eprt]), > > [0], [ignore], [ignore]) > > > > dnl Discards CLOSE_WAIT and CLOSING > > AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl > _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev