1171 lines · 27.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * INET		An implementation of the TCP/IP protocol suite for the LINUX *		operating system.  INET is implemented using the  BSD Socket *		interface as the means of communication with the user level. * *		"Ping" sockets * * Based on ipv4/udp.c code. * * Authors:	Vasiliy Kulikov / Openwall (for Linux 2.6), *		Pavel Kankovsky (for Linux 2.4.32) * * Pavel gave all rights to bugs to Vasiliy, * none of the bugs are Pavel's now. */ #include <linux/uaccess.h>#include <linux/types.h>#include <linux/fcntl.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/in.h>#include <linux/errno.h>#include <linux/timer.h>#include <linux/mm.h>#include <linux/inet.h>#include <linux/netdevice.h>#include <net/snmp.h>#include <net/ip.h>#include <net/icmp.h>#include <net/protocol.h>#include <linux/skbuff.h>#include <linux/proc_fs.h>#include <linux/export.h>#include <linux/bpf-cgroup.h>#include <net/sock.h>#include <net/ping.h>#include <net/udp.h>#include <net/route.h>#include <net/inet_common.h>#include <net/checksum.h> #if IS_ENABLED(CONFIG_IPV6)#include <linux/in6.h>#include <linux/icmpv6.h>#include <net/addrconf.h>#include <net/ipv6.h>#include <net/transp_v6.h>#endif struct ping_table {	struct hlist_head	hash[PING_HTABLE_SIZE];	spinlock_t		lock;}; static struct ping_table ping_table;struct pingv6_ops pingv6_ops; static inline u32 ping_hashfn(const struct net *net, u32 num, u32 mask){	u32 res = (num + net_hash_mix(net)) & mask; 	pr_debug("hash(%u) = %u\n", num, res);	return res;} static inline struct hlist_head *ping_hashslot(struct ping_table *table,					       struct net *net, unsigned int num){	return &table->hash[ping_hashfn(net, num, PING_HTABLE_MASK)];} int ping_get_port(struct sock *sk, unsigned short ident){	struct net *net = sock_net(sk);	struct inet_sock *isk, *isk2;	struct hlist_head *hlist;	struct sock *sk2 = NULL; 	isk = inet_sk(sk);	spin_lock(&ping_table.lock);	if (ident == 0) {		u16 result = net->ipv4.ping_port_rover + 1;		u32 i; 		for (i = 0; i < (1L << 16); i++, result++) {			if (!result)				continue; /* avoid zero */			hlist = ping_hashslot(&ping_table, net, result);			sk_for_each(sk2, hlist) {				if (!net_eq(sock_net(sk2), net))					continue;				isk2 = inet_sk(sk2); 				if (isk2->inet_num == result)					goto next_port;			} 			/* found */			net->ipv4.ping_port_rover = ident = result;			break;next_port:			;		}		if (i >= (1L << 16))			goto fail;	} else {		hlist = ping_hashslot(&ping_table, net, ident);		sk_for_each(sk2, hlist) {			if (!net_eq(sock_net(sk2), net))				continue;			isk2 = inet_sk(sk2); 			/* BUG? Why is this reuse and not reuseaddr? ping.c			 * doesn't turn off SO_REUSEADDR, and it doesn't expect			 * that other ping processes can steal its packets.			 */			if ((isk2->inet_num == ident) &&			    (sk2 != sk) &&			    (!sk2->sk_reuse || !sk->sk_reuse))				goto fail;		}	} 	pr_debug("found port/ident = %d\n", ident);	isk->inet_num = ident;	if (sk_unhashed(sk)) {		pr_debug("was not hashed\n");		sk_add_node_rcu(sk, hlist);		sock_set_flag(sk, SOCK_RCU_FREE);		sock_prot_inuse_add(net, sk->sk_prot, 1);	}	spin_unlock(&ping_table.lock);	return 0; fail:	spin_unlock(&ping_table.lock);	return -EADDRINUSE;} void ping_unhash(struct sock *sk){	struct inet_sock *isk = inet_sk(sk); 	pr_debug("ping_unhash(isk=%p,isk->num=%u)\n", isk, isk->inet_num);	spin_lock(&ping_table.lock);	if (sk_del_node_init_rcu(sk)) {		WRITE_ONCE(isk->inet_num, 0);		isk->inet_sport = 0;		sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);	}	spin_unlock(&ping_table.lock);} /* Called under rcu_read_lock() */static struct sock *ping_lookup(struct net *net, struct sk_buff *skb, u16 ident){	struct hlist_head *hslot = ping_hashslot(&ping_table, net, ident);	struct sock *sk = NULL;	struct inet_sock *isk;	int dif, sdif; 	if (skb->protocol == htons(ETH_P_IP)) {		dif = inet_iif(skb);		sdif = inet_sdif(skb);		pr_debug("try to find: num = %d, daddr = %pI4, dif = %d\n",			 (int)ident, &ip_hdr(skb)->daddr, dif);#if IS_ENABLED(CONFIG_IPV6)	} else if (skb->protocol == htons(ETH_P_IPV6)) {		dif = inet6_iif(skb);		sdif = inet6_sdif(skb);		pr_debug("try to find: num = %d, daddr = %pI6c, dif = %d\n",			 (int)ident, &ipv6_hdr(skb)->daddr, dif);#endif	} else {		return NULL;	} 	sk_for_each_rcu(sk, hslot) {		int bound_dev_if; 		if (!net_eq(sock_net(sk), net))			continue;		isk = inet_sk(sk); 		pr_debug("iterate\n");		if (READ_ONCE(isk->inet_num) != ident)			continue; 		bound_dev_if = READ_ONCE(sk->sk_bound_dev_if);		if (skb->protocol == htons(ETH_P_IP) &&		    sk->sk_family == AF_INET) {			__be32 rcv_saddr = READ_ONCE(isk->inet_rcv_saddr); 			pr_debug("found: %p: num=%d, daddr=%pI4, dif=%d\n", sk,				 ident, &rcv_saddr,				 bound_dev_if); 			if (rcv_saddr && rcv_saddr != ip_hdr(skb)->daddr)				continue;#if IS_ENABLED(CONFIG_IPV6)		} else if (skb->protocol == htons(ETH_P_IPV6) &&			   sk->sk_family == AF_INET6) { 			pr_debug("found: %p: num=%d, daddr=%pI6c, dif=%d\n", sk,				 ident,				 &sk->sk_v6_rcv_saddr,				 bound_dev_if); 			if (!ipv6_addr_any(&sk->sk_v6_rcv_saddr) &&			    !ipv6_addr_equal(&sk->sk_v6_rcv_saddr,					     &ipv6_hdr(skb)->daddr))				continue;#endif		} else {			continue;		} 		if (bound_dev_if && bound_dev_if != dif &&		    bound_dev_if != sdif)			continue; 		goto exit;	} 	sk = NULL;exit: 	return sk;} static void inet_get_ping_group_range_net(struct net *net, kgid_t *low,					  kgid_t *high){	kgid_t *data = net->ipv4.ping_group_range.range;	unsigned int seq; 	do {		seq = read_seqbegin(&net->ipv4.ping_group_range.lock); 		*low = data[0];		*high = data[1];	} while (read_seqretry(&net->ipv4.ping_group_range.lock, seq));}  int ping_init_sock(struct sock *sk){	struct net *net = sock_net(sk);	kgid_t group = current_egid();	struct group_info *group_info;	int i;	kgid_t low, high;	int ret = 0; 	if (sk->sk_family == AF_INET6)		sk->sk_ipv6only = 1; 	inet_get_ping_group_range_net(net, &low, &high);	if (gid_lte(low, group) && gid_lte(group, high))		return 0; 	group_info = get_current_groups();	for (i = 0; i < group_info->ngroups; i++) {		kgid_t gid = group_info->gid[i]; 		if (gid_lte(low, gid) && gid_lte(gid, high))			goto out_release_group;	} 	ret = -EACCES; out_release_group:	put_group_info(group_info);	return ret;} void ping_close(struct sock *sk, long timeout){	pr_debug("ping_close(sk=%p,sk->num=%u)\n",		 inet_sk(sk), inet_sk(sk)->inet_num);	pr_debug("isk->refcnt = %d\n", refcount_read(&sk->sk_refcnt)); 	sk_common_release(sk);} static int ping_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,			    int addr_len){	/* This check is replicated from __ip4_datagram_connect() and	 * intended to prevent BPF program called below from accessing bytes	 * that are out of the bound specified by user in addr_len.	 */	if (addr_len < sizeof(struct sockaddr_in))		return -EINVAL; 	return BPF_CGROUP_RUN_PROG_INET4_CONNECT_LOCK(sk, uaddr, &addr_len);} /* Checks the bind address and possibly modifies sk->sk_bound_dev_if. */static int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,				struct sockaddr_unsized *uaddr, int addr_len){	struct net *net = sock_net(sk);	if (sk->sk_family == AF_INET) {		struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;		u32 tb_id = RT_TABLE_LOCAL;		int chk_addr_ret; 		if (addr_len < sizeof(*addr))			return -EINVAL; 		if (addr->sin_family != AF_INET &&		    !(addr->sin_family == AF_UNSPEC &&		      addr->sin_addr.s_addr == htonl(INADDR_ANY)))			return -EAFNOSUPPORT; 		pr_debug("ping_check_bind_addr(sk=%p,addr=%pI4,port=%d)\n",			 sk, &addr->sin_addr.s_addr, ntohs(addr->sin_port)); 		if (addr->sin_addr.s_addr == htonl(INADDR_ANY))			return 0; 		tb_id = l3mdev_fib_table_by_index(net, sk->sk_bound_dev_if) ? : tb_id;		chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id); 		if (chk_addr_ret == RTN_MULTICAST ||		    chk_addr_ret == RTN_BROADCAST ||		    (chk_addr_ret != RTN_LOCAL &&		     !inet_can_nonlocal_bind(net, isk)))			return -EADDRNOTAVAIL; #if IS_ENABLED(CONFIG_IPV6)	} else if (sk->sk_family == AF_INET6) {		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) uaddr;		int addr_type, scoped, has_addr;		struct net_device *dev = NULL; 		if (addr_len < sizeof(*addr))			return -EINVAL; 		if (addr->sin6_family != AF_INET6)			return -EAFNOSUPPORT; 		pr_debug("ping_check_bind_addr(sk=%p,addr=%pI6c,port=%d)\n",			 sk, addr->sin6_addr.s6_addr, ntohs(addr->sin6_port)); 		addr_type = ipv6_addr_type(&addr->sin6_addr);		scoped = __ipv6_addr_needs_scope_id(addr_type);		if ((addr_type != IPV6_ADDR_ANY &&		     !(addr_type & IPV6_ADDR_UNICAST)) ||		    (scoped && !addr->sin6_scope_id))			return -EINVAL; 		rcu_read_lock();		if (addr->sin6_scope_id) {			dev = dev_get_by_index_rcu(net, addr->sin6_scope_id);			if (!dev) {				rcu_read_unlock();				return -ENODEV;			}		} 		if (!dev && sk->sk_bound_dev_if) {			dev = dev_get_by_index_rcu(net, sk->sk_bound_dev_if);			if (!dev) {				rcu_read_unlock();				return -ENODEV;			}		}		has_addr = pingv6_ops.ipv6_chk_addr(net, &addr->sin6_addr, dev,						    scoped);		rcu_read_unlock(); 		if (!(ipv6_can_nonlocal_bind(net, isk) || has_addr ||		      addr_type == IPV6_ADDR_ANY))			return -EADDRNOTAVAIL; 		if (scoped)			sk->sk_bound_dev_if = addr->sin6_scope_id;#endif	} else {		return -EAFNOSUPPORT;	}	return 0;} static void ping_set_saddr(struct sock *sk, struct sockaddr_unsized *saddr){	if (saddr->sa_family == AF_INET) {		struct inet_sock *isk = inet_sk(sk);		struct sockaddr_in *addr = (struct sockaddr_in *) saddr; 		isk->inet_saddr = addr->sin_addr.s_addr;		WRITE_ONCE(isk->inet_rcv_saddr, addr->sin_addr.s_addr);#if IS_ENABLED(CONFIG_IPV6)	} else if (saddr->sa_family == AF_INET6) {		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) saddr;		struct ipv6_pinfo *np = inet6_sk(sk);		sk->sk_v6_rcv_saddr = np->saddr = addr->sin6_addr;#endif	}} /* * We need our own bind because there are no privileged id's == local ports. * Moreover, we don't allow binding to multi- and broadcast addresses. */ int ping_bind(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len){	struct inet_sock *isk = inet_sk(sk);	unsigned short snum;	int err;	int dif = sk->sk_bound_dev_if; 	err = ping_check_bind_addr(sk, isk, uaddr, addr_len);	if (err)		return err; 	lock_sock(sk); 	err = -EINVAL;	if (isk->inet_num != 0)		goto out; 	err = -EADDRINUSE;	snum = ntohs(((struct sockaddr_in *)uaddr)->sin_port);	if (ping_get_port(sk, snum) != 0) {		/* Restore possibly modified sk->sk_bound_dev_if by ping_check_bind_addr(). */		sk->sk_bound_dev_if = dif;		goto out;	}	ping_set_saddr(sk, uaddr); 	pr_debug("after bind(): num = %hu, dif = %d\n",		 isk->inet_num,		 sk->sk_bound_dev_if); 	err = 0;	if (sk->sk_family == AF_INET && isk->inet_rcv_saddr)		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;#if IS_ENABLED(CONFIG_IPV6)	if (sk->sk_family == AF_INET6 && !ipv6_addr_any(&sk->sk_v6_rcv_saddr))		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;#endif 	if (snum)		sk->sk_userlocks |= SOCK_BINDPORT_LOCK;	isk->inet_sport = htons(isk->inet_num);	isk->inet_daddr = 0;	isk->inet_dport = 0; #if IS_ENABLED(CONFIG_IPV6)	if (sk->sk_family == AF_INET6)		memset(&sk->sk_v6_daddr, 0, sizeof(sk->sk_v6_daddr));#endif 	sk_dst_reset(sk);out:	release_sock(sk);	pr_debug("ping_v4_bind -> %d\n", err);	return err;} /* * Is this a supported type of ICMP message? */ static inline int ping_supported(int family, int type, int code){	return (family == AF_INET && type == ICMP_ECHO && code == 0) ||	       (family == AF_INET && type == ICMP_EXT_ECHO && code == 0) ||	       (family == AF_INET6 && type == ICMPV6_ECHO_REQUEST && code == 0) ||	       (family == AF_INET6 && type == ICMPV6_EXT_ECHO_REQUEST && code == 0);} /* * This routine is called by the ICMP module when it gets some * sort of error condition. */ void ping_err(struct sk_buff *skb, int offset, u32 info){	int family;	struct icmphdr *icmph;	struct inet_sock *inet_sock;	int type;	int code;	struct net *net = dev_net(skb->dev);	struct sock *sk;	int harderr;	int err; 	if (skb->protocol == htons(ETH_P_IP)) {		family = AF_INET;		type = icmp_hdr(skb)->type;		code = icmp_hdr(skb)->code;		icmph = (struct icmphdr *)(skb->data + offset);

Lines 1–500 of 1171.