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:
parent
d28e64c602
commit
a1c4952dfd
10 changed files with 456 additions and 1 deletions
66
include/net/igmp.h
Normal file
66
include/net/igmp.h
Normal 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_ */
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
319
subsys/net/ip/igmp.c
Normal 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;
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue