#include #include #include #include #include "checksum.h" #include "crc32_hash.h" #include "log_private.h" #include "ip_reassembly.h" #include "packet_parser.h" #include "packet_helper.h" #include "packet_private.h" #define IP_REASSEMBLE_DEBUG(format, ...) STELLAR_LOG_DEBUG(__thread_local_logger, "ip_reassembly", format, ##__VA_ARGS__) #define IP_REASSEMBLE_ERROR(format, ...) STELLAR_LOG_ERROR(__thread_local_logger, "ip_reassembly", format, ##__VA_ARGS__) #define IPV4_KEYLEN 1 #define IPV6_KEYLEN 4 #define PRIME_VALUE 0xeaad8405 #define IP_FRAG_HASH_FNUM 2 #define IP_FRAG_TBL_POS(assy, sig) ((assy)->table + ((sig) & (assy)->entry_mask)) #define KEY_TO_STR(key, str_str, dst_str) \ do \ { \ if ((key)->src_dst_len == IPV4_KEYLEN) \ { \ uint32_t src_addr = (key)->src_dst_addr[0] >> 32; \ uint32_t dst_addr = (key)->src_dst_addr[0] & 0xffffffff; \ inet_ntop(AF_INET, &src_addr, src_str, INET6_ADDRSTRLEN); \ inet_ntop(AF_INET, &dst_addr, dst_str, INET6_ADDRSTRLEN); \ } \ else \ { \ inet_ntop(AF_INET6, &(key)->src_dst_addr[0], src_str, INET6_ADDRSTRLEN); \ inet_ntop(AF_INET6, &(key)->src_dst_addr[2], dst_str, INET6_ADDRSTRLEN); \ } \ } while (0) #define IP_REASSEMBLE_DEBUG1(desc, key, ...) \ do \ { \ char src_str[INET6_ADDRSTRLEN] = {0}; \ char dst_str[INET6_ADDRSTRLEN] = {0}; \ KEY_TO_STR(key, src_str, dst_str); \ IP_REASSEMBLE_DEBUG("%s (%s->%s 0x%0x)", desc, src_str, dst_str, (key)->ip_id); \ } while (0) #define IP_REASSEMBLE_ERROR1(desc, key, ...) \ do \ { \ char src_str[INET6_ADDRSTRLEN] = {0}; \ char dst_str[INET6_ADDRSTRLEN] = {0}; \ KEY_TO_STR(key, src_str, dst_str); \ IP_REASSEMBLE_ERROR("%s (%s->%s 0x%0x)", desc, src_str, dst_str, (key)->ip_id); \ } while (0) /****************************************************************************** * Structs ******************************************************************************/ enum { IP_LAST_FRAG_IDX, IP_FIRST_FRAG_IDX, IP_MIN_FRAG_NUM, IP_MAX_FRAG_NUM = 64, }; struct ip_frag_hdr { void *hdr_data; // need be freed uint16_t hdr_len; uint16_t l3_offset; uint16_t l3_len; uint8_t next_proto; }; struct ip_frag { void *data; // need be freed uint16_t len; uint16_t offset; }; struct ip_frag_key { uint64_t src_dst_addr[4]; // src and dst address (only first 8 bytes used for IPv4) uint32_t src_dst_len; uint32_t ip_id; // ipv4: identification is uint16_t; ipv6: identification is uint32_t uint8_t proto; }; struct ip_frag_pkt { struct { struct ip_frag_pkt *tqe_next; struct ip_frag_pkt **tqe_prev; } lru; struct ip_frag_key key; struct ip_frag_hdr hdr; uint64_t create_time; uint32_t expected_total_size; uint32_t received_frag_size; uint32_t next_fill_idx; struct ip_frag frags[IP_MAX_FRAG_NUM]; // first two entries in the frags[] array are for the last and first fragments. }; struct ip_reassembly { // options bool enable; uint32_t timeout; uint32_t bucket_entries; // runtime uint32_t entry_used; uint32_t entry_total; uint32_t entry_mask; // stats struct ip_reassembly_stat stat; // hash table struct { struct ip_frag_pkt *tqh_first; struct ip_frag_pkt **tqh_last; } lru; struct ip_frag_pkt *last; struct ip_frag_pkt *table; // array of ip_frag_pkt }; /****************************************************************************** * utils ******************************************************************************/ #define IP_REASSEMBLY_STAT_INC(stat, filed, key) \ { \ if ((key)->src_dst_len == IPV4_KEYLEN) \ { \ (stat)->ip4_##filed++; \ } \ else \ { \ (stat)->ip6_##filed++; \ } \ } static inline void *memdup(const void *src, size_t len) { if (src == NULL || len == 0) { return NULL; } void *dst = malloc(len); if (dst == NULL) { return NULL; } return memcpy(dst, src, len); } static inline uint32_t combine32ms1b(uint32_t x) { x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return x; } static inline uint32_t align32pow2(uint32_t x) { x--; x = combine32ms1b(x); return x + 1; } static inline int is_power_of_2(uint32_t n) { return n && !(n & (n - 1)); } static int check_options(const struct ip_reassembly_options *opts) { if (opts == NULL) { IP_REASSEMBLE_DEBUG("invalid options"); return -1; } if (opts->enable) { if (opts->timeout < 1 || opts->timeout > 60000) { IP_REASSEMBLE_DEBUG("invalid timeout: %u, supported range: [1, 60000]", opts->timeout); return -1; } if (opts->bucket_entries < 1 || is_power_of_2(opts->bucket_entries) == 0) { IP_REASSEMBLE_DEBUG("invalid bucket_entries: %u, must be power of 2", opts->bucket_entries); return -1; } if (opts->bucket_num == 0) { IP_REASSEMBLE_DEBUG("invalid bucket_num: %u, supported range: [1, 4294967295]", opts->bucket_num); return -1; } } return 0; } /****************************************************************************** * ip frag key ******************************************************************************/ static inline void ipv4_frag_key_hash(const struct ip_frag_key *key, uint32_t *value1, uint32_t *value2) { uint32_t v = 0; const uint32_t *p = (const uint32_t *)&key->src_dst_addr; v = crc32_hash_4byte(p[0], PRIME_VALUE); v = crc32_hash_4byte(p[1], v); v = crc32_hash_4byte(key->ip_id, v); *value1 = v; *value2 = (v << 7) + (v >> 14); } static inline void ipv6_frag_key_hash(const struct ip_frag_key *key, uint32_t *value1, uint32_t *value2) { uint32_t v = 0; const uint32_t *p = (const uint32_t *)&key->src_dst_addr; v = crc32_hash_4byte(p[0], PRIME_VALUE); v = crc32_hash_4byte(p[1], v); v = crc32_hash_4byte(p[2], v); v = crc32_hash_4byte(p[3], v); v = crc32_hash_4byte(p[4], v); v = crc32_hash_4byte(p[5], v); v = crc32_hash_4byte(p[6], v); v = crc32_hash_4byte(p[7], v); v = crc32_hash_4byte(key->ip_id, v); *value1 = v; *value2 = (v << 7) + (v >> 14); } static inline uint64_t ip_frag_key_cmp(const struct ip_frag_key *key1, const struct ip_frag_key *key2) { if (key1->ip_id != key2->ip_id) { return 1; } if (key1->src_dst_len != key2->src_dst_len) { return 1; } for (uint32_t i = 0; i < key1->src_dst_len; i++) { if (key1->src_dst_addr[i] != key2->src_dst_addr[i]) { return 1; } } return 0; } static inline int ip_frag_key_is_empty(const struct ip_frag_key *key) { return (key->src_dst_len == 0); } static inline void ip_frag_key_zero(struct ip_frag_key *key) { key->src_dst_addr[0] = 0; key->src_dst_addr[1] = 0; key->src_dst_addr[2] = 0; key->src_dst_addr[3] = 0; key->src_dst_len = 0; key->ip_id = 0; key->proto = 0; } /****************************************************************************** * ip frag hdr ******************************************************************************/ static inline void ip_frag_hdr_init(struct ip_frag_hdr *hdr, const struct packet *pkt) { struct layer_private *layer = pkt->frag_layer; if (layer->proto == LAYER_PROTO_IPV6) { struct ip6_frag *frag_ext = ip6_hdr_get_frag_ext((const struct ip6_hdr *)layer->hdr_ptr); hdr->next_proto = frag_ext->ip6f_nxt; } else { hdr->next_proto = ip4_hdr_get_proto((const struct ip *)layer->hdr_ptr); } hdr->l3_offset = layer->hdr_offset; hdr->l3_len = layer->hdr_len; hdr->hdr_len = layer->hdr_offset + layer->hdr_len; hdr->hdr_data = memdup(pkt->data_ptr, hdr->hdr_len); } static inline void ip_frag_hdr_free(struct ip_frag_hdr *hdr) { hdr->next_proto = 0; hdr->l3_offset = 0; hdr->l3_len = 0; hdr->hdr_len = 0; if (hdr->hdr_data != NULL) { free(hdr->hdr_data); hdr->hdr_data = NULL; } } /****************************************************************************** * ip frag ******************************************************************************/ static inline void ip_frag_init(struct ip_frag *frag, void *data, uint16_t len, uint16_t offset) { frag->data = memdup(data, len); frag->len = len; frag->offset = offset; } static inline int ip_frag_free(struct ip_frag *frag) { int ret = -1; if (frag) { if (frag->data) { free(frag->data); frag->data = NULL; ret = 0; } frag->len = 0; frag->offset = 0; } return ret; } /****************************************************************************** * ip frag pkt ******************************************************************************/ static inline void ip_frag_pkt_init(struct ip_frag_pkt *frag_pkt, const struct ip_frag_key *key, uint64_t now) { static const struct ip_frag zero_frag = { .data = NULL, .len = 0, .offset = 0, }; frag_pkt->lru.tqe_next = NULL; frag_pkt->lru.tqe_prev = NULL; frag_pkt->key = *key; frag_pkt->create_time = now; frag_pkt->expected_total_size = UINT32_MAX; frag_pkt->received_frag_size = 0; frag_pkt->next_fill_idx = IP_MIN_FRAG_NUM; frag_pkt->frags[IP_LAST_FRAG_IDX] = zero_frag; frag_pkt->frags[IP_FIRST_FRAG_IDX] = zero_frag; } static inline void ip_frag_pkt_clean(struct ip_reassembly_stat *stat, struct ip_frag_pkt *frag_pkt) { for (uint32_t i = 0; i < IP_MAX_FRAG_NUM; i++) { struct ip_frag *frag = &frag_pkt->frags[i]; if (ip_frag_free(frag) == 0) { IP_REASSEMBLY_STAT_INC(stat, frags_freed, &frag_pkt->key); } } ip_frag_key_zero(&frag_pkt->key); ip_frag_hdr_free(&frag_pkt->hdr); } static inline int ip_frag_pkt_is_ready(struct ip_frag_pkt *frag_pkt) { return (frag_pkt->received_frag_size == frag_pkt->expected_total_size && frag_pkt->frags[IP_FIRST_FRAG_IDX].data != NULL); } static inline void ip_reassembly_add_frag_pkt(struct ip_reassembly *assy, struct ip_frag_pkt *frag_pkt, const struct ip_frag_key *key, uint64_t now) { ip_frag_pkt_init(frag_pkt, key, now); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_expected, &frag_pkt->key); TAILQ_INSERT_TAIL(&assy->lru, frag_pkt, lru); assy->entry_used++; } static inline void ip_reassembly_del_frag_pkt(struct ip_reassembly *assy, struct ip_frag_pkt *frag_pkt) { TAILQ_REMOVE(&assy->lru, frag_pkt, lru); assy->entry_used--; ip_frag_pkt_clean(&assy->stat, frag_pkt); } // return 0 : success // return -1 : failed static inline int ip_frag_pkt_update(struct ip_reassembly *assy, struct ip_frag_pkt *frag_pkt, const struct packet *pkt, char *frag_data, uint16_t frag_len, uint16_t frag_offset, bool more_frags) { 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 (frag_pkt->frags[IP_FIRST_FRAG_IDX].data != NULL) { IP_REASSEMBLE_DEBUG1("duplicate first fragment bypass", &frag_pkt->key); IP_REASSEMBLY_STAT_INC(&assy->stat, frags_bypass_dup_fist_frag, &frag_pkt->key); return 0; } idx = IP_FIRST_FRAG_IDX; ip_frag_hdr_init(&frag_pkt->hdr, pkt); } else if (more_frags == 0) { if (frag_pkt->frags[IP_LAST_FRAG_IDX].data != NULL) { IP_REASSEMBLE_DEBUG1("duplicate last fragment bypass", &frag_pkt->key); IP_REASSEMBLY_STAT_INC(&assy->stat, frags_bypass_dup_last_frag, &frag_pkt->key); return 0; } idx = IP_LAST_FRAG_IDX; frag_pkt->expected_total_size = frag_offset + frag_len; } else { if (frag_pkt->next_fill_idx >= IP_MAX_FRAG_NUM) { IP_REASSEMBLE_ERROR1("max number of fragment exceeded", &frag_pkt->key); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_failed_too_many_frag, &frag_pkt->key); ip_reassembly_del_frag_pkt(assy, frag_pkt); return -1; } idx = frag_pkt->next_fill_idx; frag_pkt->next_fill_idx++; } frag_pkt->received_frag_size += frag_len; struct ip_frag *frag = &frag_pkt->frags[idx]; ip_frag_init(frag, frag_data, frag_len, frag_offset); IP_REASSEMBLY_STAT_INC(&assy->stat, frags_buffered, &frag_pkt->key); return 0; } /* * if return NULL, then *free and *expired are valid * free : the first empty entry in the bucket * expired: the first timed-out entry in the bucket */ static struct ip_frag_pkt *ip_reassembly_find_frag_pkt(struct ip_reassembly *assy, const struct ip_frag_key *key, struct ip_frag_pkt **free, struct ip_frag_pkt **expired, uint64_t now) { if (assy->last != NULL && ip_frag_key_cmp(key, &assy->last->key) == 0) { return assy->last; } uint32_t sig1 = 0; uint32_t sig2 = 0; if (key->src_dst_len == IPV4_KEYLEN) { ipv4_frag_key_hash(key, &sig1, &sig2); } else { ipv6_frag_key_hash(key, &sig1, &sig2); } // get the bucket by hash struct ip_frag_pkt *p1 = IP_FRAG_TBL_POS(assy, sig1); struct ip_frag_pkt *p2 = IP_FRAG_TBL_POS(assy, sig2); // search in the bucket struct ip_frag_pkt *old = NULL; struct ip_frag_pkt *empty = NULL; uint64_t timeout = assy->timeout; uint32_t assoc = assy->bucket_entries; for (uint32_t i = 0; i != assoc; i++) { if (ip_frag_key_cmp(key, &p1[i].key) == 0) { *free = NULL; *expired = NULL; return p1 + i; } else if (ip_frag_key_is_empty(&p1[i].key)) { empty = (empty == NULL) ? (p1 + i) : empty; } else if (timeout + p1[i].create_time <= now) { old = (old == NULL) ? (p1 + i) : old; } if (ip_frag_key_cmp(key, &p2[i].key) == 0) { *free = NULL; *expired = NULL; return p2 + i; } else if (ip_frag_key_is_empty(&p2[i].key)) { empty = (empty == NULL) ? (p2 + i) : empty; } else if (timeout + p2[i].create_time <= now) { old = (old == NULL) ? (p2 + i) : old; } } *free = empty; *expired = old; return NULL; } static struct ip_frag_pkt *ip_reassembly_update_frag_pkt(struct ip_reassembly *assy, const struct ip_frag_key *key, uint64_t now) { struct ip_frag_pkt *frag_pkt = NULL; struct ip_frag_pkt *free = NULL; struct ip_frag_pkt *expired = NULL; frag_pkt = ip_reassembly_find_frag_pkt(assy, key, &free, &expired, now); if (frag_pkt == NULL) { if (expired) { IP_REASSEMBLE_DEBUG1("add ip frag pkt success: reuse expired entry", key); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_failed_timeout, key); ip_reassembly_del_frag_pkt(assy, expired); ip_reassembly_add_frag_pkt(assy, expired, key, now); assy->last = expired; return expired; } if (free) { IP_REASSEMBLE_DEBUG1("add ip frag pkt success: use free entry", key); ip_reassembly_add_frag_pkt(assy, free, key, now); assy->last = free; return free; } // no space IP_REASSEMBLE_ERROR1("add ip frag pkt failed: bucket full", key); IP_REASSEMBLY_STAT_INC(&assy->stat, frags_bypass_no_buffer, key); return NULL; } else { // expired if (assy->timeout + frag_pkt->create_time <= now) { IP_REASSEMBLE_DEBUG1("add ip frag pkt success: reuse expired entry", key); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_failed_timeout, key); ip_reassembly_del_frag_pkt(assy, frag_pkt); ip_reassembly_add_frag_pkt(assy, frag_pkt, key, now); assy->last = frag_pkt; return frag_pkt; } // not expired else { IP_REASSEMBLE_DEBUG1("find ip frag pkt success: not expire", key); assy->last = frag_pkt; return frag_pkt; } } } /****************************************************************************** * frag reassemble ******************************************************************************/ static struct packet *ip_frag_reassemble(struct ip_reassembly *assy, struct ip_frag_pkt *frag_pkt) { struct ip_frag *first = &frag_pkt->frags[IP_FIRST_FRAG_IDX]; struct ip_frag *last = &frag_pkt->frags[IP_LAST_FRAG_IDX]; struct ip_frag *temp = NULL; 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 packet_len = frag_pkt->expected_total_size + frag_pkt->hdr.hdr_len; struct packet *pkt = packet_new(packet_len); if (pkt == NULL) { IP_REASSEMBLE_ERROR("unable to allocate memory"); return NULL; } 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_REASSEMBLE_ERROR1("last frag length not match expected reassembled length", &frag_pkt->key); goto error_out_invalid_length; } 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 = frag_pkt->next_fill_idx - 1; i >= IP_MIN_FRAG_NUM; i--) { temp = &frag_pkt->frags[i]; if (temp->offset + temp->len == last_offset) { if (temp->len > end - ptr) { IP_REASSEMBLE_ERROR1("middle frag length not match expected reassembled length", &frag_pkt->key); goto error_out_invalid_length; } end -= temp->len; memcpy(end, temp->data, temp->len); last_offset = temp->offset; break; } } if (loop > frag_pkt->next_fill_idx - IP_MIN_FRAG_NUM) { IP_REASSEMBLE_ERROR1("overlap appear during frag reassemble", &frag_pkt->key); goto error_out_overlap; } loop++; } // copy fist fragment data if (first->len > end - ptr) { IP_REASSEMBLE_ERROR1("first frag length not match expected reassembled length", &frag_pkt->key); goto error_out_invalid_length; } end -= first->len; memcpy(end, first->data, first->len); // copy frag hdr if (frag_pkt->hdr.hdr_len > end - ptr) { IP_REASSEMBLE_ERROR1("packet header length not match expected reassembled length", &frag_pkt->key); goto error_out_invalid_length; } end -= frag_pkt->hdr.hdr_len; memcpy(end, frag_pkt->hdr.hdr_data, frag_pkt->hdr.hdr_len); // assert assert(ptr == end); if (frag_pkt->key.src_dst_len == IPV4_KEYLEN) { // update ip total length & ip checksum ip4_hdr = (struct ip *)(ptr + frag_pkt->hdr.l3_offset); ip4_hdr_set_total_len(ip4_hdr, packet_len - frag_pkt->hdr.l3_offset); // update total 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, frag_pkt->hdr.l3_len); } else { // update ipv6 payload length & next header ip6_hdr = (struct ip6_hdr *)(ptr + frag_pkt->hdr.l3_offset); ip6_hdr_set_payload_len(ip6_hdr, frag_pkt->expected_total_size); // update payload length ip6_hdr_set_next_header(ip6_hdr, frag_pkt->hdr.next_proto); // update next header } // create a new packet packet_parse(pkt, ptr, packet_len); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_succeed, &frag_pkt->key); ip_reassembly_del_frag_pkt(assy, frag_pkt); return pkt; error_out_invalid_length: packet_free(pkt); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_failed_invalid_length, &frag_pkt->key); ip_reassembly_del_frag_pkt(assy, frag_pkt); return NULL; error_out_overlap: packet_free(pkt); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_failed_overlap, &frag_pkt->key); ip_reassembly_del_frag_pkt(assy, frag_pkt); return NULL; } /****************************************************************************** * Public API ******************************************************************************/ struct ip_reassembly *ip_reassembly_new(const struct ip_reassembly_options *opts) { if (check_options(opts) == -1) { return NULL; } struct ip_reassembly *assy = (struct ip_reassembly *)calloc(1, sizeof(struct ip_reassembly)); if (assy == NULL) { IP_REASSEMBLE_ERROR("unable to allocate memory"); return NULL; } assy->enable = opts->enable; assy->timeout = opts->timeout; assy->bucket_entries = opts->bucket_entries; if (!assy->enable) { return assy; } uint64_t entry_total = align32pow2(opts->bucket_num) * assy->bucket_entries * IP_FRAG_HASH_FNUM; if (entry_total > UINT32_MAX) { IP_REASSEMBLE_ERROR("bucket_num * bucket_entries is too large"); free(assy); return NULL; } assy->entry_total = (uint32_t)entry_total; assy->entry_mask = (assy->entry_total - 1) & ~(assy->bucket_entries - 1); assy->table = (struct ip_frag_pkt *)calloc(assy->entry_total, sizeof(struct ip_frag_pkt)); if (assy->table == NULL) { IP_REASSEMBLE_ERROR("unable to allocate memory"); free(assy); return NULL; } TAILQ_INIT(&(assy->lru)); return assy; } void ip_reassembly_free(struct ip_reassembly *assy) { if (assy) { if (assy->table) { for (uint32_t i = 0; i < assy->entry_total; i++) { ip_frag_pkt_clean(&assy->stat, assy->table + i); } free(assy->table); assy->table = NULL; } free(assy); assy = NULL; } } void ip_reassembly_expire(struct ip_reassembly *assy, uint64_t max_free, uint64_t now) { uint64_t count = 0; struct ip_frag_pkt *frag_pkt = NULL; uint64_t timeout = assy->timeout; TAILQ_FOREACH(frag_pkt, &assy->lru, lru) { if (timeout + frag_pkt->create_time <= now) { IP_REASSEMBLE_DEBUG1("expire ip frag pkt: discarding old fragmented packets", &frag_pkt->key); IP_REASSEMBLY_STAT_INC(&assy->stat, defrags_failed_timeout, &frag_pkt->key); ip_reassembly_del_frag_pkt(assy, frag_pkt); count++; if (count >= max_free) { break; } } else { break; } } } struct ip_reassembly_stat *ip_reassembly_stat(struct ip_reassembly *assy) { if (assy) { return &(assy->stat); } else { return NULL; } } /* * Returns the reassembled packet, or NULL if the packet is not reassembled * The returned packet should be freed by calling the packet_free() function */ struct packet *ip_reassembly_packet(struct ip_reassembly *assy, const struct packet *pkt, uint64_t now) { struct packet *pkt1; struct packet *pkt2; if (!assy->enable) { return NULL; } const struct layer_private *layer = pkt->frag_layer; if (layer == NULL) { return NULL; } if (layer->proto == LAYER_PROTO_IPV4) { pkt1 = ipv4_reassembly_packet(assy, pkt, now); if (pkt1 && pkt1->frag_layer) { pkt2 = ip_reassembly_packet(assy, pkt1, now); packet_free(pkt1); return pkt2; } return pkt1; } else if (layer->proto == LAYER_PROTO_IPV6) { pkt1 = ipv6_reassembly_packet(assy, pkt, now); if (pkt1 && pkt1->frag_layer) { pkt2 = ip_reassembly_packet(assy, pkt1, now); packet_free(pkt1); return pkt2; } return pkt1; } else { return NULL; } } struct packet *ipv4_reassembly_packet(struct ip_reassembly *assy, const struct packet *pkt, uint64_t now) { const struct layer_private *layer = pkt->frag_layer; const struct ip *hdr = (const struct ip *)layer->hdr_ptr; uint16_t frag_len = ip4_hdr_get_total_len(hdr) - ip4_hdr_get_hdr_len(hdr); if (frag_len > layer->pld_len) { IP_REASSEMBLE_ERROR("unexpected header length, ip id: %lu", ip4_hdr_get_ipid(hdr)); return NULL; } struct ip_frag_key key = {}; uint64_t src_addr = hdr->ip_src.s_addr; uint64_t dst_addr = hdr->ip_dst.s_addr; key.src_dst_addr[0] = src_addr << 32 | dst_addr; key.src_dst_len = IPV4_KEYLEN; key.ip_id = ip4_hdr_get_ipid(hdr); key.proto = ip4_hdr_get_proto(hdr); IP_REASSEMBLY_STAT_INC(&assy->stat, frags, &key); struct ip_frag_pkt *frag_pkt = ip_reassembly_update_frag_pkt(assy, &key, now); if (frag_pkt == NULL) { return NULL; } char *frag_data = (char *)layer->pld_ptr; bool more_frags = ip4_hdr_get_mf_flag(hdr); uint16_t frag_offset = ip4_hdr_get_frag_offset(hdr); if (ip_frag_pkt_update(assy, frag_pkt, pkt, frag_data, frag_len, frag_offset, more_frags) != 0) { return NULL; } if (!ip_frag_pkt_is_ready(frag_pkt)) { return NULL; } return ip_frag_reassemble(assy, frag_pkt); } /* * 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 */ /* * 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| * +-----------------+-----------------+--------+--------+-//-+--------+ */ struct packet *ipv6_reassembly_packet(struct ip_reassembly *assy, const struct packet *pkt, uint64_t now) { const struct layer_private *layer = pkt->frag_layer; 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 NULL; } char *frag_data = (char *)frag_hdr + sizeof(struct ip6_frag); uint16_t frag_len = ip6_hdr_get_payload_len(hdr) - sizeof(struct ip6_frag); if (frag_data + frag_len > pkt->data_ptr + pkt->data_len) { IP_REASSEMBLE_ERROR("unexpected header length, frag id: %lu", ipv6_frag_get_ident(frag_hdr)); return NULL; } struct ip_frag_key key = {}; memcpy(&key.src_dst_addr[0], hdr->ip6_src.s6_addr, 16); memcpy(&key.src_dst_addr[2], hdr->ip6_dst.s6_addr, 16); key.src_dst_len = IPV6_KEYLEN; key.ip_id = ipv6_frag_get_ident(frag_hdr); key.proto = 0; // only first fragment has the upper layer protocol IP_REASSEMBLY_STAT_INC(&assy->stat, frags, &key); struct ip_frag_pkt *frag_pkt = ip_reassembly_update_frag_pkt(assy, &key, now); if (frag_pkt == NULL) { return NULL; } bool more_frags = ipv6_frag_get_more(frag_hdr); uint16_t frag_offset = ipv6_frag_get_offset(frag_hdr); if (ip_frag_pkt_update(assy, frag_pkt, pkt, frag_data, frag_len, frag_offset, more_frags) != 0) { return NULL; } if (!ip_frag_pkt_is_ready(frag_pkt)) { return NULL; } return ip_frag_reassemble(assy, frag_pkt); }