u8 *proto);
 
 extern int                     ipv6_skip_exthdr(const struct sk_buff *, int start,
-                                                u8 *nexthdrp);
+                                                u8 *nexthdrp, __be16 *frag_offp);
 
 extern int                     ipv6_ext_hdr(u8 nexthdr);
 
 
        const struct ipv6hdr *ip6h;
        u8 icmp6_type;
        u8 nexthdr;
+       __be16 frag_off;
        unsigned len;
        int offset;
        int err;
                return -EINVAL;
 
        nexthdr = ip6h->nexthdr;
-       offset = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr);
+       offset = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr, &frag_off);
 
        if (offset < 0 || nexthdr != IPPROTO_ICMPV6)
                return 0;
 
                return false;
        if (info->bitmask & EBT_IP6_PROTO) {
                uint8_t nexthdr = ih6->nexthdr;
+               __be16 frag_off;
                int offset_ph;
 
-               offset_ph = ipv6_skip_exthdr(skb, sizeof(_ip6h), &nexthdr);
+               offset_ph = ipv6_skip_exthdr(skb, sizeof(_ip6h), &nexthdr, &frag_off);
                if (offset_ph == -1)
                        return false;
                if (FWINV(info->protocol != nexthdr, EBT_IP6_PROTO))
 
                const struct ipv6hdr *ih;
                struct ipv6hdr _iph;
                uint8_t nexthdr;
+               __be16 frag_off;
                int offset_ph;
 
                ih = skb_header_pointer(skb, 0, sizeof(_iph), &_iph);
                printk(" IPv6 SRC=%pI6 IPv6 DST=%pI6, IPv6 priority=0x%01X, Next Header=%d",
                       &ih->saddr, &ih->daddr, ih->priority, ih->nexthdr);
                nexthdr = ih->nexthdr;
-               offset_ph = ipv6_skip_exthdr(skb, sizeof(_iph), &nexthdr);
+               offset_ph = ipv6_skip_exthdr(skb, sizeof(_iph), &nexthdr, &frag_off);
                if (offset_ph == -1)
                        goto out;
                print_ports(skb, nexthdr, offset_ph);
 
  *         it returns NULL.
  *       - First fragment header is skipped, not-first ones
  *         are considered as unparsable.
+ *       - Reports the offset field of the final fragment header so it is
+ *         possible to tell whether this is a first fragment, later fragment,
+ *         or not fragmented.
  *       - ESP is unparsable for now and considered like
  *         normal payload protocol.
  *       - Note also special handling of AUTH header. Thanks to IPsec wizards.
  * --ANK (980726)
  */
 
-int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp)
+int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
+                    __be16 *frag_offp)
 {
        u8 nexthdr = *nexthdrp;
 
+       *frag_offp = 0;
+
        while (ipv6_ext_hdr(nexthdr)) {
                struct ipv6_opt_hdr _hdr, *hp;
                int hdrlen;
                        if (fp == NULL)
                                return -1;
 
-                       if (ntohs(*fp) & ~0x7)
+                       *frag_offp = *fp;
+                       if (ntohs(*frag_offp) & ~0x7)
                                break;
                        hdrlen = 8;
                } else if (nexthdr == NEXTHDR_AUTH)
 
        int ptr = (u8 *)(ipv6_hdr(skb) + 1) - skb->data;
        int len = skb->len - ptr;
        __u8 nexthdr = ipv6_hdr(skb)->nexthdr;
+       __be16 frag_off;
 
        if (len < 0)
                return 1;
 
-       ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr);
+       ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, &frag_off);
        if (ptr < 0)
                return 0;
        if (nexthdr == IPPROTO_ICMPV6) {
        int inner_offset;
        int hash;
        u8 nexthdr;
+       __be16 frag_off;
 
        if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
                return;
        nexthdr = ((struct ipv6hdr *)skb->data)->nexthdr;
        if (ipv6_ext_hdr(nexthdr)) {
                /* now skip over extension headers */
-               inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr);
+               inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr),
+                                               &nexthdr, &frag_off);
                if (inner_offset<0)
                        return;
        } else {
 
                        u8 *ptr = skb_network_header(skb) + opt->ra;
                        struct icmp6hdr *icmp6;
                        u8 nexthdr = hdr->nexthdr;
+                       __be16 frag_off;
                        int offset;
 
                        /* Check if the value of Router Alert
                                        goto out;
                                }
                                offset = ipv6_skip_exthdr(skb, sizeof(*hdr),
-                                                         &nexthdr);
+                                                         &nexthdr, &frag_off);
                                if (offset < 0)
                                        goto out;
 
 
 {
        struct ipv6hdr *hdr = ipv6_hdr(skb);
        u8 nexthdr = hdr->nexthdr;
+       __be16 frag_off;
        int offset;
 
        if (ipv6_ext_hdr(nexthdr)) {
-               offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr);
+               offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr, &frag_off);
                if (offset < 0)
                        return 0;
        } else
 
        const __u8 tclass = DEFAULT_TOS_VALUE;
        struct dst_entry *dst = NULL;
        u8 proto;
+       __be16 frag_off;
        struct flowi6 fl6;
 
        if ((!(ipv6_addr_type(&oip6h->saddr) & IPV6_ADDR_UNICAST)) ||
        }
 
        proto = oip6h->nexthdr;
-       tcphoff = ipv6_skip_exthdr(oldskb, ((u8*)(oip6h+1) - oldskb->data), &proto);
+       tcphoff = ipv6_skip_exthdr(oldskb, ((u8*)(oip6h+1) - oldskb->data), &proto, &frag_off);
 
        if ((tcphoff < 0) || (tcphoff > oldskb->len)) {
                pr_debug("Cannot get TCP header.\n");
 
 {
        int protoff;
        u8 nexthdr;
+       __be16 frag_off;
 
        nexthdr = ipv6_hdr(skb)->nexthdr;
-       protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr);
+       protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
+                                  &frag_off);
        if (protoff < 0)
                return false;
 
 
        struct ipv6hdr _ip6h;
        const struct ipv6hdr *ih;
        u8 nexthdr;
+       __be16 frag_off;
        int offset;
 
        ih = skb_header_pointer(skb, skb_network_offset(skb), sizeof(_ip6h), &_ip6h);
 
        nexthdr = ih->nexthdr;
        offset = ipv6_skip_exthdr(skb, skb_network_offset(skb) + sizeof(_ip6h),
-                                 &nexthdr);
+                                 &nexthdr, &frag_off);
 
        audit_log_format(ab, " saddr=%pI6c daddr=%pI6c proto=%hhu",
                         &ih->saddr, &ih->daddr, nexthdr);
 
 {
        struct ipv6hdr *ipv6h = ipv6_hdr(skb);
        u8 nexthdr;
+       __be16 frag_off;
        int tcphoff;
        int ret;
 
        nexthdr = ipv6h->nexthdr;
-       tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
+       tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
        if (tcphoff < 0)
                return NF_DROP;
        ret = tcpmss_mangle_packet(skb, par->targinfo,
 
        struct ipv6hdr *ipv6h = ipv6_hdr(skb);
        int tcphoff;
        u_int8_t nexthdr;
+       __be16 frag_off;
 
        nexthdr = ipv6h->nexthdr;
-       tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
+       tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
        if (tcphoff < 0)
                return NF_DROP;
 
 
 {
        __be16 _ports[2], *ports;
        u8 nexthdr;
+       __be16 frag_off;
        int poff;
 
        memset(dst, 0, sizeof(*dst));
                      (XT_HASHLIMIT_HASH_DPT | XT_HASHLIMIT_HASH_SPT)))
                        return 0;
                nexthdr = ipv6_hdr(skb)->nexthdr;
-               protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr);
+               protoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, &frag_off);
                if ((int)protoff < 0)
                        return -1;
                break;
 
        struct icmp6hdr *icmph, _icmph;
        __be16 *ports, _ports[2];
        u8 inside_nexthdr;
+       __be16 inside_fragoff;
        int inside_hdrlen;
 
        icmph = skb_header_pointer(skb, outside_hdrlen,
                return 1;
        inside_nexthdr = inside_iph->nexthdr;
 
-       inside_hdrlen = ipv6_skip_exthdr(skb, outside_hdrlen + sizeof(_icmph) + sizeof(_inside_iph), &inside_nexthdr);
+       inside_hdrlen = ipv6_skip_exthdr(skb, outside_hdrlen + sizeof(_icmph) + sizeof(_inside_iph),
+                                        &inside_nexthdr, &inside_fragoff);
        if (inside_hdrlen < 0)
                return 1; /* hjm: Packet has no/incomplete transport layer headers. */
 
 
        int offset, ret = 0;
        struct ipv6hdr *ip6;
        u8 nexthdr;
+       __be16 frag_off;
 
        ip6 = ipv6_hdr(skb);
        if (ip6 == NULL)
        offset = skb_network_offset(skb);
        offset += sizeof(*ip6);
        nexthdr = ip6->nexthdr;
-       offset = ipv6_skip_exthdr(skb, offset, &nexthdr);
+       offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off);
        if (offset < 0)
                return 0;
        if (proto)
 
        u8 nexthdr;
        int ret = -EINVAL, offset;
        struct ipv6hdr _ipv6h, *ip6;
+       __be16 frag_off;
 
        offset = skb_network_offset(skb);
        ip6 = skb_header_pointer(skb, offset, sizeof(_ipv6h), &_ipv6h);
 
        nexthdr = ip6->nexthdr;
        offset += sizeof(_ipv6h);
-       offset = ipv6_skip_exthdr(skb, offset, &nexthdr);
+       offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off);
        if (offset < 0)
                goto out;