#include #include #include #include #include #include #include #include #include #include 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; struct flow_offload_hw_path src; struct flow_offload_hw_path dest; }; static void flow_offload_check_ethernet(struct flow_offload_tuple *tuple, struct dst_entry *dst, struct flow_offload_hw_path *path) { struct net_device *dev = path->dev; struct neighbour *n; if (dev->type != ARPHRD_ETHER) return; memcpy(path->eth_src, path->dev->dev_addr, ETH_ALEN); n = dst_neigh_lookup(dst, &tuple->src_v4); if (!n) return; memcpy(path->eth_dest, n->ha, ETH_ALEN); path->flags |= FLOW_OFFLOAD_PATH_ETHERNET; neigh_release(n); } 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_device *dev; dev = dev_get_by_index_rcu(net, tuple->iifidx); if (!dev) return -ENOENT; path->dev = dev; flow_offload_check_ethernet(tuple, dst, path); if (dev->netdev_ops->ndo_flow_offload_check) return dev->netdev_ops->ndo_flow_offload_check(path); return 0; } static int do_flow_offload_hw(struct flow_offload_hw *offload) { 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; } 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) { 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) { mutex_lock(&nf_flow_offload_hw_mutex); switch (offload->type) { case FLOW_OFFLOAD_ADD: 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: do_flow_offload_hw(offload); break; } mutex_unlock(&nf_flow_offload_hw_mutex); flow_offload_hw_free(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 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 = flow_offload_hw_prepare(net, flow); if (!offload) return; nf_conntrack_get(&ct->ct_general); offload->type = FLOW_OFFLOAD_ADD; offload->ct = ct; flow_offload_queue_work(offload); } static void flow_offload_hw_del(struct net *net, struct flow_offload *flow) { struct flow_offload_hw *offload; offload = flow_offload_hw_prepare(net, flow); if (!offload) return; offload->type = FLOW_OFFLOAD_DEL; 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) flow_offload_hw_free(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 "); MODULE_ALIAS("nf-flow-table-hw");