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