Blame ui/cmdpipe.c

Packit b802ec
/*
Packit b802ec
    mtr  --  a network diagnostic tool
Packit b802ec
    Copyright (C) 2016  Matt Kimball
Packit b802ec
Packit b802ec
    This program is free software; you can redistribute it and/or modify
Packit b802ec
    it under the terms of the GNU General Public License version 2 as
Packit b802ec
    published by the Free Software Foundation.
Packit b802ec
Packit b802ec
    This program is distributed in the hope that it will be useful,
Packit b802ec
    but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit b802ec
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit b802ec
    GNU General Public License for more details.
Packit b802ec
Packit b802ec
    You should have received a copy of the GNU General Public License
Packit b802ec
    along with this program; if not, write to the Free Software
Packit b802ec
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Packit b802ec
*/
Packit b802ec
Packit b802ec
#include "cmdpipe.h"
Packit b802ec
Packit b802ec
#include <assert.h>
Packit b802ec
#include <errno.h>
Packit b802ec
#include <fcntl.h>
Packit b802ec
#include <signal.h>
Packit b802ec
#include <stdbool.h>
Packit b802ec
#include <stdio.h>
Packit b802ec
#include <stdlib.h>
Packit b802ec
#include <string.h>
Packit b802ec
#include <strings.h>
Packit b802ec
#include <sys/wait.h>
Packit b802ec
#include <unistd.h>
Packit b802ec
Packit b802ec
#ifdef HAVE_ERROR_H
Packit b802ec
#include <error.h>
Packit b802ec
#else
Packit b802ec
#include "portability/error.h"
Packit b802ec
#endif
Packit b802ec
Packit b802ec
#include "packet/cmdparse.h"
Packit b802ec
#include "display.h"
Packit b802ec
Packit b802ec
Packit b802ec
/*  Set a file descriptor to non-blocking  */
Packit b802ec
static
Packit b802ec
void set_fd_nonblock(
Packit b802ec
    int fd)
