162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#include <linux/module.h>
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <net/sock.h>
562306a36Sopenharmony_ci#include <linux/netlink.h>
662306a36Sopenharmony_ci#include <linux/sock_diag.h>
762306a36Sopenharmony_ci#include <linux/netlink_diag.h>
862306a36Sopenharmony_ci#include <linux/rhashtable.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "af_netlink.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistatic int sk_diag_dump_groups(struct sock *sk, struct sk_buff *nlskb)
1362306a36Sopenharmony_ci{
1462306a36Sopenharmony_ci	struct netlink_sock *nlk = nlk_sk(sk);
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci	if (nlk->groups == NULL)
1762306a36Sopenharmony_ci		return 0;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	return nla_put(nlskb, NETLINK_DIAG_GROUPS, NLGRPSZ(nlk->ngroups),
2062306a36Sopenharmony_ci		       nlk->groups);
2162306a36Sopenharmony_ci}
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic int sk_diag_put_flags(struct sock *sk, struct sk_buff *skb)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct netlink_sock *nlk = nlk_sk(sk);
2662306a36Sopenharmony_ci	u32 flags = 0;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (nlk->cb_running)
2962306a36Sopenharmony_ci		flags |= NDIAG_FLAG_CB_RUNNING;
3062306a36Sopenharmony_ci	if (nlk_test_bit(RECV_PKTINFO, sk))
3162306a36Sopenharmony_ci		flags |= NDIAG_FLAG_PKTINFO;
3262306a36Sopenharmony_ci	if (nlk_test_bit(BROADCAST_SEND_ERROR, sk))
3362306a36Sopenharmony_ci		flags |= NDIAG_FLAG_BROADCAST_ERROR;
3462306a36Sopenharmony_ci	if (nlk_test_bit(RECV_NO_ENOBUFS, sk))
3562306a36Sopenharmony_ci		flags |= NDIAG_FLAG_NO_ENOBUFS;
3662306a36Sopenharmony_ci	if (nlk_test_bit(LISTEN_ALL_NSID, sk))
3762306a36Sopenharmony_ci		flags |= NDIAG_FLAG_LISTEN_ALL_NSID;
3862306a36Sopenharmony_ci	if (nlk_test_bit(CAP_ACK, sk))
3962306a36Sopenharmony_ci		flags |= NDIAG_FLAG_CAP_ACK;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	return nla_put_u32(skb, NETLINK_DIAG_FLAGS, flags);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic int sk_diag_fill(struct sock *sk, struct sk_buff *skb,
4562306a36Sopenharmony_ci			struct netlink_diag_req *req,
4662306a36Sopenharmony_ci			u32 portid, u32 seq, u32 flags, int sk_ino)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	struct nlmsghdr *nlh;
4962306a36Sopenharmony_ci	struct netlink_diag_msg *rep;
5062306a36Sopenharmony_ci	struct netlink_sock *nlk = nlk_sk(sk);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	nlh = nlmsg_put(skb, portid, seq, SOCK_DIAG_BY_FAMILY, sizeof(*rep),
5362306a36Sopenharmony_ci			flags);
5462306a36Sopenharmony_ci	if (!nlh)
5562306a36Sopenharmony_ci		return -EMSGSIZE;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	rep = nlmsg_data(nlh);
5862306a36Sopenharmony_ci	rep->ndiag_family	= AF_NETLINK;
5962306a36Sopenharmony_ci	rep->ndiag_type		= sk->sk_type;
6062306a36Sopenharmony_ci	rep->ndiag_protocol	= sk->sk_protocol;
6162306a36Sopenharmony_ci	rep->ndiag_state	= sk->sk_state;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	rep->ndiag_ino		= sk_ino;
6462306a36Sopenharmony_ci	rep->ndiag_portid	= nlk->portid;
6562306a36Sopenharmony_ci	rep->ndiag_dst_portid	= nlk->dst_portid;
6662306a36Sopenharmony_ci	rep->ndiag_dst_group	= nlk->dst_group;
6762306a36Sopenharmony_ci	sock_diag_save_cookie(sk, rep->ndiag_cookie);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	if ((req->ndiag_show & NDIAG_SHOW_GROUPS) &&
7062306a36Sopenharmony_ci	    sk_diag_dump_groups(sk, skb))
7162306a36Sopenharmony_ci		goto out_nlmsg_trim;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if ((req->ndiag_show & NDIAG_SHOW_MEMINFO) &&
7462306a36Sopenharmony_ci	    sock_diag_put_meminfo(sk, skb, NETLINK_DIAG_MEMINFO))
7562306a36Sopenharmony_ci		goto out_nlmsg_trim;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if ((req->ndiag_show & NDIAG_SHOW_FLAGS) &&
7862306a36Sopenharmony_ci	    sk_diag_put_flags(sk, skb))
7962306a36Sopenharmony_ci		goto out_nlmsg_trim;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	nlmsg_end(skb, nlh);
8262306a36Sopenharmony_ci	return 0;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ciout_nlmsg_trim:
8562306a36Sopenharmony_ci	nlmsg_cancel(skb, nlh);
8662306a36Sopenharmony_ci	return -EMSGSIZE;
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic int __netlink_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
9062306a36Sopenharmony_ci				int protocol, int s_num)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct rhashtable_iter *hti = (void *)cb->args[2];
9362306a36Sopenharmony_ci	struct netlink_table *tbl = &nl_table[protocol];
9462306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
9562306a36Sopenharmony_ci	struct netlink_diag_req *req;
9662306a36Sopenharmony_ci	struct netlink_sock *nlsk;
9762306a36Sopenharmony_ci	unsigned long flags;
9862306a36Sopenharmony_ci	struct sock *sk;
9962306a36Sopenharmony_ci	int num = 2;
10062306a36Sopenharmony_ci	int ret = 0;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	req = nlmsg_data(cb->nlh);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (s_num > 1)
10562306a36Sopenharmony_ci		goto mc_list;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	num--;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (!hti) {
11062306a36Sopenharmony_ci		hti = kmalloc(sizeof(*hti), GFP_KERNEL);
11162306a36Sopenharmony_ci		if (!hti)
11262306a36Sopenharmony_ci			return -ENOMEM;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci		cb->args[2] = (long)hti;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (!s_num)
11862306a36Sopenharmony_ci		rhashtable_walk_enter(&tbl->hash, hti);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	rhashtable_walk_start(hti);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	while ((nlsk = rhashtable_walk_next(hti))) {
12362306a36Sopenharmony_ci		if (IS_ERR(nlsk)) {
12462306a36Sopenharmony_ci			ret = PTR_ERR(nlsk);
12562306a36Sopenharmony_ci			if (ret == -EAGAIN) {
12662306a36Sopenharmony_ci				ret = 0;
12762306a36Sopenharmony_ci				continue;
12862306a36Sopenharmony_ci			}
12962306a36Sopenharmony_ci			break;
13062306a36Sopenharmony_ci		}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		sk = (struct sock *)nlsk;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		if (!net_eq(sock_net(sk), net))
13562306a36Sopenharmony_ci			continue;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		if (sk_diag_fill(sk, skb, req,
13862306a36Sopenharmony_ci				 NETLINK_CB(cb->skb).portid,
13962306a36Sopenharmony_ci				 cb->nlh->nlmsg_seq,
14062306a36Sopenharmony_ci				 NLM_F_MULTI,
14162306a36Sopenharmony_ci				 sock_i_ino(sk)) < 0) {
14262306a36Sopenharmony_ci			ret = 1;
14362306a36Sopenharmony_ci			break;
14462306a36Sopenharmony_ci		}
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	rhashtable_walk_stop(hti);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (ret)
15062306a36Sopenharmony_ci		goto done;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	rhashtable_walk_exit(hti);
15362306a36Sopenharmony_ci	num++;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cimc_list:
15662306a36Sopenharmony_ci	read_lock_irqsave(&nl_table_lock, flags);
15762306a36Sopenharmony_ci	sk_for_each_bound(sk, &tbl->mc_list) {
15862306a36Sopenharmony_ci		if (sk_hashed(sk))
15962306a36Sopenharmony_ci			continue;
16062306a36Sopenharmony_ci		if (!net_eq(sock_net(sk), net))
16162306a36Sopenharmony_ci			continue;
16262306a36Sopenharmony_ci		if (num < s_num) {
16362306a36Sopenharmony_ci			num++;
16462306a36Sopenharmony_ci			continue;
16562306a36Sopenharmony_ci		}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		if (sk_diag_fill(sk, skb, req,
16862306a36Sopenharmony_ci				 NETLINK_CB(cb->skb).portid,
16962306a36Sopenharmony_ci				 cb->nlh->nlmsg_seq,
17062306a36Sopenharmony_ci				 NLM_F_MULTI,
17162306a36Sopenharmony_ci				 __sock_i_ino(sk)) < 0) {
17262306a36Sopenharmony_ci			ret = 1;
17362306a36Sopenharmony_ci			break;
17462306a36Sopenharmony_ci		}
17562306a36Sopenharmony_ci		num++;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci	read_unlock_irqrestore(&nl_table_lock, flags);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cidone:
18062306a36Sopenharmony_ci	cb->args[0] = num;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return ret;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic int netlink_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct netlink_diag_req *req;
18862306a36Sopenharmony_ci	int s_num = cb->args[0];
18962306a36Sopenharmony_ci	int err = 0;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	req = nlmsg_data(cb->nlh);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (req->sdiag_protocol == NDIAG_PROTO_ALL) {
19462306a36Sopenharmony_ci		int i;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci		for (i = cb->args[1]; i < MAX_LINKS; i++) {
19762306a36Sopenharmony_ci			err = __netlink_diag_dump(skb, cb, i, s_num);
19862306a36Sopenharmony_ci			if (err)
19962306a36Sopenharmony_ci				break;
20062306a36Sopenharmony_ci			s_num = 0;
20162306a36Sopenharmony_ci		}
20262306a36Sopenharmony_ci		cb->args[1] = i;
20362306a36Sopenharmony_ci	} else {
20462306a36Sopenharmony_ci		if (req->sdiag_protocol >= MAX_LINKS)
20562306a36Sopenharmony_ci			return -ENOENT;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		err = __netlink_diag_dump(skb, cb, req->sdiag_protocol, s_num);
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	return err < 0 ? err : skb->len;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic int netlink_diag_dump_done(struct netlink_callback *cb)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	struct rhashtable_iter *hti = (void *)cb->args[2];
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	if (cb->args[0] == 1)
21862306a36Sopenharmony_ci		rhashtable_walk_exit(hti);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	kfree(hti);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return 0;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic int netlink_diag_handler_dump(struct sk_buff *skb, struct nlmsghdr *h)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	int hdrlen = sizeof(struct netlink_diag_req);
22862306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	if (nlmsg_len(h) < hdrlen)
23162306a36Sopenharmony_ci		return -EINVAL;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	if (h->nlmsg_flags & NLM_F_DUMP) {
23462306a36Sopenharmony_ci		struct netlink_dump_control c = {
23562306a36Sopenharmony_ci			.dump = netlink_diag_dump,
23662306a36Sopenharmony_ci			.done = netlink_diag_dump_done,
23762306a36Sopenharmony_ci		};
23862306a36Sopenharmony_ci		return netlink_dump_start(net->diag_nlsk, skb, h, &c);
23962306a36Sopenharmony_ci	} else
24062306a36Sopenharmony_ci		return -EOPNOTSUPP;
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic const struct sock_diag_handler netlink_diag_handler = {
24462306a36Sopenharmony_ci	.family = AF_NETLINK,
24562306a36Sopenharmony_ci	.dump = netlink_diag_handler_dump,
24662306a36Sopenharmony_ci};
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic int __init netlink_diag_init(void)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	return sock_diag_register(&netlink_diag_handler);
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic void __exit netlink_diag_exit(void)
25462306a36Sopenharmony_ci{
25562306a36Sopenharmony_ci	sock_diag_unregister(&netlink_diag_handler);
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cimodule_init(netlink_diag_init);
25962306a36Sopenharmony_cimodule_exit(netlink_diag_exit);
26062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
26162306a36Sopenharmony_ciMODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 16 /* AF_NETLINK */);
262