/* rrouted - userlevel request-route handler by Olaf Titz , Sept. 1998 public domain This daemon transparently replaces the request-route functionality that was removed from Linux 2.1. I.e. run this and on every IP packet with unreachable destination a script is called with the destination address as argument. Works as follows: a default route is installed via a SLIP device on which this daemon listens, and a packet sent over this device is queued and request-route invoked. After request-route has finished, the daemon tries to resend the packet. The program called from request-route has to establish its own default route (unlike diald). Needs SLIP support in the kernel (or as a module). Compile this with gcc -O3 -Wall -s -o rrouted rrouted.c -lpthread If you dont have pthreads, gcc -O3 -Wall -DNO_THREADS -s -o rrouted rrouted.c (Note that even with threads, only one request-route is invoked for each destination at once.) Usage: rrouted [-h host | -n net -m mask] [-d dummynet] [-s script] sets a route to the target "host" rsp. "net/mask" and invokes "script" when packets are routed via it. The default is "-n 0.0.0.0 -m 0.0.0.0 -s /sbin/request-route" which means that without any arguments, 2.0 kerneld behaviour is matched. "dummynet" is a netmask of unused addresses from which rrouted picks one for its SLIP device, default 10.255.255.0. Device and route are taken down when the daemon is killed. */ const char *RCSID="$Id: rrouted.c,v 1.3 1998/11/29 22:41:19 olaf Exp $"; #define _BSD_SOURCE #ifndef NO_THREADS #define _REENTRANT #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef unsigned char uchar; typedef int bool; #define true 1 #define false 0 #ifdef DEBUG #define dprintf(x) printf x #else #define dprintf(x) #endif #define MTU 1006 #ifndef LOGFAC #define LOGFAC LOG_DAEMON #endif int controlfd; /* Socket for ioctls */ int slipfd; /* File descriptor for the master pty */ char slipdev[16]; /* SLIP device name */ struct Q { /* Enqueued packet: */ struct Q *next; /* next element in list */ int len; /* packet length */ union { /* data */ struct ip iphdr; uchar buf[MTU]; } p; } *queue = NULL; /* Packet queue */ /* Parameters */ struct in_addr target; struct in_addr mask; struct in_addr dummynet; const char *script = "/sbin/request-route"; #ifdef NO_THREADS #define pthread_mutex_lock(x) #define pthread_mutex_unlock(x) #else /* Lock for the global queue structure */ pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; #endif void enq(struct Q *q) /* LOCK(qlock) */ { q->next=queue; queue=q; } void deq(struct Q *q) /* LOCK(qlock) */ { if (queue==q) { queue=queue->next; } else { struct Q *r=queue; while (r && r->next!=q) r=r->next; r->next=q->next; } } /* check if this is a valid IP packet */ bool ipcheckpkt(uchar *buf, int len) { struct ip *iph=(struct ip *)buf; unsigned short s=ntohs(iph->ip_sum), l=iph->ip_hl; unsigned long t; unsigned short *p; int i; uchar *c=alloca(4*l); if (!c) return false; if (ntohs(iph->ip_len)!=len) return false; memcpy(c, buf, 4*l); iph=(struct ip *)c; iph->ip_sum=0; for (p=(unsigned short *)c, i=0, t=0; i<2*l; ++p, ++i) t+=ntohs(*p); t=(~(t+(t>>16)))&0xFFFF; return (t==s); } /* routines like read/write to handle SLIP framing */ #define END 0300 #define ESC 0333 #define ESC_END 0334 #define ESC_ESC 0335 int slip_read(int fd, uchar *buf, int len) { int l=0; bool esc=false; uchar c; while (true) { if (read(fd, &c, 1)!=1) return -1; /* read error/trunc */ if (c==END) return l; if (c==ESC) { esc=true; } else { if (esc) { switch (c) { case ESC_END: c=END; break; case ESC_ESC: c=ESC; break; default: return -1; /* frame error */ } esc=false; } if (lp.iphdr.ip_dst), ntohs(q->p.iphdr.ip_id))); slip_write(slipfd, q->p.buf, q->len); deq(q); free(q); } /* Run request-route for a packet, then bounce it and its relatives. */ void *reqroute(void *arg) { struct Q *q=(struct Q *)arg; struct Q *r; int p, s; dprintf(("%s %s\n", script, inet_ntoa(q->p.iphdr.ip_dst))); while ((p=fork())<0) { syslog(LOG_WARNING, "fork: %m"); sleep(2); } if (!p) { execl(script, script, inet_ntoa(q->p.iphdr.ip_dst), NULL); exit(1); } waitpid(p, &s, 0); if (!WIFEXITED(s) || WEXITSTATUS(s)) syslog(LOG_WARNING, "child returned %d", s); pthread_mutex_lock(&qlock); bounce(q); pthread_mutex_unlock(&qlock); /* Bounce all packets with the same destination as this one */ do { pthread_mutex_lock(&qlock); for (r=queue; r; r=r->next) { if (r->p.iphdr.ip_dst.s_addr == q->p.iphdr.ip_dst.s_addr) { bounce(r); break; } } pthread_mutex_unlock(&qlock); } while (r); return NULL; } /* Receive one packet and act on it. q is "fresh" storage which is returned free iff return!=Hqueued. */ enum handled {Hignored, Hqueued, Hdup, Hframerr, Herror} pkthandler(struct Q *q) { struct Q *r; bool rr=true; if ((q->len=slip_read(slipfd, q->p.buf, MTU))<0) return Herror; if (q->len<=sizeof(struct ip)) return Hignored; /* empty */ if (!ipcheckpkt(q->p.buf, q->len)) return Hframerr; pthread_mutex_lock(&qlock); for (r=queue; r; r=r->next) { if (r->p.iphdr.ip_dst.s_addr == q->p.iphdr.ip_dst.s_addr) { if (r->p.iphdr.ip_id == q->p.iphdr.ip_id && r->p.iphdr.ip_off == q->p.iphdr.ip_off) { pthread_mutex_unlock(&qlock); return Hdup; } rr=false; /* already have a request-route thread for this dst */ } } dprintf(("queue %s %04x\n", inet_ntoa(q->p.iphdr.ip_dst), ntohs(q->p.iphdr.ip_id))); enq(q); pthread_mutex_unlock(&qlock); if (rr) { #ifdef NO_THREADS /* call request-route */ reqroute(q); #else /* call request-route in its own thread */ pthread_t t; pthread_attr_t a; pthread_attr_init(&a); pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); pthread_create(&t, &a, reqroute, q); #endif } return Hqueued; } /* Open a pty/tty pair, return the slave fd, leave the master fd in mfd */ int openpty(int *mfd) { static const char *maj="pqrstuvw"; static const char *min="0123456789abcdef"; const char *p, *q; int e; char b[16]; for (p=maj; *p; p++) { for (q=min; *q; q++) { sprintf(b, "/dev/pty%c%c", *p, *q); if ((*mfd=open(b, O_RDWR|O_NOCTTY))>=0) { b[5]='t'; dprintf(("Using %s\n", b)); if ((e=open(b, O_RDWR|O_NOCTTY))>=0) return e; syslog(LOG_WARNING, "open %s: %m", b); close(*mfd); } } } errno=EXFULL; /* abuse this */ return -1; } /* Find my primary address: address of first non-loopback device */ int getmyaddress(struct sockaddr_in *ap) { char b[512]; char *p, *q; struct ifreq r; FILE *f=fopen("/proc/net/dev", "r"); if (!f) return -1; while (true) { if (!fgets(b, sizeof(b), f)) { /* XXX If no device found, we create an address out of the dummy net mask. Relies on dummynet being set, internal structure of s_addr and other ugly things. */ unsigned char *x=(unsigned char *)&ap->sin_addr.s_addr; ap->sin_addr=dummynet; x[3]=254; fclose(f); return 0; } for (p=b; isspace(*p); ++p); for (q=p; *q && *q!=':'; ++q); if (*q) { *q='\0'; if (strcmp(p, "lo")) break; } } fclose(f); memset(&r, 0, sizeof(r)); strcpy(r.ifr_name, p); #define err(s) do{ syslog(LOG_ERR, "myaddress %s: %m", s); return -1; }while(0) if (ioctl(controlfd, SIOCGIFADDR, &r)<0) err("SIOCGIFADDR"); memcpy(ap, &r.ifr_addr, sizeof(*ap)); return 0; #undef err } /* Perform ifconfig/route for our slip device */ int slipup(const struct sockaddr_in *myaddr, int devnum) { struct ifreq r; struct sockaddr_in sa; struct rtentry rt; memset(&r, 0, sizeof(r)); memset(&sa, 0, sizeof(sa)); memset(&rt, 0, sizeof(rt)); sa.sin_family=AF_INET; strcpy(r.ifr_name, slipdev); #define err(s) do{ syslog(LOG_ERR, "slipup %s: %m", s); return -1; }while(0) memcpy(&r.ifr_addr, myaddr, sizeof(*myaddr)); if (ioctl(controlfd, SIOCSIFADDR, &r)<0) err("SIOCSIFADDR"); sa.sin_addr=dummynet; { /* horrible kluge to get an address from network number */ /* we know that s_addr is big-endian */ unsigned char *x=(unsigned char *)&sa.sin_addr.s_addr; x[3]=devnum+1; } memcpy(&r.ifr_addr, &sa, sizeof(sa)); if (ioctl(controlfd, SIOCSIFDSTADDR, &r)<0) err("SIOCSIFDSTADDR"); r.ifr_mtu=MTU; if (ioctl(controlfd, SIOCSIFMTU, &r)<0) err("SIOCSIFMTU"); #if 0 /* Interface metric was taken out in 2.1.121? */ r.ifr_metric=15; if (ioctl(controlfd, SIOCSIFMETRIC, &r)<0) err("SIOCSIFMETRIC"); #endif r.ifr_flags=IFF_UP|IFF_RUNNING|IFF_NOARP|IFF_POINTOPOINT; if (ioctl(controlfd, SIOCSIFFLAGS, &r)<0) err("SIOCSIFFLAGS"); #if 0 /* 2.1 sets device route by itself */ /* sa is left over from previous op */ memcpy(&rt.rt_dst, &sa, sizeof(sa)); rt.rt_flags=RTF_UP|RTF_HOST; rt.rt_metric=15; rt.rt_dev=slipdev; if (ioctl(controlfd, SIOCADDRT, &rt)<0) err("SIOCADDRT 1"); #endif /* sa is left over from previous op */ memcpy(&rt.rt_gateway, &sa, sizeof(sa)); sa.sin_addr=target; memcpy(&rt.rt_dst, &sa, sizeof(sa)); sa.sin_addr=mask; memcpy(&rt.rt_genmask, &sa, sizeof(sa)); rt.rt_metric=15; rt.rt_flags=RTF_UP|RTF_GATEWAY; if (ioctl(controlfd, SIOCADDRT, &rt)<0) err("SIOCADDRT 2"); return 0; #undef err } /* Take down the device */ int slipdown(void) { struct ifreq r; #define err(s) do{ syslog(LOG_ERR, "slipdown %s: %m", s); return -1; }while(0) strcpy(r.ifr_name, slipdev); r.ifr_flags=IFF_NOARP|IFF_POINTOPOINT; if (ioctl(controlfd, SIOCSIFFLAGS, &r)<0) err("SIOCSIFFLAGS"); return 0; #undef err } void sighandler(int s) { slipdown(); exit(0); } int lookup(const char *c, struct in_addr *ap, bool net) { struct hostent *h; struct netent *n; if (!inet_aton(c, ap)) { if (net) { if ((n=getnetbyname(c))) memcpy(&ap->s_addr, &n->n_net, sizeof(ap->s_addr)); else return -1; } else { if ((h=gethostbyname(c))) memcpy(&ap->s_addr, h->h_addr_list[0], sizeof(ap->s_addr)); else return -1; } } return 0; } void usage(const char *m, const char *n) { if (m) fprintf(stderr, "%s: %s\n", n, m); fprintf(stderr, "Usage: %s [-h host | -n net -m mask] [-d dummynet] [-s script]\n", n); exit(1); } extern char *optarg; int main(int argc, char *argv[]) { int s, i, j; struct Q *q=NULL; struct sockaddr_in myaddr; target.s_addr=htonl(INADDR_ANY); mask.s_addr=htonl(INADDR_ANY); dummynet.s_addr=htonl(0x0AFFFF00); while ((i=getopt(argc, argv, "h:n:m:d:s:"))!=EOF) { switch (i) { case 'h': if (!lookup(optarg, &target, false)<0) usage("Invalid host", argv[0]); mask.s_addr=htonl(0xFFFFFFFFUL); break; case 'n': if (!lookup(optarg, &target, true)<0) usage("Invalid net", argv[0]); break; case 'm': if (!inet_aton(optarg, &mask)) usage("Invalid mask", argv[0]); break; case 's': script=optarg; break; default: usage(NULL, argv[0]); } } #ifdef DEBUG openlog("rrouted", LOG_PERROR|LOG_NDELAY, LOGFAC); #else openlog("rrouted", 0, LOGFAC); #endif if ((controlfd=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))<0) { syslog(LOG_ERR, "socket: %m"); exit(1); } if (getmyaddress(&myaddr)<0) { syslog(LOG_ERR, "getmyaddress: %m"); exit(1); } if ((s=openpty(&slipfd))<0) { syslog(LOG_ERR, "openpty: %m"); exit(1); } i=N_SLIP; if ((j=ioctl(s, TIOCSETD, &i))<0) { syslog(LOG_ERR, "TIOCSETD: %m"); exit(1); } sprintf(slipdev, "sl%d", j); i=0; if (ioctl(s, SIOCSIFENCAP, &i)<0) { syslog(LOG_ERR, "SIOCSIFENCAP: %m"); } #ifndef DEBUG /* daemonize */ if ((i=fork())<0) { syslog(LOG_ERR, "fork: %m"); exit(1); } if (i) exit(0); setsid(); #endif signal(SIGHUP, sighandler); signal(SIGINT, sighandler); signal(SIGTERM, sighandler); if (slipup(&myaddr, j)<0) exit(1); while (true) { if (!q && !(q=malloc(sizeof(struct Q)))) { syslog(LOG_ERR, "out of memory"); slipdown(); exit(1); } switch(pkthandler(q)) { case Hdup: syslog(LOG_NOTICE, "dup packet"); /* fall thru */ case Hignored: break; case Hqueued: q=NULL; break; /* was queued, alloc a new one */ case Hframerr: syslog(LOG_NOTICE, "frame error"); break; case Herror: syslog(LOG_ERR, "pkthandler: %m"); slipdown(); exit(1); } } return 0; }