mbed-os/source/Common_Protocols/mld.c

389 lines
12 KiB
C

/*
* Copyright (c) 2016-2017, Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed 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.
*/
/*
* MLD version 1 (RFC 2710) implementation
*
* RFC 6434 (IPv6 Node Requirements) says nodes MUST support MLDv1, and that
* nodes SHOULD support MLDv2 (possibly lightweight).
*
* As we don't support Source-Specific Multicast, nothing is really gained
* by supporting MLDv2. The statement in RFC 6434 that "the presence of a single
* MLDv1 participant on a link requires that all other nodes on the link operate
* in version 1 compatibility mode" seems to be misleading. Our presence doesn't
* affect hosts, and it doesn't have much worse effect on the router or network
* than the presence of an MLDv2 Any-Source-Multicast host for that group.
*/
#include "nsconfig.h"
#include "ns_types.h"
#include "common_functions.h"
#include "randLIB.h"
#include <string.h>
#include "NWK_INTERFACE/Include/protocol.h"
#include "Common_Protocols/ip.h"
#include "Common_Protocols/ipv6.h"
#include "Common_Protocols/icmpv6.h"
#include "Common_Protocols/mld.h"
#include "6LoWPAN/Thread/thread_common.h"
#define MLD_UNSOLICITED_REPORT_INTERVAL (10 * 10) /* 10 seconds */
#if 0
// MLDv2 fragments - not yet
/* If Maximum Response Code >=32768, Maximum Response Code represents a
* floating-point value as follows:
*
* 0 1 2 3 4 5 6 7 8 9 A B C D E F
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |1| exp | mant |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Maximum Response Delay = (mant | 0x1000) << (exp+3)
*/
/* Convert Maximum Response Code to Maximum Response Delay (milliseconds) */
/* Maximum value is 0x7FFC00 (~2 hours) */
static inline uint_least24_t mldv2_mrd_from_mrc(uint16_t mrc)
{
if (mrc < 0x8000) {
return mrc;
} else {
uint_fast16_t exp = (mrc & 0x7000) >> 12;
uint_fast16_t mant = mrc & 0x0FFF;
return (UINT24_C(0x1000) | mant) << (exp + 3);
}
}
/* Convert Maximum Response Delay (milliseconds) to Maximum Response Code */
/* Maximum representable value is (0x1FFF << 10) = 0x7FFC00 (~2 hours) */
static inline uint16_t mldv2_mrc_from_mrd(uint_least24_t mrd)
{
if (mrd < 0x8000) {
return mrd;
} else if (mrd >= 0x800000) {
return 0xFFFF;
} else {
/* mrd is in range 0x8000 to 0x7FFFFF, so clz32 returns 9-16, so exp is 0-7 */
/* Example: 0x123456 - clz = 11; exp = 5; mant = 0x234; result = 0x95 */
uint16_t exp = 16 - common_count_leading_zeros_32(mrd);
uint16_t mant = (mrd >> (exp + 3)) & 0x0FFF;
return 0x8000 | (exp << 12) | mant;
}
}
/*
* If QQIC >= 128, QQIC represents a floating-point value as follows:
*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |1| exp | mant |
* +-+-+-+-+-+-+-+-+
*
* QQI = (mant | 0x10) << (exp + 3)
*/
/* Convert Querier's Query Interval Code to Querier's Query Interval (seconds) */
/* Maximum value is 0x7C00 (~9 hours) */
static inline uint16_t mldv2_qqi_from_qqic(uint8_t qqic)
{
if (qqic < 0x80) {
return qqic;
} else {
uint_fast8_t exp = (qqic & 0x70) >> 4;
uint_fast8_t mant = qqic & 0x0F;
return (UINT16_C(0x10) | mant) << (exp + 3);
}
}
/* Convert Querier's Query Interval (seconds) to Querier's Query Interval Code */
/* Maximum representable value is (0x1f << 10) = 0x7C00 (~9 hours) */
static inline uint8_t mldv2_qqic_from_qqi(uint16_t qqi)
{
if (qqi < 0x80) {
return qqi;
} else if (qqi >= 0x8000) {
return 0xFF;
} else {
/* qqi is in range 0x80 to 0x7FFF, so clz16 returns 1-8, so exp is 0-7 */
/* Example: 0x0151 - clz = 7; exp = 1; mant = 0x5; result = 0x95 */
uint8_t exp = 8 - common_count_leading_zeros_16(qqi);
uint8_t mant = (qqi >> (exp + 3)) & 0x0F;
return 0x80 | (exp << 4) | mant;
}
}
#endif
/*
* RFC 2710 Multicast Listener Discovery message
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | Code | Checksum |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Maximum Response Delay | Reserved |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* + +
* | |
* + Multicast Address +
* | |
* + +
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
buffer_t *mld_build(protocol_interface_info_entry_t *cur, uint8_t type, uint16_t max_response_delay, const uint8_t address[static 16])
{
uint8_t *ptr;
/* Validity checks - must be multicast, scope >= link-local */
if (!addr_is_ipv6_multicast(address) || addr_ipv6_multicast_scope(address) <= IPV6_SCOPE_INTERFACE_LOCAL) {
/* Except we allow unspecified for general query */
if (!(type == ICMPV6_TYPE_INFO_MCAST_LIST_QUERY && address == ADDR_UNSPECIFIED)) {
return NULL;
}
}
buffer_t *buf = buffer_get(20);
if (!buf) {
return NULL;
}
buf->options.type = type;
buf->options.code = 0;
buf->options.hop_limit = 1;
buf->options.ip_extflags |= IPEXT_HBH_ROUTER_ALERT;
buf->options.multicast_loop = true;
buf->options.mpl_permitted = false;
buf->options.traffic_class = IP_DSCP_CS5 << IP_TCLASS_DSCP_SHIFT; /* RFC 4594 */
buf->interface = cur;
buf->info = (buffer_info_t)(B_DIR_DOWN | B_FROM_ICMP | B_TO_ICMP);
buf->src_sa.addr_type = ADDR_IPV6;
if (addr_interface_get_ll_address(cur, buf->src_sa.address, 0) < 0) {
memset(buf->src_sa.address, 0, 16);
}
buf->dst_sa.addr_type = ADDR_IPV6;
if (type == ICMPV6_TYPE_INFO_MCAST_LIST_DONE) {
memcpy(buf->dst_sa.address, ADDR_LINK_LOCAL_ALL_ROUTERS, 16);
} else if (type == ICMPV6_TYPE_INFO_MCAST_LIST_QUERY && address == ADDR_UNSPECIFIED) {
memcpy(buf->dst_sa.address, ADDR_LINK_LOCAL_ALL_NODES, 16);
} else {
memcpy(buf->dst_sa.address, address, 16);
}
/* MLD packets are implicitly on-link - bypass routing */
ipv6_buffer_route_to(buf, buf->dst_sa.address, cur);
if (!buf->route) {
goto invalid;
}
ptr = buffer_data_pointer(buf);
ptr = common_write_16_bit(max_response_delay, ptr);
ptr = common_write_16_bit(0, ptr);
memcpy(ptr, address, 16);
ptr += 16;
buffer_data_end_set(buf, ptr);
return buf;
invalid:
if (buf) {
buffer_free(buf);
}
return NULL;
}
static void mld_query_entry(if_group_entry_t *entry, uint16_t mrd)
{
/* Don't do MLD for low-scope groups - ff02::1 already excluded by being in the implicit list */
if (addr_ipv6_scope(entry->group, NULL) < IPV6_SCOPE_LINK_LOCAL) {
return;
}
/* "Query received" event */
/* Timer starts/resets if it's not running, or it's set to greater than MRD */
if (entry->mld_timer == 0 || mrd < entry->mld_timer) {
entry->mld_timer = randLIB_get_random_in_range(1, mrd);
}
}
bool mld_querier = false;
buffer_t *mld_query_handler(buffer_t *buf, protocol_interface_info_entry_t *cur)
{
/* Thread has its own mechanism (as ever), and borrows the mld_timer field,
* so we can't do MLD.
*/
if (thread_info(cur)) {
goto invalid;
}
/* RFC 4541 (MLD Snooping Switches) says we should accept unspecified source */
if (!(addr_is_ipv6_link_local(buf->src_sa.address) || addr_is_ipv6_unspecified(buf->src_sa.address)) || buffer_data_length(buf) < 20) {
goto invalid;
}
const uint8_t *ptr = buffer_data_pointer(buf);
uint16_t mrd = common_read_16_bit(ptr);
const uint8_t *group = ptr + 4;
if (!addr_is_ipv6_multicast(group) && !addr_is_ipv6_unspecified(group)) {
goto invalid;
}
if (mld_querier && !addr_is_ipv6_unspecified(buf->src_sa.address)) {
uint8_t our_addr[16];
if (addr_interface_get_ll_address(cur, our_addr, 0) >= 0) {
if (memcmp(buf->src_sa.address, our_addr, 16) < 0) {
mld_querier = false;
}
}
}
/* Convert Maximum Response Delay from milliseconds to ticks. If they say "0" they get 1 tick */
mrd /= 100;
if (mrd == 0) {
mrd = 1;
}
if (addr_is_ipv6_multicast(group)) {
/* Address-Specific Query */
if_group_entry_t *entry = addr_get_group_entry(cur, group);
if (!entry) {
goto invalid;
}
mld_query_entry(entry, mrd);
} else {
/* General Query */
ns_list_foreach(if_group_entry_t, entry, &cur->ip_groups) {
mld_query_entry(entry, mrd);
}
}
invalid:
if (buf) {
buffer_free(buf);
}
return NULL;
}
buffer_t *mld_report_handler(buffer_t *buf, protocol_interface_info_entry_t *cur)
{
if (thread_info(cur)) {
goto invalid;
}
if (!addr_is_ipv6_link_local(buf->src_sa.address) || buffer_data_length(buf) < 20) {
goto invalid;
}
const uint8_t *ptr = buffer_data_pointer(buf);
const uint8_t *group = ptr + 4;
if (!addr_is_ipv6_multicast(group)) {
goto invalid;
}
if_group_entry_t *entry = addr_get_group_entry(cur, group);
if (!entry) {
goto invalid;
}
/* "Report received" event - stop timer, clear flag, become Idle Listener */
if (entry->mld_timer && !addr_is_assigned_to_interface(cur, buf->src_sa.address)) {
entry->mld_timer = 0;
entry->mld_last_reporter = false;
}
invalid:
if (buf) {
buffer_free(buf);
}
return NULL;
}
void mld_start_listening(protocol_interface_info_entry_t *interface, if_group_entry_t *entry)
{
if (thread_info(interface)) {
thread_mcast_group_change(interface, entry, true);
return;
}
/* "Send MLD" flag only controls sending unsolicited reports when we join. We will still always respond if queried */
if (interface->send_mld && addr_ipv6_multicast_scope(entry->group) >= IPV6_SCOPE_LINK_LOCAL) {
entry->mld_timer = randLIB_get_random_in_range(1, MLD_UNSOLICITED_REPORT_INTERVAL);
entry->mld_last_reporter = true;
protocol_push(mld_build(interface, ICMPV6_TYPE_INFO_MCAST_LIST_REPORT, 0, entry->group));
} else {
entry->mld_timer = 0;
entry->mld_last_reporter = false;
}
}
void mld_stop_listening(protocol_interface_info_entry_t *interface, if_group_entry_t *entry)
{
if (thread_info(interface)) {
thread_mcast_group_change(interface, entry, false);
return;
}
if (entry->mld_last_reporter) {
protocol_push(mld_build(interface, ICMPV6_TYPE_INFO_MCAST_LIST_DONE, 0, entry->group));
}
}
void mld_slow_timer(protocol_interface_info_entry_t *interface, uint_fast16_t seconds)
{
static uint8_t query_timer;
query_timer += seconds;
if (query_timer >= 125) {
query_timer = 0;
if (mld_querier) {
protocol_push(mld_build(interface, ICMPV6_TYPE_INFO_MCAST_LIST_QUERY, 10000, ADDR_UNSPECIFIED));
}
}
}
void mld_fast_timer(protocol_interface_info_entry_t *interface, uint_fast16_t ticks)
{
if (thread_info(interface)) {
return;
}
ns_list_foreach(if_group_entry_t, entry, &interface->ip_groups) {
if (entry->mld_timer == 0) {
continue;
}
if (entry->mld_timer > ticks) {
entry->mld_timer -= ticks;
continue;
}
entry->mld_timer = 0;
entry->mld_last_reporter = true;
protocol_push(mld_build(interface, ICMPV6_TYPE_INFO_MCAST_LIST_REPORT, 0, entry->group));
}
}