162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * tcp_diag.c	Module for monitoring TCP transport protocols sockets.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/net.h>
1062306a36Sopenharmony_ci#include <linux/sock_diag.h>
1162306a36Sopenharmony_ci#include <linux/inet_diag.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/tcp.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <net/netlink.h>
1662306a36Sopenharmony_ci#include <net/tcp.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
1962306a36Sopenharmony_ci			      void *_info)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	struct tcp_info *info = _info;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	if (inet_sk_state_load(sk) == TCP_LISTEN) {
2462306a36Sopenharmony_ci		r->idiag_rqueue = READ_ONCE(sk->sk_ack_backlog);
2562306a36Sopenharmony_ci		r->idiag_wqueue = READ_ONCE(sk->sk_max_ack_backlog);
2662306a36Sopenharmony_ci	} else if (sk->sk_type == SOCK_STREAM) {
2762306a36Sopenharmony_ci		const struct tcp_sock *tp = tcp_sk(sk);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci		r->idiag_rqueue = max_t(int, READ_ONCE(tp->rcv_nxt) -
3062306a36Sopenharmony_ci					     READ_ONCE(tp->copied_seq), 0);
3162306a36Sopenharmony_ci		r->idiag_wqueue = READ_ONCE(tp->write_seq) - tp->snd_una;
3262306a36Sopenharmony_ci	}
3362306a36Sopenharmony_ci	if (info)
3462306a36Sopenharmony_ci		tcp_get_info(sk, info);
3562306a36Sopenharmony_ci}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#ifdef CONFIG_TCP_MD5SIG
3862306a36Sopenharmony_cistatic void tcp_diag_md5sig_fill(struct tcp_diag_md5sig *info,
3962306a36Sopenharmony_ci				 const struct tcp_md5sig_key *key)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	info->tcpm_family = key->family;
4262306a36Sopenharmony_ci	info->tcpm_prefixlen = key->prefixlen;
4362306a36Sopenharmony_ci	info->tcpm_keylen = key->keylen;
4462306a36Sopenharmony_ci	memcpy(info->tcpm_key, key->key, key->keylen);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	if (key->family == AF_INET)
4762306a36Sopenharmony_ci		info->tcpm_addr[0] = key->addr.a4.s_addr;
4862306a36Sopenharmony_ci	#if IS_ENABLED(CONFIG_IPV6)
4962306a36Sopenharmony_ci	else if (key->family == AF_INET6)
5062306a36Sopenharmony_ci		memcpy(&info->tcpm_addr, &key->addr.a6,
5162306a36Sopenharmony_ci		       sizeof(info->tcpm_addr));
5262306a36Sopenharmony_ci	#endif
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int tcp_diag_put_md5sig(struct sk_buff *skb,
5662306a36Sopenharmony_ci			       const struct tcp_md5sig_info *md5sig)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	const struct tcp_md5sig_key *key;
5962306a36Sopenharmony_ci	struct tcp_diag_md5sig *info;
6062306a36Sopenharmony_ci	struct nlattr *attr;
6162306a36Sopenharmony_ci	int md5sig_count = 0;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	hlist_for_each_entry_rcu(key, &md5sig->head, node)
6462306a36Sopenharmony_ci		md5sig_count++;
6562306a36Sopenharmony_ci	if (md5sig_count == 0)
6662306a36Sopenharmony_ci		return 0;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	attr = nla_reserve(skb, INET_DIAG_MD5SIG,
6962306a36Sopenharmony_ci			   md5sig_count * sizeof(struct tcp_diag_md5sig));
7062306a36Sopenharmony_ci	if (!attr)
7162306a36Sopenharmony_ci		return -EMSGSIZE;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	info = nla_data(attr);
7462306a36Sopenharmony_ci	memset(info, 0, md5sig_count * sizeof(struct tcp_diag_md5sig));
7562306a36Sopenharmony_ci	hlist_for_each_entry_rcu(key, &md5sig->head, node) {
7662306a36Sopenharmony_ci		tcp_diag_md5sig_fill(info++, key);
7762306a36Sopenharmony_ci		if (--md5sig_count == 0)
7862306a36Sopenharmony_ci			break;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return 0;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci#endif
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic int tcp_diag_put_ulp(struct sk_buff *skb, struct sock *sk,
8662306a36Sopenharmony_ci			    const struct tcp_ulp_ops *ulp_ops)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	struct nlattr *nest;
8962306a36Sopenharmony_ci	int err;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	nest = nla_nest_start_noflag(skb, INET_DIAG_ULP_INFO);
9262306a36Sopenharmony_ci	if (!nest)
9362306a36Sopenharmony_ci		return -EMSGSIZE;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	err = nla_put_string(skb, INET_ULP_INFO_NAME, ulp_ops->name);
9662306a36Sopenharmony_ci	if (err)
9762306a36Sopenharmony_ci		goto nla_failure;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (ulp_ops->get_info)
10062306a36Sopenharmony_ci		err = ulp_ops->get_info(sk, skb);
10162306a36Sopenharmony_ci	if (err)
10262306a36Sopenharmony_ci		goto nla_failure;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	nla_nest_end(skb, nest);
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cinla_failure:
10862306a36Sopenharmony_ci	nla_nest_cancel(skb, nest);
10962306a36Sopenharmony_ci	return err;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int tcp_diag_get_aux(struct sock *sk, bool net_admin,
11362306a36Sopenharmony_ci			    struct sk_buff *skb)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	struct inet_connection_sock *icsk = inet_csk(sk);
11662306a36Sopenharmony_ci	int err = 0;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci#ifdef CONFIG_TCP_MD5SIG
11962306a36Sopenharmony_ci	if (net_admin) {
12062306a36Sopenharmony_ci		struct tcp_md5sig_info *md5sig;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		rcu_read_lock();
12362306a36Sopenharmony_ci		md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
12462306a36Sopenharmony_ci		if (md5sig)
12562306a36Sopenharmony_ci			err = tcp_diag_put_md5sig(skb, md5sig);
12662306a36Sopenharmony_ci		rcu_read_unlock();
12762306a36Sopenharmony_ci		if (err < 0)
12862306a36Sopenharmony_ci			return err;
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci#endif
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	if (net_admin) {
13362306a36Sopenharmony_ci		const struct tcp_ulp_ops *ulp_ops;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci		ulp_ops = icsk->icsk_ulp_ops;
13662306a36Sopenharmony_ci		if (ulp_ops)
13762306a36Sopenharmony_ci			err = tcp_diag_put_ulp(skb, sk, ulp_ops);
13862306a36Sopenharmony_ci		if (err)
13962306a36Sopenharmony_ci			return err;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci	return 0;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic size_t tcp_diag_get_aux_size(struct sock *sk, bool net_admin)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct inet_connection_sock *icsk = inet_csk(sk);
14762306a36Sopenharmony_ci	size_t size = 0;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci#ifdef CONFIG_TCP_MD5SIG
15062306a36Sopenharmony_ci	if (net_admin && sk_fullsock(sk)) {
15162306a36Sopenharmony_ci		const struct tcp_md5sig_info *md5sig;
15262306a36Sopenharmony_ci		const struct tcp_md5sig_key *key;
15362306a36Sopenharmony_ci		size_t md5sig_count = 0;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		rcu_read_lock();
15662306a36Sopenharmony_ci		md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
15762306a36Sopenharmony_ci		if (md5sig) {
15862306a36Sopenharmony_ci			hlist_for_each_entry_rcu(key, &md5sig->head, node)
15962306a36Sopenharmony_ci				md5sig_count++;
16062306a36Sopenharmony_ci		}
16162306a36Sopenharmony_ci		rcu_read_unlock();
16262306a36Sopenharmony_ci		size += nla_total_size(md5sig_count *
16362306a36Sopenharmony_ci				       sizeof(struct tcp_diag_md5sig));
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci#endif
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (net_admin && sk_fullsock(sk)) {
16862306a36Sopenharmony_ci		const struct tcp_ulp_ops *ulp_ops;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci		ulp_ops = icsk->icsk_ulp_ops;
17162306a36Sopenharmony_ci		if (ulp_ops) {
17262306a36Sopenharmony_ci			size += nla_total_size(0) +
17362306a36Sopenharmony_ci				nla_total_size(TCP_ULP_NAME_MAX);
17462306a36Sopenharmony_ci			if (ulp_ops->get_info_size)
17562306a36Sopenharmony_ci				size += ulp_ops->get_info_size(sk);
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci	return size;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic void tcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
18262306a36Sopenharmony_ci			  const struct inet_diag_req_v2 *r)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	struct inet_hashinfo *hinfo;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	hinfo = sock_net(cb->skb->sk)->ipv4.tcp_death_row.hashinfo;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	inet_diag_dump_icsk(hinfo, skb, cb, r);
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic int tcp_diag_dump_one(struct netlink_callback *cb,
19262306a36Sopenharmony_ci			     const struct inet_diag_req_v2 *req)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct inet_hashinfo *hinfo;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	hinfo = sock_net(cb->skb->sk)->ipv4.tcp_death_row.hashinfo;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	return inet_diag_dump_one_icsk(hinfo, cb, req);
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci#ifdef CONFIG_INET_DIAG_DESTROY
20262306a36Sopenharmony_cistatic int tcp_diag_destroy(struct sk_buff *in_skb,
20362306a36Sopenharmony_ci			    const struct inet_diag_req_v2 *req)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct net *net = sock_net(in_skb->sk);
20662306a36Sopenharmony_ci	struct inet_hashinfo *hinfo;
20762306a36Sopenharmony_ci	struct sock *sk;
20862306a36Sopenharmony_ci	int err;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	hinfo = net->ipv4.tcp_death_row.hashinfo;
21162306a36Sopenharmony_ci	sk = inet_diag_find_one_icsk(net, hinfo, req);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (IS_ERR(sk))
21462306a36Sopenharmony_ci		return PTR_ERR(sk);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	err = sock_diag_destroy(sk, ECONNABORTED);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	sock_gen_put(sk);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return err;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci#endif
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic const struct inet_diag_handler tcp_diag_handler = {
22562306a36Sopenharmony_ci	.dump			= tcp_diag_dump,
22662306a36Sopenharmony_ci	.dump_one		= tcp_diag_dump_one,
22762306a36Sopenharmony_ci	.idiag_get_info		= tcp_diag_get_info,
22862306a36Sopenharmony_ci	.idiag_get_aux		= tcp_diag_get_aux,
22962306a36Sopenharmony_ci	.idiag_get_aux_size	= tcp_diag_get_aux_size,
23062306a36Sopenharmony_ci	.idiag_type		= IPPROTO_TCP,
23162306a36Sopenharmony_ci	.idiag_info_size	= sizeof(struct tcp_info),
23262306a36Sopenharmony_ci#ifdef CONFIG_INET_DIAG_DESTROY
23362306a36Sopenharmony_ci	.destroy		= tcp_diag_destroy,
23462306a36Sopenharmony_ci#endif
23562306a36Sopenharmony_ci};
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic int __init tcp_diag_init(void)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	return inet_diag_register(&tcp_diag_handler);
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic void __exit tcp_diag_exit(void)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	inet_diag_unregister(&tcp_diag_handler);
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cimodule_init(tcp_diag_init);
24862306a36Sopenharmony_cimodule_exit(tcp_diag_exit);
24962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
25062306a36Sopenharmony_ciMODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-6 /* AF_INET - IPPROTO_TCP */);
251