1/*
2 * Copyright 2006-2013, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel D��rfler, axeld@pinc-software.de
7 *		Alexander von Gluck <kallisti5@unixzen.com>
8 */
9
10
11#include <arpa/inet.h>
12#include <net/if.h>
13#include <net/if_dl.h>
14#include <net/if_types.h>
15#include <netinet/in.h>
16#include <sys/socket.h>
17#include <sys/sockio.h>
18
19#include <errno.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <unistd.h>
24
25#include <NetworkAddress.h>
26
27
28extern const char* __progname;
29const char* kProgramName = __progname;
30
31
32enum modes {
33	RTM_LIST = 0,
34	RTM_DELETE,
35	RTM_ADD,
36	RTM_GET,
37
38	// TODO:
39	RTM_CHANGE,
40	RTM_FLUSH,
41};
42
43enum preferredPrefixFormat {
44	PREFIX_PREFER_NETMASK = 0,
45	PREFIX_PREFER_CIDR,
46};
47
48struct address_family {
49	int			family;
50	const char*	name;
51	const char*	identifiers[4];
52	int			maxAddressLength;
53	int			preferredPrefixFormat;
54};
55
56static const address_family kFamilies[] = {
57	{
58		AF_INET,
59		"IPv4",
60		{"AF_INET", "inet", "ipv4", NULL},
61		15,
62		PREFIX_PREFER_NETMASK,
63	},
64	{
65		AF_INET6,
66		"IPv6",
67		{"AF_INET6", "inet6", "ipv6", NULL},
68		39,
69		PREFIX_PREFER_CIDR,
70	},
71	{ -1, NULL, {NULL}, -1, -1 }
72};
73
74
75void
76usage(int status)
77{
78	printf("usage: %s [command] [<interface>] [<address family>] "
79			"<address|default> [<option/flags>...]\n"
80		"Where <command> can be the one of:\n"
81		"  add                - add a route for the specified interface\n"
82		"  delete             - deletes the specified route\n"
83		"  list               - list with filters [default]\n"
84		"<option> can be the following:\n"
85		"  netmask <addr>     - networking subnet mask\n"
86		"  prefixlen <number> - subnet mask length in bits\n"
87		"  gw <addr>          - gateway address\n"
88		"  mtu <bytes>        - maximal transfer unit\n"
89		"And <flags> can be: reject, local, host\n\n"
90		"Example:\n"
91		"\t%s add /dev/net/ipro1000/0 default gw 192.168.0.254\n",
92		kProgramName, kProgramName);
93
94	exit(status);
95}
96
97
98bool
99prepare_request(struct ifreq& request, const char* name)
100{
101	if (strlen(name) >= IF_NAMESIZE) {
102		fprintf(stderr, "%s: interface name \"%s\" is too long.\n",
103			kProgramName, name);
104		return false;
105	}
106
107	strcpy(request.ifr_name, name);
108	return true;
109}
110
111
112bool
113get_address_family(const char* argument, int32& familyIndex)
114{
115	for (int32 i = 0; kFamilies[i].family >= 0; i++) {
116		for (int32 j = 0; kFamilies[i].identifiers[j]; j++) {
117			if (!strcmp(argument, kFamilies[i].identifiers[j])) {
118				// found a match
119				familyIndex = i;
120				return true;
121			}
122		}
123	}
124
125	// defaults to AF_INET
126	familyIndex = 0;
127	return false;
128}
129
130
131bool
132parse_address(int32 familyIndex, const char* argument, BNetworkAddress& address)
133{
134	if (argument == NULL)
135		return false;
136
137	return address.SetTo(kFamilies[familyIndex].family, argument,
138		(uint16)0, B_NO_ADDRESS_RESOLUTION) == B_OK;
139}
140
141
142bool
143prefix_length_to_mask(int32 familyIndex, const char* argument,
144	BNetworkAddress& mask)
145{
146	if (argument == NULL)
147		return false;
148
149	char* end;
150	uint32 prefixLength = strtoul(argument, &end, 10);
151	if (end == argument)
152		return false;
153
154	return mask.SetToMask(kFamilies[familyIndex].family, prefixLength) == B_OK;
155}
156
157
158//	#pragma mark -
159
160
161void
162list_routes(int socket, const char *interfaceName, route_entry &route)
163{
164	// get a list of all routes
165
166	ifconf config;
167	config.ifc_len = sizeof(config.ifc_value);
168	if (ioctl(socket, SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0)
169		return;
170
171	uint32 size = (uint32)config.ifc_value;
172	if (size == 0)
173		return;
174
175	void *buffer = malloc(size);
176	if (buffer == NULL) {
177		fprintf(stderr, "%s: Out of memory.\n", kProgramName);
178		return;
179	}
180
181	config.ifc_len = size;
182	config.ifc_buf = buffer;
183	if (ioctl(socket, SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0)
184		return;
185
186	ifreq *interface = (ifreq*)buffer;
187	ifreq *end = (ifreq*)((uint8*)buffer + size);
188
189	// find family (we use the family of the first address as this is
190	// called on a socket for a single family)
191	const address_family *family = NULL;
192	for (int32 i = 0; kFamilies[i].family >= 0; i++) {
193		if (interface->ifr_route.destination->sa_family
194				== kFamilies[i].family) {
195			family = &kFamilies[i];
196			break;
197		}
198	}
199
200	int addressLength = family->maxAddressLength;
201
202	printf("%s routing table:\n", family->name);
203
204	if (family->preferredPrefixFormat == PREFIX_PREFER_NETMASK) {
205		printf("%*s %*s %*s Flags  Interface\n", addressLength, "Destination",
206			addressLength, "Netmask", addressLength, "Gateway");
207	} else {
208		printf("%*s     %*s Flags  Interface\n", addressLength, "Destination",
209			addressLength, "Gateway");
210	}
211
212	while (interface < end) {
213		route_entry& route = interface->ifr_route;
214
215		// apply filters
216		if (interfaceName == NULL
217			|| !strcmp(interfaceName, interface->ifr_name)) {
218
219			if (family != NULL) {
220				BNetworkAddress destination(*route.destination);
221				printf("%*s", addressLength, destination.ToString().String());
222				if (route.mask != NULL) {
223					BNetworkAddress mask;
224					mask.SetTo(*route.mask);
225					if (family->preferredPrefixFormat
226							== PREFIX_PREFER_NETMASK) {
227						printf(" %*s ", addressLength,
228							mask.ToString().String());
229					} else {
230						printf("/%-3zd ", mask.PrefixLength());
231					}
232				} else {
233					if (family->preferredPrefixFormat
234							== PREFIX_PREFER_NETMASK) {
235						printf(" %*s ", addressLength, "-");
236					} else
237						printf("     ");
238				}
239
240				if ((route.flags & RTF_GATEWAY) != 0) {
241					BNetworkAddress gateway;
242					if (route.gateway != NULL)
243						gateway.SetTo(*route.gateway);
244					printf("%*s ", addressLength, gateway.ToString().String());
245				} else
246					printf("%*s ", family->maxAddressLength, "-");
247			} else
248				printf("unknown family ");
249
250			if (route.flags != 0) {
251				const struct {
252					int			value;
253					const char	*name;
254				} kFlags[] = {
255					{RTF_DEFAULT, "D"},
256					{RTF_REJECT, "R"},
257					{RTF_HOST, "H"},
258					{RTF_LOCAL, "L"},
259					{RTF_DYNAMIC, "D"},
260					{RTF_MODIFIED, "M"},
261				};
262				for (uint32 i = 0; i < sizeof(kFlags) / sizeof(kFlags[0]);
263					i++) {
264					if ((route.flags & kFlags[i].value) != 0) {
265						printf(kFlags[i].name);
266					} else
267						putchar('-');
268				}
269				printf(" ");
270			} else
271				printf("------ ");
272
273			printf("%s", interface->ifr_name);
274			putchar('\n');
275		}
276
277		int32 addressSize = 0;
278		if (route.destination != NULL)
279			addressSize += route.destination->sa_len;
280		if (route.mask != NULL)
281			addressSize += route.mask->sa_len;
282		if (route.gateway != NULL)
283			addressSize += route.gateway->sa_len;
284
285		interface = (ifreq*)((addr_t)interface + IF_NAMESIZE
286			+ sizeof(route_entry) + addressSize);
287	}
288
289	putchar('\n');
290	free(buffer);
291}
292
293
294void
295delete_route(int socket, const char *interface, route_entry &route)
296{
297	ifreq request;
298	if (!prepare_request(request, interface))
299		return;
300
301	request.ifr_route = route;
302
303	if (ioctl(socket, SIOCDELRT, &request, sizeof(request)) < 0) {
304		fprintf(stderr, "%s: Could not delete route for %s: %s\n",
305			kProgramName, interface, strerror(errno));
306	}
307}
308
309
310void
311add_route(int socket, const char *interface, route_entry &route)
312{
313	ifreq request;
314	if (!prepare_request(request, interface))
315		return;
316
317	route.flags |= RTF_STATIC;
318	request.ifr_route = route;
319
320	if (ioctl(socket, SIOCADDRT, &request, sizeof(request)) < 0) {
321		fprintf(stderr, "%s: Could not add route for %s: %s\n",
322			kProgramName, interface, strerror(errno));
323	}
324}
325
326
327void
328get_route(int socket, route_entry &route)
329{
330	union {
331		route_entry request;
332		uint8 buffer[512];
333	};
334
335	request = route;
336
337	if (ioctl(socket, SIOCGETRT, buffer, sizeof(buffer)) < 0) {
338		fprintf(stderr, "%s: Could not get route: %s\n",
339			kProgramName, strerror(errno));
340		return;
341	}
342
343	// find family
344	const address_family *family = NULL;
345	for (int32 i = 0; kFamilies[i].family >= 0; i++) {
346		if (route.destination->sa_family == kFamilies[i].family) {
347			family = &kFamilies[i];
348			break;
349		}
350	}
351
352	if (family != NULL) {
353		BNetworkAddress destination(*request.destination);
354		BNetworkAddress mask(*request.mask);
355		printf("%s", destination.ToString().String());
356		printf("/%zd ", mask.PrefixLength());
357
358		BNetworkAddress gateway(*request.gateway);
359		if (request.flags & RTF_GATEWAY)
360			printf("gateway %s ", gateway.ToString().String());
361
362		BNetworkAddress source(*request.source);
363		printf("source %s\n", source.ToString().String());
364	} else {
365		printf("unknown family ");
366	}
367}
368
369
370//	#pragma mark -
371
372
373int
374main(int argc, char** argv)
375{
376	if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
377		usage(0);
378
379	int32 mode = RTM_LIST;
380
381	if (argc > 1) {
382		if (!strcmp(argv[1], "delete")
383			|| !strcmp(argv[1], "del")
384			|| !strcmp(argv[1], "-d")) {
385			// delete route
386			if (argc < 3)
387				usage(1);
388
389			mode = RTM_DELETE;
390		} else if (!strcmp(argv[1], "add")
391			|| !strcmp(argv[1], "-a")) {
392			// add route
393			if (argc < 3)
394				usage(1);
395
396			mode = RTM_ADD;
397		} else if (!strcmp(argv[1], "get")) {
398			// get route for destination
399			if (argc < 3)
400				usage(1);
401
402			mode = RTM_GET;
403		}
404	}
405
406	int32 i = 2;
407	int32 interfaceIndex = i;
408	bool familySpecified = false;
409	int32 familyIndex = 0;
410	const char *interface = NULL;
411	BNetworkAddress destination;
412	BNetworkAddress mask;
413	BNetworkAddress gateway;
414	bool defaultRoute = false;
415
416	route_entry route;
417	memset(&route, 0, sizeof(route_entry));
418
419	while (i < argc && i < 5) {
420		// try to parse address family
421		if (i <= 3 && familySpecified == false
422				&& get_address_family(argv[i], familyIndex)) {
423			familySpecified = true;
424			if (i == 2)
425				interfaceIndex = -1;
426		}
427
428		if (!strcmp(argv[i], "default")) {
429			defaultRoute = true;
430			route.flags = RTF_DEFAULT;
431			i++;
432			break;
433		} else if (parse_address(familyIndex, argv[i], destination)) {
434			i++;
435			break;
436		}
437
438		i++;
439	}
440
441	if (!defaultRoute && destination.IsEmpty() && mode != RTM_LIST)
442		usage(1);
443
444	if (i == 3)
445		interfaceIndex = -1;
446	if (interfaceIndex != -1 && interfaceIndex < argc)
447		interface = argv[interfaceIndex];
448
449	if (i < argc && parse_address(familyIndex, argv[i], mask))
450		i++;
451
452	// parse options and flags
453
454	while (i < argc) {
455		if (!strcmp(argv[i], "gw") || !strcmp(argv[i], "gateway")) {
456			if (!parse_address(familyIndex, argv[i + 1], gateway)) {
457				fprintf(stderr, "%s: Option 'gw' needs valid address "
458					"parameter\n", kProgramName);
459				exit(1);
460			}
461			i++;
462		} else if (!strcmp(argv[i], "nm") || !strcmp(argv[i], "netmask")) {
463			if (!mask.IsEmpty()) {
464				fprintf(stderr, "%s: Netmask or prefix length is specified "
465					"twice\n", kProgramName);
466				exit(1);
467			}
468			if (!parse_address(familyIndex, argv[i + 1], mask)) {
469				fprintf(stderr, "%s: Option 'netmask' needs valid address "
470					"parameter\n", kProgramName);
471				exit(1);
472			}
473			i++;
474		} else if (!strcmp(argv[i], "plen") || !strcmp(argv[i], "prefixlen")
475			|| !strcmp(argv[i], "prefix-length")) {
476			if (!mask.IsEmpty()) {
477				fprintf(stderr, "%s: Netmask or prefix length is specified "
478					"twice\n", kProgramName);
479				exit(1);
480			}
481			if (!prefix_length_to_mask(familyIndex, argv[i + 1], mask)) {
482				fprintf(stderr, "%s: Option 'prefixlen' is invalid for this "
483					"address family\n", kProgramName);
484				exit(1);
485			}
486			i++;
487		} else if (!strcmp(argv[i], "mtu")) {
488			route.mtu = argv[i + 1] ? strtol(argv[i + 1], NULL, 0) : 0;
489			if (route.mtu <= 500) {
490				fprintf(stderr, "%s: Option 'mtu' exptected valid max transfer "
491					"unit size\n", kProgramName);
492				exit(1);
493			}
494			i++;
495		} else if (!strcmp(argv[i], "host")) {
496			route.flags |= RTF_HOST;
497		} else if (!strcmp(argv[i], "local")) {
498			route.flags |= RTF_LOCAL;
499		} else if (!strcmp(argv[i], "reject")) {
500			route.flags |= RTF_REJECT;
501		} else
502			usage(1);
503
504		i++;
505	}
506
507	if (!destination.IsEmpty())
508		route.destination = (sockaddr*)destination;
509	if (!mask.IsEmpty())
510		route.mask = (sockaddr*)mask;
511	if (!gateway.IsEmpty()) {
512		route.gateway = (sockaddr*)gateway;
513		route.flags |= RTF_GATEWAY;
514	}
515
516	// we need a socket to talk to the networking stack
517	int socket = ::socket(kFamilies[familyIndex].family, SOCK_DGRAM, 0);
518	if (socket < 0) {
519		fprintf(stderr, "%s: The requested address family is not available.\n",
520			kProgramName);
521		return 1;
522	}
523
524	switch (mode) {
525		case RTM_ADD:
526			if (interface == NULL) {
527				fprintf(stderr, "%s: You need to specify an interface when "
528					"adding a route.\n", kProgramName);
529				usage(1);
530			}
531
532			add_route(socket, interface, route);
533			break;
534		case RTM_DELETE:
535			if (interface == NULL) {
536				fprintf(stderr, "%s: You need to specify an interface when "
537					"removing a route.\n", kProgramName);
538				usage(1);
539			}
540
541			delete_route(socket, interface, route);
542			break;
543
544		case RTM_LIST:
545			if (familySpecified)
546				list_routes(socket, interface, route);
547			else {
548				for (int32 i = 0; kFamilies[i].family >= 0; i++) {
549					int socket = ::socket(kFamilies[i].family, SOCK_DGRAM, 0);
550					if (socket < 0)
551						continue;
552
553					list_routes(socket, interface, route);
554					close(socket);
555				}
556			}
557			break;
558
559		case RTM_GET:
560			get_route(socket, route);
561			break;
562	}
563
564	close(socket);
565	return 0;
566}
567
568