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: Pravin B Shelar <pshelar@ovn.org> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
087892d29b
commit
c6fcc4fc5f
@ -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)
|
|||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|||||||
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)
|
|||||||
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)
|
|||||||
#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)
|
|||||||
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)
|
|||||||
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 {
|
|||||||
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 */
|
||||||
|
Loading…
Reference in New Issue
Block a user