diff mbox series

[08/12] netfilter: nf_flow_table: move ipv6 offload hook code to nf_flow_table

Message ID 20180220144954.47752-8-nbd@nbd.name
State Superseded
Delegated to: Pablo Neira
Headers show
Series None | expand

Commit Message

Felix Fietkau Feb. 20, 2018, 2:49 p.m. UTC
Useful as preparation for adding iptables support for offload

Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 net/ipv6/netfilter/nf_flow_table_ipv6.c | 232 --------------------------------
 net/netfilter/nf_flow_table_ip.c        | 215 +++++++++++++++++++++++++++++
 2 files changed, 215 insertions(+), 232 deletions(-)

Comments

Pablo Neira Ayuso Feb. 20, 2018, 3:01 p.m. UTC | #1
On Tue, Feb 20, 2018 at 03:49:50PM +0100, Felix Fietkau wrote:
> -unsigned int
> -nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
> -			  const struct nf_hook_state *state)
> -{
> -	struct flow_offload_tuple_rhash *tuplehash;
> -	struct nf_flowtable *flow_table = priv;
> -	struct flow_offload_tuple tuple = {};
> -	enum flow_offload_tuple_dir dir;
> -	struct flow_offload *flow;
> -	struct net_device *outdev;
> -	struct in6_addr *nexthop;
> -	struct ipv6hdr *ip6h;
> -	struct rt6_info *rt;
> -
> -	if (skb->protocol != htons(ETH_P_IPV6))
> -		return NF_ACCEPT;
> -
> -	if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
> -		return NF_ACCEPT;
> -
> -	tuplehash = flow_offload_lookup(flow_table, &tuple);
> -	if (tuplehash == NULL)
> -		return NF_ACCEPT;
> -
> -	outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
> -	if (!outdev)
> -		return NF_ACCEPT;
> -
> -	dir = tuplehash->tuple.dir;
> -	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
> -	rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
> -
> -	if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
> -		return NF_ACCEPT;
> -
> -	if (skb_try_make_writable(skb, sizeof(*ip6h)))
> -		return NF_DROP;
> -
> -	if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
> -	    nf_flow_nat_ipv6(flow, skb, dir) < 0)
> -		return NF_DROP;
> -
> -	flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
> -	ip6h = ipv6_hdr(skb);
> -	ip6h->hop_limit--;
> -
> -	skb->dev = outdev;
> -	nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
> -	neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
> -
> -	return NF_STOLEN;
> -}
> -EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);

Why do you need to move the hook function to this new core file?
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Felix Fietkau Feb. 20, 2018, 3:06 p.m. UTC | #2
On 2018-02-20 16:01, Pablo Neira Ayuso wrote:
> On Tue, Feb 20, 2018 at 03:49:50PM +0100, Felix Fietkau wrote:
>> -unsigned int
>> -nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
>> -			  const struct nf_hook_state *state)
>> -{
>> -	struct flow_offload_tuple_rhash *tuplehash;
>> -	struct nf_flowtable *flow_table = priv;
>> -	struct flow_offload_tuple tuple = {};
>> -	enum flow_offload_tuple_dir dir;
>> -	struct flow_offload *flow;
>> -	struct net_device *outdev;
>> -	struct in6_addr *nexthop;
>> -	struct ipv6hdr *ip6h;
>> -	struct rt6_info *rt;
>> -
>> -	if (skb->protocol != htons(ETH_P_IPV6))
>> -		return NF_ACCEPT;
>> -
>> -	if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
>> -		return NF_ACCEPT;
>> -
>> -	tuplehash = flow_offload_lookup(flow_table, &tuple);
>> -	if (tuplehash == NULL)
>> -		return NF_ACCEPT;
>> -
>> -	outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
>> -	if (!outdev)
>> -		return NF_ACCEPT;
>> -
>> -	dir = tuplehash->tuple.dir;
>> -	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
>> -	rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
>> -
>> -	if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
>> -		return NF_ACCEPT;
>> -
>> -	if (skb_try_make_writable(skb, sizeof(*ip6h)))
>> -		return NF_DROP;
>> -
>> -	if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
>> -	    nf_flow_nat_ipv6(flow, skb, dir) < 0)
>> -		return NF_DROP;
>> -
>> -	flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
>> -	ip6h = ipv6_hdr(skb);
>> -	ip6h->hop_limit--;
>> -
>> -	skb->dev = outdev;
>> -	nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
>> -	neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
>> -
>> -	return NF_STOLEN;
>> -}
>> -EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);
> 
> Why do you need to move the hook function to this new core file?
For backporting flow table support to xtables I need to move this to a
source file that doesn't depend on nftables or ipv6 directly.

