Commit message: ipf: release unhandled packets from the batch

Since 640d4db788ed ("ipf: Fix a use-after-free error, ...") the ipf
framework unconditionally allocates a new dp_packet to track
individual fragments.  This prevents a use-after-free.  However, an
additional issue was present - even when the packet buffer is cloned,
if the ip fragment handling code keeps it, the original buffer is
leaked during the refill loop.  Even in the original processing code,
the hardcoded dnsteal branches would always leak a packet buffer from
the refill loop.

This can be confirmed with valgrind:

==717566== 16,672 (4,480 direct, 12,192 indirect) bytes in 8 blocks are definitely lost in loss record 390 of 390
==717566==    at 0x484086F: malloc (vg_replace_malloc.c:380)
==717566==    by 0x537BFD: xmalloc__ (util.c:137)
==717566==    by 0x537BFD: xmalloc (util.c:172)
==717566==    by 0x46DDD4: dp_packet_new (dp-packet.c:153)
==717566==    by 0x46DDD4: dp_packet_new_with_headroom (dp-packet.c:163)
==717566==    by 0x550AA6: netdev_linux_batch_rxq_recv_sock.constprop.0 (netdev-linux.c:1262)
==717566==    by 0x5512AF: netdev_linux_rxq_recv (netdev-linux.c:1511)
==717566==    by 0x4AB7E0: netdev_rxq_recv (netdev.c:727)
==717566==    by 0x47F00D: dp_netdev_process_rxq_port (dpif-netdev.c:4699)
==717566==    by 0x47FD13: dpif_netdev_run (dpif-netdev.c:5957)
==717566==    by 0x4331D2: type_run (ofproto-dpif.c:370)
==717566==    by 0x41DFD8: ofproto_type_run (ofproto.c:1768)
==717566==    by 0x40A7FB: bridge_run__ (bridge.c:3245)
==717566==    by 0x411269: bridge_run (bridge.c:3310)
==717566==    by 0x406E6C: main (ovs-vswitchd.c:127)

The fix is to delete the original packet when it isn't able to be
reinserted into the packet batch.  Subsequent valgrind runs show that
the packets are not leaked from the batch any longer.

Fixes: 640d4db788ed ("ipf: Fix a use-after-free error, and remove the 'do_not_steal' flag.")
Fixes: 4ea96698f667 ("Userspace datapath: Add fragmentation handling.")
Reported-by: Wan Junjie <wanjunjie@bytedance.com>
Reported-at: https://github.com/openvswitch/ovs-issues/issues/226
Signed-off-by: Aaron Conole <aconole@redhat.com>
Reviewed-by: David Marchand <david.marchand@redhat.com>
Tested-by: Wan Junjie <wanjunjie@bytedance.com>
Signed-off-by: Alin-Gabriel Serdean <aserdean@ovn.org>
CVE: CVE-2021-3905
CWE: CWE-401
Vulnerability description: A memory leak was found in Open vSwitch (OVS) during userspace IP fragmentation processing. An attacker could use this flaw to potentially exhaust available memory by keeping sending packet fragments.

Project: ovs
File: ipf.c

ipf_is_first_v4_frag(const struct dp_packet *pkt)
{
    const struct ip_header *l3 = dp_packet_l3(pkt);
    if (!(l3->ip_frag_off & htons(IP_FRAG_OFF_MASK)) &&
        l3->ip_frag_off & htons(IP_MORE_FRAGMENTS)) {
        return true;
    }
    return false;
}

Project: ovs
File: ipf.c

ipf_is_v6_frag(ovs_be16 ip6f_offlg)
{
    if (ip6f_offlg & (IP6F_OFF_MASK | IP6F_MORE_FRAG)) {
        return true;
    }
    return false;
}

Project: ovs
File: ipf.c

ipf_print_reass_packet(const char *es, const void *pkt)
{
    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
    if (!VLOG_DROP_WARN(&rl)) {
        struct ds ds = DS_EMPTY_INITIALIZER;
        ds_put_hex_dump(&ds, pkt, 128, 0, false);
        VLOG_WARN("%s\n%s", es, ds_cstr(&ds));
        ds_destroy(&ds);
    }
}

