Add TCP (SYN) support

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.
This commit is contained in:
Rogier 'DocWilco' Mulhuijzen 2013-02-20 21:59:01 +01:00
parent bc41676036
commit 2cf22b2409
7 changed files with 345 additions and 14 deletions

View File

@ -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" );

1
gtk.c
View File

@ -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;

34
mtr.8
View File

@ -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

33
mtr.c
View File

@ -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);
}

262
net.c
View File

@ -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);
}

4
net.h
View File

@ -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

View File

@ -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;
}