Blame packet/command.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 "command.h"
Packit b802ec
Packit b802ec
#include <assert.h>
Packit b802ec
#include <errno.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
Packit b802ec
#include "cmdparse.h"
Packit b802ec
#include "platform.h"
Packit b802ec
#include "config.h"
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Find a parameter with a particular name in a command_t structure.
Packit b802ec
    If no such parameter exists, return NULL.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
const char *find_parameter(
Packit b802ec
    const struct command_t *command,
Packit b802ec
    const char *name_request)
Packit b802ec
{
Packit b802ec
    const char *name;
Packit b802ec
    const char *value;
Packit b802ec
    int i;
Packit b802ec
Packit b802ec
    for (i = 0; i < command->argument_count; i++) {
Packit b802ec
        name = command->argument_name[i];
Packit b802ec
        value = command->argument_value[i];
Packit b802ec
Packit b802ec
        if (!strcmp(name, name_request)) {
Packit b802ec
            return value;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    return NULL;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Returns a feature support string for a particular probe protocol  */
Packit b802ec
static
Packit b802ec
const char *check_protocol_support(
Packit b802ec
    struct net_state_t *net_state,
Packit b802ec
    int protocol)
Packit b802ec
{
Packit b802ec
    if (is_protocol_supported(net_state, protocol)) {
Packit b802ec
        return "ok";
Packit b802ec
    } else {
Packit b802ec
        return "no";
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Return a feature support string for an IP protocol version  */
Packit b802ec
static
Packit b802ec
const char *check_ip_version_support(
Packit b802ec
    struct net_state_t *net_state,
Packit b802ec
    int ip_version)
Packit b802ec
{
Packit b802ec
    if (is_ip_version_supported(net_state, ip_version)) {
Packit b802ec
        return "ok";
Packit b802ec
    } else {
Packit b802ec
        return "no";
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Given a feature name, return a string for the check-support reply  */
Packit b802ec
static
Packit b802ec
const char *check_support(
Packit b802ec
    const char *feature,
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
    if (!strcmp(feature, "version")) {
Packit b802ec
        return PACKAGE_VERSION;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(feature, "ip-4")) {
Packit b802ec
        return check_ip_version_support(net_state, 4);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(feature, "ip-6")) {
Packit b802ec
        return check_ip_version_support(net_state, 6);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(feature, "send-probe")) {
Packit b802ec
        return "ok";
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(feature, "icmp")) {
Packit b802ec
        return check_protocol_support(net_state, IPPROTO_ICMP);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(feature, "udp")) {
Packit b802ec
        return check_protocol_support(net_state, IPPROTO_UDP);
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!strcmp(feature, "tcp")) {
Packit b802ec
        return check_protocol_support(net_state, IPPROTO_TCP);
Packit b802ec
    }
Packit b802ec
#ifdef IPPROTO_SCTP
Packit b802ec
    if (!strcmp(feature, "sctp")) {
Packit b802ec
        return check_protocol_support(net_state, IPPROTO_SCTP);
Packit b802ec
    }
Packit b802ec
#endif
Packit b802ec
Packit b802ec
#ifdef SO_MARK
Packit b802ec
    if (!strcmp(feature, "mark")) {
Packit b802ec
        return "ok";
Packit b802ec
    }
Packit b802ec
#endif
Packit b802ec
Packit b802ec
    return "no";
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Handle a check-support request by checking for a particular feature  */
Packit b802ec
static
Packit b802ec
void check_support_command(
Packit b802ec
    const struct command_t *command,
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
    const char *feature;
Packit b802ec
    const char *support;
Packit b802ec
Packit b802ec
    feature = find_parameter(command, "feature");
Packit b802ec
    if (feature == NULL) {
Packit b802ec
        printf("%d invalid-argument\n", command->token);
Packit b802ec
        return;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    support = check_support(feature, net_state);
Packit b802ec
    printf("%d feature-support support %s\n", command->token, support);
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    If a named send_probe argument is recognized, fill in the probe paramters
Packit b802ec
    structure with the argument value.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
bool decode_probe_argument(
Packit b802ec
    struct probe_param_t *param,
Packit b802ec
    const char *name,
Packit b802ec
    const char *value)
Packit b802ec
{
Packit b802ec
    char *endstr = NULL;
Packit b802ec
Packit b802ec
    /*  Pass IPv4 addresses as string values  */
Packit b802ec
    if (!strcmp(name, "ip-4")) {
Packit b802ec
        param->ip_version = 4;
Packit b802ec
        param->remote_address = value;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  IPv6 address  */
Packit b802ec
    if (!strcmp(name, "ip-6")) {
Packit b802ec
        param->ip_version = 6;
Packit b802ec
        param->remote_address = value;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  IPv4 address to send from  */
Packit b802ec
    if (!strcmp(name, "local-ip-4")) {
Packit b802ec
        param->local_address = value;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  IPv6 address to send from  */
Packit b802ec
    if (!strcmp(name, "local-ip-6")) {
Packit b802ec
        param->local_address = value;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Protocol for the probe  */
Packit b802ec
    if (!strcmp(name, "protocol")) {
Packit b802ec
        if (!strcmp(value, "icmp")) {
Packit b802ec
            param->protocol = IPPROTO_ICMP;
Packit b802ec
        } else if (!strcmp(value, "udp")) {
Packit b802ec
            param->protocol = IPPROTO_UDP;
Packit b802ec
        } else if (!strcmp(value, "tcp")) {
Packit b802ec
            param->protocol = IPPROTO_TCP;
Packit b802ec
#ifdef IPPROTO_SCTP
Packit b802ec
        } else if (!strcmp(value, "sctp")) {
Packit b802ec
            param->protocol = IPPROTO_SCTP;
Packit b802ec
#endif
Packit b802ec
        } else {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Destination port for the probe  */
Packit b802ec
    if (!strcmp(name, "port")) {
Packit b802ec
        param->dest_port = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  The local port to send UDP probes from  */
Packit b802ec
    if (!strcmp(name, "local-port")) {
Packit b802ec
        param->local_port = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           Don't allow using a local port which requires
Packit b802ec
           privileged binding.
Packit b802ec
         */
Packit b802ec
        if (param->local_port < 1024) {
Packit b802ec
            param->local_port = 0;
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  The "type of service" field for the IP header  */
Packit b802ec
    if (!strcmp(name, "tos")) {
Packit b802ec
        param->type_of_service = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  The Linux packet mark for mark-based routing  */
Packit b802ec
    if (!strcmp(name, "mark")) {
Packit b802ec
        param->routing_mark = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  The size of the packet (including headers)  */
Packit b802ec
    if (!strcmp(name, "size")) {
Packit b802ec
        param->packet_size = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  The packet's bytes will be filled with this value  */
Packit b802ec
    if (!strcmp(name, "bit-pattern")) {
Packit b802ec
        param->bit_pattern = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Time-to-live values  */
Packit b802ec
    if (!strcmp(name, "ttl")) {
Packit b802ec
        param->ttl = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Number of seconds to wait for a reply  */
Packit b802ec
    if (!strcmp(name, "timeout")) {
Packit b802ec
        param->timeout = strtol(value, &endstr, 10);
Packit b802ec
        if (*endstr != 0) {
Packit b802ec
            return false;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    return true;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Check the probe parameters against currently supported features
Packit b802ec
    and report and error if there is a problem.
Packit b802ec
Packit b802ec
    Return true if everything is okay, false otherwise.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
bool validate_probe_parameters(
Packit b802ec
    struct net_state_t *net_state,
Packit b802ec
    struct probe_param_t *param)
Packit b802ec
{
Packit b802ec
    if (!is_ip_version_supported(net_state, param->ip_version)) {
Packit b802ec
        printf("%d invalid-argument reason ip-version-not-supported\n",
Packit b802ec
               param->command_token);
Packit b802ec
Packit b802ec
        return false;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!is_protocol_supported(net_state, param->protocol)) {
Packit b802ec
        printf("%d invalid-argument reason protocol-not-supported\n",
Packit b802ec
               param->command_token);
Packit b802ec
Packit b802ec
        return false;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    return true;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*  Handle "send-probe" commands  */
Packit b802ec
static
Packit b802ec
void send_probe_command(
Packit b802ec
    const struct command_t *command,
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
    struct probe_param_t param;
Packit b802ec
    int i;
Packit b802ec
    char *name;
Packit b802ec
    char *value;
Packit b802ec
Packit b802ec
    /*  We will prepare a probe_param_t for send_probe.  */
Packit b802ec
    memset(&param, 0, sizeof(struct probe_param_t));
Packit b802ec
    param.command_token = command->token;
Packit b802ec
    param.protocol = IPPROTO_ICMP;
Packit b802ec
    param.ttl = 255;
Packit b802ec
    param.packet_size = 64;
Packit b802ec
    param.timeout = 10;
Packit b802ec
Packit b802ec
    for (i = 0; i < command->argument_count; i++) {
Packit b802ec
        name = command->argument_name[i];
Packit b802ec
        value = command->argument_value[i];
Packit b802ec
Packit b802ec
        if (!decode_probe_argument(&param, name, value)) {
Packit b802ec
            printf("%d invalid-argument\n", command->token);
Packit b802ec
            return;
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (!validate_probe_parameters(net_state, &param)) {
Packit b802ec
        return;
Packit b802ec
    }
Packit b802ec
Packit b802ec
    /*  Send the probe using a platform specific mechanism  */
Packit b802ec
    send_probe(net_state, ¶m;;
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    Given a parsed command, dispatch to the handler for specific
Packit b802ec
    command requests.
Packit b802ec
*/
Packit b802ec
static
Packit b802ec
void dispatch_command(
Packit b802ec
    const struct command_t *command,
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
    if (!strcmp(command->command_name, "check-support")) {
Packit b802ec
        check_support_command(command, net_state);
Packit b802ec
    } else if (!strcmp(command->command_name, "send-probe")) {
Packit b802ec
        send_probe_command(command, net_state);
Packit b802ec
    } else {
Packit b802ec
        /*  For unrecognized commands, respond with an error  */
Packit b802ec
        printf("%d unknown-command\n", command->token);
Packit b802ec
    }
Packit b802ec
}
Packit b802ec
Packit b802ec
/*
Packit b802ec
    With newly read data in our command buffer, dispatch all completed
Packit b802ec
    command requests.
Packit b802ec
*/
Packit b802ec
void dispatch_buffer_commands(
Packit b802ec
    struct command_buffer_t *buffer,
Packit b802ec
    struct net_state_t *net_state)
Packit b802ec
{
Packit b802ec
    struct command_t command;
Packit b802ec
    char *end_of_command;
Packit b802ec
    char full_command[COMMAND_BUFFER_SIZE];
Packit b802ec
    int command_length;
Packit b802ec
    int remaining_count;
Packit b802ec
Packit b802ec
    while (true) {
Packit b802ec
        assert(buffer->incoming_read_position < COMMAND_BUFFER_SIZE);
Packit b802ec
Packit b802ec
        /*  Terminate the buffer string  */
Packit b802ec
        buffer->incoming_buffer[buffer->incoming_read_position] = 0;
Packit b802ec
Packit b802ec
        /*  Find the next newline, which terminates command requests  */
Packit b802ec
        end_of_command = index(buffer->incoming_buffer, '\n');
Packit b802ec
        if (end_of_command == NULL) {
Packit b802ec
            /*
Packit b802ec
               No newlines found, so any data we've read so far is
Packit b802ec
               not yet complete.
Packit b802ec
             */
Packit b802ec
            break;
Packit b802ec
        }
Packit b802ec
Packit b802ec
        command_length = end_of_command - buffer->incoming_buffer;
Packit b802ec
        remaining_count =
Packit b802ec
            buffer->incoming_read_position - command_length - 1;
Packit b802ec
Packit b802ec
        /*  Copy the completed command  */
Packit b802ec
        memmove(full_command, buffer->incoming_buffer, command_length);
Packit b802ec
        full_command[command_length] = 0;
Packit b802ec
Packit b802ec
        /*
Packit b802ec
           Free the space used by the completed command by advancing the
Packit b802ec
           remaining requests within the buffer.
Packit b802ec
         */
Packit b802ec
        memmove(buffer->incoming_buffer, end_of_command + 1,
Packit b802ec
                remaining_count);
Packit b802ec
        buffer->incoming_read_position -= command_length + 1;
Packit b802ec
Packit b802ec
        if (parse_command(&command, full_command)) {
Packit b802ec
            /*  If the command fails to parse, respond with an error  */
Packit b802ec
            printf("0 command-parse-error\n");
Packit b802ec
        } else {
Packit b802ec
            dispatch_command(&command, net_state);
Packit b802ec
        }
Packit b802ec
    }
Packit b802ec
Packit b802ec
    if (buffer->incoming_read_position >= COMMAND_BUFFER_SIZE - 1) {
Packit b802ec
        /*
Packit b802ec
           If we've filled the buffer without a complete command, the
Packit b802ec
           only thing we can do is discard what we've read and hope that 
Packit b802ec
           new data is better formatted.
Packit b802ec
         */
Packit b802ec
        printf("0 command-buffer-overflow\n");
Packit b802ec
        buffer->incoming_read_position = 0;
Packit b802ec
    }
Packit b802ec
}