net: ipv4: Add IGMPv2 support

Add Internet Group Management Protocol v2 support, see RFC 2236
for details.

Fixes #2336

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
Jukka Rissanen 2021-04-26 20:03:35 +03:00 committed by Jukka Rissanen
commit a1c4952dfd
10 changed files with 456 additions and 1 deletions

66
include/net/igmp.h Normal file
View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/** @file
* @brief IGMP API
*/
#ifndef ZEPHYR_INCLUDE_NET_IGMP_H_
#define ZEPHYR_INCLUDE_NET_IGMP_H_
/**
* @brief IGMP (Internet Group Management Protocol)
* @defgroup igmp IGMP API
* @ingroup networking
* @{
*/
#include <zephyr/types.h>
#include <net/net_if.h>
#include <net/net_ip.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Join a given multicast group.
*
* @param iface Network interface where join message is sent
* @param addr Multicast group to join
*
* @return Return 0 if joining was done, <0 otherwise.
*/
#if defined(CONFIG_NET_IPV4_IGMP)
int net_ipv4_igmp_join(struct net_if *iface, const struct in_addr *addr);
#else
#define net_ipv4_igmp_join(iface, addr) -ENOSYS
#endif
/**
* @brief Leave a given multicast group.
*
* @param iface Network interface where leave message is sent
* @param addr Multicast group to leave
*
* @return Return 0 if leaving is done, <0 otherwise.
*/
#if defined(CONFIG_NET_IPV4_IGMP)
int net_ipv4_igmp_leave(struct net_if *iface, const struct in_addr *addr);
#else
#define net_ipv4_igmp_leave(iface, addr) -ENOSYS
#endif
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_NET_IGMP_H_ */

View file