Project: ovs
File: ipf.c

ipf_get_v6_enabled(struct ipf *ipf)
{
    bool ifp_v6_enabled_;
    atomic_read_relaxed(&ipf->ifp_v6_enabled, &ifp_v6_enabled_);
    return ifp_v6_enabled_;
}

Project: ovs
File: ipf.c

ipf_is_last_v6_frag(ovs_be16 ip6f_offlg)
{
    if ((ip6f_offlg & IP6F_OFF_MASK) &&
        !(ip6f_offlg & IP6F_MORE_FRAG)) {
        return true;
    }
    return false;
}

Project: ovs
File: ipf.c

ipf_count(struct ipf *ipf, bool v6, enum ipf_counter_type cntr)
{
    atomic_count_inc64(v6 ? &ipf->n6frag_cnt[cntr] : &ipf->n4frag_cnt[cntr]);
}

Project: ovs
File: ipf.c

ipf_get_enabled(struct ipf *ipf)
{
    return ipf_get_v4_enabled(ipf) || ipf_get_v6_enabled(ipf);
}

Project: ovs
File: ipf.c

ipf_is_valid_v6_frag(struct ipf *ipf, struct dp_packet *pkt)
{
    const struct eth_header *l2 = dp_packet_eth(pkt);
    const struct  ovs_16aligned_ip6_hdr *l3 = dp_packet_l3(pkt);
    const char *l4 = dp_packet_l4(pkt);

    if (OVS_UNLIKELY(!l2 || !l3 || !l4)) {
        goto invalid_pkt;
    }

    size_t l3_size = dp_packet_l3_size(pkt);
    size_t l3_hdr_size = sizeof *l3;

    if (OVS_UNLIKELY(l3_size < l3_hdr_size)) {
        goto invalid_pkt;
    }

    uint8_t nw_frag = 0;
    uint8_t nw_proto = l3->ip6_nxt;
    const void *data = l3 + 1;
    size_t datasize = l3_size - l3_hdr_size;
    const struct ovs_16aligned_ip6_frag *frag_hdr = NULL;
    if (!parse_ipv6_ext_hdrs(&data, &datasize, &nw_proto, &nw_frag,
                             &frag_hdr) || !nw_frag || !frag_hdr) {
        return false;
    }

    int pl = ntohs(l3->ip6_plen);
    if (OVS_UNLIKELY(pl + l3_hdr_size != l3_size)) {
        goto invalid_pkt;
    }

    ovs_be16 ip6f_offlg = frag_hdr->ip6f_offlg;
    if (OVS_UNLIKELY(!ipf_is_v6_frag(ip6f_offlg))) {
        return false;
    }

    uint32_t min_v6_frag_size_;
    atomic_read_relaxed(&ipf->min_v6_frag_size, &min_v6_frag_size_);
    bool lf = ipf_is_last_v6_frag(ip6f_offlg);

    if (OVS_UNLIKELY(!lf && dp_packet_l3_size(pkt) < min_v6_frag_size_)) {
        ipf_count(ipf, true, IPF_NFRAGS_TOO_SMALL);
        goto invalid_pkt;
    }

    return true;

invalid_pkt:
    pkt->md.ct_state = CS_INVALID;
    return false;

}

Project: ovs
File: ipf.c

ipf_list_key_eq(const struct ipf_list_key *key1,
                const struct ipf_list_key *key2)
    /* OVS_REQUIRES(ipf_lock) */
{
    if (!memcmp(&key1->src_addr, &key2->src_addr, sizeof key1->src_addr) &&
        !memcmp(&key1->dst_addr, &key2->dst_addr, sizeof key1->dst_addr) &&
        key1->dl_type == key2->dl_type &&
        key1->ip_id == key2->ip_id &&
        key1->zone == key2->zone &&
        key1->nw_proto == key2->nw_proto &&
        key1->recirc_id == key2->recirc_id) {
        return true;
    }
    return false;
}

