+/*
+ * Set the IP multicast options in response to user setsockopt().
+ */
+__private_extern__ int
+ip_createmoptions(
+ struct ip_moptions **imop)
+{
+ struct ip_moptions *imo;
+ imo = (struct ip_moptions*) _MALLOC(sizeof(*imo), M_IPMOPTS,
+ M_WAITOK);
+
+ if (imo == NULL)
+ return (ENOBUFS);
+ *imop = imo;
+ imo->imo_multicast_ifp = NULL;
+ imo->imo_multicast_addr.s_addr = INADDR_ANY;
+ imo->imo_multicast_vif = -1;
+ imo->imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL;
+ imo->imo_multicast_loop = IP_DEFAULT_MULTICAST_LOOP;
+ imo->imo_num_memberships = 0;
+
+ return 0;
+}
+
+/*
+ * Add membership to an IPv4 multicast.
+ */
+__private_extern__ int
+ip_addmembership(
+ struct ip_moptions *imo,
+ struct ip_mreq *mreq)
+{
+ struct route ro;
+ struct sockaddr_in *dst;
+ struct ifnet *ifp = NULL;
+ int error = 0;
+ int i;
+
+ if (!IN_MULTICAST(ntohl(mreq->imr_multiaddr.s_addr))) {
+ error = EINVAL;
+ return error;
+ }
+ /*
+ * If no interface address was provided, use the interface of
+ * the route to the given multicast address.
+ */
+ if (mreq->imr_interface.s_addr == INADDR_ANY) {
+ bzero((caddr_t)&ro, sizeof(ro));
+ dst = (struct sockaddr_in *)&ro.ro_dst;
+ dst->sin_len = sizeof(*dst);
+ dst->sin_family = AF_INET;
+ dst->sin_addr = mreq->imr_multiaddr;
+ lck_mtx_lock(rt_mtx);
+ rtalloc_ign_locked(&ro, 0UL);
+ if (ro.ro_rt != NULL) {
+ ifp = ro.ro_rt->rt_ifp;
+ rtfree_locked(ro.ro_rt);
+ }
+ else {
+ /* If there's no default route, try using loopback */
+ mreq->imr_interface.s_addr = INADDR_LOOPBACK;
+ }
+ lck_mtx_unlock(rt_mtx);
+ }
+
+ if (ifp == NULL) {
+ ifp = ip_multicast_if(&mreq->imr_interface, NULL);
+ }
+
+ /*
+ * See if we found an interface, and confirm that it
+ * supports multicast.
+ */
+ if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) {
+ error = EADDRNOTAVAIL;
+ return error;
+ }
+ /*
+ * See if the membership already exists or if all the
+ * membership slots are full.
+ */
+ for (i = 0; i < imo->imo_num_memberships; ++i) {
+ if (imo->imo_membership[i]->inm_ifp == ifp &&
+ imo->imo_membership[i]->inm_addr.s_addr
+ == mreq->imr_multiaddr.s_addr)
+ break;
+ }
+ if (i < imo->imo_num_memberships) {
+ error = EADDRINUSE;
+ return error;
+ }
+ if (i == IP_MAX_MEMBERSHIPS) {
+ error = ETOOMANYREFS;
+ return error;
+ }
+ /*
+ * Everything looks good; add a new record to the multicast
+ * address list for the given interface.
+ */
+ if ((imo->imo_membership[i] =
+ in_addmulti(&mreq->imr_multiaddr, ifp)) == NULL) {
+ error = ENOBUFS;
+ return error;
+ }
+ ++imo->imo_num_memberships;
+
+ return error;
+}
+
+/*
+ * Drop membership of an IPv4 multicast.
+ */
+__private_extern__ int
+ip_dropmembership(
+ struct ip_moptions *imo,
+ struct ip_mreq *mreq)
+{
+ int error = 0;
+ struct ifnet* ifp = NULL;
+ int i;
+
+ if (!IN_MULTICAST(ntohl(mreq->imr_multiaddr.s_addr))) {
+ error = EINVAL;
+ return error;
+ }
+
+ /*
+ * If an interface address was specified, get a pointer
+ * to its ifnet structure.
+ */
+ if (mreq->imr_interface.s_addr == INADDR_ANY)
+ ifp = NULL;
+ else {
+ ifp = ip_multicast_if(&mreq->imr_interface, NULL);
+ if (ifp == NULL) {
+ error = EADDRNOTAVAIL;
+ return error;
+ }
+ }
+ /*
+ * Find the membership in the membership array.
+ */
+ for (i = 0; i < imo->imo_num_memberships; ++i) {
+ if ((ifp == NULL ||
+ imo->imo_membership[i]->inm_ifp == ifp) &&
+ imo->imo_membership[i]->inm_addr.s_addr ==
+ mreq->imr_multiaddr.s_addr)
+ break;
+ }
+ if (i == imo->imo_num_memberships) {
+ error = EADDRNOTAVAIL;
+ return error;
+ }
+ /*
+ * Give up the multicast address record to which the
+ * membership points.
+ */
+ in_delmulti(&imo->imo_membership[i]);
+ /*
+ * Remove the gap in the membership array.
+ */
+ for (++i; i < imo->imo_num_memberships; ++i)
+ imo->imo_membership[i-1] = imo->imo_membership[i];
+ --imo->imo_num_memberships;
+
+ return error;
+}
+