384 lines
8.5 KiB
C
384 lines
8.5 KiB
C
/*
|
|
* INET An implementation of the TCP/IP protocol suite for the LINUX
|
|
* operating system. INET is implemented using the BSD Socket
|
|
* interface as the means of communication with the user level.
|
|
*
|
|
* ROUTE - implementation of the IP router.
|
|
*
|
|
* Version: @(#)route.c 1.0.14 05/31/93
|
|
*
|
|
* Authors: Ross Biro, <bir7@leland.Stanford.Edu>
|
|
* Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
|
|
*
|
|
* Fixes:
|
|
* Alan Cox : Verify area fixes.
|
|
* Alan Cox : cli() protects routing changes
|
|
* Rui Oliveira : ICMP routing table updates
|
|
* (rco@di.uminho.pt) Routing table insertion and update
|
|
* Linus Torvalds : Rewrote bits to be sensible
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
#include <asm/segment.h>
|
|
#include <asm/system.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/string.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/sockios.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/in.h>
|
|
#include "inet.h"
|
|
#include "dev.h"
|
|
#include "ip.h"
|
|
#include "protocol.h"
|
|
#include "route.h"
|
|
#include "tcp.h"
|
|
#include "skbuff.h"
|
|
#include "sock.h"
|
|
#include "arp.h"
|
|
#include "icmp.h"
|
|
|
|
|
|
static struct rtable *rt_base = NULL;
|
|
static struct rtable *rt_loopback = NULL;
|
|
|
|
/* Dump the contents of a routing table entry. */
|
|
static void
|
|
rt_print(struct rtable *rt)
|
|
{
|
|
if (rt == NULL || inet_debug != DBG_RT) return;
|
|
|
|
printk("RT: %06lx NXT=%06lx FLAGS=0x%02x\n",
|
|
(long) rt, (long) rt->rt_next, rt->rt_flags);
|
|
printk(" TARGET=%s ", in_ntoa(rt->rt_dst));
|
|
printk("GW=%s ", in_ntoa(rt->rt_gateway));
|
|
printk(" DEV=%s USE=%ld REF=%d\n",
|
|
(rt->rt_dev == NULL) ? "NONE" : rt->rt_dev->name,
|
|
rt->rt_use, rt->rt_refcnt);
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove a routing table entry.
|
|
*/
|
|
static void rt_del(unsigned long dst)
|
|
{
|
|
struct rtable *r, **rp;
|
|
unsigned long flags;
|
|
|
|
DPRINTF((DBG_RT, "RT: flushing for dst %s\n", in_ntoa(dst)));
|
|
rp = &rt_base;
|
|
save_flags(flags);
|
|
cli();
|
|
while((r = *rp) != NULL) {
|
|
if (r->rt_dst != dst) {
|
|
rp = &r->rt_next;
|
|
continue;
|
|
}
|
|
*rp = r->rt_next;
|
|
if (rt_loopback == r)
|
|
rt_loopback = NULL;
|
|
kfree_s(r, sizeof(struct rtable));
|
|
}
|
|
restore_flags(flags);
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove all routing table entries for a device.
|
|
*/
|
|
void rt_flush(struct device *dev)
|
|
{
|
|
struct rtable *r;
|
|
struct rtable **rp;
|
|
unsigned long flags;
|
|
|
|
DPRINTF((DBG_RT, "RT: flushing for dev 0x%08lx (%s)\n", (long)dev, dev->name));
|
|
rp = &rt_base;
|
|
cli();
|
|
save_flags(flags);
|
|
while ((r = *rp) != NULL) {
|
|
if (r->rt_dev != dev) {
|
|
rp = &r->rt_next;
|
|
continue;
|
|
}
|
|
*rp = r->rt_next;
|
|
if (rt_loopback == r)
|
|
rt_loopback = NULL;
|
|
kfree_s(r, sizeof(struct rtable));
|
|
}
|
|
restore_flags(flags);
|
|
}
|
|
|
|
/*
|
|
* Used by 'rt_add()' when we can't get the netmask any other way..
|
|
*
|
|
* If the lower byte or two are zero, we guess the mask based on the
|
|
* number of zero 8-bit net numbers, otherwise we use the "default"
|
|
* masks judging by the destination address and our device netmask.
|
|
*/
|
|
static inline unsigned long default_mask(unsigned long dst)
|
|
{
|
|
dst = ntohl(dst);
|
|
if (IN_CLASSA(dst))
|
|
return htonl(IN_CLASSA_NET);
|
|
if (IN_CLASSB(dst))
|
|
return htonl(IN_CLASSB_NET);
|
|
return htonl(IN_CLASSC_NET);
|
|
}
|
|
|
|
static unsigned long guess_mask(unsigned long dst, struct device * dev)
|
|
{
|
|
unsigned long mask;
|
|
|
|
if (!dst)
|
|
return 0;
|
|
mask = default_mask(dst);
|
|
if ((dst ^ dev->pa_addr) & mask)
|
|
return mask;
|
|
return dev->pa_mask;
|
|
}
|
|
|
|
static inline struct device * get_gw_dev(unsigned long gw)
|
|
{
|
|
struct rtable * rt;
|
|
|
|
for (rt = rt_base ; ; rt = rt->rt_next) {
|
|
if (!rt)
|
|
return NULL;
|
|
if ((gw ^ rt->rt_dst) & rt->rt_mask)
|
|
continue;
|
|
/* gateways behind gateways are a no-no */
|
|
if (rt->rt_flags & RTF_GATEWAY)
|
|
return NULL;
|
|
return rt->rt_dev;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rewrote rt_add(), as the old one was weird. Linus
|
|
*/
|
|
void rt_add(short flags, unsigned long dst, unsigned long mask,
|
|
unsigned long gw, struct device *dev)
|
|
{
|
|
struct rtable *r, *rt;
|
|
struct rtable **rp;
|
|
unsigned long cpuflags;
|
|
|
|
if (flags & RTF_HOST) {
|
|
mask = 0xffffffff;
|
|
} else if (!mask) {
|
|
if (!((dst ^ dev->pa_addr) & dev->pa_mask)) {
|
|
mask = dev->pa_mask;
|
|
flags &= ~RTF_GATEWAY;
|
|
if (flags & RTF_DYNAMIC) {
|
|
/*printk("Dynamic route to my own net rejected\n");*/
|
|
return;
|
|
}
|
|
} else
|
|
mask = guess_mask(dst, dev);
|
|
dst &= mask;
|
|
}
|
|
if (gw == dev->pa_addr)
|
|
flags &= ~RTF_GATEWAY;
|
|
if (flags & RTF_GATEWAY) {
|
|
/* don't try to add a gateway we can't reach.. */
|
|
if (dev != get_gw_dev(gw))
|
|
return;
|
|
flags |= RTF_GATEWAY;
|
|
} else
|
|
gw = 0;
|
|
/* Allocate an entry. */
|
|
rt = (struct rtable *) kmalloc(sizeof(struct rtable), GFP_ATOMIC);
|
|
if (rt == NULL) {
|
|
DPRINTF((DBG_RT, "RT: no memory for new route!\n"));
|
|
return;
|
|
}
|
|
memset(rt, 0, sizeof(struct rtable));
|
|
rt->rt_flags = flags | RTF_UP;
|
|
rt->rt_dst = dst;
|
|
rt->rt_dev = dev;
|
|
rt->rt_gateway = gw;
|
|
rt->rt_mask = mask;
|
|
rt->rt_mtu = dev->mtu;
|
|
rt_print(rt);
|
|
/*
|
|
* What we have to do is loop though this until we have
|
|
* found the first address which has a higher generality than
|
|
* the one in rt. Then we can put rt in right before it.
|
|
*/
|
|
save_flags(cpuflags);
|
|
cli();
|
|
/* remove old route if we are getting a duplicate. */
|
|
rp = &rt_base;
|
|
while ((r = *rp) != NULL) {
|
|
if (r->rt_dst != dst) {
|
|
rp = &r->rt_next;
|
|
continue;
|
|
}
|
|
*rp = r->rt_next;
|
|
if (rt_loopback == r)
|
|
rt_loopback = NULL;
|
|
kfree_s(r, sizeof(struct rtable));
|
|
}
|
|
/* add the new route */
|
|
rp = &rt_base;
|
|
while ((r = *rp) != NULL) {
|
|
if ((r->rt_mask & mask) != mask)
|
|
break;
|
|
rp = &r->rt_next;
|
|
}
|
|
rt->rt_next = r;
|
|
*rp = rt;
|
|
if (rt->rt_dev->flags & IFF_LOOPBACK)
|
|
rt_loopback = rt;
|
|
restore_flags(cpuflags);
|
|
return;
|
|
}
|
|
|
|
static inline int bad_mask(unsigned long mask, unsigned long addr)
|
|
{
|
|
if (addr & (mask = ~mask))
|
|
return 1;
|
|
mask = ntohl(mask);
|
|
if (mask & (mask+1))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int rt_new(struct rtentry *r)
|
|
{
|
|
struct device *dev;
|
|
unsigned long flags, daddr, mask, gw;
|
|
|
|
if (r->rt_dst.sa_family != AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
|
|
flags = r->rt_flags;
|
|
daddr = ((struct sockaddr_in *) &r->rt_dst)->sin_addr.s_addr;
|
|
mask = r->rt_genmask;
|
|
gw = ((struct sockaddr_in *) &r->rt_gateway)->sin_addr.s_addr;
|
|
dev = (struct device *) r->rt_dev;
|
|
|
|
if (flags & RTF_GATEWAY) {
|
|
if (r->rt_gateway.sa_family != AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
if (!dev)
|
|
dev = get_gw_dev(gw);
|
|
} else if (!dev)
|
|
dev = dev_check(daddr);
|
|
|
|
if (dev == NULL)
|
|
return -ENETUNREACH;
|
|
|
|
if (bad_mask(mask, daddr))
|
|
mask = 0;
|
|
|
|
rt_add(flags, daddr, mask, gw, dev);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int rt_kill(struct rtentry *r)
|
|
{
|
|
struct sockaddr_in *trg;
|
|
|
|
trg = (struct sockaddr_in *) &r->rt_dst;
|
|
rt_del(trg->sin_addr.s_addr);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Called from the PROCfs module. */
|
|
int
|
|
rt_get_info(char *buffer)
|
|
{
|
|
struct rtable *r;
|
|
char *pos;
|
|
|
|
pos = buffer;
|
|
|
|
pos += sprintf(pos,
|
|
"Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\n");
|
|
|
|
/* This isn't quite right -- r->rt_dst is a struct! */
|
|
for (r = rt_base; r != NULL; r = r->rt_next) {
|
|
pos += sprintf(pos, "%s\t%08lX\t%08lX\t%02X\t%d\t%lu\t%d\t%08lX\n",
|
|
r->rt_dev->name, r->rt_dst, r->rt_gateway,
|
|
r->rt_flags, r->rt_refcnt, r->rt_use, r->rt_metric,
|
|
r->rt_mask);
|
|
}
|
|
return(pos - buffer);
|
|
}
|
|
|
|
/*
|
|
* This is hackish, but results in better code. Use "-S" to see why.
|
|
*/
|
|
#define early_out ({ goto no_route; 1; })
|
|
|
|
struct rtable * rt_route(unsigned long daddr, struct options *opt)
|
|
{
|
|
struct rtable *rt;
|
|
|
|
for (rt = rt_base; rt != NULL || early_out ; rt = rt->rt_next) {
|
|
if (!((rt->rt_dst ^ daddr) & rt->rt_mask))
|
|
break;
|
|
/* broadcast addresses can be special cases.. */
|
|
if ((rt->rt_dev->flags & IFF_BROADCAST) &&
|
|
rt->rt_dev->pa_brdaddr == daddr)
|
|
break;
|
|
}
|
|
if (daddr == rt->rt_dev->pa_addr) {
|
|
if ((rt = rt_loopback) == NULL)
|
|
goto no_route;
|
|
}
|
|
rt->rt_use++;
|
|
return rt;
|
|
no_route:
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int rt_ioctl(unsigned int cmd, void *arg)
|
|
{
|
|
struct device *dev;
|
|
struct rtentry rt;
|
|
char *devname;
|
|
int ret;
|
|
int err;
|
|
|
|
switch(cmd) {
|
|
case DDIOCSDBG:
|
|
ret = dbg_ioctl(arg, DBG_RT);
|
|
break;
|
|
case SIOCADDRT:
|
|
case SIOCDELRT:
|
|
if (!suser())
|
|
return -EPERM;
|
|
err=verify_area(VERIFY_READ, arg, sizeof(struct rtentry));
|
|
if (err)
|
|
return err;
|
|
memcpy_fromfs(&rt, arg, sizeof(struct rtentry));
|
|
if ((devname = (char *) rt.rt_dev) != NULL) {
|
|
err = getname(devname, &devname);
|
|
if (err)
|
|
return err;
|
|
dev = dev_get(devname);
|
|
putname(devname);
|
|
if (!dev)
|
|
return -EINVAL;
|
|
rt.rt_dev = dev;
|
|
}
|
|
ret = (cmd == SIOCDELRT) ? rt_kill(&rt) : rt_new(&rt);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|