From: Matt Kimball Date: Tue, 20 Dec 2016 18:09:29 +0000 (-0800) Subject: mtr-packet: TCP and SCTP probes X-Git-Tag: v0.88~15^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fcda9e8b82ca354049fa0ee9cfcb2eaaae623ee0;p=thirdparty%2Fmtr.git mtr-packet: TCP and SCTP probes Added support for generating probes using TCP or SCTP, for both IPv4 and IPv6. These protocols require that we create a unique socket for each probe and watch for either a TTL expiration of the initial packet sent during a connection attempt from that socket or for socket connection success. We now allocate a unique port number or ICMP sequence ID when a probe is created, rather than using the command token for this purpose. This relieves the calling application of the burden of picking sensible command token values, and allows command tokens values greater than 16 bits. However, the existing mtr code continues to use the same command tokens values it has previously used as port numbers, so there is no difference when the calling program is mtr. Split mtr's command pipe handling out from net.c and into cmdpipe.c in the interest of future maintainability. Split probe.py's simple probes out from the individual protocol test cases and into common code which can be used by many protocol tests. --- diff --git a/Makefile.am b/Makefile.am index 50e2970..257baf4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,6 +44,7 @@ install-exec-hook: mtr_SOURCES = mtr.c mtr.h \ net.c net.h \ + cmdpipe.c cmdpipe.h \ dns.c dns.h \ raw.c raw.h \ split.c split.h \ diff --git a/cmdpipe.c b/cmdpipe.c new file mode 100644 index 0000000..20add25 --- /dev/null +++ b/cmdpipe.c @@ -0,0 +1,717 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "cmdpipe.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ERROR_H +# include +#else +# include "portability/error.h" +#endif + +#include "packet/cmdparse.h" +#include "display.h" + + +/* Set a file descriptor to non-blocking */ +static +void set_fd_nonblock( + int fd) +{ + int flags; + + /* Get the current flags of the file descriptor */ + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + error(EXIT_FAILURE, errno, "F_GETFL failure"); + exit(1); + } + + /* Add the O_NONBLOCK bit to the current flags */ + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + error(EXIT_FAILURE, errno, "Failure to set O_NONBLOCK"); + exit(1); + } +} + + +/* + Send a command synchronously to mtr-packet, blocking until a result + is available. This is intended to be used at start-up to check the + capabilities of mtr-packet, but probes should be sent asynchronously + to avoid blocking other probes and the user interface. +*/ +static +int send_synchronous_command( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + const char *cmd, + struct command_t *result) +{ + char reply[PACKET_REPLY_BUFFER_SIZE]; + int command_length; + int write_length; + int read_length; + + /* Query send-probe support */ + command_length = strlen(cmd); + write_length = write(cmdpipe->write_fd, cmd, command_length); + + if (write_length == -1) { + return -1; + } + + if (write_length != command_length) { + errno = EIO; + return -1; + } + + /* Read the reply to our query */ + read_length = read(cmdpipe->read_fd, reply, PACKET_REPLY_BUFFER_SIZE - 1); + + if (read_length < 0) { + return -1; + } + + /* Parse the query reply */ + reply[read_length] = 0; + if (parse_command(result, reply)) { + return -1; + } + + return 0; +} + + +/* Check support for a particular feature with the mtr-packet we invoked */ +static +int check_feature( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + const char *feature) +{ + char check_command[COMMAND_BUFFER_SIZE]; + struct command_t reply; + + snprintf( + check_command, COMMAND_BUFFER_SIZE, + "1 check-support feature %s\n", feature); + + if (send_synchronous_command(ctl, cmdpipe, check_command, &reply) == -1) { + return -1; + } + + /* Check that the feature is supported */ + if (!strcmp(reply.command_name, "feature-support") + && reply.argument_count >= 1 + && !strcmp(reply.argument_name[0], "support") + && !strcmp(reply.argument_value[0], "ok")) { + + /* Looks good */ + return 0; + } + + errno = ENOTSUP; + return -1; +} + + +/* + Check the protocol selected against the mtr-packet we are using. + Returns zero if everything is fine, or -1 with errno for either + a failure during the check, or for an unsupported feature. +*/ +static +int check_packet_features( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe) +{ + if (ctl->mtrtype == IPPROTO_ICMP) { + if (check_feature(ctl, cmdpipe, "icmp")) { + return -1; + } + } else if (ctl->mtrtype == IPPROTO_UDP) { + if (check_feature(ctl, cmdpipe, "udp")) { + return -1; + } + } else if (ctl->mtrtype == IPPROTO_TCP) { + if (check_feature(ctl, cmdpipe, "tcp")) { + return -1; + } +#ifdef HAS_SCTP + } else if (ctl->mtrtype == IPPROTO_SCTP) { + if (check_feature(ctl, cmdpipe, "sctp")) { + return -1; + } +#endif + } else { + errno = EINVAL; + return -1; + } + +#ifdef SO_MARK + if (ctl->mark) { + if (check_feature(ctl, cmdpipe, "mark")) { + return -1; + } + } +#endif + + return 0; +} + + +/* + Execute mtr-packet, allowing the MTR_PACKET evironment to override + the PATH when locating the executable. +*/ +static +void execute_packet_child(void) +{ + char *mtr_packet_path; + + /* + Allow the MTR_PACKET environment variable to overrride + the path to the mtr-packet executable. This is necessary + for debugging changes for mtr-packet. + */ + mtr_packet_path = getenv("MTR_PACKET"); + if (mtr_packet_path == NULL) { + mtr_packet_path = "mtr-packet"; + } + + /* + First, try to execute using /usr/bin/env, because this + will search the PATH for mtr-packet + */ + execl("/usr/bin/env", "mtr-packet", mtr_packet_path, NULL); + + /* + If env fails to execute, try to use the MTR_PACKET environment + as a full path to the executable. This is necessary because on + Windows, minimal mtr binary distributions will lack /usr/bin/env. + + Note: A side effect is that an mtr-packet in the current directory + could be executed. This will only be the case if /usr/bin/env + doesn't exist. + */ + execl(mtr_packet_path, "mtr-packet", NULL); + + /* Both exec attempts failed, so nothing to do but exit */ + exit(1); +} + + +/* Create the command pipe to a new mtr-packet subprocess */ +int open_command_pipe( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe) +{ + int stdin_pipe[2]; + int stdout_pipe[2]; + pid_t child_pid; + int i; + + /* + We actually need two Unix pipes. One for stdin and one for + stdout on the new process. + */ + if (pipe(stdin_pipe) || pipe(stdout_pipe)) { + return errno; + } + + child_pid = fork(); + if (child_pid == -1) { + return errno; + } + + if (child_pid == 0) { + /* + In the child process, attach our created pipes to stdin + and stdout + */ + dup2(stdin_pipe[0], STDIN_FILENO); + dup2(stdout_pipe[1], STDOUT_FILENO); + + /* Close all unnecessary fds */ + for (i = STDERR_FILENO + 1; i <= stdout_pipe[1]; i++) { + close(i); + } + + execute_packet_child(); + } else { + memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t)); + + /* + In the parent process, save the opposite ends of the pipes + attached as stdin and stdout in the child. + */ + cmdpipe->pid = child_pid; + cmdpipe->read_fd = stdout_pipe[0]; + cmdpipe->write_fd = stdin_pipe[1]; + + /* We don't need the child ends of the pipe open in the parent. */ + close(stdout_pipe[1]); + close(stdin_pipe[0]); + + /* + Check that we can communicate with the client. If we failed to + execute the mtr-packet binary, we will discover that here. + */ + if (check_feature(ctl, cmdpipe, "send-probe")) { + error(EXIT_FAILURE, errno, "Failure to start mtr-packet"); + } + + if (check_packet_features(ctl, cmdpipe)) { + error(EXIT_FAILURE, errno, "Packet type unsupported"); + } + + /* We will need non-blocking reads from the child */ + set_fd_nonblock(cmdpipe->read_fd); + } + + return 0; +} + + +/* Kill the mtr-packet child process and close the command pipe */ +void close_command_pipe( + struct packet_command_pipe_t *cmdpipe) +{ + int child_exit_value; + + if (cmdpipe->pid) { + close(cmdpipe->read_fd); + close(cmdpipe->write_fd); + + kill(cmdpipe->pid, SIGTERM); + waitpid(cmdpipe->pid, &child_exit_value, 0); + } + + memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t)); +} + + +/* Start building the command string for the "send-probe" command */ +static +void construct_base_command( + struct mtr_ctl *ctl, + char *command, + int buffer_size, + int command_token, + ip_t *address) +{ + char ip_string[INET6_ADDRSTRLEN]; + const char *ip_type; + const char *protocol = NULL; + + /* Conver the remote IP address to a string */ + if (inet_ntop( + ctl->af, address, ip_string, INET6_ADDRSTRLEN) == NULL) { + + display_close(ctl); + error(EXIT_FAILURE, errno, "failure stringifying remote IP address"); + } + + if (ctl->af == AF_INET6) { + ip_type = "ip-6"; + } else { + ip_type = "ip-4"; + } + + if (ctl->mtrtype == IPPROTO_ICMP) { + protocol = "icmp"; + } else if (ctl->mtrtype == IPPROTO_UDP) { + protocol = "udp"; + } else if (ctl->mtrtype == IPPROTO_TCP) { + protocol = "tcp"; +#ifdef HAS_SCTP + } else if (ctl->mtrtype == IPPROTO_SCTP) { + protocol = "sctp"; +#endif + } else { + display_close(ctl); + error(EXIT_FAILURE, 0, "protocol unsupported by mtr-packet interface"); + } + + snprintf( + command, buffer_size, + "%d send-probe %s %s protocol %s", + command_token, ip_type, ip_string, protocol); +} + + +/* Append an argument to the "send-probe" command string */ +static +void append_command_argument( + char *command, + int buffer_size, + char *name, + int value) +{ + char argument[COMMAND_BUFFER_SIZE]; + int remaining_size; + + remaining_size = buffer_size - strlen(command) - 1; + + snprintf(argument, buffer_size, " %s %d", name, value); + strncat(command, argument, remaining_size); +} + + +/* Request a new probe from the "mtr-packet" child process */ +void send_probe_command( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + ip_t *address, + int packet_size, + int sequence, + int time_to_live) +{ + char command[COMMAND_BUFFER_SIZE]; + int remaining_size; + + construct_base_command( + ctl, command, COMMAND_BUFFER_SIZE, sequence, address); + + append_command_argument( + command, COMMAND_BUFFER_SIZE, "size", packet_size); + + append_command_argument( + command, COMMAND_BUFFER_SIZE, "bitpattern", ctl->bitpattern); + + append_command_argument( + command, COMMAND_BUFFER_SIZE, "tos", ctl->tos); + + append_command_argument( + command, COMMAND_BUFFER_SIZE, "ttl", time_to_live); + + if (ctl->remoteport) { + append_command_argument( + command, COMMAND_BUFFER_SIZE, "port", ctl->remoteport); + } + +#ifdef SO_MARK + if (ctl->mark) { + append_command_argument( + command, COMMAND_BUFFER_SIZE, "mark", ctl->mark); + } +#endif + + remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1; + strncat(command, "\n", remaining_size); + + /* Send a probe using the mtr-packet subprocess */ + if (write(cmdpipe->write_fd, command, strlen(command)) == -1) { + display_close(ctl); + error(EXIT_FAILURE, errno, "mtr-packet command pipe write failure"); + } +} + + +/* + Extract the IP address and round trip time from a reply to a probe. + Returns true if both arguments are found in the reply, false otherwise. +*/ +static +bool parse_reply_arguments( + struct mtr_ctl *ctl, + struct command_t *reply, + ip_t *fromaddress, + int *round_trip_time) +{ + bool found_round_trip; + bool found_ip; + char *arg_name; + char *arg_value; + int i; + + *round_trip_time = 0; + memset(fromaddress, 0, sizeof(ip_t)); + + found_ip = false; + found_round_trip = false; + + /* Examine the reply arguments for known values */ + for (i = 0; i < reply->argument_count; i++) { + arg_name = reply->argument_name[i]; + arg_value = reply->argument_value[i]; + + if (ctl->af == AF_INET6) { + /* IPv6 address of the responding host */ + if (!strcmp(arg_name, "ip-6")) { + if (inet_pton(AF_INET6, arg_value, fromaddress)) { + found_ip = true; + } + } + } else { + /* IPv4 address of the responding host */ + if (!strcmp(arg_name, "ip-4")) { + if (inet_pton(AF_INET, arg_value, fromaddress)) { + found_ip = true; + } + } + } + + /* The round trip time in microseconds */ + if (!strcmp(arg_name, "round-trip-time")) { + errno = 0; + *round_trip_time = strtol(arg_value, NULL, 10); + if (!errno) { + found_round_trip = true; + } + } + } + + return found_ip && found_round_trip; +} + + +/* + If an mtr-packet command has returned an error result, + report the error and exit. +*/ +static +void handle_reply_errors( + struct mtr_ctl *ctl, + struct command_t *reply) +{ + char *reply_name; + + reply_name = reply->command_name; + + if (!strcmp(reply_name, "no-route")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "No route to host"); + } + + if (!strcmp(reply_name, "network-down")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Network down"); + } + + if (!strcmp(reply_name, "probes-exhausted")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Probes exhausted"); + } + + if (!strcmp(reply_name, "invalid-argument")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "mtr-packet reported invalid argument"); + } + + if (!strcmp(reply_name, "permission-denied")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Permission denied"); + } + + if (!strcmp(reply_name, "unexpected-error")) { + display_close(ctl); + error(EXIT_FAILURE, 0, "Unexpected mtr-packet error"); + } +} + + +/* + A complete mtr-packet reply line has arrived. Parse it and record + the responding IP and round trip time, if it is a reply that we + understand. +*/ +static +void handle_command_reply( + struct mtr_ctl *ctl, + char *reply_str, + probe_reply_func_t reply_func) +{ + struct command_t reply; + ip_t fromaddress; + int seq_num; + int round_trip_time; + char *reply_name; + struct mplslen mpls; + + /* Parse the reply string */ + if (parse_command(&reply, reply_str)) { + /* + If the reply isn't well structured, something is fundamentally + wrong, as we might as well exit. Even if the reply is of an + unknown type, it should still parse. + */ + display_close(ctl); + error(EXIT_FAILURE, errno, "reply parse failure"); + return; + } + + handle_reply_errors(ctl, &reply); + + seq_num = reply.token; + reply_name = reply.command_name; + + /* If the reply type is unknown, ignore it for future compatibility */ + if (strcmp(reply_name, "reply") && strcmp(reply_name, "ttl-expired")) { + return; + } + + /* + If the reply had an IP address and a round trip time, we can + record the result. + */ + if (parse_reply_arguments(ctl, &reply, &fromaddress, &round_trip_time)) { + /* MPLS decoding */ + memset(&mpls, 0, sizeof(struct mplslen)); + mpls.labels = 0; + + reply_func( + ctl, seq_num, &mpls, (void *) &fromaddress, round_trip_time); + } +} + + +/* + Check the command pipe for completed replies to commands + we have previously sent. Record the results of those replies. +*/ +static +void consume_reply_buffer( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + probe_reply_func_t reply_func) +{ + char *reply_buffer; + char *reply_start; + char *end_of_reply; + int used_size; + int move_size; + + reply_buffer = cmdpipe->reply_buffer; + + /* Terminate the string storing the replies */ + assert(cmdpipe->reply_buffer_used < PACKET_REPLY_BUFFER_SIZE); + reply_buffer[cmdpipe->reply_buffer_used] = 0; + + reply_start = reply_buffer; + + /* + We may have multiple completed replies. Loop until we don't + have any more newlines termininating replies. + */ + while (true) { + /* If no newline is found, our reply isn't yet complete */ + end_of_reply = index(reply_start, '\n'); + if (end_of_reply == NULL) { + /* No complete replies remaining */ + break; + } + + /* + Terminate the reply string at the newline, which + is necessary in the case where we are able to read + mulitple replies arriving simultaneously. + */ + *end_of_reply = 0; + + /* Parse and record the reply results */ + handle_command_reply(ctl, reply_start, reply_func); + + reply_start = end_of_reply + 1; + } + + /* + After replies have been processed, free the space used + by the replies, and move any remaining partial reply text + to the start of the reply buffer. + */ + used_size = reply_start - reply_buffer; + move_size = cmdpipe->reply_buffer_used - used_size; + memmove(reply_buffer, reply_start, move_size); + cmdpipe->reply_buffer_used -= used_size; + + if (cmdpipe->reply_buffer_used >= PACKET_REPLY_BUFFER_SIZE - 1) { + /* + We've overflowed the reply buffer without a complete reply. + There's not much we can do about it but discard the data + we've got and hope new data coming in fits. + */ + cmdpipe->reply_buffer_used = 0; + } +} + + +/* + Read as much as we can from the reply pipe from the child process, and + process as many replies as are available. +*/ +void handle_command_replies( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + probe_reply_func_t reply_func) +{ + int read_count; + int buffer_remaining; + char *reply_buffer; + char *read_buffer; + + reply_buffer = cmdpipe->reply_buffer; + + /* + Read the available reply text, up to the the remaining + buffer space. (Minus one for the terminating NUL.) + */ + read_buffer = &reply_buffer[cmdpipe->reply_buffer_used]; + buffer_remaining = + PACKET_REPLY_BUFFER_SIZE - cmdpipe->reply_buffer_used; + read_count = read( + cmdpipe->read_fd, read_buffer, buffer_remaining - 1); + + if (read_count < 0) { + /* + EAGAIN simply indicates that there is no data currently + available on our non-blocking pipe. + */ + if (errno == EAGAIN) { + return; + } + + display_close(ctl); + error(EXIT_FAILURE, errno, "command reply read failure"); + return; + } + + if (read_count == 0) { + display_close(ctl); + + errno = EPIPE; + error(EXIT_FAILURE, EPIPE, "unexpected packet generator exit"); + } + + cmdpipe->reply_buffer_used += read_count; + + /* Handle any replies completed by this read */ + consume_reply_buffer(ctl, cmdpipe, reply_func); +} diff --git a/cmdpipe.h b/cmdpipe.h new file mode 100644 index 0000000..9c0dd86 --- /dev/null +++ b/cmdpipe.h @@ -0,0 +1,73 @@ +/* + mtr -- a network diagnostic tool + Copyright (C) 2016 Matt Kimball + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef CMDPIPE_H +#define CMDPIPE_H + +#include "mtr.h" + +#define COMMAND_BUFFER_SIZE 4096 +#define PACKET_REPLY_BUFFER_SIZE 4096 + +/* We use a pipe to the mtr-packet subprocess to generate probes */ +struct packet_command_pipe_t { + /* the process id of mtr-packet */ + pid_t pid; + + /* the end of the pipe we read for replies */ + int read_fd; + + /* the end of the pipe we write for commands */ + int write_fd; + + /* storage for incoming replies */ + char reply_buffer[PACKET_REPLY_BUFFER_SIZE]; + + /* the number of bytes currently used in reply_buffer */ + size_t reply_buffer_used; +}; + +typedef +void (*probe_reply_func_t)( + struct mtr_ctl *ctl, + int sequence, + struct mplslen *mpls, + ip_t *addr, + int round_trip_time); + +int open_command_pipe( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe); + +void close_command_pipe( + struct packet_command_pipe_t *cmdpipe); + +void send_probe_command( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + ip_t *address, + int packet_size, + int sequence, + int time_to_live); + +void handle_command_replies( + struct mtr_ctl *ctl, + struct packet_command_pipe_t *cmdpipe, + probe_reply_func_t reply_func); + +#endif diff --git a/mtr-packet.8.in b/mtr-packet.8.in index 31336f3..9dab7e6 100644 --- a/mtr-packet.8.in +++ b/mtr-packet.8.in @@ -96,7 +96,9 @@ The Internet Protocol version 6 address to probe. .HP 14 .IP The protocol to use for the network probe. -.B icmp +.BR icmp , +.BR sctp , +.BR tcp , and .B udp may be used. The default protocol is @@ -108,6 +110,9 @@ may be used. The default protocol is .HP 14 .IP The destination port to use for +.BR sctp , +.BR tcp , +or .B udp probes. .HP 7 @@ -190,6 +195,8 @@ Some features which can be checked are .BR ip-4 , .BR ip-6 , .BR icmp , +.BR sctp , +.BR tcp , .BR udp , and .BR mark . @@ -401,5 +408,6 @@ GitHub at: .SH "SEE ALSO" .BR mtr (8), .BR icmp (7), +.BR tcp (7), .BR udp (7), TCP/IP Illustrated (Stevens, ISBN 0201633469). diff --git a/net.c b/net.c index abfffec..3703e9a 100644 --- a/net.c +++ b/net.c @@ -18,53 +18,27 @@ #include "config.h" -#if defined(HAVE_SYS_XTI_H) -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_FCNTL_H -# include -#endif -#include -#include -#include -#include -#include #include -#include +#include #include +#include + #ifdef HAVE_ERROR_H # include #else # include "portability/error.h" #endif -#ifdef HAVE_LINUX_ICMP_H -# include -#endif #include "mtr.h" +#include "cmdpipe.h" #include "net.h" #include "display.h" #include "dns.h" #include "utils.h" -#include "packet/cmdparse.h" #define MinSequence 33000 #define MaxSequence 65536 -#define COMMAND_BUFFER_SIZE 4096 -#define PACKET_REPLY_BUFFER_SIZE 4096 - static int packetsize; /* packet size used by ping */ struct nethost { @@ -97,26 +71,6 @@ struct sequence { int transit; int saved_seq; struct timeval time; - int socket; -}; - - -/* We use a pipe to the mtr-packet subprocess to generate probes */ -struct packet_command_pipe_t { - /* the process id of mtr-packet */ - pid_t pid; - - /* the end of the pipe we read for replies */ - int read_fd; - - /* the end of the pipe we write for commands */ - int write_fd; - - /* storage for incoming replies */ - char reply_buffer[PACKET_REPLY_BUFFER_SIZE]; - - /* the number of bytes currently used in reply_buffer */ - size_t reply_buffer_used; }; @@ -192,103 +146,26 @@ static int new_sequence(struct mtr_ctl *ctl, int index) return seq; } -static void net_construct_base_command( - struct mtr_ctl *ctl, char *command, int buffer_size, int command_token) -{ - char ip_string[INET6_ADDRSTRLEN]; - const char *ip_type; - const char *protocol = NULL; - - /* Conver the remote IP address to a string */ - if (inet_ntop( - ctl->af, remoteaddress, ip_string, INET6_ADDRSTRLEN) == NULL) { - - display_close(ctl); - error(EXIT_FAILURE, errno, "failure stringifying remote IP address"); - } - - if (ctl->af == AF_INET6) { - ip_type = "ip-6"; - } else { - ip_type = "ip-4"; - } - - if (ctl->mtrtype == IPPROTO_ICMP) { - protocol = "icmp"; - } else if (ctl->mtrtype == IPPROTO_UDP) { - protocol = "udp"; - } else { - display_close(ctl); - error(EXIT_FAILURE, 0, "protocol unsupported by mtr-packet interface"); - } - - snprintf( - command, buffer_size, - "%d send-probe %s %s protocol %s", - command_token, ip_type, ip_string, protocol); -} - -static void net_append_command_argument( - char *command, int buffer_size, char *name, int value) -{ - char argument[COMMAND_BUFFER_SIZE]; - int remaining_size; - - remaining_size = buffer_size - strlen(command) - 1; - - snprintf(argument, buffer_size, " %s %d", name, value); - strncat(command, argument, remaining_size); -} /* Attempt to find the host at a particular number of hops away */ static void net_send_query(struct mtr_ctl *ctl, int index, int packet_size) { int seq = new_sequence(ctl, index); int time_to_live = index + 1; - char command[COMMAND_BUFFER_SIZE]; - int remaining_size; - - net_construct_base_command(ctl, command, COMMAND_BUFFER_SIZE, seq); - - net_append_command_argument( - command, COMMAND_BUFFER_SIZE, "size", packet_size); - - net_append_command_argument( - command, COMMAND_BUFFER_SIZE, "bitpattern", ctl->bitpattern); - - net_append_command_argument( - command, COMMAND_BUFFER_SIZE, "tos", ctl->tos); - - net_append_command_argument( - command, COMMAND_BUFFER_SIZE, "ttl", time_to_live); - - if (ctl->remoteport) { - net_append_command_argument( - command, COMMAND_BUFFER_SIZE, "port", ctl->remoteport); - } -#ifdef SO_MARK - if (ctl->mark) { - net_append_command_argument( - command, COMMAND_BUFFER_SIZE, "mark", ctl->mark); - } -#endif - - remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1; - strncat(command, "\n", remaining_size); - - /* Send a probe using the mtr-packet subprocess */ - if (write(packet_command_pipe.write_fd, command, strlen(command)) == -1) { - display_close(ctl); - error(EXIT_FAILURE, errno, "mtr-packet command pipe write failure"); - } + send_probe_command( + ctl, &packet_command_pipe, remoteaddress, packetsize, seq, time_to_live); } /* We got a return on something we sent out. Record the address and time. */ -static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, - void *addr, int totusec) +static void net_process_ping( + struct mtr_ctl *ctl, + int seq, + struct mplslen *mpls, + ip_t *addr, + int totusec) { int index; int oldavg; /* usedByMin */ @@ -300,7 +177,7 @@ static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, char addrcopy[sizeof(struct in_addr)]; #endif - addrcpy( (void *) &addrcopy, addr, ctl->af ); + addrcpy( (void *) &addrcopy, (char *)addr, ctl->af ); if (seq < 0 || seq >= MaxSequence) return; @@ -309,23 +186,18 @@ static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, return; sequence[seq].transit = 0; - if (sequence[seq].socket > 0) { - close(sequence[seq].socket); - sequence[seq].socket = 0; - } - index = sequence[seq].index; if ( addrcmp( (void *) &(host[index].addr), (void *) &ctl->unspec_addr, ctl->af ) == 0 ) { /* should be out of if as addr can change */ addrcpy( (void *) &(host[index].addr), addrcopy, ctl->af ); - host[index].mpls = mpls; + host[index].mpls = *mpls; display_rawhost(ctl, index, (void *) &(host[index].addr)); /* multi paths */ addrcpy( (void *) &(host[index].addrs[0]), addrcopy, ctl->af ); - host[index].mplss[0] = mpls; + host[index].mplss[0] = *mpls; } else { for( i=0; iaf ) != 0 && iaf ); - host[index].mplss[i] = mpls; + host[index].mplss[i] = *mpls; display_rawhost(ctl, index, (void *) &(host[index].addrs[i])); } } @@ -380,272 +252,13 @@ static void net_process_ping(struct mtr_ctl *ctl, int seq, struct mplslen mpls, display_rawping(ctl, index, totusec, seq); } - -/* - Extract the IP address and round trip time from a reply to a probe. - Returns true if both arguments are found in the reply, false otherwise. -*/ -static bool parse_reply_arguments( - struct mtr_ctl *ctl, struct command_t *reply, - ip_t *fromaddress, int *round_trip_time) -{ - bool found_round_trip; - bool found_ip; - char *arg_name; - char *arg_value; - int i; - - *round_trip_time = 0; - memset(fromaddress, 0, sizeof(ip_t)); - - found_ip = false; - found_round_trip = false; - - /* Examine the reply arguments for known values */ - for (i = 0; i < reply->argument_count; i++) { - arg_name = reply->argument_name[i]; - arg_value = reply->argument_value[i]; - - if (ctl->af == AF_INET6) { - /* IPv6 address of the responding host */ - if (!strcmp(arg_name, "ip-6")) { - if (inet_pton(AF_INET6, arg_value, fromaddress)) { - found_ip = true; - } - } - } else { - /* IPv4 address of the responding host */ - if (!strcmp(arg_name, "ip-4")) { - if (inet_pton(AF_INET, arg_value, fromaddress)) { - found_ip = true; - } - } - } - - /* The round trip time in microseconds */ - if (!strcmp(arg_name, "round-trip-time")) { - errno = 0; - *round_trip_time = strtol(arg_value, NULL, 10); - if (!errno) { - found_round_trip = true; - } - } - } - - return found_ip && found_round_trip; -} - - -/* - If an mtr-packet command has returned an error result, - report the error and exit. -*/ -static void net_handle_command_reply_errors( - struct mtr_ctl *ctl, struct command_t *reply) -{ - char *reply_name; - - reply_name = reply->command_name; - - if (!strcmp(reply_name, "no-route")) { - display_close(ctl); - error(EXIT_FAILURE, 0, "No route to host"); - } - - if (!strcmp(reply_name, "network-down")) { - display_close(ctl); - error(EXIT_FAILURE, 0, "Network down"); - } - - if (!strcmp(reply_name, "probes-exhausted")) { - display_close(ctl); - error(EXIT_FAILURE, 0, "Probes exhausted"); - } - - if (!strcmp(reply_name, "invalid-argument")) { - display_close(ctl); - error(EXIT_FAILURE, 0, "mtr-packet reported invalid argument"); - } - - if (!strcmp(reply_name, "permission-denied")) { - display_close(ctl); - error(EXIT_FAILURE, 0, "Permission denied"); - } - - if (!strcmp(reply_name, "unexpected-error")) { - display_close(ctl); - error(EXIT_FAILURE, 0, "Unexpected mtr-packet error"); - } -} - - -/* - A complete mtr-packet reply line has arrived. Parse it and record - the responding IP and round trip time, if it is a reply that we - understand. -*/ -static void net_process_command_reply( - struct mtr_ctl *ctl, char *reply_str) -{ - struct command_t reply; - ip_t fromaddress; - int seq_num; - int round_trip_time; - char *reply_name; - struct mplslen mpls; - - /* Parse the reply string */ - if (parse_command(&reply, reply_str)) { - /* - If the reply isn't well structured, something is fundamentally - wrong, as we might as well exit. Even if the reply is of an - unknown type, it should still parse. - */ - display_close(ctl); - error(EXIT_FAILURE, errno, "reply parse failure"); - return; - } - - net_handle_command_reply_errors(ctl, &reply); - - seq_num = reply.token; - reply_name = reply.command_name; - - /* If the reply type is unknown, ignore it for future compatibility */ - if (strcmp(reply_name, "reply") && strcmp(reply_name, "ttl-expired")) { - return; - } - - /* - If the reply had an IP address and a round trip time, we can - record the result. - */ - if (parse_reply_arguments(ctl, &reply, &fromaddress, &round_trip_time)) { - /* MPLS decoding */ - memset(&mpls, 0, sizeof(struct mplslen)); - mpls.labels = 0; - - net_process_ping( - ctl, seq_num, mpls, (void *) &fromaddress, round_trip_time); - } -} - - -/* - Check the command pipe for completed replies to commands - we have previously sent. Record the results of those replies. -*/ -static void net_process_pipe_buffer(struct mtr_ctl *ctl) -{ - char *reply_buffer; - char *reply_start; - char *end_of_reply; - int used_size; - int move_size; - - reply_buffer = packet_command_pipe.reply_buffer; - - /* Terminate the string storing the replies */ - assert(packet_command_pipe.reply_buffer_used < PACKET_REPLY_BUFFER_SIZE); - reply_buffer[packet_command_pipe.reply_buffer_used] = 0; - - reply_start = reply_buffer; - - /* - We may have multiple completed replies. Loop until we don't - have any more newlines termininating replies. - */ - while (true) { - /* If no newline is found, our reply isn't yet complete */ - end_of_reply = index(reply_start, '\n'); - if (end_of_reply == NULL) { - /* No complete replies remaining */ - break; - } - - /* - Terminate the reply string at the newline, which - is necessary in the case where we are able to read - mulitple replies arriving simultaneously. - */ - *end_of_reply = 0; - - /* Parse and record the reply results */ - net_process_command_reply(ctl, reply_start); - - reply_start = end_of_reply + 1; - } - - /* - After replies have been processed, free the space used - by the replies, and move any remaining partial reply text - to the start of the reply buffer. - */ - used_size = reply_start - reply_buffer; - move_size = packet_command_pipe.reply_buffer_used - used_size; - memmove(reply_buffer, reply_start, move_size); - packet_command_pipe.reply_buffer_used -= used_size; - - if (packet_command_pipe.reply_buffer_used >= - PACKET_REPLY_BUFFER_SIZE - 1) { - /* - We've overflowed the reply buffer without a complete reply. - There's not much we can do about it but discard the data - we've got and hope new data coming in fits. - */ - packet_command_pipe.reply_buffer_used = 0; - } -} - - /* Invoked when the read pipe from the mtr-packet subprocess is readable. If we have received a complete reply, process it. */ extern void net_process_return(struct mtr_ctl *ctl) { - int read_count; - int buffer_remaining; - char *reply_buffer; - char *read_buffer; - - reply_buffer = packet_command_pipe.reply_buffer; - - /* - Read the available reply text, up to the the remaining - buffer space. (Minus one for the terminating NUL.) - */ - read_buffer = &reply_buffer[packet_command_pipe.reply_buffer_used]; - buffer_remaining = - PACKET_REPLY_BUFFER_SIZE - packet_command_pipe.reply_buffer_used; - read_count = read( - packet_command_pipe.read_fd, read_buffer, buffer_remaining - 1); - - if (read_count < 0) { - /* - EAGAIN simply indicates that there is no data currently - available on our non-blocking pipe. - */ - if (errno == EAGAIN) { - return; - } - - display_close(ctl); - error(EXIT_FAILURE, errno, "command reply read failure"); - return; - } - - if (read_count == 0) { - display_close(ctl); - - errno = EPIPE; - error(EXIT_FAILURE, EPIPE, "unexpected packet generator exit"); - } - - packet_command_pipe.reply_buffer_used += read_count; - - /* Handle any replies completed by this read */ - net_process_pipe_buffer(ctl); + handle_command_replies(ctl, &packet_command_pipe, net_process_ping); } @@ -868,234 +481,12 @@ extern int net_send_batch(struct mtr_ctl *ctl) } -/* Set a file descriptor to non-blocking */ -static void set_fd_nonblock(int fd) -{ - int flags; - - /* Get the current flags of the file descriptor */ - flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) { - error(EXIT_FAILURE, errno, "F_GETFL failure"); - exit(1); - } - - /* Add the O_NONBLOCK bit to the current flags */ - if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { - error(EXIT_FAILURE, errno, "Failure to set O_NONBLOCK"); - exit(1); - } -} - -/* - Send a command synchronously to mtr-packet, blocking until a result - is available. This is intended to be used at start-up to check the - capabilities of mtr-packet, but probes should be sent asynchronously - to avoid blocking other probes and the user interface. -*/ -static int net_synchronous_command( - struct mtr_ctl *ctl, const char *cmd, struct command_t *result) -{ - char reply[PACKET_REPLY_BUFFER_SIZE]; - int command_length; - int write_length; - int read_length; - - /* Query send-probe support */ - command_length = strlen(cmd); - write_length = write( - packet_command_pipe.write_fd, cmd, command_length); - - if (write_length == -1) { - return -1; - } - - if (write_length != command_length) { - errno = EIO; - return -1; - } - - /* Read the reply to our query */ - read_length = read( - packet_command_pipe.read_fd, reply, PACKET_REPLY_BUFFER_SIZE - 1); - - if (read_length < 0) { - return -1; - } - - /* Parse the query reply */ - reply[read_length] = 0; - if (parse_command(result, reply)) { - return -1; - } - - return 0; -} - - -/* Check support for a particular feature with the mtr-packet we invoked */ -static int net_check_feature(struct mtr_ctl *ctl, const char *feature) -{ - char check_command[COMMAND_BUFFER_SIZE]; - struct command_t reply; - - snprintf( - check_command, COMMAND_BUFFER_SIZE, - "1 check-support feature %s\n", feature); - - if (net_synchronous_command(ctl, check_command, &reply) == -1) { - return -1; - } - - /* Check that the feature is supported */ - if (!strcmp(reply.command_name, "feature-support") - && reply.argument_count >= 1 - && !strcmp(reply.argument_name[0], "support") - && !strcmp(reply.argument_value[0], "ok")) { - - /* Looks good */ - return 0; - } - - errno = ENOTSUP; - return -1; -} - - -/* - Check the protocol selected against the mtr-packet we are using. - Returns zero if everything is fine, or -1 with errno for either - a failure during the check, or for an unsupported feature. -*/ -static int net_packet_feature_check(struct mtr_ctl *ctl) -{ - if (ctl->mtrtype == IPPROTO_ICMP) { - if (net_check_feature(ctl, "icmp")) { - return -1; - } - } else if (ctl->mtrtype == IPPROTO_UDP) { - if (net_check_feature(ctl, "udp")) { - return -1; - } - } else { - errno = EINVAL; - return -1; - } - -#ifdef SO_MARK - if (ctl->mark) { - if (net_check_feature(ctl, "mark")) { - return -1; - } - } -#endif - - return 0; -} - - -/* Create the command pipe to a new mtr-packet subprocess */ -static int net_command_pipe_open(struct mtr_ctl *ctl) -{ - int stdin_pipe[2]; - int stdout_pipe[2]; - pid_t child_pid; - int i; - char *mtr_packet_path; - - /* - We actually need two Unix pipes. One for stdin and one for - stdout on the new process. - */ - if (pipe(stdin_pipe) || pipe(stdout_pipe)) { - return errno; - } - - child_pid = fork(); - if (child_pid == -1) { - return errno; - } - - if (child_pid == 0) { - /* In the child process, attach our created pipes to stdin and stdout */ - dup2(stdin_pipe[0], STDIN_FILENO); - dup2(stdout_pipe[1], STDOUT_FILENO); - - /* Close all unnecessary fds */ - for (i = STDERR_FILENO + 1; i <= stdout_pipe[1]; i++) { - close(i); - } - - /* - Allow the MTR_PACKET environment variable to overrride - the path to the mtr-packet executable. This is necessary - for debugging changes for mtr-packet. - */ - mtr_packet_path = getenv("MTR_PACKET"); - if (mtr_packet_path == NULL) { - mtr_packet_path = "mtr-packet"; - } - - /* - First, try to execute using /usr/bin/env, because this - will search the PATH for mtr-packet - */ - execl("/usr/bin/env", "mtr-packet", mtr_packet_path, NULL); - - /* - If env fails to execute, try to use the MTR_PACKET environment as a - full path to the executable. This is necessary because on - Windows, minimal mtr binary distributions will lack /usr/bin/env. - - Note: A side effect is that an mtr-packet in the current directory - could be executed. This will only be the case if /usr/bin/env - doesn't exist. - */ - execl(mtr_packet_path, "mtr-packet", NULL); - - /* Both exec attempts failed, so nothing to do but exit */ - exit(1); - } else { - memset(&packet_command_pipe, 0, sizeof(struct packet_command_pipe_t)); - - /* - In the parent process, save the opposite ends of the pipes - attached as stdin and stdout in the child. - */ - packet_command_pipe.pid = child_pid; - packet_command_pipe.read_fd = stdout_pipe[0]; - packet_command_pipe.write_fd = stdin_pipe[1]; - - /* We don't need the child ends of the pipe open in the parent. */ - close(stdout_pipe[1]); - close(stdin_pipe[0]); - - /* - Check that we can communicate with the client. If we failed to - execute the mtr-packet binary, we will discover that here. - */ - if (net_check_feature(ctl, "send-probe")) { - error(EXIT_FAILURE, errno, "Failure to start mtr-packet"); - } - - if (net_packet_feature_check(ctl)) { - error(EXIT_FAILURE, errno, "Packet type unsupported"); - } - - /* We will need non-blocking reads from the child */ - set_fd_nonblock(packet_command_pipe.read_fd); - } - - return 0; -} - - extern int net_open(struct mtr_ctl *ctl, struct hostent * hostent) { int err; /* Spawn the mtr-packet child process */ - err = net_command_pipe_open(ctl); + err = open_command_pipe(ctl, &packet_command_pipe); if (err) { return err; } @@ -1174,10 +565,6 @@ extern void net_reset(struct mtr_ctl *ctl) for (at = 0; at < MaxSequence; at++) { sequence[at].transit = 0; - if (sequence[at].socket > 0) { - close(sequence[at].socket); - sequence[at].socket = 0; - } } } @@ -1186,17 +573,7 @@ extern void net_reset(struct mtr_ctl *ctl) /* Close the pipe to the packet generator process, and kill the process */ extern void net_close(void) { - int child_exit_value; - - if (packet_command_pipe.pid) { - close(packet_command_pipe.read_fd); - close(packet_command_pipe.write_fd); - - kill(packet_command_pipe.pid, SIGTERM); - waitpid(packet_command_pipe.pid, &child_exit_value, 0); - } - - memset(&packet_command_pipe, 0, sizeof(struct packet_command_pipe_t)); + close_command_pipe(&packet_command_pipe); } @@ -1274,20 +651,6 @@ extern void addrcpy( char * a, char * b, int family ) { } } -/* Add open sockets to select() */ -extern 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; - } - } -} - /* for GTK frontend */ extern void net_harvest_fds(struct mtr_ctl *ctl) { @@ -1298,6 +661,5 @@ extern void net_harvest_fds(struct mtr_ctl *ctl) FD_ZERO(&writefd); tv.tv_sec = 0; tv.tv_usec = 0; - net_add_fds(&writefd, &maxfd); select(maxfd, NULL, &writefd, NULL, &tv); } diff --git a/net.h b/net.h index 420ef54..e78d19c 100644 --- a/net.h +++ b/net.h @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef ENABLE_IPV6 #include #endif diff --git a/packet/command.c b/packet/command.c index 48cfdc9..0533938 100644 --- a/packet/command.c +++ b/packet/command.c @@ -97,6 +97,16 @@ const char *check_support( return check_protocol_support(net_state, IPPROTO_UDP); } + if (!strcmp(feature, "tcp")) { + return check_protocol_support(net_state, IPPROTO_TCP); + } + +#ifdef IPPROTO_SCTP + if (!strcmp(feature, "sctp")) { + return check_protocol_support(net_state, IPPROTO_SCTP); + } +#endif + #ifdef SO_MARK if (!strcmp(feature, "mark")) { return "ok"; @@ -155,6 +165,12 @@ bool decode_probe_argument( param->protocol = IPPROTO_ICMP; } else if (!strcmp(value, "udp")) { param->protocol = IPPROTO_UDP; + } else if (!strcmp(value, "tcp")) { + param->protocol = IPPROTO_TCP; +#ifdef IPPROTO_SCTP + } else if (!strcmp(value, "sctp")) { + param->protocol = IPPROTO_SCTP; +#endif } else { return false; } diff --git a/packet/construct_unix.c b/packet/construct_unix.c index 7cba815..055fd49 100644 --- a/packet/construct_unix.c +++ b/packet/construct_unix.c @@ -79,6 +79,27 @@ uint16_t length_byte_swap( } } +/* Construct a combined sockaddr from a source address and source port */ +static +void construct_addr_port( + struct sockaddr_storage *addr_with_port, + const struct sockaddr_storage *addr, + int port) +{ + struct sockaddr_in *addr4; + struct sockaddr_in6 *addr6; + + memcpy(addr_with_port, addr, sizeof(struct sockaddr_storage)); + + if (addr->ss_family == AF_INET6) { + addr6 = (struct sockaddr_in6 *)addr_with_port; + addr6->sin6_port = htons(port); + } else { + addr4 = (struct sockaddr_in *)addr_with_port; + addr4->sin_port = htons(port); + } +} + /* Construct a header for IP version 4 */ static void construct_ip4_header( @@ -110,6 +131,7 @@ void construct_ip4_header( static void construct_icmp4_header( const struct net_state_t *net_state, + int port, char *packet_buffer, int packet_size, const struct probe_param_t *param) @@ -124,7 +146,7 @@ void construct_icmp4_header( icmp->type = ICMP_ECHO; icmp->id = htons(getpid()); - icmp->sequence = htons(param->command_token); + icmp->sequence = htons(port); icmp->checksum = htons(compute_checksum(icmp, icmp_size)); } @@ -132,6 +154,7 @@ void construct_icmp4_header( static int construct_icmp6_packet( const struct net_state_t *net_state, + int port, char *packet_buffer, int packet_size, const struct probe_param_t *param) @@ -144,19 +167,19 @@ int construct_icmp6_packet( icmp->type = ICMP6_ECHO; icmp->id = htons(getpid()); - icmp->sequence = htons(param->command_token); + icmp->sequence = htons(port); return 0; } /* - Construct a header for UDP probes. We'll use the source port - as the command token, so that we can identify the probe when its - header is returned embedded in an ICMP reply. + Construct a header for UDP probes, using the port number associated + with the probe. */ static void construct_udp4_header( const struct net_state_t *net_state, + int port, char *packet_buffer, int packet_size, const struct probe_param_t *param) @@ -169,7 +192,7 @@ void construct_udp4_header( memset(udp, 0, sizeof(struct UDPHeader)); - udp->srcport = htons(param->command_token); + udp->srcport = htons(port); udp->dstport = htons(param->dest_port); udp->length = htons(udp_size); udp->checksum = 0; @@ -179,6 +202,7 @@ void construct_udp4_header( static int construct_udp6_packet( const struct net_state_t *net_state, + int port, char *packet_buffer, int packet_size, const struct probe_param_t *param) @@ -192,7 +216,7 @@ int construct_udp6_packet( memset(udp, 0, sizeof(struct UDPHeader)); - udp->srcport = htons(param->command_token); + udp->srcport = htons(port); udp->dstport = htons(param->dest_port); udp->length = htons(udp_size); udp->checksum = 0; @@ -211,6 +235,149 @@ int construct_udp6_packet( return 0; } +/* + Set the socket options for an outgoing stream protocol socket based on + the packet parameters. +*/ +static +int set_stream_socket_options( + int stream_socket, + const struct probe_param_t *param) +{ + int level; + int opt; + int reuse = 1; + + /* Allow binding to a local port previously in use */ +#ifdef SO_REUSEPORT + /* + FreeBSD wants SO_REUSEPORT in addition to SO_REUSEADDR to + bind to the same port + */ + if (setsockopt( + stream_socket, SOL_SOCKET, SO_REUSEPORT, + &reuse, sizeof(int)) == -1) { + + return -1; + } +#endif + + if (setsockopt( + stream_socket, SOL_SOCKET, SO_REUSEADDR, + &reuse, sizeof(int)) == -1) { + + return -1; + } + + /* Set the number of hops the probe will transit across */ + if (param->ip_version == 6) { + level = IPPROTO_IPV6; + opt = IPV6_UNICAST_HOPS; + } else { + level = IPPROTO_IP; + opt = IP_TTL; + } + + if (setsockopt( + stream_socket, level, opt, ¶m->ttl, sizeof(int)) == -1) { + + return -1; + } + + /* Set the "type of service" field of the IP header */ + if (param->ip_version == 6) { + level = IPPROTO_IPV6; + opt = IPV6_TCLASS; + } else { + level = IPPROTO_IP; + opt = IP_TOS; + } + + if (setsockopt( + stream_socket, level, opt, + ¶m->type_of_service, sizeof(int)) == -1) { + + return -1; + } + +#ifdef SO_MARK + if (param->routing_mark) { + if (setsockopt( + stream_socket, SOL_SOCKET, + SO_MARK, ¶m->routing_mark, sizeof(int))) { + return -1; + } + } +#endif + + return 0; +} + +/* + Open a TCP or SCTP socket, respecting the probe paramters as much as + we can, and use it as an outgoing probe. +*/ +static +int open_stream_socket( + const struct net_state_t *net_state, + int protocol, + int port, + const struct sockaddr_storage *src_sockaddr, + const struct sockaddr_storage *dest_sockaddr, + const struct probe_param_t *param) +{ + int stream_socket; + int addr_len; + struct sockaddr_storage dest_port_addr; + struct sockaddr_storage src_port_addr; + + if (param->ip_version == 6) { + stream_socket = socket(AF_INET6, SOCK_STREAM, protocol); + addr_len = sizeof(struct sockaddr_in6); + } else if (param->ip_version == 4) { + stream_socket = socket(AF_INET, SOCK_STREAM, protocol); + addr_len = sizeof(struct sockaddr_in); + } else { + errno = EINVAL; + return -1; + } + + if (stream_socket == -1) { + return -1; + } + + set_socket_nonblocking(stream_socket); + + if (set_stream_socket_options(stream_socket, param)) { + close(stream_socket); + return -1; + } + + /* + Bind to a known local port so we can identify which probe + causes a TTL expiration. + */ + construct_addr_port(&src_port_addr, src_sockaddr, port); + if (bind(stream_socket, (struct sockaddr *)&src_port_addr, addr_len)) { + close(stream_socket); + return -1; + } + + /* Attempt a connection */ + construct_addr_port(&dest_port_addr, dest_sockaddr, param->dest_port); + if (connect( + stream_socket, (struct sockaddr *)&dest_port_addr, addr_len)) { + + /* EINPROGRESS simply means the connection is in progress */ + if (errno != EINPROGRESS) { + close(stream_socket); + return -1; + } + } + + return stream_socket; +} + /* Determine the size of the constructed packet based on the packet parameters. This is the amount of space the packet *we* construct @@ -225,6 +392,16 @@ int compute_packet_size( { int packet_size; + if (param->protocol == IPPROTO_TCP) { + return 0; + } + +#ifdef IPPROTO_SCTP + if (param->protocol == IPPROTO_SCTP) { + return 0; + } +#endif + /* Start by determining the full size, including omitted headers */ if (param->ip_version == 6) { packet_size = sizeof(struct IP6Header); @@ -264,27 +441,54 @@ int compute_packet_size( } /* Construct a packet for an IPv4 probe */ +static int construct_ip4_packet( const struct net_state_t *net_state, + int *packet_socket, + int port, char *packet_buffer, int packet_size, const struct sockaddr_storage *src_sockaddr, const struct sockaddr_storage *dest_sockaddr, const struct probe_param_t *param) { - construct_ip4_header( - net_state, packet_buffer, packet_size, - src_sockaddr, dest_sockaddr, param); - - if (param->protocol == IPPROTO_ICMP) { - construct_icmp4_header( - net_state, packet_buffer, packet_size, param); - } else if (param->protocol == IPPROTO_UDP) { - construct_udp4_header( - net_state, packet_buffer, packet_size, param); + int send_socket = net_state->platform.ip4_send_socket; + bool is_stream_protocol = false; + + if (param->protocol == IPPROTO_TCP) { + is_stream_protocol = true; +#ifdef IPPROTO_SCTP + } else if (param->protocol == IPPROTO_SCTP) { + is_stream_protocol = true; +#endif } else { - errno = EINVAL; - return -1; + construct_ip4_header( + net_state, packet_buffer, packet_size, + src_sockaddr, dest_sockaddr, param); + + if (param->protocol == IPPROTO_ICMP) { + construct_icmp4_header( + net_state, port, packet_buffer, packet_size, param); + } else if (param->protocol == IPPROTO_UDP) { + construct_udp4_header( + net_state, port, packet_buffer, packet_size, param); + } else { + errno = EINVAL; + return -1; + } + } + + if (is_stream_protocol) { + send_socket = open_stream_socket( + net_state, param->protocol, port, + src_sockaddr, dest_sockaddr, param); + + if (send_socket == -1) { + return -1; + } + + *packet_socket = send_socket; + return 0; } /* @@ -300,8 +504,8 @@ int construct_ip4_packet( #ifdef SO_MARK if (param->routing_mark) { if (setsockopt( - net_state->platform.ip4_send_socket, - SOL_SOCKET, SO_MARK, ¶m->routing_mark, sizeof(int))) { + send_socket, SOL_SOCKET, + SO_MARK, ¶m->routing_mark, sizeof(int))) { return -1; } } @@ -311,8 +515,11 @@ int construct_ip4_packet( } /* Construct a packet for an IPv6 probe */ +static int construct_ip6_packet( const struct net_state_t *net_state, + int *packet_socket, + int port, char *packet_buffer, int packet_size, const struct sockaddr_storage *src_sockaddr, @@ -320,19 +527,26 @@ int construct_ip6_packet( const struct probe_param_t *param) { int send_socket; + bool is_stream_protocol = false; - if (param->protocol == IPPROTO_ICMP) { + if (param->protocol == IPPROTO_TCP) { + is_stream_protocol = true; +#ifdef IPPROTO_SCTP + } else if (param->protocol == IPPROTO_SCTP) { + is_stream_protocol = true; +#endif + } else if (param->protocol == IPPROTO_ICMP) { send_socket = net_state->platform.icmp6_send_socket; if (construct_icmp6_packet( - net_state, packet_buffer, packet_size, param)) { + net_state, port, packet_buffer, packet_size, param)) { return -1; } } else if (param->protocol == IPPROTO_UDP) { send_socket = net_state->platform.udp6_send_socket; if (construct_udp6_packet( - net_state, packet_buffer, packet_size, param)) { + net_state, port, packet_buffer, packet_size, param)) { return -1; } } else { @@ -340,6 +554,19 @@ int construct_ip6_packet( return -1; } + if (is_stream_protocol) { + send_socket = open_stream_socket( + net_state, param->protocol, port, + src_sockaddr, dest_sockaddr, param); + + if (send_socket == -1) { + return -1; + } + + *packet_socket = send_socket; + return 0; + } + /* The traffic class in IPv6 is analagous to ToS in IPv4 */ if (setsockopt( send_socket, IPPROTO_IPV6, @@ -370,6 +597,8 @@ int construct_ip6_packet( /* Construct a probe packet based on the probe parameters */ int construct_packet( const struct net_state_t *net_state, + int *packet_socket, + int port, char *packet_buffer, int packet_buffer_size, const struct sockaddr_storage *dest_sockaddr, @@ -396,13 +625,13 @@ int construct_packet( if (param->ip_version == 6) { if (construct_ip6_packet( - net_state, packet_buffer, packet_size, + net_state, packet_socket, port, packet_buffer, packet_size, &src_sockaddr, dest_sockaddr, param)) { return -1; } } else if (param->ip_version == 4) { if (construct_ip4_packet( - net_state, packet_buffer, packet_size, + net_state, packet_socket, port, packet_buffer, packet_size, &src_sockaddr, dest_sockaddr, param)) { return -1; } diff --git a/packet/construct_unix.h b/packet/construct_unix.h index 8ed6486..976c8f1 100644 --- a/packet/construct_unix.h +++ b/packet/construct_unix.h @@ -23,6 +23,8 @@ int construct_packet( const struct net_state_t *net_state, + int *packet_socket, + int port, char *packet_buffer, int packet_buffer_size, const struct sockaddr_storage *dest_sockaddr, diff --git a/packet/deconstruct_unix.c b/packet/deconstruct_unix.c index c3a087d..cbef815 100644 --- a/packet/deconstruct_unix.c +++ b/packet/deconstruct_unix.c @@ -23,26 +23,6 @@ #include "protocols.h" -/* - Compute the round trip time of a just-received probe and pass it - to the platform agnostic response handling. -*/ -static -void receive_probe( - struct probe_t *probe, - int icmp_type, - const struct sockaddr_storage *remote_addr, - struct timeval timestamp) -{ - unsigned int round_trip_us; - - round_trip_us = - (timestamp.tv_sec - probe->platform.departure_time.tv_sec) * 1000000 + - timestamp.tv_usec - probe->platform.departure_time.tv_usec; - - respond_to_probe(probe, icmp_type, remote_addr, round_trip_us); -} - /* Given an ICMP id + ICMP sequence, find the match probe we've transmitted and if found, respond to the command which sent it @@ -51,7 +31,7 @@ static void find_and_receive_probe( struct net_state_t *net_state, const struct sockaddr_storage *remote_addr, - struct timeval timestamp, + struct timeval *timestamp, int icmp_type, int protocol, int icmp_id, @@ -79,14 +59,20 @@ void handle_inner_ip4_packet( int icmp_result, const struct IPHeader *ip, int packet_length, - struct timeval timestamp) + struct timeval *timestamp) { const int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader); const int ip_udp_size = sizeof(struct IPHeader) + sizeof(struct UDPHeader); + const int ip_tcp_size = + sizeof(struct IPHeader) + sizeof(struct TCPHeader); + const int ip_sctp_size = + sizeof(struct IPHeader) + sizeof(struct SCTPHeader); const struct ICMPHeader *icmp; const struct UDPHeader *udp; + const struct TCPHeader *tcp; + const struct SCTPHeader *sctp; if (ip->protocol == IPPROTO_ICMP) { if (packet_length < ip_icmp_size) { @@ -108,6 +94,28 @@ void handle_inner_ip4_packet( find_and_receive_probe( net_state, remote_addr, timestamp, icmp_result, IPPROTO_UDP, 0, udp->srcport); + } else if (ip->protocol == IPPROTO_TCP) { + if (packet_length < ip_tcp_size) { + return; + } + + tcp = (struct TCPHeader *)(ip + 1); + + find_and_receive_probe( + net_state, remote_addr, timestamp, icmp_result, + IPPROTO_TCP, 0, tcp->srcport); +#ifdef IPPROTO_SCTP + } else if (ip->protocol == IPPROTO_SCTP) { + if (packet_length < ip_sctp_size) { + return; + } + + sctp = (struct SCTPHeader *)(ip + 1); + + find_and_receive_probe( + net_state, remote_addr, timestamp, icmp_result, + IPPROTO_SCTP, 0, sctp->srcport); +#endif } } @@ -122,14 +130,20 @@ void handle_inner_ip6_packet( int icmp_result, const struct IP6Header *ip, int packet_length, - struct timeval timestamp) + struct timeval *timestamp) { const int ip_icmp_size = sizeof(struct IP6Header) + sizeof(struct ICMPHeader); const int ip_udp_size = sizeof(struct IP6Header) + sizeof(struct UDPHeader); + const int ip_tcp_size = + sizeof(struct IP6Header) + sizeof(struct TCPHeader); + const int ip_sctp_size = + sizeof(struct IPHeader) + sizeof(struct SCTPHeader); const struct ICMPHeader *icmp; const struct UDPHeader *udp; + const struct TCPHeader *tcp; + const struct SCTPHeader *sctp; if (ip->protocol == IPPROTO_ICMPV6) { if (packet_length < ip_icmp_size) { @@ -151,6 +165,27 @@ void handle_inner_ip6_packet( find_and_receive_probe( net_state, remote_addr, timestamp, icmp_result, IPPROTO_UDP, 0, udp->srcport); + } else if (ip->protocol == IPPROTO_TCP) { + if (packet_length < ip_tcp_size) { + return; + } + + tcp = (struct TCPHeader *)(ip + 1); + find_and_receive_probe( + net_state, remote_addr, timestamp, icmp_result, + IPPROTO_TCP, 0, tcp->srcport); +#ifdef IPPROTO_SCTP + } else if (ip->protocol == IPPROTO_SCTP) { + if (packet_length < ip_sctp_size) { + return; + } + + sctp = (struct SCTPHeader *)(ip + 1); + + find_and_receive_probe( + net_state, remote_addr, timestamp, icmp_result, + IPPROTO_SCTP, 0, sctp->srcport); +#endif } } @@ -164,7 +199,7 @@ void handle_received_icmp4_packet( const struct sockaddr_storage *remote_addr, const struct ICMPHeader *icmp, int packet_length, - struct timeval timestamp) + struct timeval *timestamp) { const int icmp_ip_size = sizeof(struct ICMPHeader) + sizeof(struct IPHeader); @@ -223,7 +258,7 @@ void handle_received_icmp6_packet( const struct sockaddr_storage *remote_addr, const struct ICMPHeader *icmp, int packet_length, - struct timeval timestamp) + struct timeval *timestamp) { const int icmp_ip_size = sizeof(struct ICMPHeader) + sizeof(struct IP6Header); @@ -266,7 +301,7 @@ void handle_received_ip4_packet( const struct sockaddr_storage *remote_addr, const void *packet, int packet_length, - struct timeval timestamp) + struct timeval *timestamp) { const int ip_icmp_size = sizeof(struct IPHeader) + sizeof(struct ICMPHeader); @@ -301,7 +336,7 @@ void handle_received_ip6_packet( const struct sockaddr_storage *remote_addr, const void *packet, int packet_length, - struct timeval timestamp) + struct timeval *timestamp) { const struct ICMPHeader *icmp; diff --git a/packet/deconstruct_unix.h b/packet/deconstruct_unix.h index 616d19c..33dd0ec 100644 --- a/packet/deconstruct_unix.h +++ b/packet/deconstruct_unix.h @@ -26,20 +26,20 @@ typedef void (*received_packet_func_t)( const struct sockaddr_storage *remote_addr, const void *packet, int packet_length, - struct timeval timestamp); + struct timeval *timestamp); void handle_received_ip4_packet( struct net_state_t *net_state, const struct sockaddr_storage *remote_addr, const void *packet, int packet_length, - struct timeval timestamp); + struct timeval *timestamp); void handle_received_ip6_packet( struct net_state_t *net_state, const struct sockaddr_storage *remote_addr, const void *packet, int packet_length, - struct timeval timestamp); + struct timeval *timestamp); #endif diff --git a/packet/probe.c b/packet/probe.c index 636585e..ea364de 100644 --- a/packet/probe.c +++ b/packet/probe.c @@ -97,6 +97,8 @@ struct probe_t *alloc_probe( probe->used = true; probe->token = token; + platform_alloc_probe(net_state, probe); + return probe; } } @@ -166,7 +168,7 @@ struct probe_t *find_probe( for (i = 0; i < MAX_PROBES; i++) { probe = &net_state->probes[i]; - if (probe->used && htons(probe->token) == icmp_sequence) { + if (probe->used && htons(probe->port) == icmp_sequence) { return probe; } } diff --git a/packet/probe.h b/packet/probe.h index 17b4d70..47ddd22 100644 --- a/packet/probe.h +++ b/packet/probe.h @@ -79,9 +79,18 @@ struct probe_t /* true if this entry is in use */ bool used; + /* + The port number to use when binding sockets for this probe. + Also the ICMP sequence ID used to identify the probe. + */ + int port; + /* Command token of the probe request */ int token; + /* The address being probed */ + struct sockaddr_storage remote_addr; + /* Platform specific probe tracking */ struct probe_platform_t platform; }; @@ -134,6 +143,10 @@ struct probe_t *alloc_probe( struct net_state_t *net_state, int token); +void platform_alloc_probe( + struct net_state_t *net_state, + struct probe_t *probe); + void platform_free_probe( struct probe_t *probe); diff --git a/packet/probe_cygwin.c b/packet/probe_cygwin.c index cbf18a0..417fce7 100644 --- a/packet/probe_cygwin.c +++ b/packet/probe_cygwin.c @@ -60,6 +60,13 @@ bool is_protocol_supported( return false; } +/* No special action is required for Cygwin on probe allocation */ +void platform_alloc_probe( + struct net_state_t *net_state, + struct probe_t *probe) +{ +} + /* Free the reply buffer when the probe is freed */ void platform_free_probe( struct probe_t *probe) diff --git a/packet/probe_unix.c b/packet/probe_unix.c index d1bd7cb..df245e4 100644 --- a/packet/probe_unix.c +++ b/packet/probe_unix.c @@ -28,6 +28,7 @@ #include #include "platform.h" +#include "protocols.h" #include "construct_unix.h" #include "deconstruct_unix.h" #include "timeval.h" @@ -104,7 +105,8 @@ void check_length_order( net_state->platform.ip_length_host_order = false; packet_size = construct_packet( - net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, ¶m); + net_state, NULL, MIN_PORT, + packet, PACKET_BUFFER_SIZE, &dest_sockaddr, ¶m); if (packet_size < 0) { perror("Unable to send to localhost"); exit(1); @@ -120,7 +122,8 @@ void check_length_order( net_state->platform.ip_length_host_order = true; packet_size = construct_packet( - net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, ¶m); + net_state, NULL, MIN_PORT, + packet, PACKET_BUFFER_SIZE, &dest_sockaddr, ¶m); if (packet_size < 0) { perror("Unable to send to localhost"); exit(1); @@ -134,8 +137,29 @@ void check_length_order( } } -/* Set a socket to non-blocking mode */ +/* + Check to see if SCTP is support. We can't just rely on checking + if IPPROTO_SCTP is defined, because while that is necessary, + MacOS as of "Sierra" defines IPPROTO_SCTP, but creating an SCTP + socket results in an error. +*/ static +void check_sctp_support( + struct net_state_t *net_state) +{ + int sctp_socket; + +#ifdef IPPROTO_SCTP + sctp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP); + if (sctp_socket != -1) { + close(sctp_socket); + + net_state->platform.sctp_support = true; + } +#endif +} + +/* Set a socket to non-blocking mode */ void set_socket_nonblocking( int socket) { @@ -237,6 +261,8 @@ void init_net_state_privileged( { memset(net_state, 0, sizeof(struct net_state_t)); + net_state->platform.next_port = MIN_PORT; + open_ip4_sockets(net_state); open_ip6_sockets(net_state); } @@ -252,6 +278,7 @@ void init_net_state( set_socket_nonblocking(net_state->platform.ip6_recv_socket); check_length_order(net_state); + check_sctp_support(net_state); } /* Returns true if we can transmit probes using the specified protocol */ @@ -267,24 +294,36 @@ bool is_protocol_supported( return true; } + if (protocol == IPPROTO_TCP) { + return true; + } + +#ifdef IPPROTO_SCTP + if (protocol == IPPROTO_SCTP) { + return net_state->platform.sctp_support; + } +#endif + return false; } /* Report an error during send_probe based on the errno value */ static void report_packet_error( - const struct probe_param_t *param) + int command_token) { if (errno == EINVAL) { - printf("%d invalid-argument\n", param->command_token); + printf("%d invalid-argument\n", command_token); } else if (errno == ENETDOWN) { - printf("%d network-down\n", param->command_token); + printf("%d network-down\n", command_token); } else if (errno == ENETUNREACH) { - printf("%d no-route\n", param->command_token); + printf("%d no-route\n", command_token); } else if (errno == EPERM) { - printf("%d permission-denied\n", param->command_token); + printf("%d permission-denied\n", command_token); + } else if (errno == EADDRINUSE) { + printf("%d address-in-use\n", command_token); } else { - printf("%d unexpected-error errno %d\n", param->command_token, errno); + printf("%d unexpected-error errno %d\n", command_token, errno); } } @@ -294,53 +333,115 @@ void send_probe( const struct probe_param_t *param) { char packet[PACKET_BUFFER_SIZE]; - struct sockaddr_storage dest_sockaddr; struct probe_t *probe; int packet_size; - if (decode_dest_addr(param, &dest_sockaddr)) { - printf("%d invalid-argument\n", param->command_token); - return; - } - - packet_size = construct_packet( - net_state, packet, PACKET_BUFFER_SIZE, &dest_sockaddr, param); - if (packet_size < 0) { - report_packet_error(param); - return; - } - probe = alloc_probe(net_state, param->command_token); if (probe == NULL) { printf("%d probes-exhausted\n", param->command_token); return; } - /* - We get the time just before the send call to keep the timing - as tight as possible. - */ + if (decode_dest_addr(param, &probe->remote_addr)) { + printf("%d invalid-argument\n", param->command_token); + free_probe(probe); + return; + } + if (gettimeofday(&probe->platform.departure_time, NULL)) { perror("gettimeofday failure"); exit(1); } - if (send_packet( - net_state, param, packet, packet_size, &dest_sockaddr) == -1) { + packet_size = construct_packet( + net_state, &probe->platform.socket, probe->port, + packet, PACKET_BUFFER_SIZE, &probe->remote_addr, param); + + if (packet_size < 0) { + /* + When using a stream protocol, FreeBSD will return ECONNREFUSED + when connecting to localhost if the port doesn't exist, + even if the socket is non-blocking, so we should be + prepared for that. + */ + if (errno == ECONNREFUSED) { + receive_probe(probe, ICMP_ECHOREPLY, &probe->remote_addr, NULL); + } else { + report_packet_error(param->command_token); + free_probe(probe); + } - report_packet_error(param); - free_probe(probe); return; } + if (packet_size > 0) { + if (send_packet( + net_state, param, + packet, packet_size, &probe->remote_addr) == -1) { + + report_packet_error(param->command_token); + free_probe(probe); + return; + } + } + probe->platform.timeout_time = probe->platform.departure_time; probe->platform.timeout_time.tv_sec += param->timeout; } -/* Nothing is needed for freeing Unix probes */ +/* When allocating a probe, assign it a unique port number */ +void platform_alloc_probe( + struct net_state_t *net_state, + struct probe_t *probe) +{ + probe->port = net_state->platform.next_port++; + + if (net_state->platform.next_port > MAX_PORT) { + net_state->platform.next_port = MIN_PORT; + } +} + +/* + When freeing the probe, close the socket for the probe, + if one has been opened +*/ void platform_free_probe( struct probe_t *probe) { + if (probe->platform.socket) { + close(probe->platform.socket); + probe->platform.socket = 0; + } +} + +/* + Compute the round trip time of a just-received probe and pass it + to the platform agnostic response handling. +*/ +void receive_probe( + struct probe_t *probe, + int icmp_type, + const struct sockaddr_storage *remote_addr, + struct timeval *timestamp) +{ + unsigned int round_trip_us; + struct timeval *departure_time = &probe->platform.departure_time; + struct timeval now; + + if (timestamp == NULL) { + if (gettimeofday(&now, NULL)) { + perror("gettimeofday failure"); + exit(1); + } + + timestamp = &now; + } + + round_trip_us = + (timestamp->tv_sec - departure_time->tv_sec) * 1000000 + + timestamp->tv_usec - departure_time->tv_usec; + + respond_to_probe(probe, icmp_type, remote_addr, round_trip_us); } /* @@ -348,7 +449,7 @@ void platform_free_probe( handle any responses to probes we have preivously sent. */ static -void receive_replies_from_socket( +void receive_replies_from_icmp_socket( struct net_state_t *net_state, int socket, received_packet_func_t handle_received_packet) @@ -397,22 +498,123 @@ void receive_replies_from_socket( } handle_received_packet( - net_state, &remote_addr, packet, packet_length, timestamp); + net_state, &remote_addr, packet, packet_length, ×tamp); } +} +/* + Attempt to send using the probe's socket, in order to check whether + the connection has completed, for stream oriented protocols such as + TCP. +*/ +static +void receive_replies_from_probe_socket( + struct net_state_t *net_state, + struct probe_t *probe) +{ + int probe_socket; + struct timeval zero_time; + int err; + int err_length = sizeof(int); + fd_set write_set; + + probe_socket = probe->platform.socket; + if (!probe_socket) { + return; + } + + FD_ZERO(&write_set); + FD_SET(probe_socket, &write_set); + + zero_time.tv_sec = 0; + zero_time.tv_usec = 0; + + if (select(probe_socket + 1, NULL, &write_set, NULL, &zero_time) == -1) { + if (errno == EAGAIN) { + return; + } else { + perror("probe socket select error"); + exit(1); + } + } + + /* + If the socket is writable, the connection attempt has completed. + */ + if (!FD_ISSET(probe_socket, &write_set)) { + return; + } + + if (getsockopt(probe_socket, SOL_SOCKET, SO_ERROR, &err, &err_length)) { + perror("probe socket SO_ERROR"); + exit(1); + } + + /* + If the connection complete successfully, or was refused, we can + assume our probe arrived at the destination. + */ + if (!err || err == ECONNREFUSED) { + receive_probe(probe, ICMP_ECHOREPLY, &probe->remote_addr, NULL); + } else { + errno = err; + report_packet_error(probe->token); + free_probe(probe); + } } /* Check both the IPv4 and IPv6 sockets for incoming packets */ void receive_replies( struct net_state_t *net_state) { - receive_replies_from_socket( + int i; + struct probe_t *probe; + + receive_replies_from_icmp_socket( net_state, net_state->platform.ip4_recv_socket, handle_received_ip4_packet); - receive_replies_from_socket( + receive_replies_from_icmp_socket( net_state, net_state->platform.ip6_recv_socket, handle_received_ip6_packet); + + for (i = 0; i < MAX_PROBES; i++) { + probe = &net_state->probes[i]; + + if (probe->used) { + receive_replies_from_probe_socket(net_state, probe); + } + } +} + +/* + Put all of our probe sockets in the read set used for an upcoming + select so we can wake when any of them become readable. +*/ +int gather_probe_sockets( + const struct net_state_t *net_state, + fd_set *write_set) +{ + int i; + int probe_socket; + int nfds; + const struct probe_t *probe; + + nfds = 0; + + for (i = 0; i < MAX_PROBES; i++) { + probe = &net_state->probes[i]; + probe_socket = probe->platform.socket; + + if (probe->used && probe_socket) { + FD_SET(probe_socket, write_set); + if (probe_socket >= nfds) { + nfds = probe_socket + 1; + } + } + } + + return nfds; } /* diff --git a/packet/probe_unix.h b/packet/probe_unix.h index 75c9775..51bba8b 100644 --- a/packet/probe_unix.h +++ b/packet/probe_unix.h @@ -19,15 +19,21 @@ #ifndef PROBE_UNIX_H #define PROBE_UNIX_H +/* The range of local port numbers to use for probes */ +#define MIN_PORT 33000 +#define MAX_PORT 65535 + /* We need to track the transmission and timeouts on Unix systems */ struct probe_platform_t { + /* The socket for the outgoing connection (used by TCP probes) */ + int socket; + /* The time at which the probe is considered lost */ struct timeval timeout_time; /* The time at which the probe was sent */ struct timeval departure_time; - }; /* We'll use rack sockets to send and recieve probes on Unix systems */ @@ -53,6 +59,28 @@ struct net_state_platform_t (as opposed to network order) */ bool ip_length_host_order; + + /* true if the operating system supports SCTP sockets */ + bool sctp_support; + + /* The next port number to use when creating a new probe */ + int next_port; }; +struct net_state_t; +struct probe_t; + +void set_socket_nonblocking( + int socket); + +void receive_probe( + struct probe_t *probe, + int icmp_type, + const struct sockaddr_storage *remote_addr, + struct timeval *timestamp); + +int gather_probe_sockets( + const struct net_state_t *net_state, + fd_set *write_set); + #endif diff --git a/packet/wait_unix.c b/packet/wait_unix.c index 2fd23ef..781546b 100644 --- a/packet/wait_unix.c +++ b/packet/wait_unix.c @@ -26,38 +26,65 @@ #include /* - Sleep until we receive a new probe response, a new command on the - command stream, or a probe timeout. On Unix systems, this means - we use select to wait on file descriptors for the command stream - and the raw recieve socket. + Gather all the file descriptors which should wake our select call when + they become readable. */ -void wait_for_activity( - struct command_buffer_t *command_buffer, - struct net_state_t *net_state) +static +int gather_read_fds( + const struct command_buffer_t *command_buffer, + const struct net_state_t *net_state, + fd_set *read_set, + fd_set *write_set) { int nfds; - fd_set read_set; - struct timeval probe_timeout; - struct timeval *select_timeout; - int ready_count; - int command_stream = command_buffer->command_stream; + int probe_nfds; int ip4_socket = net_state->platform.ip4_recv_socket; int ip6_socket = net_state->platform.ip6_recv_socket; + int command_stream = command_buffer->command_stream; + + FD_ZERO(read_set); + FD_ZERO(write_set); - FD_ZERO(&read_set); - FD_SET(command_stream, &read_set); + FD_SET(command_stream, read_set); nfds = command_stream + 1; - FD_SET(ip4_socket, &read_set); + FD_SET(ip4_socket, read_set); if (ip4_socket >= nfds) { nfds = ip4_socket + 1; } - FD_SET(ip6_socket, &read_set); + FD_SET(ip6_socket, read_set); if (ip6_socket >= nfds) { nfds = ip6_socket + 1; } + probe_nfds = gather_probe_sockets(net_state, write_set); + if (probe_nfds > nfds) { + nfds = probe_nfds; + } + + return nfds; +} + +/* + Sleep until we receive a new probe response, a new command on the + command stream, or a probe timeout. On Unix systems, this means + we use select to wait on file descriptors for the command stream + and the raw recieve socket. +*/ +void wait_for_activity( + struct command_buffer_t *command_buffer, + struct net_state_t *net_state) +{ + int nfds; + fd_set read_set; + fd_set write_set; + struct timeval probe_timeout; + struct timeval *select_timeout; + int ready_count; + + nfds = gather_read_fds(command_buffer, net_state, &read_set, &write_set); + while (true) { select_timeout = NULL; @@ -67,7 +94,8 @@ void wait_for_activity( select_timeout = &probe_timeout; } - ready_count = select(nfds, &read_set, NULL, NULL, select_timeout); + ready_count = select( + nfds, &read_set, &write_set, NULL, select_timeout); /* If we didn't have an error, either one of our descriptors is diff --git a/select.c b/select.c index 2c0bfd7..c9de0fa 100644 --- a/select.c +++ b/select.c @@ -102,9 +102,6 @@ extern void select_loop(struct mtr_ctl *ctl){ FD_SET(netfd, &readfd); if(netfd >= maxfd) maxfd = netfd + 1; - if (ctl->mtrtype == IPPROTO_TCP) - net_add_fds(&writefd, &maxfd); - do { if(anyset || paused) { /* Set timeout to 0.1s. diff --git a/test/mtrpacket.py b/test/mtrpacket.py index 029836c..be21dcd 100644 --- a/test/mtrpacket.py +++ b/test/mtrpacket.py @@ -152,7 +152,6 @@ class PacketListen(object): def __init__(self, *args): self.process_args = list(args) # type: List[unicode] self.listen_process = None # type: subprocess.Popen - self.token = None # type: unicode self.attrib = None # type: Dict[unicode, unicode] def __enter__(self): @@ -168,8 +167,6 @@ class PacketListen(object): if status != 'status listening\n': raise PacketListenError('unexpected status') - self.token = str(self.listen_process.pid) - return self def __exit__(self, exc_type, exc_value, traceback): diff --git a/test/packet_listen.c b/test/packet_listen.c index 2b0ab90..eedda4e 100644 --- a/test/packet_listen.c +++ b/test/packet_listen.c @@ -27,6 +27,12 @@ #define MAX_PACKET_SIZE 9000 +/* + The first probe sent by mtr-packet will have this sequence number, + so wait for an ICMP packet with this sequence ID. +*/ +#define SEQUENCE_NUM 33000 + /* Check to see if the packet we've recieved is intended for this test process. We expected the ICMP sequence number to be equal to our @@ -48,7 +54,7 @@ bool is_packet_for_us4( ip = (struct IPHeader *)packet; icmp = (struct ICMPHeader *)(ip + 1); - expected_sequence = htons(getpid()); + expected_sequence = htons(SEQUENCE_NUM); if (icmp->sequence == expected_sequence) { return true; } @@ -73,7 +79,7 @@ bool is_packet_for_us6( icmp = (struct ICMPHeader *)packet; - expected_sequence = htons(getpid()); + expected_sequence = htons(SEQUENCE_NUM); if (icmp->sequence == expected_sequence) { return true; } diff --git a/test/param.py b/test/param.py index ae03fc2..7db108c 100755 --- a/test/param.py +++ b/test/param.py @@ -33,7 +33,7 @@ class TestParameters(mtrpacket.MtrPacketTest): 'Test probes sent with an explicit packet size' with mtrpacket.PacketListen('-4') as listen: - cmd = listen.token + ' send-probe ip-4 127.0.0.1 size 512' + cmd = '20 send-probe ip-4 127.0.0.1 size 512' self.write_command(cmd) @@ -43,7 +43,7 @@ class TestParameters(mtrpacket.MtrPacketTest): 'Test probes are filled with the requested bit pattern' with mtrpacket.PacketListen('-4') as listen: - cmd = listen.token + ' send-probe ip-4 127.0.0.1 bitpattern 44' + cmd = '20 send-probe ip-4 127.0.0.1 bitpattern 44' self.write_command(cmd) @@ -53,7 +53,7 @@ class TestParameters(mtrpacket.MtrPacketTest): 'Test setting the TOS field' with mtrpacket.PacketListen('-4') as listen: - cmd = listen.token + ' send-probe ip-4 127.0.0.1 tos 62' + cmd = '20 send-probe ip-4 127.0.0.1 tos 62' self.write_command(cmd) @@ -70,7 +70,7 @@ class TestIPv6Parameters(mtrpacket.MtrPacketTest): with mtrpacket.PacketListen('-6') as listen: param = 'size 256 bitpattern 51 tos 77' - cmd = listen.token + ' send-probe ip-6 ::1 ' + param + cmd = '20 send-probe ip-6 ::1 ' + param self.write_command(cmd) diff --git a/test/probe.py b/test/probe.py index 02c4853..a31e09c 100755 --- a/test/probe.py +++ b/test/probe.py @@ -20,6 +20,7 @@ '''Test sending probes and receiving respones.''' import socket +import sys import time import unittest @@ -42,6 +43,80 @@ def resolve_ipv6_address(hostname): # type: (str) -> str raise LookupError(hostname) +def check_feature(test, feature): + 'Check for support for a particular feature with mtr-packet' + + check_cmd = '70 check-support feature ' + feature + test.write_command(check_cmd) + + reply = test.parse_reply() + test.assertEqual(reply.command_name, 'feature-support') + test.assertIn('support', reply.argument) + + if reply.argument['support'] != 'ok': + return False + + return True + + +def test_basic_remote_probe(test, ip_version, protocol): + 'Test a probe to a remote host with a TTL of 1' + + protocol_str = 'protocol ' + protocol + if ip_version == 6: + address_str = 'ip-6 ' + resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST) + elif ip_version == 4: + address_str = 'ip-4 8.8.8.8' + else: + raise ValueError(ip_version) + + cmd = '60 send-probe ' + \ + protocol_str + ' ' + address_str + ' port 164 ttl 1' + test.write_command(cmd) + + reply = test.parse_reply() + test.assertEqual(reply.command_name, 'ttl-expired') + + +def test_basic_local_probe(test, ip_version, protocol): + 'Test a probe to a closed port on localhost' + + protocol_str = 'protocol ' + protocol + if ip_version == 6: + address_str = 'ip-6 ::1' + elif ip_version == 4: + address_str = 'ip-4 127.0.0.1' + + cmd = '61 send-probe ' + \ + protocol_str + ' ' + address_str + ' port 164' + test.write_command(cmd) + + reply = test.parse_reply() + test.assertEqual(reply.command_name, 'reply') + + if ip_version == 6: + test.assertIn('ip-6', reply.argument) + test.assertEqual(reply.argument['ip-6'], '::1') + elif ip_version == 4: + test.assertIn('ip-4', reply.argument) + test.assertEqual(reply.argument['ip-4'], '127.0.0.1') + + +def test_basic_probe(test, ip_version, protocol): + # type: (mtrpacket.MtrPacketTest, int, unicode) -> None + + '''Test a probe with TTL expiration and a probe which reaches its + destination with a particular protocol.''' + + if not check_feature(test, protocol): + err_str = 'Skipping ' + protocol + ' test due to no support\n' + sys.stderr.write(err_str.encode('utf-8')) + return + + test_basic_remote_probe(test, ip_version, protocol) + test_basic_local_probe(test, ip_version, protocol) + + class TestProbeICMPv4(mtrpacket.MtrPacketTest): '''Test sending probes using IP version 4''' @@ -241,40 +316,59 @@ class TestProbeUDP(mtrpacket.MtrPacketTest): def test_udp_v4(self): 'Test IPv4 UDP probes' - cmd = '60 send-probe protocol udp ip-4 8.8.8.8 port 164 ttl 1' - self.write_command(cmd) + test_basic_probe(self, 4, 'udp') - reply = self.parse_reply() - self.assertEqual(reply.command_name, 'ttl-expired') + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_udp_v6(self): + 'Test IPv6 UDP probes' + + test_basic_probe(self, 6, 'udp') + + +class TestProbeTCP(mtrpacket.MtrPacketTest): + 'Test TCP probe support' - cmd = '61 send-probe protocol udp ip-4 127.0.0.1 port 164' + def test_tcp_v4(self): + '''Test IPv4 TCP probes, with TTL expiration, to a refused port + and to an open port''' + + test_basic_probe(self, 4, 'tcp') + + # Probe a local port assumed to be open (ssh) + cmd = '80 send-probe ip-4 127.0.0.1 protocol tcp port 22' self.write_command(cmd) reply = self.parse_reply() self.assertEqual(reply.command_name, 'reply') - self.assertIn('ip-4', reply.argument) - self.assertEqual(reply.argument['ip-4'], '127.0.0.1') @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') - def test_udp_v6(self): - 'Test IPv6 UDP probes' + def test_tcp_v6(self): + 'Test IPv6 TCP probes' - test_addr = resolve_ipv6_address(mtrpacket.IPV6_TEST_HOST) + test_basic_probe(self, 6, 'tcp') - cmd = '62 send-probe protocol udp ip-6 ' + test_addr + \ - ' port 164 ttl 1' + # Probe a local port assumed to be open (ssh) + cmd = '80 send-probe ip-6 ::1 protocol tcp port 22' self.write_command(cmd) reply = self.parse_reply() - self.assertEqual(reply.command_name, 'ttl-expired') + self.assertEqual(reply.command_name, 'reply') - cmd = '63 send-probe protocol udp ip-6 ::1 port 164' - self.write_command(cmd) - reply = self.parse_reply() - self.assertEqual(reply.command_name, 'reply') - self.assertIn('ip-6', reply.argument) - self.assertEqual(reply.argument['ip-6'], '::1') +class TestProbeSCTP(mtrpacket.MtrPacketTest): + 'Test SCTP probes' + + def test_sctp_v4(self): + 'Test basic SCTP probes over IPv4' + + test_basic_probe(self, 4, 'sctp') + + @unittest.skipUnless(mtrpacket.HAVE_IPV6, 'No IPv6') + def test_sctp_v6(self): + 'Test basic SCTP probes over IPv6' + + test_basic_probe(self, 6, 'sctp') + if __name__ == '__main__': mtrpacket.check_running_as_root()