diff --git a/include/nuttx/net/mld.h b/include/nuttx/net/mld.h index 3e16d197b4a..d5fead36257 100644 --- a/include/nuttx/net/mld.h +++ b/include/nuttx/net/mld.h @@ -271,12 +271,17 @@ struct mld_mcast_listen_query_s { + /* The initial fields are common for MLDv1 and MLDv2 (24-bytes) */ + uint8_t type; /* Message Type: ICMPV6_MCAST_LISTEN_QUERY */ uint8_t reserved1; /* Reserved, must be zero on transmission */ uint16_t chksum; /* Checksum of ICMP header and data */ uint16_t mrc; /* Maximum response code */ uint16_t reserved2; /* Reserved, must be zero on transmission */ net_ipv6addr_t grpaddr; /* 128-bit IPv6 multicast group address */ + + /* These fields apply only to the MLDv2 query */ + uint8_t flags; /* See S and QRV flag definitions */ uint8_t qqic; /* Querier's Query Interval Code */ uint16_t nsources; /* Number of sources that follow */ @@ -329,7 +334,7 @@ struct mld_mcast_addrec_v2_s #define SIZEOF_MLD_MCAST_ADDREC_V2_S(nsources, auxdatlen) \ (sizeof(struct mld_mcast_addrec_v2_s) + \ sizeof(net_ipv6addr_t) * ((nsources) - 1) + \ - (auxdatlen) + (auxdatlen)) struct mld_mcast_listen_report_v2_s { @@ -348,10 +353,10 @@ struct mld_mcast_listen_report_v2_s * size of each variable length address record (addreclen). */ -#define SIZEOF_MLD_MCAST_ADDREC_V2_S(addreclen) \ +#define SIZEOF_MLD_MCAST_LISTEN_REPORT_V2_S(addreclen) \ (sizeof(struct mld_mcast_addrec_v2_s) - \ sizeof(net_ipv6addr_t) + \ - (addreclen) + (addreclen)) /* Version 1 Multicast Listener Done (RFC 2710) */ diff --git a/net/inet/inet.h b/net/inet/inet.h index cb08917b794..73b44e875d4 100644 --- a/net/inet/inet.h +++ b/net/inet/inet.h @@ -91,18 +91,20 @@ EXTERN uint16_t g_ipid; /* Well-known IPv6 addresses */ #ifdef CONFIG_NET_IPv6 -EXTERN const net_ipv6addr_t g_ipv6_unspecaddr; /* An address of all zeroes */ -EXTERN const net_ipv6addr_t g_ipv6_allnodes; /* All link local nodes */ +EXTERN const net_ipv6addr_t g_ipv6_unspecaddr; /* An address of all zeroes */ +EXTERN const net_ipv6addr_t g_ipv6_allnodes; /* All link local nodes */ #if defined(CONFIG_NET_ICMPv6_AUTOCONF) || defined(CONFIG_NET_ICMPv6_ROUTER) || \ defined(CONFIG_NET_MLD) /* IPv6 Multi-cast IP addresses. See RFC 2375 */ -EXTERN const net_ipv6addr_t g_ipv6_allrouters; /* All link local routers */ +EXTERN const net_ipv6addr_t g_ipv6_allrouters; /* All link local routers */ #ifdef CONFIG_NET_ICMPv6_AUTOCONF -EXTERN const net_ipv6addr_t g_ipv6_llnetmask; /* Netmask for local link address */ +EXTERN const net_ipv6addr_t g_ipv6_llnetmask; /* Netmask for local link address */ +#endif +#ifdef CONFIG_NET_MLD +EXTERN const net_ipv6addr_t g_ipv6_allmldv2routers; /* All MLDv2 link local routers */ #endif - #ifdef CONFIG_NET_ETHERNET /* IPv6 Multi-cast Ethernet addresses. Formed from the 16-bit prefix: * @@ -111,8 +113,8 @@ EXTERN const net_ipv6addr_t g_ipv6_llnetmask; /* Netmask for local link addres * and the last 32-bits of the IPv6 IP address */ -EXTERN const struct ether_addr g_ipv6_ethallnodes; /* All link local nodes */ -EXTERN const struct ether_addr g_ipv6_ethallrouters; /* All link local routers */ +EXTERN const struct ether_addr g_ipv6_ethallnodes; /* All link local nodes */ +EXTERN const struct ether_addr g_ipv6_ethallrouters; /* All link local routers */ #endif /* CONFIG_NET_ETHERNET */ #endif /* CONFIG_NET_ICMPv6_AUTOCONF || CONFIG_NET_ICMPv6_ROUTER || CONFIG_NET_MLD */ diff --git a/net/inet/inet_globals.c b/net/inet/inet_globals.c index f8f4fb475e3..b5be5dbf1e3 100644 --- a/net/inet/inet_globals.c +++ b/net/inet/inet_globals.c @@ -98,6 +98,20 @@ const net_ipv6addr_t g_ipv6_llnetmask = /* Netmask for local link address */ }; #endif /* CONFIG_NET_ICMPv6_AUTOCONF */ +#ifdef CONFIG_NET_MLD +/* Version 2 Multicast Listener Reports are sent with an IP destination + * address of FF02:0:0:0:0:0:0:16, to which all MLDv2-capable multicast + * routers listen (RFC 3810) + */ + +const net_ipv6addr_t g_ipv6_allmldv2routers = /* All MLDv2 link local routers */ +{ + HTONS(0xff02), + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + HTONS(0x0016) +}; +#endif + #ifdef CONFIG_NET_ETHERNET /* IPv6 Multi-cast Ethernet addresses. Formed from the 16-bit prefix: diff --git a/net/mld/mld.h b/net/mld/mld.h index 56f715dc3a2..a3be0327a44 100644 --- a/net/mld/mld.h +++ b/net/mld/mld.h @@ -164,8 +164,9 @@ enum mld_msgtype_e { MLD_SEND_NONE = 0, /* Nothing to send */ MLD_SEND_GENQUERY, /* Send General Query */ - MLD_SEND_REPORT, /* Send Unsolicited report */ - MLD_SEND_DONE /* Send Done message */ + MLD_SEND_V1REPORT, /* Send MLDv1 Report message */ + MLD_SEND_V2REPORT, /* Send MLDv2 Report message */ + MLD_SEND_V1DONE /* Send MLDv1 Done message */ }; /* This structure represents one group member. There is a list of groups @@ -182,7 +183,8 @@ struct mld_group_s struct mld_group_s *next; /* Implements a singly-linked list */ net_ipv6addr_t grpaddr; /* Group IPv6 address */ struct work_s work; /* For deferred timeout operations */ - WDOG_ID wdog; /* WDOG used to detect timeouts */ + 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 */ uint8_t ifindex; /* Interface index */ uint8_t flags; /* See MLD_ flags definitions */ @@ -427,29 +429,24 @@ int mld_joingroup(FAR const struct ipv6_mreq *mrec); int mld_leavegroup(FAR const struct ipv6_mreq *mrec); /**************************************************************************** - * Name: mld_starttimer + * Name: mld_start_polltimer * * Description: - * Start the MLD timer with differing time units (ticks or deciseconds). + * Start the MLD poll timer. * ****************************************************************************/ -void mld_starttimer(FAR struct mld_group_s *group, clock_t ticks); +void mld_start_polltimer(FAR struct mld_group_s *group, clock_t ticks); /**************************************************************************** - * Name: mld_cmptimer + * Name: mld_start_v1timer * * Description: - * Compare the timer remaining on the watching timer to the deci-second - * value. If maxticks > ticks-remaining, then (1) cancel the timer (to - * avoid race conditions) and return true. - * - * If true is returned then the caller must call mld_starttimer() to - * restart the timer + * Start the MLDv1 compatibility timer. * ****************************************************************************/ -bool mld_cmptimer(FAR struct mld_group_s *group, int maxticks); +void mld_start_v1timer(FAR struct mld_group_s *group, clock_t ticks); /**************************************************************************** * Name: mld_addmcastmac diff --git a/net/mld/mld_group.c b/net/mld/mld_group.c index 1aeb87ff7b6..a8b10e09338 100644 --- a/net/mld/mld_group.c +++ b/net/mld/mld_group.c @@ -130,10 +130,13 @@ FAR struct mld_group_s *mld_grpalloc(FAR struct net_driver_s *dev, nxsem_init(&group->sem, 0, 0); nxsem_setprotocol(&group->sem, SEM_PRIO_NONE); - /* Initialize the group timer (but don't start it yet) */ + /* Initialize the group timers */ - group->wdog = wd_create(); - DEBUGASSERT(group->wdog); + group->polldog = wd_create(); + DEBUGASSERT(group->polldog); + + group->v1dog = wd_create(); + DEBUGASSERT(group->v1dog); /* Save the interface index */ @@ -231,9 +234,10 @@ void mld_grpfree(FAR struct net_driver_s *dev, FAR struct mld_group_s *group) { grpinfo("Free: %p flags: %02x\n", group, group->flags); - /* Cancel the wdog */ + /* Cancel the timers */ - wd_cancel(group->wdog); + wd_cancel(group->polldog); + wd_cancel(group->v1dog); /* Remove the group structure from the group list in the device structure */ @@ -243,9 +247,10 @@ void mld_grpfree(FAR struct net_driver_s *dev, FAR struct mld_group_s *group) (void)nxsem_destroy(&group->sem); - /* Destroy the wdog */ + /* Destroy the timers */ - wd_delete(group->wdog); + wd_delete(group->polldog); + wd_delete(group->v1dog); /* Then release the group structure resources. */ diff --git a/net/mld/mld_initialize.c b/net/mld/mld_initialize.c index a224054ade7..98f2a7a677c 100644 --- a/net/mld/mld_initialize.c +++ b/net/mld/mld_initialize.c @@ -85,6 +85,7 @@ void mld_devinit(struct net_driver_s *dev) /* Allow the MLD messages at the MAC level */ - mld_addmcastmac(dev, g_ipv6_allrouters); mld_addmcastmac(dev, g_ipv6_allnodes); + mld_addmcastmac(dev, g_ipv6_allrouters); + mld_addmcastmac(dev, g_ipv6_allmldv2routers); } diff --git a/net/mld/mld_join.c b/net/mld/mld_join.c index 8c0dd5c152d..337f43e4993 100644 --- a/net/mld/mld_join.c +++ b/net/mld/mld_join.c @@ -132,13 +132,15 @@ int mld_joingroup(FAR const struct ipv6_mreq *mrec) MLD_STATINCR(g_netstats.mld.joins); - /* Send the Version 1 Multicast Listener Report */ + /* Send the Version 2 Multicast Listener Report (Assumes MLDv2 mode + * initially?). + */ MLD_STATINCR(g_netstats.mld.report_sched); - ret = mld_waitmsg(group, MLD_SEND_REPORT); + ret = mld_waitmsg(group, MLD_SEND_V2REPORT); if (ret < 0) { - nerr("ERROR: Failed to schedule message: %d\n", ret); + nerr("ERROR: Failed to schedule Report: %d\n", ret); mld_grpfree(dev, group); return ret; } @@ -147,7 +149,7 @@ int mld_joingroup(FAR const struct ipv6_mreq *mrec) SET_MLD_STARTUP(group->flags); group->count = MLD_UNSOLREPORT_COUNT - 1; - mld_starttimer(group, MSEC2TICK(MLD_UNSOLREPORT_MSEC)); + mld_start_polltimer(group, MSEC2TICK(MLD_UNSOLREPORT_MSEC)); /* Add the group (MAC) address to the Ethernet drivers MAC filter list */ diff --git a/net/mld/mld_leave.c b/net/mld/mld_leave.c index 633740759d2..a2818efa977 100644 --- a/net/mld/mld_leave.c +++ b/net/mld/mld_leave.c @@ -122,13 +122,14 @@ int mld_leavegroup(FAR const struct ipv6_mreq *mrec) { ninfo("Leaving group: %p\n", group); - /* Cancel the timer and discard any queued Reports. Canceling the + /* 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. */ - wd_cancel(group->wdog); + wd_cancel(group->polldog); + wd_cancel(group->v1dog); CLR_MLD_SCHEDMSG(group->flags); CLR_MLD_WAITMSG(group->flags); @@ -139,9 +140,12 @@ int mld_leavegroup(FAR const struct ipv6_mreq *mrec) * 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 router responds to the Done message with a multicast-address-s - * pecific (MAS) Query. If any other node responds to the Query with a + * 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? */ if (IS_MLD_LASTREPORT(group->flags)) @@ -150,7 +154,7 @@ int mld_leavegroup(FAR const struct ipv6_mreq *mrec) MLD_STATINCR(g_netstats.mld.done_sched); - ret = mld_waitmsg(group, MLD_SEND_DONE); + ret = mld_waitmsg(group, MLD_SEND_V1DONE); if (ret < 0) { nerr("ERROR: Failed to schedule message: %d\n", ret); diff --git a/net/mld/mld_query.c b/net/mld/mld_query.c index 0be7992ea44..8707cdcc224 100644 --- a/net/mld/mld_query.c +++ b/net/mld/mld_query.c @@ -37,6 +37,7 @@ #include +#include #include #include @@ -61,6 +62,97 @@ * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: mld_setup_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 void mld_setup_v1compat(FAR struct mld_group_s *group, + FAR const struct mld_mcast_listen_query_s *query, + 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 + * 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. + */ + + respmsec = MLD_QUERY_MSEC; +#endif + + /* Select MLDv1 compatibility mode (might already be selected) */ + + SET_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); + } + + /* 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))); + } +} + +/**************************************************************************** + * Name: mld_report_msgtype + * + * Description: + * Determine which type of Report to send, MLDv1 or MLDv2, depending on + * current state of compatibility mode flag. + * + ****************************************************************************/ + +static inline uint8_t mld_report_msgtype(FAR struct mld_group_s *group) +{ + if (IS_MLD_V1COMPAT(group->flags)) + { + return MLD_SEND_V1REPORT; + } + else + { + return MLD_SEND_V2REPORT; + } +} + /**************************************************************************** * Name: mld_mrc2mrd * @@ -125,7 +217,7 @@ static bool mld_cmpaddr(FAR struct net_driver_s *dev, * * Description: * Check if we are still the querier for this group (assuming that we are - * currently the querier). This comparies the IPv6 Source Address of the + * 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. @@ -152,9 +244,9 @@ static void mld_check_querier(FAR struct net_driver_s *dev, if (!IS_MLD_STARTUP(group->flags)) { - /* Yes.. cancel the timer */ + /* Yes.. cancel the poll timer */ - wd_cancel(group->wdog); + wd_cancel(group->polldog); } /* Switch to non-Querier mode */ @@ -196,6 +288,8 @@ int mld_query(FAR struct net_driver_s *dev, { FAR struct ipv6_hdr_s *ipv6 = IPv6BUF; FAR struct mld_group_s *group; + unsigned int mldsize; + bool mldv1 = false; ninfo("Multicast Listener Query\n"); @@ -207,6 +301,29 @@ int mld_query(FAR struct net_driver_s *dev, 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 = 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)) + { + nwarn("WARNING: Invalid size for MLD query: %u\n", mldsize); + + dev->d_len = 0; + return -EINVAL; + } + /* There are three variants of the Query message (RFC 3810): * * 1. A "General Query" is sent by the Querier to learn which @@ -267,6 +384,16 @@ int mld_query(FAR struct net_driver_s *dev, /* 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)) + { + nwarn("WARNING: MLDv2 query received in MLDv1 " + "compatibility mode\n"); + } } } @@ -290,9 +417,14 @@ int mld_query(FAR struct net_driver_s *dev, if (!net_ipv6addr_cmp(member->grpaddr, g_ipv6_allnodes)) { + + /* Check MLDv1 compatibility mode */ + + mld_setup_v1compat(member, query, mldv1); + /* Send one report and break out of the loop */ - mld_send(dev, member, MLD_SEND_REPORT); + mld_send(dev, member, mld_report_msgtype(member)); rptsent = true; break; } @@ -326,6 +458,13 @@ int mld_query(FAR struct net_driver_s *dev, mld_check_querier(dev, ipv6, group); + /* Warn if we received a MLDv2 query in MLDv1 compatibility mode. */ + + if (!mldv1 && IS_MLD_V1COMPAT(group->flags)) + { + nwarn("WARNING: MLDv2 query received in MLDv1 compatibility mode\n"); + } + /* Check for Multicast Address Specific Query */ if (net_ipv6addr_cmp(ipv6->destipaddr, g_ipv6_allrouters)) @@ -341,9 +480,13 @@ int mld_query(FAR struct net_driver_s *dev, MLD_STATINCR(g_netstats.mld.massq_query_received); } + /* Check MLDv1 compatibility mode */ + + mld_setup_v1compat(group, query, mldv1); + /* Send the report */ - mld_send(dev, group, MLD_SEND_REPORT); + mld_send(dev, group, mld_report_msgtype(group)); } /* Not sent to all systems. Check for Unicast General Query */ @@ -353,9 +496,13 @@ int mld_query(FAR struct net_driver_s *dev, ninfo("Unicast query\n"); MLD_STATINCR(g_netstats.mld.ucast_query_received); + /* Check MLDv1 compatibility mode */ + + mld_setup_v1compat(group, query, mldv1); + /* Send the report */ - mld_send(dev, group, MLD_SEND_REPORT); + mld_send(dev, group, mld_report_msgtype(group)); } else { diff --git a/net/mld/mld_report.c b/net/mld/mld_report.c index b54a1e0814e..6ef08999d22 100644 --- a/net/mld/mld_report.c +++ b/net/mld/mld_report.c @@ -48,12 +48,6 @@ #include "devif/devif.h" #include "mld/mld.h" -/**************************************************************************** - * Pre-processor Definitions - ****************************************************************************/ - -#define IPv6BUF ((FAR struct ipv6_hdr_s *)&dev->d_buf[NET_LL_HDRLEN(dev)]) - /**************************************************************************** * Private Functions ****************************************************************************/ @@ -67,9 +61,8 @@ * ****************************************************************************/ -int mld_report(FAR struct net_driver_s *dev) +int mld_report(FAR struct net_driver_s *dev, FAR const net_ipv6addr_t grpaddr) { - FAR struct ipv6_hdr_s *ipv6 = IPv6BUF; FAR struct mld_group_s *group; /* Reports are send to the group multicast address. Hence, the IPv6 @@ -77,9 +70,8 @@ int mld_report(FAR struct net_driver_s *dev) */ ninfo("grpaddr: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", - ipv6->destipaddr[0], ipv6->destipaddr[1], ipv6->destipaddr[2], - ipv6->destipaddr[3], ipv6->destipaddr[4], ipv6->destipaddr[5], - ipv6->destipaddr[6], ipv6->destipaddr[7]); + grpaddr[0], grpaddr[1], grpaddr[2], grpaddr[3], + grpaddr[4], grpaddr[5], grpaddr[6], grpaddr[7]); /* Find the group (or create a new one) using the incoming IP address. * If we are not a router (and I assume we are not), then can ignore @@ -89,7 +81,7 @@ int mld_report(FAR struct net_driver_s *dev) */ #ifdef CONFIG_MLD_ROUTER - group = mld_grpallocfind(dev, ipv6->destipaddr); + group = mld_grpallocfind(dev, grpaddr); if (group == NULL) { nerr("ERROR: Failed to allocate group\n"); @@ -97,7 +89,7 @@ int mld_report(FAR struct net_driver_s *dev) } #else - group = mld_grpfind(dev, ipv6->destipaddr); + group = mld_grpfind(dev, grpaddr); if (group == NULL) { nwarn("WARNING: Ignoring group. We are not a member\n"); @@ -144,15 +136,20 @@ int mld_report(FAR struct net_driver_s *dev) * so it is deleted from the list and its disappearance is made known to * the multicast routing component. * + * A node MUST accept and process any version 1 Report whose IP + * Destination Address field contains *any* of the IPv6 addresses (unicast + * or multicast) assigned to the interface on which the Report arrives. + * ****************************************************************************/ int mld_report_v1(FAR struct net_driver_s *dev, FAR const struct mld_mcast_listen_report_v1_s *report) { ninfo("Version 1 Multicast Listener Report\n"); + DEBUGASSERT(dev != NULL && report != NULL); MLD_STATINCR(g_netstats.mld.v1report_received); - return mld_report(dev); + return mld_report(dev, report->mcastaddr); } /**************************************************************************** @@ -169,13 +166,42 @@ int mld_report_v1(FAR struct net_driver_s *dev, * any of these checks fails, the packet is dropped. If the validity of * the MLD message is verified, the router starts to process the Report. * + * Version 2 Multicast Listener Reports are sent with an IP destination + * address of FF02:0:0:0:0:0:0:16, to which all MLDv2-capable multicast + * routers listen + * ****************************************************************************/ int mld_report_v2(FAR struct net_driver_s *dev, FAR const struct mld_mcast_listen_report_v2_s *report) { + int ret = -ENOENT; + int i; + ninfo("Version 2 Multicast Listener Report\n"); + DEBUGASSERT(dev != NULL && report != NULL); MLD_STATINCR(g_netstats.mld.v2report_received); - return mld_report(dev); + + for (i = 0; i < report->naddrec; i++) + { + /* Handle this mcast address in the list */ + + int status = mld_report(dev, report->addrec[i].mcast); + if (status == OK) + { + /* Return success if any address in the listed was processed */ + + ret = OK; + } + else if (status != -ENOENT) + { + /* Any result other than -ENOENT would be a problem */ + + ret = status; + break; + } + } + + return ret; } diff --git a/net/mld/mld_send.c b/net/mld/mld_send.c index 4b2693571e8..3975acd7f79 100644 --- a/net/mld/mld_send.c +++ b/net/mld/mld_send.c @@ -76,16 +76,18 @@ /* Buffer layout */ -#define IPv6BUF ((FAR struct ipv6_hdr_s *)&dev->d_buf[NET_LL_HDRLEN(dev)]) -#define RABUF ((FAR struct ipv6_router_alert_s *) \ - &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN) -#define RASIZE sizeof(struct ipv6_router_alert_s) -#define QUERYBUF ((FAR struct mld_mcast_listen_query_s *) \ - &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN + RASIZE) -#define REPORTBUF ((FAR struct mld_mcast_listen_report_v1_s *) \ - &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN + RASIZE) -#define DONEBUF ((FAR struct mld_mcast_listen_done_v1_s *) \ - &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN + RASIZE) +#define IPv6BUF ((FAR struct ipv6_hdr_s *)&dev->d_buf[NET_LL_HDRLEN(dev)]) +#define RABUF ((FAR struct ipv6_router_alert_s *) \ + &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN) +#define RASIZE sizeof(struct ipv6_router_alert_s) +#define QUERYBUF ((FAR struct mld_mcast_listen_query_s *) \ + &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN + RASIZE) +#define V1REPORTBUF ((FAR struct mld_mcast_listen_report_v1_s *) \ + &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN + RASIZE) +#define V2REPORTBUF ((FAR struct mld_mcast_listen_report_v2_s *) \ + &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN + RASIZE) +#define DONEBUF ((FAR struct mld_mcast_listen_done_v1_s *) \ + &dev->d_buf[NET_LL_HDRLEN(dev)] + IPv6_HDRLEN + RASIZE) /**************************************************************************** * Public Functions @@ -124,31 +126,46 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group, IFF_SET_IPv6(dev->d_flags); - /* What is the size of the ICMPv6 payload? Currently only Version 1 - * REPORT and DONE packets are sent (these are actually the same size). - * This will change. - */ + /* What is the size of the ICMPv6 payload? */ switch (msgtype) { case MLD_SEND_GENQUERY: /* Send General Query */ - ninfo("Send General Query, flags=%02x\n", group->flags); - mldsize = SIZEOF_MLD_MCAST_LISTEN_QUERY_S(0); + { + ninfo("Send General Query, flags=%02x\n", group->flags); + mldsize = SIZEOF_MLD_MCAST_LISTEN_QUERY_S(0); + } break; - case MLD_SEND_REPORT: /* Send Unsolicited report */ - ninfo("Send Report, flags=%02x\n", group->flags); - mldsize = sizeof(struct mld_mcast_listen_report_v1_s); + case MLD_SEND_V1REPORT: /* Send MLDv1 Report message */ + { + ninfo("Send MLDv1 Report, flags=%02x\n", group->flags); + mldsize = sizeof(struct mld_mcast_listen_report_v1_s); + } break; - case MLD_SEND_DONE: /* Send Done message */ - ninfo("Send Done message, flags=%02x\n", group->flags); - mldsize = sizeof(struct mld_mcast_listen_done_v1_s); + case MLD_SEND_V2REPORT: /* Send MLDv2 Report message */ + { + unsigned int addreclen; + + ninfo("Send MLDv2 Report, flags=%02x\n", group->flags); + addreclen = SIZEOF_MLD_MCAST_ADDREC_V2_S(0, 0); + mldsize = SIZEOF_MLD_MCAST_LISTEN_REPORT_V2_S(addreclen); + } + break; + + case MLD_SEND_V1DONE: /* Send MLDv1 Done message */ + { + ninfo("Send Done message, flags=%02x\n", group->flags); + mldsize = sizeof(struct mld_mcast_listen_done_v1_s); + } break; default: - nerr("Bad msgtype: %02x \n", msgtype); - DEBUGPANIC(); + { + nerr("Bad msgtype: %02x \n", msgtype); + DEBUGPANIC(); + } return; } @@ -196,11 +213,15 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group, destipaddr = g_ipv6_allnodes; break; - case MLD_SEND_REPORT: /* Send Unsolicited report */ + case MLD_SEND_V1REPORT: /* Send MLDv1 Report message */ destipaddr = group->grpaddr; break; - case MLD_SEND_DONE: /* Send Done message */ + case MLD_SEND_V2REPORT: /* Send MLDv2 Report message */ + destipaddr = g_ipv6_allmldv2routers; + break; + + case MLD_SEND_V1DONE: /* Send MLDv1 Done message */ destipaddr = g_ipv6_allrouters; break; } @@ -234,17 +255,26 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group, { FAR struct mld_mcast_listen_query_s *query = QUERYBUF; - /* Initializer the Query payload. In a General Query, both the + /* Initialize the Query payload. In a General Query, both the * Multicast Address field and the Number of Sources (N) * field are zero. + * + * Careful here. In MLDv1 compatibility mode, the MRC is not + * encoded and must follow the rules for MLDv1. */ memset(query, 0, sizeof(struct mld_mcast_listen_report_v1_s)); net_ipv6addr_hdrcopy(query->grpaddr, &group->grpaddr); query->type = ICMPV6_MCAST_LISTEN_QUERY; query->mrc = MLD_QRESP_MSEC; - query->flags = MLD_ROBUSTNESS; - query->qqic = MLD_QRESP_SEC; + + /* Fields unique to the extended MLDv2 query */ + + if (!IS_MLD_V1COMPAT(group->flags)) + { + query->flags = MLD_ROBUSTNESS; + query->qqic = MLD_QRESP_SEC; + } /* Calculate the ICMPv6 checksum. */ @@ -255,11 +285,11 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group, } break; - case MLD_SEND_REPORT: + case MLD_SEND_V1REPORT: /* Send MLDv1 Report message */ { - FAR struct mld_mcast_listen_report_v1_s *report = REPORTBUF; + FAR struct mld_mcast_listen_report_v1_s *report = V1REPORTBUF; - /* Initializer the Report payload */ + /* Initialize the Report payload */ memset(report, 0, sizeof(struct mld_mcast_listen_report_v1_s)); net_ipv6addr_hdrcopy(report->mcastaddr, &group->grpaddr); @@ -275,11 +305,36 @@ void mld_send(FAR struct net_driver_s *dev, FAR struct mld_group_s *group, } break; - case MLD_SEND_DONE: + case MLD_SEND_V2REPORT: /* Send MLDv2 Report message */ + { + FAR struct mld_mcast_listen_report_v2_s *report = V2REPORTBUF; + FAR struct mld_mcast_addrec_v2_s *addrec; + + /* Initialize the Report payload */ + + memset(report, 0, mldsize); + report->type = ICMPV6_MCAST_LISTEN_REPORT_V2; + report->naddrec = 1; + + addrec = report->addrec; + addrec->rectype = MODE_IS_INCLUDE; + net_ipv6addr_hdrcopy(addrec->mcast, &group->grpaddr); + + /* Calculate the ICMPv6 checksum. */ + + report->chksum = 0; + report->chksum = ~icmpv6_chksum(dev); + + SET_MLD_LASTREPORT(group->flags); /* Remember we were the last to report */ + MLD_STATINCR(g_netstats.mld.report_sent); + } + break; + + case MLD_SEND_V1DONE: /* Send MLDv1 Done message */ { FAR struct mld_mcast_listen_done_v1_s *done = DONEBUF; - /* Initializer the Done payload */ + /* Initialize the Done payload */ memset(done, 0, sizeof(struct mld_mcast_listen_done_v1_s)); done->type = ICMPV6_MCAST_LISTEN_DONE_V1; diff --git a/net/mld/mld_timer.c b/net/mld/mld_timer.c index ac360415215..6298eb34eb3 100644 --- a/net/mld/mld_timer.c +++ b/net/mld/mld_timer.c @@ -90,7 +90,28 @@ ****************************************************************************/ /**************************************************************************** - * Name: mld_timeout_work + * Name: mld_report_msgtype + * + * Description: + * Determine which type of Report to send, MLDv1 or MLDv2, depending on + * current state of compatibility mode flag. + * + ****************************************************************************/ + +static inline uint8_t mld_report_msgtype(FAR struct mld_group_s *group) +{ + if (IS_MLD_V1COMPAT(group->flags)) + { + return MLD_SEND_V1REPORT; + } + else + { + return MLD_SEND_V2REPORT; + } +} + +/**************************************************************************** + * Name: mld_polldog_work * * Description: * Timeout watchdog work @@ -100,7 +121,7 @@ * ****************************************************************************/ -static void mld_timeout_work(FAR void *arg) +static void mld_polldog_work(FAR void *arg) { FAR struct mld_group_s *group; int ret; @@ -118,7 +139,7 @@ static void mld_timeout_work(FAR void *arg) /* Schedule (and forget) the Report. */ MLD_STATINCR(g_netstats.mld.report_sched); - ret = mld_schedmsg(group, MLD_SEND_REPORT); + ret = mld_schedmsg(group, mld_report_msgtype(group)); if (ret < 0) { nerr("ERROR: Failed to schedule message: %d\n", ret); @@ -133,7 +154,7 @@ static void mld_timeout_work(FAR void *arg) /* Decrement the count and restart the timer */ group->count--; - mld_starttimer(group, MSEC2TICK(MLD_UNSOLREPORT_MSEC)); + mld_start_polltimer(group, MSEC2TICK(MLD_UNSOLREPORT_MSEC)); } else { @@ -145,7 +166,7 @@ static void mld_timeout_work(FAR void *arg) if (IS_MLD_QUERIER(group->flags)) { - mld_starttimer(group, MSEC2TICK(MLD_QUERY_MSEC)); + mld_start_polltimer(group, MSEC2TICK(MLD_QUERY_MSEC)); } } } @@ -165,25 +186,25 @@ static void mld_timeout_work(FAR void *arg) /* Restart the Querier timer */ - mld_starttimer(group, MSEC2TICK(MLD_QUERY_MSEC)); + mld_start_polltimer(group, MSEC2TICK(MLD_QUERY_MSEC)); } net_unlock(); } /**************************************************************************** - * Name: mld_timeout + * Name: mld_polldog_timout * * Description: * Timeout watchdog handler. * * Assumptions: - * This function is called from the wdog timer handler which runs in the + * This function is called from the polldog timer handler which runs in the * context of the timer interrupt handler. * ****************************************************************************/ -static void mld_timeout(int argc, uint32_t arg, ...) +static void mld_polldog_timout(int argc, uint32_t arg, ...) { FAR struct mld_group_s *group; int ret; @@ -199,7 +220,92 @@ static void mld_timeout(int argc, uint32_t arg, ...) * work queue. */ - ret = work_queue(LPWORK, &group->work, mld_timeout_work, group, 0); + ret = work_queue(LPWORK, &group->work, mld_polldog_work, group, 0); + if (ret < 0) + { + nerr("ERROR: Failed to queue timeout 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 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; + + ninfo("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) { nerr("ERROR: Failed to queue timeout work: %d\n", ret); @@ -211,78 +317,47 @@ static void mld_timeout(int argc, uint32_t arg, ...) ****************************************************************************/ /**************************************************************************** - * Name: mld_starttimer + * Name: mld_start_polltimer * * Description: - * Start the MLD timer. - * - * Assumptions: - * This function may be called from most any context. + * Start the MLD poll timer. * ****************************************************************************/ -void mld_starttimer(FAR struct mld_group_s *group, clock_t ticks) +void mld_start_polltimer(FAR struct mld_group_s *group, clock_t ticks) { int ret; /* Start the timer */ - mtmrinfo("ticks: %ld\n", (unsigned long)ticks); + mtmrinfo("ticks: %lu\n", (unsigned long)ticks); - ret = wd_start(group->wdog, ticks, mld_timeout, 1, (uint32_t)group); + ret = wd_start(group->polldog, ticks, mld_polldog_timout, 1, (uint32_t)group); DEBUGASSERT(ret == OK); UNUSED(ret); } /**************************************************************************** - * Name: mld_cmptimer + * Name: mld_start_v1timer * * Description: - * Compare the timer remaining on the watching timer to the deci-second - * value. If maxticks > ticks-remaining, then (1) cancel the timer (to - * avoid race conditions) and return true. - * - * Assumptions: - * This function may be called from most any context. If true is returned - * then the caller must call mld_starttimer() to restart the timer + * Start the MLDv1 compatibility timer. * ****************************************************************************/ -bool mld_cmptimer(FAR struct mld_group_s *group, int maxticks) +void mld_start_v1timer(FAR struct mld_group_s *group, clock_t ticks) { - irqstate_t flags; - int remaining; + int ret; - /* Disable interrupts so that there is no race condition with the actual - * timer expiration. - */ + /* Start the timer */ - flags = enter_critical_section(); + mtmrinfo("ticks: %lu\n", (unsigned long)ticks); - /* Get the timer remaining on the watchdog. A time of <= zero means that - * the watchdog was never started. - */ + ret = wd_start(group->v1dog, ticks, mld_v1dog_timout, 1, (uint32_t)group); - remaining = wd_gettime(group->wdog); - - /* A remaining time of zero means that the watchdog was never started - * or has already expired. That case should be covered in the following - * test as well. - */ - - mtmrinfo("maxticks: %d remaining: %d\n", maxticks, remaining); - if (maxticks > remaining) - { - /* Cancel the watchdog timer and return true */ - - wd_cancel(group->wdog); - leave_critical_section(flags); - return true; - } - - leave_critical_section(flags); - return false; + DEBUGASSERT(ret == OK); + UNUSED(ret); } #endif /* CONFIG_NET_MLD */