123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- 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
- @@ -828,6 +828,7 @@ struct xfrmdev_ops {
- #endif
-
- struct flow_offload;
- +struct flow_offload_hw_path;
-
- enum flow_offload_type {
- FLOW_OFFLOAD_ADD = 0,
- @@ -1065,8 +1066,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);
- @@ -1293,8 +1301,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,77 @@ 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 dst_entry *dst,
- + 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(dst, &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 dst_entry *dst,
- + 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, dst, 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;
-
- - do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_DEL);
- + return ret;
- +}
- +
- +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 +102,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 +130,56 @@ 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_s, *tuple_d;
- + struct flow_offload_hw *offload = NULL;
- +
- + rcu_read_lock_bh();
- +
- + tuple_s = &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple;
- + tuple_d = &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple;
- +
- + if (flow_offload_check_path(net, tuple_s, tuple_d->dst_cache, &src))
- + goto out;
- +
- + if (flow_offload_check_path(net, tuple_d, tuple_s->dst_cache, &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 +188,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 +219,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);
|