in bsd/netinet/in.c [640:919]
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 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 (addr.sin_len == 0) {
addr = ia->ia_addr;
hostIsNew = 0;
} else if (addr.sin_addr.s_addr ==
ia->ia_addr.sin_addr.s_addr) {
hostIsNew = 0;
}
}
if (mask.sin_len != 0) {
IFA_UNLOCK(&ia->ia_ifa);
in_ifscrub(ifp, ia, 0);
IFA_LOCK(&ia->ia_ifa);
ia->ia_sockmask.sin_len = sizeof(struct sockaddr_in);
ia->ia_sockmask.sin_family = AF_INET;
ia->ia_sockmask.sin_port = 0;
ia->ia_sockmask.sin_addr = mask.sin_addr;
bzero(&ia->ia_sockmask.sin_zero, sizeof(ia->ia_dstaddr.sin_zero));
ia->ia_subnetmask =
ntohl(ia->ia_sockmask.sin_addr.s_addr);
maskIsNew = 1;
}
if ((ifp->if_flags & IFF_POINTOPOINT) &&
(broadaddr.sin_family == AF_INET)) {
IFA_UNLOCK(&ia->ia_ifa);
in_ifscrub(ifp, ia, 0);
IFA_LOCK(&ia->ia_ifa);
ia->ia_dstaddr.sin_family = AF_INET;
ia->ia_dstaddr.sin_len = sizeof(struct sockaddr_in);
ia->ia_dstaddr.sin_port = 0;
ia->ia_dstaddr.sin_addr = broadaddr.sin_addr;
bzero(&ia->ia_dstaddr.sin_zero, sizeof(ia->ia_dstaddr.sin_zero));
maskIsNew = 1; /* We lie; but the effect's the same */
}
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) &&
(broadaddr.sin_family == AF_INET)) {
ia->ia_broadaddr.sin_family = AF_INET;
ia->ia_broadaddr.sin_len = sizeof(struct sockaddr_in);
ia->ia_broadaddr.sin_port = 0;
ia->ia_broadaddr.sin_addr = broadaddr.sin_addr;
bzero(&ia->ia_broadaddr.sin_zero, sizeof(ia->ia_broadaddr.sin_zero));
}
/*
* 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 *)(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;
dlil_post_complete_msg(ifp, &ev_msg);
} else {
IFA_UNLOCK(&ia->ia_ifa);
}
break;
}
case SIOCDIFADDR: /* struct ifreq */
VERIFY(ia != NULL);
error = ifnet_ioctl(ifp, PF_INET, SIOCDIFADDR, ia);
if (error == EOPNOTSUPP) {
error = 0;
}
if (error != 0) {
break;
}
/* Fill out the kernel event information */
ev_msg.vendor_code = KEV_VENDOR_APPLE;
ev_msg.kev_class = KEV_NETWORK_CLASS;
ev_msg.kev_subclass = KEV_INET_SUBCLASS;
ev_msg.event_code = KEV_INET_ADDR_DELETED;
IFA_LOCK(&ia->ia_ifa);
if (ia->ia_ifa.ifa_dstaddr) {
in_event_data.ia_dstaddr = ((struct sockaddr_in *)
(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;
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, 0);
ifnet_lock_exclusive(ifp);
IFA_LOCK(ifa);
/* if_detach_ifa() releases ifa_link reference */
if_detach_ifa(ifp, ifa);
/* Our reference to this address is dropped at the bottom */
IFA_UNLOCK(ifa);
/* 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) {
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);
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);
}
/* Post the kernel event */
dlil_post_complete_msg(ifp, &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) {
/*
* 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() */
IFA_REMREF(ifa);
}
(void) ifnet_notify_address(ifp, AF_INET);
break;
default:
VERIFY(0);
/* NOTREACHED */
}
return error;
}