mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-02 14:13:06 +00:00
If the net_mgmt event has some info, like IP address, that could be sent, then send it the same time. This is very useful for the receiver of the event in order to know that is happening in the system. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
363 lines
7.8 KiB
C
363 lines
7.8 KiB
C
/** @file
|
|
* @brief IPv6 MLD related functions
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2018 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL);
|
|
|
|
#include <errno.h>
|
|
#include <net/net_core.h>
|
|
#include <net/net_pkt.h>
|
|
#include <net/net_stats.h>
|
|
#include <net/net_context.h>
|
|
#include <net/net_mgmt.h>
|
|
#include "net_private.h"
|
|
#include "connection.h"
|
|
#include "icmpv6.h"
|
|
#include "udp_internal.h"
|
|
#include "tcp_internal.h"
|
|
#include "ipv6.h"
|
|
#include "nbr.h"
|
|
#include "6lo.h"
|
|
#include "route.h"
|
|
#include "net_stats.h"
|
|
|
|
/* Timeout for various buffer allocations in this file. */
|
|
#define PKT_WAIT_TIME K_MSEC(50)
|
|
|
|
#define MLDv2_MCAST_RECORD_LEN sizeof(struct net_icmpv6_mld_mcast_record)
|
|
#define IPV6_OPT_HDR_ROUTER_ALERT_LEN 8
|
|
|
|
#define MLDv2_LEN (MLDv2_MCAST_RECORD_LEN + sizeof(struct in6_addr))
|
|
|
|
static int mld_create(struct net_pkt *pkt,
|
|
const struct in6_addr *addr,
|
|
u8_t record_type,
|
|
u16_t num_sources)
|
|
{
|
|
NET_PKT_DATA_ACCESS_DEFINE(mld_access,
|
|
struct net_icmpv6_mld_mcast_record);
|
|
struct net_icmpv6_mld_mcast_record *mld;
|
|
|
|
mld = (struct net_icmpv6_mld_mcast_record *)
|
|
net_pkt_get_data(pkt, &mld_access);
|
|
if (!mld) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
mld->record_type = record_type;
|
|
mld->aux_data_len = 0U;
|
|
mld->num_sources = htons(num_sources);
|
|
|
|
net_ipaddr_copy(&mld->mcast_address, addr);
|
|
|
|
if (net_pkt_set_data(pkt, &mld_access)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
if (num_sources > 0) {
|
|
/* All source addresses, RFC 3810 ch 3 */
|
|
if (net_pkt_write(pkt,
|
|
net_ipv6_unspecified_address()->s6_addr,
|
|
sizeof(struct in6_addr))) {
|
|
return -ENOBUFS;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mld_create_packet(struct net_pkt *pkt, u16_t count)
|
|
{
|
|
struct in6_addr dst;
|
|
|
|
/* Sent to all MLDv2-capable routers */
|
|
net_ipv6_addr_create(&dst, 0xff02, 0, 0, 0, 0, 0, 0, 0x0016);
|
|
|
|
net_pkt_set_ipv6_hop_limit(pkt, 1); /* RFC 3810 ch 7.4 */
|
|
|
|
if (net_ipv6_create(pkt, net_if_ipv6_select_src_addr(
|
|
net_pkt_iface(pkt), &dst),
|
|
&dst)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* Add hop-by-hop option and router alert option, RFC 3810 ch 5. */
|
|
if (net_pkt_write_u8(pkt, IPPROTO_ICMPV6) ||
|
|
net_pkt_write_u8(pkt, 0)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* IPv6 router alert option is described in RFC 2711.
|
|
* - 0x0502 RFC 2711 ch 2.1
|
|
* - MLD (value 0)
|
|
* - 2 bytes of padding
|
|
*/
|
|
if (net_pkt_write_be16(pkt, 0x0502) ||
|
|
net_pkt_write_be16(pkt, 0) ||
|
|
net_pkt_write_be16(pkt, 0)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
net_pkt_set_ipv6_ext_len(pkt, IPV6_OPT_HDR_ROUTER_ALERT_LEN);
|
|
|
|
/* ICMPv6 header + reserved space + count.
|
|
* MLDv6 stuff will come right after
|
|
*/
|
|
if (net_icmpv6_create(pkt, NET_ICMPV6_MLDv2, 0) ||
|
|
net_pkt_write_be16(pkt, 0) ||
|
|
net_pkt_write_be16(pkt, count)) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
net_pkt_set_ipv6_next_hdr(pkt, NET_IPV6_NEXTHDR_HBHO);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mld_send(struct net_pkt *pkt)
|
|
{
|
|
net_pkt_cursor_init(pkt);
|
|
net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
|
|
|
|
if (net_send_data(pkt) < 0) {
|
|
net_stats_update_icmp_drop(net_pkt_iface(pkt));
|
|
net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return -1;
|
|
}
|
|
|
|
net_stats_update_icmp_sent(net_pkt_iface(pkt));
|
|
net_stats_update_ipv6_mld_sent(net_pkt_iface(pkt));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mld_send_generic(struct net_if *iface,
|
|
const struct in6_addr *addr,
|
|
u8_t mode)
|
|
{
|
|
struct net_pkt *pkt;
|
|
int ret;
|
|
|
|
pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
|
|
NET_ICMPV6_UNUSED_LEN +
|
|
MLDv2_MCAST_RECORD_LEN +
|
|
sizeof(struct in6_addr),
|
|
AF_INET6, IPPROTO_ICMPV6,
|
|
PKT_WAIT_TIME);
|
|
if (!pkt) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (mld_create_packet(pkt, 1) ||
|
|
mld_create(pkt, addr, mode, 1)) {
|
|
ret = -ENOBUFS;
|
|
goto drop;
|
|
}
|
|
|
|
ret = mld_send(pkt);
|
|
if (ret) {
|
|
goto drop;
|
|
}
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
net_pkt_unref(pkt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int net_ipv6_mld_join(struct net_if *iface, const struct in6_addr *addr)
|
|
{
|
|
struct net_if_mcast_addr *maddr;
|
|
int ret;
|
|
|
|
maddr = net_if_ipv6_maddr_lookup(addr, &iface);
|
|
if (maddr && net_if_ipv6_maddr_is_joined(maddr)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
if (!maddr) {
|
|
maddr = net_if_ipv6_maddr_add(iface, addr);
|
|
if (!maddr) {
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
ret = mld_send_generic(iface, addr, NET_IPV6_MLDv2_MODE_IS_EXCLUDE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
net_if_ipv6_maddr_join(maddr);
|
|
|
|
net_if_mcast_monitor(iface, addr, true);
|
|
|
|
net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_JOIN, iface,
|
|
&maddr->address.in6_addr,
|
|
sizeof(struct in6_addr));
|
|
|
|
return ret;
|
|
}
|
|
|
|
int net_ipv6_mld_leave(struct net_if *iface, const struct in6_addr *addr)
|
|
{
|
|
struct net_if_mcast_addr *maddr;
|
|
int ret;
|
|
|
|
maddr = net_if_ipv6_maddr_lookup(addr, &iface);
|
|
if (!maddr) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!net_if_ipv6_maddr_rm(iface, addr)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = mld_send_generic(iface, addr, NET_IPV6_MLDv2_MODE_IS_INCLUDE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
net_if_mcast_monitor(iface, addr, false);
|
|
|
|
net_mgmt_event_notify_with_info(NET_EVENT_IPV6_MCAST_LEAVE, iface,
|
|
&maddr->address.in6_addr,
|
|
sizeof(struct in6_addr));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void send_mld_report(struct net_if *iface)
|
|
{
|
|
struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6;
|
|
struct net_pkt *pkt;
|
|
int i, count = 0;
|
|
|
|
NET_ASSERT(ipv6);
|
|
|
|
for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
|
|
if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
|
|
continue;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
pkt = net_pkt_alloc_with_buffer(iface, IPV6_OPT_HDR_ROUTER_ALERT_LEN +
|
|
NET_ICMPV6_UNUSED_LEN +
|
|
count * MLDv2_MCAST_RECORD_LEN,
|
|
AF_INET6, IPPROTO_ICMPV6,
|
|
PKT_WAIT_TIME);
|
|
if (!pkt) {
|
|
return;
|
|
}
|
|
|
|
if (mld_create_packet(pkt, count)) {
|
|
goto drop;
|
|
}
|
|
|
|
for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) {
|
|
if (!ipv6->mcast[i].is_used || !ipv6->mcast[i].is_joined) {
|
|
continue;
|
|
}
|
|
|
|
if (!mld_create(pkt, &ipv6->mcast[i].address.in6_addr,
|
|
NET_IPV6_MLDv2_MODE_IS_EXCLUDE, 0)) {
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
if (!mld_send(pkt)) {
|
|
return;
|
|
}
|
|
|
|
drop:
|
|
net_pkt_unref(pkt);
|
|
}
|
|
|
|
#define dbg_addr(action, pkt_str, src, dst) \
|
|
do { \
|
|
NET_DBG("%s %s from %s to %s", action, pkt_str, \
|
|
log_strdup(net_sprint_ipv6_addr(src)), \
|
|
log_strdup(net_sprint_ipv6_addr(dst))); \
|
|
} while (0)
|
|
|
|
#define dbg_addr_recv(pkt_str, src, dst) \
|
|
dbg_addr("Received", pkt_str, src, dst)
|
|
|
|
static enum net_verdict handle_mld_query(struct net_pkt *pkt,
|
|
struct net_ipv6_hdr *ip_hdr,
|
|
struct net_icmp_hdr *icmp_hdr)
|
|
{
|
|
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(mld_access,
|
|
struct net_icmpv6_mld_query);
|
|
u16_t length = net_pkt_get_len(pkt);
|
|
struct net_icmpv6_mld_query *mld_query;
|
|
u16_t pkt_len;
|
|
|
|
mld_query = (struct net_icmpv6_mld_query *)
|
|
net_pkt_get_data(pkt, &mld_access);
|
|
if (!mld_query) {
|
|
NET_DBG("DROP: NULL MLD query");
|
|
goto drop;
|
|
}
|
|
|
|
net_pkt_acknowledge_data(pkt, &mld_access);
|
|
|
|
dbg_addr_recv("Multicast Listener Query", &ip_hdr->src, &ip_hdr->dst);
|
|
|
|
net_stats_update_ipv6_mld_recv(net_pkt_iface(pkt));
|
|
|
|
mld_query->num_sources = ntohs(mld_query->num_sources);
|
|
|
|
pkt_len = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) +
|
|
sizeof(struct net_icmp_hdr) +
|
|
sizeof(struct net_icmpv6_mld_query) +
|
|
sizeof(struct in6_addr) * mld_query->num_sources;
|
|
|
|
if (length < pkt_len || pkt_len > NET_IPV6_MTU ||
|
|
ip_hdr->hop_limit != 1U || icmp_hdr->code != 0U) {
|
|
goto drop;
|
|
}
|
|
|
|
/* Currently we only support an unspecified address query. */
|
|
if (!net_ipv6_addr_cmp(&mld_query->mcast_address,
|
|
net_ipv6_unspecified_address())) {
|
|
NET_DBG("DROP: only supporting unspecified address query");
|
|
goto drop;
|
|
}
|
|
|
|
send_mld_report(net_pkt_iface(pkt));
|
|
|
|
net_pkt_unref(pkt);
|
|
|
|
return NET_OK;
|
|
|
|
drop:
|
|
net_stats_update_ipv6_mld_drop(net_pkt_iface(pkt));
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
static struct net_icmpv6_handler mld_query_input_handler = {
|
|
.type = NET_ICMPV6_MLD_QUERY,
|
|
.code = 0,
|
|
.handler = handle_mld_query,
|
|
};
|
|
|
|
void net_ipv6_mld_init(void)
|
|
{
|
|
net_icmpv6_register_handler(&mld_query_input_handler);
|
|
}
|