Project: ovs
File: ipf.c

    OVS_REQUIRES(ipf->ipf_lock)
{
    struct ipf_list_key key;
    /* Initialize 4 variables for some versions of GCC. */
    uint16_t start_data_byte = 0;
    uint16_t end_data_byte = 0;
    bool ff = false;
    bool lf = false;
    bool v6 = dl_type == htons(ETH_TYPE_IPV6);

    if (v6 && ipf_get_v6_enabled(ipf)) {
        ipf_v6_key_extract(pkt, dl_type, zone, &key, &start_data_byte,
                           &end_data_byte, &ff, &lf);
    } else if (!v6 && ipf_get_v4_enabled(ipf)) {
        ipf_v4_key_extract(pkt, dl_type, zone, &key, &start_data_byte,
                           &end_data_byte, &ff, &lf);
    } else {
        return false;
    }

    unsigned int nfrag_max;
    atomic_read_relaxed(&ipf->nfrag_max, &nfrag_max);
    if (atomic_count_get(&ipf->nfrag) >= nfrag_max) {
        return false;
    }

    uint32_t hash = ipf_list_key_hash(&key, hash_basis);
    struct ipf_list *ipf_list = ipf_list_key_lookup(ipf, &key, hash);
    enum {
        IPF_FRAG_LIST_MIN_INCREMENT = 4,
        IPF_IPV6_MAX_FRAG_LIST_SIZE = 65535,
    };

    int max_frag_list_size;
    if (v6) {
        /* Because the calculation with extension headers is variable,
         * we don't calculate a hard maximum fragment list size upfront.  The
         * fragment list size is practically limited by the code, however. */
        max_frag_list_size = IPF_IPV6_MAX_FRAG_LIST_SIZE;
    } else {
        max_frag_list_size = ipf->max_v4_frag_list_size;
    }

    if (!ipf_list) {
        ipf_list = xmalloc(sizeof *ipf_list);
        ipf_list_init(ipf_list, &key,
                      MIN(max_frag_list_size, IPF_FRAG_LIST_MIN_INCREMENT));
        hmap_insert(&ipf->frag_lists, &ipf_list->node, hash);
        ipf_expiry_list_add(&ipf->frag_exp_list, ipf_list, now);
    } else if (ipf_list->state == IPF_LIST_STATE_REASS_FAIL ||
               ipf_list->state == IPF_LIST_STATE_COMPLETED) {
        /* Bail out as early as possible. */
        return false;
    } else if (ipf_list->last_inuse_idx + 1 >= ipf_list->size) {
        int increment = MIN(IPF_FRAG_LIST_MIN_INCREMENT,
                            max_frag_list_size - ipf_list->size);
        /* Enforce limit. */
        if (increment > 0) {
            ipf_list->frag_list =
                xrealloc(ipf_list->frag_list, (ipf_list->size + increment) *
                  sizeof *ipf_list->frag_list);
            ipf_list->size += increment;
        } else {
            return false;
        }
    }

    return ipf_process_frag(ipf, ipf_list, pkt, start_data_byte,
                            end_data_byte, ff, lf, v6);
}

Project: ovs
File: ipf.c

ipf_addr_hash_add(uint32_t hash, const union ipf_addr *addr)
{
    BUILD_ASSERT_DECL(sizeof *addr % 4 == 0);
    return hash_add_bytes32(hash, (const uint32_t *) addr, sizeof *addr);
}

Project: ovs
File: ipf.c