- Felix
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso Feb. 20, 2018, 3:35 p.m. UTC | #3
On Tue, Feb 20, 2018 at 04:06:11PM +0100, Felix Fietkau wrote:
> On 2018-02-20 16:01, Pablo Neira Ayuso wrote:
> > On Tue, Feb 20, 2018 at 03:49:50PM +0100, Felix Fietkau wrote:
> >> -unsigned int
> >> -nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
> >> -			  const struct nf_hook_state *state)
> >> -{
> >> -	struct flow_offload_tuple_rhash *tuplehash;
> >> -	struct nf_flowtable *flow_table = priv;
> >> -	struct flow_offload_tuple tuple = {};
> >> -	enum flow_offload_tuple_dir dir;
> >> -	struct flow_offload *flow;
> >> -	struct net_device *outdev;
> >> -	struct in6_addr *nexthop;
> >> -	struct ipv6hdr *ip6h;
> >> -	struct rt6_info *rt;
> >> -
> >> -	if (skb->protocol != htons(ETH_P_IPV6))
> >> -		return NF_ACCEPT;
> >> -
> >> -	if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
> >> -		return NF_ACCEPT;
> >> -
> >> -	tuplehash = flow_offload_lookup(flow_table, &tuple);
> >> -	if (tuplehash == NULL)
> >> -		return NF_ACCEPT;
> >> -
> >> -	outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
> >> -	if (!outdev)
> >> -		return NF_ACCEPT;
> >> -
> >> -	dir = tuplehash->tuple.dir;
> >> -	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
> >> -	rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
> >> -
> >> -	if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
> >> -		return NF_ACCEPT;
> >> -
> >> -	if (skb_try_make_writable(skb, sizeof(*ip6h)))
> >> -		return NF_DROP;
> >> -
> >> -	if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
> >> -	    nf_flow_nat_ipv6(flow, skb, dir) < 0)
> >> -		return NF_DROP;
> >> -
> >> -	flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
> >> -	ip6h = ipv6_hdr(skb);
> >> -	ip6h->hop_limit--;
> >> -
> >> -	skb->dev = outdev;
> >> -	nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
> >> -	neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
> >> -
> >> -	return NF_STOLEN;
> >> -}
> >> -EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);
> > 
> > Why do you need to move the hook function to this new core file?
>
> For backporting flow table support to xtables I need to move this to a
> source file that doesn't depend on nftables or ipv6 directly.

I guess this is related to net/netfilter/xt_FLOWOFFLOAD.c.

