]> git.ipfire.org Git - thirdparty/mtr.git/commitdiff
Add TCP (SYN) support 1/head
authorRogier 'DocWilco' Mulhuijzen <github@bsdchicks.com>
Wed, 20 Feb 2013 20:59:01 +0000 (21:59 +0100)
committerRogier 'DocWilco' Mulhuijzen <github@bsdchicks.com>
Fri, 22 Feb 2013 11:12:42 +0000 (12:12 +0100)
Instead of sending ICMP ECHO or UDP packets, this mode opens a TCP
connection to the port of choice (80 by default) and sets IP_TTL
or IPV6_UNICAST_HOPS to control the TTL of the outgoing SYN packet.

Instead of using ICMP ECHO sequence or UDP destination port, the
source port number is used to track how many hops away a router is.

For getting the final hop, sockets are left open until a timeout
is reached (10 seconds default) and a write is attempted as soon
as the socket becomes available for writing. Anything other than
a succesful write or a "Connection refused" error is ignored.

curses.c
gtk.c
mtr.8
mtr.c
net.c
net.h
select.c

index 5d6c483132ee38992163f2894d4b15721e1b6053..9f487ef6fd4c81dead79fb3e7fba842afade97fb 100644 (file)
--- a/curses.c
+++ b/curses.c
@@ -257,6 +257,7 @@ int mtr_curses_keyaction(void)
   if (tolower(c) == 'u') {
     switch ( mtrtype ) {
     case IPPROTO_ICMP:
+    case IPPROTO_TCP:
       mtrtype = IPPROTO_UDP;
       break;
     case IPPROTO_UDP:
@@ -265,6 +266,18 @@ int mtr_curses_keyaction(void)
     }
     return ActionNone;
   }
