/* * 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, * Fred N. van Kempen, * Mark Evans, * Alan Cox, * Stefan Becker, * * 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 #include #include #include #include #include #include #include #include #include "snmp.h" #include "ip.h" #include "route.h" #include "protocol.h" #include "icmp.h" #include "tcp.h" #include "snmp.h" #include #include "sock.h" #include #include #include #include #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 || atype==IS_MULTICAST) 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); }