patch-1.3.26 linux/net/ipv4/icmp.c

Next file: linux/net/ipv4/igmp.c
Previous file: linux/net/ipv4/af_inet.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.25/linux/net/ipv4/icmp.c linux/net/ipv4/icmp.c
@@ -1,109 +1,84 @@
 /*
- * 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.
+ *	NET3:	Implementation of the ICMP protocol layer. 
+ *	
+ *		Alan Cox, <[email protected]>
  *
- *		Internet Control Message Protocol (ICMP)
+ *	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.
  *
- * Version:	@(#)icmp.c	1.0.11	06/02/93
+ *	Some of the function names and the icmp unreach table for this
+ *	module were derived from [icmp.c 1.0.11 06/02/93] by
+ *	Ross Biro, Fred N. van Kempen, Mark Evans, Alan Cox, Gerhard Koerting.
+ *	Other than that this module is a complete rewrite.
  *
- * Authors:	Ross Biro, <[email protected]>
- *		Fred N. van Kempen, <[email protected]>
- *		Mark Evans, <[email protected]>
- *		Alan Cox, <[email protected]>
- *		Stefan Becker, <[email protected]>
- *
- * Fixes:	
- *		Alan Cox	:	Generic queue usage.
- *		Gerhard Koerting:	ICMP addressing corrected
- *		Alan Cox	:	Use tos/ttl settings
- *		Alan Cox	:	Protocol violations
- *		Alan Cox	:	SNMP Statistics		
- *		Alan Cox	:	Routing errors
- *		Alan Cox	:	Changes for newer routing code
- *		Alan Cox	:	Removed old debugging junk
- *		Alan Cox	:	Fixed the ICMP error status of net/host unreachable
- *	Gerhard Koerting	:	Fixed broadcast ping properly
- *		Ulrich Kunitz	:	Fixed ICMP timestamp reply
- *		A.N.Kuznetsov	:	Multihoming fixes.
- *		Laco Rusnak	:	Multihoming fixes.
- *		Alan Cox	:	Tightened up icmp_send().
- *		Alan Cox	:	Multicasts.
- *		Stefan Becker   :       ICMP redirects in icmp_send().
- *		Peter Belding	:	Tightened up ICMP redirect handling
- *		Alan Cox	:	Tightened even more.
- *		Arnt Gulbrandsen:	Misplaced #endif with net redirect and break
- *		A.N.Kuznetsov	:	ICMP timestamp still used skb+1
+ *	Fixes:
  *		Mike Shaver	:	RFC1122 checks.
- * 
  *
- *		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.
- */
-
-/* RFC1122 Status: (boy, are there a lot of rules for ICMP)
-   3.2.2 (Generic ICMP stuff)
-     MUST discard messages of unknown type. (OK)
-     MUST copy at least the first 8 bytes from the offending packet
-       when sending ICMP errors. (OK)
-     MUST pass received ICMP errors up to protocol level. (OK)
-     SHOULD send ICMP errors with TOS == 0. (OK)
-     MUST NOT send ICMP errors in reply to:
-       ICMP errors (OK)
-       Broadcast/multicast datagrams (OK)
-       MAC broadcasts (OK)
-       Non-initial fragments (OK)
-       Datagram with a source address that isn't a single host. (OK)
-  3.2.2.1 (Destination Unreachable)
-    All the rules govern the IP layer, and are dealt with in ip.c, not here.
-  3.2.2.2 (Redirect)
-    Host SHOULD NOT send ICMP_REDIRECTs.  (OK)
-    MUST update routing table in response to host or network redirects. 
-      (host OK, network NOT YET) [Intentionally -- AC]
-    SHOULD drop redirects if they're not from directly connected gateway
-      (OK -- we drop it if it's not from our old gateway, which is close
-       enough)
-  3.2.2.3 (Source Quench)
-    MUST pass incoming SOURCE_QUENCHs to transport layer (OK)
-    Other requirements are dealt with at the transport layer.
-  3.2.2.4 (Time Exceeded)
-    MUST pass TIME_EXCEEDED to transport layer (OK)
-    Other requirements dealt with at IP (generating TIME_EXCEEDED).
-  3.2.2.5 (Parameter Problem)
-    SHOULD generate these, but it doesn't say for what.  So we're OK. =)
-    MUST pass received PARAMPROBLEM to transport layer (NOT YET)
-    	[Solaris 2.X seems to assert EPROTO when this occurs] -- AC
-  3.2.2.6 (Echo Request/Reply)
-    MUST reply to ECHO_REQUEST, and give app to do ECHO stuff (OK, OK)
-    MAY discard broadcast ECHO_REQUESTs. (We don't, but that's OK.)
-    MUST reply using same source address as the request was sent to.
-      We're OK for unicast ECHOs, and it doesn't say anything about
-      how to handle broadcast ones, since it's optional.
-    MUST copy data from REQUEST to REPLY (OK)
-      unless it would require illegal fragmentation (MUST) (NOT YET)
-    MUST pass REPLYs to transport/user layer (OK)
-    MUST use any provided source route (reversed) for REPLY. (NOT YET)
- 3.2.2.7 (Information Request/Reply)
-   MUST NOT implement this. (I guess that means silently discard...?) (OK)
- 3.2.2.8 (Timestamp Request/Reply)
-   MAY implement (OK)
-   SHOULD be in-kernel for "minimum variability" (OK)
-   MAY discard broadcast REQUESTs.  (OK, but see source for inconsistency)
-   MUST reply using same source address as the request was sent to. (OK)
-   MUST reverse source route, as per ECHO (NOT YET)
-   MUST pass REPLYs to transport/user layer (requires RAW, just like ECHO) (OK)
-   MUST update clock for timestamp at least 15 times/sec (OK)
-   MUST be "correct within a few minutes" (OK)
- 3.2.2.9 (Address Mask Request/Reply)
-   MAY implement (OK)
-   MUST send a broadcast REQUEST if using this system to set netmask
-     (OK... we don't use it)
-   MUST discard received REPLYs if not using this system (OK)
-   MUST NOT send replies unless specifically made agent for this sort
-     of thing. (OK)
-*/
+ *
+ *
+ * RFC1122 Status: (boy, are there a lot of rules for ICMP)
+ *  3.2.2 (Generic ICMP stuff)
+ *   MUST discard messages of unknown type. (OK)
+ *   MUST copy at least the first 8 bytes from the offending packet
+ *     when sending ICMP errors. (OK)
+ *   MUST pass received ICMP errors up to protocol level. (OK)
+ *   SHOULD send ICMP errors with TOS == 0. (OK)
+ *   MUST NOT send ICMP errors in reply to:
+ *     ICMP errors (OK)
+ *     Broadcast/multicast datagrams (OK)
+ *     MAC broadcasts (OK)
+ *     Non-initial fragments (OK)
+ *     Datagram with a source address that isn't a single host. (OK)
+ *  3.2.2.1 (Destination Unreachable)
+ *   All the rules govern the IP layer, and are dealt with in ip.c, not here.
+ *  3.2.2.2 (Redirect)
+ *   Host SHOULD NOT send ICMP_REDIRECTs.  (OK)
+ *   MUST update routing table in response to host or network redirects. 
+ *     (host OK, network NOT YET) [Intentionally -- AC]
+ *   SHOULD drop redirects if they're not from directly connected gateway
+ *     (OK -- we drop it if it's not from our old gateway, which is close
+ *      enough)
+ * 3.2.2.3 (Source Quench)
+ *   MUST pass incoming SOURCE_QUENCHs to transport layer (OK)
+ *   Other requirements are dealt with at the transport layer.
+ * 3.2.2.4 (Time Exceeded)
+ *   MUST pass TIME_EXCEEDED to transport layer (OK)
+ *   Other requirements dealt with at IP (generating TIME_EXCEEDED).
+ * 3.2.2.5 (Parameter Problem)
+ *   SHOULD generate these, but it doesn't say for what.  So we're OK. =)
+ *   MUST pass received PARAMPROBLEM to transport layer (NOT YET)
+ *   	[Solaris 2.X seems to assert EPROTO when this occurs] -- AC
+ * 3.2.2.6 (Echo Request/Reply)
+ *   MUST reply to ECHO_REQUEST, and give app to do ECHO stuff (OK, OK)
+ *   MAY discard broadcast ECHO_REQUESTs. (We don't, but that's OK.)
+ *   MUST reply using same source address as the request was sent to.
+ *     We're OK for unicast ECHOs, and it doesn't say anything about
+ *     how to handle broadcast ones, since it's optional.
+ *   MUST copy data from REQUEST to REPLY (OK)
+ *     unless it would require illegal fragmentation (N/A)
+ *   MUST pass REPLYs to transport/user layer (OK)
+ *   MUST use any provided source route (reversed) for REPLY. (NOT YET)
+ * 3.2.2.7 (Information Request/Reply)
+ *   MUST NOT implement this. (I guess that means silently discard...?) (OK)
+ * 3.2.2.8 (Timestamp Request/Reply)
+ *   MAY implement (OK)
+ *   SHOULD be in-kernel for "minimum variability" (OK)
+ *   MAY discard broadcast REQUESTs.  (OK, but see source for inconsistency)
+ *   MUST reply using same source address as the request was sent to. (OK)
+ *   MUST reverse source route, as per ECHO (NOT YET)
+ *   MUST pass REPLYs to transport/user layer (requires RAW, just like ECHO) (OK)
+ *   MUST update clock for timestamp at least 15 times/sec (OK)
+ *   MUST be "correct within a few minutes" (OK)
+ * 3.2.2.9 (Address Mask Request/Reply)
+ *   MAY implement (OK)
+ *   MUST send a broadcast REQUEST if using this system to set netmask
+ *     (OK... we don't use it)
+ *   MUST discard received REPLYs if not using this system (OK)
+ *   MUST NOT send replies unless specifically made agent for this sort
+ *     of thing. (OK)
+ */
 
 #include <linux/types.h>
 #include <linux/sched.h>
@@ -129,18 +104,17 @@
 #include <asm/segment.h>
 #include <net/checksum.h>
 
-
 #define min(a,b)	((a)<(b)?(a):(b))
 
-
 /*
  *	Statistics
  */
  
-struct icmp_mib	icmp_statistics={0,};
-
+struct icmp_mib icmp_statistics;
 
 /* An array of errno for error messages from dest unreach. */
+/* RFC 1122: 3.2.2.1 States that NET_UNREACH, HOS_UNREACH and SR_FAIELD MUST be considered 'transient errrs'. */
+
 struct icmp_err icmp_err_convert[] = {
   { ENETUNREACH,	0 },	/*	ICMP_NET_UNREACH	*/
   { EHOSTUNREACH,	0 },	/*	ICMP_HOST_UNREACH	*/
@@ -157,238 +131,254 @@
   { EOPNOTSUPP,		0 }	/*	ICMP_HOST_UNR_TOS	*/
 };
 
+/*
+ *	A spare long used to speed up statistics udpating
+ */
+ 
+unsigned long dummy;
 
 /*
- *	Send an ICMP message in response to a situation
+ *	ICMP control array. This specifies what to do with each ICMP.
  */
  
+struct icmp_control
+{
+	unsigned long *output;		/* Address to increment on output */
+	unsigned long *input;		/* Address to increment on input */
+	void (*handler)(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 saddr, __u32 daddr, int len);
+	unsigned long error;		/* This ICMP is classed as an error message */	
+};
+
+static struct icmp_control icmp_pointers[19];
+
+/*
+ *	Build xmit assembly blocks
+ */
+
+struct icmp_bxm
+{
+	void *data_ptr;
+	int data_len;
+	struct icmphdr icmph;
+	unsigned long csum;
+};
+
+/*
+ *	The ICMP socket. This is the most convenient way to flow control
+ *	our ICMP output as well as maintain a clean interface throughout
+ *	all layers. All Socketless IP sends will soon be gone.
+ */
+	
+struct socket icmp_socket;
+
+/*
+ *	Send an ICMP frame.
+ */
+ 
+
+/*
+ *	Maintain the counters used in the SNMP statistics for outgoing ICMP
+ */
+ 
+static void icmp_out_count(int type)
+{
+	if(type>18)
+		return;
+	(*icmp_pointers[type].output)++;
+	icmp_statistics.IcmpOutMsgs++;
+}
+ 
+/*
+ *	Checksum each fragment, and on the first include the headers and final checksum.
+ */
+ 
+static void icmp_glue_bits(const void *p, __u32 saddr, char *to, unsigned int offset, unsigned int fraglen)
+{
+	struct icmp_bxm *icmp_param=(struct icmp_bxm *)p;
+	struct icmphdr *icmph;
+	if(offset)
+		icmp_param->csum=csum_partial_copy(icmp_param->data_ptr+offset-sizeof(struct icmphdr), 
+				to, fraglen,icmp_param->csum);
+	else
+	{
+#ifdef CSUM_FOLD_WORKS
+		/*
+		 *	Need this fixed to make multifragment ICMP's work again.
+		 */
+		icmp_param->csum=csum_partial_copy((void *)&icmp_param->icmph, to, sizeof(struct icmphdr),
+			icmp_param->csum);
+		icmp_param->csum=csum_partial_copy(icmp_param->data_ptr, to+sizeof(struct icmphdr),
+				fraglen-sizeof(struct icmphdr), icmp_param->csum);
+		icmph=(struct icmphdr *)to;
+		icmph->checksum = csum_fold(icmp_param->csum);
+#else
+		memcpy(to, &icmp_param->icmph, sizeof(struct icmphdr));
+		memcpy(to+sizeof(struct icmphdr), icmp_param->data_ptr, fraglen-sizeof(struct icmphdr));
+		icmph=(struct icmphdr *)to;
+		icmph->checksum=ip_compute_csum(to, fraglen);
+#endif
+				
+	}
+}
+ 
+/*
+ *	Driving logic for building and sending ICMP messages.
+ */
+ 
+static void icmp_build_xmit(struct icmp_bxm *icmp_param, __u32 saddr, __u32 daddr)
+{
+	struct sock *sk=icmp_socket.data;
+	sk->saddr=saddr;
+	icmp_param->icmph.checksum=0;
+	icmp_out_count(icmp_param->icmph.type);
+	ip_build_xmit(sk, icmp_glue_bits, icmp_param, 
+		icmp_param->data_len+sizeof(struct icmphdr),
+		daddr, 0, IPPROTO_ICMP);
+}
+
+
+/*
+ *	Send an ICMP message in response to a situation
+ *
+ *	RFC 1122: 3.2.2	MUST send at least the IP header and 8 bytes of header. MAY send more (we don't).
+ *			MUST NOT change this header information.
+ *			MUST NOT reply to a multicast/broadcast IP address.
+ *			MUST NOT reply to a multicast/broadcast MAC address.
+ *			MUST reply to only the first fragment.
+ */
+
 void icmp_send(struct sk_buff *skb_in, int type, int code, unsigned long info, struct device *dev)
 {
-	struct sk_buff *skb;
 	struct iphdr *iph;
-	int offset;
 	struct icmphdr *icmph;
-	int len;
-	struct device *ndev=NULL;	/* Make this =dev to force replies on the same interface */
-	unsigned long our_addr;
 	int atype;
+	struct icmp_bxm icmp_param;
+	__u32 saddr;
 	
 	/*
-	 *	Find the original IP header.
+	 *	Find the original header
 	 */
 	 
-	iph = (struct iphdr *) skb_in->data;
+	iph = skb_in->ip_hdr;
 	
 	/*
-	 *	No replies to MAC multicast
+	 *	No replies to physical multicast/broadcast
 	 */
 	 
 	if(skb_in->pkt_type!=PACKET_HOST)
 		return;
 		
 	/*
-	 *	No replies to IP multicasting
+	 *	Now check at the protocol level
 	 */
 	 
 	atype=ip_chk_addr(iph->daddr);
-	if(atype==IS_BROADCAST || atype==IS_MULTICAST)
+	if(atype==IS_BROADCAST||atype==IS_MULTICAST)
 		return;
-
+		
 	/*
-	 *	Only reply to first fragment.
+	 *	Only reply to fragment 0. We byte re-order the constant
+	 *	mask for efficiency.
 	 */
 	 
-	if(ntohs(iph->frag_off)&IP_OFFSET)
+	if(iph->frag_off&htons(IP_OFFSET))
 		return;
-	 		
-	/*
-	 *	We must NEVER NEVER send an ICMP error to an ICMP error message
+		
+	/* 
+	 *	If we send an ICMP error to an ICMP error a mess would result..
 	 */
 	 
-	if(type==ICMP_DEST_UNREACH||type==ICMP_REDIRECT||type==ICMP_SOURCE_QUENCH||type==ICMP_TIME_EXCEEDED)
+	if(icmp_pointers[type].error)
 	{
-
 		/*
-		 *	Is the original packet an ICMP packet?
+		 *	We are an error, check if we are replying to an ICMP error
 		 */
-
+		 
 		if(iph->protocol==IPPROTO_ICMP)
 		{
-			icmph = (struct icmphdr *) ((char *) iph +
-                                                    4 * iph->ihl);
+			icmph = (struct icmphdr *)((char *)iph + (iph->ihl<<2));
 			/*
-			 *	Check for ICMP error packets (Must never reply to
-			 *	an ICMP error).
+			 *	Assume any unknown ICMP type is an error. This isn't
+			 *	specified by the RFC, but think about it..
 			 */
-	
-			if (icmph->type == ICMP_DEST_UNREACH ||
-				icmph->type == ICMP_SOURCE_QUENCH ||
-				icmph->type == ICMP_REDIRECT ||
-				icmph->type == ICMP_TIME_EXCEEDED ||
-				icmph->type == ICMP_PARAMETERPROB)
+			if(icmph->type>18 || icmp_pointers[icmph->type].error)
 				return;
 		}
 	}
-	icmp_statistics.IcmpOutMsgs++;
 	
 	/*
-	 *	This needs a tidy.	
-	 */
-		
-	switch(type)
-	{
-		case ICMP_DEST_UNREACH:
-			icmp_statistics.IcmpOutDestUnreachs++;
-			break;
-		case ICMP_SOURCE_QUENCH:
-			icmp_statistics.IcmpOutSrcQuenchs++;
-			break;
-		case ICMP_REDIRECT:
-		/* RFC1122: (3.2.2.2) Sorta bad.  SHOULDN'T send */
-		/* ICMP_REDIRECTs unless we're a gateway. -- MS */
-		/* We don't .. this path isnt invoked -- AC */
-			icmp_statistics.IcmpOutRedirects++;
-			break;
-		case ICMP_ECHO:
-			icmp_statistics.IcmpOutEchos++;
-			break;
-		case ICMP_ECHOREPLY:
-			icmp_statistics.IcmpOutEchoReps++;
-			break;
-		case ICMP_TIME_EXCEEDED:
-			icmp_statistics.IcmpOutTimeExcds++;
-			break;
-		case ICMP_PARAMETERPROB:
-			icmp_statistics.IcmpOutParmProbs++;
-			break;
-		case ICMP_TIMESTAMP:
-			icmp_statistics.IcmpOutTimestamps++;
-			break;
-		case ICMP_TIMESTAMPREPLY:
-			icmp_statistics.IcmpOutTimestampReps++;
-			break;
-		case ICMP_ADDRESS:
-			icmp_statistics.IcmpOutAddrMasks++;
-			break;
-		case ICMP_ADDRESSREPLY:
-			icmp_statistics.IcmpOutAddrMaskReps++;
-			break;
-	}		
-	/*
-	 *	Get some memory for the reply. 
+	 *	Tell our driver what to send
 	 */
 	 
-	len = dev->hard_header_len + sizeof(struct iphdr) + sizeof(struct icmphdr) +
-		sizeof(struct iphdr) + 32;	/* amount of header to return */
-	   
-	skb = (struct sk_buff *) alloc_skb(len+15, GFP_ATOMIC);
-	if (skb == NULL) 
-	{
-		icmp_statistics.IcmpOutErrors++;
-		return;
-	}
-	skb->free = 1;
-
-	/*
-	 *	Build Layer 2-3 headers for message back to source. 
-	 */
-
-	our_addr = dev->pa_addr;
-
-	/* RFC1122: (3.2.2).  MUST NOT send ICMP in reply to */
-	/* packet with a source IP address that doesn't define a single */
-	/* host. -- MS.  Checked higher up -- AC */
-
-	if (iph->daddr != our_addr && ip_chk_addr(iph->daddr) == IS_MYADDR)
-		our_addr = iph->daddr;
-	offset = ip_build_header(skb, our_addr, iph->saddr,
-			   &ndev, IPPROTO_ICMP, NULL, len,
-			   skb_in->ip_hdr->tos,255);
-	if (offset < 0) 
-	{
-		icmp_statistics.IcmpOutErrors++;
-		skb->sk = NULL;
-		kfree_skb(skb, FREE_READ);
-		return;
-	}
-
-	/* 
-	 *	Re-adjust length according to actual IP header size. 
-	 */
-
-	skb_put(skb,sizeof(struct icmphdr) + sizeof(struct iphdr) + 8);
+	saddr=iph->daddr;
+	if(saddr!=dev->pa_addr && ip_chk_addr(saddr)!=IS_MYADDR)
+		saddr=dev->pa_addr;
+	
+	icmp_param.icmph.type=type;
+	icmp_param.icmph.code=code;
+	icmp_param.icmph.type=type;
+	icmp_param.icmph.un.gateway=0;
+	icmp_param.data_ptr=iph;
+	icmp_param.data_len=(iph->ihl<<2)+8;	/* RFC says return header + 8 bytes */
 	
 	/*
-	 *	Fill in the frame
+	 *	Set it to build.
 	 */
 	 
-	/* RFC1122: SHOULD send with TOS == 0, and I guess this does. */
-	/* Perhaps it should be explicit? -- MS */
-
-	icmph = (struct icmphdr *) (skb->data + offset);
-	icmph->type = type;
-	icmph->code = code;
-	icmph->checksum = 0;
-	icmph->un.gateway = info;	/* This might not be meant for 
-					   this form of the union but it will
-					   be right anyway */
-
-	/* RFC1122: OK. Copies the minimum 8 bytes unchanged from the offending */
-	/* packet (MUST) as per 3.2.2. -- MS */
-	memcpy(icmph + 1, iph, sizeof(struct iphdr) + 8);
-
-	icmph->checksum = ip_compute_csum((unsigned char *)icmph,
-                         sizeof(struct icmphdr) + sizeof(struct iphdr) + 8);
-
-	/*
-	 *	Send it and free it once sent.
-	 */
-	ip_queue_xmit(NULL, ndev, skb, 1);
+	icmp_build_xmit(&icmp_param, saddr, iph->saddr);
 }
 
 
 /* 
- *	Handle ICMP_UNREACH and ICMP_QUENCH. 
+ *	Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEED, and ICMP_QUENCH. 
  */
  
-static void icmp_unreach(struct icmphdr *icmph, struct sk_buff *skb)
+static void icmp_unreach(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 saddr, __u32 daddr, int len)
 {
-	struct inet_protocol *ipprot;
 	struct iphdr *iph;
-	unsigned char hash;
-	int err;
-
-	err = (icmph->type << 8) | icmph->code;
+	int hash;
+	struct inet_protocol *ipprot;
+	unsigned char *dp;	
+	
 	iph = (struct iphdr *) (icmph + 1);
 	
-	switch(icmph->code & 7) 
+	dp= ((unsigned char *)iph)+(iph->ihl<<2);
+	
+	if(icmph->type==ICMP_DEST_UNREACH)
 	{
-		case ICMP_NET_UNREACH:
-			break;
-		case ICMP_HOST_UNREACH:
-			break;
-		case ICMP_PROT_UNREACH:
-#ifdef CONFIG_NET_DEBUG
-			printk("ICMP: %s:%d: protocol unreachable.\n",
-				in_ntoa(iph->daddr), ntohs(iph->protocol));
-#endif
-			break;
-		case ICMP_PORT_UNREACH:
-			break;
-		case ICMP_FRAG_NEEDED:
-#ifdef CONFIG_NET_DEBUG
-			printk("ICMP: %s: fragmentation needed and DF set.\n",
+		switch(icmph->code & 15)
+		{
+			case ICMP_NET_UNREACH:
+				break;
+			case ICMP_HOST_UNREACH:
+				break;
+			case ICMP_PROT_UNREACH:
+				printk("ICMP: %s:%d: protocol unreachable.\n",
+					in_ntoa(iph->daddr), ntohs(iph->protocol));
+				break;
+			case ICMP_PORT_UNREACH:
+				break;
+			case ICMP_FRAG_NEEDED:
+				printk("ICMP: %s: fragmentation needed and DF set.\n",
 								in_ntoa(iph->daddr));
-#endif
-			break;
-		case ICMP_SR_FAILED:
-#ifdef CONFIG_NET_DEBUG
-			printk("ICMP: %s: Source Route Failed.\n", in_ntoa(iph->daddr));
-#endif
-			break;
-		default:
-			break;
+				break;
+			case ICMP_SR_FAILED:
+				printk("ICMP: %s: Source Route Failed.\n", in_ntoa(iph->daddr));
+				break;
+			default:
+				break;
+		}
+		if(icmph->code>12)	/* Invalid type */
+			return;
 	}
+	
+	/*
+	 *	Throw it at our lower layers
+	 *
+	 *	RFC 1122: 3.2.2 MUST extract the protocol ID from the passed header.
+	 *	RFC 1122: 3.2.2.1 MUST pass ICMP unreach messages to the transport layer.
+	 *	RFC 1122: 3.2.2.2 MUST pass ICMP time expired messages to transport layer.
+	 */
 
 	/*
 	 *	Get the protocol(s). 
@@ -398,6 +388,8 @@
 
 	/*
 	 *	This can't change while we are doing it. 
+	 *
+	 *	FIXME: Deliver to appropriate raw sockets too.
 	 */
 	 
 	ipprot = (struct inet_protocol *) inet_protos[hash];
@@ -416,7 +408,7 @@
 
 		if (iph->protocol == ipprot->protocol && ipprot->err_handler) 
 		{
-			ipprot->err_handler(err, (unsigned char *)(icmph + 1),
+			ipprot->err_handler(icmph->type, icmph->code, dp,
 					    iph->daddr, iph->saddr, ipprot);
 		}
 
@@ -430,8 +422,7 @@
  *	Handle ICMP_REDIRECT. 
  */
 
-static void icmp_redirect(struct icmphdr *icmph, struct sk_buff *skb,
-	struct device *dev, unsigned long source)
+static void icmp_redirect(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 source, __u32 daddr, int len)
 {
 #ifndef CONFIG_IP_FORWARD
 	struct rtable *rt;
@@ -500,396 +491,210 @@
   	kfree_skb(skb, FREE_READ);
 }
 
-
 /*
  *	Handle ICMP_ECHO ("ping") requests. 
+ *
+ *	RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo requests.
+ *	RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be included in the reply.
+ *	See also WRT handling of options once they are done and working.
  */
  
-static void icmp_echo(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev,
-	  unsigned long saddr, unsigned long daddr, int len,
-	  struct options *opt)
-{
-	struct icmphdr *icmphr;
-	struct sk_buff *skb2;
-	struct device *ndev=NULL;
-	int size, offset;
-
-	icmp_statistics.IcmpOutEchoReps++;
-	icmp_statistics.IcmpOutMsgs++;
-	
-	size = dev->hard_header_len + 64 + len + 15;
-	skb2 = alloc_skb(size, GFP_ATOMIC);
-
-	if (skb2 == NULL) 
-	{
-		icmp_statistics.IcmpOutErrors++;
-		kfree_skb(skb, FREE_READ);
-		return;
-	}
-	skb2->free = 1;
-
-	/* Build Layer 2-3 headers for message back to source */
-	offset = ip_build_header(skb2, daddr, saddr, &ndev,
-	 	IPPROTO_ICMP, opt, len, skb->ip_hdr->tos,255);
-	if (offset < 0) 
-	{
-		icmp_statistics.IcmpOutErrors++;
-		printk("ICMP: Could not build IP Header for ICMP ECHO Response\n");
-		kfree_skb(skb2,FREE_WRITE);
-		kfree_skb(skb, FREE_READ);
-		return;
-	}
-
-	/*
-	 *	Re-adjust length according to actual IP header size. 
-	 */
-	 
-	skb_put(skb2,len);
-
-	/*
-	 *	Build ICMP_ECHO Response message. 
-	 */
-	icmphr = (struct icmphdr *) (skb2->data + offset);
-	memcpy((char *) icmphr, (char *) icmph, len);
-
-	/* Are we copying the data from the ECHO datagram? */
-	/* We're supposed to, and it looks like we are. -- MS */
-	/* We're also supposed to truncate it if it would force */
-	/* illegal fragmentation. *sigh*  */
-
-	icmphr->type = ICMP_ECHOREPLY;
-	icmphr->code = 0;
-	icmphr->checksum = 0;
-	icmphr->checksum = ip_compute_csum((unsigned char *)icmphr, len);
-
-	/*
-	 *	Ship it out - free it when done 
-	 */
-	ip_queue_xmit((struct sock *)NULL, ndev, skb2, 1);
-
-	/*
-	 *	Free the received frame
-	 */
-	 
+static void icmp_echo(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 saddr, __u32 daddr, int len)
+{
+	struct icmp_bxm icmp_param;
+	icmp_param.icmph=*icmph;
+	icmp_param.icmph.type=ICMP_ECHOREPLY;
+	icmp_param.data_ptr=(icmph+1);
+	icmp_param.data_len=len;
+	icmp_build_xmit(&icmp_param, daddr, saddr);
 	kfree_skb(skb, FREE_READ);
 }
 
 /*
  *	Handle ICMP Timestamp requests. 
+ *	RFC 1122: 3.2.2.8 MAY implement ICMP timestamp requests.
+ *		  SHOULD be in the kernel for minimum random latency.
+ *		  MUST be accurate to a few minutes.
+ *		  MUST be updated at least at 15Hz.
  */
  
-static void icmp_timestamp(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev,
-	  unsigned long saddr, unsigned long daddr, int len,
-	  struct options *opt)
-{
-	struct icmphdr *icmphr;
-	struct sk_buff *skb2;
-	int size, offset;
-	unsigned long *timeptr, midtime;
-	struct device *ndev=NULL;
-
-        if (len < 12)
-	{
-		printk(
-		  "ICMP: Size (%d) of ICMP_TIMESTAMP request should be 20!\n",
-		  len);
-		icmp_statistics.IcmpInErrors++;		
-                /* correct answers are possible for everything >= 12 */
-	}
-
-	size = dev->hard_header_len + 84 + 15;
-
-	if (! (skb2 = alloc_skb(size, GFP_ATOMIC))) 
-	{
-		skb->sk = NULL;
-		kfree_skb(skb, FREE_READ);
-		icmp_statistics.IcmpOutErrors++;		
-		return;
-	}
-	skb2->free = 1;
- 
-/*
- *	Build Layer 2-3 headers for message back to source 
- */
- 
-	offset = ip_build_header(skb2, daddr, saddr, &ndev, IPPROTO_ICMP, opt, len, 
-				skb->ip_hdr->tos, 255);
-	if (offset < 0) 
+static void icmp_timestamp(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 saddr, __u32 daddr, int len)
+{
+	__u32 times[3];		/* So the new timestamp works on ALPHA's.. */
+	struct icmp_bxm icmp_param;
+	
+	/*
+	 *	Too short.
+	 */
+	 
+	if(len<12)
 	{
-		printk("ICMP: Could not build IP Header for ICMP TIMESTAMP Response\n");
-		kfree_skb(skb2, FREE_WRITE);
+		icmp_statistics.IcmpInErrors++;
 		kfree_skb(skb, FREE_READ);
-		icmp_statistics.IcmpOutErrors++;
 		return;
 	}
- 
-	/*
-	 *	Re-adjust length according to actual IP header size. 
-	 */
-	skb_put(skb2,20);
- 
-	/*
-	 *	Build ICMP_TIMESTAMP Response message. 
-	 */
-
-	icmphr = (struct icmphdr *) (skb2->data + offset);
-	memcpy((char *) icmphr, (char *) icmph, 12);
-	icmphr->type = ICMP_TIMESTAMPREPLY;
-	icmphr->code = icmphr->checksum = 0;
-
-	/* fill in the current time as ms since midnight UT: */
-	midtime = (xtime.tv_sec % 86400) * 1000 + xtime.tv_usec / 1000;
-	timeptr = (unsigned long *) (icmphr + 1);
-	/*
-	 *	the originate timestamp (timeptr [0]) is still in the copy: 
-	 */
-	timeptr [1] = timeptr [2] = htonl(midtime);
-
-	icmphr->checksum = ip_compute_csum((unsigned char *) icmphr, 20);
-
+	
 	/*
-	 *	Ship it out - free it when done 
+	 *	Fill in the current time as ms since midnight UT: 
 	 */
-
-	ip_queue_xmit((struct sock *) NULL, ndev, skb2, 1);
-	icmp_statistics.IcmpOutTimestampReps++;
-	kfree_skb(skb, FREE_READ);
+	 
+	times[1] = htonl((xtime.tv_sec % 86400) * 1000 + xtime.tv_usec / 1000);
+	times[2] = times[1];
+	memcpy((void *)&times[0], icmph+1, 4);		/* Incoming stamp */
+	icmp_param.icmph=*icmph;
+	icmp_param.icmph.type=ICMP_TIMESTAMPREPLY;
+	icmp_param.icmph.code=0;
+	icmp_param.data_ptr=&times;
+	icmp_param.data_len=12;
+	icmp_build_xmit(&icmp_param, daddr,saddr);
+	kfree_skb(skb,FREE_READ);
 }
- 
- 
 
 
-/*
- *	Handle the ICMP INFORMATION REQUEST. 
+/* 
+ *	Handle ICMP_ADDRESS_MASK requests.  (RFC950)
+ *
+ * RFC1122 (3.2.2.9).  A host MUST only send replies to 
+ * ADDRESS_MASK requests if it's been configured as an address mask 
+ * agent.  Receiving a request doesn't constitute implicit permission to 
+ * act as one. Of course, implementing this correctly requires (SHOULD) 
+ * a way to turn the functionality on and off.  Another one for sysctl(), 
+ * I guess. -- MS 
+ * Botched with a CONFIG option for now - Linus add scts sysctl please.. 
  */
  
-static void icmp_info(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev,
-	  unsigned long saddr, unsigned long daddr, int len,
-	  struct options *opt)
+static void icmp_address(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 saddr, __u32 daddr, int len)
 {
-	/* Obsolete */
-	kfree_skb(skb, FREE_READ);
+#ifdef CONFIG_IP_ADDR_AGENT
+	__u32 answer;
+	struct icmp_bxm icmp_param;
+	icmp_param.icmph.type=ICMP_ADDRESSREPLY;
+	icmp_param.icmph.code=0;
+	icmp_param.icmph.un.echo.id = icmph->un.echo.id;
+	icmp_param.icmph.un.echo.sequence = icmph->un.echo.sequence;
+	icmp_param.data_ptr=&dev->pa_mask;
+	icmp_param.data_len=4;
+	icmp_build_xmit(&icmp_param, daddr, saddr);
+#endif	
+	kfree_skb(skb, FREE_READ);	
 }
 
-
-/* 
- *	Handle ICMP_ADDRESS_MASK requests. 
- */
-
-/* RFC1122 (3.2.2.9).  A host MUST only send replies to */
-/* ADDRESS_MASK requests if it's been configured as an address mask */
-/* agent.  Receiving a request doesn't constitute implicit permission to */
-/* act as one. Of course, implementing this correctly requires (SHOULD) */
-/* a way to turn the functionality on and off.  Another one for sysctl(), */
-/* I guess. -- MS */
-/* Botched with a CONFIG option for now - Linus add scts sysctl please.. */
- 
-static void icmp_address(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev,
-	  unsigned long saddr, unsigned long daddr, int len,
-	  struct options *opt)
+static void icmp_discard(struct icmphdr *icmph, struct sk_buff *skb, struct device *dev, __u32 saddr, __u32 daddr, int len)
 {
-#ifdef CONFIG_IP_ADDR_AGENT
-	struct icmphdr *icmphr;
-	struct sk_buff *skb2;
-	int size, offset;
-	struct device *ndev=NULL;
-
-	icmp_statistics.IcmpOutMsgs++;
-	icmp_statistics.IcmpOutAddrMaskReps++;
-	
-	size = dev->hard_header_len + 64 + len + 15;
-	skb2 = alloc_skb(size, GFP_ATOMIC);
-	if (skb2 == NULL) 
-	{
-		icmp_statistics.IcmpOutErrors++;
-		kfree_skb(skb, FREE_READ);
-		return;
-  	}
-  	skb2->free = 1;
-	
-	/* 
-	 *	Build Layer 2-3 headers for message back to source 
-	 */
-
-	offset = ip_build_header(skb2, daddr, saddr, &ndev,
-		 	IPPROTO_ICMP, opt, len, skb->ip_hdr->tos,255);
-	if (offset < 0) 
-	{
-		icmp_statistics.IcmpOutErrors++;
-		printk("ICMP: Could not build IP Header for ICMP ADDRESS Response\n");
-		kfree_skb(skb2,FREE_WRITE);
-		kfree_skb(skb, FREE_READ);
-		return;
-	}
-
-	/*
-	 *	Re-adjust length according to actual IP header size. 
-	 */
-
-	skb_put(skb2,len);
-
-	/*
-	 *	Build ICMP ADDRESS MASK Response message. 
-	 */
-
-	icmphr = (struct icmphdr *) (skb2->data + offset);
-	icmphr->type = ICMP_ADDRESSREPLY;
-	icmphr->code = 0;
-	icmphr->checksum = 0;
-	icmphr->un.echo.id = icmph->un.echo.id;
-	icmphr->un.echo.sequence = icmph->un.echo.sequence;
-	memcpy((char *) (icmphr + 1), (char *) &dev->pa_mask, sizeof(dev->pa_mask));
-
-	icmphr->checksum = ip_compute_csum((unsigned char *)icmphr, len);
-
-	/* Ship it out - free it when done */
-	ip_queue_xmit((struct sock *)NULL, ndev, skb2, 1);
-#endif
-	skb->sk = NULL;
 	kfree_skb(skb, FREE_READ);
 }
 
-
 /* 
  *	Deal with incoming ICMP packets. 
  */
  
-int icmp_rcv(struct sk_buff *skb1, struct device *dev, struct options *opt,
-	 unsigned long daddr, unsigned short len,
-	 unsigned long saddr, int redo, struct inet_protocol *protocol)
+int icmp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
+	 __u32 daddr, unsigned short len,
+	 __u32 saddr, int redo, struct inet_protocol *protocol)
 {
-	struct icmphdr *icmph;
-	unsigned char *buff;
-
-	/*
-	 *	Drop broadcast packets. IP has done a broadcast check and ought one day
-	 *	to pass on that information.
-	 */
-	
+	struct icmphdr *icmph=(void *)skb->h.raw;
 	icmp_statistics.IcmpInMsgs++;
-	 
-  	
+	
   	/*
-  	 *	Grab the packet as an icmp object
+	 *	Validate the packet
   	 */
-
-	buff = skb1->h.raw;
-	icmph = (struct icmphdr *) buff;
-
-	/*
-	 *	Validate the packet first 
-	 */
-
+	
 	if (ip_compute_csum((unsigned char *) icmph, len)) 
 	{
 		/* Failed checksum! */
 		icmp_statistics.IcmpInErrors++;
 		printk("ICMP: failed checksum from %s!\n", in_ntoa(saddr));
-		kfree_skb(skb1, FREE_READ);
+		kfree_skb(skb, FREE_READ);
 		return(0);
 	}
-
+	
+	/*
+	 *	18 is the highest 'known' icmp type. Anything else is a mystery
+	 *
+	 *	RFC 1122: 3.2.2  Unknown ICMP messages types MUST be silently discarded.
+	 */
+	 
+	if(icmph->type > 18)
+	{
+		icmp_statistics.IcmpInErrors++;		/* Is this right - or do we ignore ? */
+		kfree_skb(skb,FREE_READ);
+		return(0);
+	}
+	
 	/*
 	 *	Parse the ICMP message 
 	 */
 
-	if (ip_chk_addr(daddr) != IS_MYADDR)
+	if (daddr!=dev->pa_addr && ip_chk_addr(daddr) == IS_BROADCAST)
 	{
+		/*
+		 *	RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be silently ignored (we don't as it is used
+		 *	by some network mapping tools).
+		 *	RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently discarded if to broadcast/multicast.
+		 */
 		if (icmph->type != ICMP_ECHO) 
-		/* RFC1122: We're allowed to reply to ICMP_TIMESTAMP */
-		/* requests in the same manner as ICMP_ECHO (optionally */
-		/* drop those to a bcast/mcast), so perhaps we should be */
-		/* consistent? -- MS */
-
 		{
 			icmp_statistics.IcmpInErrors++;
-			kfree_skb(skb1, FREE_READ);
+			kfree_skb(skb, FREE_READ);
 			return(0);
   		}
 		daddr=dev->pa_addr;
 	}
-
-	switch(icmph->type) 
-	{
-		case ICMP_TIME_EXCEEDED:
-			icmp_statistics.IcmpInTimeExcds++;
-			icmp_unreach(icmph, skb1);
-			return 0;
-		case ICMP_DEST_UNREACH:
-			icmp_statistics.IcmpInDestUnreachs++;
-			icmp_unreach(icmph, skb1);
-			return 0;
-		case ICMP_SOURCE_QUENCH:
-			icmp_statistics.IcmpInSrcQuenchs++;
-			icmp_unreach(icmph, skb1);
-			return(0);
-		case ICMP_REDIRECT:
-			icmp_statistics.IcmpInRedirects++;
-			icmp_redirect(icmph, skb1, dev, saddr);
-			return(0);
-		case ICMP_ECHO: 
-			icmp_statistics.IcmpInEchos++;
-			icmp_echo(icmph, skb1, dev, saddr, daddr, len, opt);
-			return 0;
-		case ICMP_ECHOREPLY:
-			icmp_statistics.IcmpInEchoReps++;
-			kfree_skb(skb1, FREE_READ);
-			return(0);
-		case ICMP_TIMESTAMP:
-			icmp_statistics.IcmpInTimestamps++;
-			icmp_timestamp(icmph, skb1, dev, saddr, daddr, len, opt);
-			return 0;
-		case ICMP_TIMESTAMPREPLY:
-		/* RFC1122: MUST pass TIMESTAMPREPLY messages up to app layer, */
-		/* just as with ECHOREPLY.  You have to use raw to get that */
-		/* functionality, just as with ECHOREPLY. Close enough. -- MS */
-			icmp_statistics.IcmpInTimestampReps++;
-			kfree_skb(skb1,FREE_READ);
-			return 0;
-		/* INFO is obsolete and doesn't even feature in the SNMP stats */
-		case ICMP_INFO_REQUEST:
-			icmp_info(icmph, skb1, dev, saddr, daddr, len, opt);
-			return 0;
-		case ICMP_INFO_REPLY:
-			skb1->sk = NULL;
-			kfree_skb(skb1, FREE_READ);
-			return(0);
-		case ICMP_ADDRESS:
-			icmp_statistics.IcmpInAddrMasks++;
-			icmp_address(icmph, skb1, dev, saddr, daddr, len, opt);
-			return 0;
-		case ICMP_ADDRESSREPLY:
-			/*
-			 *	We ought to set our netmask on receiving this, but 
-			 *	experience shows it's a waste of effort.
-			 */
-			icmp_statistics.IcmpInAddrMaskReps++;
-			kfree_skb(skb1, FREE_READ);
-			return(0);
-		default:
-			/* RFC1122: OK.  Silently discarding weird ICMP (MUST), */
-			/* as per 3.2.2. -- MS */
-			icmp_statistics.IcmpInErrors++;
-			kfree_skb(skb1, FREE_READ);
-			return(0);
- 	}
-  /*NOTREACHED*/
-	kfree_skb(skb1, FREE_READ);
-	return(-1);
+	
+	len-=sizeof(struct icmphdr);
+	(*icmp_pointers[icmph->type].input)++;
+	(icmp_pointers[icmph->type].handler)(icmph,skb,skb->dev,saddr,daddr,len);
+	return 0;
 }
 
-
 /*
- *	Perform any ICMP-related I/O control requests. 
- *	[to vanish soon]
+ *	This table is the definition of how we handle ICMP.
  */
  
-int icmp_ioctl(struct sock *sk, int cmd, unsigned long arg)
+static struct icmp_control icmp_pointers[19] = {
+/* ECHO REPLY (0) */
+ { &icmp_statistics.IcmpOutEchoReps, &icmp_statistics.IcmpInEchoReps, icmp_discard, 0 },
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1 },
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1 },
+/* DEST UNREACH (3) */
+ { &icmp_statistics.IcmpOutDestUnreachs, &icmp_statistics.IcmpInDestUnreachs, icmp_unreach, 1 },
+/* SOURCE QUENCH (4) */
+ { &icmp_statistics.IcmpOutSrcQuenchs, &icmp_statistics.IcmpInSrcQuenchs, icmp_unreach, 1 },
+/* REDIRECT (5) */
+ { &icmp_statistics.IcmpOutRedirects, &icmp_statistics.IcmpInRedirects, icmp_redirect, 1 },
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1 },
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1 },
+/* ECHO (8) */
+ { &icmp_statistics.IcmpOutEchos, &icmp_statistics.IcmpInEchos, icmp_echo, 0 },
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1 },
+ { &dummy, &icmp_statistics.IcmpInErrors, icmp_discard, 1 },
+/* TIME EXCEEDED (11) */
+ { &icmp_statistics.IcmpOutTimeExcds, &icmp_statistics.IcmpInTimeExcds, icmp_unreach, 1 },
+/* PARAMETER PROBLEM (12) */
+/* FIXME: RFC1122 3.2.2.5 - MUST pass PARAM_PROB messages to transport layer */
+ { &icmp_statistics.IcmpOutParmProbs, &icmp_statistics.IcmpInParmProbs, icmp_discard, 1 },
+/* TIMESTAMP (13) */
+ { &icmp_statistics.IcmpOutTimestamps, &icmp_statistics.IcmpInTimestamps, icmp_timestamp, 0 },
+/* TIMESTAMP REPLY (14) */
+ { &icmp_statistics.IcmpOutTimestampReps, &icmp_statistics.IcmpInTimestampReps, icmp_discard, 0 },
+/* INFO (15) */
+ { &dummy, &dummy, icmp_discard, 0 },
+/* INFO REPLY (16) */
+ { &dummy, &dummy, icmp_discard, 0 },
+/* ADDR MASK (17) */
+ { &icmp_statistics.IcmpOutAddrMasks, &icmp_statistics.IcmpInAddrMasks, icmp_address, 0 },
+/* ADDR MASK REPLY (18) */
+ { &icmp_statistics.IcmpOutAddrMaskReps, &icmp_statistics.IcmpInAddrMaskReps, icmp_discard, 0 }
+};
+
+void icmp_init(struct proto_ops *ops)
 {
-  	switch(cmd) 
-  	{
-		default:
-			return(-EINVAL);
-  	}
- 	return(0);
+	struct sock *sk;
+	icmp_socket.type=SOCK_RAW;
+	icmp_socket.ops=ops;
+	if(ops->create(&icmp_socket, IPPROTO_ICMP)<0)
+		panic("Failed to create the ICMP control socket.\n");
+	sk=icmp_socket.data;
+	sk->allocation=GFP_ATOMIC;
 }
+

FUNET's LINUX-ADM group, [email protected]
TCL-scripts by Sam Shen, [email protected] with Sam's (original) version
of this