route.c revision 58031
1/* 2 * PPP Routing related Module 3 * 4 * Written by Toshiharu OHNO (tony-o@iij.ad.jp) 5 * 6 * Copyright (C) 1994, Internet Initiative Japan, Inc. All rights reserverd. 7 * 8 * Redistribution and use in source and binary forms are permitted 9 * provided that the above copyright notice and this paragraph are 10 * duplicated in all such forms and that any documentation, 11 * advertising materials, and other materials related to such 12 * distribution and use acknowledge that the software was developed 13 * by the Internet Initiative Japan, Inc. The name of the 14 * IIJ may not be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 18 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 19 * 20 * $FreeBSD: head/usr.sbin/ppp/route.c 58031 2000-03-14 01:46:38Z brian $ 21 * 22 */ 23 24#include <sys/param.h> 25#include <sys/socket.h> 26#include <net/if_types.h> 27#include <net/route.h> 28#include <net/if.h> 29#include <netinet/in.h> 30#include <arpa/inet.h> 31#include <net/if_dl.h> 32#include <netinet/in_systm.h> 33#include <netinet/ip.h> 34#include <sys/un.h> 35#ifndef NOINET6 36#include <netinet6/in6.h> 37#endif 38#include <netdb.h> 39 40#include <errno.h> 41#include <stdio.h> 42#include <stdlib.h> 43#include <string.h> 44#include <sys/sysctl.h> 45#include <termios.h> 46 47#include "layer.h" 48#include "defs.h" 49#include "command.h" 50#include "mbuf.h" 51#include "log.h" 52#include "iplist.h" 53#include "timer.h" 54#include "throughput.h" 55#include "lqr.h" 56#include "hdlc.h" 57#include "fsm.h" 58#include "lcp.h" 59#include "ccp.h" 60#include "link.h" 61#include "slcompress.h" 62#include "ipcp.h" 63#include "filter.h" 64#include "descriptor.h" 65#include "mp.h" 66#ifndef NORADIUS 67#include "radius.h" 68#endif 69#include "bundle.h" 70#include "route.h" 71#include "prompt.h" 72#include "iface.h" 73 74static void 75p_sockaddr(struct prompt *prompt, struct sockaddr *phost, 76 struct sockaddr *pmask, int width) 77{ 78 char buf[29]; 79 struct sockaddr_in *ihost4 = (struct sockaddr_in *)phost; 80 struct sockaddr_in *mask4 = (struct sockaddr_in *)pmask; 81 struct sockaddr_dl *dl = (struct sockaddr_dl *)phost; 82 83 if (log_IsKept(LogDEBUG)) { 84 char tmp[50]; 85 86 log_Printf(LogDEBUG, "Found the following sockaddr:\n"); 87 log_Printf(LogDEBUG, " Family %d, len %d\n", 88 (int)phost->sa_family, (int)phost->sa_len); 89 inet_ntop(phost->sa_family, phost, tmp, sizeof tmp); 90 log_Printf(LogDEBUG, " Addr %s\n", tmp); 91 if (pmask) { 92 inet_ntop(pmask->sa_family, pmask, tmp, sizeof tmp); 93 log_Printf(LogDEBUG, " Mask %s\n", tmp); 94 } 95 } 96 97 switch (phost->sa_family) { 98 case AF_INET: 99 if (!phost) 100 buf[0] = '\0'; 101 else if (ihost4->sin_addr.s_addr == INADDR_ANY) 102 strcpy(buf, "default"); 103 else if (!pmask) 104 strcpy(buf, inet_ntoa(ihost4->sin_addr)); 105 else { 106 u_int32_t msk = ntohl(mask4->sin_addr.s_addr); 107 u_int32_t tst; 108 int bits; 109 int len; 110 struct sockaddr_in net; 111 112 for (tst = 1, bits = 32; tst; tst <<= 1, bits--) 113 if (msk & tst) 114 break; 115 116 for (tst <<= 1; tst; tst <<= 1) 117 if (!(msk & tst)) 118 break; 119 120 net.sin_addr.s_addr = ihost4->sin_addr.s_addr & mask4->sin_addr.s_addr; 121 strcpy(buf, inet_ntoa(net.sin_addr)); 122 for (len = strlen(buf); len > 3; buf[len -= 2] = '\0') 123 if (strcmp(buf + len - 2, ".0")) 124 break; 125 126 if (tst) /* non-contiguous :-( */ 127 sprintf(buf + strlen(buf),"&0x%08lx", (u_long)msk); 128 else 129 sprintf(buf + strlen(buf), "/%d", bits); 130 } 131 break; 132 133 case AF_LINK: 134 if (dl->sdl_nlen) 135 snprintf(buf, sizeof buf, "%.*s", dl->sdl_nlen, dl->sdl_data); 136 else if (dl->sdl_alen) { 137 if (dl->sdl_type == IFT_ETHER) { 138 if (dl->sdl_alen < sizeof buf / 3) { 139 int f; 140 u_char *MAC; 141 142 MAC = (u_char *)dl->sdl_data + dl->sdl_nlen; 143 for (f = 0; f < dl->sdl_alen; f++) 144 sprintf(buf+f*3, "%02x:", MAC[f]); 145 buf[f*3-1] = '\0'; 146 } else 147 strcpy(buf, "??:??:??:??:??:??"); 148 } else 149 sprintf(buf, "<IFT type %d>", dl->sdl_type); 150 } else if (dl->sdl_slen) 151 sprintf(buf, "<slen %d?>", dl->sdl_slen); 152 else 153 sprintf(buf, "link#%d", dl->sdl_index); 154 break; 155 156#ifndef NOINET6 157 case AF_INET6: 158 if (!phost) 159 buf[0] = '\0'; 160 else { 161 const u_char masks[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; 162 struct sockaddr_in6 *ihost6 = (struct sockaddr_in6 *)phost; 163 struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)pmask; 164 int masklen, len; 165 const u_char *c; 166 167 /* XXX: ?????!?!?!!!!! This is horrible ! */ 168 if (IN6_IS_ADDR_LINKLOCAL(&ihost6->sin6_addr) || 169 IN6_IS_ADDR_MC_LINKLOCAL(&ihost6->sin6_addr)) { 170 ihost6->sin6_scope_id = 171 ntohs(*(u_short *)&ihost6->sin6_addr.s6_addr[2]); 172 *(u_short *)&ihost6->sin6_addr.s6_addr[2] = 0; 173 } 174 175 if (mask6) { 176 const u_char *p, *end; 177 178 p = (const u_char *)&mask6->sin6_addr; 179 end = p + 16; 180 for (masklen = 0, end = p + 16; p < end && *p == 0xff; p++) 181 masklen += 8; 182 183 if (p < end) { 184 for (c = masks; c < masks + sizeof masks; c++) 185 if (*c == *p) { 186 masklen += c - masks; 187 break; 188 } 189 } 190 } else 191 masklen = 128; 192 193 if (masklen == 0 && IN6_IS_ADDR_UNSPECIFIED(&ihost6->sin6_addr)) 194 snprintf(buf, sizeof buf, "default"); 195 else { 196 getnameinfo(phost, ihost6->sin6_len, buf, sizeof buf, 197 NULL, 0, NI_WITHSCOPEID | NI_NUMERICHOST); 198 if (mask6 && (len = strlen(buf)) < sizeof buf - 1) 199 snprintf(buf + len, sizeof buf - len, "/%d", masklen); 200 } 201 } 202 break; 203#endif 204 205 default: 206 sprintf(buf, "<AF type %d>", phost->sa_family); 207 break; 208 } 209 210 prompt_Printf(prompt, "%-*s ", width-1, buf); 211} 212 213static struct bits { 214 u_int32_t b_mask; 215 char b_val; 216} bits[] = { 217 { RTF_UP, 'U' }, 218 { RTF_GATEWAY, 'G' }, 219 { RTF_HOST, 'H' }, 220 { RTF_REJECT, 'R' }, 221 { RTF_DYNAMIC, 'D' }, 222 { RTF_MODIFIED, 'M' }, 223 { RTF_DONE, 'd' }, 224 { RTF_CLONING, 'C' }, 225 { RTF_XRESOLVE, 'X' }, 226 { RTF_LLINFO, 'L' }, 227 { RTF_STATIC, 'S' }, 228 { RTF_PROTO1, '1' }, 229 { RTF_PROTO2, '2' }, 230 { RTF_BLACKHOLE, 'B' }, 231#ifdef RTF_WASCLONED 232 { RTF_WASCLONED, 'W' }, 233#endif 234#ifdef RTF_PRCLONING 235 { RTF_PRCLONING, 'c' }, 236#endif 237#ifdef RTF_PROTO3 238 { RTF_PROTO3, '3' }, 239#endif 240#ifdef RTF_BROADCAST 241 { RTF_BROADCAST, 'b' }, 242#endif 243 { 0, '\0' } 244}; 245 246#ifndef RTF_WASCLONED 247#define RTF_WASCLONED (0) 248#endif 249 250static void 251p_flags(struct prompt *prompt, u_int32_t f, int max) 252{ 253 char name[33], *flags; 254 register struct bits *p = bits; 255 256 if (max > sizeof name - 1) 257 max = sizeof name - 1; 258 259 for (flags = name; p->b_mask && flags - name < max; p++) 260 if (p->b_mask & f) 261 *flags++ = p->b_val; 262 *flags = '\0'; 263 prompt_Printf(prompt, "%-*.*s", max, max, name); 264} 265 266const char * 267Index2Nam(int idx) 268{ 269 /* 270 * XXX: Maybe we should select() on the routing socket so that we can 271 * notice interfaces that come & go (PCCARD support). 272 * Or we could even support a signal that resets these so that 273 * the PCCARD insert/remove events can signal ppp. 274 */ 275 static char **ifs; /* Figure these out once */ 276 static int nifs, debug_done; /* Figure out how many once, and debug once */ 277 278 if (idx > nifs || (idx > 0 && ifs[idx-1] == NULL)) { 279 int mib[6], have, had; 280 size_t needed; 281 char *buf, *ptr, *end; 282 struct sockaddr_dl *dl; 283 struct if_msghdr *ifm; 284 285 if (ifs) { 286 free(ifs); 287 ifs = NULL; 288 nifs = 0; 289 } 290 debug_done = 0; 291 292 mib[0] = CTL_NET; 293 mib[1] = PF_ROUTE; 294 mib[2] = 0; 295 mib[3] = 0; 296 mib[4] = NET_RT_IFLIST; 297 mib[5] = 0; 298 299 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) { 300 log_Printf(LogERROR, "Index2Nam: sysctl: estimate: %s\n", 301 strerror(errno)); 302 return "???"; 303 } 304 if ((buf = malloc(needed)) == NULL) 305 return "???"; 306 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { 307 free(buf); 308 return "???"; 309 } 310 end = buf + needed; 311 312 have = 0; 313 for (ptr = buf; ptr < end; ptr += ifm->ifm_msglen) { 314 ifm = (struct if_msghdr *)ptr; 315 dl = (struct sockaddr_dl *)(ifm + 1); 316 if (ifm->ifm_index > 0) { 317 if (ifm->ifm_index > have) { 318 char **newifs; 319 320 had = have; 321 have = ifm->ifm_index + 5; 322 if (had) 323 newifs = (char **)realloc(ifs, sizeof(char *) * have); 324 else 325 newifs = (char **)malloc(sizeof(char *) * have); 326 if (!newifs) { 327 log_Printf(LogDEBUG, "Index2Nam: %s\n", strerror(errno)); 328 nifs = 0; 329 if (ifs) { 330 free(ifs); 331 ifs = NULL; 332 } 333 free(buf); 334 return "???"; 335 } 336 ifs = newifs; 337 memset(ifs + had, '\0', sizeof(char *) * (have - had)); 338 } 339 if (ifs[ifm->ifm_index-1] == NULL) { 340 ifs[ifm->ifm_index-1] = (char *)malloc(dl->sdl_nlen+1); 341 memcpy(ifs[ifm->ifm_index-1], dl->sdl_data, dl->sdl_nlen); 342 ifs[ifm->ifm_index-1][dl->sdl_nlen] = '\0'; 343 if (nifs < ifm->ifm_index) 344 nifs = ifm->ifm_index; 345 } 346 } else if (log_IsKept(LogDEBUG)) 347 log_Printf(LogDEBUG, "Skipping out-of-range interface %d!\n", 348 ifm->ifm_index); 349 } 350 free(buf); 351 } 352 353 if (log_IsKept(LogDEBUG) && !debug_done) { 354 int f; 355 356 log_Printf(LogDEBUG, "Found the following interfaces:\n"); 357 for (f = 0; f < nifs; f++) 358 if (ifs[f] != NULL) 359 log_Printf(LogDEBUG, " Index %d, name \"%s\"\n", f+1, ifs[f]); 360 debug_done = 1; 361 } 362 363 if (idx < 1 || idx > nifs || ifs[idx-1] == NULL) 364 return "???"; 365 366 return ifs[idx-1]; 367} 368 369int 370route_Show(struct cmdargs const *arg) 371{ 372 struct rt_msghdr *rtm; 373 struct sockaddr *sa_dst, *sa_gw, *sa_mask; 374 char *sp, *ep, *cp, *wp; 375 size_t needed; 376 int mib[6]; 377 378 mib[0] = CTL_NET; 379 mib[1] = PF_ROUTE; 380 mib[2] = 0; 381 mib[3] = 0; 382 mib[4] = NET_RT_DUMP; 383 mib[5] = 0; 384 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) { 385 log_Printf(LogERROR, "route_Show: sysctl: estimate: %s\n", strerror(errno)); 386 return (1); 387 } 388 sp = malloc(needed); 389 if (sp == NULL) 390 return (1); 391 if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) { 392 log_Printf(LogERROR, "route_Show: sysctl: getroute: %s\n", strerror(errno)); 393 free(sp); 394 return (1); 395 } 396 ep = sp + needed; 397 398 prompt_Printf(arg->prompt, "%-20s%-20sFlags Netif\n", 399 "Destination", "Gateway"); 400 for (cp = sp; cp < ep; cp += rtm->rtm_msglen) { 401 rtm = (struct rt_msghdr *) cp; 402 wp = (char *)(rtm+1); 403 404 /* 405 * This code relies on RTA_DST, RTA_GATEWAY and RTA_NETMASK 406 * having values 1, 2 and 4 respectively (or at least relies 407 * on them being the first 3 possible sockaddrs in that order) ! 408 */ 409 410 if (rtm->rtm_addrs & RTA_DST) { 411 sa_dst = (struct sockaddr *)wp; 412 wp += sa_dst->sa_len; 413 } else 414 sa_dst = NULL; 415 416 if (rtm->rtm_addrs & RTA_GATEWAY) { 417 sa_gw = (struct sockaddr *)wp; 418 wp += sa_gw->sa_len; 419 } else 420 sa_gw = NULL; 421 422 if (rtm->rtm_addrs & RTA_NETMASK) { 423 sa_mask = (struct sockaddr *)wp; 424 wp += sa_mask->sa_len; 425 } else 426 sa_mask = NULL; 427 428 if (sa_dst && sa_gw) { 429 p_sockaddr(arg->prompt, sa_dst, sa_mask, 20); 430 p_sockaddr(arg->prompt, sa_gw, NULL, 20); 431 432 p_flags(arg->prompt, rtm->rtm_flags, 6); 433 prompt_Printf(arg->prompt, " %s\n", Index2Nam(rtm->rtm_index)); 434 } else 435 prompt_Printf(arg->prompt, "<can't parse routing entry>\n"); 436 } 437 free(sp); 438 return 0; 439} 440 441/* 442 * Delete routes associated with our interface 443 */ 444void 445route_IfDelete(struct bundle *bundle, int all) 446{ 447 struct rt_msghdr *rtm; 448 struct sockaddr *sa; 449 struct in_addr sa_dst, sa_none; 450 int pass; 451 size_t needed; 452 char *sp, *cp, *ep; 453 int mib[6]; 454 455 log_Printf(LogDEBUG, "route_IfDelete (%d)\n", bundle->iface->index); 456 sa_none.s_addr = INADDR_ANY; 457 458 mib[0] = CTL_NET; 459 mib[1] = PF_ROUTE; 460 mib[2] = 0; 461 mib[3] = 0; 462 mib[4] = NET_RT_DUMP; 463 mib[5] = 0; 464 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) { 465 log_Printf(LogERROR, "route_IfDelete: sysctl: estimate: %s\n", 466 strerror(errno)); 467 return; 468 } 469 470 sp = malloc(needed); 471 if (sp == NULL) 472 return; 473 474 if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) { 475 log_Printf(LogERROR, "route_IfDelete: sysctl: getroute: %s\n", 476 strerror(errno)); 477 free(sp); 478 return; 479 } 480 ep = sp + needed; 481 482 for (pass = 0; pass < 2; pass++) { 483 /* 484 * We do 2 passes. The first deletes all cloned routes. The second 485 * deletes all non-cloned routes. This is necessary to avoid 486 * potential errors from trying to delete route X after route Y where 487 * route X was cloned from route Y (and is no longer there 'cos it 488 * may have gone with route Y). 489 */ 490 if (RTF_WASCLONED == 0 && pass == 0) 491 /* So we can't tell ! */ 492 continue; 493 for (cp = sp; cp < ep; cp += rtm->rtm_msglen) { 494 rtm = (struct rt_msghdr *) cp; 495 sa = (struct sockaddr *) (rtm + 1); 496 log_Printf(LogDEBUG, "route_IfDelete: addrs: %x, Netif: %d (%s)," 497 " flags: %x, dst: %s ?\n", rtm->rtm_addrs, rtm->rtm_index, 498 Index2Nam(rtm->rtm_index), rtm->rtm_flags, 499 inet_ntoa(((struct sockaddr_in *) sa)->sin_addr)); 500 if (rtm->rtm_addrs & RTA_DST && rtm->rtm_addrs & RTA_GATEWAY && 501 rtm->rtm_index == bundle->iface->index && 502 (all || (rtm->rtm_flags & RTF_GATEWAY))) { 503 sa_dst.s_addr = ((struct sockaddr_in *)sa)->sin_addr.s_addr; 504 sa = (struct sockaddr *)((char *)sa + sa->sa_len); 505 if (sa->sa_family == AF_INET || sa->sa_family == AF_LINK) { 506 if ((pass == 0 && (rtm->rtm_flags & RTF_WASCLONED)) || 507 (pass == 1 && !(rtm->rtm_flags & RTF_WASCLONED))) { 508 log_Printf(LogDEBUG, "route_IfDelete: Remove it (pass %d)\n", pass); 509 bundle_SetRoute(bundle, RTM_DELETE, sa_dst, sa_none, sa_none, 0, 0); 510 } else 511 log_Printf(LogDEBUG, "route_IfDelete: Skip it (pass %d)\n", pass); 512 } else 513 log_Printf(LogDEBUG, 514 "route_IfDelete: Can't remove routes of %d family !\n", 515 sa->sa_family); 516 } 517 } 518 } 519 free(sp); 520} 521 522int 523GetIfIndex(char *name) 524{ 525 int idx; 526 const char *got; 527 528 idx = 1; 529 while (strcmp(got = Index2Nam(idx), "???")) 530 if (!strcmp(got, name)) 531 return idx; 532 else 533 idx++; 534 return -1; 535} 536 537void 538route_Change(struct bundle *bundle, struct sticky_route *r, 539 struct in_addr me, struct in_addr peer) 540{ 541 struct in_addr none, del; 542 543 none.s_addr = INADDR_ANY; 544 for (; r; r = r->next) { 545 if ((r->type & ROUTE_DSTMYADDR) && r->dst.s_addr != me.s_addr) { 546 del.s_addr = r->dst.s_addr & r->mask.s_addr; 547 bundle_SetRoute(bundle, RTM_DELETE, del, none, none, 1, 0); 548 r->dst = me; 549 if (r->type & ROUTE_GWHISADDR) 550 r->gw = peer; 551 } else if ((r->type & ROUTE_DSTHISADDR) && r->dst.s_addr != peer.s_addr) { 552 del.s_addr = r->dst.s_addr & r->mask.s_addr; 553 bundle_SetRoute(bundle, RTM_DELETE, del, none, none, 1, 0); 554 r->dst = peer; 555 if (r->type & ROUTE_GWHISADDR) 556 r->gw = peer; 557 } else if ((r->type & ROUTE_GWHISADDR) && r->gw.s_addr != peer.s_addr) 558 r->gw = peer; 559 bundle_SetRoute(bundle, RTM_ADD, r->dst, r->gw, r->mask, 1, 0); 560 } 561} 562 563void 564route_Clean(struct bundle *bundle, struct sticky_route *r) 565{ 566 struct in_addr none, del; 567 568 none.s_addr = INADDR_ANY; 569 for (; r; r = r->next) { 570 del.s_addr = r->dst.s_addr & r->mask.s_addr; 571 bundle_SetRoute(bundle, RTM_DELETE, del, none, none, 1, 0); 572 } 573} 574 575void 576route_Add(struct sticky_route **rp, int type, struct in_addr dst, 577 struct in_addr mask, struct in_addr gw) 578{ 579 struct sticky_route *r; 580 int dsttype = type & ROUTE_DSTANY; 581 582 r = NULL; 583 while (*rp) { 584 if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) || 585 (!dsttype && (*rp)->dst.s_addr == dst.s_addr)) { 586 /* Oops, we already have this route - unlink it */ 587 free(r); /* impossible really */ 588 r = *rp; 589 *rp = r->next; 590 } else 591 rp = &(*rp)->next; 592 } 593 594 if (!r) 595 r = (struct sticky_route *)malloc(sizeof(struct sticky_route)); 596 r->type = type; 597 r->next = NULL; 598 r->dst = dst; 599 r->mask = mask; 600 r->gw = gw; 601 *rp = r; 602} 603 604void 605route_Delete(struct sticky_route **rp, int type, struct in_addr dst) 606{ 607 struct sticky_route *r; 608 int dsttype = type & ROUTE_DSTANY; 609 610 for (; *rp; rp = &(*rp)->next) { 611 if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) || 612 (!dsttype && dst.s_addr == ((*rp)->dst.s_addr & (*rp)->mask.s_addr))) { 613 r = *rp; 614 *rp = r->next; 615 free(r); 616 break; 617 } 618 } 619} 620 621void 622route_DeleteAll(struct sticky_route **rp) 623{ 624 struct sticky_route *r, *rn; 625 626 for (r = *rp; r; r = rn) { 627 rn = r->next; 628 free(r); 629 } 630 *rp = NULL; 631} 632 633void 634route_ShowSticky(struct prompt *p, struct sticky_route *r, const char *tag, 635 int indent) 636{ 637 int def; 638 int tlen = strlen(tag); 639 640 if (tlen + 2 > indent) 641 prompt_Printf(p, "%s:\n%*s", tag, indent, ""); 642 else 643 prompt_Printf(p, "%s:%*s", tag, indent - tlen - 1, ""); 644 645 for (; r; r = r->next) { 646 def = r->dst.s_addr == INADDR_ANY && r->mask.s_addr == INADDR_ANY; 647 648 prompt_Printf(p, "%*sadd ", tlen ? 0 : indent, ""); 649 tlen = 0; 650 if (r->type & ROUTE_DSTMYADDR) 651 prompt_Printf(p, "MYADDR"); 652 else if (r->type & ROUTE_DSTHISADDR) 653 prompt_Printf(p, "HISADDR"); 654 else if (!def) 655 prompt_Printf(p, "%s", inet_ntoa(r->dst)); 656 657 if (def) 658 prompt_Printf(p, "default "); 659 else 660 prompt_Printf(p, " %s ", inet_ntoa(r->mask)); 661 662 if (r->type & ROUTE_GWHISADDR) 663 prompt_Printf(p, "HISADDR\n"); 664 else 665 prompt_Printf(p, "%s\n", inet_ntoa(r->gw)); 666 } 667} 668