You probably could add net/ipv4/netfilter/ipt_FLOWOFFLOAD.c and
net/ipv6/netfilter/ip6t_FLOWOFFLOAD.c, so we can skip placing ipv4 and
ipv6 code in the same file.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Felix Fietkau Feb. 20, 2018, 3:44 p.m. UTC | #4
On 2018-02-20 16:35, Pablo Neira Ayuso wrote:
> On Tue, Feb 20, 2018 at 04:06:11PM +0100, Felix Fietkau wrote:
>> On 2018-02-20 16:01, Pablo Neira Ayuso wrote:
>> > On Tue, Feb 20, 2018 at 03:49:50PM +0100, Felix Fietkau wrote:
>> >> -unsigned int
>> >> -nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
>> >> -			  const struct nf_hook_state *state)
>> >> -{
>> >> -	struct flow_offload_tuple_rhash *tuplehash;
>> >> -	struct nf_flowtable *flow_table = priv;
>> >> -	struct flow_offload_tuple tuple = {};
>> >> -	enum flow_offload_tuple_dir dir;
>> >> -	struct flow_offload *flow;
>> >> -	struct net_device *outdev;
>> >> -	struct in6_addr *nexthop;
>> >> -	struct ipv6hdr *ip6h;
>> >> -	struct rt6_info *rt;
>> >> -
>> >> -	if (skb->protocol != htons(ETH_P_IPV6))
>> >> -		return NF_ACCEPT;
>> >> -
>> >> -	if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
>> >> -		return NF_ACCEPT;
>> >> -
>> >> -	tuplehash = flow_offload_lookup(flow_table, &tuple);
>> >> -	if (tuplehash == NULL)
>> >> -		return NF_ACCEPT;
>> >> -
>> >> -	outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
>> >> -	if (!outdev)
>> >> -		return NF_ACCEPT;
>> >> -
>> >> -	dir = tuplehash->tuple.dir;
>> >> -	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
>> >> -	rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
>> >> -
>> >> -	if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
>> >> -		return NF_ACCEPT;
>> >> -
>> >> -	if (skb_try_make_writable(skb, sizeof(*ip6h)))
>> >> -		return NF_DROP;
>> >> -
>> >> -	if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
>> >> -	    nf_flow_nat_ipv6(flow, skb, dir) < 0)
>> >> -		return NF_DROP;
>> >> -
>> >> -	flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
>> >> -	ip6h = ipv6_hdr(skb);
>> >> -	ip6h->hop_limit--;
>> >> -
>> >> -	skb->dev = outdev;
>> >> -	nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
>> >> -	neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
>> >> -
>> >> -	return NF_STOLEN;
>> >> -}
>> >> -EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);
>> > 
>> > Why do you need to move the hook function to this new core file?
>>
>> For backporting flow table support to xtables I need to move this to a
>> source file that doesn't depend on nftables or ipv6 directly.
> 
> I guess this is related to net/netfilter/xt_FLOWOFFLOAD.c.
> 
> You probably could add net/ipv4/netfilter/ipt_FLOWOFFLOAD.c and
> net/ipv6/netfilter/ip6t_FLOWOFFLOAD.c, so we can skip placing ipv4 and
> ipv6 code in the same file.
That's exactly what I wanted to avoid. Since nf_flow_table_ipv6 depends
on nftables, I'd have to make two extra modules, one for the ipv4 hook,
one for the ipv6 hook.

So we'd have:
ipt_FLOWOFFLOAD.ko,
ip6t_FLOWOFFLOAD.ko
nf_flow_table_ipv4.ko (without nft bits)
nf_flow_table_ipv6.ko (without nft bits)
nft_flow_table_ipv4.ko (with just nft bits)
nft_flow_table_ipv6.ko (with just nft bits)

I'd say the overhead of having all those modules split up is not that
much smaller than the overhead of including ipv6 code in the core module
even when it may not be needed.

By the way, .text size of nf_flow_table_hw.o with IPv4 + IPv6 combined
is less than 3.5 KiB (when compiled for ARM).

- Felix
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso Feb. 20, 2018, 4:54 p.m. UTC | #5
On Tue, Feb 20, 2018 at 04:44:48PM +0100, Felix Fietkau wrote:
> On 2018-02-20 16:35, Pablo Neira Ayuso wrote:
> > On Tue, Feb 20, 2018 at 04:06:11PM +0100, Felix Fietkau wrote:
> >> On 2018-02-20 16:01, Pablo Neira Ayuso wrote:
[...]
> > I guess this is related to net/netfilter/xt_FLOWOFFLOAD.c.
> > 
> > You probably could add net/ipv4/netfilter/ipt_FLOWOFFLOAD.c and
> > net/ipv6/netfilter/ip6t_FLOWOFFLOAD.c, so we can skip placing ipv4 and
> > ipv6 code in the same file.
>
> That's exactly what I wanted to avoid.

