tcp_diag.c (5583B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * tcp_diag.c Module for monitoring TCP transport protocols sockets. 4 * 5 * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> 6 */ 7 8#include <linux/module.h> 9#include <linux/net.h> 10#include <linux/sock_diag.h> 11#include <linux/inet_diag.h> 12 13#include <linux/tcp.h> 14 15#include <net/netlink.h> 16#include <net/tcp.h> 17 18static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, 19 void *_info) 20{ 21 struct tcp_info *info = _info; 22 23 if (inet_sk_state_load(sk) == TCP_LISTEN) { 24 r->idiag_rqueue = READ_ONCE(sk->sk_ack_backlog); 25 r->idiag_wqueue = READ_ONCE(sk->sk_max_ack_backlog); 26 } else if (sk->sk_type == SOCK_STREAM) { 27 const struct tcp_sock *tp = tcp_sk(sk); 28 29 r->idiag_rqueue = max_t(int, READ_ONCE(tp->rcv_nxt) - 30 READ_ONCE(tp->copied_seq), 0); 31 r->idiag_wqueue = READ_ONCE(tp->write_seq) - tp->snd_una; 32 } 33 if (info) 34 tcp_get_info(sk, info); 35} 36 37#ifdef CONFIG_TCP_MD5SIG 38static void tcp_diag_md5sig_fill(struct tcp_diag_md5sig *info, 39 const struct tcp_md5sig_key *key) 40{ 41 info->tcpm_family = key->family; 42 info->tcpm_prefixlen = key->prefixlen; 43 info->tcpm_keylen = key->keylen; 44 memcpy(info->tcpm_key, key->key, key->keylen); 45 46 if (key->family == AF_INET) 47 info->tcpm_addr[0] = key->addr.a4.s_addr; 48 #if IS_ENABLED(CONFIG_IPV6) 49 else if (key->family == AF_INET6) 50 memcpy(&info->tcpm_addr, &key->addr.a6, 51 sizeof(info->tcpm_addr)); 52 #endif 53} 54 55static int tcp_diag_put_md5sig(struct sk_buff *skb, 56 const struct tcp_md5sig_info *md5sig) 57{ 58 const struct tcp_md5sig_key *key; 59 struct tcp_diag_md5sig *info; 60 struct nlattr *attr; 61 int md5sig_count = 0; 62 63 hlist_for_each_entry_rcu(key, &md5sig->head, node) 64 md5sig_count++; 65 if (md5sig_count == 0) 66 return 0; 67 68 attr = nla_reserve(skb, INET_DIAG_MD5SIG, 69 md5sig_count * sizeof(struct tcp_diag_md5sig)); 70 if (!attr) 71 return -EMSGSIZE; 72 73 info = nla_data(attr); 74 memset(info, 0, md5sig_count * sizeof(struct tcp_diag_md5sig)); 75 hlist_for_each_entry_rcu(key, &md5sig->head, node) { 76 tcp_diag_md5sig_fill(info++, key); 77 if (--md5sig_count == 0) 78 break; 79 } 80 81 return 0; 82} 83#endif 84 85static int tcp_diag_put_ulp(struct sk_buff *skb, struct sock *sk, 86 const struct tcp_ulp_ops *ulp_ops) 87{ 88 struct nlattr *nest; 89 int err; 90 91 nest = nla_nest_start_noflag(skb, INET_DIAG_ULP_INFO); 92 if (!nest) 93 return -EMSGSIZE; 94 95 err = nla_put_string(skb, INET_ULP_INFO_NAME, ulp_ops->name); 96 if (err) 97 goto nla_failure; 98 99 if (ulp_ops->get_info) 100 err = ulp_ops->get_info(sk, skb); 101 if (err) 102 goto nla_failure; 103 104 nla_nest_end(skb, nest); 105 return 0; 106 107nla_failure: 108 nla_nest_cancel(skb, nest); 109 return err; 110} 111 112static int tcp_diag_get_aux(struct sock *sk, bool net_admin, 113 struct sk_buff *skb) 114{ 115 struct inet_connection_sock *icsk = inet_csk(sk); 116 int err = 0; 117 118#ifdef CONFIG_TCP_MD5SIG 119 if (net_admin) { 120 struct tcp_md5sig_info *md5sig; 121 122 rcu_read_lock(); 123 md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info); 124 if (md5sig) 125 err = tcp_diag_put_md5sig(skb, md5sig); 126 rcu_read_unlock(); 127 if (err < 0) 128 return err; 129 } 130#endif 131 132 if (net_admin) { 133 const struct tcp_ulp_ops *ulp_ops; 134 135 ulp_ops = icsk->icsk_ulp_ops; 136 if (ulp_ops) 137 err = tcp_diag_put_ulp(skb, sk, ulp_ops); 138 if (err) 139 return err; 140 } 141 return 0; 142} 143 144static size_t tcp_diag_get_aux_size(struct sock *sk, bool net_admin) 145{ 146 struct inet_connection_sock *icsk = inet_csk(sk); 147 size_t size = 0; 148 149#ifdef CONFIG_TCP_MD5SIG 150 if (net_admin && sk_fullsock(sk)) { 151 const struct tcp_md5sig_info *md5sig; 152 const struct tcp_md5sig_key *key; 153 size_t md5sig_count = 0; 154 155 rcu_read_lock(); 156 md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info); 157 if (md5sig) { 158 hlist_for_each_entry_rcu(key, &md5sig->head, node) 159 md5sig_count++; 160 } 161 rcu_read_unlock(); 162 size += nla_total_size(md5sig_count * 163 sizeof(struct tcp_diag_md5sig)); 164 } 165#endif 166 167 if (net_admin && sk_fullsock(sk)) { 168 const struct tcp_ulp_ops *ulp_ops; 169 170 ulp_ops = icsk->icsk_ulp_ops; 171 if (ulp_ops) { 172 size += nla_total_size(0) + 173 nla_total_size(TCP_ULP_NAME_MAX); 174 if (ulp_ops->get_info_size) 175 size += ulp_ops->get_info_size(sk); 176 } 177 } 178 return size; 179} 180 181static void tcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, 182 const struct inet_diag_req_v2 *r) 183{ 184 inet_diag_dump_icsk(&tcp_hashinfo, skb, cb, r); 185} 186 187static int tcp_diag_dump_one(struct netlink_callback *cb, 188 const struct inet_diag_req_v2 *req) 189{ 190 return inet_diag_dump_one_icsk(&tcp_hashinfo, cb, req); 191} 192 193#ifdef CONFIG_INET_DIAG_DESTROY 194static int tcp_diag_destroy(struct sk_buff *in_skb, 195 const struct inet_diag_req_v2 *req) 196{ 197 struct net *net = sock_net(in_skb->sk); 198 struct sock *sk = inet_diag_find_one_icsk(net, &tcp_hashinfo, req); 199 int err; 200 201 if (IS_ERR(sk)) 202 return PTR_ERR(sk); 203 204 err = sock_diag_destroy(sk, ECONNABORTED); 205 206 sock_gen_put(sk); 207 208 return err; 209} 210#endif 211 212static const struct inet_diag_handler tcp_diag_handler = { 213 .dump = tcp_diag_dump, 214 .dump_one = tcp_diag_dump_one, 215 .idiag_get_info = tcp_diag_get_info, 216 .idiag_get_aux = tcp_diag_get_aux, 217 .idiag_get_aux_size = tcp_diag_get_aux_size, 218 .idiag_type = IPPROTO_TCP, 219 .idiag_info_size = sizeof(struct tcp_info), 220#ifdef CONFIG_INET_DIAG_DESTROY 221 .destroy = tcp_diag_destroy, 222#endif 223}; 224 225static int __init tcp_diag_init(void) 226{ 227 return inet_diag_register(&tcp_diag_handler); 228} 229 230static void __exit tcp_diag_exit(void) 231{ 232 inet_diag_unregister(&tcp_diag_handler); 233} 234 235module_init(tcp_diag_init); 236module_exit(tcp_diag_exit); 237MODULE_LICENSE("GPL"); 238MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-6 /* AF_INET - IPPROTO_TCP */);