blob: 0b3c5159177a620f5434cabb179e7cc98ecd8681 [file] [log] [blame]
/* Internet Control Message Protocol (ICMP) icmp.c */
/*
Copyright (C) 1992 Bob Harris
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, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The Author of tcpip package may be reached as bir7@leland.stanford.edu or
C/O Department of Mathematics; Stanford University; Stanford, CA 94305
The author of this file may be reached at rth@sparta.com or Sparta, Inc.
7926 Jones Branch Dr. Suite 900, McLean Va 22102.
*/
/* $Id: icmp.c,v 0.8.4.9 1993/01/23 18:00:11 bir7 Exp $ */
/* $Log: icmp.c,v $
* Revision 0.8.4.9 1993/01/23 18:00:11 bir7
* added volatile keyword to many variables.
*
* Revision 0.8.4.8 1993/01/22 23:21:38 bir7
* Merged with 99 pl4
*
* Revision 0.8.4.7 1992/12/12 19:25:04 bir7
* Cleaned up Log messages.
*
* Revision 0.8.4.6 1992/12/12 01:50:49 bir7
* Fixed bug in call to err routine.
*
* Revision 0.8.4.5 1992/12/05 21:35:53 bir7
* fixed type mismatch.
*
* Revision 0.8.4.4 1992/12/03 19:52:20 bir7
* Fixed minor pugs in icmp_reply.
*
* Revision 0.8.4.3 1992/11/18 15:38:03 bir7
* Fixed some printk's.
*
* Revision 0.8.4.2 1992/11/10 10:38:48 bir7
* Change free_s to kfree_s and accidently changed free_skb to kfree_skb.
*
* Revision 0.8.4.1 1992/11/10 00:17:18 bir7
* version change only.
*
* Revision 0.8.3.3 1992/11/10 00:14:47 bir7
* Changed malloc to kmalloc and added Id and Log
*
*/
/* modified by Ross Biro bir7@leland.stanford.edu to do more than just
echo responses. */
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* kfree_s */
#include <linux/fcntl.h>
#include <linux/socket.h>
#include <netinet/in.h>
#include "timer.h"
#include "ip.h"
#include "tcp.h"
#include "sock.h"
#include <linux/errno.h>
#include <linux/timer.h>
#include <asm/system.h>
#include <asm/segment.h>
#include "icmp.h"
#ifdef PRINTK
#undef PRINTK
#endif
#undef ICMP_DEBUG
#ifdef ICMP_DEBUG
#define PRINTK(x) printk x
#else
#define PRINTK(x) /**/
#endif
#define min(a,b) ((a)<(b)?(a):(b))
/* an array of errno for error messages from dest unreach. */
struct icmp_err icmp_err_convert[]=
{
{ENETUNREACH, 1},
{EHOSTUNREACH, 1},
{ENOPROTOOPT, 1},
{ECONNREFUSED, 1},
{EOPNOTSUPP, 0},
{EOPNOTSUPP, 0},
{ENETUNREACH, 1},
{EHOSTDOWN, 1},
{ENONET, 1},
{ENETUNREACH, 1},
{EHOSTUNREACH, 1},
{EOPNOTSUPP, 0},
{EOPNOTSUPP, 0}
};
void
print_icmph (struct icmp_header *icmph)
{
PRINTK ((" type = %d, code = %d, checksum = %X\n", icmph->type,
icmph->code, icmph->checksum));
PRINTK ((" gateway = %X\n", icmph->un.gateway));
}
/* sends an icmp message in response to a packet. */
void
icmp_reply (struct sk_buff *skb_in, int type, int code, struct device *dev)
{
struct sk_buff *skb;
struct ip_header *iph;
int offset;
struct icmp_header *icmph;
int len;
PRINTK (("icmp_reply (skb_in = %X, type = %d, code = %d, dev=%X)\n",
skb_in, type, code, dev));
/* get some memory for the reply. */
len = sizeof (*skb) + 8 /* amount of header to return. */ +
sizeof (struct icmp_header) +
64 /* enough for an ip header. */ +
dev->hard_header_len;
skb = kmalloc (len, GFP_ATOMIC);
if (skb == NULL) return;
skb->lock = 0;
skb->mem_addr = skb;
skb->mem_len = len;
len -= sizeof (*skb);
/* find the ip header. */
iph = (struct ip_header *)(skb_in+1);
iph = (struct ip_header *)((unsigned char *)iph + dev->hard_header_len);
/* Build Layer 2-3 headers for message back to source */
offset = ip_build_header( skb, iph->daddr, iph->saddr,
&dev, IPPROTO_ICMP, NULL, len );
if (offset < 0)
{
skb->sk = NULL;
kfree_skb (skb, FREE_READ);
return;
}
/* Readjust length according to actual IP header size */
skb->len = offset + sizeof (struct icmp_header) + 8;
icmph = (struct icmp_header *)((unsigned char *)(skb+1) + offset);
icmph->type = type;
icmph->code = code;
icmph->checksum = 0; /* we don't need to compute this. */
icmph->un.gateway = 0; /* might as well 0 it. */
memcpy (icmph+1, iph+1, 8);
/* send it and free it. */
ip_queue_xmit (NULL, dev, skb, 1);
}
/* deals 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 ip_protocol *protocol )
{
int size, offset;
struct icmp_header *icmph, *icmphr;
struct sk_buff *skb;
unsigned char *buff;
/* drop broadcast packets. */
if ((daddr & 0xff000000) == 0 || (daddr & 0xff000000) == 0xff000000)
{
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return (0);
}
buff = skb1->h.raw;
icmph = (struct icmp_header *)buff;
/* Validate the packet first */
if( icmph->checksum )
{ /* Checksums Enabled? */
if( ip_compute_csum( (unsigned char *)icmph, len ) )
{
/* Failed checksum! */
PRINTK(("ICMP ECHO failed checksum!\n"));
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return (0);
}
}
print_icmph(icmph);
/* Parse the ICMP message */
switch( icmph->type )
{
case ICMP_DEST_UNREACH:
case ICMP_SOURCE_QUENCH:
{
struct ip_header *iph;
struct ip_protocol *ipprot;
unsigned char hash;
int err;
err = icmph->type << 8 | icmph->code;
/* we need to cause the socket to be closed and the error message
to be set appropriately. */
iph = (struct ip_header *)(icmph+1);
/* get the protocol(s) */
hash = iph->protocol & (MAX_IP_PROTOS -1 );
/* this can change while we are doing it. */
for (ipprot = (struct ip_protocol *)ip_protos[hash];
ipprot != NULL; )
{
struct ip_protocol *nextip;
nextip = (struct ip_protocol *)ipprot->next;
/* pass it off to everyone who wants it. */
if (iph->protocol == ipprot->protocol && ipprot->err_handler)
ipprot->err_handler (err, (unsigned char *)(icmph+1),
iph->daddr, iph->saddr, ipprot);
ipprot = nextip;
}
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return (0);
}
case ICMP_REDIRECT:
{
/* we need to put a new route in the routing table. */
struct rtable *rt; /* we will add a new route. */
struct ip_header *iph;
iph = (struct ip_header *)(icmph+1);
rt = kmalloc (sizeof (*rt), GFP_ATOMIC);
if (rt != NULL)
{
rt->net = iph->daddr;
/* assume class C network. Technically this is incorrect,
but will give it a try. */
if ((icmph->code & 1) == 0) rt->net &= 0x00ffffff;
rt->dev = dev;
rt->router = icmph->un.gateway;
add_route (rt);
}
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return (0);
}
case ICMP_ECHO:
/* Allocate an sk_buff response buffer (assume 64 byte IP header) */
size = sizeof( struct sk_buff ) + dev->hard_header_len + 64 + len;
skb = kmalloc( size, GFP_ATOMIC );
if (skb == NULL)
{
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return (0);
}
skb->sk = NULL;
skb->lock = 0;
skb->mem_addr = skb;
skb->mem_len = size;
/* Build Layer 2-3 headers for message back to source */
offset = ip_build_header( skb, daddr, saddr, &dev, IPPROTO_ICMP, opt, len );
if (offset < 0)
{
/* Problems building header */
PRINTK(("Could not build IP Header for ICMP ECHO Response\n"));
kfree_s (skb->mem_addr, skb->mem_len);
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return( 0 ); /* just toss the received packet */
}
/* Readjust length according to actual IP header size */
skb->len = offset + len;
/* Build ICMP_ECHO Response message */
icmphr = (struct icmp_header *)( (char *)( skb + 1 ) + offset );
memcpy( (char *)icmphr, (char *)icmph, len );
icmphr->type = ICMP_ECHOREPLY;
icmphr->code = 0;
icmphr->checksum = 0;
if( icmph->checksum )
{ /* Calculate Checksum */
icmphr->checksum = ip_compute_csum( (void *)icmphr, len );
}
/* Ship it out - free it when done */
ip_queue_xmit( (volatile struct sock *)NULL, dev, skb, 1 );
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return( 0 );
default:
PRINTK(("Unsupported ICMP type = x%x\n", icmph->type ));
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return( 0 ); /* just toss the packet */
}
/* should be unecessary, but just in case. */
skb1->sk = NULL;
kfree_skb (skb1, FREE_READ);
return( 0 ); /* just toss the packet */
}