X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/de355530ae67247cbd0da700edb3a2a1dae884c2..6d2010ae8f7a6078e10b361c6962983bab233e0f:/bsd/netinet/in.c diff --git a/bsd/netinet/in.c b/bsd/netinet/in.c index c950c9931..85b9d38af 100644 --- a/bsd/netinet/in.c +++ b/bsd/netinet/in.c @@ -1,23 +1,29 @@ /* - * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000-2011 Apple Inc. All rights reserved. * - * @APPLE_LICENSE_HEADER_START@ + * @APPLE_OSREFERENCE_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 file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The 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. + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * - * @APPLE_LICENSE_HEADER_END@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1982, 1986, 1991, 1993 @@ -65,10 +71,16 @@ #include #include #include +#include +#include +#include + +#include #include #include #include +#include #include #include @@ -85,28 +97,123 @@ #include +#if PF +#include +#endif /* PF */ + +static int in_mask2len(struct in_addr *); +static void in_len2mask(struct in_addr *, int); +static int in_lifaddr_ioctl(struct socket *, u_long, caddr_t, + struct ifnet *, struct proc *); + +static void in_socktrim(struct sockaddr_in *); +static int in_ifinit(struct ifnet *, + struct in_ifaddr *, struct sockaddr_in *, int); + +#define IA_HASH_INIT(ia) { \ + (ia)->ia_hash.tqe_next = (void *)(uintptr_t)-1; \ + (ia)->ia_hash.tqe_prev = (void *)(uintptr_t)-1; \ +} -static int in_mask2len __P((struct in_addr *)); -static void in_len2mask __P((struct in_addr *, int)); -static int in_lifaddr_ioctl __P((struct socket *, u_long, caddr_t, - struct ifnet *, struct proc *)); +#define IA_IS_HASHED(ia) \ + (!((ia)->ia_hash.tqe_next == (void *)(uintptr_t)-1 || \ + (ia)->ia_hash.tqe_prev == (void *)(uintptr_t)-1)) -static void in_socktrim __P((struct sockaddr_in *)); -static int in_ifinit __P((struct ifnet *, - struct in_ifaddr *, struct sockaddr_in *, int)); +static void in_iahash_remove(struct in_ifaddr *); +static void in_iahash_insert(struct in_ifaddr *); +static void in_iahash_insert_ptp(struct in_ifaddr *); +static struct in_ifaddr *in_ifaddr_alloc(int); +static void in_ifaddr_attached(struct ifaddr *); +static void in_ifaddr_detached(struct ifaddr *); +static void in_ifaddr_free(struct ifaddr *); +static void in_ifaddr_trace(struct ifaddr *, int); static int subnetsarelocal = 0; -SYSCTL_INT(_net_inet_ip, OID_AUTO, subnets_are_local, CTLFLAG_RW, +SYSCTL_INT(_net_inet_ip, OID_AUTO, subnets_are_local, CTLFLAG_RW | CTLFLAG_LOCKED, &subnetsarelocal, 0, ""); -struct in_multihead in_multihead; /* XXX BSS initialization */ +/* Track whether or not the SIOCARPIPLL ioctl has been called */ +__private_extern__ u_int32_t ipv4_ll_arp_aware = 0; -extern void arp_rtrequest(); -extern int ether_detach_inet(struct ifnet *ifp); +#define INIFA_TRACE_HIST_SIZE 32 /* size of trace history */ -#if INET6 -extern int ip6_auto_on; -#endif +/* For gdb */ +__private_extern__ unsigned int inifa_trace_hist_size = INIFA_TRACE_HIST_SIZE; + +struct in_ifaddr_dbg { + struct in_ifaddr inifa; /* in_ifaddr */ + struct in_ifaddr inifa_old; /* saved in_ifaddr */ + u_int16_t inifa_refhold_cnt; /* # of IFA_ADDREF */ + u_int16_t inifa_refrele_cnt; /* # of IFA_REMREF */ + /* + * Alloc and free callers. + */ + ctrace_t inifa_alloc; + ctrace_t inifa_free; + /* + * Circular lists of IFA_ADDREF and IFA_REMREF callers. + */ + ctrace_t inifa_refhold[INIFA_TRACE_HIST_SIZE]; + ctrace_t inifa_refrele[INIFA_TRACE_HIST_SIZE]; + /* + * Trash list linkage + */ + TAILQ_ENTRY(in_ifaddr_dbg) inifa_trash_link; +}; + +/* List of trash in_ifaddr entries protected by inifa_trash_lock */ +static TAILQ_HEAD(, in_ifaddr_dbg) inifa_trash_head; +static decl_lck_mtx_data(, inifa_trash_lock); + +#if DEBUG +static unsigned int inifa_debug = 1; /* debugging (enabled) */ +#else +static unsigned int inifa_debug; /* debugging (disabled) */ +#endif /* !DEBUG */ +static unsigned int inifa_size; /* size of zone element */ +static struct zone *inifa_zone; /* zone for in_ifaddr */ + +#define INIFA_ZONE_MAX 64 /* maximum elements in zone */ +#define INIFA_ZONE_NAME "in_ifaddr" /* zone name */ + +/* + * Return 1 if the address is + * - loopback + * - unicast or multicast link local + * - routed via a link level gateway + * - belongs to a directly connected (sub)net + */ +int +inaddr_local(struct in_addr in) +{ + struct rtentry *rt; + struct sockaddr_in sin; + int local = 0; + + if (ntohl(in.s_addr) == INADDR_LOOPBACK || IN_LINKLOCAL(ntohl(in.s_addr))) { + local = 1; + } else if (ntohl(in.s_addr) >= INADDR_UNSPEC_GROUP && + ntohl(in.s_addr) <= INADDR_MAX_LOCAL_GROUP) { + local = 1; + } else { + sin.sin_family = AF_INET; + sin.sin_len = sizeof (sin); + sin.sin_addr = in; + rt = rtalloc1((struct sockaddr *)&sin, 0, 0); + + if (rt != NULL) { + RT_LOCK_SPIN(rt); + if (rt->rt_gateway->sa_family == AF_LINK || + (rt->rt_ifp->if_flags & IFF_LOOPBACK)) + local = 1; + RT_UNLOCK(rt); + rtfree(rt); + } else { + local = in_localaddr(in); + } + } + return (local); +} /* * Return 1 if an internet address is for a ``local'' host @@ -115,22 +222,37 @@ extern int ip6_auto_on; * Otherwise, it includes only the directly-connected (sub)nets. */ int -in_localaddr(in) - struct in_addr in; +in_localaddr(struct in_addr in) { - register u_long i = ntohl(in.s_addr); - register struct in_ifaddr *ia; + u_int32_t i = ntohl(in.s_addr); + struct in_ifaddr *ia; if (subnetsarelocal) { + lck_rw_lock_shared(in_ifaddr_rwlock); for (ia = in_ifaddrhead.tqh_first; ia; - ia = ia->ia_link.tqe_next) - if ((i & ia->ia_netmask) == ia->ia_net) + ia = ia->ia_link.tqe_next) { + IFA_LOCK(&ia->ia_ifa); + if ((i & ia->ia_netmask) == ia->ia_net) { + IFA_UNLOCK(&ia->ia_ifa); + lck_rw_done(in_ifaddr_rwlock); return (1); + } + IFA_UNLOCK(&ia->ia_ifa); + } + lck_rw_done(in_ifaddr_rwlock); } else { + lck_rw_lock_shared(in_ifaddr_rwlock); for (ia = in_ifaddrhead.tqh_first; ia; - ia = ia->ia_link.tqe_next) - if ((i & ia->ia_subnetmask) == ia->ia_subnet) + ia = ia->ia_link.tqe_next) { + IFA_LOCK(&ia->ia_ifa); + if ((i & ia->ia_subnetmask) == ia->ia_subnet) { + IFA_UNLOCK(&ia->ia_ifa); + lck_rw_done(in_ifaddr_rwlock); return (1); + } + IFA_UNLOCK(&ia->ia_ifa); + } + lck_rw_done(in_ifaddr_rwlock); } return (0); } @@ -141,11 +263,10 @@ in_localaddr(in) * may be forwarded. */ int -in_canforward(in) - struct in_addr in; +in_canforward(struct in_addr in) { - register u_long i = ntohl(in.s_addr); - register u_long net; + u_int32_t i = ntohl(in.s_addr); + u_int32_t net; if (IN_EXPERIMENTAL(i) || IN_MULTICAST(i)) return (0); @@ -161,11 +282,10 @@ in_canforward(in) * Trim a mask in a sockaddr */ static void -in_socktrim(ap) -struct sockaddr_in *ap; +in_socktrim(struct sockaddr_in *ap) { - register char *cplim = (char *) &ap->sin_addr; - register char *cp = (char *) (&ap->sin_addr + 1); + char *cplim = (char *) &ap->sin_addr; + char *cp = (char *) (&ap->sin_addr + 1); ap->sin_len = 0; while (--cp >= cplim) @@ -176,10 +296,9 @@ struct sockaddr_in *ap; } static int -in_mask2len(mask) - struct in_addr *mask; +in_mask2len(struct in_addr *mask) { - int x, y; + size_t x, y; u_char *p; p = (u_char *)mask; @@ -198,9 +317,7 @@ in_mask2len(mask) } static void -in_len2mask(mask, len) - struct in_addr *mask; - int len; +in_len2mask(struct in_addr *mask, int len) { int i; u_char *p; @@ -215,35 +332,63 @@ in_len2mask(mask, len) static int in_interfaces; /* number of external internet interfaces */ +static int +in_domifattach(struct ifnet *ifp) +{ + int error; + + if ((error = proto_plumb(PF_INET, ifp)) && error != EEXIST) + log(LOG_ERR, "%s: proto_plumb returned %d if=%s%d\n", + __func__, error, ifp->if_name, ifp->if_unit); + + return (error); +} + /* * Generic internet control operations (ioctl's). * Ifp is 0 if not an interface-specific ioctl. + * + * Returns: 0 Success + * EINVAL + * EADDRNOTAVAIL + * EDESTADDRREQ + * EPERM + * ENOBUFS + * EBUSY + * EOPNOTSUPP + * proc_suser:EPERM + * suser:EPERM + * in_lifaddr_ioctl:??? + * dlil_ioctl:??? + * in_ifinit:??? + * dlil_plumb_protocol:??? + * dlil_unplumb_protocol:??? */ /* ARGSUSED */ int -in_control(so, cmd, data, ifp, p) - struct socket *so; - u_long cmd; - caddr_t data; - register struct ifnet *ifp; - struct proc *p; +in_control( + struct socket *so, + u_long cmd, + caddr_t data, + struct ifnet *ifp, + struct proc *p) { - register struct ifreq *ifr = (struct ifreq *)data; - register struct in_ifaddr *ia = 0, *iap; - register struct ifaddr *ifa; - struct in_ifaddr *oia; + struct ifreq *ifr = (struct ifreq *)data; + struct in_ifaddr *ia = NULL, *iap; + struct ifaddr *ifa; struct in_aliasreq *ifra = (struct in_aliasreq *)data; struct sockaddr_in oldaddr; - int error, hostIsNew, maskIsNew, s; - u_long i, dl_tag; + int error = 0; + int hostIsNew, maskIsNew; struct kev_msg ev_msg; struct kev_in_data in_event_data; - + bzero(&in_event_data, sizeof(struct kev_in_data)); + bzero(&ev_msg, sizeof(struct kev_msg)); switch (cmd) { case SIOCALIFADDR: case SIOCDLIFADDR: - if (p && (error = suser(p->p_ucred, &p->p_acflag)) != 0) + if ((error = proc_suser(p)) != 0) return error; /*fall through*/ case SIOCGLIFADDR: @@ -258,103 +403,119 @@ in_control(so, cmd, data, ifp, p) * If an alias address was specified, find that one instead of * the first one on the interface. */ - if (ifp) + if (ifp) { + lck_rw_lock_shared(in_ifaddr_rwlock); for (iap = in_ifaddrhead.tqh_first; iap; iap = iap->ia_link.tqe_next) if (iap->ia_ifp == ifp) { + IFA_LOCK(&iap->ia_ifa); if (((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr == iap->ia_addr.sin_addr.s_addr) { ia = iap; + IFA_UNLOCK(&iap->ia_ifa); break; } else if (ia == NULL) { ia = iap; - if (ifr->ifr_addr.sa_family != AF_INET) + if (ifr->ifr_addr.sa_family != AF_INET) { + IFA_UNLOCK(&iap->ia_ifa); break; + } } + IFA_UNLOCK(&iap->ia_ifa); } - + /* take a reference on ia before releasing lock */ + if (ia != NULL) { + IFA_ADDREF(&ia->ia_ifa); + } + lck_rw_done(in_ifaddr_rwlock); + } switch (cmd) { case SIOCAUTOADDR: - if (p && (error = suser(p->p_ucred, &p->p_acflag)) != 0) - return error; + case SIOCARPIPLL: + if ((error = proc_suser(p)) != 0) { + goto done; + } + if (ifp == 0) { + error = EADDRNOTAVAIL; + goto done; + } break; case SIOCAIFADDR: case SIOCDIFADDR: - if (ifp == 0) - return (EADDRNOTAVAIL); + if (ifp == 0) { + error = EADDRNOTAVAIL; + goto done; + } if (ifra->ifra_addr.sin_family == AF_INET) { + struct in_ifaddr *oia; + + lck_rw_lock_shared(in_ifaddr_rwlock); for (oia = ia; ia; ia = ia->ia_link.tqe_next) { + IFA_LOCK(&ia->ia_ifa); if (ia->ia_ifp == ifp && ia->ia_addr.sin_addr.s_addr == - ifra->ifra_addr.sin_addr.s_addr) + ifra->ifra_addr.sin_addr.s_addr) { + IFA_ADDREF_LOCKED(&ia->ia_ifa); + IFA_UNLOCK(&ia->ia_ifa); break; + } + IFA_UNLOCK(&ia->ia_ifa); } + lck_rw_done(in_ifaddr_rwlock); + if (oia != NULL) + IFA_REMREF(&oia->ia_ifa); if ((ifp->if_flags & IFF_POINTOPOINT) && (cmd == SIOCAIFADDR) && (ifra->ifra_dstaddr.sin_addr.s_addr == INADDR_ANY)) { - return EDESTADDRREQ; + error = EDESTADDRREQ; + goto done; } } - else if (cmd == SIOCAIFADDR) - return (EINVAL); - if (cmd == SIOCDIFADDR && ia == 0) - return (EADDRNOTAVAIL); + else if (cmd == SIOCAIFADDR) { + error = EINVAL; + goto done; + } + if (cmd == SIOCDIFADDR && ia == 0) { + error = EADDRNOTAVAIL; + goto done; + } /* FALLTHROUGH */ case SIOCSIFADDR: case SIOCSIFNETMASK: case SIOCSIFDSTADDR: -#ifdef __APPLE__ - if ((so->so_state & SS_PRIV) == 0) - return (EPERM); -#else - if (p && (error = suser(p)) != 0) - return error; -#endif - - if (ifp == 0) - return (EADDRNOTAVAIL); - if (ifra->ifra_addr.sin_family != AF_INET && cmd == SIOCSIFADDR) - return (EINVAL); - if (ia == (struct in_ifaddr *)0) { - ia = (struct in_ifaddr *) - _MALLOC(sizeof *ia, M_IFADDR, M_WAITOK); - if (ia == (struct in_ifaddr *)NULL) - return (ENOBUFS); - bzero((caddr_t)ia, sizeof *ia); - /* - * Protect from ipintr() traversing address list - * while we're modifying it. - */ - s = splnet(); - - TAILQ_INSERT_TAIL(&in_ifaddrhead, ia, ia_link); + /* socket is NULL if called from in_purgeaddrs() */ + if (so != NULL && (so->so_state & SS_PRIV) == 0) { + error = EPERM; + goto done; + } + /* in case it's NULL, make sure it came from the kernel */ + if (so == NULL && p != kernproc) { + error = EPERM; + goto done; + } + if (ifp == 0) { + error = EADDRNOTAVAIL; + goto done; + } + if (ifra->ifra_addr.sin_family != AF_INET + && cmd == SIOCSIFADDR) { + error = EINVAL; + goto done; + } + if (ia == NULL) { + ia = in_ifaddr_alloc(M_WAITOK); + if (ia == NULL) { + error = ENOBUFS; + goto done; + } + ifnet_lock_exclusive(ifp); ifa = &ia->ia_ifa; - TAILQ_INSERT_TAIL(&ifp->if_addrhead, ifa, ifa_link); - -/* - * Temorary code for protocol attachment XXX - */ - - if (ifp->if_type == IFT_ETHER) - dl_tag = ether_attach_inet(ifp); - - if (ifp->if_type == IFT_LOOP) - dl_tag = lo_attach_inet(ifp); -#if NFAITH - /* Is this right? */ - if (ifp && ifp->if_type == IFT_FAITH) - dl_tag = faith_attach_inet(ifp); -#endif -#if NGIF - /* Is this right? */ - if (ifp && ifp->if_type == IFT_GIF) - dl_tag = gif_attach_proto_family(ifp, PF_INET); -#endif -/* End of temp code */ - - ifa->ifa_dlt = dl_tag; + IFA_LOCK(ifa); + /* Hold a reference for this routine */ + IFA_ADDREF_LOCKED(ifa); + IA_HASH_INIT(ia); ifa->ifa_addr = (struct sockaddr *)&ia->ia_addr; ifa->ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; ifa->ifa_netmask = (struct sockaddr *)&ia->ia_sockmask; @@ -366,86 +527,137 @@ in_control(so, cmd, data, ifp, p) ia->ia_ifp = ifp; if (!(ifp->if_flags & IFF_LOOPBACK)) in_interfaces++; - splx(s); + /* if_attach_ifa() holds a reference for ifa_link */ + if_attach_ifa(ifp, ifa); + /* + * If we have to go through in_ifinit(), make sure + * to avoid installing route(s) based on this address + * via PFC_IFUP event, before the link resolver (ARP) + * initializes it. + */ + if (cmd == SIOCAIFADDR || cmd == SIOCSIFADDR) + ifa->ifa_debug |= IFD_NOTREADY; + IFA_UNLOCK(ifa); + ifnet_lock_done(ifp); + lck_rw_lock_exclusive(in_ifaddr_rwlock); + /* Hold a reference for ia_link */ + IFA_ADDREF(ifa); + TAILQ_INSERT_TAIL(&in_ifaddrhead, ia, ia_link); + lck_rw_done(in_ifaddr_rwlock); + error = in_domifattach(ifp); + /* discard error,can be cold with unsupported interfaces */ + if (error) + error = 0; } break; case SIOCPROTOATTACH: case SIOCPROTODETACH: - if (p && (error = suser(p->p_ucred, &p->p_acflag)) != 0) - return error; - if (ifp == 0) - return (EADDRNOTAVAIL); - if (strcmp(ifp->if_name, "en")) - return ENODEV; + if ((error = proc_suser(p)) != 0) { + goto done; + } + if (ifp == 0) { + error = EADDRNOTAVAIL; + goto done; + } break; - + case SIOCSIFBRDADDR: -#ifdef __APPLE__ - if ((so->so_state & SS_PRIV) == 0) - return (EPERM); -#else - if (p && (error = suser(p)) != 0) - return error; -#endif + if ((so->so_state & SS_PRIV) == 0) { + error = EPERM; + goto done; + } /* FALLTHROUGH */ case SIOCGIFADDR: case SIOCGIFNETMASK: case SIOCGIFDSTADDR: case SIOCGIFBRDADDR: - if (ia == (struct in_ifaddr *)0) - return (EADDRNOTAVAIL); + if (ia == (struct in_ifaddr *)0) { + error = EADDRNOTAVAIL; + goto done; + } break; } switch (cmd) { case SIOCAUTOADDR: - if (ifp == 0) - return (EADDRNOTAVAIL); - if (ifr->ifr_data) + ifnet_lock_exclusive(ifp); + if (ifr->ifr_intval) ifp->if_eflags |= IFEF_AUTOCONFIGURING; else ifp->if_eflags &= ~IFEF_AUTOCONFIGURING; + ifnet_lock_done(ifp); + break; + + case SIOCARPIPLL: + ipv4_ll_arp_aware = 1; + ifnet_lock_exclusive(ifp); + if (ifr->ifr_data) + ifp->if_eflags |= IFEF_ARPLL; + else + ifp->if_eflags &= ~IFEF_ARPLL; + ifnet_lock_done(ifp); break; case SIOCGIFADDR: + IFA_LOCK(&ia->ia_ifa); *((struct sockaddr_in *)&ifr->ifr_addr) = ia->ia_addr; + IFA_UNLOCK(&ia->ia_ifa); break; case SIOCGIFBRDADDR: - if ((ifp->if_flags & IFF_BROADCAST) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_BROADCAST) == 0) { + error = EINVAL; + break; + } + IFA_LOCK(&ia->ia_ifa); *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_broadaddr; + IFA_UNLOCK(&ia->ia_ifa); break; case SIOCGIFDSTADDR: - if ((ifp->if_flags & IFF_POINTOPOINT) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) { + error = EINVAL; + break; + } + IFA_LOCK(&ia->ia_ifa); *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_dstaddr; + IFA_UNLOCK(&ia->ia_ifa); break; case SIOCGIFNETMASK: + IFA_LOCK(&ia->ia_ifa); *((struct sockaddr_in *)&ifr->ifr_addr) = ia->ia_sockmask; + IFA_UNLOCK(&ia->ia_ifa); break; case SIOCSIFDSTADDR: - if ((ifp->if_flags & IFF_POINTOPOINT) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) { + error = EINVAL; + break; + } + IFA_LOCK(&ia->ia_ifa); oldaddr = ia->ia_dstaddr; ia->ia_dstaddr = *(struct sockaddr_in *)&ifr->ifr_dstaddr; - error = dlil_ioctl(PF_INET, ifp, SIOCSIFDSTADDR, (caddr_t)ia); - if (error == EOPNOTSUPP) - error = 0; - + if (ia->ia_dstaddr.sin_family == AF_INET) + ia->ia_dstaddr.sin_len = sizeof (struct sockaddr_in); + IFA_UNLOCK(&ia->ia_ifa); + error = ifnet_ioctl(ifp, PF_INET, SIOCSIFDSTADDR, ia); + IFA_LOCK(&ia->ia_ifa); + if (error == EOPNOTSUPP) { + error = 0; + } if (error) { - ia->ia_dstaddr = oldaddr; - return error; + ia->ia_dstaddr = oldaddr; + IFA_UNLOCK(&ia->ia_ifa); + break; } + IFA_LOCK_ASSERT_HELD(&ia->ia_ifa); ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_INET_SUBCLASS; - + ev_msg.event_code = KEV_INET_SIFDSTADDR; if (ia->ia_ifa.ifa_dstaddr) @@ -460,9 +672,10 @@ in_control(so, cmd, data, ifp, p) in_event_data.ia_subnet = ia->ia_subnet; in_event_data.ia_subnetmask = ia->ia_subnetmask; in_event_data.ia_netbroadcast = ia->ia_netbroadcast; + IFA_UNLOCK(&ia->ia_ifa); strncpy(&in_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ); in_event_data.link_data.if_family = ifp->if_family; - in_event_data.link_data.if_unit = (unsigned long) ifp->if_unit; + in_event_data.link_data.if_unit = (u_int32_t) ifp->if_unit; ev_msg.dv[0].data_ptr = &in_event_data; ev_msg.dv[0].data_length = sizeof(struct kev_in_data); @@ -470,25 +683,36 @@ in_control(so, cmd, data, ifp, p) kev_post_msg(&ev_msg); - + lck_mtx_lock(rnh_lock); + IFA_LOCK(&ia->ia_ifa); if (ia->ia_flags & IFA_ROUTE) { ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&oldaddr; - rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); + IFA_UNLOCK(&ia->ia_ifa); + rtinit_locked(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); + IFA_LOCK(&ia->ia_ifa); ia->ia_ifa.ifa_dstaddr = - (struct sockaddr *)&ia->ia_dstaddr; - rtinit(&(ia->ia_ifa), (int)RTM_ADD, RTF_HOST|RTF_UP); + (struct sockaddr *)&ia->ia_dstaddr; + IFA_UNLOCK(&ia->ia_ifa); + rtinit_locked(&(ia->ia_ifa), (int)RTM_ADD, + RTF_HOST|RTF_UP); + } else { + IFA_UNLOCK(&ia->ia_ifa); } + lck_mtx_unlock(rnh_lock); break; case SIOCSIFBRDADDR: - if ((ifp->if_flags & IFF_BROADCAST) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_BROADCAST) == 0) { + error = EINVAL; + break; + } + IFA_LOCK(&ia->ia_ifa); ia->ia_broadaddr = *(struct sockaddr_in *)&ifr->ifr_broadaddr; ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_INET_SUBCLASS; - + ev_msg.event_code = KEV_INET_SIFBRDADDR; if (ia->ia_ifa.ifa_dstaddr) @@ -503,9 +727,10 @@ in_control(so, cmd, data, ifp, p) in_event_data.ia_subnet = ia->ia_subnet; in_event_data.ia_subnetmask = ia->ia_subnetmask; in_event_data.ia_netbroadcast = ia->ia_netbroadcast; + IFA_UNLOCK(&ia->ia_ifa); strncpy(&in_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ); in_event_data.link_data.if_family = ifp->if_family; - in_event_data.link_data.if_unit = (unsigned long) ifp->if_unit; + in_event_data.link_data.if_unit = (u_int32_t) ifp->if_unit; ev_msg.dv[0].data_ptr = &in_event_data; ev_msg.dv[0].data_length = sizeof(struct kev_in_data); @@ -516,43 +741,54 @@ in_control(so, cmd, data, ifp, p) break; case SIOCSIFADDR: - return (in_ifinit(ifp, ia, - (struct sockaddr_in *) &ifr->ifr_addr, 1)); + /* + * If this is a new address, the reference count for the + * hash table has been taken at creation time above. + */ + error = in_ifinit(ifp, ia, + (struct sockaddr_in *)&ifr->ifr_addr, 1); +#if PF + if (!error) + (void) pf_ifaddr_hook(ifp, cmd); +#endif /* PF */ + break; case SIOCPROTOATTACH: - ether_attach_inet(ifp); -#if INET6 - if (ip6_auto_on) /* FreeBSD compat mode: Acquire linklocal addresses for IPv6 for if */ - in6_if_up(ifp); -#endif - break; - + error = in_domifattach(ifp); + break; + case SIOCPROTODETACH: - // if an ip address is still present, refuse to detach - TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) - if (ifa->ifa_addr->sa_family == AF_INET) - return EBUSY; - error = ether_detach_inet(ifp); - if (error) - return(error); -#if INET6 - if (ip6_auto_on) { /* if we linked ipv6 addresses to v4, remove them now */ - in6_purgeif(ifp); - error = ether_detach_inet6(ifp); - if (error) - return(error); + /* + * If an IPv4 address is still present, refuse to detach. + */ + ifnet_lock_shared(ifp); + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + IFA_LOCK(ifa); + if (ifa->ifa_addr->sa_family == AF_INET) { + IFA_UNLOCK(ifa); + break; + } + IFA_UNLOCK(ifa); + } + ifnet_lock_done(ifp); + if (ifa != NULL) { + error = EBUSY; + break; } -#endif + + error = proto_unplumb(PF_INET, ifp); break; - - case SIOCSIFNETMASK: + case SIOCSIFNETMASK: { + u_long i; + i = ifra->ifra_addr.sin_addr.s_addr; + IFA_LOCK(&ia->ia_ifa); ia->ia_subnetmask = ntohl(ia->ia_sockmask.sin_addr.s_addr = i); ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_INET_SUBCLASS; - + ev_msg.event_code = KEV_INET_SIFNETMASK; if (ia->ia_ifa.ifa_dstaddr) @@ -567,9 +803,10 @@ in_control(so, cmd, data, ifp, p) in_event_data.ia_subnet = ia->ia_subnet; in_event_data.ia_subnetmask = ia->ia_subnetmask; in_event_data.ia_netbroadcast = ia->ia_netbroadcast; + IFA_UNLOCK(&ia->ia_ifa); strncpy(&in_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ); in_event_data.link_data.if_family = ifp->if_family; - in_event_data.link_data.if_unit = (unsigned long) ifp->if_unit; + in_event_data.link_data.if_unit = (u_int32_t) ifp->if_unit; ev_msg.dv[0].data_ptr = &in_event_data; ev_msg.dv[0].data_length = sizeof(struct kev_in_data); @@ -578,11 +815,13 @@ in_control(so, cmd, data, ifp, p) kev_post_msg(&ev_msg); break; - + } case SIOCAIFADDR: maskIsNew = 0; hostIsNew = 1; error = 0; + + IFA_LOCK(&ia->ia_ifa); if (ia->ia_addr.sin_family == AF_INET) { if (ifra->ifra_addr.sin_len == 0) { ifra->ifra_addr = ia->ia_addr; @@ -592,7 +831,9 @@ in_control(so, cmd, data, ifp, p) hostIsNew = 0; } if (ifra->ifra_mask.sin_len) { - in_ifscrub(ifp, ia); + IFA_UNLOCK(&ia->ia_ifa); + in_ifscrub(ifp, ia, 0); + IFA_LOCK(&ia->ia_ifa); ia->ia_sockmask = ifra->ifra_mask; ia->ia_subnetmask = ntohl(ia->ia_sockmask.sin_addr.s_addr); @@ -600,14 +841,25 @@ in_control(so, cmd, data, ifp, p) } if ((ifp->if_flags & IFF_POINTOPOINT) && (ifra->ifra_dstaddr.sin_family == AF_INET)) { - in_ifscrub(ifp, ia); + IFA_UNLOCK(&ia->ia_ifa); + in_ifscrub(ifp, ia, 0); + IFA_LOCK(&ia->ia_ifa); ia->ia_dstaddr = ifra->ifra_dstaddr; + ia->ia_dstaddr.sin_len = sizeof (struct sockaddr_in); maskIsNew = 1; /* We lie; but the effect's the same */ } if (ifra->ifra_addr.sin_family == AF_INET && (hostIsNew || maskIsNew)) { + IFA_UNLOCK(&ia->ia_ifa); error = in_ifinit(ifp, ia, &ifra->ifra_addr, 0); + } else { + IFA_UNLOCK(&ia->ia_ifa); } +#if PF + if (!error) + (void) pf_ifaddr_hook(ifp, cmd); +#endif /* PF */ + IFA_LOCK(&ia->ia_ifa); if ((ifp->if_flags & IFF_BROADCAST) && (ifra->ifra_broadaddr.sin_family == AF_INET)) ia->ia_broadaddr = ifra->ifra_broadaddr; @@ -638,32 +890,37 @@ in_control(so, cmd, data, ifp, p) in_event_data.ia_subnet = ia->ia_subnet; in_event_data.ia_subnetmask = ia->ia_subnetmask; in_event_data.ia_netbroadcast = ia->ia_netbroadcast; + IFA_UNLOCK(&ia->ia_ifa); strncpy(&in_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ); in_event_data.link_data.if_family = ifp->if_family; - in_event_data.link_data.if_unit = (unsigned long) ifp->if_unit; + in_event_data.link_data.if_unit = (u_int32_t) ifp->if_unit; ev_msg.dv[0].data_ptr = &in_event_data; ev_msg.dv[0].data_length = sizeof(struct kev_in_data); ev_msg.dv[1].data_length = 0; kev_post_msg(&ev_msg); + } else { + IFA_UNLOCK(&ia->ia_ifa); } - - return (error); + break; case SIOCDIFADDR: - error = dlil_ioctl(PF_INET, ifp, SIOCDIFADDR, (caddr_t)ia); - if (error == EOPNOTSUPP) - error = 0; - if (error) - return error; + error = ifnet_ioctl(ifp, PF_INET, SIOCDIFADDR, ia); + if (error == EOPNOTSUPP) + error = 0; + if (error != 0) { + break; + } + /* Fill out the kernel event information */ ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_INET_SUBCLASS; - + ev_msg.event_code = KEV_INET_ADDR_DELETED; + IFA_LOCK(&ia->ia_ifa); if (ia->ia_ifa.ifa_dstaddr) in_event_data.ia_dstaddr = ((struct sockaddr_in *)ia->ia_ifa.ifa_dstaddr)->sin_addr; @@ -676,72 +933,87 @@ in_control(so, cmd, data, ifp, p) in_event_data.ia_subnet = ia->ia_subnet; in_event_data.ia_subnetmask = ia->ia_subnetmask; in_event_data.ia_netbroadcast = ia->ia_netbroadcast; + IFA_UNLOCK(&ia->ia_ifa); strncpy(&in_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ); in_event_data.link_data.if_family = ifp->if_family; - in_event_data.link_data.if_unit = (unsigned long) ifp->if_unit; + in_event_data.link_data.if_unit = (u_int32_t) ifp->if_unit; ev_msg.dv[0].data_ptr = &in_event_data; - ev_msg.dv[0].data_length = sizeof(struct kev_in_data); + ev_msg.dv[0].data_length = sizeof(struct kev_in_data); ev_msg.dv[1].data_length = 0; - kev_post_msg(&ev_msg); + ifa = &ia->ia_ifa; + lck_rw_lock_exclusive(in_ifaddr_rwlock); + /* Release ia_link reference */ + IFA_REMREF(ifa); + TAILQ_REMOVE(&in_ifaddrhead, ia, ia_link); + IFA_LOCK(ifa); + if (IA_IS_HASHED(ia)) + in_iahash_remove(ia); + IFA_UNLOCK(ifa); + lck_rw_done(in_ifaddr_rwlock); /* * in_ifscrub kills the interface route. */ - in_ifscrub(ifp, ia); -#ifndef __APPLE__ - /* - * in_ifadown gets rid of all the rest of - * the routes. This is not quite the right - * thing to do, but at least if we are running - * a routing process they will come back. - */ - in_ifadown(&ia->ia_ifa, 1); - /* - * XXX horrible hack to detect that we are being called - * from if_detach() - */ - if (!ifnet_addrs[ifp->if_index - 1]) { - in_pcbpurgeif0(LIST_FIRST(ripcbinfo.listhead), ifp); - in_pcbpurgeif0(LIST_FIRST(udbinfo.listhead), ifp); - } -#endif + in_ifscrub(ifp, ia, 0); + ifnet_lock_exclusive(ifp); + IFA_LOCK(ifa); + /* if_detach_ifa() releases ifa_link reference */ + if_detach_ifa(ifp, ifa); + /* Our reference to this address is dropped at the bottom */ + IFA_UNLOCK(ifa); + + /* + * If the interface supports multicast, and no address is left, + * remove the "all hosts" multicast group from that interface. + */ + if ((ifp->if_flags & IFF_MULTICAST) != 0 || + ifp->if_allhostsinm != NULL ) { + + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + IFA_LOCK(ifa); + if (ifa->ifa_addr->sa_family == AF_INET) { + IFA_UNLOCK(ifa); + break; + } + IFA_UNLOCK(ifa); + } + ifnet_lock_done(ifp); - /* - * Protect from ipintr() traversing address list - * while we're modifying it. - */ - s = splnet(); + lck_mtx_lock(&ifp->if_addrconfig_lock); + if (ifa == NULL && ifp->if_allhostsinm != NULL) { + struct in_multi *inm = ifp->if_allhostsinm; + ifp->if_allhostsinm = NULL; - ifa = &ia->ia_ifa; - TAILQ_REMOVE(&ifp->if_addrhead, ifa, ifa_link); - oia = ia; - TAILQ_REMOVE(&in_ifaddrhead, oia, ia_link); - ifafree(&oia->ia_ifa); - -#ifdef __APPLE__ - /* - * If the interface supports multicast, and no address is left, - * remove the "all hosts" multicast group from that interface. - */ - if (ifp->if_flags & IFF_MULTICAST) { - struct in_addr addr; - struct in_multi *inm; + in_delmulti(inm); + /* release the reference for allhostsinm pointer */ + INM_REMREF(inm); + } + lck_mtx_unlock(&ifp->if_addrconfig_lock); + } else { + ifnet_lock_done(ifp); + } - TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) - if (ifa->ifa_addr->sa_family == AF_INET) - break; + /* Post the kernel event */ + kev_post_msg(&ev_msg); - if (ifa == 0) { - addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); - IN_LOOKUP_MULTI(addr, ifp, inm); - if (inm) - in_delmulti(inm); - } + /* + * See if there is any IPV4 address left and if so, + * reconfigure KDP to use current primary address. + */ + ifa = ifa_ifpgetprimary(ifp, AF_INET); + if (ifa != NULL) { + error = ifnet_ioctl(ifp, PF_INET, SIOCSIFADDR, ifa); + if (error == EOPNOTSUPP) + error = 0; + + /* Release reference from ifa_ifpgetprimary() */ + IFA_REMREF(ifa); } -#endif - splx(s); +#if PF + (void) pf_ifaddr_hook(ifp, cmd); +#endif /* PF */ break; #ifdef __APPLE__ @@ -751,27 +1023,23 @@ in_control(so, cmd, data, ifp, p) * Special ioctl for OpenTransport sockets */ struct inpcb *inp, *cloned_inp; - int error = 0; + int error2 = 0; int cloned_fd = *(int *)data; - s = splnet(); /* XXX */ inp = sotoinpcb(so); if (inp == NULL) { - splx(s); break; } /* let's make sure it's either -1 or a valid file descriptor */ if (cloned_fd != -1) { struct socket *cloned_so; - struct file *cloned_fp; - error = getsock(p->p_fd, cloned_fd, &cloned_fp); - if (error){ - splx(s); + error2 = file_socket(cloned_fd, &cloned_so); + if (error2){ break; } - cloned_so = (struct socket *)cloned_fp->f_data; cloned_inp = sotoinpcb(cloned_so); + file_drop(cloned_fd); } else { cloned_inp = NULL; } @@ -793,7 +1061,6 @@ in_control(so, cmd, data, ifp, p) /* Multicast options */ if (cloned_inp->inp_moptions != NULL) { - int i; struct ip_moptions *cloned_imo = cloned_inp->inp_moptions; struct ip_moptions *imo = inp->inp_moptions; @@ -802,51 +1069,29 @@ in_control(so, cmd, data, ifp, p) * No multicast option buffer attached to the pcb; * allocate one. */ - splx(); - imo = (struct ip_moptions*) - _MALLOC(sizeof(*imo), M_IPMOPTS, M_WAITOK); + imo = ip_allocmoptions(M_WAITOK); if (imo == NULL) { - error = ENOBUFS; + error2 = ENOBUFS; break; } - s = splnet(); /* XXX */ inp->inp_moptions = imo; } - imo->imo_multicast_ifp = cloned_imo->imo_multicast_ifp; - imo->imo_multicast_vif = cloned_imo->imo_multicast_vif; - imo->imo_multicast_ttl = cloned_imo->imo_multicast_ttl; - imo->imo_multicast_loop = cloned_imo->imo_multicast_loop; - imo->imo_num_memberships = cloned_imo->imo_num_memberships; - for (i = 0; i < cloned_imo->imo_num_memberships; i++) { - imo->imo_membership[i] = - in_addmulti(&cloned_imo->imo_membership[i]->inm_addr, - cloned_imo->imo_membership[i]->inm_ifp); - if (imo->imo_membership[i] == NULL) { - error = ENOBUFS; - break; - } - } - if (i < cloned_imo->imo_num_memberships) { - /* Failed, perform cleanup */ - for (i--; i >= 0; i--) - in_delmulti(imo->imo_membership[i]); - break; - } + + error2 = imo_clone(cloned_imo, imo); } } - splx(s); break; } #endif /* __APPLE__ */ default: - return EOPNOTSUPP; - /* Darwin: dlil_ioctl called from ifioctl */ -#ifndef __APPLE__ - return ((*ifp->if_ioctl)(ifp, cmd, data)); -#endif + error = EOPNOTSUPP; } - return (0); + done: + if (ia != NULL) { + IFA_REMREF(&ia->ia_ifa); + } + return (error); } /* @@ -866,12 +1111,12 @@ in_control(so, cmd, data, ifp, p) * other values may be returned from in_ioctl() */ static int -in_lifaddr_ioctl(so, cmd, data, ifp, p) - struct socket *so; - u_long cmd; - caddr_t data; - struct ifnet *ifp; - struct proc *p; +in_lifaddr_ioctl( + struct socket *so, + u_long cmd, + caddr_t data, + struct ifnet *ifp, + struct proc *p) { struct if_laddrreq *iflr = (struct if_laddrreq *)data; struct ifaddr *ifa; @@ -879,7 +1124,7 @@ in_lifaddr_ioctl(so, cmd, data, ifp, p) /* sanity checks */ if (!data || !ifp) { panic("invalid argument to in_lifaddr_ioctl"); - /*NOTRECHED*/ + /*NOTREACHED*/ } switch (cmd) { @@ -939,7 +1184,8 @@ in_lifaddr_ioctl(so, cmd, data, ifp, p) case SIOCDLIFADDR: { struct in_ifaddr *ia; - struct in_addr mask, candidate, match; + struct in_addr mask, candidate; + struct in_addr match = { 0 }; struct sockaddr_in *sin; int cmp; @@ -971,21 +1217,32 @@ in_lifaddr_ioctl(so, cmd, data, ifp, p) } } + ifnet_lock_shared(ifp); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { - if (ifa->ifa_addr->sa_family != AF_INET6) + IFA_LOCK(ifa); + if (ifa->ifa_addr->sa_family != AF_INET6) { + IFA_UNLOCK(ifa); continue; - if (!cmp) + } + if (!cmp) { + IFA_UNLOCK(ifa); break; + } candidate.s_addr = ((struct sockaddr_in *)&ifa->ifa_addr)->sin_addr.s_addr; candidate.s_addr &= mask.s_addr; + IFA_UNLOCK(ifa); if (candidate.s_addr == match.s_addr) break; } + if (ifa != NULL) + IFA_ADDREF(ifa); + ifnet_lock_done(ifp); if (!ifa) return EADDRNOTAVAIL; ia = (struct in_ifaddr *)ifa; if (cmd == SIOCGLIFADDR) { + IFA_LOCK(ifa); /* fill in the if_laddrreq structure */ bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin_len); @@ -1000,6 +1257,8 @@ in_lifaddr_ioctl(so, cmd, data, ifp, p) iflr->flags = 0; /*XXX*/ + IFA_UNLOCK(ifa); + IFA_REMREF(ifa); return 0; } else { struct in_aliasreq ifra; @@ -1009,6 +1268,7 @@ in_lifaddr_ioctl(so, cmd, data, ifp, p) bcopy(iflr->iflr_name, ifra.ifra_name, sizeof(ifra.ifra_name)); + IFA_LOCK(ifa); bcopy(&ia->ia_addr, &ifra.ifra_addr, ia->ia_addr.sin_len); if ((ifp->if_flags & IFF_POINTOPOINT) != 0) { @@ -1017,7 +1277,8 @@ in_lifaddr_ioctl(so, cmd, data, ifp, p) } bcopy(&ia->ia_sockmask, &ifra.ifra_dstaddr, ia->ia_sockmask.sin_len); - + IFA_UNLOCK(ifa); + IFA_REMREF(ifa); return in_control(so, SIOCDIFADDR, (caddr_t)&ifra, ifp, p); } @@ -1031,18 +1292,118 @@ in_lifaddr_ioctl(so, cmd, data, ifp, p) * Delete any existing route for an interface. */ void -in_ifscrub(ifp, ia) - register struct ifnet *ifp; - register struct in_ifaddr *ia; +in_ifscrub(struct ifnet *ifp, struct in_ifaddr *ia, int locked) { - - if ((ia->ia_flags & IFA_ROUTE) == 0) + IFA_LOCK(&ia->ia_ifa); + if ((ia->ia_flags & IFA_ROUTE) == 0) { + IFA_UNLOCK(&ia->ia_ifa); return; + } + IFA_UNLOCK(&ia->ia_ifa); + if (!locked) + lck_mtx_lock(rnh_lock); if (ifp->if_flags & (IFF_LOOPBACK|IFF_POINTOPOINT)) - rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); + rtinit_locked(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); else - rtinit(&(ia->ia_ifa), (int)RTM_DELETE, 0); + rtinit_locked(&(ia->ia_ifa), (int)RTM_DELETE, 0); + IFA_LOCK(&ia->ia_ifa); ia->ia_flags &= ~IFA_ROUTE; + IFA_UNLOCK(&ia->ia_ifa); + if (!locked) + lck_mtx_unlock(rnh_lock); +} + +/* + * Caller must hold in_ifaddr_rwlock as writer. + */ +static void +in_iahash_remove(struct in_ifaddr *ia) +{ + lck_rw_assert(in_ifaddr_rwlock, LCK_RW_ASSERT_EXCLUSIVE); + IFA_LOCK_ASSERT_HELD(&ia->ia_ifa); + + if (!IA_IS_HASHED(ia)) { + panic("attempt to remove wrong ia %p from hash table\n", ia); + /* NOTREACHED */ + } + TAILQ_REMOVE(INADDR_HASH(ia->ia_addr.sin_addr.s_addr), ia, ia_hash); + IA_HASH_INIT(ia); + if (IFA_REMREF_LOCKED(&ia->ia_ifa) == NULL) { + panic("%s: unexpected (missing) refcnt ifa=%p", __func__, + &ia->ia_ifa); + /* NOTREACHED */ + } +} + +/* + * Caller must hold in_ifaddr_rwlock as writer. + */ +static void +in_iahash_insert(struct in_ifaddr *ia) +{ + lck_rw_assert(in_ifaddr_rwlock, LCK_RW_ASSERT_EXCLUSIVE); + IFA_LOCK_ASSERT_HELD(&ia->ia_ifa); + + if (ia->ia_addr.sin_family != AF_INET) { + panic("attempt to insert wrong ia %p into hash table\n", ia); + /* NOTREACHED */ + } else if (IA_IS_HASHED(ia)) { + panic("attempt to double-insert ia %p into hash table\n", ia); + /* NOTREACHED */ + } + TAILQ_INSERT_HEAD(INADDR_HASH(ia->ia_addr.sin_addr.s_addr), ia, ia_hash); + IFA_ADDREF_LOCKED(&ia->ia_ifa); +} + +/* + * Some point to point interfaces that are tunnels + * borrow the address from an underlying interface (e.g. + * VPN server). In order for source address selection logic to + * find the underlying interface first, we add the address + * of borrowing point to point interfaces at the end of the list. + * (see rdar://6733789) + * + * Caller must hold in_ifaddr_rwlock as writer. + */ +static void +in_iahash_insert_ptp(struct in_ifaddr *ia) +{ + struct in_ifaddr *tmp_ifa; + struct ifnet *tmp_ifp; + + lck_rw_assert(in_ifaddr_rwlock, LCK_RW_ASSERT_EXCLUSIVE); + IFA_LOCK_ASSERT_HELD(&ia->ia_ifa); + + if (ia->ia_addr.sin_family != AF_INET) { + panic("attempt to insert wrong ia %p into hash table\n", ia); + /* NOTREACHED */ + } else if (IA_IS_HASHED(ia)) { + panic("attempt to double-insert ia %p into hash table\n", ia); + /* NOTREACHED */ + } + IFA_UNLOCK(&ia->ia_ifa); + TAILQ_FOREACH(tmp_ifa, INADDR_HASH(ia->ia_addr.sin_addr.s_addr), + ia_hash) { + IFA_LOCK(&tmp_ifa->ia_ifa); + /* ia->ia_addr won't change, so check without lock */ + if (IA_SIN(tmp_ifa)->sin_addr.s_addr == + ia->ia_addr.sin_addr.s_addr) { + IFA_UNLOCK(&tmp_ifa->ia_ifa); + break; + } + IFA_UNLOCK(&tmp_ifa->ia_ifa); + } + tmp_ifp = (tmp_ifa == NULL) ? NULL : tmp_ifa->ia_ifp; + + IFA_LOCK(&ia->ia_ifa); + if (tmp_ifp == NULL) { + TAILQ_INSERT_HEAD(INADDR_HASH(ia->ia_addr.sin_addr.s_addr), + ia, ia_hash); + } else { + TAILQ_INSERT_TAIL(INADDR_HASH(ia->ia_addr.sin_addr.s_addr), + ia, ia_hash); + } + IFA_ADDREF_LOCKED(&ia->ia_ifa); } /* @@ -1050,39 +1411,99 @@ in_ifscrub(ifp, ia) * and routing table entry. */ static int -in_ifinit(ifp, ia, sin, scrub) - register struct ifnet *ifp; - register struct in_ifaddr *ia; - struct sockaddr_in *sin; - int scrub; +in_ifinit( + struct ifnet *ifp, + struct in_ifaddr *ia, + struct sockaddr_in *sin, + int scrub) { - register u_long i = ntohl(sin->sin_addr.s_addr); + u_int32_t i = ntohl(sin->sin_addr.s_addr); struct sockaddr_in oldaddr; - int s = splimp(), flags = RTF_UP, error; - u_long dl_tag; + int flags = RTF_UP, error; + struct ifaddr *ifa0; + unsigned int cmd; + int oldremoved = 0; + + /* Take an extra reference for this routine */ + IFA_ADDREF(&ia->ia_ifa); + lck_rw_lock_exclusive(in_ifaddr_rwlock); + IFA_LOCK(&ia->ia_ifa); oldaddr = ia->ia_addr; + if (IA_IS_HASHED(ia)) { + oldremoved = 1; + in_iahash_remove(ia); + } ia->ia_addr = *sin; + ia->ia_addr.sin_len = sizeof (*sin); + if ((ifp->if_flags & IFF_POINTOPOINT)) + in_iahash_insert_ptp(ia); + else + in_iahash_insert(ia); + IFA_UNLOCK(&ia->ia_ifa); + lck_rw_done(in_ifaddr_rwlock); /* - * Give the interface a chance to initialize - * if this is its first address, - * and to validate the address if necessary. + * Give the interface a chance to initialize if this is its first + * address, and to validate the address if necessary. Send down + * SIOCSIFADDR for first address, and SIOCAIFADDR for alias(es). + * We find the first IPV4 address assigned to it and check if this + * is the same as the one passed into this routine. */ - error = dlil_ioctl(PF_INET, ifp, SIOCSIFADDR, (caddr_t)ia); + ifa0 = ifa_ifpgetprimary(ifp, AF_INET); + cmd = (&ia->ia_ifa == ifa0) ? SIOCSIFADDR : SIOCAIFADDR; + error = ifnet_ioctl(ifp, PF_INET, cmd, ia); if (error == EOPNOTSUPP) - error = 0; + error = 0; + /* + * If we've just sent down SIOCAIFADDR, send another ioctl down + * for SIOCSIFADDR for the first IPV4 address of the interface, + * because an address change on one of the addresses will result + * in the removal of the previous first IPV4 address. KDP needs + * be reconfigured with the current primary IPV4 address. + */ + if (error == 0 && cmd == SIOCAIFADDR) { + error = ifnet_ioctl(ifp, PF_INET, SIOCSIFADDR, ifa0); + if (error == EOPNOTSUPP) + error = 0; + } + + /* Release reference from ifa_ifpgetprimary() */ + IFA_REMREF(ifa0); + if (error) { - splx(s); + lck_rw_lock_exclusive(in_ifaddr_rwlock); + IFA_LOCK(&ia->ia_ifa); + if (IA_IS_HASHED(ia)) + in_iahash_remove(ia); ia->ia_addr = oldaddr; + if (oldremoved) { + if ((ifp->if_flags & IFF_POINTOPOINT)) + in_iahash_insert_ptp(ia); + else + in_iahash_insert(ia); + } + IFA_UNLOCK(&ia->ia_ifa); + lck_rw_done(in_ifaddr_rwlock); + /* Release extra reference taken above */ + IFA_REMREF(&ia->ia_ifa); return (error); } - splx(s); + lck_mtx_lock(rnh_lock); + IFA_LOCK(&ia->ia_ifa); + /* + * Address has been initialized by the link resolver (ARP) + * via ifnet_ioctl() above; it may now generate route(s). + */ + ia->ia_ifa.ifa_debug &= ~IFD_NOTREADY; if (scrub) { ia->ia_ifa.ifa_addr = (struct sockaddr *)&oldaddr; - in_ifscrub(ifp, ia); + IFA_UNLOCK(&ia->ia_ifa); + in_ifscrub(ifp, ia, 1); + IFA_LOCK(&ia->ia_ifa); ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; } + IFA_LOCK_ASSERT_HELD(&ia->ia_ifa); if (IN_CLASSA(i)) ia->ia_netmask = IN_CLASSA_NET; else if (IN_CLASSB(i)) @@ -1115,12 +1536,24 @@ in_ifinit(ifp, ia, sin, scrub) ia->ia_ifa.ifa_dstaddr = ia->ia_ifa.ifa_addr; flags |= RTF_HOST; } else if (ifp->if_flags & IFF_POINTOPOINT) { - if (ia->ia_dstaddr.sin_family != AF_INET) + if (ia->ia_dstaddr.sin_family != AF_INET) { + IFA_UNLOCK(&ia->ia_ifa); + lck_mtx_unlock(rnh_lock); + /* Release extra reference taken above */ + IFA_REMREF(&ia->ia_ifa); return (0); + } + ia->ia_dstaddr.sin_len = sizeof (*sin); flags |= RTF_HOST; } - if ((error = rtinit(&(ia->ia_ifa), (int)RTM_ADD, flags)) == 0) + IFA_UNLOCK(&ia->ia_ifa); + if ((error = rtinit_locked(&(ia->ia_ifa), (int)RTM_ADD, flags)) == 0) { + IFA_LOCK(&ia->ia_ifa); ia->ia_flags |= IFA_ROUTE; + IFA_UNLOCK(&ia->ia_ifa); + } + lck_mtx_unlock(rnh_lock); + /* XXX check if the subnet route points to the same interface */ if (error == EEXIST) error = 0; @@ -1130,14 +1563,29 @@ in_ifinit(ifp, ia, sin, scrub) * multicast group on that interface. */ if (ifp->if_flags & IFF_MULTICAST) { - struct in_multi *inm; struct in_addr addr; + lck_mtx_lock(&ifp->if_addrconfig_lock); addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); - IN_LOOKUP_MULTI(addr, ifp, inm); - if (inm == 0) - in_addmulti(&addr, ifp); + if (ifp->if_allhostsinm == NULL) { + struct in_multi *inm; + inm = in_addmulti(&addr, ifp); + + if (inm != NULL) { + /* keep the reference on inm added by + * in_addmulti above for storing the + * pointer in allhostsinm + */ + ifp->if_allhostsinm = inm; + } else { + printf("Failed to add membership to all-hosts multicast address on interface %s%d\n", ifp->if_name, ifp->if_unit); + } + } + lck_mtx_unlock(&ifp->if_addrconfig_lock); } + + /* Release extra reference taken above */ + IFA_REMREF(&ia->ia_ifa); return (error); } @@ -1146,28 +1594,24 @@ in_ifinit(ifp, ia, sin, scrub) * Return 1 if the address might be a local broadcast address. */ int -in_broadcast(in, ifp) - struct in_addr in; - struct ifnet *ifp; +in_broadcast(struct in_addr in, struct ifnet *ifp) { - register struct ifaddr *ifa; - u_long t; + struct ifaddr *ifa; + u_int32_t t; - if (in.s_addr == INADDR_BROADCAST || - in.s_addr == INADDR_ANY) - return 1; + if (in.s_addr == INADDR_BROADCAST || in.s_addr == INADDR_ANY) + return (1); if ((ifp->if_flags & IFF_BROADCAST) == 0) - return 0; + return (0); t = ntohl(in.s_addr); /* * Look through the list of addresses for a match * with a broadcast address. */ #define ia ((struct in_ifaddr *)ifa) - for (ifa = ifp->if_addrhead.tqh_first; ifa; - ifa = ifa->ifa_link.tqe_next) { - if (ifa->ifa_addr == NULL) - return (0); + ifnet_lock_shared(ifp); + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + IFA_LOCK(ifa); if (ifa->ifa_addr->sa_family == AF_INET && (in.s_addr == ia->ia_broadaddr.sin_addr.s_addr || in.s_addr == ia->ia_netbroadcast.s_addr || @@ -1180,94 +1624,258 @@ in_broadcast(in, ifp) * only exist when an interface gets a secondary * address. */ - ia->ia_subnetmask != (u_long)0xffffffff) - return 1; + ia->ia_subnetmask != (u_int32_t)0xffffffff) { + IFA_UNLOCK(ifa); + ifnet_lock_done(ifp); + return (1); + } + IFA_UNLOCK(ifa); } + ifnet_lock_done(ifp); return (0); #undef ia } -/* - * Add an address to the list of IP multicast addresses for a given interface. - */ -struct in_multi * -in_addmulti(ap, ifp) - register struct in_addr *ap; - register struct ifnet *ifp; + +void +in_purgeaddrs(struct ifnet *ifp) { - register struct in_multi *inm; - int error; - struct sockaddr_in sin; - struct ifmultiaddr *ifma; - int s = splnet(); + struct ifaddr **ifap; + int err, i; /* - * Call generic routine to add membership or increment - * refcount. It wants addresses in the form of a sockaddr, - * so we build one here (being careful to zero the unused bytes). + * Be nice, and try the civilized way first. If we can't get + * rid of them this way, then do it the rough way. We must + * only get here during detach time, after the ifnet has been + * removed from the global list and arrays. */ - bzero(&sin, sizeof sin); - sin.sin_family = AF_INET; - sin.sin_len = sizeof sin; - sin.sin_addr = *ap; - error = if_addmulti(ifp, (struct sockaddr *)&sin, &ifma); - if (error) { - splx(s); - return 0; + err = ifnet_get_address_list_family_internal(ifp, &ifap, AF_INET, 1, + M_WAITOK); + if (err == 0 && ifap != NULL) { + for (i = 0; ifap[i] != NULL; i++) { + struct ifaliasreq ifr; + struct ifaddr *ifa; + + ifa = ifap[i]; + bzero(&ifr, sizeof (ifr)); + IFA_LOCK(ifa); + ifr.ifra_addr = *ifa->ifa_addr; + if (ifa->ifa_dstaddr != NULL) + ifr.ifra_broadaddr = *ifa->ifa_dstaddr; + IFA_UNLOCK(ifa); + err = in_control(NULL, SIOCDIFADDR, (caddr_t)&ifr, ifp, + kernproc); + /* if we lost the race, ignore it */ + if (err == EADDRNOTAVAIL) + err = 0; + if (err != 0) { + char s_addr[MAX_IPv4_STR_LEN]; + char s_dstaddr[MAX_IPv4_STR_LEN]; + struct in_addr *s, *d; + + IFA_LOCK(ifa); + s = &((struct sockaddr_in *) + ifa->ifa_addr)->sin_addr; + d = &((struct sockaddr_in *) + ifa->ifa_dstaddr)->sin_addr; + (void) inet_ntop(AF_INET, &s->s_addr, s_addr, + sizeof (s_addr)); + (void) inet_ntop(AF_INET, &d->s_addr, s_dstaddr, + sizeof (s_dstaddr)); + IFA_UNLOCK(ifa); + + printf("%s: SIOCDIFADDR ifp=%p ifa_addr=%s " + "ifa_dstaddr=%s (err=%d)\n", __func__, ifp, + s_addr, s_dstaddr, err); + } + } + ifnet_free_address_list(ifap); + } else if (err != 0 && err != ENXIO) { + printf("%s: error retrieving list of AF_INET addresses for " + "ifp=%p (err=%d)\n", __func__, ifp, err); } +} - /* - * If ifma->ifma_protospec is null, then if_addmulti() created - * a new record. Otherwise, we are done. - */ - if (ifma->ifma_protospec != 0) { - splx(s); - return ifma->ifma_protospec; - } +int inet_aton(char *cp, struct in_addr *pin); +int +inet_aton(char * cp, struct in_addr * pin) +{ + u_char * b = (unsigned char *)pin; + int i; + char * p; + + for (p = cp, i = 0; i < 4; i++) { + u_int32_t l = strtoul(p, 0, 0); + if (l > 255) + return (FALSE); + b[i] = l; + p = strchr(p, '.'); + if (i < 3 && p == NULL) + return (FALSE); + p++; + } + return (TRUE); +} - inm = (struct in_multi *) _MALLOC(sizeof(*inm), M_IPMADDR, M_WAITOK); - if (inm == NULL) { - splx(s); - return (NULL); - } +int inet_ntoa2(struct in_addr * pin, char * cp, const int len); +int inet_ntoa2(struct in_addr * pin, char * cp, const int len) +{ + int ret; - bzero(inm, sizeof *inm); - inm->inm_addr = *ap; - inm->inm_ifp = ifp; - inm->inm_ifma = ifma; - ifma->ifma_protospec = inm; - LIST_INSERT_HEAD(&in_multihead, inm, inm_link); + /* address is in network byte order */ + ret = snprintf(cp, len, "%u.%u.%u.%u", pin->s_addr & 0xFF, + (pin->s_addr >> 8) & 0xFF, (pin->s_addr >> 16) & 0xFF, + (pin->s_addr >> 24) & 0xFF); - /* - * Let IGMP know that we have joined a new IP multicast group. - */ - igmp_joingroup(inm); - splx(s); - return (inm); + return ret < len ? TRUE : FALSE; } /* - * Delete a multicast address record. + * Called as part of ip_init */ void -in_delmulti(inm) - register struct in_multi *inm; +in_ifaddr_init(void) { - struct ifmultiaddr *ifma = inm->inm_ifma; - int s = splnet(); - - /* We intentionally do this a bit differently than BSD */ + in_multi_init(); - if (ifma->ifma_refcount == 1) { - /* - * No remaining claims to this record; let IGMP know that - * we are leaving the multicast group. - */ - igmp_leavegroup(inm); - ifma->ifma_protospec = 0; - LIST_REMOVE(inm, inm_link); - FREE(inm, M_IPMADDR); + PE_parse_boot_argn("ifa_debug", &inifa_debug, sizeof (inifa_debug)); + + inifa_size = (inifa_debug == 0) ? sizeof (struct in_ifaddr) : + sizeof (struct in_ifaddr_dbg); + + inifa_zone = zinit(inifa_size, INIFA_ZONE_MAX * inifa_size, + 0, INIFA_ZONE_NAME); + if (inifa_zone == NULL) { + panic("%s: failed allocating %s", __func__, INIFA_ZONE_NAME); + /* NOTREACHED */ } - /* XXX - should be separate API for when we have an ifma? */ - if_delmulti(ifma->ifma_ifp, ifma->ifma_addr); - splx(s); + zone_change(inifa_zone, Z_EXPAND, TRUE); + zone_change(inifa_zone, Z_CALLERACCT, FALSE); + + lck_mtx_init(&inifa_trash_lock, ifa_mtx_grp, ifa_mtx_attr); + TAILQ_INIT(&inifa_trash_head); +} + +static struct in_ifaddr * +in_ifaddr_alloc(int how) +{ + struct in_ifaddr *inifa; + + inifa = (how == M_WAITOK) ? zalloc(inifa_zone) : + zalloc_noblock(inifa_zone); + if (inifa != NULL) { + bzero(inifa, inifa_size); + inifa->ia_ifa.ifa_free = in_ifaddr_free; + inifa->ia_ifa.ifa_debug |= IFD_ALLOC; + ifa_lock_init(&inifa->ia_ifa); + if (inifa_debug != 0) { + struct in_ifaddr_dbg *inifa_dbg = + (struct in_ifaddr_dbg *)inifa; + inifa->ia_ifa.ifa_debug |= IFD_DEBUG; + inifa->ia_ifa.ifa_trace = in_ifaddr_trace; + inifa->ia_ifa.ifa_attached = in_ifaddr_attached; + inifa->ia_ifa.ifa_detached = in_ifaddr_detached; + ctrace_record(&inifa_dbg->inifa_alloc); + } + } + return (inifa); +} + +static void +in_ifaddr_free(struct ifaddr *ifa) +{ + IFA_LOCK_ASSERT_HELD(ifa); + + if (ifa->ifa_refcnt != 0) { + panic("%s: ifa %p bad ref cnt", __func__, ifa); + /* NOTREACHED */ + } if (!(ifa->ifa_debug & IFD_ALLOC)) { + panic("%s: ifa %p cannot be freed", __func__, ifa); + /* NOTREACHED */ + } + if (ifa->ifa_debug & IFD_DEBUG) { + struct in_ifaddr_dbg *inifa_dbg = (struct in_ifaddr_dbg *)ifa; + ctrace_record(&inifa_dbg->inifa_free); + bcopy(&inifa_dbg->inifa, &inifa_dbg->inifa_old, + sizeof (struct in_ifaddr)); + if (ifa->ifa_debug & IFD_TRASHED) { + /* Become a regular mutex, just in case */ + IFA_CONVERT_LOCK(ifa); + lck_mtx_lock(&inifa_trash_lock); + TAILQ_REMOVE(&inifa_trash_head, inifa_dbg, + inifa_trash_link); + lck_mtx_unlock(&inifa_trash_lock); + ifa->ifa_debug &= ~IFD_TRASHED; + } + } + IFA_UNLOCK(ifa); + ifa_lock_destroy(ifa); + bzero(ifa, sizeof (struct in_ifaddr)); + zfree(inifa_zone, ifa); +} + +static void +in_ifaddr_attached(struct ifaddr *ifa) +{ + struct in_ifaddr_dbg *inifa_dbg = (struct in_ifaddr_dbg *)ifa; + + IFA_LOCK_ASSERT_HELD(ifa); + + if (!(ifa->ifa_debug & IFD_DEBUG)) { + panic("%s: ifa %p has no debug structure", __func__, ifa); + /* NOTREACHED */ + } + if (ifa->ifa_debug & IFD_TRASHED) { + /* Become a regular mutex, just in case */ + IFA_CONVERT_LOCK(ifa); + lck_mtx_lock(&inifa_trash_lock); + TAILQ_REMOVE(&inifa_trash_head, inifa_dbg, inifa_trash_link); + lck_mtx_unlock(&inifa_trash_lock); + ifa->ifa_debug &= ~IFD_TRASHED; + } +} + +static void +in_ifaddr_detached(struct ifaddr *ifa) +{ + struct in_ifaddr_dbg *inifa_dbg = (struct in_ifaddr_dbg *)ifa; + + IFA_LOCK_ASSERT_HELD(ifa); + + if (!(ifa->ifa_debug & IFD_DEBUG)) { + panic("%s: ifa %p has no debug structure", __func__, ifa); + /* NOTREACHED */ + } else if (ifa->ifa_debug & IFD_TRASHED) { + panic("%s: ifa %p is already in trash list", __func__, ifa); + /* NOTREACHED */ + } + ifa->ifa_debug |= IFD_TRASHED; + /* Become a regular mutex, just in case */ + IFA_CONVERT_LOCK(ifa); + lck_mtx_lock(&inifa_trash_lock); + TAILQ_INSERT_TAIL(&inifa_trash_head, inifa_dbg, inifa_trash_link); + lck_mtx_unlock(&inifa_trash_lock); +} + +static void +in_ifaddr_trace(struct ifaddr *ifa, int refhold) +{ + struct in_ifaddr_dbg *inifa_dbg = (struct in_ifaddr_dbg *)ifa; + ctrace_t *tr; + u_int32_t idx; + u_int16_t *cnt; + + if (!(ifa->ifa_debug & IFD_DEBUG)) { + panic("%s: ifa %p has no debug structure", __func__, ifa); + /* NOTREACHED */ + } + if (refhold) { + cnt = &inifa_dbg->inifa_refhold_cnt; + tr = inifa_dbg->inifa_refhold; + } else { + cnt = &inifa_dbg->inifa_refrele_cnt; + tr = inifa_dbg->inifa_refrele; + } + + idx = atomic_add_16_ov(cnt, 1) % INIFA_TRACE_HIST_SIZE; + ctrace_record(&tr[idx]); }