--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 20 Feb 2018 15:56:02 +0100
+Subject: [PATCH] netfilter: add xt_FLOWOFFLOAD target
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ create mode 100644 net/netfilter/xt_OFFLOAD.c
+
+--- a/net/ipv4/netfilter/Kconfig
++++ b/net/ipv4/netfilter/Kconfig
+@@ -56,8 +56,6 @@ config NF_TABLES_ARP
+ help
+ This option enables the ARP support for nf_tables.
+
+-endif # NF_TABLES
+-
+ config NF_FLOW_TABLE_IPV4
+ tristate "Netfilter flow table IPv4 module"
+ depends on NF_FLOW_TABLE
+@@ -66,6 +64,8 @@ config NF_FLOW_TABLE_IPV4
+
+ To compile it as a module, choose M here.
+
++endif # NF_TABLES
++
+ config NF_DUP_IPV4
+ tristate "Netfilter IPv4 packet duplication to alternate destination"
+ depends on !NF_CONNTRACK || NF_CONNTRACK
+--- a/net/ipv6/netfilter/Kconfig
++++ b/net/ipv6/netfilter/Kconfig
+@@ -45,7 +45,6 @@ config NFT_FIB_IPV6
+ multicast or blackhole.
+
+ endif # NF_TABLES_IPV6
+-endif # NF_TABLES
+
+ config NF_FLOW_TABLE_IPV6
+ tristate "Netfilter flow table IPv6 module"
+@@ -55,6 +54,8 @@ config NF_FLOW_TABLE_IPV6
+
+ To compile it as a module, choose M here.
+
++endif # NF_TABLES
++
+ config NF_DUP_IPV6
+ tristate "Netfilter IPv6 packet duplication to alternate destination"
+ depends on !NF_CONNTRACK || NF_CONNTRACK
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -683,8 +683,6 @@ config NFT_FIB_NETDEV
+
+ endif # NF_TABLES_NETDEV
+
+-endif # NF_TABLES
+-
+ config NF_FLOW_TABLE_INET
+ tristate "Netfilter flow table mixed IPv4/IPv6 module"
+ depends on NF_FLOW_TABLE
+@@ -693,11 +691,12 @@ config NF_FLOW_TABLE_INET
+
+ To compile it as a module, choose M here.
+
++endif # NF_TABLES
++
+ config NF_FLOW_TABLE
+ tristate "Netfilter flow table module"
+ depends on NETFILTER_INGRESS
+ depends on NF_CONNTRACK
+- depends on NF_TABLES
+ help
+ This option adds the flow table core infrastructure.
+
+@@ -977,6 +976,15 @@ config NETFILTER_XT_TARGET_NOTRACK
+ depends on NETFILTER_ADVANCED
+ select NETFILTER_XT_TARGET_CT
+
++config NETFILTER_XT_TARGET_FLOWOFFLOAD
++ tristate '"FLOWOFFLOAD" target support'
++ depends on NF_FLOW_TABLE
++ depends on NETFILTER_INGRESS
++ help
++ This option adds a `FLOWOFFLOAD' target, which uses the nf_flow_offload
++ module to speed up processing of packets by bypassing the usual
++ netfilter chains
++
+ config NETFILTER_XT_TARGET_RATEEST
+ tristate '"RATEEST" target support'
+ depends on NETFILTER_ADVANCED
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -145,6 +145,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_CLASSIF
+ obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_CT) += xt_CT.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o
++obj-$(CONFIG_NETFILTER_XT_TARGET_FLOWOFFLOAD) += xt_FLOWOFFLOAD.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HMARK) += xt_HMARK.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o
+--- /dev/null
++++ b/net/netfilter/xt_FLOWOFFLOAD.c
+@@ -0,0 +1,658 @@
++/*
++ * Copyright (C) 2018-2021 Felix Fietkau <nbd@nbd.name>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/netfilter.h>
++#include <linux/netfilter/xt_FLOWOFFLOAD.h>
++#include <net/ip.h>
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_extend.h>
++#include <net/netfilter/nf_conntrack_helper.h>
++#include <net/netfilter/nf_flow_table.h>
++
++struct xt_flowoffload_hook {
++ struct hlist_node list;
++ struct nf_hook_ops ops;
++ struct net *net;
++ bool registered;
++ bool used;
++};
++
++struct xt_flowoffload_table {
++ struct nf_flowtable ft;
++ struct hlist_head hooks;
++ struct delayed_work work;
++};
++
++static DEFINE_SPINLOCK(hooks_lock);
++
++struct xt_flowoffload_table flowtable[2];
++
++static unsigned int
++xt_flowoffload_net_hook(void *priv, struct sk_buff *skb,
++ const struct nf_hook_state *state)
++{
++ struct nf_flowtable *ft = priv;
++
++ if (!atomic_read(&ft->rhashtable.nelems))
++ return NF_ACCEPT;
++
++ switch (skb->protocol) {
++ case htons(ETH_P_IP):
++ return nf_flow_offload_ip_hook(priv, skb, state);
++ case htons(ETH_P_IPV6):
++ return nf_flow_offload_ipv6_hook(priv, skb, state);
++ }
++
++ return NF_ACCEPT;
++}
++
++static int
++xt_flowoffload_create_hook(struct xt_flowoffload_table *table,
++ struct net_device *dev)
++{
++ struct xt_flowoffload_hook *hook;
++ struct nf_hook_ops *ops;
++
++ hook = kzalloc(sizeof(*hook), GFP_ATOMIC);
++ if (!hook)
++ return -ENOMEM;
++
++ ops = &hook->ops;
++ ops->pf = NFPROTO_NETDEV;
++ ops->hooknum = NF_NETDEV_INGRESS;
++ ops->priority = 10;
++ ops->priv = &table->ft;
++ ops->hook = xt_flowoffload_net_hook;
++ ops->dev = dev;
++
++ hlist_add_head(&hook->list, &table->hooks);
++ mod_delayed_work(system_power_efficient_wq, &table->work, 0);
++
++ return 0;
++}
++
++static struct xt_flowoffload_hook *
++flow_offload_lookup_hook(struct xt_flowoffload_table *table,
++ struct net_device *dev)
++{
++ struct xt_flowoffload_hook *hook;
++
++ hlist_for_each_entry(hook, &table->hooks, list) {
++ if (hook->ops.dev == dev)
++ return hook;
++ }
++
++ return NULL;
++}
++
++static void
++xt_flowoffload_check_device(struct xt_flowoffload_table *table,
++ struct net_device *dev)
++{
++ struct xt_flowoffload_hook *hook;
++
++ if (!dev)
++ return;
++
++ spin_lock_bh(&hooks_lock);
++ hook = flow_offload_lookup_hook(table, dev);
++ if (hook)
++ hook->used = true;
++ else
++ xt_flowoffload_create_hook(table, dev);
++ spin_unlock_bh(&hooks_lock);
++}
++
++static void
++xt_flowoffload_register_hooks(struct xt_flowoffload_table *table)
++{
++ struct xt_flowoffload_hook *hook;
++
++restart:
++ hlist_for_each_entry(hook, &table->hooks, list) {
++ if (hook->registered)
++ continue;
++
++ hook->registered = true;
++ hook->net = dev_net(hook->ops.dev);
++ spin_unlock_bh(&hooks_lock);
++ nf_register_net_hook(hook->net, &hook->ops);
++ if (table->ft.flags & NF_FLOWTABLE_HW_OFFLOAD)
++ table->ft.type->setup(&table->ft, hook->ops.dev,
++ FLOW_BLOCK_BIND);
++ spin_lock_bh(&hooks_lock);
++ goto restart;
++ }
++
++}
++
++static bool
++xt_flowoffload_cleanup_hooks(struct xt_flowoffload_table *table)
++{
++ struct xt_flowoffload_hook *hook;
++ bool active = false;
++
++restart:
++ spin_lock_bh(&hooks_lock);
++ hlist_for_each_entry(hook, &table->hooks, list) {
++ if (hook->used || !hook->registered) {
++ active = true;
++ continue;
++ }
++
++ hlist_del(&hook->list);
++ spin_unlock_bh(&hooks_lock);
++ if (table->ft.flags & NF_FLOWTABLE_HW_OFFLOAD)
++ table->ft.type->setup(&table->ft, hook->ops.dev,
++ FLOW_BLOCK_UNBIND);
++ nf_unregister_net_hook(hook->net, &hook->ops);
++ kfree(hook);
++ goto restart;
++ }
++ spin_unlock_bh(&hooks_lock);
++
++ return active;
++}
++
++static void
++xt_flowoffload_check_hook(struct flow_offload *flow, void *data)
++{
++ struct xt_flowoffload_table *table = data;
++ struct flow_offload_tuple *tuple0 = &flow->tuplehash[0].tuple;
++ struct flow_offload_tuple *tuple1 = &flow->tuplehash[1].tuple;
++ struct xt_flowoffload_hook *hook;
++
++ spin_lock_bh(&hooks_lock);
++ hlist_for_each_entry(hook, &table->hooks, list) {
++ if (hook->ops.dev->ifindex != tuple0->iifidx &&
++ hook->ops.dev->ifindex != tuple1->iifidx)
++ continue;
++
++ hook->used = true;
++ }
++ spin_unlock_bh(&hooks_lock);
++
++ cond_resched();
++}
++
++static void
++xt_flowoffload_hook_work(struct work_struct *work)
++{
++ struct xt_flowoffload_table *table;
++ struct xt_flowoffload_hook *hook;
++ int err;
++
++ table = container_of(work, struct xt_flowoffload_table, work.work);
++
++ spin_lock_bh(&hooks_lock);
++ xt_flowoffload_register_hooks(table);
++ hlist_for_each_entry(hook, &table->hooks, list)
++ hook->used = false;
++ spin_unlock_bh(&hooks_lock);
++
++ err = nf_flow_table_iterate(&table->ft, xt_flowoffload_check_hook,
++ table);
++ if (err && err != -EAGAIN)
++ goto out;
++
++ if (!xt_flowoffload_cleanup_hooks(table))
++ return;
++
++out:
++ queue_delayed_work(system_power_efficient_wq, &table->work, HZ);
++}
++
++static bool
++xt_flowoffload_skip(struct sk_buff *skb, int family)
++{
++ if (skb_sec_path(skb))
++ return true;
++
++ if (family == NFPROTO_IPV4) {
++ const struct ip_options *opt = &(IPCB(skb)->opt);
++
++ if (unlikely(opt->optlen))
++ return true;
++ }
++
++ return false;
++}
++
++static bool flow_is_valid_ether_device(const struct net_device *dev)
++{
++ if (!dev || (dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER ||
++ dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr))
++ return false;
++
++ return true;
++}
++
++static void
++xt_flowoffload_route_check_path(struct nf_flow_route *route,
++ const struct nf_conn *ct,
++ enum ip_conntrack_dir dir,
++ struct net_device **out_dev)
++{
++ const struct dst_entry *dst = route->tuple[dir].dst;
++ const void *daddr = &ct->tuplehash[!dir].tuple.src.u3;
++ struct net_device_path_stack stack;
++ enum net_device_path_type prev_type;
++ struct net_device *dev = dst->dev;
++ struct neighbour *n;
++ bool last = false;
++ u8 nud_state;
++ int i;
++
++ route->tuple[!dir].in.ifindex = dev->ifindex;
++ route->tuple[dir].out.ifindex = dev->ifindex;
++
++ if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_XFRM)
++ return;
++
++ if ((dev->flags & IFF_LOOPBACK) ||
++ dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN ||
++ !is_valid_ether_addr(dev->dev_addr))
++ return;
++
++ n = dst_neigh_lookup(dst, daddr);
++ if (!n)
++ return;
++
++ read_lock_bh(&n->lock);
++ nud_state = n->nud_state;
++ memcpy(route->tuple[dir].out.h_dest, n->ha, ETH_ALEN);
++ read_unlock_bh(&n->lock);
++ neigh_release(n);
++
++ if (!(nud_state & NUD_VALID))
++ return;
++
++ if (dev_fill_forward_path(dev, route->tuple[dir].out.h_dest, &stack) ||
++ !stack.num_paths)
++ return;
++
++ prev_type = DEV_PATH_ETHERNET;
++ for (i = 0; i <= stack.num_paths; i++) {
++ const struct net_device_path *path = &stack.path[i];
++ int n_encaps = route->tuple[!dir].in.num_encaps;
++
++ dev = (struct net_device *)path->dev;
++ if (flow_is_valid_ether_device(dev)) {
++ if (route->tuple[dir].xmit_type != FLOW_OFFLOAD_XMIT_DIRECT) {
++ memcpy(route->tuple[dir].out.h_source,
++ dev->dev_addr, ETH_ALEN);
++ route->tuple[dir].out.ifindex = dev->ifindex;
++ }
++ route->tuple[dir].xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
++ }
++
++ switch (path->type) {
++ case DEV_PATH_PPPOE:
++ case DEV_PATH_VLAN:
++ if (n_encaps >= NF_FLOW_TABLE_ENCAP_MAX ||
++ i == stack.num_paths) {
++ last = true;
++ break;
++ }
++
++ route->tuple[!dir].in.num_encaps++;
++ route->tuple[!dir].in.encap[n_encaps].id = path->encap.id;
++ route->tuple[!dir].in.encap[n_encaps].proto = path->encap.proto;
++ if (path->type == DEV_PATH_PPPOE)
++ memcpy(route->tuple[dir].out.h_dest,
++ path->encap.h_dest, ETH_ALEN);
++ break;
++ case DEV_PATH_BRIDGE:
++ switch (path->bridge.vlan_mode) {
++ case DEV_PATH_BR_VLAN_TAG:
++ if (n_encaps >= NF_FLOW_TABLE_ENCAP_MAX ||
++ i == stack.num_paths) {
++ last = true;
++ break;
++ }
++
++ route->tuple[!dir].in.num_encaps++;
++ route->tuple[!dir].in.encap[n_encaps].id =
++ path->bridge.vlan_id;
++ route->tuple[!dir].in.encap[n_encaps].proto =
++ path->bridge.vlan_proto;
++ break;
++ case DEV_PATH_BR_VLAN_UNTAG:
++ route->tuple[!dir].in.num_encaps--;
++ break;
++ case DEV_PATH_BR_VLAN_UNTAG_HW:
++ route->tuple[!dir].in.ingress_vlans |= BIT(n_encaps - 1);
++ break;
++ case DEV_PATH_BR_VLAN_KEEP:
++ break;
++ }
++ break;
++ default:
++ last = true;
++ break;
++ }
++
++ if (last)
++ break;
++ }
++
++ *out_dev = dev;
++ route->tuple[dir].out.hw_ifindex = dev->ifindex;
++ route->tuple[!dir].in.ifindex = dev->ifindex;
++}
++
++static int
++xt_flowoffload_route_dir(struct nf_flow_route *route, const struct nf_conn *ct,
++ enum ip_conntrack_dir dir,
++ const struct xt_action_param *par, int ifindex)
++{
++ struct dst_entry *dst = NULL;
++ struct flowi fl;
++
++ memset(&fl, 0, sizeof(fl));
++ switch (xt_family(par)) {
++ case NFPROTO_IPV4:
++ fl.u.ip4.daddr = ct->tuplehash[!dir].tuple.src.u3.ip;
++ fl.u.ip4.flowi4_oif = ifindex;
++ break;
++ case NFPROTO_IPV6:
++ fl.u.ip6.saddr = ct->tuplehash[!dir].tuple.dst.u3.in6;
++ fl.u.ip6.daddr = ct->tuplehash[!dir].tuple.src.u3.in6;
++ fl.u.ip6.flowi6_oif = ifindex;
++ break;
++ }
++
++ nf_route(xt_net(par), &dst, &fl, false, xt_family(par));
++ if (!dst)
++ return -ENOENT;
++
++ route->tuple[dir].dst = dst;
++ if (dst_xfrm(dst))
++ route->tuple[dir].xmit_type = FLOW_OFFLOAD_XMIT_XFRM;
++ else
++ route->tuple[dir].xmit_type = FLOW_OFFLOAD_XMIT_NEIGH;
++
++ return 0;
++}
++
++static int
++xt_flowoffload_route(struct sk_buff *skb, const struct nf_conn *ct,
++ const struct xt_action_param *par,
++ struct nf_flow_route *route, enum ip_conntrack_dir dir,
++ struct net_device **dev)
++{
++ int ret;
++
++ ret = xt_flowoffload_route_dir(route, ct, dir, par,
++ dev[dir]->ifindex);
++ if (ret)
++ return ret;
++
++ ret = xt_flowoffload_route_dir(route, ct, !dir, par,
++ dev[!dir]->ifindex);
++ if (ret)
++ return ret;
++
++ xt_flowoffload_route_check_path(route, ct, dir, &dev[!dir]);
++ xt_flowoffload_route_check_path(route, ct, !dir, &dev[dir]);
++
++ return 0;
++}
++
++static unsigned int
++flowoffload_tg(struct sk_buff *skb, const struct xt_action_param *par)
++{
++ struct xt_flowoffload_table *table;
++ const struct xt_flowoffload_target_info *info = par->targinfo;
++ struct tcphdr _tcph, *tcph = NULL;
++ enum ip_conntrack_info ctinfo;
++ enum ip_conntrack_dir dir;
++ struct nf_flow_route route = {};
++ struct flow_offload *flow = NULL;
++ struct net_device *devs[2] = {};
++ struct nf_conn *ct;
++ struct net *net;
++
++ if (xt_flowoffload_skip(skb, xt_family(par)))
++ return XT_CONTINUE;
++
++ ct = nf_ct_get(skb, &ctinfo);
++ if (ct == NULL)
++ return XT_CONTINUE;
++
++ switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {
++ case IPPROTO_TCP:
++ if (ct->proto.tcp.state != TCP_CONNTRACK_ESTABLISHED)
++ return XT_CONTINUE;
++
++ tcph = skb_header_pointer(skb, par->thoff,
++ sizeof(_tcph), &_tcph);
++ if (unlikely(!tcph || tcph->fin || tcph->rst))
++ return XT_CONTINUE;
++ break;
++ case IPPROTO_UDP:
++ break;
++ default:
++ return XT_CONTINUE;
++ }
++
++ if (nf_ct_ext_exist(ct, NF_CT_EXT_HELPER) ||
++ ct->status & IPS_SEQ_ADJUST)
++ return XT_CONTINUE;
++
++ if (!nf_ct_is_confirmed(ct))
++ return XT_CONTINUE;
++
++ devs[dir] = xt_out(par);
++ devs[!dir] = xt_in(par);
++
++ if (!devs[dir] || !devs[!dir])
++ return XT_CONTINUE;
++
++ if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status))
++ return XT_CONTINUE;
++
++ dir = CTINFO2DIR(ctinfo);
++
++ if (xt_flowoffload_route(skb, ct, par, &route, dir, devs) < 0)
++ goto err_flow_route;
++
++ flow = flow_offload_alloc(ct);
++ if (!flow)
++ goto err_flow_alloc;
++
++ if (flow_offload_route_init(flow, &route) < 0)
++ goto err_flow_add;
++
++ if (tcph) {
++ ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
++ ct->proto.tcp.seen[1].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
++ }
++
++ table = &flowtable[!!(info->flags & XT_FLOWOFFLOAD_HW)];
++ if (flow_offload_add(&table->ft, flow) < 0)
++ goto err_flow_add;
++
++ xt_flowoffload_check_device(table, devs[0]);
++ xt_flowoffload_check_device(table, devs[1]);
++
++ net = read_pnet(&table->ft.net);
++ if (!net)
++ write_pnet(&table->ft.net, xt_net(par));
++
++ dst_release(route.tuple[dir].dst);
++ dst_release(route.tuple[!dir].dst);
++
++ return XT_CONTINUE;
++
++err_flow_add:
++ flow_offload_free(flow);
++err_flow_alloc:
++ dst_release(route.tuple[dir].dst);
++ dst_release(route.tuple[!dir].dst);
++err_flow_route:
++ clear_bit(IPS_OFFLOAD_BIT, &ct->status);
++
++ return XT_CONTINUE;
++}
++
++static int flowoffload_chk(const struct xt_tgchk_param *par)
++{
++ struct xt_flowoffload_target_info *info = par->targinfo;
++
++ if (info->flags & ~XT_FLOWOFFLOAD_MASK)
++ return -EINVAL;
++
++ return 0;
++}
++
++static struct xt_target offload_tg_reg __read_mostly = {
++ .family = NFPROTO_UNSPEC,
++ .name = "FLOWOFFLOAD",
++ .revision = 0,
++ .targetsize = sizeof(struct xt_flowoffload_target_info),
++ .usersize = sizeof(struct xt_flowoffload_target_info),
++ .checkentry = flowoffload_chk,
++ .target = flowoffload_tg,
++ .me = THIS_MODULE,
++};
++
++static int flow_offload_netdev_event(struct notifier_block *this,
++ unsigned long event, void *ptr)
++{
++ struct xt_flowoffload_hook *hook0, *hook1;
++ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++
++ if (event != NETDEV_UNREGISTER)
++ return NOTIFY_DONE;
++
++ spin_lock_bh(&hooks_lock);
++ hook0 = flow_offload_lookup_hook(&flowtable[0], dev);
++ if (hook0)
++ hlist_del(&hook0->list);
++
++ hook1 = flow_offload_lookup_hook(&flowtable[1], dev);
++ if (hook1)
++ hlist_del(&hook1->list);
++ spin_unlock_bh(&hooks_lock);
++
++ if (hook0) {
++ nf_unregister_net_hook(hook0->net, &hook0->ops);
++ kfree(hook0);
++ }
++
++ if (hook1) {
++ nf_unregister_net_hook(hook1->net, &hook1->ops);
++ kfree(hook1);
++ }
++
++ nf_flow_table_cleanup(dev);
++
++ return NOTIFY_DONE;
++}
++
++static struct notifier_block flow_offload_netdev_notifier = {
++ .notifier_call = flow_offload_netdev_event,
++};
++
++static unsigned int
++nf_flow_offload_inet_hook(void *priv, struct sk_buff *skb,
++ const struct nf_hook_state *state)
++{
++ switch (skb->protocol) {
++ case htons(ETH_P_IP):
++ return nf_flow_offload_ip_hook(priv, skb, state);
++ case htons(ETH_P_IPV6):
++ return nf_flow_offload_ipv6_hook(priv, skb, state);
++ }
++
++ return NF_ACCEPT;
++}
++
++static int nf_flow_rule_route_inet(struct net *net,
++ const struct flow_offload *flow,
++ enum flow_offload_tuple_dir dir,
++ struct nf_flow_rule *flow_rule)
++{
++ const struct flow_offload_tuple *flow_tuple = &flow->tuplehash[dir].tuple;
++ int err;
++
++ switch (flow_tuple->l3proto) {
++ case NFPROTO_IPV4:
++ err = nf_flow_rule_route_ipv4(net, flow, dir, flow_rule);
++ break;
++ case NFPROTO_IPV6:
++ err = nf_flow_rule_route_ipv6(net, flow, dir, flow_rule);
++ break;
++ default:
++ err = -1;
++ break;
++ }
++
++ return err;
++}
++
++static struct nf_flowtable_type flowtable_inet = {
++ .family = NFPROTO_INET,
++ .init = nf_flow_table_init,
++ .setup = nf_flow_table_offload_setup,
++ .action = nf_flow_rule_route_inet,
++ .free = nf_flow_table_free,
++ .hook = nf_flow_offload_inet_hook,
++ .owner = THIS_MODULE,
++};
++
++static int init_flowtable(struct xt_flowoffload_table *tbl)
++{
++ INIT_DELAYED_WORK(&tbl->work, xt_flowoffload_hook_work);
++ tbl->ft.type = &flowtable_inet;
++
++ return nf_flow_table_init(&tbl->ft);
++}
++
++static int __init xt_flowoffload_tg_init(void)
++{
++ int ret;
++
++ register_netdevice_notifier(&flow_offload_netdev_notifier);
++
++ ret = init_flowtable(&flowtable[0]);
++ if (ret)
++ return ret;
++
++ ret = init_flowtable(&flowtable[1]);
++ if (ret)
++ goto cleanup;
++
++ flowtable[1].ft.flags = NF_FLOWTABLE_HW_OFFLOAD;
++
++ ret = xt_register_target(&offload_tg_reg);
++ if (ret)
++ goto cleanup2;
++
++ return 0;
++
++cleanup2:
++ nf_flow_table_free(&flowtable[1].ft);
++cleanup:
++ nf_flow_table_free(&flowtable[0].ft);
++ return ret;
++}
++
++static void __exit xt_flowoffload_tg_exit(void)
++{
++ xt_unregister_target(&offload_tg_reg);
++ unregister_netdevice_notifier(&flow_offload_netdev_notifier);
++ nf_flow_table_free(&flowtable[0].ft);
++ nf_flow_table_free(&flowtable[1].ft);
++}
++
++MODULE_LICENSE("GPL");
++module_init(xt_flowoffload_tg_init);
++module_exit(xt_flowoffload_tg_exit);
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -7,7 +7,6 @@
+ #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>
+@@ -356,8 +355,7 @@ flow_offload_lookup(struct nf_flowtable
+ }
+ EXPORT_SYMBOL_GPL(flow_offload_lookup);
+
+-static int
+-nf_flow_table_iterate(struct nf_flowtable *flow_table,
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
+ void (*iter)(struct flow_offload *flow, void *data),
+ void *data)
+ {
+@@ -389,6 +387,7 @@ nf_flow_table_iterate(struct nf_flowtabl
+
+ return err;
+ }
++EXPORT_SYMBOL_GPL(nf_flow_table_iterate);
+
+ static void nf_flow_offload_gc_step(struct flow_offload *flow, void *data)
+ {
+--- /dev/null
++++ b/include/uapi/linux/netfilter/xt_FLOWOFFLOAD.h
+@@ -0,0 +1,17 @@
++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
++#ifndef _XT_FLOWOFFLOAD_H
++#define _XT_FLOWOFFLOAD_H
++
++#include <linux/types.h>
++
++enum {
++ XT_FLOWOFFLOAD_HW = 1 << 0,
++
++ XT_FLOWOFFLOAD_MASK = XT_FLOWOFFLOAD_HW
++};
++
++struct xt_flowoffload_target_info {
++ __u32 flags;
++};
++
++#endif /* _XT_FLOWOFFLOAD_H */
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -266,6 +266,10 @@ void nf_flow_table_free(struct nf_flowta
+
+ void flow_offload_teardown(struct flow_offload *flow);
+
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
++ void (*iter)(struct flow_offload *flow, void *data),
++ void *data);
++
+ 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);
+++ /dev/null
-From: Felix Fietkau <nbd@nbd.name>
-Date: Tue, 20 Feb 2018 15:56:02 +0100
-Subject: [PATCH] netfilter: add xt_OFFLOAD target
-
-Signed-off-by: Felix Fietkau <nbd@nbd.name>
----
- create mode 100644 net/netfilter/xt_OFFLOAD.c
-
---- a/net/ipv4/netfilter/Kconfig
-+++ b/net/ipv4/netfilter/Kconfig
-@@ -56,8 +56,6 @@ config NF_TABLES_ARP
- help
- This option enables the ARP support for nf_tables.
-
--endif # NF_TABLES
--
- config NF_FLOW_TABLE_IPV4
- tristate "Netfilter flow table IPv4 module"
- depends on NF_FLOW_TABLE
-@@ -66,6 +64,8 @@ config NF_FLOW_TABLE_IPV4
-
- To compile it as a module, choose M here.
-
-+endif # NF_TABLES
-+
- config NF_DUP_IPV4
- tristate "Netfilter IPv4 packet duplication to alternate destination"
- depends on !NF_CONNTRACK || NF_CONNTRACK
---- a/net/ipv6/netfilter/Kconfig
-+++ b/net/ipv6/netfilter/Kconfig
-@@ -45,7 +45,6 @@ config NFT_FIB_IPV6
- multicast or blackhole.
-
- endif # NF_TABLES_IPV6
--endif # NF_TABLES
-
- config NF_FLOW_TABLE_IPV6
- tristate "Netfilter flow table IPv6 module"
-@@ -55,6 +54,8 @@ config NF_FLOW_TABLE_IPV6
-
- To compile it as a module, choose M here.
-
-+endif # NF_TABLES
-+
- config NF_DUP_IPV6
- tristate "Netfilter IPv6 packet duplication to alternate destination"
- depends on !NF_CONNTRACK || NF_CONNTRACK
---- a/net/netfilter/Kconfig
-+++ b/net/netfilter/Kconfig
-@@ -683,8 +683,6 @@ config NFT_FIB_NETDEV
-
- endif # NF_TABLES_NETDEV
-
--endif # NF_TABLES
--
- config NF_FLOW_TABLE_INET
- tristate "Netfilter flow table mixed IPv4/IPv6 module"
- depends on NF_FLOW_TABLE
-@@ -693,11 +691,12 @@ config NF_FLOW_TABLE_INET
-
- To compile it as a module, choose M here.
-
-+endif # NF_TABLES
-+
- config NF_FLOW_TABLE
- tristate "Netfilter flow table module"
- depends on NETFILTER_INGRESS
- depends on NF_CONNTRACK
-- depends on NF_TABLES
- help
- This option adds the flow table core infrastructure.
-
-@@ -977,6 +976,15 @@ config NETFILTER_XT_TARGET_NOTRACK
- depends on NETFILTER_ADVANCED
- select NETFILTER_XT_TARGET_CT
-
-+config NETFILTER_XT_TARGET_FLOWOFFLOAD
-+ tristate '"FLOWOFFLOAD" target support'
-+ depends on NF_FLOW_TABLE
-+ depends on NETFILTER_INGRESS
-+ help
-+ This option adds a `FLOWOFFLOAD' target, which uses the nf_flow_offload
-+ module to speed up processing of packets by bypassing the usual
-+ netfilter chains
-+
- config NETFILTER_XT_TARGET_RATEEST
- tristate '"RATEEST" target support'
- depends on NETFILTER_ADVANCED
---- a/net/netfilter/Makefile
-+++ b/net/netfilter/Makefile
-@@ -145,6 +145,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_CLASSIF
- obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o
- obj-$(CONFIG_NETFILTER_XT_TARGET_CT) += xt_CT.o
- obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o
-+obj-$(CONFIG_NETFILTER_XT_TARGET_FLOWOFFLOAD) += xt_FLOWOFFLOAD.o
- obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o
- obj-$(CONFIG_NETFILTER_XT_TARGET_HMARK) += xt_HMARK.o
- obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o
---- /dev/null
-+++ b/net/netfilter/xt_FLOWOFFLOAD.c
-@@ -0,0 +1,660 @@
-+/*
-+ * Copyright (C) 2018-2021 Felix Fietkau <nbd@nbd.name>
-+ *
-+ * This program is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License version 2 as
-+ * published by the Free Software Foundation.
-+ */
-+#include <linux/module.h>
-+#include <linux/init.h>
-+#include <linux/netfilter.h>
-+#include <linux/netfilter/xt_FLOWOFFLOAD.h>
-+#include <net/ip.h>
-+#include <net/netfilter/nf_conntrack.h>
-+#include <net/netfilter/nf_conntrack_extend.h>
-+#include <net/netfilter/nf_conntrack_helper.h>
-+#include <net/netfilter/nf_flow_table.h>
-+
-+struct xt_flowoffload_hook {
-+ struct hlist_node list;
-+ struct nf_hook_ops ops;
-+ struct net *net;
-+ bool registered;
-+ bool used;
-+};
-+
-+struct xt_flowoffload_table {
-+ struct nf_flowtable ft;
-+ struct hlist_head hooks;
-+ struct delayed_work work;
-+};
-+
-+static DEFINE_SPINLOCK(hooks_lock);
-+
-+struct xt_flowoffload_table flowtable[2];
-+
-+static unsigned int
-+xt_flowoffload_net_hook(void *priv, struct sk_buff *skb,
-+ const struct nf_hook_state *state)
-+{
-+ struct nf_flowtable *ft = priv;
-+
-+ if (!atomic_read(&ft->rhashtable.nelems))
-+ return NF_ACCEPT;
-+
-+ switch (skb->protocol) {
-+ case htons(ETH_P_IP):
-+ return nf_flow_offload_ip_hook(priv, skb, state);
-+ case htons(ETH_P_IPV6):
-+ return nf_flow_offload_ipv6_hook(priv, skb, state);
-+ }
-+
-+ return NF_ACCEPT;
-+}
-+
-+static int
-+xt_flowoffload_create_hook(struct xt_flowoffload_table *table,
-+ struct net_device *dev)
-+{
-+ struct xt_flowoffload_hook *hook;
-+ struct nf_hook_ops *ops;
-+
-+ hook = kzalloc(sizeof(*hook), GFP_ATOMIC);
-+ if (!hook)
-+ return -ENOMEM;
-+
-+ ops = &hook->ops;
-+ ops->pf = NFPROTO_NETDEV;
-+ ops->hooknum = NF_NETDEV_INGRESS;
-+ ops->priority = 10;
-+ ops->priv = &table->ft;
-+ ops->hook = xt_flowoffload_net_hook;
-+ ops->dev = dev;
-+
-+ hlist_add_head(&hook->list, &table->hooks);
-+ mod_delayed_work(system_power_efficient_wq, &table->work, 0);
-+
-+ return 0;
-+}
-+
-+static struct xt_flowoffload_hook *
-+flow_offload_lookup_hook(struct xt_flowoffload_table *table,
-+ struct net_device *dev)
-+{
-+ struct xt_flowoffload_hook *hook;
-+
-+ hlist_for_each_entry(hook, &table->hooks, list) {
-+ if (hook->ops.dev == dev)
-+ return hook;
-+ }
-+
-+ return NULL;
-+}
-+
-+static void
-+xt_flowoffload_check_device(struct xt_flowoffload_table *table,
-+ struct net_device *dev)
-+{
-+ struct xt_flowoffload_hook *hook;
-+
-+ if (!dev)
-+ return;
-+
-+ spin_lock_bh(&hooks_lock);
-+ hook = flow_offload_lookup_hook(table, dev);
-+ if (hook)
-+ hook->used = true;
-+ else
-+ xt_flowoffload_create_hook(table, dev);
-+ spin_unlock_bh(&hooks_lock);
-+}
-+
-+static void
-+xt_flowoffload_register_hooks(struct xt_flowoffload_table *table)
-+{
-+ struct xt_flowoffload_hook *hook;
-+
-+restart:
-+ hlist_for_each_entry(hook, &table->hooks, list) {
-+ if (hook->registered)
-+ continue;
-+
-+ hook->registered = true;
-+ hook->net = dev_net(hook->ops.dev);
-+ spin_unlock_bh(&hooks_lock);
-+ nf_register_net_hook(hook->net, &hook->ops);
-+ if (table->ft.flags & NF_FLOWTABLE_HW_OFFLOAD)
-+ table->ft.type->setup(&table->ft, hook->ops.dev,
-+ FLOW_BLOCK_BIND);
-+ spin_lock_bh(&hooks_lock);
-+ goto restart;
-+ }
-+
-+}
-+
-+static bool
-+xt_flowoffload_cleanup_hooks(struct xt_flowoffload_table *table)
-+{
-+ struct xt_flowoffload_hook *hook;
-+ bool active = false;
-+
-+restart:
-+ spin_lock_bh(&hooks_lock);
-+ hlist_for_each_entry(hook, &table->hooks, list) {
-+ if (hook->used || !hook->registered) {
-+ active = true;
-+ continue;
-+ }
-+
-+ hlist_del(&hook->list);
-+ spin_unlock_bh(&hooks_lock);
-+ if (table->ft.flags & NF_FLOWTABLE_HW_OFFLOAD)
-+ table->ft.type->setup(&table->ft, hook->ops.dev,
-+ FLOW_BLOCK_UNBIND);
-+ nf_unregister_net_hook(hook->net, &hook->ops);
-+ kfree(hook);
-+ goto restart;
-+ }
-+ spin_unlock_bh(&hooks_lock);
-+
-+ return active;
-+}
-+
-+static void
-+xt_flowoffload_check_hook(struct flow_offload *flow, void *data)
-+{
-+ struct xt_flowoffload_table *table = data;
-+ struct flow_offload_tuple *tuple = &flow->tuplehash[0].tuple;
-+ struct xt_flowoffload_hook *hook;
-+
-+ spin_lock_bh(&hooks_lock);
-+ hlist_for_each_entry(hook, &table->hooks, list) {
-+ int ifindex;
-+
-+ if (tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
-+ ifindex = tuple->out.ifidx;
-+ else
-+ ifindex = tuple->dst_cache->dev->ifindex;
-+
-+ if (hook->ops.dev->ifindex != tuple->iifidx &&
-+ hook->ops.dev->ifindex != ifindex)
-+ continue;
-+
-+ hook->used = true;
-+ }
-+ spin_unlock_bh(&hooks_lock);
-+
-+ cond_resched();
-+}
-+
-+static void
-+xt_flowoffload_hook_work(struct work_struct *work)
-+{
-+ struct xt_flowoffload_table *table;
-+ struct xt_flowoffload_hook *hook;
-+ int err;
-+
-+ table = container_of(work, struct xt_flowoffload_table, work.work);
-+
-+ spin_lock_bh(&hooks_lock);
-+ xt_flowoffload_register_hooks(table);
-+ hlist_for_each_entry(hook, &table->hooks, list)
-+ hook->used = false;
-+ spin_unlock_bh(&hooks_lock);
-+
-+ err = nf_flow_table_iterate(&table->ft, xt_flowoffload_check_hook,
-+ table);
-+ if (err && err != -EAGAIN)
-+ goto out;
-+
-+ if (!xt_flowoffload_cleanup_hooks(table))
-+ return;
-+
-+out:
-+ queue_delayed_work(system_power_efficient_wq, &table->work, HZ);
-+}
-+
-+static bool
-+xt_flowoffload_skip(struct sk_buff *skb, int family)
-+{
-+ if (skb_sec_path(skb))
-+ return true;
-+
-+ if (family == NFPROTO_IPV4) {
-+ const struct ip_options *opt = &(IPCB(skb)->opt);
-+
-+ if (unlikely(opt->optlen))
-+ return true;
-+ }
-+
-+ return false;
-+}
-+
-+static bool flow_is_valid_ether_device(const struct net_device *dev)
-+{
-+ if (!dev || (dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER ||
-+ dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr))
-+ return false;
-+
-+ return true;
-+}
-+
-+static void
-+xt_flowoffload_route_check_path(struct nf_flow_route *route,
-+ const struct nf_conn *ct,
-+ enum ip_conntrack_dir dir,
-+ struct net_device **out_dev)
-+{
-+ const struct dst_entry *dst = route->tuple[dir].dst;
-+ const void *daddr = &ct->tuplehash[!dir].tuple.src.u3;
-+ struct net_device_path_stack stack;
-+ enum net_device_path_type prev_type;
-+ struct net_device *dev = dst->dev;
-+ struct neighbour *n;
-+ bool last = false;
-+ u8 nud_state;
-+ int i;
-+
-+ route->tuple[!dir].in.ifindex = dev->ifindex;
-+
-+ if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_XFRM)
-+ return;
-+
-+ if ((dev->flags & IFF_LOOPBACK) ||
-+ dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN ||
-+ !is_valid_ether_addr(dev->dev_addr))
-+ return;
-+
-+ n = dst_neigh_lookup(dst, daddr);
-+ if (!n)
-+ return;
-+
-+ read_lock_bh(&n->lock);
-+ nud_state = n->nud_state;
-+ memcpy(route->tuple[dir].out.h_dest, n->ha, ETH_ALEN);
-+ read_unlock_bh(&n->lock);
-+ neigh_release(n);
-+
-+ if (!(nud_state & NUD_VALID))
-+ return;
-+
-+ if (dev_fill_forward_path(dev, route->tuple[dir].out.h_dest, &stack) ||
-+ !stack.num_paths)
-+ return;
-+
-+ prev_type = DEV_PATH_ETHERNET;
-+ for (i = 0; i <= stack.num_paths; i++) {
-+ const struct net_device_path *path = &stack.path[i];
-+ int n_vlans = route->tuple[!dir].in.num_vlans;
-+
-+ dev = (struct net_device *)path->dev;
-+ if (flow_is_valid_ether_device(dev)) {
-+ if (route->tuple[dir].xmit_type != FLOW_OFFLOAD_XMIT_DIRECT)
-+ memcpy(route->tuple[dir].out.h_source,
-+ dev->dev_addr, ETH_ALEN);
-+ route->tuple[dir].xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
-+ route->tuple[dir].out.ifindex = dev->ifindex;
-+ }
-+
-+ switch (path->type) {
-+ case DEV_PATH_VLAN:
-+ if (n_vlans >= NF_FLOW_TABLE_VLAN_MAX ||
-+ i == stack.num_paths) {
-+ last = true;
-+ break;
-+ }
-+
-+ route->tuple[!dir].in.num_vlans++;
-+ route->tuple[!dir].in.vid[n_vlans] = path->vlan.id;
-+ route->tuple[!dir].in.vproto[n_vlans] = path->vlan.proto;
-+ break;
-+ case DEV_PATH_BRIDGE:
-+ switch (path->bridge.vlan_mode) {
-+ case DEV_PATH_BR_VLAN_TAG:
-+ if (n_vlans >= NF_FLOW_TABLE_VLAN_MAX ||
-+ i == stack.num_paths) {
-+ last = true;
-+ break;
-+ }
-+
-+ route->tuple[!dir].in.num_vlans++;
-+ route->tuple[!dir].in.vid[n_vlans] =
-+ path->bridge.vlan_id;
-+ route->tuple[!dir].in.vproto[n_vlans] =
-+ path->bridge.vlan_proto;
-+ break;
-+ case DEV_PATH_BR_VLAN_UNTAG_HW:
-+ route->tuple[!dir].in.pvid.id =
-+ route->tuple[!dir].in.vid[n_vlans - 1];
-+ route->tuple[!dir].in.pvid.proto =
-+ route->tuple[!dir].in.vproto[n_vlans - 1];
-+ fallthrough;
-+ case DEV_PATH_BR_VLAN_UNTAG:
-+ route->tuple[!dir].in.num_vlans--;
-+ break;
-+ case DEV_PATH_BR_VLAN_KEEP:
-+ break;
-+ }
-+ break;
-+ default:
-+ last = true;
-+ break;
-+ }
-+
-+ if (last)
-+ break;
-+ }
-+
-+ *out_dev = dev;
-+ route->tuple[!dir].in.ifindex = dev->ifindex;
-+}
-+
-+static int
-+xt_flowoffload_route_dir(struct nf_flow_route *route, const struct nf_conn *ct,
-+ enum ip_conntrack_dir dir,
-+ const struct xt_action_param *par, int ifindex)
-+{
-+ struct dst_entry *dst = NULL;
-+ struct flowi fl;
-+
-+ memset(&fl, 0, sizeof(fl));
-+ switch (xt_family(par)) {
-+ case NFPROTO_IPV4:
-+ fl.u.ip4.daddr = ct->tuplehash[!dir].tuple.src.u3.ip;
-+ fl.u.ip4.flowi4_oif = ifindex;
-+ break;
-+ case NFPROTO_IPV6:
-+ fl.u.ip6.saddr = ct->tuplehash[!dir].tuple.dst.u3.in6;
-+ fl.u.ip6.daddr = ct->tuplehash[!dir].tuple.src.u3.in6;
-+ fl.u.ip6.flowi6_oif = ifindex;
-+ break;
-+ }
-+
-+ nf_route(xt_net(par), &dst, &fl, false, xt_family(par));
-+ if (!dst)
-+ return -ENOENT;
-+
-+ route->tuple[dir].dst = dst;
-+ if (dst_xfrm(dst))
-+ route->tuple[dir].xmit_type = FLOW_OFFLOAD_XMIT_XFRM;
-+ else
-+ route->tuple[dir].xmit_type = FLOW_OFFLOAD_XMIT_NEIGH;
-+
-+ return 0;
-+}
-+
-+static int
-+xt_flowoffload_route(struct sk_buff *skb, const struct nf_conn *ct,
-+ const struct xt_action_param *par,
-+ struct nf_flow_route *route, enum ip_conntrack_dir dir,
-+ struct net_device **dev)
-+{
-+ int ret;
-+
-+ ret = xt_flowoffload_route_dir(route, ct, dir, par,
-+ dev[dir]->ifindex);
-+ if (ret)
-+ return ret;
-+
-+ ret = xt_flowoffload_route_dir(route, ct, !dir, par,
-+ dev[!dir]->ifindex);
-+ if (ret)
-+ return ret;
-+
-+ xt_flowoffload_route_check_path(route, ct, dir, &dev[!dir]);
-+ xt_flowoffload_route_check_path(route, ct, !dir, &dev[dir]);
-+
-+ return 0;
-+}
-+
-+static unsigned int
-+flowoffload_tg(struct sk_buff *skb, const struct xt_action_param *par)
-+{
-+ struct xt_flowoffload_table *table;
-+ const struct xt_flowoffload_target_info *info = par->targinfo;
-+ struct tcphdr _tcph, *tcph = NULL;
-+ enum ip_conntrack_info ctinfo;
-+ enum ip_conntrack_dir dir;
-+ struct nf_flow_route route = {};
-+ struct flow_offload *flow = NULL;
-+ struct net_device *devs[2] = {};
-+ struct nf_conn *ct;
-+ struct net *net;
-+
-+ if (xt_flowoffload_skip(skb, xt_family(par)))
-+ return XT_CONTINUE;
-+
-+ ct = nf_ct_get(skb, &ctinfo);
-+ if (ct == NULL)
-+ return XT_CONTINUE;
-+
-+ switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {
-+ case IPPROTO_TCP:
-+ if (ct->proto.tcp.state != TCP_CONNTRACK_ESTABLISHED)
-+ return XT_CONTINUE;
-+
-+ tcph = skb_header_pointer(skb, par->thoff,
-+ sizeof(_tcph), &_tcph);
-+ if (unlikely(!tcph || tcph->fin || tcph->rst))
-+ return XT_CONTINUE;
-+ break;
-+ case IPPROTO_UDP:
-+ break;
-+ default:
-+ return XT_CONTINUE;
-+ }
-+
-+ if (nf_ct_ext_exist(ct, NF_CT_EXT_HELPER) ||
-+ ct->status & IPS_SEQ_ADJUST)
-+ return XT_CONTINUE;
-+
-+ if (!nf_ct_is_confirmed(ct))
-+ return XT_CONTINUE;
-+
-+ devs[dir] = xt_out(par);
-+ devs[!dir] = xt_in(par);
-+
-+ if (!devs[dir] || !devs[!dir])
-+ return XT_CONTINUE;
-+
-+ if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status))
-+ return XT_CONTINUE;
-+
-+ dir = CTINFO2DIR(ctinfo);
-+
-+ if (xt_flowoffload_route(skb, ct, par, &route, dir, devs) < 0)
-+ goto err_flow_route;
-+
-+ flow = flow_offload_alloc(ct);
-+ if (!flow)
-+ goto err_flow_alloc;
-+
-+ if (flow_offload_route_init(flow, &route) < 0)
-+ goto err_flow_add;
-+
-+ if (tcph) {
-+ ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
-+ ct->proto.tcp.seen[1].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
-+ }
-+
-+ table = &flowtable[!!(info->flags & XT_FLOWOFFLOAD_HW)];
-+ if (flow_offload_add(&table->ft, flow) < 0)
-+ goto err_flow_add;
-+
-+ xt_flowoffload_check_device(table, devs[0]);
-+ xt_flowoffload_check_device(table, devs[1]);
-+
-+ net = read_pnet(&table->ft.net);
-+ if (!net)
-+ write_pnet(&table->ft.net, xt_net(par));
-+
-+ dst_release(route.tuple[dir].dst);
-+ dst_release(route.tuple[!dir].dst);
-+
-+ return XT_CONTINUE;
-+
-+err_flow_add:
-+ flow_offload_free(flow);
-+err_flow_alloc:
-+ dst_release(route.tuple[dir].dst);
-+ dst_release(route.tuple[!dir].dst);
-+err_flow_route:
-+ clear_bit(IPS_OFFLOAD_BIT, &ct->status);
-+
-+ return XT_CONTINUE;
-+}
-+
-+static int flowoffload_chk(const struct xt_tgchk_param *par)
-+{
-+ struct xt_flowoffload_target_info *info = par->targinfo;
-+
-+ if (info->flags & ~XT_FLOWOFFLOAD_MASK)
-+ return -EINVAL;
-+
-+ return 0;
-+}
-+
-+static struct xt_target offload_tg_reg __read_mostly = {
-+ .family = NFPROTO_UNSPEC,
-+ .name = "FLOWOFFLOAD",
-+ .revision = 0,
-+ .targetsize = sizeof(struct xt_flowoffload_target_info),
-+ .usersize = sizeof(struct xt_flowoffload_target_info),
-+ .checkentry = flowoffload_chk,
-+ .target = flowoffload_tg,
-+ .me = THIS_MODULE,
-+};
-+
-+static int flow_offload_netdev_event(struct notifier_block *this,
-+ unsigned long event, void *ptr)
-+{
-+ struct xt_flowoffload_hook *hook0, *hook1;
-+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
-+
-+ if (event != NETDEV_UNREGISTER)
-+ return NOTIFY_DONE;
-+
-+ spin_lock_bh(&hooks_lock);
-+ hook0 = flow_offload_lookup_hook(&flowtable[0], dev);
-+ if (hook0)
-+ hlist_del(&hook0->list);
-+
-+ hook1 = flow_offload_lookup_hook(&flowtable[1], dev);
-+ if (hook1)
-+ hlist_del(&hook1->list);
-+ spin_unlock_bh(&hooks_lock);
-+
-+ if (hook0) {
-+ nf_unregister_net_hook(hook0->net, &hook0->ops);
-+ kfree(hook0);
-+ }
-+
-+ if (hook1) {
-+ nf_unregister_net_hook(hook1->net, &hook1->ops);
-+ kfree(hook1);
-+ }
-+
-+ nf_flow_table_cleanup(dev);
-+
-+ return NOTIFY_DONE;
-+}
-+
-+static struct notifier_block flow_offload_netdev_notifier = {
-+ .notifier_call = flow_offload_netdev_event,
-+};
-+
-+static unsigned int
-+nf_flow_offload_inet_hook(void *priv, struct sk_buff *skb,
-+ const struct nf_hook_state *state)
-+{
-+ switch (skb->protocol) {
-+ case htons(ETH_P_IP):
-+ return nf_flow_offload_ip_hook(priv, skb, state);
-+ case htons(ETH_P_IPV6):
-+ return nf_flow_offload_ipv6_hook(priv, skb, state);
-+ }
-+
-+ return NF_ACCEPT;
-+}
-+
-+static int nf_flow_rule_route_inet(struct net *net,
-+ const struct flow_offload *flow,
-+ enum flow_offload_tuple_dir dir,
-+ struct nf_flow_rule *flow_rule)
-+{
-+ const struct flow_offload_tuple *flow_tuple = &flow->tuplehash[dir].tuple;
-+ int err;
-+
-+ switch (flow_tuple->l3proto) {
-+ case NFPROTO_IPV4:
-+ err = nf_flow_rule_route_ipv4(net, flow, dir, flow_rule);
-+ break;
-+ case NFPROTO_IPV6:
-+ err = nf_flow_rule_route_ipv6(net, flow, dir, flow_rule);
-+ break;
-+ default:
-+ err = -1;
-+ break;
-+ }
-+
-+ return err;
-+}
-+
-+static struct nf_flowtable_type flowtable_inet = {
-+ .family = NFPROTO_INET,
-+ .init = nf_flow_table_init,
-+ .setup = nf_flow_table_offload_setup,
-+ .action = nf_flow_rule_route_inet,
-+ .free = nf_flow_table_free,
-+ .hook = nf_flow_offload_inet_hook,
-+ .owner = THIS_MODULE,
-+};
-+
-+static int init_flowtable(struct xt_flowoffload_table *tbl)
-+{
-+ INIT_DELAYED_WORK(&tbl->work, xt_flowoffload_hook_work);
-+ tbl->ft.type = &flowtable_inet;
-+
-+ return nf_flow_table_init(&tbl->ft);
-+}
-+
-+static int __init xt_flowoffload_tg_init(void)
-+{
-+ int ret;
-+
-+ register_netdevice_notifier(&flow_offload_netdev_notifier);
-+
-+ ret = init_flowtable(&flowtable[0]);
-+ if (ret)
-+ return ret;
-+
-+ ret = init_flowtable(&flowtable[1]);
-+ if (ret)
-+ goto cleanup;
-+
-+ flowtable[1].ft.flags = NF_FLOWTABLE_HW_OFFLOAD;
-+
-+ ret = xt_register_target(&offload_tg_reg);
-+ if (ret)
-+ goto cleanup2;
-+
-+ return 0;
-+
-+cleanup2:
-+ nf_flow_table_free(&flowtable[1].ft);
-+cleanup:
-+ nf_flow_table_free(&flowtable[0].ft);
-+ return ret;
-+}
-+
-+static void __exit xt_flowoffload_tg_exit(void)
-+{
-+ xt_unregister_target(&offload_tg_reg);
-+ unregister_netdevice_notifier(&flow_offload_netdev_notifier);
-+ nf_flow_table_free(&flowtable[0].ft);
-+ nf_flow_table_free(&flowtable[1].ft);
-+}
-+
-+MODULE_LICENSE("GPL");
-+module_init(xt_flowoffload_tg_init);
-+module_exit(xt_flowoffload_tg_exit);
---- a/net/netfilter/nf_flow_table_core.c
-+++ b/net/netfilter/nf_flow_table_core.c
-@@ -7,7 +7,6 @@
- #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>
-@@ -355,8 +354,7 @@ flow_offload_lookup(struct nf_flowtable
- }
- EXPORT_SYMBOL_GPL(flow_offload_lookup);
-
--static int
--nf_flow_table_iterate(struct nf_flowtable *flow_table,
-+int nf_flow_table_iterate(struct nf_flowtable *flow_table,
- void (*iter)(struct flow_offload *flow, void *data),
- void *data)
- {
-@@ -388,6 +386,7 @@ nf_flow_table_iterate(struct nf_flowtabl
-
- return err;
- }
-+EXPORT_SYMBOL_GPL(nf_flow_table_iterate);
-
- static void nf_flow_offload_gc_step(struct flow_offload *flow, void *data)
- {
---- /dev/null
-+++ b/include/uapi/linux/netfilter/xt_FLOWOFFLOAD.h
-@@ -0,0 +1,17 @@
-+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
-+#ifndef _XT_FLOWOFFLOAD_H
-+#define _XT_FLOWOFFLOAD_H
-+
-+#include <linux/types.h>
-+
-+enum {
-+ XT_FLOWOFFLOAD_HW = 1 << 0,
-+
-+ XT_FLOWOFFLOAD_MASK = XT_FLOWOFFLOAD_HW
-+};
-+
-+struct xt_flowoffload_target_info {
-+ __u32 flags;
-+};
-+
-+#endif /* _XT_FLOWOFFLOAD_H */
---- a/include/net/netfilter/nf_flow_table.h
-+++ b/include/net/netfilter/nf_flow_table.h
-@@ -265,6 +265,10 @@ void nf_flow_table_free(struct nf_flowta
-
- void flow_offload_teardown(struct flow_offload *flow);
-
-+int nf_flow_table_iterate(struct nf_flowtable *flow_table,
-+ void (*iter)(struct flow_offload *flow, void *data),
-+ void *data);
-+
- 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);
From: Pablo Neira Ayuso <pablo@netfilter.org>
-Date: Sun, 10 Jan 2021 15:53:58 +0100
+Date: Thu, 4 Mar 2021 23:18:11 +0100
Subject: [PATCH] net: resolve forwarding path from virtual netdevice and
HW destination address
* If a device is paired with a peer device, return the peer instance.
* The caller must be under RCU read context.
+ * int (*ndo_fill_forward_path)(struct net_device_path_ctx *ctx, struct net_device_path *path);
-+ * Get the forwarding path to reach the real device from the HW destination address
++ * Get the forwarding path to reach the real device from the HW destination address
*/
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
int (*ndo_tunnel_ctl)(struct net_device *dev,
struct ip_tunnel_parm *p, int cmd);
struct net_device * (*ndo_get_peer_dev)(struct net_device *dev);
-+ int (*ndo_fill_forward_path)(struct net_device_path_ctx *ctx,
-+ struct net_device_path *path);
++ int (*ndo_fill_forward_path)(struct net_device_path_ctx *ctx,
++ struct net_device_path *path);
};
/**
+ struct {
+ u16 id;
+ __be16 proto;
-+ } vlan;
++ } encap;
+ };
};
+ struct vlan_dev_priv *vlan = vlan_dev_priv(ctx->dev);
+
+ path->type = DEV_PATH_VLAN;
-+ path->vlan.id = vlan->vlan_id;
-+ path->vlan.proto = vlan->vlan_proto;
++ path->encap.id = vlan->vlan_id;
++ path->encap.proto = vlan->vlan_proto;
+ path->dev = ctx->dev;
+ ctx->dev = vlan->real_dev;
+
+}
+
+struct nft_forward_info {
-+ const struct net_device *dev;
++ const struct net_device *indev;
+};
+
+static void nft_dev_path_info(const struct net_device_path_stack *stack,
+ path = &stack->path[i];
+ switch (path->type) {
+ case DEV_PATH_ETHERNET:
-+ info->dev = path->dev;
++ info->indev = path->dev;
+ break;
+ case DEV_PATH_VLAN:
+ case DEV_PATH_BRIDGE:
+ default:
-+ info->dev = NULL;
++ info->indev = NULL;
+ break;
+ }
+ }
+ if (nft_dev_fill_forward_path(route, dst, ct, dir, &stack) >= 0)
+ nft_dev_path_info(&stack, &info);
+
-+ if (!info.dev || !nft_flowtable_find_dev(info.dev, ft))
++ if (!info.indev || !nft_flowtable_find_dev(info.indev, ft))
+ return;
+
-+ route->tuple[!dir].in.ifindex = info.dev->ifindex;
++ route->tuple[!dir].in.ifindex = info.indev->ifindex;
+}
+
static int nft_flow_route(const struct nft_pktinfo *pkt,
From: Pablo Neira Ayuso <pablo@netfilter.org>
-Date: Fri, 20 Nov 2020 13:49:19 +0100
+Date: Thu, 4 Mar 2021 03:26:35 +0100
Subject: [PATCH] netfilter: flowtable: use dev_fill_forward_path() to
obtain egress device
struct neighbour *n;
u8 nud_state;
-@@ -66,22 +65,35 @@ static int nft_dev_fill_forward_path(con
+@@ -66,27 +65,43 @@ static int nft_dev_fill_forward_path(con
struct nft_forward_info {
- const struct net_device *dev;
+ const struct net_device *indev;
++ const struct net_device *outdev;
+ u8 h_source[ETH_ALEN];
+ u8 h_dest[ETH_ALEN];
+ enum flow_offload_xmit_type xmit_type;
path = &stack->path[i];
switch (path->type) {
case DEV_PATH_ETHERNET:
- info->dev = path->dev;
+ info->indev = path->dev;
+ if (is_zero_ether_addr(info->h_source))
+ memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
break;
+ break;
+ case DEV_PATH_VLAN:
default:
- info->dev = NULL;
+ info->indev = NULL;
break;
-@@ -114,14 +126,22 @@ static void nft_dev_forward_path(struct
+ }
+ }
++ if (!info->outdev)
++ info->outdev = info->indev;
+ }
+
+ static bool nft_flowtable_find_dev(const struct net_device *dev,
+@@ -114,14 +129,22 @@ static void nft_dev_forward_path(struct
const struct dst_entry *dst = route->tuple[dir].dst;
struct net_device_path_stack stack;
struct nft_forward_info info = {};
+ if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0)
+ nft_dev_path_info(&stack, &info, ha);
- if (!info.dev || !nft_flowtable_find_dev(info.dev, ft))
+ if (!info.indev || !nft_flowtable_find_dev(info.indev, ft))
return;
- route->tuple[!dir].in.ifindex = info.dev->ifindex;
+ route->tuple[!dir].in.ifindex = info.indev->ifindex;
+
+ if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) {
+ memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN);
+ memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN);
-+ route->tuple[dir].out.ifindex = info.dev->ifindex;
++ route->tuple[dir].out.ifindex = info.outdev->ifindex;
+ route->tuple[dir].xmit_type = info.xmit_type;
+ }
}
FLOW_OFFLOAD_XMIT_DIRECT,
};
-+#define NF_FLOW_TABLE_VLAN_MAX 2
++#define NF_FLOW_TABLE_ENCAP_MAX 2
+
struct flow_offload_tuple {
union {
+ struct {
+ u16 id;
+ __be16 proto;
-+ } in_vlan[NF_FLOW_TABLE_VLAN_MAX];
++ } encap[NF_FLOW_TABLE_ENCAP_MAX];
/* All members above are keys for lookups, see flow_offload_hash(). */
struct { } __hash;
-
+ u8 dir:4,
+ xmit_type:2,
-+ in_vlan_num:2;
++ encap_num:2;
u16 mtu;
union {
struct dst_entry *dst_cache;
-@@ -174,6 +180,9 @@ struct nf_flow_route {
+@@ -174,6 +180,11 @@ struct nf_flow_route {
struct dst_entry *dst;
struct {
u32 ifindex;
-+ u16 vid[NF_FLOW_TABLE_VLAN_MAX];
-+ __be16 vproto[NF_FLOW_TABLE_VLAN_MAX];
-+ u8 num_vlans;
++ struct {
++ u16 id;
++ __be16 proto;
++ } encap[NF_FLOW_TABLE_ENCAP_MAX];
++ u8 num_encaps;
} in;
struct {
u32 ifindex;
}
flow_tuple->iifidx = route->tuple[dir].in.ifindex;
-+ for (i = route->tuple[dir].in.num_vlans - 1; i >= 0; i--) {
-+ flow_tuple->in_vlan[j].id = route->tuple[dir].in.vid[i];
-+ flow_tuple->in_vlan[j].proto = route->tuple[dir].in.vproto[i];
++ for (i = route->tuple[dir].in.num_encaps - 1; i >= 0; i--) {
++ flow_tuple->encap[j].id = route->tuple[dir].in.encap[i].id;
++ flow_tuple->encap[j].proto = route->tuple[dir].in.encap[i].proto;
+ j++;
+ }
-+ flow_tuple->in_vlan_num = route->tuple[dir].in.num_vlans;
++ flow_tuple->encap_num = route->tuple[dir].in.num_encaps;
switch (route->tuple[dir].xmit_type) {
case FLOW_OFFLOAD_XMIT_DIRECT:
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
-@@ -159,17 +159,35 @@ static bool ip_has_options(unsigned int
+@@ -159,17 +159,38 @@ static bool ip_has_options(unsigned int
return thoff != sizeof(struct iphdr);
}
-+static void nf_flow_tuple_vlan(struct sk_buff *skb,
-+ struct flow_offload_tuple *tuple)
++static void nf_flow_tuple_encap(struct sk_buff *skb,
++ struct flow_offload_tuple *tuple)
+{
++ int i = 0;
++
+ if (skb_vlan_tag_present(skb)) {
-+ tuple->in_vlan[0].id = skb_vlan_tag_get(skb);
-+ tuple->in_vlan[0].proto = skb->vlan_proto;
++ tuple->encap[i].id = skb_vlan_tag_get(skb);
++ tuple->encap[i].proto = skb->vlan_proto;
++ i++;
+ }
+ if (skb->protocol == htons(ETH_P_8021Q)) {
+ struct vlan_ethhdr *veth = (struct vlan_ethhdr *)skb_mac_header(skb);
+
-+ tuple->in_vlan[1].id = ntohs(veth->h_vlan_TCI);
-+ tuple->in_vlan[1].proto = skb->protocol;
++ tuple->encap[i].id = ntohs(veth->h_vlan_TCI);
++ tuple->encap[i].proto = skb->protocol;
+ }
+}
+
thoff = iph->ihl * 4;
if (ip_is_fragment(iph) ||
-@@ -191,11 +209,11 @@ static int nf_flow_tuple_ip(struct sk_bu
+@@ -191,11 +212,11 @@ static int nf_flow_tuple_ip(struct sk_bu
return -1;
thoff = iph->ihl * 4;
tuple->src_v4.s_addr = iph->saddr;
tuple->dst_v4.s_addr = iph->daddr;
-@@ -204,6 +222,7 @@ static int nf_flow_tuple_ip(struct sk_bu
+@@ -204,6 +225,7 @@ static int nf_flow_tuple_ip(struct sk_bu
tuple->l3proto = AF_INET;
tuple->l4proto = iph->protocol;
tuple->iifidx = dev->ifindex;
-+ nf_flow_tuple_vlan(skb, tuple);
++ nf_flow_tuple_encap(skb, tuple);
return 0;
}
-@@ -248,6 +267,37 @@ static unsigned int nf_flow_xmit_xfrm(st
+@@ -248,6 +270,40 @@ static unsigned int nf_flow_xmit_xfrm(st
return NF_STOLEN;
}
-+static bool nf_flow_skb_vlan_protocol(const struct sk_buff *skb, __be16 proto)
++static bool nf_flow_skb_encap_protocol(const struct sk_buff *skb, __be16 proto)
+{
+ if (skb->protocol == htons(ETH_P_8021Q)) {
+ struct vlan_ethhdr *veth;
+ return false;
+}
+
-+static void nf_flow_vlan_pop(struct sk_buff *skb,
-+ struct flow_offload_tuple_rhash *tuplehash)
++static void nf_flow_encap_pop(struct sk_buff *skb,
++ struct flow_offload_tuple_rhash *tuplehash)
+{
+ struct vlan_hdr *vlan_hdr;
+ int i;
+
-+ for (i = 0; i < tuplehash->tuple.in_vlan_num; i++) {
++ for (i = 0; i < tuplehash->tuple.encap_num; i++) {
+ if (skb_vlan_tag_present(skb)) {
+ __vlan_hwaccel_clear_tag(skb);
+ continue;
+ }
-+ vlan_hdr = (struct vlan_hdr *)skb->data;
-+ __skb_pull(skb, VLAN_HLEN);
-+ vlan_set_encap_proto(skb, vlan_hdr);
-+ skb_reset_network_header(skb);
++ if (skb->protocol == htons(ETH_P_8021Q)) {
++ vlan_hdr = (struct vlan_hdr *)skb->data;
++ __skb_pull(skb, VLAN_HLEN);
++ vlan_set_encap_proto(skb, vlan_hdr);
++ skb_reset_network_header(skb);
++ break;
++ }
+ }
+}
+
static unsigned int nf_flow_queue_xmit(struct net *net, struct sk_buff *skb,
const struct flow_offload_tuple_rhash *tuplehash,
unsigned short type)
-@@ -280,9 +330,11 @@ nf_flow_offload_ip_hook(void *priv, stru
- unsigned int thoff;
+@@ -276,13 +332,15 @@ nf_flow_offload_ip_hook(void *priv, stru
+ enum flow_offload_tuple_dir dir;
+ struct flow_offload *flow;
+ struct net_device *outdev;
++ unsigned int thoff, mtu;
+ struct rtable *rt;
+- unsigned int thoff;
struct iphdr *iph;
__be32 nexthop;
+ u32 offset = 0;
- if (skb->protocol != htons(ETH_P_IP))
+ if (skb->protocol != htons(ETH_P_IP) &&
-+ !nf_flow_skb_vlan_protocol(skb, htons(ETH_P_IP)))
++ !nf_flow_skb_encap_protocol(skb, htons(ETH_P_IP)))
return NF_ACCEPT;
if (nf_flow_tuple_ip(skb, state->in, &tuple) < 0)
-@@ -298,11 +350,15 @@ nf_flow_offload_ip_hook(void *priv, stru
- if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
+@@ -295,14 +353,19 @@ nf_flow_offload_ip_hook(void *priv, stru
+ dir = tuplehash->tuple.dir;
+ flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+
+- if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
++ mtu = flow->tuplehash[dir].tuple.mtu + offset;
++ if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
return NF_ACCEPT;
- if (skb_try_make_writable(skb, sizeof(*iph)))
return NF_ACCEPT;
flow_offload_refresh(flow_table, flow);
-@@ -312,6 +368,9 @@ nf_flow_offload_ip_hook(void *priv, stru
+@@ -312,6 +375,9 @@ nf_flow_offload_ip_hook(void *priv, stru
return NF_ACCEPT;
}
-+ nf_flow_vlan_pop(skb, tuplehash);
++ nf_flow_encap_pop(skb, tuplehash);
+ thoff -= offset;
+
if (nf_flow_nat_ip(flow, skb, thoff, dir) < 0)
return NF_DROP;
-@@ -479,14 +538,17 @@ static int nf_flow_nat_ipv6(const struct
+@@ -479,14 +545,17 @@ static int nf_flow_nat_ipv6(const struct
static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
struct flow_offload_tuple *tuple)
{
switch (ip6h->nexthdr) {
case IPPROTO_TCP:
-@@ -503,11 +565,11 @@ static int nf_flow_tuple_ipv6(struct sk_
+@@ -503,11 +572,11 @@ static int nf_flow_tuple_ipv6(struct sk_
return -1;
thoff = sizeof(*ip6h);
tuple->src_v6 = ip6h->saddr;
tuple->dst_v6 = ip6h->daddr;
-@@ -516,6 +578,7 @@ static int nf_flow_tuple_ipv6(struct sk_
+@@ -516,6 +585,7 @@ static int nf_flow_tuple_ipv6(struct sk_
tuple->l3proto = AF_INET6;
tuple->l4proto = ip6h->nexthdr;
tuple->iifidx = dev->ifindex;
-+ nf_flow_tuple_vlan(skb, tuple);
++ nf_flow_tuple_encap(skb, tuple);
return 0;
}
-@@ -533,9 +596,11 @@ nf_flow_offload_ipv6_hook(void *priv, st
+@@ -533,9 +603,12 @@ nf_flow_offload_ipv6_hook(void *priv, st
struct net_device *outdev;
struct ipv6hdr *ip6h;
struct rt6_info *rt;
++ unsigned int mtu;
+ u32 offset = 0;
int ret;
- if (skb->protocol != htons(ETH_P_IPV6))
+ if (skb->protocol != htons(ETH_P_IPV6) &&
-+ !nf_flow_skb_vlan_protocol(skb, htons(ETH_P_IPV6)))
++ !nf_flow_skb_encap_protocol(skb, htons(ETH_P_IPV6)))
return NF_ACCEPT;
if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
-@@ -551,8 +616,11 @@ nf_flow_offload_ipv6_hook(void *priv, st
- if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
+@@ -548,11 +621,15 @@ nf_flow_offload_ipv6_hook(void *priv, st
+ dir = tuplehash->tuple.dir;
+ flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
+
+- if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)))
++ mtu = flow->tuplehash[dir].tuple.mtu + offset;
++ if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
return NF_ACCEPT;
- if (nf_flow_state_check(flow, ipv6_hdr(skb)->nexthdr, skb,
return NF_ACCEPT;
flow_offload_refresh(flow_table, flow);
-@@ -562,6 +630,8 @@ nf_flow_offload_ipv6_hook(void *priv, st
+@@ -562,6 +639,8 @@ nf_flow_offload_ipv6_hook(void *priv, st
return NF_ACCEPT;
}
-+ nf_flow_vlan_pop(skb, tuplehash);
++ nf_flow_encap_pop(skb, tuplehash);
+
if (skb_try_make_writable(skb, sizeof(*ip6h)))
return NF_DROP;
--- a/net/netfilter/nft_flow_offload.c
+++ b/net/netfilter/nft_flow_offload.c
-@@ -65,6 +65,9 @@ static int nft_dev_fill_forward_path(con
-
+@@ -66,6 +66,11 @@ static int nft_dev_fill_forward_path(con
struct nft_forward_info {
- const struct net_device *dev;
-+ __u16 vid[NF_FLOW_TABLE_VLAN_MAX];
-+ __be16 vproto[NF_FLOW_TABLE_VLAN_MAX];
-+ u8 num_vlans;
+ const struct net_device *indev;
+ const struct net_device *outdev;
++ struct id {
++ __u16 id;
++ __be16 proto;
++ } encap[NF_FLOW_TABLE_ENCAP_MAX];
++ u8 num_encaps;
u8 h_source[ETH_ALEN];
u8 h_dest[ETH_ALEN];
enum flow_offload_xmit_type xmit_type;
-@@ -83,9 +86,22 @@ static void nft_dev_path_info(const stru
+@@ -84,9 +89,23 @@ static void nft_dev_path_info(const stru
path = &stack->path[i];
switch (path->type) {
case DEV_PATH_ETHERNET:
+ case DEV_PATH_VLAN:
- info->dev = path->dev;
+ info->indev = path->dev;
if (is_zero_ether_addr(info->h_source))
memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
+
+ break;
+
+ /* DEV_PATH_VLAN */
-+ if (info->num_vlans >= NF_FLOW_TABLE_VLAN_MAX) {
-+ info->dev = NULL;
++ if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) {
++ info->indev = NULL;
+ break;
+ }
-+ info->vid[info->num_vlans] = path->vlan.id;
-+ info->vproto[info->num_vlans] = path->vlan.proto;
-+ info->num_vlans++;
++ info->outdev = path->dev;
++ info->encap[info->num_encaps].id = path->encap.id;
++ info->encap[info->num_encaps].proto = path->encap.proto;
++ info->num_encaps++;
break;
case DEV_PATH_BRIDGE:
if (is_zero_ether_addr(info->h_source))
-@@ -93,7 +109,6 @@ static void nft_dev_path_info(const stru
+@@ -94,7 +113,6 @@ static void nft_dev_path_info(const stru
info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
break;
- case DEV_PATH_VLAN:
default:
- info->dev = NULL;
+ info->indev = NULL;
break;
-@@ -127,6 +142,7 @@ static void nft_dev_forward_path(struct
+@@ -130,6 +148,7 @@ static void nft_dev_forward_path(struct
struct net_device_path_stack stack;
struct nft_forward_info info = {};
unsigned char ha[ETH_ALEN];
if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0)
nft_dev_path_info(&stack, &info, ha);
-@@ -135,6 +151,11 @@ static void nft_dev_forward_path(struct
+@@ -138,6 +157,11 @@ static void nft_dev_forward_path(struct
return;
- route->tuple[!dir].in.ifindex = info.dev->ifindex;
-+ for (i = 0; i < info.num_vlans; i++) {
-+ route->tuple[!dir].in.vid[i] = info.vid[i];
-+ route->tuple[!dir].in.vproto[i] = info.vproto[i];
+ route->tuple[!dir].in.ifindex = info.indev->ifindex;
++ for (i = 0; i < info.num_encaps; i++) {
++ route->tuple[!dir].in.encap[i].id = info.encap[i].id;
++ route->tuple[!dir].in.encap[i].proto = info.encap[i].proto;
+ }
-+ route->tuple[!dir].in.num_vlans = info.num_vlans;
++ route->tuple[!dir].in.num_encaps = info.num_encaps;
if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) {
memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN);
@@ -847,10 +847,20 @@ struct net_device_path {
u16 id;
__be16 proto;
- } vlan;
+ } encap;
+ struct {
+ enum {
+ DEV_PATH_BR_VLAN_KEEP,
--- a/net/8021q/vlan_dev.c
+++ b/net/8021q/vlan_dev.c
@@ -777,6 +777,12 @@ static int vlan_dev_fill_forward_path(st
- path->vlan.proto = vlan->vlan_proto;
+ path->encap.proto = vlan->vlan_proto;
path->dev = ctx->dev;
ctx->dev = vlan->real_dev;
+ if (ctx->num_vlans >= ARRAY_SIZE(ctx->vlan))
+++ /dev/null
-From: Felix Fietkau <nbd@nbd.name>
-Date: Mon, 7 Dec 2020 20:31:48 +0100
-Subject: [PATCH] net: dsa: resolve forwarding path for dsa slave ports
-
-Add .ndo_fill_forward_path for dsa slave port devices
-
-Signed-off-by: Felix Fietkau <nbd@nbd.name>
----
-
---- a/include/linux/netdevice.h
-+++ b/include/linux/netdevice.h
-@@ -837,6 +837,7 @@ enum net_device_path_type {
- DEV_PATH_ETHERNET = 0,
- DEV_PATH_VLAN,
- DEV_PATH_BRIDGE,
-+ DEV_PATH_DSA,
- };
-
- struct net_device_path {
-@@ -856,6 +857,10 @@ struct net_device_path {
- u16 vlan_id;
- __be16 vlan_proto;
- } bridge;
-+ struct {
-+ int port;
-+ u16 proto;
-+ } dsa;
- };
- };
-
---- a/net/dsa/slave.c
-+++ b/net/dsa/slave.c
-@@ -1582,6 +1582,21 @@ static struct devlink_port *dsa_slave_ge
- return dp->ds->devlink ? &dp->devlink_port : NULL;
- }
-
-+static int dsa_slave_fill_forward_path(struct net_device_path_ctx *ctx,
-+ struct net_device_path *path)
-+{
-+ struct dsa_port *dp = dsa_slave_to_port(ctx->dev);
-+ struct dsa_port *cpu_dp = dp->cpu_dp;
-+
-+ path->dev = ctx->dev;
-+ path->type = DEV_PATH_DSA;
-+ path->dsa.proto = cpu_dp->tag_ops->proto;
-+ path->dsa.port = dp->index;
-+ ctx->dev = cpu_dp->master;
-+
-+ return 0;
-+}
-+
- static const struct net_device_ops dsa_slave_netdev_ops = {
- .ndo_open = dsa_slave_open,
- .ndo_stop = dsa_slave_close,
-@@ -1607,6 +1622,7 @@ static const struct net_device_ops dsa_s
- .ndo_vlan_rx_kill_vid = dsa_slave_vlan_rx_kill_vid,
- .ndo_get_devlink_port = dsa_slave_get_devlink_port,
- .ndo_change_mtu = dsa_slave_change_mtu,
-+ .ndo_fill_forward_path = dsa_slave_fill_forward_path,
- };
-
- static struct device_type dsa_type = {
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Sun, 24 Jan 2021 18:01:34 +0100
+Subject: [PATCH] netfilter: nft_flow_offload: add bridge vlan filtering
+ support
+
+Add the vlan tag based when PVID is set on.
+
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+---
+
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -111,6 +111,18 @@ static void nft_dev_path_info(const stru
+ if (is_zero_ether_addr(info->h_source))
+ memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
+
++ switch (path->bridge.vlan_mode) {
++ case DEV_PATH_BR_VLAN_TAG:
++ info->encap[info->num_encaps].id = path->bridge.vlan_id;
++ info->encap[info->num_encaps].proto = path->bridge.vlan_proto;
++ info->num_encaps++;
++ break;
++ case DEV_PATH_BR_VLAN_UNTAG:
++ info->num_encaps--;
++ break;
++ case DEV_PATH_BR_VLAN_KEEP:
++ break;
++ }
+ info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
+ break;
+ default:
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Tue, 2 Mar 2021 21:45:16 +0100
+Subject: [PATCH] net: ppp: resolve forwarding path for bridge pppoe
+ devices
+
+Pass on the PPPoE session ID and the real device.
+---
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -1450,12 +1450,34 @@ static void ppp_dev_priv_destructor(stru
+ ppp_destroy_interface(ppp);
+ }
+
++static int ppp_fill_forward_path(struct net_device_path_ctx *ctx,
++ struct net_device_path *path)
++{
++ struct ppp *ppp = netdev_priv(path->dev);
++ struct ppp_channel *chan;
++ struct channel *pch;
++
++ if (ppp->flags & SC_MULTILINK)
++ return -EOPNOTSUPP;
++
++ if (list_empty(&ppp->channels))
++ return -ENODEV;
++
++ pch = list_first_entry(&ppp->channels, struct channel, clist);
++ chan = pch->chan;
++ if (!chan->ops->fill_forward_path)
++ return -EOPNOTSUPP;
++
++ return chan->ops->fill_forward_path(ctx, path, chan);
++}
++
+ static const struct net_device_ops ppp_netdev_ops = {
+ .ndo_init = ppp_dev_init,
+ .ndo_uninit = ppp_dev_uninit,
+ .ndo_start_xmit = ppp_start_xmit,
+ .ndo_do_ioctl = ppp_net_ioctl,
+ .ndo_get_stats64 = ppp_get_stats64,
++ .ndo_fill_forward_path = ppp_fill_forward_path,
+ };
+
+ static struct device_type ppp_type = {
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -972,8 +972,30 @@ static int pppoe_xmit(struct ppp_channel
+ return __pppoe_xmit(sk, skb);
+ }
+
++static int pppoe_fill_forward_path(struct net_device_path_ctx *ctx,
++ struct net_device_path *path,
++ const struct ppp_channel *chan)
++{
++ struct sock *sk = (struct sock *)chan->private;
++ struct pppox_sock *po = pppox_sk(sk);
++ struct net_device *dev = po->pppoe_dev;
++
++ if (sock_flag(sk, SOCK_DEAD) ||
++ !(sk->sk_state & PPPOX_CONNECTED) || !dev)
++ return -1;
++
++ path->type = DEV_PATH_PPPOE;
++ path->encap.proto = htons(ETH_P_PPP_SES);
++ path->encap.id = be16_to_cpu(po->num);
++ path->dev = ctx->dev;
++ ctx->dev = dev;
++
++ return 0;
++}
++
+ static const struct ppp_channel_ops pppoe_chan_ops = {
+ .start_xmit = pppoe_xmit,
++ .fill_forward_path = pppoe_fill_forward_path,
+ };
+
+ static int pppoe_recvmsg(struct socket *sock, struct msghdr *m,
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -837,6 +837,7 @@ enum net_device_path_type {
+ DEV_PATH_ETHERNET = 0,
+ DEV_PATH_VLAN,
+ DEV_PATH_BRIDGE,
++ DEV_PATH_PPPOE,
+ };
+
+ struct net_device_path {
+--- a/include/linux/ppp_channel.h
++++ b/include/linux/ppp_channel.h
+@@ -28,6 +28,9 @@ struct ppp_channel_ops {
+ int (*start_xmit)(struct ppp_channel *, struct sk_buff *);
+ /* Handle an ioctl call that has come in via /dev/ppp. */
+ int (*ioctl)(struct ppp_channel *, unsigned int, unsigned long);
++ int (*fill_forward_path)(struct net_device_path_ctx *,
++ struct net_device_path *,
++ const struct ppp_channel *);
+ };
+
+ struct ppp_channel {
+++ /dev/null
-From: Pablo Neira Ayuso <pablo@netfilter.org>
-Date: Mon, 7 Dec 2020 20:31:44 +0100
-Subject: [PATCH] netfilter: flowtable: add offload support for xmit path
- types
-
-When the flow tuple xmit_type is set to FLOW_OFFLOAD_XMIT_DIRECT, the
-dst_cache pointer is not valid, and the h_source/h_dest/ifidx out fields
-need to be used.
-
-This patch also adds the FLOW_ACTION_VLAN_PUSH action to pass the VLAN
-tag to the driver.
----
-
---- a/net/netfilter/nf_flow_table_offload.c
-+++ b/net/netfilter/nf_flow_table_offload.c
-@@ -175,28 +175,45 @@ static int flow_offload_eth_src(struct n
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
- {
-- const struct flow_offload_tuple *tuple = &flow->tuplehash[!dir].tuple;
- struct flow_action_entry *entry0 = flow_action_entry_next(flow_rule);
- struct flow_action_entry *entry1 = flow_action_entry_next(flow_rule);
-- struct net_device *dev;
-+ const struct flow_offload_tuple *other_tuple, *this_tuple;
-+ struct net_device *dev = NULL;
-+ const unsigned char *addr;
- u32 mask, val;
- u16 val16;
-
-- dev = dev_get_by_index(net, tuple->iifidx);
-- if (!dev)
-- return -ENOENT;
-+ this_tuple = &flow->tuplehash[dir].tuple;
-+
-+ switch (this_tuple->xmit_type) {
-+ case FLOW_OFFLOAD_XMIT_DIRECT:
-+ addr = this_tuple->out.h_source;
-+ break;
-+ case FLOW_OFFLOAD_XMIT_NEIGH:
-+ other_tuple = &flow->tuplehash[!dir].tuple;
-+ dev = dev_get_by_index(net, other_tuple->iifidx);
-+ if (!dev)
-+ return -ENOENT;
-+
-+ addr = dev->dev_addr;
-+ break;
-+ default:
-+ return -EOPNOTSUPP;
-+ }
-
- mask = ~0xffff0000;
-- memcpy(&val16, dev->dev_addr, 2);
-+ memcpy(&val16, addr, 2);
- val = val16 << 16;
- flow_offload_mangle(entry0, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 4,
- &val, &mask);
-
- mask = ~0xffffffff;
-- memcpy(&val, dev->dev_addr + 2, 4);
-+ memcpy(&val, addr + 2, 4);
- flow_offload_mangle(entry1, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 8,
- &val, &mask);
-- dev_put(dev);
-+
-+ if (dev)
-+ dev_put(dev);
-
- return 0;
- }
-@@ -208,27 +225,40 @@ static int flow_offload_eth_dst(struct n
- {
- struct flow_action_entry *entry0 = flow_action_entry_next(flow_rule);
- struct flow_action_entry *entry1 = flow_action_entry_next(flow_rule);
-- const void *daddr = &flow->tuplehash[!dir].tuple.src_v4;
-+ const struct flow_offload_tuple *other_tuple, *this_tuple;
- const struct dst_entry *dst_cache;
- unsigned char ha[ETH_ALEN];
- struct neighbour *n;
-+ const void *daddr;
- u32 mask, val;
- u8 nud_state;
- u16 val16;
-
-- dst_cache = flow->tuplehash[dir].tuple.dst_cache;
-- n = dst_neigh_lookup(dst_cache, daddr);
-- if (!n)
-- return -ENOENT;
--
-- read_lock_bh(&n->lock);
-- nud_state = n->nud_state;
-- ether_addr_copy(ha, n->ha);
-- read_unlock_bh(&n->lock);
-+ this_tuple = &flow->tuplehash[dir].tuple;
-
-- if (!(nud_state & NUD_VALID)) {
-+ switch (this_tuple->xmit_type) {
-+ case FLOW_OFFLOAD_XMIT_DIRECT:
-+ ether_addr_copy(ha, this_tuple->out.h_dest);
-+ break;
-+ case FLOW_OFFLOAD_XMIT_NEIGH:
-+ other_tuple = &flow->tuplehash[!dir].tuple;
-+ daddr = &other_tuple->src_v4;
-+ dst_cache = this_tuple->dst_cache;
-+ n = dst_neigh_lookup(dst_cache, daddr);
-+ if (!n)
-+ return -ENOENT;
-+
-+ read_lock_bh(&n->lock);
-+ nud_state = n->nud_state;
-+ ether_addr_copy(ha, n->ha);
-+ read_unlock_bh(&n->lock);
- neigh_release(n);
-- return -ENOENT;
-+
-+ if (!(nud_state & NUD_VALID))
-+ return -ENOENT;
-+ break;
-+ default:
-+ return -EOPNOTSUPP;
- }
-
- mask = ~0xffffffff;
-@@ -241,7 +271,6 @@ static int flow_offload_eth_dst(struct n
- val = val16;
- flow_offload_mangle(entry1, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 4,
- &val, &mask);
-- neigh_release(n);
-
- return 0;
- }
-@@ -463,27 +492,52 @@ static void flow_offload_ipv4_checksum(s
- }
- }
-
--static void flow_offload_redirect(const struct flow_offload *flow,
-+static void flow_offload_redirect(struct net *net,
-+ const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
- {
-- struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
-- struct rtable *rt;
-+ const struct flow_offload_tuple *this_tuple, *other_tuple;
-+ struct flow_action_entry *entry;
-+ struct net_device *dev;
-+ int ifindex;
-+
-+ this_tuple = &flow->tuplehash[dir].tuple;
-+ switch (this_tuple->xmit_type) {
-+ case FLOW_OFFLOAD_XMIT_DIRECT:
-+ this_tuple = &flow->tuplehash[dir].tuple;
-+ ifindex = this_tuple->out.ifidx;
-+ break;
-+ case FLOW_OFFLOAD_XMIT_NEIGH:
-+ other_tuple = &flow->tuplehash[!dir].tuple;
-+ ifindex = other_tuple->iifidx;
-+ break;
-+ default:
-+ return;
-+ }
-
-- rt = (struct rtable *)flow->tuplehash[dir].tuple.dst_cache;
-+ dev = dev_get_by_index(net, ifindex);
-+ if (!dev)
-+ return;
-+
-+ entry = flow_action_entry_next(flow_rule);
- entry->id = FLOW_ACTION_REDIRECT;
-- entry->dev = rt->dst.dev;
-- dev_hold(rt->dst.dev);
-+ entry->dev = dev;
- }
-
- static void flow_offload_encap_tunnel(const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
- {
-+ const struct flow_offload_tuple *this_tuple;
- struct flow_action_entry *entry;
- struct dst_entry *dst;
-
-- dst = flow->tuplehash[dir].tuple.dst_cache;
-+ this_tuple = &flow->tuplehash[dir].tuple;
-+ if (this_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
-+ return;
-+
-+ dst = this_tuple->dst_cache;
- if (dst && dst->lwtstate) {
- struct ip_tunnel_info *tun_info;
-
-@@ -500,10 +554,15 @@ static void flow_offload_decap_tunnel(co
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
- {
-+ const struct flow_offload_tuple *other_tuple;
- struct flow_action_entry *entry;
- struct dst_entry *dst;
-
-- dst = flow->tuplehash[!dir].tuple.dst_cache;
-+ other_tuple = &flow->tuplehash[!dir].tuple;
-+ if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
-+ return;
-+
-+ dst = other_tuple->dst_cache;
- if (dst && dst->lwtstate) {
- struct ip_tunnel_info *tun_info;
-
-@@ -515,10 +574,14 @@ static void flow_offload_decap_tunnel(co
- }
- }
-
--int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
-- enum flow_offload_tuple_dir dir,
-- struct nf_flow_rule *flow_rule)
-+static int
-+nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
-+ enum flow_offload_tuple_dir dir,
-+ struct nf_flow_rule *flow_rule)
- {
-+ const struct flow_offload_tuple *other_tuple;
-+ int i;
-+
- flow_offload_decap_tunnel(flow, dir, flow_rule);
- flow_offload_encap_tunnel(flow, dir, flow_rule);
-
-@@ -526,6 +589,26 @@ int nf_flow_rule_route_ipv4(struct net *
- flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
- return -1;
-
-+ other_tuple = &flow->tuplehash[!dir].tuple;
-+
-+ for (i = 0; i < other_tuple->in_vlan_num; i++) {
-+ struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
-+
-+ entry->id = FLOW_ACTION_VLAN_PUSH;
-+ entry->vlan.vid = other_tuple->in_vlan[i].id;
-+ entry->vlan.proto = other_tuple->in_vlan[i].proto;
-+ }
-+
-+ return 0;
-+}
-+
-+int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
-+ enum flow_offload_tuple_dir dir,
-+ struct nf_flow_rule *flow_rule)
-+{
-+ if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
-+ return -1;
-+
- if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
- flow_offload_ipv4_snat(net, flow, dir, flow_rule);
- flow_offload_port_snat(net, flow, dir, flow_rule);
-@@ -538,7 +621,7 @@ int nf_flow_rule_route_ipv4(struct net *
- test_bit(NF_FLOW_DNAT, &flow->flags))
- flow_offload_ipv4_checksum(net, flow, flow_rule);
-
-- flow_offload_redirect(flow, dir, flow_rule);
-+ flow_offload_redirect(net, flow, dir, flow_rule);
-
- return 0;
- }
-@@ -548,11 +631,7 @@ int nf_flow_rule_route_ipv6(struct net *
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
- {
-- flow_offload_decap_tunnel(flow, dir, flow_rule);
-- flow_offload_encap_tunnel(flow, dir, flow_rule);
--
-- if (flow_offload_eth_src(net, flow, dir, flow_rule) < 0 ||
-- flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
-+ if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
- return -1;
-
- if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
-@@ -564,7 +643,7 @@ int nf_flow_rule_route_ipv6(struct net *
- flow_offload_port_dnat(net, flow, dir, flow_rule);
- }
-
-- flow_offload_redirect(flow, dir, flow_rule);
-+ flow_offload_redirect(net, flow, dir, flow_rule);
-
- return 0;
- }
-@@ -578,10 +657,10 @@ nf_flow_offload_rule_alloc(struct net *n
- enum flow_offload_tuple_dir dir)
- {
- const struct nf_flowtable *flowtable = offload->flowtable;
-+ const struct flow_offload_tuple *tuple, *other_tuple;
- const struct flow_offload *flow = offload->flow;
-- const struct flow_offload_tuple *tuple;
-+ struct dst_entry *other_dst = NULL;
- struct nf_flow_rule *flow_rule;
-- struct dst_entry *other_dst;
- int err = -ENOMEM;
-
- flow_rule = kzalloc(sizeof(*flow_rule), GFP_KERNEL);
-@@ -597,7 +676,10 @@ nf_flow_offload_rule_alloc(struct net *n
- flow_rule->rule->match.key = &flow_rule->match.key;
-
- tuple = &flow->tuplehash[dir].tuple;
-- other_dst = flow->tuplehash[!dir].tuple.dst_cache;
-+ other_tuple = &flow->tuplehash[!dir].tuple;
-+ if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_NEIGH)
-+ other_dst = other_tuple->dst_cache;
-+
- err = nf_flow_rule_match(&flow_rule->match, tuple, other_dst);
- if (err < 0)
- goto err_flow_match;
--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 4 Mar 2021 23:19:06 +0100
+Subject: [PATCH] net: dsa: resolve forwarding path for dsa slave ports
+
+Add .ndo_fill_forward_path for dsa slave port devices
+---
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -838,6 +838,7 @@ enum net_device_path_type {
+ DEV_PATH_VLAN,
+ DEV_PATH_BRIDGE,
+ DEV_PATH_PPPOE,
++ DEV_PATH_DSA,
+ };
+
+ struct net_device_path {
+@@ -857,6 +858,10 @@ struct net_device_path {
+ u16 vlan_id;
+ __be16 vlan_proto;
+ } bridge;
++ struct {
++ int port;
++ u16 proto;
++ } dsa;
+ };
+ };
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1582,6 +1582,21 @@ static struct devlink_port *dsa_slave_ge
+ return dp->ds->devlink ? &dp->devlink_port : NULL;
+ }
+
++static int dsa_slave_fill_forward_path(struct net_device_path_ctx *ctx,
++ struct net_device_path *path)
++{
++ struct dsa_port *dp = dsa_slave_to_port(ctx->dev);
++ struct dsa_port *cpu_dp = dp->cpu_dp;
++
++ path->dev = ctx->dev;
++ path->type = DEV_PATH_DSA;
++ path->dsa.proto = cpu_dp->tag_ops->proto;
++ path->dsa.port = dp->index;
++ ctx->dev = cpu_dp->master;
++
++ return 0;
++}
++
+ static const struct net_device_ops dsa_slave_netdev_ops = {
+ .ndo_open = dsa_slave_open,
+ .ndo_stop = dsa_slave_close,
+@@ -1607,6 +1622,7 @@ static const struct net_device_ops dsa_s
+ .ndo_vlan_rx_kill_vid = dsa_slave_vlan_rx_kill_vid,
+ .ndo_get_devlink_port = dsa_slave_get_devlink_port,
+ .ndo_change_mtu = dsa_slave_change_mtu,
++ .ndo_fill_forward_path = dsa_slave_fill_forward_path,
+ };
+
+ static struct device_type dsa_type = {
+++ /dev/null
-From: Pablo Neira Ayuso <pablo@netfilter.org>
-Date: Mon, 18 Jan 2021 22:27:45 +0100
-Subject: [PATCH] netfilter: nft_flow_offload: add dsa support
-
-Replace the master ethernet device by the dsa slave port.
-
-Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
----
-
---- a/net/netfilter/nft_flow_offload.c
-+++ b/net/netfilter/nft_flow_offload.c
-@@ -86,6 +86,7 @@ static void nft_dev_path_info(const stru
- path = &stack->path[i];
- switch (path->type) {
- case DEV_PATH_ETHERNET:
-+ case DEV_PATH_DSA:
- case DEV_PATH_VLAN:
- info->dev = path->dev;
- if (is_zero_ether_addr(info->h_source))
-@@ -93,6 +94,10 @@ static void nft_dev_path_info(const stru
-
- if (path->type == DEV_PATH_ETHERNET)
- break;
-+ if (path->type == DEV_PATH_DSA) {
-+ i = stack->num_paths;
-+ break;
-+ }
-
- /* DEV_PATH_VLAN */
- if (info->num_vlans >= NF_FLOW_TABLE_VLAN_MAX) {
+++ /dev/null
-From: Pablo Neira Ayuso <pablo@netfilter.org>
-Date: Mon, 18 Jan 2021 22:39:17 +0100
-Subject: [PATCH] dsa: slave: add support for TC_SETUP_FT
-
-The dsa infrastructure provides a well-defined hierarchy of devices,
-pass up the call to set up the flow block to the master device. From the
-software dataplane, the netfilter infrastructure uses the dsa slave
-devices to refer to the input and output device for the given skbuff.
-Similarly, the flowtable definition in the ruleset refers to the dsa
-slave port devices.
-
-This patch adds the glue code to call ndo_setup_tc with TC_SETUP_FT
-with the master device via the dsa slave devices.
-
-Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
----
-
---- a/net/dsa/slave.c
-+++ b/net/dsa/slave.c
-@@ -1202,14 +1202,32 @@ static int dsa_slave_setup_tc_block(stru
- }
- }
-
-+static int dsa_slave_setup_ft_block(struct dsa_switch *ds, int port,
-+ void *type_data)
-+{
-+ struct dsa_port *cpu_dp = dsa_to_port(ds, port)->cpu_dp;
-+ struct net_device *master = cpu_dp->master;
-+
-+ if (!master->netdev_ops->ndo_setup_tc)
-+ return -EOPNOTSUPP;
-+
-+ return master->netdev_ops->ndo_setup_tc(master, TC_SETUP_FT, type_data);
-+}
-+
- static int dsa_slave_setup_tc(struct net_device *dev, enum tc_setup_type type,
- void *type_data)
- {
- struct dsa_port *dp = dsa_slave_to_port(dev);
- struct dsa_switch *ds = dp->ds;
-
-- if (type == TC_SETUP_BLOCK)
-+ switch (type) {
-+ case TC_SETUP_BLOCK:
- return dsa_slave_setup_tc_block(dev, type_data);
-+ case TC_SETUP_FT:
-+ return dsa_slave_setup_ft_block(ds, dp->index, type_data);
-+ default:
-+ break;
-+ }
-
- if (!ds->ops->port_setup_tc)
- return -EOPNOTSUPP;
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Mon, 1 Mar 2021 23:52:49 +0100
+Subject: [PATCH] netfilter: flowtable: add pppoe support
+
+---
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -1453,7 +1453,7 @@ static void ppp_dev_priv_destructor(stru
+ static int ppp_fill_forward_path(struct net_device_path_ctx *ctx,
+ struct net_device_path *path)
+ {
+- struct ppp *ppp = netdev_priv(path->dev);
++ struct ppp *ppp = netdev_priv(ctx->dev);
+ struct ppp_channel *chan;
+ struct channel *pch;
+
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -987,6 +987,7 @@ static int pppoe_fill_forward_path(struc
+ path->type = DEV_PATH_PPPOE;
+ path->encap.proto = htons(ETH_P_PPP_SES);
+ path->encap.id = be16_to_cpu(po->num);
++ memcpy(path->encap.h_dest, po->pppoe_pa.remote, ETH_ALEN);
+ path->dev = ctx->dev;
+ ctx->dev = dev;
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -848,6 +848,7 @@ struct net_device_path {
+ struct {
+ u16 id;
+ __be16 proto;
++ u8 h_dest[ETH_ALEN];
+ } encap;
+ struct {
+ enum {
+--- a/net/netfilter/nf_flow_table_ip.c
++++ b/net/netfilter/nf_flow_table_ip.c
+@@ -7,6 +7,9 @@
+ #include <linux/ip.h>
+ #include <linux/ipv6.h>
+ #include <linux/netdevice.h>
++#include <linux/if_ether.h>
++#include <linux/if_pppox.h>
++#include <linux/ppp_defs.h>
+ #include <net/ip.h>
+ #include <net/ipv6.h>
+ #include <net/ip6_route.h>
+@@ -162,6 +165,8 @@ static bool ip_has_options(unsigned int
+ static void nf_flow_tuple_encap(struct sk_buff *skb,
+ struct flow_offload_tuple *tuple)
+ {
++ struct vlan_ethhdr *veth;
++ struct pppoe_hdr *phdr;
+ int i = 0;
+
+ if (skb_vlan_tag_present(skb)) {
+@@ -169,23 +174,35 @@ static void nf_flow_tuple_encap(struct s
+ tuple->encap[i].proto = skb->vlan_proto;
+ i++;
+ }
+- if (skb->protocol == htons(ETH_P_8021Q)) {
+- struct vlan_ethhdr *veth = (struct vlan_ethhdr *)skb_mac_header(skb);
+-
++ switch (skb->protocol) {
++ case htons(ETH_P_8021Q):
++ veth = (struct vlan_ethhdr *)skb_mac_header(skb);
+ tuple->encap[i].id = ntohs(veth->h_vlan_TCI);
+ tuple->encap[i].proto = skb->protocol;
++ break;
++ case htons(ETH_P_PPP_SES):
++ phdr = (struct pppoe_hdr *)skb_mac_header(skb);
++ tuple->encap[i].id = ntohs(phdr->sid);
++ tuple->encap[i].proto = skb->protocol;
++ break;
+ }
+ }
+
+ static int nf_flow_tuple_ip(struct sk_buff *skb, const struct net_device *dev,
+- struct flow_offload_tuple *tuple)
++ struct flow_offload_tuple *tuple, u32 *nhoff)
+ {
+ unsigned int thoff, hdrsize, offset = 0;
+ struct flow_ports *ports;
+ struct iphdr *iph;
+
+- if (skb->protocol == htons(ETH_P_8021Q))
++ switch (skb->protocol) {
++ case htons(ETH_P_8021Q):
+ offset += VLAN_HLEN;
++ break;
++ case htons(ETH_P_PPP_SES):
++ offset += PPPOE_SES_HLEN;
++ break;
++ }
+
+ if (!pskb_may_pull(skb, sizeof(*iph) + offset))
+ return -1;
+@@ -226,6 +243,7 @@ static int nf_flow_tuple_ip(struct sk_bu
+ tuple->l4proto = iph->protocol;
+ tuple->iifidx = dev->ifindex;
+ nf_flow_tuple_encap(skb, tuple);
++ *nhoff = offset;
+
+ return 0;
+ }
+@@ -270,14 +288,36 @@ static unsigned int nf_flow_xmit_xfrm(st
+ return NF_STOLEN;
+ }
+
++static inline __be16 nf_flow_pppoe_proto(const struct sk_buff *skb)
++{
++ __be16 proto;
++
++ proto = *((__be16 *)(skb_mac_header(skb) + ETH_HLEN +
++ sizeof(struct pppoe_hdr)));
++ switch (proto) {
++ case htons(PPP_IP):
++ return htons(ETH_P_IP);
++ case htons(PPP_IPV6):
++ return htons(ETH_P_IPV6);
++ }
++
++ return 0;
++}
++
+ static bool nf_flow_skb_encap_protocol(const struct sk_buff *skb, __be16 proto)
+ {
+- if (skb->protocol == htons(ETH_P_8021Q)) {
+- struct vlan_ethhdr *veth;
++ struct vlan_ethhdr *veth;
+
++ switch (skb->protocol) {
++ case htons(ETH_P_8021Q):
+ veth = (struct vlan_ethhdr *)skb_mac_header(skb);
+ if (veth->h_vlan_encapsulated_proto == proto)
+ return true;
++ break;
++ case htons(ETH_P_PPP_SES):
++ if (nf_flow_pppoe_proto(skb) == proto)
++ return true;
++ break;
+ }
+
+ return false;
+@@ -294,12 +334,18 @@ static void nf_flow_encap_pop(struct sk_
+ __vlan_hwaccel_clear_tag(skb);
+ continue;
+ }
+- if (skb->protocol == htons(ETH_P_8021Q)) {
++ switch (skb->protocol) {
++ case htons(ETH_P_8021Q):
+ vlan_hdr = (struct vlan_hdr *)skb->data;
+ __skb_pull(skb, VLAN_HLEN);
+ vlan_set_encap_proto(skb, vlan_hdr);
+ skb_reset_network_header(skb);
+ break;
++ case htons(ETH_P_PPP_SES):
++ skb->protocol = nf_flow_pppoe_proto(skb);
++ skb_pull(skb, PPPOE_SES_HLEN);
++ skb_reset_network_header(skb);
++ break;
+ }
+ }
+ }
+@@ -343,7 +389,7 @@ nf_flow_offload_ip_hook(void *priv, stru
+ !nf_flow_skb_encap_protocol(skb, htons(ETH_P_IP)))
+ return NF_ACCEPT;
+
+- if (nf_flow_tuple_ip(skb, state->in, &tuple) < 0)
++ if (nf_flow_tuple_ip(skb, state->in, &tuple, &offset) < 0)
+ return NF_ACCEPT;
+
+ tuplehash = flow_offload_lookup(flow_table, &tuple);
+@@ -357,9 +403,6 @@ nf_flow_offload_ip_hook(void *priv, stru
+ if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
+ return NF_ACCEPT;
+
+- if (skb->protocol == htons(ETH_P_8021Q))
+- offset += VLAN_HLEN;
+-
+ if (skb_try_make_writable(skb, sizeof(*iph) + offset))
+ return NF_DROP;
+
+@@ -543,14 +586,20 @@ static int nf_flow_nat_ipv6(const struct
+ }
+
+ static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
+- struct flow_offload_tuple *tuple)
++ struct flow_offload_tuple *tuple, u32 *nhoff)
+ {
+ unsigned int thoff, hdrsize, offset = 0;
+ struct flow_ports *ports;
+ struct ipv6hdr *ip6h;
+
+- if (skb->protocol == htons(ETH_P_8021Q))
++ switch (skb->protocol) {
++ case htons(ETH_P_8021Q):
+ offset += VLAN_HLEN;
++ break;
++ case htons(ETH_P_PPP_SES):
++ offset += PPPOE_SES_HLEN;
++ break;
++ }
+
+ if (!pskb_may_pull(skb, sizeof(*ip6h) + offset))
+ return -1;
+@@ -586,6 +635,7 @@ static int nf_flow_tuple_ipv6(struct sk_
+ tuple->l4proto = ip6h->nexthdr;
+ tuple->iifidx = dev->ifindex;
+ nf_flow_tuple_encap(skb, tuple);
++ *nhoff = offset;
+
+ return 0;
+ }
+@@ -611,7 +661,7 @@ nf_flow_offload_ipv6_hook(void *priv, st
+ !nf_flow_skb_encap_protocol(skb, htons(ETH_P_IPV6)))
+ return NF_ACCEPT;
+
+- if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
++ if (nf_flow_tuple_ipv6(skb, state->in, &tuple, &offset) < 0)
+ return NF_ACCEPT;
+
+ tuplehash = flow_offload_lookup(flow_table, &tuple);
+@@ -625,9 +675,6 @@ nf_flow_offload_ipv6_hook(void *priv, st
+ if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
+ return NF_ACCEPT;
+
+- if (skb->protocol == htons(ETH_P_8021Q))
+- offset += VLAN_HLEN;
+-
+ ip6h = (struct ipv6hdr *)(skb_network_header(skb) + offset);
+ if (nf_flow_state_check(flow, ip6h->nexthdr, skb, sizeof(*ip6h)))
+ return NF_ACCEPT;
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -90,6 +90,7 @@ static void nft_dev_path_info(const stru
+ switch (path->type) {
+ case DEV_PATH_ETHERNET:
+ case DEV_PATH_VLAN:
++ case DEV_PATH_PPPOE:
+ info->indev = path->dev;
+ if (is_zero_ether_addr(info->h_source))
+ memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
+@@ -97,7 +98,7 @@ static void nft_dev_path_info(const stru
+ if (path->type == DEV_PATH_ETHERNET)
+ break;
+
+- /* DEV_PATH_VLAN */
++ /* DEV_PATH_VLAN and DEV_PATH_PPPOE */
+ if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) {
+ info->indev = NULL;
+ break;
+@@ -106,6 +107,8 @@ static void nft_dev_path_info(const stru
+ info->encap[info->num_encaps].id = path->encap.id;
+ info->encap[info->num_encaps].proto = path->encap.proto;
+ info->num_encaps++;
++ if (path->type == DEV_PATH_PPPOE)
++ memcpy(info->h_dest, path->encap.h_dest, ETH_ALEN);
+ break;
+ case DEV_PATH_BRIDGE:
+ if (is_zero_ether_addr(info->h_source))
+++ /dev/null
-From: Pablo Neira Ayuso <pablo@netfilter.org>
-Date: Sun, 24 Jan 2021 18:01:34 +0100
-Subject: [PATCH] netfilter: nft_flow_offload: add bridge vlan filtering
- support
-
-Add the vlan tag based when PVID is set on.
-
-Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
----
-
---- a/net/netfilter/nft_flow_offload.c
-+++ b/net/netfilter/nft_flow_offload.c
-@@ -112,6 +112,18 @@ static void nft_dev_path_info(const stru
- if (is_zero_ether_addr(info->h_source))
- memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
-
-+ switch (path->bridge.vlan_mode) {
-+ case DEV_PATH_BR_VLAN_TAG:
-+ info->vid[info->num_vlans] = path->vlan.id;
-+ info->vproto[info->num_vlans] = path->vlan.proto;
-+ info->num_vlans++;
-+ break;
-+ case DEV_PATH_BR_VLAN_UNTAG:
-+ info->num_vlans--;
-+ break;
-+ case DEV_PATH_BR_VLAN_KEEP:
-+ break;
-+ }
- info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
- break;
- default:
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Thu, 4 Mar 2021 19:22:55 +0100
+Subject: [PATCH] netfilter: nft_flow_offload: add dsa support
+
+Replace the master ethernet device by the dsa slave port.
+
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+---
+
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -89,6 +89,7 @@ static void nft_dev_path_info(const stru
+ path = &stack->path[i];
+ switch (path->type) {
+ case DEV_PATH_ETHERNET:
++ case DEV_PATH_DSA:
+ case DEV_PATH_VLAN:
+ case DEV_PATH_PPPOE:
+ info->indev = path->dev;
+@@ -97,6 +98,10 @@ static void nft_dev_path_info(const stru
+
+ if (path->type == DEV_PATH_ETHERNET)
+ break;
++ if (path->type == DEV_PATH_DSA) {
++ i = stack->num_paths;
++ break;
++ }
+
+ /* DEV_PATH_VLAN and DEV_PATH_PPPOE */
+ if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) {
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Mon, 7 Dec 2020 20:31:44 +0100
+Subject: [PATCH] netfilter: flowtable: add offload support for xmit path
+ types
+
+When the flow tuple xmit_type is set to FLOW_OFFLOAD_XMIT_DIRECT, the
+dst_cache pointer is not valid, and the h_source/h_dest/ifidx out fields
+need to be used.
+
+This patch also adds the FLOW_ACTION_VLAN_PUSH action to pass the VLAN
+tag to the driver.
+---
+
+--- a/net/netfilter/nf_flow_table_offload.c
++++ b/net/netfilter/nf_flow_table_offload.c
+@@ -175,28 +175,45 @@ static int flow_offload_eth_src(struct n
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
+ {
+- const struct flow_offload_tuple *tuple = &flow->tuplehash[!dir].tuple;
+ struct flow_action_entry *entry0 = flow_action_entry_next(flow_rule);
+ struct flow_action_entry *entry1 = flow_action_entry_next(flow_rule);
+- struct net_device *dev;
++ const struct flow_offload_tuple *other_tuple, *this_tuple;
++ struct net_device *dev = NULL;
++ const unsigned char *addr;
+ u32 mask, val;
+ u16 val16;
+
+- dev = dev_get_by_index(net, tuple->iifidx);
+- if (!dev)
+- return -ENOENT;
++ this_tuple = &flow->tuplehash[dir].tuple;
++
++ switch (this_tuple->xmit_type) {
++ case FLOW_OFFLOAD_XMIT_DIRECT:
++ addr = this_tuple->out.h_source;
++ break;
++ case FLOW_OFFLOAD_XMIT_NEIGH:
++ other_tuple = &flow->tuplehash[!dir].tuple;
++ dev = dev_get_by_index(net, other_tuple->iifidx);
++ if (!dev)
++ return -ENOENT;
++
++ addr = dev->dev_addr;
++ break;
++ default:
++ return -EOPNOTSUPP;
++ }
+
+ mask = ~0xffff0000;
+- memcpy(&val16, dev->dev_addr, 2);
++ memcpy(&val16, addr, 2);
+ val = val16 << 16;
+ flow_offload_mangle(entry0, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 4,
+ &val, &mask);
+
+ mask = ~0xffffffff;
+- memcpy(&val, dev->dev_addr + 2, 4);
++ memcpy(&val, addr + 2, 4);
+ flow_offload_mangle(entry1, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 8,
+ &val, &mask);
+- dev_put(dev);
++
++ if (dev)
++ dev_put(dev);
+
+ return 0;
+ }
+@@ -208,27 +225,40 @@ static int flow_offload_eth_dst(struct n
+ {
+ struct flow_action_entry *entry0 = flow_action_entry_next(flow_rule);
+ struct flow_action_entry *entry1 = flow_action_entry_next(flow_rule);
+- const void *daddr = &flow->tuplehash[!dir].tuple.src_v4;
++ const struct flow_offload_tuple *other_tuple, *this_tuple;
+ const struct dst_entry *dst_cache;
+ unsigned char ha[ETH_ALEN];
+ struct neighbour *n;
++ const void *daddr;
+ u32 mask, val;
+ u8 nud_state;
+ u16 val16;
+
+- dst_cache = flow->tuplehash[dir].tuple.dst_cache;
+- n = dst_neigh_lookup(dst_cache, daddr);
+- if (!n)
+- return -ENOENT;
+-
+- read_lock_bh(&n->lock);
+- nud_state = n->nud_state;
+- ether_addr_copy(ha, n->ha);
+- read_unlock_bh(&n->lock);
++ this_tuple = &flow->tuplehash[dir].tuple;
+
+- if (!(nud_state & NUD_VALID)) {
++ switch (this_tuple->xmit_type) {
++ case FLOW_OFFLOAD_XMIT_DIRECT:
++ ether_addr_copy(ha, this_tuple->out.h_dest);
++ break;
++ case FLOW_OFFLOAD_XMIT_NEIGH:
++ other_tuple = &flow->tuplehash[!dir].tuple;
++ daddr = &other_tuple->src_v4;
++ dst_cache = this_tuple->dst_cache;
++ n = dst_neigh_lookup(dst_cache, daddr);
++ if (!n)
++ return -ENOENT;
++
++ read_lock_bh(&n->lock);
++ nud_state = n->nud_state;
++ ether_addr_copy(ha, n->ha);
++ read_unlock_bh(&n->lock);
+ neigh_release(n);
+- return -ENOENT;
++
++ if (!(nud_state & NUD_VALID))
++ return -ENOENT;
++ break;
++ default:
++ return -EOPNOTSUPP;
+ }
+
+ mask = ~0xffffffff;
+@@ -241,7 +271,6 @@ static int flow_offload_eth_dst(struct n
+ val = val16;
+ flow_offload_mangle(entry1, FLOW_ACT_MANGLE_HDR_TYPE_ETH, 4,
+ &val, &mask);
+- neigh_release(n);
+
+ return 0;
+ }
+@@ -463,27 +492,52 @@ static void flow_offload_ipv4_checksum(s
+ }
+ }
+
+-static void flow_offload_redirect(const struct flow_offload *flow,
++static void flow_offload_redirect(struct net *net,
++ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
+ {
+- struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
+- struct rtable *rt;
++ const struct flow_offload_tuple *this_tuple, *other_tuple;
++ struct flow_action_entry *entry;
++ struct net_device *dev;
++ int ifindex;
++
++ this_tuple = &flow->tuplehash[dir].tuple;
++ switch (this_tuple->xmit_type) {
++ case FLOW_OFFLOAD_XMIT_DIRECT:
++ this_tuple = &flow->tuplehash[dir].tuple;
++ ifindex = this_tuple->out.ifidx;
++ break;
++ case FLOW_OFFLOAD_XMIT_NEIGH:
++ other_tuple = &flow->tuplehash[!dir].tuple;
++ ifindex = other_tuple->iifidx;
++ break;
++ default:
++ return;
++ }
+
+- rt = (struct rtable *)flow->tuplehash[dir].tuple.dst_cache;
++ dev = dev_get_by_index(net, ifindex);
++ if (!dev)
++ return;
++
++ entry = flow_action_entry_next(flow_rule);
+ entry->id = FLOW_ACTION_REDIRECT;
+- entry->dev = rt->dst.dev;
+- dev_hold(rt->dst.dev);
++ entry->dev = dev;
+ }
+
+ static void flow_offload_encap_tunnel(const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
+ {
++ const struct flow_offload_tuple *this_tuple;
+ struct flow_action_entry *entry;
+ struct dst_entry *dst;
+
+- dst = flow->tuplehash[dir].tuple.dst_cache;
++ this_tuple = &flow->tuplehash[dir].tuple;
++ if (this_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
++ return;
++
++ dst = this_tuple->dst_cache;
+ if (dst && dst->lwtstate) {
+ struct ip_tunnel_info *tun_info;
+
+@@ -500,10 +554,15 @@ static void flow_offload_decap_tunnel(co
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
+ {
++ const struct flow_offload_tuple *other_tuple;
+ struct flow_action_entry *entry;
+ struct dst_entry *dst;
+
+- dst = flow->tuplehash[!dir].tuple.dst_cache;
++ other_tuple = &flow->tuplehash[!dir].tuple;
++ if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
++ return;
++
++ dst = other_tuple->dst_cache;
+ if (dst && dst->lwtstate) {
+ struct ip_tunnel_info *tun_info;
+
+@@ -515,10 +574,14 @@ static void flow_offload_decap_tunnel(co
+ }
+ }
+
+-int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
+- enum flow_offload_tuple_dir dir,
+- struct nf_flow_rule *flow_rule)
++static int
++nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
++ enum flow_offload_tuple_dir dir,
++ struct nf_flow_rule *flow_rule)
+ {
++ const struct flow_offload_tuple *other_tuple;
++ int i;
++
+ flow_offload_decap_tunnel(flow, dir, flow_rule);
+ flow_offload_encap_tunnel(flow, dir, flow_rule);
+
+@@ -526,6 +589,26 @@ int nf_flow_rule_route_ipv4(struct net *
+ flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
+ return -1;
+
++ other_tuple = &flow->tuplehash[!dir].tuple;
++
++ for (i = 0; i < other_tuple->encap_num; i++) {
++ struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
++
++ entry->id = FLOW_ACTION_VLAN_PUSH;
++ entry->vlan.vid = other_tuple->encap[i].id;
++ entry->vlan.proto = other_tuple->encap[i].proto;
++ }
++
++ return 0;
++}
++
++int nf_flow_rule_route_ipv4(struct net *net, const struct flow_offload *flow,
++ enum flow_offload_tuple_dir dir,
++ struct nf_flow_rule *flow_rule)
++{
++ if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
++ return -1;
++
+ if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
+ flow_offload_ipv4_snat(net, flow, dir, flow_rule);
+ flow_offload_port_snat(net, flow, dir, flow_rule);
+@@ -538,7 +621,7 @@ int nf_flow_rule_route_ipv4(struct net *
+ test_bit(NF_FLOW_DNAT, &flow->flags))
+ flow_offload_ipv4_checksum(net, flow, flow_rule);
+
+- flow_offload_redirect(flow, dir, flow_rule);
++ flow_offload_redirect(net, flow, dir, flow_rule);
+
+ return 0;
+ }
+@@ -548,11 +631,7 @@ int nf_flow_rule_route_ipv6(struct net *
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
+ {
+- flow_offload_decap_tunnel(flow, dir, flow_rule);
+- flow_offload_encap_tunnel(flow, dir, flow_rule);
+-
+- if (flow_offload_eth_src(net, flow, dir, flow_rule) < 0 ||
+- flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
++ if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
+ return -1;
+
+ if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
+@@ -564,7 +643,7 @@ int nf_flow_rule_route_ipv6(struct net *
+ flow_offload_port_dnat(net, flow, dir, flow_rule);
+ }
+
+- flow_offload_redirect(flow, dir, flow_rule);
++ flow_offload_redirect(net, flow, dir, flow_rule);
+
+ return 0;
+ }
+@@ -578,10 +657,10 @@ nf_flow_offload_rule_alloc(struct net *n
+ enum flow_offload_tuple_dir dir)
+ {
+ const struct nf_flowtable *flowtable = offload->flowtable;
++ const struct flow_offload_tuple *tuple, *other_tuple;
+ const struct flow_offload *flow = offload->flow;
+- const struct flow_offload_tuple *tuple;
++ struct dst_entry *other_dst = NULL;
+ struct nf_flow_rule *flow_rule;
+- struct dst_entry *other_dst;
+ int err = -ENOMEM;
+
+ flow_rule = kzalloc(sizeof(*flow_rule), GFP_KERNEL);
+@@ -597,7 +676,10 @@ nf_flow_offload_rule_alloc(struct net *n
+ flow_rule->rule->match.key = &flow_rule->match.key;
+
+ tuple = &flow->tuplehash[dir].tuple;
+- other_dst = flow->tuplehash[!dir].tuple.dst_cache;
++ other_tuple = &flow->tuplehash[!dir].tuple;
++ if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_NEIGH)
++ other_dst = other_tuple->dst_cache;
++
+ err = nf_flow_rule_match(&flow_rule->match, tuple, other_dst);
+ if (err < 0)
+ goto err_flow_match;
+++ /dev/null
-From: Pablo Neira Ayuso <pablo@netfilter.org>
-Date: Tue, 2 Feb 2021 17:10:07 +0100
-Subject: [PATCH] netfilter: nft_flow_offload: use direct xmit if
- hardware offload is enabled
-
-If there is a forward path to reach an ethernet device and hardware
-offload is enabled, then use the direct xmit path.
----
-
---- a/net/netfilter/nft_flow_offload.c
-+++ b/net/netfilter/nft_flow_offload.c
-@@ -73,9 +73,18 @@ struct nft_forward_info {
- enum flow_offload_xmit_type xmit_type;
- };
-
-+static bool nft_is_valid_ether_device(const struct net_device *dev)
-+{
-+ if (!dev || (dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER ||
-+ dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr))
-+ return false;
-+
-+ return true;
-+}
-+
- static void nft_dev_path_info(const struct net_device_path_stack *stack,
- struct nft_forward_info *info,
-- unsigned char *ha)
-+ unsigned char *ha, struct nf_flowtable *flowtable)
- {
- const struct net_device_path *path;
- int i;
-@@ -131,6 +140,10 @@ static void nft_dev_path_info(const stru
- break;
- }
- }
-+
-+ if (nf_flowtable_hw_offload(flowtable) &&
-+ nft_is_valid_ether_device(info->dev))
-+ info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
- }
-
- static bool nft_flowtable_find_dev(const struct net_device *dev,
-@@ -162,7 +175,7 @@ static void nft_dev_forward_path(struct
- int i;
-
- if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0)
-- nft_dev_path_info(&stack, &info, ha);
-+ nft_dev_path_info(&stack, &info, ha, &ft->data);
-
- if (!info.dev || !nft_flowtable_find_dev(info.dev, ft))
- return;
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Mon, 18 Jan 2021 22:39:17 +0100
+Subject: [PATCH] dsa: slave: add support for TC_SETUP_FT
+
+The dsa infrastructure provides a well-defined hierarchy of devices,
+pass up the call to set up the flow block to the master device. From the
+software dataplane, the netfilter infrastructure uses the dsa slave
+devices to refer to the input and output device for the given skbuff.
+Similarly, the flowtable definition in the ruleset refers to the dsa
+slave port devices.
+
+This patch adds the glue code to call ndo_setup_tc with TC_SETUP_FT
+with the master device via the dsa slave devices.
+
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+---
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1202,14 +1202,32 @@ static int dsa_slave_setup_tc_block(stru
+ }
+ }
+
++static int dsa_slave_setup_ft_block(struct dsa_switch *ds, int port,
++ void *type_data)
++{
++ struct dsa_port *cpu_dp = dsa_to_port(ds, port)->cpu_dp;
++ struct net_device *master = cpu_dp->master;
++
++ if (!master->netdev_ops->ndo_setup_tc)
++ return -EOPNOTSUPP;
++
++ return master->netdev_ops->ndo_setup_tc(master, TC_SETUP_FT, type_data);
++}
++
+ static int dsa_slave_setup_tc(struct net_device *dev, enum tc_setup_type type,
+ void *type_data)
+ {
+ struct dsa_port *dp = dsa_slave_to_port(dev);
+ struct dsa_switch *ds = dp->ds;
+
+- if (type == TC_SETUP_BLOCK)
++ switch (type) {
++ case TC_SETUP_BLOCK:
+ return dsa_slave_setup_tc_block(dev, type_data);
++ case TC_SETUP_FT:
++ return dsa_slave_setup_ft_block(ds, dp->index, type_data);
++ default:
++ break;
++ }
+
+ if (!ds->ops->port_setup_tc)
+ return -EOPNOTSUPP;
+++ /dev/null
-From: Felix Fietkau <nbd@nbd.name>
-Date: Wed, 10 Feb 2021 10:23:38 +0100
-Subject: [PATCH] netfilter: nft_flow_offload: fix bridge vlan tag handling
-
-the brigde type uses the path->bridge.vlan_* fields
-
-Signed-off-by: Felix Fietkau <nbd@nbd.name>
----
-
---- a/net/netfilter/nft_flow_offload.c
-+++ b/net/netfilter/nft_flow_offload.c
-@@ -123,8 +123,8 @@ static void nft_dev_path_info(const stru
-
- switch (path->bridge.vlan_mode) {
- case DEV_PATH_BR_VLAN_TAG:
-- info->vid[info->num_vlans] = path->vlan.id;
-- info->vproto[info->num_vlans] = path->vlan.proto;
-+ info->vid[info->num_vlans] = path->bridge.vlan_id;
-+ info->vproto[info->num_vlans] = path->bridge.vlan_proto;
- info->num_vlans++;
- break;
- case DEV_PATH_BR_VLAN_UNTAG:
+++ /dev/null
-From: Felix Fietkau <nbd@nbd.name>
-Date: Wed, 10 Feb 2021 19:39:23 +0100
-Subject: [PATCH] netfilter: flowtable: rework ingress vlan matching
-
-When dealing with bridges with VLAN filtering and DSA/switchdev offload,
-the hardware could offload adding a VLAN tag configured in the bridge.
-Since there doesn't seem to be an easy way to detect that, this patch
-reworks the code to optionally match the last VLAN tag that would otherwise
-be inserted by the bridge.
-This matters when bypassing the bridge and attaching an ingress hook on
-a DSA port below it.
-
-Signed-off-by: Felix Fietkau <nbd@nbd.name>
----
-
---- a/include/net/netfilter/nf_flow_table.h
-+++ b/include/net/netfilter/nf_flow_table.h
-@@ -115,14 +115,15 @@ struct flow_offload_tuple {
-
- u8 l3proto;
- u8 l4proto;
-- struct {
-- u16 id;
-- __be16 proto;
-- } in_vlan[NF_FLOW_TABLE_VLAN_MAX];
-
- /* All members above are keys for lookups, see flow_offload_hash(). */
- struct { } __hash;
-
-+ struct {
-+ u16 id;
-+ __be16 proto;
-+ } in_vlan[NF_FLOW_TABLE_VLAN_MAX], in_pvid;
-+
- u8 dir:4,
- xmit_type:2,
- in_vlan_num:2;
---- a/net/netfilter/nf_flow_table_ip.c
-+++ b/net/netfilter/nf_flow_table_ip.c
-@@ -281,12 +281,13 @@ static bool nf_flow_skb_vlan_protocol(co
- }
-
- static void nf_flow_vlan_pop(struct sk_buff *skb,
-- struct flow_offload_tuple_rhash *tuplehash)
-+ struct flow_offload_tuple_rhash *tuplehash,
-+ bool strip_pvid)
- {
- struct vlan_hdr *vlan_hdr;
- int i;
-
-- for (i = 0; i < tuplehash->tuple.in_vlan_num; i++) {
-+ for (i = 0; i < tuplehash->tuple.in_vlan_num + strip_pvid; i++) {
- if (skb_vlan_tag_present(skb)) {
- __vlan_hwaccel_clear_tag(skb);
- continue;
-@@ -316,6 +317,31 @@ static unsigned int nf_flow_queue_xmit(s
- return NF_STOLEN;
- }
-
-+static bool
-+nf_flow_offload_check_vlan(struct flow_offload_tuple *tuple,
-+ struct flow_offload_tuple *flow_tuple,
-+ bool *strip_pvid)
-+{
-+ int i, cur = 0;
-+
-+ if (flow_tuple->in_pvid.proto &&
-+ !memcmp(&tuple->in_vlan[0], &flow_tuple->in_pvid,
-+ sizeof(tuple->in_vlan[0])))
-+ cur++;
-+
-+ *strip_pvid = cur;
-+
-+ for (i = 0; i < flow_tuple->in_vlan_num; i++, cur++) {
-+ if (!memcmp(&tuple->in_vlan[cur], &flow_tuple->in_vlan[i],
-+ sizeof(tuple->in_vlan[0])))
-+ continue;
-+
-+ return false;
-+ }
-+
-+ return true;
-+}
-+
- unsigned int
- nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
- const struct nf_hook_state *state)
-@@ -329,6 +355,7 @@ nf_flow_offload_ip_hook(void *priv, stru
- struct rtable *rt;
- unsigned int thoff;
- struct iphdr *iph;
-+ bool strip_pvid;
- __be32 nexthop;
- u32 offset = 0;
- int ret;
-@@ -344,6 +371,10 @@ nf_flow_offload_ip_hook(void *priv, stru
- if (tuplehash == NULL)
- return NF_ACCEPT;
-
-+ if (!nf_flow_offload_check_vlan(&tuple, &tuplehash->tuple,
-+ &strip_pvid))
-+ return NF_ACCEPT;
-+
- dir = tuplehash->tuple.dir;
- flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
-
-@@ -368,7 +399,7 @@ nf_flow_offload_ip_hook(void *priv, stru
- return NF_ACCEPT;
- }
-
-- nf_flow_vlan_pop(skb, tuplehash);
-+ nf_flow_vlan_pop(skb, tuplehash, strip_pvid);
- thoff -= offset;
-
- if (nf_flow_nat_ip(flow, skb, thoff, dir) < 0)
-@@ -596,6 +627,7 @@ nf_flow_offload_ipv6_hook(void *priv, st
- struct net_device *outdev;
- struct ipv6hdr *ip6h;
- struct rt6_info *rt;
-+ bool strip_pvid;
- u32 offset = 0;
- int ret;
-
-@@ -610,6 +642,10 @@ nf_flow_offload_ipv6_hook(void *priv, st
- if (tuplehash == NULL)
- return NF_ACCEPT;
-
-+ if (!nf_flow_offload_check_vlan(&tuple, &tuplehash->tuple,
-+ &strip_pvid))
-+ return NF_ACCEPT;
-+
- dir = tuplehash->tuple.dir;
- flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
-
-@@ -630,7 +666,7 @@ nf_flow_offload_ipv6_hook(void *priv, st
- return NF_ACCEPT;
- }
-
-- nf_flow_vlan_pop(skb, tuplehash);
-+ nf_flow_vlan_pop(skb, tuplehash, strip_pvid);
-
- if (skb_try_make_writable(skb, sizeof(*ip6h)))
- return NF_DROP;
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Thu, 4 Mar 2021 19:24:11 +0100
+Subject: [PATCH] netfilter: nft_flow_offload: use direct xmit if
+ hardware offload is enabled
+
+If there is a forward path to reach an ethernet device and hardware
+offload is enabled, then use the direct xmit path.
+---
+
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -131,6 +131,7 @@ struct flow_offload_tuple {
+ struct dst_entry *dst_cache;
+ struct {
+ u32 ifidx;
++ u32 hw_ifidx;
+ u8 h_source[ETH_ALEN];
+ u8 h_dest[ETH_ALEN];
+ } out;
+@@ -188,6 +189,7 @@ struct nf_flow_route {
+ } in;
+ struct {
+ u32 ifindex;
++ u32 hw_ifindex;
+ u8 h_source[ETH_ALEN];
+ u8 h_dest[ETH_ALEN];
+ } out;
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -106,6 +106,7 @@ static int flow_offload_fill_route(struc
+ memcpy(flow_tuple->out.h_source, route->tuple[dir].out.h_source,
+ ETH_ALEN);
+ flow_tuple->out.ifidx = route->tuple[dir].out.ifindex;
++ flow_tuple->out.hw_ifidx = route->tuple[dir].out.hw_ifindex;
+ break;
+ case FLOW_OFFLOAD_XMIT_XFRM:
+ case FLOW_OFFLOAD_XMIT_NEIGH:
+--- a/net/netfilter/nf_flow_table_offload.c
++++ b/net/netfilter/nf_flow_table_offload.c
+@@ -506,7 +506,7 @@ static void flow_offload_redirect(struct
+ switch (this_tuple->xmit_type) {
+ case FLOW_OFFLOAD_XMIT_DIRECT:
+ this_tuple = &flow->tuplehash[dir].tuple;
+- ifindex = this_tuple->out.ifidx;
++ ifindex = this_tuple->out.hw_ifidx;
+ break;
+ case FLOW_OFFLOAD_XMIT_NEIGH:
+ other_tuple = &flow->tuplehash[!dir].tuple;
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -66,6 +66,7 @@ static int nft_dev_fill_forward_path(con
+ struct nft_forward_info {
+ const struct net_device *indev;
+ const struct net_device *outdev;
++ const struct net_device *hw_outdev;
+ struct id {
+ __u16 id;
+ __be16 proto;
+@@ -76,9 +77,18 @@ struct nft_forward_info {
+ enum flow_offload_xmit_type xmit_type;
+ };
+
++static bool nft_is_valid_ether_device(const struct net_device *dev)
++{
++ if (!dev || (dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER ||
++ dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr))
++ return false;
++
++ return true;
++}
++
+ static void nft_dev_path_info(const struct net_device_path_stack *stack,
+ struct nft_forward_info *info,
+- unsigned char *ha)
++ unsigned char *ha, struct nf_flowtable *flowtable)
+ {
+ const struct net_device_path *path;
+ int i;
+@@ -140,6 +150,12 @@ static void nft_dev_path_info(const stru
+ }
+ if (!info->outdev)
+ info->outdev = info->indev;
++
++ info->hw_outdev = info->indev;
++
++ if (nf_flowtable_hw_offload(flowtable) &&
++ nft_is_valid_ether_device(info->indev))
++ info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
+ }
+
+ static bool nft_flowtable_find_dev(const struct net_device *dev,
+@@ -171,7 +187,7 @@ static void nft_dev_forward_path(struct
+ int i;
+
+ if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0)
+- nft_dev_path_info(&stack, &info, ha);
++ nft_dev_path_info(&stack, &info, ha, &ft->data);
+
+ if (!info.indev || !nft_flowtable_find_dev(info.indev, ft))
+ return;
+@@ -187,6 +203,7 @@ static void nft_dev_forward_path(struct
+ memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN);
+ memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN);
+ route->tuple[dir].out.ifindex = info.outdev->ifindex;
++ route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex;
+ route->tuple[dir].xmit_type = info.xmit_type;
+ }
+ }
+++ /dev/null
-From: Felix Fietkau <nbd@nbd.name>
-Date: Wed, 10 Feb 2021 19:44:33 +0100
-Subject: [PATCH] netfilter: flowtable: handle bridge vlan filter offload
- tags from DSA/switchdev
-
-When a switchdev/DSA port is an untagged member of a bridge vlan, ingress
-packets could potentially be tagged with the id of the VLAN.
-When the VLAN port group has been uploaded to switchdev, report the bridge
-tag mode as DEV_PATH_BR_VLAN_UNTAG_HW instead of DEV_PATH_BR_VLAN_UNTAG
-and handle it in netfilter flow offloading by storing the tag in the tuple
-in_pvid field.
-This allows the ingress hook to detect the optional tag and remove it for
-software offload. This tag processing is for ingress only, egress needs to be
-fully untagged
-
-Signed-off-by: Felix Fietkau <nbd@nbd.name>
----
-
---- a/include/linux/netdevice.h
-+++ b/include/linux/netdevice.h
-@@ -853,6 +853,7 @@ struct net_device_path {
- DEV_PATH_BR_VLAN_KEEP,
- DEV_PATH_BR_VLAN_TAG,
- DEV_PATH_BR_VLAN_UNTAG,
-+ DEV_PATH_BR_VLAN_UNTAG_HW,
- } vlan_mode;
- u16 vlan_id;
- __be16 vlan_proto;
---- a/include/net/netfilter/nf_flow_table.h
-+++ b/include/net/netfilter/nf_flow_table.h
-@@ -183,6 +183,10 @@ struct nf_flow_route {
- u32 ifindex;
- u16 vid[NF_FLOW_TABLE_VLAN_MAX];
- __be16 vproto[NF_FLOW_TABLE_VLAN_MAX];
-+ struct {
-+ u16 id;
-+ __be16 proto;
-+ } pvid;
- u8 num_vlans;
- } in;
- struct {
---- a/net/bridge/br_device.c
-+++ b/net/bridge/br_device.c
-@@ -435,6 +435,7 @@ static int br_fill_forward_path(struct n
- ctx->vlan[ctx->num_vlans].proto = path->bridge.vlan_proto;
- ctx->num_vlans++;
- break;
-+ case DEV_PATH_BR_VLAN_UNTAG_HW:
- case DEV_PATH_BR_VLAN_UNTAG:
- ctx->num_vlans--;
- break;
---- a/net/bridge/br_vlan.c
-+++ b/net/bridge/br_vlan.c
-@@ -1374,6 +1374,8 @@ int br_vlan_fill_forward_path_mode(struc
-
- if (path->bridge.vlan_mode == DEV_PATH_BR_VLAN_TAG)
- path->bridge.vlan_mode = DEV_PATH_BR_VLAN_KEEP;
-+ else if (v->priv_flags & BR_VLFLAG_ADDED_BY_SWITCHDEV)
-+ path->bridge.vlan_mode = DEV_PATH_BR_VLAN_UNTAG_HW;
- else
- path->bridge.vlan_mode = DEV_PATH_BR_VLAN_UNTAG;
-
---- a/net/netfilter/nf_flow_table_core.c
-+++ b/net/netfilter/nf_flow_table_core.c
-@@ -98,6 +98,8 @@ static int flow_offload_fill_route(struc
- j++;
- }
- flow_tuple->in_vlan_num = route->tuple[dir].in.num_vlans;
-+ flow_tuple->in_pvid.id = route->tuple[dir].in.pvid.id;
-+ flow_tuple->in_pvid.proto = route->tuple[dir].in.pvid.proto;
-
- switch (route->tuple[dir].xmit_type) {
- case FLOW_OFFLOAD_XMIT_DIRECT:
---- a/net/netfilter/nft_flow_offload.c
-+++ b/net/netfilter/nft_flow_offload.c
-@@ -67,6 +67,10 @@ struct nft_forward_info {
- const struct net_device *dev;
- __u16 vid[NF_FLOW_TABLE_VLAN_MAX];
- __be16 vproto[NF_FLOW_TABLE_VLAN_MAX];
-+ struct {
-+ __u16 id;
-+ __be16 proto;
-+ } pvid;
- u8 num_vlans;
- u8 h_source[ETH_ALEN];
- u8 h_dest[ETH_ALEN];
-@@ -127,6 +131,10 @@ static void nft_dev_path_info(const stru
- info->vproto[info->num_vlans] = path->bridge.vlan_proto;
- info->num_vlans++;
- break;
-+ case DEV_PATH_BR_VLAN_UNTAG_HW:
-+ info->pvid.id = info->vid[info->num_vlans - 1];
-+ info->pvid.proto = info->vproto[info->num_vlans - 1];
-+ fallthrough;
- case DEV_PATH_BR_VLAN_UNTAG:
- info->num_vlans--;
- break;
-@@ -185,6 +193,8 @@ static void nft_dev_forward_path(struct
- route->tuple[!dir].in.vid[i] = info.vid[i];
- route->tuple[!dir].in.vproto[i] = info.vproto[i];
- }
-+ route->tuple[!dir].in.pvid.id = info.pvid.id;
-+ route->tuple[!dir].in.pvid.proto = info.pvid.proto;
- route->tuple[!dir].in.num_vlans = info.num_vlans;
-
- if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) {
--- /dev/null
+From: Felix Fietkau <nbd@nbd.name>
+Date: Mon, 8 Mar 2021 12:06:44 +0100
+Subject: [PATCH] netfilter: nf_flow_table: fix untagging with
+ hardware-offloaded bridge vlan_filtering
+
+When switchdev offloading is enabled, treat an untagged VLAN as tagged for
+ingress only
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -855,6 +855,7 @@ struct net_device_path {
+ DEV_PATH_BR_VLAN_KEEP,
+ DEV_PATH_BR_VLAN_TAG,
+ DEV_PATH_BR_VLAN_UNTAG,
++ DEV_PATH_BR_VLAN_UNTAG_HW,
+ } vlan_mode;
+ u16 vlan_id;
+ __be16 vlan_proto;
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -123,9 +123,10 @@ struct flow_offload_tuple {
+ /* All members above are keys for lookups, see flow_offload_hash(). */
+ struct { } __hash;
+
+- u8 dir:4,
++ u8 dir:2,
+ xmit_type:2,
+- encap_num:2;
++ encap_num:2,
++ in_vlan_ingress:2;
+ u16 mtu;
+ union {
+ struct dst_entry *dst_cache;
+@@ -185,7 +186,8 @@ struct nf_flow_route {
+ u16 id;
+ __be16 proto;
+ } encap[NF_FLOW_TABLE_ENCAP_MAX];
+- u8 num_encaps;
++ u8 num_encaps:2,
++ ingress_vlans:2;
+ } in;
+ struct {
+ u32 ifindex;
+--- a/net/bridge/br_device.c
++++ b/net/bridge/br_device.c
+@@ -435,6 +435,7 @@ static int br_fill_forward_path(struct n
+ ctx->vlan[ctx->num_vlans].proto = path->bridge.vlan_proto;
+ ctx->num_vlans++;
+ break;
++ case DEV_PATH_BR_VLAN_UNTAG_HW:
+ case DEV_PATH_BR_VLAN_UNTAG:
+ ctx->num_vlans--;
+ break;
+--- a/net/bridge/br_vlan.c
++++ b/net/bridge/br_vlan.c
+@@ -1374,6 +1374,8 @@ int br_vlan_fill_forward_path_mode(struc
+
+ if (path->bridge.vlan_mode == DEV_PATH_BR_VLAN_TAG)
+ path->bridge.vlan_mode = DEV_PATH_BR_VLAN_KEEP;
++ else if (v->priv_flags & BR_VLFLAG_ADDED_BY_SWITCHDEV)
++ path->bridge.vlan_mode = DEV_PATH_BR_VLAN_UNTAG_HW;
+ else
+ path->bridge.vlan_mode = DEV_PATH_BR_VLAN_UNTAG;
+
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -95,6 +95,8 @@ static int flow_offload_fill_route(struc
+ for (i = route->tuple[dir].in.num_encaps - 1; i >= 0; i--) {
+ flow_tuple->encap[j].id = route->tuple[dir].in.encap[i].id;
+ flow_tuple->encap[j].proto = route->tuple[dir].in.encap[i].proto;
++ if (route->tuple[dir].in.ingress_vlans & BIT(i))
++ flow_tuple->in_vlan_ingress |= BIT(j);
+ j++;
+ }
+ flow_tuple->encap_num = route->tuple[dir].in.num_encaps;
+--- a/net/netfilter/nf_flow_table_offload.c
++++ b/net/netfilter/nf_flow_table_offload.c
+@@ -592,8 +592,12 @@ nf_flow_rule_route_common(struct net *ne
+ other_tuple = &flow->tuplehash[!dir].tuple;
+
+ for (i = 0; i < other_tuple->encap_num; i++) {
+- struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
++ struct flow_action_entry *entry;
+
++ if (other_tuple->in_vlan_ingress & BIT(i))
++ continue;
++
++ entry = flow_action_entry_next(flow_rule);
+ entry->id = FLOW_ACTION_VLAN_PUSH;
+ entry->vlan.vid = other_tuple->encap[i].id;
+ entry->vlan.proto = other_tuple->encap[i].proto;
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -72,6 +72,7 @@ struct nft_forward_info {
+ __be16 proto;
+ } encap[NF_FLOW_TABLE_ENCAP_MAX];
+ u8 num_encaps;
++ u8 ingress_vlans;
+ u8 h_source[ETH_ALEN];
+ u8 h_dest[ETH_ALEN];
+ enum flow_offload_xmit_type xmit_type;
+@@ -130,6 +131,9 @@ static void nft_dev_path_info(const stru
+ memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
+
+ switch (path->bridge.vlan_mode) {
++ case DEV_PATH_BR_VLAN_UNTAG_HW:
++ info->ingress_vlans |= BIT(info->num_encaps - 1);
++ break;
+ case DEV_PATH_BR_VLAN_TAG:
+ info->encap[info->num_encaps].id = path->bridge.vlan_id;
+ info->encap[info->num_encaps].proto = path->bridge.vlan_proto;
+@@ -198,6 +202,7 @@ static void nft_dev_forward_path(struct
+ route->tuple[!dir].in.encap[i].proto = info.encap[i].proto;
+ }
+ route->tuple[!dir].in.num_encaps = info.num_encaps;
++ route->tuple[!dir].in.ingress_vlans = info.ingress_vlans;
+
+ if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) {
+ memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN);
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Tue, 2 Mar 2021 00:51:31 +0100
+Subject: [PATCH] net: flow_offload: add FLOW_ACTION_PPPOE_PUSH
+
+---
+
+--- a/include/net/flow_offload.h
++++ b/include/net/flow_offload.h
+@@ -147,6 +147,7 @@ enum flow_action_id {
+ FLOW_ACTION_MPLS_POP,
+ FLOW_ACTION_MPLS_MANGLE,
+ FLOW_ACTION_GATE,
++ FLOW_ACTION_PPPOE_PUSH,
+ NUM_FLOW_ACTIONS,
+ };
+
+@@ -271,6 +272,9 @@ struct flow_action_entry {
+ u32 num_entries;
+ struct action_gate_entry *entries;
+ } gate;
++ struct { /* FLOW_ACTION_PPPOE_PUSH */
++ u16 sid;
++ } pppoe;
+ };
+ struct flow_action_cookie *cookie; /* user defined action cookie */
+ };
--- /dev/null
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Tue, 2 Mar 2021 01:01:50 +0100
+Subject: [PATCH] netfilter: flowtable: support for
+ FLOW_ACTION_PPPOE_PUSH
+
+---
+
+--- a/net/netfilter/nf_flow_table_offload.c
++++ b/net/netfilter/nf_flow_table_offload.c
+@@ -598,9 +598,18 @@ nf_flow_rule_route_common(struct net *ne
+ continue;
+
+ entry = flow_action_entry_next(flow_rule);
+- entry->id = FLOW_ACTION_VLAN_PUSH;
+- entry->vlan.vid = other_tuple->encap[i].id;
+- entry->vlan.proto = other_tuple->encap[i].proto;
++
++ switch (other_tuple->encap[i].proto) {
++ case htons(ETH_P_PPP_SES):
++ entry->id = FLOW_ACTION_PPPOE_PUSH;
++ entry->pppoe.sid = other_tuple->encap[i].id;
++ break;
++ case htons(ETH_P_8021Q):
++ entry->id = FLOW_ACTION_VLAN_PUSH;
++ entry->vlan.vid = other_tuple->encap[i].id;
++ entry->vlan.proto = other_tuple->encap[i].proto;
++ break;
++ }
+ }
+
+ return 0;
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
-@@ -2031,6 +2031,8 @@ struct net_device {
+@@ -2033,6 +2033,8 @@ struct net_device {
struct netdev_hw_addr_list mc;
struct netdev_hw_addr_list dev_addrs;
};
enum gro_result {
-@@ -2411,6 +2414,26 @@ void netif_napi_add(struct net_device *d
+@@ -2413,6 +2416,26 @@ void netif_napi_add(struct net_device *d
int (*poll)(struct napi_struct *, int), int weight);
/**
#endif /* MTK_ETH_H */
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
-@@ -0,0 +1,478 @@
+@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020 Felix Fietkau <nbd@nbd.name>
+ __be16 proto;
+ u8 num;
+ } vlan;
++ struct {
++ u16 sid;
++ u8 num;
++ } pppoe;
+};
+
+struct mtk_flow_entry {
+ break;
+ case FLOW_ACTION_VLAN_PUSH:
+ if (data.vlan.num == 1 ||
-+ data.vlan.proto != ETH_P_8021Q)
++ act->vlan.proto != htons(ETH_P_8021Q))
+ return -EOPNOTSUPP;
+
+ data.vlan.id = act->vlan.vid;
+ data.vlan.proto = act->vlan.proto;
+ data.vlan.num++;
+ break;
++ case FLOW_ACTION_PPPOE_PUSH:
++ if (data.pppoe.num == 1)
++ return -EOPNOTSUPP;
++
++ data.pppoe.sid = act->pppoe.sid;
++ data.pppoe.num++;
++ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ if (data.vlan.num == 1) {
-+ if (data.vlan.proto != ETH_P_8021Q)
++ if (data.vlan.proto != htons(ETH_P_8021Q))
+ return -EOPNOTSUPP;
+
+ mtk_foe_entry_set_vlan(&foe, data.vlan.id);
+ }
++ if (data.pppoe.num == 1)
++ mtk_foe_entry_set_pppoe(&foe, data.pppoe.sid);
+
+ err = mtk_flow_set_output_device(eth, &foe, odev);
+ if (err)