#include <stdarg.h> /* va_list, va_start(), va_end(), vprintf() */
#include <stdlib.h> /* exit() */
#include <stdint.h> /* uint8_t, uint16_t, uint32_t */
#include <stdio.h> /* putc(), perror(), printf() */
#include <time.h> /* clock_gettime(), clock_nanosleep() */
#include <errno.h> /* errno */
#include <sys/time.h> /* struct timeval */
#include <sys/types.h> /* socket(), setsockopt(),
* getaddrinfo(), getnameinfo()
*/
#include <sys/socket.h> /* socket(), setsockopt(),
* getaddrinfo(), getnameinfo()
*/
#include <netdb.h> /* getaddrinfo(), getnameinfo() */
#include <unistd.h> /* close() */
#include <netinet/in.h> /* struct sockaddr_in */
#include <netinet/ip.h> /* struct iphdr */
#include <netinet/ip_icmp.h> /* struct icmphdr */
#include <arpa/inet.h> /* inet_ntop() */
/* that this is possible in C99 is fucking insane. */
#define tmp_buffer(type, len) ((type *)&(struct { char b[len]; }){ .b = { 0 } })
#define tmp_var(type, value) (*(type *)&(struct { type v; }){ .v = value })
__attribute__((noreturn))
void die (char const errlvl, char const *const fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
putc('\n', stderr);
va_end(args);
exit(errlvl);
}
__attribute__((noreturn))
void fail (char const errlvl, char const *const fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
putc(':', stderr);
putc(' ', stderr);
perror(NULL);
exit(errlvl);
}
__attribute__((noreturn))
void usage (char const *const myself) {
printf("Usage: %s <address>\n", myself);
exit(-1);
}
struct packet {
struct iphdr ip; /* IP header (TTL & source address) */
struct icmphdr icmp; /* ICMP header (id and sequence */
} __attribute__((packed));
/* Although not strictly required by spec, this function requries the
* input buffer to be padded to word alignment. Data-receiving code must
* account for the odd byte, whereas we only *send* word-aligned buffers.
*
* This allows us to not bother looking for the occasional trailing byte.
*/
uint16_t ip_checksum (uint16_t const *const buf, size_t len) {
/* account for checksum overflow */
uint32_t sum = 0;
len /= 2;
while (len--) sum += buf[len];
/* fold sum into uint16_t */
sum = (sum & 0xFFFF) + (sum >> 16);
sum += sum >> 16;
return ~sum;
}
/* returns zero on full buffer read */
__attribute__((always_inline)) inline
int fill_buffer (char const *const path, void *const buffer, size_t const bufsize) {
FILE *file;
int ret = 0;
if (NULL == (file = fopen(path, "r"))) return 1;
if (1 != fread(buffer, bufsize, 1, file)) ret = 2;
fclose(file);
return ret;
}
/* like BSD sys/time.h timersub()
* except for timespec (nanosecond resolution)
* instead of timeval (weak-ass millisecond resolution)
*/
void timersub_ns (
struct timespec const *const a,
struct timespec const *const b,
struct timespec *const res
) {
res->tv_sec = a->tv_sec - b->tv_sec;
if (a->tv_nsec < b->tv_nsec) {
res->tv_sec--;
res->tv_nsec = 1000000000 - b->tv_nsec + a->tv_nsec;
} else
res->tv_nsec = a->tv_nsec - b->tv_nsec;
}
/* sends TCP SYN to get local interface address
* drops TCP ACK on the floor.
*/
uint32_t get_source_address(const struct sockaddr_in *const target) {
int sock;
struct sockaddr_in source;
socklen_t length = sizeof(struct sockaddr);
/* SOCK_NONBLOCK so we don't have to care if the remote host doesn't
* respond -- we just don't bother waiting.
*/
if (-1 == (sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK,
IPPROTO_TCP))) fail(2, "can't open normal socket");
connect(sock, (struct sockaddr *)target, length);
errno = 0; /* clear EINPROGRESS */
getsockname(sock, (struct sockaddr *)&source, &length);
close(sock);
return source.sin_addr.s_addr;
}
/* turn a hostname/IP into a uint32_t representation
* of the IPv4 address it corresponds to
* FIXME: consider not die()-ing on failure
* NOTE: gethostbyname is deprecated
*/
uint32_t resolve (char *host) {
struct addrinfo *res, hints = (struct addrinfo){ .ai_family = AF_INET };
int err;
if ((err = getaddrinfo(host, NULL, &hints, &res)))
die(1, "failed to resolve %s: %s", host, gai_strerror(err));
return ((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr;
}
char *reverse (uint32_t ip) {
static char host[NI_MAXHOST];
int err;
if ((err = getnameinfo((struct sockaddr *)&(struct sockaddr_in){
.sin_family = AF_INET,
.sin_port = 1,
.sin_addr.s_addr = ip
}, sizeof(struct sockaddr), host, sizeof(host), NULL, 0, 0)))
die(1, "failed to reverse IP: %s", gai_strerror(err));
return host;
}
int main (int argc, char **argv) {
/* make sure we have something to resolve */
if (2 > argc) usage(*argv);
/* resolve address before we do anything else */
struct sockaddr_in target = (struct sockaddr_in){
.sin_family = AF_INET,
.sin_addr.s_addr = resolve(argv[1])
};
/* build packet */
struct packet ping = (struct packet){
.ip = (struct iphdr){
.ihl = 5,
.version = 4,
.tot_len = sizeof(ping),
.ttl = htons(-1),
.frag_off = htons(IP_DF),
.protocol = IPPROTO_ICMP,
.saddr = get_source_address(&target),
.daddr = target.sin_addr.s_addr
},
.icmp = (struct icmphdr){ .type = ICMP_ECHO }
};
/* checksum IP header */
ping.ip.check = ip_checksum((uint16_t *)&ping.ip, sizeof(ping.ip));
/* set ICMP_ECHO ID */
/* there is no secure source of entropy on linux. no, really. */
while (fill_buffer(
"/dev/urandom", &ping.icmp.un.echo.id, sizeof(ping.icmp.un.echo.id)
)) { perror("failed to read /dev/urandom"); }
/* open socket */
int sock;
if (-1 == (sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)))
fail(3, "can't get raw socket");
/* we'll be including our own IP header, for custom TTL */
setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
&tmp_var(int, 1), sizeof(int));
/* set the timeout */
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
&(struct timeval){ .tv_sec = 1 }, sizeof(struct timeval));
struct timespec begin;
clock_gettime(CLOCK_MONOTONIC, &begin);
for (uint16_t seq = 0; ; seq++) {
struct timespec then, now, delay;
struct packet pong;
struct sockaddr_in replyaddr;
ssize_t len;
/* create next packet in sequence */
ping.icmp.un.echo.sequence = htons(seq);
ping.icmp.checksum = 0;
ping.icmp.checksum = ip_checksum((uint16_t *)&ping.icmp,
sizeof(ping.icmp));
/* send it */
sendto(sock, &ping, sizeof(ping), 0,
(struct sockaddr *)&target, sizeof(struct sockaddr));
clock_gettime(CLOCK_MONOTONIC_RAW, &then);
/* listen for reply */
do {
len = recvfrom(sock, &pong, sizeof(pong), 0,
(struct sockaddr *)&replyaddr,
&tmp_var(socklen_t, sizeof(struct sockaddr)));
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
} while (0 < len && (ICMP_ECHOREPLY != pong.icmp.type ||
ping.icmp.un.echo.id != pong.icmp.un.echo.id));
if (0 > len)
fail(4, "recvfrom");
len -= 20; /* iputils only reports ICMP payload length. */
timersub_ns(&now, &then, &delay);
if (len > 0)
printf("%zd bytes from %s (%s): icmp_seq=%d ttl=%d time=%f ms\n",
len, reverse(pong.ip.saddr),
inet_ntop(AF_INET, &pong.ip.saddr,
tmp_buffer(char, INET_ADDRSTRLEN), INET_ADDRSTRLEN),
htons(pong.icmp.un.echo.sequence),
pong.ip.ttl, 1000 * delay.tv_sec
+ (float)delay.tv_nsec / 1000000);
else printf("request %d timed out pinging %s.\n",
htons(ping.icmp.un.echo.sequence), argv[1]);
begin.tv_sec++;
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &begin, NULL));
}
/* all done */
return 0;
}