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