xref: /openbsd-src/usr.sbin/arp/arp.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /*	$OpenBSD: arp.c,v 1.49 2009/09/27 12:07:15 deraadt Exp $ */
2 /*	$NetBSD: arp.c,v 1.12 1995/04/24 13:25:18 cgd Exp $ */
3 
4 /*
5  * Copyright (c) 1984, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Sun Microsystems, Inc.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 /*
37  * arp - display, set, and delete arp table entries
38  */
39 
40 #include <sys/param.h>
41 #include <sys/file.h>
42 #include <sys/socket.h>
43 #include <sys/sysctl.h>
44 
45 #include <net/if.h>
46 #include <net/if_dl.h>
47 #include <net/if_types.h>
48 #include <net/route.h>
49 #include <netinet/in.h>
50 #include <netinet/if_ether.h>
51 #include <arpa/inet.h>
52 
53 #include <netdb.h>
54 #include <errno.h>
55 #include <err.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <paths.h>
60 #include <unistd.h>
61 
62 int delete(const char *, const char *);
63 void search(in_addr_t addr, void (*action)(struct sockaddr_dl *sdl,
64 	struct sockaddr_inarp *sin, struct rt_msghdr *rtm));
65 void print_entry(struct sockaddr_dl *sdl,
66 	struct sockaddr_inarp *sin, struct rt_msghdr *rtm);
67 void nuke_entry(struct sockaddr_dl *sdl,
68 	struct sockaddr_inarp *sin, struct rt_msghdr *rtm);
69 void ether_print(const char *);
70 int file(char *);
71 int get(const char *);
72 int getinetaddr(const char *, struct in_addr *);
73 void getsocket(void);
74 int rtmsg(int);
75 int set(int, char **);
76 void usage(void);
77 
78 static pid_t pid;
79 static int replace;	/* replace entries when adding */
80 static int nflag;	/* no reverse dns lookups */
81 static int aflag;	/* do it for all entries */
82 static int s = -1;
83 static int rdomain = 0;
84 
85 extern int h_errno;
86 
87 /* ROUNDUP() is nasty, but it is identical to what's in the kernel. */
88 #define ROUNDUP(a)					\
89 	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
90 
91 /* which function we're supposed to do */
92 #define F_GET		1
93 #define F_SET		2
94 #define F_FILESET	3
95 #define F_DELETE	4
96 
97 int
98 main(int argc, char *argv[])
99 {
100 	int		 ch, func = 0, rtn;
101 	const char	*errstr;
102 
103 	pid = getpid();
104 	opterr = 0;
105 	while ((ch = getopt(argc, argv, "andsFfV:")) != -1) {
106 		switch (ch) {
107 		case 'a':
108 			aflag = 1;
109 			break;
110 		case 'n':
111 			nflag = 1;
112 			break;
113 		case 'd':
114 			if (func)
115 				usage();
116 			func = F_DELETE;
117 			break;
118 		case 's':
119 			if (func)
120 				usage();
121 			func = F_SET;
122 			break;
123 		case 'F':
124 			replace = 1;
125 			break;
126 		case 'f':
127 			if (func)
128 				usage();
129 			func = F_FILESET;
130 			break;
131 		case 'V':
132 			rdomain = strtonum(optarg, 0, RT_TABLEID_MAX, &errstr);
133 			if (errstr != NULL) {
134 				warn("bad rdomain: %s", errstr);
135 				usage();
136 			}
137 			break;
138 		default:
139 			usage();
140 			break;
141 		}
142 	}
143 	argc -= optind;
144 	argv += optind;
145 
146 	if (!func)
147 		func = F_GET;
148 	rtn = 0;
149 
150 	switch (func) {
151 	case F_GET:
152 		if (aflag && argc == 0)
153 			search(0, print_entry);
154 		else if (!aflag && argc == 1)
155 			rtn = get(argv[0]);
156 		else
157 			usage();
158 		break;
159 	case F_SET:
160 		if (argc < 2 || argc > 5)
161 			usage();
162 		if (replace)
163 			delete(argv[0], NULL);
164 		rtn = set(argc, argv) ? 1 : 0;
165 		break;
166 	case F_DELETE:
167 		if (aflag && argc == 0)
168 			search(0, nuke_entry);
169 		else if (!aflag && argc == 1)
170 			rtn = delete(argv[0], argv[1]);
171 		else
172 			usage();
173 		break;
174 	case F_FILESET:
175 		if (argc != 1)
176 			usage();
177 		rtn = file(argv[0]);
178 		break;
179 	}
180 	return (rtn);
181 }
182 
183 /*
184  * Process a file to set standard arp entries
185  */
186 int
187 file(char *name)
188 {
189 	char	 line[100], arg[5][50], *args[5];
190 	int	 i, retval;
191 	FILE	*fp;
192 
193 	if ((fp = fopen(name, "r")) == NULL)
194 		err(1, "cannot open %s", name);
195 	args[0] = &arg[0][0];
196 	args[1] = &arg[1][0];
197 	args[2] = &arg[2][0];
198 	args[3] = &arg[3][0];
199 	args[4] = &arg[4][0];
200 	retval = 0;
201 	while (fgets(line, sizeof(line), fp) != NULL) {
202 		i = sscanf(line, "%49s %49s %49s %49s %49s", arg[0], arg[1],
203 		    arg[2], arg[3], arg[4]);
204 		if (i < 2) {
205 			warnx("bad line: %s", line);
206 			retval = 1;
207 			continue;
208 		}
209 		if (replace)
210 			delete(arg[0], NULL);
211 		if (set(i, args))
212 			retval = 1;
213 	}
214 	fclose(fp);
215 	return (retval);
216 }
217 
218 void
219 getsocket(void)
220 {
221 	if (s >= 0)
222 		return;
223 	s = socket(PF_ROUTE, SOCK_RAW, 0);
224 	if (s < 0)
225 		err(1, "socket");
226 }
227 
228 struct sockaddr_in	so_mask = { 8, 0, 0, { 0xffffffff } };
229 struct sockaddr_inarp	blank_sin = { sizeof(blank_sin), AF_INET }, sin_m;
230 struct sockaddr_dl	blank_sdl = { sizeof(blank_sdl), AF_LINK }, sdl_m;
231 int			expire_time, flags, export_only, doing_proxy,
232 			    found_entry;
233 struct	{
234 	struct rt_msghdr	m_rtm;
235 	char			m_space[512];
236 }	m_rtmsg;
237 
238 /*
239  * Set an individual arp entry
240  */
241 int
242 set(int argc, char *argv[])
243 {
244 	struct sockaddr_inarp *sin;
245 	struct sockaddr_dl *sdl;
246 	struct rt_msghdr *rtm;
247 	char *eaddr = argv[1], *host = argv[0];
248 	struct ether_addr *ea;
249 
250 	sin = &sin_m;
251 	rtm = &(m_rtmsg.m_rtm);
252 
253 	getsocket();
254 	argc -= 2;
255 	argv += 2;
256 	sdl_m = blank_sdl;		/* struct copy */
257 	sin_m = blank_sin;		/* struct copy */
258 	if (getinetaddr(host, &sin->sin_addr) == -1)
259 		return (1);
260 	ea = ether_aton(eaddr);
261 	if (ea == NULL)
262 		errx(1, "invalid ethernet address: %s", eaddr);
263 	memcpy(LLADDR(&sdl_m), ea, sizeof(*ea));
264 	sdl_m.sdl_alen = 6;
265 	doing_proxy = flags = export_only = expire_time = 0;
266 	while (argc-- > 0) {
267 		if (strncmp(argv[0], "temp", 4) == 0) {
268 			struct timeval time;
269 
270 			gettimeofday(&time, 0);
271 			expire_time = time.tv_sec + 20 * 60;
272 			if (flags & RTF_PERMANENT_ARP) {
273 				/* temp or permanent, not both */
274 				usage();
275 				return (0);
276 			}
277 		} else if (strncmp(argv[0], "pub", 3) == 0) {
278 			flags |= RTF_ANNOUNCE;
279 			doing_proxy = SIN_PROXY;
280 		} else if (strncmp(argv[0], "permanent", 9) == 0) {
281 			flags |= RTF_PERMANENT_ARP;
282 			if (expire_time != 0) {
283 				/* temp or permanent, not both */
284 				usage();
285 				return (0);
286 			}
287 		} else if (strncmp(argv[0], "trail", 5) == 0)
288 			printf("%s: Sending trailers is no longer supported\n",
289 			    host);
290 
291 		argv++;
292 	}
293 
294 tryagain:
295 	if (rtmsg(RTM_GET) < 0) {
296 		warn("%s", host);
297 		return (1);
298 	}
299 	sin = (struct sockaddr_inarp *)((char *)rtm + rtm->rtm_hdrlen);
300 	sdl = (struct sockaddr_dl *)(ROUNDUP(sin->sin_len) + (char *)sin);
301 	if (sin->sin_addr.s_addr == sin_m.sin_addr.s_addr) {
302 		if (sdl->sdl_family == AF_LINK &&
303 		    (rtm->rtm_flags & RTF_LLINFO) &&
304 		    !(rtm->rtm_flags & RTF_GATEWAY))
305 			switch (sdl->sdl_type) {
306 			case IFT_ETHER:
307 			case IFT_FDDI:
308 			case IFT_ISO88023:
309 			case IFT_ISO88024:
310 			case IFT_ISO88025:
311 			case IFT_CARP:
312 				goto overwrite;
313 			}
314 
315 		if (doing_proxy == 0) {
316 			printf("set: can only proxy for %s\n", host);
317 			return (1);
318 		}
319 		if (sin_m.sin_other & SIN_PROXY) {
320 			printf("set: proxy entry exists for non 802 device\n");
321 			return (1);
322 		}
323 		sin_m.sin_other = SIN_PROXY;
324 		export_only = 1;
325 		goto tryagain;
326 	}
327 
328 overwrite:
329 	if (sdl->sdl_family != AF_LINK) {
330 		printf("cannot intuit interface index and type for %s\n", host);
331 		return (1);
332 	}
333 	sdl_m.sdl_type = sdl->sdl_type;
334 	sdl_m.sdl_index = sdl->sdl_index;
335 	return (rtmsg(RTM_ADD));
336 }
337 
338 /*
339  * Display an individual arp entry
340  */
341 int
342 get(const char *host)
343 {
344 	struct sockaddr_inarp *sin;
345 
346 	sin = &sin_m;
347 	sin_m = blank_sin;		/* struct copy */
348 	if (getinetaddr(host, &sin->sin_addr) == -1)
349 		exit(1);
350 	search(sin->sin_addr.s_addr, print_entry);
351 	if (found_entry == 0) {
352 		printf("%s (%s) -- no entry\n", host, inet_ntoa(sin->sin_addr));
353 		return (1);
354 	}
355 	return (0);
356 }
357 
358 /*
359  * Delete an arp entry
360  */
361 int
362 delete(const char *host, const char *info)
363 {
364 	struct sockaddr_inarp *sin;
365 	struct rt_msghdr *rtm;
366 	struct sockaddr_dl *sdl;
367 
368 	sin = &sin_m;
369 	rtm = &m_rtmsg.m_rtm;
370 
371 	if (info && strncmp(info, "pro", 3) )
372 		export_only = 1;
373 	getsocket();
374 	sin_m = blank_sin;		/* struct copy */
375 	if (getinetaddr(host, &sin->sin_addr) == -1)
376 		return (1);
377 tryagain:
378 	if (rtmsg(RTM_GET) < 0) {
379 		warn("%s", host);
380 		return (1);
381 	}
382 	sin = (struct sockaddr_inarp *)((char *)rtm + rtm->rtm_hdrlen);
383 	sdl = (struct sockaddr_dl *)(ROUNDUP(sin->sin_len) + (char *)sin);
384 	if (sin->sin_addr.s_addr == sin_m.sin_addr.s_addr)
385 		if (sdl->sdl_family == AF_LINK &&
386 		    (rtm->rtm_flags & RTF_LLINFO) &&
387 		    !(rtm->rtm_flags & RTF_GATEWAY))
388 			switch (sdl->sdl_type) {
389 			case IFT_ETHER:
390 			case IFT_FDDI:
391 			case IFT_ISO88023:
392 			case IFT_ISO88024:
393 			case IFT_ISO88025:
394 			case IFT_CARP:
395 				goto delete;
396 			}
397 
398 	if (sin_m.sin_other & SIN_PROXY) {
399 		warnx("delete: can't locate %s", host);
400 		return (1);
401 	} else {
402 		sin_m.sin_other = SIN_PROXY;
403 		goto tryagain;
404 	}
405 delete:
406 	if (sdl->sdl_family != AF_LINK) {
407 		printf("cannot locate %s\n", host);
408 		return (1);
409 	}
410 	if (rtmsg(RTM_DELETE))
411 		return (1);
412 	printf("%s (%s) deleted\n", host, inet_ntoa(sin->sin_addr));
413 	return (0);
414 }
415 
416 /*
417  * Search the entire arp table, and do some action on matching entries.
418  */
419 void
420 search(in_addr_t addr, void (*action)(struct sockaddr_dl *sdl,
421     struct sockaddr_inarp *sin, struct rt_msghdr *rtm))
422 {
423 	int mib[7];
424 	size_t needed;
425 	char *lim, *buf, *next;
426 	struct rt_msghdr *rtm;
427 	struct sockaddr_inarp *sin;
428 	struct sockaddr_dl *sdl;
429 
430 	mib[0] = CTL_NET;
431 	mib[1] = PF_ROUTE;
432 	mib[2] = 0;
433 	mib[3] = AF_INET;
434 	mib[4] = NET_RT_FLAGS;
435 	mib[5] = RTF_LLINFO;
436 	mib[6] = rdomain;
437 	if (sysctl(mib, 7, NULL, &needed, NULL, 0) < 0)
438 		err(1, "route-sysctl-estimate");
439 	if (needed == 0)
440 		return;
441 	if ((buf = malloc(needed)) == NULL)
442 		err(1, "malloc");
443 	if (sysctl(mib, 7, buf, &needed, NULL, 0) < 0)
444 		err(1, "actual retrieval of routing table");
445 	lim = buf + needed;
446 	for (next = buf; next < lim; next += rtm->rtm_msglen) {
447 		rtm = (struct rt_msghdr *)next;
448 		if (rtm->rtm_version != RTM_VERSION)
449 			continue;
450 		sin = (struct sockaddr_inarp *)(next + rtm->rtm_hdrlen);
451 		sdl = (struct sockaddr_dl *)(sin + 1);
452 		if (addr) {
453 			if (addr != sin->sin_addr.s_addr)
454 				continue;
455 			found_entry = 1;
456 		}
457 		(*action)(sdl, sin, rtm);
458 	}
459 	free(buf);
460 }
461 
462 /*
463  * Display an arp entry
464  */
465 void
466 print_entry(struct sockaddr_dl *sdl, struct sockaddr_inarp *sin,
467     struct rt_msghdr *rtm)
468 {
469 	char ifname[IFNAMSIZ], *host;
470 	struct hostent *hp;
471 
472 	if (nflag == 0)
473 		hp = gethostbyaddr((caddr_t)&(sin->sin_addr),
474 		    sizeof(sin->sin_addr), AF_INET);
475 	else
476 		hp = 0;
477 	if (hp)
478 		host = hp->h_name;
479 	else {
480 		host = "?";
481 		if (h_errno == TRY_AGAIN)
482 			nflag = 1;
483 	}
484 	printf("%s (%s) at ", host, inet_ntoa(sin->sin_addr));
485 	if (sdl->sdl_alen)
486 		ether_print(LLADDR(sdl));
487 	else
488 		printf("(incomplete)");
489 	if (if_indextoname(sdl->sdl_index, ifname) != NULL)
490 		printf(" on %s", ifname);
491 	if (rtm->rtm_flags & RTF_PERMANENT_ARP)
492 		printf(" permanent");
493 	if (rtm->rtm_rmx.rmx_expire == 0)
494 		printf(" static");
495 	if (sin->sin_other & SIN_PROXY)
496 		printf(" published (proxy only)");
497 	if (rtm->rtm_addrs & RTA_NETMASK) {
498 		sin = (struct sockaddr_inarp *)
499 		    (ROUNDUP(sdl->sdl_len) + (char *)sdl);
500 		if (sin->sin_addr.s_addr == 0xffffffff)
501 			printf(" published");
502 		if (sin->sin_len != 8)
503 			printf("(weird %d)", sin->sin_len);
504 	}
505 	printf("\n");
506 }
507 
508 /*
509  * Nuke an arp entry
510  */
511 void
512 nuke_entry(struct sockaddr_dl *sdl, struct sockaddr_inarp *sin,
513     struct rt_msghdr *rtm)
514 {
515 	char ip[20];
516 
517 	strlcpy(ip, inet_ntoa(sin->sin_addr), sizeof(ip));
518 	delete(ip, NULL);
519 }
520 
521 void
522 ether_print(const char *scp)
523 {
524 	const u_char *cp = (u_char *)scp;
525 
526 	printf("%02x:%02x:%02x:%02x:%02x:%02x",
527 	    cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]);
528 }
529 
530 void
531 usage(void)
532 {
533 	fprintf(stderr, "usage: arp [-adn] [-V rdomain] hostname\n");
534 	fprintf(stderr, "       arp [-F] [-f file] [-V rdomain] "
535 	    "-s hostname ether_addr\n"
536 	    "           [temp | permanent] [pub]\n");
537 	exit(1);
538 }
539 
540 int
541 rtmsg(int cmd)
542 {
543 	static int seq;
544 	struct rt_msghdr *rtm;
545 	char *cp;
546 	int l;
547 
548 	rtm = &m_rtmsg.m_rtm;
549 	cp = m_rtmsg.m_space;
550 	errno = 0;
551 
552 	if (cmd == RTM_DELETE)
553 		goto doit;
554 	memset(&m_rtmsg, 0, sizeof(m_rtmsg));
555 	rtm->rtm_flags = flags;
556 	rtm->rtm_version = RTM_VERSION;
557 	rtm->rtm_hdrlen = sizeof(*rtm);
558 	rtm->rtm_tableid = rdomain;
559 
560 	switch (cmd) {
561 	default:
562 		errx(1, "internal wrong cmd");
563 		/*NOTREACHED*/
564 	case RTM_ADD:
565 		rtm->rtm_addrs |= RTA_GATEWAY;
566 		rtm->rtm_rmx.rmx_expire = expire_time;
567 		rtm->rtm_inits = RTV_EXPIRE;
568 		rtm->rtm_flags |= (RTF_HOST | RTF_STATIC);
569 		sin_m.sin_other = 0;
570 		if (doing_proxy) {
571 			if (export_only)
572 				sin_m.sin_other = SIN_PROXY;
573 			else {
574 				rtm->rtm_addrs |= RTA_NETMASK;
575 				rtm->rtm_flags &= ~RTF_HOST;
576 			}
577 		}
578 		/* FALLTHROUGH */
579 	case RTM_GET:
580 		rtm->rtm_addrs |= RTA_DST;
581 	}
582 
583 #define NEXTADDR(w, s)					\
584 	if (rtm->rtm_addrs & (w)) {			\
585 		memcpy(cp, &s, sizeof(s));		\
586 		cp += ROUNDUP(sizeof(s));		\
587 	}
588 
589 	NEXTADDR(RTA_DST, sin_m);
590 	NEXTADDR(RTA_GATEWAY, sdl_m);
591 	NEXTADDR(RTA_NETMASK, so_mask);
592 
593 	rtm->rtm_msglen = cp - (char *)&m_rtmsg;
594 doit:
595 	l = rtm->rtm_msglen;
596 	rtm->rtm_seq = ++seq;
597 	rtm->rtm_type = cmd;
598 	if (write(s, (char *)&m_rtmsg, l) < 0)
599 		if (errno != ESRCH || cmd != RTM_DELETE) {
600 			warn("writing to routing socket");
601 			return (-1);
602 		}
603 
604 	do {
605 		l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg));
606 	} while (l > 0 && (rtm->rtm_version != RTM_VERSION ||
607 	    rtm->rtm_seq != seq || rtm->rtm_pid != pid));
608 
609 	if (l < 0)
610 		warn("read from routing socket");
611 	return (0);
612 }
613 
614 int
615 getinetaddr(const char *host, struct in_addr *inap)
616 {
617 	struct hostent *hp;
618 
619 	if (inet_aton(host, inap) == 1)
620 		return (0);
621 	if ((hp = gethostbyname(host)) == NULL) {
622 		warnx("%s: %s", host, hstrerror(h_errno));
623 		return (-1);
624 	}
625 	memcpy(inap, hp->h_addr, sizeof(*inap));
626 	return (0);
627 }
628