Packit b802ec
{
Packit b802ec
    int flags;
Packit b802ec
Packit b802ec
    /*  Get the current flags of the file descriptor  */
Packit b802ec
    flags = fcntl(fd, F_GETFL, 0);
Packit b802ec
    if (flags == -1) {
Packit b802ec
        error(EXIT_FAILURE, errno, "F_GETFL failure");
Packit b802ec
        exit(1);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Add the O_NONBLOCK bit to the current flags  */
Packit b802ec
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
Packit b802ec
        error(EXIT_FAILURE, errno, "Failure to set O_NONBLOCK");
Packit b802ec
        exit(1);
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Send a command synchronously to mtr-packet, blocking until a result
Packit b802ec
    is available.  This is intended to be used at start-up to check the
Packit b802ec
    capabilities of mtr-packet, but probes should be sent asynchronously
Packit b802ec
    to avoid blocking other probes and the user interface.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
int send_synchronous_command(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct packet_command_pipe_t *cmdpipe,
Packit b802ec
    const char *cmd,
Packit b802ec
    struct command_t *result)
Packit b802ec
{
Packit b802ec
    char reply[PACKET_REPLY_BUFFER_SIZE];
Packit b802ec
    int command_length;
Packit b802ec
    int write_length;
Packit b802ec
    int read_length;
Packit b802ec
Packit b802ec
    /*  Query send-probe support  */
Packit b802ec
    command_length = strlen(cmd);
Packit b802ec
    write_length = write(cmdpipe->write_fd, cmd, command_length);
Packit b802ec
Packit b802ec
    if (write_length == -1) {
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (write_length != command_length) {
Packit b802ec
        errno = EIO;
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Read the reply to our query  */
Packit b802ec
    read_length =
Packit b802ec
        read(cmdpipe->read_fd, reply, PACKET_REPLY_BUFFER_SIZE - 1);
Packit b802ec
Packit b802ec
    if (read_length < 0) {
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Parse the query reply  */
Packit b802ec
    reply[read_length] = 0;
Packit b802ec
    if (parse_command(result, reply)) {
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    return 0;
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*  Check support for a particular feature with the mtr-packet we invoked  */
Packit b802ec
static
Packit b802ec
int check_feature(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct packet_command_pipe_t *cmdpipe,
Packit b802ec
    const char *feature)
Packit b802ec
{
Packit b802ec
    char check_command[COMMAND_BUFFER_SIZE];
Packit b802ec
    struct command_t reply;
Packit b802ec
Packit b802ec
    snprintf(check_command, COMMAND_BUFFER_SIZE,
Packit b802ec
             "1 check-support feature %s\n", feature);
Packit b802ec
Packit b802ec
    if (send_synchronous_command(ctl, cmdpipe, check_command, &reply) ==
Packit b802ec
        -1) {
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Check that the feature is supported  */
Packit b802ec
    if (!strcmp(reply.command_name, "feature-support")
Packit b802ec
        && reply.argument_count >= 1
Packit b802ec
        && !strcmp(reply.argument_name[0], "support")
Packit b802ec
        && !strcmp(reply.argument_value[0], "ok")) {
Packit b802ec
Packit b802ec
        /*  Looks good  */
Packit b802ec
        return 0;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    errno = ENOTSUP;
Packit b802ec
    return -1;
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Check the protocol selected against the mtr-packet we are using.
Packit b802ec
    Returns zero if everything is fine, or -1 with errno for either
Packit b802ec
    a failure during the check, or for an unsupported feature.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
int check_packet_features(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct packet_command_pipe_t *cmdpipe)
Packit b802ec
{
Packit b802ec
    /*  Check the IP protocol version  */
Packit b802ec
    if (ctl->af == AF_INET6) {
Packit b802ec
        if (check_feature(ctl, cmdpipe, "ip-6")) {
Packit b802ec
            return -1;
Packit b802ec
        }
Packit b802ec
    } else if (ctl->af == AF_INET) {
Packit b802ec
        if (check_feature(ctl, cmdpipe, "ip-4")) {
Packit b802ec
            return -1;
Packit b802ec
        }
Packit b802ec
    } else {
Packit b802ec
        errno = EINVAL;
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Check the transport protocol  */
Packit b802ec
    if (ctl->mtrtype == IPPROTO_ICMP) {
Packit b802ec
        if (check_feature(ctl, cmdpipe, "icmp")) {
Packit b802ec
            return -1;
Packit b802ec
        }
Packit b802ec
    } else if (ctl->mtrtype == IPPROTO_UDP) {
Packit b802ec
        if (check_feature(ctl, cmdpipe, "udp")) {
Packit b802ec
            return -1;
Packit b802ec
        }
Packit b802ec
    } else if (ctl->mtrtype == IPPROTO_TCP) {
Packit b802ec
        if (check_feature(ctl, cmdpipe, "tcp")) {
Packit b802ec
            return -1;
Packit b802ec
        }
Packit b802ec
#ifdef HAS_SCTP
Packit b802ec
    } else if (ctl->mtrtype == IPPROTO_SCTP) {
Packit b802ec
        if (check_feature(ctl, cmdpipe, "sctp")) {
Packit b802ec
            return -1;
Packit b802ec
        }
Packit b802ec
#endif
Packit b802ec
    } else {
Packit b802ec
        errno = EINVAL;
Packit b802ec
        return -1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
#ifdef SO_MARK
Packit b802ec
    if (ctl->mark) {
Packit b802ec
        if (check_feature(ctl, cmdpipe, "mark")) {
Packit b802ec
            return -1;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
#endif
Packit b802ec
Packit b802ec
    return 0;
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Execute mtr-packet, allowing the MTR_PACKET evironment to override
Packit b802ec
    the PATH when locating the executable.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
void execute_packet_child(
Packit b802ec
    void)
Packit b802ec
{
Packit b802ec
    /*
Packit b802ec
       Allow the MTR_PACKET environment variable to override
Packit b802ec
       the path to the mtr-packet executable.  This is necessary
Packit b802ec
       for debugging changes for mtr-packet.
Packit b802ec
     */
Packit b802ec
    char *mtr_packet_path = getenv("MTR_PACKET");
Packit b802ec
    if (mtr_packet_path == NULL) {
Packit b802ec
        mtr_packet_path = "mtr-packet";
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*
Packit b802ec
       First, try to execute mtr-packet from PATH
Packit b802ec
       or MTR_PACKET environment variable.
Packit b802ec
     */
Packit b802ec
    execlp(mtr_packet_path, "mtr-packet", (char *) NULL);
Packit b802ec
Packit b802ec
    /*
Packit b802ec
       If mtr-packet is not found, try to use mtr-packet from current directory
Packit b802ec
     */
Packit b802ec
    execl("./mtr-packet", "./mtr-packet", (char *) NULL);
Packit b802ec
Packit b802ec
    /*  Both exec attempts failed, so nothing to do but exit  */
Packit b802ec
    exit(1);
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*  Create the command pipe to a new mtr-packet subprocess  */
Packit b802ec
int open_command_pipe(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct packet_command_pipe_t *cmdpipe)
Packit b802ec
{
Packit b802ec
    int stdin_pipe[2];
Packit b802ec
    int stdout_pipe[2];
Packit b802ec
    pid_t child_pid;
Packit b802ec
    int i;
Packit b802ec
Packit b802ec
    /*
Packit b802ec
       We actually need two Unix pipes.  One for stdin and one for
Packit b802ec
       stdout on the new process.
Packit b802ec
     */
Packit b802ec
    if (pipe(stdin_pipe) || pipe(stdout_pipe)) {
Packit b802ec
        return errno;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    child_pid = fork();
Packit b802ec
    if (child_pid == -1) {
Packit b802ec
        return errno;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (child_pid == 0) {
Packit b802ec
        /*
Packit b802ec
           In the child process, attach our created pipes to stdin
Packit b802ec
           and stdout
Packit b802ec
         */
Packit b802ec
        dup2(stdin_pipe[0], STDIN_FILENO);
Packit b802ec
        dup2(stdout_pipe[1], STDOUT_FILENO);
Packit b802ec
Packit b802ec
        /*  Close all unnecessary fds  */
Packit b802ec
        for (i = STDERR_FILENO + 1; i <= stdout_pipe[1]; i++) {
Packit b802ec
            close(i);
Packit b802ec
        }
Packit b802ec
Packit b802ec
        execute_packet_child();
Packit b802ec
    } else {
Packit b802ec
        memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t));
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           In the parent process, save the opposite ends of the pipes
Packit b802ec
           attached as stdin and stdout in the child.
Packit b802ec
         */
Packit b802ec
        cmdpipe->pid = child_pid;
Packit b802ec
        cmdpipe->read_fd = stdout_pipe[0];
Packit b802ec
        cmdpipe->write_fd = stdin_pipe[1];
Packit b802ec
Packit b802ec
        /*  We don't need the child ends of the pipe open in the parent.  */
Packit b802ec
        close(stdout_pipe[1]);
Packit b802ec
        close(stdin_pipe[0]);
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           Check that we can communicate with the client.  If we failed to
Packit b802ec
           execute the mtr-packet binary, we will discover that here.
Packit b802ec
         */
Packit b802ec
        if (check_feature(ctl, cmdpipe, "send-probe")) {
Packit b802ec
            error(EXIT_FAILURE, errno, "Failure to start mtr-packet");
Packit b802ec
        }
Packit b802ec
Packit b802ec
        if (check_packet_features(ctl, cmdpipe)) {
Packit b802ec
            error(EXIT_FAILURE, errno, "Packet type unsupported");
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*  We will need non-blocking reads from the child  */
Packit b802ec
        set_fd_nonblock(cmdpipe->read_fd);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    return 0;
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*  Kill the mtr-packet child process and close the command pipe  */
Packit b802ec
void close_command_pipe(
Packit b802ec
    struct packet_command_pipe_t *cmdpipe)
Packit b802ec
{
Packit b802ec
    int child_exit_value;
Packit b802ec
Packit b802ec
    if (cmdpipe->pid) {
Packit b802ec
        close(cmdpipe->read_fd);
Packit b802ec
        close(cmdpipe->write_fd);
Packit b802ec
Packit b802ec
        kill(cmdpipe->pid, SIGTERM);
Packit b802ec
        waitpid(cmdpipe->pid, &child_exit_value, 0);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    memset(cmdpipe, 0, sizeof(struct packet_command_pipe_t));
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*  Start building the command string for the "send-probe" command  */
Packit b802ec
static
Packit b802ec
void construct_base_command(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    char *command,
Packit b802ec
    int buffer_size,
Packit b802ec
    int command_token,
Packit b802ec
    ip_t * address,
Packit b802ec
    ip_t * localaddress)
Packit b802ec
{
Packit b802ec
    char ip_string[INET6_ADDRSTRLEN];
Packit b802ec
    char local_ip_string[INET6_ADDRSTRLEN];
Packit b802ec
    const char *ip_type;
Packit b802ec
    const char *local_ip_type;
Packit b802ec
    const char *protocol = NULL;
Packit b802ec
Packit b802ec
    /*  Conver the remote IP address to a string  */
Packit b802ec
    if (inet_ntop(ctl->af, address, ip_string, INET6_ADDRSTRLEN) == NULL) {
Packit b802ec
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, errno, "invalid remote IP address");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (inet_ntop(ctl->af, localaddress,
Packit b802ec
                  local_ip_string, INET6_ADDRSTRLEN) == NULL) {
Packit b802ec
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, errno, "invalid local IP address");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (ctl->af == AF_INET6) {
Packit b802ec
        ip_type = "ip-6";
Packit b802ec
        local_ip_type = "local-ip-6";
Packit b802ec
    } else {
Packit b802ec
        ip_type = "ip-4";
Packit b802ec
        local_ip_type = "local-ip-4";
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (ctl->mtrtype == IPPROTO_ICMP) {
Packit b802ec
        protocol = "icmp";
Packit b802ec
    } else if (ctl->mtrtype == IPPROTO_UDP) {
Packit b802ec
        protocol = "udp";
Packit b802ec
    } else if (ctl->mtrtype == IPPROTO_TCP) {
Packit b802ec
        protocol = "tcp";
Packit b802ec
#ifdef HAS_SCTP
Packit b802ec
    } else if (ctl->mtrtype == IPPROTO_SCTP) {
Packit b802ec
        protocol = "sctp";
Packit b802ec
#endif
Packit b802ec
    } else {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0,
Packit b802ec
              "protocol unsupported by mtr-packet interface");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    snprintf(command, buffer_size,
Packit b802ec
             "%d send-probe %s %s %s %s protocol %s",
Packit b802ec
             command_token,
Packit b802ec
             ip_type, ip_string, local_ip_type, local_ip_string, protocol);
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*  Append an argument to the "send-probe" command string  */
Packit b802ec
static
Packit b802ec
void append_command_argument(
Packit b802ec
    char *command,
Packit b802ec
    int buffer_size,
Packit b802ec
    char *name,
Packit b802ec
    int value)
Packit b802ec
{
Packit b802ec
    char argument[COMMAND_BUFFER_SIZE];
Packit b802ec
    int remaining_size;
Packit b802ec
Packit b802ec
    remaining_size = buffer_size - strlen(command) - 1;
Packit b802ec
Packit b802ec
    snprintf(argument, buffer_size, " %s %d", name, value);
Packit b802ec
    strncat(command, argument, remaining_size);
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*  Request a new probe from the "mtr-packet" child process  */
Packit b802ec
void send_probe_command(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct packet_command_pipe_t *cmdpipe,
Packit b802ec
    ip_t * address,
Packit b802ec
    ip_t * localaddress,
Packit b802ec
    int packet_size,
Packit b802ec
    int sequence,
Packit b802ec
    int time_to_live)
Packit b802ec
{
Packit b802ec
    char command[COMMAND_BUFFER_SIZE];
Packit b802ec
    int remaining_size;
Packit b802ec
    int timeout;
Packit b802ec
Packit b802ec
    construct_base_command(ctl, command, COMMAND_BUFFER_SIZE, sequence,
Packit b802ec
                           address, localaddress);
Packit b802ec
Packit b802ec
    append_command_argument(command, COMMAND_BUFFER_SIZE, "size",
Packit b802ec
                            packet_size);
Packit b802ec
Packit b802ec
    append_command_argument(command, COMMAND_BUFFER_SIZE, "bit-pattern",
Packit b802ec
                            ctl->bitpattern);
Packit b802ec
Packit b802ec
    append_command_argument(command, COMMAND_BUFFER_SIZE, "tos", ctl->tos);
Packit b802ec
Packit b802ec
    append_command_argument(command, COMMAND_BUFFER_SIZE, "ttl",
Packit b802ec
                            time_to_live);
Packit b802ec
Packit b802ec
    timeout = ctl->probe_timeout / 1000000;
Packit b802ec
    append_command_argument(command, COMMAND_BUFFER_SIZE, "timeout",
Packit b802ec
                            timeout);
Packit b802ec
Packit b802ec
    if (ctl->remoteport) {
Packit b802ec
        append_command_argument(command, COMMAND_BUFFER_SIZE, "port",
Packit b802ec
                                ctl->remoteport);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (ctl->localport) {
Packit b802ec
        append_command_argument(command, COMMAND_BUFFER_SIZE, "local-port",
Packit b802ec
                                ctl->localport);
Packit b802ec
    }
Packit b802ec
#ifdef SO_MARK
Packit b802ec
    if (ctl->mark) {
Packit b802ec
        append_command_argument(command, COMMAND_BUFFER_SIZE, "mark",
Packit b802ec
                                ctl->mark);
Packit b802ec
    }
Packit b802ec
#endif
Packit b802ec
Packit b802ec
    remaining_size = COMMAND_BUFFER_SIZE - strlen(command) - 1;
Packit b802ec
    strncat(command, "\n", remaining_size);
Packit b802ec
Packit b802ec
    /*  Send a probe using the mtr-packet subprocess  */
Packit b802ec
    if (write(cmdpipe->write_fd, command, strlen(command)) == -1) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, errno,
Packit b802ec
              "mtr-packet command pipe write failure");
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Parse a comma separated field of mpls values, filling out the mplslen
Packit b802ec
    structure with mpls labels.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
void parse_mpls_values(
Packit b802ec
    struct mplslen *mpls,
Packit b802ec
    char *value_str)
Packit b802ec
{
Packit b802ec
    char *next_value = value_str;
Packit b802ec
    char *end_of_value;
Packit b802ec
    int value;
Packit b802ec
    int label_count = 0;
Packit b802ec
    int label_field = 0;
Packit b802ec
Packit b802ec
    while (*next_value) {
Packit b802ec
        value = strtol(next_value, &end_of_value, 10);
Packit b802ec
Packit b802ec
        /*  Failure to advance means an invalid numeric value  */
Packit b802ec
        if (end_of_value == next_value) {
Packit b802ec
            return;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*  If the next character is not a comma or a NUL, we have
Packit b802ec
           an invalid string  */
Packit b802ec
        if (*end_of_value == ',') {
Packit b802ec
            next_value = end_of_value + 1;
Packit b802ec
        } else if (*end_of_value == 0) {
Packit b802ec
            next_value = end_of_value;
Packit b802ec
        } else {
Packit b802ec
            return;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           Store the converted value in the next field of the MPLS
Packit b802ec
           structure.
Packit b802ec
         */
Packit b802ec
        if (label_field == 0) {
Packit b802ec
            mpls->label[label_count] = value;
Packit b802ec
        } else if (label_field == 1) {
Packit b802ec
            mpls->exp[label_count] = value;
Packit b802ec
        } else if (label_field == 2) {
Packit b802ec
            mpls->s[label_count] = value;
Packit b802ec
        } else if (label_field == 3) {
Packit b802ec
            mpls->ttl[label_count] = value;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        label_field++;
Packit b802ec
        if (label_field > 3) {
Packit b802ec
            label_field = 0;
Packit b802ec
            label_count++;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           If we've used up all MPLS labels in the structure, return with
Packit b802ec
           what we've got
Packit b802ec
         */
Packit b802ec
        if (label_count >= MAXLABELS) {
Packit b802ec
            break;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    mpls->labels = label_count;
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Extract the IP address and round trip time from a reply to a probe.
Packit b802ec
    Returns true if both arguments are found in the reply, false otherwise.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
bool parse_reply_arguments(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct command_t *reply,
Packit b802ec
    ip_t * fromaddress,
Packit b802ec
    int *round_trip_time,
Packit b802ec
    struct mplslen *mpls)
Packit b802ec
{
Packit b802ec
    bool found_round_trip;
Packit b802ec
    bool found_ip;
Packit b802ec
    char *arg_name;
Packit b802ec
    char *arg_value;
Packit b802ec
    int i;
Packit b802ec
Packit b802ec
    *round_trip_time = 0;
Packit b802ec
    memset(fromaddress, 0, sizeof(ip_t));
Packit b802ec
    memset(mpls, 0, sizeof(struct mplslen));
Packit b802ec
Packit b802ec
    found_ip = false;
Packit b802ec
    found_round_trip = false;
Packit b802ec
Packit b802ec
    /*  Examine the reply arguments for known values  */
Packit b802ec
    for (i = 0; i < reply->argument_count; i++) {
Packit b802ec
        arg_name = reply->argument_name[i];
Packit b802ec
        arg_value = reply->argument_value[i];
Packit b802ec
Packit b802ec
        if (ctl->af == AF_INET6) {
Packit b802ec
            /*  IPv6 address of the responding host  */
Packit b802ec
            if (!strcmp(arg_name, "ip-6")) {
Packit b802ec
                if (inet_pton(AF_INET6, arg_value, fromaddress)) {
Packit b802ec
                    found_ip = true;
Packit b802ec
                }
Packit b802ec
            }
Packit b802ec
        } else {
Packit b802ec
            /*  IPv4 address of the responding host  */
Packit b802ec
            if (!strcmp(arg_name, "ip-4")) {
Packit b802ec
                if (inet_pton(AF_INET, arg_value, fromaddress)) {
Packit b802ec
                    found_ip = true;
Packit b802ec
                }
Packit b802ec
            }
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*  The round trip time in microseconds  */
Packit b802ec
        if (!strcmp(arg_name, "round-trip-time")) {
Packit b802ec
            errno = 0;
Packit b802ec
            *round_trip_time = strtol(arg_value, NULL, 10);
Packit b802ec
            if (!errno) {
Packit b802ec
                found_round_trip = true;
Packit b802ec
            }
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*  MPLS labels  */
Packit b802ec
        if (!strcmp(arg_name, "mpls")) {
Packit b802ec
            parse_mpls_values(mpls, arg_value);
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    return found_ip && found_round_trip;
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    If an mtr-packet command has returned an error result,
Packit b802ec
    report the error and exit.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
void handle_reply_errors(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct command_t *reply)
Packit b802ec
{
Packit b802ec
    char *reply_name;
Packit b802ec
Packit b802ec
    reply_name = reply->command_name;
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "no-route")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "No route to host");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "network-down")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "Network down");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "probes-exhausted")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "Probes exhausted");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "invalid-argument")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "mtr-packet reported invalid argument");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "permission-denied")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "Permission denied");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "address-in-use")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "Address in use");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "address-not-available")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "Address not available");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(reply_name, "unexpected-error")) {
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, 0, "Unexpected mtr-packet error");
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    A complete mtr-packet reply line has arrived.  Parse it and record
Packit b802ec
    the responding IP and round trip time, if it is a reply that we
Packit b802ec
    understand.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
void handle_command_reply(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    char *reply_str,
Packit b802ec
    probe_reply_func_t reply_func)
Packit b802ec
{
Packit b802ec
    struct command_t reply;
Packit b802ec
    ip_t fromaddress;
Packit b802ec
    int seq_num;
Packit b802ec
    int round_trip_time;
Packit b802ec
    char *reply_name;
Packit b802ec
    struct mplslen mpls;
Packit b802ec
Packit b802ec
    /*  Parse the reply string  */
Packit b802ec
    if (parse_command(&reply, reply_str)) {
Packit b802ec
        /*
Packit b802ec
           If the reply isn't well structured, something is fundamentally
Packit b802ec
           wrong, as we might as well exit.  Even if the reply is of an
Packit b802ec
           unknown type, it should still parse.
Packit b802ec
         */
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, errno, "reply parse failure");
Packit b802ec
        return;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    handle_reply_errors(ctl, &reply);
Packit b802ec
Packit b802ec
    seq_num = reply.token;
Packit b802ec
    reply_name = reply.command_name;
Packit b802ec
Packit b802ec
    /*  If the reply type is unknown, ignore it for future compatibility  */
Packit b802ec
    if (strcmp(reply_name, "reply") && strcmp(reply_name, "ttl-expired")) {
Packit b802ec
        return;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*
Packit b802ec
       If the reply had an IP address and a round trip time, we can
Packit b802ec
       record the result.
Packit b802ec
     */
Packit b802ec
    if (parse_reply_arguments
Packit b802ec
        (ctl, &reply, &fromaddress, &round_trip_time, &mpls)) {
Packit b802ec
Packit b802ec
        reply_func(ctl, seq_num, &mpls, (void *) &fromaddress,
Packit b802ec
                   round_trip_time);
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Check the command pipe for completed replies to commands
Packit b802ec
    we have previously sent.  Record the results of those replies.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
void consume_reply_buffer(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct packet_command_pipe_t *cmdpipe,
Packit b802ec
    probe_reply_func_t reply_func)
Packit b802ec
{
Packit b802ec
    char *reply_buffer;
Packit b802ec
    char *reply_start;
Packit b802ec
    char *end_of_reply;
Packit b802ec
    int used_size;
Packit b802ec
    int move_size;
Packit b802ec
Packit b802ec
    reply_buffer = cmdpipe->reply_buffer;
Packit b802ec
Packit b802ec
    /*  Terminate the string storing the replies  */
Packit b802ec
    assert(cmdpipe->reply_buffer_used < PACKET_REPLY_BUFFER_SIZE);
Packit b802ec
    reply_buffer[cmdpipe->reply_buffer_used] = 0;
Packit b802ec
Packit b802ec
    reply_start = reply_buffer;
Packit b802ec
Packit b802ec
    /*
Packit b802ec
       We may have multiple completed replies.  Loop until we don't
Packit b802ec
       have any more newlines termininating replies.
Packit b802ec
     */
Packit b802ec
    while (true) {
Packit b802ec
        /*  If no newline is found, our reply isn't yet complete  */
Packit b802ec
        end_of_reply = index(reply_start, '\n');
Packit b802ec
        if (end_of_reply == NULL) {
Packit b802ec
            /*  No complete replies remaining  */
Packit b802ec
            break;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           Terminate the reply string at the newline, which
Packit b802ec
           is necessary in the case where we are able to read
Packit b802ec
           mulitple replies arriving simultaneously.
Packit b802ec
         */
Packit b802ec
        *end_of_reply = 0;
Packit b802ec
Packit b802ec
        /*  Parse and record the reply results  */
Packit b802ec
        handle_command_reply(ctl, reply_start, reply_func);
Packit b802ec
Packit b802ec
        reply_start = end_of_reply + 1;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*
Packit b802ec
       After replies have been processed, free the space used
Packit b802ec
       by the replies, and move any remaining partial reply text
Packit b802ec
       to the start of the reply buffer.
Packit b802ec
     */
Packit b802ec
    used_size = reply_start - reply_buffer;
Packit b802ec
    move_size = cmdpipe->reply_buffer_used - used_size;
Packit b802ec
    memmove(reply_buffer, reply_start, move_size);
Packit b802ec
    cmdpipe->reply_buffer_used -= used_size;
Packit b802ec
Packit b802ec
    if (cmdpipe->reply_buffer_used >= PACKET_REPLY_BUFFER_SIZE - 1) {
Packit b802ec
        /*
Packit b802ec
           We've overflowed the reply buffer without a complete reply.
Packit b802ec
           There's not much we can do about it but discard the data
Packit b802ec
           we've got and hope new data coming in fits.
Packit b802ec
         */
Packit b802ec
        cmdpipe->reply_buffer_used = 0;
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Read as much as we can from the reply pipe from the child process, and
Packit b802ec
    process as many replies as are available.
Packit b802ec
*/
Packit b802ec
void handle_command_replies(
Packit b802ec
    struct mtr_ctl *ctl,
Packit b802ec
    struct packet_command_pipe_t *cmdpipe,
Packit b802ec
    probe_reply_func_t reply_func)
Packit b802ec
{
Packit b802ec
    int read_count;
Packit b802ec
    int buffer_remaining;
Packit b802ec
    char *reply_buffer;
Packit b802ec
    char *read_buffer;
Packit b802ec
Packit b802ec
    reply_buffer = cmdpipe->reply_buffer;
Packit b802ec
Packit b802ec
    /*
Packit b802ec
       Read the available reply text, up to the the remaining
Packit b802ec
       buffer space.  (Minus one for the terminating NUL.)
Packit b802ec
     */
Packit b802ec
    read_buffer = &reply_buffer[cmdpipe->reply_buffer_used];
Packit b802ec
    buffer_remaining =
Packit b802ec
        PACKET_REPLY_BUFFER_SIZE - cmdpipe->reply_buffer_used;
Packit b802ec
    read_count = read(cmdpipe->read_fd, read_buffer, buffer_remaining - 1);
Packit b802ec
Packit b802ec
    if (read_count < 0) {
Packit b802ec
        /*
Packit b802ec
           EAGAIN simply indicates that there is no data currently
Packit b802ec
           available on our non-blocking pipe.
Packit b802ec
         */
Packit b802ec
        if (errno == EAGAIN) {
Packit b802ec
            return;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        display_close(ctl);
Packit b802ec
        error(EXIT_FAILURE, errno, "command reply read failure");
Packit b802ec
        return;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (read_count == 0) {
Packit b802ec
        display_close(ctl);
Packit b802ec
Packit b802ec
        errno = EPIPE;
Packit b802ec
        error(EXIT_FAILURE, EPIPE, "unexpected packet generator exit");
Packit b802ec
    }
Packit b802ec
Packit b802ec
    cmdpipe->reply_buffer_used += read_count;
Packit b802ec
Packit b802ec
    /*  Handle any replies completed by this read  */
Packit b802ec
    consume_reply_buffer(ctl, cmdpipe, reply_func);
Packit b802ec
}