Changeset View
Changeset View
Standalone View
Standalone View
sys/netinet/in_mcast.c
Show First 20 Lines • Show All 88 Lines • ▼ Show 20 Lines | static MALLOC_DEFINE(M_INMFILTER, "in_mfilter", | ||||
"IPv4 multicast PCB-layer source filter"); | "IPv4 multicast PCB-layer source filter"); | ||||
static MALLOC_DEFINE(M_IPMADDR, "in_multi", "IPv4 multicast group"); | static MALLOC_DEFINE(M_IPMADDR, "in_multi", "IPv4 multicast group"); | ||||
static MALLOC_DEFINE(M_IPMOPTS, "ip_moptions", "IPv4 multicast options"); | static MALLOC_DEFINE(M_IPMOPTS, "ip_moptions", "IPv4 multicast options"); | ||||
static MALLOC_DEFINE(M_IPMSOURCE, "ip_msource", | static MALLOC_DEFINE(M_IPMSOURCE, "ip_msource", | ||||
"IPv4 multicast IGMP-layer source filter"); | "IPv4 multicast IGMP-layer source filter"); | ||||
/* | /* | ||||
* Locking: | * Locking: | ||||
* - Lock order is: Giant, INP_WLOCK, IN_MULTI_LIST_LOCK, IGMP_LOCK, IF_ADDR_LOCK. | * | ||||
* - Lock order is: Giant, IN_MULTI_LOCK, INP_WLOCK, | |||||
hselasky: @markj: The locking order is described here. Can you show a witness warning with the LOR? | |||||
Done Inline ActionsI haven't tried to reproduce it yet. In the code I referenced, we acquire the inp wlock, followed by the in_multi lock. markj: I haven't tried to reproduce it yet. In the code I referenced, we acquire the inp wlock… | |||||
* IN_MULTI_LIST_LOCK, IGMP_LOCK, IF_ADDR_LOCK. | |||||
* - The IF_ADDR_LOCK is implicitly taken by inm_lookup() earlier, however | * - The IF_ADDR_LOCK is implicitly taken by inm_lookup() earlier, however | ||||
* it can be taken by code in net/if.c also. | * it can be taken by code in net/if.c also. | ||||
* - ip_moptions and in_mfilter are covered by the INP_WLOCK. | * - ip_moptions and in_mfilter are covered by the INP_WLOCK. | ||||
* | * | ||||
* struct in_multi is covered by IN_MULTI_LIST_LOCK. There isn't strictly | * struct in_multi is covered by IN_MULTI_LIST_LOCK. There isn't strictly | ||||
* any need for in_multi itself to be virtualized -- it is bound to an ifp | * any need for in_multi itself to be virtualized -- it is bound to an ifp | ||||
* anyway no matter what happens. | * anyway no matter what happens. | ||||
*/ | */ | ||||
Show All 33 Lines | |||||
static struct in_msource * | static struct in_msource * | ||||
imf_graft(struct in_mfilter *, const uint8_t, | imf_graft(struct in_mfilter *, const uint8_t, | ||||
const struct sockaddr_in *); | const struct sockaddr_in *); | ||||
static void imf_leave(struct in_mfilter *); | static void imf_leave(struct in_mfilter *); | ||||
static int imf_prune(struct in_mfilter *, const struct sockaddr_in *); | static int imf_prune(struct in_mfilter *, const struct sockaddr_in *); | ||||
static void imf_purge(struct in_mfilter *); | static void imf_purge(struct in_mfilter *); | ||||
static void imf_rollback(struct in_mfilter *); | static void imf_rollback(struct in_mfilter *); | ||||
static void imf_reap(struct in_mfilter *); | static void imf_reap(struct in_mfilter *); | ||||
static int imo_grow(struct ip_moptions *); | static struct in_mfilter * | ||||
static size_t imo_match_group(const struct ip_moptions *, | imo_match_group(const struct ip_moptions *, | ||||
const struct ifnet *, const struct sockaddr *); | const struct ifnet *, const struct sockaddr *); | ||||
static struct in_msource * | static struct in_msource * | ||||
imo_match_source(const struct ip_moptions *, const size_t, | imo_match_source(struct in_mfilter *, const struct sockaddr *); | ||||
const struct sockaddr *); | |||||
static void ims_merge(struct ip_msource *ims, | static void ims_merge(struct ip_msource *ims, | ||||
const struct in_msource *lims, const int rollback); | const struct in_msource *lims, const int rollback); | ||||
static int in_getmulti(struct ifnet *, const struct in_addr *, | static int in_getmulti(struct ifnet *, const struct in_addr *, | ||||
struct in_multi **); | struct in_multi **); | ||||
static int inm_get_source(struct in_multi *inm, const in_addr_t haddr, | static int inm_get_source(struct in_multi *inm, const in_addr_t haddr, | ||||
const int noalloc, struct ip_msource **pims); | const int noalloc, struct ip_msource **pims); | ||||
#ifdef KTR | #ifdef KTR | ||||
static int inm_is_ifp_detached(const struct in_multi *); | static int inm_is_ifp_detached(const struct in_multi *); | ||||
▲ Show 20 Lines • Show All 167 Lines • ▼ Show 20 Lines | |||||
imf_init(struct in_mfilter *imf, const int st0, const int st1) | imf_init(struct in_mfilter *imf, const int st0, const int st1) | ||||
{ | { | ||||
memset(imf, 0, sizeof(struct in_mfilter)); | memset(imf, 0, sizeof(struct in_mfilter)); | ||||
RB_INIT(&imf->imf_sources); | RB_INIT(&imf->imf_sources); | ||||
imf->imf_st[0] = st0; | imf->imf_st[0] = st0; | ||||
imf->imf_st[1] = st1; | imf->imf_st[1] = st1; | ||||
} | } | ||||
struct in_mfilter * | |||||
ip_alloc_mfilter(const int mflags, const int st0, const int st1) | |||||
{ | |||||
struct in_mfilter *imf; | |||||
imf = malloc(sizeof(*imf), M_INMFILTER, mflags); | |||||
Done Inline ActionsExtre newline. markj: Extre newline. | |||||
if (imf != NULL) | |||||
imf_init(imf, st0, st1); | |||||
return (imf); | |||||
} | |||||
void | |||||
ip_free_mfilter(struct in_mfilter *imf) | |||||
{ | |||||
imf_purge(imf); | |||||
free(imf, M_INMFILTER); | |||||
} | |||||
/* | /* | ||||
* Function for looking up an in_multi record for an IPv4 multicast address | * Function for looking up an in_multi record for an IPv4 multicast address | ||||
* on a given interface. ifp must be valid. If no record found, return NULL. | * on a given interface. ifp must be valid. If no record found, return NULL. | ||||
* The IN_MULTI_LIST_LOCK and IF_ADDR_LOCK on ifp must be held. | * The IN_MULTI_LIST_LOCK and IF_ADDR_LOCK on ifp must be held. | ||||
*/ | */ | ||||
struct in_multi * | struct in_multi * | ||||
inm_lookup_locked(struct ifnet *ifp, const struct in_addr ina) | inm_lookup_locked(struct ifnet *ifp, const struct in_addr ina) | ||||
{ | { | ||||
Show All 30 Lines | inm_lookup(struct ifnet *ifp, const struct in_addr ina) | ||||
NET_EPOCH_ENTER(et); | NET_EPOCH_ENTER(et); | ||||
inm = inm_lookup_locked(ifp, ina); | inm = inm_lookup_locked(ifp, ina); | ||||
NET_EPOCH_EXIT(et); | NET_EPOCH_EXIT(et); | ||||
return (inm); | return (inm); | ||||
} | } | ||||
/* | /* | ||||
* Resize the ip_moptions vector to the next power-of-two minus 1. | |||||
* May be called with locks held; do not sleep. | |||||
*/ | |||||
static int | |||||
imo_grow(struct ip_moptions *imo) | |||||
{ | |||||
struct in_multi **nmships; | |||||
struct in_multi **omships; | |||||
struct in_mfilter *nmfilters; | |||||
struct in_mfilter *omfilters; | |||||
size_t idx; | |||||
size_t newmax; | |||||
size_t oldmax; | |||||
nmships = NULL; | |||||
nmfilters = NULL; | |||||
omships = imo->imo_membership; | |||||
omfilters = imo->imo_mfilters; | |||||
oldmax = imo->imo_max_memberships; | |||||
newmax = ((oldmax + 1) * 2) - 1; | |||||
if (newmax <= IP_MAX_MEMBERSHIPS) { | |||||
nmships = (struct in_multi **)realloc(omships, | |||||
sizeof(struct in_multi *) * newmax, M_IPMOPTS, M_NOWAIT); | |||||
nmfilters = (struct in_mfilter *)realloc(omfilters, | |||||
sizeof(struct in_mfilter) * newmax, M_INMFILTER, M_NOWAIT); | |||||
if (nmships != NULL && nmfilters != NULL) { | |||||
/* Initialize newly allocated source filter heads. */ | |||||
for (idx = oldmax; idx < newmax; idx++) { | |||||
imf_init(&nmfilters[idx], MCAST_UNDEFINED, | |||||
MCAST_EXCLUDE); | |||||
} | |||||
imo->imo_max_memberships = newmax; | |||||
imo->imo_membership = nmships; | |||||
imo->imo_mfilters = nmfilters; | |||||
} | |||||
} | |||||
if (nmships == NULL || nmfilters == NULL) { | |||||
if (nmships != NULL) | |||||
free(nmships, M_IPMOPTS); | |||||
if (nmfilters != NULL) | |||||
free(nmfilters, M_INMFILTER); | |||||
return (ETOOMANYREFS); | |||||
} | |||||
return (0); | |||||
} | |||||
/* | |||||
* Find an IPv4 multicast group entry for this ip_moptions instance | * Find an IPv4 multicast group entry for this ip_moptions instance | ||||
* which matches the specified group, and optionally an interface. | * which matches the specified group, and optionally an interface. | ||||
* Return its index into the array, or -1 if not found. | * Return its index into the array, or -1 if not found. | ||||
*/ | */ | ||||
static size_t | static struct in_mfilter * | ||||
imo_match_group(const struct ip_moptions *imo, const struct ifnet *ifp, | imo_match_group(const struct ip_moptions *imo, const struct ifnet *ifp, | ||||
const struct sockaddr *group) | const struct sockaddr *group) | ||||
{ | { | ||||
const struct sockaddr_in *gsin; | const struct sockaddr_in *gsin; | ||||
struct in_multi **pinm; | struct in_mfilter *imf; | ||||
int idx; | struct in_multi *inm; | ||||
int nmships; | |||||
gsin = (const struct sockaddr_in *)group; | gsin = (const struct sockaddr_in *)group; | ||||
/* The imo_membership array may be lazy allocated. */ | for (imf = NULL; ip_next_mfilter(&imo->imo_head, &imf); ) { | ||||
if (imo->imo_membership == NULL || imo->imo_num_memberships == 0) | inm = imf->imf_inm; | ||||
return (-1); | if (inm == NULL) | ||||
nmships = imo->imo_num_memberships; | |||||
pinm = &imo->imo_membership[0]; | |||||
for (idx = 0; idx < nmships; idx++, pinm++) { | |||||
if (*pinm == NULL) | |||||
continue; | continue; | ||||
if ((ifp == NULL || ((*pinm)->inm_ifp == ifp)) && | if ((ifp == NULL || (inm->inm_ifp == ifp)) && | ||||
in_hosteq((*pinm)->inm_addr, gsin->sin_addr)) { | in_hosteq(inm->inm_addr, gsin->sin_addr)) { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (idx >= nmships) | return (imf); | ||||
idx = -1; | |||||
return (idx); | |||||
} | } | ||||
/* | /* | ||||
* Find an IPv4 multicast source entry for this imo which matches | * Find an IPv4 multicast source entry for this imo which matches | ||||
* the given group index for this socket, and source address. | * the given group index for this socket, and source address. | ||||
* | * | ||||
* NOTE: This does not check if the entry is in-mode, merely if | * NOTE: This does not check if the entry is in-mode, merely if | ||||
* it exists, which may not be the desired behaviour. | * it exists, which may not be the desired behaviour. | ||||
*/ | */ | ||||
static struct in_msource * | static struct in_msource * | ||||
imo_match_source(const struct ip_moptions *imo, const size_t gidx, | imo_match_source(struct in_mfilter *imf, const struct sockaddr *src) | ||||
const struct sockaddr *src) | |||||
{ | { | ||||
struct ip_msource find; | struct ip_msource find; | ||||
struct in_mfilter *imf; | |||||
struct ip_msource *ims; | struct ip_msource *ims; | ||||
const sockunion_t *psa; | const sockunion_t *psa; | ||||
KASSERT(src->sa_family == AF_INET, ("%s: !AF_INET", __func__)); | KASSERT(src->sa_family == AF_INET, ("%s: !AF_INET", __func__)); | ||||
KASSERT(gidx != -1 && gidx < imo->imo_num_memberships, | |||||
("%s: invalid index %d\n", __func__, (int)gidx)); | |||||
/* The imo_mfilters array may be lazy allocated. */ | |||||
if (imo->imo_mfilters == NULL) | |||||
return (NULL); | |||||
imf = &imo->imo_mfilters[gidx]; | |||||
/* Source trees are keyed in host byte order. */ | /* Source trees are keyed in host byte order. */ | ||||
psa = (const sockunion_t *)src; | psa = (const sockunion_t *)src; | ||||
find.ims_haddr = ntohl(psa->sin.sin_addr.s_addr); | find.ims_haddr = ntohl(psa->sin.sin_addr.s_addr); | ||||
ims = RB_FIND(ip_msource_tree, &imf->imf_sources, &find); | ims = RB_FIND(ip_msource_tree, &imf->imf_sources, &find); | ||||
return ((struct in_msource *)ims); | return ((struct in_msource *)ims); | ||||
} | } | ||||
/* | /* | ||||
* Perform filtering for multicast datagrams on a socket by group and source. | * Perform filtering for multicast datagrams on a socket by group and source. | ||||
* | * | ||||
* Returns 0 if a datagram should be allowed through, or various error codes | * Returns 0 if a datagram should be allowed through, or various error codes | ||||
* if the socket was not a member of the group, or the source was muted, etc. | * if the socket was not a member of the group, or the source was muted, etc. | ||||
*/ | */ | ||||
int | int | ||||
imo_multi_filter(const struct ip_moptions *imo, const struct ifnet *ifp, | imo_multi_filter(const struct ip_moptions *imo, const struct ifnet *ifp, | ||||
const struct sockaddr *group, const struct sockaddr *src) | const struct sockaddr *group, const struct sockaddr *src) | ||||
{ | { | ||||
size_t gidx; | struct in_mfilter *imf; | ||||
struct in_msource *ims; | struct in_msource *ims; | ||||
int mode; | int mode; | ||||
KASSERT(ifp != NULL, ("%s: null ifp", __func__)); | KASSERT(ifp != NULL, ("%s: null ifp", __func__)); | ||||
gidx = imo_match_group(imo, ifp, group); | imf = imo_match_group(imo, ifp, group); | ||||
if (gidx == -1) | if (imf == NULL) | ||||
return (MCAST_NOTGMEMBER); | return (MCAST_NOTGMEMBER); | ||||
/* | /* | ||||
* Check if the source was included in an (S,G) join. | * Check if the source was included in an (S,G) join. | ||||
* Allow reception on exclusive memberships by default, | * Allow reception on exclusive memberships by default, | ||||
* reject reception on inclusive memberships by default. | * reject reception on inclusive memberships by default. | ||||
* Exclude source only if an in-mode exclude filter exists. | * Exclude source only if an in-mode exclude filter exists. | ||||
* Include source only if an in-mode include filter exists. | * Include source only if an in-mode include filter exists. | ||||
* NOTE: We are comparing group state here at IGMP t1 (now) | * NOTE: We are comparing group state here at IGMP t1 (now) | ||||
* with socket-layer t0 (since last downcall). | * with socket-layer t0 (since last downcall). | ||||
*/ | */ | ||||
mode = imo->imo_mfilters[gidx].imf_st[1]; | mode = imf->imf_st[1]; | ||||
ims = imo_match_source(imo, gidx, src); | ims = imo_match_source(imf, src); | ||||
if ((ims == NULL && mode == MCAST_INCLUDE) || | if ((ims == NULL && mode == MCAST_INCLUDE) || | ||||
(ims != NULL && ims->imsl_st[0] != mode)) | (ims != NULL && ims->imsl_st[0] != mode)) | ||||
return (MCAST_NOTSMEMBER); | return (MCAST_NOTSMEMBER); | ||||
return (MCAST_PASS); | return (MCAST_PASS); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 908 Lines • ▼ Show 20 Lines | inp_block_unblock_source(struct inpcb *inp, struct sockopt *sopt) | ||||
struct group_source_req gsr; | struct group_source_req gsr; | ||||
struct rm_priotracker in_ifa_tracker; | struct rm_priotracker in_ifa_tracker; | ||||
sockunion_t *gsa, *ssa; | sockunion_t *gsa, *ssa; | ||||
struct ifnet *ifp; | struct ifnet *ifp; | ||||
struct in_mfilter *imf; | struct in_mfilter *imf; | ||||
struct ip_moptions *imo; | struct ip_moptions *imo; | ||||
struct in_msource *ims; | struct in_msource *ims; | ||||
struct in_multi *inm; | struct in_multi *inm; | ||||
size_t idx; | |||||
uint16_t fmode; | uint16_t fmode; | ||||
int error, doblock; | int error, doblock; | ||||
ifp = NULL; | ifp = NULL; | ||||
error = 0; | error = 0; | ||||
doblock = 0; | doblock = 0; | ||||
memset(&gsr, 0, sizeof(struct group_source_req)); | memset(&gsr, 0, sizeof(struct group_source_req)); | ||||
▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | inp_block_unblock_source(struct inpcb *inp, struct sockopt *sopt) | ||||
if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr))) | if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr))) | ||||
return (EINVAL); | return (EINVAL); | ||||
/* | /* | ||||
* Check if we are actually a member of this group. | * Check if we are actually a member of this group. | ||||
*/ | */ | ||||
imo = inp_findmoptions(inp); | imo = inp_findmoptions(inp); | ||||
idx = imo_match_group(imo, ifp, &gsa->sa); | imf = imo_match_group(imo, ifp, &gsa->sa); | ||||
if (idx == -1 || imo->imo_mfilters == NULL) { | if (imf == NULL) { | ||||
error = EADDRNOTAVAIL; | error = EADDRNOTAVAIL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
inm = imf->imf_inm; | |||||
KASSERT(imo->imo_mfilters != NULL, | |||||
("%s: imo_mfilters not allocated", __func__)); | |||||
imf = &imo->imo_mfilters[idx]; | |||||
inm = imo->imo_membership[idx]; | |||||
/* | /* | ||||
* Attempting to use the delta-based API on an | * Attempting to use the delta-based API on an | ||||
* non exclusive-mode membership is an error. | * non exclusive-mode membership is an error. | ||||
*/ | */ | ||||
fmode = imf->imf_st[0]; | fmode = imf->imf_st[0]; | ||||
if (fmode != MCAST_EXCLUDE) { | if (fmode != MCAST_EXCLUDE) { | ||||
error = EINVAL; | error = EINVAL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
/* | /* | ||||
* Deal with error cases up-front: | * Deal with error cases up-front: | ||||
* Asked to block, but already blocked; or | * Asked to block, but already blocked; or | ||||
* Asked to unblock, but nothing to unblock. | * Asked to unblock, but nothing to unblock. | ||||
* If adding a new block entry, allocate it. | * If adding a new block entry, allocate it. | ||||
*/ | */ | ||||
ims = imo_match_source(imo, idx, &ssa->sa); | ims = imo_match_source(imf, &ssa->sa); | ||||
if ((ims != NULL && doblock) || (ims == NULL && !doblock)) { | if ((ims != NULL && doblock) || (ims == NULL && !doblock)) { | ||||
CTR3(KTR_IGMPV3, "%s: source 0x%08x %spresent", __func__, | CTR3(KTR_IGMPV3, "%s: source 0x%08x %spresent", __func__, | ||||
ntohl(ssa->sin.sin_addr.s_addr), doblock ? "" : "not "); | ntohl(ssa->sin.sin_addr.s_addr), doblock ? "" : "not "); | ||||
error = EADDRNOTAVAIL; | error = EADDRNOTAVAIL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
INP_WLOCK_ASSERT(inp); | INP_WLOCK_ASSERT(inp); | ||||
Show All 14 Lines | inp_block_unblock_source(struct inpcb *inp, struct sockopt *sopt) | ||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: merge imf state failed", __func__); | CTR1(KTR_IGMPV3, "%s: merge imf state failed", __func__); | ||||
goto out_imf_rollback; | goto out_imf_rollback; | ||||
} | } | ||||
/* | /* | ||||
* Begin state merge transaction at IGMP layer. | * Begin state merge transaction at IGMP layer. | ||||
*/ | */ | ||||
IN_MULTI_LOCK(); | IN_MULTI_LOCK(); | ||||
Done Inline ActionsThere is a LOR here. We hold the non-sleepable inp lock at this point. markj: There is a LOR here. We hold the non-sleepable inp lock at this point. | |||||
Done Inline ActionsI can probably move this locking a bit earlier to resolve that. hselasky: I can probably move this locking a bit earlier to resolve that. | |||||
CTR1(KTR_IGMPV3, "%s: merge inm state", __func__); | CTR1(KTR_IGMPV3, "%s: merge inm state", __func__); | ||||
IN_MULTI_LIST_LOCK(); | IN_MULTI_LIST_LOCK(); | ||||
error = inm_merge(inm, imf); | error = inm_merge(inm, imf); | ||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: failed to merge inm state", __func__); | CTR1(KTR_IGMPV3, "%s: failed to merge inm state", __func__); | ||||
IN_MULTI_LIST_UNLOCK(); | IN_MULTI_LIST_UNLOCK(); | ||||
goto out_in_multi_locked; | goto out_in_multi_locked; | ||||
} | } | ||||
Show All 26 Lines | |||||
* | * | ||||
* SMPng: NOTE: Potentially calls malloc(M_WAITOK) with Giant held. | * SMPng: NOTE: Potentially calls malloc(M_WAITOK) with Giant held. | ||||
* SMPng: NOTE: Returns with the INP write lock held. | * SMPng: NOTE: Returns with the INP write lock held. | ||||
*/ | */ | ||||
static struct ip_moptions * | static struct ip_moptions * | ||||
inp_findmoptions(struct inpcb *inp) | inp_findmoptions(struct inpcb *inp) | ||||
{ | { | ||||
struct ip_moptions *imo; | struct ip_moptions *imo; | ||||
struct in_multi **immp; | |||||
struct in_mfilter *imfp; | |||||
size_t idx; | |||||
INP_WLOCK(inp); | INP_WLOCK(inp); | ||||
if (inp->inp_moptions != NULL) | if (inp->inp_moptions != NULL) | ||||
return (inp->inp_moptions); | return (inp->inp_moptions); | ||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
imo = malloc(sizeof(*imo), M_IPMOPTS, M_WAITOK); | imo = malloc(sizeof(*imo), M_IPMOPTS, M_WAITOK); | ||||
immp = malloc(sizeof(*immp) * IP_MIN_MEMBERSHIPS, M_IPMOPTS, | |||||
M_WAITOK | M_ZERO); | |||||
imfp = malloc(sizeof(struct in_mfilter) * IP_MIN_MEMBERSHIPS, | |||||
M_INMFILTER, M_WAITOK); | |||||
imo->imo_multicast_ifp = NULL; | imo->imo_multicast_ifp = NULL; | ||||
imo->imo_multicast_addr.s_addr = INADDR_ANY; | imo->imo_multicast_addr.s_addr = INADDR_ANY; | ||||
imo->imo_multicast_vif = -1; | imo->imo_multicast_vif = -1; | ||||
imo->imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL; | imo->imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL; | ||||
imo->imo_multicast_loop = in_mcast_loop; | imo->imo_multicast_loop = in_mcast_loop; | ||||
imo->imo_num_memberships = 0; | STAILQ_INIT(&imo->imo_head); | ||||
imo->imo_max_memberships = IP_MIN_MEMBERSHIPS; | |||||
imo->imo_membership = immp; | |||||
/* Initialize per-group source filters. */ | |||||
for (idx = 0; idx < IP_MIN_MEMBERSHIPS; idx++) | |||||
imf_init(&imfp[idx], MCAST_UNDEFINED, MCAST_EXCLUDE); | |||||
imo->imo_mfilters = imfp; | |||||
INP_WLOCK(inp); | INP_WLOCK(inp); | ||||
if (inp->inp_moptions != NULL) { | if (inp->inp_moptions != NULL) { | ||||
free(imfp, M_INMFILTER); | |||||
free(immp, M_IPMOPTS); | |||||
free(imo, M_IPMOPTS); | free(imo, M_IPMOPTS); | ||||
return (inp->inp_moptions); | return (inp->inp_moptions); | ||||
} | } | ||||
inp->inp_moptions = imo; | inp->inp_moptions = imo; | ||||
return (imo); | return (imo); | ||||
} | } | ||||
static void | static void | ||||
inp_gcmoptions(struct ip_moptions *imo) | inp_gcmoptions(struct ip_moptions *imo) | ||||
{ | { | ||||
struct in_mfilter *imf; | struct in_mfilter *imf; | ||||
struct in_multi *inm; | struct in_multi *inm; | ||||
struct ifnet *ifp; | struct ifnet *ifp; | ||||
size_t idx, nmships; | |||||
nmships = imo->imo_num_memberships; | while ((imf = ip_first_mfilter(&imo->imo_head)) != NULL) { | ||||
for (idx = 0; idx < nmships; ++idx) { | ip_remove_mfilter(&imo->imo_head, imf); | ||||
imf = imo->imo_mfilters ? &imo->imo_mfilters[idx] : NULL; | |||||
if (imf) | |||||
imf_leave(imf); | imf_leave(imf); | ||||
inm = imo->imo_membership[idx]; | if ((inm = imf->imf_inm) != NULL) { | ||||
ifp = inm->inm_ifp; | if ((ifp = inm->inm_ifp) != NULL) { | ||||
if (ifp != NULL) { | |||||
CURVNET_SET(ifp->if_vnet); | CURVNET_SET(ifp->if_vnet); | ||||
(void)in_leavegroup(inm, imf); | (void)in_leavegroup(inm, imf); | ||||
CURVNET_RESTORE(); | CURVNET_RESTORE(); | ||||
} else { | } else { | ||||
(void)in_leavegroup(inm, imf); | (void)in_leavegroup(inm, imf); | ||||
} | } | ||||
if (imf) | |||||
imf_purge(imf); | |||||
} | } | ||||
ip_free_mfilter(imf); | |||||
if (imo->imo_mfilters) | } | ||||
free(imo->imo_mfilters, M_INMFILTER); | |||||
free(imo->imo_membership, M_IPMOPTS); | |||||
free(imo, M_IPMOPTS); | free(imo, M_IPMOPTS); | ||||
} | } | ||||
/* | /* | ||||
* Discard the IP multicast options (and source filters). To minimize | * Discard the IP multicast options (and source filters). To minimize | ||||
* the amount of work done while holding locks such as the INP's | * the amount of work done while holding locks such as the INP's | ||||
* pcbinfo lock (which is used in the receive path), the free | * pcbinfo lock (which is used in the receive path), the free | ||||
* operation is deferred to the epoch callback task. | * operation is deferred to the epoch callback task. | ||||
Show All 19 Lines | inp_get_source_filters(struct inpcb *inp, struct sockopt *sopt) | ||||
struct ip_moptions *imo; | struct ip_moptions *imo; | ||||
struct in_mfilter *imf; | struct in_mfilter *imf; | ||||
struct ip_msource *ims; | struct ip_msource *ims; | ||||
struct in_msource *lims; | struct in_msource *lims; | ||||
struct sockaddr_in *psin; | struct sockaddr_in *psin; | ||||
struct sockaddr_storage *ptss; | struct sockaddr_storage *ptss; | ||||
struct sockaddr_storage *tss; | struct sockaddr_storage *tss; | ||||
int error; | int error; | ||||
size_t idx, nsrcs, ncsrcs; | size_t nsrcs, ncsrcs; | ||||
INP_WLOCK_ASSERT(inp); | INP_WLOCK_ASSERT(inp); | ||||
imo = inp->inp_moptions; | imo = inp->inp_moptions; | ||||
KASSERT(imo != NULL, ("%s: null ip_moptions", __func__)); | KASSERT(imo != NULL, ("%s: null ip_moptions", __func__)); | ||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
Show All 10 Lines | if (ifp == NULL) | ||||
return (EINVAL); | return (EINVAL); | ||||
INP_WLOCK(inp); | INP_WLOCK(inp); | ||||
/* | /* | ||||
* Lookup group on the socket. | * Lookup group on the socket. | ||||
*/ | */ | ||||
gsa = (sockunion_t *)&msfr.msfr_group; | gsa = (sockunion_t *)&msfr.msfr_group; | ||||
idx = imo_match_group(imo, ifp, &gsa->sa); | imf = imo_match_group(imo, ifp, &gsa->sa); | ||||
if (idx == -1 || imo->imo_mfilters == NULL) { | if (imf == NULL) { | ||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
return (EADDRNOTAVAIL); | return (EADDRNOTAVAIL); | ||||
} | } | ||||
imf = &imo->imo_mfilters[idx]; | |||||
/* | /* | ||||
* Ignore memberships which are in limbo. | * Ignore memberships which are in limbo. | ||||
*/ | */ | ||||
if (imf->imf_st[1] == MCAST_UNDEFINED) { | if (imf->imf_st[1] == MCAST_UNDEFINED) { | ||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
return (EAGAIN); | return (EAGAIN); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 243 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
struct group_source_req gsr; | struct group_source_req gsr; | ||||
sockunion_t *gsa, *ssa; | sockunion_t *gsa, *ssa; | ||||
struct ifnet *ifp; | struct ifnet *ifp; | ||||
struct in_mfilter *imf; | struct in_mfilter *imf; | ||||
struct ip_moptions *imo; | struct ip_moptions *imo; | ||||
struct in_multi *inm; | struct in_multi *inm; | ||||
struct in_msource *lims; | struct in_msource *lims; | ||||
size_t idx; | |||||
int error, is_new; | int error, is_new; | ||||
ifp = NULL; | ifp = NULL; | ||||
imf = NULL; | |||||
lims = NULL; | lims = NULL; | ||||
error = 0; | error = 0; | ||||
is_new = 0; | |||||
memset(&gsr, 0, sizeof(struct group_source_req)); | memset(&gsr, 0, sizeof(struct group_source_req)); | ||||
gsa = (sockunion_t *)&gsr.gsr_group; | gsa = (sockunion_t *)&gsr.gsr_group; | ||||
gsa->ss.ss_family = AF_UNSPEC; | gsa->ss.ss_family = AF_UNSPEC; | ||||
ssa = (sockunion_t *)&gsr.gsr_source; | ssa = (sockunion_t *)&gsr.gsr_source; | ||||
ssa->ss.ss_family = AF_UNSPEC; | ssa->ss.ss_family = AF_UNSPEC; | ||||
switch (sopt->sopt_name) { | switch (sopt->sopt_name) { | ||||
▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Lines | CTR2(KTR_IGMPV3, "%s: unknown sopt_name %d", | ||||
__func__, sopt->sopt_name); | __func__, sopt->sopt_name); | ||||
return (EOPNOTSUPP); | return (EOPNOTSUPP); | ||||
break; | break; | ||||
} | } | ||||
if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) | if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) | ||||
return (EADDRNOTAVAIL); | return (EADDRNOTAVAIL); | ||||
IN_MULTI_LOCK(); | |||||
imo = inp_findmoptions(inp); | imo = inp_findmoptions(inp); | ||||
idx = imo_match_group(imo, ifp, &gsa->sa); | imf = imo_match_group(imo, ifp, &gsa->sa); | ||||
if (idx == -1) { | if (imf == NULL) { | ||||
is_new = 1; | is_new = 1; | ||||
inm = NULL; | |||||
} else { | } else { | ||||
inm = imo->imo_membership[idx]; | is_new = 0; | ||||
imf = &imo->imo_mfilters[idx]; | |||||
inm = imf->imf_inm; | |||||
if (ssa->ss.ss_family != AF_UNSPEC) { | if (ssa->ss.ss_family != AF_UNSPEC) { | ||||
/* | /* | ||||
* MCAST_JOIN_SOURCE_GROUP on an exclusive membership | * MCAST_JOIN_SOURCE_GROUP on an exclusive membership | ||||
* is an error. On an existing inclusive membership, | * is an error. On an existing inclusive membership, | ||||
* it just adds the source to the filter list. | * it just adds the source to the filter list. | ||||
*/ | */ | ||||
if (imf->imf_st[1] != MCAST_INCLUDE) { | if (imf->imf_st[1] != MCAST_INCLUDE) { | ||||
error = EINVAL; | error = EINVAL; | ||||
Show All 10 Lines | if (ssa->ss.ss_family != AF_UNSPEC) { | ||||
* in_msource is transactioned just as for anything | * in_msource is transactioned just as for anything | ||||
* else in SSM -- but note naive use of inm_graft() | * else in SSM -- but note naive use of inm_graft() | ||||
* below for allocating new filter entries. | * below for allocating new filter entries. | ||||
* | * | ||||
* This is only an issue if someone mixes the | * This is only an issue if someone mixes the | ||||
* full-state SSM API with the delta-based API, | * full-state SSM API with the delta-based API, | ||||
* which is discouraged in the relevant RFCs. | * which is discouraged in the relevant RFCs. | ||||
*/ | */ | ||||
lims = imo_match_source(imo, idx, &ssa->sa); | lims = imo_match_source(imf, &ssa->sa); | ||||
if (lims != NULL /*&& | if (lims != NULL /*&& | ||||
lims->imsl_st[1] == MCAST_INCLUDE*/) { | lims->imsl_st[1] == MCAST_INCLUDE*/) { | ||||
error = EADDRNOTAVAIL; | error = EADDRNOTAVAIL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
} else { | } else { | ||||
/* | /* | ||||
* MCAST_JOIN_GROUP on an existing exclusive | * MCAST_JOIN_GROUP on an existing exclusive | ||||
Show All 16 Lines | if (imf == NULL) { | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
* Begin state merge transaction at socket layer. | * Begin state merge transaction at socket layer. | ||||
*/ | */ | ||||
INP_WLOCK_ASSERT(inp); | INP_WLOCK_ASSERT(inp); | ||||
if (is_new) { | |||||
if (imo->imo_num_memberships == imo->imo_max_memberships) { | |||||
error = imo_grow(imo); | |||||
if (error) | |||||
goto out_inp_locked; | |||||
} | |||||
/* | /* | ||||
* Allocate the new slot upfront so we can deal with | |||||
* grafting the new source filter in same code path | |||||
* as for join-source on existing membership. | |||||
*/ | |||||
idx = imo->imo_num_memberships; | |||||
imo->imo_membership[idx] = NULL; | |||||
imo->imo_num_memberships++; | |||||
KASSERT(imo->imo_mfilters != NULL, | |||||
("%s: imf_mfilters vector was not allocated", __func__)); | |||||
imf = &imo->imo_mfilters[idx]; | |||||
KASSERT(RB_EMPTY(&imf->imf_sources), | |||||
("%s: imf_sources not empty", __func__)); | |||||
} | |||||
/* | |||||
* Graft new source into filter list for this inpcb's | * Graft new source into filter list for this inpcb's | ||||
* membership of the group. The in_multi may not have | * membership of the group. The in_multi may not have | ||||
* been allocated yet if this is a new membership, however, | * been allocated yet if this is a new membership, however, | ||||
* the in_mfilter slot will be allocated and must be initialized. | * the in_mfilter slot will be allocated and must be initialized. | ||||
* | * | ||||
* Note: Grafting of exclusive mode filters doesn't happen | * Note: Grafting of exclusive mode filters doesn't happen | ||||
* in this path. | * in this path. | ||||
* XXX: Should check for non-NULL lims (node exists but may | * XXX: Should check for non-NULL lims (node exists but may | ||||
* not be in-mode) for interop with full-state API. | * not be in-mode) for interop with full-state API. | ||||
*/ | */ | ||||
if (ssa->ss.ss_family != AF_UNSPEC) { | if (ssa->ss.ss_family != AF_UNSPEC) { | ||||
/* Membership starts in IN mode */ | /* Membership starts in IN mode */ | ||||
if (is_new) { | if (is_new) { | ||||
CTR1(KTR_IGMPV3, "%s: new join w/source", __func__); | CTR1(KTR_IGMPV3, "%s: new join w/source", __func__); | ||||
imf_init(imf, MCAST_UNDEFINED, MCAST_INCLUDE); | imf = ip_alloc_mfilter(M_NOWAIT, MCAST_UNDEFINED, MCAST_INCLUDE); | ||||
if (imf == NULL) { | |||||
error = ENOMEM; | |||||
goto out_inp_locked; | |||||
} | |||||
} else { | } else { | ||||
CTR2(KTR_IGMPV3, "%s: %s source", __func__, "allow"); | CTR2(KTR_IGMPV3, "%s: %s source", __func__, "allow"); | ||||
} | } | ||||
lims = imf_graft(imf, MCAST_INCLUDE, &ssa->sin); | lims = imf_graft(imf, MCAST_INCLUDE, &ssa->sin); | ||||
if (lims == NULL) { | if (lims == NULL) { | ||||
CTR1(KTR_IGMPV3, "%s: merge imf state failed", | CTR1(KTR_IGMPV3, "%s: merge imf state failed", | ||||
__func__); | __func__); | ||||
error = ENOMEM; | error = ENOMEM; | ||||
goto out_imo_free; | goto out_inp_locked; | ||||
} | } | ||||
} else { | } else { | ||||
/* No address specified; Membership starts in EX mode */ | /* No address specified; Membership starts in EX mode */ | ||||
if (is_new) { | if (is_new) { | ||||
CTR1(KTR_IGMPV3, "%s: new join w/o source", __func__); | CTR1(KTR_IGMPV3, "%s: new join w/o source", __func__); | ||||
imf_init(imf, MCAST_UNDEFINED, MCAST_EXCLUDE); | imf = ip_alloc_mfilter(M_NOWAIT, MCAST_UNDEFINED, MCAST_EXCLUDE); | ||||
if (imf == NULL) { | |||||
error = ENOMEM; | |||||
goto out_inp_locked; | |||||
} | } | ||||
} | } | ||||
} | |||||
/* | /* | ||||
* Begin state merge transaction at IGMP layer. | * Begin state merge transaction at IGMP layer. | ||||
*/ | */ | ||||
if (is_new) { | |||||
in_pcbref(inp); | in_pcbref(inp); | ||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
IN_MULTI_LOCK(); | |||||
if (is_new) { | |||||
error = in_joingroup_locked(ifp, &gsa->sin.sin_addr, imf, | error = in_joingroup_locked(ifp, &gsa->sin.sin_addr, imf, | ||||
&inm); | &imf->imf_inm); | ||||
INP_WLOCK(inp); | |||||
if (in_pcbrele_wlocked(inp)) { | |||||
error = ENXIO; | |||||
goto out_inp_unlocked; | |||||
} | |||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: in_joingroup_locked failed", | CTR1(KTR_IGMPV3, "%s: in_joingroup_locked failed", | ||||
__func__); | __func__); | ||||
IN_MULTI_LIST_UNLOCK(); | goto out_inp_locked; | ||||
goto out_imo_free; | |||||
} | } | ||||
inm_acquire(inm); | inm_acquire(imf->imf_inm); | ||||
imo->imo_membership[idx] = inm; | |||||
} else { | } else { | ||||
CTR1(KTR_IGMPV3, "%s: merge inm state", __func__); | CTR1(KTR_IGMPV3, "%s: merge inm state", __func__); | ||||
IN_MULTI_LIST_LOCK(); | IN_MULTI_LIST_LOCK(); | ||||
error = inm_merge(inm, imf); | error = inm_merge(inm, imf); | ||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: failed to merge inm state", | CTR1(KTR_IGMPV3, "%s: failed to merge inm state", | ||||
__func__); | __func__); | ||||
IN_MULTI_LIST_UNLOCK(); | IN_MULTI_LIST_UNLOCK(); | ||||
goto out_in_multi_locked; | imf_rollback(imf); | ||||
imf_reap(imf); | |||||
goto out_inp_locked; | |||||
} | } | ||||
CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__); | CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__); | ||||
error = igmp_change_state(inm); | error = igmp_change_state(inm); | ||||
IN_MULTI_LIST_UNLOCK(); | IN_MULTI_LIST_UNLOCK(); | ||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: failed igmp downcall", | CTR1(KTR_IGMPV3, "%s: failed igmp downcall", | ||||
__func__); | __func__); | ||||
goto out_in_multi_locked; | imf_rollback(imf); | ||||
imf_reap(imf); | |||||
goto out_inp_locked; | |||||
} | } | ||||
} | } | ||||
if (is_new) | |||||
ip_insert_mfilter(&imo->imo_head, imf); | |||||
out_in_multi_locked: | imf_commit(imf); | ||||
imf = NULL; | |||||
out_inp_locked: | |||||
INP_WUNLOCK(inp); | |||||
out_inp_unlocked: | |||||
IN_MULTI_UNLOCK(); | IN_MULTI_UNLOCK(); | ||||
INP_WLOCK(inp); | |||||
if (in_pcbrele_wlocked(inp)) | |||||
return (ENXIO); | |||||
if (error) { | |||||
imf_rollback(imf); | |||||
if (is_new) | |||||
imf_purge(imf); | |||||
else | |||||
imf_reap(imf); | |||||
} else { | |||||
imf_commit(imf); | |||||
} | |||||
out_imo_free: | if (is_new && imf) { | ||||
if (error && is_new) { | if (imf->imf_inm != NULL) { | ||||
inm = imo->imo_membership[idx]; | |||||
if (inm != NULL) { | |||||
IN_MULTI_LIST_LOCK(); | IN_MULTI_LIST_LOCK(); | ||||
inm_release_deferred(inm); | inm_release_deferred(imf->imf_inm); | ||||
IN_MULTI_LIST_UNLOCK(); | IN_MULTI_LIST_UNLOCK(); | ||||
} | } | ||||
imo->imo_membership[idx] = NULL; | ip_free_mfilter(imf); | ||||
--imo->imo_num_memberships; | |||||
} | } | ||||
out_inp_locked: | |||||
INP_WUNLOCK(inp); | |||||
return (error); | return (error); | ||||
} | } | ||||
/* | /* | ||||
* Leave an IPv4 multicast group on an inpcb, possibly with a source. | * Leave an IPv4 multicast group on an inpcb, possibly with a source. | ||||
*/ | */ | ||||
static int | static int | ||||
inp_leave_group(struct inpcb *inp, struct sockopt *sopt) | inp_leave_group(struct inpcb *inp, struct sockopt *sopt) | ||||
{ | { | ||||
struct group_source_req gsr; | struct group_source_req gsr; | ||||
struct ip_mreq_source mreqs; | struct ip_mreq_source mreqs; | ||||
struct rm_priotracker in_ifa_tracker; | struct rm_priotracker in_ifa_tracker; | ||||
sockunion_t *gsa, *ssa; | sockunion_t *gsa, *ssa; | ||||
struct ifnet *ifp; | struct ifnet *ifp; | ||||
struct in_mfilter *imf; | struct in_mfilter *imf; | ||||
struct ip_moptions *imo; | struct ip_moptions *imo; | ||||
struct in_msource *ims; | struct in_msource *ims; | ||||
struct in_multi *inm; | struct in_multi *inm; | ||||
size_t idx; | |||||
int error, is_final; | int error, is_final; | ||||
ifp = NULL; | ifp = NULL; | ||||
error = 0; | error = 0; | ||||
is_final = 1; | is_final = 1; | ||||
memset(&gsr, 0, sizeof(struct group_source_req)); | memset(&gsr, 0, sizeof(struct group_source_req)); | ||||
gsa = (sockunion_t *)&gsr.gsr_group; | gsa = (sockunion_t *)&gsr.gsr_group; | ||||
▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Lines | CTR2(KTR_IGMPV3, "%s: unknown sopt_name %d", | ||||
__func__, sopt->sopt_name); | __func__, sopt->sopt_name); | ||||
return (EOPNOTSUPP); | return (EOPNOTSUPP); | ||||
break; | break; | ||||
} | } | ||||
if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr))) | if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr))) | ||||
return (EINVAL); | return (EINVAL); | ||||
IN_MULTI_LOCK(); | |||||
/* | /* | ||||
* Find the membership in the membership array. | * Find the membership in the membership array. | ||||
*/ | */ | ||||
imo = inp_findmoptions(inp); | imo = inp_findmoptions(inp); | ||||
idx = imo_match_group(imo, ifp, &gsa->sa); | imf = imo_match_group(imo, ifp, &gsa->sa); | ||||
if (idx == -1) { | if (imf == NULL) { | ||||
error = EADDRNOTAVAIL; | error = EADDRNOTAVAIL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
inm = imo->imo_membership[idx]; | inm = imf->imf_inm; | ||||
imf = &imo->imo_mfilters[idx]; | |||||
if (ssa->ss.ss_family != AF_UNSPEC) | if (ssa->ss.ss_family != AF_UNSPEC) | ||||
is_final = 0; | is_final = 0; | ||||
/* | /* | ||||
* Begin state merge transaction at socket layer. | * Begin state merge transaction at socket layer. | ||||
*/ | */ | ||||
INP_WLOCK_ASSERT(inp); | INP_WLOCK_ASSERT(inp); | ||||
/* | /* | ||||
* If we were instructed only to leave a given source, do so. | * If we were instructed only to leave a given source, do so. | ||||
* MCAST_LEAVE_SOURCE_GROUP is only valid for inclusive memberships. | * MCAST_LEAVE_SOURCE_GROUP is only valid for inclusive memberships. | ||||
*/ | */ | ||||
if (is_final) { | if (is_final) { | ||||
ip_remove_mfilter(&imo->imo_head, imf); | |||||
imf_leave(imf); | imf_leave(imf); | ||||
} else { | } else { | ||||
if (imf->imf_st[0] == MCAST_EXCLUDE) { | if (imf->imf_st[0] == MCAST_EXCLUDE) { | ||||
error = EADDRNOTAVAIL; | error = EADDRNOTAVAIL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
ims = imo_match_source(imo, idx, &ssa->sa); | ims = imo_match_source(imf, &ssa->sa); | ||||
if (ims == NULL) { | if (ims == NULL) { | ||||
CTR3(KTR_IGMPV3, "%s: source 0x%08x %spresent", | CTR3(KTR_IGMPV3, "%s: source 0x%08x %spresent", | ||||
__func__, ntohl(ssa->sin.sin_addr.s_addr), "not "); | __func__, ntohl(ssa->sin.sin_addr.s_addr), "not "); | ||||
error = EADDRNOTAVAIL; | error = EADDRNOTAVAIL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
CTR2(KTR_IGMPV3, "%s: %s source", __func__, "block"); | CTR2(KTR_IGMPV3, "%s: %s source", __func__, "block"); | ||||
error = imf_prune(imf, &ssa->sin); | error = imf_prune(imf, &ssa->sin); | ||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: merge imf state failed", | CTR1(KTR_IGMPV3, "%s: merge imf state failed", | ||||
__func__); | __func__); | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
* Begin state merge transaction at IGMP layer. | * Begin state merge transaction at IGMP layer. | ||||
*/ | */ | ||||
in_pcbref(inp); | if (is_final == 0) { | ||||
Done Inline ActionsMight as well switch it to bool and write !is_final. markj: Might as well switch it to bool and write `!is_final`. | |||||
INP_WUNLOCK(inp); | |||||
IN_MULTI_LOCK(); | |||||
if (is_final) { | |||||
/* | |||||
* Give up the multicast address record to which | |||||
* the membership points. | |||||
*/ | |||||
(void)in_leavegroup_locked(inm, imf); | |||||
} else { | |||||
CTR1(KTR_IGMPV3, "%s: merge inm state", __func__); | CTR1(KTR_IGMPV3, "%s: merge inm state", __func__); | ||||
IN_MULTI_LIST_LOCK(); | IN_MULTI_LIST_LOCK(); | ||||
error = inm_merge(inm, imf); | error = inm_merge(inm, imf); | ||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: failed to merge inm state", | CTR1(KTR_IGMPV3, "%s: failed to merge inm state", | ||||
__func__); | __func__); | ||||
IN_MULTI_LIST_UNLOCK(); | IN_MULTI_LIST_UNLOCK(); | ||||
goto out_in_multi_locked; | imf_rollback(imf); | ||||
imf_reap(imf); | |||||
goto out_inp_locked; | |||||
} | } | ||||
CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__); | CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__); | ||||
error = igmp_change_state(inm); | error = igmp_change_state(inm); | ||||
IN_MULTI_LIST_UNLOCK(); | IN_MULTI_LIST_UNLOCK(); | ||||
if (error) { | if (error) { | ||||
CTR1(KTR_IGMPV3, "%s: failed igmp downcall", | CTR1(KTR_IGMPV3, "%s: failed igmp downcall", | ||||
__func__); | __func__); | ||||
imf_rollback(imf); | |||||
imf_reap(imf); | |||||
goto out_inp_locked; | |||||
} | } | ||||
} | } | ||||
out_in_multi_locked: | |||||
IN_MULTI_UNLOCK(); | |||||
INP_WLOCK(inp); | |||||
if (in_pcbrele_wlocked(inp)) | |||||
return (ENXIO); | |||||
if (error) | |||||
imf_rollback(imf); | |||||
else | |||||
imf_commit(imf); | imf_commit(imf); | ||||
imf_reap(imf); | imf_reap(imf); | ||||
if (is_final) { | |||||
/* Remove the gap in the membership and filter array. */ | |||||
KASSERT(RB_EMPTY(&imf->imf_sources), | |||||
("%s: imf_sources not empty", __func__)); | |||||
for (++idx; idx < imo->imo_num_memberships; ++idx) { | |||||
imo->imo_membership[idx - 1] = imo->imo_membership[idx]; | |||||
imo->imo_mfilters[idx - 1] = imo->imo_mfilters[idx]; | |||||
} | |||||
imf_init(&imo->imo_mfilters[idx - 1], MCAST_UNDEFINED, | |||||
MCAST_EXCLUDE); | |||||
imo->imo_num_memberships--; | |||||
} | |||||
out_inp_locked: | out_inp_locked: | ||||
INP_WUNLOCK(inp); | INP_WUNLOCK(inp); | ||||
if (is_final && imf) { | |||||
/* | |||||
* Give up the multicast address record to which | |||||
* the membership points. | |||||
*/ | |||||
(void) in_leavegroup_locked(imf->imf_inm, imf); | |||||
ip_free_mfilter(imf); | |||||
} | |||||
IN_MULTI_UNLOCK(); | |||||
return (error); | return (error); | ||||
} | } | ||||
/* | /* | ||||
* Select the interface for transmitting IPv4 multicast datagrams. | * Select the interface for transmitting IPv4 multicast datagrams. | ||||
* | * | ||||
* Either an instance of struct in_addr or an instance of struct ip_mreqn | * Either an instance of struct in_addr or an instance of struct ip_mreqn | ||||
* may be passed to this socket option. An address of INADDR_ANY or an | * may be passed to this socket option. An address of INADDR_ANY or an | ||||
▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | |||||
inp_set_source_filters(struct inpcb *inp, struct sockopt *sopt) | inp_set_source_filters(struct inpcb *inp, struct sockopt *sopt) | ||||
{ | { | ||||
struct __msfilterreq msfr; | struct __msfilterreq msfr; | ||||
sockunion_t *gsa; | sockunion_t *gsa; | ||||
struct ifnet *ifp; | struct ifnet *ifp; | ||||
struct in_mfilter *imf; | struct in_mfilter *imf; | ||||
struct ip_moptions *imo; | struct ip_moptions *imo; | ||||
struct in_multi *inm; | struct in_multi *inm; | ||||
size_t idx; | |||||
int error; | int error; | ||||
error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq), | error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq), | ||||
sizeof(struct __msfilterreq)); | sizeof(struct __msfilterreq)); | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
if (msfr.msfr_nsrcs > in_mcast_maxsocksrc) | if (msfr.msfr_nsrcs > in_mcast_maxsocksrc) | ||||
Show All 20 Lines | inp_set_source_filters(struct inpcb *inp, struct sockopt *sopt) | ||||
if (ifp == NULL) | if (ifp == NULL) | ||||
return (EADDRNOTAVAIL); | return (EADDRNOTAVAIL); | ||||
/* | /* | ||||
* Take the INP write lock. | * Take the INP write lock. | ||||
* Check if this socket is a member of this group. | * Check if this socket is a member of this group. | ||||
*/ | */ | ||||
imo = inp_findmoptions(inp); | imo = inp_findmoptions(inp); | ||||
idx = imo_match_group(imo, ifp, &gsa->sa); | imf = imo_match_group(imo, ifp, &gsa->sa); | ||||
if (idx == -1 || imo->imo_mfilters == NULL) { | if (imf == NULL) { | ||||
error = EADDRNOTAVAIL; | error = EADDRNOTAVAIL; | ||||
goto out_inp_locked; | goto out_inp_locked; | ||||
} | } | ||||
inm = imo->imo_membership[idx]; | inm = imf->imf_inm; | ||||
imf = &imo->imo_mfilters[idx]; | |||||
/* | /* | ||||
* Begin state merge transaction at socket layer. | * Begin state merge transaction at socket layer. | ||||
*/ | */ | ||||
INP_WLOCK_ASSERT(inp); | INP_WLOCK_ASSERT(inp); | ||||
imf->imf_st[1] = msfr.msfr_fmode; | imf->imf_st[1] = msfr.msfr_fmode; | ||||
▲ Show 20 Lines • Show All 439 Lines • Show Last 20 Lines |
@markj: The locking order is described here. Can you show a witness warning with the LOR?