Commit c6fcc4fc authored by pravin shelar's avatar pravin shelar Committed by David S. Miller

vxlan: avoid using stale vxlan socket.

When vxlan device is closed vxlan socket is freed. This
operation can race with vxlan-xmit function which
dereferences vxlan socket. Following patch uses RCU
mechanism to avoid this situation.
Signed-off-by: default avatarPravin B Shelar <pshelar@ovn.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 087892d2
...@@ -943,17 +943,20 @@ static bool vxlan_snoop(struct net_device *dev, ...@@ -943,17 +943,20 @@ static bool vxlan_snoop(struct net_device *dev,
static bool vxlan_group_used(struct vxlan_net *vn, struct vxlan_dev *dev) static bool vxlan_group_used(struct vxlan_net *vn, struct vxlan_dev *dev)
{ {
struct vxlan_dev *vxlan; struct vxlan_dev *vxlan;
struct vxlan_sock *sock4;
struct vxlan_sock *sock6 = NULL;
unsigned short family = dev->default_dst.remote_ip.sa.sa_family; unsigned short family = dev->default_dst.remote_ip.sa.sa_family;
sock4 = rtnl_dereference(dev->vn4_sock);
/* The vxlan_sock is only used by dev, leaving group has /* The vxlan_sock is only used by dev, leaving group has
* no effect on other vxlan devices. * no effect on other vxlan devices.
*/ */
if (family == AF_INET && dev->vn4_sock && if (family == AF_INET && sock4 && atomic_read(&sock4->refcnt) == 1)
atomic_read(&dev->vn4_sock->refcnt) == 1)
return false; return false;
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
if (family == AF_INET6 && dev->vn6_sock && sock6 = rtnl_dereference(dev->vn6_sock);
atomic_read(&dev->vn6_sock->refcnt) == 1) if (family == AF_INET6 && sock6 && atomic_read(&sock6->refcnt) == 1)
return false; return false;
#endif #endif
...@@ -961,10 +964,12 @@ static bool vxlan_group_used(struct vxlan_net *vn, struct vxlan_dev *dev) ...@@ -961,10 +964,12 @@ static bool vxlan_group_used(struct vxlan_net *vn, struct vxlan_dev *dev)
if (!netif_running(vxlan->dev) || vxlan == dev) if (!netif_running(vxlan->dev) || vxlan == dev)
continue; continue;
if (family == AF_INET && vxlan->vn4_sock != dev->vn4_sock) if (family == AF_INET &&
rtnl_dereference(vxlan->vn4_sock) != sock4)
continue; continue;
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
if (family == AF_INET6 && vxlan->vn6_sock != dev->vn6_sock) if (family == AF_INET6 &&
rtnl_dereference(vxlan->vn6_sock) != sock6)
continue; continue;
#endif #endif
...@@ -1005,22 +1010,25 @@ static bool __vxlan_sock_release_prep(struct vxlan_sock *vs) ...@@ -1005,22 +1010,25 @@ static bool __vxlan_sock_release_prep(struct vxlan_sock *vs)
static void vxlan_sock_release(struct vxlan_dev *vxlan) static void vxlan_sock_release(struct vxlan_dev *vxlan)
{ {
bool ipv4 = __vxlan_sock_release_prep(vxlan->vn4_sock); struct vxlan_sock *sock4 = rtnl_dereference(vxlan->vn4_sock);
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
bool ipv6 = __vxlan_sock_release_prep(vxlan->vn6_sock); struct vxlan_sock *sock6 = rtnl_dereference(vxlan->vn6_sock);
rcu_assign_pointer(vxlan->vn6_sock, NULL);
#endif #endif
rcu_assign_pointer(vxlan->vn4_sock, NULL);
synchronize_net(); synchronize_net();
if (ipv4) { if (__vxlan_sock_release_prep(sock4)) {
udp_tunnel_sock_release(vxlan->vn4_sock->sock); udp_tunnel_sock_release(sock4->sock);
kfree(vxlan->vn4_sock); kfree(sock4);
} }
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
if (ipv6) { if (__vxlan_sock_release_prep(sock6)) {
udp_tunnel_sock_release(vxlan->vn6_sock->sock); udp_tunnel_sock_release(sock6->sock);
kfree(vxlan->vn6_sock); kfree(sock6);
} }
#endif #endif
} }
...@@ -1036,18 +1044,21 @@ static int vxlan_igmp_join(struct vxlan_dev *vxlan) ...@@ -1036,18 +1044,21 @@ static int vxlan_igmp_join(struct vxlan_dev *vxlan)
int ret = -EINVAL; int ret = -EINVAL;
if (ip->sa.sa_family == AF_INET) { if (ip->sa.sa_family == AF_INET) {
struct vxlan_sock *sock4 = rtnl_dereference(vxlan->vn4_sock);
struct ip_mreqn mreq = { struct ip_mreqn mreq = {
.imr_multiaddr.s_addr = ip->sin.sin_addr.s_addr, .imr_multiaddr.s_addr = ip->sin.sin_addr.s_addr,
.imr_ifindex = ifindex, .imr_ifindex = ifindex,
}; };
sk = vxlan->vn4_sock->sock->sk; sk = sock4->sock->sk;
lock_sock(sk); lock_sock(sk);
ret = ip_mc_join_group(sk, &mreq); ret = ip_mc_join_group(sk, &mreq);
release_sock(sk); release_sock(sk);
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
} else { } else {
sk = vxlan->vn6_sock->sock->sk; struct vxlan_sock *sock6 = rtnl_dereference(vxlan->vn6_sock);
sk = sock6->sock->sk;
lock_sock(sk); lock_sock(sk);
ret = ipv6_stub->ipv6_sock_mc_join(sk, ifindex, ret = ipv6_stub->ipv6_sock_mc_join(sk, ifindex,
&ip->sin6.sin6_addr); &ip->sin6.sin6_addr);
...@@ -1067,18 +1078,21 @@ static int vxlan_igmp_leave(struct vxlan_dev *vxlan) ...@@ -1067,18 +1078,21 @@ static int vxlan_igmp_leave(struct vxlan_dev *vxlan)
int ret = -EINVAL; int ret = -EINVAL;
if (ip->sa.sa_family == AF_INET) { if (ip->sa.sa_family == AF_INET) {
struct vxlan_sock *sock4 = rtnl_dereference(vxlan->vn4_sock);
struct ip_mreqn mreq = { struct ip_mreqn mreq = {
.imr_multiaddr.s_addr = ip->sin.sin_addr.s_addr, .imr_multiaddr.s_addr = ip->sin.sin_addr.s_addr,
.imr_ifindex = ifindex, .imr_ifindex = ifindex,
}; };
sk = vxlan->vn4_sock->sock->sk; sk = sock4->sock->sk;
lock_sock(sk); lock_sock(sk);
ret = ip_mc_leave_group(sk, &mreq); ret = ip_mc_leave_group(sk, &mreq);
release_sock(sk); release_sock(sk);
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
} else { } else {
sk = vxlan->vn6_sock->sock->sk; struct vxlan_sock *sock6 = rtnl_dereference(vxlan->vn6_sock);
sk = sock6->sock->sk;
lock_sock(sk); lock_sock(sk);
ret = ipv6_stub->ipv6_sock_mc_drop(sk, ifindex, ret = ipv6_stub->ipv6_sock_mc_drop(sk, ifindex,
&ip->sin6.sin6_addr); &ip->sin6.sin6_addr);
...@@ -1828,11 +1842,15 @@ static struct dst_entry *vxlan6_get_route(struct vxlan_dev *vxlan, ...@@ -1828,11 +1842,15 @@ static struct dst_entry *vxlan6_get_route(struct vxlan_dev *vxlan,
struct dst_cache *dst_cache, struct dst_cache *dst_cache,
const struct ip_tunnel_info *info) const struct ip_tunnel_info *info)
{ {
struct vxlan_sock *sock6 = rcu_dereference(vxlan->vn6_sock);
bool use_cache = ip_tunnel_dst_cache_usable(skb, info); bool use_cache = ip_tunnel_dst_cache_usable(skb, info);
struct dst_entry *ndst; struct dst_entry *ndst;
struct flowi6 fl6; struct flowi6 fl6;
int err; int err;
if (!sock6)
return ERR_PTR(-EIO);
if (tos && !info) if (tos && !info)
use_cache = false; use_cache = false;
if (use_cache) { if (use_cache) {
...@@ -1850,7 +1868,7 @@ static struct dst_entry *vxlan6_get_route(struct vxlan_dev *vxlan, ...@@ -1850,7 +1868,7 @@ static struct dst_entry *vxlan6_get_route(struct vxlan_dev *vxlan,
fl6.flowi6_proto = IPPROTO_UDP; fl6.flowi6_proto = IPPROTO_UDP;
err = ipv6_stub->ipv6_dst_lookup(vxlan->net, err = ipv6_stub->ipv6_dst_lookup(vxlan->net,
vxlan->vn6_sock->sock->sk, sock6->sock->sk,
&ndst, &fl6); &ndst, &fl6);
if (err < 0) if (err < 0)
return ERR_PTR(err); return ERR_PTR(err);
...@@ -1995,9 +2013,11 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, ...@@ -1995,9 +2013,11 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
} }
if (dst->sa.sa_family == AF_INET) { if (dst->sa.sa_family == AF_INET) {
if (!vxlan->vn4_sock) struct vxlan_sock *sock4 = rcu_dereference(vxlan->vn4_sock);
if (!sock4)
goto drop; goto drop;
sk = vxlan->vn4_sock->sock->sk; sk = sock4->sock->sk;
rt = vxlan_get_route(vxlan, skb, rt = vxlan_get_route(vxlan, skb,
rdst ? rdst->remote_ifindex : 0, tos, rdst ? rdst->remote_ifindex : 0, tos,
...@@ -2050,12 +2070,13 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, ...@@ -2050,12 +2070,13 @@ static void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
src_port, dst_port, xnet, !udp_sum); src_port, dst_port, xnet, !udp_sum);
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
} else { } else {
struct vxlan_sock *sock6 = rcu_dereference(vxlan->vn6_sock);
struct dst_entry *ndst; struct dst_entry *ndst;
u32 rt6i_flags; u32 rt6i_flags;
if (!vxlan->vn6_sock) if (!sock6)
goto drop; goto drop;
sk = vxlan->vn6_sock->sock->sk; sk = sock6->sock->sk;
ndst = vxlan6_get_route(vxlan, skb, ndst = vxlan6_get_route(vxlan, skb,
rdst ? rdst->remote_ifindex : 0, tos, rdst ? rdst->remote_ifindex : 0, tos,
...@@ -2415,9 +2436,10 @@ static int vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) ...@@ -2415,9 +2436,10 @@ static int vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
dport = info->key.tp_dst ? : vxlan->cfg.dst_port; dport = info->key.tp_dst ? : vxlan->cfg.dst_port;
if (ip_tunnel_info_af(info) == AF_INET) { if (ip_tunnel_info_af(info) == AF_INET) {
struct vxlan_sock *sock4 = rcu_dereference(vxlan->vn4_sock);
struct rtable *rt; struct rtable *rt;
if (!vxlan->vn4_sock) if (!sock4)
return -EINVAL; return -EINVAL;
rt = vxlan_get_route(vxlan, skb, 0, info->key.tos, rt = vxlan_get_route(vxlan, skb, 0, info->key.tos,
info->key.u.ipv4.dst, info->key.u.ipv4.dst,
...@@ -2429,8 +2451,6 @@ static int vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) ...@@ -2429,8 +2451,6 @@ static int vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
struct dst_entry *ndst; struct dst_entry *ndst;
if (!vxlan->vn6_sock)
return -EINVAL;
ndst = vxlan6_get_route(vxlan, skb, 0, info->key.tos, ndst = vxlan6_get_route(vxlan, skb, 0, info->key.tos,
info->key.label, &info->key.u.ipv6.dst, info->key.label, &info->key.u.ipv6.dst,
&info->key.u.ipv6.src, NULL, info); &info->key.u.ipv6.src, NULL, info);
...@@ -2740,10 +2760,10 @@ static int __vxlan_sock_add(struct vxlan_dev *vxlan, bool ipv6) ...@@ -2740,10 +2760,10 @@ static int __vxlan_sock_add(struct vxlan_dev *vxlan, bool ipv6)
return PTR_ERR(vs); return PTR_ERR(vs);
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
if (ipv6) if (ipv6)
vxlan->vn6_sock = vs; rcu_assign_pointer(vxlan->vn6_sock, vs);
else else
#endif #endif
vxlan->vn4_sock = vs; rcu_assign_pointer(vxlan->vn4_sock, vs);
vxlan_vs_add_dev(vs, vxlan); vxlan_vs_add_dev(vs, vxlan);
return 0; return 0;
} }
...@@ -2754,9 +2774,9 @@ static int vxlan_sock_add(struct vxlan_dev *vxlan) ...@@ -2754,9 +2774,9 @@ static int vxlan_sock_add(struct vxlan_dev *vxlan)
bool metadata = vxlan->flags & VXLAN_F_COLLECT_METADATA; bool metadata = vxlan->flags & VXLAN_F_COLLECT_METADATA;
int ret = 0; int ret = 0;
vxlan->vn4_sock = NULL; RCU_INIT_POINTER(vxlan->vn4_sock, NULL);
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
vxlan->vn6_sock = NULL; RCU_INIT_POINTER(vxlan->vn6_sock, NULL);
if (ipv6 || metadata) if (ipv6 || metadata)
ret = __vxlan_sock_add(vxlan, true); ret = __vxlan_sock_add(vxlan, true);
#endif #endif
......
...@@ -225,9 +225,9 @@ struct vxlan_config { ...@@ -225,9 +225,9 @@ struct vxlan_config {
struct vxlan_dev { struct vxlan_dev {
struct hlist_node hlist; /* vni hash table */ struct hlist_node hlist; /* vni hash table */
struct list_head next; /* vxlan's per namespace list */ struct list_head next; /* vxlan's per namespace list */
struct vxlan_sock *vn4_sock; /* listening socket for IPv4 */ struct vxlan_sock __rcu *vn4_sock; /* listening socket for IPv4 */
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
struct vxlan_sock *vn6_sock; /* listening socket for IPv6 */ struct vxlan_sock __rcu *vn6_sock; /* listening socket for IPv6 */
#endif #endif
struct net_device *dev; struct net_device *dev;
struct net *net; /* netns for packet i/o */ struct net *net; /* netns for packet i/o */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment