home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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)
- {
- int err;
- char * devname;
- struct device * dev = NULL;
- unsigned long flags, daddr, mask, gw;
-
- if ((devname = r->rt_dev) != NULL) {
- err = getname(devname, &devname);
- if (err)
- return err;
- dev = dev_get(devname);
- putname(devname);
- if (!dev)
- return -EINVAL;
- }
-
- 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 = ((struct sockaddr_in *) &r->rt_genmask)->sin_addr.s_addr;
- gw = ((struct sockaddr_in *) &r->rt_gateway)->sin_addr.s_addr;
-
- /* BSD emulation: Permits route add someroute gw one-of-my-addresses
- to indicate which iface. Not as clean as the nice Linux dev technique
- but people keep using it... */
- if (!dev && (flags & RTF_GATEWAY)) {
- struct device *dev2;
- for (dev2 = dev_base ; dev2 != NULL ; dev2 = dev2->next) {
- if ((dev2->flags & IFF_UP) && dev2->pa_addr == gw) {
- flags &= ~RTF_GATEWAY;
- dev = dev2;
- break;
- }
- }
- }
-
- if (bad_mask(mask, daddr))
- mask = 0;
-
- if (flags & RTF_HOST)
- mask = 0xffffffff;
- else if (mask && r->rt_genmask.sa_family != AF_INET)
- return -EAFNOSUPPORT;
-
- 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;
-
- 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;
- }
-
- static int get_old_rtent(struct old_rtentry * src, struct rtentry * rt)
- {
- int err;
- struct old_rtentry tmp;
-
- err=verify_area(VERIFY_READ, src, sizeof(*src));
- if (err)
- return err;
- memcpy_fromfs(&tmp, src, sizeof(*src));
- memset(rt, 0, sizeof(*rt));
- rt->rt_dst = tmp.rt_dst;
- rt->rt_gateway = tmp.rt_gateway;
- rt->rt_genmask.sa_family = AF_INET;
- ((struct sockaddr_in *) &rt->rt_genmask)->sin_addr.s_addr = tmp.rt_genmask;
- rt->rt_flags = tmp.rt_flags;
- rt->rt_dev = tmp.rt_dev;
- return 0;
- }
-
- int rt_ioctl(unsigned int cmd, void *arg)
- {
- int err;
- struct rtentry rt;
-
- switch(cmd) {
- case DDIOCSDBG:
- return dbg_ioctl(arg, DBG_RT);
- case SIOCADDRTOLD:
- case SIOCDELRTOLD:
- if (!suser())
- return -EPERM;
- err = get_old_rtent((struct old_rtentry *) arg, &rt);
- if (err)
- return err;
- return (cmd == SIOCDELRTOLD) ? rt_kill(&rt) : rt_new(&rt);
- 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));
- return (cmd == SIOCDELRT) ? rt_kill(&rt) : rt_new(&rt);
- }
-
- return -EINVAL;
- }
-