/** @file * @brief IPv4 IGMP related functions */ /* * Copyright (c) 2021 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_DECLARE(net_ipv4, CONFIG_NET_IPV4_LOG_LEVEL); #include #include #include #include #include #include #include #include "net_private.h" #include "connection.h" #include "ipv4.h" #include "net_stats.h" /* Timeout for various buffer allocations in this file. */ #define PKT_WAIT_TIME K_MSEC(50) #define IPV4_OPT_HDR_ROUTER_ALERT_LEN 4 static const struct in_addr all_systems = { { { 224, 0, 0, 1 } } }; static const struct in_addr all_routers = { { { 224, 0, 0, 2 } } }; #define dbg_addr(action, pkt_str, src, dst) \ NET_DBG("%s %s from %s to %s", action, pkt_str, \ net_sprint_ipv4_addr(src), \ net_sprint_ipv4_addr(dst)); #define dbg_addr_recv(pkt_str, src, dst) \ dbg_addr("Received", pkt_str, src, dst) static int igmp_v2_create(struct net_pkt *pkt, const struct in_addr *addr, uint8_t type) { NET_PKT_DATA_ACCESS_DEFINE(igmp_access, struct net_ipv4_igmp_v2_report); struct net_ipv4_igmp_v2_report *igmp; igmp = (struct net_ipv4_igmp_v2_report *) net_pkt_get_data(pkt, &igmp_access); if (!igmp) { return -ENOBUFS; } igmp->type = type; igmp->max_rsp = 0U; net_ipaddr_copy(&igmp->address, addr); igmp->chksum = 0; igmp->chksum = net_calc_chksum_igmp((uint8_t *)igmp, sizeof(*igmp)); if (net_pkt_set_data(pkt, &igmp_access)) { return -ENOBUFS; } return 0; } static int igmp_v2_create_packet(struct net_pkt *pkt, const struct in_addr *dst, const struct in_addr *group, uint8_t type) { const uint32_t router_alert = 0x94040000; /* RFC 2213 ch 2.1 */ int ret; ret = net_ipv4_create_full(pkt, net_if_ipv4_select_src_addr( net_pkt_iface(pkt), dst), dst, 0U, 0U, 0U, 0U, 1U); /* TTL set to 1, RFC 3376 ch 2 */ if (ret) { return -ENOBUFS; } /* Add router alert option, RFC 3376 ch 2 */ if (net_pkt_write_be32(pkt, router_alert)) { return -ENOBUFS; } net_pkt_set_ipv4_opts_len(pkt, IPV4_OPT_HDR_ROUTER_ALERT_LEN); return igmp_v2_create(pkt, group, type); } static int igmp_send(struct net_pkt *pkt) { int ret; net_pkt_cursor_init(pkt); net_ipv4_finalize(pkt, IPPROTO_IGMP); ret = net_send_data(pkt); if (ret < 0) { net_stats_update_ipv4_igmp_drop(net_pkt_iface(pkt)); net_pkt_unref(pkt); return ret; } net_stats_update_ipv4_igmp_sent(net_pkt_iface(pkt)); return 0; } static int send_igmp_report(struct net_if *iface, struct net_ipv4_igmp_v2_query *igmp_v2_hdr) { struct net_if_ipv4 *ipv4 = iface->config.ip.ipv4; struct net_pkt *pkt = NULL; int i, count = 0; int ret = 0; if (!ipv4) { return -ENOENT; } for (i = 0; i < NET_IF_MAX_IPV4_MADDR; i++) { if (!ipv4->mcast[i].is_used || !ipv4->mcast[i].is_joined) { continue; } count++; } if (count == 0) { return -ESRCH; } for (i = 0; i < NET_IF_MAX_IPV4_MADDR; i++) { if (!ipv4->mcast[i].is_used || !ipv4->mcast[i].is_joined) { continue; } pkt = net_pkt_alloc_with_buffer(iface, IPV4_OPT_HDR_ROUTER_ALERT_LEN + sizeof(struct net_ipv4_igmp_v2_report), AF_INET, IPPROTO_IGMP, PKT_WAIT_TIME); if (!pkt) { return -ENOMEM; } /* TODO: send to arbitrary group address instead of * all_routers */ ret = igmp_v2_create_packet(pkt, &all_routers, &ipv4->mcast[i].address.in_addr, NET_IPV4_IGMP_REPORT_V2); if (ret < 0) { goto drop; } ret = igmp_send(pkt); if (ret < 0) { goto drop; } /* So that we do not free the data while it is being sent */ pkt = NULL; } drop: if (pkt) { net_pkt_unref(pkt); } return ret; } enum net_verdict net_ipv4_igmp_input(struct net_pkt *pkt, struct net_ipv4_hdr *ip_hdr) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(igmp_access, struct net_ipv4_igmp_v2_query); struct net_ipv4_igmp_v2_query *igmp_hdr; /* TODO: receive from arbitrary group address instead of * all_systems */ if (!net_ipv4_addr_cmp_raw(ip_hdr->dst, (uint8_t *)&all_systems)) { NET_DBG("DROP: Invalid dst address"); return NET_DROP; } igmp_hdr = (struct net_ipv4_igmp_v2_query *)net_pkt_get_data(pkt, &igmp_access); if (!igmp_hdr) { NET_DBG("DROP: NULL IGMP header"); return NET_DROP; } if (net_calc_chksum_igmp((uint8_t *)igmp_hdr, sizeof(*igmp_hdr)) != 0U) { NET_DBG("DROP: Invalid checksum"); goto drop; } net_pkt_acknowledge_data(pkt, &igmp_access); dbg_addr_recv("Internet Group Management Protocol", &ip_hdr->src, &ip_hdr->dst); net_stats_update_ipv4_igmp_recv(net_pkt_iface(pkt)); (void)send_igmp_report(net_pkt_iface(pkt), igmp_hdr); net_pkt_unref(pkt); return NET_OK; drop: net_stats_update_ipv4_igmp_drop(net_pkt_iface(pkt)); return NET_DROP; } static int igmp_send_generic(struct net_if *iface, const struct in_addr *addr, bool join) { struct net_pkt *pkt; int ret; pkt = net_pkt_alloc_with_buffer(iface, IPV4_OPT_HDR_ROUTER_ALERT_LEN + sizeof(struct net_ipv4_igmp_v2_report), AF_INET, IPPROTO_IGMP, PKT_WAIT_TIME); if (!pkt) { return -ENOMEM; } ret = igmp_v2_create_packet(pkt, &all_routers, addr, join ? NET_IPV4_IGMP_REPORT_V2 : NET_IPV4_IGMP_LEAVE); if (ret < 0) { goto drop; } ret = igmp_send(pkt); if (ret < 0) { goto drop; } return 0; drop: net_pkt_unref(pkt); return ret; } int net_ipv4_igmp_join(struct net_if *iface, const struct in_addr *addr) { struct net_if_mcast_addr *maddr; int ret; maddr = net_if_ipv4_maddr_lookup(addr, &iface); if (maddr && net_if_ipv4_maddr_is_joined(maddr)) { return -EALREADY; } if (!maddr) { maddr = net_if_ipv4_maddr_add(iface, addr); if (!maddr) { return -ENOMEM; } } ret = igmp_send_generic(iface, addr, true); if (ret < 0) { return ret; } net_if_ipv4_maddr_join(maddr); net_if_mcast_monitor(iface, &maddr->address, true); net_mgmt_event_notify_with_info(NET_EVENT_IPV4_MCAST_JOIN, iface, &maddr->address.in_addr, sizeof(struct in_addr)); return ret; } int net_ipv4_igmp_leave(struct net_if *iface, const struct in_addr *addr) { struct net_if_mcast_addr *maddr; int ret; maddr = net_if_ipv4_maddr_lookup(addr, &iface); if (!maddr) { return -ENOENT; } if (!net_if_ipv4_maddr_rm(iface, addr)) { return -EINVAL; } ret = igmp_send_generic(iface, addr, false); if (ret < 0) { return ret; } net_if_ipv4_maddr_leave(maddr); net_if_mcast_monitor(iface, &maddr->address, false); net_mgmt_event_notify_with_info(NET_EVENT_IPV4_MCAST_LEAVE, iface, &maddr->address.in_addr, sizeof(struct in_addr)); return ret; }