X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/8ad349bb6ed4a0be06e34c92be0d98b92e078db4..060df5ea7c632b1ac8cc8aac1fb59758165c2084:/bsd/netinet/in.c diff --git a/bsd/netinet/in.c b/bsd/netinet/in.c index 02cf2a24c..32b8c64f6 100644 --- a/bsd/netinet/in.c +++ b/bsd/netinet/in.c @@ -1,31 +1,29 @@ /* - * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved. + * Copyright (c) 2000-2008 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * - * @APPLE_LICENSE_OSREFERENCE_HEADER_START@ + * 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 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. - * - * 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, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and + * 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, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and * limitations under the License. - * - * @APPLE_LICENSE_OSREFERENCE_HEADER_END@ + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1982, 1986, 1991, 1993 @@ -74,10 +72,14 @@ #include #include #include +#include + +#include #include #include #include +#include #include #include @@ -94,6 +96,9 @@ #include +#if PF +#include +#endif /* PF */ static int in_mask2len(struct in_addr *); static void in_len2mask(struct in_addr *, int); @@ -104,17 +109,80 @@ 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; \ +} + +#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_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_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, &subnetsarelocal, 0, ""); struct in_multihead in_multihead; /* XXX BSS initialization */ -extern lck_mtx_t *rt_mtx; - /* Track whether or not the SIOCARPIPLL ioctl has been called */ __private_extern__ u_int32_t ipv4_ll_arp_aware = 0; +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 ifaref */ + u_int16_t inifa_refrele_cnt; /* # of ifafree */ + /* + * Alloc and free callers. + */ + ctrace_t inifa_alloc; + ctrace_t inifa_free; + /* + * Circular lists of ifaref and ifafree callers. + */ + ctrace_t inifa_refhold[CTRACE_HIST_SIZE]; + ctrace_t inifa_refrele[CTRACE_HIST_SIZE]; +}; + +static unsigned int inifa_debug; /* debug flags */ +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 */ + +int +inaddr_local(struct in_addr in) +{ + struct rtentry *rt; + struct sockaddr_in sin; + int local = 0; + + 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 * (one to which we have a connection). If subnetsarelocal @@ -122,30 +190,29 @@ __private_extern__ u_int32_t ipv4_ll_arp_aware = 0; * Otherwise, it includes only the directly-connected (sub)nets. */ int -in_localaddr(in) - struct in_addr in; +in_localaddr(struct in_addr in) { - u_long i = ntohl(in.s_addr); + u_int32_t i = ntohl(in.s_addr); struct in_ifaddr *ia; if (subnetsarelocal) { - lck_mtx_lock(rt_mtx); + 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) { - lck_mtx_unlock(rt_mtx); + lck_rw_done(in_ifaddr_rwlock); return (1); } - lck_mtx_unlock(rt_mtx); + lck_rw_done(in_ifaddr_rwlock); } else { - lck_mtx_lock(rt_mtx); + 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) { - lck_mtx_unlock(rt_mtx); + lck_rw_done(in_ifaddr_rwlock); return (1); } - lck_mtx_unlock(rt_mtx); + lck_rw_done(in_ifaddr_rwlock); } return (0); } @@ -156,11 +223,10 @@ in_localaddr(in) * may be forwarded. */ int -in_canforward(in) - struct in_addr in; +in_canforward(struct in_addr in) { - u_long i = ntohl(in.s_addr); - u_long net; + u_int32_t i = ntohl(in.s_addr); + u_int32_t net; if (IN_EXPERIMENTAL(i) || IN_MULTICAST(i)) return (0); @@ -176,8 +242,7 @@ in_canforward(in) * Trim a mask in a sockaddr */ static void -in_socktrim(ap) -struct sockaddr_in *ap; +in_socktrim(struct sockaddr_in *ap) { char *cplim = (char *) &ap->sin_addr; char *cp = (char *) (&ap->sin_addr + 1); @@ -191,10 +256,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; @@ -213,9 +277,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; @@ -233,6 +295,22 @@ static int in_interfaces; /* number of external internet interfaces */ /* * 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 @@ -244,21 +322,19 @@ in_control( struct proc *p) { struct ifreq *ifr = (struct ifreq *)data; - struct in_ifaddr *ia = 0, *iap; + struct in_ifaddr *ia = NULL, *iap; struct ifaddr *ifa; - struct in_ifaddr *oia; struct in_aliasreq *ifra = (struct in_aliasreq *)data; struct sockaddr_in oldaddr; - int error, hostIsNew, maskIsNew; - u_long i; + int error = 0; + int hostIsNew, maskIsNew; struct kev_msg ev_msg; struct kev_in_data in_event_data; - switch (cmd) { case SIOCALIFADDR: case SIOCDLIFADDR: - if (p && (error = proc_suser(p)) != 0) + if ((error = proc_suser(p)) != 0) return error; /*fall through*/ case SIOCGLIFADDR: @@ -274,7 +350,7 @@ in_control( * the first one on the interface. */ if (ifp) { - lck_mtx_lock(rt_mtx); + 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) { @@ -288,63 +364,91 @@ in_control( break; } } - lck_mtx_unlock(rt_mtx); + /* take a reference on ia before releasing lock */ + if (ia != NULL) { + ifaref(&ia->ia_ifa); + } + lck_rw_done(in_ifaddr_rwlock); } switch (cmd) { case SIOCAUTOADDR: case SIOCARPIPLL: - if (p && (error = proc_suser(p)) != 0) - return error; + 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) { - lck_mtx_lock(rt_mtx); + struct in_ifaddr *oia; + + lck_rw_lock_shared(in_ifaddr_rwlock); for (oia = ia; ia; ia = ia->ia_link.tqe_next) { if (ia->ia_ifp == ifp && ia->ia_addr.sin_addr.s_addr == ifra->ifra_addr.sin_addr.s_addr) break; } - lck_mtx_unlock(rt_mtx); + /* take a reference on ia before releasing lock */ + if (ia != NULL && ia != oia) { + ifaref(&ia->ia_ifa); + } + lck_rw_done(in_ifaddr_rwlock); + if (oia != NULL && oia != ia) { + ifafree(&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: - if ((so->so_state & SS_PRIV) == 0) - return (EPERM); - - if (ifp == 0) - return (EADDRNOTAVAIL); - if (ifra->ifra_addr.sin_family != AF_INET && cmd == SIOCSIFADDR) - return (EINVAL); + if ((so->so_state & SS_PRIV) == 0) { + 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 == (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. - */ - + ia = in_ifaddr_alloc(M_WAITOK); + if (ia == (struct in_ifaddr *)NULL) { + error = ENOBUFS; + goto done; + } + IA_HASH_INIT(ia); ifa = &ia->ia_ifa; - + /* Hold a reference for this routine */ + ifaref(ifa); ifa->ifa_addr = (struct sockaddr *)&ia->ia_addr; ifa->ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; ifa->ifa_netmask = (struct sockaddr *)&ia->ia_sockmask; @@ -357,54 +461,57 @@ in_control( ia->ia_ifp = ifp; if (!(ifp->if_flags & IFF_LOOPBACK)) in_interfaces++; + /* if_attach_ifa() holds a reference for ifa_link */ if_attach_ifa(ifp, ifa); ifnet_lock_done(ifp); - - lck_mtx_lock(rt_mtx); + lck_rw_lock_exclusive(in_ifaddr_rwlock); + /* Hold a reference for ia_link */ + ifaref(ifa); TAILQ_INSERT_TAIL(&in_ifaddrhead, ia, ia_link); - lck_mtx_unlock(rt_mtx); + lck_rw_done(in_ifaddr_rwlock); /* Generic protocol plumbing */ - if (error = dlil_plumb_protocol(PF_INET, ifp)) { - kprintf("in.c: warning can't plumb proto if=%s%n type %d error=%d\n", - ifp->if_name, ifp->if_unit, ifp->if_type, error); + if ((error = proto_plumb(PF_INET, ifp))) { + if (error != EEXIST) { + kprintf("in.c: warning can't plumb proto if=%s%d type %d error=%d\n", + ifp->if_name, ifp->if_unit, ifp->if_type, error); + } error = 0; /*discard error, can be cold with unsupported interfaces */ } - } break; case SIOCPROTOATTACH: case SIOCPROTODETACH: - if (p && (error = proc_suser(p)) != 0) - return error; - if (ifp == 0) - return (EADDRNOTAVAIL); + 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); ifnet_lock_exclusive(ifp); if (ifr->ifr_intval) ifp->if_eflags |= IFEF_AUTOCONFIGURING; @@ -414,8 +521,6 @@ in_control( break; case SIOCARPIPLL: - if (ifp == 0) - return (EADDRNOTAVAIL); ipv4_ll_arp_aware = 1; ifnet_lock_exclusive(ifp); if (ifr->ifr_data) @@ -430,14 +535,18 @@ in_control( break; case SIOCGIFBRDADDR: - if ((ifp->if_flags & IFF_BROADCAST) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_BROADCAST) == 0) { + error = EINVAL; + break; + } *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_broadaddr; break; case SIOCGIFDSTADDR: - if ((ifp->if_flags & IFF_POINTOPOINT) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) { + error = EINVAL; + break; + } *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_dstaddr; break; @@ -446,17 +555,21 @@ in_control( break; case SIOCSIFDSTADDR: - if ((ifp->if_flags & IFF_POINTOPOINT) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) { + error = EINVAL; + break; + } 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); + error = ifnet_ioctl(ifp, PF_INET, SIOCSIFDSTADDR, ia); + if (error == EOPNOTSUPP) { + error = 0; + } if (error) { - ia->ia_dstaddr = oldaddr; - return error; + ia->ia_dstaddr = oldaddr; + break; } ev_msg.vendor_code = KEV_VENDOR_APPLE; @@ -479,7 +592,7 @@ in_control( in_event_data.ia_netbroadcast = ia->ia_netbroadcast; 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); @@ -498,8 +611,10 @@ in_control( break; case SIOCSIFBRDADDR: - if ((ifp->if_flags & IFF_BROADCAST) == 0) - return (EINVAL); + if ((ifp->if_flags & IFF_BROADCAST) == 0) { + error = EINVAL; + break; + } ia->ia_broadaddr = *(struct sockaddr_in *)&ifr->ifr_broadaddr; ev_msg.vendor_code = KEV_VENDOR_APPLE; @@ -522,7 +637,7 @@ in_control( in_event_data.ia_netbroadcast = ia->ia_netbroadcast; 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); @@ -533,14 +648,21 @@ in_control( 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: - error = dlil_plumb_protocol(PF_INET, ifp); - if (error) - return(error); - break; + error = proto_plumb(PF_INET, ifp); + break; case SIOCPROTODETACH: // if an ip address is still present, refuse to detach @@ -549,16 +671,18 @@ in_control( if (ifa->ifa_addr->sa_family == AF_INET) break; ifnet_lock_done(ifp); - if (ifa != 0) - return EBUSY; + if (ifa != 0) { + error = EBUSY; + break; + } - error = dlil_unplumb_protocol(PF_INET, ifp); - if (error) - return(error); + error = proto_unplumb(PF_INET, ifp); break; - case SIOCSIFNETMASK: + case SIOCSIFNETMASK: { + u_long i; + i = ifra->ifra_addr.sin_addr.s_addr; ia->ia_subnetmask = ntohl(ia->ia_sockmask.sin_addr.s_addr = i); ev_msg.vendor_code = KEV_VENDOR_APPLE; @@ -581,7 +705,7 @@ in_control( in_event_data.ia_netbroadcast = ia->ia_netbroadcast; 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); @@ -590,11 +714,12 @@ in_control( kev_post_msg(&ev_msg); break; - + } case SIOCAIFADDR: maskIsNew = 0; hostIsNew = 1; error = 0; + if (ia->ia_addr.sin_family == AF_INET) { if (ifra->ifra_addr.sin_len == 0) { ifra->ifra_addr = ia->ia_addr; @@ -614,12 +739,17 @@ in_control( (ifra->ifra_dstaddr.sin_family == AF_INET)) { in_ifscrub(ifp, ia, 0); 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)) { error = in_ifinit(ifp, ia, &ifra->ifra_addr, 0); } +#if PF + if (!error) + (void) pf_ifaddr_hook(ifp, cmd); +#endif /* PF */ if ((ifp->if_flags & IFF_BROADCAST) && (ifra->ifra_broadaddr.sin_family == AF_INET)) ia->ia_broadaddr = ifra->ifra_broadaddr; @@ -652,7 +782,7 @@ in_control( in_event_data.ia_netbroadcast = ia->ia_netbroadcast; 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); @@ -660,15 +790,15 @@ in_control( kev_post_msg(&ev_msg); } - - return (error); + break; case SIOCDIFADDR: - error = dlil_ioctl(PF_INET, ifp, SIOCDIFADDR, (caddr_t)ia); + error = ifnet_ioctl(ifp, PF_INET, SIOCDIFADDR, ia); if (error == EOPNOTSUPP) error = 0; - if (error) - return error; + if (error != 0) { + break; + } /* Fill out the kernel event information */ ev_msg.vendor_code = KEV_VENDOR_APPLE; @@ -691,32 +821,36 @@ in_control( in_event_data.ia_netbroadcast = ia->ia_netbroadcast; 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; - lck_mtx_lock(rt_mtx); + ifa = &ia->ia_ifa; + lck_rw_lock_exclusive(in_ifaddr_rwlock); + /* Release ia_link reference */ + ifafree(ifa); TAILQ_REMOVE(&in_ifaddrhead, ia, ia_link); + if (IA_IS_HASHED(ia)) + in_iahash_remove(ia); + lck_rw_done(in_ifaddr_rwlock); + /* * in_ifscrub kills the interface route. */ - in_ifscrub(ifp, ia, 1); - ifa = &ia->ia_ifa; - lck_mtx_unlock(rt_mtx); + in_ifscrub(ifp, ia, 0); ifnet_lock_exclusive(ifp); + /* if_detach_ifa() releases ifa_link reference */ if_detach_ifa(ifp, ifa); - ifafree(&ia->ia_ifa); - #ifdef __APPLE__ - /* - * If the interface supports multicast, and no address is left, - * remove the "all hosts" multicast group from that interface. - */ + /* + * 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; + struct in_multi *inm = NULL; TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) if (ifa->ifa_addr->sa_family == AF_INET) @@ -735,6 +869,23 @@ in_control( /* Post the kernel event */ kev_post_msg(&ev_msg); + + /* + * 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() */ + ifafree(ifa); + } +#if PF + (void) pf_ifaddr_hook(ifp, cmd); +#endif /* PF */ break; #ifdef __APPLE__ @@ -827,9 +978,13 @@ in_control( #endif /* __APPLE__ */ default: - return EOPNOTSUPP; + error = EOPNOTSUPP; } - return (0); + done: + if (ia != NULL) { + ifafree(&ia->ia_ifa); + } + return (error); } /* @@ -922,7 +1077,8 @@ in_lifaddr_ioctl( 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; @@ -1025,14 +1181,77 @@ in_ifscrub( if ((ia->ia_flags & IFA_ROUTE) == 0) return; if (!locked) - lck_mtx_lock(rt_mtx); + lck_mtx_lock(rnh_lock); if (ifp->if_flags & (IFF_LOOPBACK|IFF_POINTOPOINT)) rtinit_locked(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); else rtinit_locked(&(ia->ia_ifa), (int)RTM_DELETE, 0); ia->ia_flags &= ~IFA_ROUTE; if (!locked) - lck_mtx_unlock(rt_mtx); + lck_mtx_unlock(rnh_lock); +} + +/* + * Caller must hold in_ifaddr_rwlock as writer. + */ +static void +in_iahash_remove(struct in_ifaddr *ia) +{ + if (!IA_IS_HASHED(ia)) + panic("attempt to remove wrong ia %p from hash table\n", ia); + + TAILQ_REMOVE(INADDR_HASH(ia->ia_addr.sin_addr.s_addr), ia, ia_hash); + IA_HASH_INIT(ia); + ifafree(&ia->ia_ifa); +} + +/* + * Caller must hold in_ifaddr_rwlock as writer. + */ +static void +in_iahash_insert(struct in_ifaddr *ia) +{ + if (ia->ia_addr.sin_family != AF_INET) + panic("attempt to insert wrong ia %p into hash table\n", ia); + else if (IA_IS_HASHED(ia)) + panic("attempt to double-insert ia %p into hash table\n", ia); + + TAILQ_INSERT_HEAD(INADDR_HASH(ia->ia_addr.sin_addr.s_addr), ia, ia_hash); + ifaref(&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; + + if (ia->ia_addr.sin_family != AF_INET) + panic("attempt to insert wrong ia %p into hash table\n", ia); + else if (IA_IS_HASHED(ia)) + panic("attempt to double-insert ia %p into hash table\n", ia); + + TAILQ_FOREACH(tmp_ifa, INADDR_HASH(ia->ia_addr.sin_addr.s_addr), ia_hash) + if (IA_SIN(tmp_ifa)->sin_addr.s_addr == ia->ia_addr.sin_addr.s_addr) + break; + tmp_ifp = (tmp_ifa == NULL) ? NULL : tmp_ifa->ia_ifp; + + 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); + + ifaref(&ia->ia_ifa); } /* @@ -1046,28 +1265,78 @@ in_ifinit( struct sockaddr_in *sin, int scrub) { - u_long i = ntohl(sin->sin_addr.s_addr); + u_int32_t i = ntohl(sin->sin_addr.s_addr); struct sockaddr_in oldaddr; int flags = RTF_UP, error; + struct ifaddr *ifa0; + unsigned int cmd; + int oldremoved = 0; + + /* Take an extra reference for this routine */ + ifaref(&ia->ia_ifa); + lck_rw_lock_exclusive(in_ifaddr_rwlock); 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); + 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() */ + ifafree(ifa0); + if (error) { + lck_rw_lock_exclusive(in_ifaddr_rwlock); + 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); + } + lck_rw_done(in_ifaddr_rwlock); + /* Release extra reference taken above */ + ifafree(&ia->ia_ifa); return (error); } + lck_mtx_lock(rnh_lock); if (scrub) { ia->ia_ifa.ifa_addr = (struct sockaddr *)&oldaddr; - in_ifscrub(ifp, ia, 0); + in_ifscrub(ifp, ia, 1); ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; } if (IN_CLASSA(i)) @@ -1102,12 +1371,19 @@ in_ifinit( 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) { + lck_mtx_unlock(rnh_lock); + /* Release extra reference taken above */ + ifafree(&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) + if ((error = rtinit_locked(&(ia->ia_ifa), (int)RTM_ADD, flags)) == 0) ia->ia_flags |= IFA_ROUTE; + lck_mtx_unlock(rnh_lock); + /* XXX check if the subnet route points to the same interface */ if (error == EEXIST) error = 0; @@ -1127,6 +1403,9 @@ in_ifinit( if (inm == 0) in_addmulti(&addr, ifp); } + + /* Release extra reference taken above */ + ifafree(&ia->ia_ifa); return (error); } @@ -1140,7 +1419,7 @@ in_broadcast( struct ifnet *ifp) { struct ifaddr *ifa; - u_long t; + u_int32_t t; if (in.s_addr == INADDR_BROADCAST || in.s_addr == INADDR_ANY) @@ -1171,7 +1450,7 @@ in_broadcast( * only exist when an interface gets a secondary * address. */ - ia->ia_subnetmask != (u_long)0xffffffff) { + ia->ia_subnetmask != (u_int32_t)0xffffffff) { ifnet_lock_done(ifp); return 1; } @@ -1192,9 +1471,9 @@ in_free_inm( * we are leaving the multicast group. */ igmp_leavegroup(inm); - lck_mtx_lock(rt_mtx); + lck_mtx_lock(rnh_lock); LIST_REMOVE(inm, inm_link); - lck_mtx_unlock(rt_mtx); + lck_mtx_unlock(rnh_lock); FREE(inm, M_IPMADDR); } @@ -1242,13 +1521,13 @@ in_addmulti( inm->inm_addr = *ap; inm->inm_ifp = ifp; inm->inm_ifma = ifma; - lck_mtx_lock(rt_mtx); + lck_mtx_lock(rnh_lock); if (ifma->ifma_protospec == NULL) { ifma->ifma_protospec = inm; ifma->ifma_free = in_free_inm; LIST_INSERT_HEAD(&in_multihead, inm, inm_link); } - lck_mtx_unlock(rt_mtx); + lck_mtx_unlock(rnh_lock); if (ifma->ifma_protospec != inm) { _FREE(inm, M_IPMADDR); @@ -1285,17 +1564,17 @@ in_delmulti( { struct in_multi *inm2; - lck_mtx_lock(rt_mtx); + lck_mtx_lock(rnh_lock); LIST_FOREACH(inm2, &in_multihead, inm_link) { if (inm2 == *inm) break; } if (inm2 != *inm) { - lck_mtx_unlock(rt_mtx); - printf("in_delmulti - ignorning invalid inm (0x%x)\n", *inm); + lck_mtx_unlock(rnh_lock); + printf("in_delmulti - ignoring invalid inm (%p)\n", *inm); return; } - lck_mtx_unlock(rt_mtx); + lck_mtx_unlock(rnh_lock); /* We intentionally do this a bit differently than BSD */ if ((*inm)->inm_ifma) { @@ -1306,15 +1585,16 @@ in_delmulti( } #if !NFSCLIENT +int inet_aton(char *cp, struct in_addr *pin); int inet_aton(char * cp, struct in_addr * pin) { - u_char * b = (char *)pin; + u_char * b = (unsigned char *)pin; int i; char * p; for (p = cp, i = 0; i < 4; i++) { - u_long l = strtoul(p, 0, 0); + u_int32_t l = strtoul(p, 0, 0); if (l > 255) return (FALSE); b[i] = l; @@ -1326,3 +1606,85 @@ inet_aton(char * cp, struct in_addr * pin) return (TRUE); } #endif + +/* + * Called as part of ip_init + */ +void +in_ifaddr_init(void) +{ + 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); + + zone_change(inifa_zone, Z_EXPAND, TRUE); +} + +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; + 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; + ctrace_record(&inifa_dbg->inifa_alloc); + } + } + return (inifa); +} + +static void +in_ifaddr_free(struct ifaddr *ifa) +{ + if (ifa->ifa_refcnt != 0) + panic("%s: ifa %p bad ref cnt", __func__, ifa); + if (!(ifa->ifa_debug & IFD_ALLOC)) + panic("%s: ifa %p cannot be freed", __func__, ifa); + + 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)); + } + bzero(ifa, sizeof (struct in_ifaddr)); + zfree(inifa_zone, ifa); +} + +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); + + 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 = OSAddAtomic16(1, (volatile SInt16 *)cnt) % CTRACE_HIST_SIZE; + ctrace_record(&tr[idx]); +}