diff --git a/sbin/rdisc/DISCLAIMER b/sbin/rdisc/DISCLAIMER new file mode 100644 index 000000000000..161e84e9e71c --- /dev/null +++ b/sbin/rdisc/DISCLAIMER @@ -0,0 +1,27 @@ +/* + * 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 + */ diff --git a/sbin/rdisc/rdisc.8 b/sbin/rdisc/rdisc.8 new file mode 100644 index 000000000000..f6d337a3c801 --- /dev/null +++ b/sbin/rdisc/rdisc.8 @@ -0,0 +1,191 @@ +.\" @(#)in.rdisc.8c 1.4 91/10/21 SMI +'\"macro stdmacro +.\" +.\" 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 +.\" +.nr X +.TH in.rdisc 8C "2 Oct 1991" +.SH NAME +in.rdisc \- network router discovery daemon +.SH SYNOPSIS +.B in.rdisc +[ +.B \-sfa +] +.LP +.B in.rdisc +.B \-r +[ +.BI \-p " preference" +] [ +.BI \-T " interval" +] +.SH DESCRIPTION +.IX "in.rdisc" "" "\fLin.rdisc\fP \(em ICMP router discovery daemon" +.IX "Internet" "ICMP router discovery daemon" "" "ICMP router discovery daemon \(em \fLin.rdisc\fP" +.IX "Internet Control Message Protocol" "See ICMP" +.IX "ICMP" "router discovery daemon" "" "router discovery daemon \(em \fLin.rdisc\fP" +.LP +\f4in.rdisc\f1 +implements the +.SM ICMP +router discover protocol. +The first form of the command is used on hosts and the +second form is used on routers. +On a host \f4in.rdisc\f1 is invoked at +boot time to populate the network routing tables with default routes. +On a router it is also invoked at boot time in order to start +advertising the router to all the hosts. +.SS "Host (First Form)" +.PP +On a host \f4in.rdisc\f1 +listens on the \f4ALL_HOSTS\f1 (224.0.0.1) multicast address +for \f4ROUTER_ADVERTISE\f1 messages from routers. The received +messages are handled by first ignoring +those listed router addresses with which the host does not share a network. +Among the remaining addresses the ones with the highest preference are selected +as default routers and a default route is entered in the kernel routing +table for each one of them. +.LP +Optionally, \f4in.rdisc\f1 can avoid waiting for routers to announce +themselves by sending out a few \f4ROUTER_SOLICITATION\f1 messages +to the \f4ALL_ROUTERS\f1 (224.0.0.2) multicast address when it is started. +.LP +A timer is associated with each router address and the address will +no longer be considered for inclusion in the the routing tables if the +timer expires before a new +.I advertise +message is received from the router. +The address will also be excluded from consideration if the host receives an +.I advertise +message with the preference being maximally negative. +.SS "Router (Second Form)" +.PP +When +\f4in.rdisc\f1 +is started on a router, it uses the +\f4SIOCGIFCONF\f1 +.BR ioctl (2) +to find the interfaces configured into the system and it starts +listening on the \f4ALL_ROUTERS\f1 multicast address on all the interfaces +that support multicast. +It sends out +.I advertise +messages to the \f4ALL_HOSTS\f1 multicast address advertising all its +.BM IP +addresses. +A few initial +.I advertise +messages are sent out during the first 30 seconds and after that it will +transmit +.I advertise +messages approximately every 600 seconds. +.LP +When \f4in.rdisc\f1 +receives a +.I solicitation +message it sends an +.I advertise +message to the host that sent the +.I solicitation +message. +.LP +When +.B in.rdisc +is terminated by a signal it sends out an +.I advertise +message with the preference being maximally negative. +.br +.ne 1i +.SH OPTIONS +.TP 15 +.B \-a +Accept all routers independently of the preference they have in their +.I advertise +messages. +Normally +.B in.rdisc +only accepts (and enters in the kernel routing tables) +the router or routers with the highest preference. +.TP +.B \-f +Run +.B in.rdisc +forever even if no routers are found. +Normally +.B in.rdisc +gives up if it has not received any +.I advertise +message after after soliciting three times, +in which case it exits with a non-zero exit code. +If +.B \-f +is not specified in the first form then +.B \-s +must be specified. +.TP +.BI \-p " preference" +Set the preference transmitted in the +.I solicitation +messages. +The default is zero. +.TP +.B \-r +Act as a router as opposed to a host. +.TP +.B \-s +Send three +.I solicitation +messages initially to quickly discover the routers +when the system is booted. +When +.B \-s +is specified +.B in.rdisc +exits with a non-zero exit code if it can not find any routers. +This can be overridden with the +.B \-f +option. +.TP +.BI \-T " interval" +Set the interval between transmitting the +.I advertise +messages. +The default time is 600 seconds. +.SH "SEE ALSO" +.BR in.routed (8C) +.BR ioctl (2), +.BR icmp (7), +.BR inet (7) +.LP +Deering, S.E.,ed +.RI `` "ICMP Router Discovery Messages" ,'' +.SM RFC +1256, Network Information Center, +.SM SRI +International, Menlo Park, Calif., +September 1991. diff --git a/sbin/rdisc/rdisc.c b/sbin/rdisc/rdisc.c new file mode 100644 index 000000000000..b9c674cbd7fa --- /dev/null +++ b/sbin/rdisc/rdisc.c @@ -0,0 +1,1576 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* + * 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 "rdisc.h" +#include +#include + +#include +#include + +/* + * TBD + * Use 255.255.255.255 for broadcasts - not the interface broadcast + * address. + */ + +char *malloc(); +void exit(); +long gethostid(); +long random(); +time_t time(); +long strtol(); + +#ifdef lint +#define ALLIGN(ptr) (ptr ? 0 : 0) +#else +#define ALLIGN(ptr) (ptr) +#endif + +#ifdef SYSV +#define signal(s,f) sigset(s,f) +#define random() rand() +#endif + +void solicitor(), advertise(), pr_pack(), init(); +void bzero(), initlog(), logerr(), logtrace(), logdebug(), logperror(); +void age_table(), record_router(), add_route(), del_route(), rtioctl(); +int support_multicast(), sendbcast(), sendmcast(), sendbcastif(), sendmcastif(); +int ismulticast(), isbroadcast(), in_cksum(), is_directly_connected(); +static int join(); + +#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 + +/* 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 { + u_long addr; + u_long preference; +}; + +/* 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 IGNORE_PREFERENCE 0x80000000 /* Maximum negative */ + +#define MAX_ADV_INT 600 + +/* Statics */ +int num_interfaces; +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; +}; +struct interface *interfaces; +int interfaces_size; /* Number of elements in interfaces */ + + +#define MAXPACKET 4096 /* max packet size */ +u_char packet[MAXPACKET]; + +/* fraser */ +int debugfile; +char usage[] = +"Usage: rdisc [-s] [-v] [-f] [-a] [send_address] [receive_address]\n\ + rdisc -r [-v] [-p ] [-T ] \n\ + [send_address] [receive_address]\n"; + + +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 responder; +int ntransmitted = 0; +int nreceived = 0; +int forever = 0; /* Never give up on host. If 0 defer fork until + * first response. + */ + +/* Router variables */ +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 */ + +/* 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. */ + + +void finish(), catcher(), timer(), initifs(); +char *pr_name(); + +static void prusage() +{ + (void) fprintf(stderr, usage); + exit(1); +} + +void +do_fork() +{ + int t; + long pid; + + if (trace) + return; + + if (pid =fork()) + exit(0); + + for (t = 0; t < 20; t++) + if (t != s) + (void) close(t); + +#ifndef SYSV + t = open("/dev/tty", 2); + if (t >= 0) { + (void) ioctl(t, TIOCNOTTY, (char *)0); + (void) close(t); + } +#else + (void) setpgrp (); +#endif + initlog(); +} + +/* + * M A I N + */ +char *sendaddress, *recvaddress; + +main(argc, argv) +char *argv[]; +{ +#ifndef SYSV + struct sigvec sv; +#endif + struct sockaddr_in from; + char **av = argv; + struct sockaddr_in *to = &whereto; + struct sockaddr_in joinaddr; + int val; + + min_adv_int =( max_adv_int * 3 / 4); + lifetime = (3*max_adv_int); + + 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; + case 'r': + responder = 1; + break; + case 'a': + best_preference = 0; + break; + case 'b': + best_preference = 1; + break; + case 'f': + forever = 1; + break; + 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; + default: + prusage(); + /* NOTREACHED*/ + } + next: + argc--, av++; + } + if( argc < 1) { + if (support_multicast()) { + if (responder) + sendaddress = ALL_HOSTS_ADDRESS; + else + sendaddress = ALL_ROUTERS_ADDRESS; + } else + sendaddress = "255.255.255.255"; + } else { + sendaddress = av[0]; + argc--; + } + + if (argc < 1) { + if (support_multicast()) { + if (responder) + recvaddress = ALL_ROUTERS_ADDRESS; + else + recvaddress = ALL_HOSTS_ADDRESS; + } else + recvaddress = "255.255.255.255"; + } else { + recvaddress = av[0]; + argc--; + } + if (argc != 0) { + (void) fprintf(stderr, "Extra paramaters\n"); + prusage(); + /* NOTREACHED */ + } + + if (solicit && responder) { + prusage(); + /* NOTREACHED */ + } + + if (!(solicit && !forever)) { + do_fork(); +/* + * Added the next line to stop forking a second time + * Fraser Gardiner - Sun Microsystems Australia + */ + forever = 1; + } + + bzero( (char *)&whereto, sizeof(struct sockaddr_in) ); + to->sin_family = AF_INET; + to->sin_addr.s_addr = inet_addr(sendaddress); + + bzero( (char *)&joinaddr, sizeof(struct sockaddr_in) ); + joinaddr.sin_family = AF_INET; + joinaddr.sin_addr.s_addr = inet_addr(recvaddress); + + if (responder) { +#ifdef SYSV + /* TBD fix this to be more random */ + srand((u_int)time((time_t *)NULL)); +#else + srandom((int)gethostid()); +#endif + } + + if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) { + logperror("socket"); + exit(5); + } + +#ifdef SYSV + setvbuf( stdout, NULL, _IOLBF, 0 ); +#else + setlinebuf( stdout ); +#endif + + (void) signal( SIGINT, finish ); + (void) signal( SIGTERM, finish ); + (void) signal( SIGHUP, initifs ); + + init(); + if (join(s, &joinaddr) < 0) { + logerr("Failed joining addresses\n"); + exit (2); + } + + +#ifdef SYSV + (void) sigset(SIGALRM, timer); +#else + /* + * Make sure that this signal actually interrupts (rather than + * restarts) the recvfrom call below. + */ + sv.sv_handler = timer; + sv.sv_mask = 0; + sv.sv_flags = SV_INTERRUPT; + (void) sigvec(SIGALRM, &sv, (struct sigvec *)NULL); +#endif + timer(); /* start things going */ + + for (;;) { + int len = sizeof (packet); + int fromlen = sizeof (from); + int cc; + + if ( (cc=recvfrom(s, (char *)packet, len, 0, + (struct sockaddr *)&from, &fromlen)) < 0) { + if( errno == EINTR ) + continue; + logperror("recvfrom"); + continue; + } + pr_pack( (char *)packet, cc, &from ); + } + /*NOTREACHED*/ +} + +#define TIMER_INTERVAL 3 +#define GETIFCONF_TIMER 30 + +static left_until_advertise; + +/* Called every TIMER_INTERVAL */ +void timer() +{ + static time; + static left_until_getifconf; + static 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; + } + if (responder && left_until_advertise <= 0) { + ntransmitted++; + advertise(&whereto); + 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 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); + (void) 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(sin) + struct sockaddr_in *sin; +{ + static u_char outpack[MAXPACKET]; + register struct icmp *icp = (struct icmp *) ALLIGN(outpack); + int packetlen, i; + + if (verbose) { + logtrace("Sending solicitation to %s\n", + pr_name(sin->sin_addr)); + } + icp->icmp_type = ICMP_ROUTER_SOLICITATION; + icp->icmp_code = 0; + icp->icmp_cksum = 0; + icp->icmp_void = 0; /* Reserved */ + packetlen = 8; + + /* Compute ICMP checksum here */ + icp->icmp_cksum = 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("sendto"); + } + logerr("wrote %s %d chars, ret=%d\n", + sendaddress, packetlen, i ); + } +} + +/* + * 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(sin) + struct sockaddr_in *sin; +{ + static u_char outpack[MAXPACKET]; + register 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 = lifetime; + 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->addr = interfaces[i].localaddr.s_addr; + ap->preference = 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\n", + 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 ); + } + } + } +} + +/* + * P R _ T Y P E + * + * Convert an ICMP "type" field to a printable string. + */ +char * +pr_type( t ) +register 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(addr) + struct in_addr addr; +{ + char *inet_ntoa(); + struct hostent *phe; + static char buf[256]; + + phe = gethostbyaddr((char *)&addr.s_addr, 4, AF_INET); + if (phe == NULL) + return( inet_ntoa(addr)); + (void) sprintf(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( buf, cc, from ) +char *buf; +int cc; +struct sockaddr_in *from; +{ + struct ip *ip; + register struct icmp *icp; + register int i; + int hlen; + + ip = (struct ip *) ALLIGN(buf); + hlen = ip->ip_hl << 2; + if (cc < hlen + ICMP_MINLEN) { + if (verbose) + logtrace("packet too short (%d bytes) from %s\n", cc, + pr_name(from->sin_addr)); + return; + } + cc -= hlen; + icp = (struct icmp *)ALLIGN(buf + hlen); + if (ip->ip_p == 0) { + /* + * Assume that we are running on a pre-4.3BSD system + * such as SunOS before 4.0 + */ + icp = (struct icmp *)ALLIGN(buf); + } + switch (icp->icmp_type) { + case ICMP_ROUTER_ADVERTISEMENT: { + struct icmp_ra *rap = (struct icmp_ra *)ALLIGN(icp); + struct icmp_ra_addr *ap; + + if (responder) + break; + + /* TBD verify that the link is multicast or broadcast */ + /* XXX Find out the link it came in over? */ +#ifdef notdef + if (debug) { + logdebug("ROUTER_ADVERTISEMENT: \n"); + pr_hex(buf+hlen, cc); + } +#endif notdef + 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 < + ICMP_MINLEN + 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, + ICMP_MINLEN + + rap->icmp_num_addrs * rap->icmp_wpa * 4); + return; + } + if (rap->icmp_lifetime < 4) { + if (verbose) + logtrace("ICMP %s from %s: Lifetime = %d\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr), + rap->icmp_lifetime); + return; + } + if (verbose) + logtrace("ICMP %s from %s, lifetime %d\n", + pr_type((int)rap->icmp_type), + pr_name(from->sin_addr), + 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 + ICMP_MINLEN + + i * rap->icmp_wpa * 4); + ina.s_addr = ntohl(ap->addr); + if (verbose) + logtrace("\taddress %s, preference 0x%x\n", + pr_name(ina), + ntohl(ap->preference)); + if (!responder) { + if (is_directly_connected(ina)) + record_router(ina, + (long)ntohl(ap->preference), + 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; + } + + 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? */ +#ifdef notdef + if (debug) { + logdebug("ROUTER_SOLICITATION: \n"); + pr_hex(buf+hlen, cc); + } +#endif notdef + if (in_cksum((u_short *)ALLIGN(buf+hlen), cc)) { + if (verbose) + logtrace("ICMP %s from %s: Bad checksum\n", + pr_type((int)icp->icmp_type), + pr_name(from->sin_addr)); + return; + } + if (icp->icmp_code != 0) { + if (verbose) + logtrace("ICMP %s from %s: Code = %d\n", + pr_type((int)icp->icmp_type), + pr_name(from->sin_addr), + icp->icmp_code); + return; + } + + if (cc < ICMP_MINLEN) { + if (verbose) + logtrace("ICMP %s from %s: Too short %d, %d\n", + pr_type((int)icp->icmp_type), + pr_name(from->sin_addr), + cc, + ICMP_MINLEN); + return; + } + + if (verbose) + logtrace("ICMP %s from %s\n", + pr_type((int)icp->icmp_type), + pr_name(from->sin_addr)); + + if (!responder) + break; + + /* Check that ip_src is either a neighboor + * on the arriving link or 0. + */ + sin.sin_family = AF_INET; + if (ip->ip_src.s_addr == 0) { + /* If it was sent to the broadcast address we respond + * to the broadcast address. + */ + if (IN_CLASSD(ip->ip_dst.s_addr)) + sin.sin_addr.s_addr = INADDR_ALLHOSTS_GROUP; + 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 { + if (!is_directly_connected(ip->ip_src)) { + if (verbose) + logtrace("ICMP %s from %s: source not directly connected\n", + pr_type((int)icp->icmp_type), + pr_name(from->sin_addr)); + break; + } + sin.sin_addr.s_addr = ip->ip_src.s_addr; + } + nreceived++; + ntransmitted++; + advertise(&sin); + break; + } + } +} + + +/* + * I N _ C K S U M + * + * Checksum routine for Internet Protocol family headers (C Version) + * + */ +in_cksum(addr, len) +u_short *addr; +int len; +{ + register int nleft = len; + register u_short *w = addr; + register u_short answer; + u_short odd_byte = 0; + 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 ) { + *(u_char *)(&odd_byte) = *(u_char *)w; + sum += odd_byte; + } + + /* + * 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() +{ + if (responder) { + int i; + + /* Send out a packet with a preference so that all + * hosts will know that we are dead. + */ + logerr("terminated\n"); + for (i = 0; i < num_interfaces; i++) + interfaces[i].preference = IGNORE_PREFERENCE; + ntransmitted++; + advertise(&whereto); + } + logtrace("\n----%s rdisc Statistics----\n", sendaddress ); + logtrace("%d packets transmitted, ", ntransmitted ); + logtrace("%d packets received, ", nreceived ); + logtrace("\n"); + (void) fflush(stdout); + exit(0); +} + +#include + +#ifdef notdef +pr_hex(data, len) +int len; +unsigned char *data; +{ + FILE *out; + + out = stdout; + + while (len) { + register int i; + char charstring[17]; + + (void)strcpy(charstring," "); /* 16 spaces */ + for (i = 0; i < 16; i++) { + /* output the bytes one at a time, + * not going pas "len" bytes + */ + if (len) { + char ch = *data & 0x7f; /* strip parity */ + if (!isprint((u_char)ch)) + ch = ' '; /* ensure printable */ + charstring[i] = ch; + (void) fprintf(out,"%02x ",*data++); + len--; + } else + (void) fprintf(out," "); + if (i==7) + (void) fprintf(out," "); + } + + (void) fprintf(out," *%s*\n",charstring); + } +} +#endif notdef + +isbroadcast(sin) + struct sockaddr_in *sin; +{ + return (sin->sin_addr.s_addr == INADDR_BROADCAST); +} + +ismulticast(sin) + struct sockaddr_in *sin; +{ + return (IN_CLASSD(sin->sin_addr.s_addr)); +} + +/* From libc/rpc/pmap_rmt.c */ + +int +sendbcast(s, packet, packetlen) + int s; + char *packet; + int packetlen; +{ + int i, cc; + + for (i = 0; i < num_interfaces; i++) { + if ((interfaces[i].flags & IFF_BROADCAST) == 0) + continue; + cc = sendbcastif(s, packet, packetlen, &interfaces[i]); + if (cc!= packetlen) { + return (cc); + } + } + return (packetlen); +} + +int +sendbcastif(s, packet, packetlen, ifp) + int s; + char *packet; + int packetlen; + struct interface *ifp; +{ + int cc; + struct sockaddr_in baddr; + + baddr.sin_family = AF_INET; + + if ((ifp->flags & IFF_BROADCAST) == 0) + return (packetlen); + + baddr.sin_addr = ifp->bcastaddr; + if (debug) + logdebug("Broadcast to %s\n", + pr_name(baddr.sin_addr)); + 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)); + } + return (cc); +} + +int +sendmcast(s, packet, packetlen, sin) + 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_MULTICAST) == 0) + continue; + cc = sendmcastif(s, packet, packetlen, sin, &interfaces[i]); + if (cc!= packetlen) { + return (cc); + } + } + return (packetlen); +} + +int +sendmcastif(s, packet, packetlen, sin, ifp) + int s; + char *packet; + int packetlen; + struct sockaddr_in *sin; + struct interface *ifp; +{ + int cc; + struct sockaddr_in ifaddr; + + ifaddr.sin_family = AF_INET; + + if ((ifp->flags & IFF_MULTICAST) == 0) + return (packetlen); + + ifaddr.sin_addr = ifp->address; + if (debug) + logdebug("Multicast to interface %s\n", + pr_name(ifaddr.sin_addr)); + if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, + (char *)&ifaddr.sin_addr, + sizeof(ifaddr.sin_addr)) < 0) { + logperror("setsockopt (IP_MULTICAST_IF)"); + logerr("Cannot send multicast packet over interface %s\n", + pr_name(ifaddr.sin_addr)); + 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\n", + pr_name(ifaddr.sin_addr)); + } + return (cc); +} + +void +init() +{ + int i; + + initifs(); + for (i = 0; i < interfaces_size; i++) + interfaces[i].preference = preference; +} + +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 (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)) == 0) + continue; + 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 = (unsigned long)0xffffffff; + 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); +} + +static int +join(sock, sin) + int sock; + struct sockaddr_in *sin; +{ + int i; + struct ip_mreq mreq; + + if (isbroadcast(sin)) + return (0); + + mreq.imr_multiaddr = sin->sin_addr; + for (i = 0; i < num_interfaces; i++) { + mreq.imr_interface = interfaces[i].address; + + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (char *)&mreq, sizeof(mreq)) < 0) { + logperror("setsockopt (IP_ADD_MEMBERSHIP)"); + return (-1); + } + } + 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(in) + 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(addr) + 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() +{ + struct table *tp; + int max = (int)IGNORE_PREFERENCE; + + 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(time) + 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 != IGNORE_PREFERENCE) { + tp = table; + while (tp) { + if (tp->preference == max && !tp->in_kernel) { + add_route(tp->router); + tp->in_kernel++; + } + tp = tp->next; + } + } + } +} + +void +record_router(router, preference, ttl) + struct in_addr router; + long 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 (debug) + logdebug("Recording %s, preference 0x%x\n", + pr_name(router), + 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 != IGNORE_PREFERENCE) { + add_route(tp->router); + tp->in_kernel++; + } + if (tp->preference == IGNORE_PREFERENCE && 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 != IGNORE_PREFERENCE) { + 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; + } + } +} + + +#include + +void +add_route(addr) + struct in_addr addr; +{ + if (debug) + logdebug("Add default route to %s\n", pr_name(addr)); + rtioctl(addr, SIOCADDRT); +} + +void +del_route(addr) + struct in_addr addr; +{ + if (debug) + logdebug("Delete default route to %s\n", pr_name(addr)); + rtioctl(addr, SIOCDELRT); +} + +void +rtioctl(addr, op) + struct in_addr addr; + int op; +{ + int sock; + struct rtentry rt; + struct sockaddr_in *sin; + bzero((char *)&rt, sizeof(struct rtentry)); + rt.rt_dst.sa_family = AF_INET; + rt.rt_gateway.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 + */ + + +static logging = 0; + +void +initlog() +{ + logging++; + openlog("in.rdiscd", LOG_PID | LOG_CONS, LOG_DAEMON); +} + +/* VARARGS1 */ +void +logerr(fmt, a,b,c,d,e,f,g,h) + char *fmt; +{ + if (logging) + syslog(LOG_ERR, fmt, a,b,c,d,e,f,g,h); + else + (void) fprintf(stderr, fmt, a,b,c,d,e,f,g,h); +} + +/* VARARGS1 */ +void +logtrace(fmt, a,b,c,d,e,f,g,h) + char *fmt; +{ + if (logging) + syslog(LOG_INFO, fmt, a,b,c,d,e,f,g,h); + else + (void) fprintf(stdout, fmt, a,b,c,d,e,f,g,h); +} + +/* VARARGS1 */ +void +logdebug(fmt, a,b,c,d,e,f,g,h) + char *fmt; +{ + if (logging) + syslog(LOG_DEBUG, fmt, a,b,c,d,e,f,g,h); + else + (void) fprintf(stdout, fmt, a,b,c,d,e,f,g,h); +} + +extern char *sys_errlist[]; +extern int errno; + +void +logperror(str) + char *str; +{ + if (logging) + syslog(LOG_ERR, "%s: %s\n", str, sys_errlist[errno]); + else + (void) fprintf(stderr, "%s: %s\n", str, sys_errlist[errno]); +} + + +#ifdef SYSV +void +bzero(cp, len) + char *cp; + int len; +{ + while (--len>=0) + *cp++ = 0; +} +#endif + diff --git a/sbin/rdisc/rdisc.h b/sbin/rdisc/rdisc.h new file mode 100644 index 000000000000..8a3d84594466 --- /dev/null +++ b/sbin/rdisc/rdisc.h @@ -0,0 +1,19 @@ +/* From net/if.h */ + +#define IFF_MULTICAST 0x800 /* supports multicast */ + +/* From netinet/in.h */ + +#define INADDR_ALLHOSTS_GROUP (u_long)0xe0000001 /* 224.0.0.1 */ +#define IP_MULTICAST_IF 0x10 /* set/get IP multicast interface */ +#define IP_ADD_MEMBERSHIP 0x13 /* add an IP group membership */ +#define IP_MULTICAST_TTL 0x11 /* set/get IP multicast timetolive */ + +/* + * Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP. + */ +struct ip_mreq { + struct in_addr imr_multiaddr; /* IP multicast address of group */ + struct in_addr imr_interface; /* local IP address of interface */ +}; +