/*
* 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 <sys/param.h>
#endif
#if defined(__sun__)
#define _XPG4_2
#define __EXTENSIONS__
#define IP_HDR_HTONS
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/uio.h>
#if defined(__linux__)
#define __FAVOR_BSD
#include <sys/ioctl.h>
#define IP_HDR_HTONS
#endif
#if defined(__OpenBSD__) && OpenBSD >= 199706
#define IP_HDR_HTONS
#endif
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(__APPLE__)
#include <stdint.h>
#endif
#if defined(DMALLOC)
#include <dmalloc.h>
#endif
#include <assert.h>
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1