The FTP and TFTP helpers were scattered all over the conntrack TU making reading the individual FTP parts a bit difficult. Now that the handling is more modular, split them out into their own files.
Signed-off-by: Aaron Conole <[email protected]> --- lib/automake.mk | 2 + lib/conntrack-ftp.c | 689 ++++++++++++++++++++++++++++++++++++++ lib/conntrack-private.h | 42 +++ lib/conntrack-tftp.c | 47 +++ lib/conntrack.c | 718 +--------------------------------------- 5 files changed, 786 insertions(+), 712 deletions(-) create mode 100644 lib/conntrack-ftp.c create mode 100644 lib/conntrack-tftp.c diff --git a/lib/automake.mk b/lib/automake.mk index c6e988906f..933b71226b 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -86,9 +86,11 @@ lib_libopenvswitch_la_SOURCES = \ lib/compiler.h \ lib/connectivity.c \ lib/connectivity.h \ + lib/conntrack-ftp.c \ lib/conntrack-icmp.c \ lib/conntrack-private.h \ lib/conntrack-tcp.c \ + lib/conntrack-tftp.c \ lib/conntrack-tp.c \ lib/conntrack-tp.h \ lib/conntrack-other.c \ diff --git a/lib/conntrack-ftp.c b/lib/conntrack-ftp.c new file mode 100644 index 0000000000..6ce17c9efe --- /dev/null +++ b/lib/conntrack-ftp.c @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2015-2019 Nicira, Inc. + * Copyright (c) 2026 Red Hat, Inc. + * + * Licensed 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. + */ + +#include <config.h> + +#include <ctype.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <string.h> + +#include "conntrack-private.h" +#include "csum.h" +#include "dp-packet.h" +#include "openvswitch/vlog.h" +#include "packets.h" +#include "unaligned.h" +#include "util.h" + +VLOG_DEFINE_THIS_MODULE(conntrack_ftp); + +/* FTP ALG mode: whether the data connection is initiated by the client + * (active) or the server (passive), and whether the session uses IPv6 + * extensions (EPRT/EPSV). */ +enum ct_alg_mode { + CT_FTP_MODE_ACTIVE, + CT_FTP_MODE_PASSIVE, + CT_TFTP_MODE, +}; + +/* String buffer used for parsing FTP string messages. + * This is sized about twice what is needed to leave some + * margin of error. */ +#define LARGEST_FTP_MSG_OF_INTEREST 128 +/* FTP port string used in active mode. */ +#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. */ +#define MAX_FTP_PORT_DGTS 3 + +/* FTP extension EPRT string used for active mode. */ +#define FTP_EPRT_CMD "EPRT" +/* FTP extension EPSV string used for passive mode. */ +#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' + +static bool +is_ftp_ctl(const enum ct_alg_ctl_type ct_alg_ctl) +{ + return ct_alg_ctl == CT_ALG_CTL_FTP; +} + +static void +replace_substring(char *substr, size_t substr_size, + size_t total_size, char *rep_str, + size_t rep_str_size) +{ + memmove(substr + rep_str_size, substr + substr_size, + total_size - substr_size); + memcpy(substr, rep_str, rep_str_size); +} + +static void +repl_bytes(char *str, char c1, char c2, int max) +{ + while (*str) { + if (*str == c1) { + *str = c2; + + if (--max == 0) { + break; + } + } + str++; + } +} + +/* Replaces a substring in the packet and rewrites the packet + * size to match. This function assumes the caller has verified + * the lengths to prevent under/over flow. */ +static void +modify_packet(struct dp_packet *pkt, char *pkt_str, size_t size, + char *repl_str, size_t repl_size, + uint32_t orig_used_size) +{ + replace_substring(pkt_str, size, + (const char *) dp_packet_tail(pkt) - pkt_str, + repl_str, repl_size); + dp_packet_set_size(pkt, orig_used_size + (int) repl_size - (int) size); +} + +/* Replace IPV4 address in FTP message with NATed address. */ +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) +{ + 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 > + dp_packet_get_allocated(pkt)) { + + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); + VLOG_WARN_RL(&rl, "Unsupported effective MTU %u used with FTP V4", + dp_packet_get_allocated(pkt)); + return 0; + } + + char v4_addr_str[INET_ADDRSTRLEN] = {0}; + ovs_assert(inet_ntop(AF_INET, &v4_addr_rep, v4_addr_str, + sizeof v4_addr_str)); + repl_bytes(v4_addr_str, '.', ',', 0); + modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start, + addr_size, v4_addr_str, strlen(v4_addr_str), + orig_used_size); + return (int) strlen(v4_addr_str) - (int) addr_size; +} + +static char * +skip_non_digits(char *str) +{ + while (!isdigit(*str) && *str != 0) { + str++; + } + return str; +} + +static char * +terminate_number_str(char *str, uint8_t max_digits) +{ + uint8_t digits_found = 0; + while (isdigit(*str) && digits_found <= max_digits) { + str++; + digits_found++; + } + + *str = 0; + return str; +} + +static void +get_ftp_ctl_msg(struct dp_packet *pkt, char *ftp_msg) +{ + struct tcp_header *th = dp_packet_l4(pkt); + char *tcp_hdr = (char *) th; + uint32_t tcp_payload_len = dp_packet_get_tcp_payload_length(pkt); + size_t tcp_payload_of_interest = MIN(tcp_payload_len, + LARGEST_FTP_MSG_OF_INTEREST); + size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4; + + ovs_strlcpy(ftp_msg, tcp_hdr + tcp_hdr_len, + tcp_payload_of_interest); +} + +static enum ftp_ctl_pkt +detect_ftp_ctl_type(const struct conn_lookup_ctx *ctx, + struct dp_packet *pkt) +{ + char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0}; + get_ftp_ctl_msg(pkt, ftp_msg); + + if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { + if (strncasecmp(ftp_msg, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD)) && + !strcasestr(ftp_msg, FTP_EPSV_REPLY)) { + return CT_FTP_CTL_OTHER; + } + } 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)) && + strncasecmp(ftp_msg, FTP_EPSV_REPLY_CODE, + strlen(FTP_EPSV_REPLY_CODE))) { + return CT_FTP_CTL_OTHER; + } + } + + return CT_FTP_CTL_INTEREST; +} + +static enum ftp_ctl_pkt +process_ftp_ctl_v4(struct conntrack *ct, + struct dp_packet *pkt, + const struct conn *conn_for_expectation, + ovs_be32 *v4_addr_rep, + char **ftp_data_v4_start, + size_t *addr_offset_from_ftp_data_start, + size_t *addr_size) +{ + struct tcp_header *th = dp_packet_l4(pkt); + size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4; + char *tcp_hdr = (char *) th; + *ftp_data_v4_start = tcp_hdr + tcp_hdr_len; + 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; + + 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; + } + + /* Find first space. */ + ftp = strchr(ftp, ' '); + if (!ftp) { + return CT_FTP_CTL_INVALID; + } + + /* Find the first digit, after space. */ + ftp = skip_non_digits(ftp); + if (*ftp == 0) { + return CT_FTP_CTL_INVALID; + } + + /* EPRT, verify address family. */ + if (extended && mode == CT_FTP_MODE_ACTIVE) { + if (ftp[0] != FTP_AF_V4 || isdigit(ftp[1])) { + return CT_FTP_CTL_INVALID; + } + + 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; + repl_bytes(ftp, ',', '.', 3); + + /* Advance to end of IP address, to terminate it. */ + while (*ftp) { + if (!isdigit(*ftp) && *ftp != '.') { + break; + } + ftp++; + } + *ftp = 0; + ftp++; + + 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; + } + + char *save_ftp = ftp; + uint16_t port_hs; + + 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; + } + + /* This is derived from the L4 port maximum is 65535. */ + if (value > 255) { + return CT_FTP_CTL_INVALID; + } + + port_hs = value; + port_hs <<= 8; + + /* Skip over comma. */ + ftp++; + 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; + } + + 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; + } + + ovs_be16 port = htons(port_hs); + ovs_be32 conn_ipv4_addr; + + switch (mode) { + case CT_FTP_MODE_ACTIVE: + *v4_addr_rep = + conn_for_expectation->key_node[CT_DIR_REV].key.dst.addr.ipv4; + conn_ipv4_addr = + conn_for_expectation->key_node[CT_DIR_FWD].key.src.addr.ipv4; + break; + case CT_FTP_MODE_PASSIVE: + *v4_addr_rep = + conn_for_expectation->key_node[CT_DIR_FWD].key.dst.addr.ipv4; + conn_ipv4_addr = + conn_for_expectation->key_node[CT_DIR_REV].key.src.addr.ipv4; + break; + case CT_TFTP_MODE: + default: + OVS_NOT_REACHED(); + } + + if (!extended || mode == CT_FTP_MODE_ACTIVE) { + 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, + !!(pkt->md.ct_state & CS_REPLY_DIR), false, false); + return CT_FTP_CTL_INTEREST; +} + +static char * +skip_ipv6_digits(char *str) +{ + while (isxdigit(*str) || *str == ':' || *str == '.') { + str++; + } + return str; +} + +static enum ftp_ctl_pkt +process_ftp_ctl_v6(struct conntrack *ct, + struct dp_packet *pkt, + const struct conn *conn_for_exp, + union ct_addr *v6_addr_rep, char **ftp_data_start, + size_t *addr_offset_from_ftp_data_start, + size_t *addr_size, enum ct_alg_mode *mode) +{ + struct tcp_header *th = dp_packet_l4(pkt); + size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4; + char *tcp_hdr = (char *) th; + char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0}; + get_ftp_ctl_msg(pkt, ftp_msg); + *ftp_data_start = tcp_hdr + tcp_hdr_len; + char *ftp = ftp_msg; + struct in6_addr ip6_addr; + + if (!strncasecmp(ftp, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD))) { + ftp = ftp_msg + strlen(FTP_EPRT_CMD); + ftp = skip_non_digits(ftp); + if (*ftp != FTP_AF_V6 || isdigit(ftp[1])) { + return CT_FTP_CTL_INVALID; + } + /* Jump over delimiter. */ + ftp += 2; + + memset(&ip6_addr, 0, sizeof ip6_addr); + char *ip_addr_start = ftp; + *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg; + ftp = skip_ipv6_digits(ftp); + *ftp = 0; + *addr_size = ftp - ip_addr_start; + int rc2 = inet_pton(AF_INET6, ip_addr_start, &ip6_addr); + if (rc2 != 1) { + return CT_FTP_CTL_INVALID; + } + ftp++; + *mode = CT_FTP_MODE_ACTIVE; + } else { + ftp = ftp_msg + strcspn(ftp_msg, "("); + ftp = skip_non_digits(ftp); + if (!isdigit(*ftp)) { + return CT_FTP_CTL_INVALID; + } + + /* Not used for passive mode. */ + *addr_offset_from_ftp_data_start = 0; + *addr_size = 0; + + *mode = CT_FTP_MODE_PASSIVE; + } + + char *save_ftp = ftp; + 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 > CT_MAX_L4_PORT) { + return CT_FTP_CTL_INVALID; + } + + uint16_t port_hs = value; + ovs_be16 port = htons(port_hs); + + switch (*mode) { + case CT_FTP_MODE_ACTIVE: + *v6_addr_rep = conn_for_exp->key_node[CT_DIR_REV].key.dst.addr; + /* Although most servers will block this exploit, there may be some + * less well managed. */ + if (memcmp(&ip6_addr, &v6_addr_rep->ipv6, sizeof ip6_addr) && + memcmp(&ip6_addr, + &conn_for_exp->key_node[CT_DIR_FWD].key.src.addr.ipv6, + sizeof ip6_addr)) { + return CT_FTP_CTL_INVALID; + } + break; + case CT_FTP_MODE_PASSIVE: + *v6_addr_rep = conn_for_exp->key_node[CT_DIR_FWD].key.dst.addr; + break; + case CT_TFTP_MODE: + default: + OVS_NOT_REACHED(); + } + + expectation_create(ct, port, conn_for_exp, + !!(pkt->md.ct_state & CS_REPLY_DIR), false, false); + return CT_FTP_CTL_INTEREST; +} + +static int +repl_ftp_v6_addr(struct dp_packet *pkt, union ct_addr v6_addr_rep, + char *ftp_data_start, + size_t addr_offset_from_ftp_data_start, + size_t addr_size, enum ct_alg_mode mode) +{ + /* This is slightly bigger than really possible. */ + enum { MAX_FTP_V6_NAT_DELTA = 45 }; + + if (mode == CT_FTP_MODE_PASSIVE) { + return 0; + } + + /* Do conservative check for pathological MTU usage. */ + uint32_t orig_used_size = dp_packet_size(pkt); + if (orig_used_size + MAX_FTP_V6_NAT_DELTA > + dp_packet_get_allocated(pkt)) { + + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); + VLOG_WARN_RL(&rl, "Unsupported effective MTU %u used with FTP V6", + dp_packet_get_allocated(pkt)); + return 0; + } + + char v6_addr_str[INET6_ADDRSTRLEN] = {0}; + ovs_assert(inet_ntop(AF_INET6, &v6_addr_rep.ipv6, v6_addr_str, + sizeof v6_addr_str)); + modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start, + addr_size, v6_addr_str, strlen(v6_addr_str), + orig_used_size); + return (int) strlen(v6_addr_str) - (int) addr_size; +} + +/* Increment/decrement a TCP sequence number. */ +static void +adj_seqnum(ovs_16aligned_be32 *val, int32_t inc) +{ + put_16aligned_be32(val, htonl(ntohl(get_16aligned_be32(val)) + inc)); +} + +static void +handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx, + struct dp_packet *pkt, struct conn *ec, long long now, + enum ftp_ctl_pkt ftp_ctl, bool nat) +{ + struct ip_header *l3_hdr = dp_packet_l3(pkt); + ovs_be32 v4_addr_rep = 0; + union ct_addr v6_addr_rep; + size_t addr_offset_from_ftp_data_start = 0; + size_t addr_size = 0; + char *ftp_data_start; + enum ct_alg_mode mode = CT_FTP_MODE_ACTIVE; + + if (detect_ftp_ctl_type(ctx, pkt) != ftp_ctl) { + return; + } + + struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt); + int64_t seq_skew = 0; + + if (ftp_ctl == CT_FTP_CTL_INTEREST) { + enum ftp_ctl_pkt rc; + if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { + rc = process_ftp_ctl_v6(ct, pkt, ec, + &v6_addr_rep, &ftp_data_start, + &addr_offset_from_ftp_data_start, + &addr_size, &mode); + } else { + rc = process_ftp_ctl_v4(ct, pkt, ec, + &v4_addr_rep, &ftp_data_start, + &addr_offset_from_ftp_data_start, + &addr_size); + } + if (rc == CT_FTP_CTL_INVALID) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); + VLOG_WARN_RL(&rl, "Invalid FTP control packet format"); + pkt->md.ct_state |= CS_TRACKED | CS_INVALID; + return; + } else if (rc == CT_FTP_CTL_INTEREST) { + uint16_t ip_len; + + if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { + if (nat) { + seq_skew = repl_ftp_v6_addr(pkt, v6_addr_rep, + ftp_data_start, + addr_offset_from_ftp_data_start, + addr_size, mode); + } + + if (seq_skew) { + ip_len = ntohs(nh6->ip6_ctlun.ip6_un1.ip6_un1_plen) + + seq_skew; + nh6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(ip_len); + } + } else { + if (nat) { + seq_skew = repl_ftp_v4_addr(pkt, v4_addr_rep, + ftp_data_start, + addr_offset_from_ftp_data_start, + addr_size); + } + if (seq_skew) { + ip_len = ntohs(l3_hdr->ip_tot_len) + seq_skew; + if (dp_packet_ip_checksum_valid(pkt)) { + dp_packet_ip_checksum_set_partial(pkt); + } else { + l3_hdr->ip_csum = recalc_csum16(l3_hdr->ip_csum, + l3_hdr->ip_tot_len, + htons(ip_len)); + } + l3_hdr->ip_tot_len = htons(ip_len); + } + } + } else { + OVS_NOT_REACHED(); + } + } + + struct tcp_header *th = dp_packet_l4(pkt); + + if (nat && ec->seq_skew != 0) { + ctx->reply != ec->seq_skew_dir ? + adj_seqnum(&th->tcp_ack, -ec->seq_skew) : + adj_seqnum(&th->tcp_seq, ec->seq_skew); + } + + if (dp_packet_l4_checksum_valid(pkt)) { + dp_packet_l4_checksum_set_partial(pkt); + } else { + th->tcp_csum = 0; + if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { + th->tcp_csum = packet_csum_upperlayer6(nh6, th, ctx->key.nw_proto, + dp_packet_l4_size(pkt)); + } else { + uint32_t tcp_csum = packet_csum_pseudoheader(l3_hdr); + th->tcp_csum = csum_finish( + csum_continue(tcp_csum, th, dp_packet_l4_size(pkt))); + } + } + + if (seq_skew) { + conn_seq_skew_set(ct, ec, now, seq_skew + ec->seq_skew, + ctx->reply); + } +} + +/* FTP requires sequence-number tracking to stay in sync with the source of + * any sequence skew introduced by address/port rewriting. This hook + * interleaves handle_ftp_ctl() calls with conn_update_state() depending on + * packet direction so that the skew accounting is always correct. */ +static bool +ftp_conn_update_state_hook(struct conntrack *ct, struct dp_packet *pkt, + struct conn_lookup_ctx *ctx, struct conn *conn, + const struct nat_action_info_t *nat_action_info, + enum ct_alg_ctl_type ct_alg_ctl, long long now, + bool *create_new_conn) +{ + if (!is_ftp_ctl(ct_alg_ctl)) { + return false; + } + + /* Keep sequence tracking in sync with the source of the sequence skew. */ + ovs_mutex_lock(&conn->lock); + if (ctx->reply != conn->seq_skew_dir) { + handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER, + !!nat_action_info); + /* conn_update_state acquires conn->lock for unrelated fields. */ + ovs_mutex_unlock(&conn->lock); + *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now); + } else { + ovs_mutex_unlock(&conn->lock); + *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now); + ovs_mutex_lock(&conn->lock); + if (!*create_new_conn) { + handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER, + !!nat_action_info); + } + ovs_mutex_unlock(&conn->lock); + } + return true; +} + +void +conntrack_ftp_init(void) +{ + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; + + if (ovsthread_once_start(&once)) { + conn_update_state_hook_register(CT_HOOK_PRI_NORMAL, + ftp_conn_update_state_hook); + alg_helpers[CT_ALG_CTL_FTP] = handle_ftp_ctl; + ovsthread_once_done(&once); + } +} diff --git a/lib/conntrack-private.h b/lib/conntrack-private.h index a5bf1bb519..8eab1d3703 100644 --- a/lib/conntrack-private.h +++ b/lib/conntrack-private.h @@ -177,6 +177,9 @@ enum ct_ephemeral_range { MAX_NAT_EPHEMERAL_PORT = 65535 }; +/* The maximum TCP or UDP port number. */ +#define CT_MAX_L4_PORT 65535 + #define IN_RANGE(curr, min, max) \ (curr >= min && curr <= max) @@ -261,6 +264,9 @@ enum ct_alg_ctl_type { /* SIP is not enabled through OpenFlow and is present only as an example * of an ALG that allows a wildcard source IP address. */ CT_ALG_CTL_SIP, + + /* MAX ALG */ + CT_ALG_CTL_MAX, }; extern struct ct_l4_proto ct_proto_tcp; @@ -289,6 +295,28 @@ struct conn_lookup_ctx { bool icmp_related; }; +/* FTP control-packet classification used by ALG helpers. + * CT_FTP_CTL_INTEREST carries an address/port specifier (PORT, PASV, EPRT, + * EPSV); CT_FTP_CTL_OTHER does not; CT_FTP_CTL_INVALID is malformed. */ +enum ftp_ctl_pkt { + CT_FTP_CTL_INTEREST, + CT_FTP_CTL_OTHER, + CT_FTP_CTL_INVALID, +}; + +/* ALG helper callback signature. Each registered helper receives the + * classified control-packet type so it can decide whether to act. */ +typedef void (*alg_helper)(struct conntrack *ct, + const struct conn_lookup_ctx *ctx, + struct dp_packet *pkt, + struct conn *conn_for_expectation, + long long now, enum ftp_ctl_pkt ftp_ctl, + bool nat); + +/* Array indexed by ct_alg_ctl_type; populated by per-module init functions + * (conntrack_ftp_init, conntrack_tftp_init, ...) before first use. */ +extern alg_helper alg_helpers[]; + /* conn_update_state_dist() hook * * Modules may register a hook to intercept connection state transitions. @@ -323,6 +351,20 @@ void conn_update_state_hook_register(int priority, conn_update_state_hook_fn); void conn_update_state_hook_unregister(conn_update_state_hook_fn); +/* Functions in conntrack.c that ALG modules need. */ +bool conn_update_state(struct conntrack *ct, struct dp_packet *pkt, + struct conn_lookup_ctx *ctx, struct conn *conn, + long long now); +void conn_seq_skew_set(struct conntrack *ct, const struct conn *conn_in, + long long now, int seq_skew, bool seq_skew_dir); +void expectation_create(struct conntrack *ct, ovs_be16 dst_port, + const struct conn *parent_conn, bool reply, + bool src_ip_wc, bool skip_nat); + +/* ALG module initialization functions. */ +void conntrack_ftp_init(void); +void conntrack_tftp_init(void); + /* conn_private_get() / conn_private_set() * * Fast-path accessors for per-connection private storage slots. Both diff --git a/lib/conntrack-tftp.c b/lib/conntrack-tftp.c new file mode 100644 index 0000000000..61297f7240 --- /dev/null +++ b/lib/conntrack-tftp.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-2019 Nicira, Inc. + * Copyright (c) 2026 Red Hat, Inc. + * + * Licensed 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. + */ + +#include <config.h> + +#include "conntrack-private.h" +#include "dp-packet.h" +#include "ovs-thread.h" +#include "packets.h" + +static void +handle_tftp_ctl(struct conntrack *ct, + const struct conn_lookup_ctx *ctx OVS_UNUSED, + struct dp_packet *pkt, struct conn *conn_for_expectation, + long long now OVS_UNUSED, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED, + bool nat OVS_UNUSED) +{ + expectation_create(ct, + conn_for_expectation->key_node[CT_DIR_FWD].key.src.port, + conn_for_expectation, + !!(pkt->md.ct_state & CS_REPLY_DIR), false, false); +} + +void +conntrack_tftp_init(void) +{ + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; + + if (ovsthread_once_start(&once)) { + alg_helpers[CT_ALG_CTL_TFTP] = handle_tftp_ctl; + ovsthread_once_done(&once); + } +} diff --git a/lib/conntrack.c b/lib/conntrack.c index d81abe456a..462c0e0ad1 100644 --- a/lib/conntrack.c +++ b/lib/conntrack.c @@ -55,20 +55,6 @@ COVERAGE_DEFINE(conntrack_l4csum_err); COVERAGE_DEFINE(conntrack_lookup_natted_miss); COVERAGE_DEFINE(conntrack_zone_full); -enum ftp_ctl_pkt { - /* Control packets with address and/or port specifiers. */ - CT_FTP_CTL_INTEREST, - /* Control packets without address and/or port specifiers. */ - CT_FTP_CTL_OTHER, - CT_FTP_CTL_INVALID, -}; - -enum ct_alg_mode { - CT_FTP_MODE_ACTIVE, - CT_FTP_MODE_PASSIVE, - CT_TFTP_MODE, -}; - struct zone_limit { struct cmap_node node; struct conntrack_zone_limit czl; @@ -117,24 +103,6 @@ static struct alg_exp_node * expectation_lookup(struct hmap *alg_expectations, const struct conn_key *key, uint32_t basis, bool src_ip_wc); -static int -repl_ftp_v4_addr(struct dp_packet *pkt, ovs_be32 v4_addr_rep, - char *ftp_data_v4_start, - size_t addr_offset_from_ftp_data_start, size_t addr_size); - -static enum ftp_ctl_pkt -process_ftp_ctl_v4(struct conntrack *ct, - struct dp_packet *pkt, - const struct conn *conn_for_expectation, - ovs_be32 *v4_addr_rep, - char **ftp_data_v4_start, - size_t *addr_offset_from_ftp_data_start, - size_t *addr_size); - -static enum ftp_ctl_pkt -detect_ftp_ctl_type(const struct conn_lookup_ctx *ctx, - struct dp_packet *pkt); - static void expectation_clean(struct conntrack *ct, const struct conn_key *parent_key); @@ -170,64 +138,8 @@ struct ct_update_hook { static struct ct_update_hook ct_update_hooks[CT_UPDATE_STATE_HOOKS_MAX]; static size_t n_ct_update_hooks; -static bool ftp_conn_update_state_hook(struct conntrack *, struct dp_packet *, - struct conn_lookup_ctx *, struct conn *, - const struct nat_action_info_t *, - enum ct_alg_ctl_type, long long, - bool *); - -static void -handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx, - struct dp_packet *pkt, struct conn *ec, long long now, - enum ftp_ctl_pkt ftp_ctl, bool nat); - -static void -handle_tftp_ctl(struct conntrack *ct, - const struct conn_lookup_ctx *ctx OVS_UNUSED, - struct dp_packet *pkt, struct conn *conn_for_expectation, - long long now OVS_UNUSED, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED, - bool nat OVS_UNUSED); - -typedef void (*alg_helper)(struct conntrack *ct, - const struct conn_lookup_ctx *ctx, - struct dp_packet *pkt, - struct conn *conn_for_expectation, - long long now, enum ftp_ctl_pkt ftp_ctl, - bool nat); - -static alg_helper alg_helpers[] = { - [CT_ALG_CTL_NONE] = NULL, - [CT_ALG_CTL_FTP] = handle_ftp_ctl, - [CT_ALG_CTL_TFTP] = handle_tftp_ctl, -}; +alg_helper alg_helpers[CT_ALG_CTL_MAX]; -/* The maximum TCP or UDP port number. */ -#define CT_MAX_L4_PORT 65535 -/* String buffer used for parsing FTP string messages. - * This is sized about twice what is needed to leave some - * margin of error. */ -#define LARGEST_FTP_MSG_OF_INTEREST 128 -/* FTP port string used in active mode. */ -#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. */ -#define MAX_FTP_PORT_DGTS 3 - -/* FTP extension EPRT string used for active mode. */ -#define FTP_EPRT_CMD "EPRT" -/* FTP extension EPSV string used for passive mode. */ -#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. * This is used for port numbers that we cannot predict in * expectations. */ @@ -311,8 +223,8 @@ conntrack_init(void) l4_protos[IPPROTO_ICMP] = &ct_proto_icmp4; l4_protos[IPPROTO_ICMPV6] = &ct_proto_icmp6; - conn_update_state_hook_register(CT_HOOK_PRI_NORMAL, - ftp_conn_update_state_hook); + conntrack_ftp_init(); + conntrack_tftp_init(); ovsthread_once_done(&setup_l4_once); } @@ -835,12 +747,6 @@ get_ip_proto(const struct dp_packet *pkt) return ip_proto; } -static bool -is_ftp_ctl(const enum ct_alg_ctl_type ct_alg_ctl) -{ - return ct_alg_ctl == CT_ALG_CTL_FTP; -} - static enum ct_alg_ctl_type get_alg_ctl_type(const struct dp_packet *pkt, const char *helper) { @@ -1044,7 +950,7 @@ nat_packet(struct dp_packet *pkt, struct conn *conn, bool reply, bool related) } } -static void +void conn_seq_skew_set(struct conntrack *ct, const struct conn *conn_in, long long now, int seq_skew, bool seq_skew_dir) { @@ -1202,7 +1108,7 @@ nat_res_exhaustion: return NULL; } -static bool +bool conn_update_state(struct conntrack *ct, struct dp_packet *pkt, struct conn_lookup_ctx *ctx, struct conn *conn, long long now) @@ -1322,38 +1228,6 @@ check_orig_tuple(struct conntrack *ct, struct dp_packet *pkt, return *conn ? true : false; } -static bool -ftp_conn_update_state_hook(struct conntrack *ct, struct dp_packet *pkt, - struct conn_lookup_ctx *ctx, struct conn *conn, - const struct nat_action_info_t *nat_action_info, - enum ct_alg_ctl_type ct_alg_ctl, long long now, - bool *create_new_conn) -{ - if (!is_ftp_ctl(ct_alg_ctl)) { - return false; - } - - /* Keep sequence tracking in sync with the source of the sequence skew. */ - ovs_mutex_lock(&conn->lock); - if (ctx->reply != conn->seq_skew_dir) { - handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER, - !!nat_action_info); - /* conn_update_state acquires conn->lock for unrelated fields. */ - ovs_mutex_unlock(&conn->lock); - *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now); - } else { - ovs_mutex_unlock(&conn->lock); - *create_new_conn = conn_update_state(ct, pkt, ctx, conn, now); - ovs_mutex_lock(&conn->lock); - if (!*create_new_conn) { - handle_ftp_ctl(ct, ctx, pkt, conn, now, CT_FTP_CTL_OTHER, - !!nat_action_info); - } - ovs_mutex_unlock(&conn->lock); - } - return true; -} - /* Distribute a connection state-transition event to registered hooks. * Returns true if a hook handled the update (and set *create_new_conn), * false if the caller should fall through to default conn_update_state(). */ @@ -3238,7 +3112,7 @@ expectation_clean(struct conntrack *ct, const struct conn_key *parent_key) ovs_rwlock_unlock(&ct->resources_lock); } -static void +void expectation_create(struct conntrack *ct, ovs_be16 dst_port, const struct conn *parent_conn, bool reply, bool src_ip_wc, bool skip_nat) @@ -3312,467 +3186,6 @@ expectation_create(struct conntrack *ct, ovs_be16 dst_port, ovs_rwlock_unlock(&ct->resources_lock); } -static void -replace_substring(char *substr, size_t substr_size, - size_t total_size, char *rep_str, - size_t rep_str_size) -{ - memmove(substr + rep_str_size, substr + substr_size, - total_size - substr_size); - memcpy(substr, rep_str, rep_str_size); -} - -static void -repl_bytes(char *str, char c1, char c2, int max) -{ - while (*str) { - if (*str == c1) { - *str = c2; - - if (--max == 0) { - break; - } - } - str++; - } -} - -/* Replaces a substring in the packet and rewrites the packet - * size to match. This function assumes the caller has verified - * the lengths to prevent under/over flow. */ -static void -modify_packet(struct dp_packet *pkt, char *pkt_str, size_t size, - char *repl_str, size_t repl_size, - uint32_t orig_used_size) -{ - replace_substring(pkt_str, size, - (const char *) dp_packet_tail(pkt) - pkt_str, - repl_str, repl_size); - dp_packet_set_size(pkt, orig_used_size + (int) repl_size - (int) size); -} - -/* Replace IPV4 address in FTP message with NATed address. */ -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) -{ - 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 > - dp_packet_get_allocated(pkt)) { - - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); - VLOG_WARN_RL(&rl, "Unsupported effective MTU %u used with FTP V4", - dp_packet_get_allocated(pkt)); - return 0; - } - - char v4_addr_str[INET_ADDRSTRLEN] = {0}; - ovs_assert(inet_ntop(AF_INET, &v4_addr_rep, v4_addr_str, - sizeof v4_addr_str)); - repl_bytes(v4_addr_str, '.', ',', 0); - modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start, - addr_size, v4_addr_str, strlen(v4_addr_str), - orig_used_size); - return (int) strlen(v4_addr_str) - (int) addr_size; -} - -static char * -skip_non_digits(char *str) -{ - while (!isdigit(*str) && *str != 0) { - str++; - } - return str; -} - -static char * -terminate_number_str(char *str, uint8_t max_digits) -{ - uint8_t digits_found = 0; - while (isdigit(*str) && digits_found <= max_digits) { - str++; - digits_found++; - } - - *str = 0; - return str; -} - - -static void -get_ftp_ctl_msg(struct dp_packet *pkt, char *ftp_msg) -{ - struct tcp_header *th = dp_packet_l4(pkt); - char *tcp_hdr = (char *) th; - uint32_t tcp_payload_len = dp_packet_get_tcp_payload_length(pkt); - size_t tcp_payload_of_interest = MIN(tcp_payload_len, - LARGEST_FTP_MSG_OF_INTEREST); - size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4; - - ovs_strlcpy(ftp_msg, tcp_hdr + tcp_hdr_len, - tcp_payload_of_interest); -} - -static enum ftp_ctl_pkt -detect_ftp_ctl_type(const struct conn_lookup_ctx *ctx, - struct dp_packet *pkt) -{ - char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0}; - get_ftp_ctl_msg(pkt, ftp_msg); - - if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { - if (strncasecmp(ftp_msg, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD)) && - !strcasestr(ftp_msg, FTP_EPSV_REPLY)) { - return CT_FTP_CTL_OTHER; - } - } 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)) && - strncasecmp(ftp_msg, FTP_EPSV_REPLY_CODE, - strlen(FTP_EPSV_REPLY_CODE))) { - return CT_FTP_CTL_OTHER; - } - } - - return CT_FTP_CTL_INTEREST; -} - -static enum ftp_ctl_pkt -process_ftp_ctl_v4(struct conntrack *ct, - struct dp_packet *pkt, - const struct conn *conn_for_expectation, - ovs_be32 *v4_addr_rep, - char **ftp_data_v4_start, - size_t *addr_offset_from_ftp_data_start, - size_t *addr_size) -{ - struct tcp_header *th = dp_packet_l4(pkt); - size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4; - char *tcp_hdr = (char *) th; - *ftp_data_v4_start = tcp_hdr + tcp_hdr_len; - 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; - - 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; - } - - /* Find first space. */ - ftp = strchr(ftp, ' '); - if (!ftp) { - return CT_FTP_CTL_INVALID; - } - - /* Find the first digit, after space. */ - ftp = skip_non_digits(ftp); - if (*ftp == 0) { - return CT_FTP_CTL_INVALID; - } - - /* EPRT, verify address family. */ - if (extended && mode == CT_FTP_MODE_ACTIVE) { - if (ftp[0] != FTP_AF_V4 || isdigit(ftp[1])) { - return CT_FTP_CTL_INVALID; - } - - 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; - repl_bytes(ftp, ',', '.', 3); - - /* Advance to end of IP address, to terminate it. */ - while (*ftp) { - if (!isdigit(*ftp) && *ftp != '.') { - break; - } - ftp++; - } - *ftp = 0; - ftp++; - - 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; - } - - char *save_ftp = ftp; - uint16_t port_hs; - - 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; - } - - /* This is derived from the L4 port maximum is 65535. */ - if (value > 255) { - return CT_FTP_CTL_INVALID; - } - - port_hs = value; - port_hs <<= 8; - - /* Skip over comma. */ - ftp++; - 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; - } - - 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; - } - - ovs_be16 port = htons(port_hs); - ovs_be32 conn_ipv4_addr; - - switch (mode) { - case CT_FTP_MODE_ACTIVE: - *v4_addr_rep = - conn_for_expectation->key_node[CT_DIR_REV].key.dst.addr.ipv4; - conn_ipv4_addr = - conn_for_expectation->key_node[CT_DIR_FWD].key.src.addr.ipv4; - break; - case CT_FTP_MODE_PASSIVE: - *v4_addr_rep = - conn_for_expectation->key_node[CT_DIR_FWD].key.dst.addr.ipv4; - conn_ipv4_addr = - conn_for_expectation->key_node[CT_DIR_REV].key.src.addr.ipv4; - break; - case CT_TFTP_MODE: - default: - OVS_NOT_REACHED(); - } - - if (!extended || mode == CT_FTP_MODE_ACTIVE) { - 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, - !!(pkt->md.ct_state & CS_REPLY_DIR), false, false); - return CT_FTP_CTL_INTEREST; -} - -static char * -skip_ipv6_digits(char *str) -{ - while (isxdigit(*str) || *str == ':' || *str == '.') { - str++; - } - return str; -} - -static enum ftp_ctl_pkt -process_ftp_ctl_v6(struct conntrack *ct, - struct dp_packet *pkt, - const struct conn *conn_for_exp, - union ct_addr *v6_addr_rep, char **ftp_data_start, - size_t *addr_offset_from_ftp_data_start, - size_t *addr_size, enum ct_alg_mode *mode) -{ - struct tcp_header *th = dp_packet_l4(pkt); - size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4; - char *tcp_hdr = (char *) th; - char ftp_msg[LARGEST_FTP_MSG_OF_INTEREST + 1] = {0}; - get_ftp_ctl_msg(pkt, ftp_msg); - *ftp_data_start = tcp_hdr + tcp_hdr_len; - char *ftp = ftp_msg; - struct in6_addr ip6_addr; - - if (!strncasecmp(ftp, FTP_EPRT_CMD, strlen(FTP_EPRT_CMD))) { - ftp = ftp_msg + strlen(FTP_EPRT_CMD); - ftp = skip_non_digits(ftp); - if (*ftp != FTP_AF_V6 || isdigit(ftp[1])) { - return CT_FTP_CTL_INVALID; - } - /* Jump over delimiter. */ - ftp += 2; - - memset(&ip6_addr, 0, sizeof ip6_addr); - char *ip_addr_start = ftp; - *addr_offset_from_ftp_data_start = ip_addr_start - ftp_msg; - ftp = skip_ipv6_digits(ftp); - *ftp = 0; - *addr_size = ftp - ip_addr_start; - int rc2 = inet_pton(AF_INET6, ip_addr_start, &ip6_addr); - if (rc2 != 1) { - return CT_FTP_CTL_INVALID; - } - ftp++; - *mode = CT_FTP_MODE_ACTIVE; - } else { - ftp = ftp_msg + strcspn(ftp_msg, "("); - ftp = skip_non_digits(ftp); - if (!isdigit(*ftp)) { - return CT_FTP_CTL_INVALID; - } - - /* Not used for passive mode. */ - *addr_offset_from_ftp_data_start = 0; - *addr_size = 0; - - *mode = CT_FTP_MODE_PASSIVE; - } - - char *save_ftp = ftp; - 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 > CT_MAX_L4_PORT) { - return CT_FTP_CTL_INVALID; - } - - uint16_t port_hs = value; - ovs_be16 port = htons(port_hs); - - switch (*mode) { - case CT_FTP_MODE_ACTIVE: - *v6_addr_rep = conn_for_exp->key_node[CT_DIR_REV].key.dst.addr; - /* Although most servers will block this exploit, there may be some - * less well managed. */ - if (memcmp(&ip6_addr, &v6_addr_rep->ipv6, sizeof ip6_addr) && - memcmp(&ip6_addr, - &conn_for_exp->key_node[CT_DIR_FWD].key.src.addr.ipv6, - sizeof ip6_addr)) { - return CT_FTP_CTL_INVALID; - } - break; - case CT_FTP_MODE_PASSIVE: - *v6_addr_rep = conn_for_exp->key_node[CT_DIR_FWD].key.dst.addr; - break; - case CT_TFTP_MODE: - default: - OVS_NOT_REACHED(); - } - - expectation_create(ct, port, conn_for_exp, - !!(pkt->md.ct_state & CS_REPLY_DIR), false, false); - return CT_FTP_CTL_INTEREST; -} - -static int -repl_ftp_v6_addr(struct dp_packet *pkt, union ct_addr v6_addr_rep, - char *ftp_data_start, - size_t addr_offset_from_ftp_data_start, - size_t addr_size, enum ct_alg_mode mode) -{ - /* This is slightly bigger than really possible. */ - enum { MAX_FTP_V6_NAT_DELTA = 45 }; - - if (mode == CT_FTP_MODE_PASSIVE) { - return 0; - } - - /* Do conservative check for pathological MTU usage. */ - uint32_t orig_used_size = dp_packet_size(pkt); - if (orig_used_size + MAX_FTP_V6_NAT_DELTA > - dp_packet_get_allocated(pkt)) { - - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); - VLOG_WARN_RL(&rl, "Unsupported effective MTU %u used with FTP V6", - dp_packet_get_allocated(pkt)); - return 0; - } - - char v6_addr_str[INET6_ADDRSTRLEN] = {0}; - ovs_assert(inet_ntop(AF_INET6, &v6_addr_rep.ipv6, v6_addr_str, - sizeof v6_addr_str)); - modify_packet(pkt, ftp_data_start + addr_offset_from_ftp_data_start, - addr_size, v6_addr_str, strlen(v6_addr_str), - orig_used_size); - return (int) strlen(v6_addr_str) - (int) addr_size; -} - -/* Increment/decrement a TCP sequence number. */ -static void -adj_seqnum(ovs_16aligned_be32 *val, int32_t inc) -{ - put_16aligned_be32(val, htonl(ntohl(get_16aligned_be32(val)) + inc)); -} - void conn_update_state_hook_register(int priority, conn_update_state_hook_fn fn) { @@ -3801,122 +3214,3 @@ conn_update_state_hook_unregister(conn_update_state_hook_fn fn) } } } - -static void -handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx, - struct dp_packet *pkt, struct conn *ec, long long now, - enum ftp_ctl_pkt ftp_ctl, bool nat) -{ - struct ip_header *l3_hdr = dp_packet_l3(pkt); - ovs_be32 v4_addr_rep = 0; - union ct_addr v6_addr_rep; - size_t addr_offset_from_ftp_data_start = 0; - size_t addr_size = 0; - char *ftp_data_start; - enum ct_alg_mode mode = CT_FTP_MODE_ACTIVE; - - if (detect_ftp_ctl_type(ctx, pkt) != ftp_ctl) { - return; - } - - struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt); - int64_t seq_skew = 0; - - if (ftp_ctl == CT_FTP_CTL_INTEREST) { - enum ftp_ctl_pkt rc; - if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { - rc = process_ftp_ctl_v6(ct, pkt, ec, - &v6_addr_rep, &ftp_data_start, - &addr_offset_from_ftp_data_start, - &addr_size, &mode); - } else { - rc = process_ftp_ctl_v4(ct, pkt, ec, - &v4_addr_rep, &ftp_data_start, - &addr_offset_from_ftp_data_start, - &addr_size); - } - if (rc == CT_FTP_CTL_INVALID) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); - VLOG_WARN_RL(&rl, "Invalid FTP control packet format"); - pkt->md.ct_state |= CS_TRACKED | CS_INVALID; - return; - } else if (rc == CT_FTP_CTL_INTEREST) { - uint16_t ip_len; - - if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { - if (nat) { - seq_skew = repl_ftp_v6_addr(pkt, v6_addr_rep, - ftp_data_start, - addr_offset_from_ftp_data_start, - addr_size, mode); - } - - if (seq_skew) { - ip_len = ntohs(nh6->ip6_ctlun.ip6_un1.ip6_un1_plen) + - seq_skew; - nh6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(ip_len); - } - } else { - if (nat) { - seq_skew = repl_ftp_v4_addr(pkt, v4_addr_rep, - ftp_data_start, - addr_offset_from_ftp_data_start, - addr_size); - } - if (seq_skew) { - ip_len = ntohs(l3_hdr->ip_tot_len) + seq_skew; - if (dp_packet_ip_checksum_valid(pkt)) { - dp_packet_ip_checksum_set_partial(pkt); - } else { - l3_hdr->ip_csum = recalc_csum16(l3_hdr->ip_csum, - l3_hdr->ip_tot_len, - htons(ip_len)); - } - l3_hdr->ip_tot_len = htons(ip_len); - } - } - } else { - OVS_NOT_REACHED(); - } - } - - struct tcp_header *th = dp_packet_l4(pkt); - - if (nat && ec->seq_skew != 0) { - ctx->reply != ec->seq_skew_dir ? - adj_seqnum(&th->tcp_ack, -ec->seq_skew) : - adj_seqnum(&th->tcp_seq, ec->seq_skew); - } - - if (dp_packet_l4_checksum_valid(pkt)) { - dp_packet_l4_checksum_set_partial(pkt); - } else { - th->tcp_csum = 0; - if (ctx->key.dl_type == htons(ETH_TYPE_IPV6)) { - th->tcp_csum = packet_csum_upperlayer6(nh6, th, ctx->key.nw_proto, - dp_packet_l4_size(pkt)); - } else { - uint32_t tcp_csum = packet_csum_pseudoheader(l3_hdr); - th->tcp_csum = csum_finish( - csum_continue(tcp_csum, th, dp_packet_l4_size(pkt))); - } - } - - if (seq_skew) { - conn_seq_skew_set(ct, ec, now, seq_skew + ec->seq_skew, - ctx->reply); - } -} - -static void -handle_tftp_ctl(struct conntrack *ct, - const struct conn_lookup_ctx *ctx OVS_UNUSED, - struct dp_packet *pkt, struct conn *conn_for_expectation, - long long now OVS_UNUSED, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED, - bool nat OVS_UNUSED) -{ - expectation_create(ct, - conn_for_expectation->key_node[CT_DIR_FWD].key.src.port, - conn_for_expectation, - !!(pkt->md.ct_state & CS_REPLY_DIR), false, false); -} -- 2.53.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
