/*
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 <assert.h>
#include <errno.h>
#ifdef HAVE_ERROR_H
#include <error.h>
#else
#include "portability/error.h"
#endif
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#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;
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>