net/mld: Add reference counting on the group joins. If there are multiple joins from the same group on this, the don't really leave the group until the matching number of leaves happen. Also add to hooks for the router case: Keep track of the number members NOT on this host. Integrate this with the Leave logic to that the group structure can persist while there no non-local members of the group as well.

This commit is contained in:
Gregory Nutt 2018-11-06 14:46:03 -06:00
parent f58e7976df
commit 62e4a41209
11 changed files with 251 additions and 71 deletions

View file

@ -375,8 +375,8 @@ struct mld_mcast_listen_done_v1_s
struct mld_stats_s
{
net_stats_t joins; /* Requests to join a group */
net_stats_t leaves; /* Requests to leave a group */
net_stats_t njoins; /* Requests to join a group */
net_stats_t nleaves; /* Requests to leave a group */
net_stats_t query_sched; /* General QUERY packets scheduled */
net_stats_t report_sched; /* Unsolicited REPORT packets scheduled */
net_stats_t done_sched; /* DONE packets scheduled */

View file

@ -8,7 +8,6 @@ if NET_ICMPv6
menuconfig NET_MLD
bool "Multicast Listener Discovery (MLD)"
default n
depends on EXPERIMENTAL
select NET_MCASTGROUP
select NETDEV_IFINDEX
---help---
@ -16,6 +15,14 @@ menuconfig NET_MLD
if NET_MLD
config NET_MLD_ROUTER
bool # "MLD Router support"
default n
depends on EXPERIMENTAL
---help---
Enables a few hooks that will be needed for router support in the
future. Not yet ready for prime time.
config NET_MLD_DEBUG
bool "Force MLD debug"
default n

View file

@ -211,10 +211,15 @@ struct mld_group_s
WDOG_ID polldog; /* Timer used for periodic or delayed events */
WDOG_ID v1dog; /* MLDv1 compatibility mode timer */
sem_t sem; /* Used to wait for message transmission */
#ifdef CONFIG_NET_MLD_ROUTER
uint16_t members; /* Number of members currently reporting (excludes us) */
uint16_t lstmbrs; /* Number of members reporting (last query) */
#endif
uint8_t ifindex; /* Interface index */
uint8_t flags; /* See MLD_ flags definitions */
uint8_t msgtype; /* Pending message type to send (if non-zero) */
uint8_t count; /* Reports remaining in repetition count */
uint8_t njoins; /* Number of joins from this host */
};
/****************************************************************************

View file

@ -81,7 +81,7 @@
int mld_done_v1(FAR struct net_driver_s *dev,
FAR const struct mld_mcast_listen_done_v1_s *done)
{
#ifdef CONFIG_MLD_ROUTER
#ifdef CONFIG_NET_MLD_ROUTER
FAR struct ipv6_hdr_s *ipv6 = IPv6BUF;
FAR struct mld_group_s *group;

View file

@ -52,6 +52,53 @@
#ifdef CONFIG_NET_MLD
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: mld_group_startup
*
* Description:
* Set up the startup actions when the first member from this host joins
* the group.
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static int mld_group_startup(FAR const struct ipv6_mreq *mrec,
FAR struct net_driver_s *dev,
FAR struct mld_group_s *group)
{
int ret;
/* Send the Version 2 Multicast Listener Report (Assumes MLDv2 mode
* initially).
*/
MLD_STATINCR(g_netstats.mld.report_sched);
ret = mld_waitmsg(group, MLD_SEND_V2REPORT);
if (ret < 0)
{
mlderr("ERROR: Failed to schedule Report: %d\n", ret);
return ret;
}
/* And start the timer at 1 second */
SET_MLD_STARTUP(group->flags);
group->count = MLD_UNSOLREPORT_COUNT - 1;
mld_start_polltimer(group, MSEC2TICK(MLD_UNSOLREPORT_MSEC));
/* Add the group (MAC) address to the Ethernet drivers MAC filter list */
mld_addmcastmac(dev, mrec->ipv6mr_multiaddr.s6_addr16);
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
@ -80,7 +127,7 @@
int mld_joingroup(FAR const struct ipv6_mreq *mrec)
{
FAR struct net_driver_s *dev;
struct mld_group_s *group;
FAR struct mld_group_s *group;
int ret;
DEBUGASSERT(mrec != NULL);
@ -130,36 +177,66 @@ int mld_joingroup(FAR const struct ipv6_mreq *mrec)
return -ENOMEM;
}
MLD_STATINCR(g_netstats.mld.joins);
/* Indicate one request to join the group from this this host */
/* Send the Version 2 Multicast Listener Report (Assumes MLDv2 mode
* initially?).
*/
group->njoins = 1;
MLD_STATINCR(g_netstats.mld.report_sched);
ret = mld_waitmsg(group, MLD_SEND_V2REPORT);
/* Set up the group startup operations */
ret = mld_group_startup(mrec, dev, group);
if (ret < 0)
{
mlderr("ERROR: Failed to schedule Report: %d\n", ret);
mlderr("ERROR: Failed to start group: %d\n", ret);
mld_grpfree(dev, group);
return ret;
}
/* And start the timer at 1 second */
MLD_STATINCR(g_netstats.mld.njoins);
}
else
{
/* The group already exists; a task from this host is joining an
* existing group.
*/
SET_MLD_STARTUP(group->flags);
group->count = MLD_UNSOLREPORT_COUNT - 1;
mld_start_polltimer(group, MSEC2TICK(MLD_UNSOLREPORT_MSEC));
mldinfo("Join existing group\n");
/* Add the group (MAC) address to the Ethernet drivers MAC filter list */
#ifdef CONFIG_NET_MLD_ROUTER
/* In the Router case this could still be the first join from this
* host. If this is the first join from this host, then we need to
* perform the group startup operations.
*/
mld_addmcastmac(dev, mrec->ipv6mr_multiaddr.s6_addr16);
return OK;
if (group->join == 0)
{
/* This is the for join from this host. Perform out start up
* operations.
*/
ret = mld_group_startup(mrec, dev, group);
if (ret < 0)
{
mlderr("ERROR: Failed to start group: %d\n", ret);
mld_grpfree(dev, group);
return ret;
}
}
#else
/* Not a router? There there must be another join from this host or
* how could the group have been created?
*/
DEBUGASSERT(group->njoins > 0);
#endif
/* Indicate one more request to join the group from this this host */
DEBUGASSERT(group->njoins < UINT8_MAX);
group->njoins++;
MLD_STATINCR(g_netstats.mld.njoins);
}
/* Return EEXIST if the address is already a member of the group */
return -EEXIST;
return OK;
}
#endif /* CONFIG_NET_MLD */

