11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * net/sched/cls_route.c ROUTE4 classifier. 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * This program is free software; you can redistribute it and/or 51da177e4SLinus Torvalds * modify it under the terms of the GNU General Public License 61da177e4SLinus Torvalds * as published by the Free Software Foundation; either version 71da177e4SLinus Torvalds * 2 of the License, or (at your option) any later version. 81da177e4SLinus Torvalds * 91da177e4SLinus Torvalds * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> 101da177e4SLinus Torvalds */ 111da177e4SLinus Torvalds 121da177e4SLinus Torvalds #include <linux/module.h> 135a0e3ad6STejun Heo #include <linux/slab.h> 141da177e4SLinus Torvalds #include <linux/types.h> 151da177e4SLinus Torvalds #include <linux/kernel.h> 161da177e4SLinus Torvalds #include <linux/string.h> 171da177e4SLinus Torvalds #include <linux/errno.h> 181da177e4SLinus Torvalds #include <linux/skbuff.h> 190ba48053SPatrick McHardy #include <net/dst.h> 200ba48053SPatrick McHardy #include <net/route.h> 210ba48053SPatrick McHardy #include <net/netlink.h> 221da177e4SLinus Torvalds #include <net/act_api.h> 231da177e4SLinus Torvalds #include <net/pkt_cls.h> 241da177e4SLinus Torvalds 251da177e4SLinus Torvalds /* 26cc7ec456SEric Dumazet * 1. For now we assume that route tags < 256. 27cc7ec456SEric Dumazet * It allows to use direct table lookups, instead of hash tables. 28cc7ec456SEric Dumazet * 2. For now we assume that "from TAG" and "fromdev DEV" statements 29cc7ec456SEric Dumazet * are mutually exclusive. 30cc7ec456SEric Dumazet * 3. "to TAG from ANY" has higher priority, than "to ANY from XXX" 311da177e4SLinus Torvalds */ 32cc7ec456SEric Dumazet struct route4_fastmap { 331da177e4SLinus Torvalds struct route4_filter *filter; 341da177e4SLinus Torvalds u32 id; 351da177e4SLinus Torvalds int iif; 361da177e4SLinus Torvalds }; 371da177e4SLinus Torvalds 38cc7ec456SEric Dumazet struct route4_head { 391da177e4SLinus Torvalds struct route4_fastmap fastmap[16]; 401109c005SJohn Fastabend struct route4_bucket __rcu *table[256 + 1]; 411109c005SJohn Fastabend struct rcu_head rcu; 421da177e4SLinus Torvalds }; 431da177e4SLinus Torvalds 44cc7ec456SEric Dumazet struct route4_bucket { 451da177e4SLinus Torvalds /* 16 FROM buckets + 16 IIF buckets + 1 wildcard bucket */ 461109c005SJohn Fastabend struct route4_filter __rcu *ht[16 + 16 + 1]; 471109c005SJohn Fastabend struct rcu_head rcu; 481da177e4SLinus Torvalds }; 491da177e4SLinus Torvalds 50cc7ec456SEric Dumazet struct route4_filter { 511109c005SJohn Fastabend struct route4_filter __rcu *next; 521da177e4SLinus Torvalds u32 id; 531da177e4SLinus Torvalds int iif; 541da177e4SLinus Torvalds 551da177e4SLinus Torvalds struct tcf_result res; 561da177e4SLinus Torvalds struct tcf_exts exts; 571da177e4SLinus Torvalds u32 handle; 581da177e4SLinus Torvalds struct route4_bucket *bkt; 591109c005SJohn Fastabend struct tcf_proto *tp; 60aaa908ffSCong Wang struct rcu_work rwork; 61c2f3f31dSCong Wang }; 621da177e4SLinus Torvalds 631da177e4SLinus Torvalds #define ROUTE4_FAILURE ((struct route4_filter *)(-1L)) 641da177e4SLinus Torvalds 65cc7ec456SEric Dumazet static inline int route4_fastmap_hash(u32 id, int iif) 661da177e4SLinus Torvalds { 671da177e4SLinus Torvalds return id & 0xF; 681da177e4SLinus Torvalds } 691da177e4SLinus Torvalds 701109c005SJohn Fastabend static DEFINE_SPINLOCK(fastmap_lock); 71cc7ec456SEric Dumazet static void 721109c005SJohn Fastabend route4_reset_fastmap(struct route4_head *head) 731da177e4SLinus Torvalds { 741109c005SJohn Fastabend spin_lock_bh(&fastmap_lock); 751da177e4SLinus Torvalds memset(head->fastmap, 0, sizeof(head->fastmap)); 761109c005SJohn Fastabend spin_unlock_bh(&fastmap_lock); 771da177e4SLinus Torvalds } 781da177e4SLinus Torvalds 79cc7ec456SEric Dumazet static void 801da177e4SLinus Torvalds route4_set_fastmap(struct route4_head *head, u32 id, int iif, 811da177e4SLinus Torvalds struct route4_filter *f) 821da177e4SLinus Torvalds { 831da177e4SLinus Torvalds int h = route4_fastmap_hash(id, iif); 84cc7ec456SEric Dumazet 851109c005SJohn Fastabend /* fastmap updates must look atomic to aling id, iff, filter */ 861109c005SJohn Fastabend spin_lock_bh(&fastmap_lock); 871da177e4SLinus Torvalds head->fastmap[h].id = id; 881da177e4SLinus Torvalds head->fastmap[h].iif = iif; 891da177e4SLinus Torvalds head->fastmap[h].filter = f; 901109c005SJohn Fastabend spin_unlock_bh(&fastmap_lock); 911da177e4SLinus Torvalds } 921da177e4SLinus Torvalds 93cc7ec456SEric Dumazet static inline int route4_hash_to(u32 id) 941da177e4SLinus Torvalds { 951da177e4SLinus Torvalds return id & 0xFF; 961da177e4SLinus Torvalds } 971da177e4SLinus Torvalds 98cc7ec456SEric Dumazet static inline int route4_hash_from(u32 id) 991da177e4SLinus Torvalds { 1001da177e4SLinus Torvalds return (id >> 16) & 0xF; 1011da177e4SLinus Torvalds } 1021da177e4SLinus Torvalds 103cc7ec456SEric Dumazet static inline int route4_hash_iif(int iif) 1041da177e4SLinus Torvalds { 1051da177e4SLinus Torvalds return 16 + ((iif >> 16) & 0xF); 1061da177e4SLinus Torvalds } 1071da177e4SLinus Torvalds 108cc7ec456SEric Dumazet static inline int route4_hash_wild(void) 1091da177e4SLinus Torvalds { 1101da177e4SLinus Torvalds return 32; 1111da177e4SLinus Torvalds } 1121da177e4SLinus Torvalds 1131da177e4SLinus Torvalds #define ROUTE4_APPLY_RESULT() \ 1141da177e4SLinus Torvalds { \ 1151da177e4SLinus Torvalds *res = f->res; \ 1166fc6d06eSJiri Pirko if (tcf_exts_has_actions(&f->exts)) { \ 1171da177e4SLinus Torvalds int r = tcf_exts_exec(skb, &f->exts, res); \ 1181da177e4SLinus Torvalds if (r < 0) { \ 1191da177e4SLinus Torvalds dont_cache = 1; \ 1201da177e4SLinus Torvalds continue; \ 1211da177e4SLinus Torvalds } \ 1221da177e4SLinus Torvalds return r; \ 1231da177e4SLinus Torvalds } else if (!dont_cache) \ 1241da177e4SLinus Torvalds route4_set_fastmap(head, id, iif, f); \ 1251da177e4SLinus Torvalds return 0; \ 1261da177e4SLinus Torvalds } 1271da177e4SLinus Torvalds 128dc7f9f6eSEric Dumazet static int route4_classify(struct sk_buff *skb, const struct tcf_proto *tp, 1291da177e4SLinus Torvalds struct tcf_result *res) 1301da177e4SLinus Torvalds { 1311109c005SJohn Fastabend struct route4_head *head = rcu_dereference_bh(tp->root); 1321da177e4SLinus Torvalds struct dst_entry *dst; 1331da177e4SLinus Torvalds struct route4_bucket *b; 1341da177e4SLinus Torvalds struct route4_filter *f; 1351da177e4SLinus Torvalds u32 id, h; 1361da177e4SLinus Torvalds int iif, dont_cache = 0; 1371da177e4SLinus Torvalds 138cc7ec456SEric Dumazet dst = skb_dst(skb); 139cc7ec456SEric Dumazet if (!dst) 1401da177e4SLinus Torvalds goto failure; 1411da177e4SLinus Torvalds 1421da177e4SLinus Torvalds id = dst->tclassid; 1431da177e4SLinus Torvalds 14492101b3bSDavid S. Miller iif = inet_iif(skb); 1451da177e4SLinus Torvalds 1461da177e4SLinus Torvalds h = route4_fastmap_hash(id, iif); 1471109c005SJohn Fastabend 1481109c005SJohn Fastabend spin_lock(&fastmap_lock); 1491da177e4SLinus Torvalds if (id == head->fastmap[h].id && 1501da177e4SLinus Torvalds iif == head->fastmap[h].iif && 1511da177e4SLinus Torvalds (f = head->fastmap[h].filter) != NULL) { 1521109c005SJohn Fastabend if (f == ROUTE4_FAILURE) { 1531109c005SJohn Fastabend spin_unlock(&fastmap_lock); 1541da177e4SLinus Torvalds goto failure; 1551109c005SJohn Fastabend } 1561da177e4SLinus Torvalds 1571da177e4SLinus Torvalds *res = f->res; 1581109c005SJohn Fastabend spin_unlock(&fastmap_lock); 1591da177e4SLinus Torvalds return 0; 1601da177e4SLinus Torvalds } 1611109c005SJohn Fastabend spin_unlock(&fastmap_lock); 1621da177e4SLinus Torvalds 1631da177e4SLinus Torvalds h = route4_hash_to(id); 1641da177e4SLinus Torvalds 1651da177e4SLinus Torvalds restart: 1661109c005SJohn Fastabend b = rcu_dereference_bh(head->table[h]); 167cc7ec456SEric Dumazet if (b) { 1681109c005SJohn Fastabend for (f = rcu_dereference_bh(b->ht[route4_hash_from(id)]); 1691109c005SJohn Fastabend f; 1701109c005SJohn Fastabend f = rcu_dereference_bh(f->next)) 1711da177e4SLinus Torvalds if (f->id == id) 1721da177e4SLinus Torvalds ROUTE4_APPLY_RESULT(); 1731da177e4SLinus Torvalds 1741109c005SJohn Fastabend for (f = rcu_dereference_bh(b->ht[route4_hash_iif(iif)]); 1751109c005SJohn Fastabend f; 1761109c005SJohn Fastabend f = rcu_dereference_bh(f->next)) 1771da177e4SLinus Torvalds if (f->iif == iif) 1781da177e4SLinus Torvalds ROUTE4_APPLY_RESULT(); 1791da177e4SLinus Torvalds 1801109c005SJohn Fastabend for (f = rcu_dereference_bh(b->ht[route4_hash_wild()]); 1811109c005SJohn Fastabend f; 1821109c005SJohn Fastabend f = rcu_dereference_bh(f->next)) 1831da177e4SLinus Torvalds ROUTE4_APPLY_RESULT(); 1841da177e4SLinus Torvalds } 1851da177e4SLinus Torvalds if (h < 256) { 1861da177e4SLinus Torvalds h = 256; 1871da177e4SLinus Torvalds id &= ~0xFFFF; 1881da177e4SLinus Torvalds goto restart; 1891da177e4SLinus Torvalds } 1901da177e4SLinus Torvalds 1911da177e4SLinus Torvalds if (!dont_cache) 1921da177e4SLinus Torvalds route4_set_fastmap(head, id, iif, ROUTE4_FAILURE); 1931da177e4SLinus Torvalds failure: 1941da177e4SLinus Torvalds return -1; 1951da177e4SLinus Torvalds } 1961da177e4SLinus Torvalds 1971da177e4SLinus Torvalds static inline u32 to_hash(u32 id) 1981da177e4SLinus Torvalds { 1991da177e4SLinus Torvalds u32 h = id & 0xFF; 200cc7ec456SEric Dumazet 2011da177e4SLinus Torvalds if (id & 0x8000) 2021da177e4SLinus Torvalds h += 256; 2031da177e4SLinus Torvalds return h; 2041da177e4SLinus Torvalds } 2051da177e4SLinus Torvalds 2061da177e4SLinus Torvalds static inline u32 from_hash(u32 id) 2071da177e4SLinus Torvalds { 2081da177e4SLinus Torvalds id &= 0xFFFF; 2091da177e4SLinus Torvalds if (id == 0xFFFF) 2101da177e4SLinus Torvalds return 32; 2111da177e4SLinus Torvalds if (!(id & 0x8000)) { 2121da177e4SLinus Torvalds if (id > 255) 2131da177e4SLinus Torvalds return 256; 2141da177e4SLinus Torvalds return id & 0xF; 2151da177e4SLinus Torvalds } 2161da177e4SLinus Torvalds return 16 + (id & 0xF); 2171da177e4SLinus Torvalds } 2181da177e4SLinus Torvalds 2198113c095SWANG Cong static void *route4_get(struct tcf_proto *tp, u32 handle) 2201da177e4SLinus Torvalds { 2211109c005SJohn Fastabend struct route4_head *head = rtnl_dereference(tp->root); 2221da177e4SLinus Torvalds struct route4_bucket *b; 2231da177e4SLinus Torvalds struct route4_filter *f; 224cc7ec456SEric Dumazet unsigned int h1, h2; 2251da177e4SLinus Torvalds 2261da177e4SLinus Torvalds h1 = to_hash(handle); 2271da177e4SLinus Torvalds if (h1 > 256) 2288113c095SWANG Cong return NULL; 2291da177e4SLinus Torvalds 2301da177e4SLinus Torvalds h2 = from_hash(handle >> 16); 2311da177e4SLinus Torvalds if (h2 > 32) 2328113c095SWANG Cong return NULL; 2331da177e4SLinus Torvalds 2341109c005SJohn Fastabend b = rtnl_dereference(head->table[h1]); 235cc7ec456SEric Dumazet if (b) { 2361109c005SJohn Fastabend for (f = rtnl_dereference(b->ht[h2]); 2371109c005SJohn Fastabend f; 2381109c005SJohn Fastabend f = rtnl_dereference(f->next)) 2391da177e4SLinus Torvalds if (f->handle == handle) 2408113c095SWANG Cong return f; 2411da177e4SLinus Torvalds } 2428113c095SWANG Cong return NULL; 2431da177e4SLinus Torvalds } 2441da177e4SLinus Torvalds 2451da177e4SLinus Torvalds static int route4_init(struct tcf_proto *tp) 2461da177e4SLinus Torvalds { 247a05c2d11SWANG Cong struct route4_head *head; 248a05c2d11SWANG Cong 249a05c2d11SWANG Cong head = kzalloc(sizeof(struct route4_head), GFP_KERNEL); 250a05c2d11SWANG Cong if (head == NULL) 251a05c2d11SWANG Cong return -ENOBUFS; 252a05c2d11SWANG Cong 253a05c2d11SWANG Cong rcu_assign_pointer(tp->root, head); 2541da177e4SLinus Torvalds return 0; 2551da177e4SLinus Torvalds } 2561da177e4SLinus Torvalds 2573fd51de5SCong Wang static void __route4_delete_filter(struct route4_filter *f) 2583fd51de5SCong Wang { 2593fd51de5SCong Wang tcf_exts_destroy(&f->exts); 2603fd51de5SCong Wang tcf_exts_put_net(&f->exts); 2613fd51de5SCong Wang kfree(f); 2623fd51de5SCong Wang } 2633fd51de5SCong Wang 264c2f3f31dSCong Wang static void route4_delete_filter_work(struct work_struct *work) 265c2f3f31dSCong Wang { 266aaa908ffSCong Wang struct route4_filter *f = container_of(to_rcu_work(work), 267aaa908ffSCong Wang struct route4_filter, 268aaa908ffSCong Wang rwork); 269c2f3f31dSCong Wang rtnl_lock(); 2703fd51de5SCong Wang __route4_delete_filter(f); 271c2f3f31dSCong Wang rtnl_unlock(); 272c2f3f31dSCong Wang } 273c2f3f31dSCong Wang 274aaa908ffSCong Wang static void route4_queue_work(struct route4_filter *f) 2751da177e4SLinus Torvalds { 276aaa908ffSCong Wang tcf_queue_work(&f->rwork, route4_delete_filter_work); 2771da177e4SLinus Torvalds } 2781da177e4SLinus Torvalds 27912db03b6SVlad Buslov static void route4_destroy(struct tcf_proto *tp, bool rtnl_held, 28012db03b6SVlad Buslov struct netlink_ext_ack *extack) 2811da177e4SLinus Torvalds { 2821109c005SJohn Fastabend struct route4_head *head = rtnl_dereference(tp->root); 2831da177e4SLinus Torvalds int h1, h2; 2841da177e4SLinus Torvalds 2851da177e4SLinus Torvalds if (head == NULL) 286763dbf63SWANG Cong return; 2871da177e4SLinus Torvalds 2881da177e4SLinus Torvalds for (h1 = 0; h1 <= 256; h1++) { 2891da177e4SLinus Torvalds struct route4_bucket *b; 2901da177e4SLinus Torvalds 2911109c005SJohn Fastabend b = rtnl_dereference(head->table[h1]); 292cc7ec456SEric Dumazet if (b) { 2931da177e4SLinus Torvalds for (h2 = 0; h2 <= 32; h2++) { 2941da177e4SLinus Torvalds struct route4_filter *f; 2951da177e4SLinus Torvalds 2961109c005SJohn Fastabend while ((f = rtnl_dereference(b->ht[h2])) != NULL) { 2971109c005SJohn Fastabend struct route4_filter *next; 2981109c005SJohn Fastabend 2991109c005SJohn Fastabend next = rtnl_dereference(f->next); 3001109c005SJohn Fastabend RCU_INIT_POINTER(b->ht[h2], next); 30118cdb37eSJohn Fastabend tcf_unbind_filter(tp, &f->res); 3023fd51de5SCong Wang if (tcf_exts_get_net(&f->exts)) 303aaa908ffSCong Wang route4_queue_work(f); 3043fd51de5SCong Wang else 3053fd51de5SCong Wang __route4_delete_filter(f); 3061da177e4SLinus Torvalds } 3071da177e4SLinus Torvalds } 3081109c005SJohn Fastabend RCU_INIT_POINTER(head->table[h1], NULL); 3091109c005SJohn Fastabend kfree_rcu(b, rcu); 3101da177e4SLinus Torvalds } 3111da177e4SLinus Torvalds } 3121109c005SJohn Fastabend kfree_rcu(head, rcu); 3131da177e4SLinus Torvalds } 3141da177e4SLinus Torvalds 315571acf21SAlexander Aring static int route4_delete(struct tcf_proto *tp, void *arg, bool *last, 31612db03b6SVlad Buslov bool rtnl_held, struct netlink_ext_ack *extack) 3171da177e4SLinus Torvalds { 3181109c005SJohn Fastabend struct route4_head *head = rtnl_dereference(tp->root); 3198113c095SWANG Cong struct route4_filter *f = arg; 3201109c005SJohn Fastabend struct route4_filter __rcu **fp; 3211109c005SJohn Fastabend struct route4_filter *nf; 3221da177e4SLinus Torvalds struct route4_bucket *b; 3231109c005SJohn Fastabend unsigned int h = 0; 324763dbf63SWANG Cong int i, h1; 3251da177e4SLinus Torvalds 3261da177e4SLinus Torvalds if (!head || !f) 3271da177e4SLinus Torvalds return -EINVAL; 3281da177e4SLinus Torvalds 3291da177e4SLinus Torvalds h = f->handle; 3301da177e4SLinus Torvalds b = f->bkt; 3311da177e4SLinus Torvalds 3321109c005SJohn Fastabend fp = &b->ht[from_hash(h >> 16)]; 3331109c005SJohn Fastabend for (nf = rtnl_dereference(*fp); nf; 3341109c005SJohn Fastabend fp = &nf->next, nf = rtnl_dereference(*fp)) { 3351109c005SJohn Fastabend if (nf == f) { 3361109c005SJohn Fastabend /* unlink it */ 3371109c005SJohn Fastabend RCU_INIT_POINTER(*fp, rtnl_dereference(f->next)); 3381da177e4SLinus Torvalds 3391109c005SJohn Fastabend /* Remove any fastmap lookups that might ref filter 3401109c005SJohn Fastabend * notice we unlink'd the filter so we can't get it 3411109c005SJohn Fastabend * back in the fastmap. 3421109c005SJohn Fastabend */ 3431109c005SJohn Fastabend route4_reset_fastmap(head); 3441da177e4SLinus Torvalds 3451109c005SJohn Fastabend /* Delete it */ 34618cdb37eSJohn Fastabend tcf_unbind_filter(tp, &f->res); 3473fd51de5SCong Wang tcf_exts_get_net(&f->exts); 348aaa908ffSCong Wang tcf_queue_work(&f->rwork, route4_delete_filter_work); 3491da177e4SLinus Torvalds 3501109c005SJohn Fastabend /* Strip RTNL protected tree */ 3511109c005SJohn Fastabend for (i = 0; i <= 32; i++) { 3521109c005SJohn Fastabend struct route4_filter *rt; 3531109c005SJohn Fastabend 3541109c005SJohn Fastabend rt = rtnl_dereference(b->ht[i]); 3551109c005SJohn Fastabend if (rt) 356763dbf63SWANG Cong goto out; 3571109c005SJohn Fastabend } 3581da177e4SLinus Torvalds 3591da177e4SLinus Torvalds /* OK, session has no flows */ 3601109c005SJohn Fastabend RCU_INIT_POINTER(head->table[to_hash(h)], NULL); 3611109c005SJohn Fastabend kfree_rcu(b, rcu); 362763dbf63SWANG Cong break; 363763dbf63SWANG Cong } 364763dbf63SWANG Cong } 3651da177e4SLinus Torvalds 366763dbf63SWANG Cong out: 367763dbf63SWANG Cong *last = true; 368763dbf63SWANG Cong for (h1 = 0; h1 <= 256; h1++) { 369763dbf63SWANG Cong if (rcu_access_pointer(head->table[h1])) { 370763dbf63SWANG Cong *last = false; 371763dbf63SWANG Cong break; 3721da177e4SLinus Torvalds } 3731da177e4SLinus Torvalds } 374763dbf63SWANG Cong 3751da177e4SLinus Torvalds return 0; 3761da177e4SLinus Torvalds } 3771da177e4SLinus Torvalds 3786fa8c014SPatrick McHardy static const struct nla_policy route4_policy[TCA_ROUTE4_MAX + 1] = { 3796fa8c014SPatrick McHardy [TCA_ROUTE4_CLASSID] = { .type = NLA_U32 }, 3806fa8c014SPatrick McHardy [TCA_ROUTE4_TO] = { .type = NLA_U32 }, 3816fa8c014SPatrick McHardy [TCA_ROUTE4_FROM] = { .type = NLA_U32 }, 3826fa8c014SPatrick McHardy [TCA_ROUTE4_IIF] = { .type = NLA_U32 }, 3836fa8c014SPatrick McHardy }; 3846fa8c014SPatrick McHardy 385c1b52739SBenjamin LaHaise static int route4_set_parms(struct net *net, struct tcf_proto *tp, 386c1b52739SBenjamin LaHaise unsigned long base, struct route4_filter *f, 387c1b52739SBenjamin LaHaise u32 handle, struct route4_head *head, 3882f7ef2f8SCong Wang struct nlattr **tb, struct nlattr *est, int new, 38950a56190SAlexander Aring bool ovr, struct netlink_ext_ack *extack) 3901da177e4SLinus Torvalds { 3911da177e4SLinus Torvalds u32 id = 0, to = 0, nhandle = 0x8000; 3921da177e4SLinus Torvalds struct route4_filter *fp; 3931da177e4SLinus Torvalds unsigned int h1; 3941da177e4SLinus Torvalds struct route4_bucket *b; 395b9a24bb7SWANG Cong int err; 3961da177e4SLinus Torvalds 397ec6743a1SVlad Buslov err = tcf_exts_validate(net, tp, tb, est, &f->exts, ovr, true, extack); 3981da177e4SLinus Torvalds if (err < 0) 3991da177e4SLinus Torvalds return err; 4001da177e4SLinus Torvalds 401add93b61SPatrick McHardy if (tb[TCA_ROUTE4_TO]) { 4021da177e4SLinus Torvalds if (new && handle & 0x8000) 4038c98d571SJiri Pirko return -EINVAL; 4041587bac4SPatrick McHardy to = nla_get_u32(tb[TCA_ROUTE4_TO]); 4051da177e4SLinus Torvalds if (to > 0xFF) 4068c98d571SJiri Pirko return -EINVAL; 4071da177e4SLinus Torvalds nhandle = to; 4081da177e4SLinus Torvalds } 4091da177e4SLinus Torvalds 410add93b61SPatrick McHardy if (tb[TCA_ROUTE4_FROM]) { 411add93b61SPatrick McHardy if (tb[TCA_ROUTE4_IIF]) 4128c98d571SJiri Pirko return -EINVAL; 4131587bac4SPatrick McHardy id = nla_get_u32(tb[TCA_ROUTE4_FROM]); 4141da177e4SLinus Torvalds if (id > 0xFF) 4158c98d571SJiri Pirko return -EINVAL; 4161da177e4SLinus Torvalds nhandle |= id << 16; 417add93b61SPatrick McHardy } else if (tb[TCA_ROUTE4_IIF]) { 4181587bac4SPatrick McHardy id = nla_get_u32(tb[TCA_ROUTE4_IIF]); 4191da177e4SLinus Torvalds if (id > 0x7FFF) 4208c98d571SJiri Pirko return -EINVAL; 4211da177e4SLinus Torvalds nhandle |= (id | 0x8000) << 16; 4221da177e4SLinus Torvalds } else 4231da177e4SLinus Torvalds nhandle |= 0xFFFF << 16; 4241da177e4SLinus Torvalds 4251da177e4SLinus Torvalds if (handle && new) { 4261da177e4SLinus Torvalds nhandle |= handle & 0x7F00; 4271da177e4SLinus Torvalds if (nhandle != handle) 4288c98d571SJiri Pirko return -EINVAL; 4291da177e4SLinus Torvalds } 4301da177e4SLinus Torvalds 4311da177e4SLinus Torvalds h1 = to_hash(nhandle); 4321109c005SJohn Fastabend b = rtnl_dereference(head->table[h1]); 433cc7ec456SEric Dumazet if (!b) { 4340da974f4SPanagiotis Issaris b = kzalloc(sizeof(struct route4_bucket), GFP_KERNEL); 4351da177e4SLinus Torvalds if (b == NULL) 4368c98d571SJiri Pirko return -ENOBUFS; 4371da177e4SLinus Torvalds 4381109c005SJohn Fastabend rcu_assign_pointer(head->table[h1], b); 4391da177e4SLinus Torvalds } else { 4401da177e4SLinus Torvalds unsigned int h2 = from_hash(nhandle >> 16); 441cc7ec456SEric Dumazet 4421109c005SJohn Fastabend for (fp = rtnl_dereference(b->ht[h2]); 4431109c005SJohn Fastabend fp; 4441109c005SJohn Fastabend fp = rtnl_dereference(fp->next)) 4451da177e4SLinus Torvalds if (fp->handle == f->handle) 4468c98d571SJiri Pirko return -EEXIST; 4471da177e4SLinus Torvalds } 4481da177e4SLinus Torvalds 449add93b61SPatrick McHardy if (tb[TCA_ROUTE4_TO]) 4501da177e4SLinus Torvalds f->id = to; 4511da177e4SLinus Torvalds 452add93b61SPatrick McHardy if (tb[TCA_ROUTE4_FROM]) 4531da177e4SLinus Torvalds f->id = to | id<<16; 454add93b61SPatrick McHardy else if (tb[TCA_ROUTE4_IIF]) 4551da177e4SLinus Torvalds f->iif = id; 4561da177e4SLinus Torvalds 4571da177e4SLinus Torvalds f->handle = nhandle; 4581da177e4SLinus Torvalds f->bkt = b; 4591109c005SJohn Fastabend f->tp = tp; 4601da177e4SLinus Torvalds 461add93b61SPatrick McHardy if (tb[TCA_ROUTE4_CLASSID]) { 4621587bac4SPatrick McHardy f->res.classid = nla_get_u32(tb[TCA_ROUTE4_CLASSID]); 4631da177e4SLinus Torvalds tcf_bind_filter(tp, &f->res, base); 4641da177e4SLinus Torvalds } 4651da177e4SLinus Torvalds 4661da177e4SLinus Torvalds return 0; 4671da177e4SLinus Torvalds } 4681da177e4SLinus Torvalds 469c1b52739SBenjamin LaHaise static int route4_change(struct net *net, struct sk_buff *in_skb, 4705a7a5555SJamal Hadi Salim struct tcf_proto *tp, unsigned long base, u32 handle, 4717306db38SAlexander Aring struct nlattr **tca, void **arg, bool ovr, 47212db03b6SVlad Buslov bool rtnl_held, struct netlink_ext_ack *extack) 4731da177e4SLinus Torvalds { 4741109c005SJohn Fastabend struct route4_head *head = rtnl_dereference(tp->root); 4751109c005SJohn Fastabend struct route4_filter __rcu **fp; 4761109c005SJohn Fastabend struct route4_filter *fold, *f1, *pfp, *f = NULL; 4771da177e4SLinus Torvalds struct route4_bucket *b; 478add93b61SPatrick McHardy struct nlattr *opt = tca[TCA_OPTIONS]; 479add93b61SPatrick McHardy struct nlattr *tb[TCA_ROUTE4_MAX + 1]; 4801da177e4SLinus Torvalds unsigned int h, th; 4811da177e4SLinus Torvalds int err; 4821109c005SJohn Fastabend bool new = true; 4831da177e4SLinus Torvalds 4841da177e4SLinus Torvalds if (opt == NULL) 4851da177e4SLinus Torvalds return handle ? -EINVAL : 0; 4861da177e4SLinus Torvalds 487fceb6435SJohannes Berg err = nla_parse_nested(tb, TCA_ROUTE4_MAX, opt, route4_policy, NULL); 488cee63723SPatrick McHardy if (err < 0) 489cee63723SPatrick McHardy return err; 4901da177e4SLinus Torvalds 4918113c095SWANG Cong fold = *arg; 4921109c005SJohn Fastabend if (fold && handle && fold->handle != handle) 4931da177e4SLinus Torvalds return -EINVAL; 4941da177e4SLinus Torvalds 4951da177e4SLinus Torvalds err = -ENOBUFS; 4960da974f4SPanagiotis Issaris f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL); 4971109c005SJohn Fastabend if (!f) 4981da177e4SLinus Torvalds goto errout; 4991da177e4SLinus Torvalds 50014215108SCong Wang err = tcf_exts_init(&f->exts, net, TCA_ROUTE4_ACT, TCA_ROUTE4_POLICE); 501b9a24bb7SWANG Cong if (err < 0) 502b9a24bb7SWANG Cong goto errout; 503b9a24bb7SWANG Cong 5041109c005SJohn Fastabend if (fold) { 5051109c005SJohn Fastabend f->id = fold->id; 5061109c005SJohn Fastabend f->iif = fold->iif; 5071109c005SJohn Fastabend f->res = fold->res; 5081109c005SJohn Fastabend f->handle = fold->handle; 5091109c005SJohn Fastabend 5101109c005SJohn Fastabend f->tp = fold->tp; 5111109c005SJohn Fastabend f->bkt = fold->bkt; 5121109c005SJohn Fastabend new = false; 5131109c005SJohn Fastabend } 5141109c005SJohn Fastabend 515c1b52739SBenjamin LaHaise err = route4_set_parms(net, tp, base, f, handle, head, tb, 51650a56190SAlexander Aring tca[TCA_RATE], new, ovr, extack); 5171da177e4SLinus Torvalds if (err < 0) 5181da177e4SLinus Torvalds goto errout; 5191da177e4SLinus Torvalds 5201da177e4SLinus Torvalds h = from_hash(f->handle >> 16); 5211109c005SJohn Fastabend fp = &f->bkt->ht[h]; 5221109c005SJohn Fastabend for (pfp = rtnl_dereference(*fp); 5231109c005SJohn Fastabend (f1 = rtnl_dereference(*fp)) != NULL; 5241109c005SJohn Fastabend fp = &f1->next) 5251da177e4SLinus Torvalds if (f->handle < f1->handle) 5261da177e4SLinus Torvalds break; 5271da177e4SLinus Torvalds 528f36fe1c4SJiri Pirko tcf_block_netif_keep_dst(tp->chain->block); 5291109c005SJohn Fastabend rcu_assign_pointer(f->next, f1); 5301109c005SJohn Fastabend rcu_assign_pointer(*fp, f); 5311da177e4SLinus Torvalds 5321109c005SJohn Fastabend if (fold && fold->handle && f->handle != fold->handle) { 5331109c005SJohn Fastabend th = to_hash(fold->handle); 5341109c005SJohn Fastabend h = from_hash(fold->handle >> 16); 5351109c005SJohn Fastabend b = rtnl_dereference(head->table[th]); 536cc7ec456SEric Dumazet if (b) { 5371109c005SJohn Fastabend fp = &b->ht[h]; 5381109c005SJohn Fastabend for (pfp = rtnl_dereference(*fp); pfp; 5391109c005SJohn Fastabend fp = &pfp->next, pfp = rtnl_dereference(*fp)) { 5401109c005SJohn Fastabend if (pfp == f) { 5411da177e4SLinus Torvalds *fp = f->next; 5421da177e4SLinus Torvalds break; 5431da177e4SLinus Torvalds } 5441da177e4SLinus Torvalds } 5451da177e4SLinus Torvalds } 5461da177e4SLinus Torvalds } 5471da177e4SLinus Torvalds 5481109c005SJohn Fastabend route4_reset_fastmap(head); 5498113c095SWANG Cong *arg = f; 55018cdb37eSJohn Fastabend if (fold) { 55118cdb37eSJohn Fastabend tcf_unbind_filter(tp, &fold->res); 5523fd51de5SCong Wang tcf_exts_get_net(&fold->exts); 553aaa908ffSCong Wang tcf_queue_work(&fold->rwork, route4_delete_filter_work); 55418cdb37eSJohn Fastabend } 5551da177e4SLinus Torvalds return 0; 5561da177e4SLinus Torvalds 5571da177e4SLinus Torvalds errout: 55821641c2eSWANG Cong if (f) 559b9a24bb7SWANG Cong tcf_exts_destroy(&f->exts); 5601da177e4SLinus Torvalds kfree(f); 5611da177e4SLinus Torvalds return err; 5621da177e4SLinus Torvalds } 5631da177e4SLinus Torvalds 56412db03b6SVlad Buslov static void route4_walk(struct tcf_proto *tp, struct tcf_walker *arg, 56512db03b6SVlad Buslov bool rtnl_held) 5661da177e4SLinus Torvalds { 5671109c005SJohn Fastabend struct route4_head *head = rtnl_dereference(tp->root); 568cc7ec456SEric Dumazet unsigned int h, h1; 5691da177e4SLinus Torvalds 5703027ff41SVlad Buslov if (head == NULL || arg->stop) 5711da177e4SLinus Torvalds return; 5721da177e4SLinus Torvalds 5731da177e4SLinus Torvalds for (h = 0; h <= 256; h++) { 5741109c005SJohn Fastabend struct route4_bucket *b = rtnl_dereference(head->table[h]); 5751da177e4SLinus Torvalds 5761da177e4SLinus Torvalds if (b) { 5771da177e4SLinus Torvalds for (h1 = 0; h1 <= 32; h1++) { 5781da177e4SLinus Torvalds struct route4_filter *f; 5791da177e4SLinus Torvalds 5801109c005SJohn Fastabend for (f = rtnl_dereference(b->ht[h1]); 5811109c005SJohn Fastabend f; 5821109c005SJohn Fastabend f = rtnl_dereference(f->next)) { 5831da177e4SLinus Torvalds if (arg->count < arg->skip) { 5841da177e4SLinus Torvalds arg->count++; 5851da177e4SLinus Torvalds continue; 5861da177e4SLinus Torvalds } 5878113c095SWANG Cong if (arg->fn(tp, f, arg) < 0) { 5881da177e4SLinus Torvalds arg->stop = 1; 5891da177e4SLinus Torvalds return; 5901da177e4SLinus Torvalds } 5911da177e4SLinus Torvalds arg->count++; 5921da177e4SLinus Torvalds } 5931da177e4SLinus Torvalds } 5941da177e4SLinus Torvalds } 5951da177e4SLinus Torvalds } 5961da177e4SLinus Torvalds } 5971da177e4SLinus Torvalds 5988113c095SWANG Cong static int route4_dump(struct net *net, struct tcf_proto *tp, void *fh, 59912db03b6SVlad Buslov struct sk_buff *skb, struct tcmsg *t, bool rtnl_held) 6001da177e4SLinus Torvalds { 6018113c095SWANG Cong struct route4_filter *f = fh; 6024b3550efSPatrick McHardy struct nlattr *nest; 6031da177e4SLinus Torvalds u32 id; 6041da177e4SLinus Torvalds 6051da177e4SLinus Torvalds if (f == NULL) 6061da177e4SLinus Torvalds return skb->len; 6071da177e4SLinus Torvalds 6081da177e4SLinus Torvalds t->tcm_handle = f->handle; 6091da177e4SLinus Torvalds 610ae0be8deSMichal Kubecek nest = nla_nest_start_noflag(skb, TCA_OPTIONS); 6114b3550efSPatrick McHardy if (nest == NULL) 6124b3550efSPatrick McHardy goto nla_put_failure; 6131da177e4SLinus Torvalds 6141da177e4SLinus Torvalds if (!(f->handle & 0x8000)) { 6151da177e4SLinus Torvalds id = f->id & 0xFF; 6161b34ec43SDavid S. Miller if (nla_put_u32(skb, TCA_ROUTE4_TO, id)) 6171b34ec43SDavid S. Miller goto nla_put_failure; 6181da177e4SLinus Torvalds } 6191da177e4SLinus Torvalds if (f->handle & 0x80000000) { 6201b34ec43SDavid S. Miller if ((f->handle >> 16) != 0xFFFF && 6211b34ec43SDavid S. Miller nla_put_u32(skb, TCA_ROUTE4_IIF, f->iif)) 6221b34ec43SDavid S. Miller goto nla_put_failure; 6231da177e4SLinus Torvalds } else { 6241da177e4SLinus Torvalds id = f->id >> 16; 6251b34ec43SDavid S. Miller if (nla_put_u32(skb, TCA_ROUTE4_FROM, id)) 6261b34ec43SDavid S. Miller goto nla_put_failure; 6271da177e4SLinus Torvalds } 6281b34ec43SDavid S. Miller if (f->res.classid && 6291b34ec43SDavid S. Miller nla_put_u32(skb, TCA_ROUTE4_CLASSID, f->res.classid)) 6301b34ec43SDavid S. Miller goto nla_put_failure; 6311da177e4SLinus Torvalds 6325da57f42SWANG Cong if (tcf_exts_dump(skb, &f->exts) < 0) 633add93b61SPatrick McHardy goto nla_put_failure; 6341da177e4SLinus Torvalds 6354b3550efSPatrick McHardy nla_nest_end(skb, nest); 6361da177e4SLinus Torvalds 6375da57f42SWANG Cong if (tcf_exts_dump_stats(skb, &f->exts) < 0) 638add93b61SPatrick McHardy goto nla_put_failure; 6391da177e4SLinus Torvalds 6401da177e4SLinus Torvalds return skb->len; 6411da177e4SLinus Torvalds 642add93b61SPatrick McHardy nla_put_failure: 6436ea3b446SJiri Pirko nla_nest_cancel(skb, nest); 6441da177e4SLinus Torvalds return -1; 6451da177e4SLinus Torvalds } 6461da177e4SLinus Torvalds 64707d79fc7SCong Wang static void route4_bind_class(void *fh, u32 classid, unsigned long cl) 64807d79fc7SCong Wang { 64907d79fc7SCong Wang struct route4_filter *f = fh; 65007d79fc7SCong Wang 65107d79fc7SCong Wang if (f && f->res.classid == classid) 65207d79fc7SCong Wang f->res.class = cl; 65307d79fc7SCong Wang } 65407d79fc7SCong Wang 6552eb9d75cSPatrick McHardy static struct tcf_proto_ops cls_route4_ops __read_mostly = { 6561da177e4SLinus Torvalds .kind = "route", 6571da177e4SLinus Torvalds .classify = route4_classify, 6581da177e4SLinus Torvalds .init = route4_init, 6591da177e4SLinus Torvalds .destroy = route4_destroy, 6601da177e4SLinus Torvalds .get = route4_get, 6611da177e4SLinus Torvalds .change = route4_change, 6621da177e4SLinus Torvalds .delete = route4_delete, 6631da177e4SLinus Torvalds .walk = route4_walk, 6641da177e4SLinus Torvalds .dump = route4_dump, 66507d79fc7SCong Wang .bind_class = route4_bind_class, 6661da177e4SLinus Torvalds .owner = THIS_MODULE, 6671da177e4SLinus Torvalds }; 6681da177e4SLinus Torvalds 6691da177e4SLinus Torvalds static int __init init_route4(void) 6701da177e4SLinus Torvalds { 6711da177e4SLinus Torvalds return register_tcf_proto_ops(&cls_route4_ops); 6721da177e4SLinus Torvalds } 6731da177e4SLinus Torvalds 6741da177e4SLinus Torvalds static void __exit exit_route4(void) 6751da177e4SLinus Torvalds { 6761da177e4SLinus Torvalds unregister_tcf_proto_ops(&cls_route4_ops); 6771da177e4SLinus Torvalds } 6781da177e4SLinus Torvalds 6791da177e4SLinus Torvalds module_init(init_route4) 6801da177e4SLinus Torvalds module_exit(exit_route4) 6811da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 682