Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/jesse/openvswitch
authorDavid S. Miller <davem@davemloft.net>
Fri, 30 Nov 2012 17:01:30 +0000 (12:01 -0500)
committerDavid S. Miller <davem@davemloft.net>
Fri, 30 Nov 2012 17:01:30 +0000 (12:01 -0500)
Conflicts:
net/ipv6/exthdrs_core.c

Jesse Gross says:

====================
This series of improvements for 3.8/net-next contains four components:
 * Support for modifying IPv6 headers
 * Support for matching and setting skb->mark for better integration with
   things like iptables
 * Ability to recognize the EtherType for RARP packets
 * Two small performance enhancements

The movement of ipv6_find_hdr() into exthdrs_core.c causes two small merge
conflicts.  I left it as is but can do the merge if you want.  The conflicts
are:
 * ipv6_find_hdr() and ipv6_find_tlv() were both moved to the bottom of
   exthdrs_core.c.  Both should stay.
 * A new use of ipv6_find_hdr() was added to net/netfilter/ipvs/ip_vs_core.c
   after this patch.  The IPVS user has two instances of the old constant
   name IP6T_FH_F_FRAG which has been renamed to IP6_FH_F_FRAG.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
1  2 
net/ipv6/exthdrs_core.c
net/ipv6/netfilter/ip6_tables.c
net/netfilter/ipvs/ip_vs_core.c

diff --combined net/ipv6/exthdrs_core.c
index e7d756e19d1d60b1ff6e125b7533454391c1ac31,11b4e29c8452bb82a5d0a45e254f19f78f15908c..c5e83fae4df423ccbe02bed8bf31ffd415014ad2
@@@ -112,46 -112,125 +112,170 @@@ int ipv6_skip_exthdr(const struct sk_bu
  }
  EXPORT_SYMBOL(ipv6_skip_exthdr);
  
 +int ipv6_find_tlv(struct sk_buff *skb, int offset, int type)
 +{
 +      const unsigned char *nh = skb_network_header(skb);
 +      int packet_len = skb->tail - skb->network_header;
 +      struct ipv6_opt_hdr *hdr;
 +      int len;
 +
 +      if (offset + 2 > packet_len)
 +              goto bad;
 +      hdr = (struct ipv6_opt_hdr *)(nh + offset);
 +      len = ((hdr->hdrlen + 1) << 3);
 +
 +      if (offset + len > packet_len)
 +              goto bad;
 +
 +      offset += 2;
 +      len -= 2;
 +
 +      while (len > 0) {
 +              int opttype = nh[offset];
 +              int optlen;
 +
 +              if (opttype == type)
 +                      return offset;
 +
 +              switch (opttype) {
 +              case IPV6_TLV_PAD1:
 +                      optlen = 1;
 +                      break;
 +              default:
 +                      optlen = nh[offset + 1] + 2;
 +                      if (optlen > len)
 +                              goto bad;
 +                      break;
 +              }
 +              offset += optlen;
 +              len -= optlen;
 +      }
 +      /* not_found */
 + bad:
 +      return -1;
 +}
 +EXPORT_SYMBOL_GPL(ipv6_find_tlv);