ipf_is_valid_v4_frag(struct ipf *ipf, struct dp_packet *pkt)
{
    if (OVS_UNLIKELY(dp_packet_ip_checksum_bad(pkt))) {
        COVERAGE_INC(ipf_l3csum_err);
        goto invalid_pkt;
    }

    const struct eth_header *l2 = dp_packet_eth(pkt);
    const struct ip_header *l3 = dp_packet_l3(pkt);

    if (OVS_UNLIKELY(!l2 || !l3)) {
        goto invalid_pkt;
    }

    size_t l3_size = dp_packet_l3_size(pkt);
    if (OVS_UNLIKELY(l3_size < IP_HEADER_LEN)) {
        goto invalid_pkt;
    }

    if (!IP_IS_FRAGMENT(l3->ip_frag_off)) {
        return false;
    }

    uint16_t ip_tot_len = ntohs(l3->ip_tot_len);
    if (OVS_UNLIKELY(ip_tot_len != l3_size)) {
        goto invalid_pkt;
    }

    size_t ip_hdr_len = IP_IHL(l3->ip_ihl_ver) * 4;
    if (OVS_UNLIKELY(ip_hdr_len < IP_HEADER_LEN)) {
        goto invalid_pkt;
    }
    if (OVS_UNLIKELY(l3_size < ip_hdr_len)) {
        goto invalid_pkt;
    }

    if (OVS_UNLIKELY(!dp_packet_ip_checksum_valid(pkt)
                     && !dp_packet_hwol_is_ipv4(pkt)
                     && csum(l3, ip_hdr_len) != 0)) {
        COVERAGE_INC(ipf_l3csum_err);
        goto invalid_pkt;
    }

    uint32_t min_v4_frag_size_;
    atomic_read_relaxed(&ipf->min_v4_frag_size, &min_v4_frag_size_);
    bool lf = ipf_is_last_v4_frag(pkt);
    if (OVS_UNLIKELY(!lf && dp_packet_l3_size(pkt) < min_v4_frag_size_)) {
        ipf_count(ipf, false, IPF_NFRAGS_TOO_SMALL);
        goto invalid_pkt;
    }
    return true;

invalid_pkt:
    pkt->md.ct_state = CS_INVALID;
    return false;
}

Project: ovs
File: ipf.c

ipf_reassembled_list_add(struct ovs_list *reassembled_pkt_list,
                         struct reassembled_pkt *rp)
    /* OVS_REQUIRES(ipf_lock) */
{
    ovs_list_push_back(reassembled_pkt_list, &rp->rp_list_node);
}

Project: ovs
File: ipf.c

ipf_expiry_list_add(struct ovs_list *frag_exp_list, struct ipf_list *ipf_list,
                    long long now)
   /* OVS_REQUIRES(ipf->ipf_lock) */
{
    enum {
        IPF_FRAG_LIST_TIMEOUT = 15000,
    };

    ipf_list->expiration = now + IPF_FRAG_LIST_TIMEOUT;
    ovs_list_push_back(frag_exp_list, &ipf_list->list_node);
}

Project: ovs
File: ipf.c

ipf_completed_list_clean(struct hmap *frag_lists,
                         struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    ipf_list_clean(frag_lists, ipf_list);
}

Project: ovs
File: ipf.c

    OVS_REQUIRES(ipf->ipf_lock)
{
    struct ipf_list *ipf_list;
    HMAP_FOR_EACH_WITH_HASH (ipf_list, node, hash, &ipf->frag_lists) {
        if (ipf_list_key_eq(&ipf_list->key, key)) {
            return ipf_list;
        }
    }
    return NULL;
}

Project: ovs
File: ipf.c

ipf_extract_frags_from_batch(struct ipf *ipf, struct dp_packet_batch *pb,
                             ovs_be16 dl_type, uint16_t zone, long long now,
                             uint32_t hash_basis)
{
    const size_t pb_cnt = dp_packet_batch_size(pb);
    int pb_idx; /* Index in a packet batch. */
    struct dp_packet *pkt;

    DP_PACKET_BATCH_REFILL_FOR_EACH (pb_idx, pb_cnt, pkt, pb) {
        if (OVS_UNLIKELY((dl_type == htons(ETH_TYPE_IP) &&
                          ipf_is_valid_v4_frag(ipf, pkt))
                          ||
                          (dl_type == htons(ETH_TYPE_IPV6) &&
                          ipf_is_valid_v6_frag(ipf, pkt)))) {

            ovs_mutex_lock(&ipf->ipf_lock);
            if (!ipf_handle_frag(ipf, pkt, dl_type, zone, now, hash_basis)) {
                dp_packet_batch_refill(pb, pkt, pb_idx);
            } else {
                dp_packet_delete(pkt);
            }
            ovs_mutex_unlock(&ipf->ipf_lock);
        } else {
            dp_packet_batch_refill(pb, pkt, pb_idx);
        }
    }
}

Project: ovs
File: ipf.c

