1138 lines · 25.7 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. * *		RAW - implementation of IP "raw" sockets. * * Authors:	Ross Biro *		Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> * * Fixes: *		Alan Cox	:	verify_area() fixed up *		Alan Cox	:	ICMP error handling *		Alan Cox	:	EMSGSIZE if you send too big a packet *		Alan Cox	: 	Now uses generic datagrams and shared *					skbuff library. No more peek crashes, *					no more backlogs *		Alan Cox	:	Checks sk->broadcast. *		Alan Cox	:	Uses skb_free_datagram/skb_copy_datagram *		Alan Cox	:	Raw passes ip options too *		Alan Cox	:	Setsocketopt added *		Alan Cox	:	Fixed error return for broadcasts *		Alan Cox	:	Removed wake_up calls *		Alan Cox	:	Use ttl/tos *		Alan Cox	:	Cleaned up old debugging *		Alan Cox	:	Use new kernel side addresses *	Arnt Gulbrandsen	:	Fixed MSG_DONTROUTE in raw sockets. *		Alan Cox	:	BSD style RAW socket demultiplexing. *		Alan Cox	:	Beginnings of mrouted support. *		Alan Cox	:	Added IP_HDRINCL option. *		Alan Cox	:	Skip broadcast check if BSDism set. *		David S. Miller	:	New socket lookup architecture. */ #include <linux/types.h>#include <linux/atomic.h>#include <asm/byteorder.h>#include <asm/current.h>#include <linux/uaccess.h>#include <asm/ioctls.h>#include <linux/stddef.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/kernel.h>#include <linux/export.h>#include <linux/spinlock.h>#include <linux/sockios.h>#include <linux/socket.h>#include <linux/in.h>#include <linux/mroute.h>#include <linux/netdevice.h>#include <linux/in_route.h>#include <linux/route.h>#include <linux/skbuff.h>#include <linux/igmp.h>#include <net/net_namespace.h>#include <net/dst.h>#include <net/sock.h>#include <linux/ip.h>#include <linux/net.h>#include <net/ip.h>#include <net/icmp.h>#include <net/udp.h>#include <net/raw.h>#include <net/snmp.h>#include <net/tcp_states.h>#include <net/inet_common.h>#include <net/checksum.h>#include <net/xfrm.h>#include <linux/rtnetlink.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv4.h>#include <linux/compat.h>#include <linux/uio.h> struct raw_frag_vec {	struct msghdr *msg;	union {		struct icmphdr icmph;		char c[1];	} hdr;	int hlen;}; struct raw_hashinfo raw_v4_hashinfo;EXPORT_SYMBOL_GPL(raw_v4_hashinfo); int raw_hash_sk(struct sock *sk){	struct raw_hashinfo *h = sk->sk_prot->h.raw_hash;	struct hlist_head *hlist; 	hlist = &h->ht[raw_hashfunc(sock_net(sk), inet_sk(sk)->inet_num)]; 	spin_lock(&h->lock);	sk_add_node_rcu(sk, hlist);	sock_set_flag(sk, SOCK_RCU_FREE);	spin_unlock(&h->lock);	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); 	return 0;} void raw_unhash_sk(struct sock *sk){	struct raw_hashinfo *h = sk->sk_prot->h.raw_hash; 	spin_lock(&h->lock);	if (sk_del_node_init_rcu(sk))		sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);	spin_unlock(&h->lock);} bool raw_v4_match(struct net *net, const struct sock *sk, unsigned short num,		  __be32 raddr, __be32 laddr, int dif, int sdif){	const struct inet_sock *inet = inet_sk(sk);	__be32 daddr, rcv_saddr; 	if (!net_eq(sock_net(sk), net) || inet->inet_num != num)		return false; 	daddr = READ_ONCE(inet->inet_daddr);	if (daddr && daddr != raddr)		return false; 	rcv_saddr = READ_ONCE(inet->inet_rcv_saddr);	if (rcv_saddr && rcv_saddr != laddr)		return false; 	return raw_sk_bound_dev_eq(net, READ_ONCE(sk->sk_bound_dev_if),				   dif, sdif);}EXPORT_SYMBOL_GPL(raw_v4_match); /* *	0 - deliver *	1 - block */static int icmp_filter(const struct sock *sk, const struct sk_buff *skb){	struct icmphdr _hdr;	const struct icmphdr *hdr; 	hdr = skb_header_pointer(skb, skb_transport_offset(skb),				 sizeof(_hdr), &_hdr);	if (!hdr)		return 1; 	if (hdr->type < 32) {		__u32 data = raw_sk(sk)->filter.data; 		return ((1U << hdr->type) & data) != 0;	} 	/* Do not block unknown ICMP types */	return 0;} /* IP input processing comes here for RAW socket delivery. * Caller owns SKB, so we must make clones. * * RFC 1122: SHOULD pass TOS value up to the transport layer. * -> It does. And not only TOS, but all IP header. */static int raw_v4_input(struct net *net, struct sk_buff *skb,			const struct iphdr *iph, int hash){	int sdif = inet_sdif(skb);	struct hlist_head *hlist;	int dif = inet_iif(skb);	int delivered = 0;	struct sock *sk; 	hlist = &raw_v4_hashinfo.ht[hash];	rcu_read_lock();	sk_for_each_rcu(sk, hlist) {		if (!raw_v4_match(net, sk, iph->protocol,				  iph->saddr, iph->daddr, dif, sdif))			continue; 		if (atomic_read(&sk->sk_rmem_alloc) >=		    READ_ONCE(sk->sk_rcvbuf)) {			sk_drops_inc(sk);			continue;		} 		delivered = 1;		if ((iph->protocol != IPPROTO_ICMP || !icmp_filter(sk, skb)) &&		    ip_mc_sf_allow(sk, iph->daddr, iph->saddr,				   skb->dev->ifindex, sdif)) {			struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC); 			/* Not releasing hash table! */			if (clone)				raw_rcv(sk, clone);		}	}	rcu_read_unlock();	return delivered;} int raw_local_deliver(struct sk_buff *skb, int protocol){	struct net *net = dev_net(skb->dev); 	return raw_v4_input(net, skb, ip_hdr(skb),			    raw_hashfunc(net, protocol));} static void raw_err(struct sock *sk, struct sk_buff *skb, u32 info){	struct inet_sock *inet = inet_sk(sk);	const int type = icmp_hdr(skb)->type;	const int code = icmp_hdr(skb)->code;	int harderr = 0;	bool recverr;	int err = 0; 	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)		ipv4_sk_update_pmtu(skb, sk, info);	else if (type == ICMP_REDIRECT) {		ipv4_sk_redirect(skb, sk);		return;	} 	/* Report error on raw socket, if:	   1. User requested ip_recverr.	   2. Socket is connected (otherwise the error indication	      is useless without ip_recverr and error is hard.	 */	recverr = inet_test_bit(RECVERR, sk);	if (!recverr && sk->sk_state != TCP_ESTABLISHED)		return; 	switch (type) {	default:	case ICMP_TIME_EXCEEDED:		err = EHOSTUNREACH;		break;	case ICMP_SOURCE_QUENCH:		return;	case ICMP_PARAMETERPROB:		err = EPROTO;		harderr = 1;		break;	case ICMP_DEST_UNREACH:		err = EHOSTUNREACH;		if (code > NR_ICMP_UNREACH)			break;		if (code == ICMP_FRAG_NEEDED) {			harderr = READ_ONCE(inet->pmtudisc) != IP_PMTUDISC_DONT;			err = EMSGSIZE;		} else {			err = icmp_err_convert[code].errno;			harderr = icmp_err_convert[code].fatal;		}	} 	if (recverr) {		const struct iphdr *iph = (const struct iphdr *)skb->data;		u8 *payload = skb->data + (iph->ihl << 2); 		if (inet_test_bit(HDRINCL, sk))			payload = skb->data;		ip_icmp_error(sk, skb, err, 0, info, payload);	} 	if (recverr || harderr) {		sk->sk_err = err;		sk_error_report(sk);	}} void raw_icmp_error(struct sk_buff *skb, int protocol, u32 info){	struct net *net = dev_net(skb->dev);	int dif = skb->dev->ifindex;	int sdif = inet_sdif(skb);	struct hlist_head *hlist;	const struct iphdr *iph;	struct sock *sk;	int hash; 	hash = raw_hashfunc(net, protocol);	hlist = &raw_v4_hashinfo.ht[hash]; 	rcu_read_lock();	sk_for_each_rcu(sk, hlist) {		iph = (const struct iphdr *)skb->data;		if (!raw_v4_match(net, sk, iph->protocol,				  iph->daddr, iph->saddr, dif, sdif))			continue;		raw_err(sk, skb, info);	}	rcu_read_unlock();} static int raw_rcv_skb(struct sock *sk, struct sk_buff *skb){	enum skb_drop_reason reason; 	/* Charge it to the socket. */ 	ipv4_pktinfo_prepare(sk, skb, true);	reason = sock_queue_rcv_skb_reason(sk, skb);	if (reason) {		sk_skb_reason_drop(sk, skb, reason);		return NET_RX_DROP;	} 	return NET_RX_SUCCESS;} int raw_rcv(struct sock *sk, struct sk_buff *skb){	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) {		sk_drops_inc(sk);		sk_skb_reason_drop(sk, skb, SKB_DROP_REASON_XFRM_POLICY);		return NET_RX_DROP;	}	nf_reset_ct(skb); 	skb_push(skb, -skb_network_offset(skb)); 	raw_rcv_skb(sk, skb);	return 0;} static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,			   struct msghdr *msg, size_t length,			   struct rtable **rtp, unsigned int flags,			   const struct sockcm_cookie *sockc){	struct inet_sock *inet = inet_sk(sk);	struct net *net = sock_net(sk);	struct iphdr *iph;	struct sk_buff *skb;	unsigned int iphlen;	int err;	struct rtable *rt = *rtp;	int hlen, tlen; 	if (length > rt->dst.dev->mtu) {		ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,			       rt->dst.dev->mtu);		return -EMSGSIZE;	}	if (length < sizeof(struct iphdr))		return -EINVAL; 	if (flags&MSG_PROBE)		goto out; 	hlen = LL_RESERVED_SPACE(rt->dst.dev);	tlen = rt->dst.dev->needed_tailroom;	skb = sock_alloc_send_skb(sk,				  length + hlen + tlen + 15,				  flags & MSG_DONTWAIT, &err);	if (!skb)		goto error;	skb_reserve(skb, hlen); 	skb->protocol = htons(ETH_P_IP);	skb->priority = sockc->priority;	skb->mark = sockc->mark;	skb_set_delivery_type_by_clockid(skb, sockc->transmit_time, sk->sk_clockid);	skb_dst_set(skb, &rt->dst);	*rtp = NULL; 	skb_reset_network_header(skb);	iph = ip_hdr(skb);	skb_put(skb, length); 	skb->ip_summed = CHECKSUM_NONE; 	skb_setup_tx_timestamp(skb, sockc); 	if (flags & MSG_CONFIRM)		skb_set_dst_pending_confirm(skb, 1); 	skb->transport_header = skb->network_header;	err = -EFAULT;	if (memcpy_from_msg(iph, msg, length))		goto error_free; 	iphlen = iph->ihl * 4; 	/*	 * We don't want to modify the ip header, but we do need to	 * be sure that it won't cause problems later along the network	 * stack.  Specifically we want to make sure that iph->ihl is a	 * sane value.  If ihl points beyond the length of the buffer passed	 * in, reject the frame as invalid	 */	err = -EINVAL;	if (iphlen > length || iphlen < sizeof(*iph))		goto error_free; 	if (iphlen >= sizeof(*iph)) {		if (!iph->saddr)			iph->saddr = fl4->saddr;		iph->check   = 0;		iph->tot_len = htons(length);		if (!iph->id)			ip_select_ident(net, skb, NULL); 		iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);		skb->transport_header += iphlen;		if (iph->protocol == IPPROTO_ICMP &&		    length >= iphlen + sizeof(struct icmphdr))			icmp_out_count(net, ((struct icmphdr *)				skb_transport_header(skb))->type);	} 	err = NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_OUT,		      net, sk, skb, NULL, rt->dst.dev,		      dst_output);	if (err > 0)		err = net_xmit_errno(err);	if (err)		goto error;out:	return 0; error_free:	kfree_skb(skb);error:	IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);	if (err == -ENOBUFS && !inet_test_bit(RECVERR, sk))		err = 0;	return err;} static int raw_probe_proto_opt(struct raw_frag_vec *rfv, struct flowi4 *fl4){	int err; 	if (fl4->flowi4_proto != IPPROTO_ICMP)		return 0; 	/* We only need the first two bytes. */	rfv->hlen = 2; 	err = memcpy_from_msg(rfv->hdr.c, rfv->msg, rfv->hlen);	if (err)		return err; 	fl4->fl4_icmp_type = rfv->hdr.icmph.type;	fl4->fl4_icmp_code = rfv->hdr.icmph.code; 	return 0;} static int raw_getfrag(void *from, char *to, int offset, int len, int odd,		       struct sk_buff *skb){	struct raw_frag_vec *rfv = from; 	if (offset < rfv->hlen) {		int copy = min(rfv->hlen - offset, len); 		if (skb->ip_summed == CHECKSUM_PARTIAL)			memcpy(to, rfv->hdr.c + offset, copy);		else			skb->csum = csum_block_add(				skb->csum,				csum_partial_copy_nocheck(rfv->hdr.c + offset,							  to, copy),				odd); 		odd = 0;		offset += copy;		to += copy;		len -= copy; 		if (!len)			return 0;	} 	offset -= rfv->hlen; 	return ip_generic_getfrag(rfv->msg, to, offset, len, odd, skb);} static int raw_sendmsg(struct sock *sk, struct msghdr *msg, size_t len){	DEFINE_RAW_FLEX(struct ip_options_rcu, opt_copy, opt.__data,			IP_OPTIONS_DATA_FIXED_SIZE);	struct inet_sock *inet = inet_sk(sk);	struct net *net = sock_net(sk);	struct ipcm_cookie ipc;	struct rtable *rt = NULL;	struct flowi4 fl4;	u8 scope;	int free = 0;	__be32 daddr;

Lines 1–500 of 1138.