version 1.79, 2017/01/16 15:44:47 |
version 1.86, 2017/03/02 05:27:39 |
Line 119 __KERNEL_RCSID(0, "$NetBSD$"); |
|
Line 119 __KERNEL_RCSID(0, "$NetBSD$"); |
|
#include <sys/kernel.h> |
#include <sys/kernel.h> |
#include <sys/callout.h> |
#include <sys/callout.h> |
#include <sys/cprng.h> |
#include <sys/cprng.h> |
|
#include <sys/rwlock.h> |
|
|
#include <net/if.h> |
#include <net/if.h> |
|
|
Line 135 __KERNEL_RCSID(0, "$NetBSD$"); |
|
Line 136 __KERNEL_RCSID(0, "$NetBSD$"); |
|
#include <net/net_osdep.h> |
#include <net/net_osdep.h> |
|
|
|
|
/* |
static krwlock_t in6_multilock __cacheline_aligned; |
* 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 |
Line 170 static void mld_starttimer(struct in6_mu |
|
Line 160 static void mld_starttimer(struct in6_mu |
|
static void mld_stoptimer(struct in6_multi *); |
static void mld_stoptimer(struct in6_multi *); |
static u_long mld_timerresid(struct in6_multi *); |
static u_long mld_timerresid(struct in6_multi *); |
|
|
|
static void in6m_ref(struct in6_multi *); |
|
static void in6m_unref(struct in6_multi *); |
|
static void in6m_destroy(struct in6_multi *); |
|
|
void |
void |
mld_init(void) |
mld_init(void) |
{ |
{ |
|
|
/* 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; |
ip6_opts.ip6po_prefer_tempaddr = IP6PO_TEMPADDR_NOTPREFER; |
|
|
|
rw_init(&in6_multilock); |
} |
} |
|
|
static void |
static void |
Line 198 mld_starttimer(struct in6_multi *in6m) |
|
Line 194 mld_starttimer(struct in6_multi *in6m) |
|
{ |
{ |
struct timeval now; |
struct timeval now; |
|
|
|
KASSERT(rw_write_held(&in6_multilock)); |
KASSERT(in6m->in6m_timer != IN6M_TIMER_UNDEF); |
KASSERT(in6m->in6m_timer != IN6M_TIMER_UNDEF); |
|
|
microtime(&now); |
microtime(&now); |
Line 213 mld_starttimer(struct in6_multi *in6m) |
|
Line 210 mld_starttimer(struct in6_multi *in6m) |
|
callout_schedule(&in6m->in6m_timer_ch, in6m->in6m_timer); |
callout_schedule(&in6m->in6m_timer_ch, in6m->in6m_timer); |
} |
} |
|
|
|
/* |
|
* mld_stoptimer releases in6_multilock when calling callout_halt. |
|
* The caller must ensure in6m won't be freed while releasing the lock. |
|
*/ |
static void |
static void |
mld_stoptimer(struct in6_multi *in6m) |
mld_stoptimer(struct in6_multi *in6m) |
{ |
{ |
|
|
|
KASSERT(rw_write_held(&in6_multilock)); |
|
|
if (in6m->in6m_timer == IN6M_TIMER_UNDEF) |
if (in6m->in6m_timer == IN6M_TIMER_UNDEF) |
return; |
return; |
|
|
callout_stop(&in6m->in6m_timer_ch); |
rw_exit(&in6_multilock); |
|
|
|
if (mutex_owned(softnet_lock)) |
|
callout_halt(&in6m->in6m_timer_ch, softnet_lock); |
|
else |
|
callout_halt(&in6m->in6m_timer_ch, NULL); |
|
|
|
rw_enter(&in6_multilock, RW_WRITER); |
|
|
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
} |
} |
Line 229 mld_timeo(void *arg) |
|
Line 240 mld_timeo(void *arg) |
|
{ |
{ |
struct in6_multi *in6m = arg; |
struct in6_multi *in6m = arg; |
|
|
|
KASSERT(in6m->in6m_refcount > 0); |
|
|
|
#ifndef NET_MPSAFE |
mutex_enter(softnet_lock); |
mutex_enter(softnet_lock); |
KERNEL_LOCK(1, NULL); |
KERNEL_LOCK(1, NULL); |
|
#endif |
|
rw_enter(&in6_multilock, RW_WRITER); |
if (in6m->in6m_timer == IN6M_TIMER_UNDEF) |
if (in6m->in6m_timer == IN6M_TIMER_UNDEF) |
goto out; |
goto out; |
|
|
Line 247 mld_timeo(void *arg) |
|
Line 262 mld_timeo(void *arg) |
|
} |
} |
|
|
out: |
out: |
|
rw_exit(&in6_multilock); |
|
#ifndef NET_MPSAFE |
KERNEL_UNLOCK_ONE(NULL); |
KERNEL_UNLOCK_ONE(NULL); |
mutex_exit(softnet_lock); |
mutex_exit(softnet_lock); |
|
#else |
|
return; |
|
#endif |
} |
} |
|
|
static u_long |
static u_long |
Line 280 mld_start_listening(struct in6_multi *in |
|
Line 300 mld_start_listening(struct in6_multi *in |
|
{ |
{ |
struct in6_addr all_in6; |
struct in6_addr all_in6; |
|
|
|
KASSERT(rw_write_held(&in6_multilock)); |
|
|
/* |
/* |
* RFC2710 page 10: |
* RFC2710 page 10: |
* The node never sends a Report or Done for the link-scope all-nodes |
* The node never sends a Report or Done for the link-scope all-nodes |
Line 312 mld_stop_listening(struct in6_multi *in6 |
|
Line 334 mld_stop_listening(struct in6_multi *in6 |
|
{ |
{ |
struct in6_addr allnode, allrouter; |
struct in6_addr allnode, allrouter; |
|
|
|
KASSERT(rw_lock_held(&in6_multilock)); |
|
|
allnode = in6addr_linklocal_allnodes; |
allnode = in6addr_linklocal_allnodes; |
if (in6_setscope(&allnode, in6m->in6m_ifp, NULL)) { |
if (in6_setscope(&allnode, in6m->in6m_ifp, NULL)) { |
/* XXX: this should not happen! */ |
/* XXX: this should not happen! */ |
Line 339 mld_input(struct mbuf *m, int off) |
|
Line 363 mld_input(struct mbuf *m, int off) |
|
struct ifnet *ifp; |
struct ifnet *ifp; |
struct in6_multi *in6m = NULL; |
struct in6_multi *in6m = NULL; |
struct in6_addr mld_addr, all_in6; |
struct in6_addr mld_addr, all_in6; |
struct in6_ifaddr *ia; |
|
u_long timer = 0; /* timer value in the MLD query header */ |
u_long timer = 0; /* timer value in the MLD query header */ |
int s; |
struct psref psref; |
|
|
ifp = m_get_rcvif(m, &s); |
ifp = m_get_rcvif_psref(m, &psref); |
|
if (__predict_false(ifp == NULL)) |
|
goto out; |
IP6_EXTHDR_GET(mldh, struct mld_hdr *, m, off, sizeof(*mldh)); |
IP6_EXTHDR_GET(mldh, struct mld_hdr *, m, off, sizeof(*mldh)); |
if (mldh == NULL) { |
if (mldh == NULL) { |
ICMP6_STATINC(ICMP6_STAT_TOOSHORT); |
ICMP6_STATINC(ICMP6_STAT_TOOSHORT); |
Line 406 mld_input(struct mbuf *m, int off) |
|
Line 431 mld_input(struct mbuf *m, int off) |
|
*/ |
*/ |
switch (mldh->mld_type) { |
switch (mldh->mld_type) { |
case MLD_LISTENER_QUERY: { |
case MLD_LISTENER_QUERY: { |
struct psref psref; |
struct in6_multi *next; |
|
|
if (ifp->if_flags & IFF_LOOPBACK) |
if (ifp->if_flags & IFF_LOOPBACK) |
break; |
break; |
Line 433 mld_input(struct mbuf *m, int off) |
|
Line 458 mld_input(struct mbuf *m, int off) |
|
*/ |
*/ |
timer = ntohs(mldh->mld_maxdelay); |
timer = ntohs(mldh->mld_maxdelay); |
|
|
ia = in6_get_ia_from_ifp_psref(ifp, &psref); |
rw_enter(&in6_multilock, RW_WRITER); |
if (ia == NULL) |
/* |
break; |
* mld_stoptimer and mld_sendpkt release in6_multilock |
|
* temporarily, so we have to prevent in6m from being freed |
/* The following operations may sleep */ |
* while releasing the lock by having an extra reference to it. |
m_put_rcvif(ifp, &s); |
* |
ifp = NULL; |
* Also in6_purge_multi might remove items from the list of the |
|
* ifp while releasing the lock. Fortunately in6_purge_multi is |
LIST_FOREACH(in6m, &ia->ia6_multiaddrs, in6m_entry) { |
* never executed as long as we have a psref of the ifp. |
|
*/ |
|
LIST_FOREACH_SAFE(in6m, &ifp->if_multiaddrs, in6m_entry, next) { |
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_in6) || |
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_in6) || |
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < |
IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < |
IPV6_ADDR_SCOPE_LINKLOCAL) |
IPV6_ADDR_SCOPE_LINKLOCAL) |
Line 455 mld_input(struct mbuf *m, int off) |
|
Line 482 mld_input(struct mbuf *m, int off) |
|
continue; |
continue; |
|
|
if (timer == 0) { |
if (timer == 0) { |
|
in6m_ref(in6m); |
|
|
/* send a report immediately */ |
/* send a report immediately */ |
mld_stoptimer(in6m); |
mld_stoptimer(in6m); |
mld_sendpkt(in6m, MLD_LISTENER_REPORT, NULL); |
mld_sendpkt(in6m, MLD_LISTENER_REPORT, NULL); |
in6m->in6m_state = MLD_IREPORTEDLAST; |
in6m->in6m_state = MLD_IREPORTEDLAST; |
|
|
|
in6m_unref(in6m); /* May free in6m */ |
} else if (in6m->in6m_timer == IN6M_TIMER_UNDEF || |
} else if (in6m->in6m_timer == IN6M_TIMER_UNDEF || |
mld_timerresid(in6m) > timer) { |
mld_timerresid(in6m) > timer) { |
in6m->in6m_timer = |
in6m->in6m_timer = |
Line 466 mld_input(struct mbuf *m, int off) |
|
Line 497 mld_input(struct mbuf *m, int off) |
|
mld_starttimer(in6m); |
mld_starttimer(in6m); |
} |
} |
} |
} |
ia6_release(ia, &psref); |
rw_exit(&in6_multilock); |
break; |
break; |
} |
} |
|
|
Line 490 mld_input(struct mbuf *m, int off) |
|
Line 521 mld_input(struct mbuf *m, int off) |
|
* 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(mld_addr, ifp, in6m); |
rw_enter(&in6_multilock, RW_WRITER); |
|
in6m = in6_lookup_multi(&mld_addr, ifp); |
if (in6m) { |
if (in6m) { |
|
in6m_ref(in6m); |
mld_stoptimer(in6m); /* transit to idle state */ |
mld_stoptimer(in6m); /* transit to idle state */ |
in6m->in6m_state = MLD_OTHERLISTENER; /* clear flag */ |
in6m->in6m_state = MLD_OTHERLISTENER; /* clear flag */ |
|
in6m_unref(in6m); |
|
in6m = NULL; /* in6m might be freed */ |
} |
} |
|
rw_exit(&in6_multilock); |
break; |
break; |
default: /* this is impossible */ |
default: /* this is impossible */ |
#if 0 |
#if 0 |
Line 511 mld_input(struct mbuf *m, int off) |
|
Line 547 mld_input(struct mbuf *m, int off) |
|
out: |
out: |
m_freem(m); |
m_freem(m); |
out_nodrop: |
out_nodrop: |
m_put_rcvif(ifp, &s); |
m_put_rcvif_psref(ifp, &psref); |
} |
} |
|
|
|
/* |
|
* XXX mld_sendpkt must be called with in6_multilock held and |
|
* will release in6_multilock before calling ip6_output and |
|
* returning to avoid locking against myself in ip6_output. |
|
*/ |
static void |
static void |
mld_sendpkt(struct in6_multi *in6m, int type, |
mld_sendpkt(struct in6_multi *in6m, int type, |
const struct in6_addr *dst) |
const struct in6_addr *dst) |
Line 528 mld_sendpkt(struct in6_multi *in6m, int |
|
Line 569 mld_sendpkt(struct in6_multi *in6m, int |
|
struct psref psref; |
struct psref psref; |
int bound; |
int bound; |
|
|
|
KASSERT(rw_write_held(&in6_multilock)); |
|
|
/* |
/* |
* At first, find a link local address on the outgoing interface |
* At first, find a link local address on the outgoing interface |
* to use as the source address of the MLD packet. |
* to use as the source address of the MLD packet. |
Line 592 mld_sendpkt(struct in6_multi *in6m, int |
|
Line 635 mld_sendpkt(struct in6_multi *in6m, int |
|
break; |
break; |
} |
} |
|
|
|
/* XXX we cannot call ip6_output with holding in6_multilock */ |
|
rw_exit(&in6_multilock); |
|
|
ip6_output(mh, &ip6_opts, NULL, ia ? 0 : IPV6_UNSPECSRC, |
ip6_output(mh, &ip6_opts, NULL, ia ? 0 : IPV6_UNSPECSRC, |
&im6o, NULL, NULL); |
&im6o, NULL, NULL); |
|
|
|
rw_enter(&in6_multilock, RW_WRITER); |
} |
} |
|
|
static struct mld_hdr * |
static struct mld_hdr * |
Line 645 mld_allocbuf(struct mbuf **mh, int len, |
|
Line 693 mld_allocbuf(struct mbuf **mh, int len, |
|
return mldh; |
return mldh; |
} |
} |
|
|
|
static void |
|
in6m_ref(struct in6_multi *in6m) |
|
{ |
|
|
|
KASSERT(rw_write_held(&in6_multilock)); |
|
in6m->in6m_refcount++; |
|
} |
|
|
|
static void |
|
in6m_unref(struct in6_multi *in6m) |
|
{ |
|
|
|
KASSERT(rw_write_held(&in6_multilock)); |
|
if (--in6m->in6m_refcount == 0) |
|
in6m_destroy(in6m); |
|
} |
|
|
/* |
/* |
* Add an address to the list of IP6 multicast addresses for a given interface. |
* Add an address to the list of IP6 multicast addresses for a given interface. |
*/ |
*/ |
Line 652 struct in6_multi * |
|
Line 717 struct in6_multi * |
|
in6_addmulti(struct in6_addr *maddr6, struct ifnet *ifp, |
in6_addmulti(struct in6_addr *maddr6, struct ifnet *ifp, |
int *errorp, int timer) |
int *errorp, int timer) |
{ |
{ |
struct in6_ifaddr *ia; |
|
struct sockaddr_in6 sin6; |
struct sockaddr_in6 sin6; |
struct in6_multi *in6m; |
struct in6_multi *in6m; |
int s = splsoftnet(); |
|
|
|
*errorp = 0; |
*errorp = 0; |
|
|
|
rw_enter(&in6_multilock, RW_WRITER); |
/* |
/* |
* See if address already in list. |
* See if address already in list. |
*/ |
*/ |
IN6_LOOKUP_MULTI(*maddr6, ifp, in6m); |
in6m = in6_lookup_multi(maddr6, ifp); |
if (in6m != NULL) { |
if (in6m != NULL) { |
/* |
/* |
* Found it; just increment the refrence count. |
* Found it; just increment the refrence count. |
*/ |
*/ |
in6m->in6m_refcount++; |
in6m->in6m_refcount++; |
} else { |
} else { |
int _s; |
|
/* |
/* |
* New address; allocate a new multicast record |
* New address; allocate a new multicast record |
* and link it into the interface's multicast list. |
* and link it into the interface's multicast list. |
Line 677 in6_addmulti(struct in6_addr *maddr6, st |
|
Line 740 in6_addmulti(struct in6_addr *maddr6, st |
|
in6m = (struct in6_multi *) |
in6m = (struct in6_multi *) |
malloc(sizeof(*in6m), M_IPMADDR, M_NOWAIT|M_ZERO); |
malloc(sizeof(*in6m), M_IPMADDR, M_NOWAIT|M_ZERO); |
if (in6m == NULL) { |
if (in6m == NULL) { |
splx(s); |
|
*errorp = ENOBUFS; |
*errorp = ENOBUFS; |
return (NULL); |
goto out; |
} |
} |
|
|
in6m->in6m_addr = *maddr6; |
in6m->in6m_addr = *maddr6; |
Line 689 in6_addmulti(struct in6_addr *maddr6, st |
|
Line 751 in6_addmulti(struct in6_addr *maddr6, st |
|
callout_init(&in6m->in6m_timer_ch, CALLOUT_MPSAFE); |
callout_init(&in6m->in6m_timer_ch, CALLOUT_MPSAFE); |
callout_setfunc(&in6m->in6m_timer_ch, mld_timeo, in6m); |
callout_setfunc(&in6m->in6m_timer_ch, mld_timeo, in6m); |
|
|
_s = pserialize_read_enter(); |
LIST_INSERT_HEAD(&ifp->if_multiaddrs, in6m, in6m_entry); |
ia = in6_get_ia_from_ifp(ifp); |
|
if (ia == NULL) { |
|
pserialize_read_exit(_s); |
|
callout_destroy(&in6m->in6m_timer_ch); |
|
free(in6m, M_IPMADDR); |
|
splx(s); |
|
*errorp = EADDRNOTAVAIL; /* appropriate? */ |
|
return (NULL); |
|
} |
|
in6m->in6m_ia = ia; |
|
ifaref(&ia->ia_ifa); /* gain a reference */ |
|
/* FIXME NOMPSAFE: need to lock */ |
|
LIST_INSERT_HEAD(&ia->ia6_multiaddrs, in6m, in6m_entry); |
|
pserialize_read_exit(_s); |
|
|
|
/* |
/* |
* Ask the network driver to update its multicast reception |
* Ask the network driver to update its multicast reception |
Line 715 in6_addmulti(struct in6_addr *maddr6, st |
|
Line 763 in6_addmulti(struct in6_addr *maddr6, st |
|
callout_destroy(&in6m->in6m_timer_ch); |
callout_destroy(&in6m->in6m_timer_ch); |
LIST_REMOVE(in6m, in6m_entry); |
LIST_REMOVE(in6m, in6m_entry); |
free(in6m, M_IPMADDR); |
free(in6m, M_IPMADDR); |
ifafree(&ia->ia_ifa); |
in6m = NULL; |
splx(s); |
goto out; |
return (NULL); |
|
} |
} |
|
|
in6m->in6m_timer = timer; |
in6m->in6m_timer = timer; |
if (in6m->in6m_timer > 0) { |
if (in6m->in6m_timer > 0) { |
in6m->in6m_state = MLD_REPORTPENDING; |
in6m->in6m_state = MLD_REPORTPENDING; |
mld_starttimer(in6m); |
mld_starttimer(in6m); |
|
goto out; |
splx(s); |
|
return (in6m); |
|
} |
} |
|
|
/* |
/* |
Line 735 in6_addmulti(struct in6_addr *maddr6, st |
|
Line 780 in6_addmulti(struct in6_addr *maddr6, st |
|
*/ |
*/ |
mld_start_listening(in6m); |
mld_start_listening(in6m); |
} |
} |
splx(s); |
out: |
return (in6m); |
rw_exit(&in6_multilock); |
|
return in6m; |
|
} |
|
|
|
static void |
|
in6m_destroy(struct in6_multi *in6m) |
|
{ |
|
struct sockaddr_in6 sin6; |
|
|
|
KASSERT(rw_write_held(&in6_multilock)); |
|
KASSERT(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); |
|
|
|
/* |
|
* Delete all references of this multicasting group from |
|
* the membership arrays |
|
*/ |
|
in6_purge_mcast_references(in6m); |
|
|
|
/* |
|
* 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; |
|
if (mutex_owned(softnet_lock)) |
|
callout_halt(&in6m->in6m_timer_ch, softnet_lock); |
|
else |
|
callout_halt(&in6m->in6m_timer_ch, NULL); |
|
callout_destroy(&in6m->in6m_timer_ch); |
|
|
|
free(in6m, M_IPMADDR); |
} |
} |
|
|
/* |
/* |
Line 745 in6_addmulti(struct in6_addr *maddr6, st |
|
Line 834 in6_addmulti(struct in6_addr *maddr6, st |
|
void |
void |
in6_delmulti(struct in6_multi *in6m) |
in6_delmulti(struct in6_multi *in6m) |
{ |
{ |
struct sockaddr_in6 sin6; |
|
struct in6_ifaddr *ia; |
|
int s = splsoftnet(); |
|
|
|
|
KASSERT(in6m->in6m_refcount > 0); |
|
|
|
rw_enter(&in6_multilock, RW_WRITER); |
|
/* |
|
* The caller should have a reference to in6m. So we don't need to care |
|
* of releasing the lock in mld_stoptimer. |
|
*/ |
mld_stoptimer(in6m); |
mld_stoptimer(in6m); |
|
if (--in6m->in6m_refcount == 0) |
|
in6m_destroy(in6m); |
|
rw_exit(&in6_multilock); |
|
} |
|
|
if (--in6m->in6m_refcount == 0) { |
/* |
int _s; |
* Look up the in6_multi record for a given IP6 multicast address |
|
* on a given interface. If no matching record is found, "in6m" |
|
* returns NULL. |
|
*/ |
|
struct in6_multi * |
|
in6_lookup_multi(const struct in6_addr *addr, const struct ifnet *ifp) |
|
{ |
|
struct in6_multi *in6m; |
|
|
/* |
KASSERT(rw_lock_held(&in6_multilock)); |
* No remaining claims to this record; let MLD6 know |
|
* that we are leaving the multicast group. |
|
*/ |
|
mld_stop_listening(in6m); |
|
|
|
/* |
LIST_FOREACH(in6m, &ifp->if_multiaddrs, in6m_entry) { |
* Unlink from list. |
if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, addr)) |
*/ |
break; |
LIST_REMOVE(in6m, in6m_entry); |
} |
if (in6m->in6m_ia != NULL) { |
return in6m; |
ifafree(&in6m->in6m_ia->ia_ifa); /* release reference */ |
} |
in6m->in6m_ia = NULL; |
|
} |
|
|
|
/* |
bool |
* Delete all references of this multicasting group from |
in6_multi_group(const struct in6_addr *addr, const struct ifnet *ifp) |
* the membership arrays |
{ |
*/ |
bool ingroup; |
_s = pserialize_read_enter(); |
|
IN6_ADDRLIST_READER_FOREACH(ia) { |
|
struct in6_multi_mship *imm; |
|
LIST_FOREACH(imm, &ia->ia6_memberships, i6mm_chain) { |
|
if (imm->i6mm_maddr == in6m) |
|
imm->i6mm_maddr = NULL; |
|
} |
|
} |
|
pserialize_read_exit(_s); |
|
|
|
/* |
rw_enter(&in6_multilock, RW_READER); |
* Notify the network driver to update its multicast |
ingroup = in6_lookup_multi(addr, ifp) != NULL; |
* reception filter. |
rw_exit(&in6_multilock); |
*/ |
|
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 */ |
return ingroup; |
in6m->in6m_timer = IN6M_TIMER_UNDEF; |
} |
#ifdef NET_MPSAFE |
|
callout_halt(&in6m->in6m_timer_ch, NULL); |
|
#else |
|
callout_halt(&in6m->in6m_timer_ch, softnet_lock); |
|
#endif |
|
callout_destroy(&in6m->in6m_timer_ch); |
|
|
|
free(in6m, M_IPMADDR); |
/* |
|
* Purge in6_multi records associated to the interface. |
|
*/ |
|
void |
|
in6_purge_multi(struct ifnet *ifp) |
|
{ |
|
struct in6_multi *in6m, *next; |
|
|
|
rw_enter(&in6_multilock, RW_WRITER); |
|
LIST_FOREACH_SAFE(in6m, &ifp->if_multiaddrs, in6m_entry, next) { |
|
/* |
|
* Normally multicast addresses are already purged at this |
|
* point. Remaining references aren't accessible via ifp, |
|
* so what we can do here is to prevent ifp from being |
|
* accessed via in6m by removing it from the list of ifp. |
|
*/ |
|
mld_stoptimer(in6m); |
|
LIST_REMOVE(in6m, in6m_entry); |
} |
} |
splx(s); |
rw_exit(&in6_multilock); |
} |
} |
|
|
|
|
struct in6_multi_mship * |
struct in6_multi_mship * |
in6_joingroup(struct ifnet *ifp, struct in6_addr *addr, |
in6_joingroup(struct ifnet *ifp, struct in6_addr *addr, |
int *errorp, int timer) |
int *errorp, int timer) |
Line 837 in6_leavegroup(struct in6_multi_mship *i |
|
Line 933 in6_leavegroup(struct in6_multi_mship *i |
|
return 0; |
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; |
|
int s; |
|
|
|
s = pserialize_read_enter(); |
|
ia = in6_get_ia_from_ifp(oia->ia_ifp); |
|
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; |
|
/* FIXME NOMPSAFE: need to lock */ |
|
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); |
|
} |
|
} |
|
pserialize_read_exit(s); |
|
} |
|
|
|
/* |
|
* 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. |
* DEPRECATED: keep it just to avoid breaking old sysctl users. |
* 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 |
static int |
in6_mkludge_sysctl(SYSCTLFN_ARGS) |
in6_mkludge_sysctl(SYSCTLFN_ARGS) |
{ |
{ |
struct multi6_kludge *mk; |
|
struct in6_multi *in6m; |
|
int error; |
|
uint32_t tmp; |
|
size_t written; |
|
|
|
if (namelen != 1) |
if (namelen != 1) |
return EINVAL; |
return EINVAL; |
|
*oldlenp = 0; |
if (oldp == NULL) { |
return 0; |
*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 |
static int |
Line 1018 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
Line 951 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
{ |
{ |
struct ifnet *ifp; |
struct ifnet *ifp; |
struct ifaddr *ifa; |
struct ifaddr *ifa; |
struct in6_ifaddr *ifa6; |
struct in6_ifaddr *ia6; |
struct in6_multi *in6m; |
struct in6_multi *in6m; |
uint32_t tmp; |
uint32_t tmp; |
int error; |
int error; |
Line 1029 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
Line 962 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
if (namelen != 1) |
if (namelen != 1) |
return EINVAL; |
return EINVAL; |
|
|
|
rw_enter(&in6_multilock, RW_READER); |
|
|
bound = curlwp_bind(); |
bound = curlwp_bind(); |
ifp = if_get_byindex(name[0], &psref); |
ifp = if_get_byindex(name[0], &psref); |
if (ifp == NULL) { |
if (ifp == NULL) { |
curlwp_bindx(bound); |
curlwp_bindx(bound); |
|
rw_exit(&in6_multilock); |
return ENODEV; |
return ENODEV; |
} |
} |
|
|
Line 1040 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
Line 976 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
*oldlenp = 0; |
*oldlenp = 0; |
s = pserialize_read_enter(); |
s = pserialize_read_enter(); |
IFADDR_READER_FOREACH(ifa, ifp) { |
IFADDR_READER_FOREACH(ifa, ifp) { |
if (ifa->ifa_addr->sa_family != AF_INET6) |
LIST_FOREACH(in6m, &ifp->if_multiaddrs, in6m_entry) { |
continue; |
|
ifa6 = (struct in6_ifaddr *)ifa; |
|
LIST_FOREACH(in6m, &ifa6->ia6_multiaddrs, in6m_entry) { |
|
*oldlenp += 2 * sizeof(struct in6_addr) + |
*oldlenp += 2 * sizeof(struct in6_addr) + |
sizeof(uint32_t); |
sizeof(uint32_t); |
} |
} |
Line 1051 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
Line 984 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
pserialize_read_exit(s); |
pserialize_read_exit(s); |
if_put(ifp, &psref); |
if_put(ifp, &psref); |
curlwp_bindx(bound); |
curlwp_bindx(bound); |
|
rw_exit(&in6_multilock); |
return 0; |
return 0; |
} |
} |
|
|
Line 1064 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
Line 998 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
ifa_acquire(ifa, &psref_ia); |
ifa_acquire(ifa, &psref_ia); |
pserialize_read_exit(s); |
pserialize_read_exit(s); |
|
|
ifa6 = (struct in6_ifaddr *)ifa; |
ia6 = ifatoia6(ifa); |
LIST_FOREACH(in6m, &ifa6->ia6_multiaddrs, in6m_entry) { |
LIST_FOREACH(in6m, &ifp->if_multiaddrs, in6m_entry) { |
if (written + 2 * sizeof(struct in6_addr) + |
if (written + 2 * sizeof(struct in6_addr) + |
sizeof(uint32_t) > *oldlenp) |
sizeof(uint32_t) > *oldlenp) |
goto done; |
goto done; |
error = sysctl_copyout(l, &ifa6->ia_addr.sin6_addr, |
/* |
|
* XXX return the first IPv6 address to keep backward |
|
* compatibility, however now multicast addresses |
|
* don't belong to any IPv6 addresses so it should be |
|
* unnecessary. |
|
*/ |
|
error = sysctl_copyout(l, &ia6->ia_addr.sin6_addr, |
oldp, sizeof(struct in6_addr)); |
oldp, sizeof(struct in6_addr)); |
if (error) |
if (error) |
goto done; |
goto done; |
Line 1091 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
Line 1031 in6_multicast_sysctl(SYSCTLFN_ARGS) |
|
|
|
s = pserialize_read_enter(); |
s = pserialize_read_enter(); |
ifa_release(ifa, &psref_ia); |
ifa_release(ifa, &psref_ia); |
|
|
|
break; |
} |
} |
pserialize_read_exit(s); |
pserialize_read_exit(s); |
done: |
done: |
ifa_release(ifa, &psref_ia); |
ifa_release(ifa, &psref_ia); |
if_put(ifp, &psref); |
if_put(ifp, &psref); |
curlwp_bindx(bound); |
curlwp_bindx(bound); |
|
rw_exit(&in6_multilock); |
*oldlenp = written; |
*oldlenp = written; |
return error; |
return error; |
} |
} |