1/*
2 * Copyright 2006-2008, 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 <arp_control.h>
11
12#include <generic_syscall_defs.h>
13#include <syscalls.h>
14
15#include <arpa/inet.h>
16#include <netdb.h>
17#include <netinet/in.h>
18
19#include <ctype.h>
20#include <errno.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25
26
27extern const char* __progname;
28const char* kProgramName = __progname;
29
30
31enum modes {
32	ARP_LIST = 0,
33	ARP_DELETE,
34	ARP_SET,
35	ARP_SET_FROM_FILE
36};
37
38
39static const char *
40ethernet_address_to_string(uint8 *address)
41{
42	static char string[64];
43	snprintf(string, sizeof(string), "%02x:%02x:%02x:%02x:%02x:%02x",
44		address[0], address[1], address[2], address[3], address[4], address[5]);
45	return string;
46}
47
48
49static bool
50parse_ethernet_address(const char *string, uint8 *address)
51{
52	return sscanf(string, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &address[0], &address[1],
53		&address[2], &address[3], &address[4], &address[5]) == 6;
54}
55
56
57static bool
58parse_inet_address(const char* string, sockaddr_in& address)
59{
60	in_addr inetAddress;
61	if (inet_aton(string, &inetAddress) != 1)
62		return false;
63
64	address.sin_family = AF_INET;
65	address.sin_len = sizeof(struct sockaddr_in);
66	address.sin_port = 0;
67	address.sin_addr = inetAddress;
68	memset(&address.sin_zero[0], 0, sizeof(address.sin_zero));
69
70	return true;
71}
72
73
74static void
75check_for_arp_syscall(void)
76{
77	uint32 version = 0;
78	status_t status = _kern_generic_syscall(ARP_SYSCALLS, B_SYSCALL_INFO,
79		&version, sizeof(version));
80	if (status != B_OK) {
81		fprintf(stderr, "\"ARP\" module not available.\n");
82		exit(1);
83	}
84}
85
86
87static void
88usage(int status)
89{
90	printf("usage: %s [<command>] [<hostname>] [<ethernet-address>] [temp] [pub]\n"
91		"       %s -f <filename>\n"
92		"Where <command> can be the one of:\n"
93		"  -s  - sets an entry in the ARP cache\n"
94		"  -d  - deletes the specified ARP cache entry\n"
95		"  -a  - list all entries [default]\n"
96		"  -f  - read ARP entries from file\n"
97		"  -F  - flush all temporary ARP entries\n"
98		"The ethernet address is specified by six hex bytes separated by colons.\n"
99		"When setting a new ARP cache entry, \"temp\" creates a temporary entry\n"
100		"that will be removed after a timeout, and \"pub\" will cause ARP to\n"
101		"answer to ARP requests for this entry.\n\n"
102		"Example:\n"
103		"\t%s -s 192.168.0.2 00:09:ab:cd:ef:12 pub\n",
104		kProgramName, kProgramName, kProgramName);
105
106	exit(status);
107}
108
109
110static bool
111set_flags(uint32 &flags, const char *argument)
112{
113	if (!strcmp(argument, "temp") || !strcmp(argument, "temporary"))
114		flags &= ~ARP_FLAG_PERMANENT;
115	else if (!strcmp(argument, "pub") || !strcmp(argument, "publish"))
116		flags |= ARP_FLAG_PUBLISH;
117	else if (!strcmp(argument, "reject"))
118		flags |= ARP_FLAG_REJECT;
119	else
120		return false;
121
122	return true;
123}
124
125
126static char *
127next_argument(char **_line)
128{
129	char *start = *_line;
130	if (!start[0])
131		return NULL;
132
133	char *end = start;
134	while (end[0] && !isspace(end[0])) {
135		end++;
136	}
137
138	if (end[0]) {
139		// skip all whitespace until next argument (or end of line)
140		end[0] = '\0';
141		end++;
142
143		while (end[0] && isspace(end[0])) {
144			end++;
145		}
146	}
147
148	*_line = end;
149	return start;
150}
151
152
153//	#pragma mark -
154
155
156static void
157list_entry(arp_control &control)
158{
159	in_addr address;
160	address.s_addr = control.address;
161	printf("%15s  %s", inet_ntoa(address),
162		ethernet_address_to_string(control.ethernet_address));
163
164	if (control.flags != 0) {
165		const struct {
166			int			value;
167			const char	*name;
168		} kFlags[] = {
169			{ARP_FLAG_PERMANENT, "permanent"},
170			{ARP_FLAG_LOCAL, "local"},
171			{ARP_FLAG_PUBLISH, "publish"},
172			{ARP_FLAG_REJECT, "reject"},
173			{ARP_FLAG_VALID, "valid"},
174		};
175		bool first = true;
176
177		for (uint32 i = 0; i < sizeof(kFlags) / sizeof(kFlags[0]); i++) {
178			if ((control.flags & kFlags[i].value) != 0) {
179				if (first) {
180					printf("  ");
181					first = false;
182				} else
183					putchar(' ');
184				fputs(kFlags[i].name, stdout);
185			}
186		}
187	}
188
189	putchar('\n');
190}
191
192
193static void
194list_entries(sockaddr_in *address)
195{
196	arp_control control;
197	status_t status;
198
199	if (address != NULL) {
200		control.address = address->sin_addr.s_addr;
201		status = _kern_generic_syscall(ARP_SYSCALLS, ARP_GET_ENTRY,
202			&control, sizeof(arp_control));
203		if (status != B_OK) {
204			fprintf(stderr, "%s: ARP entry does not exist.\n", kProgramName);
205			exit(1);
206		}
207
208		list_entry(control);
209		return;
210	}
211
212	control.cookie = 0;
213	while (_kern_generic_syscall(ARP_SYSCALLS, ARP_GET_ENTRIES,
214			&control, sizeof(arp_control)) == B_OK) {
215		list_entry(control);
216	}
217}
218
219
220static void
221delete_entry(sockaddr_in *address)
222{
223	arp_control control;
224	control.address = address->sin_addr.s_addr;
225
226	status_t status = _kern_generic_syscall(ARP_SYSCALLS, ARP_DELETE_ENTRY,
227		&control, sizeof(arp_control));
228	if (status != B_OK) {
229		fprintf(stderr, "%s: Could not delete ARP entry: %s\n",
230			kProgramName, strerror(status));
231		exit(1);
232	}
233}
234
235
236static status_t
237set_entry(sockaddr_in *address, uint8 *ethernetAddress, uint32 flags)
238{
239	arp_control control;
240	control.address = address->sin_addr.s_addr;
241	memcpy(control.ethernet_address, ethernetAddress, ETHER_ADDRESS_LENGTH);
242	control.flags = flags;
243
244	return _kern_generic_syscall(ARP_SYSCALLS, ARP_SET_ENTRY,
245		&control, sizeof(arp_control));
246}
247
248
249static int
250set_entries_from_file(const char *filename)
251{
252	FILE *file = fopen(filename, "r");
253	if (file == NULL) {
254		fprintf(stderr, "%s: Could not open file: %s\n", kProgramName, strerror(errno));
255		return 1;
256	}
257
258	int32 counter = 0;
259	char line[4096];
260
261	while (fgets(line, sizeof(line), file) != NULL) {
262		counter++;
263
264		// comments and empty lines are allowed
265		if (line[0] == '#' || !strcmp(line, "\n"))
266			continue;
267
268		// parse hostname
269
270		char *rest = line;
271		const char *argument = next_argument(&rest);
272		if (argument == NULL) {
273			fprintf(stderr, "%s: Line %" B_PRId32 " is invalid (missing hostname).\n",
274				kProgramName, counter);
275			continue;
276		}
277
278		sockaddr_in address;
279		if (!parse_inet_address(argument, address)) {
280			// get host by name
281			struct hostent *host = gethostbyname(argument);
282			if (host == NULL) {
283				fprintf(stderr, "%s: Line %" B_PRId32 ", host \"%s\" is not known in the IPv4 domain: %s\n",
284					kProgramName, counter, argument, strerror(errno));
285				continue;
286			}
287			if (host->h_addrtype != AF_INET) {
288				fprintf(stderr, "%s: Line %" B_PRId32 ", host \"%s\" is not known in the IPv4 domain.\n",
289					kProgramName, counter, argument);
290				continue;
291			}
292
293			address.sin_family = AF_INET;
294			address.sin_len = sizeof(sockaddr_in);
295			address.sin_addr.s_addr = *(in_addr_t *)host->h_addr;
296		}
297
298		// parse ethernet MAC address
299
300		argument = next_argument(&rest);
301		if (argument == NULL) {
302			fprintf(stderr, "%s: Line %" B_PRId32 " is invalid (missing ethernet address).\n",
303				kProgramName, counter);
304			continue;
305		}
306
307		uint8 ethernetAddress[6];
308		if (!parse_ethernet_address(argument, ethernetAddress)) {
309			fprintf(stderr, "%s: Line %" B_PRId32 ", \"%s\" is not a valid ethernet address.\n",
310				kProgramName, counter, argument);
311			continue;
312		}
313
314		// parse other options
315
316		uint32 flags = ARP_FLAG_PERMANENT;
317		while ((argument = next_argument(&rest)) != NULL) {
318			if (!set_flags(flags, argument)) {
319				fprintf(stderr, "%s: Line %" B_PRId32 ", ignoring invalid flag \"%s\".\n",
320					kProgramName, counter, argument);
321			}
322		}
323
324		status_t status = set_entry(&address, ethernetAddress, flags);
325		if (status != B_OK) {
326			fprintf(stderr, "%s: Line %" B_PRId32 ", ARP entry could not been set: %s\n",
327				kProgramName, counter, strerror(status));
328		}
329	}
330
331	fclose(file);
332	return 0;
333}
334
335
336static void
337flush_entries()
338{
339	arp_control control;
340
341	_kern_generic_syscall(ARP_SYSCALLS, ARP_FLUSH_ENTRIES,
342		&control, sizeof(arp_control));
343}
344
345
346//	#pragma mark -
347
348
349int
350main(int argc, char** argv)
351{
352	if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
353		usage(0);
354
355	int32 mode = ARP_LIST;
356	int32 i = 1;
357
358	if (argc > 1 && argv[1][0] == '-') {
359		if (!strcmp(argv[1], "-d")) {
360			// delete entry
361			if (argc != 3)
362				usage(1);
363
364			mode = ARP_DELETE;
365		} else if (!strcmp(argv[1], "-s")) {
366			// set entry
367			if (argc < 4)
368				usage(1);
369
370			mode = ARP_SET;
371		} else if (!strcmp(argv[1], "-a")) {
372			// list all
373			if (argc != 2)
374				usage(1);
375
376			mode = ARP_LIST;
377		} else if (!strcmp(argv[1], "-F")) {
378			// flush all entries
379			if (argc != 2)
380				usage(1);
381
382			check_for_arp_syscall();
383			flush_entries();
384			return 0;
385		} else if (!strcmp(argv[1], "-f")) {
386			// set from file
387			if (argc != 3)
388				usage(1);
389
390			check_for_arp_syscall();
391			return set_entries_from_file(argv[2]);
392		} else {
393			fprintf(stderr, "%s: Unknown option %s\n", kProgramName, argv[1]);
394			usage(1);
395		}
396
397		i++;
398	}
399
400	// parse hostname
401
402	const char *hostname = argv[i];
403	sockaddr_in address;
404
405	if (hostname != NULL && !parse_inet_address(hostname, address)) {
406		// get host by name
407		struct hostent *host = gethostbyname(hostname);
408		if (host == NULL) {
409			fprintf(stderr, "%s: Host \"%s\" is not known in the IPv4 domain: %s\n",
410				kProgramName, hostname, strerror(errno));
411			return 1;
412		}
413		if (host->h_addrtype != AF_INET) {
414			fprintf(stderr, "%s: Host \"%s\" is not known in the IPv4 domain.\n",
415				kProgramName, hostname);
416			return 1;
417		}
418
419		address.sin_family = AF_INET;
420		address.sin_len = sizeof(sockaddr_in);
421		address.sin_addr.s_addr = *(in_addr_t *)host->h_addr;
422	}
423
424	// parse other options in case of ARP_SET
425
426	uint8 ethernetAddress[6];
427	uint32 flags = ARP_FLAG_PERMANENT;
428
429	if (mode == ARP_SET) {
430		if (!parse_ethernet_address(argv[3], ethernetAddress)) {
431			fprintf(stderr, "%s: \"%s\" is not a valid ethernet address.\n",
432				kProgramName, argv[3]);
433			return 1;
434		}
435
436		for (int32 i = 4; i < argc; i++) {
437			if (!set_flags(flags, argv[i])) {
438				fprintf(stderr, "%s: Flag \"%s\" is invalid.\n",
439					kProgramName, argv[i]);
440				return 1;
441			}
442		}
443	}
444
445	check_for_arp_syscall();
446
447	switch (mode) {
448		case ARP_SET:
449		{
450			status_t status = set_entry(&address, ethernetAddress, flags);
451			if (status != B_OK) {
452				fprintf(stderr, "%s: ARP entry could not been set: %s\n",
453					kProgramName, strerror(status));
454				return 1;
455			}
456			break;
457		}
458		case ARP_DELETE:
459			delete_entry(&address);
460			break;
461		case ARP_LIST:
462			list_entries(hostname ? &address : NULL);
463			break;
464	}
465
466	return 0;
467}
468
469