View file

@ -122,30 +122,37 @@ int mld_leavegroup(FAR const struct ipv6_mreq *mrec)
{
mldinfo("Leaving group: %p\n", group);
/* Cancel the timers and discard any queued Reports. Canceling the
* timer will prevent any new Reports from being sent; clearing the
* flags will discard any pending Reports that could interfere with
* leaving the group.
/* Indicate one fewer members of the group from this host */
DEBUGASSERT(group->njoins > 0);
group->njoins--;
MLD_STATINCR(g_netstats.mld.nleaves);
/* Take no further actions if there are other members of this group
* on this host.
*/
wd_cancel(group->polldog);
wd_cancel(group->v1dog);
CLR_MLD_SCHEDMSG(group->flags);
CLR_MLD_WAITMSG(group->flags);
if (group->njoins > 0)
{
return OK;
}
MLD_STATINCR(g_netstats.mld.leaves);
/* Perform actions that would be performed only when the number of
* njoins from this host goes to zero.
*/
/* Send a leave if the LASTREPORT flag is set for the group. If there
* are other members of the group, then their reports will clear the
* LAST REPORT flag. In this case we know that there are other
* members of the group and we do not have to send the Done message.
if (group->njoins == 0)
{
/* Send a leave if the LASTREPORT flag is set for the group. If
* there are other members of the group, then their reports will
* clear the LAST REPORT flag. In this case we know that there are
* other members of the group and we do not have to send the Done
* message.
*
* The MLDv1 router responds to the Done message with a multicast-address-
* specific (MAS) Query. If any other node responds to the Query with a
* Report message the there are still listeners present.
*
* REVISIT: What if we are in MLDv2 mode? There is no Done MLDv2 Done
* messages. What should be sent (if anything) instead?
* The MLDv1 router responds to the Done message with a multicast-
* address- specific (MAS) Query. If any other node responds to
* the Query with a Report message the there are still listeners
* present.
*/
if (IS_MLD_LASTREPORT(group->flags))
@ -154,6 +161,10 @@ int mld_leavegroup(FAR const struct ipv6_mreq *mrec)
MLD_STATINCR(g_netstats.mld.done_sched);
/* REVIST: This will interfere is the are any other tasks
* waiting for a message to be sent. Can that happen?
*/
ret = mld_waitmsg(group, MLD_SEND_V1DONE);
if (ret < 0)
{
@ -161,13 +172,37 @@ int mld_leavegroup(FAR const struct ipv6_mreq *mrec)
}
}
/* Remove the group address from the Ethernet drivers MAC filter
* set
*/
mld_removemcastmac(dev, mrec->ipv6mr_multiaddr.s6_addr16);
/* Perform additional actions if not a router OR if a router and the
* number of members not on this host is also zero.
*/
#ifdef CONFIG_NET_MLD_ROUTER
if (group->members == 0 && group->lstmbrs == 0)
#endif
{
/* Cancel the timers and discard any queued Reports. Canceling
* the timer will prevent any new Reports from being sent;
* clearing the flags will discard any pending Reports that
* could interfere with freeing the group.
*/
wd_cancel(group->polldog);
wd_cancel(group->v1dog);
CLR_MLD_SCHEDMSG(group->flags);
CLR_MLD_WAITMSG(group->flags);
/* Free the group structure */
mld_grpfree(dev, group);
}
}
/* And remove the group address from the Ethernet drivers MAC filter set */
mld_removemcastmac(dev, mrec->ipv6mr_multiaddr.s6_addr16);
return OK;
}

