aboutsummaryrefslogtreecommitdiffstats
path: root/net/xfrm
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 /net/xfrm
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>
Diffstat (limited to 'net/xfrm')
-rw-r--r--net/xfrm/xfrm_policy.c38
1 files changed, 0 insertions, 38 deletions
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;