ipf_list_clean(struct hmap *frag_lists,
               struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    ovs_list_remove(&ipf_list->list_node);
    hmap_remove(frag_lists, &ipf_list->node);
    free(ipf_list->frag_list);
    free(ipf_list);
}

Project: ovs
File: ipf.c

ipf_is_frag_duped(const struct ipf_frag *frag_list, int last_inuse_idx,
                  size_t start_data_byte, size_t end_data_byte)
    /* OVS_REQUIRES(ipf_lock) */
{
    for (int i = 0; i <= last_inuse_idx; i++) {
        if ((start_data_byte >= frag_list[i].start_data_byte &&
            start_data_byte <= frag_list[i].end_data_byte) ||
            (end_data_byte >= frag_list[i].start_data_byte &&
             end_data_byte <= frag_list[i].end_data_byte)) {
            return true;
        }
    }
    return false;
}

Project: ovs
File: ipf.c

ipf_completed_list_add(struct ovs_list *frag_complete_list,
                       struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    ovs_list_push_back(frag_complete_list, &ipf_list->list_node);
}

Project: ovs
File: ipf.c

    OVS_REQUIRES(ipf->ipf_lock)
{
    enum ipf_list_state curr_state = ipf_list->state;
    enum ipf_list_state next_state;
    switch (curr_state) {
    case IPF_LIST_STATE_UNUSED:
    case IPF_LIST_STATE_OTHER_SEEN:
        if (ff) {
            next_state = IPF_LIST_STATE_FIRST_SEEN;
        } else if (lf) {
            next_state = IPF_LIST_STATE_LAST_SEEN;
        } else {
            next_state = IPF_LIST_STATE_OTHER_SEEN;
        }
        break;
    case IPF_LIST_STATE_FIRST_SEEN:
        if (lf) {
            next_state = IPF_LIST_STATE_FIRST_LAST_SEEN;
        } else {
            next_state = IPF_LIST_STATE_FIRST_SEEN;
        }
        break;
    case IPF_LIST_STATE_LAST_SEEN:
        if (ff) {
            next_state = IPF_LIST_STATE_FIRST_LAST_SEEN;
        } else {
            next_state = IPF_LIST_STATE_LAST_SEEN;
        }
        break;
    case IPF_LIST_STATE_FIRST_LAST_SEEN:
        next_state = IPF_LIST_STATE_FIRST_LAST_SEEN;
        break;
    case IPF_LIST_STATE_COMPLETED:
    case IPF_LIST_STATE_REASS_FAIL:
    case IPF_LIST_STATE_NUM:
    default:
        OVS_NOT_REACHED();
    }

    if (next_state == IPF_LIST_STATE_FIRST_LAST_SEEN) {
        ipf_sort(ipf_list->frag_list, ipf_list->last_inuse_idx);
        if (ipf_list_complete(ipf_list)) {
            struct dp_packet *reass_pkt = v6
                ? ipf_reassemble_v6_frags(ipf_list)
                : ipf_reassemble_v4_frags(ipf_list);
            if (reass_pkt) {
                struct reassembled_pkt *rp = xzalloc(sizeof *rp);
                rp->pkt = reass_pkt;
                rp->list = ipf_list;
                ipf_reassembled_list_add(&ipf->reassembled_pkt_list, rp);
                ipf_expiry_list_remove(ipf_list);
                next_state = IPF_LIST_STATE_COMPLETED;
            } else {
                next_state = IPF_LIST_STATE_REASS_FAIL;
            }
        }
    }
    ipf_list->state = next_state;
}

Project: ovs
File: ipf.c

ipf_list_init(struct ipf_list *ipf_list, struct ipf_list_key *key,
              int max_frag_list_size)
{
    ipf_list->key = *key;
    ipf_list->last_inuse_idx = IPF_INVALID_IDX;
    ipf_list->last_sent_idx = IPF_INVALID_IDX;
    ipf_list->reass_execute_ctx = NULL;
    ipf_list->state = IPF_LIST_STATE_UNUSED;
    ipf_list->size = max_frag_list_size;
    ipf_list->frag_list
        = xzalloc(ipf_list->size * sizeof *ipf_list->frag_list);
}

