On Fri, Oct 18, 2013 at 01:00:25PM +0200, Martin Pieuchot wrote: > On 18/10/13(Fri) 12:45, Alexander Bluhm wrote: > > > > Ethernet drivers connected via USB might sleep when their multicast > > group filter is modified. Unfortunately this happens from softclock > > or softnet interrupt when IPv6 decides to unconfigure its addresses > > automatically. > > > > An obvious solution is to use a work queue. I have put the workq > > storage into struct in6_multi_mship. That requires to include > > sys/workq.h before compiling this struct. > > One problem with a work queue is that you cannot guarantee the interface > will still be here when the task will be scheduled, so passing a pointer > might not be a good idea. > > That is why I wanted to change the if_get() API to use unique index for > each interface... > > But maybe there's another solution at this problem than using a workq...
Now I use the if_index to detect that the interface is gone. I this a better approach? bluhm Index: netinet6/in6.c =================================================================== RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/in6.c,v retrieving revision 1.122 diff -u -p -u -p -r1.122 in6.c --- netinet6/in6.c 24 Oct 2013 11:20:18 -0000 1.122 +++ netinet6/in6.c 30 Oct 2013 15:01:20 -0000 @@ -124,6 +124,7 @@ int in6_lifaddr_ioctl(struct socket *, u int in6_ifinit(struct ifnet *, struct in6_ifaddr *, int); void in6_unlink_ifa(struct in6_ifaddr *, struct ifnet *); void in6_ifloop_request(int, struct ifaddr *); +void in6_leavegroup_task(void *, void *); const struct sockaddr_in6 sa6_any = { sizeof(sa6_any), AF_INET6, 0, 0, IN6ADDR_ANY_INIT, 0 @@ -1777,16 +1778,18 @@ in6_delmulti(struct in6_multi *in6m) ifafree(&in6m->in6m_ia->ia_ifa); /* release reference */ } - /* - * Notify the network driver to update its multicast - * reception filter. - */ - bzero(&ifr.ifr_addr, sizeof(struct sockaddr_in6)); - ifr.ifr_addr.sin6_len = sizeof(struct sockaddr_in6); - ifr.ifr_addr.sin6_family = AF_INET6; - ifr.ifr_addr.sin6_addr = in6m->in6m_addr; - (*in6m->in6m_ifp->if_ioctl)(in6m->in6m_ifp, - SIOCDELMULTI, (caddr_t)&ifr); + if (in6m->in6m_ifp != NULL) { + /* + * Notify the network driver to update its multicast + * reception filter. + */ + bzero(&ifr.ifr_addr, sizeof(struct sockaddr_in6)); + ifr.ifr_addr.sin6_len = sizeof(struct sockaddr_in6); + ifr.ifr_addr.sin6_family = AF_INET6; + ifr.ifr_addr.sin6_addr = in6m->in6m_addr; + (*in6m->in6m_ifp->if_ioctl)(in6m->in6m_ifp, + SIOCDELMULTI, (caddr_t)&ifr); + } free(in6m, M_IPMADDR); } splx(s); @@ -1814,11 +1817,26 @@ in6_joingroup(struct ifnet *ifp, struct int in6_leavegroup(struct in6_multi_mship *imm) { - if (imm->i6mm_maddr) - in6_delmulti(imm->i6mm_maddr); - free(imm, M_IPMADDR); + workq_queue_task(NULL, &imm->wqt, 0, in6_leavegroup_task, imm, + (void *)(unsigned long)imm->i6mm_maddr->in6m_ifp->if_index); + else + free(imm, M_IPMADDR); return 0; +} + +void +in6_leavegroup_task(void *arg1, void *arg2) +{ + struct in6_multi_mship *imm = arg1; + unsigned int index = (unsigned long)arg2; + + /* If interface has been be freed, avoid call to if_ioctl(). */ + if (if_get(index) != imm->i6mm_maddr->in6m_ifp) + imm->i6mm_maddr->in6m_ifp = NULL; + + in6_delmulti(imm->i6mm_maddr); + free(imm, M_IPMADDR); } /* Index: netinet6/in6_var.h =================================================================== RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/in6_var.h,v retrieving revision 1.44 diff -u -p -u -p -r1.44 in6_var.h --- netinet6/in6_var.h 24 Oct 2013 11:31:43 -0000 1.44 +++ netinet6/in6_var.h 30 Oct 2013 15:01:20 -0000 @@ -64,6 +64,8 @@ #ifndef _NETINET6_IN6_VAR_H_ #define _NETINET6_IN6_VAR_H_ +#include <sys/workq.h> + /* * Interface address, Internet version. One of these structures * is allocated for each interface with an Internet address. @@ -480,6 +482,7 @@ do { \ * belongs to. */ struct in6_multi_mship { + struct workq_task wqt; /* Allow network driver to sleep */ struct in6_multi *i6mm_maddr; /* Multicast address pointer */ LIST_ENTRY(in6_multi_mship) i6mm_chain; /* multicast options chain */ };