aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Streetman <dan.streetman@canonical.com>2015-10-29 09:51:16 -0400
committerSteffen Klassert <steffen.klassert@secunet.com>2015-11-03 02:42:57 -0500
commita8a572a6b5f2a79280d6e302cb3c1cb1fbaeb3e8 (patch)
treea00dad46f7fccfa9c87aaf1273c6cdc78ed7c3f8
parent5f6c99e0ab805d8ec9eda105822912d49aa1d409 (diff)
xfrm: dst_entries_init() per-net dst_ops
Remove the dst_entries_init/destroy calls for xfrm4 and xfrm6 dst_ops templates; their dst_entries counters will never be used. Move the xfrm dst_ops initialization from the common xfrm/xfrm_policy.c to xfrm4/xfrm4_policy.c and xfrm6/xfrm6_policy.c, and call dst_entries_init and dst_entries_destroy for each net namespace. The ipv4 and ipv6 xfrms each create dst_ops template, and perform dst_entries_init on the templates. The template values are copied to each net namespace's xfrm.xfrm*_dst_ops. The problem there is the dst_ops pcpuc_entries field is a percpu counter and cannot be used correctly by simply copying it to another object. The result of this is a very subtle bug; changes to the dst entries counter from one net namespace may sometimes get applied to a different net namespace dst entries counter. This is because of how the percpu counter works; it has a main count field as well as a pointer to the percpu variables. Each net namespace maintains its own main count variable, but all point to one set of percpu variables. When any net namespace happens to change one of the percpu variables to outside its small batch range, its count is moved to the net namespace's main count variable. So with multiple net namespaces operating concurrently, the dst_ops entries counter can stray from the actual value that it should be; if counts are consistently moved from one net namespace to another (which my testing showed is likely), then one net namespace winds up with a negative dst_ops count while another winds up with a continually increasing count, eventually reaching its gc_thresh limit, which causes all new traffic on the net namespace to fail with -ENOBUFS. Signed-off-by: Dan Streetman <dan.streetman@canonical.com> Signed-off-by: Dan Streetman <ddstreet@ieee.org> Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
-rw-r--r--net/ipv4/xfrm4_policy.c46
-rw-r--r--net/ipv6/xfrm6_policy.c53
-rw-r--r--net/xfrm/xfrm_policy.c38
3 files changed, 75 insertions, 62 deletions
diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c
index c10a9ee68433..126ff9020bad 100644
--- a/net/ipv4/xfrm4_policy.c
+++ b/net/ipv4/xfrm4_policy.c
@@ -236,7 +236,7 @@ static void xfrm4_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
236 xfrm_dst_ifdown(dst, dev); 236 xfrm_dst_ifdown(dst, dev);
237} 237}
238 238
239static struct dst_ops xfrm4_dst_ops = { 239static struct dst_ops xfrm4_dst_ops_template = {
240 .family = AF_INET, 240 .family = AF_INET,
241 .gc = xfrm4_garbage_collect, 241 .gc = xfrm4_garbage_collect,
242 .update_pmtu = xfrm4_update_pmtu, 242 .update_pmtu = xfrm4_update_pmtu,
@@ -250,7 +250,7 @@ static struct dst_ops xfrm4_dst_ops = {
250 250
251static struct xfrm_policy_afinfo xfrm4_policy_afinfo = { 251static struct xfrm_policy_afinfo xfrm4_policy_afinfo = {
252 .family = AF_INET, 252 .family = AF_INET,
253 .dst_ops = &xfrm4_dst_ops, 253 .dst_ops = &xfrm4_dst_ops_template,
254 .dst_lookup = xfrm4_dst_lookup, 254 .dst_lookup = xfrm4_dst_lookup,
255 .get_saddr = xfrm4_get_saddr, 255 .get_saddr = xfrm4_get_saddr,
256 .decode_session = _decode_session4, 256 .decode_session = _decode_session4,
@@ -272,7 +272,7 @@ static struct ctl_table xfrm4_policy_table[] = {
272 { } 272 { }
273}; 273};
274 274
275static int __net_init xfrm4_net_init(struct net *net) 275static int __net_init xfrm4_net_sysctl_init(struct net *net)
276{ 276{
277 struct ctl_table *table; 277 struct ctl_table *table;
278 struct ctl_table_header *hdr; 278 struct ctl_table_header *hdr;
@@ -300,7 +300,7 @@ err_alloc:
300 return -ENOMEM; 300 return -ENOMEM;
301} 301}
302 302
303static void __net_exit xfrm4_net_exit(struct net *net) 303static void __net_exit xfrm4_net_sysctl_exit(struct net *net)
304{ 304{
305 struct ctl_table *table; 305 struct ctl_table *table;
306 306
@@ -312,12 +312,44 @@ static void __net_exit xfrm4_net_exit(struct net *net)
312 if (!net_eq(net, &init_net)) 312 if (!net_eq(net, &init_net))
313 kfree(table); 313 kfree(table);
314} 314}
315#else /* CONFIG_SYSCTL */
316static int inline xfrm4_net_sysctl_init(struct net *net)
317{
318 return 0;
319}
320
321static void inline xfrm4_net_sysctl_exit(struct net *net)
322{
323}
324#endif
325
326static int __net_init xfrm4_net_init(struct net *net)
327{
328 int ret;
329
330 memcpy(&net->xfrm.xfrm4_dst_ops, &xfrm4_dst_ops_template,
331 sizeof(xfrm4_dst_ops_template));
332 ret = dst_entries_init(&net->xfrm.xfrm4_dst_ops);
333 if (ret)
334 return ret;
335
336 ret = xfrm4_net_sysctl_init(net);
337 if (ret)
338 dst_entries_destroy(&net->xfrm.xfrm4_dst_ops);
339
340 return ret;
341}
342
343static void __net_exit xfrm4_net_exit(struct net *net)
344{
345 xfrm4_net_sysctl_exit(net);
346 dst_entries_destroy(&net->xfrm.xfrm4_dst_ops);
347}
315 348
316static struct pernet_operations __net_initdata xfrm4_net_ops = { 349static struct pernet_operations __net_initdata xfrm4_net_ops = {
317 .init = xfrm4_net_init, 350 .init = xfrm4_net_init,
318 .exit = xfrm4_net_exit, 351 .exit = xfrm4_net_exit,
319}; 352};
320#endif
321 353
322static void __init xfrm4_policy_init(void) 354static void __init xfrm4_policy_init(void)
323{ 355{
@@ -326,13 +358,9 @@ static void __init xfrm4_policy_init(void)
326 358
327void __init xfrm4_init(void) 359void __init xfrm4_init(void)
328{ 360{
329 dst_entries_init(&xfrm4_dst_ops);
330
331 xfrm4_state_init(); 361 xfrm4_state_init();
332 xfrm4_policy_init(); 362 xfrm4_policy_init();
333 xfrm4_protocol_init(); 363 xfrm4_protocol_init();
334#ifdef CONFIG_SYSCTL
335 register_pernet_subsys(&xfrm4_net_ops); 364 register_pernet_subsys(&xfrm4_net_ops);
336#endif
337} 365}
338 366
diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
index da55e0c85bb8..d51a18d607ac 100644
--- a/net/ipv6/xfrm6_policy.c
+++ b/net/ipv6/xfrm6_policy.c
@@ -281,7 +281,7 @@ static void xfrm6_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
281 xfrm_dst_ifdown(dst, dev); 281 xfrm_dst_ifdown(dst, dev);
282} 282}
283 283
284static struct dst_ops xfrm6_dst_ops = { 284static struct dst_ops xfrm6_dst_ops_template = {
285 .family = AF_INET6, 285 .family = AF_INET6,
286 .gc = xfrm6_garbage_collect, 286 .gc = xfrm6_garbage_collect,
287 .update_pmtu = xfrm6_update_pmtu, 287 .update_pmtu = xfrm6_update_pmtu,
@@ -295,7 +295,7 @@ static struct dst_ops xfrm6_dst_ops = {
295 295
296static struct xfrm_policy_afinfo xfrm6_policy_afinfo = { 296static struct xfrm_policy_afinfo xfrm6_policy_afinfo = {
297 .family = AF_INET6, 297 .family = AF_INET6,
298 .dst_ops = &xfrm6_dst_ops, 298 .dst_ops = &xfrm6_dst_ops_template,
299 .dst_lookup = xfrm6_dst_lookup, 299 .dst_lookup = xfrm6_dst_lookup,
300 .get_saddr = xfrm6_get_saddr, 300 .get_saddr = xfrm6_get_saddr,
301 .decode_session = _decode_session6, 301 .decode_session = _decode_session6,
@@ -327,7 +327,7 @@ static struct ctl_table xfrm6_policy_table[] = {
327 { } 327 { }
328}; 328};
329 329
330static int __net_init xfrm6_net_init(struct net *net) 330static int __net_init xfrm6_net_sysctl_init(struct net *net)
331{ 331{
332 struct ctl_table *table; 332 struct ctl_table *table;
333 struct ctl_table_header *hdr; 333 struct ctl_table_header *hdr;
@@ -355,7 +355,7 @@ err_alloc:
355 return -ENOMEM; 355 return -ENOMEM;
356} 356}
357 357
358static void __net_exit xfrm6_net_exit(struct net *net) 358static void __net_exit xfrm6_net_sysctl_exit(struct net *net)
359{ 359{
360 struct ctl_table *table; 360 struct ctl_table *table;
361 361
@@ -367,24 +367,52 @@ static void __net_exit xfrm6_net_exit(struct net *net)
367 if (!net_eq(net, &init_net)) 367 if (!net_eq(net, &init_net))
368 kfree(table); 368 kfree(table);
369} 369}
370#else /* CONFIG_SYSCTL */
371static int inline xfrm6_net_sysctl_init(struct net *net)
372{
373 return 0;
374}
375
376static void inline xfrm6_net_sysctl_exit(struct net *net)
377{
378}
379#endif
380
381static int __net_init xfrm6_net_init(struct net *net)
382{
383 int ret;
384
385 memcpy(&net->xfrm.xfrm6_dst_ops, &xfrm6_dst_ops_template,
386 sizeof(xfrm6_dst_ops_template));
387 ret = dst_entries_init(&net->xfrm.xfrm6_dst_ops);
388 if (ret)
389 return ret;
390
391 ret = xfrm6_net_sysctl_init(net);
392 if (ret)
393 dst_entries_destroy(&net->xfrm.xfrm6_dst_ops);
394
395 return ret;
396}
397
398static void __net_exit xfrm6_net_exit(struct net *net)
399{
400 xfrm6_net_sysctl_exit(net);
401 dst_entries_destroy(&net->xfrm.xfrm6_dst_ops);
402}
370 403
371static struct pernet_operations xfrm6_net_ops = { 404static struct pernet_operations xfrm6_net_ops = {
372 .init = xfrm6_net_init, 405 .init = xfrm6_net_init,
373 .exit = xfrm6_net_exit, 406 .exit = xfrm6_net_exit,
374}; 407};
375#endif
376 408
377int __init xfrm6_init(void) 409int __init xfrm6_init(void)
378{ 410{
379 int ret; 411 int ret;
380 412
381 dst_entries_init(&xfrm6_dst_ops);
382
383 ret = xfrm6_policy_init(); 413 ret = xfrm6_policy_init();
384 if (ret) { 414 if (ret)
385 dst_entries_destroy(&xfrm6_dst_ops);
386 goto out; 415 goto out;
387 }
388 ret = xfrm6_state_init(); 416 ret = xfrm6_state_init();
389 if (ret) 417 if (ret)
390 goto out_policy; 418 goto out_policy;
@@ -393,9 +421,7 @@ int __init xfrm6_init(void)
393 if (ret) 421 if (ret)
394 goto out_state; 422 goto out_state;
395 423
396#ifdef CONFIG_SYSCTL
397 register_pernet_subsys(&xfrm6_net_ops); 424 register_pernet_subsys(&xfrm6_net_ops);
398#endif
399out: 425out:
400 return ret; 426 return ret;
401out_state: 427out_state:
@@ -407,11 +433,8 @@ out_policy:
407 433
408void xfrm6_fini(void) 434void xfrm6_fini(void)
409{ 435{
410#ifdef CONFIG_SYSCTL
411 unregister_pernet_subsys(&xfrm6_net_ops); 436 unregister_pernet_subsys(&xfrm6_net_ops);
412#endif
413 xfrm6_protocol_fini(); 437 xfrm6_protocol_fini();
414 xfrm6_policy_fini(); 438 xfrm6_policy_fini();
415 xfrm6_state_fini(); 439 xfrm6_state_fini();
416 dst_entries_destroy(&xfrm6_dst_ops);
417} 440}
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 94af3d065785..bacd30bda10d 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -2807,7 +2807,6 @@ static struct neighbour *xfrm_neigh_lookup(const struct dst_entry *dst,
2807 2807
2808int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo) 2808int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo)
2809{ 2809{
2810 struct net *net;
2811 int err = 0; 2810 int err = 0;
2812 if (unlikely(afinfo == NULL)) 2811 if (unlikely(afinfo == NULL))
2813 return -EINVAL; 2812 return -EINVAL;
@@ -2838,26 +2837,6 @@ int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo)
2838 } 2837 }
2839 spin_unlock(&xfrm_policy_afinfo_lock); 2838 spin_unlock(&xfrm_policy_afinfo_lock);
2840 2839
2841 rtnl_lock();
2842 for_each_net(net) {
2843 struct dst_ops *xfrm_dst_ops;
2844
2845 switch (afinfo->family) {
2846 case AF_INET:
2847 xfrm_dst_ops = &net->xfrm.xfrm4_dst_ops;
2848 break;
2849#if IS_ENABLED(CONFIG_IPV6)
2850 case AF_INET6:
2851 xfrm_dst_ops = &net->xfrm.xfrm6_dst_ops;
2852 break;
2853#endif
2854 default:
2855 BUG();
2856 }
2857 *xfrm_dst_ops = *afinfo->dst_ops;
2858 }
2859 rtnl_unlock();
2860
2861 return err; 2840 return err;
2862} 2841}
2863EXPORT_SYMBOL(xfrm_policy_register_afinfo); 2842EXPORT_SYMBOL(xfrm_policy_register_afinfo);
@@ -2893,22 +2872,6 @@ int xfrm_policy_unregister_afinfo(struct xfrm_policy_afinfo *afinfo)
2893} 2872}
2894EXPORT_SYMBOL(xfrm_policy_unregister_afinfo); 2873EXPORT_SYMBOL(xfrm_policy_unregister_afinfo);
2895 2874
2896static void __net_init xfrm_dst_ops_init(struct net *net)
2897{
2898 struct xfrm_policy_afinfo *afinfo;
2899
2900 rcu_read_lock();
2901 afinfo = rcu_dereference(xfrm_policy_afinfo[AF_INET]);
2902 if (afinfo)
2903 net->xfrm.xfrm4_dst_ops = *afinfo->dst_ops;
2904#if IS_ENABLED(CONFIG_IPV6)
2905 afinfo = rcu_dereference(xfrm_policy_afinfo[AF_INET6]);
2906 if (afinfo)
2907 net->xfrm.xfrm6_dst_ops = *afinfo->dst_ops;
2908#endif
2909 rcu_read_unlock();
2910}
2911
2912static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr) 2875static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
2913{ 2876{
2914 struct net_device *dev = netdev_notifier_info_to_dev(ptr); 2877 struct net_device *dev = netdev_notifier_info_to_dev(ptr);
@@ -3057,7 +3020,6 @@ static int __net_init xfrm_net_init(struct net *net)
3057 rv = xfrm_policy_init(net); 3020 rv = xfrm_policy_init(net);
3058 if (rv < 0) 3021 if (rv < 0)
3059 goto out_policy; 3022 goto out_policy;
3060 xfrm_dst_ops_init(net);
3061 rv = xfrm_sysctl_init(net); 3023 rv = xfrm_sysctl_init(net);
3062 if (rv < 0) 3024 if (rv < 0)
3063 goto out_sysctl; 3025 goto out_sysctl;