kernel: add hardware offload patch for flow tables support
Supports offloading through VLAN, bridge and PPPoE devices as well Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
		@@ -149,8 +149,9 @@ define KernelPackage/nf-flow
 | 
			
		||||
	CONFIG_NF_FLOW_TABLE_HW
 | 
			
		||||
  DEPENDS:=+kmod-nf-conntrack @!LINUX_3_18 @!LINUX_4_4 @!LINUX_4_9
 | 
			
		||||
  FILES:= \
 | 
			
		||||
	$(LINUX_DIR)/net/netfilter/nf_flow_table.ko
 | 
			
		||||
  AUTOLOAD:=$(call AutoProbe,nf_flow_table)
 | 
			
		||||
	$(LINUX_DIR)/net/netfilter/nf_flow_table.ko \
 | 
			
		||||
	$(LINUX_DIR)/net/netfilter/nf_flow_table_hw.ko
 | 
			
		||||
  AUTOLOAD:=$(call AutoProbe,nf_flow_table nf_flow_table_hw)
 | 
			
		||||
endef
 | 
			
		||||
 | 
			
		||||
$(eval $(call KernelPackage,nf-flow))
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,580 @@
 | 
			
		||||
From: Pablo Neira Ayuso <pablo@netfilter.org>
 | 
			
		||||
Date: Thu, 11 Jan 2018 16:32:00 +0100
 | 
			
		||||
Subject: [PATCH] netfilter: nf_flow_table: add hardware offload support
 | 
			
		||||
 | 
			
		||||
This patch adds the infrastructure to offload flows to hardware, in case
 | 
			
		||||
the nic/switch comes with built-in flow tables capabilities.
 | 
			
		||||
 | 
			
		||||
If the hardware comes with no hardware flow tables or they have
 | 
			
		||||
limitations in terms of features, the existing infrastructure falls back
 | 
			
		||||
to the software flow table implementation.
 | 
			
		||||
 | 
			
		||||
The software flow table garbage collector skips entries that resides in
 | 
			
		||||
the hardware, so the hardware will be responsible for releasing this
 | 
			
		||||
flow table entry too via flow_offload_dead().
 | 
			
		||||
 | 
			
		||||
Hardware configuration, either to add or to delete entries, is done from
 | 
			
		||||
the hardware offload workqueue, to ensure this is done from user context
 | 
			
		||||
given that we may sleep when grabbing the mdio mutex.
 | 
			
		||||
 | 
			
		||||
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
 | 
			
		||||
---
 | 
			
		||||
 create mode 100644 net/netfilter/nf_flow_table_hw.c
 | 
			
		||||
 | 
			
		||||
--- a/include/linux/netdevice.h
 | 
			
		||||
+++ b/include/linux/netdevice.h
 | 
			
		||||
@@ -826,6 +826,13 @@ struct xfrmdev_ops {
 | 
			
		||||
 };
 | 
			
		||||
 #endif
 | 
			
		||||
 
 | 
			
		||||
+struct flow_offload;
 | 
			
		||||
+
 | 
			
		||||
+enum flow_offload_type {
 | 
			
		||||
+	FLOW_OFFLOAD_ADD	= 0,
 | 
			
		||||
+	FLOW_OFFLOAD_DEL,
 | 
			
		||||
+};
 | 
			
		||||