I would like to avoid the opposite, if possible.

> Since nf_flow_table_ipv6 depends on nftables, I'd have to make two
> extra modules, one for the ipv4 hook, one for the ipv6 hook.

What is the current dependency between nf_flow_table_ipv6 and
nftables? I tried to reduce dependencies as much as possible.

> So we'd have:
> ipt_FLOWOFFLOAD.ko,
> ip6t_FLOWOFFLOAD.ko
> nf_flow_table_ipv4.ko (without nft bits)
> nf_flow_table_ipv6.ko (without nft bits)
> nft_flow_table_ipv4.ko (with just nft bits)
> nft_flow_table_ipv6.ko (with just nft bits)
> 
> I'd say the overhead of having all those modules split up is not that
> much smaller than the overhead of including ipv6 code in the core module
> even when it may not be needed.

I see.

When we do this, ie. place IPv4 and IPv6 code in the same file, we end
up needing #ifdefs, I have bad experience with this.

What is CONFIG_IPV6 is disabled?

> By the way, .text size of nf_flow_table_hw.o with IPv4 + IPv6 combined
> is less than 3.5 KiB (when compiled for ARM).

But people can also compile this modules built-in if they want to
shrink image size, right?
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Felix Fietkau Feb. 20, 2018, 5:02 p.m. UTC | #6
On 2018-02-20 17:54, Pablo Neira Ayuso wrote:
> On Tue, Feb 20, 2018 at 04:44:48PM +0100, Felix Fietkau wrote:
>> On 2018-02-20 16:35, Pablo Neira Ayuso wrote:
>> > On Tue, Feb 20, 2018 at 04:06:11PM +0100, Felix Fietkau wrote:
>> >> On 2018-02-20 16:01, Pablo Neira Ayuso wrote:
> [...]
>> > I guess this is related to net/netfilter/xt_FLOWOFFLOAD.c.
>> > 
>> > You probably could add net/ipv4/netfilter/ipt_FLOWOFFLOAD.c and
>> > net/ipv6/netfilter/ip6t_FLOWOFFLOAD.c, so we can skip placing ipv4 and
>> > ipv6 code in the same file.
>>
>> That's exactly what I wanted to avoid.
> 
> I would like to avoid the opposite, if possible.
> 
>> Since nf_flow_table_ipv6 depends on nftables, I'd have to make two
>> extra modules, one for the ipv4 hook, one for the ipv6 hook.
> 
> What is the current dependency between nf_flow_table_ipv6 and
> nftables? I tried to reduce dependencies as much as possible.
nft_register_flowtable_type
nft_unregister_flowtable_type

Resolving those dependencies through other ways than splitting up the
code would get awkward and convoluted, since the flowtable type is what
nftables needs to be able to call into the flow offload code without
depending on it.

>> So we'd have:
>> ipt_FLOWOFFLOAD.ko,
>> ip6t_FLOWOFFLOAD.ko
>> nf_flow_table_ipv4.ko (without nft bits)
>> nf_flow_table_ipv6.ko (without nft bits)
>> nft_flow_table_ipv4.ko (with just nft bits)
>> nft_flow_table_ipv6.ko (with just nft bits)
>> 
>> I'd say the overhead of having all those modules split up is not that
>> much smaller than the overhead of including ipv6 code in the core module
>> even when it may not be needed.
> 
> I see.
> 
> When we do this, ie. place IPv4 and IPv6 code in the same file, we end
> up needing #ifdefs, I have bad experience with this.
> 
> What is CONFIG_IPV6 is disabled?
Dealing with that would only need one single #ifdef (if it needs one at
all), so it's very simple.

>> By the way, .text size of nf_flow_table_hw.o with IPv4 + IPv6 combined
>> is less than 3.5 KiB (when compiled for ARM).
> 
> But people can also compile this modules built-in if they want to
> shrink image size, right?The main reason why I have an issue with this approach is the fact that
we have to provide reasonably sized precompiled distribution as well,
but for embedded devices.
Splitting things up unnecessarily causes bloat for common configurations
that almost everybody uses.

