aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/backport-4.19/352-v4.18-netfilter-nf_flow_table-rename-nf_flow_table.c-to-nf.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/generic/backport-4.19/352-v4.18-netfilter-nf_flow_table-rename-nf_flow_table.c-to-nf.patch')
-rw-r--r--target/linux/generic/backport-4.19/352-v4.18-netfilter-nf_flow_table-rename-nf_flow_table.c-to-nf.patch952
1 files changed, 952 insertions, 0 deletions
diff --git a/target/linux/generic/backport-4.19/352-v4.18-netfilter-nf_flow_table-rename-nf_flow_table.c-to-nf.patch b/target/linux/generic/backport-4.19/352-v4.18-netfilter-nf_flow_table-rename-nf_flow_table.c-to-nf.patch
new file mode 100644
index 0000000000..5df56dd643
--- /dev/null
+++ b/target/linux/generic/backport-4.19/352-v4.18-netfilter-nf_flow_table-rename-nf_flow_table.c-to-nf.patch
@@ -0,0 +1,952 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 16 Feb 2018 11:08:47 +0100
+Subject: [PATCH] netfilter: nf_flow_table: rename nf_flow_table.c to
+ nf_flow_table_core.c
+
+Preparation for adding more code to the same module
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ rename net/netfilter/{nf_flow_table.c => nf_flow_table_core.c} (100%)
+
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -113,6 +113,8 @@ obj-$(CONFIG_NFT_FWD_NETDEV) += nft_fwd_
+
+ # flow table infrastructure
+ obj-$(CONFIG_NF_FLOW_TABLE) += nf_flow_table.o
++nf_flow_table-objs := nf_flow_table_core.o
++
+ obj-$(CONFIG_NF_FLOW_TABLE_INET) += nf_flow_table_inet.o
+
+ # generic X tables
+--- a/net/netfilter/nf_flow_table.c
++++ /dev/null
+@@ -1,462 +0,0 @@
+-#include <linux/kernel.h>
+-#include <linux/init.h>
+-#include <linux/module.h>
+-#include <linux/netfilter.h>
+-#include <linux/rhashtable.h>
+-#include <linux/netdevice.h>
+-#include <net/ip.h>
+-#include <net/ip6_route.h>
+-#include <net/netfilter/nf_tables.h>
+-#include <net/netfilter/nf_flow_table.h>
+-#include <net/netfilter/nf_conntrack.h>
+-#include <net/netfilter/nf_conntrack_core.h>
+-#include <net/netfilter/nf_conntrack_tuple.h>
+-
+-struct flow_offload_entry {
+- struct flow_offload flow;
+- struct nf_conn *ct;
+- struct rcu_head rcu_head;
+-};
+-
+-static void
+-flow_offload_fill_dir(struct flow_offload *flow, struct nf_conn *ct,
+- struct nf_flow_route *route,
+- enum flow_offload_tuple_dir dir)
+-{
+- struct flow_offload_tuple *ft = &flow->tuplehash[dir].tuple;
+- struct nf_conntrack_tuple *ctt = &ct->tuplehash[dir].tuple;
+- struct dst_entry *dst = route->tuple[dir].dst;
+-
+- ft->dir = dir;
+-
+- switch (ctt->src.l3num) {
+- case NFPROTO_IPV4:
+- ft->src_v4 = ctt->src.u3.in;
+- ft->dst_v4 = ctt->dst.u3.in;
+- ft->mtu = ip_dst_mtu_maybe_forward(dst, true);
+- break;
+- case NFPROTO_IPV6:
+- ft->src_v6 = ctt->src.u3.in6;
+- ft->dst_v6 = ctt->dst.u3.in6;
+- ft->mtu = ip6_dst_mtu_forward(dst);
+- break;
+- }
+-
+- ft->l3proto = ctt->src.l3num;
+- ft->l4proto = ctt->dst.protonum;
+- ft->src_port = ctt->src.u.tcp.port;
+- ft->dst_port = ctt->dst.u.tcp.port;
+-
+- ft->iifidx = route->tuple[dir].ifindex;
+- ft->oifidx = route->tuple[!dir].ifindex;
+- ft->dst_cache = dst;
+-}
+-
+-struct flow_offload *
+-flow_offload_alloc(struct nf_conn *ct, struct nf_flow_route *route)
+-{
+- struct flow_offload_entry *entry;
+- struct flow_offload *flow;
+-
+- if (unlikely(nf_ct_is_dying(ct) ||
+- !atomic_inc_not_zero(&ct->ct_general.use)))
+- return NULL;
+-
+- entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+- if (!entry)
+- goto err_ct_refcnt;
+-
+- flow = &entry->flow;
+-
+- if (!dst_hold_safe(route->tuple[FLOW_OFFLOAD_DIR_ORIGINAL].dst))
+- goto err_dst_cache_original;
+-
+- if (!dst_hold_safe(route->tuple[FLOW_OFFLOAD_DIR_REPLY].dst))
+- goto err_dst_cache_reply;
+-
+- entry->ct = ct;
+-
+- flow_offload_fill_dir(flow, ct, route, FLOW_OFFLOAD_DIR_ORIGINAL);
+- flow_offload_fill_dir(flow, ct, route, FLOW_OFFLOAD_DIR_REPLY);
+-
+- if (ct->status & IPS_SRC_NAT)
+- flow->flags |= FLOW_OFFLOAD_SNAT;
+- else if (ct->status & IPS_DST_NAT)
+- flow->flags |= FLOW_OFFLOAD_DNAT;
+-
+- return flow;
+-
+-err_dst_cache_reply:
+- dst_release(route->tuple[FLOW_OFFLOAD_DIR_ORIGINAL].dst);
+-err_dst_cache_original:
+- kfree(entry);
+-err_ct_refcnt:
+- nf_ct_put(ct);
+-
+- return NULL;
+-}
+-EXPORT_SYMBOL_GPL(flow_offload_alloc);
+-
+-void flow_offload_free(struct flow_offload *flow)
+-{
+- struct flow_offload_entry *e;
+-
+- dst_release(flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_cache);
+- dst_release(flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_cache);
+- e = container_of(flow, struct flow_offload_entry, flow);
+- nf_ct_delete(e->ct, 0, 0);
+- nf_ct_put(e->ct);
+- kfree_rcu(e, rcu_head);
+-}
+-EXPORT_SYMBOL_GPL(flow_offload_free);
+-
+-void flow_offload_dead(struct flow_offload *flow)
+-{
+- flow->flags |= FLOW_OFFLOAD_DYING;
+-}
+-EXPORT_SYMBOL_GPL(flow_offload_dead);
+-
+-int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow)
+-{
+- flow->timeout = (u32)jiffies;
+-
+- rhashtable_insert_fast(&flow_table->rhashtable,
+- &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].node,
+- *flow_table->type->params);
+- rhashtable_insert_fast(&flow_table->rhashtable,
+- &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].node,
+- *flow_table->type->params);
+- return 0;
+-}
+-EXPORT_SYMBOL_GPL(flow_offload_add);
+-
+-static void flow_offload_del(struct nf_flowtable *flow_table,
+- struct flow_offload *flow)
+-{
+- rhashtable_remove_fast(&flow_table->rhashtable,
+- &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].node,
+- *flow_table->type->params);
+- rhashtable_remove_fast(&flow_table->rhashtable,
+- &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].node,
+- *flow_table->type->params);
+-
+- flow_offload_free(flow);
+-}
+-
+-struct flow_offload_tuple_rhash *
+-flow_offload_lookup(struct nf_flowtable *flow_table,
+- struct flow_offload_tuple *tuple)
+-{
+- return rhashtable_lookup_fast(&flow_table->rhashtable, tuple,
+- *flow_table->type->params);
+-}
+-EXPORT_SYMBOL_GPL(flow_offload_lookup);
+-
+-int nf_flow_table_iterate(struct nf_flowtable *flow_table,
+- void (*iter)(struct flow_offload *flow, void *data),
+- void *data)
+-{
+- struct flow_offload_tuple_rhash *tuplehash;
+- struct rhashtable_iter hti;
+- struct flow_offload *flow;
+- int err;
+-
+- err = rhashtable_walk_init(&flow_table->rhashtable, &hti, GFP_KERNEL);
+- if (err)
+- return err;
+-
+- rhashtable_walk_start(&hti);
+-
+- while ((tuplehash = rhashtable_walk_next(&hti))) {
+- if (IS_ERR(tuplehash)) {
+- err = PTR_ERR(tuplehash);
+- if (err != -EAGAIN)
+- goto out;
+-
+- continue;
+- }
+- if (tuplehash->tuple.dir)
+- continue;
+-
+- flow = container_of(tuplehash, struct flow_offload, tuplehash[0]);
+-
+- iter(flow, data);
+- }
+-out:
+- rhashtable_walk_stop(&hti);
+- rhashtable_walk_exit(&hti);
+-
+- return err;
+-}
+-EXPORT_SYMBOL_GPL(nf_flow_table_iterate);
+-
+-static inline bool nf_flow_has_expired(const struct flow_offload *flow)
+-{
+- return (__s32)(flow->timeout - (u32)jiffies) <= 0;
+-}
+-
+-static inline bool nf_flow_is_dying(const struct flow_offload *flow)
+-{
+- return flow->flags & FLOW_OFFLOAD_DYING;
+-}
+-
+-static int nf_flow_offload_gc_step(struct nf_flowtable *flow_table)
+-{
+- struct flow_offload_tuple_rhash *tuplehash;
+- struct rhashtable_iter hti;
+- struct flow_offload *flow;
+- int err;
+-
+- err = rhashtable_walk_init(&flow_table->rhashtable, &hti, GFP_KERNEL);
+- if (err)
+- return 0;
+-
+- rhashtable_walk_start(&hti);
+-
+- while ((tuplehash = rhashtable_walk_next(&hti))) {
+- if (IS_ERR(tuplehash)) {
+- err = PTR_ERR(tuplehash);
+- if (err != -EAGAIN)
+- goto out;
+-
+- continue;
+- }
+- if (tuplehash->tuple.dir)
+- continue;
+-
+- flow = container_of(tuplehash, struct flow_offload, tuplehash[0]);
+-
+- if (nf_flow_has_expired(flow) ||
+- nf_flow_is_dying(flow))
+- flow_offload_del(flow_table, flow);
+- }
+-out:
+- rhashtable_walk_stop(&hti);
+- rhashtable_walk_exit(&hti);
+-
+- return 1;
+-}
+-
+-void nf_flow_offload_work_gc(struct work_struct *work)
+-{
+- struct nf_flowtable *flow_table;
+-
+- flow_table = container_of(work, struct nf_flowtable, gc_work.work);
+- nf_flow_offload_gc_step(flow_table);
+- queue_delayed_work(system_power_efficient_wq, &flow_table->gc_work, HZ);
+-}
+-EXPORT_SYMBOL_GPL(nf_flow_offload_work_gc);
+-
+-static u32 flow_offload_hash(const void *data, u32 len, u32 seed)
+-{
+- const struct flow_offload_tuple *tuple = data;
+-
+- return jhash(tuple, offsetof(struct flow_offload_tuple, dir), seed);
+-}
+-
+-static u32 flow_offload_hash_obj(const void *data, u32 len, u32 seed)
+-{
+- const struct flow_offload_tuple_rhash *tuplehash = data;
+-
+- return jhash(&tuplehash->tuple, offsetof(struct flow_offload_tuple, dir), seed);
+-}
+-
+-static int flow_offload_hash_cmp(struct rhashtable_compare_arg *arg,
+- const void *ptr)
+-{
+- const struct flow_offload_tuple *tuple = arg->key;
+- const struct flow_offload_tuple_rhash *x = ptr;
+-
+- if (memcmp(&x->tuple, tuple, offsetof(struct flow_offload_tuple, dir)))
+- return 1;
+-
+- return 0;
+-}
+-
+-const struct rhashtable_params nf_flow_offload_rhash_params = {
+- .head_offset = offsetof(struct flow_offload_tuple_rhash, node),
+- .hashfn = flow_offload_hash,
+- .obj_hashfn = flow_offload_hash_obj,
+- .obj_cmpfn = flow_offload_hash_cmp,
+- .automatic_shrinking = true,
+-};
+-EXPORT_SYMBOL_GPL(nf_flow_offload_rhash_params);
+-
+-static int nf_flow_nat_port_tcp(struct sk_buff *skb, unsigned int thoff,
+- __be16 port, __be16 new_port)
+-{
+- 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_replace2(&tcph->check, skb, port, new_port, true);
+-
+- return 0;
+-}
+-
+-static int nf_flow_nat_port_udp(struct sk_buff *skb, unsigned int thoff,
+- __be16 port, __be16 new_port)
+-{
+- 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_replace2(&udph->check, skb, port,
+- new_port, true);
+- if (!udph->check)
+- udph->check = CSUM_MANGLED_0;
+- }
+-
+- return 0;
+-}
+-
+-static int nf_flow_nat_port(struct sk_buff *skb, unsigned int thoff,
+- u8 protocol, __be16 port, __be16 new_port)
+-{
+- switch (protocol) {
+- case IPPROTO_TCP:
+- if (nf_flow_nat_port_tcp(skb, thoff, port, new_port) < 0)
+- return NF_DROP;
+- break;
+- case IPPROTO_UDP:
+- if (nf_flow_nat_port_udp(skb, thoff, port, new_port) < 0)
+- return NF_DROP;
+- break;
+- }
+-
+- return 0;
+-}
+-
+-int nf_flow_snat_port(const struct flow_offload *flow,
+- struct sk_buff *skb, unsigned int thoff,
+- u8 protocol, enum flow_offload_tuple_dir dir)
+-{
+- struct flow_ports *hdr;
+- __be16 port, new_port;
+-
+- if (!pskb_may_pull(skb, thoff + sizeof(*hdr)) ||
+- skb_try_make_writable(skb, thoff + sizeof(*hdr)))
+- return -1;
+-
+- hdr = (void *)(skb_network_header(skb) + thoff);
+-
+- switch (dir) {
+- case FLOW_OFFLOAD_DIR_ORIGINAL:
+- port = hdr->source;
+- new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_port;
+- hdr->source = new_port;
+- break;
+- case FLOW_OFFLOAD_DIR_REPLY:
+- port = hdr->dest;
+- new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_port;
+- hdr->dest = new_port;
+- break;
+- default:
+- return -1;
+- }
+-
+- return nf_flow_nat_port(skb, thoff, protocol, port, new_port);
+-}
+-EXPORT_SYMBOL_GPL(nf_flow_snat_port);
+-
+-int nf_flow_dnat_port(const struct flow_offload *flow,
+- struct sk_buff *skb, unsigned int thoff,
+- u8 protocol, enum flow_offload_tuple_dir dir)
+-{
+- struct flow_ports *hdr;
+- __be16 port, new_port;
+-
+- if (!pskb_may_pull(skb, thoff + sizeof(*hdr)) ||
+- skb_try_make_writable(skb, thoff + sizeof(*hdr)))
+- return -1;
+-
+- hdr = (void *)(skb_network_header(skb) + thoff);
+-
+- switch (dir) {
+- case FLOW_OFFLOAD_DIR_ORIGINAL:
+- port = hdr->dest;
+- new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_port;
+- hdr->dest = new_port;
+- break;
+- case FLOW_OFFLOAD_DIR_REPLY:
+- port = hdr->source;
+- new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_port;
+- hdr->source = new_port;
+- break;
+- default:
+- return -1;
+- }
+-
+- return nf_flow_nat_port(skb, thoff, protocol, port, new_port);
+-}
+-EXPORT_SYMBOL_GPL(nf_flow_dnat_port);
+-
+-static void nf_flow_table_do_cleanup(struct flow_offload *flow, void *data)
+-{
+- struct net_device *dev = data;
+-
+- if (dev && flow->tuplehash[0].tuple.iifidx != dev->ifindex)
+- return;
+-
+- flow_offload_dead(flow);
+-}
+-
+-static void nf_flow_table_iterate_cleanup(struct nf_flowtable *flowtable,
+- void *data)
+-{
+- nf_flow_table_iterate(flowtable, nf_flow_table_do_cleanup, data);
+- flush_delayed_work(&flowtable->gc_work);
+-}
+-
+-void nf_flow_table_cleanup(struct net *net, struct net_device *dev)
+-{
+- nft_flow_table_iterate(net, nf_flow_table_iterate_cleanup, dev);
+-}
+-EXPORT_SYMBOL_GPL(nf_flow_table_cleanup);
+-
+-void nf_flow_table_free(struct nf_flowtable *flow_table)
+-{
+- nf_flow_table_iterate(flow_table, nf_flow_table_do_cleanup, NULL);
+- WARN_ON(!nf_flow_offload_gc_step(flow_table));
+-}
+-EXPORT_SYMBOL_GPL(nf_flow_table_free);
+-
+-static int nf_flow_table_netdev_event(struct notifier_block *this,
+- unsigned long event, void *ptr)
+-{
+- struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+-
+- if (event != NETDEV_DOWN)
+- return NOTIFY_DONE;
+-
+- nf_flow_table_cleanup(dev_net(dev), dev);
+-
+- return NOTIFY_DONE;
+-}
+-
+-static struct notifier_block flow_offload_netdev_notifier = {
+- .notifier_call = nf_flow_table_netdev_event,
+-};
+-
+-static int __init nf_flow_table_module_init(void)
+-{
+- return register_netdevice_notifier(&flow_offload_netdev_notifier);
+-}
+-
+-static void __exit nf_flow_table_module_exit(void)
+-{
+- unregister_netdevice_notifier(&flow_offload_netdev_notifier);
+-}
+-
+-module_init(nf_flow_table_module_init);
+-module_exit(nf_flow_table_module_exit);
+-
+-MODULE_LICENSE("GPL");
+-MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
+--- /dev/null
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -0,0 +1,462 @@
++#include <linux/kernel.h>
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/netfilter.h>
++#include <linux/rhashtable.h>
++#include <linux/netdevice.h>
++#include <net/ip.h>
++#include <net/ip6_route.h>
++#include <net/netfilter/nf_tables.h>
++#include <net/netfilter/nf_flow_table.h>
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_core.h>
++#include <net/netfilter/nf_conntrack_tuple.h>
++
++struct flow_offload_entry {
++ struct flow_offload flow;
++ struct nf_conn *ct;
++ struct rcu_head rcu_head;
++};
++
++static void
++flow_offload_fill_dir(struct flow_offload *flow, struct nf_conn *ct,
++ struct nf_flow_route *route,
++ enum flow_offload_tuple_dir dir)
++{
++ struct flow_offload_tuple *ft = &flow->tuplehash[dir].tuple;
++ struct nf_conntrack_tuple *ctt = &ct->tuplehash[dir].tuple;
++ struct dst_entry *dst = route->tuple[dir].dst;
++
++ ft->dir = dir;
++
++ switch (ctt->src.l3num) {
++ case NFPROTO_IPV4:
++ ft->src_v4 = ctt->src.u3.in;
++ ft->dst_v4 = ctt->dst.u3.in;
++ ft->mtu = ip_dst_mtu_maybe_forward(dst, true);
++ break;
++ case NFPROTO_IPV6:
++ ft->src_v6 = ctt->src.u3.in6;
++ ft->dst_v6 = ctt->dst.u3.in6;
++ ft->mtu = ip6_dst_mtu_forward(dst);
++ break;
++ }
++
++ ft->l3proto = ctt->src.l3num;
++ ft->l4proto = ctt->dst.protonum;
++ ft->src_port = ctt->src.u.tcp.port;
++ ft->dst_port = ctt->dst.u.tcp.port;
++
++ ft->iifidx = route->tuple[dir].ifindex;
++ ft->oifidx = route->tuple[!dir].ifindex;
++ ft->dst_cache = dst;
++}
++
++struct flow_offload *
++flow_offload_alloc(struct nf_conn *ct, struct nf_flow_route *route)
++{
++ struct flow_offload_entry *entry;
++ struct flow_offload *flow;
++
++ if (unlikely(nf_ct_is_dying(ct) ||
++ !atomic_inc_not_zero(&ct->ct_general.use)))
++ return NULL;
++
++ entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
++ if (!entry)
++ goto err_ct_refcnt;
++
++ flow = &entry->flow;
++
++ if (!dst_hold_safe(route->tuple[FLOW_OFFLOAD_DIR_ORIGINAL].dst))
++ goto err_dst_cache_original;
++
++ if (!dst_hold_safe(route->tuple[FLOW_OFFLOAD_DIR_REPLY].dst))
++ goto err_dst_cache_reply;
++
++ entry->ct = ct;
++
++ flow_offload_fill_dir(flow, ct, route, FLOW_OFFLOAD_DIR_ORIGINAL);
++ flow_offload_fill_dir(flow, ct, route, FLOW_OFFLOAD_DIR_REPLY);
++
++ if (ct->status & IPS_SRC_NAT)
++ flow->flags |= FLOW_OFFLOAD_SNAT;
++ else if (ct->status & IPS_DST_NAT)
++ flow->flags |= FLOW_OFFLOAD_DNAT;
++
++ return flow;
++
++err_dst_cache_reply:
++ dst_release(route->tuple[FLOW_OFFLOAD_DIR_ORIGINAL].dst);
++err_dst_cache_original:
++ kfree(entry);
++err_ct_refcnt:
++ nf_ct_put(ct);
++
++ return NULL;
++}
++EXPORT_SYMBOL_GPL(flow_offload_alloc);
++
++void flow_offload_free(struct flow_offload *flow)
++{
++ struct flow_offload_entry *e;
++
++ dst_release(flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_cache);
++ dst_release(flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_cache);
++ e = container_of(flow, struct flow_offload_entry, flow);
++ nf_ct_delete(e->ct, 0, 0);
++ nf_ct_put(e->ct);
++ kfree_rcu(e, rcu_head);
++}
++EXPORT_SYMBOL_GPL(flow_offload_free);
++
++void flow_offload_dead(struct flow_offload *flow)
++{
++ flow->flags |= FLOW_OFFLOAD_DYING;
++}
++EXPORT_SYMBOL_GPL(flow_offload_dead);
++
++int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow)
++{
++ flow->timeout = (u32)jiffies;
++
++ rhashtable_insert_fast(&flow_table->rhashtable,
++ &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].node,
++ *flow_table->type->params);
++ rhashtable_insert_fast(&flow_table->rhashtable,
++ &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].node,
++ *flow_table->type->params);
++ return 0;
++}
++EXPORT_SYMBOL_GPL(flow_offload_add);
++
++static void flow_offload_del(struct nf_flowtable *flow_table,
++ struct flow_offload *flow)
++{
++ rhashtable_remove_fast(&flow_table->rhashtable,
++ &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].node,
++ *flow_table->type->params);
++ rhashtable_remove_fast(&flow_table->rhashtable,
++ &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].node,
++ *flow_table->type->params);
++
++ flow_offload_free(flow);
++}
++
++struct flow_offload_tuple_rhash *
++flow_offload_lookup(struct nf_flowtable *flow_table,
++ struct flow_offload_tuple *tuple)
++{
++ return rhashtable_lookup_fast(&flow_table->rhashtable, tuple,
++ *flow_table->type->params);
++}
++EXPORT_SYMBOL_GPL(flow_offload_lookup);
++
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
++ void (*iter)(struct flow_offload *flow, void *data),
++ void *data)
++{
++ struct flow_offload_tuple_rhash *tuplehash;
++ struct rhashtable_iter hti;
++ struct flow_offload *flow;
++ int err;
++
++ err = rhashtable_walk_init(&flow_table->rhashtable, &hti, GFP_KERNEL);
++ if (err)
++ return err;
++
++ rhashtable_walk_start(&hti);
++
++ while ((tuplehash = rhashtable_walk_next(&hti))) {
++ if (IS_ERR(tuplehash)) {
++ err = PTR_ERR(tuplehash);
++ if (err != -EAGAIN)
++ goto out;
++
++ continue;
++ }
++ if (tuplehash->tuple.dir)
++ continue;
++
++ flow = container_of(tuplehash, struct flow_offload, tuplehash[0]);
++
++ iter(flow, data);
++ }
++out:
++ rhashtable_walk_stop(&hti);
++ rhashtable_walk_exit(&hti);
++
++ return err;
++}
++EXPORT_SYMBOL_GPL(nf_flow_table_iterate);
++
++static inline bool nf_flow_has_expired(const struct flow_offload *flow)
++{
++ return (__s32)(flow->timeout - (u32)jiffies) <= 0;
++}
++
++static inline bool nf_flow_is_dying(const struct flow_offload *flow)
++{
++ return flow->flags & FLOW_OFFLOAD_DYING;
++}
++
++static int nf_flow_offload_gc_step(struct nf_flowtable *flow_table)
++{
++ struct flow_offload_tuple_rhash *tuplehash;
++ struct rhashtable_iter hti;
++ struct flow_offload *flow;
++ int err;
++
++ err = rhashtable_walk_init(&flow_table->rhashtable, &hti, GFP_KERNEL);
++ if (err)
++ return 0;
++
++ rhashtable_walk_start(&hti);
++
++ while ((tuplehash = rhashtable_walk_next(&hti))) {
++ if (IS_ERR(tuplehash)) {
++ err = PTR_ERR(tuplehash);
++ if (err != -EAGAIN)
++ goto out;
++
++ continue;
++ }
++ if (tuplehash->tuple.dir)
++ continue;
++
++ flow = container_of(tuplehash, struct flow_offload, tuplehash[0]);
++
++ if (nf_flow_has_expired(flow) ||
++ nf_flow_is_dying(flow))
++ flow_offload_del(flow_table, flow);
++ }
++out:
++ rhashtable_walk_stop(&hti);
++ rhashtable_walk_exit(&hti);
++
++ return 1;
++}
++
++void nf_flow_offload_work_gc(struct work_struct *work)
++{
++ struct nf_flowtable *flow_table;
++
++ flow_table = container_of(work, struct nf_flowtable, gc_work.work);
++ nf_flow_offload_gc_step(flow_table);
++ queue_delayed_work(system_power_efficient_wq, &flow_table->gc_work, HZ);
++}
++EXPORT_SYMBOL_GPL(nf_flow_offload_work_gc);
++
++static u32 flow_offload_hash(const void *data, u32 len, u32 seed)
++{
++ const struct flow_offload_tuple *tuple = data;
++
++ return jhash(tuple, offsetof(struct flow_offload_tuple, dir), seed);
++}
++
++static u32 flow_offload_hash_obj(const void *data, u32 len, u32 seed)
++{
++ const struct flow_offload_tuple_rhash *tuplehash = data;
++
++ return jhash(&tuplehash->tuple, offsetof(struct flow_offload_tuple, dir), seed);
++}
++
++static int flow_offload_hash_cmp(struct rhashtable_compare_arg *arg,
++ const void *ptr)
++{
++ const struct flow_offload_tuple *tuple = arg->key;
++ const struct flow_offload_tuple_rhash *x = ptr;
++
++ if (memcmp(&x->tuple, tuple, offsetof(struct flow_offload_tuple, dir)))
++ return 1;
++
++ return 0;
++}
++
++const struct rhashtable_params nf_flow_offload_rhash_params = {
++ .head_offset = offsetof(struct flow_offload_tuple_rhash, node),
++ .hashfn = flow_offload_hash,
++ .obj_hashfn = flow_offload_hash_obj,
++ .obj_cmpfn = flow_offload_hash_cmp,
++ .automatic_shrinking = true,
++};
++EXPORT_SYMBOL_GPL(nf_flow_offload_rhash_params);
++
++static int nf_flow_nat_port_tcp(struct sk_buff *skb, unsigned int thoff,
++ __be16 port, __be16 new_port)
++{
++ 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_replace2(&tcph->check, skb, port, new_port, true);
++
++ return 0;
++}
++
++static int nf_flow_nat_port_udp(struct sk_buff *skb, unsigned int thoff,
++ __be16 port, __be16 new_port)
++{
++ 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_replace2(&udph->check, skb, port,
++ new_port, true);
++ if (!udph->check)
++ udph->check = CSUM_MANGLED_0;
++ }
++
++ return 0;
++}
++
++static int nf_flow_nat_port(struct sk_buff *skb, unsigned int thoff,
++ u8 protocol, __be16 port, __be16 new_port)
++{
++ switch (protocol) {
++ case IPPROTO_TCP:
++ if (nf_flow_nat_port_tcp(skb, thoff, port, new_port) < 0)
++ return NF_DROP;
++ break;
++ case IPPROTO_UDP:
++ if (nf_flow_nat_port_udp(skb, thoff, port, new_port) < 0)
++ return NF_DROP;
++ break;
++ }
++
++ return 0;
++}
++
++int nf_flow_snat_port(const struct flow_offload *flow,
++ struct sk_buff *skb, unsigned int thoff,
++ u8 protocol, enum flow_offload_tuple_dir dir)
++{
++ struct flow_ports *hdr;
++ __be16 port, new_port;
++
++ if (!pskb_may_pull(skb, thoff + sizeof(*hdr)) ||
++ skb_try_make_writable(skb, thoff + sizeof(*hdr)))
++ return -1;
++
++ hdr = (void *)(skb_network_header(skb) + thoff);
++
++ switch (dir) {
++ case FLOW_OFFLOAD_DIR_ORIGINAL:
++ port = hdr->source;
++ new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_port;
++ hdr->source = new_port;
++ break;
++ case FLOW_OFFLOAD_DIR_REPLY:
++ port = hdr->dest;
++ new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_port;
++ hdr->dest = new_port;
++ break;
++ default:
++ return -1;
++ }
++
++ return nf_flow_nat_port(skb, thoff, protocol, port, new_port);
++}
++EXPORT_SYMBOL_GPL(nf_flow_snat_port);
++
++int nf_flow_dnat_port(const struct flow_offload *flow,
++ struct sk_buff *skb, unsigned int thoff,
++ u8 protocol, enum flow_offload_tuple_dir dir)
++{
++ struct flow_ports *hdr;
++ __be16 port, new_port;
++
++ if (!pskb_may_pull(skb, thoff + sizeof(*hdr)) ||
++ skb_try_make_writable(skb, thoff + sizeof(*hdr)))
++ return -1;
++
++ hdr = (void *)(skb_network_header(skb) + thoff);
++
++ switch (dir) {
++ case FLOW_OFFLOAD_DIR_ORIGINAL:
++ port = hdr->dest;
++ new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_port;
++ hdr->dest = new_port;
++ break;
++ case FLOW_OFFLOAD_DIR_REPLY:
++ port = hdr->source;
++ new_port = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_port;
++ hdr->source = new_port;
++ break;
++ default:
++ return -1;
++ }
++
++ return nf_flow_nat_port(skb, thoff, protocol, port, new_port);
++}
++EXPORT_SYMBOL_GPL(nf_flow_dnat_port);
++
++static void nf_flow_table_do_cleanup(struct flow_offload *flow, void *data)
++{
++ struct net_device *dev = data;
++
++ if (dev && flow->tuplehash[0].tuple.iifidx != dev->ifindex)
++ return;
++
++ flow_offload_dead(flow);
++}
++
++static void nf_flow_table_iterate_cleanup(struct nf_flowtable *flowtable,
++ void *data)
++{
++ nf_flow_table_iterate(flowtable, nf_flow_table_do_cleanup, data);
++ flush_delayed_work(&flowtable->gc_work);
++}
++
++void nf_flow_table_cleanup(struct net *net, struct net_device *dev)
++{
++ nft_flow_table_iterate(net, nf_flow_table_iterate_cleanup, dev);
++}
++EXPORT_SYMBOL_GPL(nf_flow_table_cleanup);
++
++void nf_flow_table_free(struct nf_flowtable *flow_table)
++{
++ nf_flow_table_iterate(flow_table, nf_flow_table_do_cleanup, NULL);
++ WARN_ON(!nf_flow_offload_gc_step(flow_table));
++}
++EXPORT_SYMBOL_GPL(nf_flow_table_free);
++
++static int nf_flow_table_netdev_event(struct notifier_block *this,
++ unsigned long event, void *ptr)
++{
++ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++
++ if (event != NETDEV_DOWN)
++ return NOTIFY_DONE;
++
++ nf_flow_table_cleanup(dev_net(dev), dev);
++
++ return NOTIFY_DONE;
++}
++
++static struct notifier_block flow_offload_netdev_notifier = {
++ .notifier_call = nf_flow_table_netdev_event,
++};
++
++static int __init nf_flow_table_module_init(void)
++{
++ return register_netdevice_notifier(&flow_offload_netdev_notifier);
++}
++
++static void __exit nf_flow_table_module_exit(void)
++{
++ unregister_netdevice_notifier(&flow_offload_netdev_notifier);
++}
++
++module_init(nf_flow_table_module_init);
++module_exit(nf_flow_table_module_exit);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");