xref: /openbsd-src/usr.sbin/arp/arp.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: arp.c,v 1.56 2014/03/18 14:18:22 mikeb 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, delete arp table entries and wake up hosts.
38  */
39 
40 #include <sys/param.h>
41 #include <sys/file.h>
42 #include <sys/socket.h>
43 #include <sys/sysctl.h>
44 #include <sys/ioctl.h>
45 #include <net/bpf.h>
46 #include <net/if.h>
47 #include <net/if_dl.h>
48 #include <net/if_types.h>
49 #include <net/route.h>
50 #include <netinet/in.h>
51 #include <netinet/if_ether.h>
52 #include <arpa/inet.h>
53 
54 #include <netdb.h>
55 #include <errno.h>
56 #include <err.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <paths.h>
61 #include <unistd.h>
62 #include <ifaddrs.h>
63 
64 int delete(const char *, const char *);
65 void search(in_addr_t addr, void (*action)(struct sockaddr_dl *sdl,
66 	struct sockaddr_inarp *sin, struct rt_msghdr *rtm));
67 void print_entry(struct sockaddr_dl *sdl,
68 	struct sockaddr_inarp *sin, struct rt_msghdr *rtm);
69 void nuke_entry(struct sockaddr_dl *sdl,
70 	struct sockaddr_inarp *sin, struct rt_msghdr *rtm);
71 int wake(const char *ether_addr, const char *iface);
72 void ether_print(const char *);
73 int file(char *);
74 int get(const char *);
75 int getinetaddr(const char *, struct in_addr *);
76 void getsocket(void);
77 int rtmsg(int);
78 int set(int, char **);
79 void usage(void);
80 
81 static pid_t pid;
82 static int replace;	/* replace entries when adding */
83 static int nflag;	/* no reverse dns lookups */
84 static int aflag;	/* do it for all entries */
85 static int s = -1;
86 static int rdomain;
87 
88 extern int h_errno;
89 
90 /* ROUNDUP() is nasty, but it is identical to what's in the kernel. */
91 #define ROUNDUP(a)					\
92 	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
93 
94 /* which function we're supposed to do */
95 #define F_GET		1
96 #define F_SET		2
97 #define F_FILESET	3
98 #define F_DELETE	4
99 #define F_WAKE		5
100 
101 int
102 main(int argc, char *argv[])
103 {
104 	int		 ch, func = 0, rtn;
105 	const char	*errstr;
106 
107 	pid = getpid();
108 	opterr = 0;
109 	rdomain = getrtable();
110 	while ((ch = getopt(argc, argv, "andsFfV:W")) != -1) {
111 		switch (ch) {
112 		case 'a':
113 			aflag = 1;
114 			break;
115 		case 'n':
116 			nflag = 1;
117 			break;
118 		case 'd':
119 			if (func)
120 				usage();
121 			func = F_DELETE;
122 			break;
123 		case 's':
124 			if (func)
125 				usage();
126 			func = F_SET;
127 			break;
128 		case 'F':
129 			replace = 1;
130 			break;
131 		case 'f':
132 			if (func)
133 				usage();
134 			func = F_FILESET;
135 			break;
136 		case 'V':
137 			rdomain = strtonum(optarg, 0, RT_TABLEID_MAX, &errstr);
138 			if (errstr != NULL) {
139 				warn("bad rdomain: %s", errstr);
140 				usage();
141 			}
142 			break;
143 		case 'W':
144 			if (func)
145 				usage();
146 			func = F_WAKE;
147 			break;
148 		default:
149 			usage();
150 			break;
151 		}
152 	}
153 	argc -= optind;
154 	argv += optind;
155 
156 	if (!func)
157 		func = F_GET;
158 	rtn = 0;
159 
160 	switch (func) {
161 	case F_GET:
162 		if (aflag && argc == 0)
163 			search(0, print_entry);
164 		else if (!aflag && argc == 1)
165 			rtn = get(argv[0]);
166 		else
167 			usage();
168 		break;
169 	case F_SET:
170 		if (argc < 2 || argc > 5)
171 			usage();
172 		if (replace)
173 			delete(argv[0], NULL);
174 		rtn = set(argc, argv) ? 1 : 0;
175 		break;
176 	case F_DELETE:
177 		if (aflag && argc == 0)
178 			search(0, nuke_entry);
179 		else if (!aflag && argc == 1)
180 			rtn = delete(argv[0], argv[1]);
181 		else
182 			usage();
183 		break;
184 	case F_FILESET:
185 		if (argc != 1)
186 			usage();
187 		rtn = file(argv[0]);
188 		break;
189 	case F_WAKE:
190 		if (aflag || nflag || replace || rdomain > 0)
191 			usage();
192 		if (argc == 1)
193 			rtn = wake(argv[0], NULL);
194 		else if (argc == 2)
195 			rtn = wake(argv[0], argv[1]);
196 		else
197 			usage();
198 		break;
199 	}
200 	return (rtn);
201 }
202 
203 /*
204  * Process a file to set standard arp entries
205  */
206 int
207 file(char *name)
208 {
209 	char	 line[100], arg[5][50], *args[5];
210 	int	 i, retval;
211 	FILE	*fp;
212 
213 	if ((fp = fopen(name, "r")) == NULL)
214 		err(1, "cannot open %s", name);
215 	args[0] = &arg[0][0];
216 	args[1] = &arg[1][0];
217 	args[2] = &arg[2][0];
218 	args[3] = &arg[3][0];
219 	args[4] = &arg[4][0];
220 	retval = 0;
221 	while (fgets(line, sizeof(line), fp) != NULL) {
222 		i = sscanf(line, "%49s %49s %49s %49s %49s", arg[0], arg[1],
223 		    arg[2], arg[3], arg[4]);
224 		if (i < 2) {
225 			warnx("bad line: %s", line);
226 			retval = 1;
227 			continue;
228 		}
229 		if (replace)
230 			delete(arg[0], NULL);
231 		if (set(i, args))
232 			retval = 1;
233 	}
234 	fclose(fp);
235 	return (retval);
236 }
237 
238 void
239 getsocket(void)
240 {
241 	socklen_t len = sizeof(rdomain);
242 
243 	if (s >= 0)
244 		return;
245 	s = socket(PF_ROUTE, SOCK_RAW, 0);
246 	if (s < 0)
247 		err(1, "socket");
248 	if (setsockopt(s, PF_ROUTE, ROUTE_TABLEFILTER, &rdomain, len) < 0)
249 		err(1, "ROUTE_TABLEFILTER");
250 }
251 
252 struct sockaddr_in	so_mask = { 8, 0, 0, { 0xffffffff } };
253 struct sockaddr_inarp	blank_sin = { sizeof(blank_sin), AF_INET }, sin_m;
254 struct sockaddr_dl	blank_sdl = { sizeof(blank_sdl), AF_LINK }, sdl_m;
255 time_t			expire_time;
256 int			flags, export_only, doing_proxy, found_entry;
257 struct	{
258 	struct rt_msghdr	m_rtm;
259 	char			m_space[512];
260 }	m_rtmsg;
261 
262 /*
263  * Set an individual arp entry
264  */
265 int
266 set(int argc, char *argv[])
267 {
268 	struct sockaddr_inarp *sin;
269 	struct sockaddr_dl *sdl;
270 	struct rt_msghdr *rtm;
271 	char *eaddr = argv[1], *host = argv[0];
272 	struct ether_addr *ea;
273 
274 	sin = &sin_m;
275 	rtm = &(m_rtmsg.m_rtm);
276 
277 	getsocket();
278 	argc -= 2;
279 	argv += 2;
280 	sdl_m = blank_sdl;		/* struct copy */
281 	sin_m = blank_sin;		/* struct copy */
282 	if (getinetaddr(host, &sin->sin_addr) == -1)
283 		return (1);
284 	ea = ether_aton(eaddr);
285 	if (ea == NULL)
286 		errx(1, "invalid ethernet address: %s", eaddr);
287 	memcpy(LLADDR(&sdl_m), ea, sizeof(*ea));
288 	sdl_m.sdl_alen = 6;
289 	expire_time = 0;
290 	doing_proxy = flags = export_only = 0;
291 	while (argc-- > 0) {
292 		if (strncmp(argv[0], "temp", 4) == 0) {
293 			struct timeval now;
294 
295 			gettimeofday(&now, 0);
296 			expire_time = now.tv_sec + 20 * 60;
297 			if (flags & RTF_PERMANENT_ARP) {
298 				/* temp or permanent, not both */
299 				usage();
300 				return (0);
301 			}
302 		} else if (strncmp(argv[0], "pub", 3) == 0) {
303 			flags |= RTF_ANNOUNCE;
304 			doing_proxy = SIN_PROXY;
305 		} else if (strncmp(argv[0], "permanent", 9) == 0) {
306 			flags |= RTF_PERMANENT_ARP;
307 			if (expire_time != 0) {
308 				/* temp or permanent, not both */
309 				usage();
310 				return (0);
311 			}
312 		} else if (strncmp(argv[0], "trail", 5) == 0)
313 			printf("%s: Sending trailers is no longer supported\n",
314 			    host);
315 
316 		argv++;
317 	}
318 
319 tryagain:
320 	if (rtmsg(RTM_GET) < 0) {
321 		warn("%s", host);
322 		return (1);
323 	}
324 	sin = (struct sockaddr_inarp *)((char *)rtm + rtm->rtm_hdrlen);
325 	sdl = (struct sockaddr_dl *)(ROUNDUP(sin->sin_len) + (char *)sin);
326 	if (sin->sin_addr.s_addr == sin_m.sin_addr.s_addr) {
327 		if (sdl->sdl_family == AF_LINK &&
328 		    (rtm->rtm_flags & RTF_LLINFO) &&
329 		    !(rtm->rtm_flags & RTF_GATEWAY))
330 			switch (sdl->sdl_type) {
331 			case IFT_ETHER:
332 			case IFT_FDDI:
333 			case IFT_ISO88023:
334 			case IFT_ISO88024:
335 			case IFT_ISO88025:
336 			case IFT_CARP:
337 				goto overwrite;
338 			}
339 
340 		if (doing_proxy == 0) {
341 			printf("set: can only proxy for %s\n", host);
342 			return (1);
343 		}
344 		if (sin_m.sin_other & SIN_PROXY) {
345 			printf("set: proxy entry exists for non 802 device\n");
346 			return (1);
347 		}
348 		sin_m.sin_other = SIN_PROXY;
349 		export_only = 1;
350 		goto tryagain;
351 	}
352 
353 overwrite:
354 	if (sdl->sdl_family != AF_LINK) {
355 		printf("cannot intuit interface index and type for %s\n", host);
356 		return (1);
357 	}
358 	sdl_m.sdl_type = sdl->sdl_type;
359 	sdl_m.sdl_index = sdl->sdl_index;
360 	return (rtmsg(RTM_ADD));
361 }
362 
363 /*
364  * Display an individual arp entry
365  */
366 int
367 get(const char *host)
368 {
369 	struct sockaddr_inarp *sin;
370 
371 	sin = &sin_m;
372 	sin_m = blank_sin;		/* struct copy */
373 	if (getinetaddr(host, &sin->sin_addr) == -1)
374 		exit(1);
375 	search(sin->sin_addr.s_addr, print_entry);
376 	if (found_entry == 0) {
377 		printf("%s (%s) -- no entry\n", host, inet_ntoa(sin->sin_addr));
378 		return (1);
379 	}
380 	return (0);
381 }
382 
383 /*
384  * Delete an arp entry
385  */
386 int
387 delete(const char *host, const char *info)
388 {
389 	struct sockaddr_inarp *sin;
390 	struct rt_msghdr *rtm;
391 	struct sockaddr_dl *sdl;
392 
393 	sin = &sin_m;
394 	rtm = &m_rtmsg.m_rtm;
395 
396 	if (info && strncmp(info, "pro", 3) )
397 		export_only = 1;
398 	getsocket();
399 	sin_m = blank_sin;		/* struct copy */
400 	if (getinetaddr(host, &sin->sin_addr) == -1)
401 		return (1);
402 tryagain:
403 	if (rtmsg(RTM_GET) < 0) {
404 		warn("%s", host);
405 		return (1);
406 	}
407 	sin = (struct sockaddr_inarp *)((char *)rtm + rtm->rtm_hdrlen);
408 	sdl = (struct sockaddr_dl *)(ROUNDUP(sin->sin_len) + (char *)sin);
409 	if (sin->sin_addr.s_addr == sin_m.sin_addr.s_addr)
410 		if (sdl->sdl_family == AF_LINK &&
411 		    (rtm->rtm_flags & RTF_LLINFO) &&
412 		    !(rtm->rtm_flags & RTF_GATEWAY))
413 			switch (sdl->sdl_type) {
414 			case IFT_ETHER:
415 			case IFT_FDDI:
416 			case IFT_ISO88023:
417 			case IFT_ISO88024:
418 			case IFT_ISO88025:
419 			case IFT_CARP:
420 				goto delete;
421 			}
422 
423 	if (sin_m.sin_other & SIN_PROXY) {
424 		warnx("delete: can't locate %s", host);
425 		return (1);
426 	} else {
427 		sin_m.sin_other = SIN_PROXY;
428 		goto tryagain;
429 	}
430 delete:
431 	if (sdl->sdl_family != AF_LINK) {
432 		printf("cannot locate %s\n", host);
433 		return (1);
434 	}
435 	if (rtmsg(RTM_DELETE))
436 		return (1);
437 	printf("%s (%s) deleted\n", host, inet_ntoa(sin->sin_addr));
438 	return (0);
439 }
440 
441 /*
442  * Search the entire arp table, and do some action on matching entries.
443  */
444 void
445 search(in_addr_t addr, void (*action)(struct sockaddr_dl *sdl,
446     struct sockaddr_inarp *sin, struct rt_msghdr *rtm))
447 {
448 	int mib[7];
449 	size_t needed;
450 	char *lim, *buf = NULL, *next;
451 	struct rt_msghdr *rtm;
452 	struct sockaddr_inarp *sin;
453 	struct sockaddr_dl *sdl;
454 
455 	mib[0] = CTL_NET;
456 	mib[1] = PF_ROUTE;
457 	mib[2] = 0;
458 	mib[3] = AF_INET;
459 	mib[4] = NET_RT_FLAGS;
460 	mib[5] = RTF_LLINFO;
461 	mib[6] = rdomain;
462 	while (1) {
463 		if (sysctl(mib, 7, NULL, &needed, NULL, 0) == -1)
464 			err(1, "route-sysctl-estimate");
465 		if (needed == 0)
466 			return;
467 		if ((buf = realloc(buf, needed)) == NULL)
468 			err(1, "malloc");
469 		if (sysctl(mib, 7, buf, &needed, NULL, 0) == -1) {
470 			if (errno == ENOMEM)
471 				continue;
472 			err(1, "actual retrieval of routing table");
473 		}
474 		lim = buf + needed;
475 		break;
476 	}
477 	for (next = buf; next < lim; next += rtm->rtm_msglen) {
478 		rtm = (struct rt_msghdr *)next;
479 		if (rtm->rtm_version != RTM_VERSION)
480 			continue;
481 		sin = (struct sockaddr_inarp *)(next + rtm->rtm_hdrlen);
482 		sdl = (struct sockaddr_dl *)(sin + 1);
483 		if (addr) {
484 			if (addr != sin->sin_addr.s_addr)
485 				continue;
486 			found_entry = 1;
487 		}
488 		(*action)(sdl, sin, rtm);
489 	}
490 	free(buf);
491 }
492 
493 /*
494  * Display an arp entry
495  */
496 void
497 print_entry(struct sockaddr_dl *sdl, struct sockaddr_inarp *sin,
498     struct rt_msghdr *rtm)
499 {
500 	char ifname[IFNAMSIZ], *host;
501 	struct hostent *hp;
502 
503 	if (nflag == 0)
504 		hp = gethostbyaddr((caddr_t)&(sin->sin_addr),
505 		    sizeof(sin->sin_addr), AF_INET);
506 	else
507 		hp = 0;
508 	if (hp)
509 		host = hp->h_name;
510 	else {
511 		host = "?";
512 		if (h_errno == TRY_AGAIN)
513 			nflag = 1;
514 	}
515 	printf("%s (%s) at ", host, inet_ntoa(sin->sin_addr));
516 	if (sdl->sdl_alen)
517 		ether_print(LLADDR(sdl));
518 	else
519 		printf("(incomplete)");
520 	if (if_indextoname(sdl->sdl_index, ifname) != NULL)
521 		printf(" on %s", ifname);
522 	if (rtm->rtm_flags & RTF_PERMANENT_ARP)
523 		printf(" permanent");
524 	if (rtm->rtm_rmx.rmx_expire == 0)
525 		printf(" static");
526 	if (sin->sin_other & SIN_PROXY)
527 		printf(" published (proxy only)");
528 	if (rtm->rtm_addrs & RTA_NETMASK) {
529 		sin = (struct sockaddr_inarp *)
530 		    (ROUNDUP(sdl->sdl_len) + (char *)sdl);
531 		if (sin->sin_addr.s_addr == 0xffffffff)
532 			printf(" published");
533 		if (sin->sin_len != 8)
534 			printf("(weird %d)", sin->sin_len);
535 	}
536 	printf("\n");
537 }
538 
539 /*
540  * Nuke an arp entry
541  */
542 void
543 nuke_entry(struct sockaddr_dl *sdl, struct sockaddr_inarp *sin,
544     struct rt_msghdr *rtm)
545 {
546 	char ip[20];
547 
548 	strlcpy(ip, inet_ntoa(sin->sin_addr), sizeof(ip));
549 	delete(ip, NULL);
550 }
551 
552 void
553 ether_print(const char *scp)
554 {
555 	const u_char *cp = (u_char *)scp;
556 
557 	printf("%02x:%02x:%02x:%02x:%02x:%02x",
558 	    cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]);
559 }
560 
561 void
562 usage(void)
563 {
564 	fprintf(stderr, "usage: arp [-adn] [-V rdomain] hostname\n");
565 	fprintf(stderr, "       arp [-F] [-f file] [-V rdomain] "
566 	    "-s hostname ether_addr\n"
567 	    "           [temp | permanent] [pub]\n");
568 	fprintf(stderr, "       arp -W ether_addr [iface]\n");
569 	exit(1);
570 }
571 
572 int
573 rtmsg(int cmd)
574 {
575 	static int seq;
576 	struct rt_msghdr *rtm;
577 	char *cp;
578 	int l;
579 
580 	rtm = &m_rtmsg.m_rtm;
581 	cp = m_rtmsg.m_space;
582 	errno = 0;
583 
584 	if (cmd == RTM_DELETE)
585 		goto doit;
586 	memset(&m_rtmsg, 0, sizeof(m_rtmsg));
587 	rtm->rtm_flags = flags;
588 	rtm->rtm_version = RTM_VERSION;
589 	rtm->rtm_hdrlen = sizeof(*rtm);
590 	rtm->rtm_tableid = rdomain;
591 
592 	switch (cmd) {
593 	default:
594 		errx(1, "internal wrong cmd");
595 		/*NOTREACHED*/
596 	case RTM_ADD:
597 		rtm->rtm_addrs |= RTA_GATEWAY;
598 		rtm->rtm_rmx.rmx_expire = expire_time;
599 		rtm->rtm_inits = RTV_EXPIRE;
600 		rtm->rtm_flags |= (RTF_HOST | RTF_STATIC);
601 		sin_m.sin_other = 0;
602 		if (doing_proxy) {
603 			if (export_only)
604 				sin_m.sin_other = SIN_PROXY;
605 			else {
606 				rtm->rtm_addrs |= RTA_NETMASK;
607 				rtm->rtm_flags &= ~RTF_HOST;
608 			}
609 		}
610 		/* FALLTHROUGH */
611 	case RTM_GET:
612 		rtm->rtm_addrs |= RTA_DST;
613 	}
614 
615 #define NEXTADDR(w, s)					\
616 	if (rtm->rtm_addrs & (w)) {			\
617 		memcpy(cp, &s, sizeof(s));		\
618 		cp += ROUNDUP(sizeof(s));		\
619 	}
620 
621 	NEXTADDR(RTA_DST, sin_m);
622 	NEXTADDR(RTA_GATEWAY, sdl_m);
623 	NEXTADDR(RTA_NETMASK, so_mask);
624 
625 	rtm->rtm_msglen = cp - (char *)&m_rtmsg;
626 doit:
627 	l = rtm->rtm_msglen;
628 	rtm->rtm_seq = ++seq;
629 	rtm->rtm_type = cmd;
630 	if (write(s, (char *)&m_rtmsg, l) < 0)
631 		if (errno != ESRCH || cmd != RTM_DELETE) {
632 			warn("writing to routing socket");
633 			return (-1);
634 		}
635 
636 	do {
637 		l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg));
638 	} while (l > 0 && (rtm->rtm_version != RTM_VERSION ||
639 	    rtm->rtm_seq != seq || rtm->rtm_pid != pid));
640 
641 	if (l < 0)
642 		warn("read from routing socket");
643 	return (0);
644 }
645 
646 int
647 getinetaddr(const char *host, struct in_addr *inap)
648 {
649 	struct hostent *hp;
650 
651 	if (inet_aton(host, inap) == 1)
652 		return (0);
653 	if ((hp = gethostbyname(host)) == NULL) {
654 		warnx("%s: %s", host, hstrerror(h_errno));
655 		return (-1);
656 	}
657 	memcpy(inap, hp->h_addr, sizeof(*inap));
658 	return (0);
659 }
660 
661 /*
662  * Copyright (c) 2011 Jasper Lievisse Adriaanse <jasper@openbsd.org>
663  * Copyright (C) 2006,2007,2008,2009 Marc Balmer <mbalmer@openbsd.org>
664  * Copyright (C) 2000 Eugene M. Kim.  All rights reserved.
665  *
666  * Redistribution and use in source and binary forms, with or without
667  * modification, are permitted provided that the following conditions
668  * are met:
669  *
670  * 1. Redistributions of source code must retain the above copyright
671  *    notice, this list of conditions and the following disclaimer.
672  * 2. Author's name may not be used endorse or promote products derived
673  *    from this software without specific prior written permission.
674  *
675  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
676  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
677  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
678  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
679  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
680  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
681  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
682  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
683  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
684  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
685  * POSSIBILITY OF SUCH DAMAGE.
686  */
687 
688 #ifndef BPF_PATH_FORMAT
689 #define BPF_PATH_FORMAT "/dev/bpf%u"
690 #endif
691 
692 int	do_wakeup(const char *, const char *, int);
693 int	get_bpf(void);
694 int	bind_if_to_bpf(const char *, int);
695 int	get_ether(const char *, struct ether_addr *);
696 int	send_frame(int, const struct ether_addr *);
697 
698 int
699 wake(const char *ether_addr, const char *iface)
700 {
701 	struct ifaddrs		*ifa, *ifap;
702 	char			*pname = NULL;
703 	int			 bpf;
704 
705 	bpf = get_bpf();
706 	if (bpf == -1)
707 		errx(1, "Failed to bind to bpf.");
708 
709 	if (iface == NULL) {
710 		if (getifaddrs(&ifa) == -1)
711 			errx(1, "Could not get interface addresses.");
712 
713 		for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next){
714 			if (pname && !strcmp(pname, ifap->ifa_name))
715 				continue;
716 			pname = ifap->ifa_name;
717 
718 			/*
719 			 * We're only interested in sending the WoL frame on
720 			 * certain interfaces. So skip the loopback interface,
721 			 * as well as point-to-point and down interfaces.
722 			 */
723 			if ((ifap->ifa_flags & IFF_LOOPBACK) ||
724 			    (ifap->ifa_flags & IFF_POINTOPOINT) ||
725 			    (!(ifap->ifa_flags & IFF_UP)) ||
726 			    (!(ifap->ifa_flags & IFF_BROADCAST)))
727 				continue;
728 
729 			do_wakeup(ether_addr, ifap->ifa_name, bpf);
730 		}
731 		freeifaddrs(ifa);
732 	} else {
733 		do_wakeup(ether_addr, iface, bpf);
734 	}
735 
736 	(void)close(bpf);
737 
738 	return 0;
739 }
740 
741 int
742 do_wakeup(const char *eaddr, const char *iface, int bpf)
743 {
744 	struct ether_addr	 macaddr;
745 
746 	if (get_ether(eaddr, &macaddr) != 0)
747 		errx(1, "Invalid Ethernet address: %s", eaddr);
748 	if (bind_if_to_bpf(iface, bpf) != 0)
749 		errx(1, "Failed to bind %s to bpf.", iface);
750 	if (send_frame(bpf, &macaddr) != 0)
751 		errx(1, "Failed to send WoL frame on %s", iface);
752 	return 0;
753 }
754 
755 int
756 get_bpf(void)
757 {
758 	char path[MAXPATHLEN];
759 	int i, fd;
760 
761 	for (i = 0; ; i++) {
762 		if (snprintf(path, sizeof(path), BPF_PATH_FORMAT, i) == -1)
763 			return -1;
764 		fd = open(path, O_RDWR);
765 		if (fd != -1)
766 			return fd;
767 		if (errno == EBUSY)
768 			continue;
769 		break;
770 	}
771 	return -1;
772 }
773 
774 int
775 bind_if_to_bpf(const char *ifname, int bpf)
776 {
777 	struct ifreq ifr;
778 	u_int dlt;
779 
780 	if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >=
781 	    sizeof(ifr.ifr_name))
782 		return -1;
783 	if (ioctl(bpf, BIOCSETIF, &ifr) == -1)
784 		return -1;
785 	if (ioctl(bpf, BIOCGDLT, &dlt) == -1)
786 		return -1;
787 	if (dlt != DLT_EN10MB)
788 		return -1;
789 	return 0;
790 }
791 
792 int
793 get_ether(const char *text, struct ether_addr *addr)
794 {
795 	struct ether_addr *eaddr;
796 
797 	eaddr = ether_aton(text);
798 
799 	if (eaddr == NULL) {
800 		if (ether_hostton(text, addr))
801 			return -1;
802 	} else {
803 		*addr = *eaddr;
804 		return 0;
805 	}
806 
807 	return 0;
808 }
809 
810 #define SYNC_LEN 6
811 #define DESTADDR_COUNT 16
812 
813 int
814 send_frame(int bpf, const struct ether_addr *addr)
815 {
816 	struct {
817 		struct ether_header hdr;
818 		u_char sync[SYNC_LEN];
819 		u_char dest[ETHER_ADDR_LEN * DESTADDR_COUNT];
820 	} __packed pkt;
821 	u_char *p;
822 	int i;
823 
824 	(void)memset(&pkt, 0, sizeof(pkt));
825 	(void)memset(&pkt.hdr.ether_dhost, 0xff, sizeof(pkt.hdr.ether_dhost));
826 	pkt.hdr.ether_type = htons(0);
827 	(void)memset(pkt.sync, 0xff, SYNC_LEN);
828 	for (p = pkt.dest, i = 0; i < DESTADDR_COUNT; p += ETHER_ADDR_LEN, i++)
829 		bcopy(addr->ether_addr_octet, p, ETHER_ADDR_LEN);
830 	if (write(bpf, &pkt, sizeof(pkt)) != sizeof(pkt))
831 		return (errno);
832 	return (0);
833 }
834