- Felix
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso Feb. 20, 2018, 5:07 p.m. UTC | #7
On Tue, Feb 20, 2018 at 06:02:27PM +0100, Felix Fietkau wrote:
> we have to provide reasonably sized precompiled distribution as well,
> but for embedded devices.
> Splitting things up unnecessarily causes bloat for common configurations
> that almost everybody uses.

OK, thanks for explaining. Not related, but this is making me think we
can probably reduce modularization in nf_tables, will prepare some
patches for nf-next.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox series

Patch

diff --git a/net/ipv6/netfilter/nf_flow_table_ipv6.c b/net/ipv6/netfilter/nf_flow_table_ipv6.c
index f530efd3e378..f1804ce8d561 100644
--- a/net/ipv6/netfilter/nf_flow_table_ipv6.c
+++ b/net/ipv6/netfilter/nf_flow_table_ipv6.c
@@ -3,240 +3,8 @@ 
 #include <linux/module.h>
 #include <linux/netfilter.h>
 #include <linux/rhashtable.h>
-#include <linux/ipv6.h>
-#include <linux/netdevice.h>
-#include <net/ipv6.h>
-#include <net/ip6_route.h>
-#include <net/neighbour.h>
 #include <net/netfilter/nf_flow_table.h>
 #include <net/netfilter/nf_tables.h>