View file

@ -394,6 +394,16 @@ int mld_query(FAR struct net_driver_s *dev,
mldinfo("WARNING: MLDv2 query received in MLDv1 "
"compatibility mode\n");
}
#ifdef CONFIG_NET_MLD_ROUTER
/* Save the number of members that reported in the previous
* query cycle; reset the number of members that have
* reported in the new query cycle.
*/
member->lstmbrs = member->members;
member->members = 0;
#endif
}
}
@ -458,6 +468,15 @@ int mld_query(FAR struct net_driver_s *dev,
mld_check_querier(dev, ipv6, group);
#ifdef CONFIG_NET_MLD_ROUTER
/* Save the number of members that reported in the previous query cycle;
* reset the number of members that have reported in the new query cycle.
*/
group->lstmbrs = group->members;
group->members = 0;
#endif
/* Warn if we received a MLDv2 query in MLDv1 compatibility mode. */
if (!mldv1 && IS_MLD_V1COMPAT(group->flags))

View file

@ -80,7 +80,7 @@ int mld_report(FAR struct net_driver_s *dev, FAR const net_ipv6addr_t grpaddr)
* REVISIT: Router support is not yet implemented.
*/
#ifdef CONFIG_MLD_ROUTER
#ifdef CONFIG_NET_MLD_ROUTER
group = mld_grpallocfind(dev, grpaddr);
if (group == NULL)
{
@ -103,6 +103,12 @@ int mld_report(FAR struct net_driver_s *dev, FAR const net_ipv6addr_t grpaddr)
CLR_MLD_LASTREPORT(group->flags);
#ifdef CONFIG_NET_MLD_ROUTER
/* Increment the number of members that reported in this query cycle. */
group->members++;
#endif
/* Need to set d_len to zero to indication that nothing is being sent */
dev->d_len = 0;

View file

@ -284,6 +284,15 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group,
query->chksum = ~icmpv6_chksum(dev, MLD_HDRLEN);
MLD_STATINCR(g_netstats.mld.query_sent);
#ifdef CONFIG_NET_MLD_ROUTER
/* Save the number of members that reported in the previous query cycle;
* reset the number of members that have reported in the new query cycle.
*/
group->lstmbrs = group->members;
group->members = 0;
#endif
}
break;

View file

@ -145,6 +145,27 @@ static void mld_polldog_work(FAR void *arg)
else if (IS_MLD_QUERIER(group->flags))
{
#ifdef CONFIG_NET_MLD_ROUTER
if (group->njoins == 0 group->members == 0 && group->lstmbrs == 0)
{
/* Cancel the timers and discard any queued Reports. Canceling
* the timer will prevent any new Reports from being sent;
* clearing the flags will discard any pending Reports that
* could interfere with freeing the group.
*/
wd_cancel(group->polldog);
wd_cancel(group->v1dog);
CLR_MLD_SCHEDMSG(group->flags);
CLR_MLD_WAITMSG(group->flags);
/* Free the group structure */
mld_grpfree(dev, group);
}
else
#endif
{
/* Schedule (and forget) the general query. */
MLD_STATINCR(g_netstats.mld.query_sched);
@ -158,6 +179,7 @@ static void mld_polldog_work(FAR void *arg)
mld_start_polltimer(group, MSEC2TICK(MLD_QUERY_MSEC));
}
}
net_unlock();
}

View file

@ -123,9 +123,9 @@ static int netprocfs_joinleave(FAR struct netprocfs_file_s *netfile)
int len;
len = snprintf(netfile->line, NET_LINELEN, "Joins: %04x ",
g_netstats.mld.joins);
g_netstats.mld.njoins);
len += snprintf(&netfile->line[len], NET_LINELEN - len, "Leaves: %04x\n",
g_netstats.mld.leaves);
g_netstats.mld.nleaves);
return len;
}