#include #include "uthash.h" #include "checksum.h" #include "ip_reassembly.h" #include "packet_parser.h" #include "packet_helper.h" #include "packet_internal.h" #define IP_REASSEMBLY_LOG_ERROR(format, ...) STELLAR_LOG_ERROR(__thread_local_logger, "IP reassembly", format, ##__VA_ARGS__) #define IP_REASSEMBLY_LOG_INFO(format, ...) STELLAR_LOG_INFO(__thread_local_logger, "IP reassembly", format, ##__VA_ARGS__) /* * https://datatracker.ietf.org/doc/html/rfc8200#section-4.5 * * Note: unlike IPv4, fragmentation in IPv6 is performed only by source nodes, * not by routers along a packet's delivery path -- see Section 5. */ /* * original packet: * +-----------------+-----------------+--------+--------+-//-+--------+ * | Per-Fragment |Ext & Upper-Layer| first | second | | last | * | Headers | Headers |fragment|fragment|....|fragment| * +-----------------+-----------------+--------+--------+-//-+--------+ * * fragment packets: * +-----------------+--------+-------------------+----------+ * | Per-Fragment |Fragment| Ext & Upper-Layer | first | * | Headers | Header | Headers | fragment | * +-----------------+--------+-------------------+----------+ * * +-----------------+--------+----------+ * | Per-Fragment |Fragment| second | * | Headers | Header | fragment | * +-----------------+--------+----------+ * o * o * o * +-----------------+--------+----------+ * | Per-Fragment |Fragment| last | * | Headers | Header | fragment | * +-----------------+--------+----------+ * * reassembled packet: * +-----------------+-----------------+--------+--------+-//-+--------+ * | Per-Fragment |Ext & Upper-Layer| first | second | | last | * | Headers | Headers |fragment|fragment|....|fragment| * +-----------------+-----------------+--------+--------+-//-+--------+ */ #define IP_FIRST_FRAG_IDX 0 #define IP_LAST_FRAG_IDX 1 #define IP_MIN_FRAG_NUM 2 struct frag_key { uint8_t ip_version; union { struct in_addr v4; // network order struct in6_addr v6; // network order } saddr, daddr; uint32_t ip_id; // IPv4 identification is uint16_t; IPv6 identification is uint32_t uint8_t next_proto; // IPv4 every fragment has next protocol; IPv6 only first fragment has next protocol }; struct frag_encap { const void *data; uint16_t len; // Eth header len + tunnel header len + IP header len uint16_t l3_offset; // Eth header len + tunnel header len uint16_t l3_len; // IP header len uint8_t next_proto; }; struct frag { const void *data; // IP payload data uint16_t len; // IP payload length uint16_t offset; // IP frag offset bool more; // IP frag more struct packet *pkt; }; struct frag_queue { uint64_t start_time; uint32_t expect_size; uint32_t recved_size; uint32_t next_fill; uint32_t frag_used; struct frag_key key; struct frag_encap encap; UT_hash_handle hh; TAILQ_ENTRY(frag_queue) tqe; struct frag frags[]; }; TAILQ_HEAD(frag_queue_list, frag_queue); struct ip_reassembly { uint64_t timeout_ms; uint64_t fq_num; uint64_t fq_size; struct frag_queue *htable; struct frag_queue_list free_list; struct frag_queue_list lru_list; struct packet_queue evict_pkt; struct ip_reassembly_stat stat; }; #define STAT_INC(stat, filed, key) \ { \ if ((key)->ip_version == 4) \ (stat)->ip4_##filed++; \ else \ (stat)->ip6_##filed++; \ } #define STAT_ADD(stat, filed, key, n) \ { \ if ((key)->ip_version == 4) \ (stat)->ip4_##filed += n; \ else \ (stat)->ip6_##filed += n; \ } #define IP_DEFRAG_ERROR_WITH_KEY(desc, key, ...) \ do \ { \ char saddr_str[INET6_ADDRSTRLEN] = {0}; \ char daddr_str[INET6_ADDRSTRLEN] = {0}; \ if ((key)->ip_version == 4) \ { \ inet_ntop(AF_INET, &(key)->saddr.v4, saddr_str, INET6_ADDRSTRLEN); \ inet_ntop(AF_INET, &(key)->daddr.v4, daddr_str, INET6_ADDRSTRLEN); \ } \ else \ { \ inet_ntop(AF_INET6, &(key)->saddr.v6, saddr_str, INET6_ADDRSTRLEN); \ inet_ntop(AF_INET6, &(key)->daddr.v6, daddr_str, INET6_ADDRSTRLEN); \ } \ IP_REASSEMBLY_LOG_ERROR("%s (%s-%s 0x%0x)", (desc), saddr_str, daddr_str, (key)->ip_id); \ } while (0) static int frag_key_init(struct frag_key *key, const struct packet *pkt) { memset(key, 0, sizeof(struct frag_key)); const struct layer_internal *layer = pkt->frag_layer; if (layer->proto == LAYER_PROTO_IPV4) { const struct ip *hdr = (const struct ip *)layer->hdr_ptr; key->ip_version = 4; key->saddr.v4 = ip4_hdr_get_src_in_addr(hdr); key->daddr.v4 = ip4_hdr_get_dst_in_addr(hdr); key->ip_id = ip4_hdr_get_ipid(hdr); key->next_proto = ip4_hdr_get_proto(hdr); } else { const struct ip6_hdr *hdr = (const struct ip6_hdr *)layer->hdr_ptr; const struct ip6_frag *frag = ip6_hdr_get_frag_ext(hdr); if (frag == NULL) { return -1; } key->ip_version = 6; key->saddr.v6 = ip6_hdr_get_src_in6_addr(hdr); key->daddr.v6 = ip6_hdr_get_dst_in6_addr(hdr); key->ip_id = ipv6_frag_get_ident(frag); key->next_proto = 0; // only first fragment has the upper layer protocol } return 0; } static void frag_encap_init(struct frag_encap *encap, const struct packet *pkt) { struct layer_internal *layer = pkt->frag_layer; encap->data = (void *)pkt->data_ptr; encap->len = layer->hdr_offset + layer->hdr_len; encap->l3_offset = layer->hdr_offset; encap->l3_len = layer->hdr_len; if (layer->proto == LAYER_PROTO_IPV6) { const struct ip6_hdr *hdr = (const struct ip6_hdr *)layer->hdr_ptr; const struct ip6_frag *frag = ip6_hdr_get_frag_ext(hdr); encap->next_proto = ipv6_frag_get_next_header(frag); } else { const struct ip *hdr = (const struct ip *)layer->hdr_ptr; encap->next_proto = ip4_hdr_get_proto(hdr); } } static void frag_encap_clean(struct frag_encap *encap) { encap->data = NULL; encap->len = 0; encap->l3_offset = 0; encap->l3_len = 0; encap->next_proto = 0; } static int frag_init(struct frag *frag, struct packet *pkt) { const struct layer_internal *layer = pkt->frag_layer; if (layer->proto == LAYER_PROTO_IPV4) { const struct ip *hdr = (const struct ip *)layer->hdr_ptr; frag->data = layer->pld_ptr; frag->len = layer->pld_len; if ((char *)frag->data + frag->len > pkt->data_ptr + pkt->data_len) { return -1; } frag->offset = ip4_hdr_get_frag_offset(hdr); frag->more = ip4_hdr_get_mf_flag(hdr); frag->pkt = pkt; return 0; } else { const struct ip6_hdr *hdr = (const struct ip6_hdr *)layer->hdr_ptr; const struct ip6_frag *frag_hdr = ip6_hdr_get_frag_ext(hdr); if (frag_hdr == NULL) { return -1; } frag->data = (char *)frag_hdr + sizeof(struct ip6_frag); frag->len = ip6_hdr_get_payload_len(hdr) - sizeof(struct ip6_frag); if ((char *)frag->data + frag->len > pkt->data_ptr + pkt->data_len) { return -1; } frag->offset = ipv6_frag_get_offset(frag_hdr); frag->more = ipv6_frag_get_more(frag_hdr); frag->pkt = pkt; return 0; } } static void frag_clean(struct frag *frag) { frag->data = NULL; frag->len = 0; frag->offset = 0; frag->more = false; frag->pkt = NULL; } static struct frag_queue *ip_reassembly_new_fq(struct ip_reassembly *ip_reass) { struct frag_queue *fq = TAILQ_FIRST(&ip_reass->free_list); if (fq) { TAILQ_REMOVE(&ip_reass->free_list, fq, tqe); return fq; } else { return NULL; } } static void ip_reassembly_free_fq(struct ip_reassembly *ip_reass, struct frag_queue *fq) { if (fq) { TAILQ_INSERT_TAIL(&ip_reass->free_list, fq, tqe); } } static void ip_reassembly_add_fq(struct ip_reassembly *ip_reass, struct frag_queue *fq, const struct frag_key *key, uint64_t now) { static const struct frag zero = { .data = NULL, .len = 0, .offset = 0, .more = false, .pkt = NULL, }; fq->start_time = now; fq->expect_size = UINT32_MAX; fq->recved_size = 0; fq->next_fill = IP_MIN_FRAG_NUM; fq->frag_used = 0; fq->key = *key; fq->frags[IP_LAST_FRAG_IDX] = zero; fq->frags[IP_FIRST_FRAG_IDX] = zero; HASH_ADD(hh, ip_reass->htable, key, sizeof(struct frag_key), fq); TAILQ_INSERT_TAIL(&ip_reass->lru_list, fq, tqe); STAT_INC(&ip_reass->stat, defrags_expected, key) } static void ip_reassembly_del_fq(struct ip_reassembly *ip_reass, struct frag_queue *fq) { if (fq) { HASH_DELETE(hh, ip_reass->htable, fq); TAILQ_REMOVE(&ip_reass->lru_list, fq, tqe); frag_encap_clean(&fq->encap); for (uint32_t i = 0; i < fq->next_fill; i++) { struct frag *frag = &fq->frags[i]; if (frag->pkt) { TAILQ_INSERT_TAIL(&ip_reass->evict_pkt, frag->pkt, frag_tqe); } frag_clean(frag); } STAT_ADD(&ip_reass->stat, frags_freed, &fq->key, fq->frag_used); } } static struct frag_queue *ip_reassembly_find_fq(struct ip_reassembly *ip_reass, struct frag_key *key) { struct frag_queue *fq = NULL; HASH_FIND(hh, ip_reass->htable, key, sizeof(struct frag_key), fq); return fq; } static int ip_reassembly_update_fq(struct ip_reassembly *ip_reass, struct frag_queue *fq, struct frag *frag) { uint32_t idx; /* * Internet Protocol, Version 6 (IPv6) Specification * * https://datatracker.ietf.org/doc/html/rfc8200#section-4.5 * * It should be noted that fragments may be duplicated in the * network. Instead of treating these exact duplicate fragments * as overlapping fragments, an implementation may choose to * detect this case and drop exact duplicate fragments while * keeping the other fragments belonging to the same packet. */ if (frag->offset == 0) { if (fq->frags[IP_FIRST_FRAG_IDX].data != NULL) { IP_DEFRAG_ERROR_WITH_KEY("duplicate first fragment", &fq->key); STAT_INC(&ip_reass->stat, frags_overlap, &fq->key) goto error_out; } idx = IP_FIRST_FRAG_IDX; frag_encap_init(&fq->encap, frag->pkt); } else if (frag->more == 0) { if (fq->frags[IP_LAST_FRAG_IDX].data != NULL) { IP_DEFRAG_ERROR_WITH_KEY("duplicate last fragment", &fq->key); STAT_INC(&ip_reass->stat, frags_overlap, &fq->key) goto error_out; } idx = IP_LAST_FRAG_IDX; fq->expect_size = frag->offset + frag->len; } else { if (fq->next_fill >= ip_reass->fq_size) { IP_DEFRAG_ERROR_WITH_KEY("max number of fragment exceeded", &fq->key); STAT_INC(&ip_reass->stat, frags_too_many, &fq->key) goto error_out; } idx = fq->next_fill; fq->next_fill++; } fq->frag_used++; fq->recved_size += frag->len; TAILQ_REMOVE(&ip_reass->lru_list, fq, tqe); TAILQ_INSERT_TAIL(&ip_reass->lru_list, fq, tqe); memcpy(&fq->frags[idx], frag, sizeof(struct frag)); STAT_INC(&ip_reass->stat, frags_buffered, &fq->key) return 0; error_out: STAT_INC(&ip_reass->stat, defrags_failed, &fq->key) ip_reassembly_del_fq(ip_reass, fq); ip_reassembly_free_fq(ip_reass, fq); TAILQ_INSERT_TAIL(&ip_reass->evict_pkt, frag->pkt, frag_tqe); return -1; } static struct packet *ip_reassembly_defrag_fq(struct ip_reassembly *ip_reass, struct frag_queue *fq) { struct frag *frag = NULL; struct frag *first = &fq->frags[IP_FIRST_FRAG_IDX]; struct frag *last = &fq->frags[IP_LAST_FRAG_IDX]; uint32_t loop = 0; uint16_t last_offset = last->offset; struct ip *ip4_hdr = NULL; struct ip6_hdr *ip6_hdr = NULL; // calculate the length of the reassembled packet uint32_t total_len = fq->expect_size + fq->encap.len; struct packet *pkt = packet_new(total_len); if (pkt == NULL) { IP_REASSEMBLY_LOG_ERROR("unable to allocate memory"); // TODO stat goto error_out; } char *ptr = (char *)packet_get_raw_data(pkt); char *end = ptr + packet_get_raw_len(pkt); // copy last frag if (last->len > end - ptr) { IP_DEFRAG_ERROR_WITH_KEY("last frag length not match expected reassembled length", &fq->key); STAT_INC(&ip_reass->stat, frags_invalid_length, &fq->key) goto error_out; } end -= last->len; memcpy(end, last->data, last->len); while (first->len != last_offset) { /* * https://datatracker.ietf.org/doc/html/rfc791 * * In the case that two or more fragments contain the same data * either identically or through a partial overlap, this procedure * will use the more recently arrived copy in the data buffer and * datagram delivered. */ for (uint32_t i = fq->next_fill - 1; i >= IP_MIN_FRAG_NUM; i--) { frag = &fq->frags[i]; if (frag->offset + frag->len == last_offset) { if (frag->len > end - ptr) { IP_DEFRAG_ERROR_WITH_KEY("middle frag length not match expected reassembled length", &fq->key); STAT_INC(&ip_reass->stat, frags_invalid_length, &fq->key) goto error_out; } end -= frag->len; memcpy(end, frag->data, frag->len); last_offset = frag->offset; break; } } if (loop > fq->next_fill - IP_MIN_FRAG_NUM) { IP_DEFRAG_ERROR_WITH_KEY("overlap appear during frag reassemble", &fq->key); STAT_INC(&ip_reass->stat, frags_overlap, &fq->key) goto error_out; } loop++; } // copy fist frag if (first->len > end - ptr) { IP_DEFRAG_ERROR_WITH_KEY("first frag length not match expected reassembled length", &fq->key); STAT_INC(&ip_reass->stat, frags_invalid_length, &fq->key) goto error_out; } end -= first->len; memcpy(end, first->data, first->len); // copy frag hdr if (fq->encap.len > end - ptr) { IP_DEFRAG_ERROR_WITH_KEY("packet header length not match expected reassembled length", &fq->key); STAT_INC(&ip_reass->stat, frags_invalid_length, &fq->key) goto error_out; } end -= fq->encap.len; memcpy(end, fq->encap.data, fq->encap.len); // assert assert(ptr == end); if (fq->key.ip_version == 4) { // update ip header length & ip checksum ip4_hdr = (struct ip *)(ptr + fq->encap.l3_offset); ip4_hdr_set_total_len(ip4_hdr, total_len - fq->encap.l3_offset); // update ip header length ip4_hdr_set_mf_flag(ip4_hdr, false); // update more fragment flag ip4_hdr_set_frag_offset(ip4_hdr, 0); // update fragment offset ip4_hdr->ip_sum = 0; // update checksum ip4_hdr->ip_sum = checksum((const void *)ip4_hdr, fq->encap.l3_len); } else { // update ipv6 payload length & next header ip6_hdr = (struct ip6_hdr *)(ptr + fq->encap.l3_offset); ip6_hdr_set_payload_len(ip6_hdr, fq->expect_size); // update payload length ip6_hdr_set_next_header(ip6_hdr, fq->encap.next_proto); // update next header } // create a new packet packet_parse(pkt, ptr, total_len); packet_set_defraged(pkt); memcpy(&pkt->meta, &first->pkt->meta, sizeof(struct metadata)); packet_push_frag(pkt, first->pkt); first->pkt = NULL; for (uint32_t i = IP_MIN_FRAG_NUM; i < fq->next_fill; i++) { frag = &fq->frags[i]; packet_push_frag(pkt, frag->pkt); frag->pkt = NULL; } packet_push_frag(pkt, last->pkt); last->pkt = NULL; STAT_INC(&ip_reass->stat, defrags_succeed, &fq->key) ip_reassembly_del_fq(ip_reass, fq); ip_reassembly_free_fq(ip_reass, fq); return pkt; error_out: STAT_INC(&ip_reass->stat, defrags_failed, &fq->key) ip_reassembly_del_fq(ip_reass, fq); ip_reassembly_free_fq(ip_reass, fq); packet_free(pkt); return NULL; } static int frag_queue_is_ready(struct frag_queue *fq) { return (fq->recved_size == fq->expect_size && fq->frags[IP_FIRST_FRAG_IDX].data != NULL); } /****************************************************************************** * core ******************************************************************************/ struct ip_reassembly *ip_reassembly_new(uint64_t timeout_ms, uint64_t fq_num, uint64_t fq_size) { struct ip_reassembly *ip_reass = (struct ip_reassembly *)calloc(1, sizeof(struct ip_reassembly)); if (ip_reass == NULL) { IP_REASSEMBLY_LOG_ERROR("unable to allocate memory for ip_reassembly"); return NULL; } ip_reass->timeout_ms = timeout_ms; ip_reass->fq_num = fq_num; ip_reass->fq_size = fq_size; ip_reass->htable = NULL; TAILQ_INIT(&ip_reass->free_list); TAILQ_INIT(&ip_reass->lru_list); TAILQ_INIT(&ip_reass->evict_pkt); for (uint64_t i = 0; i < ip_reass->fq_num; i++) { struct frag_queue *fq = (struct frag_queue *)calloc(1, sizeof(struct frag_queue) + sizeof(struct frag) * ip_reass->fq_size); if (fq == NULL) { IP_REASSEMBLY_LOG_ERROR("unable to allocate memory for frag_queue"); goto error_out; } TAILQ_INSERT_TAIL(&ip_reass->free_list, fq, tqe); } return ip_reass; error_out: ip_reassembly_free(ip_reass); return NULL; } void ip_reassembly_free(struct ip_reassembly *ip_reass) { struct packet *pkt; struct frag_queue *fq; if (ip_reass) { while ((fq = TAILQ_FIRST(&ip_reass->lru_list))) { STAT_INC(&ip_reass->stat, defrags_failed, &fq->key) ip_reassembly_del_fq(ip_reass, fq); ip_reassembly_free_fq(ip_reass, fq); } while ((fq = TAILQ_FIRST(&ip_reass->free_list))) { TAILQ_REMOVE(&ip_reass->free_list, fq, tqe); free(fq); fq = NULL; } assert(HASH_COUNT(ip_reass->htable) == 0); while ((pkt = TAILQ_FIRST(&ip_reass->evict_pkt))) { TAILQ_REMOVE(&ip_reass->evict_pkt, pkt, frag_tqe); packet_free(pkt); } free(ip_reass); ip_reass = NULL; } } struct packet *ip_reassembly_defrag(struct ip_reassembly *ip_reass, struct packet *pkt, uint64_t now) { struct frag_key key; if (frag_key_init(&key, pkt) != 0) { IP_REASSEMBLY_LOG_ERROR("unable to init frag key"); TAILQ_INSERT_TAIL(&ip_reass->evict_pkt, pkt, frag_tqe); return NULL; } struct frag frag; if (frag_init(&frag, pkt) != 0) { IP_REASSEMBLY_LOG_ERROR("unable to init frag"); TAILQ_INSERT_TAIL(&ip_reass->evict_pkt, pkt, frag_tqe); return NULL; } STAT_INC(&ip_reass->stat, frags, &key) struct frag_queue *fq = ip_reassembly_find_fq(ip_reass, &key); if (fq == NULL) { fq = ip_reassembly_new_fq(ip_reass); if (fq == NULL) { TAILQ_INSERT_TAIL(&ip_reass->evict_pkt, pkt, frag_tqe); STAT_INC(&ip_reass->stat, frags_no_buffer, &key) return NULL; } ip_reassembly_add_fq(ip_reass, fq, &key, now); } if (ip_reassembly_update_fq(ip_reass, fq, &frag) != 0) { return NULL; } if (frag_queue_is_ready(fq)) { return ip_reassembly_defrag_fq(ip_reass, fq); } else { return NULL; } } struct packet *ip_reassembly_clean(struct ip_reassembly *ip_reass, uint64_t now_ms) { struct frag_queue *fq; while ((fq = TAILQ_FIRST(&ip_reass->lru_list))) { if (fq->start_time + ip_reass->timeout_ms > now_ms) { break; } STAT_INC(&ip_reass->stat, defrags_failed, &fq->key) STAT_ADD(&ip_reass->stat, frags_timeout, &fq->key, fq->frag_used) ip_reassembly_del_fq(ip_reass, fq); ip_reassembly_free_fq(ip_reass, fq); } struct packet *pkt = TAILQ_FIRST(&ip_reass->evict_pkt); if (pkt) { TAILQ_REMOVE(&ip_reass->evict_pkt, pkt, frag_tqe); return pkt; } else { return NULL; } } struct ip_reassembly_stat *ip_reassembly_get_stat(struct ip_reassembly *ip_reass) { if (ip_reass) { return &(ip_reass->stat); } else { return NULL; } } void ip_reassembly_print_stat(struct ip_reassembly *ip_reass) { if (ip_reass) { IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_defrags_expected : %lu", ip_reass, ip_reass->stat.ip4_defrags_expected); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_defrags_succeed : %lu", ip_reass, ip_reass->stat.ip4_defrags_succeed); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_defrags_failed : %lu", ip_reass, ip_reass->stat.ip4_defrags_failed); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags : %lu", ip_reass, ip_reass->stat.ip4_frags); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags_freed : %lu", ip_reass, ip_reass->stat.ip4_frags_freed); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags_buffered : %lu", ip_reass, ip_reass->stat.ip4_frags_buffered); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags_no_buffer : %lu", ip_reass, ip_reass->stat.ip4_frags_no_buffer); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags_timeout : %lu", ip_reass, ip_reass->stat.ip4_frags_timeout); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags_invalid_length : %lu", ip_reass, ip_reass->stat.ip4_frags_invalid_length); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags_overlap : %lu", ip_reass, ip_reass->stat.ip4_frags_overlap); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip4_frags_too_many : %lu", ip_reass, ip_reass->stat.ip4_frags_too_many); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_defrags_expected : %lu", ip_reass, ip_reass->stat.ip6_defrags_expected); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_defrags_succeed : %lu", ip_reass, ip_reass->stat.ip6_defrags_succeed); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_defrags_failed : %lu", ip_reass, ip_reass->stat.ip6_defrags_failed); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags : %lu", ip_reass, ip_reass->stat.ip6_frags); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags_freed : %lu", ip_reass, ip_reass->stat.ip6_frags_freed); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags_buffered : %lu", ip_reass, ip_reass->stat.ip6_frags_buffered); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags_no_buffer : %lu", ip_reass, ip_reass->stat.ip6_frags_no_buffer); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags_timeout : %lu", ip_reass, ip_reass->stat.ip6_frags_timeout); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags_invalid_length : %lu", ip_reass, ip_reass->stat.ip6_frags_invalid_length); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags_overlap : %lu", ip_reass, ip_reass->stat.ip6_frags_overlap); IP_REASSEMBLY_LOG_INFO("ip_reass: %p, ip6_frags_too_many : %lu", ip_reass, ip_reass->stat.ip6_frags_too_many); } } uint64_t ip_reassembly_stat_get(struct ip_reassembly_stat *stat, enum ip_reass_stat_type type) { switch (type) { #define XX(_type, _name) \ case _type: \ return stat->_name; IP_REASS_STAT_MAP(XX) #undef XX default: return 0; } }