+
 | 
			
		||||
 /*
 | 
			
		||||
  * This structure defines the management hooks for network devices.
 | 
			
		||||
  * The following hooks can be defined; unless noted otherwise, they are
 | 
			
		||||
@@ -1057,6 +1064,10 @@ struct xfrmdev_ops {
 | 
			
		||||
  * int (*ndo_bridge_dellink)(struct net_device *dev, struct nlmsghdr *nlh,
 | 
			
		||||
  *			     u16 flags);
 | 
			
		||||
  *
 | 
			
		||||
+ * int (*ndo_flow_offload)(enum flow_offload_type type,
 | 
			
		||||
+ *			   struct flow_offload *flow);
 | 
			
		||||
+ *	Adds/deletes flow entry to/from net device flowtable.
 | 
			
		||||
+ *
 | 
			
		||||
  * int (*ndo_change_carrier)(struct net_device *dev, bool new_carrier);
 | 
			
		||||
  *	Called to change device carrier. Soft-devices (like dummy, team, etc)
 | 
			
		||||
  *	which do not represent real hardware may define this to allow their
 | 
			
		||||
@@ -1281,6 +1292,8 @@ struct net_device_ops {
 | 
			
		||||
 	int			(*ndo_bridge_dellink)(struct net_device *dev,
 | 
			
		||||
 						      struct nlmsghdr *nlh,
 | 
			
		||||
 						      u16 flags);
 | 
			
		||||
+	int			(*ndo_flow_offload)(enum flow_offload_type type,
 | 
			
		||||
+						    struct flow_offload *flow);
 | 
			
		||||
 	int			(*ndo_change_carrier)(struct net_device *dev,
 | 
			
		||||
 						      bool new_carrier);
 | 
			
		||||
 	int			(*ndo_get_phys_port_id)(struct net_device *dev,
 | 
			
		||||
--- a/include/net/netfilter/nf_flow_table.h
 | 
			
		||||
+++ b/include/net/netfilter/nf_flow_table.h
 | 
			
		||||
@@ -20,11 +20,17 @@ struct nf_flowtable_type {
 | 
			
		||||
 	struct module			*owner;
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
+enum nf_flowtable_flags {
 | 
			
		||||
+	NF_FLOWTABLE_F_HW		= 0x1,
 | 
			
		||||
+};
 | 
			
		||||
+
 | 
			
		||||
 struct nf_flowtable {
 | 
			
		||||
 	struct list_head		list;
 | 
			
		||||
 	struct rhashtable		rhashtable;
 | 
			
		||||
 	const struct nf_flowtable_type	*type;
 | 
			
		||||
+	u32				flags;
 | 
			
		||||
 	struct delayed_work		gc_work;
 | 
			
		||||
+	possible_net_t			ft_net;
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
 enum flow_offload_tuple_dir {
 | 
			
		||||
@@ -69,6 +75,7 @@ struct flow_offload_tuple_rhash {
 | 
			
		||||
 #define FLOW_OFFLOAD_DNAT	0x2
 | 
			
		||||
 #define FLOW_OFFLOAD_DYING	0x4
 | 
			
		||||
 #define FLOW_OFFLOAD_TEARDOWN	0x8
 | 
			
		||||
+#define FLOW_OFFLOAD_HW		0x10
 | 
			
		||||
 
 | 
			
		||||
 struct flow_offload {
 | 
			
		||||
 	struct flow_offload_tuple_rhash		tuplehash[FLOW_OFFLOAD_DIR_MAX];
 | 
			
		||||
@@ -126,6 +133,22 @@ unsigned int nf_flow_offload_ip_hook(voi
 | 
			
		||||
 unsigned int nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 | 
			
		||||
 				       const struct nf_hook_state *state);
 | 
			
		||||
 
 | 
			
		||||
+void nf_flow_offload_hw_add(struct net *net, struct flow_offload *flow,
 | 
			
		||||
+			    struct nf_conn *ct);
 | 
			
		||||
+void nf_flow_offload_hw_del(struct net *net, struct flow_offload *flow);
 | 
			
		||||
+
 | 
			
		||||
+struct nf_flow_table_hw {
 | 
			
		||||
+	struct module	*owner;
 | 
			
		||||
+	void		(*add)(struct net *net, struct flow_offload *flow,
 | 
			
		||||
+			       struct nf_conn *ct);
 | 
			
		||||
+	void		(*del)(struct net *net, struct flow_offload *flow);
 | 
			
		||||
+};
 | 
			
		||||
+
 | 
			
		||||
+int nf_flow_table_hw_register(const struct nf_flow_table_hw *offload);
 | 
			
		||||
+void nf_flow_table_hw_unregister(const struct nf_flow_table_hw *offload);
 | 
			
		||||
+
 | 
			
		||||
+extern struct work_struct nf_flow_offload_hw_work;
 | 
			
		||||
+
 | 
			
		||||
 #define MODULE_ALIAS_NF_FLOWTABLE(family)	\
 | 
			
		||||
 	MODULE_ALIAS("nf-flowtable-" __stringify(family))
 | 
			
		||||
 
 | 
			
		||||
--- a/include/uapi/linux/netfilter/nf_tables.h
 | 
			
		||||
+++ b/include/uapi/linux/netfilter/nf_tables.h
 | 
			
		||||
@@ -1341,6 +1341,7 @@ enum nft_object_attributes {
 | 
			
		||||
  * @NFTA_FLOWTABLE_HOOK: netfilter hook configuration(NLA_U32)
 | 
			
		||||
  * @NFTA_FLOWTABLE_USE: number of references to this flow table (NLA_U32)
 | 
			
		||||
  * @NFTA_FLOWTABLE_HANDLE: object handle (NLA_U64)
 | 
			
		||||
+ * @NFTA_FLOWTABLE_FLAGS: flags (NLA_U32)
 | 
			
		||||
  */
 | 
			
		||||
 enum nft_flowtable_attributes {
 | 
			
		||||
 	NFTA_FLOWTABLE_UNSPEC,
 | 
			
		||||
@@ -1350,6 +1351,7 @@ enum nft_flowtable_attributes {
 | 
			
		||||
 	NFTA_FLOWTABLE_USE,
 | 
			
		||||
 	NFTA_FLOWTABLE_HANDLE,
 | 
			
		||||
 	NFTA_FLOWTABLE_PAD,
 | 
			
		||||
+	NFTA_FLOWTABLE_FLAGS,
 | 
			
		||||
 	__NFTA_FLOWTABLE_MAX
 | 
			
		||||
 };
 | 
			
		||||
 #define NFTA_FLOWTABLE_MAX	(__NFTA_FLOWTABLE_MAX - 1)
 | 
			
		||||
--- a/net/netfilter/Kconfig
 | 
			
		||||
+++ b/net/netfilter/Kconfig
 | 
			
		||||
@@ -686,6 +686,15 @@ config NF_FLOW_TABLE
 | 
			
		||||
 
 | 
			
		||||
 	  To compile it as a module, choose M here.
 | 
			
		||||
 
 | 
			
		||||
+config NF_FLOW_TABLE_HW
 | 
			
		||||
+	tristate "Netfilter flow table hardware offload module"
 | 
			
		||||
+	depends on NF_FLOW_TABLE
 | 
			
		||||
+	help
 | 
			
		||||
+	  This option adds hardware offload support for the flow table core
 | 
			
		||||
+	  infrastructure.
 | 
			
		||||
+
 | 
			
		||||
+	  To compile it as a module, choose M here.
 | 
			
		||||
+
 | 
			
		||||
 config NETFILTER_XTABLES
 | 
			
		||||
 	tristate "Netfilter Xtables support (required for ip_tables)"
 | 
			
		||||
 	default m if NETFILTER_ADVANCED=n
 | 
			
		||||
--- a/net/netfilter/Makefile
 | 
			
		||||
+++ b/net/netfilter/Makefile
 | 
			
		||||
@@ -116,6 +116,7 @@ obj-$(CONFIG_NF_FLOW_TABLE)	+= nf_flow_t
 | 
			
		||||
 nf_flow_table-objs := nf_flow_table_core.o nf_flow_table_ip.o
 | 
			
		||||
 
 | 
			
		||||
 obj-$(CONFIG_NF_FLOW_TABLE_INET) += nf_flow_table_inet.o
 | 
			
		||||
+obj-$(CONFIG_NF_FLOW_TABLE_HW)	+= nf_flow_table_hw.o
 | 
			
		||||
 
 | 
			
		||||
 # generic X tables 
 | 
			
		||||
 obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o
 | 
			
		||||
--- a/net/netfilter/nf_flow_table_core.c
 | 
			
		||||
+++ b/net/netfilter/nf_flow_table_core.c
 | 
			
		||||
@@ -199,10 +199,16 @@ int flow_offload_add(struct nf_flowtable
 | 
			
		||||
 }
 | 
			
		||||
 EXPORT_SYMBOL_GPL(flow_offload_add);
 | 
			
		||||
 
 | 
			
		||||
+static inline bool nf_flow_in_hw(const struct flow_offload *flow)
 | 
			
		||||
+{
 | 
			
		||||
+	return flow->flags & FLOW_OFFLOAD_HW;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 static void flow_offload_del(struct nf_flowtable *flow_table,
 | 
			
		||||
 			     struct flow_offload *flow)
 | 
			
		||||
 {
 | 
			
		||||
 	struct flow_offload_entry *e;
 | 
			
		||||
+	struct net *net = read_pnet(&flow_table->ft_net);
 | 
			
		||||
 
 | 
			
		||||
 	rhashtable_remove_fast(&flow_table->rhashtable,
 | 
			
		||||
 			       &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].node,
 | 
			
		||||
@@ -214,6 +220,9 @@ static void flow_offload_del(struct nf_f
 | 
			
		||||
 	e = container_of(flow, struct flow_offload_entry, flow);
 | 
			
		||||
 	clear_bit(IPS_OFFLOAD_BIT, &e->ct->status);
 | 
			
		||||
 
 | 
			
		||||
+	if (nf_flow_in_hw(flow))
 | 
			
		||||
+		nf_flow_offload_hw_del(net, flow);
 | 
			
		||||
+
 | 
			
		||||
 	flow_offload_free(flow);
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
@@ -307,6 +316,7 @@ static int nf_flow_offload_gc_step(struc
 | 
			
		||||
 	rhashtable_walk_start(&hti);
 | 
			
		||||
 
 | 
			
		||||
 	while ((tuplehash = rhashtable_walk_next(&hti))) {
 | 
			
		||||
+		bool teardown;
 | 
			
		||||
 		if (IS_ERR(tuplehash)) {
 | 
			
		||||
 			err = PTR_ERR(tuplehash);
 | 
			
		||||
 			if (err != -EAGAIN)
 | 
			
		||||
@@ -319,9 +329,13 @@ static int nf_flow_offload_gc_step(struc
 | 
			
		||||
 
 | 
			
		||||
 		flow = container_of(tuplehash, struct flow_offload, tuplehash[0]);
 | 
			
		||||
 
 | 
			
		||||
-		if (nf_flow_has_expired(flow) ||
 | 
			
		||||
-		    (flow->flags & (FLOW_OFFLOAD_DYING |
 | 
			
		||||
-				    FLOW_OFFLOAD_TEARDOWN)))
 | 
			
		||||
+		teardown = flow->flags & (FLOW_OFFLOAD_DYING |
 | 
			
		||||
+					  FLOW_OFFLOAD_TEARDOWN);
 | 
			
		||||
+
 | 
			
		||||
+		if (nf_flow_in_hw(flow) && !teardown)
 | 
			
		||||
+			continue;
 | 
			
		||||
+
 | 
			
		||||
+		if (nf_flow_has_expired(flow) || teardown)
 | 
			
		||||
 			flow_offload_del(flow_table, flow);
 | 
			
		||||
 	}
 | 
			
		||||
 out:
 | 
			
		||||
@@ -456,10 +470,43 @@ int nf_flow_dnat_port(const struct flow_
 | 
			
		||||
 }
 | 
			
		||||
 EXPORT_SYMBOL_GPL(nf_flow_dnat_port);
 | 
			
		||||
 
 | 
			
		||||
+static const struct nf_flow_table_hw __rcu *nf_flow_table_hw_hook __read_mostly;
 | 
			
		||||
+
 | 
			
		||||
+static int nf_flow_offload_hw_init(struct nf_flowtable *flow_table)
 | 
			
		||||
+{
 | 
			
		||||
+	const struct nf_flow_table_hw *offload;
 | 
			
		||||
+
 | 
			
		||||
+	if (!rcu_access_pointer(nf_flow_table_hw_hook))
 | 
			
		||||
+		request_module("nf-flow-table-hw");
 | 
			
		||||
+
 | 
			
		||||
+	rcu_read_lock();
 | 
			
		||||
+	offload = rcu_dereference(nf_flow_table_hw_hook);
 | 
			
		||||
+	if (!offload)
 | 
			
		||||
+		goto err_no_hw_offload;
 | 
			
		||||
+
 | 
			
		||||
+	if (!try_module_get(offload->owner))
 | 
			
		||||
+		goto err_no_hw_offload;
 | 
			
		||||
+
 | 
			
		||||
+	rcu_read_unlock();
 | 
			
		||||
+
 | 
			
		||||
+	return 0;
 | 
			
		||||
+
 | 
			
		||||
+err_no_hw_offload:
 | 
			
		||||
+	rcu_read_unlock();
 | 
			
		||||
+
 | 
			
		||||
+	return -EOPNOTSUPP;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 int nf_flow_table_init(struct nf_flowtable *flowtable)
 | 
			
		||||
 {
 | 
			
		||||
 	int err;
 | 
			
		||||
 
 | 
			
		||||
+	if (flowtable->flags & NF_FLOWTABLE_F_HW) {
 | 
			
		||||
+		err = nf_flow_offload_hw_init(flowtable);
 | 
			
		||||
+		if (err)
 | 
			
		||||
+			return err;
 | 
			
		||||
+	}
 | 
			
		||||
+
 | 
			
		||||
 	INIT_DEFERRABLE_WORK(&flowtable->gc_work, nf_flow_offload_work_gc);
 | 
			
		||||
 
 | 
			
		||||
 	err = rhashtable_init(&flowtable->rhashtable,
 | 
			
		||||
@@ -497,6 +544,8 @@ static void nf_flow_table_iterate_cleanu
 | 
			
		||||
 {
 | 
			
		||||
 	nf_flow_table_iterate(flowtable, nf_flow_table_do_cleanup, dev);
 | 
			
		||||
 	flush_delayed_work(&flowtable->gc_work);
 | 
			
		||||
+	if (flowtable->flags & NF_FLOWTABLE_F_HW)
 | 
			
		||||
+		flush_work(&nf_flow_offload_hw_work);
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
 void nf_flow_table_cleanup(struct net *net, struct net_device *dev)
 | 
			
		||||
@@ -510,6 +559,26 @@ void nf_flow_table_cleanup(struct net *n
 | 
			
		||||
 }
 | 
			
		||||
 EXPORT_SYMBOL_GPL(nf_flow_table_cleanup);
 | 
			
		||||
 
 | 
			
		||||
+struct work_struct nf_flow_offload_hw_work;
 | 
			
		||||
+EXPORT_SYMBOL_GPL(nf_flow_offload_hw_work);
 | 
			
		||||
+
 | 
			
		||||
+/* Give the hardware workqueue the chance to remove entries from hardware.*/
 | 
			
		||||
+static void nf_flow_offload_hw_free(struct nf_flowtable *flowtable)
 | 
			
		||||
+{
 | 
			
		||||
+	const struct nf_flow_table_hw *offload;
 | 
			
		||||
+
 | 
			
		||||
+	flush_work(&nf_flow_offload_hw_work);
 | 
			
		||||
+
 | 
			
		||||
+	rcu_read_lock();
 | 
			
		||||
+	offload = rcu_dereference(nf_flow_table_hw_hook);
 | 
			
		||||
+	if (!offload) {
 | 
			
		||||
+		rcu_read_unlock();
 | 
			
		||||
+		return;
 | 
			
		||||
+	}
 | 
			
		||||
+	module_put(offload->owner);
 | 
			
		||||
+	rcu_read_unlock();
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 void nf_flow_table_free(struct nf_flowtable *flow_table)
 | 
			
		||||
 {
 | 
			
		||||
 	mutex_lock(&flowtable_lock);
 | 
			
		||||
@@ -519,9 +588,58 @@ void nf_flow_table_free(struct nf_flowta
 | 
			
		||||
 	nf_flow_table_iterate(flow_table, nf_flow_table_do_cleanup, NULL);
 | 
			
		||||
 	WARN_ON(!nf_flow_offload_gc_step(flow_table));
 | 
			
		||||
 	rhashtable_destroy(&flow_table->rhashtable);
 | 
			
		||||
+	if (flow_table->flags & NF_FLOWTABLE_F_HW)
 | 
			
		||||
+		nf_flow_offload_hw_free(flow_table);
 | 
			
		||||
 }
 | 
			
		||||
 EXPORT_SYMBOL_GPL(nf_flow_table_free);
 | 
			
		||||
 
 | 
			
		||||
+/* Must be called from user context. */
 | 
			
		||||
+void nf_flow_offload_hw_add(struct net *net, struct flow_offload *flow,
 | 
			
		||||
+			    struct nf_conn *ct)
 | 
			
		||||
+{
 | 
			
		||||
+	const struct nf_flow_table_hw *offload;
 | 
			
		||||
+
 | 
			
		||||
+	rcu_read_lock();
 | 
			
		||||
+	offload = rcu_dereference(nf_flow_table_hw_hook);
 | 
			
		||||
+	if (offload)
 | 
			
		||||
+		offload->add(net, flow, ct);
 | 
			
		||||
+	rcu_read_unlock();
 | 
			
		||||
+}
 | 
			
		||||
+EXPORT_SYMBOL_GPL(nf_flow_offload_hw_add);
 | 
			
		||||
+
 | 
			
		||||
+/* Must be called from user context. */
 | 
			
		||||
+void nf_flow_offload_hw_del(struct net *net, struct flow_offload *flow)
 | 
			
		||||
+{
 | 
			
		||||
+	const struct nf_flow_table_hw *offload;
 | 
			
		||||
+
 | 
			
		||||
+	rcu_read_lock();
 | 
			
		||||
+	offload = rcu_dereference(nf_flow_table_hw_hook);
 | 
			
		||||
+	if (offload)
 | 
			
		||||
+		offload->del(net, flow);
 | 
			
		||||
+	rcu_read_unlock();
 | 
			
		||||
+}
 | 
			
		||||
+EXPORT_SYMBOL_GPL(nf_flow_offload_hw_del);
 | 
			
		||||
+
 | 
			
		||||
+int nf_flow_table_hw_register(const struct nf_flow_table_hw *offload)
 | 
			
		||||
+{
 | 
			
		||||
+	if (rcu_access_pointer(nf_flow_table_hw_hook))
 | 
			
		||||
+		return -EBUSY;
 | 
			
		||||
+
 | 
			
		||||
+	rcu_assign_pointer(nf_flow_table_hw_hook, offload);
 | 
			
		||||
+
 | 
			
		||||
+	return 0;
 | 
			
		||||
+}
 | 
			
		||||
+EXPORT_SYMBOL_GPL(nf_flow_table_hw_register);
 | 
			
		||||
+
 | 
			
		||||
+void nf_flow_table_hw_unregister(const struct nf_flow_table_hw *offload)
 | 
			
		||||
+{
 | 
			
		||||
+	WARN_ON(rcu_access_pointer(nf_flow_table_hw_hook) != offload);
 | 
			
		||||
+	rcu_assign_pointer(nf_flow_table_hw_hook, NULL);
 | 
			
		||||
+
 | 
			
		||||
+	synchronize_rcu();
 | 
			
		||||
+}
 | 
			
		||||
+EXPORT_SYMBOL_GPL(nf_flow_table_hw_unregister);
 | 
			
		||||
+
 | 
			
		||||
 static int nf_flow_table_netdev_event(struct notifier_block *this,
 | 
			
		||||
 				      unsigned long event, void *ptr)
 | 
			
		||||
 {
 | 
			
		||||
--- /dev/null
 | 
			
		||||
+++ b/net/netfilter/nf_flow_table_hw.c
 | 
			
		||||
@@ -0,0 +1,169 @@
 | 
			
		||||
+#include <linux/kernel.h>
 | 
			
		||||
+#include <linux/init.h>
 | 
			
		||||
+#include <linux/module.h>
 | 
			
		||||
+#include <linux/netfilter.h>
 | 
			
		||||
+#include <linux/rhashtable.h>
 | 
			
		||||
+#include <linux/netdevice.h>
 | 
			
		||||
+#include <net/netfilter/nf_flow_table.h>
 | 
			
		||||
+#include <net/netfilter/nf_conntrack.h>
 | 
			
		||||
+#include <net/netfilter/nf_conntrack_core.h>
 | 
			
		||||
+#include <net/netfilter/nf_conntrack_tuple.h>
 | 
			
		||||
+
 | 
			
		||||
+static DEFINE_SPINLOCK(flow_offload_hw_pending_list_lock);
 | 
			
		||||
+static LIST_HEAD(flow_offload_hw_pending_list);
 | 
			
		||||
+
 | 
			
		||||
+static DEFINE_MUTEX(nf_flow_offload_hw_mutex);
 | 
			
		||||
+
 | 
			
		||||
+struct flow_offload_hw {
 | 
			
		||||
+	struct list_head	list;
 | 
			
		||||
+	enum flow_offload_type	type;
 | 
			
		||||
+	struct flow_offload	*flow;
 | 
			
		||||
+	struct nf_conn		*ct;
 | 
			
		||||
+	possible_net_t		flow_hw_net;
 | 
			
		||||
+};
 | 
			
		||||
+
 | 
			
		||||
+static int do_flow_offload_hw(struct net *net, struct flow_offload *flow,
 | 
			
		||||
+			      int type)
 | 
			
		||||
+{
 | 
			
		||||
+	struct net_device *indev;
 | 
			
		||||
+	int ret, ifindex;
 | 
			
		||||
+
 | 
			
		||||
+	ifindex = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.iifidx;
 | 
			
		||||
+	indev = dev_get_by_index(net, ifindex);
 | 
			
		||||
+	if (WARN_ON(!indev))
 | 
			
		||||
+		return 0;
 | 
			
		||||
+
 | 
			
		||||
+	mutex_lock(&nf_flow_offload_hw_mutex);
 | 
			
		||||
+	ret = indev->netdev_ops->ndo_flow_offload(type, flow);
 | 
			
		||||
+	mutex_unlock(&nf_flow_offload_hw_mutex);
 | 
			
		||||
+
 | 
			
		||||
+	dev_put(indev);
 | 
			
		||||
+
 | 
			
		||||
+	return ret;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static void flow_offload_hw_work_add(struct flow_offload_hw *offload)
 | 
			
		||||
+{
 | 
			
		||||
+	struct net *net;
 | 
			
		||||
+	int ret;
 | 
			
		||||
+
 | 
			
		||||
+	if (nf_ct_is_dying(offload->ct))
 | 
			
		||||
+		return;
 | 
			
		||||
+
 | 
			
		||||
+	net = read_pnet(&offload->flow_hw_net);
 | 
			
		||||
+	ret = do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_ADD);
 | 
			
		||||
+	if (ret >= 0)
 | 
			
		||||
+		offload->flow->flags |= FLOW_OFFLOAD_HW;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static void flow_offload_hw_work_del(struct flow_offload_hw *offload)
 | 
			
		||||
+{
 | 
			
		||||
+	struct net *net = read_pnet(&offload->flow_hw_net);
 | 
			
		||||
+
 | 
			
		||||
+	do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_DEL);
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static void flow_offload_hw_work(struct work_struct *work)
 | 
			
		||||
+{
 | 
			
		||||
+	struct flow_offload_hw *offload, *next;
 | 
			
		||||
+	LIST_HEAD(hw_offload_pending);
 | 
			
		||||
+
 | 
			
		||||
+	spin_lock_bh(&flow_offload_hw_pending_list_lock);
 | 
			
		||||
+	list_replace_init(&flow_offload_hw_pending_list, &hw_offload_pending);
 | 
			
		||||
+	spin_unlock_bh(&flow_offload_hw_pending_list_lock);
 | 
			
		||||
+
 | 
			
		||||
+	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
 | 
			
		||||
+		switch (offload->type) {
 | 
			
		||||
+		case FLOW_OFFLOAD_ADD:
 | 
			
		||||
+			flow_offload_hw_work_add(offload);
 | 
			
		||||
+			break;
 | 
			
		||||
+		case FLOW_OFFLOAD_DEL:
 | 
			
		||||
+			flow_offload_hw_work_del(offload);
 | 
			
		||||
+			break;
 | 
			
		||||
+		}
 | 
			
		||||
+		if (offload->ct)
 | 
			
		||||
+			nf_conntrack_put(&offload->ct->ct_general);
 | 
			
		||||
+		list_del(&offload->list);
 | 
			
		||||
+		kfree(offload);
 | 
			
		||||
+	}
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static void flow_offload_queue_work(struct flow_offload_hw *offload)
 | 
			
		||||
+{
 | 
			
		||||
+	spin_lock_bh(&flow_offload_hw_pending_list_lock);
 | 
			
		||||
+	list_add_tail(&offload->list, &flow_offload_hw_pending_list);
 | 
			
		||||
+	spin_unlock_bh(&flow_offload_hw_pending_list_lock);
 | 
			
		||||
+
 | 
			
		||||
+	schedule_work(&nf_flow_offload_hw_work);
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static void flow_offload_hw_add(struct net *net, struct flow_offload *flow,
 | 
			
		||||
+				struct nf_conn *ct)
 | 
			
		||||
+{
 | 
			
		||||
+	struct flow_offload_hw *offload;
 | 
			
		||||
+
 | 
			
		||||
+	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
 | 
			
		||||
+	if (!offload)
 | 
			
		||||
+		return;
 | 
			
		||||
+
 | 
			
		||||
+	nf_conntrack_get(&ct->ct_general);
 | 
			
		||||
+	offload->type = FLOW_OFFLOAD_ADD;
 | 
			
		||||
+	offload->ct = ct;
 | 
			
		||||
+	offload->flow = flow;
 | 
			
		||||
+	write_pnet(&offload->flow_hw_net, net);
 | 
			
		||||
+
 | 
			
		||||
+	flow_offload_queue_work(offload);
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static void flow_offload_hw_del(struct net *net, struct flow_offload *flow)
 | 
			
		||||
+{
 | 
			
		||||
+	struct flow_offload_hw *offload;
 | 
			
		||||
+
 | 
			
		||||
+	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
 | 
			
		||||
+	if (!offload)
 | 
			
		||||
+		return;
 | 
			
		||||
+
 | 
			
		||||
+	offload->type = FLOW_OFFLOAD_DEL;
 | 
			
		||||
+	offload->ct = NULL;
 | 
			
		||||
+	offload->flow = flow;
 | 
			
		||||
+	write_pnet(&offload->flow_hw_net, net);
 | 
			
		||||
+
 | 
			
		||||
+	flow_offload_queue_work(offload);
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static const struct nf_flow_table_hw flow_offload_hw = {
 | 
			
		||||
+	.add	= flow_offload_hw_add,
 | 
			
		||||
+	.del	= flow_offload_hw_del,
 | 
			
		||||
+	.owner	= THIS_MODULE,
 | 
			
		||||
+};
 | 
			
		||||
+
 | 
			
		||||
+static int __init nf_flow_table_hw_module_init(void)
 | 
			
		||||
+{
 | 
			
		||||
+	INIT_WORK(&nf_flow_offload_hw_work, flow_offload_hw_work);
 | 
			
		||||
+	nf_flow_table_hw_register(&flow_offload_hw);
 | 
			
		||||
+
 | 
			
		||||
+	return 0;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+static void __exit nf_flow_table_hw_module_exit(void)
 | 
			
		||||
+{
 | 
			
		||||
+	struct flow_offload_hw *offload, *next;
 | 
			
		||||
+	LIST_HEAD(hw_offload_pending);
 | 
			
		||||
+
 | 
			
		||||
+	nf_flow_table_hw_unregister(&flow_offload_hw);
 | 
			
		||||
+	cancel_work_sync(&nf_flow_offload_hw_work);
 | 
			
		||||
+
 | 
			
		||||
+	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
 | 
			
		||||
+		if (offload->ct)
 | 
			
		||||
+			nf_conntrack_put(&offload->ct->ct_general);
 | 
			
		||||
+		list_del(&offload->list);
 | 
			
		||||
+		kfree(offload);
 | 
			
		||||
+	}
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+module_init(nf_flow_table_hw_module_init);
 | 
			
		||||
+module_exit(nf_flow_table_hw_module_exit);
 | 
			
		||||
+
 | 
			
		||||
+MODULE_LICENSE("GPL");
 | 
			
		||||
+MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
 | 
			
		||||
+MODULE_ALIAS("nf-flow-table-hw");
 | 
			
		||||
--- a/net/netfilter/nf_tables_api.c
 | 
			
		||||
+++ b/net/netfilter/nf_tables_api.c
 | 
			
		||||
@@ -4866,6 +4866,14 @@ static int nf_tables_flowtable_parse_hoo
 | 
			
		||||
 	if (err < 0)
 | 
			
		||||
 		goto err1;
 | 
			
		||||
 
 | 
			
		||||
+	for (i = 0; i < n; i++) {
 | 
			
		||||
+		if (flowtable->data.flags & NF_FLOWTABLE_F_HW &&
 | 
			
		||||
+		    !dev_array[i]->netdev_ops->ndo_flow_offload) {
 | 
			
		||||
+			err = -EOPNOTSUPP;
 | 
			
		||||
+			goto err1;
 | 
			
		||||
+		}
 | 
			
		||||
+	}
 | 
			
		||||
+
 | 
			
		||||
 	ops = kzalloc(sizeof(struct nf_hook_ops) * n, GFP_KERNEL);
 | 
			
		||||
 	if (!ops) {
 | 
			
		||||
 		err = -ENOMEM;
 | 
			
		||||
@@ -4996,10 +5004,19 @@ static int nf_tables_newflowtable(struct
 | 
			
		||||
 	}
 | 
			
		||||
 
 | 
			
		||||
 	flowtable->data.type = type;
 | 
			
		||||
+	write_pnet(&flowtable->data.ft_net, net);
 | 
			
		||||
+
 | 
			
		||||
 	err = type->init(&flowtable->data);
 | 
			
		||||
 	if (err < 0)
 | 
			
		||||
 		goto err3;
 | 
			
		||||
 
 | 
			
		||||
+	if (nla[NFTA_FLOWTABLE_FLAGS]) {
 | 
			
		||||
+		flowtable->data.flags =
 | 
			
		||||
+			ntohl(nla_get_be32(nla[NFTA_FLOWTABLE_FLAGS]));
 | 
			
		||||
+		if (flowtable->data.flags & ~NF_FLOWTABLE_F_HW)
 | 
			
		||||
+			goto err4;
 | 
			
		||||
+	}
 | 
			
		||||
+
 | 
			
		||||
 	err = nf_tables_flowtable_parse_hook(&ctx, nla[NFTA_FLOWTABLE_HOOK],
 | 
			
		||||
 					     flowtable);
 | 
			
		||||
 	if (err < 0)
 | 
			
		||||
@@ -5097,7 +5114,8 @@ static int nf_tables_fill_flowtable_info
 | 
			
		||||
 	    nla_put_string(skb, NFTA_FLOWTABLE_NAME, flowtable->name) ||
 | 
			
		||||
 	    nla_put_be32(skb, NFTA_FLOWTABLE_USE, htonl(flowtable->use)) ||
 | 
			
		||||
 	    nla_put_be64(skb, NFTA_FLOWTABLE_HANDLE, cpu_to_be64(flowtable->handle),
 | 
			
		||||
-			 NFTA_FLOWTABLE_PAD))
 | 
			
		||||
+			 NFTA_FLOWTABLE_PAD) ||
 | 
			
		||||
+	    nla_put_be32(skb, NFTA_FLOWTABLE_FLAGS, htonl(flowtable->data.flags)))
 | 
			
		||||
 		goto nla_put_failure;
 | 
			
		||||
 
 | 
			
		||||
 	nest = nla_nest_start(skb, NFTA_FLOWTABLE_HOOK);
 | 
			
		||||
--- a/net/netfilter/nft_flow_offload.c
 | 
			
		||||
+++ b/net/netfilter/nft_flow_offload.c
 | 
			
		||||
@@ -121,6 +121,9 @@ static void nft_flow_offload_eval(const
 | 
			
		||||
 	if (ret < 0)
 | 
			
		||||
 		goto err_flow_add;
 | 
			
		||||
 
 | 
			
		||||
+	if (flowtable->flags & NF_FLOWTABLE_F_HW)
 | 
			
		||||
+		nf_flow_offload_hw_add(nft_net(pkt), flow, ct);
 | 
			
		||||
+
 | 
			
		||||
 	return;
 | 
			
		||||
 
 | 
			
		||||
 err_flow_add:
 | 
			
		||||
@@ -0,0 +1,302 @@
 | 
			
		||||
From: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
Date: Thu, 15 Mar 2018 20:46:31 +0100
 | 
			
		||||
Subject: [PATCH] netfilter: nf_flow_table: support hw offload through
 | 
			
		||||
 virtual interfaces
 | 
			
		||||
 | 
			
		||||
There are hardware offload devices that support offloading VLANs and
 | 
			
		||||
PPPoE devices. Additionally, it is useful to be able to offload packets
 | 
			
		||||
routed through bridge interfaces as well.
 | 
			
		||||
Add support for finding the path to the offload device through these
 | 
			
		||||
virtual interfaces, while collecting useful parameters for the offload
 | 
			
		||||
device, like VLAN ID/protocol, PPPoE session and Ethernet MAC address.
 | 
			
		||||
 | 
			
		||||
Signed-off-by: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
--- a/include/linux/netdevice.h
 | 
			
		||||
+++ b/include/linux/netdevice.h
 | 
			
		||||
@@ -827,6 +827,7 @@ struct xfrmdev_ops {
 | 
			
		||||
 #endif
 | 
			
		||||
 
 | 
			
		||||
 struct flow_offload;
 | 
			
		||||
+struct flow_offload_hw_path;
 | 
			
		||||
 
 | 
			
		||||
 enum flow_offload_type {
 | 
			
		||||
 	FLOW_OFFLOAD_ADD	= 0,
 | 
			
		||||
@@ -1064,8 +1065,15 @@ enum flow_offload_type {
 | 
			
		||||
  * int (*ndo_bridge_dellink)(struct net_device *dev, struct nlmsghdr *nlh,
 | 
			
		||||
  *			     u16 flags);
 | 
			
		||||
  *
 | 
			
		||||
+ * int (*ndo_flow_offload_check)(struct flow_offload_hw_path *path);
 | 
			
		||||
+ *	For virtual devices like bridges, vlan, and pppoe, fill in the
 | 
			
		||||
+ *	underlying network device that can be used for offloading connections.
 | 
			
		||||
+ *	Return an error if offloading is not supported.
 | 
			
		||||
+ *
 | 
			
		||||
  * int (*ndo_flow_offload)(enum flow_offload_type type,
 | 
			
		||||
- *			   struct flow_offload *flow);
 | 
			
		||||
+ *			   struct flow_offload *flow,
 | 
			
		||||
+ *			   struct flow_offload_hw_path *src,
 | 
			
		||||
+ *			   struct flow_offload_hw_path *dest);
 | 
			
		||||
  *	Adds/deletes flow entry to/from net device flowtable.
 | 
			
		||||
  *
 | 
			
		||||
  * int (*ndo_change_carrier)(struct net_device *dev, bool new_carrier);
 | 
			
		||||
@@ -1292,8 +1300,11 @@ struct net_device_ops {
 | 
			
		||||
 	int			(*ndo_bridge_dellink)(struct net_device *dev,
 | 
			
		||||
 						      struct nlmsghdr *nlh,
 | 
			
		||||
 						      u16 flags);
 | 
			
		||||
+	int			(*ndo_flow_offload_check)(struct flow_offload_hw_path *path);
 | 
			
		||||
 	int			(*ndo_flow_offload)(enum flow_offload_type type,
 | 
			
		||||
-						    struct flow_offload *flow);
 | 
			
		||||
+						    struct flow_offload *flow,
 | 
			
		||||
+						    struct flow_offload_hw_path *src,
 | 
			
		||||
+						    struct flow_offload_hw_path *dest);
 | 
			
		||||
 	int			(*ndo_change_carrier)(struct net_device *dev,
 | 
			
		||||
 						      bool new_carrier);
 | 
			
		||||
 	int			(*ndo_get_phys_port_id)(struct net_device *dev,
 | 
			
		||||
--- a/include/net/netfilter/nf_flow_table.h
 | 
			
		||||
+++ b/include/net/netfilter/nf_flow_table.h
 | 
			
		||||
@@ -86,6 +86,21 @@ struct flow_offload {
 | 
			
		||||
 	};
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
+#define FLOW_OFFLOAD_PATH_ETHERNET	BIT(0)
 | 
			
		||||
+#define FLOW_OFFLOAD_PATH_VLAN		BIT(1)
 | 
			
		||||
+#define FLOW_OFFLOAD_PATH_PPPOE		BIT(2)
 | 
			
		||||
+
 | 
			
		||||
+struct flow_offload_hw_path {
 | 
			
		||||
+	struct net_device *dev;
 | 
			
		||||
+	u32 flags;
 | 
			
		||||
+
 | 
			
		||||
+	u8 eth_src[ETH_ALEN];
 | 
			
		||||
+	u8 eth_dest[ETH_ALEN];
 | 
			
		||||
+	u16 vlan_proto;
 | 
			
		||||
+	u16 vlan_id;
 | 
			
		||||
+	u16 pppoe_sid;
 | 
			
		||||
+};
 | 
			
		||||
+
 | 
			
		||||
 #define NF_FLOW_TIMEOUT (30 * HZ)
 | 
			
		||||
 
 | 
			
		||||
 struct nf_flow_route {
 | 
			
		||||
--- a/net/netfilter/nf_flow_table_hw.c
 | 
			
		||||
+++ b/net/netfilter/nf_flow_table_hw.c
 | 
			
		||||
@@ -19,48 +19,75 @@ struct flow_offload_hw {
 | 
			
		||||
 	enum flow_offload_type	type;
 | 
			
		||||
 	struct flow_offload	*flow;
 | 
			
		||||
 	struct nf_conn		*ct;
 | 
			
		||||
-	possible_net_t		flow_hw_net;
 | 
			
		||||
+
 | 
			
		||||
+	struct flow_offload_hw_path src;
 | 
			
		||||
+	struct flow_offload_hw_path dest;
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
-static int do_flow_offload_hw(struct net *net, struct flow_offload *flow,
 | 
			
		||||
-			      int type)
 | 
			
		||||
+static void flow_offload_check_ethernet(struct flow_offload_tuple *tuple,
 | 
			
		||||
+					struct flow_offload_hw_path *path)
 | 
			
		||||
 {
 | 
			
		||||
-	struct net_device *indev;
 | 
			
		||||
-	int ret, ifindex;
 | 
			
		||||
+	struct net_device *dev = path->dev;
 | 
			
		||||
+	struct neighbour *n;
 | 
			
		||||
 
 | 
			
		||||
-	ifindex = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.iifidx;
 | 
			
		||||
-	indev = dev_get_by_index(net, ifindex);
 | 
			
		||||
-	if (WARN_ON(!indev))
 | 
			
		||||
-		return 0;
 | 
			
		||||
+	if (dev->type != ARPHRD_ETHER)
 | 
			
		||||
+		return;
 | 
			
		||||
 
 | 
			
		||||
-	mutex_lock(&nf_flow_offload_hw_mutex);
 | 
			
		||||
-	ret = indev->netdev_ops->ndo_flow_offload(type, flow);
 | 
			
		||||
-	mutex_unlock(&nf_flow_offload_hw_mutex);
 | 
			
		||||
+	memcpy(path->eth_src, path->dev->dev_addr, ETH_ALEN);
 | 
			
		||||
+	n = dst_neigh_lookup(tuple->dst_cache, &tuple->src_v4);
 | 
			
		||||
+	if (!n)
 | 
			
		||||
+		return;
 | 
			
		||||
 
 | 
			
		||||
-	dev_put(indev);
 | 
			
		||||
+	memcpy(path->eth_dest, n->ha, ETH_ALEN);
 | 
			
		||||
+	path->flags |= FLOW_OFFLOAD_PATH_ETHERNET;
 | 
			
		||||
+	neigh_release(n);
 | 
			
		||||
+}
 | 
			
		||||
 
 | 
			
		||||
-	return ret;
 | 
			
		||||
+static int flow_offload_check_path(struct net *net,
 | 
			
		||||
+				   struct flow_offload_tuple *tuple,
 | 
			
		||||
+				   struct flow_offload_hw_path *path)
 | 
			
		||||
+{
 | 
			
		||||
+	struct net_device *dev;
 | 
			
		||||
+
 | 
			
		||||
+	dev = dev_get_by_index_rcu(net, tuple->iifidx);
 | 
			
		||||
+	if (!dev)
 | 
			
		||||
+		return -ENOENT;
 | 
			
		||||
+
 | 
			
		||||
+	path->dev = dev;
 | 
			
		||||
+	flow_offload_check_ethernet(tuple, path);
 | 
			
		||||
+
 | 
			
		||||
+	if (dev->netdev_ops->ndo_flow_offload_check)
 | 
			
		||||
+		return dev->netdev_ops->ndo_flow_offload_check(path);
 | 
			
		||||
+
 | 
			
		||||
+	return 0;
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
-static void flow_offload_hw_work_add(struct flow_offload_hw *offload)
 | 
			
		||||
+static int do_flow_offload_hw(struct flow_offload_hw *offload)
 | 
			
		||||
 {
 | 
			
		||||
-	struct net *net;
 | 
			
		||||
+	struct net_device *src_dev = offload->src.dev;
 | 
			
		||||
+	struct net_device *dest_dev = offload->dest.dev;
 | 
			
		||||
 	int ret;
 | 
			
		||||
 
 | 
			
		||||
-	if (nf_ct_is_dying(offload->ct))
 | 
			
		||||
-		return;
 | 
			
		||||
+	ret = src_dev->netdev_ops->ndo_flow_offload(offload->type,
 | 
			
		||||
+						    offload->flow,
 | 
			
		||||
+						    &offload->src,
 | 
			
		||||
+						    &offload->dest);
 | 
			
		||||
 
 | 
			
		||||
-	net = read_pnet(&offload->flow_hw_net);
 | 
			
		||||
-	ret = do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_ADD);
 | 
			
		||||
-	if (ret >= 0)
 | 
			
		||||
-		offload->flow->flags |= FLOW_OFFLOAD_HW;
 | 
			
		||||
+	/* restore devices in case the driver mangled them */
 | 
			
		||||
+	offload->src.dev = src_dev;
 | 
			
		||||
+	offload->dest.dev = dest_dev;
 | 
			
		||||
+
 | 
			
		||||
+	return ret;
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
-static void flow_offload_hw_work_del(struct flow_offload_hw *offload)
 | 
			
		||||
+static void flow_offload_hw_free(struct flow_offload_hw *offload)
 | 
			
		||||
 {
 | 
			
		||||
-	struct net *net = read_pnet(&offload->flow_hw_net);
 | 
			
		||||
-
 | 
			
		||||
-	do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_DEL);
 | 
			
		||||
+	dev_put(offload->src.dev);
 | 
			
		||||
+	dev_put(offload->dest.dev);
 | 
			
		||||
+	if (offload->ct)
 | 
			
		||||
+		nf_conntrack_put(&offload->ct->ct_general);
 | 
			
		||||
+	list_del(&offload->list);
 | 
			
		||||
+	kfree(offload);
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
 static void flow_offload_hw_work(struct work_struct *work)
 | 
			
		||||
@@ -73,18 +100,22 @@ static void flow_offload_hw_work(struct
 | 
			
		||||
 	spin_unlock_bh(&flow_offload_hw_pending_list_lock);
 | 
			
		||||
 
 | 
			
		||||
 	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
 | 
			
		||||
+		mutex_lock(&nf_flow_offload_hw_mutex);
 | 
			
		||||
 		switch (offload->type) {
 | 
			
		||||
 		case FLOW_OFFLOAD_ADD:
 | 
			
		||||
-			flow_offload_hw_work_add(offload);
 | 
			
		||||
+			if (nf_ct_is_dying(offload->ct))
 | 
			
		||||
+				break;
 | 
			
		||||
+
 | 
			
		||||
+			if (do_flow_offload_hw(offload) >= 0)
 | 
			
		||||
+				offload->flow->flags |= FLOW_OFFLOAD_HW;
 | 
			
		||||
 			break;
 | 
			
		||||
 		case FLOW_OFFLOAD_DEL:
 | 
			
		||||
-			flow_offload_hw_work_del(offload);
 | 
			
		||||
+			do_flow_offload_hw(offload);
 | 
			
		||||
 			break;
 | 
			
		||||
 		}
 | 
			
		||||
-		if (offload->ct)
 | 
			
		||||
-			nf_conntrack_put(&offload->ct->ct_general);
 | 
			
		||||
-		list_del(&offload->list);
 | 
			
		||||
-		kfree(offload);
 | 
			
		||||
+		mutex_unlock(&nf_flow_offload_hw_mutex);
 | 
			
		||||
+
 | 
			
		||||
+		flow_offload_hw_free(offload);
 | 
			
		||||
 	}
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
@@ -97,20 +128,55 @@ static void flow_offload_queue_work(stru
 | 
			
		||||
 	schedule_work(&nf_flow_offload_hw_work);
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
+static struct flow_offload_hw *
 | 
			
		||||
+flow_offload_hw_prepare(struct net *net, struct flow_offload *flow)
 | 
			
		||||
+{
 | 
			
		||||
+	struct flow_offload_hw_path src = {};
 | 
			
		||||
+	struct flow_offload_hw_path dest = {};
 | 
			
		||||
+	struct flow_offload_tuple *tuple;
 | 
			
		||||
+	struct flow_offload_hw *offload = NULL;
 | 
			
		||||
+
 | 
			
		||||
+	rcu_read_lock_bh();
 | 
			
		||||
+
 | 
			
		||||
+	tuple = &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple;
 | 
			
		||||
+	if (flow_offload_check_path(net, tuple, &src))
 | 
			
		||||
+		goto out;
 | 
			
		||||
+
 | 
			
		||||
+	tuple = &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple;
 | 
			
		||||
+	if (flow_offload_check_path(net, tuple, &dest))
 | 
			
		||||
+		goto out;
 | 
			
		||||
+
 | 
			
		||||
+	if (!src.dev->netdev_ops->ndo_flow_offload)
 | 
			
		||||
+		goto out;
 | 
			
		||||
+
 | 
			
		||||
+	offload = kzalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
 | 
			
		||||
+	if (!offload)
 | 
			
		||||
+		goto out;
 | 
			
		||||
+
 | 
			
		||||
+	dev_hold(src.dev);
 | 
			
		||||
+	dev_hold(dest.dev);
 | 
			
		||||
+	offload->src = src;
 | 
			
		||||
+	offload->dest = dest;
 | 
			
		||||
+	offload->flow = flow;
 | 
			
		||||
+
 | 
			
		||||
+out:
 | 
			
		||||
+	rcu_read_unlock_bh();
 | 
			
		||||
+
 | 
			
		||||
+	return offload;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 static void flow_offload_hw_add(struct net *net, struct flow_offload *flow,
 | 
			
		||||
 				struct nf_conn *ct)
 | 
			
		||||
 {
 | 
			
		||||
 	struct flow_offload_hw *offload;
 | 
			
		||||
 
 | 
			
		||||
-	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
 | 
			
		||||
+	offload = flow_offload_hw_prepare(net, flow);
 | 
			
		||||
 	if (!offload)
 | 
			
		||||
 		return;
 | 
			
		||||
 
 | 
			
		||||
 	nf_conntrack_get(&ct->ct_general);
 | 
			
		||||
 	offload->type = FLOW_OFFLOAD_ADD;
 | 
			
		||||
 	offload->ct = ct;
 | 
			
		||||
-	offload->flow = flow;
 | 
			
		||||
-	write_pnet(&offload->flow_hw_net, net);
 | 
			
		||||
 
 | 
			
		||||
 	flow_offload_queue_work(offload);
 | 
			
		||||
 }
 | 
			
		||||
@@ -119,14 +185,11 @@ static void flow_offload_hw_del(struct n
 | 
			
		||||
 {
 | 
			
		||||
 	struct flow_offload_hw *offload;
 | 
			
		||||
 
 | 
			
		||||
-	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
 | 
			
		||||
+	offload = flow_offload_hw_prepare(net, flow);
 | 
			
		||||
 	if (!offload)
 | 
			
		||||
 		return;
 | 
			
		||||
 
 | 
			
		||||
 	offload->type = FLOW_OFFLOAD_DEL;
 | 
			
		||||
-	offload->ct = NULL;
 | 
			
		||||
-	offload->flow = flow;
 | 
			
		||||
-	write_pnet(&offload->flow_hw_net, net);
 | 
			
		||||
 
 | 
			
		||||
 	flow_offload_queue_work(offload);
 | 
			
		||||
 }
 | 
			
		||||
@@ -153,12 +216,8 @@ static void __exit nf_flow_table_hw_modu
 | 
			
		||||
 	nf_flow_table_hw_unregister(&flow_offload_hw);
 | 
			
		||||
 	cancel_work_sync(&nf_flow_offload_hw_work);
 | 
			
		||||
 
 | 
			
		||||
-	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
 | 
			
		||||
-		if (offload->ct)
 | 
			
		||||
-			nf_conntrack_put(&offload->ct->ct_general);
 | 
			
		||||
-		list_del(&offload->list);
 | 
			
		||||
-		kfree(offload);
 | 
			
		||||
-	}
 | 
			
		||||
+	list_for_each_entry_safe(offload, next, &hw_offload_pending, list)
 | 
			
		||||
+		flow_offload_hw_free(offload);
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
 module_init(nf_flow_table_hw_module_init);
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
From: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
Date: Thu, 15 Mar 2018 20:49:58 +0100
 | 
			
		||||
Subject: [PATCH] net: 8021q: support hardware flow table offload
 | 
			
		||||
 | 
			
		||||
Add the VLAN ID and protocol information
 | 
			
		||||
 | 
			
		||||
Signed-off-by: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
--- a/net/8021q/vlan_dev.c
 | 
			
		||||
+++ b/net/8021q/vlan_dev.c
 | 
			
		||||
@@ -29,8 +29,10 @@
 | 
			
		||||
 #include <linux/net_tstamp.h>
 | 
			
		||||
 #include <linux/etherdevice.h>
 | 
			
		||||
 #include <linux/ethtool.h>
 | 
			
		||||
+#include <linux/netfilter.h>
 | 
			
		||||
 #include <net/arp.h>
 | 
			
		||||
 #include <net/switchdev.h>
 | 
			
		||||
+#include <net/netfilter/nf_flow_table.h>
 | 
			
		||||
 
 | 
			
		||||
 #include "vlan.h"
 | 
			
		||||
 #include "vlanproc.h"
 | 
			
		||||
@@ -762,6 +764,25 @@ static int vlan_dev_get_iflink(const str
 | 
			
		||||
 	return real_dev->ifindex;
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
+static int vlan_dev_flow_offload_check(struct flow_offload_hw_path *path)
 | 
			
		||||
+{
 | 
			
		||||
+	struct net_device *dev = path->dev;
 | 
			
		||||
+	struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
 | 
			
		||||
+
 | 
			
		||||
+	if (path->flags & FLOW_OFFLOAD_PATH_VLAN)
 | 
			
		||||
+		return -EEXIST;
 | 
			
		||||
+
 | 
			
		||||
+	path->flags |= FLOW_OFFLOAD_PATH_VLAN;
 | 
			
		||||
+	path->vlan_proto = vlan->vlan_proto;
 | 
			
		||||
+	path->vlan_id = vlan->vlan_id;
 | 
			
		||||
+	path->dev = vlan->real_dev;
 | 
			
		||||
+
 | 
			
		||||
+	if (vlan->real_dev->netdev_ops->ndo_flow_offload_check)
 | 
			
		||||
+		return vlan->real_dev->netdev_ops->ndo_flow_offload_check(path);
 | 
			
		||||
+
 | 
			
		||||
+	return 0;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 static const struct ethtool_ops vlan_ethtool_ops = {
 | 
			
		||||
 	.get_link_ksettings	= vlan_ethtool_get_link_ksettings,
 | 
			
		||||
 	.get_drvinfo	        = vlan_ethtool_get_drvinfo,
 | 
			
		||||
@@ -799,6 +820,7 @@ static const struct net_device_ops vlan_
 | 
			
		||||
 	.ndo_fix_features	= vlan_dev_fix_features,
 | 
			
		||||
 	.ndo_get_lock_subclass  = vlan_dev_get_lock_subclass,
 | 
			
		||||
 	.ndo_get_iflink		= vlan_dev_get_iflink,
 | 
			
		||||
+	.ndo_flow_offload_check = vlan_dev_flow_offload_check,
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
 static void vlan_dev_free(struct net_device *dev)
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
From: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
Date: Thu, 15 Mar 2018 20:50:37 +0100
 | 
			
		||||
Subject: [PATCH] net: bridge: support hardware flow table offload
 | 
			
		||||
 | 
			
		||||
Look up the real device and pass it on
 | 
			
		||||
 | 
			
		||||
Signed-off-by: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
--- a/net/bridge/br_device.c
 | 
			
		||||
+++ b/net/bridge/br_device.c
 | 
			
		||||
@@ -18,6 +18,8 @@
 | 
			
		||||
 #include <linux/ethtool.h>
 | 
			
		||||
 #include <linux/list.h>
 | 
			
		||||
 #include <linux/netfilter_bridge.h>
 | 
			
		||||
+#include <linux/netfilter.h>
 | 
			
		||||
+#include <net/netfilter/nf_flow_table.h>
 | 
			
		||||
 
 | 
			
		||||
 #include <linux/uaccess.h>
 | 
			
		||||
 #include "br_private.h"
 | 
			
		||||
@@ -340,6 +342,26 @@ static const struct ethtool_ops br_ethto
 | 
			
		||||
 	.get_link	= ethtool_op_get_link,
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
+static int br_flow_offload_check(struct flow_offload_hw_path *path)
 | 
			
		||||
+{
 | 
			
		||||
+	struct net_device *dev = path->dev;
 | 
			
		||||
+	struct net_bridge *br = netdev_priv(dev);
 | 
			
		||||
+	struct net_bridge_fdb_entry *dst;
 | 
			
		||||
+
 | 
			
		||||
+	if (!(path->flags & FLOW_OFFLOAD_PATH_ETHERNET))
 | 
			
		||||
+		return -EINVAL;
 | 
			
		||||
+
 | 
			
		||||
+	dst = br_fdb_find_rcu(br, path->eth_dest, path->vlan_id);
 | 
			
		||||
+	if (!dst || !dst->dst)
 | 
			
		||||
+		return -ENOENT;
 | 
			
		||||
+
 | 
			
		||||
+	path->dev = dst->dst->dev;
 | 
			
		||||
+	if (path->dev->netdev_ops->ndo_flow_offload_check)
 | 
			
		||||
+		return path->dev->netdev_ops->ndo_flow_offload_check(path);
 | 
			
		||||
+
 | 
			
		||||
+	return 0;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 static const struct net_device_ops br_netdev_ops = {
 | 
			
		||||
 	.ndo_open		 = br_dev_open,
 | 
			
		||||
 	.ndo_stop		 = br_dev_stop,
 | 
			
		||||
@@ -367,6 +389,7 @@ static const struct net_device_ops br_ne
 | 
			
		||||
 	.ndo_bridge_setlink	 = br_setlink,
 | 
			
		||||
 	.ndo_bridge_dellink	 = br_dellink,
 | 
			
		||||
 	.ndo_features_check	 = passthru_features_check,
 | 
			
		||||
+	.ndo_flow_offload_check	 = br_flow_offload_check,
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
 static struct device_type br_type = {
 | 
			
		||||
@@ -0,0 +1,110 @@
 | 
			
		||||
From: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
Date: Thu, 15 Mar 2018 21:15:00 +0100
 | 
			
		||||
Subject: [PATCH] net: pppoe: support hardware flow table offload
 | 
			
		||||
 | 
			
		||||
Pass on the PPPoE session ID and the remote MAC address
 | 
			
		||||
 | 
			
		||||
Signed-off-by: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
--- a/drivers/net/ppp/ppp_generic.c
 | 
			
		||||
+++ b/drivers/net/ppp/ppp_generic.c
 | 
			
		||||
@@ -56,6 +56,9 @@
 | 
			
		||||
 #include <net/net_namespace.h>
 | 
			
		||||
 #include <net/netns/generic.h>
 | 
			
		||||
 
 | 
			
		||||
+#include <linux/netfilter.h>
 | 
			
		||||
+#include <net/netfilter/nf_flow_table.h>
 | 
			
		||||
+
 | 
			
		||||
 #define PPP_VERSION	"2.4.2"
 | 
			
		||||
 
 | 
			
		||||
 /*
 | 
			
		||||
@@ -1383,12 +1386,33 @@ static void ppp_dev_priv_destructor(stru
 | 
			
		||||
 		ppp_destroy_interface(ppp);
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
+static int ppp_flow_offload_check(struct flow_offload_hw_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->flow_offload_check)
 | 
			
		||||
+		return -EOPNOTSUPP;
 | 
			
		||||
+
 | 
			
		||||
+	return chan->ops->flow_offload_check(chan, path);
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 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_flow_offload_check = ppp_flow_offload_check,
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
 static struct device_type ppp_type = {
 | 
			
		||||
--- a/drivers/net/ppp/pppoe.c
 | 
			
		||||
+++ b/drivers/net/ppp/pppoe.c
 | 
			
		||||
@@ -77,6 +77,8 @@
 | 
			
		||||
 #include <linux/file.h>
 | 
			
		||||
 #include <linux/proc_fs.h>
 | 
			
		||||
 #include <linux/seq_file.h>
 | 
			
		||||
+#include <linux/netfilter.h>
 | 
			
		||||
+#include <net/netfilter/nf_flow_table.h>
 | 
			
		||||
 
 | 
			
		||||
 #include <linux/nsproxy.h>
 | 
			
		||||
 #include <net/net_namespace.h>
 | 
			
		||||
@@ -970,8 +972,32 @@ static int pppoe_xmit(struct ppp_channel
 | 
			
		||||
 	return __pppoe_xmit(sk, skb);
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
+static int pppoe_flow_offload_check(struct ppp_channel *chan,
 | 
			
		||||
+				    struct flow_offload_hw_path *path)
 | 
			
		||||
+{
 | 
			
		||||
+	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 -ENODEV;
 | 
			
		||||
+
 | 
			
		||||
+	path->dev = po->pppoe_dev;
 | 
			
		||||
+	path->flags |= FLOW_OFFLOAD_PATH_PPPOE;
 | 
			
		||||
+	memcpy(path->eth_src, po->pppoe_dev->dev_addr, ETH_ALEN);
 | 
			
		||||
+	memcpy(path->eth_dest, po->pppoe_pa.remote, ETH_ALEN);
 | 
			
		||||
+	path->pppoe_sid = be16_to_cpu(po->num);
 | 
			
		||||
+
 | 
			
		||||
+	if (path->dev->netdev_ops->ndo_flow_offload_check)
 | 
			
		||||
+		return path->dev->netdev_ops->ndo_flow_offload_check(path);
 | 
			
		||||
+
 | 
			
		||||
+	return 0;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 static const struct ppp_channel_ops pppoe_chan_ops = {
 | 
			
		||||
 	.start_xmit = pppoe_xmit,
 | 
			
		||||
+	.flow_offload_check = pppoe_flow_offload_check,
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
 static int pppoe_recvmsg(struct socket *sock, struct msghdr *m,
 | 
			
		||||
--- a/include/linux/ppp_channel.h
 | 
			
		||||
+++ b/include/linux/ppp_channel.h
 | 
			
		||||
@@ -32,6 +32,8 @@ 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	(*flow_offload_check)(struct ppp_channel *, struct flow_offload_hw_path *);
 | 
			
		||||
 };
 | 
			
		||||
 
 | 
			
		||||
 struct ppp_channel {
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
From: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
Date: Sun, 25 Mar 2018 21:10:55 +0200
 | 
			
		||||
Subject: [PATCH] netfilter: nf_flow_table: rework hardware offload timeout
 | 
			
		||||
 handling
 | 
			
		||||
 | 
			
		||||
Some offload implementations send keepalive packets + explicit
 | 
			
		||||
notifications of TCP FIN/RST packets. In this case it is more convenient
 | 
			
		||||
to simply let the driver update flow->timeout handling and use the
 | 
			
		||||
regular flow offload gc step.
 | 
			
		||||
 | 
			
		||||
For drivers that manage their own lifetime, a separate flag can be set
 | 
			
		||||
to avoid gc timeouts.
 | 
			
		||||
 | 
			
		||||
Signed-off-by: Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
--- a/include/net/netfilter/nf_flow_table.h
 | 
			
		||||
+++ b/include/net/netfilter/nf_flow_table.h
 | 
			
		||||
@@ -76,6 +76,7 @@ struct flow_offload_tuple_rhash {
 | 
			
		||||
 #define FLOW_OFFLOAD_DYING	0x4
 | 
			
		||||
 #define FLOW_OFFLOAD_TEARDOWN	0x8
 | 
			
		||||
 #define FLOW_OFFLOAD_HW		0x10
 | 
			
		||||
+#define FLOW_OFFLOAD_KEEP	0x20
 | 
			
		||||
 
 | 
			
		||||
 struct flow_offload {
 | 
			
		||||
 	struct flow_offload_tuple_rhash		tuplehash[FLOW_OFFLOAD_DIR_MAX];
 | 
			
		||||
--- a/net/netfilter/nf_flow_table_core.c
 | 
			
		||||
+++ b/net/netfilter/nf_flow_table_core.c
 | 
			
		||||
@@ -332,7 +332,7 @@ static int nf_flow_offload_gc_step(struc
 | 
			
		||||
 		teardown = flow->flags & (FLOW_OFFLOAD_DYING |
 | 
			
		||||
 					  FLOW_OFFLOAD_TEARDOWN);
 | 
			
		||||
 
 | 
			
		||||
-		if (nf_flow_in_hw(flow) && !teardown)
 | 
			
		||||
+		if ((flow->flags & FLOW_OFFLOAD_KEEP) && !teardown)
 | 
			
		||||
 			continue;
 | 
			
		||||
 
 | 
			
		||||
 		if (nf_flow_has_expired(flow) || teardown)
 | 
			
		||||
		Reference in New Issue
	
	Block a user