/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "events.h" #include "ncp.h" #include "ncu.h" #include "util.h" /* * routing_events.c - this file contains routines to retrieve routing socket * events and package them for high level processing. */ #define RTMBUFSZ sizeof (struct rt_msghdr) + \ (RTAX_MAX * sizeof (struct sockaddr_storage)) static void printaddrs(int, void *); static char *printaddr(void **); static void *getaddr(int, int, void *); static void setaddr(int, int *, void *, struct sockaddr *); union rtm_buf { /* Routing information. */ struct { struct rt_msghdr rtm; struct sockaddr_storage addr[RTAX_MAX]; } r; /* Interface information. */ struct { struct if_msghdr ifm; struct sockaddr_storage addr[RTAX_MAX]; } im; /* Interface address information. */ struct { struct ifa_msghdr ifa; struct sockaddr_storage addr[RTAX_MAX]; } ia; }; static int v4_sock = -1; static int v6_sock = -1; static pthread_t v4_routing, v6_routing; static int seq = 0; static const char * rtmtype_str(int type) { static char typestr[12]; /* strlen("type ") + enough for an int */ switch (type) { case RTM_NEWADDR: return ("NEWADDR"); case RTM_DELADDR: return ("DELADDR"); case RTM_CHGADDR: return ("CHGADDR"); case RTM_FREEADDR: return ("FREEADDR"); default: (void) snprintf(typestr, sizeof (typestr), "type %d", type); return (typestr); } } /* ARGSUSED0 */ static void * routing_events_v4(void *arg) { int n; union rtm_buf buffer; struct rt_msghdr *rtm; struct ifa_msghdr *ifa; char *addrs, *if_name; struct sockaddr_dl *addr_dl; struct sockaddr *addr, *netmask; nwamd_event_t ip_event; nlog(LOG_DEBUG, "v4 routing socket %d", v4_sock); for (;;) { rtm = &buffer.r.rtm; n = read(v4_sock, &buffer, sizeof (buffer)); if (n == -1 && errno == EAGAIN) { continue; } else if (n == -1) { nlog(LOG_ERR, "error reading routing socket " "%d: %m", v4_sock); /* Low likelihood. What's recovery path? */ continue; } if (rtm->rtm_msglen < n) { nlog(LOG_ERR, "only read %d bytes from " "routing socket but message claims to be " "of length %d", rtm->rtm_msglen); continue; } if (rtm->rtm_version != RTM_VERSION) { nlog(LOG_ERR, "tossing routing message of " "version %d type %d", rtm->rtm_version, rtm->rtm_type); continue; } if (rtm->rtm_msglen != n) { nlog(LOG_DEBUG, "routing message of %d size came from " "read of %d on socket %d", rtm->rtm_msglen, n, v4_sock); } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: case RTM_CHGADDR: case RTM_FREEADDR: ifa = (void *)rtm; addrs = (char *)ifa + sizeof (*ifa); nlog(LOG_DEBUG, "v4 routing message %s: " "index %d flags %x", rtmtype_str(rtm->rtm_type), ifa->ifam_index, ifa->ifam_flags); if ((addr = (struct sockaddr *)getaddr(RTA_IFA, ifa->ifam_addrs, addrs)) == NULL) break; /* Ignore routing socket messages for 0.0.0.0 */ /*LINTED*/ if (((struct sockaddr_in *)addr)->sin_addr.s_addr == INADDR_ANY) { nlog(LOG_DEBUG, "routing_events_v4: " "tossing message for 0.0.0.0"); break; } if ((netmask = (struct sockaddr *)getaddr(RTA_NETMASK, ifa->ifam_addrs, addrs)) == NULL) break; if ((addr_dl = (struct sockaddr_dl *)getaddr (RTA_IFP, ifa->ifam_addrs, addrs)) == NULL) break; /* * We don't use the lladdr in this structure so we can * run over it. */ addr_dl->sdl_data[addr_dl->sdl_nlen] = 0; if_name = addr_dl->sdl_data; /* no lifnum */ if (ifa->ifam_index == 0) { nlog(LOG_DEBUG, "tossing index 0 message"); break; } if (ifa->ifam_type != rtm->rtm_type) { nlog(LOG_INFO, "routing_events_v4: unhandled type %d", ifa->ifam_type); break; } printaddrs(ifa->ifam_addrs, addrs); /* Create and enqueue IF_STATE event */ ip_event = nwamd_event_init_if_state(if_name, ifa->ifam_flags, (rtm->rtm_type == RTM_NEWADDR || rtm->rtm_type == RTM_CHGADDR ? B_TRUE : B_FALSE), addr, netmask); if (ip_event != NULL) nwamd_event_enqueue(ip_event); break; } } /* NOTREACHED */ return (NULL); } /* ARGSUSED0 */ static void * routing_events_v6(void *arg) { int n; union rtm_buf buffer; struct rt_msghdr *rtm; struct ifa_msghdr *ifa; char *addrs, *if_name; struct sockaddr_dl *addr_dl; struct sockaddr *addr, *netmask; nwamd_event_t ip_event; nlog(LOG_DEBUG, "v6 routing socket %d", v6_sock); for (;;) { rtm = &buffer.r.rtm; n = read(v6_sock, &buffer, sizeof (buffer)); if (n == -1 && errno == EAGAIN) { continue; } else if (n == -1) { nlog(LOG_ERR, "error reading routing socket " "%d: %m", v6_sock); /* Low likelihood. What's recovery path? */ continue; } if (rtm->rtm_msglen < n) { nlog(LOG_ERR, "only read %d bytes from " "routing socket but message claims to be " "of length %d", rtm->rtm_msglen); continue; } if (rtm->rtm_version != RTM_VERSION) { nlog(LOG_ERR, "tossing routing message of " "version %d type %d", rtm->rtm_version, rtm->rtm_type); continue; } if (rtm->rtm_msglen != n) { nlog(LOG_DEBUG, "routing message of %d size came from " "read of %d on socket %d", rtm->rtm_msglen, n, v6_sock); } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: case RTM_CHGADDR: case RTM_FREEADDR: ifa = (void *)rtm; addrs = (char *)ifa + sizeof (*ifa); nlog(LOG_DEBUG, "v6 routing message %s: " "index %d flags %x", rtmtype_str(rtm->rtm_type), ifa->ifam_index, ifa->ifam_flags); if ((addr = (struct sockaddr *)getaddr(RTA_IFA, ifa->ifam_addrs, addrs)) == NULL) break; /* Ignore routing socket messages for :: & linklocal */ /*LINTED*/ if (IN6_IS_ADDR_UNSPECIFIED( &((struct sockaddr_in6 *)addr)->sin6_addr)) { nlog(LOG_INFO, "routing_events_v6: " "tossing message for ::"); break; } /*LINTED*/ if (IN6_IS_ADDR_LINKLOCAL( &((struct sockaddr_in6 *)addr)->sin6_addr)) { nlog(LOG_INFO, "routing_events_v6: " "tossing message for link local address"); break; } if ((netmask = (struct sockaddr *)getaddr(RTA_NETMASK, ifa->ifam_addrs, addrs)) == NULL) break; if ((addr_dl = (struct sockaddr_dl *)getaddr (RTA_IFP, ifa->ifam_addrs, addrs)) == NULL) break; /* * We don't use the lladdr in this structure so we can * run over it. */ addr_dl->sdl_data[addr_dl->sdl_nlen] = 0; if_name = addr_dl->sdl_data; /* no lifnum */ if (ifa->ifam_index == 0) { nlog(LOG_DEBUG, "tossing index 0 message"); break; } if (ifa->ifam_type != rtm->rtm_type) { nlog(LOG_DEBUG, "routing_events_v6: unhandled type %d", ifa->ifam_type); break; } printaddrs(ifa->ifam_addrs, addrs); /* Create and enqueue IF_STATE event */ ip_event = nwamd_event_init_if_state(if_name, ifa->ifam_flags, (rtm->rtm_type == RTM_NEWADDR || rtm->rtm_type == RTM_CHGADDR ? B_TRUE : B_FALSE), addr, netmask); if (ip_event != NULL) nwamd_event_enqueue(ip_event); break; } } /* NOTREACHED */ return (NULL); } void nwamd_routing_events_init(void) { pthread_attr_t attr; /* * Initialize routing sockets here so that we know the routing threads * (and any requests to add a route) will be working with a valid socket * by the time we start handling events. */ v4_sock = socket(AF_ROUTE, SOCK_RAW, AF_INET); if (v4_sock == -1) pfail("failed to open v4 routing socket: %m"); v6_sock = socket(AF_ROUTE, SOCK_RAW, AF_INET6); if (v6_sock == -1) pfail("failed to open v6 routing socket: %m"); (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create(&v4_routing, &attr, routing_events_v4, NULL) != 0 || pthread_create(&v6_routing, &attr, routing_events_v6, NULL) != 0) pfail("routing thread creation failed"); (void) pthread_attr_destroy(&attr); } void nwamd_routing_events_fini(void) { (void) pthread_cancel(v4_routing); (void) pthread_cancel(v6_routing); } void nwamd_add_route(struct sockaddr *dest, struct sockaddr *mask, struct sockaddr *gateway, const char *ifname) { char rtbuf[RTMBUFSZ]; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct rt_msghdr *rtm = (struct rt_msghdr *)rtbuf; void *addrs = rtbuf + sizeof (struct rt_msghdr); struct sockaddr_dl sdl; int rlen, index; int af; af = gateway->sa_family; /* retrieve the index value for the interface */ if ((index = if_nametoindex(ifname)) == 0) { nlog(LOG_ERR, "nwamd_add_route: if_nametoindex failed on %s", ifname); return; } (void) bzero(&sdl, sizeof (struct sockaddr_dl)); sdl.sdl_family = AF_LINK; sdl.sdl_index = index; (void) bzero(rtm, RTMBUFSZ); rtm->rtm_pid = getpid(); rtm->rtm_type = RTM_ADD; rtm->rtm_flags = RTF_UP | RTF_STATIC | RTF_GATEWAY; rtm->rtm_version = RTM_VERSION; rtm->rtm_seq = ++seq; rtm->rtm_msglen = sizeof (rtbuf); setaddr(RTA_DST, &rtm->rtm_addrs, &addrs, dest); setaddr(RTA_GATEWAY, &rtm->rtm_addrs, &addrs, gateway); setaddr(RTA_NETMASK, &rtm->rtm_addrs, &addrs, mask); setaddr(RTA_IFP, &rtm->rtm_addrs, &addrs, (struct sockaddr *)&sdl); if ((rlen = write(af == AF_INET ? v4_sock : v6_sock, rtbuf, rtm->rtm_msglen)) < 0) { nlog(LOG_ERR, "nwamd_add_route: " "got error %s writing to routing socket", strerror(errno)); } else if (rlen < rtm->rtm_msglen) { nlog(LOG_ERR, "nwamd_add_route: " "only wrote %d bytes of %d to routing socket\n", rlen, rtm->rtm_msglen); } } static char * printaddr(void **address) { static char buffer[80]; sa_family_t family = *(sa_family_t *)*address; struct sockaddr_in *s4 = *address; struct sockaddr_in6 *s6 = *address; struct sockaddr_dl *dl = *address; switch (family) { case AF_UNSPEC: (void) inet_ntop(AF_UNSPEC, &s4->sin_addr, buffer, sizeof (buffer)); *address = (char *)*address + sizeof (*s4); break; case AF_INET: (void) inet_ntop(AF_INET, &s4->sin_addr, buffer, sizeof (buffer)); *address = (char *)*address + sizeof (*s4); break; case AF_INET6: (void) inet_ntop(AF_INET6, &s6->sin6_addr, buffer, sizeof (buffer)); *address = (char *)*address + sizeof (*s6); break; case AF_LINK: (void) snprintf(buffer, sizeof (buffer), "link %.*s", dl->sdl_nlen, dl->sdl_data); *address = (char *)*address + sizeof (*dl); break; default: /* * We can't reliably update the size of this thing * because we don't know what its type is. So bump * it by a sockaddr_in and see what happens. The * caller should really make sure this never happens. */ *address = (char *)*address + sizeof (*s4); (void) snprintf(buffer, sizeof (buffer), "unknown address family %d", family); break; } return (buffer); } static void printaddrs(int mask, void *address) { if (mask == 0) return; if (mask & RTA_DST) nlog(LOG_DEBUG, "destination address: %s", printaddr(&address)); if (mask & RTA_GATEWAY) nlog(LOG_DEBUG, "gateway address: %s", printaddr(&address)); if (mask & RTA_NETMASK) nlog(LOG_DEBUG, "netmask: %s", printaddr(&address)); if (mask & RTA_GENMASK) nlog(LOG_DEBUG, "cloning mask: %s", printaddr(&address)); if (mask & RTA_IFP) nlog(LOG_DEBUG, "interface name: %s", printaddr(&address)); if (mask & RTA_IFA) nlog(LOG_DEBUG, "interface address: %s", printaddr(&address)); if (mask & RTA_AUTHOR) nlog(LOG_DEBUG, "author: %s", printaddr(&address)); if (mask & RTA_BRD) nlog(LOG_DEBUG, "broadcast address: %s", printaddr(&address)); } static void nextaddr(void **address) { sa_family_t family = *(sa_family_t *)*address; switch (family) { case AF_UNSPEC: case AF_INET: *address = (char *)*address + sizeof (struct sockaddr_in); break; case AF_INET6: *address = (char *)*address + sizeof (struct sockaddr_in6); break; case AF_LINK: *address = (char *)*address + sizeof (struct sockaddr_dl); break; default: nlog(LOG_ERR, "unknown af (%d) while parsing rtm", family); break; } } static void * getaddr(int addrid, int mask, void *addresses) { int i; void *p = addresses; if ((mask & addrid) == 0) return (NULL); for (i = 1; i < addrid; i <<= 1) { if (i & mask) nextaddr(&p); } return (p); } static void setaddr(int addrid, int *maskp, void *addressesp, struct sockaddr *address) { struct sockaddr *p = *((struct sockaddr **)addressesp); *maskp |= addrid; switch (address->sa_family) { case AF_INET: (void) memcpy(p, address, sizeof (struct sockaddr_in)); break; case AF_INET6: (void) memcpy(p, address, sizeof (struct sockaddr_in6)); break; case AF_LINK: (void) memcpy(p, address, sizeof (struct sockaddr_dl)); break; default: nlog(LOG_ERR, "setaddr: unknown af (%d) while setting addr", address->sa_family); break; } nextaddr(addressesp); }