xref: /haiku/src/bin/network/arp/arp.cpp (revision 1e36cfc2721ef13a187c6f7354dc9cbc485e89d3)
1 /*
2  * Copyright 2006, 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.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 
27 extern const char* __progname;
28 const char* kProgramName = __progname;
29 
30 
31 enum modes {
32 	ARP_LIST = 0,
33 	ARP_DELETE,
34 	ARP_SET,
35 	ARP_SET_FROM_FILE,
36 	ARP_FLUSH,
37 };
38 
39 
40 static const char *
41 ethernet_address_to_string(uint8 *address)
42 {
43 	static char string[64];
44 	snprintf(string, sizeof(string), "%02x:%02x:%02x:%02x:%02x:%02x",
45 		address[0], address[1], address[2], address[3], address[4], address[5]);
46 	return string;
47 }
48 
49 
50 static bool
51 parse_ethernet_address(const char *string, uint8 *address)
52 {
53 	return sscanf(string, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &address[0], &address[1],
54 		&address[2], &address[3], &address[4], &address[5]) == 6;
55 }
56 
57 
58 static bool
59 parse_inet_address(const char* string, sockaddr_in& address)
60 {
61 	in_addr inetAddress;
62 	if (inet_aton(string, &inetAddress) != 1)
63 		return false;
64 
65 	address.sin_family = AF_INET;
66 	address.sin_len = sizeof(struct sockaddr_in);
67 	address.sin_port = 0;
68 	address.sin_addr = inetAddress;
69 	memset(&address.sin_zero[0], 0, sizeof(address.sin_zero));
70 
71 	return true;
72 }
73 
74 
75 static void
76 check_for_arp_syscall(void)
77 {
78 	uint32 version = 0;
79 	status_t status = _kern_generic_syscall(ARP_SYSCALLS, B_SYSCALL_INFO,
80 		&version, sizeof(version));
81 	if (status != B_OK) {
82 		fprintf(stderr, "\"ARP\" module not available.\n");
83 		exit(1);
84 	}
85 }
86 
87 
88 void
89 usage(int status)
90 {
91 	printf("usage: %s [<command>] [<hostname>] [<ethernet-address>] [temp] [pub]\n"
92 		"       %s -f <filename>\n"
93 		"Where <command> can be the one of:\n"
94 		"  -s  - sets an entry in the ARP cache\n"
95 		"  -d  - deletes the specified ARP cache entry\n"
96 		"  -a  - list all entries [default]\n"
97 		"  -f  - read ARP entries from file\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 
110 bool
111 set_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 
126 static char *
127 next_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 
156 void
157 list_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 				printf(kFlags[i].name);
185 			}
186 		}
187 	}
188 
189 	putchar('\n');
190 }
191 
192 
193 void
194 list_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 
220 void
221 delete_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 
236 status_t
237 set_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 
249 int
250 set_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 %ld 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 %ld, 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 %ld, 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 %ld 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 %ld, \"%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 %ld, 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 %ld, 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 
336 //	#pragma mark -
337 
338 
339 int
340 main(int argc, char** argv)
341 {
342 	if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
343 		usage(0);
344 
345 	int32 mode = ARP_LIST;
346 	int32 i = 1;
347 
348 	if (argc > 1 && argv[1][0] == '-') {
349 		if (!strcmp(argv[1], "-d")) {
350 			// delete entry
351 			if (argc != 3)
352 				usage(1);
353 
354 			mode = ARP_DELETE;
355 		} else if (!strcmp(argv[1], "-s")) {
356 			// set entry
357 			if (argc < 4)
358 				usage(1);
359 
360 			mode = ARP_SET;
361 		} else if (!strcmp(argv[1], "-a")) {
362 			// list all
363 			if (argc != 2)
364 				usage(1);
365 
366 			mode = ARP_LIST;
367 		} else if (!strcmp(argv[1], "-F")) {
368 			// flush all entries
369 			if (argc != 2)
370 				usage(1);
371 
372 			mode = ARP_FLUSH;
373 		} else if (!strcmp(argv[1], "-f")) {
374 			// set from file
375 			if (argc != 3)
376 				usage(1);
377 
378 			check_for_arp_syscall();
379 			return set_entries_from_file(argv[2]);
380 		} else {
381 			fprintf(stderr, "%s: Unknown option %s\n", kProgramName, argv[1]);
382 			usage(1);
383 		}
384 
385 		i++;
386 	}
387 
388 	// parse hostname
389 
390 	const char *hostname = argv[i];
391 	sockaddr_in address;
392 
393 	if (hostname != NULL && !parse_inet_address(hostname, address)) {
394 		// get host by name
395 		struct hostent *host = gethostbyname(hostname);
396 		if (host == NULL) {
397 			fprintf(stderr, "%s: Host \"%s\" is not known in the IPv4 domain: %s\n",
398 				kProgramName, hostname, strerror(errno));
399 			return 1;
400 		}
401 		if (host->h_addrtype != AF_INET) {
402 			fprintf(stderr, "%s: Host \"%s\" is not known in the IPv4 domain.\n",
403 				kProgramName, hostname);
404 			return 1;
405 		}
406 
407 		address.sin_family = AF_INET;
408 		address.sin_len = sizeof(sockaddr_in);
409 		address.sin_addr.s_addr = *(in_addr_t *)host->h_addr;
410 	}
411 
412 	// parse other options in case of ARP_SET
413 
414 	uint8 ethernetAddress[6];
415 	uint32 flags = ARP_FLAG_PERMANENT;
416 
417 	if (mode == ARP_SET) {
418 		if (!parse_ethernet_address(argv[3], ethernetAddress)) {
419 			fprintf(stderr, "%s: \"%s\" is not a valid ethernet address.\n",
420 				kProgramName, argv[3]);
421 			return 1;
422 		}
423 
424 		for (int32 i = 4; i < argc; i++) {
425 			if (!set_flags(flags, argv[i])) {
426 				fprintf(stderr, "%s: Flag \"%s\" is invalid.\n",
427 					kProgramName, argv[i]);
428 				return 1;
429 			}
430 		}
431 	}
432 
433 	check_for_arp_syscall();
434 
435 	switch (mode) {
436 		case ARP_SET:
437 		{
438 			status_t status = set_entry(&address, ethernetAddress, flags);
439 			if (status != B_OK) {
440 				fprintf(stderr, "%s: ARP entry could not been set: %s\n",
441 					kProgramName, strerror(status));
442 				return 1;
443 			}
444 			break;
445 		}
446 		case ARP_DELETE:
447 			delete_entry(&address);
448 			break;
449 		case ARP_LIST:
450 			list_entries(hostname ? &address : NULL);
451 			break;
452 	}
453 
454 	return 0;
455 }
456 
457