1200 lines · 28.6 KB · C
RawDownload
/* * 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 * *		This program is free software; you can redistribute it and/or *		modify it under the terms of the GNU General Public License *		as published by the Free Software Foundation; either version *		2 of the License, or (at your option) any later version. * * 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 <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 ping_table;struct pingv6_ops pingv6_ops;EXPORT_SYMBOL_GPL(pingv6_ops); static u16 ping_port_rover; static inline int ping_hashfn(struct net *net, unsigned int num, unsigned int mask){	int res = (num + net_hash_mix(net)) & mask; 	pr_debug("hash(%d) = %d\n", num, res);	return res;}EXPORT_SYMBOL_GPL(ping_hash); static inline struct hlist_nulls_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 hlist_nulls_node *node;	struct hlist_nulls_head *hlist;	struct inet_sock *isk, *isk2;	struct sock *sk2 = NULL; 	isk = inet_sk(sk);	write_lock_bh(&ping_table.lock);	if (ident == 0) {		u32 i;		u16 result = ping_port_rover + 1; 		for (i = 0; i < (1L << 16); i++, result++) {			if (!result)				result++; /* avoid zero */			hlist = ping_hashslot(&ping_table, sock_net(sk),					    result);			ping_portaddr_for_each_entry(sk2, node, hlist) {				isk2 = inet_sk(sk2); 				if (isk2->inet_num == result)					goto next_port;			} 			/* found */			ping_port_rover = ident = result;			break;next_port:			;		}		if (i >= (1L << 16))			goto fail;	} else {		hlist = ping_hashslot(&ping_table, sock_net(sk), ident);		ping_portaddr_for_each_entry(sk2, node, hlist) {			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");		sock_hold(sk);		hlist_nulls_add_head(&sk->sk_nulls_node, hlist);		sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);	}	write_unlock_bh(&ping_table.lock);	return 0; fail:	write_unlock_bh(&ping_table.lock);	return 1;}EXPORT_SYMBOL_GPL(ping_get_port); void ping_hash(struct sock *sk){	pr_debug("ping_hash(sk->port=%u)\n", inet_sk(sk)->inet_num);	BUG(); /* "Please do not press this button again." */} 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);	if (sk_hashed(sk)) {		write_lock_bh(&ping_table.lock);		hlist_nulls_del(&sk->sk_nulls_node);		sock_put(sk);		isk->inet_num = 0;		isk->inet_sport = 0;		sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);		write_unlock_bh(&ping_table.lock);	}}EXPORT_SYMBOL_GPL(ping_unhash); static struct sock *ping_lookup(struct net *net, struct sk_buff *skb, u16 ident){	struct hlist_nulls_head *hslot = ping_hashslot(&ping_table, net, ident);	struct sock *sk = NULL;	struct inet_sock *isk;	struct hlist_nulls_node *hnode;	int dif = skb->dev->ifindex; 	if (skb->protocol == htons(ETH_P_IP)) {		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)) {		pr_debug("try to find: num = %d, daddr = %pI6c, dif = %d\n",			 (int)ident, &ipv6_hdr(skb)->daddr, dif);#endif	} 	read_lock_bh(&ping_table.lock); 	ping_portaddr_for_each_entry(sk, hnode, hslot) {		isk = inet_sk(sk); 		pr_debug("iterate\n");		if (isk->inet_num != ident)			continue; 		if (skb->protocol == htons(ETH_P_IP) &&		    sk->sk_family == AF_INET) {			pr_debug("found: %p: num=%d, daddr=%pI4, dif=%d\n", sk,				 (int) isk->inet_num, &isk->inet_rcv_saddr,				 sk->sk_bound_dev_if); 			if (isk->inet_rcv_saddr &&			    isk->inet_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,				 (int) isk->inet_num,				 &sk->sk_v6_rcv_saddr,				 sk->sk_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		} 		if (sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif)			continue; 		sock_hold(sk);		goto exit;	} 	sk = NULL;exit:	read_unlock_bh(&ping_table.lock); 	return sk;} static void inet_get_ping_group_range_net(struct net *net, kgid_t *low,					  kgid_t *high){	kgid_t *data = net->ipv4.sysctl_ping_group_range;	unsigned int seq; 	do {		seq = read_seqbegin(&net->ipv4.sysctl_local_ports.lock); 		*low = data[0];		*high = data[1];	} while (read_seqretry(&net->ipv4.sysctl_local_ports.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 = get_current_groups();	int i, j, count = group_info->ngroups;	kgid_t low, high; 	inet_get_ping_group_range_net(net, &low, &high);	if (gid_lte(low, group) && gid_lte(group, high))		return 0; 	for (i = 0; i < group_info->nblocks; i++) {		int cp_count = min_t(int, NGROUPS_PER_BLOCK, count);		for (j = 0; j < cp_count; j++) {			kgid_t gid = group_info->blocks[i][j];			if (gid_lte(low, gid) && gid_lte(gid, high))				return 0;		} 		count -= cp_count;	} 	return -EACCES;}EXPORT_SYMBOL_GPL(ping_init_sock); 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", sk->sk_refcnt.counter); 	sk_common_release(sk);}EXPORT_SYMBOL_GPL(ping_close); /* 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 *uaddr, int addr_len) {	struct net *net = sock_net(sk);	if (sk->sk_family == AF_INET) {		struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;		int chk_addr_ret; 		if (addr_len < sizeof(*addr))			return -EINVAL; 		pr_debug("ping_check_bind_addr(sk=%p,addr=%pI4,port=%d)\n",			 sk, &addr->sin_addr.s_addr, ntohs(addr->sin_port)); 		chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr); 		if (addr->sin_addr.s_addr == htonl(INADDR_ANY))			chk_addr_ret = RTN_LOCAL; 		if ((sysctl_ip_nonlocal_bind == 0 &&		    isk->freebind == 0 && isk->transparent == 0 &&		     chk_addr_ret != RTN_LOCAL) ||		    chk_addr_ret == RTN_MULTICAST ||		    chk_addr_ret == RTN_BROADCAST)			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; 		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;			}		}		has_addr = pingv6_ops.ipv6_chk_addr(net, &addr->sin6_addr, dev,						    scoped);		rcu_read_unlock(); 		if (!(isk->freebind || isk->transparent || 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 *saddr){	if (saddr->sa_family == AF_INET) {		struct inet_sock *isk = inet_sk(sk);		struct sockaddr_in *addr = (struct sockaddr_in *) saddr;		isk->inet_rcv_saddr = isk->inet_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	}} static void ping_clear_saddr(struct sock *sk, int dif){	sk->sk_bound_dev_if = dif;	if (sk->sk_family == AF_INET) {		struct inet_sock *isk = inet_sk(sk);		isk->inet_rcv_saddr = isk->inet_saddr = 0;#if IS_ENABLED(CONFIG_IPV6)	} else if (sk->sk_family == AF_INET6) {		struct ipv6_pinfo *np = inet6_sk(sk);		memset(&sk->sk_v6_rcv_saddr, 0, sizeof(sk->sk_v6_rcv_saddr));		memset(&np->saddr, 0, sizeof(np->saddr));#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 *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;	ping_set_saddr(sk, uaddr);	snum = ntohs(((struct sockaddr_in *)uaddr)->sin_port);	if (ping_get_port(sk, snum) != 0) {		ping_clear_saddr(sk, dif);		goto out;	} 	pr_debug("after bind(): num = %d, dif = %d\n",		 (int)isk->inet_num,		 (int)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;}EXPORT_SYMBOL_GPL(ping_bind); /* * 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_INET6 && type == ICMPV6_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);	} else if (skb->protocol == htons(ETH_P_IPV6)) {		family = AF_INET6;		type = icmp6_hdr(skb)->icmp6_type;		code = icmp6_hdr(skb)->icmp6_code;		icmph = (struct icmphdr *) (skb->data + offset);	} else {		BUG();	} 	/* We assume the packet has already been checked by icmp_unreach */ 	if (!ping_supported(family, icmph->type, icmph->code))		return; 	pr_debug("ping_err(proto=0x%x,type=%d,code=%d,id=%04x,seq=%04x)\n",		 skb->protocol, type, code, ntohs(icmph->un.echo.id),		 ntohs(icmph->un.echo.sequence)); 	sk = ping_lookup(net, skb, ntohs(icmph->un.echo.id));	if (sk == NULL) {		pr_debug("no socket, dropping\n");		return;	/* No socket for error */	}	pr_debug("err on socket %p\n", sk); 

Lines 1–500 of 1200.