+  if (tolower(c) == 't') {
+    switch ( mtrtype ) {
+    case IPPROTO_ICMP:
+    case IPPROTO_UDP:
+      mtrtype = IPPROTO_TCP;
+      break;
+    case IPPROTO_TCP:
+      mtrtype = IPPROTO_ICMP;
+      break;
+    }
+    return ActionNone;
+  }
   /* reserve to display help message -Min */
   if (tolower(c) == '?'|| tolower(c) == 'h') {
     mvprintw(2, 0, "Command:\n" );
diff --git a/gtk.c b/gtk.c
index b45ac81ffb33b267c972da979194c0a6fb0b254e..2024f580e070e31de6692b8b9596e9941cd0defd 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -576,6 +576,7 @@ gint gtk_ping(UNUSED gpointer data)
 {
   gtk_redraw();
   net_send_batch();
+  net_harvest_fds();
   g_source_remove (tag);
   gtk_add_ping_timeout ();
   return TRUE;
diff --git a/mtr.8 b/mtr.8
index 81e192680ac7056fb566f36a4e6c61f632eabded..c13d05a0798c077aa9af8db70cd7b933c7ba3dfc 100644 (file)
--- a/mtr.8
+++ b/mtr.8
@@ -8,7 +8,7 @@ mtr \- a network diagnostic tool
 .SH SYNOPSIS
 .B mtr 
 [\c
-.B \-hvrctglspeniu46\c
+.B \-hvrctglspeniuTP46\c
 ]
 [\c
 .B \-\-help\c
@@ -55,6 +55,15 @@ mtr \- a network diagnostic tool
 [\c
 .B \-\-psize\ BYTES | -s BYTES\c
 ]
+[\c
+.B \-\-tcp\c
+]
+[\c
+.B \-\-port\ PORT\c
+]
+[\c
+.B \-\-timeout\ SECONDS\c
+]
 .B HOSTNAME [PACKETSIZE]
 
 
@@ -290,6 +299,29 @@ ECHO requests.  The default value for this parameter is one second.
 .br
 Use UDP datagrams instead of ICMP ECHO.
 
+.TP
+.B \-T
+.TP
+.B \-\-tcp
+.br
+Use TCP SYN packets instead of ICMP ECHO. PACKETSIZE is ignored, since
+SYN packets can not contain data.
+
+.TP
+.B \-P\ PORT
+.TP
+.B \-\-port\ PORT
+.br
+The target port number for TCP traces.
+
+.TP
+.B \-\-timeout\ SECONDS
+.br
+The number of seconds to keep the TCP socket open before giving up on
+the connection. This will only affect the final hop. Using large values
+for this, especially combined with a short interval, will use up a lot
+of file descriptors.
+
 .TP
 .B \-4
 .br
diff --git a/mtr.c b/mtr.c
index 9246bb1a1f684b039f53e2483ef5b1856db221f0..88a39cd0990b528c4b649df1db15fa4dcf567d49 100644 (file)
--- a/mtr.c
+++ b/mtr.c
@@ -76,6 +76,8 @@ int  fstTTL = 1;                /* default start at first hop */
 /*int maxTTL = MaxHost-1;  */     /* max you can go is 255 hops */
 int   maxTTL = 30;              /* inline with traceroute */
                                 /* end ttl window stuff. */
+int remoteport = 80;            /* for TCP tracing */
+int timeout = 10 * 1000000;     /* for TCP tracing */
 
 
 /* default display field(defined by key in net.h) and order */
@@ -152,6 +154,9 @@ void parse_arg (int argc, char **argv)
     { "first-ttl", 1, 0, 'f' },        /* -f & -m are borrowed from traceroute */
     { "max-ttl", 1, 0, 'm' },
     { "udp", 0, 0, 'u' },      /* UDP (default is ICMP) */
+    { "tcp", 0, 0, 'T' },      /* TCP (default is ICMP) */
+    { "port", 1, 0, 'P' },      /* target port number for TCP */
+    { "timeout", 1, 0, 'Z' },   /* timeout for TCP sockets */
     { "inet", 0, 0, '4' },     /* IPv4 only */
     { "inet6", 0, 0, '6' },    /* IPv6 only */
     { "aslookup", 0, 0, 'z' },  /* Do AS lookup */
@@ -162,7 +167,7 @@ void parse_arg (int argc, char **argv)
   while(1) {
     /* added f:m:o: byMin */
     opt = getopt_long(argc, argv,
-                     "vhrwxtglpo:B:i:c:s:Q:ena:f:m:ubz46", long_options, NULL);
+                     "vhrwxtglpo:B:i:c:s:Q:ena:f:m:uTP:Zbz46", long_options, NULL);
     if(opt == -1)
       break;
 
@@ -273,11 +278,33 @@ void parse_arg (int argc, char **argv)
       }
       break;
     case 'u':
+      if (mtrtype != IPPROTO_ICMP) {
+        fprintf(stderr, "-u and -T are mutually exclusive.\n");
+        exit(EXIT_FAILURE);
+      }
       mtrtype = IPPROTO_UDP;
       break;
+    case 'T':
+      if (mtrtype != IPPROTO_ICMP) {
+        fprintf(stderr, "-u and -T are mutually exclusive.\n");
+        exit(EXIT_FAILURE);
+      }
+      mtrtype = IPPROTO_TCP;
+      break;
     case 'b':
       show_ips = 1;
       break;
+    case 'P':
+      remoteport = atoi(optarg);
+      if (remoteport > 65535 || remoteport < 1) {
+        fprintf(stderr, "Illegal port number.\n");
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case 'Z':
+      timeout = atoi(optarg);
+      timeout *= 1000000;
+      break;
     case '4':
       af = AF_INET;
       break;
@@ -393,12 +420,12 @@ int main(int argc, char **argv)
   }
 
   if (PrintHelp) {
-    printf("usage: %s [-hvrwctglspniu46] [--help] [--version] [--report]\n"
+    printf("usage: %s [-hvrwctglspniuT46] [--help] [--version] [--report]\n"
           "\t\t[--report-wide] [--report-cycles=COUNT] [--curses] [--gtk]\n"
            "\t\t[--raw] [--split] [--mpls] [--no-dns] [--show-ips]\n"
            "\t\t[--address interface]  [--aslookup]\n" /* BL */
            "\t\t[--psize=bytes/-s bytes]\n"            /* ok */
-           "\t\t[--report-wide|-w] [-u]\n"            /* rew */
+           "\t\t[--report-wide|-w] [-u|-T] [--port=PORT] [--timeout=SECONDS]\n"            /* rew */
           "\t\t[--interval=SECONDS] HOSTNAME [PACKETSIZE]\n", argv[0]);
     exit(0);
   }
diff --git a/net.c b/net.c
index 431ad1e37159f60e7090bedaff5c730c2738280b..ca1776459216a4befd840f14b42e4bb7a658e988 100644 (file)
--- a/net.c
+++ b/net.c
@@ -28,6 +28,8 @@
 #include <sys/types.h>
 #include <sys/time.h>
 #include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
 #include <netinet/in.h>
 #include <memory.h>
 #include <unistd.h>
@@ -64,6 +66,13 @@ struct UDPHeader {
   uint16 checksum;
 };
 
+/* Structure of an TCP header, as far as we need it.  */
+struct TCPHeader {
+  uint16 srcport;
+  uint16 dstport;
+  uint32 seq;
+};
+
 /* Structure of an IPv4 UDP pseudoheader.  */
 struct UDPv4PHeader {
   uint32 saddr;
@@ -132,6 +141,7 @@ struct sequence {
   int transit;
   int saved_seq;
   struct timeval time;
+  int socket;
 };
 
 
@@ -201,6 +211,8 @@ extern int bitpattern;              /* packet bit pattern used by ping */
 extern int tos;                        /* type of service set in ping packet*/
 extern int af;                 /* address family of remote target */
 extern int mtrtype;            /* type of query packet used */
+extern int remoteport;          /* target port for TCP tracing */
+extern int timeout;             /* timeout for TCP connections */
 
 /* return the number of microseconds to wait before sending the next
    ping */
@@ -257,15 +269,8 @@ int udp_checksum(void *pheader, void *udata, int psize, int dsize)
 }
 
 
-int new_sequence(int index) 
+void save_sequence(int index, int seq)
 {
-  static int next_sequence = MinSequence;
-  int seq;
-
-  seq = next_sequence++;
-  if (next_sequence >= MaxSequence)
-    next_sequence = MinSequence;
-
   sequence[seq].index = index;
   sequence[seq].transit = 1;
   sequence[seq].saved_seq = ++host[index].xmit;
@@ -276,14 +281,139 @@ int new_sequence(int index)
     host[index].up = 0;
   host[index].sent = 1;
   net_save_xmit(index);
-  
+}
+
+int new_sequence(int index)
+{
+  static int next_sequence = MinSequence;
+  int seq;
+
+  seq = next_sequence++;
+  if (next_sequence >= MaxSequence)
+    next_sequence = MinSequence;
+
+  save_sequence(index, seq);
+
   return seq;
 }
 
+/*  Attempt to connect to a TCP port with a TTL */
+void net_send_tcp(int index)
+{
+  int ttl, s;
+  int opt = 1;
+  int port;
+  struct sockaddr_storage local;
+  struct sockaddr_storage remote;
+  struct sockaddr_in *local4 = (struct sockaddr_in *) &local;
+  struct sockaddr_in6 *local6 = (struct sockaddr_in6 *) &local;
+  struct sockaddr_in *remote4 = (struct sockaddr_in *) &remote;
+  struct sockaddr_in6 *remote6 = (struct sockaddr_in6 *) &remote;
+  socklen_t len;
+
+  ttl = index + 1;
+
+  s = socket(af, SOCK_STREAM, 0);
+  if (s < 0) {
+    display_clear();
+    perror("socket()");
+    exit(EXIT_FAILURE);
+  }
+
+  memset(&local, 0, sizeof (local));
+  memset(&remote, 0, sizeof (remote));
+  local.ss_family = af;
+  remote.ss_family = af;
+
+  switch (af) {
+  case AF_INET:
+    addrcpy((void *) &local4->sin_addr, (void *) &ssa4->sin_addr, af);
+    addrcpy((void *) &remote4->sin_addr, (void *) remoteaddress, af);
+    remote4->sin_port = htons(remoteport);
+    break;
+#ifdef ENABLE_IPV6
+  case AF_INET6:
+    addrcpy((void *) &local6->sin6_addr, (void *) &ssa6->sin6_addr, af);
+    addrcpy((void *) &remote6->sin6_addr, (void *) remoteaddress, af);
+    remote6->sin6_port = htons(remoteport);
+    break;
+#endif
+  }
+
+  if (bind(s, (struct sockaddr *) &local, sizeof (local))) {
+    display_clear();
+    perror("bind()");
+    exit(EXIT_FAILURE);
+  }
+
+  len = sizeof (local);
+  if (getsockname(s, (struct sockaddr *) &local, &len)) {
+    display_clear();
+    perror("getsockname()");
+    exit(EXIT_FAILURE);
+  }
+
+  opt = 1;
+  if (ioctl(s, FIONBIO, &opt)) {
+    display_clear();
+    perror("ioctl FIONBIO");
+    exit(EXIT_FAILURE);
+  }
+
+  switch (af) {
+  case AF_INET:
+    if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof (ttl))) {
+      display_clear();
+      perror("setsockopt IP_TTL");
+      exit(EXIT_FAILURE);
+    }
+    if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof (tos))) {
+      display_clear();
+      perror("setsockopt IP_TOS");
+      exit(EXIT_FAILURE);
+    }
+    break;
+#ifdef ENABLE_IPV6
+  case AF_INET6:
+    if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof (ttl))) {
+      display_clear();
+      perror("setsockopt IP_TTL");
+      exit(EXIT_FAILURE);
+    }
+    break;
+#endif
+  }
+
+  switch (local.ss_family) {
+  case AF_INET:
+    port = ntohs(local4->sin_port);
+    break;
+#ifdef ENABLE_IPV6
+  case AF_INET6:
+    port = ntohs(local6->sin6_port);
+    break;
+#endif
+  default:
+    display_clear();
+    perror("unknown AF?");
+    exit(EXIT_FAILURE);
+  }
+
+  save_sequence(index, port);
+  gettimeofday(&sequence[port].time, NULL);
+  sequence[port].socket = s;
+
+  connect(s, (struct sockaddr *) &remote, sizeof (remote));
+}
 
 /*  Attempt to find the host at a particular number of hops away  */
 void net_send_query(int index) 
 {
+  if (mtrtype == IPPROTO_TCP) {
+    net_send_tcp(index);
+    return;
+  }
+
   /*ok  char packet[sizeof(struct IPHeader) + sizeof(struct ICMPHeader)];*/
   char packet[MAXPACKET];
   struct IPHeader *ip = (struct IPHeader *) packet;
@@ -458,6 +588,11 @@ void net_process_ping(int seq, struct mplslen mpls, void * addr, struct timeval
     return;
   sequence[seq].transit = 0;
 
+  if (sequence[seq].socket > 0) {
+    close(sequence[seq].socket);
+    sequence[seq].socket = 0;
+  }
+
   index = sequence[seq].index;
 
   totusec = (now.tv_sec  - sequence[seq].time.tv_sec ) * 1000000 +
@@ -564,6 +699,7 @@ void net_process_return(void)
   int num;
   struct ICMPHeader *header = NULL;
   struct UDPHeader *udpheader = NULL;
+  struct TCPHeader *tcpheader = NULL;
   struct timeval now;
   ip_t * fromaddress = NULL;
   int echoreplytype = 0, timeexceededtype = 0, unreachabletype = 0;
@@ -695,6 +831,43 @@ void net_process_return(void)
       sequence = ntohs(udpheader->dstport);
     }
     break;
+
+  case IPPROTO_TCP:
+    if (header->type == timeexceededtype || header->type == unreachabletype) {
+      switch ( af ) {
+      case AF_INET:
+
+        if ((size_t) num < sizeof(struct IPHeader) +
+                           sizeof(struct ICMPHeader) +
+                           sizeof (struct IPHeader) +
+                           sizeof (struct TCPHeader))
+          return;
+        tcpheader = (struct TCPHeader *)(packet + sizeof (struct IPHeader) +
+                                                  sizeof (struct ICMPHeader) +
+                                                  sizeof (struct IPHeader));
+
+        if(num > 160)
+          decodempls(num, packet, &mpls, 156);
+
+      break;
+#ifdef ENABLE_IPV6
+      case AF_INET6:
+        if ( num < sizeof (struct ICMPHeader) +
+                   sizeof (struct ip6_hdr) + sizeof (struct TCPHeader) )
+          return;
+        tcpheader = (struct TCPHeader *) ( packet +
+                                           sizeof (struct ICMPHeader) +
+                                           sizeof (struct ip6_hdr) );
+
+        if(num > 140)
+          decodempls(num, packet, &mpls, 136);
+
+        break;
+#endif
+      }
+      sequence = ntohs(tcpheader->srcport);
+    }
+    break;
   }
 
   if (sequence)
