18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * net/sched/cls_route.c ROUTE4 classifier. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/slab.h> 108c2ecf20Sopenharmony_ci#include <linux/types.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/string.h> 138c2ecf20Sopenharmony_ci#include <linux/errno.h> 148c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 158c2ecf20Sopenharmony_ci#include <net/dst.h> 168c2ecf20Sopenharmony_ci#include <net/route.h> 178c2ecf20Sopenharmony_ci#include <net/netlink.h> 188c2ecf20Sopenharmony_ci#include <net/act_api.h> 198c2ecf20Sopenharmony_ci#include <net/pkt_cls.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* 228c2ecf20Sopenharmony_ci * 1. For now we assume that route tags < 256. 238c2ecf20Sopenharmony_ci * It allows to use direct table lookups, instead of hash tables. 248c2ecf20Sopenharmony_ci * 2. For now we assume that "from TAG" and "fromdev DEV" statements 258c2ecf20Sopenharmony_ci * are mutually exclusive. 268c2ecf20Sopenharmony_ci * 3. "to TAG from ANY" has higher priority, than "to ANY from XXX" 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_cistruct route4_fastmap { 298c2ecf20Sopenharmony_ci struct route4_filter *filter; 308c2ecf20Sopenharmony_ci u32 id; 318c2ecf20Sopenharmony_ci int iif; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct route4_head { 358c2ecf20Sopenharmony_ci struct route4_fastmap fastmap[16]; 368c2ecf20Sopenharmony_ci struct route4_bucket __rcu *table[256 + 1]; 378c2ecf20Sopenharmony_ci struct rcu_head rcu; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistruct route4_bucket { 418c2ecf20Sopenharmony_ci /* 16 FROM buckets + 16 IIF buckets + 1 wildcard bucket */ 428c2ecf20Sopenharmony_ci struct route4_filter __rcu *ht[16 + 16 + 1]; 438c2ecf20Sopenharmony_ci struct rcu_head rcu; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistruct route4_filter { 478c2ecf20Sopenharmony_ci struct route4_filter __rcu *next; 488c2ecf20Sopenharmony_ci u32 id; 498c2ecf20Sopenharmony_ci int iif; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci struct tcf_result res; 528c2ecf20Sopenharmony_ci struct tcf_exts exts; 538c2ecf20Sopenharmony_ci u32 handle; 548c2ecf20Sopenharmony_ci struct route4_bucket *bkt; 558c2ecf20Sopenharmony_ci struct tcf_proto *tp; 568c2ecf20Sopenharmony_ci struct rcu_work rwork; 578c2ecf20Sopenharmony_ci}; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci#define ROUTE4_FAILURE ((struct route4_filter *)(-1L)) 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic inline int route4_fastmap_hash(u32 id, int iif) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci return id & 0xF; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(fastmap_lock); 678c2ecf20Sopenharmony_cistatic void 688c2ecf20Sopenharmony_ciroute4_reset_fastmap(struct route4_head *head) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci spin_lock_bh(&fastmap_lock); 718c2ecf20Sopenharmony_ci memset(head->fastmap, 0, sizeof(head->fastmap)); 728c2ecf20Sopenharmony_ci spin_unlock_bh(&fastmap_lock); 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic void 768c2ecf20Sopenharmony_ciroute4_set_fastmap(struct route4_head *head, u32 id, int iif, 778c2ecf20Sopenharmony_ci struct route4_filter *f) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci int h = route4_fastmap_hash(id, iif); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci /* fastmap updates must look atomic to aling id, iff, filter */ 828c2ecf20Sopenharmony_ci spin_lock_bh(&fastmap_lock); 838c2ecf20Sopenharmony_ci head->fastmap[h].id = id; 848c2ecf20Sopenharmony_ci head->fastmap[h].iif = iif; 858c2ecf20Sopenharmony_ci head->fastmap[h].filter = f; 868c2ecf20Sopenharmony_ci spin_unlock_bh(&fastmap_lock); 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic inline int route4_hash_to(u32 id) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci return id & 0xFF; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic inline int route4_hash_from(u32 id) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci return (id >> 16) & 0xF; 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic inline int route4_hash_iif(int iif) 1008c2ecf20Sopenharmony_ci{ 1018c2ecf20Sopenharmony_ci return 16 + ((iif >> 16) & 0xF); 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic inline int route4_hash_wild(void) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci return 32; 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci#define ROUTE4_APPLY_RESULT() \ 1108c2ecf20Sopenharmony_ci{ \ 1118c2ecf20Sopenharmony_ci *res = f->res; \ 1128c2ecf20Sopenharmony_ci if (tcf_exts_has_actions(&f->exts)) { \ 1138c2ecf20Sopenharmony_ci int r = tcf_exts_exec(skb, &f->exts, res); \ 1148c2ecf20Sopenharmony_ci if (r < 0) { \ 1158c2ecf20Sopenharmony_ci dont_cache = 1; \ 1168c2ecf20Sopenharmony_ci continue; \ 1178c2ecf20Sopenharmony_ci } \ 1188c2ecf20Sopenharmony_ci return r; \ 1198c2ecf20Sopenharmony_ci } else if (!dont_cache) \ 1208c2ecf20Sopenharmony_ci route4_set_fastmap(head, id, iif, f); \ 1218c2ecf20Sopenharmony_ci return 0; \ 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic int route4_classify(struct sk_buff *skb, const struct tcf_proto *tp, 1258c2ecf20Sopenharmony_ci struct tcf_result *res) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct route4_head *head = rcu_dereference_bh(tp->root); 1288c2ecf20Sopenharmony_ci struct dst_entry *dst; 1298c2ecf20Sopenharmony_ci struct route4_bucket *b; 1308c2ecf20Sopenharmony_ci struct route4_filter *f; 1318c2ecf20Sopenharmony_ci u32 id, h; 1328c2ecf20Sopenharmony_ci int iif, dont_cache = 0; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci dst = skb_dst(skb); 1358c2ecf20Sopenharmony_ci if (!dst) 1368c2ecf20Sopenharmony_ci goto failure; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci id = dst->tclassid; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci iif = inet_iif(skb); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci h = route4_fastmap_hash(id, iif); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci spin_lock(&fastmap_lock); 1458c2ecf20Sopenharmony_ci if (id == head->fastmap[h].id && 1468c2ecf20Sopenharmony_ci iif == head->fastmap[h].iif && 1478c2ecf20Sopenharmony_ci (f = head->fastmap[h].filter) != NULL) { 1488c2ecf20Sopenharmony_ci if (f == ROUTE4_FAILURE) { 1498c2ecf20Sopenharmony_ci spin_unlock(&fastmap_lock); 1508c2ecf20Sopenharmony_ci goto failure; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci *res = f->res; 1548c2ecf20Sopenharmony_ci spin_unlock(&fastmap_lock); 1558c2ecf20Sopenharmony_ci return 0; 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci spin_unlock(&fastmap_lock); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci h = route4_hash_to(id); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cirestart: 1628c2ecf20Sopenharmony_ci b = rcu_dereference_bh(head->table[h]); 1638c2ecf20Sopenharmony_ci if (b) { 1648c2ecf20Sopenharmony_ci for (f = rcu_dereference_bh(b->ht[route4_hash_from(id)]); 1658c2ecf20Sopenharmony_ci f; 1668c2ecf20Sopenharmony_ci f = rcu_dereference_bh(f->next)) 1678c2ecf20Sopenharmony_ci if (f->id == id) 1688c2ecf20Sopenharmony_ci ROUTE4_APPLY_RESULT(); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci for (f = rcu_dereference_bh(b->ht[route4_hash_iif(iif)]); 1718c2ecf20Sopenharmony_ci f; 1728c2ecf20Sopenharmony_ci f = rcu_dereference_bh(f->next)) 1738c2ecf20Sopenharmony_ci if (f->iif == iif) 1748c2ecf20Sopenharmony_ci ROUTE4_APPLY_RESULT(); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci for (f = rcu_dereference_bh(b->ht[route4_hash_wild()]); 1778c2ecf20Sopenharmony_ci f; 1788c2ecf20Sopenharmony_ci f = rcu_dereference_bh(f->next)) 1798c2ecf20Sopenharmony_ci ROUTE4_APPLY_RESULT(); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci if (h < 256) { 1828c2ecf20Sopenharmony_ci h = 256; 1838c2ecf20Sopenharmony_ci id &= ~0xFFFF; 1848c2ecf20Sopenharmony_ci goto restart; 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci if (!dont_cache) 1888c2ecf20Sopenharmony_ci route4_set_fastmap(head, id, iif, ROUTE4_FAILURE); 1898c2ecf20Sopenharmony_cifailure: 1908c2ecf20Sopenharmony_ci return -1; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic inline u32 to_hash(u32 id) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci u32 h = id & 0xFF; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci if (id & 0x8000) 1988c2ecf20Sopenharmony_ci h += 256; 1998c2ecf20Sopenharmony_ci return h; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic inline u32 from_hash(u32 id) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci id &= 0xFFFF; 2058c2ecf20Sopenharmony_ci if (id == 0xFFFF) 2068c2ecf20Sopenharmony_ci return 32; 2078c2ecf20Sopenharmony_ci if (!(id & 0x8000)) { 2088c2ecf20Sopenharmony_ci if (id > 255) 2098c2ecf20Sopenharmony_ci return 256; 2108c2ecf20Sopenharmony_ci return id & 0xF; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci return 16 + (id & 0xF); 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic void *route4_get(struct tcf_proto *tp, u32 handle) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci struct route4_head *head = rtnl_dereference(tp->root); 2188c2ecf20Sopenharmony_ci struct route4_bucket *b; 2198c2ecf20Sopenharmony_ci struct route4_filter *f; 2208c2ecf20Sopenharmony_ci unsigned int h1, h2; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci h1 = to_hash(handle); 2238c2ecf20Sopenharmony_ci if (h1 > 256) 2248c2ecf20Sopenharmony_ci return NULL; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci h2 = from_hash(handle >> 16); 2278c2ecf20Sopenharmony_ci if (h2 > 32) 2288c2ecf20Sopenharmony_ci return NULL; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci b = rtnl_dereference(head->table[h1]); 2318c2ecf20Sopenharmony_ci if (b) { 2328c2ecf20Sopenharmony_ci for (f = rtnl_dereference(b->ht[h2]); 2338c2ecf20Sopenharmony_ci f; 2348c2ecf20Sopenharmony_ci f = rtnl_dereference(f->next)) 2358c2ecf20Sopenharmony_ci if (f->handle == handle) 2368c2ecf20Sopenharmony_ci return f; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci return NULL; 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_cistatic int route4_init(struct tcf_proto *tp) 2428c2ecf20Sopenharmony_ci{ 2438c2ecf20Sopenharmony_ci struct route4_head *head; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci head = kzalloc(sizeof(struct route4_head), GFP_KERNEL); 2468c2ecf20Sopenharmony_ci if (head == NULL) 2478c2ecf20Sopenharmony_ci return -ENOBUFS; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci rcu_assign_pointer(tp->root, head); 2508c2ecf20Sopenharmony_ci return 0; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic void __route4_delete_filter(struct route4_filter *f) 2548c2ecf20Sopenharmony_ci{ 2558c2ecf20Sopenharmony_ci tcf_exts_destroy(&f->exts); 2568c2ecf20Sopenharmony_ci tcf_exts_put_net(&f->exts); 2578c2ecf20Sopenharmony_ci kfree(f); 2588c2ecf20Sopenharmony_ci} 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic void route4_delete_filter_work(struct work_struct *work) 2618c2ecf20Sopenharmony_ci{ 2628c2ecf20Sopenharmony_ci struct route4_filter *f = container_of(to_rcu_work(work), 2638c2ecf20Sopenharmony_ci struct route4_filter, 2648c2ecf20Sopenharmony_ci rwork); 2658c2ecf20Sopenharmony_ci rtnl_lock(); 2668c2ecf20Sopenharmony_ci __route4_delete_filter(f); 2678c2ecf20Sopenharmony_ci rtnl_unlock(); 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cistatic void route4_queue_work(struct route4_filter *f) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci tcf_queue_work(&f->rwork, route4_delete_filter_work); 2738c2ecf20Sopenharmony_ci} 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic void route4_destroy(struct tcf_proto *tp, bool rtnl_held, 2768c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 2778c2ecf20Sopenharmony_ci{ 2788c2ecf20Sopenharmony_ci struct route4_head *head = rtnl_dereference(tp->root); 2798c2ecf20Sopenharmony_ci int h1, h2; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci if (head == NULL) 2828c2ecf20Sopenharmony_ci return; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci for (h1 = 0; h1 <= 256; h1++) { 2858c2ecf20Sopenharmony_ci struct route4_bucket *b; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci b = rtnl_dereference(head->table[h1]); 2888c2ecf20Sopenharmony_ci if (b) { 2898c2ecf20Sopenharmony_ci for (h2 = 0; h2 <= 32; h2++) { 2908c2ecf20Sopenharmony_ci struct route4_filter *f; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci while ((f = rtnl_dereference(b->ht[h2])) != NULL) { 2938c2ecf20Sopenharmony_ci struct route4_filter *next; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci next = rtnl_dereference(f->next); 2968c2ecf20Sopenharmony_ci RCU_INIT_POINTER(b->ht[h2], next); 2978c2ecf20Sopenharmony_ci tcf_unbind_filter(tp, &f->res); 2988c2ecf20Sopenharmony_ci if (tcf_exts_get_net(&f->exts)) 2998c2ecf20Sopenharmony_ci route4_queue_work(f); 3008c2ecf20Sopenharmony_ci else 3018c2ecf20Sopenharmony_ci __route4_delete_filter(f); 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci } 3048c2ecf20Sopenharmony_ci RCU_INIT_POINTER(head->table[h1], NULL); 3058c2ecf20Sopenharmony_ci kfree_rcu(b, rcu); 3068c2ecf20Sopenharmony_ci } 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci kfree_rcu(head, rcu); 3098c2ecf20Sopenharmony_ci} 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_cistatic int route4_delete(struct tcf_proto *tp, void *arg, bool *last, 3128c2ecf20Sopenharmony_ci bool rtnl_held, struct netlink_ext_ack *extack) 3138c2ecf20Sopenharmony_ci{ 3148c2ecf20Sopenharmony_ci struct route4_head *head = rtnl_dereference(tp->root); 3158c2ecf20Sopenharmony_ci struct route4_filter *f = arg; 3168c2ecf20Sopenharmony_ci struct route4_filter __rcu **fp; 3178c2ecf20Sopenharmony_ci struct route4_filter *nf; 3188c2ecf20Sopenharmony_ci struct route4_bucket *b; 3198c2ecf20Sopenharmony_ci unsigned int h = 0; 3208c2ecf20Sopenharmony_ci int i, h1; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci if (!head || !f) 3238c2ecf20Sopenharmony_ci return -EINVAL; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci h = f->handle; 3268c2ecf20Sopenharmony_ci b = f->bkt; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci fp = &b->ht[from_hash(h >> 16)]; 3298c2ecf20Sopenharmony_ci for (nf = rtnl_dereference(*fp); nf; 3308c2ecf20Sopenharmony_ci fp = &nf->next, nf = rtnl_dereference(*fp)) { 3318c2ecf20Sopenharmony_ci if (nf == f) { 3328c2ecf20Sopenharmony_ci /* unlink it */ 3338c2ecf20Sopenharmony_ci RCU_INIT_POINTER(*fp, rtnl_dereference(f->next)); 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci /* Remove any fastmap lookups that might ref filter 3368c2ecf20Sopenharmony_ci * notice we unlink'd the filter so we can't get it 3378c2ecf20Sopenharmony_ci * back in the fastmap. 3388c2ecf20Sopenharmony_ci */ 3398c2ecf20Sopenharmony_ci route4_reset_fastmap(head); 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci /* Delete it */ 3428c2ecf20Sopenharmony_ci tcf_unbind_filter(tp, &f->res); 3438c2ecf20Sopenharmony_ci tcf_exts_get_net(&f->exts); 3448c2ecf20Sopenharmony_ci tcf_queue_work(&f->rwork, route4_delete_filter_work); 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci /* Strip RTNL protected tree */ 3478c2ecf20Sopenharmony_ci for (i = 0; i <= 32; i++) { 3488c2ecf20Sopenharmony_ci struct route4_filter *rt; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci rt = rtnl_dereference(b->ht[i]); 3518c2ecf20Sopenharmony_ci if (rt) 3528c2ecf20Sopenharmony_ci goto out; 3538c2ecf20Sopenharmony_ci } 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci /* OK, session has no flows */ 3568c2ecf20Sopenharmony_ci RCU_INIT_POINTER(head->table[to_hash(h)], NULL); 3578c2ecf20Sopenharmony_ci kfree_rcu(b, rcu); 3588c2ecf20Sopenharmony_ci break; 3598c2ecf20Sopenharmony_ci } 3608c2ecf20Sopenharmony_ci } 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ciout: 3638c2ecf20Sopenharmony_ci *last = true; 3648c2ecf20Sopenharmony_ci for (h1 = 0; h1 <= 256; h1++) { 3658c2ecf20Sopenharmony_ci if (rcu_access_pointer(head->table[h1])) { 3668c2ecf20Sopenharmony_ci *last = false; 3678c2ecf20Sopenharmony_ci break; 3688c2ecf20Sopenharmony_ci } 3698c2ecf20Sopenharmony_ci } 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci return 0; 3728c2ecf20Sopenharmony_ci} 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_cistatic const struct nla_policy route4_policy[TCA_ROUTE4_MAX + 1] = { 3758c2ecf20Sopenharmony_ci [TCA_ROUTE4_CLASSID] = { .type = NLA_U32 }, 3768c2ecf20Sopenharmony_ci [TCA_ROUTE4_TO] = { .type = NLA_U32 }, 3778c2ecf20Sopenharmony_ci [TCA_ROUTE4_FROM] = { .type = NLA_U32 }, 3788c2ecf20Sopenharmony_ci [TCA_ROUTE4_IIF] = { .type = NLA_U32 }, 3798c2ecf20Sopenharmony_ci}; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_cistatic int route4_set_parms(struct net *net, struct tcf_proto *tp, 3828c2ecf20Sopenharmony_ci unsigned long base, struct route4_filter *f, 3838c2ecf20Sopenharmony_ci u32 handle, struct route4_head *head, 3848c2ecf20Sopenharmony_ci struct nlattr **tb, struct nlattr *est, int new, 3858c2ecf20Sopenharmony_ci bool ovr, struct netlink_ext_ack *extack) 3868c2ecf20Sopenharmony_ci{ 3878c2ecf20Sopenharmony_ci u32 id = 0, to = 0, nhandle = 0x8000; 3888c2ecf20Sopenharmony_ci struct route4_filter *fp; 3898c2ecf20Sopenharmony_ci unsigned int h1; 3908c2ecf20Sopenharmony_ci struct route4_bucket *b; 3918c2ecf20Sopenharmony_ci int err; 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, true, extack); 3948c2ecf20Sopenharmony_ci if (err < 0) 3958c2ecf20Sopenharmony_ci return err; 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci if (tb[TCA_ROUTE4_TO]) { 3988c2ecf20Sopenharmony_ci if (new && handle & 0x8000) 3998c2ecf20Sopenharmony_ci return -EINVAL; 4008c2ecf20Sopenharmony_ci to = nla_get_u32(tb[TCA_ROUTE4_TO]); 4018c2ecf20Sopenharmony_ci if (to > 0xFF) 4028c2ecf20Sopenharmony_ci return -EINVAL; 4038c2ecf20Sopenharmony_ci nhandle = to; 4048c2ecf20Sopenharmony_ci } 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci if (tb[TCA_ROUTE4_FROM]) { 4078c2ecf20Sopenharmony_ci if (tb[TCA_ROUTE4_IIF]) 4088c2ecf20Sopenharmony_ci return -EINVAL; 4098c2ecf20Sopenharmony_ci id = nla_get_u32(tb[TCA_ROUTE4_FROM]); 4108c2ecf20Sopenharmony_ci if (id > 0xFF) 4118c2ecf20Sopenharmony_ci return -EINVAL; 4128c2ecf20Sopenharmony_ci nhandle |= id << 16; 4138c2ecf20Sopenharmony_ci } else if (tb[TCA_ROUTE4_IIF]) { 4148c2ecf20Sopenharmony_ci id = nla_get_u32(tb[TCA_ROUTE4_IIF]); 4158c2ecf20Sopenharmony_ci if (id > 0x7FFF) 4168c2ecf20Sopenharmony_ci return -EINVAL; 4178c2ecf20Sopenharmony_ci nhandle |= (id | 0x8000) << 16; 4188c2ecf20Sopenharmony_ci } else 4198c2ecf20Sopenharmony_ci nhandle |= 0xFFFF << 16; 4208c2ecf20Sopenharmony_ci 4218c2ecf20Sopenharmony_ci if (handle && new) { 4228c2ecf20Sopenharmony_ci nhandle |= handle & 0x7F00; 4238c2ecf20Sopenharmony_ci if (nhandle != handle) 4248c2ecf20Sopenharmony_ci return -EINVAL; 4258c2ecf20Sopenharmony_ci } 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci if (!nhandle) { 4288c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Replacing with handle of 0 is invalid"); 4298c2ecf20Sopenharmony_ci return -EINVAL; 4308c2ecf20Sopenharmony_ci } 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci h1 = to_hash(nhandle); 4338c2ecf20Sopenharmony_ci b = rtnl_dereference(head->table[h1]); 4348c2ecf20Sopenharmony_ci if (!b) { 4358c2ecf20Sopenharmony_ci b = kzalloc(sizeof(struct route4_bucket), GFP_KERNEL); 4368c2ecf20Sopenharmony_ci if (b == NULL) 4378c2ecf20Sopenharmony_ci return -ENOBUFS; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci rcu_assign_pointer(head->table[h1], b); 4408c2ecf20Sopenharmony_ci } else { 4418c2ecf20Sopenharmony_ci unsigned int h2 = from_hash(nhandle >> 16); 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci for (fp = rtnl_dereference(b->ht[h2]); 4448c2ecf20Sopenharmony_ci fp; 4458c2ecf20Sopenharmony_ci fp = rtnl_dereference(fp->next)) 4468c2ecf20Sopenharmony_ci if (fp->handle == f->handle) 4478c2ecf20Sopenharmony_ci return -EEXIST; 4488c2ecf20Sopenharmony_ci } 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci if (tb[TCA_ROUTE4_TO]) 4518c2ecf20Sopenharmony_ci f->id = to; 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci if (tb[TCA_ROUTE4_FROM]) 4548c2ecf20Sopenharmony_ci f->id = to | id<<16; 4558c2ecf20Sopenharmony_ci else if (tb[TCA_ROUTE4_IIF]) 4568c2ecf20Sopenharmony_ci f->iif = id; 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci f->handle = nhandle; 4598c2ecf20Sopenharmony_ci f->bkt = b; 4608c2ecf20Sopenharmony_ci f->tp = tp; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci if (tb[TCA_ROUTE4_CLASSID]) { 4638c2ecf20Sopenharmony_ci f->res.classid = nla_get_u32(tb[TCA_ROUTE4_CLASSID]); 4648c2ecf20Sopenharmony_ci tcf_bind_filter(tp, &f->res, base); 4658c2ecf20Sopenharmony_ci } 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci return 0; 4688c2ecf20Sopenharmony_ci} 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_cistatic int route4_change(struct net *net, struct sk_buff *in_skb, 4718c2ecf20Sopenharmony_ci struct tcf_proto *tp, unsigned long base, u32 handle, 4728c2ecf20Sopenharmony_ci struct nlattr **tca, void **arg, bool ovr, 4738c2ecf20Sopenharmony_ci bool rtnl_held, struct netlink_ext_ack *extack) 4748c2ecf20Sopenharmony_ci{ 4758c2ecf20Sopenharmony_ci struct route4_head *head = rtnl_dereference(tp->root); 4768c2ecf20Sopenharmony_ci struct route4_filter __rcu **fp; 4778c2ecf20Sopenharmony_ci struct route4_filter *fold, *f1, *pfp, *f = NULL; 4788c2ecf20Sopenharmony_ci struct route4_bucket *b; 4798c2ecf20Sopenharmony_ci struct nlattr *opt = tca[TCA_OPTIONS]; 4808c2ecf20Sopenharmony_ci struct nlattr *tb[TCA_ROUTE4_MAX + 1]; 4818c2ecf20Sopenharmony_ci unsigned int h, th; 4828c2ecf20Sopenharmony_ci int err; 4838c2ecf20Sopenharmony_ci bool new = true; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci if (!handle) { 4868c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Creating with handle of 0 is invalid"); 4878c2ecf20Sopenharmony_ci return -EINVAL; 4888c2ecf20Sopenharmony_ci } 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci if (opt == NULL) 4918c2ecf20Sopenharmony_ci return handle ? -EINVAL : 0; 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_ROUTE4_MAX, opt, 4948c2ecf20Sopenharmony_ci route4_policy, NULL); 4958c2ecf20Sopenharmony_ci if (err < 0) 4968c2ecf20Sopenharmony_ci return err; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci fold = *arg; 4998c2ecf20Sopenharmony_ci if (fold && handle && fold->handle != handle) 5008c2ecf20Sopenharmony_ci return -EINVAL; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci err = -ENOBUFS; 5038c2ecf20Sopenharmony_ci f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL); 5048c2ecf20Sopenharmony_ci if (!f) 5058c2ecf20Sopenharmony_ci goto errout; 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci err = tcf_exts_init(&f->exts, net, TCA_ROUTE4_ACT, TCA_ROUTE4_POLICE); 5088c2ecf20Sopenharmony_ci if (err < 0) 5098c2ecf20Sopenharmony_ci goto errout; 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci if (fold) { 5128c2ecf20Sopenharmony_ci f->id = fold->id; 5138c2ecf20Sopenharmony_ci f->iif = fold->iif; 5148c2ecf20Sopenharmony_ci f->handle = fold->handle; 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ci f->tp = fold->tp; 5178c2ecf20Sopenharmony_ci f->bkt = fold->bkt; 5188c2ecf20Sopenharmony_ci new = false; 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci err = route4_set_parms(net, tp, base, f, handle, head, tb, 5228c2ecf20Sopenharmony_ci tca[TCA_RATE], new, ovr, extack); 5238c2ecf20Sopenharmony_ci if (err < 0) 5248c2ecf20Sopenharmony_ci goto errout; 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci h = from_hash(f->handle >> 16); 5278c2ecf20Sopenharmony_ci fp = &f->bkt->ht[h]; 5288c2ecf20Sopenharmony_ci for (pfp = rtnl_dereference(*fp); 5298c2ecf20Sopenharmony_ci (f1 = rtnl_dereference(*fp)) != NULL; 5308c2ecf20Sopenharmony_ci fp = &f1->next) 5318c2ecf20Sopenharmony_ci if (f->handle < f1->handle) 5328c2ecf20Sopenharmony_ci break; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci tcf_block_netif_keep_dst(tp->chain->block); 5358c2ecf20Sopenharmony_ci rcu_assign_pointer(f->next, f1); 5368c2ecf20Sopenharmony_ci rcu_assign_pointer(*fp, f); 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci if (fold) { 5398c2ecf20Sopenharmony_ci th = to_hash(fold->handle); 5408c2ecf20Sopenharmony_ci h = from_hash(fold->handle >> 16); 5418c2ecf20Sopenharmony_ci b = rtnl_dereference(head->table[th]); 5428c2ecf20Sopenharmony_ci if (b) { 5438c2ecf20Sopenharmony_ci fp = &b->ht[h]; 5448c2ecf20Sopenharmony_ci for (pfp = rtnl_dereference(*fp); pfp; 5458c2ecf20Sopenharmony_ci fp = &pfp->next, pfp = rtnl_dereference(*fp)) { 5468c2ecf20Sopenharmony_ci if (pfp == fold) { 5478c2ecf20Sopenharmony_ci rcu_assign_pointer(*fp, fold->next); 5488c2ecf20Sopenharmony_ci break; 5498c2ecf20Sopenharmony_ci } 5508c2ecf20Sopenharmony_ci } 5518c2ecf20Sopenharmony_ci } 5528c2ecf20Sopenharmony_ci } 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci route4_reset_fastmap(head); 5558c2ecf20Sopenharmony_ci *arg = f; 5568c2ecf20Sopenharmony_ci if (fold) { 5578c2ecf20Sopenharmony_ci tcf_unbind_filter(tp, &fold->res); 5588c2ecf20Sopenharmony_ci tcf_exts_get_net(&fold->exts); 5598c2ecf20Sopenharmony_ci tcf_queue_work(&fold->rwork, route4_delete_filter_work); 5608c2ecf20Sopenharmony_ci } 5618c2ecf20Sopenharmony_ci return 0; 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_cierrout: 5648c2ecf20Sopenharmony_ci if (f) 5658c2ecf20Sopenharmony_ci tcf_exts_destroy(&f->exts); 5668c2ecf20Sopenharmony_ci kfree(f); 5678c2ecf20Sopenharmony_ci return err; 5688c2ecf20Sopenharmony_ci} 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_cistatic void route4_walk(struct tcf_proto *tp, struct tcf_walker *arg, 5718c2ecf20Sopenharmony_ci bool rtnl_held) 5728c2ecf20Sopenharmony_ci{ 5738c2ecf20Sopenharmony_ci struct route4_head *head = rtnl_dereference(tp->root); 5748c2ecf20Sopenharmony_ci unsigned int h, h1; 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci if (head == NULL || arg->stop) 5778c2ecf20Sopenharmony_ci return; 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci for (h = 0; h <= 256; h++) { 5808c2ecf20Sopenharmony_ci struct route4_bucket *b = rtnl_dereference(head->table[h]); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci if (b) { 5838c2ecf20Sopenharmony_ci for (h1 = 0; h1 <= 32; h1++) { 5848c2ecf20Sopenharmony_ci struct route4_filter *f; 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci for (f = rtnl_dereference(b->ht[h1]); 5878c2ecf20Sopenharmony_ci f; 5888c2ecf20Sopenharmony_ci f = rtnl_dereference(f->next)) { 5898c2ecf20Sopenharmony_ci if (arg->count < arg->skip) { 5908c2ecf20Sopenharmony_ci arg->count++; 5918c2ecf20Sopenharmony_ci continue; 5928c2ecf20Sopenharmony_ci } 5938c2ecf20Sopenharmony_ci if (arg->fn(tp, f, arg) < 0) { 5948c2ecf20Sopenharmony_ci arg->stop = 1; 5958c2ecf20Sopenharmony_ci return; 5968c2ecf20Sopenharmony_ci } 5978c2ecf20Sopenharmony_ci arg->count++; 5988c2ecf20Sopenharmony_ci } 5998c2ecf20Sopenharmony_ci } 6008c2ecf20Sopenharmony_ci } 6018c2ecf20Sopenharmony_ci } 6028c2ecf20Sopenharmony_ci} 6038c2ecf20Sopenharmony_ci 6048c2ecf20Sopenharmony_cistatic int route4_dump(struct net *net, struct tcf_proto *tp, void *fh, 6058c2ecf20Sopenharmony_ci struct sk_buff *skb, struct tcmsg *t, bool rtnl_held) 6068c2ecf20Sopenharmony_ci{ 6078c2ecf20Sopenharmony_ci struct route4_filter *f = fh; 6088c2ecf20Sopenharmony_ci struct nlattr *nest; 6098c2ecf20Sopenharmony_ci u32 id; 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_ci if (f == NULL) 6128c2ecf20Sopenharmony_ci return skb->len; 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci t->tcm_handle = f->handle; 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci nest = nla_nest_start_noflag(skb, TCA_OPTIONS); 6178c2ecf20Sopenharmony_ci if (nest == NULL) 6188c2ecf20Sopenharmony_ci goto nla_put_failure; 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_ci if (!(f->handle & 0x8000)) { 6218c2ecf20Sopenharmony_ci id = f->id & 0xFF; 6228c2ecf20Sopenharmony_ci if (nla_put_u32(skb, TCA_ROUTE4_TO, id)) 6238c2ecf20Sopenharmony_ci goto nla_put_failure; 6248c2ecf20Sopenharmony_ci } 6258c2ecf20Sopenharmony_ci if (f->handle & 0x80000000) { 6268c2ecf20Sopenharmony_ci if ((f->handle >> 16) != 0xFFFF && 6278c2ecf20Sopenharmony_ci nla_put_u32(skb, TCA_ROUTE4_IIF, f->iif)) 6288c2ecf20Sopenharmony_ci goto nla_put_failure; 6298c2ecf20Sopenharmony_ci } else { 6308c2ecf20Sopenharmony_ci id = f->id >> 16; 6318c2ecf20Sopenharmony_ci if (nla_put_u32(skb, TCA_ROUTE4_FROM, id)) 6328c2ecf20Sopenharmony_ci goto nla_put_failure; 6338c2ecf20Sopenharmony_ci } 6348c2ecf20Sopenharmony_ci if (f->res.classid && 6358c2ecf20Sopenharmony_ci nla_put_u32(skb, TCA_ROUTE4_CLASSID, f->res.classid)) 6368c2ecf20Sopenharmony_ci goto nla_put_failure; 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_ci if (tcf_exts_dump(skb, &f->exts) < 0) 6398c2ecf20Sopenharmony_ci goto nla_put_failure; 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ci nla_nest_end(skb, nest); 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_ci if (tcf_exts_dump_stats(skb, &f->exts) < 0) 6448c2ecf20Sopenharmony_ci goto nla_put_failure; 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_ci return skb->len; 6478c2ecf20Sopenharmony_ci 6488c2ecf20Sopenharmony_cinla_put_failure: 6498c2ecf20Sopenharmony_ci nla_nest_cancel(skb, nest); 6508c2ecf20Sopenharmony_ci return -1; 6518c2ecf20Sopenharmony_ci} 6528c2ecf20Sopenharmony_ci 6538c2ecf20Sopenharmony_cistatic void route4_bind_class(void *fh, u32 classid, unsigned long cl, void *q, 6548c2ecf20Sopenharmony_ci unsigned long base) 6558c2ecf20Sopenharmony_ci{ 6568c2ecf20Sopenharmony_ci struct route4_filter *f = fh; 6578c2ecf20Sopenharmony_ci 6588c2ecf20Sopenharmony_ci if (f && f->res.classid == classid) { 6598c2ecf20Sopenharmony_ci if (cl) 6608c2ecf20Sopenharmony_ci __tcf_bind_filter(q, &f->res, base); 6618c2ecf20Sopenharmony_ci else 6628c2ecf20Sopenharmony_ci __tcf_unbind_filter(q, &f->res); 6638c2ecf20Sopenharmony_ci } 6648c2ecf20Sopenharmony_ci} 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_cistatic struct tcf_proto_ops cls_route4_ops __read_mostly = { 6678c2ecf20Sopenharmony_ci .kind = "route", 6688c2ecf20Sopenharmony_ci .classify = route4_classify, 6698c2ecf20Sopenharmony_ci .init = route4_init, 6708c2ecf20Sopenharmony_ci .destroy = route4_destroy, 6718c2ecf20Sopenharmony_ci .get = route4_get, 6728c2ecf20Sopenharmony_ci .change = route4_change, 6738c2ecf20Sopenharmony_ci .delete = route4_delete, 6748c2ecf20Sopenharmony_ci .walk = route4_walk, 6758c2ecf20Sopenharmony_ci .dump = route4_dump, 6768c2ecf20Sopenharmony_ci .bind_class = route4_bind_class, 6778c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 6788c2ecf20Sopenharmony_ci}; 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_cistatic int __init init_route4(void) 6818c2ecf20Sopenharmony_ci{ 6828c2ecf20Sopenharmony_ci return register_tcf_proto_ops(&cls_route4_ops); 6838c2ecf20Sopenharmony_ci} 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_cistatic void __exit exit_route4(void) 6868c2ecf20Sopenharmony_ci{ 6878c2ecf20Sopenharmony_ci unregister_tcf_proto_ops(&cls_route4_ops); 6888c2ecf20Sopenharmony_ci} 6898c2ecf20Sopenharmony_ci 6908c2ecf20Sopenharmony_cimodule_init(init_route4) 6918c2ecf20Sopenharmony_cimodule_exit(exit_route4) 6928c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 693