-/* For layer 4 checksum field offset. */
-#include <linux/tcp.h>
-#include <linux/udp.h>
-
-static int nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
-				struct in6_addr *addr,
-				struct in6_addr *new_addr)
-{
-	struct tcphdr *tcph;
-
-	if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) ||
-	    skb_try_make_writable(skb, thoff + sizeof(*tcph)))
-		return -1;
-
-	tcph = (void *)(skb_network_header(skb) + thoff);
-	inet_proto_csum_replace16(&tcph->check, skb, addr->s6_addr32,
-				  new_addr->s6_addr32, true);
-
-	return 0;
-}
-
-static int nf_flow_nat_ipv6_udp(struct sk_buff *skb, unsigned int thoff,
-				struct in6_addr *addr,
-				struct in6_addr *new_addr)
-{
-	struct udphdr *udph;
-
-	if (!pskb_may_pull(skb, thoff + sizeof(*udph)) ||
-	    skb_try_make_writable(skb, thoff + sizeof(*udph)))
-		return -1;
-
-	udph = (void *)(skb_network_header(skb) + thoff);
-	if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) {
-		inet_proto_csum_replace16(&udph->check, skb, addr->s6_addr32,
-					  new_addr->s6_addr32, true);
-		if (!udph->check)
-			udph->check = CSUM_MANGLED_0;
-	}
-
-	return 0;
-}
-
-static int nf_flow_nat_ipv6_l4proto(struct sk_buff *skb, struct ipv6hdr *ip6h,
-				    unsigned int thoff, struct in6_addr *addr,
-				    struct in6_addr *new_addr)
-{
-	switch (ip6h->nexthdr) {
-	case IPPROTO_TCP:
-		if (nf_flow_nat_ipv6_tcp(skb, thoff, addr, new_addr) < 0)
-			return NF_DROP;
-		break;
-	case IPPROTO_UDP:
-		if (nf_flow_nat_ipv6_udp(skb, thoff, addr, new_addr) < 0)
-			return NF_DROP;
-		break;
-	}
-
-	return 0;
-}
-
-static int nf_flow_snat_ipv6(const struct flow_offload *flow,
-			     struct sk_buff *skb, struct ipv6hdr *ip6h,
-			     unsigned int thoff,
-			     enum flow_offload_tuple_dir dir)
-{
-	struct in6_addr addr, new_addr;
-
-	switch (dir) {
-	case FLOW_OFFLOAD_DIR_ORIGINAL:
-		addr = ip6h->saddr;
-		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6;
-		ip6h->saddr = new_addr;
-		break;
-	case FLOW_OFFLOAD_DIR_REPLY:
-		addr = ip6h->daddr;
-		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6;
-		ip6h->daddr = new_addr;
-		break;
-	default:
-		return -1;
-	}
-
-	return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
-}
-
-static int nf_flow_dnat_ipv6(const struct flow_offload *flow,
-			     struct sk_buff *skb, struct ipv6hdr *ip6h,
-			     unsigned int thoff,
-			     enum flow_offload_tuple_dir dir)
-{
-	struct in6_addr addr, new_addr;
-
-	switch (dir) {
-	case FLOW_OFFLOAD_DIR_ORIGINAL:
-		addr = ip6h->daddr;
-		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6;
-		ip6h->daddr = new_addr;
-		break;
-	case FLOW_OFFLOAD_DIR_REPLY:
-		addr = ip6h->saddr;
-		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6;
-		ip6h->saddr = new_addr;
-		break;
-	default:
-		return -1;
-	}
-
-	return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
-}
-
-static int nf_flow_nat_ipv6(const struct flow_offload *flow,
-			    struct sk_buff *skb,
-			    enum flow_offload_tuple_dir dir)
-{
-	struct ipv6hdr *ip6h = ipv6_hdr(skb);
-	unsigned int thoff = sizeof(*ip6h);
-
-	if (flow->flags & FLOW_OFFLOAD_SNAT &&
-	    (nf_flow_snat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
-	     nf_flow_snat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
-		return -1;
-	if (flow->flags & FLOW_OFFLOAD_DNAT &&
-	    (nf_flow_dnat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
-	     nf_flow_dnat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
-		return -1;
-
-	return 0;
-}
-
-static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
-			      struct flow_offload_tuple *tuple)
-{
-	struct flow_ports *ports;
-	struct ipv6hdr *ip6h;
-	unsigned int thoff;
-
-	if (!pskb_may_pull(skb, sizeof(*ip6h)))
-		return -1;
-
-	ip6h = ipv6_hdr(skb);
-
-	if (ip6h->nexthdr != IPPROTO_TCP &&
-	    ip6h->nexthdr != IPPROTO_UDP)
-		return -1;
-
-	thoff = sizeof(*ip6h);
-	if (!pskb_may_pull(skb, thoff + sizeof(*ports)))
-		return -1;
-
-	ports = (struct flow_ports *)(skb_network_header(skb) + thoff);
-
-	tuple->src_v6		= ip6h->saddr;
-	tuple->dst_v6		= ip6h->daddr;
-	tuple->src_port		= ports->source;
-	tuple->dst_port		= ports->dest;
-	tuple->l3proto		= AF_INET6;
-	tuple->l4proto		= ip6h->nexthdr;
-	tuple->iifidx		= dev->ifindex;
-
-	return 0;
-}
-
-/* Based on ip_exceeds_mtu(). */
-static bool nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu)
-{
-	if (skb->len <= mtu)
-		return false;
-
-	if (skb_is_gso(skb) && skb_gso_validate_mtu(skb, mtu))
-		return false;
-
-	return true;
-}
-
-unsigned int
-nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
-			  const struct nf_hook_state *state)
-{
-	struct flow_offload_tuple_rhash *tuplehash;
-	struct nf_flowtable *flow_table = priv;
-	struct flow_offload_tuple tuple = {};
-	enum flow_offload_tuple_dir dir;
-	struct flow_offload *flow;
-	struct net_device *outdev;
-	struct in6_addr *nexthop;
-	struct ipv6hdr *ip6h;
-	struct rt6_info *rt;
-
-	if (skb->protocol != htons(ETH_P_IPV6))
-		return NF_ACCEPT;
-
-	if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
-		return NF_ACCEPT;
-
-	tuplehash = flow_offload_lookup(flow_table, &tuple);
-	if (tuplehash == NULL)
-		return NF_ACCEPT;
-
-	outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
-	if (!outdev)
-		return NF_ACCEPT;
-
-	dir = tuplehash->tuple.dir;
-	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
-	rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
-
-	if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
-		return NF_ACCEPT;
-
-	if (skb_try_make_writable(skb, sizeof(*ip6h)))
-		return NF_DROP;
-
-	if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
-	    nf_flow_nat_ipv6(flow, skb, dir) < 0)
-		return NF_DROP;
-
-	flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
-	ip6h = ipv6_hdr(skb);
-	ip6h->hop_limit--;
-
-	skb->dev = outdev;
-	nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
-	neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
-
-	return NF_STOLEN;
-}
-EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);
 
 static struct nf_flowtable_type flowtable_ipv6 = {
 	.family		= NFPROTO_IPV6,
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 79976c25891b..fc6f00c42d48 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -4,8 +4,11 @@ 
 #include <linux/netfilter.h>
 #include <linux/rhashtable.h>
 #include <linux/ip.h>
+#include <linux/ipv6.h>
 #include <linux/netdevice.h>
 #include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/ip6_route.h>
 #include <net/neighbour.h>
 #include <net/netfilter/nf_flow_table.h>
 /* For layer 4 checksum field offset. */
@@ -241,3 +244,215 @@  nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
 	return NF_STOLEN;
 }
 EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
