/*
 * Copyright 2006-2013, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Alexander von Gluck <kallisti5@unixzen.com>
 */
 
 
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/sockio.h>
 
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
#include <NetworkAddress.h>
 
 
extern const char* __progname;
const char* kProgramName = __progname;
 
 
enum modes {
	RTM_LIST = 0,
	RTM_DELETE,
	RTM_ADD,
	RTM_GET,
 
	// TODO:
	RTM_CHANGE,
	RTM_FLUSH,
};
 
enum preferredPrefixFormat {
	PREFIX_PREFER_NETMASK = 0,
	PREFIX_PREFER_CIDR,
};
 
struct address_family {
	int			family;
	const char*	name;
	const char*	identifiers[4];
	int			maxAddressLength;
	int			preferredPrefixFormat;
};
 
static const address_family kFamilies[] = {
	{
		AF_INET,
		"IPv4",
		{"AF_INET", "inet", "ipv4", NULL},
		15,
		PREFIX_PREFER_NETMASK,
	},
	{
		AF_INET6,
		"IPv6",
		{"AF_INET6", "inet6", "ipv6", NULL},
		39,
		PREFIX_PREFER_CIDR,
	},
	{ -1, NULL, {NULL}, -1, -1 }
};
 
 
void
usage(int status)
{
	printf("usage: %s [command] [<interface>] [<address family>] "
			"<address|default> [<option/flags>...]\n"
		"Where <command> can be the one of:\n"
		"  add                - add a route for the specified interface\n"
		"  delete             - deletes the specified route\n"
		"  list               - list with filters [default]\n"
		"<option> can be the following:\n"
		"  netmask <addr>     - networking subnet mask\n"
		"  prefixlen <number> - subnet mask length in bits\n"
		"  gw <addr>          - gateway address\n"
		"  mtu <bytes>        - maximal transfer unit\n"
		"And <flags> can be: reject, local, host\n\n"
		"Example:\n"
		"\t%s add /dev/net/ipro1000/0 default gw 192.168.0.254\n",
		kProgramName, kProgramName);
 
	exit(status);
}
 
 
bool
prepare_request(struct ifreq& request, const char* name)
{
	if (strlen(name) >= IF_NAMESIZE) {
		fprintf(stderr, "%s: interface name \"%s\" is too long.\n",
			kProgramName, name);
		return false;
	}
 
	strcpy(request.ifr_name, name);
	return true;
}
 
 
bool
get_address_family(const char* argument, int32& familyIndex)
{
	for (int32 i = 0; kFamilies[i].family >= 0; i++) {
		for (int32 j = 0; kFamilies[i].identifiers[j]; j++) {
			if (!strcmp(argument, kFamilies[i].identifiers[j])) {
				// found a match
				familyIndex = i;
				return true;
			}
		}
	}
 
	// defaults to AF_INET
	familyIndex = 0;
	return false;
}
 
 
bool
parse_address(int32 familyIndex, const char* argument, BNetworkAddress& address)
{
	if (argument == NULL)
		return false;
 
	return address.SetTo(kFamilies[familyIndex].family, argument,
		(uint16)0, B_NO_ADDRESS_RESOLUTION) == B_OK;
}
 
 
bool
prefix_length_to_mask(int32 familyIndex, const char* argument,
	BNetworkAddress& mask)
{
	if (argument == NULL)
		return false;
 
	char* end;
	uint32 prefixLength = strtoul(argument, &end, 10);
	if (end == argument)
		return false;
 
	return mask.SetToMask(kFamilies[familyIndex].family, prefixLength) == B_OK;
}
 
 
//	#pragma mark -
 
 
void
list_routes(int socket, const char *interfaceName, route_entry &route)
{
	// get a list of all routes
 
	ifconf config;
	config.ifc_len = sizeof(config.ifc_value);
	if (ioctl(socket, SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0)
		return;
 
	uint32 size = (uint32)config.ifc_value;
	if (size == 0)
		return;
 
	void *buffer = malloc(size);
	if (buffer == NULL) {
		fprintf(stderr, "%s: Out of memory.\n", kProgramName);
		return;
	}
 
	config.ifc_len = size;
	config.ifc_buf = buffer;
	if (ioctl(socket, SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0)
		return;
 
	ifreq *interface = (ifreq*)buffer;
	ifreq *end = (ifreq*)((uint8*)buffer + size);
 
	// find family (we use the family of the first address as this is
	// called on a socket for a single family)
	const address_family *family = NULL;
	for (int32 i = 0; kFamilies[i].family >= 0; i++) {
		if (interface->ifr_route.destination->sa_family
				== kFamilies[i].family) {
			family = &kFamilies[i];
			break;
		}
	}
 
	int addressLength = family->maxAddressLength;
 
	printf("%s routing table:\n", family->name);
 
	if (family->preferredPrefixFormat == PREFIX_PREFER_NETMASK) {
		printf("%*s %*s %*s Flags  Interface\n", addressLength, "Destination",
			addressLength, "Netmask", addressLength, "Gateway");
	} else {
		printf("%*s     %*s Flags  Interface\n", addressLength, "Destination",
			addressLength, "Gateway");
	}
 
	while (interface < end) {
		route_entry& route = interface->ifr_route;
 
		// apply filters
		if (interfaceName == NULL
			|| !strcmp(interfaceName, interface->ifr_name)) {
 
			if (family != NULL) {
				BNetworkAddress destination(*route.destination);
				printf("%*s", addressLength, destination.ToString().String());
				if (route.mask != NULL) {
					BNetworkAddress mask;
					mask.SetTo(*route.mask);
					if (family->preferredPrefixFormat
							== PREFIX_PREFER_NETMASK) {
						printf(" %*s ", addressLength,
							mask.ToString().String());
					} else {
						printf("/%-3zd ", mask.PrefixLength());
					}
				} else {
					if (family->preferredPrefixFormat
							== PREFIX_PREFER_NETMASK) {
						printf(" %*s ", addressLength, "-");
					} else
						printf("     ");
				}
 
				if ((route.flags & RTF_GATEWAY) != 0) {
					BNetworkAddress gateway;
					if (route.gateway != NULL)
						gateway.SetTo(*route.gateway);
					printf("%*s ", addressLength, gateway.ToString().String());
				} else
					printf("%*s ", family->maxAddressLength, "-");
			} else
				printf("unknown family ");
 
			if (route.flags != 0) {
				const struct {
					int			value;
					const char	*name;
				} kFlags[] = {
					{RTF_DEFAULT, "D"},
					{RTF_REJECT, "R"},
					{RTF_HOST, "H"},
					{RTF_LOCAL, "L"},
					{RTF_DYNAMIC, "D"},
					{RTF_MODIFIED, "M"},
				};
				for (uint32 i = 0; i < sizeof(kFlags) / sizeof(kFlags[0]);
					i++) {
					if ((route.flags & kFlags[i].value) != 0) {
						printf(kFlags[i].name);
					} else
						putchar('-');
				}
				printf(" ");
			} else
				printf("------ ");
 
			printf("%s", interface->ifr_name);
			putchar('\n');
		}
 
		int32 addressSize = 0;
		if (route.destination != NULL)
			addressSize += route.destination->sa_len;
		if (route.mask != NULL)
			addressSize += route.mask->sa_len;
		if (route.gateway != NULL)
			addressSize += route.gateway->sa_len;
 
		interface = (ifreq*)((addr_t)interface + IF_NAMESIZE
			+ sizeof(route_entry) + addressSize);
	}
 
	putchar('\n');
	free(buffer);
}
 
 
void
delete_route(int socket, const char *interface, route_entry &route)
{
	ifreq request;
	if (!prepare_request(request, interface))
		return;
 
	request.ifr_route = route;
 
	if (ioctl(socket, SIOCDELRT, &request, sizeof(request)) < 0) {
		fprintf(stderr, "%s: Could not delete route for %s: %s\n",
			kProgramName, interface, strerror(errno));
	}
}
 
 
void
add_route(int socket, const char *interface, route_entry &route)
{
	ifreq request;
	if (!prepare_request(request, interface))
		return;
 
	route.flags |= RTF_STATIC;
	request.ifr_route = route;
 
	if (ioctl(socket, SIOCADDRT, &request, sizeof(request)) < 0) {
		fprintf(stderr, "%s: Could not add route for %s: %s\n",
			kProgramName, interface, strerror(errno));
	}
}
 
 
void
get_route(int socket, route_entry &route)
{
	union {
		route_entry request;
		uint8 buffer[512];
	};
 
	request = route;
 
	if (ioctl(socket, SIOCGETRT, buffer, sizeof(buffer)) < 0) {
		fprintf(stderr, "%s: Could not get route: %s\n",
			kProgramName, strerror(errno));
		return;
	}
 
	// find family
	const address_family *family = NULL;
	for (int32 i = 0; kFamilies[i].family >= 0; i++) {
		if (route.destination->sa_family == kFamilies[i].family) {
			family = &kFamilies[i];
			break;
		}
	}
 
	if (family != NULL) {
		BNetworkAddress destination(*request.destination);
		BNetworkAddress mask(*request.mask);
		printf("%s", destination.ToString().String());
		printf("/%zd ", mask.PrefixLength());
 
		BNetworkAddress gateway(*request.gateway);
		if (request.flags & RTF_GATEWAY)
			printf("gateway %s ", gateway.ToString().String());
 
		BNetworkAddress source(*request.source);
		printf("source %s\n", source.ToString().String());
	} else {
		printf("unknown family ");
	}
}
 
 
//	#pragma mark -
 
 
int
main(int argc, char** argv)
{
	if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
		usage(0);
 
	int32 mode = RTM_LIST;
 
	if (argc > 1) {
		if (!strcmp(argv[1], "delete")
			|| !strcmp(argv[1], "del")
			|| !strcmp(argv[1], "-d")) {
			// delete route
			if (argc < 3)
				usage(1);
 
			mode = RTM_DELETE;
		} else if (!strcmp(argv[1], "add")
			|| !strcmp(argv[1], "-a")) {
			// add route
			if (argc < 3)
				usage(1);
 
			mode = RTM_ADD;
		} else if (!strcmp(argv[1], "get")) {
			// get route for destination
			if (argc < 3)
				usage(1);
 
			mode = RTM_GET;
		}
	}
 
	int32 i = 2;
	int32 interfaceIndex = i;
	bool familySpecified = false;
	int32 familyIndex = 0;
	const char *interface = NULL;
	BNetworkAddress destination;
	BNetworkAddress mask;
	BNetworkAddress gateway;
	bool defaultRoute = false;
 
	route_entry route;
	memset(&route, 0, sizeof(route_entry));
 
	while (i < argc && i < 5) {
		// try to parse address family
		if (i <= 3 && familySpecified == false
				&& get_address_family(argv[i], familyIndex)) {
			familySpecified = true;
			if (i == 2)
				interfaceIndex = -1;
		}
 
		if (!strcmp(argv[i], "default")) {
			defaultRoute = true;
			route.flags = RTF_DEFAULT;
			i++;
			break;
		} else if (parse_address(familyIndex, argv[i], destination)) {
			i++;
			break;
		}
 
		i++;
	}
 
	if (!defaultRoute && destination.IsEmpty() && mode != RTM_LIST)
		usage(1);
 
	if (i == 3)
		interfaceIndex = -1;
	if (interfaceIndex != -1 && interfaceIndex < argc)
		interface = argv[interfaceIndex];
 
	if (i < argc && parse_address(familyIndex, argv[i], mask))
		i++;
 
	// parse options and flags
 
	while (i < argc) {
		if (!strcmp(argv[i], "gw") || !strcmp(argv[i], "gateway")) {
			if (!parse_address(familyIndex, argv[i + 1], gateway)) {
				fprintf(stderr, "%s: Option 'gw' needs valid address "
					"parameter\n", kProgramName);
				exit(1);
			}
			i++;
		} else if (!strcmp(argv[i], "nm") || !strcmp(argv[i], "netmask")) {
			if (!mask.IsEmpty()) {
				fprintf(stderr, "%s: Netmask or prefix length is specified "
					"twice\n", kProgramName);
				exit(1);
			}
			if (!parse_address(familyIndex, argv[i + 1], mask)) {
				fprintf(stderr, "%s: Option 'netmask' needs valid address "
					"parameter\n", kProgramName);
				exit(1);
			}
			i++;
		} else if (!strcmp(argv[i], "plen") || !strcmp(argv[i], "prefixlen")
			|| !strcmp(argv[i], "prefix-length")) {
			if (!mask.IsEmpty()) {
				fprintf(stderr, "%s: Netmask or prefix length is specified "
					"twice\n", kProgramName);
				exit(1);
			}
			if (!prefix_length_to_mask(familyIndex, argv[i + 1], mask)) {
				fprintf(stderr, "%s: Option 'prefixlen' is invalid for this "
					"address family\n", kProgramName);
				exit(1);
			}
			i++;
		} else if (!strcmp(argv[i], "mtu")) {
			route.mtu = argv[i + 1] ? strtol(argv[i + 1], NULL, 0) : 0;
			if (route.mtu <= 500) {
				fprintf(stderr, "%s: Option 'mtu' exptected valid max transfer "
					"unit size\n", kProgramName);
				exit(1);
			}
			i++;
		} else if (!strcmp(argv[i], "host")) {
			route.flags |= RTF_HOST;
		} else if (!strcmp(argv[i], "local")) {
			route.flags |= RTF_LOCAL;
		} else if (!strcmp(argv[i], "reject")) {
			route.flags |= RTF_REJECT;
		} else
			usage(1);
 
		i++;
	}
 
	if (!destination.IsEmpty())
		route.destination = (sockaddr*)destination;
	if (!mask.IsEmpty())
		route.mask = (sockaddr*)mask;
	if (!gateway.IsEmpty()) {
		route.gateway = (sockaddr*)gateway;
		route.flags |= RTF_GATEWAY;
	}
 
	// we need a socket to talk to the networking stack
	int socket = ::socket(kFamilies[familyIndex].family, SOCK_DGRAM, 0);
	if (socket < 0) {
		fprintf(stderr, "%s: The requested address family is not available.\n",
			kProgramName);
		return 1;
	}
 
	switch (mode) {
		case RTM_ADD:
			if (interface == NULL) {
				fprintf(stderr, "%s: You need to specify an interface when "
					"adding a route.\n", kProgramName);
				usage(1);
			}
 
			add_route(socket, interface, route);
			break;
		case RTM_DELETE:
			if (interface == NULL) {
				fprintf(stderr, "%s: You need to specify an interface when "
					"removing a route.\n", kProgramName);
				usage(1);
			}
 
			delete_route(socket, interface, route);
			break;
 
		case RTM_LIST:
			if (familySpecified)
				list_routes(socket, interface, route);
			else {
				for (int32 i = 0; kFamilies[i].family >= 0; i++) {
					int socket = ::socket(kFamilies[i].family, SOCK_DGRAM, 0);
					if (socket < 0)
						continue;
 
					list_routes(socket, interface, route);
					close(socket);
				}
			}
			break;
 
		case RTM_GET:
			get_route(socket, route);
			break;
	}
 
	close(socket);
	return 0;
}
 

V595 The 'family' pointer was utilized before it was verified against nullptr. Check lines: 200, 219.