diff options
author | Anas Nashif <anas.nashif@intel.com> | 2012-12-05 02:05:02 -0800 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2012-12-05 02:05:02 -0800 |
commit | c098f623bf888728e262e7813ffcdfe02df74937 (patch) | |
tree | 6188990bce1cee38fc0fd44ff2c294b877118887 /rdisc.c | |
download | iputils-c098f623bf888728e262e7813ffcdfe02df74937.tar.gz iputils-c098f623bf888728e262e7813ffcdfe02df74937.tar.bz2 iputils-c098f623bf888728e262e7813ffcdfe02df74937.zip |
Imported Upstream version 20121126upstream/s20121126upstream/20121126upstream
Diffstat (limited to 'rdisc.c')
-rw-r--r-- | rdisc.c | 1528 |
1 files changed, 1528 insertions, 0 deletions
@@ -0,0 +1,1528 @@ +/* + * Rdisc (this program) was developed by Sun Microsystems, Inc. and is + * provided for unrestricted use provided that this legend is included on + * all tape media and as a part of the software program in whole or part. + * Users may copy or modify Rdisc without charge, and they may freely + * distribute it. + * + * RDISC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Rdisc is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY RDISC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +#include <stdio.h> +#include <errno.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/time.h> +/* Do not use "improved" glibc version! */ +#include <linux/limits.h> + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/file.h> +#include <malloc.h> + +#include <sys/ioctl.h> +#include <linux/if.h> +#include <linux/route.h> + +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> + +/* + * The next include contains all defs and structures for multicast + * that are not in SunOS 4.1.x. On a SunOS 4.1.x system none of this code + * is ever used because it does not support multicast + * Fraser Gardiner - Sun Microsystems Australia + */ + +#include <netdb.h> +#include <arpa/inet.h> + +#include <string.h> +#include <syslog.h> + +#include "SNAPSHOT.h" + +struct interface +{ + struct in_addr address; /* Used to identify the interface */ + struct in_addr localaddr; /* Actual address if the interface */ + int preference; + int flags; + struct in_addr bcastaddr; + struct in_addr remoteaddr; + struct in_addr netmask; + int ifindex; + char name[IFNAMSIZ]; +}; + +/* + * TBD + * Use 255.255.255.255 for broadcasts - not the interface broadcast + * address. + */ + +#define ALLIGN(ptr) (ptr) + +static int join(int sock, struct sockaddr_in *sin); +static void solicitor(struct sockaddr_in *); +#ifdef RDISC_SERVER +static void advertise(struct sockaddr_in *, int lft); +#endif +static char *pr_name(struct in_addr addr); +static void pr_pack(char *buf, int cc, struct sockaddr_in *from); +static void age_table(int time); +static void record_router(struct in_addr router, int preference, int ttl); +static void add_route(struct in_addr addr); +static void del_route(struct in_addr addr); +static void rtioctl(struct in_addr addr, int op); +static int support_multicast(void); +static int sendbcast(int s, char *packet, int packetlen); +static int sendmcast(int s, char *packet, int packetlen, struct sockaddr_in *); +static int sendbcastif(int s, char *packet, int packetlen, struct interface *ifp); +static int sendmcastif(int s, char *packet, int packetlen, struct sockaddr_in *sin, struct interface *ifp); +static int is_directly_connected(struct in_addr in); +static void initlog(void); +static void discard_table(void); +static void init(void); + +#define ICMP_ROUTER_ADVERTISEMENT 9 +#define ICMP_ROUTER_SOLICITATION 10 + +#define ALL_HOSTS_ADDRESS "224.0.0.1" +#define ALL_ROUTERS_ADDRESS "224.0.0.2" + +#define MAXIFS 32 + +#if !defined(__GLIBC__) || __GLIBC__ < 2 +/* For router advertisement */ +struct icmp_ra +{ + u_char icmp_type; /* type of message, see below */ + u_char icmp_code; /* type sub code */ + u_short icmp_cksum; /* ones complement cksum of struct */ + u_char icmp_num_addrs; + u_char icmp_wpa; /* Words per address */ + short icmp_lifetime; +}; + +struct icmp_ra_addr +{ + __u32 ira_addr; + __u32 ira_preference; +}; +#else +#define icmp_ra icmp +#endif + +/* Router constants */ +#define MAX_INITIAL_ADVERT_INTERVAL 16 +#define MAX_INITIAL_ADVERTISEMENTS 3 +#define MAX_RESPONSE_DELAY 2 /* Not used */ + +/* Host constants */ +#define MAX_SOLICITATIONS 3 +#define SOLICITATION_INTERVAL 3 +#define MAX_SOLICITATION_DELAY 1 /* Not used */ + +#define INELIGIBLE_PREF 0x80000000 /* Maximum negative */ + +#define MAX_ADV_INT 600 + +/* Statics */ +static int num_interfaces; + +static struct interface *interfaces; +static int interfaces_size; /* Number of elements in interfaces */ + + +#define MAXPACKET 4096 /* max packet size */ + +/* fraser */ +int debugfile; + +const char usage[] = +"Usage: rdisc [-b] [-d] [-s] [-v] [-f] [-a] [-V] [send_address] [receive_address]\n" +#ifdef RDISC_SERVER +" rdisc -r [-b] [-d] [-s] [-v] [-f] [-a] [-V] [-p <preference>] [-T <secs>]\n" +" [send_address] [receive_address]\n" +#endif +; + + +int s; /* Socket file descriptor */ +struct sockaddr_in whereto;/* Address to send to */ + +/* Common variables */ +int verbose = 0; +int debug = 0; +int trace = 0; +int solicit = 0; +int ntransmitted = 0; +int nreceived = 0; +int forever = 0; /* Never give up on host. If 0 defer fork until + * first response. + */ + +#ifdef RDISC_SERVER +/* Router variables */ +int responder; +int max_adv_int = MAX_ADV_INT; +int min_adv_int; +int lifetime; +int initial_advert_interval = MAX_INITIAL_ADVERT_INTERVAL; +int initial_advertisements = MAX_INITIAL_ADVERTISEMENTS; +int preference = 0; /* Setable with -p option */ +#endif + +/* Host variables */ +int max_solicitations = MAX_SOLICITATIONS; +unsigned int solicitation_interval = SOLICITATION_INTERVAL; +int best_preference = 1; /* Set to record only the router(s) with the + best preference in the kernel. Not set + puts all routes in the kernel. */ + + +static void graceful_finish(void); +static void finish(void); +static void timer(void); +static void initifs(void); +static u_short in_cksum(u_short *addr, int len); + +static int logging = 0; + +#define logerr(fmt...) ({ if (logging) syslog(LOG_ERR, fmt); \ + else fprintf(stderr, fmt); }) +#define logtrace(fmt...) ({ if (logging) syslog(LOG_INFO, fmt); \ + else fprintf(stderr, fmt); }) +#define logdebug(fmt...) ({ if (logging) syslog(LOG_DEBUG, fmt); \ + else fprintf(stderr, fmt); }) +static void logperror(char *str); + +static __inline__ int isbroadcast(struct sockaddr_in *sin) +{ + return (sin->sin_addr.s_addr == INADDR_BROADCAST); +} + +static __inline__ int ismulticast(struct sockaddr_in *sin) +{ + return IN_CLASSD(ntohl(sin->sin_addr.s_addr)); +} + +static void prusage(void) +{ + fputs(usage, stderr); + exit(1); +} + +void do_fork(void) +{ + int t; + pid_t pid; + long open_max; + + if (trace) + return; + if ((open_max = sysconf(_SC_OPEN_MAX)) == -1) { + if (errno == 0) { + (void) fprintf(stderr, "OPEN_MAX is not supported\n"); + } + else { + (void) fprintf(stderr, "sysconf() error\n"); + } + exit(1); + } + + + if ((pid=fork()) != 0) + exit(0); + + for (t = 0; t < open_max; t++) + if (t != s) + close(t); + + setsid(); + initlog(); +} + +void signal_setup(int signo, void (*handler)(void)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + + sa.sa_handler = (void (*)(int))handler; +#ifdef SA_INTERRUPT + sa.sa_flags = SA_INTERRUPT; +#endif + sigaction(signo, &sa, NULL); +} + +/* + * M A I N + */ +char *sendaddress, *recvaddress; + +int main(int argc, char **argv) +{ + struct sockaddr_in from; + char **av = argv; + struct sockaddr_in *to = &whereto; + struct sockaddr_in joinaddr; + sigset_t sset, sset_empty; +#ifdef RDISC_SERVER + int val; + + min_adv_int =( max_adv_int * 3 / 4); + lifetime = (3*max_adv_int); +#endif + + argc--, av++; + while (argc > 0 && *av[0] == '-') { + while (*++av[0]) { + switch (*av[0]) { + case 'd': + debug = 1; + break; + case 't': + trace = 1; + break; + case 'v': + verbose++; + break; + case 's': + solicit = 1; + break; +#ifdef RDISC_SERVER + case 'r': + responder = 1; + break; +#endif + case 'a': + best_preference = 0; + break; + case 'b': + best_preference = 1; + break; + case 'f': + forever = 1; + break; + case 'V': + printf("rdisc utility, iputils-%s\n", SNAPSHOT); + exit(0); +#ifdef RDISC_SERVER + case 'T': + argc--, av++; + if (argc != 0) { + val = strtol(av[0], (char **)NULL, 0); + if (val < 4 || val > 1800) { + (void) fprintf(stderr, + "Bad Max Advertizement Interval\n"); + exit(1); + } + max_adv_int = val; + min_adv_int =( max_adv_int * 3 / 4); + lifetime = (3*max_adv_int); + } else { + prusage(); + /* NOTREACHED*/ + } + goto next; + case 'p': + argc--, av++; + if (argc != 0) { + val = strtol(av[0], (char **)NULL, 0); + preference = val; + } else { + prusage(); + /* NOTREACHED*/ + } + goto next; +#endif + default: + prusage(); + /* NOTREACHED*/ + } + } +#ifdef RDISC_SERVER +next: +#endif + argc--, av++; + } + if( argc < 1) { + if (support_multicast()) { + sendaddress = ALL_ROUTERS_ADDRESS; +#ifdef RDISC_SERVER + if (responder) + sendaddress = ALL_HOSTS_ADDRESS; +#endif + } else + sendaddress = "255.255.255.255"; + } else { + sendaddress = av[0]; + argc--; + } + + if (argc < 1) { + if (support_multicast()) { + recvaddress = ALL_HOSTS_ADDRESS; +#ifdef RDISC_SERVER + if (responder) + recvaddress = ALL_ROUTERS_ADDRESS; +#endif + } else + recvaddress = "255.255.255.255"; + } else { + recvaddress = av[0]; + argc--; + } + if (argc != 0) { + (void) fprintf(stderr, "Extra parameters\n"); + prusage(); + /* NOTREACHED */ + } + +#ifdef RDISC_SERVER + if (solicit && responder) { + prusage(); + /* NOTREACHED */ + } +#endif + + if (!(solicit && !forever)) { + do_fork(); +/* + * Added the next line to stop forking a second time + * Fraser Gardiner - Sun Microsystems Australia + */ + forever = 1; + } + + memset( (char *)&whereto, 0, sizeof(struct sockaddr_in) ); + to->sin_family = AF_INET; + to->sin_addr.s_addr = inet_addr(sendaddress); + + memset( (char *)&joinaddr, 0, sizeof(struct sockaddr_in) ); + joinaddr.sin_family = AF_INET; + joinaddr.sin_addr.s_addr = inet_addr(recvaddress); + +#ifdef RDISC_SERVER + if (responder) + srandom((int)gethostid()); +#endif + + if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { + logperror("socket"); + exit(5); + } + + setlinebuf( stdout ); + + signal_setup(SIGINT, finish ); + signal_setup(SIGTERM, graceful_finish ); + signal_setup(SIGHUP, initifs ); + signal_setup(SIGALRM, timer ); + + sigemptyset(&sset); + sigemptyset(&sset_empty); + sigaddset(&sset, SIGALRM); + sigaddset(&sset, SIGHUP); + sigaddset(&sset, SIGTERM); + sigaddset(&sset, SIGINT); + + init(); + if (join(s, &joinaddr) < 0) { + logerr("Failed joining addresses\n"); + exit (2); + } + + timer(); /* start things going */ + + for (;;) { + u_char packet[MAXPACKET]; + int len = sizeof (packet); + socklen_t fromlen = sizeof (from); + int cc; + + cc=recvfrom(s, (char *)packet, len, 0, + (struct sockaddr *)&from, &fromlen); + if (cc<0) { + if (errno == EINTR) + continue; + logperror("recvfrom"); + continue; + } + + sigprocmask(SIG_SETMASK, &sset, NULL); + pr_pack( (char *)packet, cc, &from ); + sigprocmask(SIG_SETMASK, &sset_empty, NULL); + } + /*NOTREACHED*/ +} + +#define TIMER_INTERVAL 3 +#define GETIFCONF_TIMER 30 + +static int left_until_advertise; + +/* Called every TIMER_INTERVAL */ +void timer() +{ + static int time; + static int left_until_getifconf; + static int left_until_solicit; + + + time += TIMER_INTERVAL; + + left_until_getifconf -= TIMER_INTERVAL; + left_until_advertise -= TIMER_INTERVAL; + left_until_solicit -= TIMER_INTERVAL; + + if (left_until_getifconf < 0) { + initifs(); + left_until_getifconf = GETIFCONF_TIMER; + } +#ifdef RDISC_SERVER + if (responder && left_until_advertise <= 0) { + ntransmitted++; + advertise(&whereto, lifetime); + if (ntransmitted < initial_advertisements) + left_until_advertise = initial_advert_interval; + else + left_until_advertise = min_adv_int + + ((max_adv_int - min_adv_int) * + (random() % 1000)/1000); + } else +#endif + if (solicit && left_until_solicit <= 0) { + ntransmitted++; + solicitor(&whereto); + if (ntransmitted < max_solicitations) + left_until_solicit = solicitation_interval; + else { + solicit = 0; + if (!forever && nreceived == 0) + exit(5); + } + } + age_table(TIMER_INTERVAL); + alarm(TIMER_INTERVAL); +} + +/* + * S O L I C I T O R + * + * Compose and transmit an ICMP ROUTER SOLICITATION REQUEST packet. + * The IP packet will be added on by the kernel. + */ +void +solicitor(struct sockaddr_in *sin) +{ + static u_char outpack[MAXPACKET]; + struct icmphdr *icp = (struct icmphdr *) ALLIGN(outpack); + int packetlen, i; + + if (verbose) { + logtrace("Sending solicitation to %s\n", + pr_name(sin->sin_addr)); + } + icp->type = ICMP_ROUTER_SOLICITATION; + icp->code = 0; + icp->checksum = 0; + icp->un.gateway = 0; /* Reserved */ + packetlen = 8; + + /* Compute ICMP checksum here */ + icp->checksum = in_cksum( (u_short *)icp, packetlen ); + + if (isbroadcast(sin)) + i = sendbcast(s, (char *)outpack, packetlen); + else if (ismulticast(sin)) + i = sendmcast(s, (char *)outpack, packetlen, sin); + else + i = sendto( s, (char *)outpack, packetlen, 0, + (struct sockaddr *)sin, sizeof(struct sockaddr)); + + if( i < 0 || i != packetlen ) { + if( i<0 ) { + logperror("solicitor:sendto"); + } + logerr("wrote %s %d chars, ret=%d\n", + sendaddress, packetlen, i ); + } +} + +#ifdef RDISC_SERVER +/* + * A V E R T I S E + * + * Compose and transmit an ICMP ROUTER ADVERTISEMENT packet. + * The IP packet will be added on by the kernel. + */ +void +advertise(struct sockaddr_in *sin, int lft) +{ + static u_char outpack[MAXPACKET]; + struct icmp_ra *rap = (struct icmp_ra *) ALLIGN(outpack); + struct icmp_ra_addr *ap; + int packetlen, i, cc; + + if (verbose) { + logtrace("Sending advertisement to %s\n", + pr_name(sin->sin_addr)); + } + + for (i = 0; i < num_interfaces; i++) { + rap->icmp_type = ICMP_ROUTER_ADVERTISEMENT; + rap->icmp_code = 0; + rap->icmp_cksum = 0; + rap->icmp_num_addrs = 0; + rap->icmp_wpa = 2; + rap->icmp_lifetime = htons(lft); + packetlen = 8; + + /* + * TODO handle multiple logical interfaces per + * physical interface. (increment with rap->icmp_wpa * 4 for + * each address.) + */ + ap = (struct icmp_ra_addr *)ALLIGN(outpack + ICMP_MINLEN); + ap->ira_addr = interfaces[i].localaddr.s_addr; + ap->ira_preference = htonl(interfaces[i].preference); + packetlen += rap->icmp_wpa * 4; + rap->icmp_num_addrs++; + + /* Compute ICMP checksum here */ + rap->icmp_cksum = in_cksum( (u_short *)rap, packetlen ); + + if (isbroadcast(sin)) + cc = sendbcastif(s, (char *)outpack, packetlen, + &interfaces[i]); + else if (ismulticast(sin)) + cc = sendmcastif( s, (char *)outpack, packetlen, sin, + &interfaces[i]); + else { + struct interface *ifp = &interfaces[i]; + /* + * Verify that the interface matches the destination + * address. + */ + if ((sin->sin_addr.s_addr & ifp->netmask.s_addr) == + (ifp->address.s_addr & ifp->netmask.s_addr)) { + if (debug) { + logdebug("Unicast to %s ", + pr_name(sin->sin_addr)); + logdebug("on interface %s, %s\n", + ifp->name, + pr_name(ifp->address)); + } + cc = sendto( s, (char *)outpack, packetlen, 0, + (struct sockaddr *)sin, + sizeof(struct sockaddr)); + } else + cc = packetlen; + } + if( cc < 0 || cc != packetlen ) { + if (cc < 0) { + logperror("sendto"); + } else { + logerr("wrote %s %d chars, ret=%d\n", + sendaddress, packetlen, cc ); + } + } + } +} +#endif + +/* + * P R _ T Y P E + * + * Convert an ICMP "type" field to a printable string. + */ +char * +pr_type(int t) +{ + static char *ttab[] = { + "Echo Reply", + "ICMP 1", + "ICMP 2", + "Dest Unreachable", + "Source Quench", + "Redirect", + "ICMP 6", + "ICMP 7", + "Echo", + "Router Advertise", + "Router Solicitation", + "Time Exceeded", + "Parameter Problem", + "Timestamp", + "Timestamp Reply", + "Info Request", + "Info Reply", + "Netmask Request", + "Netmask Reply" + }; + + if ( t < 0 || t > 16 ) + return("OUT-OF-RANGE"); + + return(ttab[t]); +} + +/* + * P R _ N A M E + * + * Return a string name for the given IP address. + */ +char *pr_name(struct in_addr addr) +{ + struct hostent *phe; + static char buf[80]; + + phe = gethostbyaddr((char *)&addr.s_addr, 4, AF_INET); + if (phe == NULL) + return( inet_ntoa(addr)); + snprintf(buf, sizeof(buf), "%s (%s)", phe->h_name, inet_ntoa(addr)); + return(buf); +} + +/* + * P R _ P A C K + * + * Print out the packet, if it came from us. This logic is necessary + * because ALL readers of the ICMP socket get a copy of ALL ICMP packets + * which arrive ('tis only fair). This permits multiple copies of this + * program to be run without having intermingled output (or statistics!). + */ +void +pr_pack(char *buf, int cc, struct sockaddr_in *from) +{ + struct iphdr *ip; + struct icmphdr *icp; + int i; + int hlen; + + ip = (struct iphdr *) ALLIGN(buf); + hlen = ip->ihl << 2; + if (cc < hlen + 8) { + if (verbose) + logtrace("packet too short (%d bytes) from %s\n", cc, + pr_name(from->sin_addr)); + return; + } + cc -= hlen; + icp = (struct icmphdr *)ALLIGN(buf + hlen); + + switch (icp->type) { + case ICMP_ROUTER_ADVERTISEMENT: + { + struct icmp_ra *rap = (struct icmp_ra *)ALLIGN(icp); + struct icmp_ra_addr *ap; + +#ifdef RDISC_SERVER + if (responder) + break; +#endif + + /* TBD verify that the link is multicast or broadcast */ + /* XXX Find out the link it came in over? */ + if (in_cksum((u_short *)ALLIGN(buf+hlen), cc)) { + if (verbose) + logtrace("ICMP %s from %s: Bad checksum\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr)); + return; + } + if (rap->icmp_code != 0) { + if (verbose) + logtrace("ICMP %s from %s: Code = %d\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr), + rap->icmp_code); + return; + } + if (rap->icmp_num_addrs < 1) { + if (verbose) + logtrace("ICMP %s from %s: No addresses\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr)); + return; + } + if (rap->icmp_wpa < 2) { + if (verbose) + logtrace("ICMP %s from %s: Words/addr = %d\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr), + rap->icmp_wpa); + return; + } + if ((unsigned)cc < + 8 + rap->icmp_num_addrs * rap->icmp_wpa * 4) { + if (verbose) + logtrace("ICMP %s from %s: Too short %d, %d\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr), + cc, + 8 + rap->icmp_num_addrs * rap->icmp_wpa * 4); + return; + } + + if (verbose) + logtrace("ICMP %s from %s, lifetime %d\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr), + ntohs(rap->icmp_lifetime)); + + /* Check that at least one router address is a neighboor + * on the arriving link. + */ + for (i = 0; (unsigned)i < rap->icmp_num_addrs; i++) { + struct in_addr ina; + ap = (struct icmp_ra_addr *) + ALLIGN(buf + hlen + 8 + + i * rap->icmp_wpa * 4); + ina.s_addr = ap->ira_addr; + if (verbose) + logtrace("\taddress %s, preference 0x%x\n", + pr_name(ina), + (unsigned int)ntohl(ap->ira_preference)); + if (is_directly_connected(ina)) + record_router(ina, + ntohl(ap->ira_preference), + ntohs(rap->icmp_lifetime)); + } + nreceived++; + if (!forever) { + do_fork(); + forever = 1; +/* + * The next line was added so that the alarm is set for the new procces + * Fraser Gardiner Sun Microsystems Australia + */ + (void) alarm(TIMER_INTERVAL); + } + break; + } + +#ifdef RDISC_SERVER + case ICMP_ROUTER_SOLICITATION: + { + struct sockaddr_in sin; + + if (!responder) + break; + + /* TBD verify that the link is multicast or broadcast */ + /* XXX Find out the link it came in over? */ + + if (in_cksum((u_short *)ALLIGN(buf+hlen), cc)) { + if (verbose) + logtrace("ICMP %s from %s: Bad checksum\n", + pr_type((int)icp->type), + pr_name(from->sin_addr)); + return; + } + if (icp->code != 0) { + if (verbose) + logtrace("ICMP %s from %s: Code = %d\n", + pr_type((int)icp->type), + pr_name(from->sin_addr), + icp->code); + return; + } + + if (cc < ICMP_MINLEN) { + if (verbose) + logtrace("ICMP %s from %s: Too short %d, %d\n", + pr_type((int)icp->type), + pr_name(from->sin_addr), + cc, + ICMP_MINLEN); + return; + } + + if (verbose) + logtrace("ICMP %s from %s\n", + pr_type((int)icp->type), + pr_name(from->sin_addr)); + + /* Check that ip_src is either a neighboor + * on the arriving link or 0. + */ + sin.sin_family = AF_INET; + if (ip->saddr == 0) { + /* If it was sent to the broadcast address we respond + * to the broadcast address. + */ + if (IN_CLASSD(ntohl(ip->daddr))) + sin.sin_addr.s_addr = htonl(0xe0000001); + else + sin.sin_addr.s_addr = INADDR_BROADCAST; + /* Restart the timer when we broadcast */ + left_until_advertise = min_adv_int + + ((max_adv_int - min_adv_int) + * (random() % 1000)/1000); + } else { + sin.sin_addr.s_addr = ip->saddr; + if (!is_directly_connected(sin.sin_addr)) { + if (verbose) + logtrace("ICMP %s from %s: source not directly connected\n", + pr_type((int)icp->type), + pr_name(from->sin_addr)); + break; + } + } + nreceived++; + ntransmitted++; + advertise(&sin, lifetime); + break; + } +#endif + } +} + + +/* + * I N _ C K S U M + * + * Checksum routine for Internet Protocol family headers (C Version) + * + */ +u_short in_cksum(u_short *addr, int len) +{ + register int nleft = len; + register u_short *w = addr; + register u_short answer; + register int sum = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), + * we add sequential 16 bit words to it, and at the end, fold + * back all the carry bits from the top 16 bits into the lower + * 16 bits. + */ + while( nleft > 1 ) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if( nleft == 1 ) + sum += htons(*(u_char *)w<<8); + + /* + * add back carry outs from top 16 bits to low 16 bits + */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return (answer); +} + +/* + * F I N I S H + * + * Print out statistics, and give up. + * Heavily buffered STDIO is used here, so that all the statistics + * will be written with 1 sys-write call. This is nice when more + * than one copy of the program is running on a terminal; it prevents + * the statistics output from becomming intermingled. + */ +void +finish() +{ +#ifdef RDISC_SERVER + if (responder) { + /* Send out a packet with a preference so that all + * hosts will know that we are dead. + * + * Wrong comment, wrong code. + * ttl must be set to 0 instead. --ANK + */ + logerr("terminated\n"); + ntransmitted++; + advertise(&whereto, 0); + } +#endif + logtrace("\n----%s rdisc Statistics----\n", sendaddress ); + logtrace("%d packets transmitted, ", ntransmitted ); + logtrace("%d packets received, ", nreceived ); + logtrace("\n"); + (void) fflush(stdout); + exit(0); +} + +void +graceful_finish() +{ + discard_table(); + finish(); + exit(0); +} + + +/* From libc/rpc/pmap_rmt.c */ + +int +sendbcast(int s, char *packet, int packetlen) +{ + int i, cc; + + for (i = 0; i < num_interfaces; i++) { + if ((interfaces[i].flags & (IFF_BROADCAST|IFF_POINTOPOINT)) == 0) + continue; + cc = sendbcastif(s, packet, packetlen, &interfaces[i]); + if (cc!= packetlen) { + return (cc); + } + } + return (packetlen); +} + +int +sendbcastif(int s, char *packet, int packetlen, struct interface *ifp) +{ + int on; + int cc; + struct sockaddr_in baddr; + + baddr.sin_family = AF_INET; + baddr.sin_addr = ifp->bcastaddr; + if (debug) + logdebug("Broadcast to %s\n", + pr_name(baddr.sin_addr)); + on = 1; + setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&on, sizeof(on)); + cc = sendto(s, packet, packetlen, 0, + (struct sockaddr *)&baddr, sizeof (struct sockaddr)); + if (cc!= packetlen) { + logperror("sendbcast: sendto"); + logerr("Cannot send broadcast packet to %s\n", + pr_name(baddr.sin_addr)); + } + on = 0; + setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&on, sizeof(on)); + return (cc); +} + +int +sendmcast(int s, char *packet, int packetlen, struct sockaddr_in *sin) +{ + int i, cc; + + for (i = 0; i < num_interfaces; i++) { + if ((interfaces[i].flags & (IFF_BROADCAST|IFF_POINTOPOINT|IFF_MULTICAST)) == 0) + continue; + cc = sendmcastif(s, packet, packetlen, sin, &interfaces[i]); + if (cc!= packetlen) { + return (cc); + } + } + return (packetlen); +} + +int +sendmcastif(int s, char *packet, int packetlen, struct sockaddr_in *sin, + struct interface *ifp) +{ + int cc; + struct ip_mreqn mreq; + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_ifindex = ifp->ifindex; + mreq.imr_address = ifp->localaddr; + if (debug) + logdebug("Multicast to interface %s, %s\n", + ifp->name, + pr_name(mreq.imr_address)); + if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, + (char *)&mreq, + sizeof(mreq)) < 0) { + logperror("setsockopt (IP_MULTICAST_IF)"); + logerr("Cannot send multicast packet over interface %s, %s\n", + ifp->name, + pr_name(mreq.imr_address)); + return (-1); + } + cc = sendto(s, packet, packetlen, 0, + (struct sockaddr *)sin, sizeof (struct sockaddr)); + if (cc!= packetlen) { + logperror("sendmcast: sendto"); + logerr("Cannot send multicast packet over interface %s, %s\n", + ifp->name, pr_name(mreq.imr_address)); + } + return (cc); +} + +void +init() +{ + initifs(); +#ifdef RDISC_SERVER + { + int i; + for (i = 0; i < interfaces_size; i++) + interfaces[i].preference = preference; + } +#endif +} + +void +initifs() +{ + int sock; + struct ifconf ifc; + struct ifreq ifreq, *ifr; + struct sockaddr_in *sin; + int n, i; + char *buf; + int numifs; + unsigned bufsize; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + logperror("initifs: socket"); + return; + } +#ifdef SIOCGIFNUM + if (ioctl(sock, SIOCGIFNUM, (char *)&numifs) < 0) { + numifs = MAXIFS; + } +#else + numifs = MAXIFS; +#endif + bufsize = numifs * sizeof(struct ifreq); + buf = (char *)malloc(bufsize); + if (buf == NULL) { + logerr("out of memory\n"); + (void) close(sock); + return; + } + if (interfaces) + interfaces = (struct interface *)ALLIGN(realloc((char *)interfaces, + numifs * sizeof(struct interface))); + else + interfaces = (struct interface *)ALLIGN(malloc(numifs * + sizeof(struct interface))); + if (interfaces == NULL) { + logerr("out of memory\n"); + (void) close(sock); + (void) free(buf); + return; + } + interfaces_size = numifs; + + ifc.ifc_len = bufsize; + ifc.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, (char *)&ifc) < 0) { + logperror("initifs: ioctl (get interface configuration)"); + (void) close(sock); + (void) free(buf); + return; + } + ifr = ifc.ifc_req; + for (i = 0, n = ifc.ifc_len/sizeof (struct ifreq); n > 0; n--, ifr++) { + ifreq = *ifr; + if (strlen(ifreq.ifr_name) >= IFNAMSIZ) + continue; + if (ioctl(sock, SIOCGIFFLAGS, (char *)&ifreq) < 0) { + logperror("initifs: ioctl (get interface flags)"); + continue; + } + if (ifr->ifr_addr.sa_family != AF_INET) + continue; + if ((ifreq.ifr_flags & IFF_UP) == 0) + continue; + if (ifreq.ifr_flags & IFF_LOOPBACK) + continue; + if ((ifreq.ifr_flags & (IFF_MULTICAST|IFF_BROADCAST|IFF_POINTOPOINT)) == 0) + continue; + strncpy(interfaces[i].name, ifr->ifr_name, IFNAMSIZ-1); + + sin = (struct sockaddr_in *)ALLIGN(&ifr->ifr_addr); + interfaces[i].localaddr = sin->sin_addr; + interfaces[i].flags = ifreq.ifr_flags; + interfaces[i].netmask.s_addr = (__u32)0xffffffff; + if (ioctl(sock, SIOCGIFINDEX, (char *)&ifreq) < 0) { + logperror("initifs: ioctl (get ifindex)"); + continue; + } + interfaces[i].ifindex = ifreq.ifr_ifindex; + if (ifreq.ifr_flags & IFF_POINTOPOINT) { + if (ioctl(sock, SIOCGIFDSTADDR, (char *)&ifreq) < 0) { + logperror("initifs: ioctl (get destination addr)"); + continue; + } + sin = (struct sockaddr_in *)ALLIGN(&ifreq.ifr_addr); + /* A pt-pt link is identified by the remote address */ + interfaces[i].address = sin->sin_addr; + interfaces[i].remoteaddr = sin->sin_addr; + /* Simulate broadcast for pt-pt */ + interfaces[i].bcastaddr = sin->sin_addr; + interfaces[i].flags |= IFF_BROADCAST; + } else { + /* Non pt-pt links are identified by the local address */ + interfaces[i].address = interfaces[i].localaddr; + interfaces[i].remoteaddr = interfaces[i].address; + if (ioctl(sock, SIOCGIFNETMASK, (char *)&ifreq) < 0) { + logperror("initifs: ioctl (get netmask)"); + continue; + } + sin = (struct sockaddr_in *)ALLIGN(&ifreq.ifr_addr); + interfaces[i].netmask = sin->sin_addr; + if (ifreq.ifr_flags & IFF_BROADCAST) { + if (ioctl(sock, SIOCGIFBRDADDR, (char *)&ifreq) < 0) { + logperror("initifs: ioctl (get broadcast address)"); + continue; + } + sin = (struct sockaddr_in *)ALLIGN(&ifreq.ifr_addr); + interfaces[i].bcastaddr = sin->sin_addr; + } + } +#ifdef notdef + if (debug) + logdebug("Found interface %s, flags 0x%x\n", + pr_name(interfaces[i].localaddr), + interfaces[i].flags); +#endif + i++; + } + num_interfaces = i; +#ifdef notdef + if (debug) + logdebug("Found %d interfaces\n", num_interfaces); +#endif + (void) close(sock); + (void) free(buf); +} + +int +join(int sock, struct sockaddr_in *sin) +{ + int i, j; + struct ip_mreqn mreq; + int joined[num_interfaces]; + + memset(joined, 0, sizeof(joined)); + + if (isbroadcast(sin)) + return (0); + + mreq.imr_multiaddr = sin->sin_addr; + for (i = 0; i < num_interfaces; i++) { + for (j = 0; j < i; j++) { + if (joined[j] == interfaces[i].ifindex) + break; + } + if (j != i) + continue; + + mreq.imr_ifindex = interfaces[i].ifindex; + mreq.imr_address.s_addr = 0; + + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (char *)&mreq, sizeof(mreq)) < 0) { + logperror("setsockopt (IP_ADD_MEMBERSHIP)"); + return (-1); + } + + joined[i] = interfaces[i].ifindex; + } + return (0); +} + +int support_multicast() +{ + int sock; + u_char ttl = 1; + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + logperror("support_multicast: socket"); + return (0); + } + + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, + (char *)&ttl, sizeof(ttl)) < 0) { + (void) close(sock); + return (0); + } + (void) close(sock); + return (1); +} + +int +is_directly_connected(struct in_addr in) +{ + int i; + + for (i = 0; i < num_interfaces; i++) { + /* Check that the subnetwork numbers match */ + + if ((in.s_addr & interfaces[i].netmask.s_addr ) == + (interfaces[i].remoteaddr.s_addr & interfaces[i].netmask.s_addr)) + return (1); + } + return (0); +} + +/* + * TABLES + */ +struct table { + struct in_addr router; + int preference; + int remaining_time; + int in_kernel; + struct table *next; +}; + +struct table *table; + +struct table * +find_router(struct in_addr addr) +{ + struct table *tp; + + tp = table; + while (tp) { + if (tp->router.s_addr == addr.s_addr) + return (tp); + tp = tp->next; + } + return (NULL); +} + +int max_preference(void) +{ + struct table *tp; + int max = (int)INELIGIBLE_PREF; + + tp = table; + while (tp) { + if (tp->preference > max) + max = tp->preference; + tp = tp->next; + } + return (max); +} + + +/* Note: this might leave the kernel with no default route for a short time. */ +void +age_table(int time) +{ + struct table **tpp, *tp; + int recalculate_max = 0; + int max = max_preference(); + + tpp = &table; + while (*tpp != NULL) { + tp = *tpp; + tp->remaining_time -= time; + if (tp->remaining_time <= 0) { + *tpp = tp->next; + if (tp->in_kernel) + del_route(tp->router); + if (best_preference && + tp->preference == max) + recalculate_max++; + free((char *)tp); + } else { + tpp = &tp->next; + } + } + if (recalculate_max) { + int max = max_preference(); + + if (max != INELIGIBLE_PREF) { + tp = table; + while (tp) { + if (tp->preference == max && !tp->in_kernel) { + add_route(tp->router); + tp->in_kernel++; + } + tp = tp->next; + } + } + } +} + +void discard_table(void) +{ + struct table **tpp, *tp; + + tpp = &table; + while (*tpp != NULL) { + tp = *tpp; + *tpp = tp->next; + if (tp->in_kernel) + del_route(tp->router); + free((char *)tp); + } +} + + +void +record_router(struct in_addr router, int preference, int ttl) +{ + struct table *tp; + int old_max = max_preference(); + int changed_up = 0; /* max preference could have increased */ + int changed_down = 0; /* max preference could have decreased */ + + if (ttl < 4) + preference = INELIGIBLE_PREF; + + if (debug) + logdebug("Recording %s, ttl %d, preference 0x%x\n", + pr_name(router), + ttl, + preference); + tp = find_router(router); + if (tp) { + if (tp->preference > preference && + tp->preference == old_max) + changed_down++; + else if (preference > tp->preference) + changed_up++; + tp->preference = preference; + tp->remaining_time = ttl; + } else { + if (preference > old_max) + changed_up++; + tp = (struct table *)ALLIGN(malloc(sizeof(struct table))); + if (tp == NULL) { + logerr("Out of memory\n"); + return; + } + tp->router = router; + tp->preference = preference; + tp->remaining_time = ttl; + tp->in_kernel = 0; + tp->next = table; + table = tp; + } + if (!tp->in_kernel && + (!best_preference || tp->preference == max_preference()) && + tp->preference != INELIGIBLE_PREF) { + add_route(tp->router); + tp->in_kernel++; + } + if (tp->preference == INELIGIBLE_PREF && tp->in_kernel) { + del_route(tp->router); + tp->in_kernel = 0; + } + if (best_preference && changed_down) { + /* Check if we should add routes */ + int new_max = max_preference(); + if (new_max != INELIGIBLE_PREF) { + tp = table; + while (tp) { + if (tp->preference == new_max && + !tp->in_kernel) { + add_route(tp->router); + tp->in_kernel++; + } + tp = tp->next; + } + } + } + if (best_preference && (changed_up || changed_down)) { + /* Check if we should remove routes already in the kernel */ + int new_max = max_preference(); + tp = table; + while (tp) { + if (tp->preference < new_max && tp->in_kernel) { + del_route(tp->router); + tp->in_kernel = 0; + } + tp = tp->next; + } + } +} + +void +add_route(struct in_addr addr) +{ + if (debug) + logdebug("Add default route to %s\n", pr_name(addr)); + rtioctl(addr, SIOCADDRT); +} + +void +del_route(struct in_addr addr) +{ + if (debug) + logdebug("Delete default route to %s\n", pr_name(addr)); + rtioctl(addr, SIOCDELRT); +} + +void +rtioctl(struct in_addr addr, int op) +{ + int sock; + struct rtentry rt; + struct sockaddr_in *sin; + + memset((char *)&rt, 0, sizeof(struct rtentry)); + rt.rt_dst.sa_family = AF_INET; + rt.rt_gateway.sa_family = AF_INET; + rt.rt_genmask.sa_family = AF_INET; + sin = (struct sockaddr_in *)ALLIGN(&rt.rt_gateway); + sin->sin_addr = addr; + rt.rt_flags = RTF_UP | RTF_GATEWAY; + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + logperror("rtioctl: socket"); + return; + } + if (ioctl(sock, op, (char *)&rt) < 0) { + if (!(op == SIOCADDRT && errno == EEXIST)) + logperror("ioctl (add/delete route)"); + } + (void) close(sock); +} + +/* + * LOGGER + */ + +void initlog(void) +{ + logging++; + openlog("in.rdiscd", LOG_PID | LOG_CONS, LOG_DAEMON); +} + + +void +logperror(char *str) +{ + if (logging) + syslog(LOG_ERR, "%s: %m", str); + else + (void) fprintf(stderr, "%s: %s\n", str, strerror(errno)); +} |