/* * scamper_icmp4.c * * $Id: scamper_icmp4.c,v 1.68 2007/05/14 04:15:08 mjl Exp $ * * Copyright (C) 2003-2007 The University of Waikato * * 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, version 2. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #if defined(__FreeBSD__) #include #endif #if defined(__sun__) #define _XPG4_2 #define __EXTENSIONS__ #define IP_HDR_HTONS #endif #include #include #include #include #if defined(__linux__) #define __FAVOR_BSD #include #define IP_HDR_HTONS #endif #if defined(__OpenBSD__) && OpenBSD >= 199706 #define IP_HDR_HTONS #endif #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__APPLE__) #include #endif #if defined(DMALLOC) #include #endif #include #include "scamper.h" #include "scamper_addr.h" #include "scamper_dl.h" #include "scamper_probe.h" #include "scamper_icmp_resp.h" #include "scamper_icmp4.h" #include "scamper_privsep.h" #include "scamper_debug.h" #include "utils.h" /* * if the [linux] system has SO_TIMESTAMP, then do not use SIOCGSTAMP, as * that requires an extra system call */ #if defined(SO_TIMESTAMP) #undef SIOCGSTAMP #endif static uint8_t *pktbuf = NULL; static size_t pktbuf_len = 0; static void ip4_build(scamper_probe_t *probe, uint8_t *buf) { struct ip *ip = (struct ip *)buf; ip->ip_v = 4; ip->ip_hl = 5; ip->ip_tos = probe->pr_ip_tos; ip->ip_len = htons(20 + 8 + probe->pr_len); ip->ip_id = htons(probe->pr_ip_id); ip->ip_off = htons(IP_DF); ip->ip_ttl = probe->pr_ip_ttl; ip->ip_p = IPPROTO_ICMP; ip->ip_sum = 0; memcpy(&ip->ip_src, probe->pr_ip_src->addr, sizeof(ip->ip_src)); memcpy(&ip->ip_dst, probe->pr_ip_dst->addr, sizeof(ip->ip_dst)); ip->ip_sum = in_cksum(ip, sizeof(struct ip)); return; } static void icmp4_build(scamper_probe_t *probe, uint8_t *buf) { struct icmp *icmp = (struct icmp *)buf; icmp->icmp_type = probe->pr_icmp_type; icmp->icmp_code = probe->pr_icmp_code; icmp->icmp_cksum = 0; icmp->icmp_id = htons(probe->pr_icmp_id); icmp->icmp_seq = htons(probe->pr_icmp_seq); /* if there is data to include in the payload, copy it in now */ if(probe->pr_len > 0) { memcpy(buf + 8, probe->pr_data, probe->pr_len); } icmp->icmp_cksum = in_cksum(icmp, (size_t)(probe->pr_len + 8)); return; } int scamper_icmp4_build(scamper_probe_t *probe, uint8_t *buf, size_t *len) { size_t req = 20 + 8 + probe->pr_len; if(req <= *len) { ip4_build(probe, buf); icmp4_build(probe, buf + 20); *len = req; return 0; } *len = req; return -1; } /* * scamper_icmp4_probe * * send an ICMP probe to a destination */ int scamper_icmp4_probe(scamper_probe_t *probe) { struct sockaddr_in sin4; char addr[128]; size_t len; int i, icmphdrlen; uint8_t *buf; #if !defined(IP_HDR_HTONS) struct ip *ip; #endif assert(probe != NULL); assert(probe->pr_ip_proto == IPPROTO_ICMP); assert(probe->pr_ip_dst != NULL); assert(probe->pr_ip_src != NULL); assert(probe->pr_len > 0 || probe->pr_data == NULL); switch(probe->pr_icmp_type) { case ICMP_ECHO: icmphdrlen = (1 + 1 + 2 + 2 + 2); break; default: probe->pr_errno = EINVAL; return -1; } len = sizeof(struct ip) + icmphdrlen + probe->pr_len; i = len; if(setsockopt(probe->pr_fd, SOL_SOCKET, SO_SNDBUF, (char *)&i, sizeof(i)) == -1) { printerror(errno, strerror, __func__, "could not set buffer to %d bytes", i); return -1; } if(pktbuf_len < len) { if((buf = realloc(pktbuf, len)) == NULL) { printerror(errno, strerror, __func__, "could not realloc"); return -1; } pktbuf = buf; pktbuf_len = len; } /* build the IPv4 header from the probe structure */ ip4_build(probe, pktbuf); /* byte swap the length and offset fields back to host-byte order if reqd */ #if !defined(IP_HDR_HTONS) ip = (struct ip *)pktbuf; ip->ip_len = ntohs(ip->ip_len); ip->ip_off = ntohs(ip->ip_off); #endif icmp4_build(probe, pktbuf + 20); sockaddr_compose((struct sockaddr *)&sin4, AF_INET, probe->pr_ip_dst->addr, 0); /* get the transmit time immediately before we send the packet */ gettimeofday_wrap(&probe->pr_tx); i = sendto(probe->pr_fd, pktbuf, len, 0, (struct sockaddr *)&sin4, sizeof(struct sockaddr_in)); if(i < 0) { /* error condition, could not send the packet at all */ probe->pr_errno = errno; printerror(probe->pr_errno, strerror, __func__, "could not send to %s (%d ttl, %d seq, %d len)", scamper_addr_tostr(probe->pr_ip_dst, addr, sizeof(addr)), probe->pr_ip_ttl, probe->pr_icmp_seq, len); return -1; } else if((size_t)i != len) { /* error condition, sent a portion of the probe */ fprintf(stderr, "scamper_icmp4_probe: sent %d bytes of %d byte packet to %s", i, (int)len, scamper_addr_tostr(probe->pr_ip_dst, addr, sizeof(addr))); return -1; } return 0; } /* * scamper_icmp4_icmp_ip_len * * this function returns the ip header's length field inside an icmp message * in a consistent fashion based on the system it is running on and the * type of the message. * * thanks to the use of an ICMP_FILTER or scamper's own type filtering, the * two ICMP types scamper has to deal with are ICMP_TIMXCEED and ICMP_UNREACH * * note that the filtering will filter any ICMP_TIMXCEED message with a code * other than ICMP_TIMXCEED_INTRANS, but we might as well deal with the whole * type. * * the pragmatic way is just to use pcap, which passes packets up in network * byte order consistently. */ static uint16_t scamper_icmp4_icmp_ip_len(const struct icmp *icmp) { uint16_t len; #if defined(__linux__) || defined(__OpenBSD__) || defined(__sun__) len = ntohs(icmp->icmp_ip.ip_len); #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(__DragonFly__) if(icmp->icmp_type == ICMP_TIMXCEED) { if(icmp->icmp_code <= 1) { len = icmp->icmp_ip.ip_len; } else { len = ntohs(icmp->icmp_ip.ip_len); } } else { switch(icmp->icmp_code) { case ICMP_UNREACH_NET: case ICMP_UNREACH_HOST: case ICMP_UNREACH_PROTOCOL: case ICMP_UNREACH_PORT: case ICMP_UNREACH_SRCFAIL: case ICMP_UNREACH_NEEDFRAG: case ICMP_UNREACH_NET_UNKNOWN: case ICMP_UNREACH_NET_PROHIB: case ICMP_UNREACH_TOSNET: case ICMP_UNREACH_HOST_UNKNOWN: case ICMP_UNREACH_ISOLATED: case ICMP_UNREACH_HOST_PROHIB: case ICMP_UNREACH_TOSHOST: # if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) case ICMP_UNREACH_HOST_PRECEDENCE: case ICMP_UNREACH_PRECEDENCE_CUTOFF: case ICMP_UNREACH_FILTER_PROHIB: # endif len = icmp->icmp_ip.ip_len; break; default: len = ntohs(icmp->icmp_ip.ip_len); } } #else len = icmp->icmp_ip.ip_len; #endif return len; } /* * scamper_icmp4_ip_len * * given the ip header encapsulating the icmp response, return the length * of the ip packet */ static uint16_t scamper_icmp4_ip_len(const struct ip *ip) { uint16_t len; #if defined(__linux__) || defined(__OpenBSD__) || defined(__sun__) len = ntohs(ip->ip_len); #else len = ip->ip_len + (ip->ip_hl << 2); #endif return len; } /* * icmp4_recv_ip_outer * * copy the outer-details of the ICMP message into the response structure. * get details of the time the packet was received. */ static void icmp4_recv_ip_outer(int fd, scamper_icmp_resp_t *resp, struct msghdr *msg, struct ip *ip, struct icmp *icmp) { /* * to start with, get a timestamp from the kernel if we can, otherwise * just get one from user-space. */ #if defined(SO_TIMESTAMP) struct cmsghdr *cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(msg); while(cmsg != NULL) { if(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) { timeval_cpy(&resp->ir_rx, (struct timeval *)CMSG_DATA(cmsg)); resp->ir_flags |= SCAMPER_ICMP_RESP_FLAG_KERNRX; break; } cmsg = (struct cmsghdr *)CMSG_NXTHDR(msg, cmsg); } #elif defined(SIOCGSTAMP) if(ioctl(fd, SIOCGSTAMP, &resp->ir_rx) != -1) { resp->ir_flags |= SCAMPER_ICMP_RESP_FLAG_KERNRX; } #else gettimeofday_wrap(&resp->ir_rx); #endif /* the response came from ... */ memcpy(&resp->ir_ip_src.v4, &ip->ip_src, sizeof(struct in_addr)); resp->ir_af = AF_INET; resp->ir_ip_ttl = ip->ip_ttl; resp->ir_ip_id = ntohs(ip->ip_id); resp->ir_ip_tos = ip->ip_tos; resp->ir_ip_size = scamper_icmp4_ip_len(ip); resp->ir_icmp_type = icmp->icmp_type; resp->ir_icmp_code = icmp->icmp_code; return; } int scamper_icmp4_recv(int fd, scamper_icmp_resp_t *resp) { struct sockaddr_in from; uint8_t pbuf[256]; ssize_t poffset; ssize_t pbuflen; uint8_t ctrlbuf[256]; struct icmp *icmp; struct ip *ip_outer, *ip_inner; struct udphdr *udp; struct tcphdr *tcp; uint8_t type, code; uint8_t nh; int iphdrlen; struct msghdr msg; struct iovec iov; memset(&iov, 0, sizeof(iov)); iov.iov_base = (caddr_t)pbuf; iov.iov_len = sizeof(pbuf); msg.msg_name = (caddr_t)&from; msg.msg_namelen = sizeof(from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = (caddr_t)ctrlbuf; msg.msg_controllen = sizeof(ctrlbuf); if((pbuflen = recvmsg(fd, &msg, 0)) == -1) { printerror(errno, strerror, __func__, "could not recvmsg"); return -1; } ip_outer = (struct ip *)pbuf; iphdrlen = ip_outer->ip_hl << 2; /* * an ICMP header has to be at least 8 bytes: * 1 byte type, 1 byte code, 2 bytes checksum, 4 bytes 'data' */ if(pbuflen < iphdrlen + 8) { scamper_debug(__func__, "pbuflen [%d] < iphdrlen [%d] + 8", pbuflen, iphdrlen); return -1; } icmp = (struct icmp *)(pbuf + iphdrlen); type = icmp->icmp_type; code = icmp->icmp_code; /* check to see if the ICMP type / code is what we want */ if((type != ICMP_TIMXCEED || code != ICMP_TIMXCEED_INTRANS) && type != ICMP_UNREACH && type != ICMP_ECHOREPLY) { scamper_debug(__func__, "type %d, code %d not wanted", type, code); return -1; } memset(resp, 0, sizeof(scamper_icmp_resp_t)); /* * if we get an ICMP echo reply, there is no 'inner' IP packet as there * was no error condition. * so get the outer packet's details and be done */ if(type == ICMP_ECHOREPLY) { resp->ir_icmp_id = ntohs(icmp->icmp_id); resp->ir_icmp_seq = ntohs(icmp->icmp_seq); memcpy(&resp->ir_inner_ip_dst.v4, &ip_outer->ip_src, sizeof(struct in_addr)); icmp4_recv_ip_outer(fd, resp, &msg, ip_outer, icmp); return 0; } ip_inner = &icmp->icmp_ip; poffset = iphdrlen + ICMP_MINLEN + (ip_inner->ip_hl << 2); nh = ip_inner->ip_p; /* search for an ICMP or a UDP header in this packet */ while(poffset + (ssize_t)sizeof(struct udphdr) <= pbuflen) { /* if we can't deal with the inner header, then stop now */ if(nh != IPPROTO_UDP && nh != IPPROTO_ICMP && nh != IPPROTO_TCP) { scamper_debug(__func__, "unhandled next header %d", nh); return -1; } resp->ir_flags |= SCAMPER_ICMP_RESP_FLAG_INNER_IP; /* record details of the IP header and the ICMP headers */ icmp4_recv_ip_outer(fd, resp, &msg, ip_outer, icmp); /* record details of the IP header found in the ICMP error message */ memcpy(&resp->ir_inner_ip_dst.v4, &ip_inner->ip_dst, sizeof(struct in_addr)); resp->ir_inner_ip_proto = nh; resp->ir_inner_ip_ttl = ip_inner->ip_ttl; resp->ir_inner_ip_id = ntohs(ip_inner->ip_id); resp->ir_inner_ip_tos = ip_inner->ip_tos; resp->ir_inner_ip_size = scamper_icmp4_icmp_ip_len(icmp); if(type == ICMP_UNREACH && code == ICMP_UNREACH_NEEDFRAG) { resp->ir_icmp_nhmtu = ntohs(icmp->icmp_nextmtu); } if(nh == IPPROTO_UDP) { udp = (struct udphdr *)(pbuf+poffset); resp->ir_inner_udp_sport = ntohs(udp->uh_sport); resp->ir_inner_udp_dport = ntohs(udp->uh_dport); resp->ir_inner_udp_sum = udp->uh_sum; } else if(nh == IPPROTO_ICMP) { icmp = (struct icmp *)(pbuf+poffset); resp->ir_inner_icmp_type = icmp->icmp_type; resp->ir_inner_icmp_code = icmp->icmp_code; resp->ir_inner_icmp_id = ntohs(icmp->icmp_id); resp->ir_inner_icmp_seq = ntohs(icmp->icmp_seq); } else if(nh == IPPROTO_TCP) { tcp = (struct tcphdr *)(pbuf+poffset); resp->ir_inner_tcp_sport = ntohs(tcp->th_sport); resp->ir_inner_tcp_dport = ntohs(tcp->th_dport); resp->ir_inner_tcp_seq = ntohl(tcp->th_seq); } scamper_icmp_resp_print(resp); return 0; } scamper_debug(__func__, "packet not ours"); return -1; } void scamper_icmp4_read_cb(const int fd, void *param) { scamper_icmp_resp_t resp; if(scamper_icmp4_recv(fd, &resp) == 0) { scamper_icmp_resp_handle(&resp); } return; } void scamper_icmp4_cleanup() { if(pktbuf != NULL) { free(pktbuf); pktbuf = NULL; } return; } int scamper_icmp4_open() { int fd = -1; int opt; #if defined(ICMP_FILTER) struct icmp_filter filter; #endif #if defined(WITHOUT_PRIVSEP) if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) #else if((fd = scamper_privsep_open_rawsock(AF_INET, IPPROTO_ICMP)) == -1) #endif { printerror(errno, strerror, __func__, "could not open ICMP socket"); goto err; } opt = 1; if(setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt)) == -1) { printerror(errno, strerror, __func__, "could not set IP_HDRINCL"); goto err; } opt = 65535 + 128; if(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) == -1) { printerror(errno, strerror, __func__, "could not set SO_RCVBUF"); goto err; } #if defined(SO_TIMESTAMP) opt = 1; if(setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) == -1) { printerror(errno, strerror, __func__, "could not set SO_TIMESTAMP"); goto err; } #endif /* * on linux systems with ICMP_FILTER defined, filter all messages except * destination unreachable and time exceeded messages */ #if defined(ICMP_FILTER) filter.data = ~((1 << ICMP_DEST_UNREACH) | (1 << ICMP_TIME_EXCEEDED) | (1 << ICMP_ECHOREPLY) ); if(setsockopt(fd, SOL_RAW, ICMP_FILTER, &filter, sizeof(filter)) == -1) { printerror(errno, strerror, __func__, "could not set ICMP_FILTER"); goto err; } #endif return fd; err: if(fd != -1) close(fd); return -1; }