| /*- |
| * Copyright (c) 2007-2009 Bruce Simpson. |
| * Copyright (c) 1988 Stephen Deering. |
| * Copyright (c) 1992, 1993 |
| * The Regents of the University of California. All rights reserved. |
| * |
| * This code is derived from software contributed to Berkeley by |
| * Stephen Deering of Stanford University. |
| * |
| * 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. |
| * 4. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. |
| * |
| * @(#)igmp.c 8.1 (Berkeley) 7/19/93 |
| */ |
| |
| /* |
| * Internet Group Management Protocol (IGMP) routines. |
| * [RFC1112, RFC2236, RFC3376] |
| * |
| * Written by Steve Deering, Stanford, May 1988. |
| * Modified by Rosen Sharma, Stanford, Aug 1994. |
| * Modified by Bill Fenner, Xerox PARC, Feb 1995. |
| * Modified to fully comply to IGMPv2 by Bill Fenner, Oct 1995. |
| * Significantly rewritten for IGMPv3, VIMAGE, and SMP by Bruce Simpson. |
| * |
| * MULTICAST Revision: 3.5.1.4 |
| */ |
| |
| #include <sys/bsd_cdefs.h> |
| //__FBSDID("$FreeBSD$"); |
| #include "bsd_core.h" |
| |
| #include <sys/bsd_param.h> |
| #include <sys/bsd_systm.h> |
| //#include <sys/bsd_module.h> |
| #include <sys/bsd_malloc.h> |
| #include <sys/bsd_mbuf.h> |
| #include <sys/bsd_socket.h> |
| #include <sys/bsd_protosw.h> |
| #include <sys/bsd_kernel.h> |
| //baoyg//#include <sys/bsd_sysctl.h> |
| #include <sys/bsd_ktr.h> |
| #include <sys/bsd_condvar.h> |
| |
| #include <net/bsd_if.h> |
| #include <net/bsd_netisr.h> |
| #include <net/bsd_vnet.h> |
| |
| #include <netinet/bsd_in.h> |
| #include <netinet/bsd_in_var.h> |
| #include <netinet/bsd_in_systm.h> |
| #include <netinet/bsd_ip.h> |
| #include <netinet/bsd_ip_var.h> |
| #include <netinet/bsd_ip_options.h> |
| #include <netinet/bsd_igmp.h> |
| #include <netinet/bsd_igmp_var.h> |
| |
| #include <machine/bsd_in_cksum.h> |
| |
| #ifdef MAC |
| #include <security/mac/bsd_mac_framework.h> |
| #endif |
| |
| #ifndef KTR_IGMPV3 |
| #define KTR_IGMPV3 KTR_INET |
| #endif |
| |
| static struct igmp_ifinfo * |
| igi_alloc_locked(struct ifnet *); |
| static void igi_delete_locked(const struct ifnet *); |
| static void igmp_dispatch_queue(struct ifqueue *, int, const int); |
| static void igmp_fasttimo_vnet(void); |
| static void igmp_final_leave(struct in_multi *, struct igmp_ifinfo *); |
| static int igmp_handle_state_change(struct in_multi *, |
| struct igmp_ifinfo *); |
| static int igmp_initial_join(struct in_multi *, struct igmp_ifinfo *); |
| static int igmp_input_v1_query(struct ifnet *, const struct ip *, |
| const struct igmp *); |
| static int igmp_input_v2_query(struct ifnet *, const struct ip *, |
| const struct igmp *); |
| static int igmp_input_v3_query(struct ifnet *, const struct ip *, |
| /*const*/ struct igmpv3 *); |
| static int igmp_input_v3_group_query(struct in_multi *, |
| struct igmp_ifinfo *, int, /*const*/ struct igmpv3 *); |
| static int igmp_input_v1_report(struct ifnet *, /*const*/ struct ip *, |
| /*const*/ struct igmp *); |
| static int igmp_input_v2_report(struct ifnet *, /*const*/ struct ip *, |
| /*const*/ struct igmp *); |
| static void igmp_intr(struct mbuf *); |
| static int igmp_isgroupreported(const struct in_addr); |
| static struct mbuf * |
| igmp_ra_alloc(void); |
| #ifdef KTR |
| static char * igmp_rec_type_to_str(const int); |
| #endif |
| static void igmp_set_version(struct igmp_ifinfo *, const int); |
| static void igmp_slowtimo_vnet(void); |
| static int igmp_v1v2_queue_report(struct in_multi *, const int); |
| static void igmp_v1v2_process_group_timer(struct in_multi *, const int); |
| static void igmp_v1v2_process_querier_timers(struct igmp_ifinfo *); |
| static void igmp_v2_update_group(struct in_multi *, const int); |
| static void igmp_v3_cancel_link_timers(struct igmp_ifinfo *); |
| static void igmp_v3_dispatch_general_query(struct igmp_ifinfo *); |
| static struct mbuf * |
| igmp_v3_encap_report(struct ifnet *, struct mbuf *); |
| static int igmp_v3_enqueue_group_record(struct ifqueue *, |
| struct in_multi *, const int, const int, const int); |
| static int igmp_v3_enqueue_filter_change(struct ifqueue *, |
| struct in_multi *); |
| static void igmp_v3_process_group_timers(struct igmp_ifinfo *, |
| struct ifqueue *, struct ifqueue *, struct in_multi *, |
| const int); |
| static int igmp_v3_merge_state_changes(struct in_multi *, |
| struct ifqueue *); |
| static void igmp_v3_suppress_group_record(struct in_multi *); |
| //baoygstatic int sysctl_igmp_default_version(SYSCTL_HANDLER_ARGS); |
| //baoygstatic int sysctl_igmp_gsr(SYSCTL_HANDLER_ARGS); |
| //baoygstatic int sysctl_igmp_ifinfo(SYSCTL_HANDLER_ARGS); |
| |
| static const struct netisr_handler igmp_nh = { |
| .nh_name = "igmp", |
| .nh_handler = igmp_intr, |
| .nh_proto = NETISR_IGMP, |
| .nh_policy = NETISR_POLICY_SOURCE, |
| }; |
| |
| /* |
| * System-wide globals. |
| * |
| * Unlocked access to these is OK, except for the global IGMP output |
| * queue. The IGMP subsystem lock ends up being system-wide for the moment, |
| * because all VIMAGEs have to share a global output queue, as netisrs |
| * themselves are not virtualized. |
| * |
| * Locking: |
| * * The permitted lock order is: IN_MULTI_LOCK, IGMP_LOCK, IF_ADDR_LOCK. |
| * Any may be taken independently; if any are held at the same |
| * time, the above lock order must be followed. |
| * * All output is delegated to the netisr. |
| * Now that Giant has been eliminated, the netisr may be inlined. |
| * * IN_MULTI_LOCK covers in_multi. |
| * * IGMP_LOCK covers igmp_ifinfo and any global variables in this file, |
| * including the output queue. |
| * * IF_ADDR_LOCK covers if_multiaddrs, which is used for a variety of |
| * per-link state iterators. |
| * * igmp_ifinfo is valid as long as PF_INET is attached to the interface, |
| * therefore it is not refcounted. |
| * We allow unlocked reads of igmp_ifinfo when accessed via in_multi. |
| * |
| * Reference counting |
| * * IGMP acquires its own reference every time an in_multi is passed to |
| * it and the group is being joined for the first time. |
| * * IGMP releases its reference(s) on in_multi in a deferred way, |
| * because the operations which process the release run as part of |
| * a loop whose control variables are directly affected by the release |
| * (that, and not recursing on the IF_ADDR_LOCK). |
| * |
| * VIMAGE: Each in_multi corresponds to an ifp, and each ifp corresponds |
| * to a vnet in ifp->if_vnet. |
| * |
| * SMPng: XXX We may potentially race operations on ifma_protospec. |
| * The problem is that we currently lack a clean way of taking the |
| * IF_ADDR_LOCK() between the ifnet and in layers w/o recursing, |
| * as anything which modifies ifma needs to be covered by that lock. |
| * So check for ifma_protospec being NULL before proceeding. |
| */ |
| struct mtx igmp_mtx; |
| |
| struct mbuf *m_raopt; /* Router Alert option */ |
| MALLOC_DEFINE(M_IGMP, "igmp", "igmp state"); |
| |
| /* |
| * VIMAGE-wide globals. |
| * |
| * The IGMPv3 timers themselves need to run per-image, however, |
| * protosw timers run globally (see tcp). |
| * An ifnet can only be in one vimage at a time, and the loopback |
| * ifnet, loif, is itself virtualized. |
| * It would otherwise be possible to seriously hose IGMP state, |
| * and create inconsistencies in upstream multicast routing, if you have |
| * multiple VIMAGEs running on the same link joining different multicast |
| * groups, UNLESS the "primary IP address" is different. This is because |
| * IGMP for IPv4 does not force link-local addresses to be used for each |
| * node, unlike MLD for IPv6. |
| * Obviously the IGMPv3 per-interface state has per-vimage granularity |
| * also as a result. |
| * |
| * FUTURE: Stop using IFP_TO_IA/INADDR_ANY, and use source address selection |
| * policy to control the address used by IGMP on the link. |
| */ |
| static VNET_DEFINE(int, interface_timers_running); /* IGMPv3 general |
| * query response */ |
| static VNET_DEFINE(int, state_change_timers_running); /* IGMPv3 state-change |
| * retransmit */ |
| static VNET_DEFINE(int, current_state_timers_running); /* IGMPv1/v2 host |
| * report; IGMPv3 g/sg |
| * query response */ |
| |
| #define V_interface_timers_running VNET(interface_timers_running) |
| #define V_state_change_timers_running VNET(state_change_timers_running) |
| #define V_current_state_timers_running VNET(current_state_timers_running) |
| |
| static VNET_DEFINE(LIST_HEAD(, igmp_ifinfo), igi_head); |
| static VNET_DEFINE(struct igmpstat, igmpstat) = { |
| .igps_version = IGPS_VERSION_3, |
| .igps_len = sizeof(struct igmpstat), |
| }; |
| //static VNET_DEFINE(struct timeval, igmp_gsrdelay) = {10, 0}; |
| |
| #define V_igi_head VNET(igi_head) |
| #define V_igmpstat VNET(igmpstat) |
| #define V_igmp_gsrdelay VNET(igmp_gsrdelay) |
| |
| static VNET_DEFINE(int, igmp_recvifkludge) = 1; |
| static VNET_DEFINE(int, igmp_sendra) = 1; |
| static VNET_DEFINE(int, igmp_sendlocal) = 1; |
| static VNET_DEFINE(int, igmp_v1enable) = 1; |
| static VNET_DEFINE(int, igmp_v2enable) = 1; |
| static VNET_DEFINE(int, igmp_legacysupp); |
| static VNET_DEFINE(int, igmp_default_version) = IGMP_VERSION_3; |
| |
| #define V_igmp_recvifkludge VNET(igmp_recvifkludge) |
| #define V_igmp_sendra VNET(igmp_sendra) |
| #define V_igmp_sendlocal VNET(igmp_sendlocal) |
| #define V_igmp_v1enable VNET(igmp_v1enable) |
| #define V_igmp_v2enable VNET(igmp_v2enable) |
| #define V_igmp_legacysupp VNET(igmp_legacysupp) |
| #define V_igmp_default_version VNET(igmp_default_version) |
| |
| /* |
| * Virtualized sysctls. |
| */ |
| /* |
| SYSCTL_VNET_STRUCT(_net_inet_igmp, IGMPCTL_STATS, stats, CTLFLAG_RW, |
| &VNET_NAME(igmpstat), igmpstat, ""); |
| SYSCTL_VNET_INT(_net_inet_igmp, OID_AUTO, recvifkludge, CTLFLAG_RW, |
| &VNET_NAME(igmp_recvifkludge), 0, |
| "Rewrite IGMPv1/v2 reports from 0.0.0.0 to contain subnet address"); |
| SYSCTL_VNET_INT(_net_inet_igmp, OID_AUTO, sendra, CTLFLAG_RW, |
| &VNET_NAME(igmp_sendra), 0, |
| "Send IP Router Alert option in IGMPv2/v3 messages"); |
| SYSCTL_VNET_INT(_net_inet_igmp, OID_AUTO, sendlocal, CTLFLAG_RW, |
| &VNET_NAME(igmp_sendlocal), 0, |
| "Send IGMP membership reports for 224.0.0.0/24 groups"); |
| SYSCTL_VNET_INT(_net_inet_igmp, OID_AUTO, v1enable, CTLFLAG_RW, |
| &VNET_NAME(igmp_v1enable), 0, |
| "Enable backwards compatibility with IGMPv1"); |
| SYSCTL_VNET_INT(_net_inet_igmp, OID_AUTO, v2enable, CTLFLAG_RW, |
| &VNET_NAME(igmp_v2enable), 0, |
| "Enable backwards compatibility with IGMPv2"); |
| SYSCTL_VNET_INT(_net_inet_igmp, OID_AUTO, legacysupp, CTLFLAG_RW, |
| &VNET_NAME(igmp_legacysupp), 0, |
| "Allow v1/v2 reports to suppress v3 group responses"); |
| SYSCTL_VNET_PROC(_net_inet_igmp, OID_AUTO, default_version, |
| CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, |
| &VNET_NAME(igmp_default_version), 0, sysctl_igmp_default_version, "I", |
| "Default version of IGMP to run on each interface"); |
| SYSCTL_VNET_PROC(_net_inet_igmp, OID_AUTO, gsrdelay, |
| CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, |
| &VNET_NAME(igmp_gsrdelay.tv_sec), 0, sysctl_igmp_gsr, "I", |
| "Rate limit for IGMPv3 Group-and-Source queries in seconds"); |
| */ |
| /* |
| * Non-virtualized sysctls. |
| */ |
| /* |
| SYSCTL_NODE(_net_inet_igmp, OID_AUTO, ifinfo, CTLFLAG_RD | CTLFLAG_MPSAFE, |
| sysctl_igmp_ifinfo, "Per-interface IGMPv3 state"); |
| */ |
| |
| static __inline void |
| igmp_save_context(struct mbuf *m, struct ifnet *ifp) |
| { |
| |
| #ifdef VIMAGE |
| m->m_pkthdr.header = ifp->if_vnet; |
| #endif /* VIMAGE */ |
| m->m_pkthdr.flowid = ifp->if_index; |
| } |
| |
| static __inline void |
| igmp_scrub_context(struct mbuf *m) |
| { |
| |
| m->m_pkthdr.header = NULL; |
| m->m_pkthdr.flowid = 0; |
| } |
| |
| #ifdef KTR |
| static __inline char * |
| inet_ntoa_haddr(in_addr_t haddr) |
| { |
| struct in_addr ia; |
| |
| ia.s_addr = htonl(haddr); |
| return (inet_ntoa(ia)); |
| } |
| #endif |
| |
| /* |
| * Restore context from a queued IGMP output chain. |
| * Return saved ifindex. |
| * |
| * VIMAGE: The assertion is there to make sure that we |
| * actually called CURVNET_SET() with what's in the mbuf chain. |
| */ |
| static __inline uint32_t |
| igmp_restore_context(struct mbuf *m) |
| { |
| |
| #ifdef notyet |
| #if defined(VIMAGE) && defined(INVARIANTS) |
| KASSERT(curvnet == (m->m_pkthdr.header), |
| ("%s: called when curvnet was not restored", __func__)); |
| #endif |
| #endif |
| return (m->m_pkthdr.flowid); |
| } |
| |
| /* |
| * Retrieve or set default IGMP version. |
| * |
| * VIMAGE: Assume curvnet set by caller. |
| * SMPng: NOTE: Serialized by IGMP lock. |
| */ |
| /* |
| static int |
| sysctl_igmp_default_version(SYSCTL_HANDLER_ARGS) |
| { |
| int error; |
| int new; |
| |
| error = sysctl_wire_old_buffer(req, sizeof(int)); |
| if (error) |
| return (error); |
| |
| IGMP_LOCK(); |
| |
| new = V_igmp_default_version; |
| |
| error = sysctl_handle_int(oidp, &new, 0, req); |
| if (error || !req->newptr) |
| goto out_locked; |
| |
| if (new < IGMP_VERSION_1 || new > IGMP_VERSION_3) { |
| error = EINVAL; |
| goto out_locked; |
| } |
| |
| CTR2(KTR_IGMPV3, "change igmp_default_version from %d to %d", |
| V_igmp_default_version, new); |
| |
| V_igmp_default_version = new; |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| return (error); |
| } |
| */ |
| |
| /* |
| * Retrieve or set threshold between group-source queries in seconds. |
| * |
| * VIMAGE: Assume curvnet set by caller. |
| * SMPng: NOTE: Serialized by IGMP lock. |
| */ |
| /* |
| static int |
| sysctl_igmp_gsr(SYSCTL_HANDLER_ARGS) |
| { |
| int error; |
| int i; |
| |
| error = sysctl_wire_old_buffer(req, sizeof(int)); |
| if (error) |
| return (error); |
| |
| IGMP_LOCK(); |
| |
| i = V_igmp_gsrdelay.tv_sec; |
| |
| error = sysctl_handle_int(oidp, &i, 0, req); |
| if (error || !req->newptr) |
| goto out_locked; |
| |
| if (i < -1 || i >= 60) { |
| error = EINVAL; |
| goto out_locked; |
| } |
| |
| CTR2(KTR_IGMPV3, "change igmp_gsrdelay from %d to %d", |
| V_igmp_gsrdelay.tv_sec, i); |
| V_igmp_gsrdelay.tv_sec = i; |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| return (error); |
| } |
| */ |
| /* |
| * Expose struct igmp_ifinfo to userland, keyed by ifindex. |
| * For use by ifmcstat(8). |
| * |
| * SMPng: NOTE: Does an unlocked ifindex space read. |
| * VIMAGE: Assume curvnet set by caller. The node handler itself |
| * is not directly virtualized. |
| */ |
| /* |
| static int |
| sysctl_igmp_ifinfo(SYSCTL_HANDLER_ARGS) |
| { |
| int *name; |
| int error; |
| u_int namelen; |
| struct ifnet *ifp; |
| struct igmp_ifinfo *igi; |
| |
| name = (int *)arg1; |
| namelen = arg2; |
| |
| if (req->newptr != NULL) |
| return (EPERM); |
| |
| if (namelen != 1) |
| return (EINVAL); |
| |
| error = sysctl_wire_old_buffer(req, sizeof(struct igmp_ifinfo)); |
| if (error) |
| return (error); |
| |
| IN_MULTI_LOCK(); |
| IGMP_LOCK(); |
| |
| if (name[0] <= 0 || name[0] > V_if_index) { |
| error = ENOENT; |
| goto out_locked; |
| } |
| |
| error = ENOENT; |
| |
| ifp = ifnet_byindex(name[0]); |
| if (ifp == NULL) |
| goto out_locked; |
| |
| LIST_FOREACH(igi, &V_igi_head, igi_link) { |
| if (ifp == igi->igi_ifp) { |
| error = SYSCTL_OUT(req, igi, |
| sizeof(struct igmp_ifinfo)); |
| break; |
| } |
| } |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| IN_MULTI_UNLOCK(); |
| return (error); |
| } |
| */ |
| /* |
| * Dispatch an entire queue of pending packet chains |
| * using the netisr. |
| * VIMAGE: Assumes the vnet pointer has been set. |
| */ |
| static void |
| igmp_dispatch_queue(struct ifqueue *ifq, int limit, const int loop) |
| { |
| struct mbuf *m; |
| |
| for (;;) { |
| _IF_DEQUEUE(ifq, m); |
| if (m == NULL) |
| break; |
| CTR3(KTR_IGMPV3, "%s: dispatch %p from %p", __func__, ifq, m); |
| if (loop) |
| m->m_flags |= M_IGMP_LOOP; |
| //netisr_dispatch(NETISR_IGMP, m); |
| if (--limit == 0) |
| break; |
| } |
| } |
| |
| /* |
| * Filter outgoing IGMP report state by group. |
| * |
| * Reports are ALWAYS suppressed for ALL-HOSTS (224.0.0.1). |
| * If the net.inet.igmp.sendlocal sysctl is 0, then IGMP reports are |
| * disabled for all groups in the 224.0.0.0/24 link-local scope. However, |
| * this may break certain IGMP snooping switches which rely on the old |
| * report behaviour. |
| * |
| * Return zero if the given group is one for which IGMP reports |
| * should be suppressed, or non-zero if reports should be issued. |
| */ |
| static __inline int |
| igmp_isgroupreported(const struct in_addr addr) |
| { |
| |
| if (in_allhosts(addr) || |
| ((!V_igmp_sendlocal && IN_LOCAL_GROUP(ntohl(addr.s_addr))))) |
| return (0); |
| |
| return (1); |
| } |
| |
| /* |
| * Construct a Router Alert option to use in outgoing packets. |
| */ |
| static struct mbuf * |
| igmp_ra_alloc(void) |
| { |
| struct mbuf *m; |
| struct ipoption *p; |
| |
| MGET(m, M_DONTWAIT, MT_DATA); |
| p = mtod(m, struct ipoption *); |
| p->ipopt_dst.s_addr = INADDR_ANY; |
| p->ipopt_list[0] = IPOPT_RA; /* Router Alert Option */ |
| p->ipopt_list[1] = 0x04; /* 4 bytes long */ |
| p->ipopt_list[2] = IPOPT_EOL; /* End of IP option list */ |
| p->ipopt_list[3] = 0x00; /* pad byte */ |
| m->m_len = sizeof(p->ipopt_dst) + p->ipopt_list[1]; |
| |
| return (m); |
| } |
| |
| /* |
| * Attach IGMP when PF_INET is attached to an interface. |
| */ |
| struct igmp_ifinfo * |
| igmp_domifattach(struct ifnet *ifp) |
| { |
| struct igmp_ifinfo *igi; |
| |
| CTR3(KTR_IGMPV3, "%s: called for ifp %p(%s)", |
| __func__, ifp, ifp->if_xname); |
| |
| IGMP_LOCK(); |
| |
| igi = igi_alloc_locked(ifp); |
| if (!(ifp->if_flags & IFF_MULTICAST)) |
| igi->igi_flags |= IGIF_SILENT; |
| |
| IGMP_UNLOCK(); |
| |
| return (igi); |
| } |
| |
| /* |
| * VIMAGE: assume curvnet set by caller. |
| */ |
| static struct igmp_ifinfo * |
| igi_alloc_locked(/*const*/ struct ifnet *ifp) |
| { |
| struct igmp_ifinfo *igi; |
| |
| IGMP_LOCK_ASSERT(); |
| |
| igi = bsd_malloc(sizeof(struct igmp_ifinfo), M_IGMP, M_NOWAIT|M_ZERO); |
| if (igi == NULL) |
| goto out; |
| |
| igi->igi_ifp = ifp; |
| igi->igi_version = V_igmp_default_version; |
| igi->igi_flags = 0; |
| igi->igi_rv = IGMP_RV_INIT; |
| igi->igi_qi = IGMP_QI_INIT; |
| igi->igi_qri = IGMP_QRI_INIT; |
| igi->igi_uri = IGMP_URI_INIT; |
| |
| SLIST_INIT(&igi->igi_relinmhead); |
| |
| /* |
| * Responses to general queries are subject to bounds. |
| */ |
| IFQ_SET_MAXLEN(&igi->igi_gq, IGMP_MAX_RESPONSE_PACKETS); |
| |
| LIST_INSERT_HEAD(&V_igi_head, igi, igi_link); |
| |
| CTR2(KTR_IGMPV3, "allocate igmp_ifinfo for ifp %p(%s)", |
| ifp, ifp->if_xname); |
| |
| out: |
| return (igi); |
| } |
| |
| /* |
| * Hook for ifdetach. |
| * |
| * NOTE: Some finalization tasks need to run before the protocol domain |
| * is detached, but also before the link layer does its cleanup. |
| * |
| * SMPNG: igmp_ifdetach() needs to take IF_ADDR_LOCK(). |
| * XXX This is also bitten by unlocked ifma_protospec access. |
| */ |
| void |
| igmp_ifdetach(struct ifnet *ifp) |
| { |
| struct igmp_ifinfo *igi; |
| struct ifmultiaddr *ifma; |
| struct in_multi *inm, *tinm; |
| |
| CTR3(KTR_IGMPV3, "%s: called for ifp %p(%s)", __func__, ifp, |
| ifp->if_xname); |
| |
| IGMP_LOCK(); |
| |
| igi = ((struct in_ifinfo *)ifp->if_afdata[AF_INET])->ii_igmp; |
| if (igi->igi_version == IGMP_VERSION_3) { |
| IF_ADDR_LOCK(ifp); |
| TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { |
| if (ifma->ifma_addr->sa_family != AF_INET || |
| ifma->ifma_protospec == NULL) |
| continue; |
| #if 0 |
| KASSERT(ifma->ifma_protospec != NULL, |
| ("%s: ifma_protospec is NULL", __func__)); |
| #endif |
| inm = (struct in_multi *)ifma->ifma_protospec; |
| if (inm->inm_state == IGMP_LEAVING_MEMBER) { |
| SLIST_INSERT_HEAD(&igi->igi_relinmhead, |
| inm, inm_nrele); |
| } |
| inm_clear_recorded(inm); |
| } |
| IF_ADDR_UNLOCK(ifp); |
| /* |
| * Free the in_multi reference(s) for this IGMP lifecycle. |
| */ |
| SLIST_FOREACH_SAFE(inm, &igi->igi_relinmhead, inm_nrele, |
| tinm) { |
| SLIST_REMOVE_HEAD(&igi->igi_relinmhead, inm_nrele); |
| inm_release_locked(inm); |
| } |
| } |
| |
| IGMP_UNLOCK(); |
| } |
| |
| /* |
| * Hook for domifdetach. |
| */ |
| void |
| igmp_domifdetach(struct ifnet *ifp) |
| { |
| struct igmp_ifinfo *igi; |
| |
| CTR3(KTR_IGMPV3, "%s: called for ifp %p(%s)", |
| __func__, ifp, ifp->if_xname); |
| |
| IGMP_LOCK(); |
| |
| igi = ((struct in_ifinfo *)ifp->if_afdata[AF_INET])->ii_igmp; |
| igi_delete_locked(ifp); |
| |
| IGMP_UNLOCK(); |
| } |
| |
| static void |
| igi_delete_locked(const struct ifnet *ifp) |
| { |
| struct igmp_ifinfo *igi, *tigi; |
| |
| CTR3(KTR_IGMPV3, "%s: freeing igmp_ifinfo for ifp %p(%s)", |
| __func__, ifp, ifp->if_xname); |
| |
| IGMP_LOCK_ASSERT(); |
| |
| LIST_FOREACH_SAFE(igi, &V_igi_head, igi_link, tigi) { |
| if (igi->igi_ifp == ifp) { |
| /* |
| * Free deferred General Query responses. |
| */ |
| _IF_DRAIN(&igi->igi_gq); |
| |
| LIST_REMOVE(igi, igi_link); |
| |
| KASSERT(SLIST_EMPTY(&igi->igi_relinmhead), |
| ("%s: there are dangling in_multi references", |
| __func__)); |
| |
| bsd_free(igi, M_IGMP); |
| return; |
| } |
| } |
| |
| #ifdef INVARIANTS |
| panic("%s: igmp_ifinfo not found for ifp %p\n", __func__, ifp); |
| #endif |
| } |
| |
| /* |
| * Process a received IGMPv1 query. |
| * Return non-zero if the message should be dropped. |
| * |
| * VIMAGE: The curvnet pointer is derived from the input ifp. |
| */ |
| static int |
| igmp_input_v1_query(struct ifnet *ifp, const struct ip *ip, |
| const struct igmp *igmp) |
| { |
| struct ifmultiaddr *ifma; |
| struct igmp_ifinfo *igi; |
| struct in_multi *inm; |
| |
| /* |
| * IGMPv1 Host Mmembership Queries SHOULD always be addressed to |
| * 224.0.0.1. They are always treated as General Queries. |
| * igmp_group is always ignored. Do not drop it as a userland |
| * daemon may wish to see it. |
| * XXX SMPng: unlocked increments in igmpstat assumed atomic. |
| */ |
| if (!in_allhosts(ip->ip_dst) || !in_nullhost(igmp->igmp_group)) { |
| IGMPSTAT_INC(igps_rcv_badqueries); |
| return (0); |
| } |
| IGMPSTAT_INC(igps_rcv_gen_queries); |
| |
| IN_MULTI_LOCK(); |
| IGMP_LOCK(); |
| |
| igi = ((struct in_ifinfo *)ifp->if_afdata[AF_INET])->ii_igmp; |
| KASSERT(igi != NULL, ("%s: no igmp_ifinfo for ifp %p", __func__, ifp)); |
| |
| if (igi->igi_flags & IGIF_LOOPBACK) { |
| CTR2(KTR_IGMPV3, "ignore v1 query on IGIF_LOOPBACK ifp %p(%s)", |
| ifp, ifp->if_xname); |
| goto out_locked; |
| } |
| |
| /* |
| * Switch to IGMPv1 host compatibility mode. |
| */ |
| igmp_set_version(igi, IGMP_VERSION_1); |
| |
| CTR2(KTR_IGMPV3, "process v1 query on ifp %p(%s)", ifp, ifp->if_xname); |
| |
| /* |
| * Start the timers in all of our group records |
| * for the interface on which the query arrived, |
| * except those which are already running. |
| */ |
| IF_ADDR_LOCK(ifp); |
| TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { |
| if (ifma->ifma_addr->sa_family != AF_INET || |
| ifma->ifma_protospec == NULL) |
| continue; |
| inm = (struct in_multi *)ifma->ifma_protospec; |
| if (inm->inm_timer != 0) |
| continue; |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| break; |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| case IGMP_REPORTING_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| inm->inm_state = IGMP_REPORTING_MEMBER; |
| inm->inm_timer = IGMP_RANDOM_DELAY( |
| IGMP_V1V2_MAX_RI * PR_FASTHZ); |
| V_current_state_timers_running = 1; |
| break; |
| case IGMP_LEAVING_MEMBER: |
| break; |
| } |
| } |
| IF_ADDR_UNLOCK(ifp); |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| IN_MULTI_UNLOCK(); |
| |
| return (0); |
| } |
| |
| /* |
| * Process a received IGMPv2 general or group-specific query. |
| */ |
| static int |
| igmp_input_v2_query(struct ifnet *ifp, const struct ip *ip, |
| const struct igmp *igmp) |
| { |
| struct ifmultiaddr *ifma; |
| struct igmp_ifinfo *igi; |
| struct in_multi *inm; |
| int is_general_query; |
| uint16_t timer; |
| |
| is_general_query = 0; |
| |
| /* |
| * Validate address fields upfront. |
| * XXX SMPng: unlocked increments in igmpstat assumed atomic. |
| */ |
| if (in_nullhost(igmp->igmp_group)) { |
| /* |
| * IGMPv2 General Query. |
| * If this was not sent to the all-hosts group, ignore it. |
| */ |
| if (!in_allhosts(ip->ip_dst)) |
| return (0); |
| IGMPSTAT_INC(igps_rcv_gen_queries); |
| is_general_query = 1; |
| } else { |
| /* IGMPv2 Group-Specific Query. */ |
| IGMPSTAT_INC(igps_rcv_group_queries); |
| } |
| |
| IN_MULTI_LOCK(); |
| IGMP_LOCK(); |
| |
| igi = ((struct in_ifinfo *)ifp->if_afdata[AF_INET])->ii_igmp; |
| KASSERT(igi != NULL, ("%s: no igmp_ifinfo for ifp %p", __func__, ifp)); |
| |
| if (igi->igi_flags & IGIF_LOOPBACK) { |
| CTR2(KTR_IGMPV3, "ignore v2 query on IGIF_LOOPBACK ifp %p(%s)", |
| ifp, ifp->if_xname); |
| goto out_locked; |
| } |
| |
| /* |
| * Ignore v2 query if in v1 Compatibility Mode. |
| */ |
| if (igi->igi_version == IGMP_VERSION_1) |
| goto out_locked; |
| |
| igmp_set_version(igi, IGMP_VERSION_2); |
| |
| timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE; |
| if (timer == 0) |
| timer = 1; |
| |
| if (is_general_query) { |
| /* |
| * For each reporting group joined on this |
| * interface, kick the report timer. |
| */ |
| CTR2(KTR_IGMPV3, "process v2 general query on ifp %p(%s)", |
| ifp, ifp->if_xname); |
| IF_ADDR_LOCK(ifp); |
| TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { |
| if (ifma->ifma_addr->sa_family != AF_INET || |
| ifma->ifma_protospec == NULL) |
| continue; |
| inm = (struct in_multi *)ifma->ifma_protospec; |
| igmp_v2_update_group(inm, timer); |
| } |
| IF_ADDR_UNLOCK(ifp); |
| } else { |
| /* |
| * Group-specific IGMPv2 query, we need only |
| * look up the single group to process it. |
| */ |
| inm = inm_lookup(ifp, igmp->igmp_group); |
| if (inm != NULL) { |
| CTR3(KTR_IGMPV3, "process v2 query %s on ifp %p(%s)", |
| inet_ntoa(igmp->igmp_group), ifp, ifp->if_xname); |
| igmp_v2_update_group(inm, timer); |
| } |
| } |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| IN_MULTI_UNLOCK(); |
| |
| return (0); |
| } |
| |
| /* |
| * Update the report timer on a group in response to an IGMPv2 query. |
| * |
| * If we are becoming the reporting member for this group, start the timer. |
| * If we already are the reporting member for this group, and timer is |
| * below the threshold, reset it. |
| * |
| * We may be updating the group for the first time since we switched |
| * to IGMPv3. If we are, then we must clear any recorded source lists, |
| * and transition to REPORTING state; the group timer is overloaded |
| * for group and group-source query responses. |
| * |
| * Unlike IGMPv3, the delay per group should be jittered |
| * to avoid bursts of IGMPv2 reports. |
| */ |
| static void |
| igmp_v2_update_group(struct in_multi *inm, const int timer) |
| { |
| |
| CTR4(KTR_IGMPV3, "%s: %s/%s timer=%d", __func__, |
| inet_ntoa(inm->inm_addr), inm->inm_ifp->if_xname, timer); |
| |
| IN_MULTI_LOCK_ASSERT(); |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| break; |
| case IGMP_REPORTING_MEMBER: |
| if (inm->inm_timer != 0 && |
| inm->inm_timer <= timer) { |
| CTR1(KTR_IGMPV3, "%s: REPORTING and timer running, " |
| "skipping.", __func__); |
| break; |
| } |
| /* FALLTHROUGH */ |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| CTR1(KTR_IGMPV3, "%s: ->REPORTING", __func__); |
| inm->inm_state = IGMP_REPORTING_MEMBER; |
| inm->inm_timer = IGMP_RANDOM_DELAY(timer); |
| V_current_state_timers_running = 1; |
| break; |
| case IGMP_SLEEPING_MEMBER: |
| CTR1(KTR_IGMPV3, "%s: ->AWAKENING", __func__); |
| inm->inm_state = IGMP_AWAKENING_MEMBER; |
| break; |
| case IGMP_LEAVING_MEMBER: |
| break; |
| } |
| } |
| |
| /* |
| * Process a received IGMPv3 general, group-specific or |
| * group-and-source-specific query. |
| * Assumes m has already been pulled up to the full IGMP message length. |
| * Return 0 if successful, otherwise an appropriate error code is returned. |
| */ |
| static int |
| igmp_input_v3_query(struct ifnet *ifp, const struct ip *ip, |
| /*const*/ struct igmpv3 *igmpv3) |
| { |
| struct igmp_ifinfo *igi; |
| struct in_multi *inm; |
| int is_general_query; |
| uint32_t maxresp, nsrc, qqi; |
| uint16_t timer; |
| uint8_t qrv; |
| |
| is_general_query = 0; |
| |
| CTR2(KTR_IGMPV3, "process v3 query on ifp %p(%s)", ifp, ifp->if_xname); |
| |
| maxresp = igmpv3->igmp_code; /* in 1/10ths of a second */ |
| if (maxresp >= 128) { |
| maxresp = IGMP_MANT(igmpv3->igmp_code) << |
| (IGMP_EXP(igmpv3->igmp_code) + 3); |
| } |
| |
| /* |
| * Robustness must never be less than 2 for on-wire IGMPv3. |
| * FUTURE: Check if ifp has IGIF_LOOPBACK set, as we will make |
| * an exception for interfaces whose IGMPv3 state changes |
| * are redirected to loopback (e.g. MANET). |
| */ |
| qrv = IGMP_QRV(igmpv3->igmp_misc); |
| if (qrv < 2) { |
| CTR3(KTR_IGMPV3, "%s: clamping qrv %d to %d", __func__, |
| qrv, IGMP_RV_INIT); |
| qrv = IGMP_RV_INIT; |
| } |
| |
| qqi = igmpv3->igmp_qqi; |
| if (qqi >= 128) { |
| qqi = IGMP_MANT(igmpv3->igmp_qqi) << |
| (IGMP_EXP(igmpv3->igmp_qqi) + 3); |
| } |
| |
| timer = maxresp * PR_FASTHZ / IGMP_TIMER_SCALE; |
| if (timer == 0) |
| timer = 1; |
| |
| nsrc = ntohs(igmpv3->igmp_numsrc); |
| |
| /* |
| * Validate address fields and versions upfront before |
| * accepting v3 query. |
| * XXX SMPng: Unlocked access to igmpstat counters here. |
| */ |
| if (in_nullhost(igmpv3->igmp_group)) { |
| /* |
| * IGMPv3 General Query. |
| * |
| * General Queries SHOULD be directed to 224.0.0.1. |
| * A general query with a source list has undefined |
| * behaviour; discard it. |
| */ |
| IGMPSTAT_INC(igps_rcv_gen_queries); |
| if (!in_allhosts(ip->ip_dst) || nsrc > 0) { |
| IGMPSTAT_INC(igps_rcv_badqueries); |
| return (0); |
| } |
| is_general_query = 1; |
| } else { |
| /* Group or group-source specific query. */ |
| if (nsrc == 0) |
| IGMPSTAT_INC(igps_rcv_group_queries); |
| else |
| IGMPSTAT_INC(igps_rcv_gsr_queries); |
| } |
| |
| IN_MULTI_LOCK(); |
| IGMP_LOCK(); |
| |
| igi = ((struct in_ifinfo *)ifp->if_afdata[AF_INET])->ii_igmp; |
| KASSERT(igi != NULL, ("%s: no igmp_ifinfo for ifp %p", __func__, ifp)); |
| |
| if (igi->igi_flags & IGIF_LOOPBACK) { |
| CTR2(KTR_IGMPV3, "ignore v3 query on IGIF_LOOPBACK ifp %p(%s)", |
| ifp, ifp->if_xname); |
| goto out_locked; |
| } |
| |
| /* |
| * Discard the v3 query if we're in Compatibility Mode. |
| * The RFC is not obviously worded that hosts need to stay in |
| * compatibility mode until the Old Version Querier Present |
| * timer expires. |
| */ |
| if (igi->igi_version != IGMP_VERSION_3) { |
| CTR3(KTR_IGMPV3, "ignore v3 query in v%d mode on ifp %p(%s)", |
| igi->igi_version, ifp, ifp->if_xname); |
| goto out_locked; |
| } |
| |
| igmp_set_version(igi, IGMP_VERSION_3); |
| igi->igi_rv = qrv; |
| igi->igi_qi = qqi; |
| igi->igi_qri = maxresp; |
| |
| CTR4(KTR_IGMPV3, "%s: qrv %d qi %d qri %d", __func__, qrv, qqi, |
| maxresp); |
| |
| if (is_general_query) { |
| /* |
| * Schedule a current-state report on this ifp for |
| * all groups, possibly containing source lists. |
| * If there is a pending General Query response |
| * scheduled earlier than the selected delay, do |
| * not schedule any other reports. |
| * Otherwise, reset the interface timer. |
| */ |
| CTR2(KTR_IGMPV3, "process v3 general query on ifp %p(%s)", |
| ifp, ifp->if_xname); |
| if (igi->igi_v3_timer == 0 || igi->igi_v3_timer >= timer) { |
| igi->igi_v3_timer = IGMP_RANDOM_DELAY(timer); |
| V_interface_timers_running = 1; |
| } |
| } else { |
| /* |
| * Group-source-specific queries are throttled on |
| * a per-group basis to defeat denial-of-service attempts. |
| * Queries for groups we are not a member of on this |
| * link are simply ignored. |
| */ |
| inm = inm_lookup(ifp, igmpv3->igmp_group); |
| if (inm == NULL) |
| goto out_locked; |
| if (nsrc > 0) { |
| /* if (!ratecheck(&inm->inm_lastgsrtv, |
| &V_igmp_gsrdelay)) { |
| CTR1(KTR_IGMPV3, "%s: GS query throttled.", |
| __func__); |
| IGMPSTAT_INC(igps_drop_gsr_queries); |
| goto out_locked; |
| }*/ |
| } |
| CTR3(KTR_IGMPV3, "process v3 %s query on ifp %p(%s)", |
| inet_ntoa(igmpv3->igmp_group), ifp, ifp->if_xname); |
| /* |
| * If there is a pending General Query response |
| * scheduled sooner than the selected delay, no |
| * further report need be scheduled. |
| * Otherwise, prepare to respond to the |
| * group-specific or group-and-source query. |
| */ |
| if (igi->igi_v3_timer == 0 || igi->igi_v3_timer >= timer) |
| igmp_input_v3_group_query(inm, igi, timer, igmpv3); |
| } |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| IN_MULTI_UNLOCK(); |
| |
| return (0); |
| } |
| |
| /* |
| * Process a recieved IGMPv3 group-specific or group-and-source-specific |
| * query. |
| * Return <0 if any error occured. Currently this is ignored. |
| */ |
| static int |
| igmp_input_v3_group_query(struct in_multi *inm, struct igmp_ifinfo *igi, |
| int timer, /*const*/ struct igmpv3 *igmpv3) |
| { |
| int retval; |
| uint16_t nsrc; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| retval = 0; |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_LEAVING_MEMBER: |
| return (retval); |
| break; |
| case IGMP_REPORTING_MEMBER: |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| break; |
| } |
| |
| nsrc = ntohs(igmpv3->igmp_numsrc); |
| |
| /* |
| * Deal with group-specific queries upfront. |
| * If any group query is already pending, purge any recorded |
| * source-list state if it exists, and schedule a query response |
| * for this group-specific query. |
| */ |
| if (nsrc == 0) { |
| if (inm->inm_state == IGMP_G_QUERY_PENDING_MEMBER || |
| inm->inm_state == IGMP_SG_QUERY_PENDING_MEMBER) { |
| inm_clear_recorded(inm); |
| timer = min(inm->inm_timer, timer); |
| } |
| inm->inm_state = IGMP_G_QUERY_PENDING_MEMBER; |
| inm->inm_timer = IGMP_RANDOM_DELAY(timer); |
| V_current_state_timers_running = 1; |
| return (retval); |
| } |
| |
| /* |
| * Deal with the case where a group-and-source-specific query has |
| * been received but a group-specific query is already pending. |
| */ |
| if (inm->inm_state == IGMP_G_QUERY_PENDING_MEMBER) { |
| timer = min(inm->inm_timer, timer); |
| inm->inm_timer = IGMP_RANDOM_DELAY(timer); |
| V_current_state_timers_running = 1; |
| return (retval); |
| } |
| |
| /* |
| * Finally, deal with the case where a group-and-source-specific |
| * query has been received, where a response to a previous g-s-r |
| * query exists, or none exists. |
| * In this case, we need to parse the source-list which the Querier |
| * has provided us with and check if we have any source list filter |
| * entries at T1 for these sources. If we do not, there is no need |
| * schedule a report and the query may be dropped. |
| * If we do, we must record them and schedule a current-state |
| * report for those sources. |
| * FIXME: Handling source lists larger than 1 mbuf requires that |
| * we pass the mbuf chain pointer down to this function, and use |
| * m_getptr() to walk the chain. |
| */ |
| if (inm->inm_nsrc > 0) { |
| const struct in_addr *ap; |
| int i, nrecorded; |
| |
| ap = (const struct in_addr *)(igmpv3 + 1); |
| nrecorded = 0; |
| for (i = 0; i < nsrc; i++, ap++) { |
| retval = inm_record_source(inm, ap->s_addr); |
| if (retval < 0) |
| break; |
| nrecorded += retval; |
| } |
| if (nrecorded > 0) { |
| CTR1(KTR_IGMPV3, |
| "%s: schedule response to SG query", __func__); |
| inm->inm_state = IGMP_SG_QUERY_PENDING_MEMBER; |
| inm->inm_timer = IGMP_RANDOM_DELAY(timer); |
| V_current_state_timers_running = 1; |
| } |
| } |
| |
| return (retval); |
| } |
| |
| /* |
| * Process a received IGMPv1 host membership report. |
| * |
| * NOTE: 0.0.0.0 workaround breaks const correctness. |
| */ |
| static int |
| igmp_input_v1_report(struct ifnet *ifp, /*const*/ struct ip *ip, |
| /*const*/ struct igmp *igmp) |
| { |
| struct in_ifaddr *ia; |
| struct in_multi *inm; |
| |
| IGMPSTAT_INC(igps_rcv_reports); |
| |
| if (ifp->if_flags & IFF_LOOPBACK) |
| return (0); |
| |
| if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr) || |
| !in_hosteq(igmp->igmp_group, ip->ip_dst))) { |
| IGMPSTAT_INC(igps_rcv_badreports); |
| return (EINVAL); |
| } |
| |
| /* |
| * RFC 3376, Section 4.2.13, 9.2, 9.3: |
| * Booting clients may use the source address 0.0.0.0. Some |
| * IGMP daemons may not know how to use IP_RECVIF to determine |
| * the interface upon which this message was received. |
| * Replace 0.0.0.0 with the subnet address if told to do so. |
| */ |
| if (V_igmp_recvifkludge && in_nullhost(ip->ip_src)) { |
| IFP_TO_IA(ifp, ia); |
| if (ia != NULL) { |
| ip->ip_src.s_addr = htonl(ia->ia_subnet); |
| ifa_free(&ia->ia_ifa); |
| } |
| } |
| |
| CTR3(KTR_IGMPV3, "process v1 report %s on ifp %p(%s)", |
| inet_ntoa(igmp->igmp_group), ifp, ifp->if_xname); |
| |
| /* |
| * IGMPv1 report suppression. |
| * If we are a member of this group, and our membership should be |
| * reported, stop our group timer and transition to the 'lazy' state. |
| */ |
| IN_MULTI_LOCK(); |
| inm = inm_lookup(ifp, igmp->igmp_group); |
| if (inm != NULL) { |
| struct igmp_ifinfo *igi; |
| |
| igi = inm->inm_igi; |
| if (igi == NULL) { |
| KASSERT(igi != NULL, |
| ("%s: no igi for ifp %p", __func__, ifp)); |
| goto out_locked; |
| } |
| |
| IGMPSTAT_INC(igps_rcv_ourreports); |
| |
| /* |
| * If we are in IGMPv3 host mode, do not allow the |
| * other host's IGMPv1 report to suppress our reports |
| * unless explicitly configured to do so. |
| */ |
| if (igi->igi_version == IGMP_VERSION_3) { |
| if (V_igmp_legacysupp) |
| igmp_v3_suppress_group_record(inm); |
| goto out_locked; |
| } |
| |
| inm->inm_timer = 0; |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| break; |
| case IGMP_IDLE_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| CTR3(KTR_IGMPV3, |
| "report suppressed for %s on ifp %p(%s)", |
| inet_ntoa(igmp->igmp_group), ifp, ifp->if_xname); |
| case IGMP_SLEEPING_MEMBER: |
| inm->inm_state = IGMP_SLEEPING_MEMBER; |
| break; |
| case IGMP_REPORTING_MEMBER: |
| CTR3(KTR_IGMPV3, |
| "report suppressed for %s on ifp %p(%s)", |
| inet_ntoa(igmp->igmp_group), ifp, ifp->if_xname); |
| if (igi->igi_version == IGMP_VERSION_1) |
| inm->inm_state = IGMP_LAZY_MEMBER; |
| else if (igi->igi_version == IGMP_VERSION_2) |
| inm->inm_state = IGMP_SLEEPING_MEMBER; |
| break; |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| case IGMP_LEAVING_MEMBER: |
| break; |
| } |
| } |
| |
| out_locked: |
| IN_MULTI_UNLOCK(); |
| |
| return (0); |
| } |
| |
| /* |
| * Process a received IGMPv2 host membership report. |
| * |
| * NOTE: 0.0.0.0 workaround breaks const correctness. |
| */ |
| static int |
| igmp_input_v2_report(struct ifnet *ifp, /*const*/ struct ip *ip, |
| /*const*/ struct igmp *igmp) |
| { |
| struct in_ifaddr *ia; |
| struct in_multi *inm; |
| |
| /* |
| * Make sure we don't hear our own membership report. Fast |
| * leave requires knowing that we are the only member of a |
| * group. |
| */ |
| IFP_TO_IA(ifp, ia); |
| if (ia != NULL && in_hosteq(ip->ip_src, IA_SIN(ia)->sin_addr)) { |
| ifa_free(&ia->ia_ifa); |
| return (0); |
| } |
| |
| IGMPSTAT_INC(igps_rcv_reports); |
| |
| if (ifp->if_flags & IFF_LOOPBACK) { |
| if (ia != NULL) |
| ifa_free(&ia->ia_ifa); |
| return (0); |
| } |
| |
| if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr)) || |
| !in_hosteq(igmp->igmp_group, ip->ip_dst)) { |
| if (ia != NULL) |
| ifa_free(&ia->ia_ifa); |
| IGMPSTAT_INC(igps_rcv_badreports); |
| return (EINVAL); |
| } |
| |
| /* |
| * RFC 3376, Section 4.2.13, 9.2, 9.3: |
| * Booting clients may use the source address 0.0.0.0. Some |
| * IGMP daemons may not know how to use IP_RECVIF to determine |
| * the interface upon which this message was received. |
| * Replace 0.0.0.0 with the subnet address if told to do so. |
| */ |
| if (V_igmp_recvifkludge && in_nullhost(ip->ip_src)) { |
| if (ia != NULL) |
| ip->ip_src.s_addr = htonl(ia->ia_subnet); |
| } |
| if (ia != NULL) |
| ifa_free(&ia->ia_ifa); |
| |
| CTR3(KTR_IGMPV3, "process v2 report %s on ifp %p(%s)", |
| inet_ntoa(igmp->igmp_group), ifp, ifp->if_xname); |
| |
| /* |
| * IGMPv2 report suppression. |
| * If we are a member of this group, and our membership should be |
| * reported, and our group timer is pending or about to be reset, |
| * stop our group timer by transitioning to the 'lazy' state. |
| */ |
| IN_MULTI_LOCK(); |
| inm = inm_lookup(ifp, igmp->igmp_group); |
| if (inm != NULL) { |
| struct igmp_ifinfo *igi; |
| |
| igi = inm->inm_igi; |
| KASSERT(igi != NULL, ("%s: no igi for ifp %p", __func__, ifp)); |
| |
| IGMPSTAT_INC(igps_rcv_ourreports); |
| |
| /* |
| * If we are in IGMPv3 host mode, do not allow the |
| * other host's IGMPv1 report to suppress our reports |
| * unless explicitly configured to do so. |
| */ |
| if (igi->igi_version == IGMP_VERSION_3) { |
| if (V_igmp_legacysupp) |
| igmp_v3_suppress_group_record(inm); |
| goto out_locked; |
| } |
| |
| inm->inm_timer = 0; |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| break; |
| case IGMP_REPORTING_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| CTR3(KTR_IGMPV3, |
| "report suppressed for %s on ifp %p(%s)", |
| inet_ntoa(igmp->igmp_group), ifp, ifp->if_xname); |
| case IGMP_LAZY_MEMBER: |
| inm->inm_state = IGMP_LAZY_MEMBER; |
| break; |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| case IGMP_LEAVING_MEMBER: |
| break; |
| } |
| } |
| |
| out_locked: |
| IN_MULTI_UNLOCK(); |
| |
| return (0); |
| } |
| |
| void |
| igmp_input(struct mbuf *m, int off) |
| { |
| int iphlen; |
| struct ifnet *ifp; |
| struct igmp *igmp; |
| struct ip *ip; |
| int igmplen; |
| int minlen; |
| int queryver; |
| |
| CTR3(KTR_IGMPV3, "%s: called w/mbuf (%p,%d)", __func__, m, off); |
| |
| ifp = m->m_pkthdr.rcvif; |
| |
| IGMPSTAT_INC(igps_rcv_total); |
| |
| ip = mtod(m, struct ip *); |
| iphlen = off; |
| igmplen = ip->ip_len; |
| |
| /* |
| * Validate lengths. |
| */ |
| if (igmplen < IGMP_MINLEN) { |
| IGMPSTAT_INC(igps_rcv_tooshort); |
| m_freem(m); |
| return; |
| } |
| |
| /* |
| * Always pullup to the minimum size for v1/v2 or v3 |
| * to amortize calls to m_pullup(). |
| */ |
| minlen = iphlen; |
| if (igmplen >= IGMP_V3_QUERY_MINLEN) |
| minlen += IGMP_V3_QUERY_MINLEN; |
| else |
| minlen += IGMP_MINLEN; |
| if ((m->m_flags & M_EXT || m->m_len < minlen) && |
| (m = m_pullup(m, minlen)) == 0) { |
| IGMPSTAT_INC(igps_rcv_tooshort); |
| return; |
| } |
| ip = mtod(m, struct ip *); |
| |
| if (ip->ip_ttl != 1) { |
| IGMPSTAT_INC(igps_rcv_badttl); |
| m_freem(m); |
| return; |
| } |
| |
| /* |
| * Validate checksum. |
| */ |
| m->m_data += iphlen; |
| m->m_len -= iphlen; |
| igmp = mtod(m, struct igmp *); |
| if (in_cksum(m, igmplen)) { |
| IGMPSTAT_INC(igps_rcv_badsum); |
| m_freem(m); |
| return; |
| } |
| m->m_data -= iphlen; |
| m->m_len += iphlen; |
| |
| switch (igmp->igmp_type) { |
| case IGMP_HOST_MEMBERSHIP_QUERY: |
| if (igmplen == IGMP_MINLEN) { |
| if (igmp->igmp_code == 0) |
| queryver = IGMP_VERSION_1; |
| else |
| queryver = IGMP_VERSION_2; |
| } else if (igmplen >= IGMP_V3_QUERY_MINLEN) { |
| queryver = IGMP_VERSION_3; |
| } else { |
| IGMPSTAT_INC(igps_rcv_tooshort); |
| m_freem(m); |
| return; |
| } |
| |
| switch (queryver) { |
| case IGMP_VERSION_1: |
| IGMPSTAT_INC(igps_rcv_v1v2_queries); |
| if (!V_igmp_v1enable) |
| break; |
| if (igmp_input_v1_query(ifp, ip, igmp) != 0) { |
| m_freem(m); |
| return; |
| } |
| break; |
| |
| case IGMP_VERSION_2: |
| IGMPSTAT_INC(igps_rcv_v1v2_queries); |
| if (!V_igmp_v2enable) |
| break; |
| if (igmp_input_v2_query(ifp, ip, igmp) != 0) { |
| m_freem(m); |
| return; |
| } |
| break; |
| |
| case IGMP_VERSION_3: { |
| struct igmpv3 *igmpv3; |
| uint16_t igmpv3len; |
| uint16_t srclen; |
| int nsrc; |
| |
| IGMPSTAT_INC(igps_rcv_v3_queries); |
| igmpv3 = (struct igmpv3 *)igmp; |
| /* |
| * Validate length based on source count. |
| */ |
| nsrc = ntohs(igmpv3->igmp_numsrc); |
| srclen = sizeof(struct in_addr) * nsrc; |
| if (nsrc * sizeof(in_addr_t) > srclen) { |
| IGMPSTAT_INC(igps_rcv_tooshort); |
| return; |
| } |
| /* |
| * m_pullup() may modify m, so pullup in |
| * this scope. |
| */ |
| igmpv3len = iphlen + IGMP_V3_QUERY_MINLEN + |
| srclen; |
| if ((m->m_flags & M_EXT || |
| m->m_len < igmpv3len) && |
| (m = m_pullup(m, igmpv3len)) == NULL) { |
| IGMPSTAT_INC(igps_rcv_tooshort); |
| return; |
| } |
| igmpv3 = (struct igmpv3 *)(mtod(m, uint8_t *) |
| + iphlen); |
| if (igmp_input_v3_query(ifp, ip, igmpv3) != 0) { |
| m_freem(m); |
| return; |
| } |
| } |
| break; |
| } |
| break; |
| |
| case IGMP_v1_HOST_MEMBERSHIP_REPORT: |
| if (!V_igmp_v1enable) |
| break; |
| if (igmp_input_v1_report(ifp, ip, igmp) != 0) { |
| m_freem(m); |
| return; |
| } |
| break; |
| |
| case IGMP_v2_HOST_MEMBERSHIP_REPORT: |
| if (!V_igmp_v2enable) |
| break; |
| if (!ip_checkrouteralert(m)) |
| IGMPSTAT_INC(igps_rcv_nora); |
| if (igmp_input_v2_report(ifp, ip, igmp) != 0) { |
| m_freem(m); |
| return; |
| } |
| break; |
| |
| case IGMP_v3_HOST_MEMBERSHIP_REPORT: |
| /* |
| * Hosts do not need to process IGMPv3 membership reports, |
| * as report suppression is no longer required. |
| */ |
| if (!ip_checkrouteralert(m)) |
| IGMPSTAT_INC(igps_rcv_nora); |
| break; |
| |
| default: |
| break; |
| } |
| |
| /* |
| * Pass all valid IGMP packets up to any process(es) listening on a |
| * raw IGMP socket. |
| */ |
| rip_input(m, off); |
| } |
| |
| |
| /* |
| * Fast timeout handler (global). |
| * VIMAGE: Timeout handlers are expected to service all vimages. |
| */ |
| void |
| igmp_fasttimo(void) |
| { |
| VNET_ITERATOR_DECL(vnet_iter); |
| |
| VNET_LIST_RLOCK_NOSLEEP(); |
| VNET_FOREACH(vnet_iter) { |
| CURVNET_SET(vnet_iter); |
| igmp_fasttimo_vnet(); |
| CURVNET_RESTORE(); |
| } |
| VNET_LIST_RUNLOCK_NOSLEEP(); |
| } |
| |
| /* |
| * Fast timeout handler (per-vnet). |
| * Sends are shuffled off to a netisr to deal with Giant. |
| * |
| * VIMAGE: Assume caller has set up our curvnet. |
| */ |
| static void |
| igmp_fasttimo_vnet(void) |
| { |
| struct ifqueue scq; /* State-change packets */ |
| struct ifqueue qrq; /* Query response packets */ |
| struct ifnet *ifp; |
| struct igmp_ifinfo *igi; |
| struct ifmultiaddr *ifma, *tifma; |
| struct in_multi *inm; |
| int loop, uri_fasthz; |
| |
| loop = 0; |
| uri_fasthz = 0; |
| |
| /* |
| * Quick check to see if any work needs to be done, in order to |
| * minimize the overhead of fasttimo processing. |
| * SMPng: XXX Unlocked reads. |
| */ |
| if (!V_current_state_timers_running && |
| !V_interface_timers_running && |
| !V_state_change_timers_running) |
| return; |
| |
| IN_MULTI_LOCK(); |
| IGMP_LOCK(); |
| |
| /* |
| * IGMPv3 General Query response timer processing. |
| */ |
| if (V_interface_timers_running) { |
| CTR1(KTR_IGMPV3, "%s: interface timers running", __func__); |
| |
| V_interface_timers_running = 0; |
| LIST_FOREACH(igi, &V_igi_head, igi_link) { |
| if (igi->igi_v3_timer == 0) { |
| /* Do nothing. */ |
| } else if (--igi->igi_v3_timer == 0) { |
| igmp_v3_dispatch_general_query(igi); |
| } else { |
| V_interface_timers_running = 1; |
| } |
| } |
| } |
| |
| if (!V_current_state_timers_running && |
| !V_state_change_timers_running) |
| goto out_locked; |
| |
| V_current_state_timers_running = 0; |
| V_state_change_timers_running = 0; |
| |
| CTR1(KTR_IGMPV3, "%s: state change timers running", __func__); |
| |
| /* |
| * IGMPv1/v2/v3 host report and state-change timer processing. |
| * Note: Processing a v3 group timer may remove a node. |
| */ |
| LIST_FOREACH(igi, &V_igi_head, igi_link) { |
| ifp = igi->igi_ifp; |
| |
| if (igi->igi_version == IGMP_VERSION_3) { |
| loop = (igi->igi_flags & IGIF_LOOPBACK) ? 1 : 0; |
| uri_fasthz = IGMP_RANDOM_DELAY(igi->igi_uri * |
| PR_FASTHZ); |
| |
| memset(&qrq, 0, sizeof(struct ifqueue)); |
| IFQ_SET_MAXLEN(&qrq, IGMP_MAX_G_GS_PACKETS); |
| |
| memset(&scq, 0, sizeof(struct ifqueue)); |
| IFQ_SET_MAXLEN(&scq, IGMP_MAX_STATE_CHANGE_PACKETS); |
| } |
| |
| IF_ADDR_LOCK(ifp); |
| TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, |
| tifma) { |
| if (ifma->ifma_addr->sa_family != AF_INET || |
| ifma->ifma_protospec == NULL) |
| continue; |
| inm = (struct in_multi *)ifma->ifma_protospec; |
| switch (igi->igi_version) { |
| case IGMP_VERSION_1: |
| case IGMP_VERSION_2: |
| igmp_v1v2_process_group_timer(inm, |
| igi->igi_version); |
| break; |
| case IGMP_VERSION_3: |
| igmp_v3_process_group_timers(igi, &qrq, |
| &scq, inm, uri_fasthz); |
| break; |
| } |
| } |
| IF_ADDR_UNLOCK(ifp); |
| |
| if (igi->igi_version == IGMP_VERSION_3) { |
| struct in_multi *tinm; |
| |
| igmp_dispatch_queue(&qrq, 0, loop); |
| igmp_dispatch_queue(&scq, 0, loop); |
| |
| /* |
| * Free the in_multi reference(s) for this |
| * IGMP lifecycle. |
| */ |
| SLIST_FOREACH_SAFE(inm, &igi->igi_relinmhead, |
| inm_nrele, tinm) { |
| SLIST_REMOVE_HEAD(&igi->igi_relinmhead, |
| inm_nrele); |
| inm_release_locked(inm); |
| } |
| } |
| } |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| IN_MULTI_UNLOCK(); |
| } |
| |
| /* |
| * Update host report group timer for IGMPv1/v2. |
| * Will update the global pending timer flags. |
| */ |
| static void |
| igmp_v1v2_process_group_timer(struct in_multi *inm, const int version) |
| { |
| int report_timer_expired; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| if (inm->inm_timer == 0) { |
| report_timer_expired = 0; |
| } else if (--inm->inm_timer == 0) { |
| report_timer_expired = 1; |
| } else { |
| V_current_state_timers_running = 1; |
| return; |
| } |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| break; |
| case IGMP_REPORTING_MEMBER: |
| if (report_timer_expired) { |
| inm->inm_state = IGMP_IDLE_MEMBER; |
| (void)igmp_v1v2_queue_report(inm, |
| (version == IGMP_VERSION_2) ? |
| IGMP_v2_HOST_MEMBERSHIP_REPORT : |
| IGMP_v1_HOST_MEMBERSHIP_REPORT); |
| } |
| break; |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| case IGMP_LEAVING_MEMBER: |
| break; |
| } |
| } |
| |
| /* |
| * Update a group's timers for IGMPv3. |
| * Will update the global pending timer flags. |
| * Note: Unlocked read from igi. |
| */ |
| static void |
| igmp_v3_process_group_timers(struct igmp_ifinfo *igi, |
| struct ifqueue *qrq, struct ifqueue *scq, |
| struct in_multi *inm, const int uri_fasthz) |
| { |
| int query_response_timer_expired; |
| int state_change_retransmit_timer_expired; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| query_response_timer_expired = 0; |
| state_change_retransmit_timer_expired = 0; |
| |
| /* |
| * During a transition from v1/v2 compatibility mode back to v3, |
| * a group record in REPORTING state may still have its group |
| * timer active. This is a no-op in this function; it is easier |
| * to deal with it here than to complicate the slow-timeout path. |
| */ |
| if (inm->inm_timer == 0) { |
| query_response_timer_expired = 0; |
| } else if (--inm->inm_timer == 0) { |
| query_response_timer_expired = 1; |
| } else { |
| V_current_state_timers_running = 1; |
| } |
| |
| if (inm->inm_sctimer == 0) { |
| state_change_retransmit_timer_expired = 0; |
| } else if (--inm->inm_sctimer == 0) { |
| state_change_retransmit_timer_expired = 1; |
| } else { |
| V_state_change_timers_running = 1; |
| } |
| |
| /* We are in fasttimo, so be quick about it. */ |
| if (!state_change_retransmit_timer_expired && |
| !query_response_timer_expired) |
| return; |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| break; |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| /* |
| * Respond to a previously pending Group-Specific |
| * or Group-and-Source-Specific query by enqueueing |
| * the appropriate Current-State report for |
| * immediate transmission. |
| */ |
| if (query_response_timer_expired) { |
| int retval; |
| |
| retval = igmp_v3_enqueue_group_record(qrq, inm, 0, 1, |
| (inm->inm_state == IGMP_SG_QUERY_PENDING_MEMBER)); |
| CTR2(KTR_IGMPV3, "%s: enqueue record = %d", |
| __func__, retval); |
| inm->inm_state = IGMP_REPORTING_MEMBER; |
| /* XXX Clear recorded sources for next time. */ |
| inm_clear_recorded(inm); |
| } |
| /* FALLTHROUGH */ |
| case IGMP_REPORTING_MEMBER: |
| case IGMP_LEAVING_MEMBER: |
| if (state_change_retransmit_timer_expired) { |
| /* |
| * State-change retransmission timer fired. |
| * If there are any further pending retransmissions, |
| * set the global pending state-change flag, and |
| * reset the timer. |
| */ |
| if (--inm->inm_scrv > 0) { |
| inm->inm_sctimer = uri_fasthz; |
| V_state_change_timers_running = 1; |
| } |
| /* |
| * Retransmit the previously computed state-change |
| * report. If there are no further pending |
| * retransmissions, the mbuf queue will be consumed. |
| * Update T0 state to T1 as we have now sent |
| * a state-change. |
| */ |
| (void)igmp_v3_merge_state_changes(inm, scq); |
| |
| inm_commit(inm); |
| CTR3(KTR_IGMPV3, "%s: T1 -> T0 for %s/%s", __func__, |
| inet_ntoa(inm->inm_addr), inm->inm_ifp->if_xname); |
| |
| /* |
| * If we are leaving the group for good, make sure |
| * we release IGMP's reference to it. |
| * This release must be deferred using a SLIST, |
| * as we are called from a loop which traverses |
| * the in_ifmultiaddr TAILQ. |
| */ |
| if (inm->inm_state == IGMP_LEAVING_MEMBER && |
| inm->inm_scrv == 0) { |
| inm->inm_state = IGMP_NOT_MEMBER; |
| SLIST_INSERT_HEAD(&igi->igi_relinmhead, |
| inm, inm_nrele); |
| } |
| } |
| break; |
| } |
| } |
| |
| |
| /* |
| * Suppress a group's pending response to a group or source/group query. |
| * |
| * Do NOT suppress state changes. This leads to IGMPv3 inconsistency. |
| * Do NOT update ST1/ST0 as this operation merely suppresses |
| * the currently pending group record. |
| * Do NOT suppress the response to a general query. It is possible but |
| * it would require adding another state or flag. |
| */ |
| static void |
| igmp_v3_suppress_group_record(struct in_multi *inm) |
| { |
| |
| IN_MULTI_LOCK_ASSERT(); |
| |
| KASSERT(inm->inm_igi->igi_version == IGMP_VERSION_3, |
| ("%s: not IGMPv3 mode on link", __func__)); |
| |
| if (inm->inm_state != IGMP_G_QUERY_PENDING_MEMBER || |
| inm->inm_state != IGMP_SG_QUERY_PENDING_MEMBER) |
| return; |
| |
| if (inm->inm_state == IGMP_SG_QUERY_PENDING_MEMBER) |
| inm_clear_recorded(inm); |
| |
| inm->inm_timer = 0; |
| inm->inm_state = IGMP_REPORTING_MEMBER; |
| } |
| |
| /* |
| * Switch to a different IGMP version on the given interface, |
| * as per Section 7.2.1. |
| */ |
| static void |
| igmp_set_version(struct igmp_ifinfo *igi, const int version) |
| { |
| int old_version_timer; |
| |
| IGMP_LOCK_ASSERT(); |
| |
| CTR4(KTR_IGMPV3, "%s: switching to v%d on ifp %p(%s)", __func__, |
| version, igi->igi_ifp, igi->igi_ifp->if_xname); |
| |
| if (version == IGMP_VERSION_1 || version == IGMP_VERSION_2) { |
| /* |
| * Compute the "Older Version Querier Present" timer as per |
| * Section 8.12. |
| */ |
| old_version_timer = igi->igi_rv * igi->igi_qi + igi->igi_qri; |
| old_version_timer *= PR_SLOWHZ; |
| |
| if (version == IGMP_VERSION_1) { |
| igi->igi_v1_timer = old_version_timer; |
| igi->igi_v2_timer = 0; |
| } else if (version == IGMP_VERSION_2) { |
| igi->igi_v1_timer = 0; |
| igi->igi_v2_timer = old_version_timer; |
| } |
| } |
| |
| if (igi->igi_v1_timer == 0 && igi->igi_v2_timer > 0) { |
| if (igi->igi_version != IGMP_VERSION_2) { |
| igi->igi_version = IGMP_VERSION_2; |
| igmp_v3_cancel_link_timers(igi); |
| } |
| } else if (igi->igi_v1_timer > 0) { |
| if (igi->igi_version != IGMP_VERSION_1) { |
| igi->igi_version = IGMP_VERSION_1; |
| igmp_v3_cancel_link_timers(igi); |
| } |
| } |
| } |
| |
| /* |
| * Cancel pending IGMPv3 timers for the given link and all groups |
| * joined on it; state-change, general-query, and group-query timers. |
| * |
| * Only ever called on a transition from v3 to Compatibility mode. Kill |
| * the timers stone dead (this may be expensive for large N groups), they |
| * will be restarted if Compatibility Mode deems that they must be due to |
| * query processing. |
| */ |
| static void |
| igmp_v3_cancel_link_timers(struct igmp_ifinfo *igi) |
| { |
| struct ifmultiaddr *ifma; |
| struct ifnet *ifp; |
| struct in_multi *inm; |
| |
| CTR3(KTR_IGMPV3, "%s: cancel v3 timers on ifp %p(%s)", __func__, |
| igi->igi_ifp, igi->igi_ifp->if_xname); |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| /* |
| * Stop the v3 General Query Response on this link stone dead. |
| * If fasttimo is woken up due to V_interface_timers_running, |
| * the flag will be cleared if there are no pending link timers. |
| */ |
| igi->igi_v3_timer = 0; |
| |
| /* |
| * Now clear the current-state and state-change report timers |
| * for all memberships scoped to this link. |
| */ |
| ifp = igi->igi_ifp; |
| IF_ADDR_LOCK(ifp); |
| TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { |
| if (ifma->ifma_addr->sa_family != AF_INET || |
| ifma->ifma_protospec == NULL) |
| continue; |
| inm = (struct in_multi *)ifma->ifma_protospec; |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| /* |
| * These states are either not relevant in v3 mode, |
| * or are unreported. Do nothing. |
| */ |
| break; |
| case IGMP_LEAVING_MEMBER: |
| /* |
| * If we are leaving the group and switching to |
| * compatibility mode, we need to release the final |
| * reference held for issuing the INCLUDE {}, and |
| * transition to REPORTING to ensure the host leave |
| * message is sent upstream to the old querier -- |
| * transition to NOT would lose the leave and race. |
| * |
| * SMPNG: Must drop and re-acquire IF_ADDR_LOCK |
| * around inm_release_locked(), as it is not |
| * a recursive mutex. |
| */ |
| IF_ADDR_UNLOCK(ifp); |
| inm_release_locked(inm); |
| IF_ADDR_LOCK(ifp); |
| /* FALLTHROUGH */ |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| inm_clear_recorded(inm); |
| /* FALLTHROUGH */ |
| case IGMP_REPORTING_MEMBER: |
| inm->inm_state = IGMP_REPORTING_MEMBER; |
| break; |
| } |
| /* |
| * Always clear state-change and group report timers. |
| * Free any pending IGMPv3 state-change records. |
| */ |
| inm->inm_sctimer = 0; |
| inm->inm_timer = 0; |
| _IF_DRAIN(&inm->inm_scq); |
| } |
| IF_ADDR_UNLOCK(ifp); |
| } |
| |
| /* |
| * Update the Older Version Querier Present timers for a link. |
| * See Section 7.2.1 of RFC 3376. |
| */ |
| static void |
| igmp_v1v2_process_querier_timers(struct igmp_ifinfo *igi) |
| { |
| |
| IGMP_LOCK_ASSERT(); |
| |
| if (igi->igi_v1_timer == 0 && igi->igi_v2_timer == 0) { |
| /* |
| * IGMPv1 and IGMPv2 Querier Present timers expired. |
| * |
| * Revert to IGMPv3. |
| */ |
| if (igi->igi_version != IGMP_VERSION_3) { |
| CTR5(KTR_IGMPV3, |
| "%s: transition from v%d -> v%d on %p(%s)", |
| __func__, igi->igi_version, IGMP_VERSION_3, |
| igi->igi_ifp, igi->igi_ifp->if_xname); |
| igi->igi_version = IGMP_VERSION_3; |
| } |
| } else if (igi->igi_v1_timer == 0 && igi->igi_v2_timer > 0) { |
| /* |
| * IGMPv1 Querier Present timer expired, |
| * IGMPv2 Querier Present timer running. |
| * If IGMPv2 was disabled since last timeout, |
| * revert to IGMPv3. |
| * If IGMPv2 is enabled, revert to IGMPv2. |
| */ |
| if (!V_igmp_v2enable) { |
| CTR5(KTR_IGMPV3, |
| "%s: transition from v%d -> v%d on %p(%s)", |
| __func__, igi->igi_version, IGMP_VERSION_3, |
| igi->igi_ifp, igi->igi_ifp->if_xname); |
| igi->igi_v2_timer = 0; |
| igi->igi_version = IGMP_VERSION_3; |
| } else { |
| --igi->igi_v2_timer; |
| if (igi->igi_version != IGMP_VERSION_2) { |
| CTR5(KTR_IGMPV3, |
| "%s: transition from v%d -> v%d on %p(%s)", |
| __func__, igi->igi_version, IGMP_VERSION_2, |
| igi->igi_ifp, igi->igi_ifp->if_xname); |
| igi->igi_version = IGMP_VERSION_2; |
| } |
| } |
| } else if (igi->igi_v1_timer > 0) { |
| /* |
| * IGMPv1 Querier Present timer running. |
| * Stop IGMPv2 timer if running. |
| * |
| * If IGMPv1 was disabled since last timeout, |
| * revert to IGMPv3. |
| * If IGMPv1 is enabled, reset IGMPv2 timer if running. |
| */ |
| if (!V_igmp_v1enable) { |
| CTR5(KTR_IGMPV3, |
| "%s: transition from v%d -> v%d on %p(%s)", |
| __func__, igi->igi_version, IGMP_VERSION_3, |
| igi->igi_ifp, igi->igi_ifp->if_xname); |
| igi->igi_v1_timer = 0; |
| igi->igi_version = IGMP_VERSION_3; |
| } else { |
| --igi->igi_v1_timer; |
| } |
| if (igi->igi_v2_timer > 0) { |
| CTR3(KTR_IGMPV3, |
| "%s: cancel v2 timer on %p(%s)", |
| __func__, igi->igi_ifp, igi->igi_ifp->if_xname); |
| igi->igi_v2_timer = 0; |
| } |
| } |
| } |
| |
| /* |
| * Global slowtimo handler. |
| * VIMAGE: Timeout handlers are expected to service all vimages. |
| */ |
| void |
| igmp_slowtimo(void) |
| { |
| VNET_ITERATOR_DECL(vnet_iter); |
| |
| VNET_LIST_RLOCK_NOSLEEP(); |
| VNET_FOREACH(vnet_iter) { |
| CURVNET_SET(vnet_iter); |
| igmp_slowtimo_vnet(); |
| CURVNET_RESTORE(); |
| } |
| VNET_LIST_RUNLOCK_NOSLEEP(); |
| } |
| |
| /* |
| * Per-vnet slowtimo handler. |
| */ |
| static void |
| igmp_slowtimo_vnet(void) |
| { |
| struct igmp_ifinfo *igi; |
| |
| IGMP_LOCK(); |
| |
| LIST_FOREACH(igi, &V_igi_head, igi_link) { |
| igmp_v1v2_process_querier_timers(igi); |
| } |
| |
| IGMP_UNLOCK(); |
| } |
| |
| /* |
| * Dispatch an IGMPv1/v2 host report or leave message. |
| * These are always small enough to fit inside a single mbuf. |
| */ |
| static int |
| igmp_v1v2_queue_report(struct in_multi *inm, const int type) |
| { |
| struct ifnet *ifp; |
| struct igmp *igmp; |
| struct ip *ip; |
| struct mbuf *m; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| ifp = inm->inm_ifp; |
| |
| MGETHDR(m, M_DONTWAIT, MT_DATA); |
| if (m == NULL) |
| return (ENOMEM); |
| MH_ALIGN(m, sizeof(struct ip) + sizeof(struct igmp)); |
| |
| m->m_pkthdr.len = sizeof(struct ip) + sizeof(struct igmp); |
| |
| m->m_data += sizeof(struct ip); |
| m->m_len = sizeof(struct igmp); |
| |
| igmp = mtod(m, struct igmp *); |
| igmp->igmp_type = type; |
| igmp->igmp_code = 0; |
| igmp->igmp_group = inm->inm_addr; |
| igmp->igmp_cksum = 0; |
| igmp->igmp_cksum = in_cksum(m, sizeof(struct igmp)); |
| |
| m->m_data -= sizeof(struct ip); |
| m->m_len += sizeof(struct ip); |
| |
| ip = mtod(m, struct ip *); |
| ip->ip_tos = 0; |
| ip->ip_len = sizeof(struct ip) + sizeof(struct igmp); |
| ip->ip_off = 0; |
| ip->ip_p = IPPROTO_IGMP; |
| ip->ip_src.s_addr = INADDR_ANY; |
| |
| if (type == IGMP_HOST_LEAVE_MESSAGE) |
| ip->ip_dst.s_addr = htonl(INADDR_ALLRTRS_GROUP); |
| else |
| ip->ip_dst = inm->inm_addr; |
| |
| igmp_save_context(m, ifp); |
| |
| m->m_flags |= M_IGMPV2; |
| if (inm->inm_igi->igi_flags & IGIF_LOOPBACK) |
| m->m_flags |= M_IGMP_LOOP; |
| |
| CTR2(KTR_IGMPV3, "%s: netisr_dispatch(NETISR_IGMP, %p)", __func__, m); |
| netisr_dispatch(NETISR_IGMP, m); |
| |
| return (0); |
| } |
| |
| /* |
| * Process a state change from the upper layer for the given IPv4 group. |
| * |
| * Each socket holds a reference on the in_multi in its own ip_moptions. |
| * The socket layer will have made the necessary updates to.the group |
| * state, it is now up to IGMP to issue a state change report if there |
| * has been any change between T0 (when the last state-change was issued) |
| * and T1 (now). |
| * |
| * We use the IGMPv3 state machine at group level. The IGMP module |
| * however makes the decision as to which IGMP protocol version to speak. |
| * A state change *from* INCLUDE {} always means an initial join. |
| * A state change *to* INCLUDE {} always means a final leave. |
| * |
| * FUTURE: If IGIF_V3LITE is enabled for this interface, then we can |
| * save ourselves a bunch of work; any exclusive mode groups need not |
| * compute source filter lists. |
| * |
| * VIMAGE: curvnet should have been set by caller, as this routine |
| * is called from the socket option handlers. |
| */ |
| int |
| igmp_change_state(struct in_multi *inm) |
| { |
| struct igmp_ifinfo *igi; |
| struct ifnet *ifp; |
| int error; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| |
| error = 0; |
| |
| /* |
| * Try to detect if the upper layer just asked us to change state |
| * for an interface which has now gone away. |
| */ |
| 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__)); |
| } |
| |
| IGMP_LOCK(); |
| |
| igi = ((struct in_ifinfo *)ifp->if_afdata[AF_INET])->ii_igmp; |
| KASSERT(igi != NULL, ("%s: no igmp_ifinfo for ifp %p", __func__, ifp)); |
| |
| /* |
| * If we detect a state transition to or from MCAST_UNDEFINED |
| * for this group, then we are starting or finishing an IGMP |
| * life cycle for this group. |
| */ |
| if (inm->inm_st[1].iss_fmode != inm->inm_st[0].iss_fmode) { |
| CTR3(KTR_IGMPV3, "%s: inm transition %d -> %d", __func__, |
| inm->inm_st[0].iss_fmode, inm->inm_st[1].iss_fmode); |
| if (inm->inm_st[0].iss_fmode == MCAST_UNDEFINED) { |
| CTR1(KTR_IGMPV3, "%s: initial join", __func__); |
| error = igmp_initial_join(inm, igi); |
| goto out_locked; |
| } else if (inm->inm_st[1].iss_fmode == MCAST_UNDEFINED) { |
| CTR1(KTR_IGMPV3, "%s: final leave", __func__); |
| igmp_final_leave(inm, igi); |
| goto out_locked; |
| } |
| } else { |
| CTR1(KTR_IGMPV3, "%s: filter set change", __func__); |
| } |
| |
| error = igmp_handle_state_change(inm, igi); |
| |
| out_locked: |
| IGMP_UNLOCK(); |
| return (error); |
| } |
| |
| /* |
| * Perform the initial join for an IGMP group. |
| * |
| * When joining a group: |
| * If the group should have its IGMP traffic suppressed, do nothing. |
| * IGMPv1 starts sending IGMPv1 host membership reports. |
| * IGMPv2 starts sending IGMPv2 host membership reports. |
| * IGMPv3 will schedule an IGMPv3 state-change report containing the |
| * initial state of the membership. |
| */ |
| static int |
| igmp_initial_join(struct in_multi *inm, struct igmp_ifinfo *igi) |
| { |
| struct ifnet *ifp; |
| struct ifqueue *ifq; |
| int error, retval, syncstates; |
| |
| CTR4(KTR_IGMPV3, "%s: initial join %s on ifp %p(%s)", |
| __func__, inet_ntoa(inm->inm_addr), inm->inm_ifp, |
| inm->inm_ifp->if_xname); |
| |
| error = 0; |
| syncstates = 1; |
| |
| ifp = inm->inm_ifp; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| KASSERT(igi && igi->igi_ifp == ifp, ("%s: inconsistent ifp", __func__)); |
| |
| /* |
| * Groups joined on loopback or marked as 'not reported', |
| * e.g. 224.0.0.1, enter the IGMP_SILENT_MEMBER state and |
| * are never reported in any IGMP protocol exchanges. |
| * All other groups enter the appropriate IGMP state machine |
| * for the version in use on this link. |
| * A link marked as IGIF_SILENT causes IGMP to be completely |
| * disabled for the link. |
| */ |
| if ((ifp->if_flags & IFF_LOOPBACK) || |
| (igi->igi_flags & IGIF_SILENT) || |
| !igmp_isgroupreported(inm->inm_addr)) { |
| CTR1(KTR_IGMPV3, |
| "%s: not kicking state machine for silent group", __func__); |
| inm->inm_state = IGMP_SILENT_MEMBER; |
| inm->inm_timer = 0; |
| } else { |
| /* |
| * Deal with overlapping in_multi lifecycle. |
| * If this group was LEAVING, then make sure |
| * we drop the reference we picked up to keep the |
| * group around for the final INCLUDE {} enqueue. |
| */ |
| if (igi->igi_version == IGMP_VERSION_3 && |
| inm->inm_state == IGMP_LEAVING_MEMBER) |
| inm_release_locked(inm); |
| |
| inm->inm_state = IGMP_REPORTING_MEMBER; |
| |
| switch (igi->igi_version) { |
| case IGMP_VERSION_1: |
| case IGMP_VERSION_2: |
| inm->inm_state = IGMP_IDLE_MEMBER; |
| error = igmp_v1v2_queue_report(inm, |
| (igi->igi_version == IGMP_VERSION_2) ? |
| IGMP_v2_HOST_MEMBERSHIP_REPORT : |
| IGMP_v1_HOST_MEMBERSHIP_REPORT); |
| if (error == 0) { |
| inm->inm_timer = IGMP_RANDOM_DELAY( |
| IGMP_V1V2_MAX_RI * PR_FASTHZ); |
| V_current_state_timers_running = 1; |
| } |
| break; |
| |
| case IGMP_VERSION_3: |
| /* |
| * Defer update of T0 to T1, until the first copy |
| * of the state change has been transmitted. |
| */ |
| syncstates = 0; |
| |
| /* |
| * Immediately enqueue a State-Change Report for |
| * this interface, freeing any previous reports. |
| * Don't kick the timers if there is nothing to do, |
| * or if an error occurred. |
| */ |
| ifq = &inm->inm_scq; |
| _IF_DRAIN(ifq); |
| retval = igmp_v3_enqueue_group_record(ifq, inm, 1, |
| 0, 0); |
| CTR2(KTR_IGMPV3, "%s: enqueue record = %d", |
| __func__, retval); |
| if (retval <= 0) { |
| error = retval * -1; |
| break; |
| } |
| |
| /* |
| * Schedule transmission of pending state-change |
| * report up to RV times for this link. The timer |
| * will fire at the next igmp_fasttimo (~200ms), |
| * giving us an opportunity to merge the reports. |
| */ |
| if (igi->igi_flags & IGIF_LOOPBACK) { |
| inm->inm_scrv = 1; |
| } else { |
| KASSERT(igi->igi_rv > 1, |
| ("%s: invalid robustness %d", __func__, |
| igi->igi_rv)); |
| inm->inm_scrv = igi->igi_rv; |
| } |
| inm->inm_sctimer = 1; |
| V_state_change_timers_running = 1; |
| |
| error = 0; |
| break; |
| } |
| } |
| |
| /* |
| * Only update the T0 state if state change is atomic, |
| * i.e. we don't need to wait for a timer to fire before we |
| * can consider the state change to have been communicated. |
| */ |
| if (syncstates) { |
| inm_commit(inm); |
| CTR3(KTR_IGMPV3, "%s: T1 -> T0 for %s/%s", __func__, |
| inet_ntoa(inm->inm_addr), inm->inm_ifp->if_xname); |
| } |
| |
| return (error); |
| } |
| |
| /* |
| * Issue an intermediate state change during the IGMP life-cycle. |
| */ |
| static int |
| igmp_handle_state_change(struct in_multi *inm, struct igmp_ifinfo *igi) |
| { |
| struct ifnet *ifp; |
| int retval; |
| |
| CTR4(KTR_IGMPV3, "%s: state change for %s on ifp %p(%s)", |
| __func__, inet_ntoa(inm->inm_addr), inm->inm_ifp, |
| inm->inm_ifp->if_xname); |
| |
| ifp = inm->inm_ifp; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| KASSERT(igi && igi->igi_ifp == ifp, ("%s: inconsistent ifp", __func__)); |
| |
| if ((ifp->if_flags & IFF_LOOPBACK) || |
| (igi->igi_flags & IGIF_SILENT) || |
| !igmp_isgroupreported(inm->inm_addr) || |
| (igi->igi_version != IGMP_VERSION_3)) { |
| if (!igmp_isgroupreported(inm->inm_addr)) { |
| CTR1(KTR_IGMPV3, |
| "%s: not kicking state machine for silent group", __func__); |
| } |
| CTR1(KTR_IGMPV3, "%s: nothing to do", __func__); |
| inm_commit(inm); |
| CTR3(KTR_IGMPV3, "%s: T1 -> T0 for %s/%s", __func__, |
| inet_ntoa(inm->inm_addr), inm->inm_ifp->if_xname); |
| return (0); |
| } |
| |
| _IF_DRAIN(&inm->inm_scq); |
| |
| retval = igmp_v3_enqueue_group_record(&inm->inm_scq, inm, 1, 0, 0); |
| CTR2(KTR_IGMPV3, "%s: enqueue record = %d", __func__, retval); |
| if (retval <= 0) |
| return (-retval); |
| |
| /* |
| * If record(s) were enqueued, start the state-change |
| * report timer for this group. |
| */ |
| inm->inm_scrv = ((igi->igi_flags & IGIF_LOOPBACK) ? 1 : igi->igi_rv); |
| inm->inm_sctimer = 1; |
| V_state_change_timers_running = 1; |
| |
| return (0); |
| } |
| |
| /* |
| * Perform the final leave for an IGMP group. |
| * |
| * When leaving a group: |
| * IGMPv1 does nothing. |
| * IGMPv2 sends a host leave message, if and only if we are the reporter. |
| * IGMPv3 enqueues a state-change report containing a transition |
| * to INCLUDE {} for immediate transmission. |
| */ |
| static void |
| igmp_final_leave(struct in_multi *inm, struct igmp_ifinfo *igi) |
| { |
| int syncstates; |
| |
| syncstates = 1; |
| |
| CTR4(KTR_IGMPV3, "%s: final leave %s on ifp %p(%s)", |
| __func__, inet_ntoa(inm->inm_addr), inm->inm_ifp, |
| inm->inm_ifp->if_xname); |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| case IGMP_LEAVING_MEMBER: |
| /* Already leaving or left; do nothing. */ |
| CTR1(KTR_IGMPV3, |
| "%s: not kicking state machine for silent group", __func__); |
| break; |
| case IGMP_REPORTING_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| if (igi->igi_version == IGMP_VERSION_2) { |
| #ifdef INVARIANTS |
| if (inm->inm_state == IGMP_G_QUERY_PENDING_MEMBER || |
| inm->inm_state == IGMP_SG_QUERY_PENDING_MEMBER) |
| panic("%s: IGMPv3 state reached, not IGMPv3 mode", |
| __func__); |
| #endif |
| igmp_v1v2_queue_report(inm, IGMP_HOST_LEAVE_MESSAGE); |
| inm->inm_state = IGMP_NOT_MEMBER; |
| } else if (igi->igi_version == IGMP_VERSION_3) { |
| /* |
| * Stop group timer and all pending reports. |
| * Immediately enqueue a state-change report |
| * TO_IN {} to be sent on the next fast timeout, |
| * giving us an opportunity to merge reports. |
| */ |
| _IF_DRAIN(&inm->inm_scq); |
| inm->inm_timer = 0; |
| if (igi->igi_flags & IGIF_LOOPBACK) { |
| inm->inm_scrv = 1; |
| } else { |
| inm->inm_scrv = igi->igi_rv; |
| } |
| CTR4(KTR_IGMPV3, "%s: Leaving %s/%s with %d " |
| "pending retransmissions.", __func__, |
| inet_ntoa(inm->inm_addr), |
| inm->inm_ifp->if_xname, inm->inm_scrv); |
| if (inm->inm_scrv == 0) { |
| inm->inm_state = IGMP_NOT_MEMBER; |
| inm->inm_sctimer = 0; |
| } else { |
| int retval; |
| |
| inm_acquire_locked(inm); |
| |
| retval = igmp_v3_enqueue_group_record( |
| &inm->inm_scq, inm, 1, 0, 0); |
| KASSERT(retval != 0, |
| ("%s: enqueue record = %d", __func__, |
| retval)); |
| |
| inm->inm_state = IGMP_LEAVING_MEMBER; |
| inm->inm_sctimer = 1; |
| V_state_change_timers_running = 1; |
| syncstates = 0; |
| } |
| break; |
| } |
| break; |
| case IGMP_LAZY_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| /* Our reports are suppressed; do nothing. */ |
| break; |
| } |
| |
| if (syncstates) { |
| inm_commit(inm); |
| CTR3(KTR_IGMPV3, "%s: T1 -> T0 for %s/%s", __func__, |
| inet_ntoa(inm->inm_addr), inm->inm_ifp->if_xname); |
| inm->inm_st[1].iss_fmode = MCAST_UNDEFINED; |
| CTR3(KTR_IGMPV3, "%s: T1 now MCAST_UNDEFINED for %s/%s", |
| __func__, inet_ntoa(inm->inm_addr), inm->inm_ifp->if_xname); |
| } |
| } |
| |
| /* |
| * Enqueue an IGMPv3 group record to the given output queue. |
| * |
| * XXX This function could do with having the allocation code |
| * split out, and the multiple-tree-walks coalesced into a single |
| * routine as has been done in igmp_v3_enqueue_filter_change(). |
| * |
| * If is_state_change is zero, a current-state record is appended. |
| * If is_state_change is non-zero, a state-change report is appended. |
| * |
| * If is_group_query is non-zero, an mbuf packet chain is allocated. |
| * If is_group_query is zero, and if there is a packet with free space |
| * at the tail of the queue, it will be appended to providing there |
| * is enough free space. |
| * Otherwise a new mbuf packet chain is allocated. |
| * |
| * If is_source_query is non-zero, each source is checked to see if |
| * it was recorded for a Group-Source query, and will be omitted if |
| * it is not both in-mode and recorded. |
| * |
| * The function will attempt to allocate leading space in the packet |
| * for the IP/IGMP header to be prepended without fragmenting the chain. |
| * |
| * If successful the size of all data appended to the queue is returned, |
| * otherwise an error code less than zero is returned, or zero if |
| * no record(s) were appended. |
| */ |
| static int |
| igmp_v3_enqueue_group_record(struct ifqueue *ifq, struct in_multi *inm, |
| const int is_state_change, const int is_group_query, |
| const int is_source_query) |
| { |
| struct igmp_grouprec ig; |
| struct igmp_grouprec *pig; |
| struct ifnet *ifp; |
| struct ip_msource *ims, *nims; |
| struct mbuf *m0, *m, *md; |
| int error, is_filter_list_change; |
| int minrec0len, m0srcs, msrcs, nbytes, off; |
| int record_has_sources; |
| int now; |
| int type; |
| in_addr_t naddr; |
| uint8_t mode; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| |
| error = 0; |
| ifp = inm->inm_ifp; |
| is_filter_list_change = 0; |
| m = NULL; |
| m0 = NULL; |
| m0srcs = 0; |
| msrcs = 0; |
| nbytes = 0; |
| nims = NULL; |
| record_has_sources = 1; |
| pig = NULL; |
| type = IGMP_DO_NOTHING; |
| mode = inm->inm_st[1].iss_fmode; |
| |
| /* |
| * If we did not transition out of ASM mode during t0->t1, |
| * and there are no source nodes to process, we can skip |
| * the generation of source records. |
| */ |
| if (inm->inm_st[0].iss_asm > 0 && inm->inm_st[1].iss_asm > 0 && |
| inm->inm_nsrc == 0) |
| record_has_sources = 0; |
| |
| if (is_state_change) { |
| /* |
| * Queue a state change record. |
| * If the mode did not change, and there are non-ASM |
| * listeners or source filters present, |
| * we potentially need to issue two records for the group. |
| * If we are transitioning to MCAST_UNDEFINED, we need |
| * not send any sources. |
| * If there are ASM listeners, and there was no filter |
| * mode transition of any kind, do nothing. |
| */ |
| if (mode != inm->inm_st[0].iss_fmode) { |
| if (mode == MCAST_EXCLUDE) { |
| CTR1(KTR_IGMPV3, "%s: change to EXCLUDE", |
| __func__); |
| type = IGMP_CHANGE_TO_EXCLUDE_MODE; |
| } else { |
| CTR1(KTR_IGMPV3, "%s: change to INCLUDE", |
| __func__); |
| type = IGMP_CHANGE_TO_INCLUDE_MODE; |
| if (mode == MCAST_UNDEFINED) |
| record_has_sources = 0; |
| } |
| } else { |
| if (record_has_sources) { |
| is_filter_list_change = 1; |
| } else { |
| type = IGMP_DO_NOTHING; |
| } |
| } |
| } else { |
| /* |
| * Queue a current state record. |
| */ |
| if (mode == MCAST_EXCLUDE) { |
| type = IGMP_MODE_IS_EXCLUDE; |
| } else if (mode == MCAST_INCLUDE) { |
| type = IGMP_MODE_IS_INCLUDE; |
| KASSERT(inm->inm_st[1].iss_asm == 0, |
| ("%s: inm %p is INCLUDE but ASM count is %d", |
| __func__, inm, inm->inm_st[1].iss_asm)); |
| } |
| } |
| |
| /* |
| * Generate the filter list changes using a separate function. |
| */ |
| if (is_filter_list_change) |
| return (igmp_v3_enqueue_filter_change(ifq, inm)); |
| |
| if (type == IGMP_DO_NOTHING) { |
| CTR3(KTR_IGMPV3, "%s: nothing to do for %s/%s", |
| __func__, inet_ntoa(inm->inm_addr), |
| inm->inm_ifp->if_xname); |
| return (0); |
| } |
| |
| /* |
| * If any sources are present, we must be able to fit at least |
| * one in the trailing space of the tail packet's mbuf, |
| * ideally more. |
| */ |
| minrec0len = sizeof(struct igmp_grouprec); |
| if (record_has_sources) |
| minrec0len += sizeof(in_addr_t); |
| |
| CTR4(KTR_IGMPV3, "%s: queueing %s for %s/%s", __func__, |
| igmp_rec_type_to_str(type), inet_ntoa(inm->inm_addr), |
| inm->inm_ifp->if_xname); |
| |
| /* |
| * Check if we have a packet in the tail of the queue for this |
| * group into which the first group record for this group will fit. |
| * Otherwise allocate a new packet. |
| * Always allocate leading space for IP+RA_OPT+IGMP+REPORT. |
| * Note: Group records for G/GSR query responses MUST be sent |
| * in their own packet. |
| */ |
| m0 = ifq->ifq_tail; |
| if (!is_group_query && |
| m0 != NULL && |
| (m0->m_pkthdr.PH_vt.vt_nrecs + 1 <= IGMP_V3_REPORT_MAXRECS) && |
| (m0->m_pkthdr.len + minrec0len) < |
| (ifp->if_mtu - IGMP_LEADINGSPACE)) { |
| m0srcs = (ifp->if_mtu - m0->m_pkthdr.len - |
| sizeof(struct igmp_grouprec)) / sizeof(in_addr_t); |
| m = m0; |
| CTR1(KTR_IGMPV3, "%s: use existing packet", __func__); |
| } else { |
| if (_IF_QFULL(ifq)) { |
| CTR1(KTR_IGMPV3, "%s: outbound queue full", __func__); |
| return (-ENOMEM); |
| } |
| m = NULL; |
| m0srcs = (ifp->if_mtu - IGMP_LEADINGSPACE - |
| sizeof(struct igmp_grouprec)) / sizeof(in_addr_t); |
| if (!is_state_change && !is_group_query) { |
| m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); |
| if (m) |
| m->m_data += IGMP_LEADINGSPACE; |
| } |
| if (m == NULL) { |
| m = m_gethdr(M_DONTWAIT, MT_DATA); |
| if (m) |
| MH_ALIGN(m, IGMP_LEADINGSPACE); |
| } |
| if (m == NULL) |
| return (-ENOMEM); |
| |
| igmp_save_context(m, ifp); |
| |
| CTR1(KTR_IGMPV3, "%s: allocated first packet", __func__); |
| } |
| |
| /* |
| * Append group record. |
| * If we have sources, we don't know how many yet. |
| */ |
| ig.ig_type = type; |
| ig.ig_datalen = 0; |
| ig.ig_numsrc = 0; |
| ig.ig_group = inm->inm_addr; |
| if (!m_append(m, sizeof(struct igmp_grouprec), (void *)&ig)) { |
| if (m != m0) |
| m_freem(m); |
| CTR1(KTR_IGMPV3, "%s: m_append() failed.", __func__); |
| return (-ENOMEM); |
| } |
| nbytes += sizeof(struct igmp_grouprec); |
| |
| /* |
| * Append as many sources as will fit in the first packet. |
| * If we are appending to a new packet, the chain allocation |
| * may potentially use clusters; use m_getptr() in this case. |
| * If we are appending to an existing packet, we need to obtain |
| * a pointer to the group record after m_append(), in case a new |
| * mbuf was allocated. |
| * Only append sources which are in-mode at t1. If we are |
| * transitioning to MCAST_UNDEFINED state on the group, do not |
| * include source entries. |
| * Only report recorded sources in our filter set when responding |
| * to a group-source query. |
| */ |
| if (record_has_sources) { |
| if (m == m0) { |
| md = m_last(m); |
| pig = (struct igmp_grouprec *)(mtod(md, uint8_t *) + |
| md->m_len - nbytes); |
| } else { |
| md = m_getptr(m, 0, &off); |
| pig = (struct igmp_grouprec *)(mtod(md, uint8_t *) + |
| off); |
| } |
| msrcs = 0; |
| RB_FOREACH_SAFE(ims, ip_msource_tree, &inm->inm_srcs, nims) { |
| CTR2(KTR_IGMPV3, "%s: visit node %s", __func__, |
| inet_ntoa_haddr(ims->ims_haddr)); |
| now = ims_get_mode(inm, ims, 1); |
| CTR2(KTR_IGMPV3, "%s: node is %d", __func__, now); |
| if ((now != mode) || |
| (now == mode && mode == MCAST_UNDEFINED)) { |
| CTR1(KTR_IGMPV3, "%s: skip node", __func__); |
| continue; |
| } |
| if (is_source_query && ims->ims_stp == 0) { |
| CTR1(KTR_IGMPV3, "%s: skip unrecorded node", |
| __func__); |
| continue; |
| } |
| CTR1(KTR_IGMPV3, "%s: append node", __func__); |
| naddr = htonl(ims->ims_haddr); |
| if (!m_append(m, sizeof(in_addr_t), (void *)&naddr)) { |
| if (m != m0) |
| m_freem(m); |
| CTR1(KTR_IGMPV3, "%s: m_append() failed.", |
| __func__); |
| return (-ENOMEM); |
| } |
| nbytes += sizeof(in_addr_t); |
| ++msrcs; |
| if (msrcs == m0srcs) |
| break; |
| } |
| CTR2(KTR_IGMPV3, "%s: msrcs is %d this packet", __func__, |
| msrcs); |
| pig->ig_numsrc = htons(msrcs); |
| nbytes += (msrcs * sizeof(in_addr_t)); |
| } |
| |
| if (is_source_query && msrcs == 0) { |
| CTR1(KTR_IGMPV3, "%s: no recorded sources to report", __func__); |
| if (m != m0) |
| m_freem(m); |
| return (0); |
| } |
| |
| /* |
| * We are good to go with first packet. |
| */ |
| if (m != m0) { |
| CTR1(KTR_IGMPV3, "%s: enqueueing first packet", __func__); |
| m->m_pkthdr.PH_vt.vt_nrecs = 1; |
| _IF_ENQUEUE(ifq, m); |
| } else |
| m->m_pkthdr.PH_vt.vt_nrecs++; |
| |
| /* |
| * No further work needed if no source list in packet(s). |
| */ |
| if (!record_has_sources) |
| return (nbytes); |
| |
| /* |
| * Whilst sources remain to be announced, we need to allocate |
| * a new packet and fill out as many sources as will fit. |
| * Always try for a cluster first. |
| */ |
| while (nims != NULL) { |
| if (_IF_QFULL(ifq)) { |
| CTR1(KTR_IGMPV3, "%s: outbound queue full", __func__); |
| return (-ENOMEM); |
| } |
| m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); |
| if (m) |
| m->m_data += IGMP_LEADINGSPACE; |
| if (m == NULL) { |
| m = m_gethdr(M_DONTWAIT, MT_DATA); |
| if (m) |
| MH_ALIGN(m, IGMP_LEADINGSPACE); |
| } |
| if (m == NULL) |
| return (-ENOMEM); |
| igmp_save_context(m, ifp); |
| md = m_getptr(m, 0, &off); |
| pig = (struct igmp_grouprec *)(mtod(md, uint8_t *) + off); |
| CTR1(KTR_IGMPV3, "%s: allocated next packet", __func__); |
| |
| if (!m_append(m, sizeof(struct igmp_grouprec), (void *)&ig)) { |
| if (m != m0) |
| m_freem(m); |
| CTR1(KTR_IGMPV3, "%s: m_append() failed.", __func__); |
| return (-ENOMEM); |
| } |
| m->m_pkthdr.PH_vt.vt_nrecs = 1; |
| nbytes += sizeof(struct igmp_grouprec); |
| |
| m0srcs = (ifp->if_mtu - IGMP_LEADINGSPACE - |
| sizeof(struct igmp_grouprec)) / sizeof(in_addr_t); |
| |
| msrcs = 0; |
| RB_FOREACH_FROM(ims, ip_msource_tree, nims) { |
| CTR2(KTR_IGMPV3, "%s: visit node %s", __func__, |
| inet_ntoa_haddr(ims->ims_haddr)); |
| now = ims_get_mode(inm, ims, 1); |
| if ((now != mode) || |
| (now == mode && mode == MCAST_UNDEFINED)) { |
| CTR1(KTR_IGMPV3, "%s: skip node", __func__); |
| continue; |
| } |
| if (is_source_query && ims->ims_stp == 0) { |
| CTR1(KTR_IGMPV3, "%s: skip unrecorded node", |
| __func__); |
| continue; |
| } |
| CTR1(KTR_IGMPV3, "%s: append node", __func__); |
| naddr = htonl(ims->ims_haddr); |
| if (!m_append(m, sizeof(in_addr_t), (void *)&naddr)) { |
| if (m != m0) |
| m_freem(m); |
| CTR1(KTR_IGMPV3, "%s: m_append() failed.", |
| __func__); |
| return (-ENOMEM); |
| } |
| ++msrcs; |
| if (msrcs == m0srcs) |
| break; |
| } |
| pig->ig_numsrc = htons(msrcs); |
| nbytes += (msrcs * sizeof(in_addr_t)); |
| |
| CTR1(KTR_IGMPV3, "%s: enqueueing next packet", __func__); |
| _IF_ENQUEUE(ifq, m); |
| } |
| |
| return (nbytes); |
| } |
| |
| /* |
| * Type used to mark record pass completion. |
| * We exploit the fact we can cast to this easily from the |
| * current filter modes on each ip_msource node. |
| */ |
| typedef enum { |
| REC_NONE = 0x00, /* MCAST_UNDEFINED */ |
| REC_ALLOW = 0x01, /* MCAST_INCLUDE */ |
| REC_BLOCK = 0x02, /* MCAST_EXCLUDE */ |
| REC_FULL = REC_ALLOW | REC_BLOCK |
| } rectype_t; |
| |
| /* |
| * Enqueue an IGMPv3 filter list change to the given output queue. |
| * |
| * Source list filter state is held in an RB-tree. When the filter list |
| * for a group is changed without changing its mode, we need to compute |
| * the deltas between T0 and T1 for each source in the filter set, |
| * and enqueue the appropriate ALLOW_NEW/BLOCK_OLD records. |
| * |
| * As we may potentially queue two record types, and the entire R-B tree |
| * needs to be walked at once, we break this out into its own function |
| * so we can generate a tightly packed queue of packets. |
| * |
| * XXX This could be written to only use one tree walk, although that makes |
| * serializing into the mbuf chains a bit harder. For now we do two walks |
| * which makes things easier on us, and it may or may not be harder on |
| * the L2 cache. |
| * |
| * If successful the size of all data appended to the queue is returned, |
| * otherwise an error code less than zero is returned, or zero if |
| * no record(s) were appended. |
| */ |
| static int |
| igmp_v3_enqueue_filter_change(struct ifqueue *ifq, struct in_multi *inm) |
| { |
| static const int MINRECLEN = |
| sizeof(struct igmp_grouprec) + sizeof(in_addr_t); |
| struct ifnet *ifp; |
| struct igmp_grouprec ig; |
| struct igmp_grouprec *pig; |
| struct ip_msource *ims, *nims; |
| struct mbuf *m, *m0, *md; |
| in_addr_t naddr; |
| int m0srcs, nbytes, npbytes, off, rsrcs, schanged; |
| int nallow, nblock; |
| uint8_t mode, now, then; |
| rectype_t crt, drt, nrt; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| |
| if (inm->inm_nsrc == 0 || |
| (inm->inm_st[0].iss_asm > 0 && inm->inm_st[1].iss_asm > 0)) |
| return (0); |
| |
| ifp = inm->inm_ifp; /* interface */ |
| mode = inm->inm_st[1].iss_fmode; /* filter mode at t1 */ |
| crt = REC_NONE; /* current group record type */ |
| drt = REC_NONE; /* mask of completed group record types */ |
| nrt = REC_NONE; /* record type for current node */ |
| m0srcs = 0; /* # source which will fit in current mbuf chain */ |
| nbytes = 0; /* # of bytes appended to group's state-change queue */ |
| npbytes = 0; /* # of bytes appended this packet */ |
| rsrcs = 0; /* # sources encoded in current record */ |
| schanged = 0; /* # nodes encoded in overall filter change */ |
| nallow = 0; /* # of source entries in ALLOW_NEW */ |
| nblock = 0; /* # of source entries in BLOCK_OLD */ |
| nims = NULL; /* next tree node pointer */ |
| |
| /* |
| * For each possible filter record mode. |
| * The first kind of source we encounter tells us which |
| * is the first kind of record we start appending. |
| * If a node transitioned to UNDEFINED at t1, its mode is treated |
| * as the inverse of the group's filter mode. |
| */ |
| while (drt != REC_FULL) { |
| do { |
| m0 = ifq->ifq_tail; |
| if (m0 != NULL && |
| (m0->m_pkthdr.PH_vt.vt_nrecs + 1 <= |
| IGMP_V3_REPORT_MAXRECS) && |
| (m0->m_pkthdr.len + MINRECLEN) < |
| (ifp->if_mtu - IGMP_LEADINGSPACE)) { |
| m = m0; |
| m0srcs = (ifp->if_mtu - m0->m_pkthdr.len - |
| sizeof(struct igmp_grouprec)) / |
| sizeof(in_addr_t); |
| CTR1(KTR_IGMPV3, |
| "%s: use previous packet", __func__); |
| } else { |
| m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); |
| if (m) |
| m->m_data += IGMP_LEADINGSPACE; |
| if (m == NULL) { |
| m = m_gethdr(M_DONTWAIT, MT_DATA); |
| if (m) |
| MH_ALIGN(m, IGMP_LEADINGSPACE); |
| } |
| if (m == NULL) { |
| CTR1(KTR_IGMPV3, |
| "%s: m_get*() failed", __func__); |
| return (-ENOMEM); |
| } |
| m->m_pkthdr.PH_vt.vt_nrecs = 0; |
| igmp_save_context(m, ifp); |
| m0srcs = (ifp->if_mtu - IGMP_LEADINGSPACE - |
| sizeof(struct igmp_grouprec)) / |
| sizeof(in_addr_t); |
| npbytes = 0; |
| CTR1(KTR_IGMPV3, |
| "%s: allocated new packet", __func__); |
| } |
| /* |
| * Append the IGMP group record header to the |
| * current packet's data area. |
| * Recalculate pointer to free space for next |
| * group record, in case m_append() allocated |
| * a new mbuf or cluster. |
| */ |
| memset(&ig, 0, sizeof(ig)); |
| ig.ig_group = inm->inm_addr; |
| if (!m_append(m, sizeof(ig), (void *)&ig)) { |
| if (m != m0) |
| m_freem(m); |
| CTR1(KTR_IGMPV3, |
| "%s: m_append() failed", __func__); |
| return (-ENOMEM); |
| } |
| npbytes += sizeof(struct igmp_grouprec); |
| if (m != m0) { |
| /* new packet; offset in c hain */ |
| md = m_getptr(m, npbytes - |
| sizeof(struct igmp_grouprec), &off); |
| pig = (struct igmp_grouprec *)(mtod(md, |
| uint8_t *) + off); |
| } else { |
| /* current packet; offset from last append */ |
| md = m_last(m); |
| pig = (struct igmp_grouprec *)(mtod(md, |
| uint8_t *) + md->m_len - |
| sizeof(struct igmp_grouprec)); |
| } |
| /* |
| * Begin walking the tree for this record type |
| * pass, or continue from where we left off |
| * previously if we had to allocate a new packet. |
| * Only report deltas in-mode at t1. |
| * We need not report included sources as allowed |
| * if we are in inclusive mode on the group, |
| * however the converse is not true. |
| */ |
| rsrcs = 0; |
| if (nims == NULL) |
| nims = RB_MIN(ip_msource_tree, &inm->inm_srcs); |
| RB_FOREACH_FROM(ims, ip_msource_tree, nims) { |
| CTR2(KTR_IGMPV3, "%s: visit node %s", |
| __func__, inet_ntoa_haddr(ims->ims_haddr)); |
| now = ims_get_mode(inm, ims, 1); |
| then = ims_get_mode(inm, ims, 0); |
| CTR3(KTR_IGMPV3, "%s: mode: t0 %d, t1 %d", |
| __func__, then, now); |
| if (now == then) { |
| CTR1(KTR_IGMPV3, |
| "%s: skip unchanged", __func__); |
| continue; |
| } |
| if (mode == MCAST_EXCLUDE && |
| now == MCAST_INCLUDE) { |
| CTR1(KTR_IGMPV3, |
| "%s: skip IN src on EX group", |
| __func__); |
| continue; |
| } |
| nrt = (rectype_t)now; |
| if (nrt == REC_NONE) |
| nrt = (rectype_t)(~mode & REC_FULL); |
| if (schanged++ == 0) { |
| crt = nrt; |
| } else if (crt != nrt) |
| continue; |
| naddr = htonl(ims->ims_haddr); |
| if (!m_append(m, sizeof(in_addr_t), |
| (void *)&naddr)) { |
| if (m != m0) |
| m_freem(m); |
| CTR1(KTR_IGMPV3, |
| "%s: m_append() failed", __func__); |
| return (-ENOMEM); |
| } |
| nallow += !!(crt == REC_ALLOW); |
| nblock += !!(crt == REC_BLOCK); |
| if (++rsrcs == m0srcs) |
| break; |
| } |
| /* |
| * If we did not append any tree nodes on this |
| * pass, back out of allocations. |
| */ |
| if (rsrcs == 0) { |
| npbytes -= sizeof(struct igmp_grouprec); |
| if (m != m0) { |
| CTR1(KTR_IGMPV3, |
| "%s: m_free(m)", __func__); |
| m_freem(m); |
| } else { |
| CTR1(KTR_IGMPV3, |
| "%s: m_adj(m, -ig)", __func__); |
| m_adj(m, -((int)sizeof( |
| struct igmp_grouprec))); |
| } |
| continue; |
| } |
| npbytes += (rsrcs * sizeof(in_addr_t)); |
| if (crt == REC_ALLOW) |
| pig->ig_type = IGMP_ALLOW_NEW_SOURCES; |
| else if (crt == REC_BLOCK) |
| pig->ig_type = IGMP_BLOCK_OLD_SOURCES; |
| pig->ig_numsrc = htons(rsrcs); |
| /* |
| * Count the new group record, and enqueue this |
| * packet if it wasn't already queued. |
| */ |
| m->m_pkthdr.PH_vt.vt_nrecs++; |
| if (m != m0) |
| _IF_ENQUEUE(ifq, m); |
| nbytes += npbytes; |
| } while (nims != NULL); |
| drt |= crt; |
| crt = (~crt & REC_FULL); |
| } |
| |
| CTR3(KTR_IGMPV3, "%s: queued %d ALLOW_NEW, %d BLOCK_OLD", __func__, |
| nallow, nblock); |
| |
| return (nbytes); |
| } |
| |
| static int |
| igmp_v3_merge_state_changes(struct in_multi *inm, struct ifqueue *ifscq) |
| { |
| struct ifqueue *gq; |
| struct mbuf *m; /* pending state-change */ |
| struct mbuf *m0; /* copy of pending state-change */ |
| struct mbuf *mt; /* last state-change in packet */ |
| int docopy, domerge; |
| u_int recslen; |
| |
| docopy = 0; |
| domerge = 0; |
| recslen = 0; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| /* |
| * If there are further pending retransmissions, make a writable |
| * copy of each queued state-change message before merging. |
| */ |
| if (inm->inm_scrv > 0) |
| docopy = 1; |
| |
| gq = &inm->inm_scq; |
| #ifdef KTR |
| if (gq->ifq_head == NULL) { |
| CTR2(KTR_IGMPV3, "%s: WARNING: queue for inm %p is empty", |
| __func__, inm); |
| } |
| #endif |
| |
| m = gq->ifq_head; |
| while (m != NULL) { |
| /* |
| * Only merge the report into the current packet if |
| * there is sufficient space to do so; an IGMPv3 report |
| * packet may only contain 65,535 group records. |
| * Always use a simple mbuf chain concatentation to do this, |
| * as large state changes for single groups may have |
| * allocated clusters. |
| */ |
| domerge = 0; |
| mt = ifscq->ifq_tail; |
| if (mt != NULL) { |
| recslen = m_length(m, NULL); |
| |
| if ((mt->m_pkthdr.PH_vt.vt_nrecs + |
| m->m_pkthdr.PH_vt.vt_nrecs <= |
| IGMP_V3_REPORT_MAXRECS) && |
| (mt->m_pkthdr.len + recslen <= |
| (inm->inm_ifp->if_mtu - IGMP_LEADINGSPACE))) |
| domerge = 1; |
| } |
| |
| if (!domerge && _IF_QFULL(gq)) { |
| CTR2(KTR_IGMPV3, |
| "%s: outbound queue full, skipping whole packet %p", |
| __func__, m); |
| mt = m->m_nextpkt; |
| if (!docopy) |
| m_freem(m); |
| m = mt; |
| continue; |
| } |
| |
| if (!docopy) { |
| CTR2(KTR_IGMPV3, "%s: dequeueing %p", __func__, m); |
| _IF_DEQUEUE(gq, m0); |
| m = m0->m_nextpkt; |
| } else { |
| CTR2(KTR_IGMPV3, "%s: copying %p", __func__, m); |
| m0 = m_dup(m, M_NOWAIT); |
| if (m0 == NULL) |
| return (ENOMEM); |
| m0->m_nextpkt = NULL; |
| m = m->m_nextpkt; |
| } |
| |
| if (!domerge) { |
| CTR3(KTR_IGMPV3, "%s: queueing %p to ifscq %p)", |
| __func__, m0, ifscq); |
| _IF_ENQUEUE(ifscq, m0); |
| } else { |
| struct mbuf *mtl; /* last mbuf of packet mt */ |
| |
| CTR3(KTR_IGMPV3, "%s: merging %p with ifscq tail %p)", |
| __func__, m0, mt); |
| |
| mtl = m_last(mt); |
| m0->m_flags &= ~M_PKTHDR; |
| mt->m_pkthdr.len += recslen; |
| mt->m_pkthdr.PH_vt.vt_nrecs += |
| m0->m_pkthdr.PH_vt.vt_nrecs; |
| |
| mtl->m_next = m0; |
| } |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Respond to a pending IGMPv3 General Query. |
| */ |
| static void |
| igmp_v3_dispatch_general_query(struct igmp_ifinfo *igi) |
| { |
| struct ifmultiaddr *ifma, *tifma; |
| struct ifnet *ifp; |
| struct in_multi *inm; |
| int retval, loop; |
| |
| IN_MULTI_LOCK_ASSERT(); |
| IGMP_LOCK_ASSERT(); |
| |
| KASSERT(igi->igi_version == IGMP_VERSION_3, |
| ("%s: called when version %d", __func__, igi->igi_version)); |
| |
| ifp = igi->igi_ifp; |
| |
| IF_ADDR_LOCK(ifp); |
| TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, tifma) { |
| if (ifma->ifma_addr->sa_family != AF_INET || |
| ifma->ifma_protospec == NULL) |
| continue; |
| |
| inm = (struct in_multi *)ifma->ifma_protospec; |
| KASSERT(ifp == inm->inm_ifp, |
| ("%s: inconsistent ifp", __func__)); |
| |
| switch (inm->inm_state) { |
| case IGMP_NOT_MEMBER: |
| case IGMP_SILENT_MEMBER: |
| break; |
| case IGMP_REPORTING_MEMBER: |
| case IGMP_IDLE_MEMBER: |
| case IGMP_LAZY_MEMBER: |
| case IGMP_SLEEPING_MEMBER: |
| case IGMP_AWAKENING_MEMBER: |
| inm->inm_state = IGMP_REPORTING_MEMBER; |
| retval = igmp_v3_enqueue_group_record(&igi->igi_gq, |
| inm, 0, 0, 0); |
| CTR2(KTR_IGMPV3, "%s: enqueue record = %d", |
| __func__, retval); |
| break; |
| case IGMP_G_QUERY_PENDING_MEMBER: |
| case IGMP_SG_QUERY_PENDING_MEMBER: |
| case IGMP_LEAVING_MEMBER: |
| break; |
| } |
| } |
| IF_ADDR_UNLOCK(ifp); |
| |
| loop = (igi->igi_flags & IGIF_LOOPBACK) ? 1 : 0; |
| igmp_dispatch_queue(&igi->igi_gq, IGMP_MAX_RESPONSE_BURST, loop); |
| |
| /* |
| * Slew transmission of bursts over 500ms intervals. |
| */ |
| if (igi->igi_gq.ifq_head != NULL) { |
| igi->igi_v3_timer = 1 + IGMP_RANDOM_DELAY( |
| IGMP_RESPONSE_BURST_INTERVAL); |
| V_interface_timers_running = 1; |
| } |
| } |
| |
| /* |
| * Transmit the next pending IGMP message in the output queue. |
| * |
| * We get called from netisr_processqueue(). A mutex private to igmpoq |
| * will be acquired and released around this routine. |
| * |
| * VIMAGE: Needs to store/restore vnet pointer on a per-mbuf-chain basis. |
| * MRT: Nothing needs to be done, as IGMP traffic is always local to |
| * a link and uses a link-scope multicast address. |
| */ |
| static void |
| igmp_intr(struct mbuf *m) |
| { |
| struct ip_moptions imo; |
| struct ifnet *ifp; |
| struct mbuf *ipopts, *m0; |
| int error; |
| uint32_t ifindex; |
| |
| CTR2(KTR_IGMPV3, "%s: transmit %p", __func__, m); |
| |
| /* |
| * Set VNET image pointer from enqueued mbuf chain |
| * before doing anything else. Whilst we use interface |
| * indexes to guard against interface detach, they are |
| * unique to each VIMAGE and must be retrieved. |
| */ |
| CURVNET_SET((struct vnet *)(m->m_pkthdr.header)); |
| ifindex = igmp_restore_context(m); |
| |
| /* |
| * Check if the ifnet still exists. This limits the scope of |
| * any race in the absence of a global ifp lock for low cost |
| * (an array lookup). |
| */ |
| ifp = ifnet_byindex(ifindex); |
| if (ifp == NULL) { |
| CTR3(KTR_IGMPV3, "%s: dropped %p as ifindex %u went away.", |
| __func__, m, ifindex); |
| m_freem(m); |
| IPSTAT_INC(ips_noroute); |
| goto out; |
| } |
| |
| ipopts = V_igmp_sendra ? m_raopt : NULL; |
| |
| imo.imo_multicast_ttl = 1; |
| imo.imo_multicast_vif = -1; |
| imo.imo_multicast_loop = (V_ip_mrouter != NULL); |
| |
| /* |
| * If the user requested that IGMP traffic be explicitly |
| * redirected to the loopback interface (e.g. they are running a |
| * MANET interface and the routing protocol needs to see the |
| * updates), handle this now. |
| */ |
| if (m->m_flags & M_IGMP_LOOP) |
| imo.imo_multicast_ifp = V_loif; |
| else |
| imo.imo_multicast_ifp = ifp; |
| |
| if (m->m_flags & M_IGMPV2) { |
| m0 = m; |
| } else { |
| m0 = igmp_v3_encap_report(ifp, m); |
| if (m0 == NULL) { |
| CTR2(KTR_IGMPV3, "%s: dropped %p", __func__, m); |
| m_freem(m); |
| IPSTAT_INC(ips_odropped); |
| goto out; |
| } |
| } |
| |
| igmp_scrub_context(m0); |
| m->m_flags &= ~(M_PROTOFLAGS); |
| m0->m_pkthdr.rcvif = V_loif; |
| #ifdef MAC |
| mac_netinet_igmp_send(ifp, m0); |
| #endif |
| error = ip_output(m0, ipopts, NULL, 0, &imo, NULL); |
| if (error) { |
| CTR3(KTR_IGMPV3, "%s: ip_output(%p) = %d", __func__, m0, error); |
| goto out; |
| } |
| |
| IGMPSTAT_INC(igps_snd_reports); |
| |
| out: |
| /* |
| * We must restore the existing vnet pointer before |
| * continuing as we are run from netisr context. |
| */ |
| CURVNET_RESTORE(); |
| } |
| |
| /* |
| * Encapsulate an IGMPv3 report. |
| * |
| * The internal mbuf flag M_IGMPV3_HDR is used to indicate that the mbuf |
| * chain has already had its IP/IGMPv3 header prepended. In this case |
| * the function will not attempt to prepend; the lengths and checksums |
| * will however be re-computed. |
| * |
| * Returns a pointer to the new mbuf chain head, or NULL if the |
| * allocation failed. |
| */ |
| static struct mbuf * |
| igmp_v3_encap_report(struct ifnet *ifp, struct mbuf *m) |
| { |
| struct igmp_report *igmp; |
| struct ip *ip; |
| int hdrlen, igmpreclen; |
| |
| KASSERT((m->m_flags & M_PKTHDR), |
| ("%s: mbuf chain %p is !M_PKTHDR", __func__, m)); |
| |
| igmpreclen = m_length(m, NULL); |
| hdrlen = sizeof(struct ip) + sizeof(struct igmp_report); |
| |
| if (m->m_flags & M_IGMPV3_HDR) { |
| igmpreclen -= hdrlen; |
| } else { |
| M_PREPEND(m, hdrlen, M_DONTWAIT); |
| if (m == NULL) |
| return (NULL); |
| m->m_flags |= M_IGMPV3_HDR; |
| } |
| |
| CTR2(KTR_IGMPV3, "%s: igmpreclen is %d", __func__, igmpreclen); |
| |
| m->m_data += sizeof(struct ip); |
| m->m_len -= sizeof(struct ip); |
| |
| igmp = mtod(m, struct igmp_report *); |
| igmp->ir_type = IGMP_v3_HOST_MEMBERSHIP_REPORT; |
| igmp->ir_rsv1 = 0; |
| igmp->ir_rsv2 = 0; |
| igmp->ir_numgrps = htons(m->m_pkthdr.PH_vt.vt_nrecs); |
| igmp->ir_cksum = 0; |
| igmp->ir_cksum = in_cksum(m, sizeof(struct igmp_report) + igmpreclen); |
| m->m_pkthdr.PH_vt.vt_nrecs = 0; |
| |
| m->m_data -= sizeof(struct ip); |
| m->m_len += sizeof(struct ip); |
| |
| ip = mtod(m, struct ip *); |
| ip->ip_tos = IPTOS_PREC_INTERNETCONTROL; |
| ip->ip_len = hdrlen + igmpreclen; |
| ip->ip_off = IP_DF; |
| ip->ip_p = IPPROTO_IGMP; |
| ip->ip_sum = 0; |
| |
| ip->ip_src.s_addr = INADDR_ANY; |
| |
| if (m->m_flags & M_IGMP_LOOP) { |
| struct in_ifaddr *ia; |
| |
| IFP_TO_IA(ifp, ia); |
| if (ia != NULL) { |
| ip->ip_src = ia->ia_addr.sin_addr; |
| ifa_free(&ia->ia_ifa); |
| } |
| } |
| |
| ip->ip_dst.s_addr = htonl(INADDR_ALLRPTS_GROUP); |
| |
| return (m); |
| } |
| |
| #ifdef KTR |
| static char * |
| igmp_rec_type_to_str(const int type) |
| { |
| |
| switch (type) { |
| case IGMP_CHANGE_TO_EXCLUDE_MODE: |
| return "TO_EX"; |
| break; |
| case IGMP_CHANGE_TO_INCLUDE_MODE: |
| return "TO_IN"; |
| break; |
| case IGMP_MODE_IS_EXCLUDE: |
| return "MODE_EX"; |
| break; |
| case IGMP_MODE_IS_INCLUDE: |
| return "MODE_IN"; |
| break; |
| case IGMP_ALLOW_NEW_SOURCES: |
| return "ALLOW_NEW"; |
| break; |
| case IGMP_BLOCK_OLD_SOURCES: |
| return "BLOCK_OLD"; |
| break; |
| default: |
| break; |
| } |
| return "unknown"; |
| } |
| #endif |
| |
| static void |
| igmp_init(void *unused ) |
| { |
| |
| CTR1(KTR_IGMPV3, "%s: initializing", __func__); |
| |
| IGMP_LOCK_INIT(); |
| |
| m_raopt = igmp_ra_alloc(); |
| |
| netisr_register(&igmp_nh); |
| } |
| SYSINIT(igmp_init, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, igmp_init, NULL); |
| |
| #if 0 |
| static void |
| igmp_uninit(void *unused ) |
| { |
| |
| CTR1(KTR_IGMPV3, "%s: tearing down", __func__); |
| |
| netisr_unregister(&igmp_nh); |
| |
| m_free(m_raopt); |
| m_raopt = NULL; |
| |
| IGMP_LOCK_DESTROY(); |
| } |
| SYSUNINIT(igmp_uninit, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, igmp_uninit, NULL); |
| |
| static void |
| vnet_igmp_init(const void *unused ) |
| { |
| |
| CTR1(KTR_IGMPV3, "%s: initializing", __func__); |
| |
| LIST_INIT(&V_igi_head); |
| } |
| //VNET_SYSINIT(vnet_igmp_init, SI_SUB_PSEUDO, SI_ORDER_ANY, vnet_igmp_init, NULL); |
| |
| static void |
| vnet_igmp_uninit(const void *unused ) |
| { |
| |
| CTR1(KTR_IGMPV3, "%s: tearing down", __func__); |
| |
| KASSERT(LIST_EMPTY(&V_igi_head), |
| ("%s: igi list not empty; ifnets not detached?", __func__)); |
| } |
| //VNET_SYSUNINIT(vnet_igmp_uninit, SI_SUB_PSEUDO, SI_ORDER_ANY, vnet_igmp_uninit, NULL); |
| #endif //0 |
| /* |
| static int |
| igmp_modevent(module_t mod, int type, void *unused ) |
| { |
| |
| switch (type) { |
| case MOD_LOAD: |
| case MOD_UNLOAD: |
| break; |
| default: |
| return (EOPNOTSUPP); |
| } |
| return (0); |
| } |
| |
| static moduledata_t igmp_mod = { |
| "igmp", |
| igmp_modevent, |
| 0 |
| }; |
| DECLARE_MODULE(igmp, igmp_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); |
| */ |