kernel: nf_conntrack_rtcache: fix WARNING on forward path
[openwrt/openwrt.git] / target / linux / generic / backport-4.14 / 020-backport_netfilter_rtcache.patch
1 From 1bb0c3ec899827cfa4668bb63a08713a40744d21 Mon Sep 17 00:00:00 2001
2 From: Florian Westphal <fw@strlen.de>
3 Date: Sun, 9 Jul 2017 08:58:30 +0200
4 Subject: [PATCH] netfilter: conntrack: cache route for forwarded connections
5
6 ... to avoid per-packet FIB lookup if possible.
7
8 The cached dst is re-used provided the input interface
9 is the same as that of the previous packet in the same direction.
10
11 If not, the cached dst is invalidated.
12
13 For ipv6 we also need to store sernum, else dst_check doesn't work,
14 pointed out by Eric Dumazet.
15
16 This should speed up forwarding when conntrack is already in use
17 anyway, especially when using reverse path filtering -- active RPF
18 enforces two FIB lookups for each packet.
19
20 Before the routing cache removal this didn't matter since RPF was performed
21 only when route cache didn't yield a result; but without route cache it
22 comes at higher price.
23
24 Julian Anastasov suggested to add NETDEV_UNREGISTER handler to
25 avoid holding on to dsts of 'frozen' conntracks.
26
27 Signed-off-by: Florian Westphal <fw@strlen.de>
28 ---
29 include/net/netfilter/nf_conntrack_extend.h | 4 +
30 include/net/netfilter/nf_conntrack_rtcache.h | 34 +++
31 net/netfilter/Kconfig | 12 +
32 net/netfilter/Makefile | 3 +
33 net/netfilter/nf_conntrack_rtcache.c | 428 +++++++++++++++++++++++++++
34 5 files changed, 481 insertions(+)
35 create mode 100644 include/net/netfilter/nf_conntrack_rtcache.h
36 create mode 100644 net/netfilter/nf_conntrack_rtcache.c
37
38 --- a/include/net/netfilter/nf_conntrack_extend.h
39 +++ b/include/net/netfilter/nf_conntrack_extend.h
40 @@ -28,6 +28,9 @@ enum nf_ct_ext_id {
41 #if IS_ENABLED(CONFIG_NETFILTER_SYNPROXY)
42 NF_CT_EXT_SYNPROXY,
43 #endif
44 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_RTCACHE)
45 + NF_CT_EXT_RTCACHE,
46 +#endif
47 NF_CT_EXT_NUM,
48 };
49
50 @@ -40,6 +43,7 @@ enum nf_ct_ext_id {
51 #define NF_CT_EXT_TIMEOUT_TYPE struct nf_conn_timeout
52 #define NF_CT_EXT_LABELS_TYPE struct nf_conn_labels
53 #define NF_CT_EXT_SYNPROXY_TYPE struct nf_conn_synproxy
54 +#define NF_CT_EXT_RTCACHE_TYPE struct nf_conn_rtcache
55
56 /* Extensions: optional stuff which isn't permanently in struct. */
57 struct nf_ct_ext {
58 --- /dev/null
59 +++ b/include/net/netfilter/nf_conntrack_rtcache.h
60 @@ -0,0 +1,34 @@
61 +#include <linux/gfp.h>
62 +#include <net/netfilter/nf_conntrack.h>
63 +#include <net/netfilter/nf_conntrack_extend.h>
64 +
65 +struct dst_entry;
66 +
67 +struct nf_conn_dst_cache {
68 + struct dst_entry *dst;
69 + int iif;
70 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_IPV6)
71 + u32 cookie;
72 +#endif
73 +
74 +};
75 +
76 +struct nf_conn_rtcache {
77 + struct nf_conn_dst_cache cached_dst[IP_CT_DIR_MAX];
78 +};
79 +
80 +static inline
81 +struct nf_conn_rtcache *nf_ct_rtcache_find(const struct nf_conn *ct)
82 +{
83 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_RTCACHE)
84 + return nf_ct_ext_find(ct, NF_CT_EXT_RTCACHE);
85 +#else
86 + return NULL;
87 +#endif
88 +}
89 +
90 +static inline int nf_conn_rtcache_iif_get(const struct nf_conn_rtcache *rtc,
91 + enum ip_conntrack_dir dir)
92 +{
93 + return rtc->cached_dst[dir].iif;
94 +}
95 --- a/net/netfilter/Kconfig
96 +++ b/net/netfilter/Kconfig
97 @@ -118,6 +118,18 @@ config NF_CONNTRACK_EVENTS
98
99 If unsure, say `N'.
100
101 +config NF_CONNTRACK_RTCACHE
102 + tristate "Cache route entries in conntrack objects"
103 + depends on NETFILTER_ADVANCED
104 + depends on NF_CONNTRACK
105 + help
106 + If this option is enabled, the connection tracking code will
107 + cache routing information for each connection that is being
108 + forwarded, at a cost of 32 bytes per conntrack object.
109 +
110 + To compile it as a module, choose M here. If unsure, say N.
111 + The module will be called nf_conntrack_rtcache.
112 +
113 config NF_CONNTRACK_TIMEOUT
114 bool 'Connection tracking timeout'
115 depends on NETFILTER_ADVANCED
116 --- a/net/netfilter/Makefile
117 +++ b/net/netfilter/Makefile
118 @@ -19,6 +19,9 @@ obj-$(CONFIG_NETFILTER_NETLINK_LOG) += n
119 # connection tracking
120 obj-$(CONFIG_NF_CONNTRACK) += nf_conntrack.o
121
122 +# optional conntrack route cache extension
123 +obj-$(CONFIG_NF_CONNTRACK_RTCACHE) += nf_conntrack_rtcache.o
124 +
125 obj-$(CONFIG_NF_CT_PROTO_GRE) += nf_conntrack_proto_gre.o
126
127 # netlink interface for nf_conntrack
128 --- /dev/null
129 +++ b/net/netfilter/nf_conntrack_rtcache.c
130 @@ -0,0 +1,430 @@
131 +/* route cache for netfilter.
132 + *
133 + * (C) 2014 Red Hat GmbH
134 + *
135 + * This program is free software; you can redistribute it and/or modify
136 + * it under the terms of the GNU General Public License version 2 as
137 + * published by the Free Software Foundation.
138 + */
139 +
140 +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
141 +
142 +#include <linux/types.h>
143 +#include <linux/netfilter.h>
144 +#include <linux/skbuff.h>
145 +#include <linux/stddef.h>
146 +#include <linux/kernel.h>
147 +#include <linux/netdevice.h>
148 +#include <linux/export.h>
149 +#include <linux/module.h>
150 +
151 +#include <net/dst.h>
152 +
153 +#include <net/netfilter/nf_conntrack.h>
154 +#include <net/netfilter/nf_conntrack_core.h>
155 +#include <net/netfilter/nf_conntrack_extend.h>
156 +#include <net/netfilter/nf_conntrack_rtcache.h>
157 +
158 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_IPV6)
159 +#include <net/ip6_fib.h>
160 +#endif
161 +
162 +static void __nf_conn_rtcache_destroy(struct nf_conn_rtcache *rtc,
163 + enum ip_conntrack_dir dir)
164 +{
165 + struct dst_entry *dst = rtc->cached_dst[dir].dst;
166 +
167 + dst_release(dst);
168 +}
169 +
170 +static void nf_conn_rtcache_destroy(struct nf_conn *ct)
171 +{
172 + struct nf_conn_rtcache *rtc = nf_ct_rtcache_find(ct);
173 +
174 + if (!rtc)
175 + return;
176 +
177 + __nf_conn_rtcache_destroy(rtc, IP_CT_DIR_ORIGINAL);
178 + __nf_conn_rtcache_destroy(rtc, IP_CT_DIR_REPLY);
179 +}
180 +
181 +static void nf_ct_rtcache_ext_add(struct nf_conn *ct)
182 +{
183 + struct nf_conn_rtcache *rtc;
184 +
185 + rtc = nf_ct_ext_add(ct, NF_CT_EXT_RTCACHE, GFP_ATOMIC);
186 + if (rtc) {
187 + rtc->cached_dst[IP_CT_DIR_ORIGINAL].iif = -1;
188 + rtc->cached_dst[IP_CT_DIR_ORIGINAL].dst = NULL;
189 + rtc->cached_dst[IP_CT_DIR_REPLY].iif = -1;
190 + rtc->cached_dst[IP_CT_DIR_REPLY].dst = NULL;
191 + }
192 +}
193 +
194 +static struct nf_conn_rtcache *nf_ct_rtcache_find_usable(struct nf_conn *ct)
195 +{
196 + return nf_ct_rtcache_find(ct);
197 +}
198 +
199 +static struct dst_entry *
200 +nf_conn_rtcache_dst_get(const struct nf_conn_rtcache *rtc,
201 + enum ip_conntrack_dir dir)
202 +{
203 + return rtc->cached_dst[dir].dst;
204 +}
205 +
206 +static u32 nf_rtcache_get_cookie(int pf, const struct dst_entry *dst)
207 +{
208 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_IPV6)
209 + if (pf == NFPROTO_IPV6) {
210 + const struct rt6_info *rt = (const struct rt6_info *)dst;
211 +
212 + if (rt->rt6i_node)
213 + return (u32)rt->rt6i_node->fn_sernum;
214 + }
215 +#endif
216 + return 0;
217 +}
218 +
219 +static void nf_conn_rtcache_dst_set(int pf,
220 + struct nf_conn_rtcache *rtc,
221 + struct dst_entry *dst,
222 + enum ip_conntrack_dir dir, int iif)
223 +{
224 + if (rtc->cached_dst[dir].iif != iif)
225 + rtc->cached_dst[dir].iif = iif;
226 +
227 + if (rtc->cached_dst[dir].dst != dst) {
228 + struct dst_entry *old;
229 +
230 + dst_hold(dst);
231 +
232 + old = xchg(&rtc->cached_dst[dir].dst, dst);
233 + dst_release(old);
234 +
235 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_IPV6)
236 + if (pf == NFPROTO_IPV6)
237 + rtc->cached_dst[dir].cookie =
238 + nf_rtcache_get_cookie(pf, dst);
239 +#endif
240 + }
241 +}
242 +
243 +static void nf_conn_rtcache_dst_obsolete(struct nf_conn_rtcache *rtc,
244 + enum ip_conntrack_dir dir)
245 +{
246 + struct dst_entry *old;
247 +
248 + pr_debug("Invalidate iif %d for dir %d on cache %p\n",
249 + rtc->cached_dst[dir].iif, dir, rtc);
250 +
251 + old = xchg(&rtc->cached_dst[dir].dst, NULL);
252 + dst_release(old);
253 + rtc->cached_dst[dir].iif = -1;
254 +}
255 +
256 +static unsigned int nf_rtcache_in(u_int8_t pf,
257 + struct sk_buff *skb,
258 + const struct nf_hook_state *state)
259 +{
260 + struct nf_conn_rtcache *rtc;
261 + enum ip_conntrack_info ctinfo;
262 + enum ip_conntrack_dir dir;
263 + struct dst_entry *dst;
264 + struct nf_conn *ct;
265 + int iif;
266 + u32 cookie;
267 +
268 + if (skb_dst(skb) || skb->sk)
269 + return NF_ACCEPT;
270 +
271 + ct = nf_ct_get(skb, &ctinfo);
272 + if (!ct)
273 + return NF_ACCEPT;
274 +
275 + rtc = nf_ct_rtcache_find_usable(ct);
276 + if (!rtc)
277 + return NF_ACCEPT;
278 +
279 + /* if iif changes, don't use cache and let ip stack
280 + * do route lookup.
281 + *
282 + * If rp_filter is enabled it might toss skb, so
283 + * we don't want to avoid these checks.
284 + */
285 + dir = CTINFO2DIR(ctinfo);
286 + iif = nf_conn_rtcache_iif_get(rtc, dir);
287 + if (state->in->ifindex != iif) {
288 + pr_debug("ct %p, iif %d, cached iif %d, skip cached entry\n",
289 + ct, iif, state->in->ifindex);
290 + return NF_ACCEPT;
291 + }
292 + dst = nf_conn_rtcache_dst_get(rtc, dir);
293 + if (dst == NULL)
294 + return NF_ACCEPT;
295 +
296 + cookie = nf_rtcache_get_cookie(pf, dst);
297 +
298 + dst = dst_check(dst, cookie);
299 + pr_debug("obtained dst %p for skb %p, cookie %d\n", dst, skb, cookie);
300 + if (likely(dst))
301 + skb_dst_set_noref(skb, dst);
302 + else
303 + nf_conn_rtcache_dst_obsolete(rtc, dir);
304 +
305 + return NF_ACCEPT;
306 +}
307 +
308 +static unsigned int nf_rtcache_forward(u_int8_t pf,
309 + struct sk_buff *skb,
310 + const struct nf_hook_state *state)
311 +{
312 + struct nf_conn_rtcache *rtc;
313 + enum ip_conntrack_info ctinfo;
314 + enum ip_conntrack_dir dir;
315 + struct nf_conn *ct;
316 + struct dst_entry *dst = skb_dst(skb);
317 + int iif;
318 +
319 + ct = nf_ct_get(skb, &ctinfo);
320 + if (!ct)
321 + return NF_ACCEPT;
322 +
323 + if (dst && dst_xfrm(dst))
324 + return NF_ACCEPT;
325 +
326 + if (!nf_ct_is_confirmed(ct)) {
327 + if (WARN_ON(nf_ct_rtcache_find(ct)))
328 + return NF_ACCEPT;
329 + nf_ct_rtcache_ext_add(ct);
330 + return NF_ACCEPT;
331 + }
332 +
333 + rtc = nf_ct_rtcache_find_usable(ct);
334 + if (!rtc)
335 + return NF_ACCEPT;
336 +
337 + dir = CTINFO2DIR(ctinfo);
338 + iif = nf_conn_rtcache_iif_get(rtc, dir);
339 + pr_debug("ct %p, skb %p, dir %d, iif %d, cached iif %d\n",
340 + ct, skb, dir, iif, state->in->ifindex);
341 + if (likely(state->in->ifindex == iif))
342 + return NF_ACCEPT;
343 +
344 + nf_conn_rtcache_dst_set(pf, rtc, skb_dst(skb), dir, state->in->ifindex);
345 + return NF_ACCEPT;
346 +}
347 +
348 +static unsigned int nf_rtcache_in4(void *priv,
349 + struct sk_buff *skb,
350 + const struct nf_hook_state *state)
351 +{
352 + return nf_rtcache_in(NFPROTO_IPV4, skb, state);
353 +}
354 +
355 +static unsigned int nf_rtcache_forward4(void *priv,
356 + struct sk_buff *skb,
357 + const struct nf_hook_state *state)
358 +{
359 + return nf_rtcache_forward(NFPROTO_IPV4, skb, state);
360 +}
361 +
362 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_IPV6)
363 +static unsigned int nf_rtcache_in6(void *priv,
364 + struct sk_buff *skb,
365 + const struct nf_hook_state *state)
366 +{
367 + return nf_rtcache_in(NFPROTO_IPV6, skb, state);
368 +}
369 +
370 +static unsigned int nf_rtcache_forward6(void *priv,
371 + struct sk_buff *skb,
372 + const struct nf_hook_state *state)
373 +{
374 + return nf_rtcache_forward(NFPROTO_IPV6, skb, state);
375 +}
376 +#endif
377 +
378 +static int nf_rtcache_dst_remove(struct nf_conn *ct, void *data)
379 +{
380 + struct nf_conn_rtcache *rtc = nf_ct_rtcache_find(ct);
381 + struct net_device *dev = data;
382 +
383 + if (!rtc)
384 + return 0;
385 +
386 + if (dev->ifindex == rtc->cached_dst[IP_CT_DIR_ORIGINAL].iif ||
387 + dev->ifindex == rtc->cached_dst[IP_CT_DIR_REPLY].iif) {
388 + nf_conn_rtcache_dst_obsolete(rtc, IP_CT_DIR_ORIGINAL);
389 + nf_conn_rtcache_dst_obsolete(rtc, IP_CT_DIR_REPLY);
390 + }
391 +
392 + return 0;
393 +}
394 +
395 +static int nf_rtcache_netdev_event(struct notifier_block *this,
396 + unsigned long event, void *ptr)
397 +{
398 + struct net_device *dev = netdev_notifier_info_to_dev(ptr);
399 + struct net *net = dev_net(dev);
400 +
401 + if (event == NETDEV_DOWN)
402 + nf_ct_iterate_cleanup_net(net, nf_rtcache_dst_remove, dev, 0, 0);
403 +
404 + return NOTIFY_DONE;
405 +}
406 +
407 +static struct notifier_block nf_rtcache_notifier = {
408 + .notifier_call = nf_rtcache_netdev_event,
409 +};
410 +
411 +static struct nf_hook_ops rtcache_ops[] = {
412 + {
413 + .hook = nf_rtcache_in4,
414 + .pf = NFPROTO_IPV4,
415 + .hooknum = NF_INET_PRE_ROUTING,
416 + .priority = NF_IP_PRI_LAST,
417 + },
418 + {
419 + .hook = nf_rtcache_forward4,
420 + .pf = NFPROTO_IPV4,
421 + .hooknum = NF_INET_FORWARD,
422 + .priority = NF_IP_PRI_LAST,
423 + },
424 +#if IS_ENABLED(CONFIG_NF_CONNTRACK_IPV6)
425 + {
426 + .hook = nf_rtcache_in6,
427 + .pf = NFPROTO_IPV6,
428 + .hooknum = NF_INET_PRE_ROUTING,
429 + .priority = NF_IP_PRI_LAST,
430 + },
431 + {
432 + .hook = nf_rtcache_forward6,
433 + .pf = NFPROTO_IPV6,
434 + .hooknum = NF_INET_FORWARD,
435 + .priority = NF_IP_PRI_LAST,
436 + },
437 +#endif
438 +};
439 +
440 +static struct nf_ct_ext_type rtcache_extend __read_mostly = {
441 + .len = sizeof(struct nf_conn_rtcache),
442 + .align = __alignof__(struct nf_conn_rtcache),
443 + .id = NF_CT_EXT_RTCACHE,
444 + .destroy = nf_conn_rtcache_destroy,
445 +};
446 +
447 +static int __net_init rtcache_net_init(struct net *net)
448 +{
449 + return nf_register_net_hooks(net, rtcache_ops, ARRAY_SIZE(rtcache_ops));
450 +}
451 +
452 +static void __net_exit rtcache_net_exit(struct net *net)
453 +{
454 + /* remove hooks so no new connections get rtcache extension */
455 + nf_unregister_net_hooks(net, rtcache_ops, ARRAY_SIZE(rtcache_ops));
456 +}
457 +
458 +static struct pernet_operations rtcache_ops_net_ops = {
459 + .init = rtcache_net_init,
460 + .exit = rtcache_net_exit,
461 +};
462 +
463 +static int __init nf_conntrack_rtcache_init(void)
464 +{
465 + int ret = nf_ct_extend_register(&rtcache_extend);
466 +
467 + if (ret < 0) {
468 + pr_err("nf_conntrack_rtcache: Unable to register extension\n");
469 + return ret;
470 + }
471 +
472 + ret = register_pernet_subsys(&rtcache_ops_net_ops);
473 + if (ret) {
474 + nf_ct_extend_unregister(&rtcache_extend);
475 + return ret;
476 + }
477 +
478 + ret = register_netdevice_notifier(&nf_rtcache_notifier);
479 + if (ret) {
480 + nf_ct_extend_unregister(&rtcache_extend);
481 + unregister_pernet_subsys(&rtcache_ops_net_ops);
482 + }
483 +
484 + return ret;
485 +}
486 +
487 +static int nf_rtcache_ext_remove(struct nf_conn *ct, void *data)
488 +{
489 + struct nf_conn_rtcache *rtc = nf_ct_rtcache_find(ct);
490 +
491 + return rtc != NULL;
492 +}
493 +
494 +static bool __exit nf_conntrack_rtcache_wait_for_dying(struct net *net)
495 +{
496 + bool wait = false;
497 + int cpu;
498 +
499 + for_each_possible_cpu(cpu) {
500 + struct nf_conntrack_tuple_hash *h;
501 + struct hlist_nulls_node *n;
502 + struct nf_conn *ct;
503 + struct ct_pcpu *pcpu = per_cpu_ptr(net->ct.pcpu_lists, cpu);
504 +
505 + rcu_read_lock();
506 + spin_lock_bh(&pcpu->lock);
507 +
508 + hlist_nulls_for_each_entry(h, n, &pcpu->dying, hnnode) {
509 + ct = nf_ct_tuplehash_to_ctrack(h);
510 + if (nf_ct_rtcache_find(ct) != NULL) {
511 + wait = true;
512 + break;
513 + }
514 + }
515 + spin_unlock_bh(&pcpu->lock);
516 + rcu_read_unlock();
517 + }
518 +
519 + return wait;
520 +}
521 +
522 +static void __exit nf_conntrack_rtcache_fini(void)
523 +{
524 + struct net *net;
525 + int count = 0;
526 +
527 + synchronize_net();
528 +
529 + unregister_netdevice_notifier(&nf_rtcache_notifier);
530 + unregister_pernet_subsys(&rtcache_ops_net_ops);
531 + for_each_net(net)
532 + nf_unregister_net_hooks(net, rtcache_ops, ARRAY_SIZE(rtcache_ops));
533 +
534 + synchronize_net();
535 +
536 + rtnl_lock();
537 +
538 + /* zap all conntracks with rtcache extension */
539 + for_each_net(net)
540 + nf_ct_iterate_cleanup_net(net, nf_rtcache_ext_remove, NULL, 0, 0);
541 +
542 + for_each_net(net) {
543 + /* .. and make sure they're gone from dying list, too */
544 + while (nf_conntrack_rtcache_wait_for_dying(net)) {
545 + msleep(200);
546 + WARN_ONCE(++count > 25, "Waiting for all rtcache conntracks to go away\n");
547 + }
548 + }
549 +
550 + rtnl_unlock();
551 +
552 + synchronize_net();
553 + nf_ct_extend_unregister(&rtcache_extend);
554 +}
555 +module_init(nf_conntrack_rtcache_init);
556 +module_exit(nf_conntrack_rtcache_fini);
557 +
558 +MODULE_LICENSE("GPL");
559 +MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
560 +MODULE_DESCRIPTION("Conntrack route cache extension");