blob: 1bdde0dd2d23ad1e6bbe4ba195951b2df7d9eca1 [file] [log] [blame]
/*
* 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
*
* 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;
/* 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, *x, *p;
unsigned long flags;
DPRINTF((DBG_RT, "RT: flushing for dst %s\n", in_ntoa(dst)));
if ((r = rt_base) == NULL) return;
save_flags(flags);
cli();
p = NULL;
while(r != NULL) {
if (r->rt_dst == dst) {
if (p == NULL) rt_base = r->rt_next;
else p->rt_next = r->rt_next;
x = r->rt_next;
kfree_s(r, sizeof(struct rtable));
r = x;
} else {
p = r;
r = r->rt_next;
}
}
restore_flags(flags);
}
/* Remove all routing table entries for a device. */
void
rt_flush(struct device *dev)
{
struct rtable *r, *x, *p;
unsigned long flags;
DPRINTF((DBG_RT, "RT: flushing for dev 0x%08lx (%s)\n", (long)dev, dev->name));
if ((r = rt_base) == NULL) return;
cli();
save_flags(flags);
p = NULL;
while(r != NULL) {
if (r->rt_dev == dev) {
if (p == NULL) rt_base = r->rt_next;
else p->rt_next = r->rt_next;
x = r->rt_next;
kfree_s(r, sizeof(struct rtable));
r = x;
} else {
p = r;
r = r->rt_next;
}
}
restore_flags(flags);
}
void
rt_add(short flags, unsigned long dst, unsigned long gw, struct device *dev)
{
struct rtable *r, *r1;
struct rtable *rt;
int mask;
unsigned long cpuflags;
/* 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;
}
/* Fill in the fields. */
memset(rt, 0, sizeof(struct rtable));
rt->rt_flags = (flags | RTF_UP);
if (gw != 0) rt->rt_flags |= RTF_GATEWAY;
rt->rt_dev = dev;
rt->rt_gateway = gw;
/*
* If this is coming from an ICMP redirect message, truncate
* the TARGET if we are creating an entry for a NETWORK. Use
* an Internet class C network mask. Yuck :-(
*/
if (flags & RTF_DYNAMIC) {
if (flags & RTF_HOST)
rt->rt_dst = dst;
else{
rt->rt_dst = (dst & dev->pa_mask);
/* We don't want new routes to our own net*/
if(rt->rt_dst == (dev->pa_addr & dev->pa_mask)){
kfree_s(rt, sizeof(struct rtable));
/*printk("Dynamic route to my own net rejected\n");*/
return;
}
}
} else rt->rt_dst = dst;
rt_print(rt);
if (rt_base == NULL) {
rt->rt_next = NULL;
rt_base = rt;
return;
}
/*
* What we have to do is loop though this until we have
* found the first address which has the same generality
* as the one in rt. Then we can put rt in after it.
*/
for (mask = 0xff000000L; mask != 0xffffffffL; mask = (mask >> 8) | mask) {
if (mask & dst) {
mask = mask << 8;
break;
}
}
DPRINTF((DBG_RT, "RT: mask = %X\n", mask));
save_flags(cpuflags);
cli();
r1 = rt_base;
/* See if we are getting a duplicate. */
for (r = rt_base; r != NULL; r = r->rt_next) {
if (r->rt_dst == dst) {
if (r == rt_base) {
rt->rt_next = r->rt_next;
rt_base = rt;
} else {
rt->rt_next = r->rt_next;
r1->rt_next = rt;
}
kfree_s(r, sizeof(struct rtable));
restore_flags(cpuflags);
return;
}
r1 = r;
}
r1 = rt_base;
for (r = rt_base; r != NULL; r = r->rt_next) {
if (! (r->rt_dst & mask)) {
DPRINTF((DBG_RT, "RT: adding before r=%X\n", r));
rt_print(r);
if (r == rt_base) {
rt->rt_next = rt_base;
rt_base = rt;
restore_flags(cpuflags);
return;
}
rt->rt_next = r;
r1->rt_next = rt;
restore_flags(cpuflags);
return;
}
r1 = r;
}
DPRINTF((DBG_RT, "RT: adding after r1=%X\n", r1));
rt_print(r1);
/* Goes at the end. */
rt->rt_next = NULL;
r1->rt_next = rt;
}
static int
rt_new(struct rtentry *r)
{
struct device *dev;
struct rtable *rt;
if ((r->rt_dst.sa_family != AF_INET) ||
(r->rt_gateway.sa_family != AF_INET)) {
DPRINTF((DBG_RT, "RT: We only know about AF_INET !\n"));
return(-EAFNOSUPPORT);
}
/*
* I admit that the following bits of code were "inspired" by
* the Berkeley UNIX system source code. I could think of no
* other way to find out how to make it compatible with it (I
* want this to be compatible to get "routed" up and running).
* -FvK
*/
/* If we have a 'gateway' route here, check the correct address. */
if (!(r->rt_flags & RTF_GATEWAY))
dev = dev_check(((struct sockaddr_in *) &r->rt_dst)->sin_addr.s_addr);
else
if ((rt = rt_route(((struct sockaddr_in *) &r->rt_gateway)->sin_addr.
s_addr,NULL)))
dev = rt->rt_dev;
else
dev = NULL;
DPRINTF((DBG_RT, "RT: dev for %s gw ",
in_ntoa((*(struct sockaddr_in *)&r->rt_dst).sin_addr.s_addr)));
DPRINTF((DBG_RT, "%s (0x%04X) is 0x%X (%s)\n",
in_ntoa((*(struct sockaddr_in *)&r->rt_gateway).sin_addr.s_addr),
r->rt_flags, dev, (dev == NULL) ? "NONE" : dev->name));
if (dev == NULL) return(-ENETUNREACH);
rt_add(r->rt_flags, (*(struct sockaddr_in *) &r->rt_dst).sin_addr.s_addr,
(*(struct sockaddr_in *) &r->rt_gateway).sin_addr.s_addr, 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\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\n",
r->rt_dev->name, r->rt_dst, r->rt_gateway,
r->rt_flags, r->rt_refcnt, r->rt_use, r->rt_metric);
}
return(pos - buffer);
}
struct rtable *
rt_route(unsigned long daddr, struct options *opt)
{
struct rtable *rt;
/*
* This is a hack, I think. -FvK
*/
if (chk_addr(daddr) == IS_MYADDR) daddr = my_addr();
/*
* Loop over the IP routing table to find a route suitable
* for this packet. Note that we really should have a look
* at the IP options to see if we have been given a hint as
* to what kind of path we should use... -FvK
*/
for (rt = rt_base; rt != NULL; rt = rt->rt_next)
if ((rt->rt_flags & RTF_HOST) && rt->rt_dst == daddr) {
DPRINTF((DBG_RT, "%s (%s)\n",
rt->rt_dev->name, in_ntoa(rt->rt_gateway)));
rt->rt_use++;
return(rt);
}
for (rt = rt_base; rt != NULL; rt = rt->rt_next) {
DPRINTF((DBG_RT, "RT: %s via ", in_ntoa(daddr)));
if (!(rt->rt_flags & RTF_HOST) && ip_addr_match(rt->rt_dst, daddr)) {
DPRINTF((DBG_RT, "%s (%s)\n",
rt->rt_dev->name, in_ntoa(rt->rt_gateway)));
rt->rt_use++;
return(rt);
}
if ((rt->rt_dev->flags & IFF_BROADCAST) &&
ip_addr_match(rt->rt_dev->pa_brdaddr, daddr)) {
DPRINTF((DBG_RT, "%s (BCAST %s)\n",
rt->rt_dev->name, in_ntoa(rt->rt_dev->pa_brdaddr)));
rt->rt_use++;
return(rt);
}
}
DPRINTF((DBG_RT, "NONE\n"));
return(NULL);
};
int
rt_ioctl(unsigned int cmd, void *arg)
{
struct device *dev;
struct rtentry rt;
char namebuf[32];
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 (rt.rt_dev) {
err=verify_area(VERIFY_READ, rt.rt_dev, sizeof namebuf);
if(err)
return err;
memcpy_fromfs(&namebuf, rt.rt_dev, sizeof namebuf);
dev = dev_get(namebuf);
rt.rt_dev = dev;
}
ret = (cmd == SIOCDELRT) ? rt_kill(&rt) : rt_new(&rt);
break;
default:
ret = -EINVAL;
}
return(ret);
}