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