static verdict validate_inner6(struct xlation *state, struct pkt_metadata const *outer_meta) { union { struct ipv6hdr ip6; struct frag_hdr frag; struct icmp6hdr icmp; } buffer; union { struct ipv6hdr *ip6; struct frag_hdr *frag; struct icmp6hdr *icmp; } ptr; struct pkt_metadata meta; verdict result; ptr.ip6 = skb_hdr_ptr(state->in.skb, outer_meta->payload_offset, buffer.ip6); if (!ptr.ip6) return truncated(state, "inner IPv6 header"); if (unlikely(ptr.ip6->version != 6)) return inhdr6(state, "Version is not 6."); result = summarize_skb6(state, outer_meta->payload_offset, &meta); if (result != VERDICT_CONTINUE) return result; if (meta.has_frag_hdr) { ptr.frag = skb_hdr_ptr(state->in.skb, meta.frag_offset, buffer.frag); if (!ptr.frag) return truncated(state, "inner fragment header"); if (!is_first_frag6(ptr.frag)) return inhdr6(state, "Inner packet is not a first fragment."); } if (meta.l4_proto == L4PROTO_ICMP) { ptr.icmp = skb_hdr_ptr(state->in.skb, meta.l4_offset, buffer.icmp); if (!ptr.icmp) return truncated(state, "inner ICMPv6 header"); if (has_inner_pkt6(ptr.icmp->icmp6_type)) return inhdr6(state, "Packet inside packet inside packet."); } if (!pskb_may_pull(state->in.skb, meta.payload_offset)) { log_debug("Could not 'pull' the headers out of the skb."); return truncated(state, "inner headers"); } return VERDICT_CONTINUE; }
static struct reassembly_buffer *add_pkt(struct packet *pkt) { struct reassembly_buffer *buffer; struct frag_hdr *hdr_frag = pkt_frag_hdr(pkt); unsigned int payload_len; /* Does it already exist? If so, add to and return existing buffer */ buffer = fragdb_table_get(&table, pkt); if (buffer) { if (WARN(is_first_frag6(hdr_frag), "Non-first fragment's offset is zero." COMMON_MSG)) return NULL; *buffer->next_slot = pkt->skb; buffer->next_slot = &pkt->skb->next; /* Why this? Dunno, both defrags do it when they support frag_list. */ /* * Note, since we're in the middle of its sort of initialization, pkt is illegal at this * point. It looks like we should call pkt_payload_len_frag() instead of * pkt_payload_len_pkt(), but that's not the case because it represents a subsequent * fragment. Be careful with the calculation of this length. */ payload_len = pkt_payload_len_pkt(pkt); buffer->pkt.skb->len += payload_len; buffer->pkt.skb->data_len += payload_len; buffer->pkt.skb->truesize += pkt->skb->truesize; skb_pull(pkt->skb, pkt_hdrs_len(pkt)); return buffer; } if (WARN(!is_first_frag6(hdr_frag), "First fragment's offset is nonzero." COMMON_MSG)) return NULL; /* * TODO (fine) Maybe pskb_expand_head() can be used here as fallback. * I decided to leave this as is for the moment because it's such an * obnoxious ridiculous corner case scenario and it will probably never * cause any problems. * Until somebody complains, I think I should probably work on more * pressing stuff. * I learned about pskb_expand_head() in the defrag modules. */ if (skb_cloned(pkt->skb)) { log_debug("Packet is cloned, so I can't edit its shared area. Canceling translation."); return NULL; } /* Create buffer, add the packet to it, index */ buffer = kmem_cache_alloc(buffer_cache, GFP_ATOMIC); if (!buffer) return NULL; buffer->pkt = *pkt; buffer->pkt.original_pkt = &buffer->pkt; buffer->next_slot = &skb_shinfo(pkt->skb)->frag_list; buffer->dying_time = jiffies + config_get_ttl_frag(); if (is_error(fragdb_table_put(&table, pkt, buffer))) { kmem_cache_free(buffer_cache, buffer); return NULL; } /* Schedule for automatic deletion */ list_add(&buffer->list_hook, expire_list.prev); if (!timer_pending(&expire_timer)) { mod_timer(&expire_timer, buffer->dying_time); log_debug("The fragment cleaning timer will awake in %u msecs.", jiffies_to_msecs(expire_timer.expires - jiffies)); } return buffer; }
/** * Walks through @skb's headers, collecting data and adding it to @meta. * * @hdr6_offset number of bytes between skb->data and the IPv6 header. * * BTW: You might want to read summarize_skb4() first, since it's a lot simpler. */ static verdict summarize_skb6(struct xlation *state, unsigned int hdr6_offset, struct pkt_metadata *meta) { union { struct ipv6_opt_hdr opt; struct frag_hdr frag; struct tcphdr tcp; } buffer; union { struct ipv6_opt_hdr *opt; struct frag_hdr *frag; struct tcphdr *tcp; u8 *nexthdr; } ptr; struct sk_buff *skb = state->in.skb; u8 nexthdr; unsigned int offset; bool is_first = true; ptr.nexthdr = skb_hdr_ptr(skb, hdr6_offset + offsetof(struct ipv6hdr, nexthdr), nexthdr); if (!ptr.nexthdr) return truncated(state, "IPv6 header"); nexthdr = *ptr.nexthdr; offset = hdr6_offset + sizeof(struct ipv6hdr); meta->has_frag_hdr = false; do { switch (nexthdr) { case NEXTHDR_TCP: meta->l4_proto = L4PROTO_TCP; meta->l4_offset = offset; meta->payload_offset = offset; if (is_first) { ptr.tcp = skb_hdr_ptr(skb, offset, buffer.tcp); if (!ptr.tcp) return truncated(state, "TCP header"); meta->payload_offset += tcp_hdr_len(ptr.tcp); } return VERDICT_CONTINUE; case NEXTHDR_UDP: meta->l4_proto = L4PROTO_UDP; meta->l4_offset = offset; meta->payload_offset = is_first ? (offset + sizeof(struct udphdr)) : offset; return VERDICT_CONTINUE; case NEXTHDR_ICMP: meta->l4_proto = L4PROTO_ICMP; meta->l4_offset = offset; meta->payload_offset = is_first ? (offset + sizeof(struct icmp6hdr)) : offset; return VERDICT_CONTINUE; case NEXTHDR_FRAGMENT: ptr.frag = skb_hdr_ptr(skb, offset, buffer.frag); if (!ptr.frag) return truncated(state, "fragment header"); meta->has_frag_hdr = true; meta->frag_offset = offset; is_first = is_first_frag6(ptr.frag); offset += sizeof(struct frag_hdr); nexthdr = ptr.frag->nexthdr; break; case NEXTHDR_HOP: case NEXTHDR_ROUTING: case NEXTHDR_DEST: ptr.opt = skb_hdr_ptr(skb, offset, buffer.opt); if (!ptr.opt) return truncated(state, "extension header"); offset += ipv6_optlen(ptr.opt); nexthdr = ptr.opt->nexthdr; break; default: meta->l4_proto = L4PROTO_OTHER; meta->l4_offset = offset; meta->payload_offset = offset; return VERDICT_CONTINUE; } } while (true); return VERDICT_CONTINUE; /* whatever. */ }