939 lines · 21.3 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/ipv6.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>  static struct ping_table ping_table; 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;} 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)];} static int ping_v4_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); 			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;} static void ping_v4_hash(struct sock *sk){	pr_debug("ping_v4_hash(sk->port=%u)\n", inet_sk(sk)->inet_num);	BUG(); /* "Please do not press this button again." */} static void ping_v4_unhash(struct sock *sk){	struct inet_sock *isk = inet_sk(sk);	pr_debug("ping_v4_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);	}} static struct sock *ping_v4_lookup(struct net *net, __be32 saddr, __be32 daddr,				   u16 ident, int dif){	struct hlist_nulls_head *hslot = ping_hashslot(&ping_table, net, ident);	struct sock *sk = NULL;	struct inet_sock *isk;	struct hlist_nulls_node *hnode; 	pr_debug("try to find: num = %d, daddr = %pI4, dif = %d\n",		 (int)ident, &daddr, dif);	read_lock_bh(&ping_table.lock); 	ping_portaddr_for_each_entry(sk, hnode, hslot) {		isk = inet_sk(sk); 		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); 		pr_debug("iterate\n");		if (isk->inet_num != ident)			continue;		if (isk->inet_rcv_saddr && isk->inet_rcv_saddr != daddr)			continue;		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, gid_t *low,					  gid_t *high){	gid_t *data = net->ipv4.sysctl_ping_group_range;	unsigned int seq; 	do {		seq = read_seqbegin(&sysctl_local_ports.lock); 		*low = data[0];		*high = data[1];	} while (read_seqretry(&sysctl_local_ports.lock, seq));}  static int ping_init_sock(struct sock *sk){	struct net *net = sock_net(sk);	gid_t group = current_egid();	gid_t range[2];	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, range, range+1);	low = make_kgid(&init_user_ns, range[0]);	high = make_kgid(&init_user_ns, range[1]);	if (!gid_valid(low) || !gid_valid(high) || gid_lt(high, low))		return -EACCES; 	if (range[0] <= group && group <= range[1])		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;} static 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);} /* * 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. */ static int ping_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len){	struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;	struct inet_sock *isk = inet_sk(sk);	unsigned short snum;	int chk_addr_ret;	int err; 	if (addr_len < sizeof(struct sockaddr_in))		return -EINVAL; 	pr_debug("ping_v4_bind(sk=%p,sa_addr=%08x,sa_port=%d)\n",		 sk, addr->sin_addr.s_addr, ntohs(addr->sin_port)); 	chk_addr_ret = inet_addr_type(sock_net(sk), 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; 	lock_sock(sk); 	err = -EINVAL;	if (isk->inet_num != 0)		goto out; 	err = -EADDRINUSE;	isk->inet_rcv_saddr = isk->inet_saddr = addr->sin_addr.s_addr;	snum = ntohs(addr->sin_port);	if (ping_v4_get_port(sk, snum) != 0) {		isk->inet_saddr = isk->inet_rcv_saddr = 0;		goto out;	} 	pr_debug("after bind(): num = %d, daddr = %pI4, dif = %d\n",		 (int)isk->inet_num,		 &isk->inet_rcv_saddr,		 (int)sk->sk_bound_dev_if); 	err = 0;	if (isk->inet_rcv_saddr)		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;	if (snum)		sk->sk_userlocks |= SOCK_BINDPORT_LOCK;	isk->inet_sport = htons(isk->inet_num);	isk->inet_daddr = 0;	isk->inet_dport = 0;	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 type, int code){	if (type == ICMP_ECHO && code == 0)		return 1;	return 0;} /* * This routine is called by the ICMP module when it gets some * sort of error condition. */ static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb); void ping_err(struct sk_buff *skb, u32 info){	struct iphdr *iph = (struct iphdr *)skb->data;	struct icmphdr *icmph = (struct icmphdr *)(skb->data+(iph->ihl<<2));	struct inet_sock *inet_sock;	int type = icmph->type;	int code = icmph->code;	struct net *net = dev_net(skb->dev);	struct sock *sk;	int harderr;	int err; 	/* We assume the packet has already been checked by icmp_unreach */ 	if (!ping_supported(icmph->type, icmph->code))		return; 	pr_debug("ping_err(type=%04x,code=%04x,id=%04x,seq=%04x)\n", type,		 code, ntohs(icmph->un.echo.id), ntohs(icmph->un.echo.sequence)); 	sk = ping_v4_lookup(net, iph->daddr, iph->saddr,			    ntohs(icmph->un.echo.id), skb->dev->ifindex);	if (sk == NULL) {		pr_debug("no socket, dropping\n");		return;	/* No socket for error */	}	pr_debug("err on socket %p\n", sk); 	err = 0;	harderr = 0;	inet_sock = inet_sk(sk); 	switch (type) {	default:	case ICMP_TIME_EXCEEDED:		err = EHOSTUNREACH;		break;	case ICMP_SOURCE_QUENCH:		/* This is not a real error but ping wants to see it.		 * Report it with some fake errno. */		err = EREMOTEIO;		break;	case ICMP_PARAMETERPROB:		err = EPROTO;		harderr = 1;		break;	case ICMP_DEST_UNREACH:		if (code == ICMP_FRAG_NEEDED) { /* Path MTU discovery */			if (inet_sock->pmtudisc != IP_PMTUDISC_DONT) {				err = EMSGSIZE;				harderr = 1;				break;			}			goto out;		}		err = EHOSTUNREACH;		if (code <= NR_ICMP_UNREACH) {			harderr = icmp_err_convert[code].fatal;			err = icmp_err_convert[code].errno;		}		break;	case ICMP_REDIRECT:		/* See ICMP_SOURCE_QUENCH */		err = EREMOTEIO;		break;	} 	/*	 *      RFC1122: OK.  Passes ICMP errors back to application, as per	 *	4.1.3.3.	 */	if (!inet_sock->recverr) {		if (!harderr || sk->sk_state != TCP_ESTABLISHED)			goto out;	} else {		ip_icmp_error(sk, skb, err, 0 /* no remote port */,			 info, (u8 *)icmph);	}	sk->sk_err = err;	sk->sk_error_report(sk);out:	sock_put(sk);} /* *	Copy and checksum an ICMP Echo packet from user space into a buffer. */ struct pingfakehdr {	struct icmphdr icmph;	struct iovec *iov;	__wsum wcheck;}; static int ping_getfrag(void *from, char *to,			int offset, int fraglen, int odd, struct sk_buff *skb){	struct pingfakehdr *pfh = (struct pingfakehdr *)from; 	if (offset == 0) {		if (fraglen < sizeof(struct icmphdr))			BUG();		if (csum_partial_copy_fromiovecend(to + sizeof(struct icmphdr),			    pfh->iov, 0, fraglen - sizeof(struct icmphdr),			    &pfh->wcheck))			return -EFAULT; 		return 0;	}	if (offset < sizeof(struct icmphdr))		BUG();	if (csum_partial_copy_fromiovecend			(to, pfh->iov, offset - sizeof(struct icmphdr),			 fraglen, &pfh->wcheck))		return -EFAULT;	return 0;} static int ping_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,				    struct flowi4 *fl4){	struct sk_buff *skb = skb_peek(&sk->sk_write_queue); 	pfh->wcheck = csum_partial((char *)&pfh->icmph,		sizeof(struct icmphdr), pfh->wcheck);	pfh->icmph.checksum = csum_fold(pfh->wcheck);	memcpy(icmp_hdr(skb), &pfh->icmph, sizeof(struct icmphdr));	skb->ip_summed = CHECKSUM_NONE;	return ip_push_pending_frames(sk, fl4);} static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,			size_t len){	struct net *net = sock_net(sk);	struct flowi4 fl4;	struct inet_sock *inet = inet_sk(sk);	struct ipcm_cookie ipc;	struct icmphdr user_icmph;	struct pingfakehdr pfh;	struct rtable *rt = NULL;	struct ip_options_data opt_copy;	int free = 0;	__be32 saddr, daddr, faddr;	u8  tos;	int err; 	pr_debug("ping_sendmsg(sk=%p,sk->num=%u)\n", inet, inet->inet_num);  	if (len > 0xFFFF)		return -EMSGSIZE; 	/*	 *	Check the flags.	 */ 	/* Mirror BSD error message compatibility */	if (msg->msg_flags & MSG_OOB)		return -EOPNOTSUPP; 	/*	 *	Fetch the ICMP header provided by the userland.	 *	iovec is modified!	 */ 	if (memcpy_fromiovec((u8 *)&user_icmph, msg->msg_iov,			     sizeof(struct icmphdr)))		return -EFAULT;	if (!ping_supported(user_icmph.type, user_icmph.code))		return -EINVAL; 	/*	 *	Get and verify the address.	 */

Lines 1–500 of 939.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t