Project: ovs
File: ipf.c

ipf_is_first_v6_frag(ovs_be16 ip6f_offlg)
{
    if (!(ip6f_offlg & IP6F_OFF_MASK) &&
        ip6f_offlg & IP6F_MORE_FRAG) {
        return true;
    }
    return false;
}

Project: ovs
File: ipf.c

ipf_reassembled_list_remove(struct reassembled_pkt *rp)
    /* OVS_REQUIRES(ipf_lock) */
{
    ovs_list_remove(&rp->rp_list_node);
}

Project: ovs
File: ipf.c

ipf_v6_key_extract(struct dp_packet *pkt, ovs_be16 dl_type, uint16_t zone,
                   struct ipf_list_key *key, uint16_t *start_data_byte,
                   uint16_t *end_data_byte, bool *ff, bool *lf)
{
    const struct ovs_16aligned_ip6_hdr *l3 = dp_packet_l3(pkt);
    uint8_t nw_frag = 0;
    uint8_t nw_proto = l3->ip6_nxt;
    const void *data = l3 + 1;
    size_t datasize = dp_packet_l3_size(pkt) - sizeof *l3;
    const struct ovs_16aligned_ip6_frag *frag_hdr = NULL;

    parse_ipv6_ext_hdrs(&data, &datasize, &nw_proto, &nw_frag, &frag_hdr);
    ovs_assert(nw_frag && frag_hdr);
    ovs_be16 ip6f_offlg = frag_hdr->ip6f_offlg;
    *start_data_byte = ntohs(ip6f_offlg & IP6F_OFF_MASK) +
        sizeof (struct ovs_16aligned_ip6_frag);
    *end_data_byte = *start_data_byte + dp_packet_l4_size(pkt) - 1;
    *ff = ipf_is_first_v6_frag(ip6f_offlg);
    *lf = ipf_is_last_v6_frag(ip6f_offlg);
    memset(key, 0, sizeof *key);
    key->ip_id = get_16aligned_be32(&frag_hdr->ip6f_ident);
    key->dl_type = dl_type;
    memcpy(&key->src_addr.ipv6, &l3->ip6_src, sizeof key->src_addr.ipv6);
    /* We are not supporting parsing of the routing header to use as the
     * dst address part of the key. */
    memcpy(&key->dst_addr.ipv6, &l3->ip6_dst, sizeof key->dst_addr.ipv6);
    key->nw_proto = 0;   /* Not used for key for V6. */
    key->zone = zone;
    key->recirc_id = pkt->md.recirc_id;
}

Project: ovs
File: ipf.c

ipf_reassemble_v4_frags(struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    struct ipf_frag *frag_list = ipf_list->frag_list;
    struct dp_packet *pkt = dp_packet_clone(frag_list[0].pkt);
    dp_packet_set_size(pkt, dp_packet_size(pkt) - dp_packet_l2_pad_size(pkt));
    struct ip_header *l3 = dp_packet_l3(pkt);
    int len = ntohs(l3->ip_tot_len);

    int rest_len = frag_list[ipf_list->last_inuse_idx].end_data_byte -
                   frag_list[1].start_data_byte + 1;

    if (len + rest_len > IPV4_PACKET_MAX_SIZE) {
        ipf_print_reass_packet(
            "Unsupported big reassembled v4 packet; v4 hdr:", l3);
        dp_packet_delete(pkt);
        return NULL;
    }

    dp_packet_prealloc_tailroom(pkt, rest_len);

    for (int i = 1; i <= ipf_list->last_inuse_idx; i++) {
        size_t add_len = frag_list[i].end_data_byte -
                         frag_list[i].start_data_byte + 1;
        const char *l4 = dp_packet_l4(frag_list[i].pkt);
        dp_packet_put(pkt, l4, add_len);
    }

    len += rest_len;
    l3 = dp_packet_l3(pkt);
    ovs_be16 new_ip_frag_off = l3->ip_frag_off & ~htons(IP_MORE_FRAGMENTS);
    if (!dp_packet_hwol_is_ipv4(pkt)) {
        l3->ip_csum = recalc_csum16(l3->ip_csum, l3->ip_frag_off,
                                    new_ip_frag_off);
        l3->ip_csum = recalc_csum16(l3->ip_csum, l3->ip_tot_len, htons(len));
    }
    l3->ip_tot_len = htons(len);
    l3->ip_frag_off = new_ip_frag_off;
    dp_packet_set_l2_pad_size(pkt, 0);

    return pkt;
}

