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 \
--- /dev/null
+/*
+ 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#ifdef HAVE_ERROR_H
+# include <error.h>
+#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);
+}
--- /dev/null
+/*
+ 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
.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
.HP 14
.IP
The destination port to use for
+.BR sctp ,
+.BR tcp ,
+or
.B udp
probes.
.HP 7
.BR ip-4 ,
.BR ip-6 ,
.BR icmp ,
+.BR sctp ,
+.BR tcp ,
.BR udp ,
and
.BR mark .
.SH "SEE ALSO"
.BR mtr (8),
.BR icmp (7),
+.BR tcp (7),
.BR udp (7),
TCP/IP Illustrated (Stevens, ISBN 0201633469).
#include "config.h"
-#if defined(HAVE_SYS_XTI_H)
-# include <sys/xti.h>
-#endif
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#include <sys/select.h>
-#include <sys/wait.h>
-#include <netinet/in.h>
-#include <assert.h>
-#include <memory.h>
-#include <unistd.h>
-#ifdef HAVE_FCNTL_H
-# include <fcntl.h>
-#endif
-#include <signal.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
#include <math.h>
-#include <errno.h>
+#include <stdlib.h>
#include <string.h>
+#include <sys/select.h>
+
#ifdef HAVE_ERROR_H
# include <error.h>
#else
# include "portability/error.h"
#endif
-#ifdef HAVE_LINUX_ICMP_H
-# include <linux/icmp.h>
-#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 {
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;
};
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 */
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;
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; i<MAXPATH; ) {
if( addrcmp( (void *) &(host[index].addrs[i]), (void *) &addrcopy,
if( addrcmp( (void *) &(host[index].addrs[i]), addrcopy, ctl->af ) != 0 &&
i<MAXPATH ) {
addrcpy( (void *) &(host[index].addrs[i]), addrcopy, ctl->af );
- host[index].mplss[i] = mpls;
+ host[index].mplss[i] = *mpls;
display_rawhost(ctl, index, (void *) &(host[index].addrs[i]));
}
}
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);
}
}
-/* 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;
}
for (at = 0; at < MaxSequence; at++) {
sequence[at].transit = 0;
- if (sequence[at].socket > 0) {
- close(sequence[at].socket);
- sequence[at].socket = 0;
- }
}
}
/* 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);
}
}
}
-/* 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)
{
FD_ZERO(&writefd);
tv.tv_sec = 0;
tv.tv_usec = 0;
- net_add_fds(&writefd, &maxfd);
select(maxfd, NULL, &writefd, NULL, &tv);
}
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
+#include <sys/types.h>
#ifdef ENABLE_IPV6
#include <netinet/ip6.h>
#endif
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";
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;
}
}
}
+/* 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(
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)
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));
}
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)
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)
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;
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)
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;
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
{
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);
}
/* 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;
}
/*
#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;
}
}
}
/* 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,
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 {
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,
/* 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,
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;
}
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,
#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
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,
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) {
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
}
}
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) {
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
}
}
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);
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);
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);
const struct sockaddr_storage *remote_addr,
const void *packet,
int packet_length,
- struct timeval timestamp)
+ struct timeval *timestamp)
{
const struct ICMPHeader *icmp;
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
probe->used = true;
probe->token = token;
+ platform_alloc_probe(net_state, probe);
+
return 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;
}
}
/* 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;
};
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);
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)
#include <unistd.h>
#include "platform.h"
+#include "protocols.h"
#include "construct_unix.h"
#include "deconstruct_unix.h"
#include "timeval.h"
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);
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);
}
}
-/* 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)
{
{
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);
}
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 */
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);
}
}
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);
}
/*
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)
}
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;
}
/*
#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 */
(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
#include <sys/select.h>
/*
- 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;
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
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.
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):
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):
#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
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;
}
icmp = (struct ICMPHeader *)packet;
- expected_sequence = htons(getpid());
+ expected_sequence = htons(SEQUENCE_NUM);
if (icmp->sequence == expected_sequence) {
return true;
}
'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)
'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)
'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)
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)
'''Test sending probes and receiving respones.'''
import socket
+import sys
import time
import unittest
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'''
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()