X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/2d21ac55c334faf3a56e5634905ed6987fc787d4..ecc0ceb4089d506a0b8d16686a95817b331af9cb:/bsd/netinet/in.c diff --git a/bsd/netinet/in.c b/bsd/netinet/in.c index 2663dd4c3..9f65560f8 100644 --- a/bsd/netinet/in.c +++ b/bsd/netinet/in.c @@ -1,8 +1,8 @@ /* - * Copyright (c) 2000-2007 Apple Inc. All rights reserved. + * Copyright (c) 2000-2015 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_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 @@ -11,10 +11,10 @@ * 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, @@ -22,7 +22,7 @@ * 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_OSREFERENCE_LICENSE_HEADER_END@ */ /* @@ -58,7 +58,6 @@ * SUCH DAMAGE. * * @(#)in.c 8.4 (Berkeley) 1/9/95 - * $FreeBSD: src/sys/netinet/in.c,v 1.44.2.5 2001/08/13 16:26:17 ume Exp $ */ #include @@ -72,48 +71,133 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include +#if PF +#include +#endif /* PF */ #include #include #include - #include -#include - #include - #include #include #include -#include +static int inctl_associd(struct socket *, u_long, caddr_t); +static int inctl_connid(struct socket *, u_long, caddr_t); +static int inctl_conninfo(struct socket *, u_long, caddr_t); +static int inctl_autoaddr(struct ifnet *, struct ifreq *); +static int inctl_arpipll(struct ifnet *, struct ifreq *); +static int inctl_setrouter(struct ifnet *, struct ifreq *); +static int inctl_ifaddr(struct ifnet *, struct in_ifaddr *, u_long, + struct ifreq *); +static int inctl_ifdstaddr(struct ifnet *, struct in_ifaddr *, u_long, + struct ifreq *); +static int inctl_ifbrdaddr(struct ifnet *, struct in_ifaddr *, u_long, + struct ifreq *); +static int inctl_ifnetmask(struct ifnet *, struct in_ifaddr *, u_long, + struct ifreq *); + +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_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 in_getassocids(struct socket *, uint32_t *, user_addr_t); +static int in_getconnids(struct socket *, sae_associd_t, uint32_t *, user_addr_t); +static int in_getconninfo(struct socket *, sae_connid_t, uint32_t *, + uint32_t *, int32_t *, user_addr_t, socklen_t *, user_addr_t, socklen_t *, + uint32_t *, user_addr_t, uint32_t *); -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 int subnetsarelocal = 0; +SYSCTL_INT(_net_inet_ip, OID_AUTO, subnets_are_local, + CTLFLAG_RW | CTLFLAG_LOCKED, &subnetsarelocal, 0, ""); -static void in_socktrim(struct sockaddr_in *); -static int in_ifinit(struct ifnet *, - struct in_ifaddr *, struct sockaddr_in *, int); +/* Track whether or not the SIOCARPIPLL ioctl has been called */ +u_int32_t ipv4_ll_arp_aware = 0; -static int subnetsarelocal = 0; -SYSCTL_INT(_net_inet_ip, OID_AUTO, subnets_are_local, CTLFLAG_RW, - &subnetsarelocal, 0, ""); +#define INIFA_TRACE_HIST_SIZE 32 /* size of trace history */ -struct in_multihead in_multihead; /* XXX BSS initialization */ +/* For gdb */ +__private_extern__ unsigned int inifa_trace_hist_size = INIFA_TRACE_HIST_SIZE; -/* 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 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 */ +static const unsigned int in_extra_size = sizeof (struct in_ifextra); +static const unsigned int in_extra_bufsize = in_extra_size + + sizeof (void *) + sizeof (uint64_t); + +/* + * 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) { @@ -121,18 +205,28 @@ inaddr_local(struct in_addr in) 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, 0UL); - - if (rt != NULL) { - if (rt->rt_gateway->sa_family == AF_LINK || - (rt->rt_ifp->if_flags & IFF_LOOPBACK)) + 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; - rtfree(rt); } else { - local = in_localaddr(in); + 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); } @@ -146,27 +240,35 @@ inaddr_local(struct in_addr in) int 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); - for (ia = in_ifaddrhead.tqh_first; ia; - ia = ia->ia_link.tqe_next) + lck_rw_lock_shared(in_ifaddr_rwlock); + for (ia = in_ifaddrhead.tqh_first; ia != NULL; + ia = ia->ia_link.tqe_next) { + IFA_LOCK(&ia->ia_ifa); if ((i & ia->ia_netmask) == ia->ia_net) { - lck_mtx_unlock(rt_mtx); + IFA_UNLOCK(&ia->ia_ifa); + lck_rw_done(in_ifaddr_rwlock); return (1); } - lck_mtx_unlock(rt_mtx); + IFA_UNLOCK(&ia->ia_ifa); + } + lck_rw_done(in_ifaddr_rwlock); } else { - lck_mtx_lock(rt_mtx); - for (ia = in_ifaddrhead.tqh_first; ia; - ia = ia->ia_link.tqe_next) + lck_rw_lock_shared(in_ifaddr_rwlock); + for (ia = in_ifaddrhead.tqh_first; ia != NULL; + ia = ia->ia_link.tqe_next) { + IFA_LOCK(&ia->ia_ifa); if ((i & ia->ia_subnetmask) == ia->ia_subnet) { - lck_mtx_unlock(rt_mtx); + IFA_UNLOCK(&ia->ia_ifa); + lck_rw_done(in_ifaddr_rwlock); return (1); } - lck_mtx_unlock(rt_mtx); + IFA_UNLOCK(&ia->ia_ifa); + } + lck_rw_done(in_ifaddr_rwlock); } return (0); } @@ -176,20 +278,20 @@ in_localaddr(struct in_addr in) * that may not be forwarded, or whether datagrams to that destination * may be forwarded. */ -int +boolean_t 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); + return (FALSE); if (IN_CLASSA(i)) { net = i & IN_CLASSA_NET; if (net == 0 || net == (IN_LOOPBACKNET << IN_CLASSA_NSHIFT)) - return (0); + return (FALSE); } - return (1); + return (TRUE); } /* @@ -198,629 +300,531 @@ in_canforward(struct in_addr in) static void in_socktrim(struct sockaddr_in *ap) { - char *cplim = (char *) &ap->sin_addr; - char *cp = (char *) (&ap->sin_addr + 1); - - ap->sin_len = 0; - while (--cp >= cplim) - if (*cp) { - (ap)->sin_len = cp - (char *) (ap) + 1; - break; - } + char *cplim = (char *)&ap->sin_addr; + char *cp = (char *)(&ap->sin_addr + 1); + + ap->sin_len = 0; + while (--cp >= cplim) + if (*cp) { + (ap)->sin_len = cp - (char *)(ap) + 1; + break; + } } +static int in_interfaces; /* number of external internet interfaces */ + static int -in_mask2len(struct in_addr *mask) +in_domifattach(struct ifnet *ifp) { - size_t x, y; - u_char *p; + int error; - p = (u_char *)mask; - for (x = 0; x < sizeof(*mask); x++) { - if (p[x] != 0xff) - break; - } - y = 0; - if (x < sizeof(*mask)) { - for (y = 0; y < 8; y++) { - if ((p[x] & (0x80 >> y)) == 0) - break; + VERIFY(ifp != NULL); + + if ((error = proto_plumb(PF_INET, ifp)) && error != EEXIST) { + log(LOG_ERR, "%s: proto_plumb returned %d if=%s\n", + __func__, error, if_name(ifp)); + } else if (error == 0 && ifp->if_inetdata == NULL) { + void **pbuf, *base; + struct in_ifextra *ext; + int errorx; + + if ((ext = (struct in_ifextra *)_MALLOC(in_extra_bufsize, + M_IFADDR, M_WAITOK|M_ZERO)) == NULL) { + error = ENOMEM; + errorx = proto_unplumb(PF_INET, ifp); + if (errorx != 0) { + log(LOG_ERR, + "%s: proto_unplumb returned %d if=%s%d\n", + __func__, errorx, ifp->if_name, + ifp->if_unit); + } + goto done; } - } - return x * 8 + y; -} -static void -in_len2mask(struct in_addr *mask, int len) -{ - int i; - u_char *p; - - p = (u_char *)mask; - bzero(mask, sizeof(*mask)); - for (i = 0; i < len / 8; i++) - p[i] = 0xff; - if (len % 8) - p[i] = (0xff00 >> (len % 8)) & 0xff; + /* Align on 64-bit boundary */ + base = (void *)P2ROUNDUP((intptr_t)ext + sizeof (uint64_t), + sizeof (uint64_t)); + VERIFY(((intptr_t)base + in_extra_size) <= + ((intptr_t)ext + in_extra_bufsize)); + pbuf = (void **)((intptr_t)base - sizeof (void *)); + *pbuf = ext; + ifp->if_inetdata = base; + VERIFY(IS_P2ALIGNED(ifp->if_inetdata, sizeof (uint64_t))); + } +done: + if (error == 0 && ifp->if_inetdata != NULL) { + /* + * Since the structure is never freed, we need to + * zero out its contents to avoid reusing stale data. + * A little redundant with allocation above, but it + * keeps the code simpler for all cases. + */ + bzero(ifp->if_inetdata, in_extra_size); + } + return (error); } -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 -in_control( - struct socket *so, - u_long cmd, - caddr_t data, - struct ifnet *ifp, - struct proc *p) +static __attribute__((noinline)) int +inctl_associd(struct socket *so, u_long cmd, caddr_t data) { - 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 = 0; - int hostIsNew, maskIsNew; - struct kev_msg ev_msg; - struct kev_in_data in_event_data; + union { + struct so_aidreq32 a32; + struct so_aidreq64 a64; + } u; + VERIFY(so != NULL); switch (cmd) { - case SIOCALIFADDR: - case SIOCDLIFADDR: - if (p && (error = proc_suser(p)) != 0) - return error; - /*fall through*/ - case SIOCGLIFADDR: - if (!ifp) - return EINVAL; - return in_lifaddr_ioctl(so, cmd, data, ifp, p); - } - - /* - * Find address for this interface, if it exists. - * - * If an alias address was specified, find that one instead of - * the first one on the interface. - */ - if (ifp) { - lck_mtx_lock(rt_mtx); - for (iap = in_ifaddrhead.tqh_first; iap; - iap = iap->ia_link.tqe_next) - if (iap->ia_ifp == ifp) { - if (((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr == - iap->ia_addr.sin_addr.s_addr) { - ia = iap; - break; - } else if (ia == NULL) { - ia = iap; - if (ifr->ifr_addr.sa_family != AF_INET) - break; - } - } - /* take a reference on ia before releasing mutex */ - if (ia != NULL) { - ifaref(&ia->ia_ifa); - } - lck_mtx_unlock(rt_mtx); - } - switch (cmd) { - case SIOCAUTOADDR: - case SIOCARPIPLL: - if (p && (error = proc_suser(p)) != 0) { - goto done; - } - if (ifp == 0) { - error = EADDRNOTAVAIL; - goto done; - } + case SIOCGASSOCIDS32: /* struct so_aidreq32 */ + bcopy(data, &u.a32, sizeof (u.a32)); + error = in_getassocids(so, &u.a32.sar_cnt, u.a32.sar_aidp); + if (error == 0) + bcopy(&u.a32, data, sizeof (u.a32)); break; - case SIOCAIFADDR: - case SIOCDIFADDR: - if (ifp == 0) { - error = EADDRNOTAVAIL; - goto done; - } - if (ifra->ifra_addr.sin_family == AF_INET) { - struct in_ifaddr *oia; - - lck_mtx_lock(rt_mtx); - 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; - } - /* take a reference on ia before releasing mutex */ - if (ia != NULL && ia != oia) { - ifaref(&ia->ia_ifa); - } - lck_mtx_unlock(rt_mtx); - 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)) { - error = EDESTADDRREQ; - goto done; - } - } - 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) { - 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) { - error = ENOBUFS; - goto done; - } - bzero((caddr_t)ia, sizeof *ia); - /* - * Protect from ipintr() traversing address list - * while we're modifying it. - */ - - ifa = &ia->ia_ifa; + case SIOCGASSOCIDS64: /* struct so_aidreq64 */ + bcopy(data, &u.a64, sizeof (u.a64)); + error = in_getassocids(so, &u.a64.sar_cnt, u.a64.sar_aidp); + if (error == 0) + bcopy(&u.a64, data, sizeof (u.a64)); + break; - ia->ia_addr.sin_family = AF_INET; - ia->ia_addr.sin_len = sizeof (ia->ia_addr); - ifa->ifa_addr = (struct sockaddr *)&ia->ia_addr; - ifa->ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; - ifa->ifa_netmask = (struct sockaddr *)&ia->ia_sockmask; - ia->ia_sockmask.sin_len = 8; - ifnet_lock_exclusive(ifp); - if (ifp->if_flags & IFF_BROADCAST) { - ia->ia_broadaddr.sin_len = sizeof(ia->ia_addr); - ia->ia_broadaddr.sin_family = AF_INET; - } - ia->ia_ifp = ifp; - if (!(ifp->if_flags & IFF_LOOPBACK)) - in_interfaces++; - if_attach_ifa(ifp, ifa); - ifnet_lock_done(ifp); + default: + VERIFY(0); + /* NOTREACHED */ + } - lck_mtx_lock(rt_mtx); - ifaref(&ia->ia_ifa); - TAILQ_INSERT_TAIL(&in_ifaddrhead, ia, ia_link); - lck_mtx_unlock(rt_mtx); + return (error); +} - /* Generic protocol plumbing */ +static __attribute__((noinline)) int +inctl_connid(struct socket *so, u_long cmd, caddr_t data) +{ + int error = 0; + union { + struct so_cidreq32 c32; + struct so_cidreq64 c64; + } u; - 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 */ - } + VERIFY(so != NULL); - } + switch (cmd) { + case SIOCGCONNIDS32: /* struct so_cidreq32 */ + bcopy(data, &u.c32, sizeof (u.c32)); + error = in_getconnids(so, u.c32.scr_aid, &u.c32.scr_cnt, + u.c32.scr_cidp); + if (error == 0) + bcopy(&u.c32, data, sizeof (u.c32)); break; - case SIOCPROTOATTACH: - case SIOCPROTODETACH: - if (p && (error = proc_suser(p)) != 0) { - goto done; - } - if (ifp == 0) { - error = EADDRNOTAVAIL; - goto done; - } + case SIOCGCONNIDS64: /* struct so_cidreq64 */ + bcopy(data, &u.c64, sizeof (u.c64)); + error = in_getconnids(so, u.c64.scr_aid, &u.c64.scr_cnt, + u.c64.scr_cidp); + if (error == 0) + bcopy(&u.c64, data, sizeof (u.c64)); break; - - case SIOCSIFBRDADDR: -#ifdef __APPLE__ - if ((so->so_state & SS_PRIV) == 0) { - error = EPERM; - goto done; - } -#else - if (p && (error = suser(p)) != 0) { - goto done; - } -#endif - /* FALLTHROUGH */ - case SIOCGIFADDR: - case SIOCGIFNETMASK: - case SIOCGIFDSTADDR: - case SIOCGIFBRDADDR: - if (ia == (struct in_ifaddr *)0) { - error = EADDRNOTAVAIL; - goto done; - } - break; + default: + VERIFY(0); + /* NOTREACHED */ } - switch (cmd) { - case SIOCAUTOADDR: - 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: - *((struct sockaddr_in *)&ifr->ifr_addr) = ia->ia_addr; - break; + return (error); +} - case SIOCGIFBRDADDR: - if ((ifp->if_flags & IFF_BROADCAST) == 0) { - error = EINVAL; - break; - } - *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_broadaddr; - break; +static __attribute__((noinline)) int +inctl_conninfo(struct socket *so, u_long cmd, caddr_t data) +{ + int error = 0; + union { + struct so_cinforeq32 ci32; + struct so_cinforeq64 ci64; + } u; - case SIOCGIFDSTADDR: - if ((ifp->if_flags & IFF_POINTOPOINT) == 0) { - error = EINVAL; - break; - } - *((struct sockaddr_in *)&ifr->ifr_dstaddr) = ia->ia_dstaddr; - break; + VERIFY(so != NULL); - case SIOCGIFNETMASK: - *((struct sockaddr_in *)&ifr->ifr_addr) = ia->ia_sockmask; + switch (cmd) { + case SIOCGCONNINFO32: /* struct so_cinforeq32 */ + bcopy(data, &u.ci32, sizeof (u.ci32)); + error = in_getconninfo(so, u.ci32.scir_cid, &u.ci32.scir_flags, + &u.ci32.scir_ifindex, &u.ci32.scir_error, u.ci32.scir_src, + &u.ci32.scir_src_len, u.ci32.scir_dst, &u.ci32.scir_dst_len, + &u.ci32.scir_aux_type, u.ci32.scir_aux_data, + &u.ci32.scir_aux_len); + if (error == 0) + bcopy(&u.ci32, data, sizeof (u.ci32)); break; - case SIOCSIFDSTADDR: - if ((ifp->if_flags & IFF_POINTOPOINT) == 0) { - error = EINVAL; - break; - } - oldaddr = ia->ia_dstaddr; - ia->ia_dstaddr = *(struct sockaddr_in *)&ifr->ifr_dstaddr; - error = ifnet_ioctl(ifp, PF_INET, SIOCSIFDSTADDR, ia); - if (error == EOPNOTSUPP) { - error = 0; - } - if (error) { - ia->ia_dstaddr = oldaddr; - break; - } + case SIOCGCONNINFO64: /* struct so_cinforeq64 */ + bcopy(data, &u.ci64, sizeof (u.ci64)); + error = in_getconninfo(so, u.ci64.scir_cid, &u.ci64.scir_flags, + &u.ci64.scir_ifindex, &u.ci64.scir_error, u.ci64.scir_src, + &u.ci64.scir_src_len, u.ci64.scir_dst, &u.ci64.scir_dst_len, + &u.ci64.scir_aux_type, u.ci64.scir_aux_data, + &u.ci64.scir_aux_len); + if (error == 0) + bcopy(&u.ci64, data, sizeof (u.ci64)); + break; - 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) - in_event_data.ia_dstaddr = - ((struct sockaddr_in *)ia->ia_ifa.ifa_dstaddr)->sin_addr; - else - in_event_data.ia_dstaddr.s_addr = 0; - - in_event_data.ia_addr = ia->ia_addr.sin_addr; - in_event_data.ia_net = ia->ia_net; - in_event_data.ia_netmask = ia->ia_netmask; - in_event_data.ia_subnet = ia->ia_subnet; - in_event_data.ia_subnetmask = ia->ia_subnetmask; - 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; + default: + VERIFY(0); + /* NOTREACHED */ + } - 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; + return (error); +} - kev_post_msg(&ev_msg); +/* + * Caller passes in the ioctl data pointer directly via "ifr", with the + * expectation that this routine always uses bcopy() or other byte-aligned + * memory accesses. + */ +static __attribute__((noinline)) int +inctl_autoaddr(struct ifnet *ifp, struct ifreq *ifr) +{ + int error = 0, intval; + VERIFY(ifp != NULL); - if (ia->ia_flags & IFA_ROUTE) { - ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&oldaddr; - rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST); - ia->ia_ifa.ifa_dstaddr = - (struct sockaddr *)&ia->ia_dstaddr; - rtinit(&(ia->ia_ifa), (int)RTM_ADD, RTF_HOST|RTF_UP); - } - break; + bcopy(&ifr->ifr_intval, &intval, sizeof (intval)); - case SIOCSIFBRDADDR: - if ((ifp->if_flags & IFF_BROADCAST) == 0) { - error = EINVAL; - break; + ifnet_lock_exclusive(ifp); + if (intval) { + /* + * An interface in IPv4 router mode implies that it + * is configured with a static IP address and should + * not act as a DHCP client; prevent SIOCAUTOADDR from + * being set in that mode. + */ + if (ifp->if_eflags & IFEF_IPV4_ROUTER) { + intval = 0; /* be safe; clear flag if set */ + error = EBUSY; + } else { + ifp->if_eflags |= IFEF_AUTOCONFIGURING; } - ia->ia_broadaddr = *(struct sockaddr_in *)&ifr->ifr_broadaddr; + } + if (!intval) + ifp->if_eflags &= ~IFEF_AUTOCONFIGURING; + ifnet_lock_done(ifp); - 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; + return (error); +} - if (ia->ia_ifa.ifa_dstaddr) - in_event_data.ia_dstaddr = - ((struct sockaddr_in *)ia->ia_ifa.ifa_dstaddr)->sin_addr; - else - in_event_data.ia_dstaddr.s_addr = 0; - - in_event_data.ia_addr = ia->ia_addr.sin_addr; - in_event_data.ia_net = ia->ia_net; - in_event_data.ia_netmask = ia->ia_netmask; - in_event_data.ia_subnet = ia->ia_subnet; - in_event_data.ia_subnetmask = ia->ia_subnetmask; - 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; +/* + * Caller passes in the ioctl data pointer directly via "ifr", with the + * expectation that this routine always uses bcopy() or other byte-aligned + * memory accesses. + */ +static __attribute__((noinline)) int +inctl_arpipll(struct ifnet *ifp, struct ifreq *ifr) +{ + int error = 0, intval; - 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; + VERIFY(ifp != NULL); - kev_post_msg(&ev_msg); + bcopy(&ifr->ifr_intval, &intval, sizeof (intval)); + ipv4_ll_arp_aware = 1; - break; + ifnet_lock_exclusive(ifp); + if (intval) { + /* + * An interface in IPv4 router mode implies that it + * is configured with a static IP address and should + * not have to deal with IPv4 Link-Local Address; + * prevent SIOCARPIPLL from being set in that mode. + */ + if (ifp->if_eflags & IFEF_IPV4_ROUTER) { + intval = 0; /* be safe; clear flag if set */ + error = EBUSY; + } else { + ifp->if_eflags |= IFEF_ARPLL; + } + } + if (!intval) + ifp->if_eflags &= ~IFEF_ARPLL; + ifnet_lock_done(ifp); - case SIOCSIFADDR: - error = in_ifinit(ifp, ia, (struct sockaddr_in *) &ifr->ifr_addr, 1); - break; + return (error); +} - case SIOCPROTOATTACH: - error = proto_plumb(PF_INET, ifp); - break; - - case SIOCPROTODETACH: - // if an ip address is still present, refuse to detach - ifnet_lock_shared(ifp); - TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) - if (ifa->ifa_addr->sa_family == AF_INET) - break; - ifnet_lock_done(ifp); - if (ifa != 0) { - error = EBUSY; - break; - } +/* + * Handle SIOCSETROUTERMODE to set or clear the IPv4 router mode flag on + * the interface. When in this mode, IPv4 Link-Local Address support is + * disabled in ARP, and DHCP client support is disabled in IP input; turning + * any of them on would cause an error to be returned. Entering or exiting + * this mode will result in the removal of IPv4 addresses currently configured + * on the interface. + * + * Caller passes in the ioctl data pointer directly via "ifr", with the + * expectation that this routine always uses bcopy() or other byte-aligned + * memory accesses. + */ +static __attribute__((noinline)) int +inctl_setrouter(struct ifnet *ifp, struct ifreq *ifr) +{ + int error = 0, intval; - error = proto_unplumb(PF_INET, ifp); - break; - + VERIFY(ifp != NULL); - 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; - ev_msg.kev_class = KEV_NETWORK_CLASS; - ev_msg.kev_subclass = KEV_INET_SUBCLASS; - - ev_msg.event_code = KEV_INET_SIFNETMASK; + /* Router mode isn't valid for loopback */ + if (ifp->if_flags & IFF_LOOPBACK) + return (ENODEV); - if (ia->ia_ifa.ifa_dstaddr) - in_event_data.ia_dstaddr = - ((struct sockaddr_in *)ia->ia_ifa.ifa_dstaddr)->sin_addr; - else - in_event_data.ia_dstaddr.s_addr = 0; - - in_event_data.ia_addr = ia->ia_addr.sin_addr; - in_event_data.ia_net = ia->ia_net; - in_event_data.ia_netmask = ia->ia_netmask; - in_event_data.ia_subnet = ia->ia_subnet; - in_event_data.ia_subnetmask = ia->ia_subnetmask; - 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; + bcopy(&ifr->ifr_intval, &intval, sizeof (intval)); - 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; + ifnet_lock_exclusive(ifp); + if (intval) { + ifp->if_eflags |= IFEF_IPV4_ROUTER; + ifp->if_eflags &= ~(IFEF_ARPLL | IFEF_AUTOCONFIGURING); + } else { + ifp->if_eflags &= ~IFEF_IPV4_ROUTER; + } + ifnet_lock_done(ifp); - kev_post_msg(&ev_msg); + /* purge all IPv4 addresses configured on this interface */ + in_purgeaddrs(ifp); + + return (error); +} + +/* + * Caller passes in the ioctl data pointer directly via "ifr", with the + * expectation that this routine always uses bcopy() or other byte-aligned + * memory accesses. + */ +static __attribute__((noinline)) int +inctl_ifaddr(struct ifnet *ifp, struct in_ifaddr *ia, u_long cmd, + struct ifreq *ifr) +{ + struct kev_in_data in_event_data; + struct kev_msg ev_msg; + struct sockaddr_in addr; + struct ifaddr *ifa; + int error = 0; + + VERIFY(ifp != NULL); + + bzero(&in_event_data, sizeof (struct kev_in_data)); + bzero(&ev_msg, sizeof (struct kev_msg)); + switch (cmd) { + case SIOCGIFADDR: /* struct ifreq */ + if (ia == NULL) { + error = EADDRNOTAVAIL; + break; + } + IFA_LOCK(&ia->ia_ifa); + bcopy(&ia->ia_addr, &ifr->ifr_addr, sizeof (addr)); + IFA_UNLOCK(&ia->ia_ifa); break; - } - case SIOCAIFADDR: + + case SIOCSIFADDR: /* struct ifreq */ + VERIFY(ia != NULL); + bcopy(&ifr->ifr_addr, &addr, sizeof (addr)); + /* + * 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, &addr, 1); + if (error == 0) { + (void) ifnet_notify_address(ifp, AF_INET); + } + break; + + case SIOCAIFADDR: { /* struct {if,in_}aliasreq */ + struct in_aliasreq *ifra = (struct in_aliasreq *)ifr; + struct sockaddr_in broadaddr, mask; + int hostIsNew, maskIsNew; + + VERIFY(ia != NULL); + bcopy(&ifra->ifra_addr, &addr, sizeof (addr)); + bcopy(&ifra->ifra_broadaddr, &broadaddr, sizeof (broadaddr)); + bcopy(&ifra->ifra_mask, &mask, sizeof (mask)); + 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; + if (addr.sin_len == 0) { + addr = ia->ia_addr; hostIsNew = 0; - } else if (ifra->ifra_addr.sin_addr.s_addr == - ia->ia_addr.sin_addr.s_addr) + } else if (addr.sin_addr.s_addr == + ia->ia_addr.sin_addr.s_addr) { hostIsNew = 0; + } } - if (ifra->ifra_mask.sin_len) { + if (mask.sin_len) { + IFA_UNLOCK(&ia->ia_ifa); in_ifscrub(ifp, ia, 0); - ia->ia_sockmask = ifra->ifra_mask; + IFA_LOCK(&ia->ia_ifa); + ia->ia_sockmask = mask; ia->ia_subnetmask = - ntohl(ia->ia_sockmask.sin_addr.s_addr); + ntohl(ia->ia_sockmask.sin_addr.s_addr); maskIsNew = 1; } if ((ifp->if_flags & IFF_POINTOPOINT) && - (ifra->ifra_dstaddr.sin_family == AF_INET)) { + (broadaddr.sin_family == AF_INET)) { + IFA_UNLOCK(&ia->ia_ifa); in_ifscrub(ifp, ia, 0); - ia->ia_dstaddr = ifra->ifra_dstaddr; + IFA_LOCK(&ia->ia_ifa); + ia->ia_dstaddr = broadaddr; + 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 (addr.sin_family == AF_INET && (hostIsNew || maskIsNew)) { + IFA_UNLOCK(&ia->ia_ifa); + error = in_ifinit(ifp, ia, &addr, 0); + } else { + IFA_UNLOCK(&ia->ia_ifa); + } + if (error == 0) { + (void) ifnet_notify_address(ifp, AF_INET); } + IFA_LOCK(&ia->ia_ifa); if ((ifp->if_flags & IFF_BROADCAST) && - (ifra->ifra_broadaddr.sin_family == AF_INET)) - ia->ia_broadaddr = ifra->ifra_broadaddr; + (broadaddr.sin_family == AF_INET)) + ia->ia_broadaddr = broadaddr; /* * Report event. */ - if ((error == 0) || (error == EEXIST)) { - ev_msg.vendor_code = KEV_VENDOR_APPLE; - ev_msg.kev_class = KEV_NETWORK_CLASS; - ev_msg.kev_subclass = KEV_INET_SUBCLASS; - - if (hostIsNew) - ev_msg.event_code = KEV_INET_NEW_ADDR; - else - ev_msg.event_code = KEV_INET_CHANGED_ADDR; - - if (ia->ia_ifa.ifa_dstaddr) - in_event_data.ia_dstaddr = - ((struct sockaddr_in *)ia->ia_ifa.ifa_dstaddr)->sin_addr; - else - in_event_data.ia_dstaddr.s_addr = 0; - - in_event_data.ia_addr = ia->ia_addr.sin_addr; - in_event_data.ia_net = ia->ia_net; - in_event_data.ia_netmask = ia->ia_netmask; - in_event_data.ia_subnet = ia->ia_subnet; - in_event_data.ia_subnetmask = ia->ia_subnetmask; - 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; - - 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); + ev_msg.vendor_code = KEV_VENDOR_APPLE; + ev_msg.kev_class = KEV_NETWORK_CLASS; + ev_msg.kev_subclass = KEV_INET_SUBCLASS; + + if (hostIsNew) + ev_msg.event_code = KEV_INET_NEW_ADDR; + else + ev_msg.event_code = KEV_INET_CHANGED_ADDR; + + if (ia->ia_ifa.ifa_dstaddr) { + in_event_data.ia_dstaddr = + ((struct sockaddr_in *)(void *)ia-> + ia_ifa.ifa_dstaddr)->sin_addr; + } else { + in_event_data.ia_dstaddr.s_addr = INADDR_ANY; + } + in_event_data.ia_addr = ia->ia_addr.sin_addr; + in_event_data.ia_net = ia->ia_net; + in_event_data.ia_netmask = ia->ia_netmask; + 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); + (void) strlcpy(&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 = 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); } break; + } - case SIOCDIFADDR: + case SIOCDIFADDR: /* struct ifreq */ + VERIFY(ia != NULL); error = ifnet_ioctl(ifp, PF_INET, SIOCDIFADDR, ia); if (error == EOPNOTSUPP) error = 0; - if (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; - - if (ia->ia_ifa.ifa_dstaddr) - in_event_data.ia_dstaddr = - ((struct sockaddr_in *)ia->ia_ifa.ifa_dstaddr)->sin_addr; - else - in_event_data.ia_dstaddr.s_addr = 0; - - in_event_data.ia_addr = ia->ia_addr.sin_addr; - in_event_data.ia_net = ia->ia_net; - in_event_data.ia_netmask = ia->ia_netmask; - in_event_data.ia_subnet = ia->ia_subnet; - in_event_data.ia_subnetmask = ia->ia_subnetmask; - in_event_data.ia_netbroadcast = ia->ia_netbroadcast; - strncpy(&in_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ); + 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 *) + (void *)ia->ia_ifa.ifa_dstaddr)->sin_addr; + } else { + in_event_data.ia_dstaddr.s_addr = INADDR_ANY; + } + in_event_data.ia_addr = ia->ia_addr.sin_addr; + in_event_data.ia_net = ia->ia_net; + in_event_data.ia_netmask = ia->ia_netmask; + 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); + (void) strlcpy(&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 */ + 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, 1); - ifa = &ia->ia_ifa; -#if CONFIG_FORCE_OUT_IFP - // Cleanup any pdp hack related route - if (ia->ia_route) - { - ia->ia_route->rt_flags &= ~RTF_UP; - rtfree_locked(ia->ia_route); - ia->ia_route = NULL; - } -#endif - lck_mtx_unlock(rt_mtx); + in_ifscrub(ifp, ia, 0); ifnet_lock_exclusive(ifp); + IFA_LOCK(ifa); + /* if_detach_ifa() releases ifa_link reference */ if_detach_ifa(ifp, 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 = NULL; - - TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) - if (ifa->ifa_addr->sa_family == AF_INET) - break; + /* Our reference to this address is dropped at the bottom */ + IFA_UNLOCK(ifa); + + /* invalidate route caches */ + routegenid_inet_update(); + + /* + * 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) || + ifp->if_allhostsinm != NULL) { - if (ifa == 0) { - addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); - IN_LOOKUP_MULTI(addr, ifp, inm); + 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 (inm) - in_delmulti(&inm); - } else + + 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; + + in_delmulti(inm); + /* release the reference for allhostsinm */ + INM_REMREF(inm); + } + lck_mtx_unlock(&ifp->if_addrconfig_lock); + } else { ifnet_lock_done(ifp); -#endif + } /* Post the kernel event */ kev_post_msg(&ev_msg); @@ -831,316 +835,763 @@ in_control( */ ifa = ifa_ifpgetprimary(ifp, AF_INET); if (ifa != NULL) { + /* + * NOTE: SIOCSIFADDR is defined with struct ifreq + * as parameter, but here we are sending it down + * to the interface with a pointer to struct ifaddr, + * for legacy reasons. + */ error = ifnet_ioctl(ifp, PF_INET, SIOCSIFADDR, ifa); if (error == EOPNOTSUPP) error = 0; /* Release reference from ifa_ifpgetprimary() */ - ifafree(ifa); + IFA_REMREF(ifa); } + (void) ifnet_notify_address(ifp, AF_INET); break; -#ifdef __APPLE__ - case SIOCSETOT: { - /* - * Inspiration from tcp_ctloutput() and ip_ctloutput() - * Special ioctl for OpenTransport sockets - */ - struct inpcb *inp, *cloned_inp; - int error2 = 0; - int cloned_fd = *(int *)data; - - inp = sotoinpcb(so); - if (inp == NULL) { - break; - } - - /* let's make sure it's either -1 or a valid file descriptor */ - if (cloned_fd != -1) { - struct socket *cloned_so; - error2 = file_socket(cloned_fd, &cloned_so); - if (error2){ - break; - } - cloned_inp = sotoinpcb(cloned_so); - file_drop(cloned_fd); - } else { - cloned_inp = NULL; - } - - if (cloned_inp == NULL) { - /* OT always uses IP_PORTRANGE_HIGH */ - inp->inp_flags &= ~(INP_LOWPORT); - inp->inp_flags |= INP_HIGHPORT; - /* For UDP, OT allows broadcast by default */ - if (so->so_type == SOCK_DGRAM) - so->so_options |= SO_BROADCAST; - /* For TCP we want to see MSG_OOB when receive urgent data */ - else if (so->so_type == SOCK_STREAM) - so->so_options |= SO_WANTOOBFLAG; - } else { - inp->inp_ip_tos = cloned_inp->inp_ip_tos; - inp->inp_ip_ttl = cloned_inp->inp_ip_ttl; - inp->inp_flags = cloned_inp->inp_flags; - - /* 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; - - if (imo == NULL) { - /* - * No multicast option buffer attached to the pcb; - * allocate one. - */ - imo = (struct ip_moptions*) - _MALLOC(sizeof(*imo), M_IPMOPTS, M_WAITOK); - if (imo == NULL) { - error2 = ENOBUFS; - break; - } - 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) { - error2 = ENOBUFS; - break; - } - } - if (i < cloned_imo->imo_num_memberships) { - /* Failed, perform cleanup */ - for (i--; i >= 0; i--) - in_delmulti(&imo->imo_membership[i]); - imo->imo_num_memberships = 0; - break; - } - } - } - break; - } -#endif /* __APPLE__ */ - default: - error = EOPNOTSUPP; - } - done: - if (ia != NULL) { - ifafree(&ia->ia_ifa); + VERIFY(0); + /* NOTREACHED */ } + return (error); } /* - * SIOC[GAD]LIFADDR. - * SIOCGLIFADDR: get first address. (?!?) - * SIOCGLIFADDR with IFLR_PREFIX: - * get first address that matches the specified prefix. - * SIOCALIFADDR: add the specified address. - * SIOCALIFADDR with IFLR_PREFIX: - * EINVAL since we can't deduce hostid part of the address. - * SIOCDLIFADDR: delete the specified address. - * SIOCDLIFADDR with IFLR_PREFIX: - * delete the first address that matches the specified prefix. - * return values: - * EINVAL on invalid parameters - * EADDRNOTAVAIL on prefix match failed/specified address not found - * other values may be returned from in_ioctl() + * Caller passes in the ioctl data pointer directly via "ifr", with the + * expectation that this routine always uses bcopy() or other byte-aligned + * memory accesses. */ -static int -in_lifaddr_ioctl( - struct socket *so, - u_long cmd, - caddr_t data, - struct ifnet *ifp, - struct proc *p) +static __attribute__((noinline)) int +inctl_ifdstaddr(struct ifnet *ifp, struct in_ifaddr *ia, u_long cmd, + struct ifreq *ifr) { - struct if_laddrreq *iflr = (struct if_laddrreq *)data; - struct ifaddr *ifa; + struct kev_in_data in_event_data; + struct kev_msg ev_msg; + struct sockaddr_in dstaddr; + int error = 0; - /* sanity checks */ - if (!data || !ifp) { - panic("invalid argument to in_lifaddr_ioctl"); - /*NOTRECHED*/ - } + VERIFY(ifp != NULL); + + if (!(ifp->if_flags & IFF_POINTOPOINT)) + return (EINVAL); + + bzero(&in_event_data, sizeof (struct kev_in_data)); + bzero(&ev_msg, sizeof (struct kev_msg)); switch (cmd) { - case SIOCGLIFADDR: - /* address must be specified on GET with IFLR_PREFIX */ - if ((iflr->flags & IFLR_PREFIX) == 0) + case SIOCGIFDSTADDR: /* struct ifreq */ + if (ia == NULL) { + error = EADDRNOTAVAIL; break; - /*FALLTHROUGH*/ - case SIOCALIFADDR: - case SIOCDLIFADDR: - /* address must be specified on ADD and DELETE */ - if (iflr->addr.ss_family != AF_INET) - return EINVAL; - if (iflr->addr.ss_len != sizeof(struct sockaddr_in)) - return EINVAL; - /* XXX need improvement */ - if (iflr->dstaddr.ss_family - && iflr->dstaddr.ss_family != AF_INET) - return EINVAL; - if (iflr->dstaddr.ss_family - && iflr->dstaddr.ss_len != sizeof(struct sockaddr_in)) - return EINVAL; + } + IFA_LOCK(&ia->ia_ifa); + bcopy(&ia->ia_dstaddr, &ifr->ifr_dstaddr, sizeof (dstaddr)); + IFA_UNLOCK(&ia->ia_ifa); + break; + + case SIOCSIFDSTADDR: /* struct ifreq */ + VERIFY(ia != NULL); + IFA_LOCK(&ia->ia_ifa); + dstaddr = ia->ia_dstaddr; + bcopy(&ifr->ifr_dstaddr, &ia->ia_dstaddr, sizeof (dstaddr)); + if (ia->ia_dstaddr.sin_family == AF_INET) + ia->ia_dstaddr.sin_len = sizeof (struct sockaddr_in); + IFA_UNLOCK(&ia->ia_ifa); + /* + * NOTE: SIOCSIFDSTADDR is defined with struct ifreq + * as parameter, but here we are sending it down + * to the interface with a pointer to struct ifaddr, + * for legacy reasons. + */ + error = ifnet_ioctl(ifp, PF_INET, SIOCSIFDSTADDR, ia); + IFA_LOCK(&ia->ia_ifa); + if (error == EOPNOTSUPP) + error = 0; + if (error != 0) { + ia->ia_dstaddr = dstaddr; + 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) { + in_event_data.ia_dstaddr = ((struct sockaddr_in *) + (void *)ia->ia_ifa.ifa_dstaddr)->sin_addr; + } else { + in_event_data.ia_dstaddr.s_addr = INADDR_ANY; + } + + in_event_data.ia_addr = ia->ia_addr.sin_addr; + in_event_data.ia_net = ia->ia_net; + in_event_data.ia_netmask = ia->ia_netmask; + 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); + (void) strlcpy(&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 = (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); + + lck_mtx_lock(rnh_lock); + IFA_LOCK(&ia->ia_ifa); + if (ia->ia_flags & IFA_ROUTE) { + ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&dstaddr; + 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; + 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; - default: /*shouldn't happen*/ - return EOPNOTSUPP; + + + + default: + VERIFY(0); + /* NOTREACHED */ } - if (sizeof(struct in_addr) * 8 < iflr->prefixlen) - return EINVAL; + + return (error); +} + +/* + * Caller passes in the ioctl data pointer directly via "ifr", with the + * expectation that this routine always uses bcopy() or other byte-aligned + * memory accesses. + */ +static __attribute__((noinline)) int +inctl_ifbrdaddr(struct ifnet *ifp, struct in_ifaddr *ia, u_long cmd, + struct ifreq *ifr) +{ + struct kev_in_data in_event_data; + struct kev_msg ev_msg; + int error = 0; + + VERIFY(ifp != NULL); + + if (ia == NULL) + return (EADDRNOTAVAIL); + + if (!(ifp->if_flags & IFF_BROADCAST)) + return (EINVAL); + + bzero(&in_event_data, sizeof (struct kev_in_data)); + bzero(&ev_msg, sizeof (struct kev_msg)); switch (cmd) { - case SIOCALIFADDR: - { - struct in_aliasreq ifra; + case SIOCGIFBRDADDR: /* struct ifreq */ + IFA_LOCK(&ia->ia_ifa); + bcopy(&ia->ia_broadaddr, &ifr->ifr_broadaddr, + sizeof (struct sockaddr_in)); + IFA_UNLOCK(&ia->ia_ifa); + break; - if (iflr->flags & IFLR_PREFIX) - return EINVAL; + case SIOCSIFBRDADDR: /* struct ifreq */ + IFA_LOCK(&ia->ia_ifa); + bcopy(&ifr->ifr_broadaddr, &ia->ia_broadaddr, + sizeof (struct sockaddr_in)); - /* copy args to in_aliasreq, perform ioctl(SIOCAIFADDR_IN6). */ - bzero(&ifra, sizeof(ifra)); - bcopy(iflr->iflr_name, ifra.ifra_name, - sizeof(ifra.ifra_name)); + ev_msg.vendor_code = KEV_VENDOR_APPLE; + ev_msg.kev_class = KEV_NETWORK_CLASS; + ev_msg.kev_subclass = KEV_INET_SUBCLASS; - bcopy(&iflr->addr, &ifra.ifra_addr, iflr->addr.ss_len); + ev_msg.event_code = KEV_INET_SIFBRDADDR; - if (iflr->dstaddr.ss_family) { /*XXX*/ - bcopy(&iflr->dstaddr, &ifra.ifra_dstaddr, - iflr->dstaddr.ss_len); + if (ia->ia_ifa.ifa_dstaddr) { + in_event_data.ia_dstaddr = ((struct sockaddr_in *) + (void *)ia->ia_ifa.ifa_dstaddr)->sin_addr; + } else { + in_event_data.ia_dstaddr.s_addr = INADDR_ANY; } + in_event_data.ia_addr = ia->ia_addr.sin_addr; + in_event_data.ia_net = ia->ia_net; + in_event_data.ia_netmask = ia->ia_netmask; + 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); + (void) strlcpy(&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 = (u_int32_t)ifp->if_unit; - ifra.ifra_mask.sin_family = AF_INET; - ifra.ifra_mask.sin_len = sizeof(struct sockaddr_in); - in_len2mask(&ifra.ifra_mask.sin_addr, iflr->prefixlen); - - return in_control(so, SIOCAIFADDR, (caddr_t)&ifra, ifp, p); - } - case SIOCGLIFADDR: - case SIOCDLIFADDR: - { - struct in_ifaddr *ia; - struct in_addr mask, candidate; - struct in_addr match = { 0 }; - struct sockaddr_in *sin; - int cmp; - - bzero(&mask, sizeof(mask)); - if (iflr->flags & IFLR_PREFIX) { - /* lookup a prefix rather than address. */ - in_len2mask(&mask, iflr->prefixlen); - - sin = (struct sockaddr_in *)&iflr->addr; - match.s_addr = sin->sin_addr.s_addr; - match.s_addr &= mask.s_addr; - - /* if you set extra bits, that's wrong */ - if (match.s_addr != sin->sin_addr.s_addr) - return EINVAL; - - cmp = 1; - } else { - if (cmd == SIOCGLIFADDR) { - /* on getting an address, take the 1st match */ - cmp = 0; /*XXX*/ - } else { - /* on deleting an address, do exact match */ - in_len2mask(&mask, 32); - sin = (struct sockaddr_in *)&iflr->addr; - match.s_addr = sin->sin_addr.s_addr; + 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; - cmp = 1; - } + kev_post_msg(&ev_msg); + break; + + default: + VERIFY(0); + /* NOTREACHED */ + } + + return (error); +} + +/* + * Caller passes in the ioctl data pointer directly via "ifr", with the + * expectation that this routine always uses bcopy() or other byte-aligned + * memory accesses. + */ +static __attribute__((noinline)) int +inctl_ifnetmask(struct ifnet *ifp, struct in_ifaddr *ia, u_long cmd, + struct ifreq *ifr) +{ + struct kev_in_data in_event_data; + struct kev_msg ev_msg; + struct sockaddr_in mask; + int error = 0; + + VERIFY(ifp != NULL); + + bzero(&in_event_data, sizeof (struct kev_in_data)); + bzero(&ev_msg, sizeof (struct kev_msg)); + + switch (cmd) { + case SIOCGIFNETMASK: /* struct ifreq */ + if (ia == NULL) { + error = EADDRNOTAVAIL; + break; + } + IFA_LOCK(&ia->ia_ifa); + bcopy(&ia->ia_sockmask, &ifr->ifr_addr, sizeof (mask)); + IFA_UNLOCK(&ia->ia_ifa); + break; + + case SIOCSIFNETMASK: { /* struct ifreq */ + in_addr_t i; + + bcopy(&ifr->ifr_addr, &mask, sizeof (mask)); + i = mask.sin_addr.s_addr; + + VERIFY(ia != NULL); + 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) { + in_event_data.ia_dstaddr = ((struct sockaddr_in *) + (void *)ia->ia_ifa.ifa_dstaddr)->sin_addr; + } else { + in_event_data.ia_dstaddr.s_addr = INADDR_ANY; } + in_event_data.ia_addr = ia->ia_addr.sin_addr; + in_event_data.ia_net = ia->ia_net; + in_event_data.ia_netmask = ia->ia_netmask; + 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); + (void) strlcpy(&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 = (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); + break; + } + + default: + VERIFY(0); + /* NOTREACHED */ + } + + return (error); +} +/* + * Generic INET control operations (ioctl's). + * + * ifp is NULL if not an interface-specific ioctl. + * + * Most of the routines called to handle the ioctls would end up being + * tail-call optimized, which unfortunately causes this routine to + * consume too much stack space; this is the reason for the "noinline" + * attribute used on those routines. + * + * If called directly from within the networking stack (as opposed to via + * pru_control), the socket parameter may be NULL. + */ +int +in_control(struct socket *so, u_long cmd, caddr_t data, struct ifnet *ifp, + struct proc *p) +{ + struct ifreq *ifr = (struct ifreq *)(void *)data; + struct sockaddr_in addr, dstaddr; + struct sockaddr_in sin, *sa = NULL; + boolean_t privileged = (proc_suser(p) == 0); + boolean_t so_unlocked = FALSE; + struct in_ifaddr *ia = NULL; + struct ifaddr *ifa; + int error = 0; + + /* In case it's NULL, make sure it came from the kernel */ + VERIFY(so != NULL || p == kernproc); + + /* + * ioctls which don't require ifp, but require socket. + */ + switch (cmd) { + case SIOCGASSOCIDS32: /* struct so_aidreq32 */ + case SIOCGASSOCIDS64: /* struct so_aidreq64 */ + return (inctl_associd(so, cmd, data)); + /* NOTREACHED */ + + case SIOCGCONNIDS32: /* struct so_cidreq32 */ + case SIOCGCONNIDS64: /* struct so_cidreq64 */ + return (inctl_connid(so, cmd, data)); + /* NOTREACHED */ + + case SIOCGCONNINFO32: /* struct so_cinforeq32 */ + case SIOCGCONNINFO64: /* struct so_cinforeq64 */ + return (inctl_conninfo(so, cmd, data)); + /* NOTREACHED */ + } + + /* + * The rest of ioctls require ifp; reject if we don't have one; + * return ENXIO to be consistent with ifioctl(). + */ + if (ifp == NULL) + return (ENXIO); + + /* + * ioctls which require ifp but not interface address. + */ + switch (cmd) { + case SIOCAUTOADDR: /* struct ifreq */ + if (!privileged) + return (EPERM); + return (inctl_autoaddr(ifp, ifr)); + /* NOTREACHED */ + + case SIOCARPIPLL: /* struct ifreq */ + if (!privileged) + return (EPERM); + return (inctl_arpipll(ifp, ifr)); + /* NOTREACHED */ + + case SIOCSETROUTERMODE: /* struct ifreq */ + if (!privileged) + return (EPERM); + return (inctl_setrouter(ifp, ifr)); + /* NOTREACHED */ + + case SIOCPROTOATTACH: /* struct ifreq */ + if (!privileged) + return (EPERM); + return (in_domifattach(ifp)); + /* NOTREACHED */ + + case SIOCPROTODETACH: /* struct ifreq */ + if (!privileged) + return (EPERM); + + /* + * If an IPv4 address is still present, refuse to detach. + */ ifnet_lock_shared(ifp); - TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { - if (ifa->ifa_addr->sa_family != AF_INET6) - continue; - if (!cmp) - break; - candidate.s_addr = ((struct sockaddr_in *)&ifa->ifa_addr)->sin_addr.s_addr; - candidate.s_addr &= mask.s_addr; - if (candidate.s_addr == match.s_addr) + 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) - return EADDRNOTAVAIL; - ia = (struct in_ifaddr *)ifa; + return ((ifa == NULL) ? proto_unplumb(PF_INET, ifp) : EBUSY); + /* NOTREACHED */ + } - if (cmd == SIOCGLIFADDR) { - /* fill in the if_laddrreq structure */ - bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin_len); + /* + * ioctls which require interface address; obtain sockaddr_in. + */ + switch (cmd) { + case SIOCAIFADDR: /* struct {if,in_}aliasreq */ + if (!privileged) + return (EPERM); + bcopy(&((struct in_aliasreq *)(void *)data)->ifra_addr, + &sin, sizeof (sin)); + sa = &sin; + break; - if ((ifp->if_flags & IFF_POINTOPOINT) != 0) { - bcopy(&ia->ia_dstaddr, &iflr->dstaddr, - ia->ia_dstaddr.sin_len); - } else - bzero(&iflr->dstaddr, sizeof(iflr->dstaddr)); + case SIOCDIFADDR: /* struct ifreq */ + case SIOCSIFADDR: /* struct ifreq */ + case SIOCSIFDSTADDR: /* struct ifreq */ + case SIOCSIFNETMASK: /* struct ifreq */ + case SIOCSIFBRDADDR: /* struct ifreq */ + if (!privileged) + return (EPERM); + /* FALLTHRU */ + case SIOCGIFADDR: /* struct ifreq */ + case SIOCGIFDSTADDR: /* struct ifreq */ + case SIOCGIFNETMASK: /* struct ifreq */ + case SIOCGIFBRDADDR: /* struct ifreq */ + bcopy(&ifr->ifr_addr, &sin, sizeof (sin)); + sa = &sin; + break; + } - iflr->prefixlen = - in_mask2len(&ia->ia_sockmask.sin_addr); + /* + * Find address for this interface, if it exists. + * + * If an alias address was specified, find that one instead of + * the first one on the interface, if possible. + */ + VERIFY(ia == NULL); + if (sa != NULL) { + struct in_ifaddr *iap; - iflr->flags = 0; /*XXX*/ + /* + * Any failures from this point on must take into account + * a non-NULL "ia" with an outstanding reference count, and + * therefore requires IFA_REMREF. Jump to "done" label + * instead of calling return if "ia" is valid. + */ + lck_rw_lock_shared(in_ifaddr_rwlock); + TAILQ_FOREACH(iap, INADDR_HASH(sa->sin_addr.s_addr), ia_hash) { + IFA_LOCK(&iap->ia_ifa); + if (iap->ia_ifp == ifp && + iap->ia_addr.sin_addr.s_addr == + sa->sin_addr.s_addr) { + ia = iap; + 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); + + if (ia == NULL) { + ifnet_lock_shared(ifp); + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + iap = ifatoia(ifa); + IFA_LOCK(&iap->ia_ifa); + if (iap->ia_addr.sin_family == AF_INET) { + ia = iap; + 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); + ifnet_lock_done(ifp); + } + } - return 0; + /* + * Unlock the socket since ifnet_ioctl() may be invoked by + * one of the ioctl handlers below. Socket will be re-locked + * prior to returning. + */ + if (so != NULL) { + socket_unlock(so, 0); + so_unlocked = TRUE; + } + + switch (cmd) { + case SIOCAIFADDR: /* struct {if,in_}aliasreq */ + case SIOCDIFADDR: /* struct ifreq */ + if (cmd == SIOCAIFADDR) { + bcopy(&((struct in_aliasreq *)(void *)data)-> + ifra_addr, &addr, sizeof (addr)); + bcopy(&((struct in_aliasreq *)(void *)data)-> + ifra_dstaddr, &dstaddr, sizeof (dstaddr)); } else { - struct in_aliasreq ifra; - - /* fill in_aliasreq and do ioctl(SIOCDIFADDR_IN6) */ - bzero(&ifra, sizeof(ifra)); - bcopy(iflr->iflr_name, ifra.ifra_name, - sizeof(ifra.ifra_name)); - - bcopy(&ia->ia_addr, &ifra.ifra_addr, - ia->ia_addr.sin_len); - if ((ifp->if_flags & IFF_POINTOPOINT) != 0) { - bcopy(&ia->ia_dstaddr, &ifra.ifra_dstaddr, - ia->ia_dstaddr.sin_len); + VERIFY(cmd == SIOCDIFADDR); + bcopy(&((struct ifreq *)(void *)data)->ifr_addr, + &addr, sizeof (addr)); + bzero(&dstaddr, sizeof (dstaddr)); + } + + if (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 == + 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) && + (dstaddr.sin_addr.s_addr == INADDR_ANY)) { + error = EDESTADDRREQ; + goto done; } - bcopy(&ia->ia_sockmask, &ifra.ifra_dstaddr, - ia->ia_sockmask.sin_len); + } else if (cmd == SIOCAIFADDR) { + error = EINVAL; + goto done; + } + if (cmd == SIOCDIFADDR && ia == NULL) { + error = EADDRNOTAVAIL; + goto done; + } + /* FALLTHROUGH */ + case SIOCSIFADDR: /* struct ifreq */ + case SIOCSIFDSTADDR: /* struct ifreq */ + case SIOCSIFNETMASK: /* struct ifreq */ + if (cmd == SIOCAIFADDR) { + /* fell thru from above; just repeat it */ + bcopy(&((struct in_aliasreq *)(void *)data)-> + ifra_addr, &addr, sizeof (addr)); + } else { + VERIFY(cmd == SIOCDIFADDR || cmd == SIOCSIFADDR || + cmd == SIOCSIFNETMASK || cmd == SIOCSIFDSTADDR); + bcopy(&((struct ifreq *)(void *)data)->ifr_addr, + &addr, sizeof (addr)); + } - return in_control(so, SIOCDIFADDR, (caddr_t)&ifra, - ifp, p); + if (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; + 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; + ia->ia_sockmask.sin_len = 8; + if (ifp->if_flags & IFF_BROADCAST) { + ia->ia_broadaddr.sin_len = sizeof (ia->ia_addr); + ia->ia_broadaddr.sin_family = AF_INET; + } + 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); + /* + * 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); + /* discard error */ + (void) in_domifattach(ifp); + error = 0; + } + break; + } + + switch (cmd) { + case SIOCGIFDSTADDR: /* struct ifreq */ + case SIOCSIFDSTADDR: /* struct ifreq */ + error = inctl_ifdstaddr(ifp, ia, cmd, ifr); + break; + + case SIOCGIFBRDADDR: /* struct ifreq */ + case SIOCSIFBRDADDR: /* struct ifreq */ + error = inctl_ifbrdaddr(ifp, ia, cmd, ifr); + break; + + case SIOCGIFNETMASK: /* struct ifreq */ + case SIOCSIFNETMASK: /* struct ifreq */ + error = inctl_ifnetmask(ifp, ia, cmd, ifr); + break; + + case SIOCGIFADDR: /* struct ifreq */ + case SIOCSIFADDR: /* struct ifreq */ + case SIOCAIFADDR: /* struct {if,in_}aliasreq */ + case SIOCDIFADDR: /* struct ifreq */ + error = inctl_ifaddr(ifp, ia, cmd, ifr); + break; + + default: + error = EOPNOTSUPP; + break; } +done: + if (ia != NULL) + IFA_REMREF(&ia->ia_ifa); + if (so_unlocked) + socket_lock(so, 0); - return EOPNOTSUPP; /*just for safety*/ + return (error); } /* * Delete any existing route for an interface. */ void -in_ifscrub( - struct ifnet *ifp, - struct in_ifaddr *ia, - int locked) +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(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); + IFA_LOCK(&ia->ia_ifa); ia->ia_flags &= ~IFA_ROUTE; + IFA_UNLOCK(&ia->ia_ifa); 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) +{ + 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); } /* @@ -1148,20 +1599,40 @@ in_ifscrub( * and routing table entry. */ static int -in_ifinit( - struct ifnet *ifp, - 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) { - 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 */ + 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; + /* + * Interface addresses should not contain port or sin_zero information. + */ + SIN(&ia->ia_addr)->sin_family = AF_INET; + SIN(&ia->ia_addr)->sin_len = sizeof (struct sockaddr_in); + SIN(&ia->ia_addr)->sin_port = 0; + bzero(&SIN(&ia->ia_addr)->sin_zero, sizeof (sin->sin_zero)); + 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 @@ -1183,23 +1654,53 @@ in_ifinit( * be reconfigured with the current primary IPV4 address. */ if (error == 0 && cmd == SIOCAIFADDR) { + /* + * NOTE: SIOCSIFADDR is defined with struct ifreq + * as parameter, but here we are sending it down + * to the interface with a pointer to struct ifaddr, + * for legacy reasons. + */ error = ifnet_ioctl(ifp, PF_INET, SIOCSIFADDR, ifa0); if (error == EOPNOTSUPP) error = 0; } /* Release reference from ifa_ifpgetprimary() */ - ifafree(ifa0); + IFA_REMREF(ifa0); if (error) { + 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); } + 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, 0); + 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)) @@ -1225,19 +1726,32 @@ in_ifinit( ia->ia_ifa.ifa_metric = ifp->if_metric; if (ifp->if_flags & IFF_BROADCAST) { ia->ia_broadaddr.sin_addr.s_addr = - htonl(ia->ia_subnet | ~ia->ia_subnetmask); + htonl(ia->ia_subnet | ~ia->ia_subnetmask); ia->ia_netbroadcast.s_addr = - htonl(ia->ia_net | ~ ia->ia_netmask); + htonl(ia->ia_net | ~ ia->ia_netmask); } else if (ifp->if_flags & IFF_LOOPBACK) { 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 (struct sockaddr_in); 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; @@ -1247,48 +1761,64 @@ in_ifinit( * 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); - ifnet_lock_shared(ifp); - IN_LOOKUP_MULTI(addr, ifp, inm); - ifnet_lock_done(ifp); - 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("%s: failed to add membership to " + "all-hosts multicast address on %s\n", + __func__, if_name(ifp)); + } + } + lck_mtx_unlock(&ifp->if_addrconfig_lock); } + + /* Release extra reference taken above */ + IFA_REMREF(&ia->ia_ifa); + + if (error == 0) { + /* invalidate route caches */ + routegenid_inet_update(); + } + return (error); } - /* - * Return 1 if the address might be a local broadcast address. + * Return TRUE if the address might be a local broadcast address. */ -int -in_broadcast( - struct in_addr in, - struct ifnet *ifp) +boolean_t +in_broadcast(struct in_addr in, struct ifnet *ifp) { struct ifaddr *ifa; - u_long t; + u_int32_t t; - if (in.s_addr == INADDR_BROADCAST || - in.s_addr == INADDR_ANY) - return 1; - if ((ifp->if_flags & IFF_BROADCAST) == 0) - return 0; + if (in.s_addr == INADDR_BROADCAST || in.s_addr == INADDR_ANY) + return (TRUE); + if (!(ifp->if_flags & IFF_BROADCAST)) + return (FALSE); t = ntohl(in.s_addr); + /* * Look through the list of addresses for a match * with a broadcast address. */ -#define ia ((struct in_ifaddr *)ifa) +#define ia ((struct in_ifaddr *)ifa) ifnet_lock_shared(ifp); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { - if (ifa->ifa_addr == NULL) { - ifnet_lock_done(ifp); - return (0); - } + 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 || @@ -1301,159 +1831,497 @@ in_broadcast( * only exist when an interface gets a secondary * address. */ - ia->ia_subnetmask != (u_long)0xffffffff) { + ia->ia_subnetmask != (u_int32_t)0xffffffff) { + IFA_UNLOCK(ifa); ifnet_lock_done(ifp); - return 1; + return (TRUE); } + IFA_UNLOCK(ifa); } ifnet_lock_done(ifp); - return (0); + return (FALSE); #undef ia } -static void -in_free_inm( - void* ifma_protospec) +void +in_purgeaddrs(struct ifnet *ifp) { - struct in_multi *inm = ifma_protospec; - + struct ifaddr **ifap; + int err, i; + + VERIFY(ifp != NULL); + /* - * No remaining claims to this record; let IGMP know that - * we are leaving the multicast group. + * 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. */ - igmp_leavegroup(inm); - lck_mtx_lock(rt_mtx); - LIST_REMOVE(inm, inm_link); - lck_mtx_unlock(rt_mtx); - FREE(inm, M_IPMADDR); + err = ifnet_get_address_list_family_internal(ifp, &ifap, AF_INET, 1, + M_WAITOK, 0); + if (err == 0 && ifap != NULL) { + struct ifreq ifr; + + bzero(&ifr, sizeof (ifr)); + (void) snprintf(ifr.ifr_name, sizeof (ifr.ifr_name), + "%s", if_name(ifp)); + + for (i = 0; ifap[i] != NULL; i++) { + struct ifaddr *ifa; + + ifa = ifap[i]; + IFA_LOCK(ifa); + bcopy(ifa->ifa_addr, &ifr.ifr_addr, + sizeof (struct sockaddr_in)); + 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 *) + (void *)ifa->ifa_addr)->sin_addr; + d = &((struct sockaddr_in *) + (void *)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=%s ifa_addr=%s " + "ifa_dstaddr=%s (err=%d)\n", __func__, + ifp->if_xname, 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=%s (err=%d)\n", __func__, ifp->if_xname, err); + } } /* - * Add an address to the list of IP multicast addresses for a given interface. + * Select endpoint address(es). For now just take the first matching + * address and discard the rest, if present. */ -struct in_multi * -in_addmulti( - struct in_addr *ap, - struct ifnet *ifp) +int +in_selectaddrs(int af, struct sockaddr_list **src_sl, + struct sockaddr_entry **src_se, struct sockaddr_list **dst_sl, + struct sockaddr_entry **dst_se) { - struct in_multi *inm; - int error; - struct sockaddr_in sin; - struct ifmultiaddr *ifma; + struct sockaddr_entry *se = NULL; + struct sockaddr_entry *tse = NULL; + int error = 0; - /* - * 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). - */ - 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) { - return 0; - } + VERIFY(src_sl != NULL && dst_sl != NULL && *dst_sl != NULL); + VERIFY(src_se != NULL && dst_se != NULL); - /* - * If ifma->ifma_protospec is null, then if_addmulti() created - * a new record. Otherwise, we are done. - */ - if (ifma->ifma_protospec != 0) { - return ifma->ifma_protospec; - } + *src_se = *dst_se = NULL; - inm = (struct in_multi *) _MALLOC(sizeof(*inm), M_IPMADDR, M_WAITOK); - if (inm == NULL) { - return (NULL); + /* pick a source address, if available */ + if (*src_sl != NULL) { + TAILQ_FOREACH(se, &(*src_sl)->sl_head, se_link) { + VERIFY(se->se_addr != NULL); + /* + * Take the first source address, or the first + * one with matching address family. + */ + if (af == AF_UNSPEC || se->se_addr->sa_family == af) { + sockaddrlist_remove(*src_sl, se); + *src_se = se; + break; + } + } + /* get rid of the rest */ + TAILQ_FOREACH_SAFE(se, &(*src_sl)->sl_head, se_link, tse) { + sockaddrlist_remove(*src_sl, se); + sockaddrentry_free(se); + } + if (*src_se != NULL) { + /* insert the first src address back in */ + sockaddrlist_insert(*src_sl, *src_se); + VERIFY((*src_sl)->sl_cnt == 1); + /* destination address must be of this family */ + af = (*src_se)->se_addr->sa_family; + } else { + /* no usable source address with matching family */ + VERIFY(af != AF_UNSPEC); + error = EAFNOSUPPORT; + goto out; + } } - - bzero(inm, sizeof *inm); - inm->inm_addr = *ap; - inm->inm_ifp = ifp; - inm->inm_ifma = ifma; - lck_mtx_lock(rt_mtx); - if (ifma->ifma_protospec == NULL) { - ifma->ifma_protospec = inm; - ifma->ifma_free = in_free_inm; - LIST_INSERT_HEAD(&in_multihead, inm, inm_link); + /* pick a (matching) destination address */ + TAILQ_FOREACH(se, &(*dst_sl)->sl_head, se_link) { + VERIFY(se->se_addr != NULL); + /* + * Take the first destination address; if source is specified, + * find one which uses the same address family. + */ + if (af == AF_UNSPEC || se->se_addr->sa_family == af) { + sockaddrlist_remove(*dst_sl, se); + *dst_se = se; + break; + } + } + /* get rid of the rest */ + TAILQ_FOREACH_SAFE(se, &(*dst_sl)->sl_head, se_link, tse) { + sockaddrlist_remove(*dst_sl, se); + sockaddrentry_free(se); } - lck_mtx_unlock(rt_mtx); - - if (ifma->ifma_protospec != inm) { - _FREE(inm, M_IPMADDR); - return ifma->ifma_protospec; + if (*dst_se != NULL) { + /* insert the first dst address back in */ + sockaddrlist_insert(*dst_sl, *dst_se); + VERIFY((*dst_sl)->sl_cnt == 1); + } else { + /* source and destination address families don't match */ + error = EAFNOSUPPORT; + goto out; } - /* - * Let IGMP know that we have joined a new IP multicast group. - */ - error = igmp_joingroup(inm); - if (error) { - char addrbuf[16]; - - /* - * We can't free the inm because someone else may already be - * using it. Once we put it in to ifma->ifma_protospec, it - * must exist as long as the ifma does. Might be nice to flag - * the error so we can try igmp_joingroup the next time through. - */ - log(LOG_ERR, "igmp_joingroup error %d joining multicast %s on %s%d\n", - error, inet_ntop(AF_INET, &sin.sin_addr, addrbuf, sizeof(addrbuf)), - ifp->if_name, ifp->if_unit); + af = (*dst_se)->se_addr->sa_family; + VERIFY(*src_se == NULL || (*src_se)->se_addr->sa_family == af); + + /* verify address length */ + switch (af) { + case AF_INET: + if ((*dst_se)->se_addr->sa_len != + sizeof (struct sockaddr_in)) { + error = EAFNOSUPPORT; + goto out; + } + break; +#if INET6 + case AF_INET6: + if ((*dst_se)->se_addr->sa_len != + sizeof (struct sockaddr_in6)) { + error = EAFNOSUPPORT; + goto out; + } + break; +#endif /* INET6 */ + default: + error = EAFNOSUPPORT; + goto out; + } + + /* if source address is specified, length must match destination */ + if (*src_se != NULL && (*src_se)->se_addr->sa_len != + (*dst_se)->se_addr->sa_len) { + error = EAFNOSUPPORT; + goto out; } - - return (inm); +out: + return (error); } /* - * Delete a multicast address record. + * Called as part of ip_init */ void -in_delmulti( - struct in_multi **inm) +in_ifaddr_init(void) { - struct in_multi *inm2; - - lck_mtx_lock(rt_mtx); - LIST_FOREACH(inm2, &in_multihead, inm_link) { - if (inm2 == *inm) - break; + in_multi_init(); + + 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 */ } - if (inm2 != *inm) { - lck_mtx_unlock(rt_mtx); - printf("in_delmulti - ignoring invalid inm (%p)\n", *inm); - return; + 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); + } } - lck_mtx_unlock(rt_mtx); - - /* We intentionally do this a bit differently than BSD */ - if ((*inm)->inm_ifma) { - if_delmultiaddr((*inm)->inm_ifma, 0); - ifma_release((*inm)->inm_ifma); + 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 */ } - *inm = NULL; + 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); } -#if !NFSCLIENT -int inet_aton(char *cp, struct in_addr *pin); -int -inet_aton(char * cp, struct in_addr * pin) +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) { - 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); - if (l > 255) - return (FALSE); - b[i] = l; - p = strchr(p, '.'); - if (i < 3 && p == NULL) - return (FALSE); - p++; - } - return (TRUE); + 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]); +} + +/* + * Handle SIOCGASSOCIDS ioctl for PF_INET domain. + */ +static int +in_getassocids(struct socket *so, uint32_t *cnt, user_addr_t aidp) +{ + struct inpcb *inp = sotoinpcb(so); + sae_associd_t aid; + + if (inp == NULL || inp->inp_state == INPCB_STATE_DEAD) + return (EINVAL); + + /* INPCB has no concept of association */ + aid = SAE_ASSOCID_ANY; + *cnt = 0; + + /* just asking how many there are? */ + if (aidp == USER_ADDR_NULL) + return (0); + + return (copyout(&aid, aidp, sizeof (aid))); +} + +/* + * Handle SIOCGCONNIDS ioctl for PF_INET domain. + */ +static int +in_getconnids(struct socket *so, sae_associd_t aid, uint32_t *cnt, + user_addr_t cidp) +{ + struct inpcb *inp = sotoinpcb(so); + sae_connid_t cid; + + if (inp == NULL || inp->inp_state == INPCB_STATE_DEAD) + return (EINVAL); + + if (aid != SAE_ASSOCID_ANY && aid != SAE_ASSOCID_ALL) + return (EINVAL); + + /* if connected, return 1 connection count */ + *cnt = ((so->so_state & SS_ISCONNECTED) ? 1 : 0); + + /* just asking how many there are? */ + if (cidp == USER_ADDR_NULL) + return (0); + + /* if INPCB is connected, assign it connid 1 */ + cid = ((*cnt != 0) ? 1 : SAE_CONNID_ANY); + + return (copyout(&cid, cidp, sizeof (cid))); +} + +/* + * Handle SIOCGCONNINFO ioctl for PF_INET domain. + */ +static int +in_getconninfo(struct socket *so, sae_connid_t cid, uint32_t *flags, + uint32_t *ifindex, int32_t *soerror, user_addr_t src, socklen_t *src_len, + user_addr_t dst, socklen_t *dst_len, uint32_t *aux_type, + user_addr_t aux_data, uint32_t *aux_len) +{ +#pragma unused(aux_data) + struct inpcb *inp = sotoinpcb(so); + struct sockaddr_in sin; + struct ifnet *ifp = NULL; + int error = 0; + u_int32_t copy_len = 0; + + /* + * Don't test for INPCB_STATE_DEAD since this may be called + * after SOF_PCBCLEARING is set, e.g. after tcp_close(). + */ + if (inp == NULL) { + error = EINVAL; + goto out; + } + + if (cid != SAE_CONNID_ANY && cid != SAE_CONNID_ALL && cid != 1) { + error = EINVAL; + goto out; + } + + ifp = inp->inp_last_outifp; + *ifindex = ((ifp != NULL) ? ifp->if_index : 0); + *soerror = so->so_error; + *flags = 0; + if (so->so_state & SS_ISCONNECTED) + *flags |= (CIF_CONNECTED | CIF_PREFERRED); + if (inp->inp_flags & INP_BOUND_IF) + *flags |= CIF_BOUND_IF; + if (!(inp->inp_flags & INP_INADDR_ANY)) + *flags |= CIF_BOUND_IP; + if (!(inp->inp_flags & INP_ANONPORT)) + *flags |= CIF_BOUND_PORT; + + bzero(&sin, sizeof (sin)); + sin.sin_len = sizeof (sin); + sin.sin_family = AF_INET; + + /* source address and port */ + sin.sin_port = inp->inp_lport; + sin.sin_addr.s_addr = inp->inp_laddr.s_addr; + if (*src_len == 0) { + *src_len = sin.sin_len; + } else { + if (src != USER_ADDR_NULL) { + copy_len = min(*src_len, sizeof (sin)); + error = copyout(&sin, src, copy_len); + if (error != 0) + goto out; + *src_len = copy_len; + } + } + + /* destination address and port */ + sin.sin_port = inp->inp_fport; + sin.sin_addr.s_addr = inp->inp_faddr.s_addr; + if (*dst_len == 0) { + *dst_len = sin.sin_len; + } else { + if (dst != USER_ADDR_NULL) { + copy_len = min(*dst_len, sizeof (sin)); + error = copyout(&sin, dst, copy_len); + if (error != 0) + goto out; + *dst_len = copy_len; + } + } + + *aux_type = 0; + *aux_len = 0; + if (SOCK_PROTO(so) == IPPROTO_TCP) { + struct conninfo_tcp tcp_ci; + + *aux_type = CIAUX_TCP; + if (*aux_len == 0) { + *aux_len = sizeof (tcp_ci); + } else { + if (aux_data != USER_ADDR_NULL) { + copy_len = min(*aux_len, sizeof (tcp_ci)); + bzero(&tcp_ci, sizeof (tcp_ci)); + tcp_getconninfo(so, &tcp_ci); + error = copyout(&tcp_ci, aux_data, copy_len); + if (error != 0) + goto out; + *aux_len = copy_len; + } + } + } + +out: + return (error); } -#endif