Project: ovs
File: ipf.c

ipf_reassemble_v6_frags(struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    struct ipf_frag *frag_list = ipf_list->frag_list;
    struct dp_packet *pkt = dp_packet_clone(frag_list[0].pkt);
    dp_packet_set_size(pkt, dp_packet_size(pkt) - dp_packet_l2_pad_size(pkt));
    struct  ovs_16aligned_ip6_hdr *l3 = dp_packet_l3(pkt);
    int pl = ntohs(l3->ip6_plen) - sizeof(struct ovs_16aligned_ip6_frag);

    int rest_len = frag_list[ipf_list->last_inuse_idx].end_data_byte -
                   frag_list[1].start_data_byte + 1;

    if (pl + rest_len > IPV6_PACKET_MAX_DATA) {
        ipf_print_reass_packet(
             "Unsupported big reassembled v6 packet; v6 hdr:", l3);
        dp_packet_delete(pkt);
        return NULL;
    }

    dp_packet_prealloc_tailroom(pkt, rest_len);

    for (int i = 1; i <= ipf_list->last_inuse_idx; i++) {
        size_t add_len = frag_list[i].end_data_byte -
                          frag_list[i].start_data_byte + 1;
        const char *l4 = dp_packet_l4(frag_list[i].pkt);
        dp_packet_put(pkt, l4, add_len);
    }

    pl += rest_len;
    l3 = dp_packet_l3(pkt);

    uint8_t nw_proto = l3->ip6_nxt;
    uint8_t nw_frag = 0;
    const void *data = l3 + 1;
    size_t datasize = pl;

    const struct ovs_16aligned_ip6_frag *frag_hdr = NULL;
    if (!parse_ipv6_ext_hdrs(&data, &datasize, &nw_proto, &nw_frag, &frag_hdr)
        || !nw_frag || !frag_hdr) {

        ipf_print_reass_packet("Unparsed reassembled v6 packet; v6 hdr:", l3);
        dp_packet_delete(pkt);
        return NULL;
    }

    struct ovs_16aligned_ip6_frag *fh =
        CONST_CAST(struct ovs_16aligned_ip6_frag *, frag_hdr);
    fh->ip6f_offlg = 0;
    l3->ip6_plen = htons(pl);
    l3->ip6_ctlun.ip6_un1.ip6_un1_nxt = nw_proto;
    dp_packet_set_l2_pad_size(pkt, 0);
    return pkt;
}

Project: ovs
File: ipf.c

ipf_expiry_list_remove(struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    ovs_list_remove(&ipf_list->list_node);
}

Project: ovs
File: ipf.c

ipf_v4_key_extract(struct dp_packet *pkt, ovs_be16 dl_type, uint16_t zone,
                   struct ipf_list_key *key, uint16_t *start_data_byte,
                   uint16_t *end_data_byte, bool *ff, bool *lf)
{
    const struct ip_header *l3 = dp_packet_l3(pkt);
    uint16_t ip_tot_len = ntohs(l3->ip_tot_len);
    size_t ip_hdr_len = IP_IHL(l3->ip_ihl_ver) * 4;

    *start_data_byte = ntohs(l3->ip_frag_off & htons(IP_FRAG_OFF_MASK)) * 8;
    *end_data_byte = *start_data_byte + ip_tot_len - ip_hdr_len - 1;
    *ff = ipf_is_first_v4_frag(pkt);
    *lf = ipf_is_last_v4_frag(pkt);
    memset(key, 0, sizeof *key);
    key->ip_id = be16_to_be32(l3->ip_id);
    key->dl_type = dl_type;
    key->src_addr.ipv4 = get_16aligned_be32(&l3->ip_src);
    key->dst_addr.ipv4 = get_16aligned_be32(&l3->ip_dst);
    key->nw_proto = l3->ip_proto;
    key->zone = zone;
    key->recirc_id = pkt->md.recirc_id;
    return true;
}

