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:
authorHauke Mehrtens <hauke@hauke-m.de>2018-11-01 17:57:55 +0100
committerHauke Mehrtens <hauke@hauke-m.de>2018-12-15 12:50:06 +0100
commit52a82ce3dd901a1536c7d7d9d963e9c2d761c816 (patch)
tree79020fb59420fcea7bdc4b12272f7251e101fe64 /target/linux/generic/backport-4.19/352-v4.18-netfilter-nf_flow_table-rename-nf_flow_table.c-to-nf.patch
parenta272af75cd8c67d265400f96c1e6bb172315c23c (diff)
downloadupstream-52a82ce3dd901a1536c7d7d9d963e9c2d761c816.tar.gz
upstream-52a82ce3dd901a1536c7d7d9d963e9c2d761c816.tar.bz2
upstream-52a82ce3dd901a1536c7d7d9d963e9c2d761c816.zip
kernel: Copy patches from kernel 4.14 to 4.19
This just copies the files from the kernel 4.14 specific folders into the kernel 4.19 specific folder, no changes are done to the files in this commit. Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
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>");