handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,
               struct dp_packet *pkt,
               const struct conn *conn_for_expectation,
               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;
    struct ct_addr v6_addr_rep;
    size_t addr_offset_from_ftp_data_start = 0;
    size_t addr_size = 0;
    char *ftp_data_start;
    bool do_seq_skew_adj = true;
    enum ct_alg_mode mode = CT_FTP_MODE_ACTIVE;

    if (detect_ftp_ctl_type(ctx, pkt) != ftp_ctl) {
        return;
    }

    if (!nat || !conn_for_expectation->seq_skew) {
        do_seq_skew_adj = false;
    }

    struct ovs_16aligned_ip6_hdr *nh6 = dp_packet_l3(pkt);
    int64_t seq_skew = 0;
    bool seq_skew_dir;
    if (ftp_ctl == CT_FTP_CTL_OTHER) {
        seq_skew = conn_for_expectation->seq_skew;
        seq_skew_dir = conn_for_expectation->seq_skew_dir;
    } else 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, conn_for_expectation,
                                    now, &v6_addr_rep, &ftp_data_start,
                                    &addr_offset_from_ftp_data_start,
                                    &addr_size, &mode);
        } else {
            rc = process_ftp_ctl_v4(ct, pkt, conn_for_expectation,
                                    now, &v4_addr_rep, &ftp_data_start,
                                    &addr_offset_from_ftp_data_start);
        }
        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)) {
                seq_skew = repl_ftp_v6_addr(pkt, v6_addr_rep, ftp_data_start,
                                            addr_offset_from_ftp_data_start,
                                            addr_size, mode);
                seq_skew_dir = ctx->reply;
                if (seq_skew) {
                    ip_len = ntohs(nh6->ip6_ctlun.ip6_un1.ip6_un1_plen);
                    ip_len += seq_skew;
                    nh6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(ip_len);
                    conn_seq_skew_set(ct, &conn_for_expectation->key, now,
                                      seq_skew, seq_skew_dir);
                }
            } else {
                seq_skew = repl_ftp_v4_addr(pkt, v4_addr_rep, ftp_data_start,
                                            addr_offset_from_ftp_data_start);
                seq_skew_dir = ctx->reply;
                ip_len = ntohs(l3_hdr->ip_tot_len);
                if (seq_skew) {
                    ip_len += seq_skew;
                    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);
                    conn_seq_skew_set(ct, &conn_for_expectation->key, now,
                                      seq_skew, seq_skew_dir);
                }
            }
        } else {
            OVS_NOT_REACHED();
        }
    } else {
        OVS_NOT_REACHED();
    }

    struct tcp_header *th = dp_packet_l4(pkt);
    if (do_seq_skew_adj && seq_skew != 0) {
        if (ctx->reply != conn_for_expectation->seq_skew_dir) {

            uint32_t tcp_ack = ntohl(get_16aligned_be32(&th->tcp_ack));

            if ((seq_skew > 0) && (tcp_ack < seq_skew)) {
                /* Should not be possible; will be marked invalid. */
                tcp_ack = 0;
            } else if ((seq_skew < 0) && (UINT32_MAX - tcp_ack < -seq_skew)) {
                tcp_ack = (-seq_skew) - (UINT32_MAX - tcp_ack);
            } else {
                tcp_ack -= seq_skew;
            }
            ovs_be32 new_tcp_ack = htonl(tcp_ack);
            put_16aligned_be32(&th->tcp_ack, new_tcp_ack);
        } else {
            uint32_t tcp_seq = ntohl(get_16aligned_be32(&th->tcp_seq));
            if ((seq_skew > 0) && (UINT32_MAX - tcp_seq < seq_skew)) {
                tcp_seq = seq_skew - (UINT32_MAX - tcp_seq);
            } else if ((seq_skew < 0) && (tcp_seq < -seq_skew)) {
                /* Should not be possible; will be marked invalid. */
                tcp_seq = 0;
            } else {
                tcp_seq += seq_skew;
            }
            ovs_be32 new_tcp_seq = htonl(tcp_seq);
            put_16aligned_be32(&th->tcp_seq, new_tcp_seq);
        }
    }

    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)));
    }

    return;
}