+
+static int nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
+				struct in6_addr *addr,
+				struct in6_addr *new_addr)
+{
+	struct tcphdr *tcph;
+
+	if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) ||
+	    skb_try_make_writable(skb, thoff + sizeof(*tcph)))
+		return -1;
+
+	tcph = (void *)(skb_network_header(skb) + thoff);
+	inet_proto_csum_replace16(&tcph->check, skb, addr->s6_addr32,
+				  new_addr->s6_addr32, true);
+
+	return 0;
+}
+
+static int nf_flow_nat_ipv6_udp(struct sk_buff *skb, unsigned int thoff,
+				struct in6_addr *addr,
+				struct in6_addr *new_addr)
+{
+	struct udphdr *udph;
+
+	if (!pskb_may_pull(skb, thoff + sizeof(*udph)) ||
+	    skb_try_make_writable(skb, thoff + sizeof(*udph)))
+		return -1;
+
+	udph = (void *)(skb_network_header(skb) + thoff);
+	if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) {
+		inet_proto_csum_replace16(&udph->check, skb, addr->s6_addr32,
+					  new_addr->s6_addr32, true);
+		if (!udph->check)
+			udph->check = CSUM_MANGLED_0;
+	}
+
+	return 0;
+}
+
+static int nf_flow_nat_ipv6_l4proto(struct sk_buff *skb, struct ipv6hdr *ip6h,
+				    unsigned int thoff, struct in6_addr *addr,
+				    struct in6_addr *new_addr)
+{
+	switch (ip6h->nexthdr) {
+	case IPPROTO_TCP:
+		if (nf_flow_nat_ipv6_tcp(skb, thoff, addr, new_addr) < 0)
+			return NF_DROP;
+		break;
+	case IPPROTO_UDP:
+		if (nf_flow_nat_ipv6_udp(skb, thoff, addr, new_addr) < 0)
+			return NF_DROP;
+		break;
+	}
+
+	return 0;
+}
+
+static int nf_flow_snat_ipv6(const struct flow_offload *flow,
+			     struct sk_buff *skb, struct ipv6hdr *ip6h,
+			     unsigned int thoff,
+			     enum flow_offload_tuple_dir dir)
+{
+	struct in6_addr addr, new_addr;
+
+	switch (dir) {
+	case FLOW_OFFLOAD_DIR_ORIGINAL:
+		addr = ip6h->saddr;
+		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6;
+		ip6h->saddr = new_addr;
+		break;
+	case FLOW_OFFLOAD_DIR_REPLY:
+		addr = ip6h->daddr;
+		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6;
+		ip6h->daddr = new_addr;
+		break;
+	default:
+		return -1;
+	}
+
+	return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
+}
+
+static int nf_flow_dnat_ipv6(const struct flow_offload *flow,
+			     struct sk_buff *skb, struct ipv6hdr *ip6h,
+			     unsigned int thoff,
+			     enum flow_offload_tuple_dir dir)
+{
+	struct in6_addr addr, new_addr;
+
+	switch (dir) {
+	case FLOW_OFFLOAD_DIR_ORIGINAL:
+		addr = ip6h->daddr;
+		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6;
+		ip6h->daddr = new_addr;
+		break;
+	case FLOW_OFFLOAD_DIR_REPLY:
+		addr = ip6h->saddr;
+		new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6;
+		ip6h->saddr = new_addr;
+		break;
+	default:
+		return -1;
+	}
+
+	return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
+}
+
+static int nf_flow_nat_ipv6(const struct flow_offload *flow,
+			    struct sk_buff *skb,
+			    enum flow_offload_tuple_dir dir)
+{
+	struct ipv6hdr *ip6h = ipv6_hdr(skb);
+	unsigned int thoff = sizeof(*ip6h);
+
+	if (flow->flags & FLOW_OFFLOAD_SNAT &&
+	    (nf_flow_snat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
+	     nf_flow_snat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
+		return -1;
+	if (flow->flags & FLOW_OFFLOAD_DNAT &&
+	    (nf_flow_dnat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
+	     nf_flow_dnat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
+		return -1;
+
+	return 0;
+}
+
+static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
+			      struct flow_offload_tuple *tuple)
+{
+	struct flow_ports *ports;
+	struct ipv6hdr *ip6h;
+	unsigned int thoff;
+
+	if (!pskb_may_pull(skb, sizeof(*ip6h)))
+		return -1;
+
+	ip6h = ipv6_hdr(skb);
+
+	if (ip6h->nexthdr != IPPROTO_TCP &&
+	    ip6h->nexthdr != IPPROTO_UDP)
+		return -1;
+
+	thoff = sizeof(*ip6h);
+	if (!pskb_may_pull(skb, thoff + sizeof(*ports)))
+		return -1;
+
+	ports = (struct flow_ports *)(skb_network_header(skb) + thoff);
+
+	tuple->src_v6		= ip6h->saddr;
+	tuple->dst_v6		= ip6h->daddr;
+	tuple->src_port		= ports->source;
+	tuple->dst_port		= ports->dest;
+	tuple->l3proto		= AF_INET6;
+	tuple->l4proto		= ip6h->nexthdr;
+	tuple->iifidx		= dev->ifindex;
+
+	return 0;
+}
+
+unsigned int
+nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
+			  const struct nf_hook_state *state)
+{
+	struct flow_offload_tuple_rhash *tuplehash;
+	struct nf_flowtable *flow_table = priv;
+	struct flow_offload_tuple tuple = {};
+	enum flow_offload_tuple_dir dir;
+	struct flow_offload *flow;
+	struct net_device *outdev;
+	struct in6_addr *nexthop;
+	struct ipv6hdr *ip6h;
+	struct rt6_info *rt;
+
+	if (skb->protocol != htons(ETH_P_IPV6))
+		return NF_ACCEPT;
+
+	if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
+		return NF_ACCEPT;
+
+	tuplehash = flow_offload_lookup(flow_table, &tuple);
+	if (tuplehash == NULL)
+		return NF_ACCEPT;
+
+	outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
+	if (!outdev)
+		return NF_ACCEPT;
+
+	dir = tuplehash->tuple.dir;
+	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+	rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
+
+	if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
+		return NF_ACCEPT;
+
+	if (skb_try_make_writable(skb, sizeof(*ip6h)))
+		return NF_DROP;
+
+	if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
+	    nf_flow_nat_ipv6(flow, skb, dir) < 0)
+		return NF_DROP;
+
+	flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
+	ip6h = ipv6_hdr(skb);
+	ip6h->hop_limit--;
+
+	skb->dev = outdev;
+	nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
+	neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
+
+	return NF_STOLEN;
+}
+EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);