18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/* MPTCP socket monitoring support
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (c) 2020 Red Hat
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Author: Paolo Abeni <pabeni@redhat.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/net.h>
118c2ecf20Sopenharmony_ci#include <linux/inet_diag.h>
128c2ecf20Sopenharmony_ci#include <net/netlink.h>
138c2ecf20Sopenharmony_ci#include <uapi/linux/mptcp.h>
148c2ecf20Sopenharmony_ci#include "protocol.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_cistatic int sk_diag_dump(struct sock *sk, struct sk_buff *skb,
178c2ecf20Sopenharmony_ci			struct netlink_callback *cb,
188c2ecf20Sopenharmony_ci			const struct inet_diag_req_v2 *req,
198c2ecf20Sopenharmony_ci			struct nlattr *bc, bool net_admin)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	if (!inet_diag_bc_sk(bc, sk))
228c2ecf20Sopenharmony_ci		return 0;
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci	return inet_sk_diag_fill(sk, inet_csk(sk), skb, cb, req, NLM_F_MULTI,
258c2ecf20Sopenharmony_ci				 net_admin);
268c2ecf20Sopenharmony_ci}
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int mptcp_diag_dump_one(struct netlink_callback *cb,
298c2ecf20Sopenharmony_ci			       const struct inet_diag_req_v2 *req)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	struct sk_buff *in_skb = cb->skb;
328c2ecf20Sopenharmony_ci	struct mptcp_sock *msk = NULL;
338c2ecf20Sopenharmony_ci	struct sk_buff *rep;
348c2ecf20Sopenharmony_ci	int err = -ENOENT;
358c2ecf20Sopenharmony_ci	struct net *net;
368c2ecf20Sopenharmony_ci	struct sock *sk;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	net = sock_net(in_skb->sk);
398c2ecf20Sopenharmony_ci	msk = mptcp_token_get_sock(net, req->id.idiag_cookie[0]);
408c2ecf20Sopenharmony_ci	if (!msk)
418c2ecf20Sopenharmony_ci		goto out_nosk;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	err = -ENOMEM;
448c2ecf20Sopenharmony_ci	sk = (struct sock *)msk;
458c2ecf20Sopenharmony_ci	rep = nlmsg_new(nla_total_size(sizeof(struct inet_diag_msg)) +
468c2ecf20Sopenharmony_ci			inet_diag_msg_attrs_size() +
478c2ecf20Sopenharmony_ci			nla_total_size(sizeof(struct mptcp_info)) +
488c2ecf20Sopenharmony_ci			nla_total_size(sizeof(struct inet_diag_meminfo)) + 64,
498c2ecf20Sopenharmony_ci			GFP_KERNEL);
508c2ecf20Sopenharmony_ci	if (!rep)
518c2ecf20Sopenharmony_ci		goto out;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	err = inet_sk_diag_fill(sk, inet_csk(sk), rep, cb, req, 0,
548c2ecf20Sopenharmony_ci				netlink_net_capable(in_skb, CAP_NET_ADMIN));
558c2ecf20Sopenharmony_ci	if (err < 0) {
568c2ecf20Sopenharmony_ci		WARN_ON(err == -EMSGSIZE);
578c2ecf20Sopenharmony_ci		kfree_skb(rep);
588c2ecf20Sopenharmony_ci		goto out;
598c2ecf20Sopenharmony_ci	}
608c2ecf20Sopenharmony_ci	err = netlink_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid,
618c2ecf20Sopenharmony_ci			      MSG_DONTWAIT);
628c2ecf20Sopenharmony_ci	if (err > 0)
638c2ecf20Sopenharmony_ci		err = 0;
648c2ecf20Sopenharmony_ciout:
658c2ecf20Sopenharmony_ci	sock_put(sk);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ciout_nosk:
688c2ecf20Sopenharmony_ci	return err;
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic void mptcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
728c2ecf20Sopenharmony_ci			    const struct inet_diag_req_v2 *r)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN);
758c2ecf20Sopenharmony_ci	struct net *net = sock_net(skb->sk);
768c2ecf20Sopenharmony_ci	struct inet_diag_dump_data *cb_data;
778c2ecf20Sopenharmony_ci	struct mptcp_sock *msk;
788c2ecf20Sopenharmony_ci	struct nlattr *bc;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	cb_data = cb->data;
818c2ecf20Sopenharmony_ci	bc = cb_data->inet_diag_nla_bc;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	while ((msk = mptcp_token_iter_next(net, &cb->args[0], &cb->args[1])) !=
848c2ecf20Sopenharmony_ci	       NULL) {
858c2ecf20Sopenharmony_ci		struct inet_sock *inet = (struct inet_sock *)msk;
868c2ecf20Sopenharmony_ci		struct sock *sk = (struct sock *)msk;
878c2ecf20Sopenharmony_ci		int ret = 0;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci		if (!(r->idiag_states & (1 << sk->sk_state)))
908c2ecf20Sopenharmony_ci			goto next;
918c2ecf20Sopenharmony_ci		if (r->sdiag_family != AF_UNSPEC &&
928c2ecf20Sopenharmony_ci		    sk->sk_family != r->sdiag_family)
938c2ecf20Sopenharmony_ci			goto next;
948c2ecf20Sopenharmony_ci		if (r->id.idiag_sport != inet->inet_sport &&
958c2ecf20Sopenharmony_ci		    r->id.idiag_sport)
968c2ecf20Sopenharmony_ci			goto next;
978c2ecf20Sopenharmony_ci		if (r->id.idiag_dport != inet->inet_dport &&
988c2ecf20Sopenharmony_ci		    r->id.idiag_dport)
998c2ecf20Sopenharmony_ci			goto next;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci		ret = sk_diag_dump(sk, skb, cb, r, bc, net_admin);
1028c2ecf20Sopenharmony_cinext:
1038c2ecf20Sopenharmony_ci		sock_put(sk);
1048c2ecf20Sopenharmony_ci		if (ret < 0) {
1058c2ecf20Sopenharmony_ci			/* will retry on the same position */
1068c2ecf20Sopenharmony_ci			cb->args[1]--;
1078c2ecf20Sopenharmony_ci			break;
1088c2ecf20Sopenharmony_ci		}
1098c2ecf20Sopenharmony_ci		cond_resched();
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic void mptcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
1148c2ecf20Sopenharmony_ci				void *_info)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	struct mptcp_sock *msk = mptcp_sk(sk);
1178c2ecf20Sopenharmony_ci	struct mptcp_info *info = _info;
1188c2ecf20Sopenharmony_ci	u32 flags = 0;
1198c2ecf20Sopenharmony_ci	bool slow;
1208c2ecf20Sopenharmony_ci	u8 val;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	r->idiag_rqueue = sk_rmem_alloc_get(sk);
1238c2ecf20Sopenharmony_ci	r->idiag_wqueue = sk_wmem_alloc_get(sk);
1248c2ecf20Sopenharmony_ci	if (!info)
1258c2ecf20Sopenharmony_ci		return;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	slow = lock_sock_fast(sk);
1288c2ecf20Sopenharmony_ci	info->mptcpi_subflows = READ_ONCE(msk->pm.subflows);
1298c2ecf20Sopenharmony_ci	info->mptcpi_add_addr_signal = READ_ONCE(msk->pm.add_addr_signaled);
1308c2ecf20Sopenharmony_ci	info->mptcpi_add_addr_accepted = READ_ONCE(msk->pm.add_addr_accepted);
1318c2ecf20Sopenharmony_ci	info->mptcpi_subflows_max = READ_ONCE(msk->pm.subflows_max);
1328c2ecf20Sopenharmony_ci	val = READ_ONCE(msk->pm.add_addr_signal_max);
1338c2ecf20Sopenharmony_ci	info->mptcpi_add_addr_signal_max = val;
1348c2ecf20Sopenharmony_ci	val = READ_ONCE(msk->pm.add_addr_accept_max);
1358c2ecf20Sopenharmony_ci	info->mptcpi_add_addr_accepted_max = val;
1368c2ecf20Sopenharmony_ci	if (test_bit(MPTCP_FALLBACK_DONE, &msk->flags))
1378c2ecf20Sopenharmony_ci		flags |= MPTCP_INFO_FLAG_FALLBACK;
1388c2ecf20Sopenharmony_ci	if (READ_ONCE(msk->can_ack))
1398c2ecf20Sopenharmony_ci		flags |= MPTCP_INFO_FLAG_REMOTE_KEY_RECEIVED;
1408c2ecf20Sopenharmony_ci	info->mptcpi_flags = flags;
1418c2ecf20Sopenharmony_ci	info->mptcpi_token = READ_ONCE(msk->token);
1428c2ecf20Sopenharmony_ci	info->mptcpi_write_seq = READ_ONCE(msk->write_seq);
1438c2ecf20Sopenharmony_ci	info->mptcpi_snd_una = atomic64_read(&msk->snd_una);
1448c2ecf20Sopenharmony_ci	info->mptcpi_rcv_nxt = READ_ONCE(msk->ack_seq);
1458c2ecf20Sopenharmony_ci	unlock_sock_fast(sk, slow);
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic const struct inet_diag_handler mptcp_diag_handler = {
1498c2ecf20Sopenharmony_ci	.dump		 = mptcp_diag_dump,
1508c2ecf20Sopenharmony_ci	.dump_one	 = mptcp_diag_dump_one,
1518c2ecf20Sopenharmony_ci	.idiag_get_info  = mptcp_diag_get_info,
1528c2ecf20Sopenharmony_ci	.idiag_type	 = IPPROTO_MPTCP,
1538c2ecf20Sopenharmony_ci	.idiag_info_size = sizeof(struct mptcp_info),
1548c2ecf20Sopenharmony_ci};
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic int __init mptcp_diag_init(void)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	return inet_diag_register(&mptcp_diag_handler);
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_cistatic void __exit mptcp_diag_exit(void)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	inet_diag_unregister(&mptcp_diag_handler);
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cimodule_init(mptcp_diag_init);
1678c2ecf20Sopenharmony_cimodule_exit(mptcp_diag_exit);
1688c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1698c2ecf20Sopenharmony_ciMODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-262 /* AF_INET - IPPROTO_MPTCP */);
170