162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * udp_diag.c	Module for monitoring UDP transport protocols sockets.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:	Pavel Emelyanov, <xemul@parallels.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/inet_diag.h>
1162306a36Sopenharmony_ci#include <linux/udp.h>
1262306a36Sopenharmony_ci#include <net/udp.h>
1362306a36Sopenharmony_ci#include <net/udplite.h>
1462306a36Sopenharmony_ci#include <linux/sock_diag.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic int sk_diag_dump(struct sock *sk, struct sk_buff *skb,
1762306a36Sopenharmony_ci			struct netlink_callback *cb,
1862306a36Sopenharmony_ci			const struct inet_diag_req_v2 *req,
1962306a36Sopenharmony_ci			struct nlattr *bc, bool net_admin)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	if (!inet_diag_bc_sk(bc, sk))
2262306a36Sopenharmony_ci		return 0;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	return inet_sk_diag_fill(sk, NULL, skb, cb, req, NLM_F_MULTI,
2562306a36Sopenharmony_ci				 net_admin);
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic int udp_dump_one(struct udp_table *tbl,
2962306a36Sopenharmony_ci			struct netlink_callback *cb,
3062306a36Sopenharmony_ci			const struct inet_diag_req_v2 *req)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct sk_buff *in_skb = cb->skb;
3362306a36Sopenharmony_ci	int err;
3462306a36Sopenharmony_ci	struct sock *sk = NULL;
3562306a36Sopenharmony_ci	struct sk_buff *rep;
3662306a36Sopenharmony_ci	struct net *net = sock_net(in_skb->sk);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	rcu_read_lock();
3962306a36Sopenharmony_ci	if (req->sdiag_family == AF_INET)
4062306a36Sopenharmony_ci		/* src and dst are swapped for historical reasons */
4162306a36Sopenharmony_ci		sk = __udp4_lib_lookup(net,
4262306a36Sopenharmony_ci				req->id.idiag_src[0], req->id.idiag_sport,
4362306a36Sopenharmony_ci				req->id.idiag_dst[0], req->id.idiag_dport,
4462306a36Sopenharmony_ci				req->id.idiag_if, 0, tbl, NULL);
4562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
4662306a36Sopenharmony_ci	else if (req->sdiag_family == AF_INET6)
4762306a36Sopenharmony_ci		sk = __udp6_lib_lookup(net,
4862306a36Sopenharmony_ci				(struct in6_addr *)req->id.idiag_src,
4962306a36Sopenharmony_ci				req->id.idiag_sport,
5062306a36Sopenharmony_ci				(struct in6_addr *)req->id.idiag_dst,
5162306a36Sopenharmony_ci				req->id.idiag_dport,
5262306a36Sopenharmony_ci				req->id.idiag_if, 0, tbl, NULL);
5362306a36Sopenharmony_ci#endif
5462306a36Sopenharmony_ci	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
5562306a36Sopenharmony_ci		sk = NULL;
5662306a36Sopenharmony_ci	rcu_read_unlock();
5762306a36Sopenharmony_ci	err = -ENOENT;
5862306a36Sopenharmony_ci	if (!sk)
5962306a36Sopenharmony_ci		goto out_nosk;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	err = sock_diag_check_cookie(sk, req->id.idiag_cookie);
6262306a36Sopenharmony_ci	if (err)
6362306a36Sopenharmony_ci		goto out;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	err = -ENOMEM;
6662306a36Sopenharmony_ci	rep = nlmsg_new(nla_total_size(sizeof(struct inet_diag_msg)) +
6762306a36Sopenharmony_ci			inet_diag_msg_attrs_size() +
6862306a36Sopenharmony_ci			nla_total_size(sizeof(struct inet_diag_meminfo)) + 64,
6962306a36Sopenharmony_ci			GFP_KERNEL);
7062306a36Sopenharmony_ci	if (!rep)
7162306a36Sopenharmony_ci		goto out;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	err = inet_sk_diag_fill(sk, NULL, rep, cb, req, 0,
7462306a36Sopenharmony_ci				netlink_net_capable(in_skb, CAP_NET_ADMIN));
7562306a36Sopenharmony_ci	if (err < 0) {
7662306a36Sopenharmony_ci		WARN_ON(err == -EMSGSIZE);
7762306a36Sopenharmony_ci		kfree_skb(rep);
7862306a36Sopenharmony_ci		goto out;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci	err = nlmsg_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ciout:
8362306a36Sopenharmony_ci	if (sk)
8462306a36Sopenharmony_ci		sock_put(sk);
8562306a36Sopenharmony_ciout_nosk:
8662306a36Sopenharmony_ci	return err;
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic void udp_dump(struct udp_table *table, struct sk_buff *skb,
9062306a36Sopenharmony_ci		     struct netlink_callback *cb,
9162306a36Sopenharmony_ci		     const struct inet_diag_req_v2 *r)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN);
9462306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
9562306a36Sopenharmony_ci	struct inet_diag_dump_data *cb_data;
9662306a36Sopenharmony_ci	int num, s_num, slot, s_slot;
9762306a36Sopenharmony_ci	struct nlattr *bc;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	cb_data = cb->data;
10062306a36Sopenharmony_ci	bc = cb_data->inet_diag_nla_bc;
10162306a36Sopenharmony_ci	s_slot = cb->args[0];
10262306a36Sopenharmony_ci	num = s_num = cb->args[1];
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	for (slot = s_slot; slot <= table->mask; s_num = 0, slot++) {
10562306a36Sopenharmony_ci		struct udp_hslot *hslot = &table->hash[slot];
10662306a36Sopenharmony_ci		struct sock *sk;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		num = 0;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		if (hlist_empty(&hslot->head))
11162306a36Sopenharmony_ci			continue;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		spin_lock_bh(&hslot->lock);
11462306a36Sopenharmony_ci		sk_for_each(sk, &hslot->head) {
11562306a36Sopenharmony_ci			struct inet_sock *inet = inet_sk(sk);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci			if (!net_eq(sock_net(sk), net))
11862306a36Sopenharmony_ci				continue;
11962306a36Sopenharmony_ci			if (num < s_num)
12062306a36Sopenharmony_ci				goto next;
12162306a36Sopenharmony_ci			if (!(r->idiag_states & (1 << sk->sk_state)))
12262306a36Sopenharmony_ci				goto next;
12362306a36Sopenharmony_ci			if (r->sdiag_family != AF_UNSPEC &&
12462306a36Sopenharmony_ci					sk->sk_family != r->sdiag_family)
12562306a36Sopenharmony_ci				goto next;
12662306a36Sopenharmony_ci			if (r->id.idiag_sport != inet->inet_sport &&
12762306a36Sopenharmony_ci			    r->id.idiag_sport)
12862306a36Sopenharmony_ci				goto next;
12962306a36Sopenharmony_ci			if (r->id.idiag_dport != inet->inet_dport &&
13062306a36Sopenharmony_ci			    r->id.idiag_dport)
13162306a36Sopenharmony_ci				goto next;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci			if (sk_diag_dump(sk, skb, cb, r, bc, net_admin) < 0) {
13462306a36Sopenharmony_ci				spin_unlock_bh(&hslot->lock);
13562306a36Sopenharmony_ci				goto done;
13662306a36Sopenharmony_ci			}
13762306a36Sopenharmony_cinext:
13862306a36Sopenharmony_ci			num++;
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci		spin_unlock_bh(&hslot->lock);
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_cidone:
14362306a36Sopenharmony_ci	cb->args[0] = slot;
14462306a36Sopenharmony_ci	cb->args[1] = num;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic void udp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
14862306a36Sopenharmony_ci			  const struct inet_diag_req_v2 *r)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	udp_dump(sock_net(cb->skb->sk)->ipv4.udp_table, skb, cb, r);
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic int udp_diag_dump_one(struct netlink_callback *cb,
15462306a36Sopenharmony_ci			     const struct inet_diag_req_v2 *req)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	return udp_dump_one(sock_net(cb->skb->sk)->ipv4.udp_table, cb, req);
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic void udp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
16062306a36Sopenharmony_ci		void *info)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	r->idiag_rqueue = udp_rqueue_get(sk);
16362306a36Sopenharmony_ci	r->idiag_wqueue = sk_wmem_alloc_get(sk);
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci#ifdef CONFIG_INET_DIAG_DESTROY
16762306a36Sopenharmony_cistatic int __udp_diag_destroy(struct sk_buff *in_skb,
16862306a36Sopenharmony_ci			      const struct inet_diag_req_v2 *req,
16962306a36Sopenharmony_ci			      struct udp_table *tbl)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct net *net = sock_net(in_skb->sk);
17262306a36Sopenharmony_ci	struct sock *sk;
17362306a36Sopenharmony_ci	int err;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	rcu_read_lock();
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (req->sdiag_family == AF_INET)
17862306a36Sopenharmony_ci		sk = __udp4_lib_lookup(net,
17962306a36Sopenharmony_ci				req->id.idiag_dst[0], req->id.idiag_dport,
18062306a36Sopenharmony_ci				req->id.idiag_src[0], req->id.idiag_sport,
18162306a36Sopenharmony_ci				req->id.idiag_if, 0, tbl, NULL);
18262306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
18362306a36Sopenharmony_ci	else if (req->sdiag_family == AF_INET6) {
18462306a36Sopenharmony_ci		if (ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_dst) &&
18562306a36Sopenharmony_ci		    ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_src))
18662306a36Sopenharmony_ci			sk = __udp4_lib_lookup(net,
18762306a36Sopenharmony_ci					req->id.idiag_dst[3], req->id.idiag_dport,
18862306a36Sopenharmony_ci					req->id.idiag_src[3], req->id.idiag_sport,
18962306a36Sopenharmony_ci					req->id.idiag_if, 0, tbl, NULL);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci		else
19262306a36Sopenharmony_ci			sk = __udp6_lib_lookup(net,
19362306a36Sopenharmony_ci					(struct in6_addr *)req->id.idiag_dst,
19462306a36Sopenharmony_ci					req->id.idiag_dport,
19562306a36Sopenharmony_ci					(struct in6_addr *)req->id.idiag_src,
19662306a36Sopenharmony_ci					req->id.idiag_sport,
19762306a36Sopenharmony_ci					req->id.idiag_if, 0, tbl, NULL);
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci#endif
20062306a36Sopenharmony_ci	else {
20162306a36Sopenharmony_ci		rcu_read_unlock();
20262306a36Sopenharmony_ci		return -EINVAL;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
20662306a36Sopenharmony_ci		sk = NULL;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	rcu_read_unlock();
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	if (!sk)
21162306a36Sopenharmony_ci		return -ENOENT;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (sock_diag_check_cookie(sk, req->id.idiag_cookie)) {
21462306a36Sopenharmony_ci		sock_put(sk);
21562306a36Sopenharmony_ci		return -ENOENT;
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	err = sock_diag_destroy(sk, ECONNABORTED);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	sock_put(sk);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return err;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic int udp_diag_destroy(struct sk_buff *in_skb,
22662306a36Sopenharmony_ci			    const struct inet_diag_req_v2 *req)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	return __udp_diag_destroy(in_skb, req, sock_net(in_skb->sk)->ipv4.udp_table);
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic int udplite_diag_destroy(struct sk_buff *in_skb,
23262306a36Sopenharmony_ci				const struct inet_diag_req_v2 *req)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	return __udp_diag_destroy(in_skb, req, &udplite_table);
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci#endif
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic const struct inet_diag_handler udp_diag_handler = {
24062306a36Sopenharmony_ci	.dump		 = udp_diag_dump,
24162306a36Sopenharmony_ci	.dump_one	 = udp_diag_dump_one,
24262306a36Sopenharmony_ci	.idiag_get_info  = udp_diag_get_info,
24362306a36Sopenharmony_ci	.idiag_type	 = IPPROTO_UDP,
24462306a36Sopenharmony_ci	.idiag_info_size = 0,
24562306a36Sopenharmony_ci#ifdef CONFIG_INET_DIAG_DESTROY
24662306a36Sopenharmony_ci	.destroy	 = udp_diag_destroy,
24762306a36Sopenharmony_ci#endif
24862306a36Sopenharmony_ci};
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic void udplite_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
25162306a36Sopenharmony_ci			      const struct inet_diag_req_v2 *r)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	udp_dump(&udplite_table, skb, cb, r);
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic int udplite_diag_dump_one(struct netlink_callback *cb,
25762306a36Sopenharmony_ci				 const struct inet_diag_req_v2 *req)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	return udp_dump_one(&udplite_table, cb, req);
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic const struct inet_diag_handler udplite_diag_handler = {
26362306a36Sopenharmony_ci	.dump		 = udplite_diag_dump,
26462306a36Sopenharmony_ci	.dump_one	 = udplite_diag_dump_one,
26562306a36Sopenharmony_ci	.idiag_get_info  = udp_diag_get_info,
26662306a36Sopenharmony_ci	.idiag_type	 = IPPROTO_UDPLITE,
26762306a36Sopenharmony_ci	.idiag_info_size = 0,
26862306a36Sopenharmony_ci#ifdef CONFIG_INET_DIAG_DESTROY
26962306a36Sopenharmony_ci	.destroy	 = udplite_diag_destroy,
27062306a36Sopenharmony_ci#endif
27162306a36Sopenharmony_ci};
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic int __init udp_diag_init(void)
27462306a36Sopenharmony_ci{
27562306a36Sopenharmony_ci	int err;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	err = inet_diag_register(&udp_diag_handler);
27862306a36Sopenharmony_ci	if (err)
27962306a36Sopenharmony_ci		goto out;
28062306a36Sopenharmony_ci	err = inet_diag_register(&udplite_diag_handler);
28162306a36Sopenharmony_ci	if (err)
28262306a36Sopenharmony_ci		goto out_lite;
28362306a36Sopenharmony_ciout:
28462306a36Sopenharmony_ci	return err;
28562306a36Sopenharmony_ciout_lite:
28662306a36Sopenharmony_ci	inet_diag_unregister(&udp_diag_handler);
28762306a36Sopenharmony_ci	goto out;
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_cistatic void __exit udp_diag_exit(void)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	inet_diag_unregister(&udplite_diag_handler);
29362306a36Sopenharmony_ci	inet_diag_unregister(&udp_diag_handler);
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cimodule_init(udp_diag_init);
29762306a36Sopenharmony_cimodule_exit(udp_diag_exit);
29862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
29962306a36Sopenharmony_ciMODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-17 /* AF_INET - IPPROTO_UDP */);
30062306a36Sopenharmony_ciMODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-136 /* AF_INET - IPPROTO_UDPLITE */);
301