++
+ /*
+  * find the offset to specified header or the protocol number of last header
+  * if target < 0. "last header" is transport protocol header, ESP, or
+  * "No next header".
+  *
+  * Note that *offset is used as input/output parameter. an if it is not zero,
+  * then it must be a valid offset to an inner IPv6 header. This can be used
+  * to explore inner IPv6 header, eg. ICMPv6 error messages.
+  *
+  * If target header is found, its offset is set in *offset and return protocol
+  * number. Otherwise, return -1.
+  *
+  * If the first fragment doesn't contain the final protocol header or
+  * NEXTHDR_NONE it is considered invalid.
+  *
+  * Note that non-1st fragment is special case that "the protocol number
+  * of last header" is "next header" field in Fragment header. In this case,
+  * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
+  * isn't NULL.
+  *
+  * if flags is not NULL and it's a fragment, then the frag flag
+  * IP6_FH_F_FRAG will be set. If it's an AH header, the
+  * IP6_FH_F_AUTH flag is set and target < 0, then this function will
+  * stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this
+  * function will skip all those routing headers, where segements_left was 0.
+  */
+ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
+                 int target, unsigned short *fragoff, int *flags)
+ {
+       unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
+       u8 nexthdr = ipv6_hdr(skb)->nexthdr;
+       unsigned int len;
+       bool found;
+       if (fragoff)
+               *fragoff = 0;
+       if (*offset) {
+               struct ipv6hdr _ip6, *ip6;
+               ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
+               if (!ip6 || (ip6->version != 6)) {
+                       printk(KERN_ERR "IPv6 header not found\n");
+                       return -EBADMSG;
+               }
+               start = *offset + sizeof(struct ipv6hdr);
+               nexthdr = ip6->nexthdr;
+       }
+       len = skb->len - start;
+       do {
+               struct ipv6_opt_hdr _hdr, *hp;
+               unsigned int hdrlen;
+               found = (nexthdr == target);
+               if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
+                       if (target < 0)
+                               break;
+                       return -ENOENT;
+               }
+               hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
+               if (hp == NULL)
+                       return -EBADMSG;
+               if (nexthdr == NEXTHDR_ROUTING) {
+                       struct ipv6_rt_hdr _rh, *rh;
+                       rh = skb_header_pointer(skb, start, sizeof(_rh),
+                                               &_rh);
+                       if (rh == NULL)
+                               return -EBADMSG;
+                       if (flags && (*flags & IP6_FH_F_SKIP_RH) &&
+                           rh->segments_left == 0)
+                               found = false;
+               }
+               if (nexthdr == NEXTHDR_FRAGMENT) {
+                       unsigned short _frag_off;
+                       __be16 *fp;
+                       if (flags)      /* Indicate that this is a fragment */
+                               *flags |= IP6_FH_F_FRAG;
+                       fp = skb_header_pointer(skb,
+                                               start+offsetof(struct frag_hdr,
+                                                              frag_off),
+                                               sizeof(_frag_off),
+                                               &_frag_off);
+                       if (fp == NULL)
+                               return -EBADMSG;
+                       _frag_off = ntohs(*fp) & ~0x7;
+                       if (_frag_off) {
+                               if (target < 0 &&
+                                   ((!ipv6_ext_hdr(hp->nexthdr)) ||
+                                    hp->nexthdr == NEXTHDR_NONE)) {
+                                       if (fragoff)
+                                               *fragoff = _frag_off;
+                                       return hp->nexthdr;
+                               }
+                               return -ENOENT;
+                       }
+                       hdrlen = 8;
+               } else if (nexthdr == NEXTHDR_AUTH) {
+                       if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))
+                               break;
+                       hdrlen = (hp->hdrlen + 2) << 2;
+               } else
+                       hdrlen = ipv6_optlen(hp);
+               if (!found) {
+                       nexthdr = hp->nexthdr;
+                       len -= hdrlen;
+                       start += hdrlen;
+               }
+       } while (!found);
+       *offset = start;
+       return nexthdr;
+ }
+ EXPORT_SYMBOL(ipv6_find_hdr);
++
index 74cadd0719a585525e4c0505d7b1a92a54c1dd83,1ce4f157ce4f726a8992e55bfb5508f6092ea7a6..125a90d6a795967f13fcdfd19b3c24e24ce02504
@@@ -207,7 -207,8 +207,7 @@@ ip6t_get_target_c(const struct ip6t_ent
        return ip6t_get_target((struct ip6t_entry *)e);
  }
  
 -#if defined(CONFIG_NETFILTER_XT_TARGET_TRACE) || \
 -    defined(CONFIG_NETFILTER_XT_TARGET_TRACE_MODULE)
 +#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
  /* This cries for unification! */
  static const char *const hooknames[] = {
        [NF_INET_PRE_ROUTING]           = "PREROUTING",
@@@ -380,7 -381,8 +380,7 @@@ ip6t_do_table(struct sk_buff *skb
                t = ip6t_get_target_c(e);
                IP_NF_ASSERT(t->u.kernel.target);
  
 -#if defined(CONFIG_NETFILTER_XT_TARGET_TRACE) || \
 -    defined(CONFIG_NETFILTER_XT_TARGET_TRACE_MODULE)
 +#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE)
                /* The packet is traced: log it */
                if (unlikely(skb->nf_trace))
                        trace_packet(skb, hook, in, out,
@@@ -1854,7 -1856,7 +1854,7 @@@ compat_do_ip6t_set_ctl(struct sock *sk
  {
        int ret;
  
 -      if (!capable(CAP_NET_ADMIN))
 +      if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
                return -EPERM;
  
        switch (cmd) {
@@@ -1969,7 -1971,7 +1969,7 @@@ compat_do_ip6t_get_ctl(struct sock *sk
  {
        int ret;
  
 -      if (!capable(CAP_NET_ADMIN))
 +      if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
                return -EPERM;
  
        switch (cmd) {
@@@ -1991,7 -1993,7 +1991,7 @@@ do_ip6t_set_ctl(struct sock *sk, int cm
  {
        int ret;
  
 -      if (!capable(CAP_NET_ADMIN))
 +      if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
                return -EPERM;
  
        switch (cmd) {
@@@ -2016,7 -2018,7 +2016,7 @@@ do_ip6t_get_ctl(struct sock *sk, int cm
  {
        int ret;
  
 -      if (!capable(CAP_NET_ADMIN))
 +      if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
                return -EPERM;
  
        switch (cmd) {
@@@ -2271,112 -2273,9 +2271,9 @@@ static void __exit ip6_tables_fini(void
        unregister_pernet_subsys(&ip6_tables_net_ops);
  }
  
- /*
-  * find the offset to specified header or the protocol number of last header
-  * if target < 0. "last header" is transport protocol header, ESP, or
-  * "No next header".
-  *
-  * Note that *offset is used as input/output parameter. an if it is not zero,
-  * then it must be a valid offset to an inner IPv6 header. This can be used
-  * to explore inner IPv6 header, eg. ICMPv6 error messages.
-  *
-  * If target header is found, its offset is set in *offset and return protocol
-  * number. Otherwise, return -1.
-  *
-  * If the first fragment doesn't contain the final protocol header or
-  * NEXTHDR_NONE it is considered invalid.
-  *
-  * Note that non-1st fragment is special case that "the protocol number
-  * of last header" is "next header" field in Fragment header. In this case,
-  * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
-  * isn't NULL.
-  *
-  * if flags is not NULL and it's a fragment, then the frag flag IP6T_FH_F_FRAG
-  * will be set. If it's an AH header, the IP6T_FH_F_AUTH flag is set and
-  * target < 0, then this function will stop at the AH header.
-  */
- int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
-                 int target, unsigned short *fragoff, int *flags)
- {
-       unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
-       u8 nexthdr = ipv6_hdr(skb)->nexthdr;
-       unsigned int len;
-       if (fragoff)
-               *fragoff = 0;
-       if (*offset) {
-               struct ipv6hdr _ip6, *ip6;
-               ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
-               if (!ip6 || (ip6->version != 6)) {
-                       printk(KERN_ERR "IPv6 header not found\n");
-                       return -EBADMSG;
-               }
-               start = *offset + sizeof(struct ipv6hdr);
-               nexthdr = ip6->nexthdr;
-       }
-       len = skb->len - start;
-       while (nexthdr != target) {
-               struct ipv6_opt_hdr _hdr, *hp;
-               unsigned int hdrlen;
-               if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
-                       if (target < 0)
-                               break;
-                       return -ENOENT;
-               }
-               hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
-               if (hp == NULL)
-                       return -EBADMSG;
-               if (nexthdr == NEXTHDR_FRAGMENT) {
-                       unsigned short _frag_off;
-                       __be16 *fp;
-                       if (flags)      /* Indicate that this is a fragment */
-                               *flags |= IP6T_FH_F_FRAG;
-                       fp = skb_header_pointer(skb,
-                                               start+offsetof(struct frag_hdr,
-                                                              frag_off),
-                                               sizeof(_frag_off),
-                                               &_frag_off);
-                       if (fp == NULL)
-                               return -EBADMSG;
-                       _frag_off = ntohs(*fp) & ~0x7;
-                       if (_frag_off) {
-                               if (target < 0 &&
-                                   ((!ipv6_ext_hdr(hp->nexthdr)) ||
-                                    hp->nexthdr == NEXTHDR_NONE)) {
-                                       if (fragoff)
-                                               *fragoff = _frag_off;
-                                       return hp->nexthdr;
-                               }
-                               return -ENOENT;
-                       }
-                       hdrlen = 8;
-               } else if (nexthdr == NEXTHDR_AUTH) {
-                       if (flags && (*flags & IP6T_FH_F_AUTH) && (target < 0))
-                               break;
-                       hdrlen = (hp->hdrlen + 2) << 2;
-               } else
-                       hdrlen = ipv6_optlen(hp);
-               nexthdr = hp->nexthdr;
-               len -= hdrlen;
-               start += hdrlen;
-       }
-       *offset = start;
-       return nexthdr;
- }
  EXPORT_SYMBOL(ip6t_register_table);
  EXPORT_SYMBOL(ip6t_unregister_table);
  EXPORT_SYMBOL(ip6t_do_table);
- EXPORT_SYMBOL(ipv6_find_hdr);
  
  module_init(ip6_tables_init);
  module_exit(ip6_tables_fini);
index fb45640dc1fb7dff034c9909302b1bf355877c6d,58918e20f9d5b038c2181b893b0e0458dbd3a8f2..47edf5a40a5939d2401dc11baa89142b9bcd64b6
@@@ -222,10 -222,11 +222,10 @@@ ip_vs_conn_fill_param_persist(const str
   */
  static struct ip_vs_conn *
  ip_vs_sched_persist(struct ip_vs_service *svc,
 -                  struct sk_buff *skb,
 -                  __be16 src_port, __be16 dst_port, int *ignored)
 +                  struct sk_buff *skb, __be16 src_port, __be16 dst_port,
 +                  int *ignored, struct ip_vs_iphdr *iph)
  {
        struct ip_vs_conn *cp = NULL;
 -      struct ip_vs_iphdr iph;
        struct ip_vs_dest *dest;
        struct ip_vs_conn *ct;
        __be16 dport = 0;               /* destination port to forward */
        union nf_inet_addr snet;        /* source network of the client,
                                           after masking */
  
 -      ip_vs_fill_iphdr(svc->af, skb_network_header(skb), &iph);
 -
        /* Mask saddr with the netmask to adjust template granularity */
  #ifdef CONFIG_IP_VS_IPV6
        if (svc->af == AF_INET6)
 -              ipv6_addr_prefix(&snet.in6, &iph.saddr.in6, svc->netmask);
 +              ipv6_addr_prefix(&snet.in6, &iph->saddr.in6, svc->netmask);
        else
  #endif
 -              snet.ip = iph.saddr.ip & svc->netmask;
 +              snet.ip = iph->saddr.ip & svc->netmask;
  
        IP_VS_DBG_BUF(6, "p-schedule: src %s:%u dest %s:%u "
                      "mnet %s\n",
 -                    IP_VS_DBG_ADDR(svc->af, &iph.saddr), ntohs(src_port),
 -                    IP_VS_DBG_ADDR(svc->af, &iph.daddr), ntohs(dst_port),
 +                    IP_VS_DBG_ADDR(svc->af, &iph->saddr), ntohs(src_port),
 +                    IP_VS_DBG_ADDR(svc->af, &iph->daddr), ntohs(dst_port),
                      IP_VS_DBG_ADDR(svc->af, &snet));
  
        /*
         * is created for other persistent services.
         */
        {
 -              int protocol = iph.protocol;
 -              const union nf_inet_addr *vaddr = &iph.daddr;
 +              int protocol = iph->protocol;
 +              const union nf_inet_addr *vaddr = &iph->daddr;
                __be16 vport = 0;
  
                if (dst_port == svc->port) {
                dport = dest->port;
  
        flags = (svc->flags & IP_VS_SVC_F_ONEPACKET
 -               && iph.protocol == IPPROTO_UDP)?
 +               && iph->protocol == IPPROTO_UDP) ?
                IP_VS_CONN_F_ONE_PACKET : 0;
  
        /*
         *    Create a new connection according to the template
         */
 -      ip_vs_conn_fill_param(svc->net, svc->af, iph.protocol, &iph.saddr,
 -                            src_port, &iph.daddr, dst_port, &param);
 +      ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol, &iph->saddr,
 +                            src_port, &iph->daddr, dst_port, &param);
  
        cp = ip_vs_conn_new(&param, &dest->addr, dport, flags, dest, skb->mark);
        if (cp == NULL) {
   */
  struct ip_vs_conn *
  ip_vs_schedule(struct ip_vs_service *svc, struct sk_buff *skb,
 -             struct ip_vs_proto_data *pd, int *ignored)
 +             struct ip_vs_proto_data *pd, int *ignored,
 +             struct ip_vs_iphdr *iph)
  {
        struct ip_vs_protocol *pp = pd->pp;
        struct ip_vs_conn *cp = NULL;
 -      struct ip_vs_iphdr iph;
        struct ip_vs_dest *dest;
        __be16 _ports[2], *pptr;
        unsigned int flags;
  
        *ignored = 1;
 -      ip_vs_fill_iphdr(svc->af, skb_network_header(skb), &iph);
 -      pptr = skb_header_pointer(skb, iph.len, sizeof(_ports), _ports);
 +      /*
 +       * IPv6 frags, only the first hit here.
 +       */
 +      pptr = frag_safe_skb_hp(skb, iph->len, sizeof(_ports), _ports, iph);
        if (pptr == NULL)
                return NULL;
  
         *    Do not schedule replies from local real server.
         */
        if ((!skb->dev || skb->dev->flags & IFF_LOOPBACK) &&
 -          (cp = pp->conn_in_get(svc->af, skb, &iph, iph.len, 1))) {
 +          (cp = pp->conn_in_get(svc->af, skb, iph, 1))) {
                IP_VS_DBG_PKT(12, svc->af, pp, skb, 0,
                              "Not scheduling reply for existing connection");
                __ip_vs_conn_put(cp);
         *    Persistent service
         */
        if (svc->flags & IP_VS_SVC_F_PERSISTENT)
 -              return ip_vs_sched_persist(svc, skb, pptr[0], pptr[1], ignored);
 +              return ip_vs_sched_persist(svc, skb, pptr[0], pptr[1], ignored,
 +                                         iph);
  
        *ignored = 0;
  
        }
  
        flags = (svc->flags & IP_VS_SVC_F_ONEPACKET
 -               && iph.protocol == IPPROTO_UDP)?
 +               && iph->protocol == IPPROTO_UDP) ?
                IP_VS_CONN_F_ONE_PACKET : 0;
  
        /*
        {
                struct ip_vs_conn_param p;
  
 -              ip_vs_conn_fill_param(svc->net, svc->af, iph.protocol,
 -                                    &iph.saddr, pptr[0], &iph.daddr, pptr[1],
 -                                    &p);
 +              ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol,
 +                                    &iph->saddr, pptr[0], &iph->daddr,
 +                                    pptr[1], &p);
                cp = ip_vs_conn_new(&p, &dest->addr,
                                    dest->port ? dest->port : pptr[1],
                                    flags, dest, skb->mark);
   *  no destination is available for a new connection.
   */
  int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb,
 -              struct ip_vs_proto_data *pd)
 +              struct ip_vs_proto_data *pd, struct ip_vs_iphdr *iph)
  {
        __be16 _ports[2], *pptr;
 -      struct ip_vs_iphdr iph;
  #ifdef CONFIG_SYSCTL
        struct net *net;
        struct netns_ipvs *ipvs;
        int unicast;
  #endif
  
 -      ip_vs_fill_iphdr(svc->af, skb_network_header(skb), &iph);
 -
 -      pptr = skb_header_pointer(skb, iph.len, sizeof(_ports), _ports);
 +      pptr = frag_safe_skb_hp(skb, iph->len, sizeof(_ports), _ports, iph);
        if (pptr == NULL) {
                ip_vs_service_put(svc);
                return NF_DROP;
  
  #ifdef CONFIG_IP_VS_IPV6
        if (svc->af == AF_INET6)
 -              unicast = ipv6_addr_type(&iph.daddr.in6) & IPV6_ADDR_UNICAST;
 +              unicast = ipv6_addr_type(&iph->daddr.in6) & IPV6_ADDR_UNICAST;
        else
  #endif
 -              unicast = (inet_addr_type(net, iph.daddr.ip) == RTN_UNICAST);
 +              unicast = (inet_addr_type(net, iph->daddr.ip) == RTN_UNICAST);
  
        /* if it is fwmark-based service, the cache_bypass sysctl is up
           and the destination is a non-local unicast, then create
                int ret;
                struct ip_vs_conn *cp;
                unsigned int flags = (svc->flags & IP_VS_SVC_F_ONEPACKET &&
 -                                    iph.protocol == IPPROTO_UDP)?
 +                                    iph->protocol == IPPROTO_UDP) ?
                                      IP_VS_CONN_F_ONE_PACKET : 0;
                union nf_inet_addr daddr =  { .all = { 0, 0, 0, 0 } };
  
                IP_VS_DBG(6, "%s(): create a cache_bypass entry\n", __func__);
                {
                        struct ip_vs_conn_param p;
 -                      ip_vs_conn_fill_param(svc->net, svc->af, iph.protocol,
 -                                            &iph.saddr, pptr[0],
 -                                            &iph.daddr, pptr[1], &p);
 +                      ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol,
 +                                            &iph->saddr, pptr[0],
 +                                            &iph->daddr, pptr[1], &p);
                        cp = ip_vs_conn_new(&p, &daddr, 0,
                                            IP_VS_CONN_F_BYPASS | flags,
                                            NULL, skb->mark);
                ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd);
  
                /* transmit the first SYN packet */
 -              ret = cp->packet_xmit(skb, cp, pd->pp);
 +              ret = cp->packet_xmit(skb, cp, pd->pp, iph);
                /* do not touch skb anymore */
  
                atomic_inc(&cp->in_pkts);
@@@ -651,6 -654,14 +651,6 @@@ static inline int ip_vs_gather_frags(st
        return err;
  }
  
 -#ifdef CONFIG_IP_VS_IPV6
 -static inline int ip_vs_gather_frags_v6(struct sk_buff *skb, u_int32_t user)
 -{
 -      /* TODO IPv6: Find out what to do here for IPv6 */
 -      return 0;
 -}
 -#endif
 -
  static int ip_vs_route_me_harder(int af, struct sk_buff *skb)
  {
  #ifdef CONFIG_IP_VS_IPV6
@@@ -721,19 -732,10 +721,19 @@@ void ip_vs_nat_icmp_v6(struct sk_buff *
                    struct ip_vs_conn *cp, int inout)
  {
        struct ipv6hdr *iph      = ipv6_hdr(skb);
 -      unsigned int icmp_offset = sizeof(struct ipv6hdr);
 -      struct icmp6hdr *icmph   = (struct icmp6hdr *)(skb_network_header(skb) +
 -                                                    icmp_offset);
 -      struct ipv6hdr *ciph     = (struct ipv6hdr *)(icmph + 1);
 +      unsigned int icmp_offset = 0;
 +      unsigned int offs        = 0; /* header offset*/
 +      int protocol;
 +      struct icmp6hdr *icmph;
 +      struct ipv6hdr *ciph;
 +      unsigned short fragoffs;
 +
 +      ipv6_find_hdr(skb, &icmp_offset, IPPROTO_ICMPV6, &fragoffs, NULL);
 +      icmph = (struct icmp6hdr *)(skb_network_header(skb) + icmp_offset);
 +      offs = icmp_offset + sizeof(struct icmp6hdr);
 +      ciph = (struct ipv6hdr *)(skb_network_header(skb) + offs);
 +
 +      protocol = ipv6_find_hdr(skb, &offs, -1, &fragoffs, NULL);
  
        if (inout) {
                iph->saddr = cp->vaddr.in6;
        }
  
        /* the TCP/UDP/SCTP port */
 -      if (IPPROTO_TCP == ciph->nexthdr || IPPROTO_UDP == ciph->nexthdr ||
 -          IPPROTO_SCTP == ciph->nexthdr) {
 -              __be16 *ports = (void *)ciph + sizeof(struct ipv6hdr);
 +      if (!fragoffs && (IPPROTO_TCP == protocol || IPPROTO_UDP == protocol ||
 +                        IPPROTO_SCTP == protocol)) {
 +              __be16 *ports = (void *)(skb_network_header(skb) + offs);
  
 +              IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__,
 +                            ntohs(inout ? ports[1] : ports[0]),
 +                            ntohs(inout ? cp->vport : cp->dport));
                if (inout)
                        ports[1] = cp->vport;
                else
@@@ -899,35 -898,51 +899,35 @@@ static int ip_vs_out_icmp(struct sk_buf
        IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
                      "Checking outgoing ICMP for");
  
 -      offset += cih->ihl * 4;
 -
 -      ip_vs_fill_iphdr(AF_INET, cih, &ciph);
 +      ip_vs_fill_ip4hdr(cih, &ciph);
 +      ciph.len += offset;
        /* The embedded headers contain source and dest in reverse order */
 -      cp = pp->conn_out_get(AF_INET, skb, &ciph, offset, 1);
 +      cp = pp->conn_out_get(AF_INET, skb, &ciph, 1);
        if (!cp)
                return NF_ACCEPT;
  
        snet.ip = iph->saddr;
        return handle_response_icmp(AF_INET, skb, &snet, cih->protocol, cp,
 -                                  pp, offset, ihl);
 +                                  pp, ciph.len, ihl);
  }
  
  #ifdef CONFIG_IP_VS_IPV6
  static int ip_vs_out_icmp_v6(struct sk_buff *skb, int *related,
 -                           unsigned int hooknum)
 +                           unsigned int hooknum, struct ip_vs_iphdr *ipvsh)
  {
 -      struct ipv6hdr *iph;
        struct icmp6hdr _icmph, *ic;
 -      struct ipv6hdr  _ciph, *cih;    /* The ip header contained
 -                                         within the ICMP */
 -      struct ip_vs_iphdr ciph;
 +      struct ipv6hdr _ip6h, *ip6h; /* The ip header contained within ICMP */
 +      struct ip_vs_iphdr ciph = {.flags = 0, .fragoffs = 0};/*Contained IP */
        struct ip_vs_conn *cp;
        struct ip_vs_protocol *pp;
 -      unsigned int offset;
        union nf_inet_addr snet;
 +      unsigned int writable;
  
        *related = 1;
 -
 -      /* reassemble IP fragments */
 -      if (ipv6_hdr(skb)->nexthdr == IPPROTO_FRAGMENT) {
 -              if (ip_vs_gather_frags_v6(skb, ip_vs_defrag_user(hooknum)))
 -                      return NF_STOLEN;
 -      }
 -
 -      iph = ipv6_hdr(skb);
 -      offset = sizeof(struct ipv6hdr);
 -      ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph);
 +      ic = frag_safe_skb_hp(skb, ipvsh->len, sizeof(_icmph), &_icmph, ipvsh);
        if (ic == NULL)
                return NF_DROP;
  
 -      IP_VS_DBG(12, "Outgoing ICMPv6 (%d,%d) %pI6->%pI6\n",
 -                ic->icmp6_type, ntohs(icmpv6_id(ic)),
 -                &iph->saddr, &iph->daddr);
 -
        /*
         * Work through seeing if this is for us.
         * These checks are supposed to be in an order that means easy
         * this means that some packets will manage to get a long way
         * down this stack and then be rejected, but that's life.
         */
 -      if ((ic->icmp6_type != ICMPV6_DEST_UNREACH) &&
 -          (ic->icmp6_type != ICMPV6_PKT_TOOBIG) &&
 -          (ic->icmp6_type != ICMPV6_TIME_EXCEED)) {
 +      if (ic->icmp6_type & ICMPV6_INFOMSG_MASK) {
                *related = 0;
                return NF_ACCEPT;
        }
-       if (ipvsh->flags & IP6T_FH_F_FRAG)
 +      /* Fragment header that is before ICMP header tells us that:
 +       * it's not an error message since they can't be fragmented.
 +       */
++      if (ipvsh->flags & IP6_FH_F_FRAG)
 +              return NF_DROP;
 +
 +      IP_VS_DBG(8, "Outgoing ICMPv6 (%d,%d) %pI6c->%pI6c\n",
 +                ic->icmp6_type, ntohs(icmpv6_id(ic)),
 +                &ipvsh->saddr, &ipvsh->daddr);
  
        /* Now find the contained IP header */
 -      offset += sizeof(_icmph);
 -      cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
 -      if (cih == NULL)
 +      ciph.len = ipvsh->len + sizeof(_icmph);
 +      ip6h = skb_header_pointer(skb, ciph.len, sizeof(_ip6h), &_ip6h);
 +      if (ip6h == NULL)
                return NF_ACCEPT; /* The packet looks wrong, ignore */
 -
 -      pp = ip_vs_proto_get(cih->nexthdr);
 +      ciph.saddr.in6 = ip6h->saddr; /* conn_out_get() handles reverse order */
 +      ciph.daddr.in6 = ip6h->daddr;
 +      /* skip possible IPv6 exthdrs of contained IPv6 packet */
 +      ciph.protocol = ipv6_find_hdr(skb, &ciph.len, -1, &ciph.fragoffs, NULL);
 +      if (ciph.protocol < 0)
 +              return NF_ACCEPT; /* Contained IPv6 hdr looks wrong, ignore */
 +
 +      pp = ip_vs_proto_get(ciph.protocol);
        if (!pp)
                return NF_ACCEPT;
  
 -      /* Is the embedded protocol header present? */
 -      /* TODO: we don't support fragmentation at the moment anyways */
 -      if (unlikely(cih->nexthdr == IPPROTO_FRAGMENT && pp->dont_defrag))
 -              return NF_ACCEPT;
 -
 -      IP_VS_DBG_PKT(11, AF_INET6, pp, skb, offset,
 -                    "Checking outgoing ICMPv6 for");
 -
 -      offset += sizeof(struct ipv6hdr);
 -
 -      ip_vs_fill_iphdr(AF_INET6, cih, &ciph);
        /* The embedded headers contain source and dest in reverse order */
 -      cp = pp->conn_out_get(AF_INET6, skb, &ciph, offset, 1);
 +      cp = pp->conn_out_get(AF_INET6, skb, &ciph, 1);
        if (!cp)
                return NF_ACCEPT;
  
 -      snet.in6 = iph->saddr;
 -      return handle_response_icmp(AF_INET6, skb, &snet, cih->nexthdr, cp,
 -                                  pp, offset, sizeof(struct ipv6hdr));
 +      snet.in6 = ciph.saddr.in6;
 +      writable = ciph.len;
 +      return handle_response_icmp(AF_INET6, skb, &snet, ciph.protocol, cp,
 +                                  pp, writable, sizeof(struct ipv6hdr));
  }
  #endif
  
@@@ -1006,17 -1018,17 +1006,17 @@@ static inline int is_tcp_reset(const st
   */
  static unsigned int
  handle_response(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd,
 -              struct ip_vs_conn *cp, int ihl)
 +              struct ip_vs_conn *cp, struct ip_vs_iphdr *iph)
  {
        struct ip_vs_protocol *pp = pd->pp;
  
        IP_VS_DBG_PKT(11, af, pp, skb, 0, "Outgoing packet");
  
 -      if (!skb_make_writable(skb, ihl))
 +      if (!skb_make_writable(skb, iph->len))
                goto drop;
  
        /* mangle the packet */
 -      if (pp->snat_handler && !pp->snat_handler(skb, pp, cp))
 +      if (pp->snat_handler && !pp->snat_handler(skb, pp, cp, iph))
                goto drop;
  
  #ifdef CONFIG_IP_VS_IPV6
@@@ -1103,22 -1115,17 +1103,22 @@@ ip_vs_out(unsigned int hooknum, struct 
        if (!net_ipvs(net)->enable)
                return NF_ACCEPT;
  
 -      ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
 +      ip_vs_fill_iph_skb(af, skb, &iph);
  #ifdef CONFIG_IP_VS_IPV6
        if (af == AF_INET6) {
 +              if (!iph.fragoffs && skb_nfct_reasm(skb)) {
 +                      struct sk_buff *reasm = skb_nfct_reasm(skb);
 +                      /* Save fw mark for coming frags */
 +                      reasm->ipvs_property = 1;
 +                      reasm->mark = skb->mark;
 +              }
                if (unlikely(iph.protocol == IPPROTO_ICMPV6)) {
                        int related;
                        int verdict = ip_vs_out_icmp_v6(skb, &related,
 -                                                      hooknum);
 +                                                      hooknum, &iph);
  
                        if (related)
                                return verdict;
 -                      ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
                }
        } else
  #endif
  
                        if (related)
                                return verdict;
 -                      ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
                }
  
        pd = ip_vs_proto_data_get(net, iph.protocol);
  
        /* reassemble IP fragments */
  #ifdef CONFIG_IP_VS_IPV6
 -      if (af == AF_INET6) {
 -              if (ipv6_hdr(skb)->nexthdr == IPPROTO_FRAGMENT) {
 -                      if (ip_vs_gather_frags_v6(skb,
 -                                                ip_vs_defrag_user(hooknum)))
 -                              return NF_STOLEN;
 -              }
 -
 -              ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
 -      } else
 +      if (af == AF_INET)
  #endif
                if (unlikely(ip_is_fragment(ip_hdr(skb)) && !pp->dont_defrag)) {
                        if (ip_vs_gather_frags(skb,
                                               ip_vs_defrag_user(hooknum)))
                                return NF_STOLEN;
  
 -                      ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
 +                      ip_vs_fill_ip4hdr(skb_network_header(skb), &iph);
                }
  
        /*
         * Check if the packet belongs to an existing entry
         */
 -      cp = pp->conn_out_get(af, skb, &iph, iph.len, 0);
 +      cp = pp->conn_out_get(af, skb, &iph, 0);
  
        if (likely(cp))
 -              return handle_response(af, skb, pd, cp, iph.len);
 +              return handle_response(af, skb, pd, cp, &iph);
        if (sysctl_nat_icmp_send(net) &&
            (pp->protocol == IPPROTO_TCP ||
             pp->protocol == IPPROTO_UDP ||
             pp->protocol == IPPROTO_SCTP)) {
                __be16 _ports[2], *pptr;
  
 -              pptr = skb_header_pointer(skb, iph.len,
 -                                        sizeof(_ports), _ports);
 +              pptr = frag_safe_skb_hp(skb, iph.len,
 +                                       sizeof(_ports), _ports, &iph);
                if (pptr == NULL)
                        return NF_ACCEPT;       /* Not for me */
                if (ip_vs_lookup_real_service(net, af, iph.protocol,
@@@ -1359,13 -1375,13 +1359,13 @@@ ip_vs_in_icmp(struct sk_buff *skb, int 
                      "Checking incoming ICMP for");
  
        offset2 = offset;
 -      offset += cih->ihl * 4;
 -
 -      ip_vs_fill_iphdr(AF_INET, cih, &ciph);
 +      ip_vs_fill_ip4hdr(cih, &ciph);
 +      ciph.len += offset;
 +      offset = ciph.len;
        /* The embedded headers contain source and dest in reverse order.
         * For IPIP this is error for request, not for reply.
         */
 -      cp = pp->conn_in_get(AF_INET, skb, &ciph, offset, ipip ? 0 : 1);
 +      cp = pp->conn_in_get(AF_INET, skb, &ciph, ipip ? 0 : 1);
        if (!cp)
                return NF_ACCEPT;
  
@@@ -1434,7 -1450,7 +1434,7 @@@ ignore_ipip
        ip_vs_in_stats(cp, skb);
        if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol)
                offset += 2 * sizeof(__u16);
 -      verdict = ip_vs_icmp_xmit(skb, cp, pp, offset, hooknum);
 +      verdict = ip_vs_icmp_xmit(skb, cp, pp, offset, hooknum, &ciph);
  
  out:
        __ip_vs_conn_put(cp);
  }
  
  #ifdef CONFIG_IP_VS_IPV6
 -static int
 -ip_vs_in_icmp_v6(struct sk_buff *skb, int *related, unsigned int hooknum)
 +static int ip_vs_in_icmp_v6(struct sk_buff *skb, int *related,
 +                          unsigned int hooknum, struct ip_vs_iphdr *iph)
  {
        struct net *net = NULL;
 -      struct ipv6hdr *iph;
 +      struct ipv6hdr _ip6h, *ip6h;
        struct icmp6hdr _icmph, *ic;
 -      struct ipv6hdr  _ciph, *cih;    /* The ip header contained
 -                                         within the ICMP */
 -      struct ip_vs_iphdr ciph;
 +      struct ip_vs_iphdr ciph = {.flags = 0, .fragoffs = 0};/*Contained IP */
        struct ip_vs_conn *cp;
        struct ip_vs_protocol *pp;
        struct ip_vs_proto_data *pd;
 -      unsigned int offset, verdict;
 +      unsigned int offs_ciph, writable, verdict;
  
        *related = 1;
  
 -      /* reassemble IP fragments */
 -      if (ipv6_hdr(skb)->nexthdr == IPPROTO_FRAGMENT) {
 -              if (ip_vs_gather_frags_v6(skb, ip_vs_defrag_user(hooknum)))
 -                      return NF_STOLEN;
 -      }
 -
 -      iph = ipv6_hdr(skb);
 -      offset = sizeof(struct ipv6hdr);
 -      ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph);
 +      ic = frag_safe_skb_hp(skb, iph->len, sizeof(_icmph), &_icmph, iph);
        if (ic == NULL)
                return NF_DROP;
  
 -      IP_VS_DBG(12, "Incoming ICMPv6 (%d,%d) %pI6->%pI6\n",
 -                ic->icmp6_type, ntohs(icmpv6_id(ic)),
 -                &iph->saddr, &iph->daddr);
 -
        /*
         * Work through seeing if this is for us.
         * These checks are supposed to be in an order that means easy
         * this means that some packets will manage to get a long way
         * down this stack and then be rejected, but that's life.
         */
 -      if ((ic->icmp6_type != ICMPV6_DEST_UNREACH) &&
 -          (ic->icmp6_type != ICMPV6_PKT_TOOBIG) &&
 -          (ic->icmp6_type != ICMPV6_TIME_EXCEED)) {
 +      if (ic->icmp6_type & ICMPV6_INFOMSG_MASK) {
                *related = 0;
                return NF_ACCEPT;
        }
-       if (iph->flags & IP6T_FH_F_FRAG)
 +      /* Fragment header that is before ICMP header tells us that:
 +       * it's not an error message since they can't be fragmented.
 +       */
++      if (iph->flags & IP6_FH_F_FRAG)
 +              return NF_DROP;
 +
 +      IP_VS_DBG(8, "Incoming ICMPv6 (%d,%d) %pI6c->%pI6c\n",
 +                ic->icmp6_type, ntohs(icmpv6_id(ic)),
 +                &iph->saddr, &iph->daddr);
  
        /* Now find the contained IP header */
 -      offset += sizeof(_icmph);
 -      cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
 -      if (cih == NULL)
 +      ciph.len = iph->len + sizeof(_icmph);
 +      offs_ciph = ciph.len; /* Save ip header offset */
 +      ip6h = skb_header_pointer(skb, ciph.len, sizeof(_ip6h), &_ip6h);
 +      if (ip6h == NULL)
                return NF_ACCEPT; /* The packet looks wrong, ignore */
 +      ciph.saddr.in6 = ip6h->saddr; /* conn_in_get() handles reverse order */
 +      ciph.daddr.in6 = ip6h->daddr;
 +      /* skip possible IPv6 exthdrs of contained IPv6 packet */
 +      ciph.protocol = ipv6_find_hdr(skb, &ciph.len, -1, &ciph.fragoffs, NULL);
 +      if (ciph.protocol < 0)
 +              return NF_ACCEPT; /* Contained IPv6 hdr looks wrong, ignore */
  
        net = skb_net(skb);
 -      pd = ip_vs_proto_data_get(net, cih->nexthdr);
 +      pd = ip_vs_proto_data_get(net, ciph.protocol);
        if (!pd)
                return NF_ACCEPT;
        pp = pd->pp;
  
 -      /* Is the embedded protocol header present? */
 -      /* TODO: we don't support fragmentation at the moment anyways */
 -      if (unlikely(cih->nexthdr == IPPROTO_FRAGMENT && pp->dont_defrag))
 +      /* Cannot handle fragmented embedded protocol */
 +      if (ciph.fragoffs)
                return NF_ACCEPT;
  
 -      IP_VS_DBG_PKT(11, AF_INET6, pp, skb, offset,
 +      IP_VS_DBG_PKT(11, AF_INET6, pp, skb, offs_ciph,
                      "Checking incoming ICMPv6 for");
  
 -      offset += sizeof(struct ipv6hdr);
 +      /* The embedded headers contain source and dest in reverse order
 +       * if not from localhost
 +       */
 +      cp = pp->conn_in_get(AF_INET6, skb, &ciph,
 +                           (hooknum == NF_INET_LOCAL_OUT) ? 0 : 1);
  
 -      ip_vs_fill_iphdr(AF_INET6, cih, &ciph);
 -      /* The embedded headers contain source and dest in reverse order */
 -      cp = pp->conn_in_get(AF_INET6, skb, &ciph, offset, 1);
        if (!cp)
                return NF_ACCEPT;
 +      /* VS/TUN, VS/DR and LOCALNODE just let it go */
 +      if ((hooknum == NF_INET_LOCAL_OUT) &&
 +          (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)) {
 +              __ip_vs_conn_put(cp);
 +              return NF_ACCEPT;
 +      }
  
        /* do the statistics and put it back */
        ip_vs_in_stats(cp, skb);
 -      if (IPPROTO_TCP == cih->nexthdr || IPPROTO_UDP == cih->nexthdr ||
 -          IPPROTO_SCTP == cih->nexthdr)
 -              offset += 2 * sizeof(__u16);
 -      verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, offset, hooknum);
 +
 +      /* Need to mangle contained IPv6 header in ICMPv6 packet */
 +      writable = ciph.len;
 +      if (IPPROTO_TCP == ciph.protocol || IPPROTO_UDP == ciph.protocol ||
 +          IPPROTO_SCTP == ciph.protocol)
 +              writable += 2 * sizeof(__u16); /* Also mangle ports */
 +
 +      verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, writable, hooknum, &ciph);
  
        __ip_vs_conn_put(cp);
  
@@@ -1568,7 -1574,7 +1568,7 @@@ ip_vs_in(unsigned int hooknum, struct s
        if (unlikely((skb->pkt_type != PACKET_HOST &&
                      hooknum != NF_INET_LOCAL_OUT) ||
                     !skb_dst(skb))) {
 -              ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
 +              ip_vs_fill_iph_skb(af, skb, &iph);
                IP_VS_DBG_BUF(12, "packet type=%d proto=%d daddr=%s"
                              " ignored in hook %u\n",
                              skb->pkt_type, iph.protocol,
        if (!net_ipvs(net)->enable)
                return NF_ACCEPT;
  
 -      ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
 +      ip_vs_fill_iph_skb(af, skb, &iph);
  
        /* Bad... Do not break raw sockets */
        if (unlikely(skb->sk != NULL && hooknum == NF_INET_LOCAL_OUT &&
  
  #ifdef CONFIG_IP_VS_IPV6
        if (af == AF_INET6) {
 +              if (!iph.fragoffs && skb_nfct_reasm(skb)) {
 +                      struct sk_buff *reasm = skb_nfct_reasm(skb);
 +                      /* Save fw mark for coming frags. */
 +                      reasm->ipvs_property = 1;
 +                      reasm->mark = skb->mark;
 +              }
                if (unlikely(iph.protocol == IPPROTO_ICMPV6)) {
                        int related;
 -                      int verdict = ip_vs_in_icmp_v6(skb, &related, hooknum);
 +                      int verdict = ip_vs_in_icmp_v6(skb, &related, hooknum,
 +                                                     &iph);
  
                        if (related)
                                return verdict;
 -                      ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
                }
        } else
  #endif
  
                        if (related)
                                return verdict;
 -                      ip_vs_fill_iphdr(af, skb_network_header(skb), &iph);
                }
  
        /* Protocol supported? */
        /*
         * Check if the packet belongs to an existing connection entry
         */
 -      cp = pp->conn_in_get(af, skb, &iph, iph.len, 0);
 -
 -      if (unlikely(!cp)) {
 +      cp = pp->conn_in_get(af, skb, &iph, 0);
 +      if (unlikely(!cp) && !iph.fragoffs) {
 +              /* No (second) fragments need to enter here, as nf_defrag_ipv6
 +               * replayed fragment zero will already have created the cp
 +               */
                int v;
  
 -              if (!pp->conn_schedule(af, skb, pd, &v, &cp))
 +              /* Schedule and create new connection entry into &cp */
 +              if (!pp->conn_schedule(af, skb, pd, &v, &cp, &iph))
                        return v;
        }
  
                /* sorry, all this trouble for a no-hit :) */
                IP_VS_DBG_PKT(12, af, pp, skb, 0,
                              "ip_vs_in: packet continues traversal as normal");
 +              if (iph.fragoffs && !skb_nfct_reasm(skb)) {
 +                      /* Fragment that couldn't be mapped to a conn entry
 +                       * and don't have any pointer to a reasm skb
 +                       * is missing module nf_defrag_ipv6
 +                       */
 +                      IP_VS_DBG_RL("Unhandled frag, load nf_defrag_ipv6\n");
 +                      IP_VS_DBG_PKT(7, af, pp, skb, 0, "unhandled fragment");
 +              }
                return NF_ACCEPT;
        }
  
        ip_vs_in_stats(cp, skb);
        ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd);
        if (cp->packet_xmit)
 -              ret = cp->packet_xmit(skb, cp, pp);
 +              ret = cp->packet_xmit(skb, cp, pp, &iph);
                /* do not touch skb anymore */
        else {
                IP_VS_DBG_RL("warning: packet_xmit is null");
@@@ -1733,38 -1723,6 +1733,38 @@@ ip_vs_local_request4(unsigned int hookn
  
  #ifdef CONFIG_IP_VS_IPV6
  
 +/*
 + * AF_INET6 fragment handling
 + * Copy info from first fragment, to the rest of them.
 + */
 +static unsigned int
 +ip_vs_preroute_frag6(unsigned int hooknum, struct sk_buff *skb,
 +                   const struct net_device *in,
 +                   const struct net_device *out,
 +                   int (*okfn)(struct sk_buff *))
 +{
 +      struct sk_buff *reasm = skb_nfct_reasm(skb);
 +      struct net *net;
 +
 +      /* Skip if not a "replay" from nf_ct_frag6_output or first fragment.
 +       * ipvs_property is set when checking first fragment
 +       * in ip_vs_in() and ip_vs_out().
 +       */
 +      if (reasm)
 +              IP_VS_DBG(2, "Fragment recv prop:%d\n", reasm->ipvs_property);
 +      if (!reasm || !reasm->ipvs_property)
 +              return NF_ACCEPT;
 +
 +      net = skb_net(skb);
 +      if (!net_ipvs(net)->enable)
 +              return NF_ACCEPT;
 +
 +      /* Copy stored fw mark, saved in ip_vs_{in,out} */
 +      skb->mark = reasm->mark;
 +
 +      return NF_ACCEPT;
 +}
 +
  /*
   *    AF_INET6 handler in NF_INET_LOCAL_IN chain
   *    Schedule and forward packets from remote clients
@@@ -1835,10 -1793,8 +1835,10 @@@ ip_vs_forward_icmp_v6(unsigned int hook
  {
        int r;
        struct net *net;
 +      struct ip_vs_iphdr iphdr;
  
 -      if (ipv6_hdr(skb)->nexthdr != IPPROTO_ICMPV6)
 +      ip_vs_fill_iph_skb(AF_INET6, skb, &iphdr);
 +      if (iphdr.protocol != IPPROTO_ICMPV6)
                return NF_ACCEPT;
  
        /* ipvs enabled in this netns ? */
        if (!net_ipvs(net)->enable)
                return NF_ACCEPT;
  
 -      return ip_vs_in_icmp_v6(skb, &r, hooknum);
 +      return ip_vs_in_icmp_v6(skb, &r, hooknum, &iphdr);
  }
  #endif
  
@@@ -1904,14 -1860,6 +1904,14 @@@ static struct nf_hook_ops ip_vs_ops[] _
                .priority       = 100,
        },
  #ifdef CONFIG_IP_VS_IPV6
 +      /* After mangle & nat fetch 2:nd fragment and following */
 +      {
 +              .hook           = ip_vs_preroute_frag6,
 +              .owner          = THIS_MODULE,
 +              .pf             = NFPROTO_IPV6,
 +              .hooknum        = NF_INET_PRE_ROUTING,
 +              .priority       = NF_IP6_PRI_NAT_DST + 1,
 +      },
        /* After packet filtering, change source only for VS/NAT */
        {
                .hook           = ip_vs_reply6,