diff options
author | Michael I. Bushnell <mib@gnu.org> | 1995-07-12 15:42:50 +0000 |
---|---|---|
committer | Michael I. Bushnell <mib@gnu.org> | 1995-07-12 15:42:50 +0000 |
commit | a81cd86c8d93236ffccfbee44b5818ba21523463 (patch) | |
tree | f098368d79e9ed9b39907730c28ed3182b69519d /pfinet/linux-inet/icmp.c | |
parent | c7923f6aa252a29ccb4f16bd91469c9000a2bd94 (diff) |
entered into RCS
Diffstat (limited to 'pfinet/linux-inet/icmp.c')
-rw-r--r-- | pfinet/linux-inet/icmp.c | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/pfinet/linux-inet/icmp.c b/pfinet/linux-inet/icmp.c new file mode 100644 index 00000000..c023eab2 --- /dev/null +++ b/pfinet/linux-inet/icmp.c @@ -0,0 +1,774 @@ +/* + * 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. + * + * Internet Control Message Protocol (ICMP) + * + * Version: @(#)icmp.c 1.0.11 06/02/93 + * + * Authors: Ross Biro, <bir7@leland.Stanford.Edu> + * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> + * Mark Evans, <evansmp@uhura.aston.ac.uk> + * Alan Cox, <gw4pts@gw4pts.ampr.org> + * Stefan Becker, <stefanb@yello.ping.de> + * + * 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(). + * + * + * + * 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. + */ +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/fcntl.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/string.h> +#include "snmp.h" +#include "ip.h" +#include "route.h" +#include "protocol.h" +#include "icmp.h" +#include "tcp.h" +#include "snmp.h" +#include <linux/skbuff.h> +#include "sock.h" +#include <linux/errno.h> +#include <linux/timer.h> +#include <asm/system.h> +#include <asm/segment.h> + + +#define min(a,b) ((a)<(b)?(a):(b)) + + +/* + * Statistics + */ + +struct icmp_mib icmp_statistics={0,}; + + +/* An array of errno for error messages from dest unreach. */ +struct icmp_err icmp_err_convert[] = { + { ENETUNREACH, 0 }, /* ICMP_NET_UNREACH */ + { EHOSTUNREACH, 0 }, /* ICMP_HOST_UNREACH */ + { ENOPROTOOPT, 1 }, /* ICMP_PROT_UNREACH */ + { ECONNREFUSED, 1 }, /* ICMP_PORT_UNREACH */ + { EOPNOTSUPP, 0 }, /* ICMP_FRAG_NEEDED */ + { EOPNOTSUPP, 0 }, /* ICMP_SR_FAILED */ + { ENETUNREACH, 1 }, /* ICMP_NET_UNKNOWN */ + { EHOSTDOWN, 1 }, /* ICMP_HOST_UNKNOWN */ + { ENONET, 1 }, /* ICMP_HOST_ISOLATED */ + { ENETUNREACH, 1 }, /* ICMP_NET_ANO */ + { EHOSTUNREACH, 1 }, /* ICMP_HOST_ANO */ + { EOPNOTSUPP, 0 }, /* ICMP_NET_UNR_TOS */ + { EOPNOTSUPP, 0 } /* ICMP_HOST_UNR_TOS */ +}; + + +/* + * Send an ICMP message in response to a situation + * + * Fixme: Fragment handling is wrong really. + */ + +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; + + /* + * Find the original IP header. + */ + + iph = (struct iphdr *) (skb_in->data + dev->hard_header_len); + + /* + * No replies to MAC multicast + */ + + if(skb_in->pkt_type!=PACKET_HOST) + return; + + /* + * No replies to IP multicasting + */ + + atype=ip_chk_addr(iph->daddr); + if(atype==IS_BROADCAST || IN_MULTICAST(iph->daddr)) + return; + + /* + * Only reply to first fragment. + */ + + if(ntohs(iph->frag_off)&IP_OFFSET) + return; + + /* + * We must NEVER NEVER send an ICMP error to an ICMP error message + */ + + if(type==ICMP_DEST_UNREACH||type==ICMP_REDIRECT||type==ICMP_SOURCE_QUENCH||type==ICMP_TIME_EXCEEDED) + { + + /* + * Is the original packet an ICMP packet? + */ + + if(iph->protocol==IPPROTO_ICMP) + { + icmph = (struct icmphdr *) ((char *) iph + + 4 * iph->ihl); + /* + * Check for ICMP error packets (Must never reply to + * an ICMP error). + */ + + if (icmph->type == ICMP_DEST_UNREACH || + icmph->type == ICMP_SOURCE_QUENCH || + icmph->type == ICMP_REDIRECT || + icmph->type == ICMP_TIME_EXCEEDED || + icmph->type == ICMP_PARAMETERPROB) + 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: + 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. + */ + + 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, 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; + 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->len = offset + sizeof(struct icmphdr) + sizeof(struct iphdr) + 8; + + /* + * Fill in the frame + */ + + 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 */ + 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); +} + + +/* + * Handle ICMP_UNREACH and ICMP_QUENCH. + */ + +static void icmp_unreach(struct icmphdr *icmph, struct sk_buff *skb) +{ + struct inet_protocol *ipprot; + struct iphdr *iph; + unsigned char hash; + int err; + + err = (icmph->type << 8) | icmph->code; + iph = (struct iphdr *) (icmph + 1); + + switch(icmph->code & 7) + { + 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)); + break; + case ICMP_SR_FAILED: + printk("ICMP: %s: Source Route Failed.\n", in_ntoa(iph->daddr)); + break; + default: + break; + } + + /* + * Get the protocol(s). + */ + + hash = iph->protocol & (MAX_INET_PROTOS -1); + + /* + * This can't change while we are doing it. + */ + + ipprot = (struct inet_protocol *) inet_protos[hash]; + while(ipprot != NULL) + { + struct inet_protocol *nextip; + + nextip = (struct inet_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; + } + kfree_skb(skb, FREE_READ); +} + + +/* + * Handle ICMP_REDIRECT. + */ + +static void icmp_redirect(struct icmphdr *icmph, struct sk_buff *skb, + struct device *dev, unsigned long source) +{ + struct rtable *rt; + struct iphdr *iph; + unsigned long ip; + + /* + * Get the copied header of the packet that caused the redirect + */ + + iph = (struct iphdr *) (icmph + 1); + ip = iph->daddr; + + switch(icmph->code & 7) + { + case ICMP_REDIR_NET: + /* + * This causes a problem with subnetted networks. What we should do + * is use ICMP_ADDRESS to get the subnet mask of the problem route + * and set both. But we don't.. + */ +#ifdef not_a_good_idea + ip_rt_add((RTF_DYNAMIC | RTF_MODIFIED | RTF_GATEWAY), + ip, 0, icmph->un.gateway, dev,0, 0); + break; +#endif + case ICMP_REDIR_HOST: + /* + * Add better route to host. + * But first check that the redirect + * comes from the old gateway.. + * And make sure it's an ok host address + * (not some confused thing sending our + * address) + */ + rt = ip_rt_route(ip, NULL, NULL); + if (!rt) + break; + if (rt->rt_gateway != source || ip_chk_addr(icmph->un.gateway)) + break; + printk("redirect from %s\n", in_ntoa(source)); + ip_rt_add((RTF_DYNAMIC | RTF_MODIFIED | RTF_HOST | RTF_GATEWAY), + ip, 0, icmph->un.gateway, dev,0, 0); + break; + case ICMP_REDIR_NETTOS: + case ICMP_REDIR_HOSTTOS: + printk("ICMP: cannot handle TOS redirects yet!\n"); + break; + default: + break; + } + + /* + * Discard the original packet + */ + + kfree_skb(skb, FREE_READ); +} + + +/* + * Handle ICMP_ECHO ("ping") requests. + */ + +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; + 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. + */ + + skb2->len = offset + len; + + /* + * Build ICMP_ECHO Response message. + */ + icmphr = (struct icmphdr *) (skb2->data + offset); + memcpy((char *) icmphr, (char *) icmph, len); + 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 + */ + + kfree_skb(skb, FREE_READ); +} + +/* + * Handle ICMP Timestamp requests. + */ + +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 != 20) + { + printk( + "ICMP: Size (%d) of ICMP_TIMESTAMP request should be 20!\n", + len); + icmp_statistics.IcmpInErrors++; +#if 1 + /* correct answers are possible for everything >= 12 */ + if (len < 12) +#endif + return; + } + + size = dev->hard_header_len + 84; + + 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) + { + printk("ICMP: Could not build IP Header for ICMP TIMESTAMP Response\n"); + kfree_skb(skb2, FREE_WRITE); + kfree_skb(skb, FREE_READ); + icmp_statistics.IcmpOutErrors++; + return; + } + + /* + * Re-adjust length according to actual IP header size. + */ + skb2->len = offset + 20; + + /* + * Build ICMP_TIMESTAMP Response message. + */ + + icmphr = (struct icmphdr *) ((char *) (skb2 + 1) + 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 + */ + + ip_queue_xmit((struct sock *) NULL, ndev, skb2, 1); + icmp_statistics.IcmpOutTimestampReps++; + kfree_skb(skb, FREE_READ); +} + + + + +/* + * Handle the ICMP INFORMATION REQUEST. + */ + +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) +{ + /* Obsolete */ + kfree_skb(skb, FREE_READ); +} + + +/* + * Handle ICMP_ADDRESS_MASK requests. + */ + +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) +{ + 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; + 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. + */ + + skb2->len = offset + 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); + + 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) +{ + struct icmphdr *icmph; + unsigned char *buff; + + /* + * Drop broadcast packets. IP has done a broadcast check and ought one day + * to pass on that information. + */ + + icmp_statistics.IcmpInMsgs++; + + + /* + * Grab the packet as an icmp object + */ + + 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); + return(0); + } + + /* + * Parse the ICMP message + */ + + if (ip_chk_addr(daddr) != IS_MYADDR) + { + if (icmph->type != ICMP_ECHO) + { + icmp_statistics.IcmpInErrors++; + kfree_skb(skb1, 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: + 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: + icmp_statistics.IcmpInErrors++; + kfree_skb(skb1, FREE_READ); + return(0); + } + /*NOTREACHED*/ + kfree_skb(skb1, FREE_READ); + return(-1); +} + + +/* + * Perform any ICMP-related I/O control requests. + * [to vanish soon] + */ + +int icmp_ioctl(struct sock *sk, int cmd, unsigned long arg) +{ + switch(cmd) + { + default: + return(-EINVAL); + } + return(0); +} |