@ -135,6 +135,8 @@ enum net_event_ipv4_cmd {
NET_EVENT_IPV4_CMD_DHCP_START,
NET_EVENT_IPV4_CMD_DHCP_BOUND,
NET_EVENT_IPV4_CMD_DHCP_STOP,
NET_EVENT_IPV4_CMD_MCAST_JOIN,
NET_EVENT_IPV4_CMD_MCAST_LEAVE,
};
#define NET_EVENT_IPV4_ADDR_ADD \
@ -158,6 +160,12 @@ enum net_event_ipv4_cmd {
#define NET_EVENT_IPV4_DHCP_STOP \
(_NET_EVENT_IPV4_BASE | NET_EVENT_IPV4_CMD_DHCP_STOP)
#define NET_EVENT_IPV4_MCAST_JOIN \
(_NET_EVENT_IPV4_BASE | NET_EVENT_IPV4_CMD_MCAST_JOIN)
#define NET_EVENT_IPV4_MCAST_LEAVE \
(_NET_EVENT_IPV4_BASE | NET_EVENT_IPV4_CMD_MCAST_LEAVE)
/* L4 network events */
#define _NET_L4_LAYER NET_MGMT_LAYER_L4

View file

@ -62,6 +62,7 @@ extern "C" {
enum net_ip_protocol {
IPPROTO_IP = 0, /**< IP protocol (pseudo-val for setsockopt() */
IPPROTO_ICMP = 1, /**< ICMP protocol */
IPPROTO_IGMP = 2, /**< IGMP protocol */
IPPROTO_IPIP = 4, /**< IPIP tunnels */
IPPROTO_TCP = 6, /**< TCP protocol */
IPPROTO_UDP = 17, /**< UDP protocol */

View file

@ -27,6 +27,7 @@ zephyr_library_sources_ifdef(CONFIG_NET_6LO 6lo.c)
zephyr_library_sources_ifdef(CONFIG_NET_DHCPV4 dhcpv4.c)
zephyr_library_sources_ifdef(CONFIG_NET_IPV4_AUTO ipv4_autoconf.c)
zephyr_library_sources_ifdef(CONFIG_NET_IPV4 icmpv4.c ipv4.c)
zephyr_library_sources_ifdef(CONFIG_NET_IPV4_IGMP igmp.c)
zephyr_library_sources_ifdef(CONFIG_NET_IPV6 icmpv6.c nbr.c
ipv6.c ipv6_nbr.c)
zephyr_library_sources_ifdef(CONFIG_NET_IPV6_MLD ipv6_mld.c)

View file

@ -46,6 +46,16 @@ config NET_IPV4_ACCEPT_ZERO_BROADCAST
If set, then accept UDP packets destined to non-standard
0.0.0.0 broadcast address as described in RFC 1122 ch. 3.3.6
config NET_IPV4_IGMP
bool "Internet Group Management Protocol (IGMP) support"
select NET_IPV4_HDR_OPTIONS
help
The value depends on your network needs. IGMP should normally be
enabled if IPv4 multicast support is needed. Currently we support
IGMPv2 and earlier versions. This requires IPv4 header support
because IP Router Alert option must be sent.
See RFC 2236 for details.
config NET_DHCPV4
bool "Enable DHCPv4 client"
select NET_MGMT

319
subsys/net/ip/igmp.c Normal file
View file

@ -0,0 +1,319 @@
/** @file
* @brief IPv4 IGMP related functions
*/
/*
* Copyright (c) 2021 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_DECLARE(net_ipv4, CONFIG_NET_IPV4_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/igmp.h>
#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, \
log_strdup(net_sprint_ipv4_addr(src)), \
log_strdup(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;
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(&ip_hdr->dst, &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_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_mgmt_event_notify_with_info(NET_EVENT_IPV4_MCAST_LEAVE, iface,
&maddr->address.in_addr,
sizeof(struct in_addr));
return ret;
}

View file

@ -326,6 +326,14 @@ enum net_verdict net_ipv4_input(struct net_pkt *pkt)
goto drop;
}
return verdict;
#if defined(CONFIG_NET_IPV4_IGMP)
case IPPROTO_IGMP:
verdict = net_ipv4_igmp_input(pkt, hdr);
if (verdict == NET_DROP) {
goto drop;
}
return verdict;
#endif
case IPPROTO_TCP:
proto_hdr.tcp = net_tcp_input(pkt, &tcp_access);
if (proto_hdr.tcp) {

View file

@ -27,6 +27,7 @@
#define NET_IPV4_OPTS_NOP 1 /* No operation */
#define NET_IPV4_OPTS_RR 7 /* Record Route */
#define NET_IPV4_OPTS_TS 68 /* Timestamp */
#define NET_IPV4_OPTS_RA 148 /* Router Alert */
/* IPv4 Options Timestamp flags */
#define NET_IPV4_TS_OPT_TS_ONLY 0 /* Timestamp only */
@ -39,6 +40,26 @@
#define NET_IPV4_MF BIT(0) /* More fragments */
#define NET_IPV4_DF BIT(1) /* Do not fragment */
#define NET_IPV4_IGMP_QUERY 0x11 /* Membership query */
#define NET_IPV4_IGMP_REPORT_V1 0x12 /* v1 Membership report */
#define NET_IPV4_IGMP_REPORT_V2 0x16 /* v2 Membership report */
#define NET_IPV4_IGMP_LEAVE 0x17 /* v2 Leave group */
#define NET_IPV4_IGMP_REPORT_V3 0x22 /* v3 Membership report */
struct net_ipv4_igmp_v2_query {
uint8_t type;
uint8_t max_rsp;
uint16_t chksum;
struct in_addr address;
} __packed;
struct net_ipv4_igmp_v2_report {
uint8_t type;
uint8_t max_rsp;
uint16_t chksum;
struct in_addr address;
} __packed;
/**
* @brief Create IPv4 packet in provided net_pkt with option to set all the
* caller settable values.

View file

@ -165,6 +165,15 @@ enum net_verdict net_context_packet_received(struct net_conn *conn,
extern uint16_t net_calc_chksum_ipv4(struct net_pkt *pkt);
#endif /* CONFIG_NET_IPV4 */
#if defined(CONFIG_NET_IPV4_IGMP)
uint16_t net_calc_chksum_igmp(uint8_t *data, size_t len);
enum net_verdict net_ipv4_igmp_input(struct net_pkt *pkt,
struct net_ipv4_hdr *ip_hdr);
#else
#define net_ipv4_igmp_input(...)
#define net_calc_chksum_igmp(data, len) 0U
#endif /* CONFIG_NET_IPV4_IGMP */
static inline uint16_t net_calc_chksum_icmpv6(struct net_pkt *pkt)
{
return net_calc_chksum(pkt, IPPROTO_ICMPV6);

View file

@ -615,6 +615,18 @@ uint16_t net_calc_chksum_ipv4(struct net_pkt *pkt)
}
#endif /* CONFIG_NET_IPV4 */
#if defined(CONFIG_NET_IPV4_IGMP)
uint16_t net_calc_chksum_igmp(uint8_t *data, size_t len)
{
uint16_t sum;
sum = calc_chksum(0, data, len);
sum = (sum == 0U) ? 0xffff : htons(sum);
return ~sum;
}
#endif /* CONFIG_NET_IPV4_IGMP */
#if defined(CONFIG_NET_IPV6) || defined(CONFIG_NET_IPV4)
static bool convert_port(const char *buf, uint16_t *port)
{