7056e6ac47
demand-dial links with dynamic IP numbers where the program that causes the dial bind()s to an interface address that is subsequently changed after ppp negotiation. The problem is defeated by adding negotiated addresses to the tun interface as additional alias addresses and providing a set of ``iface'' commands for managing the interface. Libalias is also required (and what a name clash!) - it happily IP-aliases the address so that the source is that of the primary (negotiated) interface and un-IP-aliases it on the way back. An ``enable iface-alias'' is done implicitly by the -alias command line switch. If -alias isn't given, iface-aliasing is disabled by default and can't be enabled 'till an ``alias enable yes'' is done. ``alias enable no'' silently disables iface-alias. So, for dynamic-IP-type-connections, running ``ppp -alias -auto blah'' will work for the first connection, although existing bindings will not survive a disconnect/connect as the TCP peer will be trying to send to the old IP address - the packets won't route. It's now a lot easier to add IPXCP to ppp with minor updates to the new iface.[ch] (if anyone ever gets 'round to it). It's also now possible to manually add interface aliases with something like ``iface add 1.2.3.4/24 5.6.7.8''. This allows multi-homed ppp links :-)
571 lines
15 KiB
C
571 lines
15 KiB
C
/*
|
|
* PPP Routing related Module
|
|
*
|
|
* Written by Toshiharu OHNO (tony-o@iij.ad.jp)
|
|
*
|
|
* Copyright (C) 1994, Internet Initiative Japan, Inc. All rights reserverd.
|
|
*
|
|
* Redistribution and use in source and binary forms are permitted
|
|
* provided that the above copyright notice and this paragraph are
|
|
* duplicated in all such forms and that any documentation,
|
|
* advertising materials, and other materials related to such
|
|
* distribution and use acknowledge that the software was developed
|
|
* by the Internet Initiative Japan, Inc. The name of the
|
|
* IIJ may not be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* $Id: route.c,v 1.53 1998/08/17 06:42:40 brian Exp $
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <net/if_types.h>
|
|
#include <net/route.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <net/if_dl.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/sysctl.h>
|
|
#include <termios.h>
|
|
|
|
#include "defs.h"
|
|
#include "command.h"
|
|
#include "mbuf.h"
|
|
#include "log.h"
|
|
#include "iplist.h"
|
|
#include "timer.h"
|
|
#include "throughput.h"
|
|
#include "lqr.h"
|
|
#include "hdlc.h"
|
|
#include "fsm.h"
|
|
#include "lcp.h"
|
|
#include "ccp.h"
|
|
#include "link.h"
|
|
#include "slcompress.h"
|
|
#include "ipcp.h"
|
|
#include "filter.h"
|
|
#include "descriptor.h"
|
|
#include "mp.h"
|
|
#include "bundle.h"
|
|
#include "route.h"
|
|
#include "prompt.h"
|
|
#include "iface.h"
|
|
|
|
static void
|
|
p_sockaddr(struct prompt *prompt, struct sockaddr *phost,
|
|
struct sockaddr *pmask, int width)
|
|
{
|
|
char buf[29];
|
|
struct sockaddr_in *ihost = (struct sockaddr_in *)phost;
|
|
struct sockaddr_in *mask = (struct sockaddr_in *)pmask;
|
|
struct sockaddr_dl *dl = (struct sockaddr_dl *)phost;
|
|
|
|
switch (phost->sa_family) {
|
|
case AF_INET:
|
|
if (!phost)
|
|
buf[0] = '\0';
|
|
else if (ihost->sin_addr.s_addr == INADDR_ANY)
|
|
strcpy(buf, "default");
|
|
else if (!mask)
|
|
strcpy(buf, inet_ntoa(ihost->sin_addr));
|
|
else {
|
|
u_int32_t msk = ntohl(mask->sin_addr.s_addr);
|
|
u_int32_t tst;
|
|
int bits;
|
|
int len;
|
|
struct sockaddr_in net;
|
|
|
|
for (tst = 1, bits=32; tst; tst <<= 1, bits--)
|
|
if (msk & tst)
|
|
break;
|
|
|
|
for (tst <<= 1; tst; tst <<= 1)
|
|
if (!(msk & tst))
|
|
break;
|
|
|
|
net.sin_addr.s_addr = ihost->sin_addr.s_addr & mask->sin_addr.s_addr;
|
|
strcpy(buf, inet_ntoa(net.sin_addr));
|
|
for (len = strlen(buf); len > 3; buf[len -= 2] = '\0')
|
|
if (strcmp(buf + len - 2, ".0"))
|
|
break;
|
|
|
|
if (tst) /* non-contiguous :-( */
|
|
sprintf(buf + strlen(buf),"&0x%08lx", (u_long)msk);
|
|
else
|
|
sprintf(buf + strlen(buf), "/%d", bits);
|
|
}
|
|
break;
|
|
|
|
case AF_LINK:
|
|
if (dl->sdl_nlen)
|
|
snprintf(buf, sizeof buf, "%.*s", dl->sdl_nlen, dl->sdl_data);
|
|
else if (dl->sdl_alen) {
|
|
if (dl->sdl_type == IFT_ETHER) {
|
|
if (dl->sdl_alen < sizeof buf / 3) {
|
|
int f;
|
|
u_char *MAC;
|
|
|
|
MAC = (u_char *)dl->sdl_data + dl->sdl_nlen;
|
|
for (f = 0; f < dl->sdl_alen; f++)
|
|
sprintf(buf+f*3, "%02x:", MAC[f]);
|
|
buf[f*3-1] = '\0';
|
|
} else
|
|
strcpy(buf, "??:??:??:??:??:??");
|
|
} else
|
|
sprintf(buf, "<IFT type %d>", dl->sdl_type);
|
|
} else if (dl->sdl_slen)
|
|
sprintf(buf, "<slen %d?>", dl->sdl_slen);
|
|
else
|
|
sprintf(buf, "link#%d", dl->sdl_index);
|
|
break;
|
|
|
|
default:
|
|
sprintf(buf, "<AF type %d>", phost->sa_family);
|
|
break;
|
|
}
|
|
|
|
prompt_Printf(prompt, "%-*s ", width-1, buf);
|
|
}
|
|
|
|
static struct bits {
|
|
u_int32_t b_mask;
|
|
char b_val;
|
|
} bits[] = {
|
|
{ RTF_UP, 'U' },
|
|
{ RTF_GATEWAY, 'G' },
|
|
{ RTF_HOST, 'H' },
|
|
{ RTF_REJECT, 'R' },
|
|
{ RTF_DYNAMIC, 'D' },
|
|
{ RTF_MODIFIED, 'M' },
|
|
{ RTF_DONE, 'd' },
|
|
{ RTF_CLONING, 'C' },
|
|
{ RTF_XRESOLVE, 'X' },
|
|
{ RTF_LLINFO, 'L' },
|
|
{ RTF_STATIC, 'S' },
|
|
{ RTF_PROTO1, '1' },
|
|
{ RTF_PROTO2, '2' },
|
|
{ RTF_BLACKHOLE, 'B' },
|
|
#ifdef RTF_WASCLONED
|
|
{ RTF_WASCLONED, 'W' },
|
|
#endif
|
|
#ifdef RTF_PRCLONING
|
|
{ RTF_PRCLONING, 'c' },
|
|
#endif
|
|
#ifdef RTF_PROTO3
|
|
{ RTF_PROTO3, '3' },
|
|
#endif
|
|
#ifdef RTF_BROADCAST
|
|
{ RTF_BROADCAST, 'b' },
|
|
#endif
|
|
{ 0, '\0' }
|
|
};
|
|
|
|
#ifndef RTF_WASCLONED
|
|
#define RTF_WASCLONED (0)
|
|
#endif
|
|
|
|
static void
|
|
p_flags(struct prompt *prompt, u_int32_t f, int max)
|
|
{
|
|
char name[33], *flags;
|
|
register struct bits *p = bits;
|
|
|
|
if (max > sizeof name - 1)
|
|
max = sizeof name - 1;
|
|
|
|
for (flags = name; p->b_mask && flags - name < max; p++)
|
|
if (p->b_mask & f)
|
|
*flags++ = p->b_val;
|
|
*flags = '\0';
|
|
prompt_Printf(prompt, "%-*.*s", max, max, name);
|
|
}
|
|
|
|
const char *
|
|
Index2Nam(int idx)
|
|
{
|
|
/*
|
|
* XXX: Maybe we should select() on the routing socket so that we can
|
|
* notice interfaces that come & go (PCCARD support).
|
|
* Or we could even support a signal that resets these so that
|
|
* the PCCARD insert/remove events can signal ppp.
|
|
*/
|
|
static char **ifs; /* Figure these out once */
|
|
static int nifs, debug_done; /* Figure out how many once, and debug once */
|
|
|
|
if (!nifs) {
|
|
int mib[6], have, had;
|
|
size_t needed;
|
|
char *buf, *ptr, *end;
|
|
struct sockaddr_dl *dl;
|
|
struct if_msghdr *ifm;
|
|
|
|
mib[0] = CTL_NET;
|
|
mib[1] = PF_ROUTE;
|
|
mib[2] = 0;
|
|
mib[3] = 0;
|
|
mib[4] = NET_RT_IFLIST;
|
|
mib[5] = 0;
|
|
|
|
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
|
|
log_Printf(LogERROR, "Index2Nam: sysctl: estimate: %s\n",
|
|
strerror(errno));
|
|
return "???";
|
|
}
|
|
if ((buf = malloc(needed)) == NULL)
|
|
return "???";
|
|
if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
|
|
free(buf);
|
|
return "???";
|
|
}
|
|
end = buf + needed;
|
|
|
|
have = 0;
|
|
for (ptr = buf; ptr < end; ptr += ifm->ifm_msglen) {
|
|
ifm = (struct if_msghdr *)ptr;
|
|
dl = (struct sockaddr_dl *)(ifm + 1);
|
|
if (ifm->ifm_index > 0) {
|
|
if (ifm->ifm_index > have) {
|
|
char **newifs;
|
|
|
|
had = have;
|
|
have = ifm->ifm_index + 5;
|
|
if (had)
|
|
newifs = (char **)realloc(ifs, sizeof(char *) * have);
|
|
else
|
|
newifs = (char **)malloc(sizeof(char *) * have);
|
|
if (!newifs) {
|
|
log_Printf(LogDEBUG, "Index2Nam: %s\n", strerror(errno));
|
|
nifs = 0;
|
|
if (ifs)
|
|
free(ifs);
|
|
return "???";
|
|
}
|
|
ifs = newifs;
|
|
memset(ifs + had, '\0', sizeof(char *) * (have - had));
|
|
}
|
|
if (ifs[ifm->ifm_index-1] == NULL) {
|
|
ifs[ifm->ifm_index-1] = (char *)malloc(dl->sdl_nlen+1);
|
|
memcpy(ifs[ifm->ifm_index-1], dl->sdl_data, dl->sdl_nlen);
|
|
ifs[ifm->ifm_index-1][dl->sdl_nlen] = '\0';
|
|
if (nifs < ifm->ifm_index)
|
|
nifs = ifm->ifm_index;
|
|
}
|
|
} else if (log_IsKept(LogDEBUG))
|
|
log_Printf(LogDEBUG, "Skipping out-of-range interface %d!\n",
|
|
ifm->ifm_index);
|
|
}
|
|
free(buf);
|
|
}
|
|
|
|
if (log_IsKept(LogDEBUG) && !debug_done) {
|
|
int f;
|
|
|
|
log_Printf(LogDEBUG, "Found the following interfaces:\n");
|
|
for (f = 0; f < nifs; f++)
|
|
if (ifs[f] != NULL)
|
|
log_Printf(LogDEBUG, " Index %d, name \"%s\"\n", f+1, ifs[f]);
|
|
debug_done = 1;
|
|
}
|
|
|
|
if (idx < 1 || idx > nifs || ifs[idx-1] == NULL)
|
|
return "???";
|
|
|
|
return ifs[idx-1];
|
|
}
|
|
|
|
int
|
|
route_Show(struct cmdargs const *arg)
|
|
{
|
|
struct rt_msghdr *rtm;
|
|
struct sockaddr *sa_dst, *sa_gw, *sa_mask;
|
|
char *sp, *ep, *cp, *wp;
|
|
size_t needed;
|
|
int mib[6];
|
|
|
|
mib[0] = CTL_NET;
|
|
mib[1] = PF_ROUTE;
|
|
mib[2] = 0;
|
|
mib[3] = 0;
|
|
mib[4] = NET_RT_DUMP;
|
|
mib[5] = 0;
|
|
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
|
|
log_Printf(LogERROR, "route_Show: sysctl: estimate: %s\n", strerror(errno));
|
|
return (1);
|
|
}
|
|
sp = malloc(needed);
|
|
if (sp == NULL)
|
|
return (1);
|
|
if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
|
|
log_Printf(LogERROR, "route_Show: sysctl: getroute: %s\n", strerror(errno));
|
|
free(sp);
|
|
return (1);
|
|
}
|
|
ep = sp + needed;
|
|
|
|
prompt_Printf(arg->prompt, "%-20s%-20sFlags Netif\n",
|
|
"Destination", "Gateway");
|
|
for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
|
|
rtm = (struct rt_msghdr *) cp;
|
|
wp = (char *)(rtm+1);
|
|
|
|
if (rtm->rtm_addrs & RTA_DST) {
|
|
sa_dst = (struct sockaddr *)wp;
|
|
wp += sa_dst->sa_len;
|
|
} else
|
|
sa_dst = NULL;
|
|
|
|
if (rtm->rtm_addrs & RTA_GATEWAY) {
|
|
sa_gw = (struct sockaddr *)wp;
|
|
wp += sa_gw->sa_len;
|
|
} else
|
|
sa_gw = NULL;
|
|
|
|
if (rtm->rtm_addrs & RTA_NETMASK) {
|
|
sa_mask = (struct sockaddr *)wp;
|
|
wp += sa_mask->sa_len;
|
|
} else
|
|
sa_mask = NULL;
|
|
|
|
p_sockaddr(arg->prompt, sa_dst, sa_mask, 20);
|
|
p_sockaddr(arg->prompt, sa_gw, NULL, 20);
|
|
|
|
p_flags(arg->prompt, rtm->rtm_flags, 6);
|
|
prompt_Printf(arg->prompt, " %s\n", Index2Nam(rtm->rtm_index));
|
|
}
|
|
free(sp);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Delete routes associated with our interface
|
|
*/
|
|
void
|
|
route_IfDelete(struct bundle *bundle, int all)
|
|
{
|
|
struct rt_msghdr *rtm;
|
|
struct sockaddr *sa;
|
|
struct in_addr sa_dst, sa_none;
|
|
int pass;
|
|
size_t needed;
|
|
char *sp, *cp, *ep;
|
|
int mib[6];
|
|
|
|
log_Printf(LogDEBUG, "route_IfDelete (%d)\n", bundle->iface->index);
|
|
sa_none.s_addr = INADDR_ANY;
|
|
|
|
mib[0] = CTL_NET;
|
|
mib[1] = PF_ROUTE;
|
|
mib[2] = 0;
|
|
mib[3] = 0;
|
|
mib[4] = NET_RT_DUMP;
|
|
mib[5] = 0;
|
|
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
|
|
log_Printf(LogERROR, "route_IfDelete: sysctl: estimate: %s\n",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
|
|
sp = malloc(needed);
|
|
if (sp == NULL)
|
|
return;
|
|
|
|
if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
|
|
log_Printf(LogERROR, "route_IfDelete: sysctl: getroute: %s\n",
|
|
strerror(errno));
|
|
free(sp);
|
|
return;
|
|
}
|
|
ep = sp + needed;
|
|
|
|
for (pass = 0; pass < 2; pass++) {
|
|
/*
|
|
* We do 2 passes. The first deletes all cloned routes. The second
|
|
* deletes all non-cloned routes. This is necessary to avoid
|
|
* potential errors from trying to delete route X after route Y where
|
|
* route X was cloned from route Y (and is no longer there 'cos it
|
|
* may have gone with route Y).
|
|
*/
|
|
if (RTF_WASCLONED == 0 && pass == 0)
|
|
/* So we can't tell ! */
|
|
continue;
|
|
for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
|
|
rtm = (struct rt_msghdr *) cp;
|
|
sa = (struct sockaddr *) (rtm + 1);
|
|
log_Printf(LogDEBUG, "route_IfDelete: addrs: %x, Netif: %d (%s),"
|
|
" flags: %x, dst: %s ?\n", rtm->rtm_addrs, rtm->rtm_index,
|
|
Index2Nam(rtm->rtm_index), rtm->rtm_flags,
|
|
inet_ntoa(((struct sockaddr_in *) sa)->sin_addr));
|
|
if (rtm->rtm_addrs & RTA_DST && rtm->rtm_addrs & RTA_GATEWAY &&
|
|
rtm->rtm_index == bundle->iface->index &&
|
|
(all || (rtm->rtm_flags & RTF_GATEWAY))) {
|
|
sa_dst.s_addr = ((struct sockaddr_in *)sa)->sin_addr.s_addr;
|
|
sa = (struct sockaddr *)((char *)sa + sa->sa_len);
|
|
if (sa->sa_family == AF_INET || sa->sa_family == AF_LINK) {
|
|
if ((pass == 0 && (rtm->rtm_flags & RTF_WASCLONED)) ||
|
|
(pass == 1 && !(rtm->rtm_flags & RTF_WASCLONED))) {
|
|
log_Printf(LogDEBUG, "route_IfDelete: Remove it (pass %d)\n", pass);
|
|
bundle_SetRoute(bundle, RTM_DELETE, sa_dst, sa_none, sa_none, 0, 0);
|
|
} else
|
|
log_Printf(LogDEBUG, "route_IfDelete: Skip it (pass %d)\n", pass);
|
|
} else
|
|
log_Printf(LogDEBUG,
|
|
"route_IfDelete: Can't remove routes of %d family !\n",
|
|
sa->sa_family);
|
|
}
|
|
}
|
|
}
|
|
free(sp);
|
|
}
|
|
|
|
int
|
|
GetIfIndex(char *name)
|
|
{
|
|
int idx;
|
|
const char *got;
|
|
|
|
idx = 1;
|
|
while (strcmp(got = Index2Nam(idx), "???"))
|
|
if (!strcmp(got, name))
|
|
return idx;
|
|
else
|
|
idx++;
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
route_Change(struct bundle *bundle, struct sticky_route *r,
|
|
struct in_addr me, struct in_addr peer)
|
|
{
|
|
struct in_addr none, del;
|
|
|
|
none.s_addr = INADDR_ANY;
|
|
for (; r; r = r->next) {
|
|
if ((r->type & ROUTE_DSTMYADDR) && r->dst.s_addr != me.s_addr) {
|
|
del.s_addr = r->dst.s_addr & r->mask.s_addr;
|
|
bundle_SetRoute(bundle, RTM_DELETE, del, none, none, 1, 0);
|
|
r->dst = me;
|
|
if (r->type & ROUTE_GWHISADDR)
|
|
r->gw = peer;
|
|
} else if ((r->type & ROUTE_DSTHISADDR) && r->dst.s_addr != peer.s_addr) {
|
|
del.s_addr = r->dst.s_addr & r->mask.s_addr;
|
|
bundle_SetRoute(bundle, RTM_DELETE, del, none, none, 1, 0);
|
|
r->dst = peer;
|
|
if (r->type & ROUTE_GWHISADDR)
|
|
r->gw = peer;
|
|
} else if ((r->type & ROUTE_GWHISADDR) && r->gw.s_addr != peer.s_addr)
|
|
r->gw = peer;
|
|
bundle_SetRoute(bundle, RTM_ADD, r->dst, r->gw, r->mask, 1, 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
route_Clean(struct bundle *bundle, struct sticky_route *r)
|
|
{
|
|
struct in_addr none, del;
|
|
|
|
none.s_addr = INADDR_ANY;
|
|
for (; r; r = r->next) {
|
|
del.s_addr = r->dst.s_addr & r->mask.s_addr;
|
|
bundle_SetRoute(bundle, RTM_DELETE, del, none, none, 1, 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
route_Add(struct sticky_route **rp, int type, struct in_addr dst,
|
|
struct in_addr mask, struct in_addr gw)
|
|
{
|
|
if (type != ROUTE_STATIC) {
|
|
struct sticky_route *r;
|
|
int dsttype = type & ROUTE_DSTANY;
|
|
|
|
r = NULL;
|
|
while (*rp) {
|
|
if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) ||
|
|
(!dsttype && (*rp)->dst.s_addr == dst.s_addr)) {
|
|
r = *rp;
|
|
*rp = r->next;
|
|
} else
|
|
rp = &(*rp)->next;
|
|
}
|
|
|
|
if (!r)
|
|
r = (struct sticky_route *)malloc(sizeof(struct sticky_route));
|
|
r->type = type;
|
|
r->next = NULL;
|
|
r->dst = dst;
|
|
r->mask = mask;
|
|
r->gw = gw;
|
|
*rp = r;
|
|
}
|
|
}
|
|
|
|
void
|
|
route_Delete(struct sticky_route **rp, int type, struct in_addr dst)
|
|
{
|
|
struct sticky_route *r;
|
|
int dsttype = type & ROUTE_DSTANY;
|
|
|
|
for (; *rp; rp = &(*rp)->next) {
|
|
if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) ||
|
|
(!dsttype && dst.s_addr == ((*rp)->dst.s_addr & (*rp)->mask.s_addr))) {
|
|
r = *rp;
|
|
*rp = r->next;
|
|
free(r);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
route_DeleteAll(struct sticky_route **rp)
|
|
{
|
|
struct sticky_route *r, *rn;
|
|
|
|
for (r = *rp; r; r = rn) {
|
|
rn = r->next;
|
|
free(r);
|
|
}
|
|
*rp = NULL;
|
|
}
|
|
|
|
void
|
|
route_ShowSticky(struct prompt *p, struct sticky_route *r)
|
|
{
|
|
int def;
|
|
|
|
prompt_Printf(p, "Sticky routes:\n");
|
|
for (; r; r = r->next) {
|
|
def = r->dst.s_addr == INADDR_ANY && r->mask.s_addr == INADDR_ANY;
|
|
|
|
prompt_Printf(p, " add ");
|
|
if (r->type & ROUTE_DSTMYADDR)
|
|
prompt_Printf(p, "MYADDR");
|
|
else if (r->type & ROUTE_DSTHISADDR)
|
|
prompt_Printf(p, "HISADDR");
|
|
else if (!def)
|
|
prompt_Printf(p, "%s", inet_ntoa(r->dst));
|
|
|
|
if (def)
|
|
prompt_Printf(p, "default ");
|
|
else
|
|
prompt_Printf(p, " %s ", inet_ntoa(r->mask));
|
|
|
|
if (r->type & ROUTE_GWHISADDR)
|
|
prompt_Printf(p, "HISADDR\n");
|
|
else
|
|
prompt_Printf(p, "%s\n", inet_ntoa(r->gw));
|
|
}
|
|
}
|