freebsd-dev/usr.sbin/ppp/route.c
Brian Somers 8fa6ebe47d Solve the ``first connection'' problem that occurs on
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 :-)
1998-10-22 02:32:50 +00:00

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));
}
}