summaryrefslogtreecommitdiff
path: root/net/tipc/udp_media.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/tipc/udp_media.c')
-rw-r--r--net/tipc/udp_media.c107
1 files changed, 59 insertions, 48 deletions
diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c
index 2c66b356025a..230645cc01c9 100644
--- a/net/tipc/udp_media.c
+++ b/net/tipc/udp_media.c
@@ -40,6 +40,7 @@
#include <linux/igmp.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
+#include <linux/wait_bit.h>
#include <linux/list.h>
#include <net/sock.h>
#include <net/ip.h>
@@ -89,17 +90,21 @@ struct udp_replicast {
/**
* struct udp_bearer - ip/udp bearer data structure
* @bearer: associated generic tipc bearer
- * @ubsock: bearer associated socket
+ * @sk: bearer associated socket
* @ifindex: local address scope
* @work: used to schedule deferred work on a bearer
* @rcast: associated udp_replicast container
+ * @rcast_lock: serialize updates to @rcast.list against concurrent updaters
+ * @disabled: bearer is being torn down; reject further @rcast.list additions
*/
struct udp_bearer {
struct tipc_bearer __rcu *bearer;
- struct socket *ubsock;
+ struct sock *sk;
u32 ifindex;
struct work_struct work;
struct udp_replicast rcast;
+ spinlock_t rcast_lock;
+ bool disabled;
};
static int tipc_udp_is_mcast_addr(struct udp_media_addr *addr)
@@ -194,7 +199,7 @@ static int tipc_udp_xmit(struct net *net, struct sk_buff *skb,
}
ttl = ip4_dst_hoplimit(&rt->dst);
- udp_tunnel_xmit_skb(rt, ub->ubsock->sk, skb, src->ipv4.s_addr,
+ udp_tunnel_xmit_skb(rt, ub->sk, skb, src->ipv4.s_addr,
dst->ipv4.s_addr, 0, ttl, 0, src->port,
dst->port, false, true, 0);
#if IS_ENABLED(CONFIG_IPV6)
@@ -206,7 +211,7 @@ static int tipc_udp_xmit(struct net *net, struct sk_buff *skb,
.saddr = src->ipv6,
.flowi6_proto = IPPROTO_UDP
};
- ndst = ip6_dst_lookup_flow(net, ub->ubsock->sk,
+ ndst = ip6_dst_lookup_flow(net, ub->sk,
&fl6, NULL);
if (IS_ERR(ndst)) {
err = PTR_ERR(ndst);
@@ -215,7 +220,7 @@ static int tipc_udp_xmit(struct net *net, struct sk_buff *skb,
dst_cache_set_ip6(cache, ndst, &fl6.saddr);
}
ttl = ip6_dst_hoplimit(ndst);
- udp_tunnel6_xmit_skb(ndst, ub->ubsock->sk, skb, NULL,
+ udp_tunnel6_xmit_skb(ndst, ub->sk, skb, NULL,
&src->ipv6, &dst->ipv6, 0, ttl, 0,
src->port, dst->port, false, 0);
#endif
@@ -277,26 +282,6 @@ out:
return err;
}
-static bool tipc_udp_is_known_peer(struct tipc_bearer *b,
- struct udp_media_addr *addr)
-{
- struct udp_replicast *rcast, *tmp;
- struct udp_bearer *ub;
-
- ub = rcu_dereference_rtnl(b->media_ptr);
- if (!ub) {
- pr_err_ratelimited("UDP bearer instance not found\n");
- return false;
- }
-
- list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) {
- if (!memcmp(&rcast->addr, addr, sizeof(struct udp_media_addr)))
- return true;
- }
-
- return false;
-}
-
static int tipc_udp_rcast_add(struct tipc_bearer *b,
struct udp_media_addr *addr)
{
@@ -307,16 +292,34 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b,
if (!ub)
return -ENODEV;
+ spin_lock_bh(&ub->rcast_lock);
+ if (ub->disabled) {
+ spin_unlock_bh(&ub->rcast_lock);
+ return 0;
+ }
+ list_for_each_entry(rcast, &ub->rcast.list, list) {
+ if (!memcmp(&rcast->addr, addr, sizeof(*addr))) {
+ spin_unlock_bh(&ub->rcast_lock);
+ return 0;
+ }
+ }
+
rcast = kmalloc_obj(*rcast, GFP_ATOMIC);
- if (!rcast)
+ if (!rcast) {
+ spin_unlock_bh(&ub->rcast_lock);
return -ENOMEM;
+ }
if (dst_cache_init(&rcast->dst_cache, GFP_ATOMIC)) {
+ spin_unlock_bh(&ub->rcast_lock);
kfree(rcast);
return -ENOMEM;
}
memcpy(&rcast->addr, addr, sizeof(struct udp_media_addr));
+ list_add_rcu(&rcast->list, &ub->rcast.list);
+ b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT;
+ spin_unlock_bh(&ub->rcast_lock);
if (ntohs(addr->proto) == ETH_P_IP)
pr_info("New replicast peer: %pI4\n", &rcast->addr.ipv4);
@@ -324,8 +327,6 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b,
else if (ntohs(addr->proto) == ETH_P_IPV6)
pr_info("New replicast peer: %pI6\n", &rcast->addr.ipv6);
#endif
- b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT;
- list_add_rcu(&rcast->list, &ub->rcast.list);
return 0;
}
@@ -360,9 +361,6 @@ static int tipc_udp_rcast_disc(struct tipc_bearer *b, struct sk_buff *skb)
return 0;
}
- if (likely(tipc_udp_is_known_peer(b, &src)))
- return 0;
-
return tipc_udp_rcast_add(b, &src);
}
@@ -405,9 +403,9 @@ out:
static int enable_mcast(struct udp_bearer *ub, struct udp_media_addr *remote)
{
- int err = 0;
+ struct sock *sk = ub->sk;
struct ip_mreqn mreqn;
- struct sock *sk = ub->ubsock->sk;
+ int err = 0;
if (ntohs(remote->proto) == ETH_P_IP) {
mreqn.imr_multiaddr = remote->ipv4;
@@ -643,9 +641,6 @@ int tipc_udp_nl_bearer_add(struct tipc_bearer *b, struct nlattr *attr)
return -EINVAL;
}
- if (tipc_udp_is_known_peer(b, &addr))
- return 0;
-
return tipc_udp_rcast_add(b, &addr);
}
@@ -670,6 +665,7 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b,
struct nlattr *opts[TIPC_NLA_UDP_MAX + 1];
u8 node_id[NODE_ID_LEN] = {0,};
struct net_device *dev;
+ struct socket *sock;
int rmcast = 0;
ub = kzalloc_obj(*ub, GFP_ATOMIC);
@@ -677,6 +673,7 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b,
return -ENOMEM;
INIT_LIST_HEAD(&ub->rcast.list);
+ spin_lock_init(&ub->rcast_lock);
if (!attrs[TIPC_NLA_BEARER_UDP_OPTS])
goto err;
@@ -764,14 +761,16 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b,
goto err;
}
udp_conf.local_udp_port = local.port;
- err = udp_sock_create(net, &udp_conf, &ub->ubsock);
+ err = udp_sock_create(net, &udp_conf, &sock);
if (err)
goto err;
+
+ ub->sk = sock->sk;
tuncfg.sk_user_data = ub;
tuncfg.encap_type = 1;
tuncfg.encap_rcv = tipc_udp_recv;
tuncfg.encap_destroy = NULL;
- setup_udp_tunnel_sock(net, ub->ubsock, &tuncfg);
+ setup_udp_tunnel_sock(net, ub->sk, &tuncfg);
err = dst_cache_init(&ub->rcast.dst_cache, GFP_ATOMIC);
if (err)
@@ -793,12 +792,21 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b,
free:
dst_cache_destroy(&ub->rcast.dst_cache);
- udp_tunnel_sock_release(ub->ubsock);
+ udp_tunnel_sock_release(ub->sk);
+ synchronize_rcu();
err:
kfree(ub);
return err;
}
+static void rcast_free_rcu(struct rcu_head *rcu)
+{
+ struct udp_replicast *rcast = container_of(rcu, struct udp_replicast, rcu);
+
+ dst_cache_destroy(&rcast->dst_cache);
+ kfree(rcast);
+}
+
/* cleanup_bearer - break the socket/bearer association */
static void cleanup_bearer(struct work_struct *work)
{
@@ -806,20 +814,23 @@ static void cleanup_bearer(struct work_struct *work)
struct udp_replicast *rcast, *tmp;
struct tipc_net *tn;
+ spin_lock_bh(&ub->rcast_lock);
list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) {
- dst_cache_destroy(&rcast->dst_cache);
list_del_rcu(&rcast->list);
- kfree_rcu(rcast, rcu);
+ call_rcu_hurry(&rcast->rcu, rcast_free_rcu);
}
+ ub->disabled = true;
+ spin_unlock_bh(&ub->rcast_lock);
- tn = tipc_net(sock_net(ub->ubsock->sk));
+ tn = tipc_net(sock_net(ub->sk));
- dst_cache_destroy(&ub->rcast.dst_cache);
- udp_tunnel_sock_release(ub->ubsock);
+ udp_tunnel_sock_release(ub->sk);
- /* Note: could use a call_rcu() to avoid another synchronize_net() */
synchronize_net();
- atomic_dec(&tn->wq_count);
+
+ dst_cache_destroy(&ub->rcast.dst_cache);
+ if (atomic_dec_and_test(&tn->wq_count))
+ wake_up_var(&tn->wq_count);
kfree(ub);
}
@@ -833,11 +844,11 @@ static void tipc_udp_disable(struct tipc_bearer *b)
pr_err("UDP bearer instance not found\n");
return;
}
- sock_set_flag(ub->ubsock->sk, SOCK_DEAD);
+ sock_set_flag(ub->sk, SOCK_DEAD);
RCU_INIT_POINTER(ub->bearer, NULL);
/* sock_release need to be done outside of rtnl lock */
- atomic_inc(&tipc_net(sock_net(ub->ubsock->sk))->wq_count);
+ atomic_inc(&tipc_net(sock_net(ub->sk))->wq_count);
INIT_WORK(&ub->work, cleanup_bearer);
schedule_work(&ub->work);
}