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) 4551d7cccfSAndrey Vagin sk = __udp4_lib_lookup(net, 46a925aa00SPavel Emelyanov req->id.idiag_src[0], req->id.idiag_sport, 47a925aa00SPavel Emelyanov req->id.idiag_dst[0], req->id.idiag_dport, 48fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 4986e62ad6SPavel Emelyanov #if IS_ENABLED(CONFIG_IPV6) 50a925aa00SPavel Emelyanov else if (req->sdiag_family == AF_INET6) 5151d7cccfSAndrey Vagin sk = __udp6_lib_lookup(net, 52a925aa00SPavel Emelyanov (struct in6_addr *)req->id.idiag_src, 53a925aa00SPavel Emelyanov req->id.idiag_sport, 54a925aa00SPavel Emelyanov (struct in6_addr *)req->id.idiag_dst, 55a925aa00SPavel Emelyanov req->id.idiag_dport, 561801b570SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 5786e62ad6SPavel Emelyanov #endif 5841c6d650SReshetova, Elena if (sk && !refcount_inc_not_zero(&sk->sk_refcnt)) 59ca065d0cSEric Dumazet sk = NULL; 60ca065d0cSEric Dumazet rcu_read_unlock(); 61a925aa00SPavel Emelyanov err = -ENOENT; 6251456b29SIan Morris if (!sk) 63a925aa00SPavel Emelyanov goto out_nosk; 64a925aa00SPavel Emelyanov 65f65c1b53SPavel Emelyanov err = sock_diag_check_cookie(sk, req->id.idiag_cookie); 66a925aa00SPavel Emelyanov if (err) 67a925aa00SPavel Emelyanov goto out; 68a925aa00SPavel Emelyanov 69a925aa00SPavel Emelyanov err = -ENOMEM; 70573ce260SHong zhi guo rep = nlmsg_new(sizeof(struct inet_diag_msg) + 71573ce260SHong zhi guo sizeof(struct inet_diag_meminfo) + 64, 72573ce260SHong zhi guo GFP_KERNEL); 73a925aa00SPavel Emelyanov if (!rep) 74a925aa00SPavel Emelyanov goto out; 75a925aa00SPavel Emelyanov 76a925aa00SPavel Emelyanov err = inet_sk_diag_fill(sk, NULL, rep, req, 77e32123e5SPatrick McHardy sk_user_ns(NETLINK_CB(in_skb).sk), 7815e47304SEric W. Biederman NETLINK_CB(in_skb).portid, 79d545cacaSLorenzo Colitti nlh->nlmsg_seq, 0, nlh, 80d545cacaSLorenzo Colitti netlink_net_capable(in_skb, CAP_NET_ADMIN)); 81a925aa00SPavel Emelyanov if (err < 0) { 82a925aa00SPavel Emelyanov WARN_ON(err == -EMSGSIZE); 83a925aa00SPavel Emelyanov kfree_skb(rep); 84a925aa00SPavel Emelyanov goto out; 85a925aa00SPavel Emelyanov } 8615e47304SEric W. Biederman err = netlink_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid, 87a925aa00SPavel Emelyanov MSG_DONTWAIT); 88a925aa00SPavel Emelyanov if (err > 0) 89a925aa00SPavel Emelyanov err = 0; 90a925aa00SPavel Emelyanov out: 91a925aa00SPavel Emelyanov if (sk) 92a925aa00SPavel Emelyanov sock_put(sk); 93a925aa00SPavel Emelyanov out_nosk: 94a925aa00SPavel Emelyanov return err; 9552b7c59bSPavel Emelyanov } 9652b7c59bSPavel Emelyanov 9734160ea3SEric Dumazet static void udp_dump(struct udp_table *table, struct sk_buff *skb, 9834160ea3SEric Dumazet struct netlink_callback *cb, 9934160ea3SEric Dumazet const struct inet_diag_req_v2 *r, struct nlattr *bc) 10052b7c59bSPavel Emelyanov { 101d545cacaSLorenzo Colitti bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN); 10251d7cccfSAndrey Vagin struct net *net = sock_net(skb->sk); 103ca065d0cSEric Dumazet int num, s_num, slot, s_slot; 104b6d640c2SPavel Emelyanov 105b6d640c2SPavel Emelyanov s_slot = cb->args[0]; 106b6d640c2SPavel Emelyanov num = s_num = cb->args[1]; 107b6d640c2SPavel Emelyanov 10886f3cddbSHerbert Xu for (slot = s_slot; slot <= table->mask; s_num = 0, slot++) { 109b6d640c2SPavel Emelyanov struct udp_hslot *hslot = &table->hash[slot]; 110ca065d0cSEric Dumazet struct sock *sk; 111b6d640c2SPavel Emelyanov 11286f3cddbSHerbert Xu num = 0; 11386f3cddbSHerbert Xu 114ca065d0cSEric Dumazet if (hlist_empty(&hslot->head)) 115b6d640c2SPavel Emelyanov continue; 116b6d640c2SPavel Emelyanov 117b6d640c2SPavel Emelyanov spin_lock_bh(&hslot->lock); 118ca065d0cSEric Dumazet sk_for_each(sk, &hslot->head) { 119b6d640c2SPavel Emelyanov struct inet_sock *inet = inet_sk(sk); 120b6d640c2SPavel Emelyanov 12151d7cccfSAndrey Vagin if (!net_eq(sock_net(sk), net)) 12251d7cccfSAndrey Vagin continue; 123b6d640c2SPavel Emelyanov if (num < s_num) 124b6d640c2SPavel Emelyanov goto next; 125b6d640c2SPavel Emelyanov if (!(r->idiag_states & (1 << sk->sk_state))) 126b6d640c2SPavel Emelyanov goto next; 127b6d640c2SPavel Emelyanov if (r->sdiag_family != AF_UNSPEC && 128b6d640c2SPavel Emelyanov sk->sk_family != r->sdiag_family) 129b6d640c2SPavel Emelyanov goto next; 130b6d640c2SPavel Emelyanov if (r->id.idiag_sport != inet->inet_sport && 131b6d640c2SPavel Emelyanov r->id.idiag_sport) 132b6d640c2SPavel Emelyanov goto next; 133b6d640c2SPavel Emelyanov if (r->id.idiag_dport != inet->inet_dport && 134b6d640c2SPavel Emelyanov r->id.idiag_dport) 135b6d640c2SPavel Emelyanov goto next; 136b6d640c2SPavel Emelyanov 137d545cacaSLorenzo Colitti if (sk_diag_dump(sk, skb, cb, r, bc, net_admin) < 0) { 138b6d640c2SPavel Emelyanov spin_unlock_bh(&hslot->lock); 139b6d640c2SPavel Emelyanov goto done; 140b6d640c2SPavel Emelyanov } 141b6d640c2SPavel Emelyanov next: 142b6d640c2SPavel Emelyanov num++; 143b6d640c2SPavel Emelyanov } 144b6d640c2SPavel Emelyanov spin_unlock_bh(&hslot->lock); 145b6d640c2SPavel Emelyanov } 146b6d640c2SPavel Emelyanov done: 147b6d640c2SPavel Emelyanov cb->args[0] = slot; 148b6d640c2SPavel Emelyanov cb->args[1] = num; 14952b7c59bSPavel Emelyanov } 15052b7c59bSPavel Emelyanov 15152b7c59bSPavel Emelyanov static void udp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, 15234160ea3SEric Dumazet const struct inet_diag_req_v2 *r, struct nlattr *bc) 15352b7c59bSPavel Emelyanov { 15452b7c59bSPavel Emelyanov udp_dump(&udp_table, skb, cb, r, bc); 15552b7c59bSPavel Emelyanov } 15652b7c59bSPavel Emelyanov 15752b7c59bSPavel Emelyanov static int udp_diag_dump_one(struct sk_buff *in_skb, const struct nlmsghdr *nlh, 15834160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 15952b7c59bSPavel Emelyanov { 16052b7c59bSPavel Emelyanov return udp_dump_one(&udp_table, in_skb, nlh, req); 16152b7c59bSPavel Emelyanov } 16252b7c59bSPavel Emelyanov 16362ad6fcdSShan Wei static void udp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, 16462ad6fcdSShan Wei void *info) 16562ad6fcdSShan Wei { 1666c206b20SPaolo Abeni r->idiag_rqueue = udp_rqueue_get(sk); 16762ad6fcdSShan Wei r->idiag_wqueue = sk_wmem_alloc_get(sk); 16862ad6fcdSShan Wei } 16962ad6fcdSShan Wei 1705d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 1715d77dca8SDavid Ahern static int __udp_diag_destroy(struct sk_buff *in_skb, 1725d77dca8SDavid Ahern const struct inet_diag_req_v2 *req, 1735d77dca8SDavid Ahern struct udp_table *tbl) 1745d77dca8SDavid Ahern { 1755d77dca8SDavid Ahern struct net *net = sock_net(in_skb->sk); 1765d77dca8SDavid Ahern struct sock *sk; 1775d77dca8SDavid Ahern int err; 1785d77dca8SDavid Ahern 1795d77dca8SDavid Ahern rcu_read_lock(); 1805d77dca8SDavid Ahern 1815d77dca8SDavid Ahern if (req->sdiag_family == AF_INET) 1825d77dca8SDavid Ahern sk = __udp4_lib_lookup(net, 1835d77dca8SDavid Ahern req->id.idiag_dst[0], req->id.idiag_dport, 1845d77dca8SDavid Ahern req->id.idiag_src[0], req->id.idiag_sport, 185fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 1865d77dca8SDavid Ahern #if IS_ENABLED(CONFIG_IPV6) 1875d77dca8SDavid Ahern else if (req->sdiag_family == AF_INET6) { 1885d77dca8SDavid Ahern if (ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_dst) && 1895d77dca8SDavid Ahern ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_src)) 1905d77dca8SDavid Ahern sk = __udp4_lib_lookup(net, 191f95bf346SLorenzo Colitti req->id.idiag_dst[3], req->id.idiag_dport, 192f95bf346SLorenzo Colitti req->id.idiag_src[3], req->id.idiag_sport, 193fb74c277SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 1945d77dca8SDavid Ahern 1955d77dca8SDavid Ahern else 1965d77dca8SDavid Ahern sk = __udp6_lib_lookup(net, 1975d77dca8SDavid Ahern (struct in6_addr *)req->id.idiag_dst, 1985d77dca8SDavid Ahern req->id.idiag_dport, 1995d77dca8SDavid Ahern (struct in6_addr *)req->id.idiag_src, 2005d77dca8SDavid Ahern req->id.idiag_sport, 2011801b570SDavid Ahern req->id.idiag_if, 0, tbl, NULL); 2025d77dca8SDavid Ahern } 2035d77dca8SDavid Ahern #endif 2045d77dca8SDavid Ahern else { 2055d77dca8SDavid Ahern rcu_read_unlock(); 2065d77dca8SDavid Ahern return -EINVAL; 2075d77dca8SDavid Ahern } 2085d77dca8SDavid Ahern 20941c6d650SReshetova, Elena if (sk && !refcount_inc_not_zero(&sk->sk_refcnt)) 2105d77dca8SDavid Ahern sk = NULL; 2115d77dca8SDavid Ahern 2125d77dca8SDavid Ahern rcu_read_unlock(); 2135d77dca8SDavid Ahern 2145d77dca8SDavid Ahern if (!sk) 2155d77dca8SDavid Ahern return -ENOENT; 2165d77dca8SDavid Ahern 2175d77dca8SDavid Ahern if (sock_diag_check_cookie(sk, req->id.idiag_cookie)) { 2185d77dca8SDavid Ahern sock_put(sk); 2195d77dca8SDavid Ahern return -ENOENT; 2205d77dca8SDavid Ahern } 2215d77dca8SDavid Ahern 2225d77dca8SDavid Ahern err = sock_diag_destroy(sk, ECONNABORTED); 2235d77dca8SDavid Ahern 2245d77dca8SDavid Ahern sock_put(sk); 2255d77dca8SDavid Ahern 2265d77dca8SDavid Ahern return err; 2275d77dca8SDavid Ahern } 2285d77dca8SDavid Ahern 2295d77dca8SDavid Ahern static int udp_diag_destroy(struct sk_buff *in_skb, 2305d77dca8SDavid Ahern const struct inet_diag_req_v2 *req) 2315d77dca8SDavid Ahern { 2325d77dca8SDavid Ahern return __udp_diag_destroy(in_skb, req, &udp_table); 2335d77dca8SDavid Ahern } 2345d77dca8SDavid Ahern 2355d77dca8SDavid Ahern static int udplite_diag_destroy(struct sk_buff *in_skb, 2365d77dca8SDavid Ahern const struct inet_diag_req_v2 *req) 2375d77dca8SDavid Ahern { 2385d77dca8SDavid Ahern return __udp_diag_destroy(in_skb, req, &udplite_table); 2395d77dca8SDavid Ahern } 2405d77dca8SDavid Ahern 2415d77dca8SDavid Ahern #endif 2425d77dca8SDavid Ahern 24352b7c59bSPavel Emelyanov static const struct inet_diag_handler udp_diag_handler = { 24452b7c59bSPavel Emelyanov .dump = udp_diag_dump, 24552b7c59bSPavel Emelyanov .dump_one = udp_diag_dump_one, 24662ad6fcdSShan Wei .idiag_get_info = udp_diag_get_info, 24752b7c59bSPavel Emelyanov .idiag_type = IPPROTO_UDP, 2483fd22af8SCraig Gallek .idiag_info_size = 0, 2495d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 2505d77dca8SDavid Ahern .destroy = udp_diag_destroy, 2515d77dca8SDavid Ahern #endif 25252b7c59bSPavel Emelyanov }; 25352b7c59bSPavel Emelyanov 25452b7c59bSPavel Emelyanov static void udplite_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, 25534160ea3SEric Dumazet const struct inet_diag_req_v2 *r, 25634160ea3SEric Dumazet struct nlattr *bc) 25752b7c59bSPavel Emelyanov { 25852b7c59bSPavel Emelyanov udp_dump(&udplite_table, skb, cb, r, bc); 25952b7c59bSPavel Emelyanov } 26052b7c59bSPavel Emelyanov 26152b7c59bSPavel Emelyanov static int udplite_diag_dump_one(struct sk_buff *in_skb, const struct nlmsghdr *nlh, 26234160ea3SEric Dumazet const struct inet_diag_req_v2 *req) 26352b7c59bSPavel Emelyanov { 26452b7c59bSPavel Emelyanov return udp_dump_one(&udplite_table, in_skb, nlh, req); 26552b7c59bSPavel Emelyanov } 26652b7c59bSPavel Emelyanov 26752b7c59bSPavel Emelyanov static const struct inet_diag_handler udplite_diag_handler = { 26852b7c59bSPavel Emelyanov .dump = udplite_diag_dump, 26952b7c59bSPavel Emelyanov .dump_one = udplite_diag_dump_one, 27062ad6fcdSShan Wei .idiag_get_info = udp_diag_get_info, 27152b7c59bSPavel Emelyanov .idiag_type = IPPROTO_UDPLITE, 2723fd22af8SCraig Gallek .idiag_info_size = 0, 2735d77dca8SDavid Ahern #ifdef CONFIG_INET_DIAG_DESTROY 2745d77dca8SDavid Ahern .destroy = udplite_diag_destroy, 2755d77dca8SDavid Ahern #endif 27652b7c59bSPavel Emelyanov }; 27752b7c59bSPavel Emelyanov 27852b7c59bSPavel Emelyanov static int __init udp_diag_init(void) 27952b7c59bSPavel Emelyanov { 28052b7c59bSPavel Emelyanov int err; 28152b7c59bSPavel Emelyanov 28252b7c59bSPavel Emelyanov err = inet_diag_register(&udp_diag_handler); 28352b7c59bSPavel Emelyanov if (err) 28452b7c59bSPavel Emelyanov goto out; 28552b7c59bSPavel Emelyanov err = inet_diag_register(&udplite_diag_handler); 28652b7c59bSPavel Emelyanov if (err) 28752b7c59bSPavel Emelyanov goto out_lite; 28852b7c59bSPavel Emelyanov out: 28952b7c59bSPavel Emelyanov return err; 29052b7c59bSPavel Emelyanov out_lite: 29152b7c59bSPavel Emelyanov inet_diag_unregister(&udp_diag_handler); 29252b7c59bSPavel Emelyanov goto out; 29352b7c59bSPavel Emelyanov } 29452b7c59bSPavel Emelyanov 29552b7c59bSPavel Emelyanov static void __exit udp_diag_exit(void) 29652b7c59bSPavel Emelyanov { 29752b7c59bSPavel Emelyanov inet_diag_unregister(&udplite_diag_handler); 29852b7c59bSPavel Emelyanov inet_diag_unregister(&udp_diag_handler); 29952b7c59bSPavel Emelyanov } 30052b7c59bSPavel Emelyanov 30152b7c59bSPavel Emelyanov module_init(udp_diag_init); 30252b7c59bSPavel Emelyanov module_exit(udp_diag_exit); 30352b7c59bSPavel Emelyanov MODULE_LICENSE("GPL"); 304aec8dc62SPavel Emelyanov MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-17 /* AF_INET - IPPROTO_UDP */); 305aec8dc62SPavel Emelyanov MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 2-136 /* AF_INET - IPPROTO_UDPLITE */); 306