152b7c59bSPavel Emelyanov /* 252b7c59bSPavel Emelyanov * udp_diag.c Module for monitoring UDP transport protocols sockets. 352b7c59bSPavel Emelyanov * 452b7c59bSPavel Emelyanov * Authors: Pavel Emelyanov, <xemul@parallels.com> 552b7c59bSPavel Emelyanov * 652b7c59bSPavel Emelyanov * This program is free software; you can redistribute it and/or 752b7c59bSPavel Emelyanov * modify it under the terms of the GNU General Public License 852b7c59bSPavel Emelyanov * as published by the Free Software Foundation; either version 952b7c59bSPavel Emelyanov * 2 of the License, or (at your option) any later version. 1052b7c59bSPavel Emelyanov */ 1152b7c59bSPavel Emelyanov 1252b7c59bSPavel Emelyanov 1352b7c59bSPavel Emelyanov #include <linux/module.h> 1452b7c59bSPavel Emelyanov #include <linux/inet_diag.h> 1552b7c59bSPavel Emelyanov #include <linux/udp.h> 1652b7c59bSPavel Emelyanov #include <net/udp.h> 1752b7c59bSPavel Emelyanov #include <net/udplite.h> 1852b7c59bSPavel Emelyanov #include <linux/sock_diag.h> 1952b7c59bSPavel Emelyanov 20b6d640c2SPavel Emelyanov static int sk_diag_dump(struct sock *sk, struct sk_buff *skb, 2134160ea3SEric Dumazet struct netlink_callback *cb, 2234160ea3SEric Dumazet const struct inet_diag_req_v2 *req, 23d545cacaSLorenzo Colitti struct nlattr *bc, bool net_admin) 24b6d640c2SPavel Emelyanov { 25b6d640c2SPavel Emelyanov if (!inet_diag_bc_sk(bc, sk)) 26b6d640c2SPavel Emelyanov return 0; 27b6d640c2SPavel Emelyanov 28d06ca956SEric W. Biederman return inet_sk_diag_fill(sk, NULL, skb, req, 29e32123e5SPatrick McHardy sk_user_ns(NETLINK_CB(cb->skb).sk), 3015e47304SEric W. Biederman NETLINK_CB(cb->skb).portid, 31d545cacaSLorenzo Colitti cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh, net_admin); 32b6d640c2SPavel Emelyanov } 33b6d640c2SPavel Emelyanov 3452b7c59bSPavel Emelyanov static int udp_dump_one(struct udp_table *tbl, struct sk_buff *in_skb, 3534160ea3SEric Dumazet const struct nlmsghdr *nlh, 3634160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 3752b7c59bSPavel Emelyanov { 38a925aa00SPavel Emelyanov int err = -EINVAL; 39ca065d0cSEric Dumazet struct sock *sk = NULL; 40a925aa00SPavel Emelyanov struct sk_buff *rep; 4151d7cccfSAndrey Vagin struct net *net = sock_net(in_skb->sk); 42a925aa00SPavel Emelyanov 43ca065d0cSEric Dumazet rcu_read_lock(); 44a925aa00SPavel Emelyanov if (req->sdiag_family == AF_INET) 45747569b0SLorenzo Colitti /* src and dst are swapped for historical reasons */ 4651d7cccfSAndrey Vagin sk = __udp4_lib_lookup(net, 47a925aa00SPavel Emelyanov req->id.idiag_src[0], req->id.idiag_sport, 48a925aa00SPavel Emelyanov req->id.idiag_dst[0], req->id.idiag_dport, 49fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 5086e62ad6SPavel Emelyanov #if IS_ENABLED(CONFIG_IPV6) 51a925aa00SPavel Emelyanov else if (req->sdiag_family == AF_INET6) 5251d7cccfSAndrey Vagin sk = __udp6_lib_lookup(net, 53a925aa00SPavel Emelyanov (struct in6_addr *)req->id.idiag_src, 54a925aa00SPavel Emelyanov req->id.idiag_sport, 55a925aa00SPavel Emelyanov (struct in6_addr *)req->id.idiag_dst, 56a925aa00SPavel Emelyanov req->id.idiag_dport, 571801b570SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 5886e62ad6SPavel Emelyanov #endif 5941c6d650SReshetova, Elena if (sk && !refcount_inc_not_zero(&sk->sk_refcnt)) 60ca065d0cSEric Dumazet sk = NULL; 61ca065d0cSEric Dumazet rcu_read_unlock(); 62a925aa00SPavel Emelyanov err = -ENOENT; 6351456b29SIan Morris if (!sk) 64a925aa00SPavel Emelyanov goto out_nosk; 65a925aa00SPavel Emelyanov 66f65c1b53SPavel Emelyanov err = sock_diag_check_cookie(sk, req->id.idiag_cookie); 67a925aa00SPavel Emelyanov if (err) 68a925aa00SPavel Emelyanov goto out; 69a925aa00SPavel Emelyanov 70a925aa00SPavel Emelyanov err = -ENOMEM; 71573ce260SHong zhi guo rep = nlmsg_new(sizeof(struct inet_diag_msg) + 72573ce260SHong zhi guo sizeof(struct inet_diag_meminfo) + 64, 73573ce260SHong zhi guo GFP_KERNEL); 74a925aa00SPavel Emelyanov if (!rep) 75a925aa00SPavel Emelyanov goto out; 76a925aa00SPavel Emelyanov 77a925aa00SPavel Emelyanov err = inet_sk_diag_fill(sk, NULL, rep, req, 78e32123e5SPatrick McHardy sk_user_ns(NETLINK_CB(in_skb).sk), 7915e47304SEric W. Biederman NETLINK_CB(in_skb).portid, 80d545cacaSLorenzo Colitti nlh->nlmsg_seq, 0, nlh, 81d545cacaSLorenzo Colitti netlink_net_capable(in_skb, CAP_NET_ADMIN)); 82a925aa00SPavel Emelyanov if (err < 0) { 83a925aa00SPavel Emelyanov WARN_ON(err == -EMSGSIZE); 84a925aa00SPavel Emelyanov kfree_skb(rep); 85a925aa00SPavel Emelyanov goto out; 86a925aa00SPavel Emelyanov } 8715e47304SEric W. Biederman err = netlink_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid, 88a925aa00SPavel Emelyanov MSG_DONTWAIT); 89a925aa00SPavel Emelyanov if (err > 0) 90a925aa00SPavel Emelyanov err = 0; 91a925aa00SPavel Emelyanov out: 92a925aa00SPavel Emelyanov if (sk) 93a925aa00SPavel Emelyanov sock_put(sk); 94a925aa00SPavel Emelyanov out_nosk: 95a925aa00SPavel Emelyanov return err; 9652b7c59bSPavel Emelyanov } 9752b7c59bSPavel Emelyanov 9834160ea3SEric Dumazet static void udp_dump(struct udp_table *table, struct sk_buff *skb, 9934160ea3SEric Dumazet struct netlink_callback *cb, 10034160ea3SEric Dumazet const struct inet_diag_req_v2 *r, struct nlattr *bc) 10152b7c59bSPavel Emelyanov { 102d545cacaSLorenzo Colitti bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN); 10351d7cccfSAndrey Vagin struct net *net = sock_net(skb->sk); 104ca065d0cSEric Dumazet int num, s_num, slot, s_slot; 105b6d640c2SPavel Emelyanov 106b6d640c2SPavel Emelyanov s_slot = cb->args[0]; 107b6d640c2SPavel Emelyanov num = s_num = cb->args[1]; 108b6d640c2SPavel Emelyanov 10986f3cddbSHerbert Xu for (slot = s_slot; slot <= table->mask; s_num = 0, slot++) { 110b6d640c2SPavel Emelyanov struct udp_hslot *hslot = &table->hash[slot]; 111ca065d0cSEric Dumazet struct sock *sk; 112b6d640c2SPavel Emelyanov 11386f3cddbSHerbert Xu num = 0; 11486f3cddbSHerbert Xu 115ca065d0cSEric Dumazet if (hlist_empty(&hslot->head)) 116b6d640c2SPavel Emelyanov continue; 117b6d640c2SPavel Emelyanov 118b6d640c2SPavel Emelyanov spin_lock_bh(&hslot->lock); 119ca065d0cSEric Dumazet sk_for_each(sk, &hslot->head) { 120b6d640c2SPavel Emelyanov struct inet_sock *inet = inet_sk(sk); 121b6d640c2SPavel Emelyanov 12251d7cccfSAndrey Vagin if (!net_eq(sock_net(sk), net)) 12351d7cccfSAndrey Vagin continue; 124b6d640c2SPavel Emelyanov if (num < s_num) 125b6d640c2SPavel Emelyanov goto next; 126b6d640c2SPavel Emelyanov if (!(r->idiag_states & (1 << sk->sk_state))) 127b6d640c2SPavel Emelyanov goto next; 128b6d640c2SPavel Emelyanov if (r->sdiag_family != AF_UNSPEC && 129b6d640c2SPavel Emelyanov sk->sk_family != r->sdiag_family) 130b6d640c2SPavel Emelyanov goto next; 131b6d640c2SPavel Emelyanov if (r->id.idiag_sport != inet->inet_sport && 132b6d640c2SPavel Emelyanov r->id.idiag_sport) 133b6d640c2SPavel Emelyanov goto next; 134b6d640c2SPavel Emelyanov if (r->id.idiag_dport != inet->inet_dport && 135b6d640c2SPavel Emelyanov r->id.idiag_dport) 136b6d640c2SPavel Emelyanov goto next; 137b6d640c2SPavel Emelyanov 138d545cacaSLorenzo Colitti if (sk_diag_dump(sk, skb, cb, r, bc, net_admin) < 0) { 139b6d640c2SPavel Emelyanov spin_unlock_bh(&hslot->lock); 140b6d640c2SPavel Emelyanov goto done; 141b6d640c2SPavel Emelyanov } 142b6d640c2SPavel Emelyanov next: 143b6d640c2SPavel Emelyanov num++; 144b6d640c2SPavel Emelyanov } 145b6d640c2SPavel Emelyanov spin_unlock_bh(&hslot->lock); 146b6d640c2SPavel Emelyanov } 147b6d640c2SPavel Emelyanov done: 148b6d640c2SPavel Emelyanov cb->args[0] = slot; 149b6d640c2SPavel Emelyanov cb->args[1] = num; 15052b7c59bSPavel Emelyanov } 15152b7c59bSPavel Emelyanov 15252b7c59bSPavel Emelyanov static void udp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, 15334160ea3SEric Dumazet const struct inet_diag_req_v2 *r, struct nlattr *bc) 15452b7c59bSPavel Emelyanov { 15552b7c59bSPavel Emelyanov udp_dump(&udp_table, skb, cb, r, bc); 15652b7c59bSPavel Emelyanov } 15752b7c59bSPavel Emelyanov 15852b7c59bSPavel Emelyanov static int udp_diag_dump_one(struct sk_buff *in_skb, const struct nlmsghdr *nlh, 15934160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 16052b7c59bSPavel Emelyanov { 16152b7c59bSPavel Emelyanov return udp_dump_one(&udp_table, in_skb, nlh, req); 16252b7c59bSPavel Emelyanov } 16352b7c59bSPavel Emelyanov 16462ad6fcdSShan Wei static void udp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, 16562ad6fcdSShan Wei void *info) 16662ad6fcdSShan Wei { 1676c206b20SPaolo Abeni r->idiag_rqueue = udp_rqueue_get(sk); 16862ad6fcdSShan Wei r->idiag_wqueue = sk_wmem_alloc_get(sk); 16962ad6fcdSShan Wei } 17062ad6fcdSShan Wei 1715d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 1725d77dca8SDavid Ahern static int __udp_diag_destroy(struct sk_buff *in_skb, 1735d77dca8SDavid Ahern const struct inet_diag_req_v2 *req, 1745d77dca8SDavid Ahern struct udp_table *tbl) 1755d77dca8SDavid Ahern { 1765d77dca8SDavid Ahern struct net *net = sock_net(in_skb->sk); 1775d77dca8SDavid Ahern struct sock *sk; 1785d77dca8SDavid Ahern int err; 1795d77dca8SDavid Ahern 1805d77dca8SDavid Ahern rcu_read_lock(); 1815d77dca8SDavid Ahern 1825d77dca8SDavid Ahern if (req->sdiag_family == AF_INET) 1835d77dca8SDavid Ahern sk = __udp4_lib_lookup(net, 1845d77dca8SDavid Ahern req->id.idiag_dst[0], req->id.idiag_dport, 1855d77dca8SDavid Ahern req->id.idiag_src[0], req->id.idiag_sport, 186fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 1875d77dca8SDavid Ahern #if IS_ENABLED(CONFIG_IPV6) 1885d77dca8SDavid Ahern else if (req->sdiag_family == AF_INET6) { 1895d77dca8SDavid Ahern if (ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_dst) && 1905d77dca8SDavid Ahern ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_src)) 1915d77dca8SDavid Ahern sk = __udp4_lib_lookup(net, 192f95bf346SLorenzo Colitti req->id.idiag_dst[3], req->id.idiag_dport, 193f95bf346SLorenzo Colitti req->id.idiag_src[3], req->id.idiag_sport, 194fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 1955d77dca8SDavid Ahern 1965d77dca8SDavid Ahern else 1975d77dca8SDavid Ahern sk = __udp6_lib_lookup(net, 1985d77dca8SDavid Ahern (struct in6_addr *)req->id.idiag_dst, 1995d77dca8SDavid Ahern req->id.idiag_dport, 2005d77dca8SDavid Ahern (struct in6_addr *)req->id.idiag_src, 2015d77dca8SDavid Ahern req->id.idiag_sport, 2021801b570SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 2035d77dca8SDavid Ahern } 2045d77dca8SDavid Ahern #endif 2055d77dca8SDavid Ahern else { 2065d77dca8SDavid Ahern rcu_read_unlock(); 2075d77dca8SDavid Ahern return -EINVAL; 2085d77dca8SDavid Ahern } 2095d77dca8SDavid Ahern 21041c6d650SReshetova, Elena if (sk && !refcount_inc_not_zero(&sk->sk_refcnt)) 2115d77dca8SDavid Ahern sk = NULL; 2125d77dca8SDavid Ahern 2135d77dca8SDavid Ahern rcu_read_unlock(); 2145d77dca8SDavid Ahern 2155d77dca8SDavid Ahern if (!sk) 2165d77dca8SDavid Ahern return -ENOENT; 2175d77dca8SDavid Ahern 2185d77dca8SDavid Ahern if (sock_diag_check_cookie(sk, req->id.idiag_cookie)) { 2195d77dca8SDavid Ahern sock_put(sk); 2205d77dca8SDavid Ahern return -ENOENT; 2215d77dca8SDavid Ahern } 2225d77dca8SDavid Ahern 2235d77dca8SDavid Ahern err = sock_diag_destroy(sk, ECONNABORTED); 2245d77dca8SDavid Ahern 2255d77dca8SDavid Ahern sock_put(sk); 2265d77dca8SDavid Ahern 2275d77dca8SDavid Ahern return err; 2285d77dca8SDavid Ahern } 2295d77dca8SDavid Ahern 2305d77dca8SDavid Ahern static int udp_diag_destroy(struct sk_buff *in_skb, 2315d77dca8SDavid Ahern const struct inet_diag_req_v2 *req) 2325d77dca8SDavid Ahern { 2335d77dca8SDavid Ahern return __udp_diag_destroy(in_skb, req, &udp_table); 2345d77dca8SDavid Ahern } 2355d77dca8SDavid Ahern 2365d77dca8SDavid Ahern static int udplite_diag_destroy(struct sk_buff *in_skb, 2375d77dca8SDavid Ahern const struct inet_diag_req_v2 *req) 2385d77dca8SDavid Ahern { 2395d77dca8SDavid Ahern return __udp_diag_destroy(in_skb, req, &udplite_table); 2405d77dca8SDavid Ahern } 2415d77dca8SDavid Ahern 2425d77dca8SDavid Ahern #endif 2435d77dca8SDavid Ahern 24452b7c59bSPavel Emelyanov static const struct inet_diag_handler udp_diag_handler = { 24552b7c59bSPavel Emelyanov .dump = udp_diag_dump, 24652b7c59bSPavel Emelyanov .dump_one = udp_diag_dump_one, 24762ad6fcdSShan Wei .idiag_get_info = udp_diag_get_info, 24852b7c59bSPavel Emelyanov .idiag_type = IPPROTO_UDP, 2493fd22af8SCraig Gallek .idiag_info_size = 0, 2505d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 2515d77dca8SDavid Ahern .destroy = udp_diag_destroy, 2525d77dca8SDavid Ahern #endif 25352b7c59bSPavel Emelyanov }; 25452b7c59bSPavel Emelyanov 25552b7c59bSPavel Emelyanov static void udplite_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, 25634160ea3SEric Dumazet const struct inet_diag_req_v2 *r, 25734160ea3SEric Dumazet struct nlattr *bc) 25852b7c59bSPavel Emelyanov { 25952b7c59bSPavel Emelyanov udp_dump(&udplite_table, skb, cb, r, bc); 26052b7c59bSPavel Emelyanov } 26152b7c59bSPavel Emelyanov 26252b7c59bSPavel Emelyanov static int udplite_diag_dump_one(struct sk_buff *in_skb, const struct nlmsghdr *nlh, 26334160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 26452b7c59bSPavel Emelyanov { 26552b7c59bSPavel Emelyanov return udp_dump_one(&udplite_table, in_skb, nlh, req); 26652b7c59bSPavel Emelyanov } 26752b7c59bSPavel Emelyanov 26852b7c59bSPavel Emelyanov static const struct inet_diag_handler udplite_diag_handler = { 26952b7c59bSPavel Emelyanov .dump = udplite_diag_dump, 27052b7c59bSPavel Emelyanov .dump_one = udplite_diag_dump_one, 27162ad6fcdSShan Wei .idiag_get_info = udp_diag_get_info, 27252b7c59bSPavel Emelyanov .idiag_type = IPPROTO_UDPLITE, 2733fd22af8SCraig Gallek .idiag_info_size = 0, 2745d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 2755d77dca8SDavid Ahern .destroy = udplite_diag_destroy, 2765d77dca8SDavid Ahern #endif 27752b7c59bSPavel Emelyanov }; 27852b7c59bSPavel Emelyanov 27952b7c59bSPavel Emelyanov static int __init udp_diag_init(void) 28052b7c59bSPavel Emelyanov { 28152b7c59bSPavel Emelyanov int err; 28252b7c59bSPavel Emelyanov 28352b7c59bSPavel Emelyanov err = inet_diag_register(&udp_diag_handler); 28452b7c59bSPavel Emelyanov if (err) 28552b7c59bSPavel Emelyanov goto out; 28652b7c59bSPavel Emelyanov err = inet_diag_register(&udplite_diag_handler); 28752b7c59bSPavel Emelyanov if (err) 28852b7c59bSPavel Emelyanov goto out_lite; 28952b7c59bSPavel Emelyanov out: 29052b7c59bSPavel Emelyanov return err; 29152b7c59bSPavel Emelyanov out_lite: 29252b7c59bSPavel Emelyanov inet_diag_unregister(&udp_diag_handler); 29352b7c59bSPavel Emelyanov goto out; 29452b7c59bSPavel Emelyanov } 29552b7c59bSPavel Emelyanov 29652b7c59bSPavel Emelyanov static void __exit udp_diag_exit(void) 29752b7c59bSPavel Emelyanov { 29852b7c59bSPavel Emelyanov inet_diag_unregister(&udplite_diag_handler); 29952b7c59bSPavel Emelyanov inet_diag_unregister(&udp_diag_handler); 30052b7c59bSPavel Emelyanov } 30152b7c59bSPavel Emelyanov 30252b7c59bSPavel Emelyanov module_init(udp_diag_init); 30352b7c59bSPavel Emelyanov module_exit(udp_diag_exit); 30452b7c59bSPavel Emelyanov MODULE_LICENSE("GPL"); 305aec8dc62SPavel Emelyanov MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-17 /* AF_INET - IPPROTO_UDP */); 306aec8dc62SPavel Emelyanov MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-136 /* AF_INET - IPPROTO_UDPLITE */); 307