diff --git a/exercises/ex04/trout/.#util.c b/exercises/ex04/trout/.#util.c new file mode 100644 index 00000000..c922e404 --- /dev/null +++ b/exercises/ex04/trout/.#util.c @@ -0,0 +1 @@ +downey@rocky.colby.edu.25374:1 diff --git a/exercises/ex04/trout/COPYRIGHT b/exercises/ex04/trout/COPYRIGHT new file mode 100644 index 00000000..ec58690f --- /dev/null +++ b/exercises/ex04/trout/COPYRIGHT @@ -0,0 +1,47 @@ +TROUT is a simple version of traceroute. + +It is based on code written by W. Richard Stevens and published in +"UNIX Network Programming Volume 1, Second Edition Networking APIs: +Sockets and XTI," Prentice Hall PTR, Upper Saddle River, NJ 07458. + +It has been modified a fair amount by Allen Downey +(downey@colby.edu). Among other things, I did the following + +1) I collected all the procedures from Stevens's extensive library + and put them in util.c, and put their prototypes in trout.h + +2) I killed all the IPv6 support, because I didn't need it and + because I hate ifdefs and pointers to functions. + +3) For the same reason, I took out some of the configuration + ifdefs. As a result, I don't know if this will still run + on anything other than Linux. + +4) I fixed a race condition that may or may not have been the + cause of some early problems I had. Anyway, the fix also + came from Stevens, in Section 18.5 of Unix Network Programming. + +5) I split things up into more procedures. + +The copyright for the original code is held by Prentice Hall, +but they make it available under a license that is more or +less identical to the GNU GPL. + +This version is Copyright (C) 1999 Allen B. Downey. + +I am making it available under the GNU General Public License +(which does not, I think, violate the original copyright). + +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. + +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. diff --git a/exercises/ex04/trout/Makefile b/exercises/ex04/trout/Makefile new file mode 100644 index 00000000..0fcbc669 --- /dev/null +++ b/exercises/ex04/trout/Makefile @@ -0,0 +1,2 @@ +trout: trout.h trout.c util.h util.c main.c + gcc -Wall -g -o trout trout.c main.c util.c diff --git a/exercises/ex04/trout/main.c b/exercises/ex04/trout/main.c new file mode 100644 index 00000000..b9551102 --- /dev/null +++ b/exercises/ex04/trout/main.c @@ -0,0 +1,45 @@ +#include "trout.h" + +int main (int argc, char **argv) +{ + int c; + struct addrinfo *ai; + char *host; + + opterr = 0; + while ( (c = getopt (argc, argv, "m:")) != -1) { + switch (c) { + case 'm': + if ( (max_ttl = atoi(optarg)) <= 1) { + err_quit ("invalid -m value"); + } + break; + default: + err_quit ("unrecognizd option: %c", c); + } + } + + if (optind != argc - 1) { + err_quit ("usage: trout [ -m ] "); + } + host = argv[optind]; + ai = Host_serv (host, NULL, 0, 0); + + printf ("trout to %s (%s): %d hops max, %d data bytes\n", + ai->ai_canonname, + Sock_ntop_host (ai->ai_addr, ai->ai_addrlen), + max_ttl, datalen); + + if (ai->ai_family != AF_INET) { + err_quit ("unknown address family %d", ai->ai_family); + } + + sasend = ai->ai_addr; + salen = ai->ai_addrlen; + sarecv = Calloc (1, salen); + salast = Calloc (1, salen); + sabind = Calloc (1, salen); + + loop_ttl (); + exit (0); +} diff --git a/exercises/ex04/trout/trout (1) b/exercises/ex04/trout/trout (1) new file mode 100644 index 00000000..f7f2b277 Binary files /dev/null and b/exercises/ex04/trout/trout (1) differ diff --git a/exercises/ex04/trout/trout.c b/exercises/ex04/trout/trout.c new file mode 100644 index 00000000..0cf8f328 --- /dev/null +++ b/exercises/ex04/trout/trout.c @@ -0,0 +1,261 @@ +#include "trout.h" + +/* variables we might want to configure */ +Rec *rec = (Rec *) sendbuf; + +void sig_alrm (int signo) +{ + Write (pipefd[1], "", 1); /* write 1 null byte to pipe */ + return; +} + +int process_ip (struct ip *ip, int len) +{ + int hlen1, hlen2, icmplen; + struct icmp *icmp; + struct ip *hip; + struct udphdr *udp; + seq = 0; + u_short dport = 32768 + 668; /* destination port -- hopefully unused */ + + hlen1 = ip->ip_hl << 2; /* length of IP header */ + icmp = (struct icmp *) (recvbuf + hlen1); + icmplen = len - hlen1; + + if (icmplen < 8 + 20 + 8) return 0; + + if (icmp->icmp_type != ICMP_TIME_EXCEEDED && + icmp->icmp_type != ICMP_DEST_UNREACH) + return 0; + + /* hip is the header of the enclosed IP packets, supposedly + the header of the packet that caused the error */ + + hip = (struct ip *) (recvbuf + hlen1 + 8); + if (hip->ip_p != IPPROTO_UDP) return 0; + + hlen2 = hip->ip_hl << 2; + udp = (struct udphdr *) (recvbuf + hlen1 + 8 + hlen2); + + if (udp->source != htons (sport)) return 0; + if (udp->dest != htons (dport + seq)) return 0; + + if (icmp->icmp_type == ICMP_TIME_EXCEEDED) { + if (icmp->icmp_code == ICMP_EXC_TTL) { + return -2; + } else { + return 0; + } + } + + if (icmp->icmp_type == ICMP_DEST_UNREACH) { + if (icmp->icmp_code == ICMP_PORT_UNREACH) { + return -1; + } else { + return 0; + } + } + return 0; +} + + + +int recv_dgram () +{ + int err; + socklen_t len; + ssize_t n; + struct ip *ip; + int maxfdp1 = max (recvfd, pipefd[0]) + 1; + fd_set rset[1]; + FD_ZERO (rset); + + alarm(3); /* set the timeout alarm to handle dropped packets */ + + while (1) { + FD_SET (recvfd, rset); + FD_SET (pipefd[0], rset); + + n = select (maxfdp1, rset, NULL, NULL, NULL); + if (n < 0 && errno != EINTR) { + err_sys ("select error"); + } + + if (FD_ISSET (recvfd, rset)) { + len = salen; + n = recvfrom (recvfd, recvbuf, sizeof(recvbuf), 0, sarecv, &len); + err = errno; + Gettimeofday (recvtv, NULL); /* get time of packet arrival */ + if (n < 0 && err != EAGAIN) { + err_sys ("recvfrom error"); + } + } + + if (FD_ISSET (pipefd[0], rset)) { + Read (pipefd[0], &n, 1); + return -3; /* timeout */ + } + + ip = (struct ip *) recvbuf; + return process_ip (ip, n); + } +} + +/* sub_tv: subtract minus from plus and put the result in res */ + +void sub_tv (Timeval *plus, Timeval *minus, Timeval *res) +{ + res->tv_sec = plus->tv_sec - minus->tv_sec; + res->tv_usec = plus->tv_usec - minus->tv_usec; + + if (res->tv_usec < 0) { + res->tv_sec--; + res->tv_usec += 1000000; + } +} + +/* time_to_double: convert a Timeval to a double. This only + works with Timevals that are small (like the difference between + two real Timevals) */ + +double time_to_double (Timeval *time) +{ + return time->tv_sec * 1000.0 + time->tv_usec / 1000.0; +} + +/* print_report: prints all the information about a successful round trip */ + +void print_report () +{ + int stat; + char str[NI_MAXHOST]; + + stat = sock_cmp_addr (sarecv, salast, salen); + + /* if this reply comes from source different from the previous + one, print the full host information */ + + if (stat != 0) { + stat = getnameinfo (sarecv, salen, str, sizeof(str), NULL, 0, 0); + if (stat == 0) { + printf (" %s (%s)", str, Sock_ntop_host (sarecv, salen)); + } else { + printf (" %s", Sock_ntop_host (sarecv, salen)); + } + memcpy (salast, sarecv, salen); + } + + /* calculate and print the round trip time using user-level timestamps */ + + sub_tv (recvtv, sendtv, difftv); + + printf (" %.3f", time_to_double (difftv)); +} + +/* send_dgram: generate an outgoing UDP packet */ + + /* the second effort send is a kludge to handle a funny + thing, which is that the last host seems to refuse the + second or third connection consistently, which might + might mean that something breaks when we get the + ICMP_DEST_UNREACH error. The second attempt seems + to succeed consistently. */ + +void send_dgram (int ttl) +{ + int n; + datalen = sizeof (Rec); /* length of the data in a datagram */ + seq = 0; + u_short dport = 32768 + 668; /* destination port -- hopefully unused */ + + rec->seq = seq++; + sock_set_port (sasend, salen, htons(dport+seq)); + + Gettimeofday (sendtv, NULL); + n = sendto(sendfd, sendbuf, datalen, 0, sasend, salen); + + if (n==-1 && errno == ECONNREFUSED) { + Gettimeofday (sendtv, NULL); + n = sendto(sendfd, sendbuf, datalen, 0, sasend, salen); + } + + if (n != datalen) { + err_sys("sendto error"); + } +} + +/* send_probes: sends a set of probes with the given ttl and + then waits for the replies. The weird TOS options are there + as a signal to the kernel to identify clink packets so it can + fill in the timestamps. I am assuming that they don't have + any actual effect. */ + +int send_probes (int ttl) +{ + int probe, code, done; + + Setsockopt (sendfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(int)); + bzero (salast, salen); + int nprobes = 2; + + printf ("%2d ", ttl); + fflush (stdout); + + done = 0; /* count the number of probes that generate an ICMP_DEST_UNREACH */ + + for (probe = 0; probe < nprobes; probe++) { + send_dgram (ttl); + code = recv_dgram (); + + if (code == -3) { + printf (" *"); + } else { + print_report (); + } + + if (code == -1) done++; + fflush (stdout); + } + printf ("ms\n"); + return done; +} + +/* loop_ttl: starting with ttl=1, gradually increase ttl until + we start getting ICMP_DEST_UNREACH instead of ICMP_TIME_EXCEEDED */ + +void loop_ttl () +{ + int ttl, done; + + Pipe (pipefd); /* the pipe for the alarm handler */ + max_ttl = 30; + + + recvfd = socket (sasend->sa_family, SOCK_RAW, IPPROTO_ICMP); + if (recvfd == -1) { + if (errno == EPERM) { + printf ("\nclink was unable to open a raw socket. The most\n"); + printf ("likely cause is that you are not running it as root.\n"); + exit (1); + } else { + err_sys ("opening raw socket in clink"); + } + } + + fcntl (recvfd, F_SETFL, O_NONBLOCK); + setuid (getuid ()); + + sendfd = socket (sasend->sa_family, SOCK_DGRAM, 0); + + sabind->sa_family = sasend->sa_family; + sport = (getpid() & 0xffff) | 0x8000; /* source UDP port # */ + sock_set_port (sabind, salen, htons(sport)); + Bind (sendfd, sabind, salen); + + Signal (SIGALRM, sig_alrm); + + for (ttl = 1; ttl <= max_ttl; ttl++) { + done = send_probes (ttl); + if (done > 0) break; + } +} diff --git a/exercises/ex04/trout/trout.h b/exercises/ex04/trout/trout.h new file mode 100644 index 00000000..e445646d --- /dev/null +++ b/exercises/ex04/trout/trout.h @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSIZE 1500 + +typedef struct rec { /* outgoing UDP data */ + u_short seq; /* sequence number */ +} Rec; + +typedef struct timeval Timeval; +typedef struct sockaddr Sockaddr; + +#define max(a,b) ((a) > (b) ? (a) : (b)) + +/* the following are prototypes for the Stevens utilities in util.c */ + +void Sendto(int fd, const void *ptr, size_t nbytes, int flags, + const struct sockaddr *sa, socklen_t salen); + +Sockaddr *sasend; /* socket addresses for various purposes */ +Sockaddr *sarecv; +Sockaddr *salast; +Sockaddr *sabind; + +socklen_t salen; /* length of a socket address */ + +/* other global variables */ + +int seq; + +char recvbuf[BUFSIZE]; +char sendbuf[BUFSIZE]; + +int sendfd, recvfd; +int pipefd[2]; /* the pipe for the alarm handler */ + +// Sockaddr *sasend; /* socket addresses for various purposes */ +// Sockaddr *sarecv; +// Sockaddr *salast; +// Sockaddr *sabind; + +// socklen_t salen; /* length of a socket address */ +int datalen; /* length of the data in a datagram */ +int max_ttl; + +u_short sport; /* source UDP port # */ + + /* 668 = the neighbor of the beast */ +Timeval sendtv[1]; +Timeval recvtv[1]; +Timeval difftv[1]; + +void err_sys (char *fmt, ...); +void err_quit (char *fmt, ...); +char *Sock_ntop_host(const struct sockaddr *sa, socklen_t salen); +int sock_cmp_addr(const struct sockaddr *sa1, + const struct sockaddr *sa2, socklen_t salen); +void sock_set_port(struct sockaddr *sa, socklen_t salen, int port); +void tv_sub (struct timeval *out, struct timeval *in); +char *icmpcode_v4(int code); +void *Calloc(size_t n, size_t size); +void Gettimeofday(struct timeval *tv, void *foo); +void Pipe(int *fds); +void Bind(int fd, const struct sockaddr *sa, socklen_t salen); +void Setsockopt(int fd, int level, int optname, const void *optval, + socklen_t optlen); +struct addrinfo *Host_serv(const char *host, const char *serv, + int family, int socktype); +ssize_t Read(int fd, void *ptr, size_t nbytes); +void Write(int fd, void *ptr, size_t nbytes); +ssize_t Recvfrom(int fd, void *ptr, size_t nbytes, int flags, + struct sockaddr *sa, socklen_t *salenptr); + +typedef void Sigfunc(int); +Sigfunc *Signal(int signo, Sigfunc *func); + +void loop_ttl (); diff --git a/exercises/ex04/trout/util.c b/exercises/ex04/trout/util.c new file mode 100644 index 00000000..ce9b0a40 --- /dev/null +++ b/exercises/ex04/trout/util.c @@ -0,0 +1,284 @@ +#include "util.h" + +void err_doit (int errnoflag, int level, char *fmt, va_list ap) +{ + int errno_save, n; + char buf[MAXLINE]; + + errno_save = errno; /* value caller might want printed */ + vsnprintf (buf, sizeof(buf), fmt, ap); + n = strnlen (buf, MAXLINE); + if (errnoflag) + snprintf (buf+n, sizeof(buf) - n, ": %s", strerror(errno_save)); + strcat (buf, "\n"); + + fflush (stdout); + fputs (buf, stderr); + fflush (stderr); +} + +void err_sys (char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + err_doit (1, LOG_ERR, fmt, ap); + va_end(ap); + exit(1); +} + +void err_quit (char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + err_doit (0, LOG_ERR, fmt, ap); + va_end(ap); + exit(1); +} + +char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen) +{ + static char str[128]; + + switch (sa->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + + if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) { + return NULL; + } else { + return str; + } + } + case AF_UNIX: { + struct sockaddr_un *unp = (struct sockaddr_un *) sa; + + /* OK to have no pathname bound to the socket: happens on + every connect() unless client calls bind() first. */ + if (unp->sun_path[0] == 0) + strcpy(str, "(no pathname bound)"); + else + snprintf(str, sizeof(str), "%s", unp->sun_path); + return str; + } + default: + snprintf(str, sizeof(str), "sock_ntop_host: unknown AF_xxx: %d, len %d", + sa->sa_family, salen); + return str; + } + return NULL; +} + +char *Sock_ntop_host(const struct sockaddr *sa, socklen_t salen) +{ + char *ptr; + + if ( (ptr = sock_ntop_host(sa, salen)) == NULL) + err_sys("sock_ntop_host error"); /* inet_ntop() sets errno */ + return(ptr); +} + +void sock_set_port(struct sockaddr *sa, socklen_t salen, int port) +{ + switch (sa->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + + sin->sin_port = port; + return; + } + return; + } +} + +int sock_cmp_addr(const struct sockaddr *sa1, const struct sockaddr *sa2, + socklen_t salen) +{ + if (sa1->sa_family != sa2->sa_family) + return(-1); + + switch (sa1->sa_family) { + case AF_INET: { + return(memcmp( &((struct sockaddr_in *) sa1)->sin_addr, + &((struct sockaddr_in *) sa2)->sin_addr, + sizeof(struct in_addr))); + } + + case AF_UNIX: { + return(strcmp( ((struct sockaddr_un *) sa1)->sun_path, + ((struct sockaddr_un *) sa2)->sun_path)); + } + } + return -1; +} + +void tv_sub (struct timeval *out, struct timeval *in) +{ + if ( (out->tv_usec -= in->tv_usec) < 0) { /* out -= in */ + --out->tv_sec; + out->tv_usec += 1000000; + } + out->tv_sec -= in->tv_sec; +} + +char *icmpcode_v4(int code) +{ + switch (code) { + case 0: return("network unreachable"); + case 1: return("host unreachable"); + case 2: return("protocol unreachable"); + case 3: return("port unreachable"); + case 4: return("fragmentation required but DF bit set"); + case 5: return("source route failed"); + case 6: return("destination network unknown"); + case 7: return("destination host unknown"); + case 8: return("source host isolated (obsolete)"); + case 9: return("destination network administratively prohibited"); + case 10: return("destination host administratively prohibited"); + case 11: return("network unreachable for TOS"); + case 12: return("host unreachable for TOS"); + case 13: return("communication administratively prohibited by filtering"); + case 14: return("host recedence violation"); + case 15: return("precedence cutoff in effect"); + default: return("[unknown code]"); + } +} + +Sigfunc *signal(int signo, Sigfunc *func) +{ + struct sigaction act, oact; + + act.sa_handler = func; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + if (signo == SIGALRM) { + act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x (and Linux, apparently) */ + } + if (sigaction(signo, &act, &oact) < 0) + return(SIG_ERR); + return(oact.sa_handler); +} + +Sigfunc *Signal(int signo, Sigfunc *func) /* for our signal() function */ +{ + Sigfunc *sigfunc; + + if ( (sigfunc = signal(signo, func)) == SIG_ERR) + err_sys("signal error"); + return(sigfunc); +} + +void *Malloc(size_t size) +{ + void *ptr; + + if ( (ptr = malloc(size)) == NULL) + err_sys("malloc error"); + return(ptr); +} + +void *Calloc(size_t n, size_t size) +{ + void *ptr; + + if ( (ptr = calloc(n, size)) == NULL) + err_sys("calloc error"); + return(ptr); +} + +void Gettimeofday(struct timeval *tv, void *foo) +{ + if (gettimeofday(tv, foo) == -1) + err_sys("gettimeofday error"); + return; +} + +void Pipe(int *fds) +{ + if (pipe(fds) < 0) + err_sys("pipe error"); +} + +void Bind(int fd, const struct sockaddr *sa, socklen_t salen) +{ + if (bind(fd, sa, salen) < 0) + err_sys("bind error"); +} + +void Setsockopt(int fd, int level, int optname, const void *optval, + socklen_t optlen) +{ + if (setsockopt(fd, level, optname, optval, optlen) < 0) + err_sys("setsockopt error"); +} + +struct addrinfo * +host_serv (char *host, char *serv, int family, int socktype) +{ + int n; + struct addrinfo hints, *res; + + bzero (&hints, sizeof(struct addrinfo)); + hints.ai_flags = AI_CANONNAME; /* return canonical name */ + hints.ai_family = family; + hints.ai_socktype = socktype; + + n = getaddrinfo (host, serv, &hints, &res); + if (n != 0) { + return NULL; + } else { + return res; /* return pointer to first on linked list */ + } +} + +/* + * There is no easy way to pass back the integer return code from + * getaddrinfo() in the function above, short of adding another argument + * that is a pointer, so the easiest way to provide the wrapper function + * is just to duplicate the simple function as we do here. + */ + +struct addrinfo * +Host_serv(const char *host, const char *serv, int family, int socktype) +{ + int n; + struct addrinfo hints, *res; + + bzero(&hints, sizeof(struct addrinfo)); + hints.ai_flags = AI_CANONNAME; /* always return canonical name */ + hints.ai_family = family; /* 0, AF_INET, AF_INET6, etc. */ + hints.ai_socktype = socktype; /* 0, SOCK_STREAM, SOCK_DGRAM, etc. */ + + if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) + err_quit("host_serv error for %s, %s: %s", + (host == NULL) ? "(no hostname)" : host, + (serv == NULL) ? "(no service name)" : serv, + gai_strerror(n)); + + return(res); /* return pointer to first on linked list */ +} + +ssize_t Read(int fd, void *ptr, size_t nbytes) +{ + ssize_t n; + + if ( (n = read(fd, ptr, nbytes)) == -1) + err_sys("read error"); + return(n); +} + +void Write(int fd, void *ptr, size_t nbytes) +{ + if (write(fd, ptr, nbytes) != nbytes) + err_sys("write error"); +} + +ssize_t Recvfrom(int fd, void *ptr, size_t nbytes, int flags, + struct sockaddr *sa, socklen_t *salenptr) +{ + ssize_t n; + + n = recvfrom(fd, ptr, nbytes, flags, sa, salenptr); + if (n < 0) + err_sys("recvfrom error"); + return(n); +} diff --git a/exercises/ex04/trout/util.h b/exercises/ex04/trout/util.h new file mode 100644 index 00000000..b04ff27b --- /dev/null +++ b/exercises/ex04/trout/util.h @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXLINE 4096 + +/* the following are a few definitions from Stevens' unp.h */ +typedef void Sigfunc(int); /* for signal handlers */ + +void err_sys (char *fmt, ...); +void err_quit (char *fmt, ...); +char *Sock_ntop_host(const struct sockaddr *sa, socklen_t salen); +int sock_cmp_addr(const struct sockaddr *sa1, + const struct sockaddr *sa2, socklen_t salen); +void sock_set_port(struct sockaddr *sa, socklen_t salen, int port); +void tv_sub (struct timeval *out, struct timeval *in); +char *icmpcode_v4(int code); +Sigfunc *Signal(int signo, Sigfunc *func); +void *Calloc(size_t n, size_t size); +void Gettimeofday(struct timeval *tv, void *foo); +void Pipe(int *fds); +void Bind(int fd, const struct sockaddr *sa, socklen_t salen); +void Setsockopt(int fd, int level, int optname, const void *optval, + socklen_t optlen); +struct addrinfo *Host_serv(const char *host, const char *serv, + int family, int socktype); +ssize_t Read(int fd, void *ptr, size_t nbytes); +void Write(int fd, void *ptr, size_t nbytes); +ssize_t Recvfrom(int fd, void *ptr, size_t nbytes, int flags, + struct sockaddr *sa, socklen_t *salenptr);