xref: /haiku/src/bin/network/arp/arp.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
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 		};
174 		bool first = true;
175 
176 		for (uint32 i = 0; i < sizeof(kFlags) / sizeof(kFlags[0]); i++) {
177 			if ((control.flags & kFlags[i].value) != 0) {
178 				if (first) {
179 					printf("  ");
180 					first = false;
181 				} else
182 					putchar(' ');
183 				printf(kFlags[i].name);
184 			}
185 		}
186 	}
187 
188 	putchar('\n');
189 }
190 
191 
192 void
193 list_entries(sockaddr_in *address)
194 {
195 	arp_control control;
196 	status_t status;
197 
198 	if (address != NULL) {
199 		control.address = address->sin_addr.s_addr;
200 		status = _kern_generic_syscall(ARP_SYSCALLS, ARP_GET_ENTRY,
201 			&control, sizeof(arp_control));
202 		if (status != B_OK) {
203 			fprintf(stderr, "%s: ARP entry does not exist.\n", kProgramName);
204 			exit(1);
205 		}
206 
207 		list_entry(control);
208 		return;
209 	}
210 
211 	control.cookie = 0;
212 	while (_kern_generic_syscall(ARP_SYSCALLS, ARP_GET_ENTRIES,
213 			&control, sizeof(arp_control)) == B_OK) {
214 		list_entry(control);
215 	}
216 }
217 
218 
219 void
220 delete_entry(sockaddr_in *address)
221 {
222 	arp_control control;
223 	control.address = address->sin_addr.s_addr;
224 
225 	status_t status = _kern_generic_syscall(ARP_SYSCALLS, ARP_DELETE_ENTRY,
226 		&control, sizeof(arp_control));
227 	if (status != B_OK) {
228 		fprintf(stderr, "%s: Could not delete ARP entry: %s\n",
229 			kProgramName, strerror(status));
230 		exit(1);
231 	}
232 }
233 
234 
235 status_t
236 set_entry(sockaddr_in *address, uint8 *ethernetAddress, uint32 flags)
237 {
238 	arp_control control;
239 	control.address = address->sin_addr.s_addr;
240 	memcpy(control.ethernet_address, ethernetAddress, ETHER_ADDRESS_LENGTH);
241 	control.flags = flags;
242 
243 	return _kern_generic_syscall(ARP_SYSCALLS, ARP_SET_ENTRY,
244 		&control, sizeof(arp_control));
245 }
246 
247 
248 int
249 set_entries_from_file(const char *filename)
250 {
251 	FILE *file = fopen(filename, "r");
252 	if (file == NULL) {
253 		fprintf(stderr, "%s: Could not open file: %s\n", kProgramName, strerror(errno));
254 		return 1;
255 	}
256 
257 	int32 counter = 0;
258 	char line[4096];
259 
260 	while (fgets(line, sizeof(line), file) != NULL) {
261 		counter++;
262 
263 		// comments and empty lines are allowed
264 		if (line[0] == '#' || !strcmp(line, "\n"))
265 			continue;
266 
267 		// parse hostname
268 
269 		char *rest = line;
270 		const char *argument = next_argument(&rest);
271 		if (argument == NULL) {
272 			fprintf(stderr, "%s: Line %ld is invalid (missing hostname).\n",
273 				kProgramName, counter);
274 			continue;
275 		}
276 
277 		sockaddr_in address;
278 		if (!parse_inet_address(argument, address)) {
279 			// get host by name
280 			struct hostent *host = gethostbyname(argument);
281 			if (host == NULL) {
282 				fprintf(stderr, "%s: Line %ld, host \"%s\" is not known in the IPv4 domain: %s\n",
283 					kProgramName, counter, argument, strerror(errno));
284 				continue;
285 			}
286 			if (host->h_addrtype != AF_INET) {
287 				fprintf(stderr, "%s: Line %ld, host \"%s\" is not known in the IPv4 domain.\n",
288 					kProgramName, counter, argument);
289 				continue;
290 			}
291 
292 			address.sin_family = AF_INET;
293 			address.sin_len = sizeof(sockaddr_in);
294 			address.sin_addr.s_addr = *(in_addr_t *)host->h_addr;
295 		}
296 
297 		// parse ethernet MAC address
298 
299 		argument = next_argument(&rest);
300 		if (argument == NULL) {
301 			fprintf(stderr, "%s: Line %ld is invalid (missing ethernet address).\n",
302 				kProgramName, counter);
303 			continue;
304 		}
305 
306 		uint8 ethernetAddress[6];
307 		if (!parse_ethernet_address(argument, ethernetAddress)) {
308 			fprintf(stderr, "%s: Line %ld, \"%s\" is not a valid ethernet address.\n",
309 				kProgramName, counter, argument);
310 			continue;
311 		}
312 
313 		// parse other options
314 
315 		uint32 flags = ARP_FLAG_PERMANENT;
316 		while ((argument = next_argument(&rest)) != NULL) {
317 			if (!set_flags(flags, argument)) {
318 				fprintf(stderr, "%s: Line %ld, ignoring invalid flag \"%s\".\n",
319 					kProgramName, counter, argument);
320 			}
321 		}
322 
323 		status_t status = set_entry(&address, ethernetAddress, flags);
324 		if (status != B_OK) {
325 			fprintf(stderr, "%s: Line %ld, ARP entry could not been set: %s\n",
326 				kProgramName, counter, strerror(status));
327 		}
328 	}
329 
330 	fclose(file);
331 	return 0;
332 }
333 
334 
335 //	#pragma mark -
336 
337 
338 int
339 main(int argc, char** argv)
340 {
341 	if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
342 		usage(0);
343 
344 	int32 mode = ARP_LIST;
345 	int32 i = 1;
346 
347 	if (argc > 1 && argv[1][0] == '-') {
348 		if (!strcmp(argv[1], "-d")) {
349 			// delete entry
350 			if (argc != 3)
351 				usage(1);
352 
353 			mode = ARP_DELETE;
354 		} else if (!strcmp(argv[1], "-s")) {
355 			// set entry
356 			if (argc < 4)
357 				usage(1);
358 
359 			mode = ARP_SET;
360 		} else if (!strcmp(argv[1], "-a")) {
361 			// list all
362 			if (argc != 2)
363 				usage(1);
364 
365 			mode = ARP_LIST;
366 		} else if (!strcmp(argv[1], "-F")) {
367 			// flush all entries
368 			if (argc != 2)
369 				usage(1);
370 
371 			mode = ARP_FLUSH;
372 		} else if (!strcmp(argv[1], "-f")) {
373 			// set from file
374 			if (argc != 3)
375 				usage(1);
376 
377 			check_for_arp_syscall();
378 			return set_entries_from_file(argv[2]);
379 		} else {
380 			fprintf(stderr, "%s: Unknown option %s\n", kProgramName, argv[1]);
381 			usage(1);
382 		}
383 
384 		i++;
385 	}
386 
387 	// parse hostname
388 
389 	const char *hostname = argv[i];
390 	sockaddr_in address;
391 
392 	if (hostname != NULL && !parse_inet_address(hostname, address)) {
393 		// get host by name
394 		struct hostent *host = gethostbyname(hostname);
395 		if (host == NULL) {
396 			fprintf(stderr, "%s: Host \"%s\" is not known in the IPv4 domain: %s\n",
397 				kProgramName, hostname, strerror(errno));
398 			return 1;
399 		}
400 		if (host->h_addrtype != AF_INET) {
401 			fprintf(stderr, "%s: Host \"%s\" is not known in the IPv4 domain.\n",
402 				kProgramName, hostname);
403 			return 1;
404 		}
405 
406 		address.sin_family = AF_INET;
407 		address.sin_len = sizeof(sockaddr_in);
408 		address.sin_addr.s_addr = *(in_addr_t *)host->h_addr;
409 	}
410 
411 	// parse other options in case of ARP_SET
412 
413 	uint8 ethernetAddress[6];
414 	uint32 flags = ARP_FLAG_PERMANENT;
415 
416 	if (mode == ARP_SET) {
417 		if (!parse_ethernet_address(argv[3], ethernetAddress)) {
418 			fprintf(stderr, "%s: \"%s\" is not a valid ethernet address.\n",
419 				kProgramName, argv[3]);
420 			return 1;
421 		}
422 
423 		for (int32 i = 4; i < argc; i++) {
424 			if (!set_flags(flags, argv[i])) {
425 				fprintf(stderr, "%s: Flag \"%s\" is invalid.\n",
426 					kProgramName, argv[i]);
427 				return 1;
428 			}
429 		}
430 	}
431 
432 	check_for_arp_syscall();
433 
434 	switch (mode) {
435 		case ARP_SET:
436 		{
437 			status_t status = set_entry(&address, ethernetAddress, flags);
438 			if (status != B_OK) {
439 				fprintf(stderr, "%s: ARP entry could not been set: %s\n",
440 					kProgramName, strerror(status));
441 				return 1;
442 			}
443 			break;
444 		}
445 		case ARP_DELETE:
446 			delete_entry(&address);
447 			break;
448 		case ARP_LIST:
449 			list_entries(hostname ? &address : NULL);
450 			break;
451 	}
452 
453 	return 0;
454 }
455 
456