@@ -1137,6 +1310,10 @@ void net_reset(void)
   
   for (at = 0; at < MaxSequence; at++) {
     sequence[at].transit = 0;
+    if (sequence[at].socket > 0) {
+      close(sequence[at].socket);
+      sequence[at].socket = 0;
+    }
   }
 
   gettimeofday(&reset, NULL);
@@ -1333,3 +1510,70 @@ void decodempls(int num, char *packet, struct mplslen *mpls, int offset) {
     }
   }
 }
+
+/* Add open sockets to select() */
+void net_add_fds(fd_set *writefd, int *maxfd)
+{
+  int at, fd;
+  for (at = 0; at < MaxSequence; at++) {
+    fd = sequence[at].socket;
+    if (fd > 0) {
+      FD_SET(fd, writefd);
+      if (fd >= *maxfd)
+        *maxfd = fd + 1;
+    }
+  }
+}
+
+/* check if we got connection or error on any fds */
+void net_process_fds(fd_set *writefd)
+{
+  int at, fd, r;
+  struct timeval now;
+  uint64_t unow, utime;
+
+  /* Can't do MPLS decoding */
+  struct mplslen mpls;
+  mpls.labels = 0;
+
+  gettimeofday(&now, NULL);
+  unow = now.tv_sec * 1000000L + now.tv_usec;
+
+  for (at = 0; at < MaxSequence; at++) {
+    fd = sequence[at].socket;
+    if (fd > 0 && FD_ISSET(fd, writefd)) {
+      r = write(fd, "G", 1);
+      /* if write was successful, or connection refused we have
+       * (probably) reached the remote address. Anything else happens to the
+       * connection, we write it off to avoid leaking sockets */
+      if (r == 1 || errno == ECONNREFUSED)
+        net_process_ping(at, mpls, remoteaddress, now);
+      else if (errno != EAGAIN) {
+        close(fd);
+        sequence[at].socket = 0;
+      }
+    }
+    if (fd > 0) {
+      utime = sequence[at].time.tv_sec * 1000000L + sequence[at].time.tv_usec;
+      if (unow - utime > timeout) {
+        close(fd);
+        sequence[at].socket = 0;
+      }
+    }
+  }
+}
+
+/* for GTK frontend */
+void net_harvest_fds(void)
+{
+  fd_set writefd;
+  int maxfd = 0;
+  struct timeval tv;
+
+  FD_ZERO(&writefd);
+  tv.tv_sec = 0;
+  tv.tv_usec = 0;
+  net_add_fds(&writefd, &maxfd);
+  select(maxfd, NULL, &writefd, NULL, &tv);
+  net_process_fds(&writefd);
+}
\ No newline at end of file
diff --git a/net.h b/net.h
index 6e2c172374756d69425153f9a60da061f421aa57..b36d630aa64e7cc4ed51da0a0796755adc1f65c8 100644 (file)
--- a/net.h
+++ b/net.h
@@ -35,6 +35,7 @@ void net_reset(void);
 void net_close(void);
 int net_waitfd(void);
 void net_process_return(void);
