| /**************************************************************************** |
| * 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; |
| } |