/* * rip6adv - advertise IPv6routes via RIPng * * Usage: rip6adv [-a] [-p] * [src-addr][/src-port] target-addr[/target-port] * route-spec [route-spec ...] * * As its sole author, I explicitly place this file in the public * domain. It may be used by anyone in any way for any purpose, * though I would appreciate credit where it's due. * * der Mouse * * mouse@rodents.montreal.qc.ca * 7D C8 61 52 5D E7 2D 39 4E F1 31 3E E8 B3 27 4B */ #include #include #include #include #include #include #include #include #include #include #include #include /* This macro is, unfortunately, both dependent on using the kame interface and not exported by any system include file.... */ #define IN6_LINKLOCAL_IFINDEX(addr) ((addr).s6_addr[2] << 8 | (addr).s6_addr[3]) extern const char *__progname; typedef struct rt RT; struct rt { struct in6_addr to; unsigned char width; unsigned int given; #define GAVE_VIA 0x00000001 #define GAVE_METRIC 0x00000002 #define GAVE_TAG 0x00000004 struct in6_addr via; unsigned char metric; unsigned short int tag; } ; #define MIN_METRIC 1 #define MAX_METRIC 16 #define METRIC_NEXTHOP 0xff /* in packets, not RT.metric field */ static int nrt; static RT *rts; static unsigned char *pkt; static int pktlen; static int ripsock; static int timersock; static int aflag; static int pflag; static struct sockaddr_in6 src_sin; static struct sockaddr_in6 dst_sin; static void usage(void) { fprintf(stderr,"Usage: %s [-a] [-p]\n",__progname); fprintf(stderr," [src-addr][/src-port] dst-addr[/dst-port]\n"); fprintf(stderr," route-spec [route-spec ...]\n"); fprintf(stderr,"A route-spec is: dest [keyword value [keyword value ...]]\n"); fprintf(stderr,"keywords: via, metric, tag\n"); fprintf(stderr,"dest is address/width or the word `default', optionally preceded by `to'\n"); } static void parse_addr_port(const char *arg, struct sockaddr_in6 *sin6, void (*noaddrfn)(struct sockaddr_in6 *), const char *tag) { const char *slash; const char *addr; const char *port; char *tmp; struct addrinfo hints; struct addrinfo *ai; int err; bzero(sin6,sizeof(*sin6)); slash = index(arg,'/'); if (slash == arg) { addr = 0; port = slash + 1; } else if (slash) { int l; l = slash - arg; tmp = malloc(l+1); bcopy(arg,tmp,l); tmp[l] = '\0'; addr = tmp; port = slash + 1; } else { addr = arg; tmp = 0; port = "521"; } hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; hints.ai_family = PF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; err = getaddrinfo(addr,port,&hints,&ai); if (err) { fprintf(stderr,"%s: %s %s/%s: %s\n",__progname,tag,addr?:"",port,gai_strerror(err)); exit(1); } if (! ai) { fprintf(stderr,"%s: %s %s/%s resolved but to no addresses?\n",__progname,tag,addr?:"",port); exit(1); } if (ai->ai_next) { fprintf(stderr,"%s: %s %s/%s resolved to multiple addresses?\n",__progname,tag,addr?:"",port); exit(1); } if (ai->ai_addr->sa_family != AF_INET6) { fprintf(stderr,"%s: %s %s/%s resolved to non-INET6 address?\n",__progname,tag,addr?:"",port); exit(1); } bcopy(ai->ai_addr,sin6,sizeof(struct sockaddr_in6)); if (addr == 0) (*noaddrfn)(sin6); if (tmp) free(tmp); } static void no_addr_dst(struct sockaddr_in6 *sin6 __attribute__((__unused__))) { fprintf(stderr,"%s: missing destination address\n",__progname); exit(1); } static void no_addr_src(struct sockaddr_in6 *sin6) { struct ifconf ifc; char *cbuf; int cbufsize; struct ifreq *ifr; int s; int i; int n; int inx; struct sockaddr_in6 *a6; static void gifconf(void *ifcp) { if (ioctl(s,SIOCGIFCONF,ifcp) < 0) { fprintf(stderr,"%s: SIOCGIFCONF: %s\n",__progname,strerror(errno)); exit(1); } } if (!( IN6_IS_ADDR_LINKLOCAL(&dst_sin.sin6_addr) || IN6_IS_ADDR_MC_LINKLOCAL(&dst_sin.sin6_addr) )) { fprintf(stderr,"%s: can't omit source address unless dest address is link-local\n",__progname); exit(1); } inx = IN6_LINKLOCAL_IFINDEX(dst_sin.sin6_addr); if (inx == 0) inx = dst_sin.sin6_scope_id; if (inx == 0) { fprintf(stderr,"%s: can't figure out scope of dest address\n",__progname); exit(1); } s = socket(AF_INET6,SOCK_DGRAM,0); if (s < 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(errno)); exit(1); } /* growl, should be a better way to find the actual size! */ cbufsize = 2000; cbuf = malloc(cbufsize); ifc.ifc_buf = cbuf; ifc.ifc_len = cbufsize / 2; gifconf(&ifc); i = ifc.ifc_len; while (1) { ifc.ifc_buf = cbuf; ifc.ifc_len = cbufsize; gifconf(&ifc); if (ifc.ifc_len == i) break; i = ifc.ifc_len; free(cbuf); cbufsize <<= 1; cbuf = malloc(cbufsize); } i = 0; while (i < ifc.ifc_len) { ifr = (void *)(i+(char *)ifc.ifc_req); if (i+sizeof(struct ifreq) > ifc.ifc_len) abort(); n = ifr->ifr_addr.sa_len; if (n < sizeof(ifr->ifr_addr)) n = sizeof(ifr->ifr_addr); n += sizeof(ifr->ifr_name); i += n; if (i > ifc.ifc_len) abort(); if (ifr->ifr_addr.sa_family != AF_INET6) continue; a6 = (struct sockaddr_in6 *) &ifr->ifr_addr; if (! IN6_IS_ADDR_LINKLOCAL(&a6->sin6_addr)) continue; if (IN6_LINKLOCAL_IFINDEX(a6->sin6_addr) != inx) continue; sin6->sin6_addr = a6->sin6_addr; close(s); free(cbuf); return; } fprintf(stderr,"%s: can't find a matching link-local address to use\n",__progname); exit(1); } static void key_via(const char *s, RT *rt) { int i; if (rt->given & GAVE_VIA) { fprintf(stderr,"%s: router specified more than once\n",__progname); exit(1); } i = inet_pton(AF_INET6,s,&rt->via); switch (i) { case 1: break; case 0: fprintf(stderr,"%s: missing/invalid router address: %s\n",__progname,s); exit(1); break; case -1: fprintf(stderr,"%s: missing/invalid router address (%s): %s\n",__progname,strerror(errno),s); exit(1); break; default: fprintf(stderr,"%s: impossible return value %d from inet_pton\n",__progname,i); exit(1); break; } rt->given |= GAVE_VIA; } static void key_metric(const char *s, RT *rt) { char *ep; long int v; if (rt->given & GAVE_METRIC) { fprintf(stderr,"%s: metric specified more than once\n",__progname); exit(1); } v = strtol(s,&ep,0); if (*ep || (ep == s)) { fprintf(stderr,"%s: missing/invalid metric: %s\n",__progname,s); exit(1); } if ((v < MIN_METRIC) || (v > MAX_METRIC)) { fprintf(stderr,"%s: metric %ld out of range\n",__progname,v); exit(1); } rt->metric = v; rt->given |= GAVE_METRIC; } static void key_tag(const char *s, RT *rt) { char *ep; long int v; if (rt->given & GAVE_TAG) { fprintf(stderr,"%s: route tag specified more than once\n",__progname); exit(1); } v = strtol(s,&ep,0); if (*ep || (ep == s)) { fprintf(stderr,"%s: missing/invalid route tag: %s\n",__progname,s); exit(1); } if ((v < 0) || (v > 65535)) { fprintf(stderr,"%s: route tag %ld out of range\n",__progname,v); exit(1); } rt->tag = v; rt->given |= GAVE_TAG; } static void parse_route_spec(const char *s, RT *rt) { const char *slash; char *tmp; int l; int i; long int v; auto void badspec(void) __attribute__((__noreturn__)); auto void badspec(void) { fprintf(stderr,"%s: bad route spec, must be addr/width: %s\n",__progname,s); exit(1); } slash = index(s,'/'); if (! slash) badspec(); l = slash - s; tmp = malloc(l+1); bcopy(s,tmp,l); tmp[l] = '\0'; i = inet_pton(AF_INET6,tmp,&rt->to); switch (i) { case 1: break; case 0: badspec(); break; case -1: fprintf(stderr,"%s: error converting route spec (%s): %s\n",__progname,strerror(errno),s); exit(1); break; default: fprintf(stderr,"%s: impossible return value %d from inet_pton\n",__progname,i); exit(1); break; } free(tmp); slash ++; v = strtol(slash,&tmp,0); if (*tmp || (tmp == slash)) badspec(); if ((v < 0) || (v > 128)) { fprintf(stderr,"%s: prefix width out of range\n",__progname); exit(1); } rt->width = v; } static void parse_args(int ac, char **av) { RT rt; while (1) { ac --; av ++; if (ac < 1) { usage(); exit(1); } if (!strcmp(av[0],"-a")) { aflag = 1; continue; } if (!strcmp(av[0],"-p")) { pflag = 1; continue; } break; } if (ac < 2) { usage(); exit(1); } /* do dst first so no_addr_src can look at dst_sin */ parse_addr_port(av[1],&dst_sin,no_addr_dst,"dst"); parse_addr_port(av[0],&src_sin,no_addr_src,"src"); ac -= 2; av += 2; nrt = 0; rt.given = 0; while (1) { static struct { const char *key; void (*parsearg)(const char *, RT *); } keys[] = { { "via", key_via }, { "metric", key_metric }, { "tag", key_tag }, { 0 } }; int i; if (ac < 1) break; if (!strcmp(av[0],"to")) { ac --; av ++; if (ac < 1) { fprintf(stderr,"%s: missing destination address\n",__progname); exit(1); } } if (!strcmp(av[0],"default")) { bzero(&rt.to,sizeof(rt.to)); rt.width = 0; } else { parse_route_spec(av[0],&rt); } ac --; av ++; while (1) { if (ac < 2) break; for (i=0;keys[i].key;i++) if (!strcmp(keys[i].key,av[0])) break; if (! keys[i].key) break; (*keys[i].parsearg)(av[1],&rt); ac -= 2; av += 2; } if ((ac > 0) && !index(av[0],':')) { fprintf(stderr,"%s: unrecognized keyword: %s\n",__progname,av[0]); exit(1); } rts = realloc(rts,(nrt+1)*sizeof(RT)); rts[nrt] = rt; nrt ++; } } static void build_packet(void) { struct in6_addr nexthop; int i; int nrte; RT *rt; int px; bzero(&nexthop,sizeof(nexthop)); nrte = 0; for (i=0;igiven & GAVE_VIA) { if (IN6_ARE_ADDR_EQUAL(&nexthop,&rt->via)) { /* redundant - drop it */ rt->given &= ~GAVE_VIA; } else { nrte ++; nexthop = rt->via; } } if (! (rt->given & GAVE_METRIC)) rt->metric = 1; if (! (rt->given & GAVE_TAG)) rt->tag = 0; nrte ++; } pktlen = 4 + (20 * nrte); pkt = malloc(pktlen); pkt[0] = 2; /* command, 2=response */ pkt[1] = 1; /* version */ pkt[2] = 0; /* mbz */ pkt[3] = 0; /* mbz */ px = 4; for (i=0;igiven & GAVE_VIA) { bcopy(&rt->via,&pkt[px],16); px += 16; pkt[px++] = 0; /* mbz */ pkt[px++] = 0; /* mbz */ pkt[px++] = 0; /* mbz */ pkt[px++] = METRIC_NEXTHOP; /* magic value indicating next-hop */ } bcopy(&rt->to,&pkt[px],16); px += 16; pkt[px++] = rt->tag >> 8; pkt[px++] = rt->tag && 0xff; pkt[px++] = rt->width; pkt[px++] = rt->metric; } if (px != pktlen) abort(); } static void setup_sockets(void) { int v; struct itimerval itv; ripsock = socket(AF_INET6,SOCK_DGRAM,0); if (ripsock < 0) { fprintf(stderr,"%s: RIP socket: %s\n",__progname,strerror(errno)); exit(1); } v = 1; if (aflag && (setsockopt(ripsock,SOL_SOCKET,SO_REUSEADDR,&v,sizeof(v)) < 0)) { fprintf(stderr,"%s: warning: SO_REUSEADDR: %s\n",__progname,strerror(errno)); } if (pflag) { #ifdef SO_REUSEPORT if (setsockopt(ripsock,SOL_SOCKET,SO_REUSEPORT,&v,sizeof(v)) < 0) { fprintf(stderr,"%s: warning: SO_REUSEPORT: %s\n",__progname,strerror(errno)); } #else fprintf(stderr,"%s: warning: SO_REUSEPORT not supported in this build\n",__progname); #endif } v = 255; if (setsockopt(ripsock,IPPROTO_IPV6,IPV6_MULTICAST_HOPS,&v,sizeof(v)) < 0) { fprintf(stderr,"%s: warning: IPV6_MULTICAST_HOPS: %s\n",__progname,strerror(errno)); } v = 0; if (setsockopt(ripsock,IPPROTO_IPV6,IPV6_MULTICAST_LOOP,&v,sizeof(v)) < 0) { fprintf(stderr,"%s: warning: IPV6_MULTICAST_LOOP: %s\n",__progname,strerror(errno)); } if (bind(ripsock,(struct sockaddr *)&src_sin,sizeof(src_sin)) < 0) { fprintf(stderr,"%s: bind: %s\n",__progname,strerror(errno)); exit(1); } timersock = socket(AF_TIMER,SOCK_STREAM,0); if (timersock < 0) { fprintf(stderr,"%s: timer socket: %s\n",__progname,strerror(errno)); exit(1); } itv.it_interval.tv_sec = 30; itv.it_interval.tv_usec = 0; itv.it_value = itv.it_interval; write(timersock,&itv,sizeof(itv)); } static void sendpkt(void) { if (sendto(ripsock,pkt,pktlen,0,(struct sockaddr *)&dst_sin,sizeof(dst_sin)) < 0) { fprintf(stderr,"%s: warning: sendto: %s\n",__progname,strerror(errno)); } } static void timerwait(void) { struct timersock_event ev; read(timersock,&ev,sizeof(ev)); } int main(int, char **); int main(int ac, char **av) { parse_args(ac,av); build_packet(); setup_sockets(); while (1) { sendpkt(); timerwait(); } }