You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
8.6 KiB
303 lines
8.6 KiB
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;
|
|
-
|
|
- mutex_lock(&nf_flow_offload_hw_mutex);
|
|
- ret = indev->netdev_ops->ndo_flow_offload(type, flow);
|
|
- mutex_unlock(&nf_flow_offload_hw_mutex);
|
|
+ if (dev->type != ARPHRD_ETHER)
|
|
+ return;
|
|
|
|
- dev_put(indev);
|
|
+ memcpy(path->eth_src, path->dev->dev_addr, ETH_ALEN);
|
|
+ n = dst_neigh_lookup(tuple->dst_cache, &tuple->src_v4);
|
|
+ if (!n)
|
|
+ return;
|
|
|
|
- return ret;
|
|
+ memcpy(path->eth_dest, n->ha, ETH_ALEN);
|
|
+ path->flags |= FLOW_OFFLOAD_PATH_ETHERNET;
|
|
+ neigh_release(n);
|
|
}
|
|
|
|
-static void flow_offload_hw_work_add(struct flow_offload_hw *offload)
|
|
+static int flow_offload_check_path(struct net *net,
|
|
+ struct flow_offload_tuple *tuple,
|
|
+ struct flow_offload_hw_path *path)
|
|
{
|
|
- struct net *net;
|
|
- int ret;
|
|
+ struct net_device *dev;
|
|
|
|
- if (nf_ct_is_dying(offload->ct))
|
|
- return;
|
|
+ dev = dev_get_by_index_rcu(net, tuple->iifidx);
|
|
+ if (!dev)
|
|
+ return -ENOENT;
|
|
+
|
|
+ path->dev = dev;
|
|
+ flow_offload_check_ethernet(tuple, path);
|
|
|
|
- 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;
|
|
+ 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_del(struct flow_offload_hw *offload)
|
|
+static int do_flow_offload_hw(struct flow_offload_hw *offload)
|
|
{
|
|
- struct net *net = read_pnet(&offload->flow_hw_net);
|
|
+ struct net_device *src_dev = offload->src.dev;
|
|
+ struct net_device *dest_dev = offload->dest.dev;
|
|
+ int ret;
|
|
+
|
|
+ ret = src_dev->netdev_ops->ndo_flow_offload(offload->type,
|
|
+ offload->flow,
|
|
+ &offload->src,
|
|
+ &offload->dest);
|
|
+
|
|
+ /* restore devices in case the driver mangled them */
|
|
+ offload->src.dev = src_dev;
|
|
+ offload->dest.dev = dest_dev;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
|
|
- do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_DEL);
|
|
+static void flow_offload_hw_free(struct flow_offload_hw *offload)
|
|
+{
|
|
+ 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);
|
|
|