Squashed commit of the following:

The MLD implementation did not follow the RFC correctly when it is the Querier.  The Querier should use a general query and get query messages from all members of all groups.  This would be driven by a single timer per sub-nset since all groups are queried at once. Instead, the design used a Multicast Address Specific Query with one timer per group and ignores groups that we are not members of.

    Similary, the MLDv1 compatibility timer should be a single, separate timer, not a per-group timer.

    net/mld: Group may be NULL when sending a general query
This commit is contained in:
Gregory Nutt 2018-11-11 11:38:29 -06:00
parent f14cf966c9
commit 8a3fc26b74
12 changed files with 564 additions and 368 deletions

16
TODO
View File

@ -19,7 +19,7 @@ nuttx/:
(9) Kernel/Protected Build
(3) C++ Support
(5) Binary loaders (binfmt/)
(19) Network (net/, drivers/net)
(18) Network (net/, drivers/net)
(4) USB (drivers/usbdev, drivers/usbhost)
(2) Other drivers (drivers/)
(11) Libraries (libs/libc/, libs/libm/)
@ -1489,20 +1489,6 @@ o Network (net/, drivers/net)
anything but a well-known point-to-point configuration
impossible.
Title: MLD QUERIER DESIGN
Description: The MLD implementation does not follow the RFC correct when it is
the Querier. The Querier should use a general query and get
query messages from all members of all groups. This would be
driven by a single timer per network device since all groups on
the sub-net are queried at once. Instead, the design currently
uses a Multicast Address Specific Query with one timer per group
and ignores groups that we are not members of.
Similary, the MLDv1 compatibility timer should be a single,
separate timer per network device, not a per-group timer.
Status: Open
Priority: Low. There are no customers of MLD as far as I know.
o USB (drivers/usbdev, drivers/usbhost)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -51,7 +51,9 @@
#include <nuttx/config.h>
#include <stdint.h>
#include <queue.h>
#include <nuttx/wdog.h>
#include <nuttx/net/ip.h>
#include <nuttx/net/icmpv6.h>
@ -370,6 +372,22 @@ struct mld_mcast_listen_done_s
net_ipv6addr_t mcastaddr; /* Multicast address */
};
/* This structure represents the overall MLD state for a single network.
* This structure in included withing the net_driver_s structure.
*
* There will be a group for the all systems group address but this
* will not run the state machine as it is used to kick off reports
* from all the other groups
*/
struct mld_netdev_s
{
sq_queue_t grplist; /* MLD group list */
WDOG_ID gendog; /* General query timer */
WDOG_ID v1dog; /* MLDv1 compatibility timer */
uint8_t flags; /* See MLD_ flags definitions */
};
#ifdef CONFIG_NET_STATISTICS
/* MLD statistic counters */

View File

@ -346,7 +346,7 @@ struct net_driver_s
sq_queue_t d_igmp_grplist; /* IGMP group list */
#endif
#ifdef CONFIG_NET_MLD
sq_queue_t d_mld_grplist; /* MLD group list */
struct mld_netdev_s d_mld; /* MLD state information */
#endif
#ifdef CONFIG_NETDEV_STATISTICS

View File

