/* * 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> * * 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%02lx\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=%ld\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; DPRINTF((DBG_RT, "RT: flushing for dst %s\n", in_ntoa(dst))); if ((r = rt_base) == NULL) return; 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; } } } /* Remove all routing table entries for a device. */ void rt_flush(struct device *dev) { struct rtable *r, *x, *p; DPRINTF((DBG_RT, "RT: flushing for dev 0x%08lx (%s)\n", (long)dev, dev->name)); if ((r = rt_base) == NULL) return; 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; } } } void rt_add(short flags, unsigned long dst, unsigned long gw, struct device *dev) { struct rtable *r, *r1; struct rtable *rt; int mask; /* 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 & htonl(IN_CLASSC_NET)); } 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 = 0xff000000; mask != 0xffffffff; mask = (mask >> 8) | mask) { if (mask & dst) { mask = mask << 8; break; } } DPRINTF((DBG_RT, "RT: mask = %X\n", mask)); 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)); return; } 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; return; } rt->rt_next = r; r1->rt_next = rt; 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\n"); for (r = rt_base; r != NULL; r = r->rt_next) { pos += sprintf(pos, "%s\t%08X\t%08X\t%02X\t%d\t%d\n", r->rt_dev->name, r->rt_dst, r->rt_gateway, r->rt_flags, r->rt_refcnt, r->rt_use); } 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 rtentry rt; int ret; switch(cmd) { case DDIOCSDBG: ret = dbg_ioctl(arg, DBG_RT); break; case SIOCADDRT: if (!suser()) return(-EPERM); verify_area(VERIFY_WRITE, arg, sizeof(struct rtentry)); memcpy_fromfs(&rt, arg, sizeof(struct rtentry)); ret = rt_new(&rt); break; case SIOCDELRT: if (!suser()) return(-EPERM); verify_area(VERIFY_WRITE, arg, sizeof(struct rtentry)); memcpy_fromfs(&rt, arg, sizeof(struct rtentry)); ret = rt_kill(&rt); break; default: ret = -EINVAL; } return(ret); }