12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 252b7c59bSPavel Emelyanov /* 352b7c59bSPavel Emelyanov * udp_diag.c Module for monitoring UDP transport protocols sockets. 452b7c59bSPavel Emelyanov * 552b7c59bSPavel Emelyanov * Authors: Pavel Emelyanov, <xemul@parallels.com> 652b7c59bSPavel Emelyanov */ 752b7c59bSPavel Emelyanov 852b7c59bSPavel Emelyanov 952b7c59bSPavel Emelyanov #include <linux/module.h> 1052b7c59bSPavel Emelyanov #include <linux/inet_diag.h> 1152b7c59bSPavel Emelyanov #include <linux/udp.h> 1252b7c59bSPavel Emelyanov #include <net/udp.h> 1352b7c59bSPavel Emelyanov #include <net/udplite.h> 1452b7c59bSPavel Emelyanov #include <linux/sock_diag.h> 1552b7c59bSPavel Emelyanov 16b6d640c2SPavel Emelyanov static int sk_diag_dump(struct sock *sk, struct sk_buff *skb, 1734160ea3SEric Dumazet struct netlink_callback *cb, 1834160ea3SEric Dumazet const struct inet_diag_req_v2 *req, 19d545cacaSLorenzo Colitti struct nlattr *bc, bool net_admin) 20b6d640c2SPavel Emelyanov { 21b6d640c2SPavel Emelyanov if (!inet_diag_bc_sk(bc, sk)) 22b6d640c2SPavel Emelyanov return 0; 23b6d640c2SPavel Emelyanov 24d06ca956SEric W. Biederman return inet_sk_diag_fill(sk, NULL, skb, req, 25e32123e5SPatrick McHardy sk_user_ns(NETLINK_CB(cb->skb).sk), 2615e47304SEric W. Biederman NETLINK_CB(cb->skb).portid, 27d545cacaSLorenzo Colitti cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh, net_admin); 28b6d640c2SPavel Emelyanov } 29b6d640c2SPavel Emelyanov 3052b7c59bSPavel Emelyanov static int udp_dump_one(struct udp_table *tbl, struct sk_buff *in_skb, 3134160ea3SEric Dumazet const struct nlmsghdr *nlh, 3234160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 3352b7c59bSPavel Emelyanov { 34a925aa00SPavel Emelyanov int err = -EINVAL; 35ca065d0cSEric Dumazet struct sock *sk = NULL; 36a925aa00SPavel Emelyanov struct sk_buff *rep; 3751d7cccfSAndrey Vagin struct net *net = sock_net(in_skb->sk); 38a925aa00SPavel Emelyanov 39ca065d0cSEric Dumazet rcu_read_lock(); 40a925aa00SPavel Emelyanov if (req->sdiag_family == AF_INET) 41747569b0SLorenzo Colitti /* src and dst are swapped for historical reasons */ 4251d7cccfSAndrey Vagin sk = __udp4_lib_lookup(net, 43a925aa00SPavel Emelyanov req->id.idiag_src[0], req->id.idiag_sport, 44a925aa00SPavel Emelyanov req->id.idiag_dst[0], req->id.idiag_dport, 45fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 4686e62ad6SPavel Emelyanov #if IS_ENABLED(CONFIG_IPV6) 47a925aa00SPavel Emelyanov else if (req->sdiag_family == AF_INET6) 4851d7cccfSAndrey Vagin sk = __udp6_lib_lookup(net, 49a925aa00SPavel Emelyanov (struct in6_addr *)req->id.idiag_src, 50a925aa00SPavel Emelyanov req->id.idiag_sport, 51a925aa00SPavel Emelyanov (struct in6_addr *)req->id.idiag_dst, 52a925aa00SPavel Emelyanov req->id.idiag_dport, 531801b570SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 5486e62ad6SPavel Emelyanov #endif 5541c6d650SReshetova, Elena if (sk && !refcount_inc_not_zero(&sk->sk_refcnt)) 56ca065d0cSEric Dumazet sk = NULL; 57ca065d0cSEric Dumazet rcu_read_unlock(); 58a925aa00SPavel Emelyanov err = -ENOENT; 5951456b29SIan Morris if (!sk) 60a925aa00SPavel Emelyanov goto out_nosk; 61a925aa00SPavel Emelyanov 62f65c1b53SPavel Emelyanov err = sock_diag_check_cookie(sk, req->id.idiag_cookie); 63a925aa00SPavel Emelyanov if (err) 64a925aa00SPavel Emelyanov goto out; 65a925aa00SPavel Emelyanov 66a925aa00SPavel Emelyanov err = -ENOMEM; 67573ce260SHong zhi guo rep = nlmsg_new(sizeof(struct inet_diag_msg) + 68573ce260SHong zhi guo sizeof(struct inet_diag_meminfo) + 64, 69573ce260SHong zhi guo GFP_KERNEL); 70a925aa00SPavel Emelyanov if (!rep) 71a925aa00SPavel Emelyanov goto out; 72a925aa00SPavel Emelyanov 73a925aa00SPavel Emelyanov err = inet_sk_diag_fill(sk, NULL, rep, req, 74e32123e5SPatrick McHardy sk_user_ns(NETLINK_CB(in_skb).sk), 7515e47304SEric W. Biederman NETLINK_CB(in_skb).portid, 76d545cacaSLorenzo Colitti nlh->nlmsg_seq, 0, nlh, 77d545cacaSLorenzo Colitti netlink_net_capable(in_skb, CAP_NET_ADMIN)); 78a925aa00SPavel Emelyanov if (err < 0) { 79a925aa00SPavel Emelyanov WARN_ON(err == -EMSGSIZE); 80a925aa00SPavel Emelyanov kfree_skb(rep); 81a925aa00SPavel Emelyanov goto out; 82a925aa00SPavel Emelyanov } 8315e47304SEric W. Biederman err = netlink_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid, 84a925aa00SPavel Emelyanov MSG_DONTWAIT); 85a925aa00SPavel Emelyanov if (err > 0) 86a925aa00SPavel Emelyanov err = 0; 87a925aa00SPavel Emelyanov out: 88a925aa00SPavel Emelyanov if (sk) 89a925aa00SPavel Emelyanov sock_put(sk); 90a925aa00SPavel Emelyanov out_nosk: 91a925aa00SPavel Emelyanov return err; 9252b7c59bSPavel Emelyanov } 9352b7c59bSPavel Emelyanov 9434160ea3SEric Dumazet static void udp_dump(struct udp_table *table, struct sk_buff *skb, 9534160ea3SEric Dumazet struct netlink_callback *cb, 9634160ea3SEric Dumazet const struct inet_diag_req_v2 *r, struct nlattr *bc) 9752b7c59bSPavel Emelyanov { 98d545cacaSLorenzo Colitti bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN); 9951d7cccfSAndrey Vagin struct net *net = sock_net(skb->sk); 100ca065d0cSEric Dumazet int num, s_num, slot, s_slot; 101b6d640c2SPavel Emelyanov 102b6d640c2SPavel Emelyanov s_slot = cb->args[0]; 103b6d640c2SPavel Emelyanov num = s_num = cb->args[1]; 104b6d640c2SPavel Emelyanov 10586f3cddbSHerbert Xu for (slot = s_slot; slot <= table->mask; s_num = 0, slot++) { 106b6d640c2SPavel Emelyanov struct udp_hslot *hslot = &table->hash[slot]; 107ca065d0cSEric Dumazet struct sock *sk; 108b6d640c2SPavel Emelyanov 10986f3cddbSHerbert Xu num = 0; 11086f3cddbSHerbert Xu 111ca065d0cSEric Dumazet if (hlist_empty(&hslot->head)) 112b6d640c2SPavel Emelyanov continue; 113b6d640c2SPavel Emelyanov 114b6d640c2SPavel Emelyanov spin_lock_bh(&hslot->lock); 115ca065d0cSEric Dumazet sk_for_each(sk, &hslot->head) { 116b6d640c2SPavel Emelyanov struct inet_sock *inet = inet_sk(sk); 117b6d640c2SPavel Emelyanov 11851d7cccfSAndrey Vagin if (!net_eq(sock_net(sk), net)) 11951d7cccfSAndrey Vagin continue; 120b6d640c2SPavel Emelyanov if (num < s_num) 121b6d640c2SPavel Emelyanov goto next; 122b6d640c2SPavel Emelyanov if (!(r->idiag_states & (1 << sk->sk_state))) 123b6d640c2SPavel Emelyanov goto next; 124b6d640c2SPavel Emelyanov if (r->sdiag_family != AF_UNSPEC && 125b6d640c2SPavel Emelyanov sk->sk_family != r->sdiag_family) 126b6d640c2SPavel Emelyanov goto next; 127b6d640c2SPavel Emelyanov if (r->id.idiag_sport != inet->inet_sport && 128b6d640c2SPavel Emelyanov r->id.idiag_sport) 129b6d640c2SPavel Emelyanov goto next; 130b6d640c2SPavel Emelyanov if (r->id.idiag_dport != inet->inet_dport && 131b6d640c2SPavel Emelyanov r->id.idiag_dport) 132b6d640c2SPavel Emelyanov goto next; 133b6d640c2SPavel Emelyanov 134d545cacaSLorenzo Colitti if (sk_diag_dump(sk, skb, cb, r, bc, net_admin) < 0) { 135b6d640c2SPavel Emelyanov spin_unlock_bh(&hslot->lock); 136b6d640c2SPavel Emelyanov goto done; 137b6d640c2SPavel Emelyanov } 138b6d640c2SPavel Emelyanov next: 139b6d640c2SPavel Emelyanov num++; 140b6d640c2SPavel Emelyanov } 141b6d640c2SPavel Emelyanov spin_unlock_bh(&hslot->lock); 142b6d640c2SPavel Emelyanov } 143b6d640c2SPavel Emelyanov done: 144b6d640c2SPavel Emelyanov cb->args[0] = slot; 145b6d640c2SPavel Emelyanov cb->args[1] = num; 14652b7c59bSPavel Emelyanov } 14752b7c59bSPavel Emelyanov 14852b7c59bSPavel Emelyanov static void udp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, 14934160ea3SEric Dumazet const struct inet_diag_req_v2 *r, struct nlattr *bc) 15052b7c59bSPavel Emelyanov { 15152b7c59bSPavel Emelyanov udp_dump(&udp_table, skb, cb, r, bc); 15252b7c59bSPavel Emelyanov } 15352b7c59bSPavel Emelyanov 15452b7c59bSPavel Emelyanov static int udp_diag_dump_one(struct sk_buff *in_skb, const struct nlmsghdr *nlh, 15534160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 15652b7c59bSPavel Emelyanov { 15752b7c59bSPavel Emelyanov return udp_dump_one(&udp_table, in_skb, nlh, req); 15852b7c59bSPavel Emelyanov } 15952b7c59bSPavel Emelyanov 16062ad6fcdSShan Wei static void udp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, 16162ad6fcdSShan Wei void *info) 16262ad6fcdSShan Wei { 1636c206b20SPaolo Abeni r->idiag_rqueue = udp_rqueue_get(sk); 16462ad6fcdSShan Wei r->idiag_wqueue = sk_wmem_alloc_get(sk); 16562ad6fcdSShan Wei } 16662ad6fcdSShan Wei 1675d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 1685d77dca8SDavid Ahern static int __udp_diag_destroy(struct sk_buff *in_skb, 1695d77dca8SDavid Ahern const struct inet_diag_req_v2 *req, 1705d77dca8SDavid Ahern struct udp_table *tbl) 1715d77dca8SDavid Ahern { 1725d77dca8SDavid Ahern struct net *net = sock_net(in_skb->sk); 1735d77dca8SDavid Ahern struct sock *sk; 1745d77dca8SDavid Ahern int err; 1755d77dca8SDavid Ahern 1765d77dca8SDavid Ahern rcu_read_lock(); 1775d77dca8SDavid Ahern 1785d77dca8SDavid Ahern if (req->sdiag_family == AF_INET) 1795d77dca8SDavid Ahern sk = __udp4_lib_lookup(net, 1805d77dca8SDavid Ahern req->id.idiag_dst[0], req->id.idiag_dport, 1815d77dca8SDavid Ahern req->id.idiag_src[0], req->id.idiag_sport, 182fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 1835d77dca8SDavid Ahern #if IS_ENABLED(CONFIG_IPV6) 1845d77dca8SDavid Ahern else if (req->sdiag_family == AF_INET6) { 1855d77dca8SDavid Ahern if (ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_dst) && 1865d77dca8SDavid Ahern ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_src)) 1875d77dca8SDavid Ahern sk = __udp4_lib_lookup(net, 188f95bf346SLorenzo Colitti req->id.idiag_dst[3], req->id.idiag_dport, 189f95bf346SLorenzo Colitti req->id.idiag_src[3], req->id.idiag_sport, 190fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 1915d77dca8SDavid Ahern 1925d77dca8SDavid Ahern else 1935d77dca8SDavid Ahern sk = __udp6_lib_lookup(net, 1945d77dca8SDavid Ahern (struct in6_addr *)req->id.idiag_dst, 1955d77dca8SDavid Ahern req->id.idiag_dport, 1965d77dca8SDavid Ahern (struct in6_addr *)req->id.idiag_src, 1975d77dca8SDavid Ahern req->id.idiag_sport, 1981801b570SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 1995d77dca8SDavid Ahern } 2005d77dca8SDavid Ahern #endif 2015d77dca8SDavid Ahern else { 2025d77dca8SDavid Ahern rcu_read_unlock(); 2035d77dca8SDavid Ahern return -EINVAL; 2045d77dca8SDavid Ahern } 2055d77dca8SDavid Ahern 20641c6d650SReshetova, Elena if (sk && !refcount_inc_not_zero(&sk->sk_refcnt)) 2075d77dca8SDavid Ahern sk = NULL; 2085d77dca8SDavid Ahern 2095d77dca8SDavid Ahern rcu_read_unlock(); 2105d77dca8SDavid Ahern 2115d77dca8SDavid Ahern if (!sk) 2125d77dca8SDavid Ahern return -ENOENT; 2135d77dca8SDavid Ahern 2145d77dca8SDavid Ahern if (sock_diag_check_cookie(sk, req->id.idiag_cookie)) { 2155d77dca8SDavid Ahern sock_put(sk); 2165d77dca8SDavid Ahern return -ENOENT; 2175d77dca8SDavid Ahern } 2185d77dca8SDavid Ahern 2195d77dca8SDavid Ahern err = sock_diag_destroy(sk, ECONNABORTED); 2205d77dca8SDavid Ahern 2215d77dca8SDavid Ahern sock_put(sk); 2225d77dca8SDavid Ahern 2235d77dca8SDavid Ahern return err; 2245d77dca8SDavid Ahern } 2255d77dca8SDavid Ahern 2265d77dca8SDavid Ahern static int udp_diag_destroy(struct sk_buff *in_skb, 2275d77dca8SDavid Ahern const struct inet_diag_req_v2 *req) 2285d77dca8SDavid Ahern { 2295d77dca8SDavid Ahern return __udp_diag_destroy(in_skb, req, &udp_table); 2305d77dca8SDavid Ahern } 2315d77dca8SDavid Ahern 2325d77dca8SDavid Ahern static int udplite_diag_destroy(struct sk_buff *in_skb, 2335d77dca8SDavid Ahern const struct inet_diag_req_v2 *req) 2345d77dca8SDavid Ahern { 2355d77dca8SDavid Ahern return __udp_diag_destroy(in_skb, req, &udplite_table); 2365d77dca8SDavid Ahern } 2375d77dca8SDavid Ahern 2385d77dca8SDavid Ahern #endif 2395d77dca8SDavid Ahern 24052b7c59bSPavel Emelyanov static const struct inet_diag_handler udp_diag_handler = { 24152b7c59bSPavel Emelyanov .dump = udp_diag_dump, 24252b7c59bSPavel Emelyanov .dump_one = udp_diag_dump_one, 24362ad6fcdSShan Wei .idiag_get_info = udp_diag_get_info, 24452b7c59bSPavel Emelyanov .idiag_type = IPPROTO_UDP, 2453fd22af8SCraig Gallek .idiag_info_size = 0, 2465d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 2475d77dca8SDavid Ahern .destroy = udp_diag_destroy, 2485d77dca8SDavid Ahern #endif 24952b7c59bSPavel Emelyanov }; 25052b7c59bSPavel Emelyanov 25152b7c59bSPavel Emelyanov static void udplite_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, 25234160ea3SEric Dumazet const struct inet_diag_req_v2 *r, 25334160ea3SEric Dumazet struct nlattr *bc) 25452b7c59bSPavel Emelyanov { 25552b7c59bSPavel Emelyanov udp_dump(&udplite_table, skb, cb, r, bc); 25652b7c59bSPavel Emelyanov } 25752b7c59bSPavel Emelyanov 25852b7c59bSPavel Emelyanov static int udplite_diag_dump_one(struct sk_buff *in_skb, const struct nlmsghdr *nlh, 25934160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 26052b7c59bSPavel Emelyanov { 26152b7c59bSPavel Emelyanov return udp_dump_one(&udplite_table, in_skb, nlh, req); 26252b7c59bSPavel Emelyanov } 26352b7c59bSPavel Emelyanov 26452b7c59bSPavel Emelyanov static const struct inet_diag_handler udplite_diag_handler = { 26552b7c59bSPavel Emelyanov .dump = udplite_diag_dump, 26652b7c59bSPavel Emelyanov .dump_one = udplite_diag_dump_one, 26762ad6fcdSShan Wei .idiag_get_info = udp_diag_get_info, 26852b7c59bSPavel Emelyanov .idiag_type = IPPROTO_UDPLITE, 2693fd22af8SCraig Gallek .idiag_info_size = 0, 2705d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 2715d77dca8SDavid Ahern .destroy = udplite_diag_destroy, 2725d77dca8SDavid Ahern #endif 27352b7c59bSPavel Emelyanov }; 27452b7c59bSPavel Emelyanov 27552b7c59bSPavel Emelyanov static int __init udp_diag_init(void) 27652b7c59bSPavel Emelyanov { 27752b7c59bSPavel Emelyanov int err; 27852b7c59bSPavel Emelyanov 27952b7c59bSPavel Emelyanov err = inet_diag_register(&udp_diag_handler); 28052b7c59bSPavel Emelyanov if (err) 28152b7c59bSPavel Emelyanov goto out; 28252b7c59bSPavel Emelyanov err = inet_diag_register(&udplite_diag_handler); 28352b7c59bSPavel Emelyanov if (err) 28452b7c59bSPavel Emelyanov goto out_lite; 28552b7c59bSPavel Emelyanov out: 28652b7c59bSPavel Emelyanov return err; 28752b7c59bSPavel Emelyanov out_lite: 28852b7c59bSPavel Emelyanov inet_diag_unregister(&udp_diag_handler); 28952b7c59bSPavel Emelyanov goto out; 29052b7c59bSPavel Emelyanov } 29152b7c59bSPavel Emelyanov 29252b7c59bSPavel Emelyanov static void __exit udp_diag_exit(void) 29352b7c59bSPavel Emelyanov { 29452b7c59bSPavel Emelyanov inet_diag_unregister(&udplite_diag_handler); 29552b7c59bSPavel Emelyanov inet_diag_unregister(&udp_diag_handler); 29652b7c59bSPavel Emelyanov } 29752b7c59bSPavel Emelyanov 29852b7c59bSPavel Emelyanov module_init(udp_diag_init); 29952b7c59bSPavel Emelyanov module_exit(udp_diag_exit); 30052b7c59bSPavel Emelyanov MODULE_LICENSE("GPL"); 301aec8dc62SPavel Emelyanov MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-17 /* AF_INET - IPPROTO_UDP */); 302aec8dc62SPavel Emelyanov MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-136 /* AF_INET - IPPROTO_UDPLITE */); 303