incubator-nuttx/net/mld/mld_query.c

480 lines
15 KiB
C

/****************************************************************************
* net/mld/mld_query.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <nuttx/wdog.h>
#include <nuttx/net/netconfig.h>
#include <nuttx/net/netstats.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/ip.h>
#include <nuttx/net/mld.h>
#include "devif/devif.h"
#include "inet/inet.h"
#include "mld/mld.h"
#include "utils/utils.h"
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mld_check_v1compat
*
* Description:
* If this is for MLDv1 query, then select MLDv1 compatibility mode and
* start (or re-start) the compatibility timer. We need to make this
* check BEFORE sending the report.
*
****************************************************************************/
static inline void mld_check_v1compat(FAR struct net_driver_s *dev,
bool mldv1)
{
if (mldv1)
{
/* 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
* an MLDv1 query is received, MLDv2 hosts set their Older Version
* Querier Present Timer to [Older Version Querier Present Timeout].
*
* "This value MUST be ([Robustness Variable] times (the [Query
* Interval] in the last Query received)) plus ([Query Response
* Interval])."
*
* I am not sure how to do that since the MLDv1 version has no QQI
* field. That is an MLDv2 extension.
*/
/* Select MLDv1 compatibility mode (might already be selected) */
SET_MLD_V1COMPAT(dev->d_mld.flags);
/* REVISIT: Whenever a host changes its compatibility mode, it cancels
* all its pending responses and retransmission timers. Logic Missing.
*/
/* And start the MLDv1 compatibility timer. If the timer is already
* running, this will reset the timer.
*/
mld_start_v1timer(dev,
MSEC2TICK(MLD_V1PRESENT_MSEC((clock_t)MLD_QUERY_MSEC)));
}
}
/****************************************************************************
* Name: mld_mrc2mrd
*
* Description:
* Convert the MLD Maximum Response Code (MRC) to the Maximum Response
* Delay (MRD) in units of system clock ticks.
*
****************************************************************************/
#if 0 /* Not used */
static clock_t mld_mrc2mrd(uint16_t mrc)
{
uint32_t mrd; /* Units of milliseconds */
/* If bit 15 is not set (i.e., mrc < 32768),
* then no conversion is required.
*/
if (mrc < 32768)
{
mrd = mrc;
}
else
{
/* Conversion required */
mrd = MLD_MRD_VALUE(mrc);
}
/* Return the MRD in units of clock ticks */
return MSEC2TICK((clock_t)mrd);
}
#endif
/****************************************************************************
* Name: mld_cmpaddr
*
* Description:
* Perform a numerical comparison of the IPv6 Source Address and the IPv6
* address of the link. Return true if the source address is less than
* the link address.
*
****************************************************************************/
static bool mld_cmpaddr(FAR struct net_driver_s *dev,
const net_ipv6addr_t srcaddr)
{
FAR const uint16_t *lladdr = netdev_ipv6_lladdr(dev);
int i;
if (lladdr == NULL)
{
/* If no link-local address presents, regard address as ::, then nobody
* can be less than it.
*/
return false;
}
for (i = 0; i < 8; i++)
{
if (srcaddr[i] < lladdr[i])
{
return true;
}
}
return false;
}
/****************************************************************************
* Name: mld_check_querier
*
* Description:
* 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)
{
/* Check if this member is a Querier */
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.
*/
if (mld_cmpaddr(dev, ipv6->srcipaddr))
{
/* Switch to non-Querier mode */
CLR_MLD_QUERIER(dev->d_mld.flags);
}
}
/* Check if the member is a Non-Querier. */
if (!IS_MLD_QUERIER(dev->d_mld.flags))
{
/* Yes.. [re-]start the 'Other Querier Present' Timeout. */
mld_start_gentimer(dev, MSEC2TICK(MLD_OQUERY_MSEC));
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: mld_query
*
* Description:
* Called from icmpv6_input() when a Multicast Listener Query is received.
*
* A router may assume one of two roles: Querier or Non-Querier. There is
* normally only one Querier per link. All routers start up as a Querier
* on each of their attached links. If a router hears a Query message
* whose IPv6 Source Address is numerically less than its own selected
* address for that link, it MUST become a Non-Querier on that link. If a
* delay passes without receiving, from a particular attached link, any
* Queries from a router with an address less than its own, a router
* resumes the role of Querier on that link.
*
* A Querier for a link periodically sends a General Query on that link,
* to solicit reports of all multicast addresses of interest on that link.
* On startup, a router SHOULD send multiple General Queries spaced closely
* together Interval] on all attached links in order to quickly and
* reliably discover the presence of multicast listeners on those links.
*
****************************************************************************/
int mld_query(FAR struct net_driver_s *dev,
FAR const struct mld_mcast_listen_query_s *query)
{
FAR struct ipv6_hdr_s *ipv6 = IPv6BUF;
FAR struct mld_group_s *group;
unsigned int mldsize;
bool mldv1 = false;
mldinfo("Multicast Listener Query\n");
#if 0 /* Not used */
/* Max Response Delay. The Max Response Code field specifies the maximum
* allowed time before sending a responding report in units of 1/10 second.
*/
mrc = NTOHS(query->mrc);
#endif
/* The MLD version of a Multicast Listener Query message is determined
* as follows:
*
* MLDv1 Query: length = 24 octets
* MLDv2 Query: length >= 28 octets
*
* Query messages that do not match any of the above conditions (e.g., a
* Query of length 26 octets) MUST be silently ignored.
*/
mldsize = (unsigned int)ipv6->len[0] << 8 | ipv6->len[1];
if (mldsize == sizeof(struct mld_mcast_listen_report_v1_s))
{
mldv1 = true;
}
else if (mldsize < SIZEOF_MLD_MCAST_LISTEN_QUERY_S(0))
{
mldinfo("WARNING: Invalid size for MLD query: %u\n", mldsize);
dev->d_len = 0;
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
* multicast addresses have listeners on an attached link. In a
* General Query, both the Multicast Address field and the Number
* of Sources (N) field are zero.
* 2. A "Multicast Address Specific Query" is sent by the Querier to
* learn if a particular multicast address has any listeners on an
* attached link. In a Multicast Address Specific Query, the
* Multicast Address field contains the multicast address of
* interest, while the Number of Sources (N) field is set to zero.
* 3. A "Multicast Address and Source Specific Query" is sent by the
* Querier to learn if any of the sources from the specified list for
* the particular multicast address has any listeners on an attached
* link or not. In a Multicast Address and Source Specific Query the
* Multicast Address field contains the multicast address of
* interest, while the Source Address [i] field(s) contain(s) the
* source address(es) of interest.
*
* Another possibility is a Unicast query that is sent specifically
* to our local IP address.
*/
/* Check the destination address. This varies with the type of message
* being sent:
*
* MESSAGE DESTINATION ADDRESS
* General Query Message: The link-local, all nodes multicast address
* MAS Query Messages: The group multicast address
*/
/* Check for a General Query */
if (net_ipv6addr_cmp(ipv6->destipaddr, g_ipv6_allnodes) &&
net_ipv6addr_cmp(query->grpaddr, g_ipv6_unspecaddr) &&
query->nsources == 0)
{
FAR struct mld_group_s *member;
bool rptsent = false;
/* This is the general query */
mldinfo("General multicast query\n");
MLD_STATINCR(g_netstats.mld.gm_query_received);
/* Check if we are still the querier for this sub-net */
mld_check_querier(dev, ipv6);
#ifdef CONFIG_NET_MLD_ROUTER
/* Update accumulated membership at the beginning of each new poll
* cycle
*/
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))
{
/* Have we already sent a report from this loop? */
if (rptsent)
{
/* Yes.. Just mark that a report as pending. The pending
* flag will checked on the next driver poll.
*/
SET_MLD_RPTPEND(member->flags);
}
else
{
/* No.. Send one report now. */
mld_send(dev, member, mld_report_msgtype(dev));
rptsent = true;
CLR_MLD_RPTPEND(member->flags);
}
}
}
/* Need to set d_len to zero if nothing is being sent */
if (!rptsent)
{
dev->d_len = 0;
}
return OK;
}
/* All of other Queries are sent with the group address set. Find the
* group instance associated with this group address. For the purpose of
* sending reports, we only care about the query if we are a member of the
* group.
*/
group = mld_grpfind(dev, query->grpaddr);
if (group == NULL)
{
mldinfo("We are not a member of this group\n");
dev->d_len = 0;
return -ENOENT;
}
/* Check if we are still the querier for this group */
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.
*/
group->lstmbrs = group->members;
group->members = 0;
#endif
/* Warn if we received a MLDv2 query in MLDv1 compatibility mode. */
if (!mldv1 && IS_MLD_V1COMPAT(dev->d_mld.flags))
{
mldinfo("WARNING: MLDv2 query received in MLDv1 compatibility mode\n");
}
/* Check for Multicast Address Specific (MAS) Query or a Multicast Address
* and Source Specific (MASS) Query. MAS and MASS queries are sent with
* an IP destination address equal to the multicast address of interest.
*/
if (net_ipv6addr_cmp(ipv6->destipaddr, group->grpaddr))
{
if (query->nsources == 0)
{
mldinfo("Multicast Address Specific Query\n");
MLD_STATINCR(g_netstats.mld.mas_query_received);
}
else
{
mldinfo("Multicast Address and Source Specific Query\n");
MLD_STATINCR(g_netstats.mld.mass_query_received);
}
/* Check MLDv1 compatibility mode */
mld_check_v1compat(dev, mldv1);
/* Send the report */
mld_send(dev, group, mld_report_msgtype(dev));
CLR_MLD_RPTPEND(group->flags);
}
/* Not sent to all systems. Check for Unicast General Query */
else if (NETDEV_IS_MY_V6ADDR(dev, ipv6->destipaddr))
{
mldinfo("Unicast query\n");
MLD_STATINCR(g_netstats.mld.ucast_query_received);
/* Check MLDv1 compatibility mode */
mld_check_v1compat(dev, mldv1);
/* Send the report */
mld_send(dev, group, mld_report_msgtype(dev));
CLR_MLD_RPTPEND(group->flags);
}
else
{
mldinfo("WARNING: Unhandled query\n");
MLD_STATINCR(g_netstats.mld.bad_query_received);
/* Need to set d_len to zero to indication that nothing is being sent */
dev->d_len = 0;
}
return OK;
}