Project: ovs
File: ipf.c

ipf_is_last_v4_frag(const struct dp_packet *pkt)
{
    const struct ip_header *l3 = dp_packet_l3(pkt);
    if (l3->ip_frag_off & htons(IP_FRAG_OFF_MASK) &&
        !(l3->ip_frag_off & htons(IP_MORE_FRAGMENTS))) {
        return true;
    }
    return false;
}

Project: ovs
File: ipf.c

    OVS_REQUIRES(ipf->ipf_lock)
{
    bool duped_frag = ipf_is_frag_duped(ipf_list->frag_list,
        ipf_list->last_inuse_idx, start_data_byte, end_data_byte);
    int last_inuse_idx = ipf_list->last_inuse_idx;

    if (!duped_frag) {
        if (last_inuse_idx < ipf_list->size - 1) {
            /* In the case of dpdk, it would be unfortunate if we had
             * to create a clone fragment outside the dpdk mp due to the
             * mempool size being too limited. We will otherwise need to
             * recommend not setting the mempool number of buffers too low
             * and also clamp the number of fragments. */
            struct ipf_frag *frag = &ipf_list->frag_list[last_inuse_idx + 1];
            frag->pkt = dp_packet_clone(pkt);
            frag->start_data_byte = start_data_byte;
            frag->end_data_byte = end_data_byte;
            ipf_list->last_inuse_idx++;
            atomic_count_inc(&ipf->nfrag);
            ipf_count(ipf, v6, IPF_NFRAGS_ACCEPTED);
            ipf_list_state_transition(ipf, ipf_list, ff, lf, v6);
        } else {
            OVS_NOT_REACHED();
        }
    } else {
        ipf_count(ipf, v6, IPF_NFRAGS_OVERLAP);
        pkt->md.ct_state = CS_INVALID;
        return false;
    }
    return true;
}

Project: ovs
File: ipf.c

ipf_get_v4_enabled(struct ipf *ipf)
{
    bool ifp_v4_enabled_;
    atomic_read_relaxed(&ipf->ifp_v4_enabled, &ifp_v4_enabled_);
    return ifp_v4_enabled_;
}

Project: ovs
File: ipf.c

ipf_list_key_hash(const struct ipf_list_key *key, uint32_t basis)
{
    uint32_t hsrc, hdst, hash;
    hsrc = hdst = basis;
    hsrc = ipf_addr_hash_add(hsrc, &key->src_addr);
    hdst = ipf_addr_hash_add(hdst, &key->dst_addr);
    hash = hsrc ^ hdst;

    /* Hash the rest of the key. */
    return hash_words((uint32_t *) (&key->dst_addr + 1),
                      (uint32_t *) (key + 1) -
                      (uint32_t *) (&key->dst_addr + 1),
                      hash);
}

Project: ovs
File: ipf.c

ipf_list_complete(const struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    for (int i = 1; i <= ipf_list->last_inuse_idx; i++) {
        if (ipf_list->frag_list[i - 1].end_data_byte + 1
            != ipf_list->frag_list[i].start_data_byte) {
            return false;
        }
    }
    return true;
}

Project: ovs
File: ipf.c

ipf_expiry_list_clean(struct hmap *frag_lists,
                      struct ipf_list *ipf_list)
    /* OVS_REQUIRES(ipf_lock) */
{
    ipf_list_clean(frag_lists, ipf_list);
}

Project: ovs
File: ipf.c

ipf_sort(struct ipf_frag *frag_list, size_t last_idx)
    /* OVS_REQUIRES(ipf_lock) */
{
    for (int li = 1; li <= last_idx; li++) {
        struct ipf_frag ipf_frag = frag_list[li];
        int ci = li - 1;
        while (ci >= 0 &&
               frag_list[ci].start_data_byte > ipf_frag.start_data_byte) {
            frag_list[ci + 1] = frag_list[ci];
            ci--;
        }
        frag_list[ci + 1] = ipf_frag;
    }
}