@ -123,35 +123,45 @@
* Pre-processor Definitions
****************************************************************************/
/* Group flags */
/* Global flags */
#define MLD_QUERIER (1 << 0) /* Querier */
#define MLD_V1COMPAT (1 << 1) /* MLDv1 compatibility mode */
#define MLD_GENPEND (1 << 2) /* General query pending */
#define SET_MLD_QUERIER(f) do { (f) |= MLD_QUERIER; } while (0)
#define SET_MLD_V1COMPAT(f) do { (f) |= MLD_V1COMPAT; } while (0)
#define SET_MLD_GENPEND(f) do { (f) |= MLD_GENPEND; } while (0)
#define CLR_MLD_QUERIER(f) do { (f) &= ~MLD_QUERIER; } while (0)
#define CLR_MLD_V1COMPAT(f) do { (f) &= ~MLD_V1COMPAT; } while (0)
#define CLR_MLD_GENPEND(f) do { (f) &= ~MLD_GENPEND; } while (0)
#define IS_MLD_QUERIER(f) (((f) & MLD_QUERIER) != 0)
#define IS_MLD_V1COMPAT(f) (((f) & MLD_V1COMPAT) != 0)
#define IS_MLD_GENPEND(f) (((f) & MLD_GENPEND) != 0)
/* Group flags */
#define MLD_STARTUP (1 << 1) /* Startup unsolicited Reports */
#define MLD_V1COMPAT (1 << 2) /* Version 1 compatibility mode */
#define MLD_LASTREPORT (1 << 3) /* We were the last to report */
#define MLD_SCHEDMSG (1 << 4) /* Outgoing message scheduled */
#define MLD_WAITMSG (1 << 5) /* Block until message sent */
#define MLD_RPTPEND (1 << 6) /* Report pending */
#define SET_MLD_QUERIER(f) do { (f) |= MLD_QUERIER; } while (0)
#define SET_MLD_STARTUP(f) do { (f) |= MLD_STARTUP; } while (0)
#define SET_MLD_V1COMPAT(f) do { (f) |= MLD_V1COMPAT; } while (0)
#define SET_MLD_LASTREPORT(f) do { (f) |= MLD_LASTREPORT; } while (0)
#define SET_MLD_SCHEDMSG(f) do { (f) |= MLD_SCHEDMSG; } while (0)
#define SET_MLD_WAITMSG(f) do { (f) |= MLD_WAITMSG; } while (0)
#define SET_MLD_RPTPEND(f) do { (f) |= MLD_RPTPEND; } while (0)
#define CLR_MLD_QUERIER(f) do { (f) &= ~MLD_QUERIER; } while (0)
#define CLR_MLD_STARTUP(f) do { (f) &= ~MLD_STARTUP; } while (0)
#define CLR_MLD_V1COMPAT(f) do { (f) &= ~MLD_V1COMPAT; } while (0)
#define CLR_MLD_LASTREPORT(f) do { (f) &= ~MLD_LASTREPORT; } while (0)
#define CLR_MLD_SCHEDMSG(f) do { (f) &= ~MLD_SCHEDMSG; } while (0)
#define CLR_MLD_WAITMSG(f) do { (f) &= ~MLD_WAITMSG; } while (0)
#define CLR_MLD_RPTPEND(f) do { (f) &= ~MLD_RPTPEND; } while (0)
#define IS_MLD_QUERIER(f) (((f) & MLD_QUERIER) != 0)
#define IS_MLD_STARTUP(f) (((f) & MLD_STARTUP) != 0)
#define IS_MLD_V1COMPAT(f) (((f) & MLD_V1COMPAT) != 0)
#define IS_MLD_LASTREPORT(f) (((f) & MLD_LASTREPORT) != 0)
#define IS_MLD_SCHEDMSG(f) (((f) & MLD_SCHEDMSG) != 0)
#define IS_MLD_WAITMSG(f) (((f) & MLD_WAITMSG) != 0)
@ -201,10 +211,6 @@ enum mld_msgtype_e
/* This structure represents one group member. There is a list of groups
* for each device interface structure.
*
* There will be a group for the all systems group address but this
* will not run the state machine as it is used to kick off reports
* from all the other groups
*/
typedef FAR struct wdog_s *WDOG_ID;
@ -214,7 +220,6 @@ struct mld_group_s
net_ipv6addr_t grpaddr; /* Group IPv6 address */
struct work_s work; /* For deferred timeout operations */
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) */
@ -287,7 +292,7 @@ int mld_query(FAR struct net_driver_s *dev,
* Name: mld_report_v1
*
* Description:
* Called from icmpv6_input() when a Version 1 Multicast Listener Report is
* Called from icmpv6_input() when a MLDv1 Multicast Listener Report is
* received.
*
****************************************************************************/
@ -363,6 +368,18 @@ FAR struct mld_group_s *mld_grpallocfind(FAR struct net_driver_s *dev,
void mld_grpfree(FAR struct net_driver_s *dev,
FAR struct mld_group_s *group);
/****************************************************************************
* Name: mld_new_pollcycle
*
* Description:
* Update accumulated membership at the beginning of each new poll cycle
*
****************************************************************************/
#ifdef CONFIG_NET_MLD_ROUTER
void mld_new_pollcycle(FAR struct net_driver_s *dev);
#endif
/****************************************************************************
* Name: mld_schedmsg
*
@ -431,7 +448,7 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group,
*
****************************************************************************/
uint8_t mld_report_msgtype(FAR struct mld_group_s *group);
uint8_t mld_report_msgtype(FAR struct net_driver_s *dev);
/****************************************************************************
* Name: mld_joingroup
@ -474,14 +491,14 @@ int mld_joingroup(FAR const struct ipv6_mreq *mrec);
int mld_leavegroup(FAR const struct ipv6_mreq *mrec);
/****************************************************************************
* Name: mld_start_polltimer
* Name: mld_start_gentimer
*
* Description:
* Start the MLD poll timer.
* Start/Re-start the general query timer.
*
****************************************************************************/
void mld_start_polltimer(FAR struct mld_group_s *group, clock_t ticks);
void mld_start_gentimer(FAR struct net_driver_s *dev, clock_t ticks);
/****************************************************************************
* Name: mld_start_v1timer
@ -491,7 +508,17 @@ void mld_start_polltimer(FAR struct mld_group_s *group, clock_t ticks);
*
****************************************************************************/
void mld_start_v1timer(FAR struct mld_group_s *group, clock_t ticks);
void mld_start_v1timer(FAR struct net_driver_s *dev, clock_t ticks);
/****************************************************************************
* Name: mld_start_polltimer
*
* Description:
* Start the MLD poll timer.
*
****************************************************************************/
void mld_start_polltimer(FAR struct mld_group_s *group, clock_t ticks);
/****************************************************************************
* Name: mld_addmcastmac

View File

@ -109,31 +109,28 @@ FAR struct mld_group_s *mld_grpalloc(FAR struct net_driver_s *dev,
goto errout_with_sem;
}
group->v1dog = wd_create();
DEBUGASSERT(group->v1dog != NULL);
if (group->v1dog == NULL)
{
goto errout_with_polldog;
}
/* Save the interface index */
group->ifindex = dev->d_ifindex;
/* All routers start up as a Querier on each of their attached links. */
#ifndef CONFIG_CONFIG_NET_MLD_ROUTER
/* Start the query timer if we are the Querier and this is the first
* group member of the group.
*/
SET_MLD_QUERIER(group->flags);
if (dev->d_mld.grplist.head == NULL)
{
mld_start_gentimer(dev, MSEC2TICK(MLD_QUERY_MSEC));
}
#endif
/* Add the group structure to the list in the device structure */
sq_addfirst((FAR sq_entry_t *)group, &dev->d_mld_grplist);
sq_addfirst((FAR sq_entry_t *)group, &dev->d_mld.grplist);
}
return group;
errout_with_polldog:
wd_delete(group->polldog);
errout_with_sem:
(void)nxsem_destroy(&group->sem);
kmm_free(group);
@ -160,7 +157,7 @@ FAR struct mld_group_s *mld_grpfind(FAR struct net_driver_s *dev,
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6],
addr[7]);
for (group = (FAR struct mld_group_s *)dev->d_mld_grplist.head;
for (group = (FAR struct mld_group_s *)dev->d_mld.grplist.head;
group;
group = group->next)
{
@ -222,11 +219,10 @@ void mld_grpfree(FAR struct net_driver_s *dev, FAR struct mld_group_s *group)
/* Cancel the timers */
wd_cancel(group->polldog);
wd_cancel(group->v1dog);
/* Remove the group structure from the group list in the device structure */
sq_rem((FAR sq_entry_t *)group, &dev->d_mld_grplist);
sq_rem((FAR sq_entry_t *)group, &dev->d_mld.grplist);
/* Destroy the wait semaphore */
@ -235,12 +231,53 @@ void mld_grpfree(FAR struct net_driver_s *dev, FAR struct mld_group_s *group)
/* Destroy the timers */
wd_delete(group->polldog);
wd_delete(group->v1dog);
/* Then release the group structure resources. */
mldinfo("Call sched_kfree()\n");
kmm_free(group);
#ifndef CONFIG_CONFIG_NET_MLD_ROUTER
/* If there are no longer any groups, then stop the general query and v1
* compatibility timers.
*/
if (dev->d_mld.grplist.head == NULL)
{
wd_cancel(dev->d_mld.gendog);
wd_cancel(dev->d_mld.v1dog);
}
#endif
}
/****************************************************************************
* Name: mld_new_pollcycle
*
* Description:
* Update accumulated membership at the beginning of each new poll cycle
*
****************************************************************************/
#ifdef CONFIG_NET_MLD_ROUTER
void mld_new_pollcycle(FAR struct net_driver_s *dev)
{
FAR struct mld_group_s *member;
/* Update member ship in every group */
for (member = (FAR struct mld_group_s *)dev->d_mld.grplist.head;
member;
member = member->next)
{
/* 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
#endif /* CONFIG_NET_MLD */

View File

@ -38,9 +38,11 @@
#include <nuttx/config.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/wdog.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/ip.h>
#include <nuttx/net/mld.h>
@ -77,7 +79,18 @@ void mld_initialize(void)
void mld_devinit(struct net_driver_s *dev)
{
mldinfo("MLD initializing dev %p\n", dev);
DEBUGASSERT(dev->d_mld_grplist.head == NULL);
/* Initialize the MLD state in the device structure */
memset(&dev->d_mld, 0, sizeof(struct mld_netdev_s));
dev->d_mld.gendog = wd_create();
dev->d_mld.v1dog = wd_create();
DEBUGASSERT(dev->d_mld.gendog != NULL && dev->d_mld.v1dog != NULL);
/* All routers start up as a Querier on each of their attached links. */
SET_MLD_QUERIER(dev->d_mld.flags);
/* Add the all nodes address to the group */
@ -88,4 +101,10 @@ void mld_devinit(struct net_driver_s *dev)
mld_addmcastmac(dev, g_ipv6_allnodes);
mld_addmcastmac(dev, g_ipv6_allrouters);
mld_addmcastmac(dev, g_ipv6_allmldv2routers);
#ifdef CONFIG_CONFIG_NET_MLD_ROUTER
/* Start the general query timer. */
mld_start_gentimer(dev, MSEC2TICK(MLD_QUERY_MSEC));
#endif
}

View File

@ -193,7 +193,6 @@ int mld_leavegroup(FAR const struct ipv6_mreq *mrec)
*/
wd_cancel(group->polldog);
wd_cancel(group->v1dog);
CLR_MLD_SCHEDMSG(group->flags);
CLR_MLD_WAITMSG(group->flags);

View File

@ -78,9 +78,28 @@ void mld_poll(FAR struct net_driver_s *dev)
dev->d_len = 0;
dev->d_sndlen = 0;
/* Check if a general query is pending */
if (IS_MLD_GENPEND(dev->d_mld.flags))
{
/* Clear the pending flag */
CLR_MLD_GENPEND(dev->d_mld.flags);
/* Are we still the querier? */
if (IS_MLD_QUERIER(dev->d_mld.flags))
{
/* Yes, send the general query and return */
mld_send(dev, NULL, MLD_SEND_GENQUERY);
return;
}
}
/* Check each member of the group */
for (group = (FAR struct mld_group_s *)dev->d_mld_grplist.head;
for (group = (FAR struct mld_group_s *)dev->d_mld.grplist.head;
group;
group = group->next)
{
@ -120,7 +139,7 @@ void mld_poll(FAR struct net_driver_s *dev)
{
/* Yes.. create the MLD message in the driver buffer */
mld_send(dev, group, mld_report_msgtype(group));
mld_send(dev, group, mld_report_msgtype(dev));
/* Indicate that the report is no longer pending */

View File

@ -65,7 +65,7 @@
****************************************************************************/
/****************************************************************************
* Name: mld_setup_v1compat
* Name: mld_check_v1compat
*
* Description:
* If this is for MLDv1 query, then select MLDv1 compatibility mode and
@ -74,21 +74,11 @@
*
****************************************************************************/
static void mld_setup_v1compat(FAR struct mld_group_s *group,
FAR const struct mld_mcast_listen_query_s *query,
bool mldv1)
static inline void mld_check_v1compat(FAR struct net_driver_s *dev,
bool mldv1)
{
unsigned int respmsec;
if (mldv1)
{
#if 0 /* REVISIT */
/* Get the QQI from the query. Since this is MLDv1, we know that
* the value is not encoded.
*/
respmsec = MSEC_PER_SEC * MLD_QQI_VALUE(query->qqic);
#else
/* REVISIT: I am confused. Per RFC 3810:
* "The Older Version Querier Present Timeout is the time-out for
* transitioning a host back to MLDv2 Host Compatibility Mode. When
@ -103,34 +93,20 @@ static void mld_setup_v1compat(FAR struct mld_group_s *group,
* field. That is an MLDv2 extension.
*/
respmsec = MLD_QUERY_MSEC;
#endif
/* Select MLDv1 compatibility mode (might already be selected) */
SET_MLD_V1COMPAT(group->flags);
SET_MLD_V1COMPAT(dev->d_mld.flags);
/* Whenever a host changes its compatibility mode, it cancels all its
* pending responses and retransmission timers.
/* REVISIT: Whenever a host changes its compatibility mode, it cancels
* all its pending responses and retransmission timers. Logic Missing.
*/
wd_cancel(group->polldog);
/* REVISIT: We cannot cancel a pending message if there is a waiter.
* Some additional logic would be required to avoid a hang.
*/
if (!IS_MLD_WAITMSG(group->flags))
{
CLR_MLD_SCHEDMSG(group->flags);
}
/* And start the MLDv1 compatibility timer. If the timer is already
* running, this will reset the timer.
*/
mld_start_v1timer(group,
MSEC2TICK(MLD_V1PRESENT_MSEC((clock_t)respmsec)));
mld_start_v1timer(dev,
MSEC2TICK(MLD_V1PRESENT_MSEC((clock_t)MLD_QUERY_MSEC)));
}
}
@ -197,21 +173,19 @@ static bool mld_cmpaddr(FAR struct net_driver_s *dev,
* Name: mld_check_querier
*
* Description:
* Check if we are still the querier for this group (assuming that we are
* currently the querier). This compares the IPv6 Source Address of the
* query against and the IPv6 address of the link. Ff the source address
* is numerically less than the link address, when we are no longer the
* querier.
* Check if we are still the querier (assuming that we are currently the
* querier). This compares the IPv6 Source Address of the query against
* the IPv6 address of the link. If the source address is numerically
* less than the link address, when we are no longer the querier.
*
****************************************************************************/
static void mld_check_querier(FAR struct net_driver_s *dev,
FAR struct ipv6_hdr_s *ipv6,
FAR struct mld_group_s *group)
FAR struct ipv6_hdr_s *ipv6)
{
/* Check if this member is a Querier */
if (IS_MLD_QUERIER(group->flags))
if (IS_MLD_QUERIER(dev->d_mld.flags))
{
/* This is a querier, check if the IPv6 source address is numerically
* less than the IPv6 address assigned to this link.
@ -221,21 +195,17 @@ static void mld_check_querier(FAR struct net_driver_s *dev,
{
/* Switch to non-Querier mode */
CLR_MLD_QUERIER(group->flags);
CLR_MLD_QUERIER(dev->d_mld.flags);
}
}
/* Check if the member is a Non-Querier AND that we are past the start up
* phase (where the timer is used for a different purpose)?
*/
/* Check if the member is a Non-Querier. */
if (!IS_MLD_QUERIER(group->flags) && !IS_MLD_STARTUP(group->flags))
if (!IS_MLD_QUERIER(dev->d_mld.flags))
{
/* Yes.. cancel the poll timer and [re-]start the 'Other Querier
* Present' Timeout.
*/
/* Yes.. [re-]start the 'Other Querier Present' Timeout. */
mld_start_polltimer(group, MSEC2TICK(MLD_OQUERY_MSEC));
mld_start_gentimer(dev, MSEC2TICK(MLD_OQUERY_MSEC));
}
}
@ -307,6 +277,13 @@ int mld_query(FAR struct net_driver_s *dev,
return -EINVAL;
}
/* Warn if we received a MLDv2 query in MLDv1 compatibility mode. */
if (!mldv1 && IS_MLD_V1COMPAT(dev->d_mld.flags))
{
mldwarn("WARNING: MLDv2 query received in MLDv1 compatibility mode\n");
}
/* There are three variants of the Query message (RFC 3810):
*
* 1. A "General Query" is sent by the Querier to learn which
@ -352,64 +329,37 @@ int mld_query(FAR struct net_driver_s *dev,
mldinfo("General multicast query\n");
MLD_STATINCR(g_netstats.mld.gm_query_received);
/* Two passes through the member list. On the first, just check if we
* are still the querier for the qroup.
*/
/* Check if we are still the querier for this sub-net */
for (member = (FAR struct mld_group_s *)dev->d_mld_grplist.head;
member;
member = member->next)
{
/* Skip over the all systems group entry */
if (!net_ipv6addr_cmp(member->grpaddr, g_ipv6_allnodes))
{
/* Check if we are still the querier for this group */
mld_check_querier(dev, ipv6, member);
/* Warn if we received a MLDv2 query in MLDv1 compatibility
* mode.
*/
if (!mldv1 && IS_MLD_V1COMPAT(member->flags))
{
mldinfo("WARNING: MLDv2 query received in MLDv1 "
"compatibility mode\n");
}
mld_check_querier(dev, ipv6);
#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
}
}
/* On the second time through, we send the Report in response to the
* query. This has to be done twice because because there is only
* a single packet buffer that is used for both incoming and outgoing
* packets. When the report is sent, it will clobber the incoming
* query. Any attempt to send an additional Report would also clobber
* a preceding report
/* Update accumulated membership at the beginning of each new poll
* cycle
*/
for (member = (FAR struct mld_group_s *)dev->d_mld_grplist.head;
member;
mld_new_pollcycle(dev)
#endif
/* Check MLDv1 compatibility mode */
mld_check_v1compat(dev, mldv1);
/* Send the Report in response to the query. This has to be done
* multiple times because because there is only a single packet buffer
* that is used for both incoming and outgoing packets. When the
* report is sent, it will clobber the incoming* query. Any attempt
* to send an additional Report would also clobber a preceding report
*/
for (member = (FAR struct mld_group_s *)dev->d_mld.grplist.head;
member != NULL;
member = member->next)
{
/* Skip over the all systems group entry */
if (!net_ipv6addr_cmp(member->grpaddr, g_ipv6_allnodes))
{
/* Check MLDv1 compatibility mode */
mld_setup_v1compat(member, query, mldv1);
/* Have we already sent a report from this loop? */
if (rptsent)
@ -424,7 +374,7 @@ int mld_query(FAR struct net_driver_s *dev,
{
/* No.. Send one report now. */
mld_send(dev, member, mld_report_msgtype(member));
mld_send(dev, member, mld_report_msgtype(dev));
rptsent = true;
CLR_MLD_RPTPEND(member->flags);
}
@ -458,7 +408,7 @@ int mld_query(FAR struct net_driver_s *dev,
/* Check if we are still the querier for this group */
mld_check_querier(dev, ipv6, group);
mld_check_querier(dev, ipv6);
#ifdef CONFIG_NET_MLD_ROUTER
/* Save the number of members that reported in the previous query cycle;
@ -471,7 +421,7 @@ int mld_query(FAR struct net_driver_s *dev,
/* Warn if we received a MLDv2 query in MLDv1 compatibility mode. */
if (!mldv1 && IS_MLD_V1COMPAT(group->flags))
if (!mldv1 && IS_MLD_V1COMPAT(dev->d_mld.flags))
{
mldinfo("WARNING: MLDv2 query received in MLDv1 compatibility mode\n");
}
@ -496,11 +446,12 @@ int mld_query(FAR struct net_driver_s *dev,
/* Check MLDv1 compatibility mode */
mld_setup_v1compat(group, query, mldv1);
mld_check_v1compat(dev, mldv1);
/* Send the report */
mld_send(dev, group, mld_report_msgtype(group));
mld_send(dev, group, mld_report_msgtype(dev));
CLR_MLD_RPTPEND(group->flags);
}
/* Not sent to all systems. Check for Unicast General Query */
@ -512,11 +463,12 @@ int mld_query(FAR struct net_driver_s *dev,
/* Check MLDv1 compatibility mode */
mld_setup_v1compat(group, query, mldv1);
mld_check_v1compat(dev, mldv1);
/* Send the report */
mld_send(dev, group, mld_report_msgtype(group));
mld_send(dev, group, mld_report_msgtype(dev));
CLR_MLD_RPTPEND(group->flags);
}
else
{

View File

@ -157,7 +157,7 @@ int mld_report(FAR struct net_driver_s *dev, FAR const net_ipv6addr_t grpaddr)
int mld_report_v1(FAR struct net_driver_s *dev,
FAR const struct mld_mcast_listen_report_v1_s *report)
{
mldinfo("Version 1 Multicast Listener Report\n");
mldinfo("MLDv1 Multicast Listener Report\n");
DEBUGASSERT(dev != NULL && report != NULL);
MLD_STATINCR(g_netstats.mld.v1report_received);

View File

@ -120,6 +120,11 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group,
FAR const uint16_t *destipaddr;
unsigned int mldsize;
/* Only a general query message can have a NULL group */
DEBUGASSERT(dev != NULL);
DEBUGASSERT(msgtype == MLD_SEND_GENQUERY || group != NULL);
/* Select IPv6 */
IFF_SET_IPv6(dev->d_flags);
@ -131,7 +136,8 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group,
case MLD_SEND_GENQUERY: /* Send General Query */
case MLD_SEND_MASQUERY: /* Send Multicast Address Specific (MAS) Query */
{
mldinfo("Send General Query, flags=%02x\n", group->flags);
mldinfo("Send General/MAS Query, flags=%02x\n",
group != NULL ? group->flags : dev->d_mld.flags);
mldsize = SIZEOF_MLD_MCAST_LISTEN_QUERY_S(0);
}
break;
@ -290,7 +296,7 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group,
/* Fields unique to the extended MLDv2 query */
if (!IS_MLD_V1COMPAT(group->flags))
if (!IS_MLD_V1COMPAT(dev->d_mld.flags))
{
query->flags = MLD_ROBUSTNESS;
query->qqic = MLD_QRESP_SEC;
@ -307,13 +313,21 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group,
/* 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.
*
* REVISIT: This would have to be done for all groups, not just
* this one.
*/
group->lstmbrs = group->members;
group->members = 0;
if (msgtype == MLD_SEND_GENQUERY)
{
/* Update accumulated membership for all groups. */
mld_new_pollcycle(dev)
}
else
{
/* Updated accumulated membership only for this group */
group->lstmbrs = group->members;
group->members = 0;
}
#endif
}
break;
@ -404,9 +418,9 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group,
*
****************************************************************************/
uint8_t mld_report_msgtype(FAR struct mld_group_s *group)
uint8_t mld_report_msgtype(FAR struct net_driver_s *dev)
{
if (IS_MLD_V1COMPAT(group->flags))
if (IS_MLD_V1COMPAT(dev->d_mld.flags))
{
return MLD_SEND_V1REPORT;
}

View File

@ -46,19 +46,276 @@
#include <nuttx/wqueue.h>
#include <nuttx/net/netconfig.h>
#include <nuttx/net/net.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/netstats.h>
#include <nuttx/net/mld.h>
#include "devif/devif.h"
#include "netdev/netdev.h"
#include "mld/mld.h"
#include "utils/utils.h"
#ifdef CONFIG_NET_MLD
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define MLD_FREE_WORK 4
/****************************************************************************
* Private Data
****************************************************************************/
/* This is a small pool of work structures that are used for rare, general
* timer and MLDv1 compatibility timers.
*
* REVISIT: A better place would be in the device structure. But then we
* would either have to pass a device structure in the timeout (which could
* become stale) or look up the device using an interface index. But we
* cannot access the network device list from the timer interrupt handler.
*/
static struct work_s g_mld_work[MLD_FREE_WORK];
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mld_find_work
*
* Description:
* Fin an available work queue structure.
*
* Assumptions:
* Called only from the timer interrupt handler with interrupts disabled.
*
****************************************************************************/
static FAR struct work_s *mld_find_work(void)
{
int i;
for (i = 0; i < MLD_FREE_WORK; i++)
{
if (work_available(&g_mld_work[i]))
{
return &g_mld_work[i];
}
}
return NULL;
}
/****************************************************************************
* Name: mld_gendog_work
*
* Description:
* General query watchdog timeout work.
*
* Assumptions:
* This function is called from a work queue thread.
*
****************************************************************************/
static void mld_gendog_work(FAR void *arg)
{
FAR struct net_driver_s *dev;
int ifindex;
/* Recover the interface index and find the device associated with the
* index.
*/
ifindex = (int)arg;
DEBUGASSERT(ifindex > 0);
net_lock();
dev = netdev_findbyindex(ifindex);
if (dev == NULL)
{
/* This could be a normal consequence if the device is unregistered
* while the timer is pending.
*/
fwarn("WARNING: No device associated with ifindex=%d\n", ifindex);
net_unlock();
return;
}
/* Check for an Other Querier Present Timeout. This timer is set in non-
* Querier mode to detect the case where we have lost the Querier.
*/
if (!IS_MLD_QUERIER(dev->d_mld.flags))
{
/* We are not the Querier. This is an Other Querier Present Timeout.
* If this timeout expires, it means that there are no Queriers for
* the group. Let's revert to Querier mode.
*/
SET_MLD_QUERIER(dev->d_mld.flags);
}
/* Check if this we are the Querier */
if (IS_MLD_QUERIER(dev->d_mld.flags))
{
/* Schedule (and forget) the general query. */
MLD_STATINCR(g_netstats.mld.query_sched);
SET_MLD_GENPEND(dev->d_mld.flags);
/* Notify the device that we have a packet to send */
netdev_txnotify_dev(dev);
/* Restart the Querier timer */
mld_start_gentimer(dev, MSEC2TICK(MLD_QUERY_MSEC));
}
else
{
/* Not the Querier... Restart the Other Querier Present Timeout */
mld_start_gentimer(dev, MSEC2TICK(MLD_OQUERY_MSEC));
}
net_unlock();
}
/****************************************************************************
* Name: mld_gendog_timout
*
* Description:
* General query watchdog handler.
*
* Assumptions:
* This function is called from the context of the timer interrupt handler.
*
****************************************************************************/
static void mld_gendog_timout(int argc, uint32_t arg, ...)
{
FAR struct work_s *work;
int ret;
mldinfo("Timeout!\n");
DEBUGASSERT(arg != 0);
/* Find an available work structure. This is awkward. */
work = mld_find_work();
DEBUGASSERT(work != NULL);
if (work == NULL)
{
mlderr("ERROR: No free work structures.\n");
}
else
{
/* Perform the general query-related operations on (preferably) the
* low priority work queue.
*/
ret = work_queue(LPWORK, work, mld_gendog_work, (FAR void *)arg, 0);
if (ret < 0)
{
mlderr("ERROR: Failed to queue general query work: %d\n", ret);
}
}
}
/****************************************************************************
* Name: mld_v1dog_work
*
* Description:
* Timeout watchdog work
*
* Assumptions:
* This function is called from a work queue thread.
*
****************************************************************************/
static void mld_v1dog_work(FAR void *arg)
{
FAR struct net_driver_s *dev;
int ifindex;
/* Recover the interface index and find the device associated with the
* index.
*/
ifindex = (int)arg;
DEBUGASSERT(ifindex > 0);
net_lock();
dev = netdev_findbyindex(ifindex);
if (dev == NULL)
{
/* This could be a normal consequence if the device is unregistered
* while the timer is pending.
*/
fwarn("WARNING: No device associated with ifindex=%d\n", ifindex);
}
else
{
/* Exit MLDv1 compatibility mode. */
CLR_MLD_V1COMPAT(dev->d_mld.flags);
/* REVIST: Whenever a host changes its compatibility mode, it cancels
* all of its pending responses and retransmission timers.
*/
}
net_unlock();
}
/****************************************************************************
* Name: mld_v1dog_timout
*
* Description:
* Timeout watchdog handler.
*
* Assumptions:
* This function is called from the context of the timer interrupt handler.
*
****************************************************************************/
static void mld_v1dog_timout(int argc, uint32_t arg, ...)
{
FAR struct work_s *work;
int ret;
mldinfo("Timeout!\n");
DEBUGASSERT(arg != 0);
/* Find an available work structure. This is awkward. */
work = mld_find_work();
DEBUGASSERT(work != NULL);
if (work == NULL)
{
mlderr("ERROR: No free work structures.\n");
}
else
{
/* Perform the general query-related operations on (preferably) the
* low priority work queue.
*/
ret = work_queue(LPWORK, work, mld_v1dog_work, (FAR void *)arg, 0);
if (ret < 0)
{
mlderr("ERROR: Failed to queue MLDv1 timeout work: %d\n", ret);
}
}
}
/****************************************************************************
* Name: mld_polldog_work
*
@ -73,6 +330,7 @@
static void mld_polldog_work(FAR void *arg)
{
FAR struct mld_group_s *group;
FAR struct net_driver_s *dev;
int ret;
/* Recover the reference to the group */
@ -85,10 +343,25 @@ static void mld_polldog_work(FAR void *arg)
net_lock();
if (IS_MLD_STARTUP(group->flags))
{
MLD_STATINCR(g_netstats.mld.report_sched);
/* Get a reference to the device serving the sub-net */
dev = netdev_findbyindex(group->ifindex);
if (dev == NULL)
{
/* This could be a normal consequence if the device is
* unregistered while the timer is pending.
*/
fwarn("WARNING: No device associated with ifindex=%d\n",
group->ifindex);
return;
}
/* Schedule (and forget) the Report. */
MLD_STATINCR(g_netstats.mld.report_sched);
ret = mld_schedmsg(group, mld_report_msgtype(group));
ret = mld_schedmsg(group, mld_report_msgtype(dev));
if (ret < 0)
{
mlderr("ERROR: Failed to schedule message: %d\n", ret);
@ -110,93 +383,7 @@ static void mld_polldog_work(FAR void *arg)
/* Terminate the start-up sequence */
CLR_MLD_STARTUP(group->flags);
/* If in Querier mode, start the Querier timer */
if (IS_MLD_QUERIER(group->flags))
{
mld_start_polltimer(group, MSEC2TICK(MLD_QUERY_MSEC));
}
}
net_unlock();
return;
}
#ifdef CONFIG_NET_MLD_ROUTER
/* This is a Query-related timeout. Destroy the group if there are
* no members of the group detected in the last two Query cycles.
*/
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
* 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);
net_unlock();
return;
}
#endif
/* Check for an Other Querier Present Timeout. This timer is set in non-
* Querier mode to detect the case where we have lost the Querier.
*/
if (!IS_MLD_QUERIER(group->flags))
{
/* We are not the Querier. This is an Other Querier Present Timeout.
* If this timeout expires, it means that there are no Queriers for
* the group. Let's revert to Querier mode.
*/
SET_MLD_QUERIER(group->flags);
}
/* Check if this is Querier */
if (IS_MLD_QUERIER(group->flags))
{
/* Schedule (and forget) the general query or MAS query. */
MLD_STATINCR(g_netstats.mld.query_sched);
#ifdef CONFIG_NET_MLD_ROUTER
/* REVISIT: In order to support the RFC correctly, we would need
* separate, single general query timer that is not part of the
* group structure. The Querier should query across all groups,
* with a single query, not very via multiple MAS queries as is
* done here.
*/
ret = mld_schedmsg(group, MLD_SEND_GENQUERY);
#else
ret = mld_schedmsg(group, MLD_SEND_MASQUERY);
#endif
if (ret < 0)
{
mlderr("ERROR: Failed to schedule message: %d\n", ret);
}
/* Restart the Querier timer */
mld_start_polltimer(group, MSEC2TICK(MLD_QUERY_MSEC));
}
else
{
/* Not the Querier... Restart the Other Querier Present Timeout */
mld_start_polltimer(group, MSEC2TICK(MLD_OQUERY_MSEC));
}
net_unlock();
@ -209,8 +396,7 @@ static void mld_polldog_work(FAR void *arg)
* Timeout watchdog handler.
*
* Assumptions:
* This function is called from the polldog timer handler which runs in the
* context of the timer interrupt handler.
* This function is called from the context of the timer interrupt handler.
*
****************************************************************************/
@ -237,95 +423,58 @@ static void mld_polldog_timout(int argc, uint32_t arg, ...)
}
}
/****************************************************************************
* Name: mld_v1dog_work
*
* Description:
* Timeout watchdog work
*
* Assumptions:
* This function is called from a work queue thread.
*
****************************************************************************/
static void mld_v1dog_work(FAR void *arg)
{
FAR struct mld_group_s *group;
/* Recover the reference to the group */
group = (FAR struct mld_group_s *)arg;
DEBUGASSERT(group != NULL);
net_lock();
if (IS_MLD_V1COMPAT(group->flags))
{
/* Exit MLDv1 compatibility mode. */
CLR_MLD_V1COMPAT(group->flags);
/* Whenever a host changes its compatibility mode, it cancels all its
* pending responses and retransmission timers.
*/
wd_cancel(group->polldog);
/* REVISIT: We cannot cancel a pending message if there is a waiter.
* Some additional logic would be required to avoid a hang.
*/
if (!IS_MLD_WAITMSG(group->flags))
{
CLR_MLD_SCHEDMSG(group->flags);
}
}
net_unlock();
}
/****************************************************************************
* Name: mld_v1dog_timout
*
* Description:
* Timeout watchdog handler.
*
* Assumptions:
* This function is called from the v1dog timer handler which runs in the
* context of the timer interrupt handler.
*
****************************************************************************/
static void mld_v1dog_timout(int argc, uint32_t arg, ...)
{
FAR struct mld_group_s *group;
int ret;
mldinfo("Timeout!\n");
/* Recover the reference to the group */
group = (FAR struct mld_group_s *)arg;
DEBUGASSERT(argc == 1 && group != NULL);
/* Cancels all its pending responses and retransmission timers */
wd_cancel(group->polldog);
/* Perform the timeout-related operations on (preferably) the low priority
* work queue.
*/
ret = work_queue(LPWORK, &group->work, mld_v1dog_work, group, 0);
if (ret < 0)
{
mlderr("ERROR: Failed to queue timeout work: %d\n", ret);
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: mld_start_gentimer
*
* Description:
* Start/Re-start the general query timer.
*
****************************************************************************/
void mld_start_gentimer(FAR struct net_driver_s *dev, clock_t ticks)
{
int ret;
/* Start the timer */
mldinfo("ticks: %lu\n", (unsigned long)ticks);
ret = wd_start(dev->d_mld.gendog, ticks, mld_gendog_timout, 1,
dev->d_ifindex);
DEBUGASSERT(ret == OK);
UNUSED(ret);
}
/****************************************************************************
* Name: mld_start_v1timer
*
* Description:
* Start the MLDv1 compatibility timer.
*
* REVISIT: This should be a single global timer, not a per-group timer.
*
****************************************************************************/
void mld_start_v1timer(FAR struct net_driver_s *dev, clock_t ticks)
{
int ret;
/* Start the timer */
mldinfo("ticks: %lu\n", (unsigned long)ticks);
ret = wd_start(dev->d_mld.v1dog, ticks, mld_v1dog_timout, 1,
dev->d_ifindex);
DEBUGASSERT(ret == OK);
UNUSED(ret);
}
/****************************************************************************
* Name: mld_start_polltimer
*
@ -348,28 +497,4 @@ void mld_start_polltimer(FAR struct mld_group_s *group, clock_t ticks)
UNUSED(ret);
}
/****************************************************************************
* Name: mld_start_v1timer
*
* Description:
* Start the MLDv1 compatibility timer.
*
* REVISIT: This should be a single global timer, not a per-group timer.
*
****************************************************************************/
void mld_start_v1timer(FAR struct mld_group_s *group, clock_t ticks)
{
int ret;
/* Start the timer */
mldinfo("ticks: %lu\n", (unsigned long)ticks);
ret = wd_start(group->v1dog, ticks, mld_v1dog_timout, 1, (uint32_t)group);
DEBUGASSERT(ret == OK);
UNUSED(ret);
}
#endif /* CONFIG_NET_MLD */