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