version 1.18.8.1, 2002/06/20 15:52:50 |
version 1.68, 2016/06/21 03:28:27 |
|
|
*/ |
*/ |
|
|
/* |
/* |
* Copyright (c) 1988 Stephen Deering. |
|
* Copyright (c) 1992, 1993 |
* Copyright (c) 1992, 1993 |
* The Regents of the University of California. All rights reserved. |
* The Regents of the University of California. All rights reserved. |
* |
* |
|
|
* 2. Redistributions in binary form must reproduce the above copyright |
* 2. Redistributions in binary form must reproduce the above copyright |
* notice, this list of conditions and the following disclaimer in the |
* notice, this list of conditions and the following disclaimer in the |
* documentation and/or other materials provided with the distribution. |
* documentation and/or other materials provided with the distribution. |
|
* 3. 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 |
|
*/ |
|
|
|
/* |
|
* Copyright (c) 1988 Stephen Deering. |
|
* |
|
* 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. |
* 3. All advertising materials mentioning features or use of this software |
* 3. All advertising materials mentioning features or use of this software |
* must display the following acknowledgement: |
* must display the following acknowledgement: |
* This product includes software developed by the University of |
* This product includes software developed by the University of |
|
|
#include <sys/cdefs.h> |
#include <sys/cdefs.h> |
__KERNEL_RCSID(0, "$NetBSD$"); |
__KERNEL_RCSID(0, "$NetBSD$"); |
|
|
|
#ifdef _KERNEL_OPT |
#include "opt_inet.h" |
#include "opt_inet.h" |
|
#endif |
|
|
#include <sys/param.h> |
#include <sys/param.h> |
#include <sys/systm.h> |
#include <sys/systm.h> |
#include <sys/mbuf.h> |
#include <sys/mbuf.h> |
#include <sys/socket.h> |
#include <sys/socket.h> |
|
#include <sys/socketvar.h> |
#include <sys/protosw.h> |
#include <sys/protosw.h> |
#include <sys/syslog.h> |
#include <sys/syslog.h> |
|
#include <sys/sysctl.h> |
|
#include <sys/kernel.h> |
|
#include <sys/callout.h> |
|
#include <sys/cprng.h> |
|
|
#include <net/if.h> |
#include <net/if.h> |
|
|
#include <netinet/in.h> |
#include <netinet/in.h> |
#include <netinet/in_var.h> |
#include <netinet/in_var.h> |
|
#include <netinet6/in6_var.h> |
#include <netinet/ip6.h> |
#include <netinet/ip6.h> |
#include <netinet6/ip6_var.h> |
#include <netinet6/ip6_var.h> |
|
#include <netinet6/scope6_var.h> |
#include <netinet/icmp6.h> |
#include <netinet/icmp6.h> |
|
#include <netinet6/icmp6_private.h> |
#include <netinet6/mld6_var.h> |
#include <netinet6/mld6_var.h> |
|
|
#include <net/net_osdep.h> |
#include <net/net_osdep.h> |
|
|
|
|
|
/* |
|
* This structure is used to keep track of in6_multi chains which belong to |
|
* deleted interface addresses. |
|
*/ |
|
static LIST_HEAD(, multi6_kludge) in6_mk = LIST_HEAD_INITIALIZER(in6_mk); |
|
|
|
struct multi6_kludge { |
|
LIST_ENTRY(multi6_kludge) mk_entry; |
|
struct ifnet *mk_ifp; |
|
struct in6_multihead mk_head; |
|
}; |
|
|
|
|
/* |
/* |
* Protocol constants |
* Protocol constants |
*/ |
*/ |
|
|
/* denotes that the MLD max response delay field specifies time in milliseconds */ |
|
#define MLD6_TIMER_SCALE 1000 |
|
/* |
/* |
* time between repetitions of a node's initial report of interest in a |
* time between repetitions of a node's initial report of interest in a |
* multicast address(in seconds) |
* multicast address(in seconds) |
*/ |
*/ |
#define MLD6_UNSOLICITED_REPORT_INTERVAL 10 |
#define MLD_UNSOLICITED_REPORT_INTERVAL 10 |
|
|
static struct ip6_pktopts ip6_opts; |
static struct ip6_pktopts ip6_opts; |
static int mld6_timers_are_running; |
|
/* XXX: These are necessary for KAME's link-local hack */ |
|
static struct in6_addr mld6_all_nodes_linklocal = IN6ADDR_LINKLOCAL_ALLNODES_INIT; |
|
static struct in6_addr mld6_all_routers_linklocal = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT; |
|
|
|
static void mld6_sendpkt __P((struct in6_multi *, int, const struct in6_addr *)); |
static void mld_start_listening(struct in6_multi *); |
|
static void mld_stop_listening(struct in6_multi *); |
|
|
|
static struct mld_hdr * mld_allocbuf(struct mbuf **, int, struct in6_multi *, |
|
int); |
|
static void mld_sendpkt(struct in6_multi *, int, const struct in6_addr *); |
|
static void mld_starttimer(struct in6_multi *); |
|
static void mld_stoptimer(struct in6_multi *); |
|
static u_long mld_timerresid(struct in6_multi *); |
|
|
void |
void |
mld6_init() |
mld_init(void) |
{ |
{ |
static u_int8_t hbh_buf[8]; |
static u_int8_t hbh_buf[8]; |
struct ip6_hbh *hbh = (struct ip6_hbh *)hbh_buf; |
struct ip6_hbh *hbh = (struct ip6_hbh *)hbh_buf; |
u_int16_t rtalert_code = htons((u_int16_t)IP6OPT_RTALERT_MLD); |
u_int16_t rtalert_code = htons((u_int16_t)IP6OPT_RTALERT_MLD); |
|
|
mld6_timers_are_running = 0; |
|
|
|
/* ip6h_nxt will be fill in later */ |
/* ip6h_nxt will be fill in later */ |
hbh->ip6h_len = 0; /* (8 >> 3) - 1 */ |
hbh->ip6h_len = 0; /* (8 >> 3) - 1 */ |
|
|
|
|
hbh_buf[3] = 0; |
hbh_buf[3] = 0; |
hbh_buf[4] = IP6OPT_RTALERT; |
hbh_buf[4] = IP6OPT_RTALERT; |
hbh_buf[5] = IP6OPT_RTALERT_LEN - 2; |
hbh_buf[5] = IP6OPT_RTALERT_LEN - 2; |
bcopy((caddr_t)&rtalert_code, &hbh_buf[6], sizeof(u_int16_t)); |
memcpy(&hbh_buf[6], (void *)&rtalert_code, sizeof(u_int16_t)); |
|
|
ip6_opts.ip6po_hbh = hbh; |
ip6_opts.ip6po_hbh = hbh; |
/* We will specify the hoplimit by a multicast option. */ |
/* We will specify the hoplimit by a multicast option. */ |
ip6_opts.ip6po_hlim = -1; |
ip6_opts.ip6po_hlim = -1; |
|
ip6_opts.ip6po_prefer_tempaddr = IP6PO_TEMPADDR_NOTPREFER; |
} |
} |
|
|
void |
static void |
mld6_start_listening(in6m) |
mld_starttimer(struct in6_multi *in6m) |
struct in6_multi *in6m; |
{ |
|
struct timeval now; |
|
|
|
KASSERT(in6m->in6m_timer != IN6M_TIMER_UNDEF); |
|
|
|
microtime(&now); |
|
in6m->in6m_timer_expire.tv_sec = now.tv_sec + in6m->in6m_timer / hz; |
|
in6m->in6m_timer_expire.tv_usec = now.tv_usec + |
|
(in6m->in6m_timer % hz) * (1000000 / hz); |
|
if (in6m->in6m_timer_expire.tv_usec > 1000000) { |
|
in6m->in6m_timer_expire.tv_sec++; |
|
in6m->in6m_timer_expire.tv_usec -= 1000000; |
|
} |
|
|
|
/* start or restart the timer */ |
|
callout_schedule(&in6m->in6m_timer_ch, in6m->in6m_timer); |
|
} |
|
|
|
static void |
|
mld_stoptimer(struct in6_multi *in6m) |
|
{ |
|
if (in6m->in6m_timer == IN6M_TIMER_UNDEF) |
|
return; |
|
|
|
callout_stop(&in6m->in6m_timer_ch); |
|
|
|
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
|
} |
|
|
|
static void |
|
mld_timeo(void *arg) |
|
{ |
|
struct in6_multi *in6m = arg; |
|
|
|
mutex_enter(softnet_lock); |
|
KERNEL_LOCK(1, NULL); |
|
|
|
if (in6m->in6m_timer == IN6M_TIMER_UNDEF) |
|
goto out; |
|
|
|
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
|
|
|
switch (in6m->in6m_state) { |
|
case MLD_REPORTPENDING: |
|
mld_start_listening(in6m); |
|
break; |
|
default: |
|
mld_sendpkt(in6m, MLD_LISTENER_REPORT, NULL); |
|
break; |
|
} |
|
|
|
out: |
|
KERNEL_UNLOCK_ONE(NULL); |
|
mutex_exit(softnet_lock); |
|
} |
|
|
|
static u_long |
|
mld_timerresid(struct in6_multi *in6m) |
{ |
{ |
int s = splsoftnet(); |
struct timeval now, diff; |
|
|
|
microtime(&now); |
|
|
|
if (now.tv_sec > in6m->in6m_timer_expire.tv_sec || |
|
(now.tv_sec == in6m->in6m_timer_expire.tv_sec && |
|
now.tv_usec > in6m->in6m_timer_expire.tv_usec)) { |
|
return (0); |
|
} |
|
diff = in6m->in6m_timer_expire; |
|
diff.tv_sec -= now.tv_sec; |
|
diff.tv_usec -= now.tv_usec; |
|
if (diff.tv_usec < 0) { |
|
diff.tv_sec--; |
|
diff.tv_usec += 1000000; |
|
} |
|
|
|
/* return the remaining time in milliseconds */ |
|
return diff.tv_sec * 1000 + diff.tv_usec / 1000; |
|
} |
|
|
|
static void |
|
mld_start_listening(struct in6_multi *in6m) |
|
{ |
|
struct in6_addr all_in6; |
|
|
/* |
/* |
* RFC2710 page 10: |
* RFC2710 page 10: |
Line 149 mld6_start_listening(in6m) |
|
Line 287 mld6_start_listening(in6m) |
|
* MLD messages are never sent for multicast addresses whose scope is 0 |
* MLD messages are never sent for multicast addresses whose scope is 0 |
* (reserved) or 1 (node-local). |
* (reserved) or 1 (node-local). |
*/ |
*/ |
mld6_all_nodes_linklocal.s6_addr16[1] = |
all_in6 = in6addr_linklocal_allnodes; |
htons(in6m->in6m_ifp->if_index); /* XXX */ |
if (in6_setscope(&all_in6, in6m->in6m_ifp, NULL)) { |
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &mld6_all_nodes_linklocal) || |
/* XXX: this should not happen! */ |
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < IPV6_ADDR_SCOPE_LINKLOCAL) { |
|
in6m->in6m_timer = 0; |
in6m->in6m_timer = 0; |
in6m->in6m_state = MLD6_OTHERLISTENER; |
in6m->in6m_state = MLD_OTHERLISTENER; |
|
} |
|
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_in6) || |
|
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < IPV6_ADDR_SCOPE_LINKLOCAL) { |
|
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
|
in6m->in6m_state = MLD_OTHERLISTENER; |
} else { |
} else { |
mld6_sendpkt(in6m, MLD6_LISTENER_REPORT, NULL); |
mld_sendpkt(in6m, MLD_LISTENER_REPORT, NULL); |
in6m->in6m_timer = |
in6m->in6m_timer = cprng_fast32() % |
MLD6_RANDOM_DELAY(MLD6_UNSOLICITED_REPORT_INTERVAL * |
(MLD_UNSOLICITED_REPORT_INTERVAL * hz); |
PR_FASTHZ); |
in6m->in6m_state = MLD_IREPORTEDLAST; |
in6m->in6m_state = MLD6_IREPORTEDLAST; |
|
mld6_timers_are_running = 1; |
mld_starttimer(in6m); |
} |
} |
splx(s); |
|
} |
} |
|
|
void |
static void |
mld6_stop_listening(in6m) |
mld_stop_listening(struct in6_multi *in6m) |
struct in6_multi *in6m; |
|
{ |
{ |
mld6_all_nodes_linklocal.s6_addr16[1] = |
struct in6_addr allnode, allrouter; |
htons(in6m->in6m_ifp->if_index); /* XXX */ |
|
mld6_all_routers_linklocal.s6_addr16[1] = |
allnode = in6addr_linklocal_allnodes; |
htons(in6m->in6m_ifp->if_index); /* XXX: necessary when mrouting */ |
if (in6_setscope(&allnode, in6m->in6m_ifp, NULL)) { |
|
/* XXX: this should not happen! */ |
if (in6m->in6m_state == MLD6_IREPORTEDLAST && |
return; |
(!IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &mld6_all_nodes_linklocal)) && |
} |
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) > IPV6_ADDR_SCOPE_NODELOCAL) |
allrouter = in6addr_linklocal_allrouters; |
mld6_sendpkt(in6m, MLD6_LISTENER_DONE, |
if (in6_setscope(&allrouter, in6m->in6m_ifp, NULL)) { |
&mld6_all_routers_linklocal); |
/* XXX impossible */ |
|
return; |
|
} |
|
|
|
if (in6m->in6m_state == MLD_IREPORTEDLAST && |
|
(!IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &allnode)) && |
|
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) > |
|
IPV6_ADDR_SCOPE_INTFACELOCAL) { |
|
mld_sendpkt(in6m, MLD_LISTENER_DONE, &allrouter); |
|
} |
} |
} |
|
|
void |
void |
mld6_input(m, off) |
mld_input(struct mbuf *m, int off) |
struct mbuf *m; |
{ |
int off; |
struct ip6_hdr *ip6; |
{ |
struct mld_hdr *mldh; |
struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); |
struct ifnet *ifp; |
struct mld6_hdr *mldh; |
struct in6_multi *in6m = NULL; |
struct ifnet *ifp = m->m_pkthdr.rcvif; |
struct in6_addr mld_addr, all_in6; |
struct in6_multi *in6m; |
|
struct in6_ifaddr *ia; |
struct in6_ifaddr *ia; |
int timer; /* timer value in the MLD query header */ |
u_long timer = 0; /* timer value in the MLD query header */ |
|
int s; |
|
|
#ifndef PULLDOWN_TEST |
ifp = m_get_rcvif(m, &s); |
IP6_EXTHDR_CHECK(m, off, sizeof(*mldh),); |
IP6_EXTHDR_GET(mldh, struct mld_hdr *, m, off, sizeof(*mldh)); |
mldh = (struct mld6_hdr *)(mtod(m, caddr_t) + off); |
|
#else |
|
IP6_EXTHDR_GET(mldh, struct mld6_hdr *, m, off, sizeof(*mldh)); |
|
if (mldh == NULL) { |
if (mldh == NULL) { |
icmp6stat.icp6s_tooshort++; |
ICMP6_STATINC(ICMP6_STAT_TOOSHORT); |
return; |
goto out_nodrop; |
} |
} |
#endif |
|
|
|
/* source address validation */ |
/* source address validation */ |
ip6 = mtod(m, struct ip6_hdr *);/* in case mpullup */ |
ip6 = mtod(m, struct ip6_hdr *);/* in case mpullup */ |
if (!IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) { |
if (!IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) { |
|
/* |
|
* RFC3590 allows the IPv6 unspecified address as the source |
|
* address of MLD report and done messages. However, as this |
|
* same document says, this special rule is for snooping |
|
* switches and the RFC requires routers to discard MLD packets |
|
* with the unspecified source address. The RFC only talks |
|
* about hosts receiving an MLD query or report in Security |
|
* Considerations, but this is probably the correct intention. |
|
* RFC3590 does not talk about other cases than link-local and |
|
* the unspecified source addresses, but we believe the same |
|
* rule should be applied. |
|
* As a result, we only allow link-local addresses as the |
|
* source address; otherwise, simply discard the packet. |
|
*/ |
#if 0 |
#if 0 |
log(LOG_ERR, |
|
"mld6_input: src %s is not link-local (grp=%s)\n", |
|
ip6_sprintf(&ip6->ip6_src), |
|
ip6_sprintf(&mldh->mld6_addr)); |
|
#endif |
|
/* |
/* |
* spec (RFC2710) does not explicitly |
* XXX: do not log in an input path to avoid log flooding, |
* specify to discard the packet from a non link-local |
* though RFC3590 says "SHOULD log" if the source of a query |
* source address. But we believe it's expected to do so. |
* is the unspecified address. |
* XXX: do we have to allow :: as source? |
|
*/ |
*/ |
m_freem(m); |
log(LOG_INFO, |
return; |
"mld_input: src %s is not link-local (grp=%s)\n", |
|
ip6_sprintf(&ip6->ip6_src), ip6_sprintf(&mldh->mld_addr)); |
|
#endif |
|
goto out; |
} |
} |
|
|
/* |
/* |
* In the MLD6 specification, there are 3 states and a flag. |
* make a copy for local work (in6_setscope() may modify the 1st arg) |
|
*/ |
|
mld_addr = mldh->mld_addr; |
|
if (in6_setscope(&mld_addr, ifp, NULL)) { |
|
/* XXX: this should not happen! */ |
|
goto out; |
|
} |
|
|
|
/* |
|
* In the MLD specification, there are 3 states and a flag. |
* |
* |
* In Non-Listener state, we simply don't have a membership record. |
* In Non-Listener state, we simply don't have a membership record. |
* In Delaying Listener state, our timer is running (in6m->in6m_timer) |
* In Delaying Listener state, our timer is running (in6m->in6m_timer) |
* In Idle Listener state, our timer is not running (in6m->in6m_timer==0) |
* In Idle Listener state, our timer is not running |
|
* (in6m->in6m_timer==IN6M_TIMER_UNDEF) |
* |
* |
* The flag is in6m->in6m_state, it is set to MLD6_OTHERLISTENER if |
* The flag is in6m->in6m_state, it is set to MLD_OTHERLISTENER if |
* we have heard a report from another member, or MLD6_IREPORTEDLAST |
* we have heard a report from another member, or MLD_IREPORTEDLAST |
* if we sent the last report. |
* if we sent the last report. |
*/ |
*/ |
switch (mldh->mld6_type) { |
switch (mldh->mld_type) { |
case MLD6_LISTENER_QUERY: |
case MLD_LISTENER_QUERY: |
if (ifp->if_flags & IFF_LOOPBACK) |
if (ifp->if_flags & IFF_LOOPBACK) |
break; |
break; |
|
|
if (!IN6_IS_ADDR_UNSPECIFIED(&mldh->mld6_addr) && |
if (!IN6_IS_ADDR_UNSPECIFIED(&mld_addr) && |
!IN6_IS_ADDR_MULTICAST(&mldh->mld6_addr)) |
!IN6_IS_ADDR_MULTICAST(&mld_addr)) |
break; /* print error or log stat? */ |
break; /* print error or log stat? */ |
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld6_addr)) |
|
mldh->mld6_addr.s6_addr16[1] = |
all_in6 = in6addr_linklocal_allnodes; |
htons(ifp->if_index); /* XXX */ |
if (in6_setscope(&all_in6, ifp, NULL)) { |
|
/* XXX: this should not happen! */ |
|
break; |
|
} |
|
|
/* |
/* |
* - Start the timers in all of our membership records |
* - Start the timers in all of our membership records |
Line 253 mld6_input(m, off) |
|
Line 422 mld6_input(m, off) |
|
* which the query arrived excl. those that belong |
* which the query arrived excl. those that belong |
* to the "all-nodes" group (ff02::1). |
* to the "all-nodes" group (ff02::1). |
* - Restart any timer that is already running but has |
* - Restart any timer that is already running but has |
* A value longer than the requested timeout. |
* a value longer than the requested timeout. |
* - Use the value specified in the query message as |
* - Use the value specified in the query message as |
* the maximum timeout. |
* the maximum timeout. |
*/ |
*/ |
|
timer = ntohs(mldh->mld_maxdelay); |
|
|
IFP_TO_IA6(ifp, ia); |
IFP_TO_IA6(ifp, ia); |
if (ia == NULL) |
if (ia == NULL) |
break; |
break; |
|
|
/* |
LIST_FOREACH(in6m, &ia->ia6_multiaddrs, in6m_entry) { |
* XXX: System timer resolution is too low to handle Max |
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_in6) || |
* Response Delay, so set 1 to the internal timer even if |
|
* the calculated value equals to zero when Max Response |
|
* Delay is positive. |
|
*/ |
|
timer = ntohs(mldh->mld6_maxdelay)*PR_FASTHZ/MLD6_TIMER_SCALE; |
|
if (timer == 0 && mldh->mld6_maxdelay) |
|
timer = 1; |
|
mld6_all_nodes_linklocal.s6_addr16[1] = |
|
htons(ifp->if_index); /* XXX */ |
|
|
|
for (in6m = ia->ia6_multiaddrs.lh_first; |
|
in6m; |
|
in6m = in6m->in6m_entry.le_next) |
|
{ |
|
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, |
|
&mld6_all_nodes_linklocal) || |
|
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < |
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < |
IPV6_ADDR_SCOPE_LINKLOCAL) |
IPV6_ADDR_SCOPE_LINKLOCAL) |
continue; |
continue; |
|
|
if (IN6_IS_ADDR_UNSPECIFIED(&mldh->mld6_addr) || |
if (in6m->in6m_state == MLD_REPORTPENDING) |
IN6_ARE_ADDR_EQUAL(&mldh->mld6_addr, |
continue; /* we are not yet ready */ |
&in6m->in6m_addr)) |
|
{ |
if (!IN6_IS_ADDR_UNSPECIFIED(&mld_addr) && |
if (timer == 0) { |
!IN6_ARE_ADDR_EQUAL(&mld_addr, &in6m->in6m_addr)) |
/* send a report immediately */ |
continue; |
mld6_sendpkt(in6m, MLD6_LISTENER_REPORT, |
|
NULL); |
if (timer == 0) { |
in6m->in6m_timer = 0; /* reset timer */ |
/* send a report immediately */ |
in6m->in6m_state = MLD6_IREPORTEDLAST; |
mld_stoptimer(in6m); |
} |
mld_sendpkt(in6m, MLD_LISTENER_REPORT, NULL); |
else if (in6m->in6m_timer == 0 || /*idle state*/ |
in6m->in6m_state = MLD_IREPORTEDLAST; |
in6m->in6m_timer > timer) { |
} else if (in6m->in6m_timer == IN6M_TIMER_UNDEF || |
in6m->in6m_timer = |
mld_timerresid(in6m) > timer) { |
MLD6_RANDOM_DELAY(timer); |
in6m->in6m_timer = |
mld6_timers_are_running = 1; |
1 + (cprng_fast32() % timer) * hz / 1000; |
} |
mld_starttimer(in6m); |
} |
} |
} |
} |
|
|
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld6_addr)) |
|
mldh->mld6_addr.s6_addr16[1] = 0; /* XXX */ |
|
break; |
break; |
case MLD6_LISTENER_REPORT: |
|
|
case MLD_LISTENER_REPORT: |
/* |
/* |
* For fast leave to work, we have to know that we are the |
* For fast leave to work, we have to know that we are the |
* last person to send a report for this group. Reports |
* last person to send a report for this group. Reports |
Line 319 mld6_input(m, off) |
|
Line 472 mld6_input(m, off) |
|
if (m->m_flags & M_LOOP) /* XXX: grotty flag, but efficient */ |
if (m->m_flags & M_LOOP) /* XXX: grotty flag, but efficient */ |
break; |
break; |
|
|
if (!IN6_IS_ADDR_MULTICAST(&mldh->mld6_addr)) |
if (!IN6_IS_ADDR_MULTICAST(&mldh->mld_addr)) |
break; |
break; |
|
|
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld6_addr)) |
|
mldh->mld6_addr.s6_addr16[1] = |
|
htons(ifp->if_index); /* XXX */ |
|
/* |
/* |
* If we belong to the group being reported, stop |
* If we belong to the group being reported, stop |
* our timer for that group. |
* our timer for that group. |
*/ |
*/ |
IN6_LOOKUP_MULTI(mldh->mld6_addr, ifp, in6m); |
IN6_LOOKUP_MULTI(mld_addr, ifp, in6m); |
if (in6m) { |
if (in6m) { |
in6m->in6m_timer = 0; /* transit to idle state */ |
mld_stoptimer(in6m); /* transit to idle state */ |
in6m->in6m_state = MLD6_OTHERLISTENER; /* clear flag */ |
in6m->in6m_state = MLD_OTHERLISTENER; /* clear flag */ |
} |
} |
|
|
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld6_addr)) |
|
mldh->mld6_addr.s6_addr16[1] = 0; /* XXX */ |
|
break; |
break; |
default: /* this is impossible */ |
default: /* this is impossible */ |
#if 0 |
#if 0 |
Line 345 mld6_input(m, off) |
|
Line 492 mld6_input(m, off) |
|
* icmp6_input(). But we explicitly disabled this part |
* icmp6_input(). But we explicitly disabled this part |
* just in case. |
* just in case. |
*/ |
*/ |
log(LOG_ERR, "mld6_input: illegal type(%d)", mldh->mld6_type); |
log(LOG_ERR, "mld_input: illegal type(%d)", mldh->mld_type); |
#endif |
#endif |
break; |
break; |
} |
} |
|
|
|
out: |
m_freem(m); |
m_freem(m); |
} |
out_nodrop: |
|
m_put_rcvif(ifp, &s); |
void |
|
mld6_fasttimeo() |
|
{ |
|
struct in6_multi *in6m; |
|
struct in6_multistep step; |
|
int s; |
|
/* |
|
* Quick check to see if any work needs to be done, in order |
|
* to minimize the overhead of fasttimo processing. |
|
*/ |
|
if (!mld6_timers_are_running) |
|
return; |
|
|
|
s = splsoftnet(); |
|
mld6_timers_are_running = 0; |
|
IN6_FIRST_MULTI(step, in6m); |
|
while (in6m != NULL) { |
|
if (in6m->in6m_timer == 0) { |
|
/* do nothing */ |
|
} else if (--in6m->in6m_timer == 0) { |
|
mld6_sendpkt(in6m, MLD6_LISTENER_REPORT, NULL); |
|
in6m->in6m_state = MLD6_IREPORTEDLAST; |
|
} else { |
|
mld6_timers_are_running = 1; |
|
} |
|
IN6_NEXT_MULTI(step, in6m); |
|
} |
|
splx(s); |
|
} |
} |
|
|
static void |
static void |
mld6_sendpkt(in6m, type, dst) |
mld_sendpkt(struct in6_multi *in6m, int type, |
struct in6_multi *in6m; |
const struct in6_addr *dst) |
int type; |
|
const struct in6_addr *dst; |
|
{ |
{ |
struct mbuf *mh, *md; |
struct mbuf *mh; |
struct mld6_hdr *mldh; |
struct mld_hdr *mldh; |
struct ip6_hdr *ip6; |
struct ip6_hdr *ip6 = NULL; |
struct ip6_moptions im6o; |
struct ip6_moptions im6o; |
struct in6_ifaddr *ia; |
struct in6_ifaddr *ia = NULL; |
struct ifnet *ifp = in6m->in6m_ifp; |
struct ifnet *ifp = in6m->in6m_ifp; |
int ignflags; |
int ignflags; |
|
|
Line 409 mld6_sendpkt(in6m, type, dst) |
|
Line 527 mld6_sendpkt(in6m, type, dst) |
|
if ((ia->ia6_flags & IN6_IFF_TENTATIVE)) |
if ((ia->ia6_flags & IN6_IFF_TENTATIVE)) |
ia = NULL; |
ia = NULL; |
|
|
|
/* Allocate two mbufs to store IPv6 header and MLD header */ |
|
mldh = mld_allocbuf(&mh, sizeof(struct mld_hdr), in6m, type); |
|
if (mldh == NULL) |
|
return; |
|
|
|
/* fill src/dst here */ |
|
ip6 = mtod(mh, struct ip6_hdr *); |
|
ip6->ip6_src = ia ? ia->ia_addr.sin6_addr : in6addr_any; |
|
ip6->ip6_dst = dst ? *dst : in6m->in6m_addr; |
|
|
|
mldh->mld_addr = in6m->in6m_addr; |
|
in6_clearscope(&mldh->mld_addr); /* XXX */ |
|
mldh->mld_cksum = in6_cksum(mh, IPPROTO_ICMPV6, sizeof(struct ip6_hdr), |
|
sizeof(struct mld_hdr)); |
|
|
|
/* construct multicast option */ |
|
memset(&im6o, 0, sizeof(im6o)); |
|
im6o.im6o_multicast_if_index = if_get_index(ifp); |
|
im6o.im6o_multicast_hlim = 1; |
|
|
|
/* |
|
* Request loopback of the report if we are acting as a multicast |
|
* router, so that the process-level routing daemon can hear it. |
|
*/ |
|
im6o.im6o_multicast_loop = (ip6_mrouter != NULL); |
|
|
|
/* increment output statictics */ |
|
ICMP6_STATINC(ICMP6_STAT_OUTHIST + type); |
|
icmp6_ifstat_inc(ifp, ifs6_out_msg); |
|
switch (type) { |
|
case MLD_LISTENER_QUERY: |
|
icmp6_ifstat_inc(ifp, ifs6_out_mldquery); |
|
break; |
|
case MLD_LISTENER_REPORT: |
|
icmp6_ifstat_inc(ifp, ifs6_out_mldreport); |
|
break; |
|
case MLD_LISTENER_DONE: |
|
icmp6_ifstat_inc(ifp, ifs6_out_mlddone); |
|
break; |
|
} |
|
|
|
ip6_output(mh, &ip6_opts, NULL, ia ? 0 : IPV6_UNSPECSRC, |
|
&im6o, NULL, NULL); |
|
} |
|
|
|
static struct mld_hdr * |
|
mld_allocbuf(struct mbuf **mh, int len, struct in6_multi *in6m, |
|
int type) |
|
{ |
|
struct mbuf *md; |
|
struct mld_hdr *mldh; |
|
struct ip6_hdr *ip6; |
|
|
/* |
/* |
* Allocate mbufs to store ip6 header and MLD header. |
* Allocate mbufs to store ip6 header and MLD header. |
* We allocate 2 mbufs and make chain in advance because |
* We allocate 2 mbufs and make chain in advance because |
* it is more convenient when inserting the hop-by-hop option later. |
* it is more convenient when inserting the hop-by-hop option later. |
*/ |
*/ |
MGETHDR(mh, M_DONTWAIT, MT_HEADER); |
MGETHDR(*mh, M_DONTWAIT, MT_HEADER); |
if (mh == NULL) |
if (*mh == NULL) |
return; |
return NULL; |
MGET(md, M_DONTWAIT, MT_DATA); |
MGET(md, M_DONTWAIT, MT_DATA); |
if (md == NULL) { |
if (md == NULL) { |
m_free(mh); |
m_free(*mh); |
return; |
*mh = NULL; |
|
return NULL; |
} |
} |
mh->m_next = md; |
(*mh)->m_next = md; |
|
md->m_next = NULL; |
|
|
mh->m_pkthdr.rcvif = NULL; |
m_reset_rcvif((*mh)); |
mh->m_pkthdr.len = sizeof(struct ip6_hdr) + sizeof(struct mld6_hdr); |
(*mh)->m_pkthdr.len = sizeof(struct ip6_hdr) + len; |
mh->m_len = sizeof(struct ip6_hdr); |
(*mh)->m_len = sizeof(struct ip6_hdr); |
MH_ALIGN(mh, sizeof(struct ip6_hdr)); |
MH_ALIGN(*mh, sizeof(struct ip6_hdr)); |
|
|
/* fill in the ip6 header */ |
/* fill in the ip6 header */ |
ip6 = mtod(mh, struct ip6_hdr *); |
ip6 = mtod(*mh, struct ip6_hdr *); |
|
memset(ip6, 0, sizeof(*ip6)); |
ip6->ip6_flow = 0; |
ip6->ip6_flow = 0; |
ip6->ip6_vfc &= ~IPV6_VERSION_MASK; |
ip6->ip6_vfc &= ~IPV6_VERSION_MASK; |
ip6->ip6_vfc |= IPV6_VERSION; |
ip6->ip6_vfc |= IPV6_VERSION; |
/* ip6_plen will be set later */ |
/* ip6_plen will be set later */ |
ip6->ip6_nxt = IPPROTO_ICMPV6; |
ip6->ip6_nxt = IPPROTO_ICMPV6; |
/* ip6_hlim will be set by im6o.im6o_multicast_hlim */ |
/* ip6_hlim will be set by im6o.im6o_multicast_hlim */ |
ip6->ip6_src = ia ? ia->ia_addr.sin6_addr : in6addr_any; |
/* ip6_src/dst will be set by mld_sendpkt() or mld_sendbuf() */ |
ip6->ip6_dst = dst ? *dst : in6m->in6m_addr; |
|
|
|
/* fill in the MLD header */ |
/* fill in the MLD header as much as possible */ |
md->m_len = sizeof(struct mld6_hdr); |
md->m_len = len; |
mldh = mtod(md, struct mld6_hdr *); |
mldh = mtod(md, struct mld_hdr *); |
mldh->mld6_type = type; |
memset(mldh, 0, len); |
mldh->mld6_code = 0; |
mldh->mld_type = type; |
mldh->mld6_cksum = 0; |
return mldh; |
/* XXX: we assume the function will not be called for query messages */ |
} |
mldh->mld6_maxdelay = 0; |
|
mldh->mld6_reserved = 0; |
|
mldh->mld6_addr = in6m->in6m_addr; |
|
if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld6_addr)) |
|
mldh->mld6_addr.s6_addr16[1] = 0; /* XXX */ |
|
mldh->mld6_cksum = in6_cksum(mh, IPPROTO_ICMPV6, |
|
sizeof(struct ip6_hdr), |
|
sizeof(struct mld6_hdr)); |
|
|
|
/* construct multicast option */ |
/* |
bzero(&im6o, sizeof(im6o)); |
* Add an address to the list of IP6 multicast addresses for a given interface. |
im6o.im6o_multicast_ifp = ifp; |
*/ |
im6o.im6o_multicast_hlim = 1; |
struct in6_multi * |
|
in6_addmulti(struct in6_addr *maddr6, struct ifnet *ifp, |
|
int *errorp, int timer) |
|
{ |
|
struct in6_ifaddr *ia; |
|
struct sockaddr_in6 sin6; |
|
struct in6_multi *in6m; |
|
int s = splsoftnet(); |
|
|
|
*errorp = 0; |
|
|
/* |
/* |
* Request loopback of the report if we are acting as a multicast |
* See if address already in list. |
* router, so that the process-level routing daemon can hear it. |
|
*/ |
*/ |
im6o.im6o_multicast_loop = (ip6_mrouter != NULL); |
IN6_LOOKUP_MULTI(*maddr6, ifp, in6m); |
|
if (in6m != NULL) { |
|
/* |
|
* Found it; just increment the refrence count. |
|
*/ |
|
in6m->in6m_refcount++; |
|
} else { |
|
/* |
|
* New address; allocate a new multicast record |
|
* and link it into the interface's multicast list. |
|
*/ |
|
in6m = (struct in6_multi *) |
|
malloc(sizeof(*in6m), M_IPMADDR, M_NOWAIT|M_ZERO); |
|
if (in6m == NULL) { |
|
splx(s); |
|
*errorp = ENOBUFS; |
|
return (NULL); |
|
} |
|
|
/* increment output statictics */ |
in6m->in6m_addr = *maddr6; |
icmp6stat.icp6s_outhist[type]++; |
in6m->in6m_ifp = ifp; |
icmp6_ifstat_inc(ifp, ifs6_out_msg); |
in6m->in6m_refcount = 1; |
switch (type) { |
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
case MLD6_LISTENER_QUERY: |
callout_init(&in6m->in6m_timer_ch, CALLOUT_MPSAFE); |
icmp6_ifstat_inc(ifp, ifs6_out_mldquery); |
callout_setfunc(&in6m->in6m_timer_ch, mld_timeo, in6m); |
break; |
|
case MLD6_LISTENER_REPORT: |
IFP_TO_IA6(ifp, ia); |
icmp6_ifstat_inc(ifp, ifs6_out_mldreport); |
if (ia == NULL) { |
break; |
callout_destroy(&in6m->in6m_timer_ch); |
case MLD6_LISTENER_DONE: |
free(in6m, M_IPMADDR); |
icmp6_ifstat_inc(ifp, ifs6_out_mlddone); |
splx(s); |
break; |
*errorp = EADDRNOTAVAIL; /* appropriate? */ |
|
return (NULL); |
|
} |
|
in6m->in6m_ia = ia; |
|
ifaref(&ia->ia_ifa); /* gain a reference */ |
|
LIST_INSERT_HEAD(&ia->ia6_multiaddrs, in6m, in6m_entry); |
|
|
|
/* |
|
* Ask the network driver to update its multicast reception |
|
* filter appropriately for the new address. |
|
*/ |
|
sockaddr_in6_init(&sin6, maddr6, 0, 0, 0); |
|
*errorp = if_mcast_op(ifp, SIOCADDMULTI, sin6tosa(&sin6)); |
|
if (*errorp) { |
|
callout_destroy(&in6m->in6m_timer_ch); |
|
LIST_REMOVE(in6m, in6m_entry); |
|
free(in6m, M_IPMADDR); |
|
ifafree(&ia->ia_ifa); |
|
splx(s); |
|
return (NULL); |
|
} |
|
|
|
in6m->in6m_timer = timer; |
|
if (in6m->in6m_timer > 0) { |
|
in6m->in6m_state = MLD_REPORTPENDING; |
|
mld_starttimer(in6m); |
|
|
|
splx(s); |
|
return (in6m); |
|
} |
|
|
|
/* |
|
* Let MLD6 know that we have joined a new IP6 multicast |
|
* group. |
|
*/ |
|
mld_start_listening(in6m); |
|
} |
|
splx(s); |
|
return (in6m); |
|
} |
|
|
|
/* |
|
* Delete a multicast address record. |
|
*/ |
|
void |
|
in6_delmulti(struct in6_multi *in6m) |
|
{ |
|
struct sockaddr_in6 sin6; |
|
struct in6_ifaddr *ia; |
|
int s = splsoftnet(); |
|
|
|
mld_stoptimer(in6m); |
|
|
|
if (--in6m->in6m_refcount == 0) { |
|
/* |
|
* No remaining claims to this record; let MLD6 know |
|
* that we are leaving the multicast group. |
|
*/ |
|
mld_stop_listening(in6m); |
|
|
|
/* |
|
* Unlink from list. |
|
*/ |
|
LIST_REMOVE(in6m, in6m_entry); |
|
if (in6m->in6m_ia != NULL) { |
|
ifafree(&in6m->in6m_ia->ia_ifa); /* release reference */ |
|
in6m->in6m_ia = NULL; |
|
} |
|
|
|
/* |
|
* Delete all references of this multicasting group from |
|
* the membership arrays |
|
*/ |
|
for (ia = in6_ifaddr; ia; ia = ia->ia_next) { |
|
struct in6_multi_mship *imm; |
|
LIST_FOREACH(imm, &ia->ia6_memberships, i6mm_chain) { |
|
if (imm->i6mm_maddr == in6m) |
|
imm->i6mm_maddr = NULL; |
|
} |
|
} |
|
|
|
/* |
|
* Notify the network driver to update its multicast |
|
* reception filter. |
|
*/ |
|
sockaddr_in6_init(&sin6, &in6m->in6m_addr, 0, 0, 0); |
|
if_mcast_op(in6m->in6m_ifp, SIOCDELMULTI, sin6tosa(&sin6)); |
|
|
|
/* Tell mld_timeo we're halting the timer */ |
|
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
|
callout_halt(&in6m->in6m_timer_ch, softnet_lock); |
|
callout_destroy(&in6m->in6m_timer_ch); |
|
|
|
free(in6m, M_IPMADDR); |
|
} |
|
splx(s); |
|
} |
|
|
|
|
|
struct in6_multi_mship * |
|
in6_joingroup(struct ifnet *ifp, struct in6_addr *addr, |
|
int *errorp, int timer) |
|
{ |
|
struct in6_multi_mship *imm; |
|
|
|
imm = malloc(sizeof(*imm), M_IPMADDR, M_NOWAIT|M_ZERO); |
|
if (imm == NULL) { |
|
*errorp = ENOBUFS; |
|
return NULL; |
|
} |
|
|
|
imm->i6mm_maddr = in6_addmulti(addr, ifp, errorp, timer); |
|
if (!imm->i6mm_maddr) { |
|
/* *errorp is already set */ |
|
free(imm, M_IPMADDR); |
|
return NULL; |
} |
} |
|
return imm; |
|
} |
|
|
|
int |
|
in6_leavegroup(struct in6_multi_mship *imm) |
|
{ |
|
|
|
if (imm->i6mm_maddr) { |
|
in6_delmulti(imm->i6mm_maddr); |
|
} |
|
free(imm, M_IPMADDR); |
|
return 0; |
|
} |
|
|
|
|
|
/* |
|
* Multicast address kludge: |
|
* If there were any multicast addresses attached to this interface address, |
|
* either move them to another address on this interface, or save them until |
|
* such time as this interface is reconfigured for IPv6. |
|
*/ |
|
void |
|
in6_savemkludge(struct in6_ifaddr *oia) |
|
{ |
|
struct in6_ifaddr *ia; |
|
struct in6_multi *in6m; |
|
|
|
IFP_TO_IA6(oia->ia_ifp, ia); |
|
if (ia) { /* there is another address */ |
|
KASSERT(ia != oia); |
|
while ((in6m = LIST_FIRST(&oia->ia6_multiaddrs)) != NULL) { |
|
LIST_REMOVE(in6m, in6m_entry); |
|
ifaref(&ia->ia_ifa); |
|
ifafree(&in6m->in6m_ia->ia_ifa); |
|
in6m->in6m_ia = ia; |
|
LIST_INSERT_HEAD(&ia->ia6_multiaddrs, in6m, in6m_entry); |
|
} |
|
} else { /* last address on this if deleted, save */ |
|
struct multi6_kludge *mk; |
|
|
|
LIST_FOREACH(mk, &in6_mk, mk_entry) { |
|
if (mk->mk_ifp == oia->ia_ifp) |
|
break; |
|
} |
|
if (mk == NULL) /* this should not happen! */ |
|
panic("in6_savemkludge: no kludge space"); |
|
|
|
while ((in6m = LIST_FIRST(&oia->ia6_multiaddrs)) != NULL) { |
|
LIST_REMOVE(in6m, in6m_entry); |
|
ifafree(&in6m->in6m_ia->ia_ifa); /* release reference */ |
|
in6m->in6m_ia = NULL; |
|
LIST_INSERT_HEAD(&mk->mk_head, in6m, in6m_entry); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* Continuation of multicast address hack: |
|
* If there was a multicast group list previously saved for this interface, |
|
* then we re-attach it to the first address configured on the i/f. |
|
*/ |
|
void |
|
in6_restoremkludge(struct in6_ifaddr *ia, struct ifnet *ifp) |
|
{ |
|
struct multi6_kludge *mk; |
|
struct in6_multi *in6m; |
|
|
|
LIST_FOREACH(mk, &in6_mk, mk_entry) { |
|
if (mk->mk_ifp == ifp) |
|
break; |
|
} |
|
if (mk == NULL) |
|
return; |
|
while ((in6m = LIST_FIRST(&mk->mk_head)) != NULL) { |
|
LIST_REMOVE(in6m, in6m_entry); |
|
in6m->in6m_ia = ia; |
|
ifaref(&ia->ia_ifa); |
|
LIST_INSERT_HEAD(&ia->ia6_multiaddrs, in6m, in6m_entry); |
|
} |
|
} |
|
|
|
/* |
|
* Allocate space for the kludge at interface initialization time. |
|
* Formerly, we dynamically allocated the space in in6_savemkludge() with |
|
* malloc(M_WAITOK). However, it was wrong since the function could be called |
|
* under an interrupt context (software timer on address lifetime expiration). |
|
* Also, we cannot just give up allocating the strucutre, since the group |
|
* membership structure is very complex and we need to keep it anyway. |
|
* Of course, this function MUST NOT be called under an interrupt context. |
|
* Specifically, it is expected to be called only from in6_ifattach(), though |
|
* it is a global function. |
|
*/ |
|
void |
|
in6_createmkludge(struct ifnet *ifp) |
|
{ |
|
struct multi6_kludge *mk; |
|
|
|
LIST_FOREACH(mk, &in6_mk, mk_entry) { |
|
/* If we've already had one, do not allocate. */ |
|
if (mk->mk_ifp == ifp) |
|
return; |
|
} |
|
|
|
mk = malloc(sizeof(*mk), M_IPMADDR, M_ZERO|M_WAITOK); |
|
|
|
LIST_INIT(&mk->mk_head); |
|
mk->mk_ifp = ifp; |
|
LIST_INSERT_HEAD(&in6_mk, mk, mk_entry); |
|
} |
|
|
|
void |
|
in6_purgemkludge(struct ifnet *ifp) |
|
{ |
|
struct multi6_kludge *mk; |
|
struct in6_multi *in6m, *next; |
|
|
|
LIST_FOREACH(mk, &in6_mk, mk_entry) { |
|
if (mk->mk_ifp == ifp) |
|
break; |
|
} |
|
if (mk == NULL) |
|
return; |
|
|
|
/* leave from all multicast groups joined */ |
|
for (in6m = LIST_FIRST(&mk->mk_head); in6m != NULL; in6m = next) { |
|
next = LIST_NEXT(in6m, in6m_entry); |
|
in6_delmulti(in6m); |
|
} |
|
LIST_REMOVE(mk, mk_entry); |
|
free(mk, M_IPMADDR); |
|
} |
|
|
|
static int |
|
in6_mkludge_sysctl(SYSCTLFN_ARGS) |
|
{ |
|
struct multi6_kludge *mk; |
|
struct in6_multi *in6m; |
|
int error; |
|
uint32_t tmp; |
|
size_t written; |
|
|
|
if (namelen != 1) |
|
return EINVAL; |
|
|
|
if (oldp == NULL) { |
|
*oldlenp = 0; |
|
LIST_FOREACH(mk, &in6_mk, mk_entry) { |
|
if (mk->mk_ifp->if_index == name[0]) |
|
continue; |
|
LIST_FOREACH(in6m, &mk->mk_head, in6m_entry) { |
|
*oldlenp += sizeof(struct in6_addr) + |
|
sizeof(uint32_t); |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
error = 0; |
|
written = 0; |
|
LIST_FOREACH(mk, &in6_mk, mk_entry) { |
|
if (mk->mk_ifp->if_index == name[0]) |
|
continue; |
|
LIST_FOREACH(in6m, &mk->mk_head, in6m_entry) { |
|
if (written + sizeof(struct in6_addr) + |
|
sizeof(uint32_t) > *oldlenp) |
|
goto done; |
|
error = sysctl_copyout(l, &in6m->in6m_addr, |
|
oldp, sizeof(struct in6_addr)); |
|
if (error) |
|
goto done; |
|
oldp = (char *)oldp + sizeof(struct in6_addr); |
|
written += sizeof(struct in6_addr); |
|
tmp = in6m->in6m_refcount; |
|
error = sysctl_copyout(l, &tmp, oldp, sizeof(tmp)); |
|
if (error) |
|
goto done; |
|
oldp = (char *)oldp + sizeof(tmp); |
|
written += sizeof(tmp); |
|
} |
|
} |
|
|
|
done: |
|
*oldlenp = written; |
|
return error; |
|
} |
|
|
|
static int |
|
in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
{ |
|
struct ifnet *ifp; |
|
struct ifaddr *ifa; |
|
struct in6_ifaddr *ifa6; |
|
struct in6_multi *in6m; |
|
uint32_t tmp; |
|
int error; |
|
size_t written; |
|
struct psref psref; |
|
int bound; |
|
|
|
if (namelen != 1) |
|
return EINVAL; |
|
|
|
bound = curlwp_bind(); |
|
ifp = if_get_byindex(name[0], &psref); |
|
if (ifp == NULL) { |
|
curlwp_bindx(bound); |
|
return ENODEV; |
|
} |
|
|
|
if (oldp == NULL) { |
|
*oldlenp = 0; |
|
IFADDR_FOREACH(ifa, ifp) { |
|
if (ifa->ifa_addr == NULL) |
|
continue; |
|
if (ifa->ifa_addr->sa_family != AF_INET6) |
|
continue; |
|
ifa6 = (struct in6_ifaddr *)ifa; |
|
LIST_FOREACH(in6m, &ifa6->ia6_multiaddrs, in6m_entry) { |
|
*oldlenp += 2 * sizeof(struct in6_addr) + |
|
sizeof(uint32_t); |
|
} |
|
} |
|
if_put(ifp, &psref); |
|
curlwp_bindx(bound); |
|
return 0; |
|
} |
|
|
|
error = 0; |
|
written = 0; |
|
IFADDR_FOREACH(ifa, ifp) { |
|
if (ifa->ifa_addr == NULL) |
|
continue; |
|
if (ifa->ifa_addr->sa_family != AF_INET6) |
|
continue; |
|
ifa6 = (struct in6_ifaddr *)ifa; |
|
LIST_FOREACH(in6m, &ifa6->ia6_multiaddrs, in6m_entry) { |
|
if (written + 2 * sizeof(struct in6_addr) + |
|
sizeof(uint32_t) > *oldlenp) |
|
goto done; |
|
error = sysctl_copyout(l, &ifa6->ia_addr.sin6_addr, |
|
oldp, sizeof(struct in6_addr)); |
|
if (error) |
|
goto done; |
|
oldp = (char *)oldp + sizeof(struct in6_addr); |
|
written += sizeof(struct in6_addr); |
|
error = sysctl_copyout(l, &in6m->in6m_addr, |
|
oldp, sizeof(struct in6_addr)); |
|
if (error) |
|
goto done; |
|
oldp = (char *)oldp + sizeof(struct in6_addr); |
|
written += sizeof(struct in6_addr); |
|
tmp = in6m->in6m_refcount; |
|
error = sysctl_copyout(l, &tmp, oldp, sizeof(tmp)); |
|
if (error) |
|
goto done; |
|
oldp = (char *)oldp + sizeof(tmp); |
|
written += sizeof(tmp); |
|
} |
|
} |
|
done: |
|
if_put(ifp, &psref); |
|
curlwp_bindx(bound); |
|
*oldlenp = written; |
|
return error; |
|
} |
|
|
|
SYSCTL_SETUP(sysctl_in6_mklude_setup, "sysctl net.inet6.multicast_kludge subtree setup") |
|
{ |
|
|
ip6_output(mh, &ip6_opts, NULL, ia ? 0 : IPV6_UNSPECSRC, &im6o, NULL); |
sysctl_createv(clog, 0, NULL, NULL, |
|
CTLFLAG_PERMANENT, |
|
CTLTYPE_NODE, "inet6", NULL, |
|
NULL, 0, NULL, 0, |
|
CTL_NET, PF_INET6, CTL_EOL); |
|
|
|
sysctl_createv(clog, 0, NULL, NULL, |
|
CTLFLAG_PERMANENT, |
|
CTLTYPE_NODE, "multicast", |
|
SYSCTL_DESCR("Multicast information"), |
|
in6_multicast_sysctl, 0, NULL, 0, |
|
CTL_NET, PF_INET6, CTL_CREATE, CTL_EOL); |
|
|
|
sysctl_createv(clog, 0, NULL, NULL, |
|
CTLFLAG_PERMANENT, |
|
CTLTYPE_NODE, "multicast_kludge", |
|
SYSCTL_DESCR("multicast kludge information"), |
|
in6_mkludge_sysctl, 0, NULL, 0, |
|
CTL_NET, PF_INET6, CTL_CREATE, CTL_EOL); |
} |
} |