+void net_harvest_fds(void);
 
 int net_max(void);
 int net_min(void);
@@ -80,6 +81,9 @@ void sockaddrtop( struct sockaddr * saddr, char * strptr, size_t len );
 int addrcmp( char * a, char * b, int af );
 void addrcpy( char * a, char * b, int af );
 
+void net_add_fds(fd_set *writefd, int *maxfd);
+void net_process_fds(fd_set *writefd);
+
 #define MAXPATH 8
 #define MaxHost 256
 #define MinSequence 33000
index 413408016a37fae53c6a8c81a9abb72576f40db9..2b7f93581113078c5641b76c4ce072765499eb17 100644 (file)
--- a/select.c
+++ b/select.c
@@ -38,6 +38,7 @@ extern int MaxPing;
 extern int ForceMaxPing;
 extern float WaitTime;
 double dnsinterval;
+extern int mtrtype;
 
 static struct timeval intervaltime;
 int display_offset = 0;
@@ -45,6 +46,7 @@ int display_offset = 0;
 
 void select_loop(void) {
   fd_set readfd;
+  fd_set writefd;
   int anyset = 0;
   int maxfd = 0;
   int dnsfd, netfd;
@@ -65,6 +67,7 @@ void select_loop(void) {
     intervaltime.tv_usec = dt % 1000000;
 
     FD_ZERO(&readfd);
+    FD_ZERO(&writefd);
 
     maxfd = 0;
 
@@ -92,12 +95,15 @@ void select_loop(void) {
     FD_SET(netfd, &readfd);
     if(netfd >= maxfd) maxfd = netfd + 1;
 
+    if (mtrtype == IPPROTO_TCP)
+      net_add_fds(&writefd, &maxfd);
+
     do {
       if(anyset || paused) {
        selecttime.tv_sec = 0;
        selecttime.tv_usec = 0;
       
-       rv = select(maxfd, (void *)&readfd, NULL, NULL, &selecttime);
+       rv = select(maxfd, (void *)&readfd, &writefd, NULL, &selecttime);
 
       } else {
        if(Interactive) display_redraw();
@@ -214,6 +220,10 @@ void select_loop(void) {
       }
       anyset = 1;
     }
+
+    /* Check for activity on open sockets */
+    if (mtrtype == IPPROTO_TCP)
+      net_process_fds(&writefd);
   }
   return;
 }