/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include "defs.h" #define IF_SEPARATOR ':' struct interface *ifnet; static int setup_listen_sock(int ifindex); static void addrouteforif(struct interface *ifp); static void resetup_listen_sock(struct interface *, int); /* * This is called at startup and after that, every CHECK_INTERVAL seconds or * when a SIGHUP is received. */ void initifs(void) { static char *buf = NULL; static uint_t maxbufsize = 0; int bufsize; int numifs; struct lifnum lifn; struct lifconf lifc; struct lifreq lifr; struct lifreq *lifrp; int n; struct interface ifs; struct interface *ifp; int netmaskchange = 0; boolean_t changes = _B_FALSE; lifn.lifn_family = AF_INET6; lifn.lifn_flags = 0; if (ioctl(iocsoc, SIOCGLIFNUM, (char *)&lifn) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface numbers): %m"); return; } numifs = lifn.lifn_count; bufsize = numifs * sizeof (struct lifreq); if (buf == NULL || bufsize > maxbufsize) { if (buf != NULL) free(buf); maxbufsize = bufsize; buf = (char *)malloc(maxbufsize); if (buf == NULL) { syslog(LOG_ERR, "initifs: out of memory"); return; } } lifc.lifc_family = AF_INET6; lifc.lifc_flags = 0; lifc.lifc_len = bufsize; lifc.lifc_buf = buf; if (ioctl(iocsoc, SIOCGLIFCONF, (char *)&lifc) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface configuration): %m"); return; } /* * Mark all of the currently known interfaces in order to determine * which of the these interfaces no longer exist. */ for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) ifp->int_flags |= RIP6_IFF_MARKED; lifrp = lifc.lifc_req; for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifrp++) { bzero((char *)&ifs, sizeof (ifs)); (void) strncpy(lifr.lifr_name, lifrp->lifr_name, sizeof (lifr.lifr_name)); if (ioctl(iocsoc, SIOCGLIFFLAGS, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface flags): %m"); continue; } if (!(lifr.lifr_flags & IFF_IPV6) || !(lifr.lifr_flags & IFF_MULTICAST) || (lifr.lifr_flags & IFF_LOOPBACK)) continue; ifp = if_ifwithname(lifr.lifr_name); if (ifp != NULL) ifp->int_flags &= ~RIP6_IFF_MARKED; if (lifr.lifr_flags & IFF_POINTOPOINT) ifs.int_flags |= RIP6_IFF_POINTOPOINT; if (lifr.lifr_flags & IFF_NORTEXCH) ifs.int_flags |= RIP6_IFF_NORTEXCH; if (lifr.lifr_flags & IFF_PRIVATE) ifs.int_flags |= RIP6_IFF_PRIVATE; if (lifr.lifr_flags & IFF_UP) { ifs.int_flags |= RIP6_IFF_UP; } else { if (ifp != NULL) { if (ifp->int_flags & RIP6_IFF_UP) { /* * If there is an transition from up to * down for an exisiting interface, * increment the counter. */ ifp->int_transitions++; changes = _B_TRUE; } if_purge(ifp); } continue; } if (ifs.int_flags & RIP6_IFF_POINTOPOINT) { /* * For point-to-point interfaces, retrieve both the * local and the remote addresses. */ if (ioctl(iocsoc, SIOCGLIFADDR, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface address): " "%m"); continue; } ifs.int_addr = ((struct sockaddr_in6 *)&lifr.lifr_addr)->sin6_addr; if (ioctl(iocsoc, SIOCGLIFDSTADDR, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get destination address): " "%m"); continue; } ifs.int_dstaddr = ((struct sockaddr_in6 *) &lifr.lifr_dstaddr)->sin6_addr; ifs.int_prefix_length = IPV6_ABITS; } else { /* * For other interfaces, retreieve the prefix (including * the prefix length. */ if (ioctl(iocsoc, SIOCGLIFSUBNET, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get subnet prefix): %m"); continue; } /* * This should never happen but check for it in any case * since the kernel stores it as an signed integer. */ if (lifr.lifr_addrlen < 0 || lifr.lifr_addrlen > IPV6_ABITS) { syslog(LOG_ERR, "initifs: ioctl (get subnet prefix) " "returned invalid prefix length of %d", lifr.lifr_addrlen); continue; } ifs.int_prefix_length = lifr.lifr_addrlen; ifs.int_addr = ((struct sockaddr_in6 *) &lifr.lifr_subnet)->sin6_addr; } if (ioctl(iocsoc, SIOCGLIFMETRIC, (char *)&lifr) < 0 || lifr.lifr_metric < 0) ifs.int_metric = 1; else ifs.int_metric = lifr.lifr_metric + 1; if (ioctl(iocsoc, SIOCGLIFINDEX, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get index): %m"); continue; } ifs.int_ifindex = lifr.lifr_index; if (ioctl(iocsoc, SIOCGLIFMTU, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get mtu): %m"); continue; } /* * If the interface's recorded MTU doesn't make sense, use * IPV6_MIN_MTU instead. */ if (lifr.lifr_mtu < IPV6_MIN_MTU) ifs.int_mtu = IPV6_MIN_MTU; else ifs.int_mtu = lifr.lifr_mtu; if (ifp != NULL) { /* * RIP6_IFF_NORTEXCH flag change by itself shouldn't * cause an if_purge() call, which also purges all the * routes heard off this interface. So, let's suppress * changes of RIP6_IFF_NORTEXCH in the following * comparisons. */ if (ifp->int_prefix_length == ifs.int_prefix_length && ((ifp->int_flags | RIP6_IFF_NORTEXCH) == (ifs.int_flags | RIP6_IFF_NORTEXCH)) && ifp->int_metric == ifs.int_metric && ifp->int_ifindex == ifs.int_ifindex) { /* * Now let's make sure we capture the latest * value of RIP6_IFF_NORTEXCH flag. */ if (ifs.int_flags & RIP6_IFF_NORTEXCH) ifp->int_flags |= RIP6_IFF_NORTEXCH; else ifp->int_flags &= ~RIP6_IFF_NORTEXCH; if (!(ifp->int_flags & RIP6_IFF_POINTOPOINT) && IN6_ARE_ADDR_EQUAL(&ifp->int_addr, &ifs.int_addr)) continue; if ((ifp->int_flags & RIP6_IFF_POINTOPOINT) && IN6_ARE_ADDR_EQUAL(&ifp->int_dstaddr, &ifs.int_dstaddr)) continue; } if_purge(ifp); if (ifp->int_prefix_length != ifs.int_prefix_length) netmaskchange = 1; ifp->int_addr = ifs.int_addr; ifp->int_dstaddr = ifs.int_dstaddr; ifp->int_metric = ifs.int_metric; /* * If there is an transition from down to up for an * exisiting interface, increment the counter. */ if (!(ifp->int_flags & RIP6_IFF_UP) && (ifs.int_flags & RIP6_IFF_UP)) ifp->int_transitions++; ifp->int_flags |= ifs.int_flags; ifp->int_prefix_length = ifs.int_prefix_length; /* * If the interface index has changed, we may need to * set up the listen socket again. */ if (ifp->int_ifindex != ifs.int_ifindex) { if (ifp->int_sock != -1) { resetup_listen_sock(ifp, ifs.int_ifindex); } ifp->int_ifindex = ifs.int_ifindex; } ifp->int_mtu = ifs.int_mtu; } else { char *cp; int log_num; ifp = (struct interface *) malloc(sizeof (struct interface)); if (ifp == NULL) { syslog(LOG_ERR, "initifs: out of memory"); return; } *ifp = ifs; ifp->int_name = ifp->int_ifbase = NULL; ifp->int_name = (char *)malloc((size_t)strlen(lifr.lifr_name) + 1); if (ifp->int_name == NULL) { free(ifp); syslog(LOG_ERR, "initifs: out of memory"); return; } (void) strcpy(ifp->int_name, lifr.lifr_name); ifp->int_ifbase = (char *)malloc((size_t)strlen(lifr.lifr_name) + 1); if (ifp->int_ifbase == NULL) { free(ifp->int_name); free(ifp); syslog(LOG_ERR, "initifs: out of memory"); return; } (void) strcpy(ifp->int_ifbase, lifr.lifr_name); cp = (char *)index(ifp->int_ifbase, IF_SEPARATOR); if (cp != NULL) { /* * Verify that the value following the separator * is an integer greater than zero (the only * possible value for a logical interface). */ log_num = atoi((char *)(cp + 1)); if (log_num <= 0) { free(ifp->int_ifbase); free(ifp->int_name); free(ifp); syslog(LOG_ERR, "initifs: interface name %s could " "not be parsed", ifp->int_name); return; } *cp = '\0'; } else { log_num = 0; } if (log_num == 0) { ifp->int_sock = setup_listen_sock(ifp->int_ifindex); } else { ifp->int_sock = -1; } ifp->int_next = ifnet; ifnet = ifp; traceinit(ifp); } addrouteforif(ifp); changes = _B_TRUE; } /* * Any remaining interfaces that are still marked and which were in an * up state (RIP6_IFF_UP) need to removed from the routing table. */ for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) { if ((ifp->int_flags & (RIP6_IFF_MARKED | RIP6_IFF_UP)) == (RIP6_IFF_MARKED | RIP6_IFF_UP)) { if_purge(ifp); ifp->int_flags &= ~RIP6_IFF_MARKED; changes = _B_TRUE; } } if (netmaskchange) rtchangeall(); if (supplier & changes) dynamic_update((struct interface *)NULL); } static void addrouteforif(struct interface *ifp) { struct rt_entry *rt; struct in6_addr *dst; if (ifp->int_flags & RIP6_IFF_POINTOPOINT) dst = &ifp->int_dstaddr; else dst = &ifp->int_addr; rt = rtlookup(dst, ifp->int_prefix_length); if (rt != NULL) { if (rt->rt_state & RTS_INTERFACE) return; rtdelete(rt); } rtadd(dst, &ifp->int_addr, ifp->int_prefix_length, ifp->int_metric, 0, _B_TRUE, ifp); } static int setup_listen_sock(int ifindex) { int sock; struct sockaddr_in6 sin6; uint_t hops; struct ipv6_mreq allrouters_mreq; int on = 1; int off = 0; int recvsize; sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock == -1) goto sock_fail; if (setsockopt(sock, IPPROTO_IPV6, IPV6_BOUND_IF, (char *)&ifindex, sizeof (ifindex)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_BOUND_IF: %m"); goto sock_fail; } hops = IPV6_MAX_HOPS; if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&hops, sizeof (hops)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_UNICAST_HOPS: %m"); goto sock_fail; } if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&hops, sizeof (hops)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_MULTICAST_HOPS: %m"); goto sock_fail; } if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&off, sizeof (off)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_MULTICAST_LOOP: %m"); goto sock_fail; } allrouters_mreq.ipv6mr_multiaddr = allrouters_in6; allrouters_mreq.ipv6mr_interface = ifindex; if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&allrouters_mreq, sizeof (allrouters_mreq)) < 0) { if (errno != EADDRINUSE) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: " "IPV6_JOIN_GROUP: %m"); goto sock_fail; } } if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, (char *)&on, sizeof (off)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_RECVHOPLIMIT: %m"); goto sock_fail; } if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: SO_REUSEADDR: %m"); goto sock_fail; } recvsize = RCVBUFSIZ; if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&recvsize, sizeof (int)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: SO_RCVBUF: %m"); goto sock_fail; } bzero((char *)&sin6, sizeof (sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = rip6_port; if (bind(sock, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) { syslog(LOG_ERR, "setup_listen_sock: bind: %m"); goto sock_fail; } poll_ifs_num++; if (poll_ifs == NULL) { poll_ifs = (struct pollfd *) malloc(max_poll_ifs * sizeof (struct pollfd)); } else if (poll_ifs_num > max_poll_ifs) { max_poll_ifs *= 2; poll_ifs = (struct pollfd *)realloc((char *)poll_ifs, max_poll_ifs * sizeof (struct pollfd)); } if (poll_ifs == NULL) { syslog(LOG_ERR, "setup_listen_sock: out of memory"); goto sock_fail; } poll_ifs[poll_ifs_num - 1].fd = sock; poll_ifs[poll_ifs_num - 1].events = POLLIN; return (sock); sock_fail: if (sock > 0) (void) close(sock); return (-1); } /* * resetup_listen_sock is primarily used in the case where a tunnel was * plumbed, unplumbed, then plumbed again. This would cause the binding set by * IPV6_BOUND_IF to be useless, and sends to the associated socket will be * transmitted on the wrong interface. resetup_listen_sock * closes the socket, * removes the socket from poll_ifs[] * plugs the hole in poll_ifs[] * calls setup_listen_sock to set up the socket again */ void resetup_listen_sock(struct interface *ifp, int newindex) { int i; (void) close(ifp->int_sock); /* Remove socket from poll_ifs[]. */ for (i = poll_ifs_num - 1; i >= 0; i--) { if (poll_ifs[i].fd == ifp->int_sock) { poll_ifs[i].fd = 0; poll_ifs[i].events = 0; /* * Remove hole in poll_ifs. Possibly exchange * poll_ifs[i] with poll_ifs[poll_ifs_num-1]. */ if (i != poll_ifs_num - 1) { poll_ifs[i] = poll_ifs[poll_ifs_num - 1]; poll_ifs[poll_ifs_num - 1].fd = 0; poll_ifs[poll_ifs_num - 1].events = 0; } poll_ifs_num--; /* Now set everything up again. */ ifp->int_sock = setup_listen_sock(newindex); break; } } }