/* * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define KERNEL_PRIVATE #include #undef KERNEL_PRIVATE #include #include "timer.h" #include "configthreads_common.h" #include "ip6config_utils.h" #ifdef LLOCAL_DEBUG #define LOG_DEBUG LOG_ERR #endif typedef struct { timer_callout_t * timer; } Service_llocal_t; typedef struct { struct rt_msghdr m_rtm; char m_space[512]; } llocal_rtmsg_t; static int siocprotoattach(int s, char * name) { struct in6_aliasreq ifra; bzero(&ifra, sizeof(ifra)); strncpy(ifra.ifra_name, name, sizeof(ifra.ifra_name)); return (ioctl(s, SIOCPROTOATTACH_IN6, &ifra)); } static int siocprotodetach(int s, char * name) { struct in6_ifreq ifr; bzero(&ifr, sizeof(ifr)); strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); return (ioctl(s, SIOCPROTODETACH_IN6, &ifr)); } static int linklocal_start(int s, char * name) { struct in6_aliasreq ifra_in6; bzero(&ifra_in6, sizeof(ifra_in6)); strncpy(ifra_in6.ifra_name, name, sizeof(ifra_in6.ifra_name)); return (ioctl(s, SIOCLL_START, &ifra_in6)); } static int linklocal_stop(int s, char * name) { struct in6_ifreq ifr; bzero(&ifr, sizeof(ifr)); strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); return (ioctl(s, SIOCLL_STOP, &ifr)); } /* * The following clears non-permanent routes added through * neighbor discovery * (from ndp.c) */ static const struct sockaddr_in6 blank_sin = {sizeof(blank_sin), AF_INET6 }; static int send_rtmsg(int s, int cmd, llocal_rtmsg_t *m_rtmsg, struct sockaddr_in6 *sin_m) { static int pid = 0; int rlen; static int seq; register int l; register char *cp = m_rtmsg->m_space; register struct rt_msghdr *rtm = &m_rtmsg->m_rtm; if (pid == 0) { pid = getpid(); } errno = 0; if (cmd == RTM_DELETE) goto doit; bzero((char *)m_rtmsg, sizeof(*m_rtmsg)); rtm->rtm_flags = 0; rtm->rtm_version = RTM_VERSION; if (cmd == RTM_GET) { rtm->rtm_addrs = RTA_DST; } if (sin_m) { bcopy((char *)sin_m, cp, sizeof(*sin_m)); cp += sizeof(*sin_m); rtm->rtm_msglen = cp - (char *)m_rtmsg; } doit: l = rtm->rtm_msglen; rtm->rtm_seq = ++seq; rtm->rtm_type = cmd; if ((rlen = write(s, (char *)m_rtmsg, l)) < 0) { if (errno != ESRCH || cmd != RTM_DELETE) { my_log(LOG_ERR, "rtmsg: error writing to routing socket"); return (-1); } } do { l = read(s, (char *)m_rtmsg, sizeof(*m_rtmsg)); } while (l > 0 && (rtm->rtm_seq != seq || rtm->rtm_pid != pid)); if (l < 0) { my_log(LOG_ERR, "rtmsg: error reading from routing socket: %s\n", strerror(errno)); } return (0); } /* packing rule for routing socket */ #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) static void delete_ndroute(struct sockaddr_in6 *sin_del) { llocal_rtmsg_t m_rtmsg; struct sockaddr_in6 sin_m; register struct rt_msghdr *rtm = &m_rtmsg.m_rtm; struct sockaddr_in6 *sin = &sin_m; struct sockaddr_dl *sdl; int s = inet6_routing_socket(); if (s < 0) { my_log(LOG_ERR, "delete_ndroute: error opening routing socket: %s (%d)", strerror(errno), errno); return; } my_log(LOG_DEBUG, "delete_ndroute: ", IP6_FORMAT, IP6_LIST(&sin_del->sin6_addr)); sin_m = blank_sin; sin->sin6_addr = sin_del->sin6_addr; if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) { *(u_int16_t *)&sin->sin6_addr.s6_addr[2] = sin_del->sin6_scope_id; } if (send_rtmsg(s, RTM_GET, &m_rtmsg, &sin_m) != 0) { goto bad; } sin = (struct sockaddr_in6 *)(rtm + 1); sdl = (struct sockaddr_dl *)(ROUNDUP(sin->sin6_len) + (char *)sin); if (!IN6_ARE_ADDR_EQUAL(&sin->sin6_addr, &sin_m.sin6_addr)) { goto bad; } else { if (sdl->sdl_family == AF_LINK && (rtm->rtm_flags & RTF_LLINFO) && !(rtm->rtm_flags & RTF_GATEWAY)) { if (sdl->sdl_family != AF_LINK) { my_log(LOG_DEBUG, "cannot locate ", IP6_FORMAT, IP6_LIST(&sin_del->sin6_addr)); goto bad; } if (send_rtmsg(s, RTM_DELETE, &m_rtmsg, NULL) == 0) { struct sockaddr_in6 s6 = *sin; /* XXX: for safety */ if (IN6_IS_ADDR_LINKLOCAL(&s6.sin6_addr)) { s6.sin6_scope_id = ntohs(*(u_int16_t *)&s6.sin6_addr.s6_addr[2]); *(u_int16_t *)&s6.sin6_addr.s6_addr[2] = 0; } #if LLOCAL_DEBUG { char host_buf[NI_MAXHOST]; getnameinfo((struct sockaddr *)&s6, s6.sin6_len, host_buf, sizeof(host_buf), NULL, 0, NI_WITHSCOPEID); my_log(LOG_DEBUG, "%s (%s) deleted\n", host, host_buf); } #endif /* LLOCAL_DEBUG */ } } else { my_log(LOG_ERR, "delete_ndroute: cannot delete non-NDP entry\n"); goto bad; } } bad: if (s > 0) { close(s); } return; } static int linklocal_flush_ndroutes(void) { int err = 0; int mib[6]; size_t needed; char *lim, *buf = NULL, *next; struct rt_msghdr *rtm; struct sockaddr_in6 *sin; struct sockaddr_dl *sdl; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = AF_INET6; mib[4] = NET_RT_FLAGS; mib[5] = RTF_LLINFO; if ((sysctl(mib, 6, NULL, &needed, NULL, 0)) < 0) { err = errno; my_log(LOG_DEBUG, "linklocal_flush_ndroutes: sysctl(PF_ROUTE estimate): %s, (%d)", strerror(errno), errno); goto done; } if (needed > 0) { if ((buf = malloc(needed)) == NULL) { err = ENOMEM; my_log(LOG_DEBUG, "linklocal_flush_ndroutes: malloc failed"); goto done; } if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { err = errno; my_log(LOG_DEBUG, "linklocal_flush_ndroutes: sysctl(PF_ROUTE, NET_RT_FLAGS): %s, (%d)", strerror(errno), errno); goto done; } lim = buf + needed; } else buf = lim = NULL; for (next = buf; next && next < lim; next += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)next; sin = (struct sockaddr_in6 *)(rtm + 1); sdl = (struct sockaddr_dl *)((char *)sin + ROUNDUP(sin->sin6_len)); /* * Some OSes can produce a route that has the LINK flag but * has a non-AF_LINK gateway (e.g. fe80::xx%lo0 on FreeBSD * and BSD/OS, where xx is not the interface identifier on * lo0). * XXX: such routes should have the GATEWAY flag, not the * LINK flag. However, there are rotten routing software * that advertises all routes that have the GATEWAY flag. * Thus, KAME kernel intentionally does not set the LINK flag. * What is to be fixed is not ndp, but such routing software * (and the kernel workaround)... */ if (sdl->sdl_family != AF_LINK) continue; if (IN6_IS_ADDR_MULTICAST(&sin->sin6_addr)) continue; if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr) || IN6_IS_ADDR_MC_LINKLOCAL(&sin->sin6_addr)) { if (sin->sin6_scope_id == 0) sin->sin6_scope_id = sdl->sdl_index; /* KAME specific hack; removed the embedded id */ *(u_int16_t *)&sin->sin6_addr.s6_addr[2] = 0; } if (rtm->rtm_flags & RTF_WASCLONED) { delete_ndroute(sin); } continue; } done: if (buf) free(buf); return err; } static int inet6_attach_interface(int s, char * ifname) { int ret = 0; if (siocprotoattach(s, ifname) < 0) { ret = errno; my_log(LOG_DEBUG, "siocprotoattach(%s) failed, %s (%d)", ifname, strerror(errno), errno); } if (ifflags_set(s, ifname, IFF_UP) < 0) { my_log(LOG_DEBUG, "inet6_attach_interface %s: ifflags_set failed", ifname, strerror(errno), errno); } done: return (ret); } static int inet6_detach_interface(char * ifname) { int ret = 0; int s = inet6_dgram_socket(); if (s < 0) { ret = errno; goto done; } my_log(LOG_DEBUG, "inet6_detach_interface %s", ifname); if (linklocal_stop(s, ifname) < 0) { ret = errno; my_log(LOG_DEBUG, "linklocal_stop(%s) failed, %s (%d)", ifname, strerror(errno), errno); } if (siocprotodetach(s, ifname) < 0) { ret = errno; my_log(LOG_DEBUG, "siocprotodetach(%s) failed, %s (%d)", ifname, strerror(errno), errno); } if (linklocal_flush_ndroutes() != 0) { ret = errno; my_log(LOG_DEBUG, "linklocal_flush_ndroutes(%s) failed, %s (%d)", ifname, strerror(errno), errno); } close(s); done: return (ret); } static void llocal_cancel_pending_events(Service_t * service_p) { Service_llocal_t * llocal = (Service_llocal_t *)service_p->private; if (llocal == NULL) return; if (llocal->timer) { timer_cancel(llocal->timer); } return; } static void llocal_link_timer(void * arg0, void * arg1, void * arg2) { Service_t * service_p = (Service_t *)arg0; interface_t * if_p = service_interface(service_p); int s = inet6_dgram_socket(); if (s < 0) { return; } my_log(LOG_DEBUG, "llocal_link_timer %s", if_name(if_p)); /* stop linklocal */ if (linklocal_stop(s, if_name(if_p)) != 0) { my_log(LOG_ERR, "LINKLOCAL: error stopping linklocal on interface %s", if_name(if_p)); } if (linklocal_flush_ndroutes() != 0) { my_log(LOG_ERR, "linklocal_ndflush(%s) failed", if_name(if_p)); } close(s); return; } __private_extern__ ip6config_status_t linklocal_thread(Service_t * service_p, IFEventID_t evid, void * event_data) { interface_t * if_p = service_interface(service_p); Service_llocal_t * llocal = (Service_llocal_t *)service_p->private; ip6config_status_t status = ip6config_status_success_e; switch (evid) { case IFEventID_start_e: { my_log(LOG_DEBUG, "LINKLOCAL %s: STARTING", if_name(if_p)); if (llocal) { my_log(LOG_DEBUG, "LINKLOCAL %s: re-entering start state", if_name(if_p)); status = ip6config_status_internal_error_e; break; } llocal = calloc(1, sizeof(*llocal)); if (llocal == NULL) { my_log(LOG_ERR, "LINKLOCAL %s: calloc failed", if_name(if_p)); status = ip6config_status_allocation_failed_e; break; } service_p->private = llocal; llocal->timer = timer_callout_init(); if (llocal->timer == NULL) { my_log(LOG_ERR, "LINKLOCAL %s: timer_callout_init failed", if_name(if_p)); status = ip6config_status_allocation_failed_e; goto stop; } /* attach interface and start linklocal*/ { int s = inet6_dgram_socket(); int ret; if (s < 0) { status = ip6config_status_internal_error_e; goto stop; } ret = inet6_attach_interface(s, if_name(if_p)); if (ret != 0) { my_log(LOG_ERR, "LINKLOCAL: inet6_attach_interface(%s) failed, %s (%d)", if_name(if_p), strerror(ret), ret); } else if (service_link_status(service_p)->valid == TRUE) { if (service_link_status(service_p)->active == TRUE) { if (linklocal_start(s, if_name(if_p)) < 0) { my_log(LOG_DEBUG, "linklocal_start(%s) failed, %s (%d)", if_name(if_p), strerror(errno), errno); } } } close(s); } break; } stop: case IFEventID_stop_e: { my_log(LOG_DEBUG, "LINKLOCAL %s: STOPPING", if_name(if_p)); if (llocal == NULL) { my_log(LOG_DEBUG, "LINKLOCAL %s: private data is NULL", if_name(if_p)); status = ip6config_status_internal_error_e; break; } /* clean-up resources */ if (llocal->timer) { timer_callout_free(&llocal->timer); } /* stop linklocal and detach interface */ if (inet6_detach_interface(if_name(if_p)) != 0) { my_log(LOG_DEBUG, "LINKLOCAL: error detaching interface %s", if_name(if_p)); } free(llocal); service_p->private = NULL; break; } case IFEventID_media_e: { my_log(LOG_DEBUG, "LINKLOCAL %s: MEDIA CHANGE", if_name(if_p)); if (llocal == NULL) return (ip6config_status_internal_error_e); if (service_link_status(service_p)->valid == TRUE) { if (service_link_status(service_p)->active == TRUE) { /* start linklocal */ int s = inet6_dgram_socket(); llocal_cancel_pending_events(service_p); if (s < 0) { return (ip6config_status_internal_error_e); } if (linklocal_start(s, if_name(if_p)) != 0) { my_log(LOG_ERR, "LINKLOCAL: error starting linklocal on interface %s", if_name(if_p)); } close(s); } else { struct timeval tv; /* if link goes down and stays down long enough, unpublish */ llocal_cancel_pending_events(service_p); tv.tv_sec = LINK_INACTIVE_WAIT_SECS; tv.tv_usec = 0; timer_set_relative(llocal->timer, tv, (timer_func_t *)llocal_link_timer, service_p, NULL, NULL); } } break; } default: break; } /* switch */ return (status); }