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