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