/bsd/sys/netinet/in_mcast.cc
C++ | 2917 lines | 1911 code | 360 blank | 646 comment | 500 complexity | 1b3e94ec695765527d52c9d6fa8357eb MD5 | raw file
Possible License(s): BSD-3-Clause, 0BSD, MPL-2.0-no-copyleft-exception
Large files files are truncated, but you can click here to view the full file
- /*-
- * Copyright (c) 2007-2009 Bruce Simpson.
- * Copyright (c) 2005 Robert N. M. Watson.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote
- * products derived from this software without specific prior written
- * permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
- /*
- * IPv4 multicast socket, group, and socket option processing module.
- */
- #include <sys/cdefs.h>
- #include <bsd/porting/netport.h>
- #include <bsd/porting/sync_stub.h>
- #include <bsd/sys/sys/param.h>
- #include <bsd/sys/sys/mbuf.h>
- #include <bsd/sys/sys/protosw.h>
- #include <bsd/sys/sys/socket.h>
- #include <bsd/sys/sys/socketvar.h>
- #include <bsd/sys/sys/protosw.h>
- #include <bsd/sys/sys/tree.h>
- #include <bsd/sys/net/if.h>
- #include <bsd/sys/net/if_dl.h>
- #include <bsd/sys/net/route.h>
- #include <bsd/sys/net/vnet.h>
- #include <bsd/sys/netinet/in.h>
- #include <bsd/sys/netinet/in_systm.h>
- #include <bsd/sys/netinet/in_pcb.h>
- #include <bsd/sys/netinet/in_var.h>
- #include <bsd/sys/netinet/ip_var.h>
- #include <bsd/sys/netinet/igmp_var.h>
- #ifndef KTR_IGMPV3
- #define KTR_IGMPV3 KTR_INET
- #endif
- #ifndef __SOCKUNION_DECLARED
- union sockunion {
- struct bsd_sockaddr_storage ss;
- struct bsd_sockaddr sa;
- struct bsd_sockaddr_dl sdl;
- struct bsd_sockaddr_in sin;
- };
- typedef union sockunion sockunion_t;
- #define __SOCKUNION_DECLARED
- #endif /* __SOCKUNION_DECLARED */
- MALLOC_DEFINE(M_INMFILTER, "in_mfilter",
- "IPv4 multicast PCB-layer source filter");
- MALLOC_DEFINE(M_IPMADDR, "in_multi", "IPv4 multicast group");
- MALLOC_DEFINE(M_IPMOPTS, "ip_moptions", "IPv4 multicast options");
- MALLOC_DEFINE(M_IPMSOURCE, "ip_msource",
- "IPv4 multicast IGMP-layer source filter");
- /*
- * Locking:
- * - Lock order is: Giant, INP_WLOCK, IN_MULTI_LOCK, IGMP_LOCK, IF_ADDR_LOCK.
- * - The IF_ADDR_LOCK is implicitly taken by inm_lookup() earlier, however
- * it can be taken by code in net/if.c also.
- * - ip_moptions and in_mfilter are covered by the INP_WLOCK.
- *
- * struct in_multi is covered by IN_MULTI_LOCK. There isn't strictly
- * any need for in_multi itself to be virtualized -- it is bound to an ifp
- * anyway no matter what happens.
- */
- struct mtx in_multi_mtx = {};
- /*
- * Functions with non-static linkage defined in this file should be
- * declared in in_var.h:
- * imo_multi_filter()
- * in_addmulti()
- * in_delmulti()
- * in_joingroup()
- * in_joingroup_locked()
- * in_leavegroup()
- * in_leavegroup_locked()
- * and ip_var.h:
- * inp_freemoptions()
- * inp_getmoptions()
- * inp_setmoptions()
- *
- * XXX: Both carp and pf need to use the legacy (*,G) KPIs in_addmulti()
- * and in_delmulti().
- */
- static void imf_commit(struct in_mfilter *);
- static int imf_get_source(struct in_mfilter *imf,
- const struct bsd_sockaddr_in *psin,
- struct in_msource **);
- static struct in_msource *
- imf_graft(struct in_mfilter *, const uint8_t,
- const struct bsd_sockaddr_in *);
- static void imf_leave(struct in_mfilter *);
- static int imf_prune(struct in_mfilter *, const struct bsd_sockaddr_in *);
- static void imf_purge(struct in_mfilter *);
- static void imf_rollback(struct in_mfilter *);
- static void imf_reap(struct in_mfilter *);
- static int imo_grow(struct ip_moptions *);
- static size_t imo_match_group(const struct ip_moptions *,
- const struct ifnet *, const struct bsd_sockaddr *);
- static struct in_msource *
- imo_match_source(const struct ip_moptions *, const size_t,
- const struct bsd_sockaddr *);
- static void ims_merge(struct ip_msource *ims,
- const struct in_msource *lims, const int rollback);
- static int in_getmulti(struct ifnet *, const struct in_addr *,
- struct in_multi **);
- static int inm_get_source(struct in_multi *inm, const in_addr_t haddr,
- const int noalloc, struct ip_msource **pims);
- static int inm_is_ifp_detached(const struct in_multi *);
- static int inm_merge(struct in_multi *, /*const*/ struct in_mfilter *);
- static void inm_purge(struct in_multi *);
- static void inm_reap(struct in_multi *);
- static struct ip_moptions *
- inp_findmoptions(struct inpcb *);
- static int inp_get_source_filters(struct inpcb *, struct sockopt *);
- static int inp_join_group(struct inpcb *, struct sockopt *);
- static int inp_leave_group(struct inpcb *, struct sockopt *);
- static struct ifnet *
- inp_lookup_mcast_ifp(const struct inpcb *,
- const struct bsd_sockaddr_in *, const struct in_addr);
- static int inp_block_unblock_source(struct inpcb *, struct sockopt *);
- static int inp_set_multicast_if(struct inpcb *, struct sockopt *);
- static int inp_set_source_filters(struct inpcb *, struct sockopt *);
- SYSCTL_NODE(_net_inet_ip, OID_AUTO, mcast, CTLFLAG_RW, 0, "IPv4 multicast");
- static u_long in_mcast_maxgrpsrc = IP_MAX_GROUP_SRC_FILTER;
- SYSCTL_ULONG(_net_inet_ip_mcast, OID_AUTO, maxgrpsrc,
- CTLFLAG_RW | CTLFLAG_TUN, &in_mcast_maxgrpsrc, 0,
- "Max source filters per group");
- TUNABLE_ULONG("net.inet.ip.mcast.maxgrpsrc", &in_mcast_maxgrpsrc);
- static u_long in_mcast_maxsocksrc = IP_MAX_SOCK_SRC_FILTER;
- SYSCTL_ULONG(_net_inet_ip_mcast, OID_AUTO, maxsocksrc,
- CTLFLAG_RW | CTLFLAG_TUN, &in_mcast_maxsocksrc, 0,
- "Max source filters per socket");
- TUNABLE_ULONG("net.inet.ip.mcast.maxsocksrc", &in_mcast_maxsocksrc);
- int in_mcast_loop = IP_DEFAULT_MULTICAST_LOOP;
- SYSCTL_INT(_net_inet_ip_mcast, OID_AUTO, loop, CTLFLAG_RW | CTLFLAG_TUN,
- &in_mcast_loop, 0, "Loopback multicast datagrams by default");
- TUNABLE_INT("net.inet.ip.mcast.loop", &in_mcast_loop);
- SYSCTL_NODE(_net_inet_ip_mcast, OID_AUTO, filters,
- CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_ip_mcast_filters,
- "Per-interface stack-wide source filters");
- /*
- * Inline function which wraps assertions for a valid ifp.
- * The ifnet layer will set the ifma's ifp pointer to NULL if the ifp
- * is detached.
- */
- static int __inline
- inm_is_ifp_detached(const struct in_multi *inm)
- {
- struct ifnet *ifp;
- KASSERT(inm->inm_ifma != NULL, ("%s: no ifma", __func__));
- ifp = inm->inm_ifma->ifma_ifp;
- if (ifp != NULL) {
- /*
- * Sanity check that netinet's notion of ifp is the
- * same as net's.
- */
- KASSERT(inm->inm_ifp == ifp, ("%s: bad ifp", __func__));
- }
- return (ifp == NULL);
- }
- /*
- * Initialize an in_mfilter structure to a known state at t0, t1
- * with an empty source filter list.
- */
- static __inline void
- imf_init(struct in_mfilter *imf, const int st0, const int st1)
- {
- memset(imf, 0, sizeof(struct in_mfilter));
- RB_INIT(&imf->imf_sources);
- imf->imf_st[0] = st0;
- imf->imf_st[1] = st1;
- }
- /*
- * Resize the ip_moptions vector to the next power-of-two minus 1.
- * May be called with locks held; do not sleep.
- */
- static int
- imo_grow(struct ip_moptions *imo)
- {
- struct in_multi **nmships;
- struct in_multi **omships;
- struct in_mfilter *nmfilters;
- struct in_mfilter *omfilters;
- size_t idx;
- size_t newmax;
- size_t oldmax;
- nmships = NULL;
- nmfilters = NULL;
- omships = imo->imo_membership;
- omfilters = imo->imo_mfilters;
- oldmax = imo->imo_max_memberships;
- newmax = ((oldmax + 1) * 2) - 1;
- if (newmax <= IP_MAX_MEMBERSHIPS) {
- nmships = (struct in_multi **)realloc(omships,
- sizeof(struct in_multi *) * newmax);
- nmfilters = (struct in_mfilter *)realloc(omfilters,
- sizeof(struct in_mfilter) * newmax);
- if (nmships != NULL && nmfilters != NULL) {
- /* Initialize newly allocated source filter heads. */
- for (idx = oldmax; idx < newmax; idx++) {
- imf_init(&nmfilters[idx], MCAST_UNDEFINED,
- MCAST_EXCLUDE);
- }
- imo->imo_max_memberships = newmax;
- imo->imo_membership = nmships;
- imo->imo_mfilters = nmfilters;
- }
- }
- if (nmships == NULL || nmfilters == NULL) {
- if (nmships != NULL)
- free(nmships);
- if (nmfilters != NULL)
- free(nmfilters);
- return (ETOOMANYREFS);
- }
- return (0);
- }
- /*
- * Find an IPv4 multicast group entry for this ip_moptions instance
- * which matches the specified group, and optionally an interface.
- * Return its index into the array, or -1 if not found.
- */
- static size_t
- imo_match_group(const struct ip_moptions *imo, const struct ifnet *ifp,
- const struct bsd_sockaddr *group)
- {
- const struct bsd_sockaddr_in *gsin;
- struct in_multi **pinm;
- int idx;
- int nmships;
- gsin = (const struct bsd_sockaddr_in *)group;
- /* The imo_membership array may be lazy allocated. */
- if (imo->imo_membership == NULL || imo->imo_num_memberships == 0)
- return (-1);
- nmships = imo->imo_num_memberships;
- pinm = &imo->imo_membership[0];
- for (idx = 0; idx < nmships; idx++, pinm++) {
- if (*pinm == NULL)
- continue;
- if ((ifp == NULL || ((*pinm)->inm_ifp == ifp)) &&
- in_hosteq((*pinm)->inm_addr, gsin->sin_addr)) {
- break;
- }
- }
- if (idx >= nmships)
- idx = -1;
- return (idx);
- }
- /*
- * Find an IPv4 multicast source entry for this imo which matches
- * the given group index for this socket, and source address.
- *
- * NOTE: This does not check if the entry is in-mode, merely if
- * it exists, which may not be the desired behaviour.
- */
- static struct in_msource *
- imo_match_source(const struct ip_moptions *imo, const size_t gidx,
- const struct bsd_sockaddr *src)
- {
- struct ip_msource find;
- struct in_mfilter *imf;
- struct ip_msource *ims;
- const sockunion_t *psa;
- KASSERT(src->sa_family == AF_INET, ("%s: !AF_INET", __func__));
- KASSERT(gidx != -1 && gidx < imo->imo_num_memberships,
- ("%s: invalid index %d\n", __func__, (int)gidx));
- /* The imo_mfilters array may be lazy allocated. */
- if (imo->imo_mfilters == NULL)
- return (NULL);
- imf = &imo->imo_mfilters[gidx];
- /* Source trees are keyed in host byte order. */
- psa = (const sockunion_t *)src;
- find.ims_haddr = ntohl(psa->sin.sin_addr.s_addr);
- ims = RB_FIND(ip_msource_tree, &imf->imf_sources, &find);
- return ((struct in_msource *)ims);
- }
- /*
- * Perform filtering for multicast datagrams on a socket by group and source.
- *
- * Returns 0 if a datagram should be allowed through, or various error codes
- * if the socket was not a member of the group, or the source was muted, etc.
- */
- int
- imo_multi_filter(const struct ip_moptions *imo, const struct ifnet *ifp,
- const struct bsd_sockaddr *group, const struct bsd_sockaddr *src)
- {
- size_t gidx;
- struct in_msource *ims;
- int mode;
- KASSERT(ifp != NULL, ("%s: null ifp", __func__));
- gidx = imo_match_group(imo, ifp, group);
- if (gidx == -1)
- return (MCAST_NOTGMEMBER);
- /*
- * Check if the source was included in an (S,G) join.
- * Allow reception on exclusive memberships by default,
- * reject reception on inclusive memberships by default.
- * Exclude source only if an in-mode exclude filter exists.
- * Include source only if an in-mode include filter exists.
- * NOTE: We are comparing group state here at IGMP t1 (now)
- * with socket-layer t0 (since last downcall).
- */
- mode = imo->imo_mfilters[gidx].imf_st[1];
- ims = imo_match_source(imo, gidx, src);
- if ((ims == NULL && mode == MCAST_INCLUDE) ||
- (ims != NULL && ims->imsl_st[0] != mode))
- return (MCAST_NOTSMEMBER);
- return (MCAST_PASS);
- }
- /*
- * Find and return a reference to an in_multi record for (ifp, group),
- * and bump its reference count.
- * If one does not exist, try to allocate it, and update link-layer multicast
- * filters on ifp to listen for group.
- * Assumes the IN_MULTI lock is held across the call.
- * Return 0 if successful, otherwise return an appropriate error code.
- */
- static int
- in_getmulti(struct ifnet *ifp, const struct in_addr *group,
- struct in_multi **pinm)
- {
- struct bsd_sockaddr_in gsin;
- struct ifmultiaddr *ifma;
- struct in_ifinfo *ii;
- struct in_multi *inm;
- int error;
- IN_MULTI_LOCK_ASSERT();
- ii = (struct in_ifinfo *)ifp->if_afdata[AF_INET];
- inm = inm_lookup(ifp, *group);
- if (inm != NULL) {
- /*
- * If we already joined this group, just bump the
- * refcount and return it.
- */
- KASSERT(inm->inm_refcount >= 1,
- ("%s: bad refcount %d", __func__, inm->inm_refcount));
- ++inm->inm_refcount;
- *pinm = inm;
- return (0);
- }
- memset(&gsin, 0, sizeof(gsin));
- gsin.sin_family = AF_INET;
- gsin.sin_len = sizeof(struct bsd_sockaddr_in);
- gsin.sin_addr = *group;
- /*
- * Check if a link-layer group is already associated
- * with this network-layer group on the given ifnet.
- */
- error = if_addmulti(ifp, (struct bsd_sockaddr *)&gsin, &ifma);
- if (error != 0)
- return (error);
- /* XXX ifma_protospec must be covered by IF_ADDR_LOCK */
- IF_ADDR_WLOCK(ifp);
- /*
- * If something other than netinet is occupying the link-layer
- * group, print a meaningful error message and back out of
- * the allocation.
- * Otherwise, bump the refcount on the existing network-layer
- * group association and return it.
- */
- if (ifma->ifma_protospec != NULL) {
- inm = (struct in_multi *)ifma->ifma_protospec;
- #ifdef INVARIANTS
- KASSERT(ifma->ifma_addr != NULL, ("%s: no ifma_addr",
- __func__));
- KASSERT(ifma->ifma_addr->sa_family == AF_INET,
- ("%s: ifma not AF_INET", __func__));
- KASSERT(inm != NULL, ("%s: no ifma_protospec", __func__));
- if (inm->inm_ifma != ifma || inm->inm_ifp != ifp ||
- !in_hosteq(inm->inm_addr, *group))
- panic("%s: ifma %p is inconsistent with %p (%s)",
- __func__, ifma, inm, inet_ntoa(*group));
- #endif
- ++inm->inm_refcount;
- *pinm = inm;
- IF_ADDR_WUNLOCK(ifp);
- return (0);
- }
- IF_ADDR_WLOCK_ASSERT(ifp);
- /*
- * A new in_multi record is needed; allocate and initialize it.
- * We DO NOT perform an IGMP join as the in_ layer may need to
- * push an initial source list down to IGMP to support SSM.
- *
- * The initial source filter state is INCLUDE, {} as per the RFC.
- */
- inm = (in_multi *)malloc(sizeof(*inm));
- if (inm == NULL) {
- if_delmulti_ifma(ifma);
- IF_ADDR_WUNLOCK(ifp);
- return (ENOMEM);
- }
- bzero(inm, sizeof(*inm));
- inm->inm_addr = *group;
- inm->inm_ifp = ifp;
- inm->inm_igi = ii->ii_igmp;
- inm->inm_ifma = ifma;
- inm->inm_refcount = 1;
- inm->inm_state = IGMP_NOT_MEMBER;
- /*
- * Pending state-changes per group are subject to a bounds check.
- */
- IFQ_SET_MAXLEN(&inm->inm_scq, IGMP_MAX_STATE_CHANGES);
- inm->inm_st[0].iss_fmode = MCAST_UNDEFINED;
- inm->inm_st[1].iss_fmode = MCAST_UNDEFINED;
- RB_INIT(&inm->inm_srcs);
- ifma->ifma_protospec = inm;
- *pinm = inm;
- IF_ADDR_WUNLOCK(ifp);
- return (0);
- }
- /*
- * Drop a reference to an in_multi record.
- *
- * If the refcount drops to 0, free the in_multi record and
- * delete the underlying link-layer membership.
- */
- void
- inm_release_locked(struct in_multi *inm)
- {
- struct ifmultiaddr *ifma;
- IN_MULTI_LOCK_ASSERT();
- CTR2(KTR_IGMPV3, "%s: refcount is %d", __func__, inm->inm_refcount);
- if (--inm->inm_refcount > 0) {
- CTR2(KTR_IGMPV3, "%s: refcount is now %d", __func__,
- inm->inm_refcount);
- return;
- }
- CTR2(KTR_IGMPV3, "%s: freeing inm %p", __func__, inm);
- ifma = inm->inm_ifma;
- /* XXX this access is not covered by IF_ADDR_LOCK */
- CTR2(KTR_IGMPV3, "%s: purging ifma %p", __func__, ifma);
- KASSERT(ifma->ifma_protospec == inm,
- ("%s: ifma_protospec != inm", __func__));
- ifma->ifma_protospec = NULL;
- inm_purge(inm);
- free(inm);
- if_delmulti_ifma(ifma);
- }
- /*
- * Clear recorded source entries for a group.
- * Used by the IGMP code. Caller must hold the IN_MULTI lock.
- * FIXME: Should reap.
- */
- void
- inm_clear_recorded(struct in_multi *inm)
- {
- struct ip_msource *ims;
- IN_MULTI_LOCK_ASSERT();
- RB_FOREACH(ims, ip_msource_tree, &inm->inm_srcs) {
- if (ims->ims_stp) {
- ims->ims_stp = 0;
- --inm->inm_st[1].iss_rec;
- }
- }
- KASSERT(inm->inm_st[1].iss_rec == 0,
- ("%s: iss_rec %d not 0", __func__, inm->inm_st[1].iss_rec));
- }
- /*
- * Record a source as pending for a Source-Group IGMPv3 query.
- * This lives here as it modifies the shared tree.
- *
- * inm is the group descriptor.
- * naddr is the address of the source to record in network-byte order.
- *
- * If the net.inet.igmp.sgalloc sysctl is non-zero, we will
- * lazy-allocate a source node in response to an SG query.
- * Otherwise, no allocation is performed. This saves some memory
- * with the trade-off that the source will not be reported to the
- * router if joined in the window between the query response and
- * the group actually being joined on the local host.
- *
- * VIMAGE: XXX: Currently the igmp_sgalloc feature has been removed.
- * This turns off the allocation of a recorded source entry if
- * the group has not been joined.
- *
- * Return 0 if the source didn't exist or was already marked as recorded.
- * Return 1 if the source was marked as recorded by this function.
- * Return <0 if any error occured (negated errno code).
- */
- int
- inm_record_source(struct in_multi *inm, const in_addr_t naddr)
- {
- struct ip_msource find;
- struct ip_msource *ims, *nims;
- IN_MULTI_LOCK_ASSERT();
- find.ims_haddr = ntohl(naddr);
- ims = RB_FIND(ip_msource_tree, &inm->inm_srcs, &find);
- if (ims && ims->ims_stp)
- return (0);
- if (ims == NULL) {
- if (inm->inm_nsrc == in_mcast_maxgrpsrc)
- return (-ENOSPC);
- nims = (ip_msource *)malloc(sizeof(struct ip_msource));
- if (nims == NULL)
- return (-ENOMEM);
- bzero(nims, sizeof(struct ip_msource));
- nims->ims_haddr = find.ims_haddr;
- RB_INSERT(ip_msource_tree, &inm->inm_srcs, nims);
- ++inm->inm_nsrc;
- ims = nims;
- }
- /*
- * Mark the source as recorded and update the recorded
- * source count.
- */
- ++ims->ims_stp;
- ++inm->inm_st[1].iss_rec;
- return (1);
- }
- /*
- * Return a pointer to an in_msource owned by an in_mfilter,
- * given its source address.
- * Lazy-allocate if needed. If this is a new entry its filter state is
- * undefined at t0.
- *
- * imf is the filter set being modified.
- * haddr is the source address in *host* byte-order.
- *
- * SMPng: May be called with locks held; malloc must not block.
- */
- static int
- imf_get_source(struct in_mfilter *imf, const struct bsd_sockaddr_in *psin,
- struct in_msource **plims)
- {
- struct ip_msource find;
- struct ip_msource *ims, *nims;
- struct in_msource *lims;
- int error;
- error = 0;
- ims = NULL;
- lims = NULL;
- /* key is host byte order */
- find.ims_haddr = ntohl(psin->sin_addr.s_addr);
- ims = RB_FIND(ip_msource_tree, &imf->imf_sources, &find);
- lims = (struct in_msource *)ims;
- if (lims == NULL) {
- if (imf->imf_nsrc == in_mcast_maxsocksrc)
- return (ENOSPC);
- // FIXME: mismatch between allocation size and type
- nims = (ip_msource *)malloc(sizeof(struct in_msource));
- if (nims == NULL)
- return (ENOMEM);
- bzero(nims, sizeof(struct in_msource));
- lims = (struct in_msource *)nims;
- lims->ims_haddr = find.ims_haddr;
- lims->imsl_st[0] = MCAST_UNDEFINED;
- RB_INSERT(ip_msource_tree, &imf->imf_sources, nims);
- ++imf->imf_nsrc;
- }
- *plims = lims;
- return (error);
- }
- /*
- * Graft a source entry into an existing socket-layer filter set,
- * maintaining any required invariants and checking allocations.
- *
- * The source is marked as being in the new filter mode at t1.
- *
- * Return the pointer to the new node, otherwise return NULL.
- */
- static struct in_msource *
- imf_graft(struct in_mfilter *imf, const uint8_t st1,
- const struct bsd_sockaddr_in *psin)
- {
- struct ip_msource *nims;
- struct in_msource *lims;
- // FIXME: mismatch between allocated size and type
- nims = (ip_msource *)malloc(sizeof(struct in_msource));
- if (nims == NULL)
- return (NULL);
- bzero(nims, sizeof(struct in_msource));
- lims = (struct in_msource *)nims;
- lims->ims_haddr = ntohl(psin->sin_addr.s_addr);
- lims->imsl_st[0] = MCAST_UNDEFINED;
- lims->imsl_st[1] = st1;
- RB_INSERT(ip_msource_tree, &imf->imf_sources, nims);
- ++imf->imf_nsrc;
- return (lims);
- }
- /*
- * Prune a source entry from an existing socket-layer filter set,
- * maintaining any required invariants and checking allocations.
- *
- * The source is marked as being left at t1, it is not freed.
- *
- * Return 0 if no error occurred, otherwise return an errno value.
- */
- static int
- imf_prune(struct in_mfilter *imf, const struct bsd_sockaddr_in *psin)
- {
- struct ip_msource find;
- struct ip_msource *ims;
- struct in_msource *lims;
- /* key is host byte order */
- find.ims_haddr = ntohl(psin->sin_addr.s_addr);
- ims = RB_FIND(ip_msource_tree, &imf->imf_sources, &find);
- if (ims == NULL)
- return (ENOENT);
- lims = (struct in_msource *)ims;
- lims->imsl_st[1] = MCAST_UNDEFINED;
- return (0);
- }
- /*
- * Revert socket-layer filter set deltas at t1 to t0 state.
- */
- static void
- imf_rollback(struct in_mfilter *imf)
- {
- struct ip_msource *ims, *tims;
- struct in_msource *lims;
- RB_FOREACH_SAFE(ims, ip_msource_tree, &imf->imf_sources, tims) {
- lims = (struct in_msource *)ims;
- if (lims->imsl_st[0] == lims->imsl_st[1]) {
- /* no change at t1 */
- continue;
- } else if (lims->imsl_st[0] != MCAST_UNDEFINED) {
- /* revert change to existing source at t1 */
- lims->imsl_st[1] = lims->imsl_st[0];
- } else {
- /* revert source added t1 */
- CTR2(KTR_IGMPV3, "%s: free ims %p", __func__, ims);
- RB_REMOVE(ip_msource_tree, &imf->imf_sources, ims);
- free(ims);
- imf->imf_nsrc--;
- }
- }
- imf->imf_st[1] = imf->imf_st[0];
- }
- /*
- * Mark socket-layer filter set as INCLUDE {} at t1.
- */
- static void
- imf_leave(struct in_mfilter *imf)
- {
- struct ip_msource *ims;
- struct in_msource *lims;
- RB_FOREACH(ims, ip_msource_tree, &imf->imf_sources) {
- lims = (struct in_msource *)ims;
- lims->imsl_st[1] = MCAST_UNDEFINED;
- }
- imf->imf_st[1] = MCAST_INCLUDE;
- }
- /*
- * Mark socket-layer filter set deltas as committed.
- */
- static void
- imf_commit(struct in_mfilter *imf)
- {
- struct ip_msource *ims;
- struct in_msource *lims;
- RB_FOREACH(ims, ip_msource_tree, &imf->imf_sources) {
- lims = (struct in_msource *)ims;
- lims->imsl_st[0] = lims->imsl_st[1];
- }
- imf->imf_st[0] = imf->imf_st[1];
- }
- /*
- * Reap unreferenced sources from socket-layer filter set.
- */
- static void
- imf_reap(struct in_mfilter *imf)
- {
- struct ip_msource *ims, *tims;
- struct in_msource *lims;
- RB_FOREACH_SAFE(ims, ip_msource_tree, &imf->imf_sources, tims) {
- lims = (struct in_msource *)ims;
- if ((lims->imsl_st[0] == MCAST_UNDEFINED) &&
- (lims->imsl_st[1] == MCAST_UNDEFINED)) {
- CTR2(KTR_IGMPV3, "%s: free lims %p", __func__, ims);
- RB_REMOVE(ip_msource_tree, &imf->imf_sources, ims);
- free(ims);
- imf->imf_nsrc--;
- }
- }
- }
- /*
- * Purge socket-layer filter set.
- */
- static void
- imf_purge(struct in_mfilter *imf)
- {
- struct ip_msource *ims, *tims;
- RB_FOREACH_SAFE(ims, ip_msource_tree, &imf->imf_sources, tims) {
- CTR2(KTR_IGMPV3, "%s: free ims %p", __func__, ims);
- RB_REMOVE(ip_msource_tree, &imf->imf_sources, ims);
- free(ims);
- imf->imf_nsrc--;
- }
- imf->imf_st[0] = imf->imf_st[1] = MCAST_UNDEFINED;
- KASSERT(RB_EMPTY(&imf->imf_sources),
- ("%s: imf_sources not empty", __func__));
- }
- /*
- * Look up a source filter entry for a multicast group.
- *
- * inm is the group descriptor to work with.
- * haddr is the host-byte-order IPv4 address to look up.
- * noalloc may be non-zero to suppress allocation of sources.
- * *pims will be set to the address of the retrieved or allocated source.
- *
- * SMPng: NOTE: may be called with locks held.
- * Return 0 if successful, otherwise return a non-zero error code.
- */
- static int
- inm_get_source(struct in_multi *inm, const in_addr_t haddr,
- const int noalloc, struct ip_msource **pims)
- {
- struct ip_msource find;
- struct ip_msource *ims, *nims;
- #ifdef KTR
- struct in_addr ia;
- #endif
- find.ims_haddr = haddr;
- ims = RB_FIND(ip_msource_tree, &inm->inm_srcs, &find);
- if (ims == NULL && !noalloc) {
- if (inm->inm_nsrc == in_mcast_maxgrpsrc)
- return (ENOSPC);
- nims = (ip_msource *)malloc(sizeof(struct ip_msource));
- if (nims == NULL)
- return (ENOMEM);
- bzero(nims, sizeof(struct ip_msource));
- nims->ims_haddr = haddr;
- RB_INSERT(ip_msource_tree, &inm->inm_srcs, nims);
- ++inm->inm_nsrc;
- ims = nims;
- #ifdef KTR
- ia.s_addr = htonl(haddr);
- CTR3(KTR_IGMPV3, "%s: allocated %s as %p", __func__,
- inet_ntoa(ia), ims);
- #endif
- }
- *pims = ims;
- return (0);
- }
- /*
- * Merge socket-layer source into IGMP-layer source.
- * If rollback is non-zero, perform the inverse of the merge.
- */
- static void
- ims_merge(struct ip_msource *ims, const struct in_msource *lims,
- const int rollback)
- {
- int n = rollback ? -1 : 1;
- #ifdef KTR
- struct in_addr ia;
- ia.s_addr = htonl(ims->ims_haddr);
- #endif
- if (lims->imsl_st[0] == MCAST_EXCLUDE) {
- CTR3(KTR_IGMPV3, "%s: t1 ex -= %d on %s",
- __func__, n, inet_ntoa(ia));
- ims->ims_st[1].ex -= n;
- } else if (lims->imsl_st[0] == MCAST_INCLUDE) {
- CTR3(KTR_IGMPV3, "%s: t1 in -= %d on %s",
- __func__, n, inet_ntoa(ia));
- ims->ims_st[1].in -= n;
- }
- if (lims->imsl_st[1] == MCAST_EXCLUDE) {
- CTR3(KTR_IGMPV3, "%s: t1 ex += %d on %s",
- __func__, n, inet_ntoa(ia));
- ims->ims_st[1].ex += n;
- } else if (lims->imsl_st[1] == MCAST_INCLUDE) {
- CTR3(KTR_IGMPV3, "%s: t1 in += %d on %s",
- __func__, n, inet_ntoa(ia));
- ims->ims_st[1].in += n;
- }
- }
- /*
- * Atomically update the global in_multi state, when a membership's
- * filter list is being updated in any way.
- *
- * imf is the per-inpcb-membership group filter pointer.
- * A fake imf may be passed for in-kernel consumers.
- *
- * XXX This is a candidate for a set-symmetric-difference style loop
- * which would eliminate the repeated lookup from root of ims nodes,
- * as they share the same key space.
- *
- * If any error occurred this function will back out of refcounts
- * and return a non-zero value.
- */
- static int
- inm_merge(struct in_multi *inm, /*const*/ struct in_mfilter *imf)
- {
- struct ip_msource *ims, *nims;
- struct in_msource *lims;
- int schanged, error;
- int nsrc0, nsrc1;
- schanged = 0;
- error = 0;
- nsrc1 = nsrc0 = 0;
- nims = NULL;
- /*
- * Update the source filters first, as this may fail.
- * Maintain count of in-mode filters at t0, t1. These are
- * used to work out if we transition into ASM mode or not.
- * Maintain a count of source filters whose state was
- * actually modified by this operation.
- */
- RB_FOREACH(ims, ip_msource_tree, &imf->imf_sources) {
- lims = (struct in_msource *)ims;
- if (lims->imsl_st[0] == imf->imf_st[0]) nsrc0++;
- if (lims->imsl_st[1] == imf->imf_st[1]) nsrc1++;
- if (lims->imsl_st[0] == lims->imsl_st[1]) continue;
- error = inm_get_source(inm, lims->ims_haddr, 0, &nims);
- ++schanged;
- if (error)
- break;
- ims_merge(nims, lims, 0);
- }
- if (error) {
- struct ip_msource *bims;
- RB_FOREACH_REVERSE_FROM(ims, ip_msource_tree, nims) {
- lims = (struct in_msource *)ims;
- if (lims->imsl_st[0] == lims->imsl_st[1])
- continue;
- (void)inm_get_source(inm, lims->ims_haddr, 1, &bims);
- if (bims == NULL)
- continue;
- ims_merge(bims, lims, 1);
- }
- goto out_reap;
- }
- CTR3(KTR_IGMPV3, "%s: imf filters in-mode: %d at t0, %d at t1",
- __func__, nsrc0, nsrc1);
- /* Handle transition between INCLUDE {n} and INCLUDE {} on socket. */
- if (imf->imf_st[0] == imf->imf_st[1] &&
- imf->imf_st[1] == MCAST_INCLUDE) {
- if (nsrc1 == 0) {
- CTR1(KTR_IGMPV3, "%s: --in on inm at t1", __func__);
- --inm->inm_st[1].iss_in;
- }
- }
- /* Handle filter mode transition on socket. */
- if (imf->imf_st[0] != imf->imf_st[1]) {
- CTR3(KTR_IGMPV3, "%s: imf transition %d to %d",
- __func__, imf->imf_st[0], imf->imf_st[1]);
- if (imf->imf_st[0] == MCAST_EXCLUDE) {
- CTR1(KTR_IGMPV3, "%s: --ex on inm at t1", __func__);
- --inm->inm_st[1].iss_ex;
- } else if (imf->imf_st[0] == MCAST_INCLUDE) {
- CTR1(KTR_IGMPV3, "%s: --in on inm at t1", __func__);
- --inm->inm_st[1].iss_in;
- }
- if (imf->imf_st[1] == MCAST_EXCLUDE) {
- CTR1(KTR_IGMPV3, "%s: ex++ on inm at t1", __func__);
- inm->inm_st[1].iss_ex++;
- } else if (imf->imf_st[1] == MCAST_INCLUDE && nsrc1 > 0) {
- CTR1(KTR_IGMPV3, "%s: in++ on inm at t1", __func__);
- inm->inm_st[1].iss_in++;
- }
- }
- /*
- * Track inm filter state in terms of listener counts.
- * If there are any exclusive listeners, stack-wide
- * membership is exclusive.
- * Otherwise, if only inclusive listeners, stack-wide is inclusive.
- * If no listeners remain, state is undefined at t1,
- * and the IGMP lifecycle for this group should finish.
- */
- if (inm->inm_st[1].iss_ex > 0) {
- CTR1(KTR_IGMPV3, "%s: transition to EX", __func__);
- inm->inm_st[1].iss_fmode = MCAST_EXCLUDE;
- } else if (inm->inm_st[1].iss_in > 0) {
- CTR1(KTR_IGMPV3, "%s: transition to IN", __func__);
- inm->inm_st[1].iss_fmode = MCAST_INCLUDE;
- } else {
- CTR1(KTR_IGMPV3, "%s: transition to UNDEF", __func__);
- inm->inm_st[1].iss_fmode = MCAST_UNDEFINED;
- }
- /* Decrement ASM listener count on transition out of ASM mode. */
- if (imf->imf_st[0] == MCAST_EXCLUDE && nsrc0 == 0) {
- if ((imf->imf_st[1] != MCAST_EXCLUDE) ||
- (imf->imf_st[1] == MCAST_EXCLUDE && nsrc1 > 0))
- CTR1(KTR_IGMPV3, "%s: --asm on inm at t1", __func__);
- --inm->inm_st[1].iss_asm;
- }
- /* Increment ASM listener count on transition to ASM mode. */
- if (imf->imf_st[1] == MCAST_EXCLUDE && nsrc1 == 0) {
- CTR1(KTR_IGMPV3, "%s: asm++ on inm at t1", __func__);
- inm->inm_st[1].iss_asm++;
- }
- CTR3(KTR_IGMPV3, "%s: merged imf %p to inm %p", __func__, imf, inm);
- inm_print(inm);
- out_reap:
- if (schanged > 0) {
- CTR1(KTR_IGMPV3, "%s: sources changed; reaping", __func__);
- inm_reap(inm);
- }
- return (error);
- }
- /*
- * Mark an in_multi's filter set deltas as committed.
- * Called by IGMP after a state change has been enqueued.
- */
- void
- inm_commit(struct in_multi *inm)
- {
- struct ip_msource *ims;
- CTR2(KTR_IGMPV3, "%s: commit inm %p", __func__, inm);
- CTR1(KTR_IGMPV3, "%s: pre commit:", __func__);
- inm_print(inm);
- RB_FOREACH(ims, ip_msource_tree, &inm->inm_srcs) {
- ims->ims_st[0] = ims->ims_st[1];
- }
- inm->inm_st[0] = inm->inm_st[1];
- }
- /*
- * Reap unreferenced nodes from an in_multi's filter set.
- */
- static void
- inm_reap(struct in_multi *inm)
- {
- struct ip_msource *ims, *tims;
- RB_FOREACH_SAFE(ims, ip_msource_tree, &inm->inm_srcs, tims) {
- if (ims->ims_st[0].ex > 0 || ims->ims_st[0].in > 0 ||
- ims->ims_st[1].ex > 0 || ims->ims_st[1].in > 0 ||
- ims->ims_stp != 0)
- continue;
- CTR2(KTR_IGMPV3, "%s: free ims %p", __func__, ims);
- RB_REMOVE(ip_msource_tree, &inm->inm_srcs, ims);
- free(ims);
- inm->inm_nsrc--;
- }
- }
- /*
- * Purge all source nodes from an in_multi's filter set.
- */
- static void
- inm_purge(struct in_multi *inm)
- {
- struct ip_msource *ims, *tims;
- RB_FOREACH_SAFE(ims, ip_msource_tree, &inm->inm_srcs, tims) {
- CTR2(KTR_IGMPV3, "%s: free ims %p", __func__, ims);
- RB_REMOVE(ip_msource_tree, &inm->inm_srcs, ims);
- free(ims);
- inm->inm_nsrc--;
- }
- }
- /*
- * Join a multicast group; unlocked entry point.
- *
- * SMPng: XXX: in_joingroup() is called from in_control() when Giant
- * is not held. Fortunately, ifp is unlikely to have been detached
- * at this point, so we assume it's OK to recurse.
- */
- int
- in_joingroup(struct ifnet *ifp, const struct in_addr *gina,
- /*const*/ struct in_mfilter *imf, struct in_multi **pinm)
- {
- int error;
- IN_MULTI_LOCK();
- error = in_joingroup_locked(ifp, gina, imf, pinm);
- IN_MULTI_UNLOCK();
- return (error);
- }
- /*
- * Join a multicast group; real entry point.
- *
- * Only preserves atomicity at inm level.
- * NOTE: imf argument cannot be const due to sys/tree.h limitations.
- *
- * If the IGMP downcall fails, the group is not joined, and an error
- * code is returned.
- */
- int
- in_joingroup_locked(struct ifnet *ifp, const struct in_addr *gina,
- /*const*/ struct in_mfilter *imf, struct in_multi **pinm)
- {
- struct in_mfilter timf;
- struct in_multi *inm;
- int error;
- IN_MULTI_LOCK_ASSERT();
- CTR4(KTR_IGMPV3, "%s: join %s on %p(%s))", __func__,
- inet_ntoa(*gina), ifp, ifp->if_xname);
- error = 0;
- inm = NULL;
- /*
- * If no imf was specified (i.e. kernel consumer),
- * fake one up and assume it is an ASM join.
- */
- if (imf == NULL) {
- imf_init(&timf, MCAST_UNDEFINED, MCAST_EXCLUDE);
- imf = &timf;
- }
- error = in_getmulti(ifp, gina, &inm);
- if (error) {
- CTR1(KTR_IGMPV3, "%s: in_getmulti() failure", __func__);
- return (error);
- }
- CTR1(KTR_IGMPV3, "%s: merge inm state", __func__);
- error = inm_merge(inm, imf);
- if (error) {
- CTR1(KTR_IGMPV3, "%s: failed to merge inm state", __func__);
- goto out_inm_release;
- }
- CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__);
- error = igmp_change_state(inm);
- if (error) {
- CTR1(KTR_IGMPV3, "%s: failed to update source", __func__);
- goto out_inm_release;
- }
- out_inm_release:
- if (error) {
- CTR2(KTR_IGMPV3, "%s: dropping ref on %p", __func__, inm);
- inm_release_locked(inm);
- } else {
- *pinm = inm;
- }
- return (error);
- }
- /*
- * Leave a multicast group; unlocked entry point.
- */
- int
- in_leavegroup(struct in_multi *inm, /*const*/ struct in_mfilter *imf)
- {
- struct ifnet *ifp;
- int error;
- ifp = inm->inm_ifp;
- IN_MULTI_LOCK();
- error = in_leavegroup_locked(inm, imf);
- IN_MULTI_UNLOCK();
- return (error);
- }
- /*
- * Leave a multicast group; real entry point.
- * All source filters will be expunged.
- *
- * Only preserves atomicity at inm level.
- *
- * Holding the write lock for the INP which contains imf
- * is highly advisable. We can't assert for it as imf does not
- * contain a back-pointer to the owning inp.
- *
- * Note: This is not the same as inm_release(*) as this function also
- * makes a state change downcall into IGMP.
- */
- int
- in_leavegroup_locked(struct in_multi *inm, /*const*/ struct in_mfilter *imf)
- {
- struct in_mfilter timf;
- int error;
- error = 0;
- IN_MULTI_LOCK_ASSERT();
- CTR5(KTR_IGMPV3, "%s: leave inm %p, %s/%s, imf %p", __func__,
- inm, inet_ntoa(inm->inm_addr),
- (inm_is_ifp_detached(inm) ? "null" : inm->inm_ifp->if_xname),
- imf);
- /*
- * If no imf was specified (i.e. kernel consumer),
- * fake one up and assume it is an ASM join.
- */
- if (imf == NULL) {
- imf_init(&timf, MCAST_EXCLUDE, MCAST_UNDEFINED);
- imf = &timf;
- }
- /*
- * Begin state merge transaction at IGMP layer.
- *
- * As this particular invocation should not cause any memory
- * to be allocated, and there is no opportunity to roll back
- * the transaction, it MUST NOT fail.
- */
- CTR1(KTR_IGMPV3, "%s: merge inm state", __func__);
- error = inm_merge(inm, imf);
- KASSERT(error == 0, ("%s: failed to merge inm state", __func__));
- CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__);
- error = igmp_change_state(inm);
- if (error)
- CTR1(KTR_IGMPV3, "%s: failed igmp downcall", __func__);
- CTR2(KTR_IGMPV3, "%s: dropping ref on %p", __func__, inm);
- inm_release_locked(inm);
- return (error);
- }
- /*#ifndef BURN_BRIDGES*/
- /*
- * Join an IPv4 multicast group in (*,G) exclusive mode.
- * The group must be a 224.0.0.0/24 link-scope group.
- * This KPI is for legacy kernel consumers only.
- */
- struct in_multi *
- in_addmulti(struct in_addr *ap, struct ifnet *ifp)
- {
- struct in_multi *pinm;
- int error;
- KASSERT(IN_LOCAL_GROUP(ntohl(ap->s_addr)),
- ("%s: %s not in 224.0.0.0/24", __func__, inet_ntoa(*ap)));
- error = in_joingroup(ifp, ap, NULL, &pinm);
- if (error != 0)
- pinm = NULL;
- return (pinm);
- }
- /*
- * Leave an IPv4 multicast group, assumed to be in exclusive (*,G) mode.
- * This KPI is for legacy kernel consumers only.
- */
- void
- in_delmulti(struct in_multi *inm)
- {
- (void)in_leavegroup(inm, NULL);
- }
- /*#endif*/
- /*
- * Block or unblock an ASM multicast source on an inpcb.
- * This implements the delta-based API described in RFC 3678.
- *
- * The delta-based API applies only to exclusive-mode memberships.
- * An IGMP downcall will be performed.
- *
- * SMPng: NOTE: Must take Giant as a join may create a new ifma.
- *
- * Return 0 if successful, otherwise return an appropriate error code.
- */
- static int
- inp_block_unblock_source(struct inpcb *inp, struct sockopt *sopt)
- {
- struct group_source_req gsr;
- sockunion_t *gsa, *ssa;
- struct ifnet *ifp;
- struct in_mfilter *imf;
- struct ip_moptions *imo;
- struct in_msource *ims;
- struct in_multi *inm;
- size_t idx;
- uint16_t fmode;
- int error, doblock;
- ifp = NULL;
- error = 0;
- doblock = 0;
- memset(&gsr, 0, sizeof(struct group_source_req));
- gsa = (sockunion_t *)&gsr.gsr_group;
- ssa = (sockunion_t *)&gsr.gsr_source;
- switch (sopt->sopt_name) {
- case IP_BLOCK_SOURCE:
- case IP_UNBLOCK_SOURCE: {
- struct ip_mreq_source mreqs;
- error = sooptcopyin(sopt, &mreqs,
- sizeof(struct ip_mreq_source),
- sizeof(struct ip_mreq_source));
- if (error)
- return (error);
- gsa->sin.sin_family = AF_INET;
- gsa->sin.sin_len = sizeof(struct bsd_sockaddr_in);
- gsa->sin.sin_addr = mreqs.imr_multiaddr;
- ssa->sin.sin_family = AF_INET;
- ssa->sin.sin_len = sizeof(struct bsd_sockaddr_in);
- ssa->sin.sin_addr = mreqs.imr_sourceaddr;
- if (!in_nullhost(mreqs.imr_interface))
- INADDR_TO_IFP(mreqs.imr_interface, ifp);
- if (sopt->sopt_name == IP_BLOCK_SOURCE)
- doblock = 1;
- CTR3(KTR_IGMPV3, "%s: imr_interface = %s, ifp = %p",
- __func__, inet_ntoa(mreqs.imr_interface), ifp);
- break;
- }
- case MCAST_BLOCK_SOURCE:
- case MCAST_UNBLOCK_SOURCE:
- error = sooptcopyin(sopt, &gsr,
- sizeof(struct group_source_req),
- sizeof(struct group_source_req));
- if (error)
- return (error);
- if (gsa->sin.sin_family != AF_INET ||
- gsa->sin.sin_len != sizeof(struct bsd_sockaddr_in))
- return (EINVAL);
- if (ssa->sin.sin_family != AF_INET ||
- ssa->sin.sin_len != sizeof(struct bsd_sockaddr_in))
- return (EINVAL);
- if (gsr.gsr_interface == 0 || V_if_index < gsr.gsr_interface)
- return (EADDRNOTAVAIL);
- ifp = ifnet_byindex(gsr.gsr_interface);
- if (sopt->sopt_name == MCAST_BLOCK_SOURCE)
- doblock = 1;
- break;
- default:
- CTR2(KTR_IGMPV3, "%s: unknown sopt_name %d",
- __func__, sopt->sopt_name);
- return (EOPNOTSUPP);
- break;
- }
- if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
- return (EINVAL);
- /*
- * Check if we are actually a member of this group.
- */
- imo = inp_findmoptions(inp);
- idx = imo_match_group(imo, ifp, &gsa->sa);
- if (idx == -1 || imo->imo_mfilters == NULL) {
- error = EADDRNOTAVAIL;
- goto out_inp_locked;
- }
- KASSERT(imo->imo_mfilters != NULL,
- ("%s: imo_mfilters not allocated", __func__));
- imf = &imo->imo_mfilters[idx];
- inm = imo->imo_membership[idx];
- /*
- * Attempting to use the delta-based API on an
- * non exclusive-mode membership is an error.
- */
- fmode = imf->imf_st[0];
- if (fmode != MCAST_EXCLUDE) {
- error = EINVAL;
- goto out_inp_locked;
- }
- /*
- * Deal with error cases up-front:
- * Asked to block, but already blocked; or
- * Asked to unblock, but nothing to unblock.
- * If adding a new block entry, allocate it.
- */
- ims = imo_match_source(imo, idx, &ssa->sa);
- if ((ims != NULL && doblock) || (ims == NULL && !doblock)) {
- CTR3(KTR_IGMPV3, "%s: source %s %spresent", __func__,
- inet_ntoa(ssa->sin.sin_addr), doblock ? "" : "not ");
- error = EADDRNOTAVAIL;
- goto out_inp_locked;
- }
- INP_LOCK_ASSERT(inp);
- /*
- * Begin state merge transaction at socket layer.
- */
- if (doblock) {
- CTR2(KTR_IGMPV3, "%s: %s source", __func__, "block");
- ims = imf_graft(imf, fmode, &ssa->sin);
- if (ims == NULL)
- error = ENOMEM;
- } else {
- CTR2(KTR_IGMPV3, "%s: %s source", __func__, "allow");
- error = imf_prune(imf, &ssa->sin);
- }
- if (error) {
- CTR1(KTR_IGMPV3, "%s: merge imf state failed", __func__);
- goto out_imf_rollback;
- }
- /*
- * Begin state merge transaction at IGMP layer.
- */
- IN_MULTI_LOCK();
- CTR1(KTR_IGMPV3, "%s: merge inm state", __func__);
- error = inm_merge(inm, imf);
- if (error) {
- CTR1(KTR_IGMPV3, "%s: failed to merge inm state", __func__);
- goto out_imf_rollback;
- }
- CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__);
- error = igmp_change_state(inm);
- if (error)
- CTR1(KTR_IGMPV3, "%s: failed igmp downcall", __func__);
- IN_MULTI_UNLOCK();
- out_imf_rollback:
- if (error)
- imf_rollback(imf);
- else
- imf_commit(imf);
- imf_reap(imf);
- out_inp_locked:
- INP_UNLOCK(inp);
- return (error);
- }
- /*
- * Given an inpcb, return its multicast options structure pointer. Accepts
- * an unlocked inpcb pointer, but will return it locked. May sleep.
- *
- * SMPng: NOTE: Potentially calls malloc(M_WAITOK) with Giant held.
- * SMPng: NOTE: Returns with the INP write lock held.
- */
- static struct ip_moptions *
- inp_findmoptions(struct inpcb *inp)
- {
- struct ip_moptions *imo;
- struct in_multi **immp;
- struct in_mfilter *imfp;
- size_t idx;
- INP_LOCK(inp);
- if (inp->inp_moptions != NULL)
- return (inp->inp_moptions);
- INP_UNLOCK(inp);
- imo = (ip_moptions *)malloc(sizeof(*imo));
- immp = (in_multi **)malloc(sizeof(*immp) * IP_MIN_MEMBERSHIPS);
- bzero(immp, sizeof(*immp) * IP_MIN_MEMBERSHIPS);
- imfp = (in_mfilter *)malloc(sizeof(struct in_mfilter) * IP_MIN_MEMBERSHIPS);
- 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 = in_mcast_loop;
- imo->imo_num_memberships = 0;
- imo->imo_max_memberships = IP_MIN_MEMBERSHIPS;
- imo->imo_membership = immp;
- /* Initialize per-group source filters. */
- for (idx = 0; idx < IP_MIN_MEMBERSHIPS; idx++)
- imf_init(&imfp[idx], MCAST_UNDEFINED, MCAST_EXCLUDE);
- imo->imo_mfilters = imfp;
- INP_LOCK(inp);
- if (inp->inp_moptions != NULL) {
- free(imfp);
- free(immp);
- free(imo);
- return (inp->inp_moptions);
- }
- inp->inp_moptions = imo;
- return (imo);
- }
- /*
- * Discard the IP multicast options (and source filters).
- *
- * SMPng: NOTE: assumes INP write lock is held.
- */
- void
- inp_freemoptions(struct ip_moptions *imo)
- {
- struct in_mfilter *imf;
- size_t idx, nmships;
- KASSERT(imo != NULL, ("%s: ip_moptions is NULL", __func__));
- nmships = imo->imo_num_memberships;
- for (idx = 0; idx < nmships; ++idx) {
- imf = imo->imo_mfilters ? &imo->imo_mfilters[idx] : NULL;
- if (imf)
- imf_leave(imf);
- (void)in_leavegroup(imo->imo_membership[idx], imf);
- if (imf)
- imf_purge(imf);
- }
- if (imo->imo_mfilters)
- free(imo->imo_mfilters);
- free(imo->imo_membership);
- free(imo);
- }
- /*
- * Atomically get source filters on a socket for an IPv4 multicast group.
- * Called with INP lock held; returns with lock released.
- */
- static int
- inp_get_source_filters(struct inpcb *inp, struct sockopt *sopt)
- {
- struct __msfilterreq msfr;
- sockunion_t *gsa;
- struct ifnet *ifp;
- struct ip_moptions *imo;
- struct in_mfilter *imf;
- struct ip_msource *ims;
- struct in_msource *lims;
- struct bsd_sockaddr_in *psin;
- struct bsd_sockaddr_storage *ptss;
- struct bsd_sockaddr_storage *tss;
- int error;
- size_t idx, nsrcs, ncsrcs;
- INP_LOCK_ASSERT(inp);
- imo = inp->inp_moptions;
- KASSERT(imo != NULL, ("%s: null ip_moptions", __func__));
- INP_UNLOCK(inp);
- error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq),
- sizeof(struct __msfilterreq));
- if (error)
- return (error);
- if (msfr.msfr_ifindex == 0 || V_if_index < msfr.msfr_ifindex)
- return (EINVAL);
- ifp = ifnet_byindex(msfr.msfr_ifindex);
- if (ifp == NULL)
- return (EINVAL);
- INP_LOCK(inp);
- /*
- * Lookup group on the socket.
- */
- gsa = (sockunion_t *)&msfr.msfr_group;
- idx = imo_match_group(imo, ifp, &gsa->sa);
- if (idx == -1 || imo->imo_mfilters == NULL) {
- INP_UNLOCK(inp);
- return (EADDRNOTAVAIL);
- }
- imf = &imo->imo_mfilters[idx];
- /*
- * Ignore memberships which are in limbo.
- */
- if (imf->imf_st[1] == MCAST_UNDEFINED) {
- INP_UNLOCK(inp);
- return (EAGAIN);
- }
- msfr.msfr_fmode = imf->imf_st[1];
- /*
- * If the user specified a buffer, copy out the source filter
- * entries to userland gracefully.
- * We only copy out the number of entries which userland
- * has asked for, but we always tell userland how big the
- * buffer really needs to be.
- */
- tss = NULL;
- if (msfr.msfr_srcs != NULL && msfr.msfr_nsrcs > 0) {
- tss = (bsd_sockaddr_storage *)malloc(sizeof(struct bsd_sockaddr_storage) * msfr.msfr_nsrcs);
- if (tss == NULL) {
- INP_UNLOCK(inp);
- return (ENOBUFS);
- }
- bzero(tss, sizeof(struct bsd_sockaddr_storage) * msfr.msfr_nsrcs);
- }
- /*
- * Count number of sources in-mode at t0.
- * If buffer space exists and remains, copy out source entries.
- */
- nsrcs = msfr.msfr_nsrcs;
- ncsrcs = 0;
- ptss = tss;
- RB_FOREACH(ims, ip_msource_tree, &imf->imf_sources) {
- lims = (struct in_msource *)ims;
- if (lims->imsl_st[0] == MCAST_UNDEFINED ||
- lims->imsl_st[0] != imf->imf_st[0])
- continue;
- ++ncsrcs;
- if (tss != NULL && nsrcs > 0) {
- psin = (struct bsd_sockaddr_in *)ptss;
- psin->sin_family = AF_INET;
- psin->sin_len = sizeof(struct bsd_sockaddr_in);
- psin->sin_addr.s_addr = htonl(lims->ims_haddr);
- psin->sin_port = 0;
- ++ptss;
- --nsrcs;
- }
- }
- INP_UNLOCK(inp);
- if (tss != NULL) {
- error = copyout(tss, msfr.msfr_srcs,
- sizeof(struct bsd_sockaddr_storage) * msfr.msfr_nsrcs);
- free(tss);
- if (error)
- return (error);
- }
- msfr.msfr_nsrcs = ncsrcs;
- error = sooptcopyout(sopt, &msfr, sizeof(struct __msfilterreq));
- return (error);
- }
- /*
- * Return the IP multicast options in response to user getsockopt().
- */
- int
- inp_getmoptions(struct inpcb *inp, struct sockopt *sopt)
- {
- struct ip_mreqn mreqn;
- struct ip_moptions *imo;
- struct ifnet *ifp;
- struct in_ifaddr *ia;
- int error, optval;
- u_char coptval;
- INP_LOCK(inp);
- imo = inp->inp_moptions;
- /*
- * If socket is neither of type SOCK_RAW or SOCK_DGRAM,
- * or is a divert socket, reject it.
- */
- if (inp->inp_socket->so_proto->pr_protocol == IPPROTO_DIVERT ||
- (inp->inp_socket->so_proto->pr_type != SOCK_RAW &&
- inp->inp_socket->so_proto->pr_type != SOCK_DGRAM)) {
- INP_UNLOCK(inp);
- return (EOPNOTSUPP);
- }
- error = 0;
- switch (sopt->sopt_name) {
- case IP_MULTICAST_VIF:
- if (imo != NULL)
- optval = imo->imo_multicast_vif;
- else
- optval = -1;
- INP_UNLOCK(inp);
- error = sooptcopyout(sopt, &optval, sizeof(int));
- break;
- case IP_MULTICAST_IF:
- memset(&mreqn, 0, sizeof(struct ip_mreqn));
- if (imo != NULL) {
- ifp = imo->imo_multicast_ifp;
- if (!in_nullhost(imo->imo_multicast_addr)) {
- mreqn.imr_address = imo->imo_multicast_addr;
- } else if (ifp != NULL) {
- mreqn.imr_ifindex = ifp->if_index;
- IFP_TO_IA(ifp, ia);
- if (ia != NULL) {
- mreqn.imr_address =
- IA_SIN(ia)->sin_addr;
- ifa_free(&ia->ia_ifa);
- }
- }
- }
- INP_UNLOCK(inp);
- if (sopt->sopt_valsize == sizeof(struct ip_mreqn)) {
- error = sooptcopyout(sopt, &mreqn,
- sizeof(struct ip_mreqn));
- } else {
- error = sooptcopyout(sopt, &mreqn.imr_address,
- sizeof(struct in_addr));
- }
- break;
- case IP_MULTICAST_TTL:
- if (imo == 0)
- optval = coptval = IP_DEFAULT_MULTICAST_TTL;
- else
- optval = coptval = imo->imo_multicast_ttl;
- INP_UNLOCK(inp);
- if (sopt->sopt_valsize == sizeof(u_char))
- error = sooptcopyout(sopt, &coptval, sizeof(u_char));
- else
- error = sooptcopyout(sopt, &optval, sizeof(int));
- break;
- case IP_MULTICAST_LOOP:
- if (imo == 0)
- optval = coptval = IP_DEFAULT_MULTICAST_LOOP;
- else
- optval = coptval = imo->imo_multicast_loop;
- INP_UNLOCK(inp);
- if (sopt->sopt_valsize == sizeof(u_char))
- error = sooptcopyout(sopt, &coptval, sizeof(u_char));
- else
- error = sooptcopyout(sopt, &optval, sizeof(int));
- break;
- case IP_MSFILTER:
- if (imo == NULL) {
- error = EADDRNOTAVAIL;
- INP_UNLOCK(inp);
- } else {
- error = inp_get_source_filters(inp, sopt);
- }
- break;
- default:
- INP_UNLOCK(inp);
- error = ENOPROTOOPT;
- break;
- }
- INP_UNLOCK_ASSERT(inp);
- return (error);
- }
- /*
- * Look up the ifnet to use for a multicast group membership,
- * given the IPv4 address of an interface, and the IPv4 group address.
- *
- * This routine exists to support legacy multicast applications
- * which do not understand that multicast memberships are scoped to
- * specific physical links in the networking stack, or which need
- * to join link-scope groups before IPv4 addresses are configured.
- *
- * If inp is non-NULL, use this socket's current FIB number for any
- * required FIB lookup.
- * If ina is INADDR_ANY, look up the group address in the unicast FIB,
- * and use its ifp; usually, this points to the default next-hop.
- *
- * If the FIB lookup fails, attempt to use the first non-loopback
- * interface with multicast capability in the system as a
- * last resort. The legacy IPv4 ASM API requires that we do
- * this in order to allow groups to be joined when the routing
- * table has not yet been populated during boot.
- *
- * Returns NULL if no ifp could be found.
- *
- * SMPng: TODO: Acquire the appropriate locks for INADDR_TO_IFP.
- * FUTURE: Implement IPv4 source-address selection.
- */
- static struct ifnet *
- inp_lookup_mcast_ifp(const struct inpcb *inp,
- const struct bsd_sockaddr_in *gsin, const struct in_addr ina)
- {
- struct ifnet *ifp;
- KASSERT(gsin->sin_family == AF_INET, ("%s: not AF_INET", __func__));
- KASSERT(IN_MULTICAST(ntohl(gsin->sin_addr.s_addr)),
- ("%s: not multicast", __func__));
- ifp = NULL;
- if (!in_nullhost(ina)) {
- INADDR_TO_IFP(ina, ifp);
- } else {
- struct route ro;
- ro.ro_rt = NULL;
- memcpy(&ro.ro_dst, gsin, sizeof(struct bsd_sockaddr_in));
- in_rtalloc_ign(&ro, 0, inp ? inp->inp_inc.inc_fibnum : 0);
- if (ro.ro_rt != NULL) {
- ifp = ro.ro_rt->rt_ifp;
- KASSERT(ifp != NULL, ("%s: null ifp", __func__));
- RTFREE(ro.ro_rt);
- } else {
- struct in_ifaddr *ia;
- struct ifnet *mifp;
- mifp = NULL;
- IN_IFADDR_RLOCK();
- TAILQ_FOREACH(ia, &V_in_ifaddrhead, ia_link) {
- mifp = ia->ia_ifp;
- if (!(mifp->if_flags & IFF_LOOPBACK) &&
- (mifp->if_fla…
Large files files are truncated, but you can click here to view the full file