Merge branch 'feature/ipv6_socket_options' into 'master'

IPV6 multicast socket options

See merge request !964
This commit is contained in:
Angus Gratton 2017-10-09 09:03:30 +08:00
commit 110c71d3f1
17 changed files with 1050 additions and 76 deletions

View file

@ -35,11 +35,25 @@ config LWIP_THREAD_LOCAL_STORAGE_INDEX
config LWIP_SO_REUSE
bool "Enable SO_REUSEADDR option"
default n
default y
help
Enabling this option allows binding to a port which remains in
TIME_WAIT.
config LWIP_SO_REUSE_RXTOALL
bool "SO_REUSEADDR copies broadcast/multicast to all matches"
depends on LWIP_SO_REUSE
default y
help
Enabling this option means that any incoming broadcast or multicast
packet will be copied to all of the local sockets that it matches
(may be more than one if SO_REUSEADDR is set on the socket.)
This increases memory overhead as the packets need to be copied,
however they are only copied per matching socket. You can safely
disable it if you don't plan to receive broadcast or multicast
traffic on more than one socket at a time.
config LWIP_SO_RCVBUF
bool "Enable SO_RCVBUF option"
default n
@ -88,7 +102,6 @@ config LWIP_ETHARP_TRUST_IP_MAC
the peer is not already in the ARP table, adding a little latency.
The peer *is* in the ARP table if it requested our address before.
Also notice that this slows down input processing of every IP packet!
config TCPIP_RECVMBOX_SIZE
int "TCPIP receive mail box size"
@ -98,6 +111,72 @@ config TCPIP_RECVMBOX_SIZE
Set TCPIP receive mail box size. Generally bigger value means higher throughput
but more memory. The value should be bigger than UDP/TCP mail box size.
config LWIP_DHCP_DOES_ARP_CHECK
bool "DHCP: Perform ARP check on any offered address"
default y
help
Enabling this option performs a check (via ARP request) if the offered IP address
is not already in use by another host on the network.
menuconfig LWIP_AUTOIP
bool "Enable IPV4 Link-Local Addressing (AUTOIP)"
default n
help
Enabling this option allows the device to self-assign an address
in the 169.256/16 range if none is assigned statically or via DHCP.
See RFC 3927.
config LWIP_AUTOIP_TRIES
int "DHCP Probes before self-assigning IPv4 LL address"
range 1 100
default 2
depends on LWIP_AUTOIP
help
DHCP client will send this many probes before self-assigning a
link local address.
From LWIP help: "This can be set as low as 1 to get an AutoIP
address very quickly, but you should be prepared to handle a
changing IP address when DHCP overrides AutoIP." (In the case of
ESP-IDF, this means multiple SYSTEM_EVENT_STA_GOT_IP events.)
config LWIP_AUTOIP_MAX_CONFLICTS
int "Max IP conflicts before rate limiting"
range 1 100
default 9
depends on LWIP_AUTOIP
help
If the AUTOIP functionality detects this many IP conflicts while
self-assigning an address, it will go into a rate limited mode.
config LWIP_AUTOIP_RATE_LIMIT_INTERVAL
int "Rate limited interval (seconds)"
range 5 120
default 20
depends on LWIP_AUTOIP
help
If rate limiting self-assignment requests, wait this long between
each request.
menuconfig LWIP_NETIF_LOOPBACK
bool "Support per-interface loopback"
default y
help
Enabling this option means that if a packet is sent with a destination
address equal to the interface's own IP address, it will "loop back" and
be received by this interface.
config LWIP_LOOPBACK_MAX_PBUFS
int "Max queued loopback packets per interface"
range 0 16
default 8
depends on LWIP_NETIF_LOOPBACK
help
Configure the maximum number of packets which can be queued for
loopback on a given interface. Reducing this number may cause packets
to be dropped, but will avoid filling memory with queued packet data.
menu "TCP"
config TCP_MAXRTX
@ -234,13 +313,6 @@ config UDP_RECVMBOX_SIZE
endmenu # UDP
config LWIP_DHCP_DOES_ARP_CHECK
bool "Enable an ARP check on the offered address"
default y
help
Enabling this option allows check if the offered IP address is not already
in use by another host on the network.
config TCPIP_TASK_STACK_SIZE
int "TCP/IP Task Stack Size"
default 2560

View file

@ -180,6 +180,16 @@ recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p,
return;
}
#if LWIP_IPV6
/* This should be eventually moved to a flag on the UDP PCB, and this drop can happen
more correctly in udp_input(). This will also allow icmp_dest_unreach() to be called. */
if (conn->flags & NETCONN_FLAG_IPV6_V6ONLY && !ip_current_is_v6()) {
LWIP_DEBUGF(API_MSG_DEBUG, ("recv_udp: Dropping IPv4 UDP packet (IPv6-only socket)"));
pbuf_free(p);
return;
}
#endif
buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
if (buf == NULL) {
pbuf_free(p);
@ -1388,6 +1398,12 @@ lwip_netconn_do_send(void *m)
if (ERR_IS_FATAL(msg->conn->last_err)) {
msg->err = msg->conn->last_err;
#if LWIP_IPV4 && LWIP_IPV6
} else if ((msg->conn->flags & NETCONN_FLAG_IPV6_V6ONLY) &&
IP_IS_V4MAPPEDV6(&msg->msg.b->addr)) {
LWIP_DEBUGF(API_MSG_DEBUG, ("lwip_netconn_do_send: Dropping IPv4 packet on IPv6-only socket"));
msg->err = ERR_VAL;
#endif /* LWIP_IPV4 && LWIP_IPV6 */
} else {
msg->err = ERR_CONN;
if (msg->conn->pcb.tcp != NULL) {

View file

@ -261,7 +261,7 @@ lwip_freeaddrinfo(struct addrinfo *ai)
* @param res pointer to a pointer where to store the result (set to NULL on failure)
* @return 0 on success, non-zero on failure
*
* @todo: implement AI_V4MAPPED, AI_ADDRCONFIG
* @todo: implement AI_ADDRCONFIG
*/
int
lwip_getaddrinfo(const char *nodename, const char *servname,
@ -330,6 +330,9 @@ lwip_getaddrinfo(const char *nodename, const char *servname,
type = NETCONN_DNS_IPV4;
} else if (ai_family == AF_INET6) {
type = NETCONN_DNS_IPV6;
if (hints->ai_flags & AI_V4MAPPED) {
type = NETCONN_DNS_IPV6_IPV4;
}
}
#endif /* LWIP_IPV4 && LWIP_IPV6 */
err = netconn_gethostbyname_addrtype(nodename, &addr, type);
@ -346,6 +349,14 @@ lwip_getaddrinfo(const char *nodename, const char *servname,
}
}
#if LWIP_IPV4 && LWIP_IPV6
if (ai_family == AF_INET6 && (hints->ai_flags & AI_V4MAPPED)
&& IP_GET_TYPE(&addr) == IPADDR_TYPE_V4) {
/* Convert native V4 address to a V4-mapped IPV6 address */
ip_addr_make_ip4_mapped_ip6(&addr, &addr);
}
#endif
total_size = sizeof(struct addrinfo) + sizeof(struct sockaddr_storage);
if (nodename != NULL) {
namelen = strlen(nodename);

View file

@ -46,6 +46,7 @@
#include "lwip/api.h"
#include "lwip/sys.h"
#include "lwip/igmp.h"
#include "lwip/mld6.h"
#include "lwip/inet.h"
#include "lwip/tcp.h"
#include "lwip/raw.h"
@ -364,21 +365,21 @@ union sockaddr_aligned {
#define LWIP_SOCKET_MAX_MEMBERSHIPS NUM_SOCKETS
#endif
/* This is to keep track of IP_ADD_MEMBERSHIP calls to drop the membership when
a socket is closed */
/* This is to keep track of IP_ADD_MEMBERSHIP/IPV6_ADD_MEMBERSHIP calls to drop
the membership when a socket is closed */
struct lwip_socket_multicast_pair {
/** the socket (+1 to not require initialization) */
int sa;
/** the interface address */
ip4_addr_t if_addr;
ip_addr_t if_addr;
/** the group address */
ip4_addr_t multi_addr;
ip_addr_t multi_addr;
};
struct lwip_socket_multicast_pair socket_ipv4_multicast_memberships[LWIP_SOCKET_MAX_MEMBERSHIPS];
struct lwip_socket_multicast_pair socket_multicast_memberships[LWIP_SOCKET_MAX_MEMBERSHIPS];
static int lwip_socket_register_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr);
static void lwip_socket_unregister_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr);
static int lwip_socket_register_membership(int s, const ip_addr_t *if_addr, const ip_addr_t *multi_addr);
static void lwip_socket_unregister_membership(int s, const ip_addr_t *if_addr, const ip_addr_t *multi_addr);
static void lwip_socket_drop_registered_memberships(int s);
#endif /* LWIP_IGMP */
@ -852,7 +853,7 @@ lwip_close(int s)
LWIP_ASSERT("lwip_close: sock->lastdata == NULL", sock->lastdata == NULL);
}
#if LWIP_IGMP
#if (LWIP_IGMP) || (LWIP_IPV6_MLD && LWIP_MULTICAST_TX_OPTIONS)
/* drop all possibly joined IGMP memberships */
lwip_socket_drop_registered_memberships(s);
#endif /* LWIP_IGMP */
@ -2372,10 +2373,6 @@ lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *opt
switch (optname) {
case IPV6_V6ONLY:
LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, *optlen, int);
/* @todo: this does not work for datagram sockets, yet */
if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_TCP) {
return ENOPROTOOPT;
}
*(int*)optval = (netconn_get_ipv6only(sock->conn) ? 1 : 0);
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, IPV6_V6ONLY) = %d\n",
s, *(int *)optval));
@ -2387,6 +2384,38 @@ lwip_getsockopt_impl(int s, int level, int optname, void *optval, socklen_t *opt
break;
} /* switch (optname) */
break;
#if LWIP_IPV6_MLD && LWIP_MULTICAST_TX_OPTIONS /* Multicast options, similar to LWIP_IGMP options for IPV4 */
case IPV6_MULTICAST_IF: /* NB: like IP_MULTICAST_IF, this returns an IP not an index */
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, struct in6_addr);
if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
return ENOPROTOOPT;
}
inet6_addr_from_ip6addr((struct in6_addr*)optval,
udp_get_multicast_netif_ip6addr(sock->conn->pcb.udp));
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, IPV6_MULTICAST_IF) = 0x%"X32_F"\n",
s, *(u32_t *)optval));
break;
case IPV6_MULTICAST_HOPS:
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, u8_t);
if (NETCONNTYPE_GROUP(netconn_type(sock->conn)) != NETCONN_UDP) {
return ENOPROTOOPT;
}
*(u8_t*)optval = sock->conn->pcb.udp->mcast_ttl;
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, IP_MULTICAST_LOOP) = %d\n",
s, *(int *)optval));
break;
case IPV6_MULTICAST_LOOP:
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB(sock, *optlen, u8_t);
if ((sock->conn->pcb.udp->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) {
*(u8_t*)optval = 1;
} else {
*(u8_t*)optval = 0;
}
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_getsockopt(%d, IPPROTO_IPV6, IP_MULTICAST_LOOP) = %d\n",
s, *(int *)optval));
break;
#endif /* LWIP_IPV6_MLD && LWIP_MULTICAST_TX_OPTIONS */
#endif /* LWIP_IPV6 */
#if LWIP_UDP && LWIP_UDPLITE
@ -2685,21 +2714,21 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_
/* @todo: assign membership to this socket so that it is dropped when state the socket */
err_t igmp_err;
const struct ip_mreq *imr = (const struct ip_mreq *)optval;
ip4_addr_t if_addr;
ip4_addr_t multi_addr;
ip_addr_t if_addr;
ip_addr_t multi_addr;
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, struct ip_mreq, NETCONN_UDP);
inet_addr_to_ipaddr(&if_addr, &imr->imr_interface);
inet_addr_to_ipaddr(&multi_addr, &imr->imr_multiaddr);
inet_addr_to_ipaddr(ip_2_ip4(&if_addr), &imr->imr_interface);
inet_addr_to_ipaddr(ip_2_ip4(&multi_addr), &imr->imr_multiaddr);
if (optname == IP_ADD_MEMBERSHIP) {
if (!lwip_socket_register_membership(s, &if_addr, &multi_addr)) {
/* cannot track membership (out of memory) */
err = ENOMEM;
igmp_err = ERR_OK;
} else {
igmp_err = igmp_joingroup(&if_addr, &multi_addr);
igmp_err = igmp_joingroup(ip_2_ip4(&if_addr), ip_2_ip4(&multi_addr));
}
} else {
igmp_err = igmp_leavegroup(&if_addr, &multi_addr);
igmp_err = igmp_leavegroup(ip_2_ip4(&if_addr), ip_2_ip4(&multi_addr));
lwip_socket_unregister_membership(s, &if_addr, &multi_addr);
}
if (igmp_err != ERR_OK) {
@ -2778,12 +2807,7 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_
case IPPROTO_IPV6:
switch (optname) {
case IPV6_V6ONLY:
/* @todo: this does not work for datagram sockets, yet */
#if CONFIG_MDNS
//LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, int, NETCONN_TCP);
#else
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, int, NETCONN_TCP);
#endif
LWIP_SOCKOPT_CHECK_OPTLEN_CONN(sock, optlen, int);
if (*(const int*)optval) {
netconn_set_ipv6only(sock->conn, 1);
} else {
@ -2792,6 +2816,55 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IPV6, IPV6_V6ONLY, ..) -> %d\n",
s, (netconn_get_ipv6only(sock->conn) ? 1 : 0)));
break;
#if LWIP_IPV6_MLD && LWIP_MULTICAST_TX_OPTIONS /* Multicast options, similar to LWIP_IGMP options for IPV4 */
case IPV6_MULTICAST_IF: /* NB: like IP_MULTICAST_IF, this takes an IP not an index */
{
ip6_addr_t if_addr;
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, struct in6_addr, NETCONN_UDP);
inet6_addr_to_ip6addr(&if_addr, (const struct in6_addr*)optval);
udp_set_multicast_netif_ip6addr(sock->conn->pcb.udp, &if_addr);
}
break;
case IPV6_MULTICAST_HOPS:
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, u8_t, NETCONN_UDP);
sock->conn->pcb.udp->mcast_ttl = (u8_t)(*(const u8_t*)optval);
break;
case IPV6_MULTICAST_LOOP:
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, u8_t, NETCONN_UDP);
if (*(const u8_t*)optval) {
udp_setflags(sock->conn->pcb.udp, udp_flags(sock->conn->pcb.udp) | UDP_FLAGS_MULTICAST_LOOP);
} else {
udp_setflags(sock->conn->pcb.udp, udp_flags(sock->conn->pcb.udp) & ~UDP_FLAGS_MULTICAST_LOOP);
}
break;
case IPV6_ADD_MEMBERSHIP:
case IPV6_DROP_MEMBERSHIP:
{
err_t mld_err;
const struct ip6_mreq *imr = (const struct ip6_mreq *)optval;
ip_addr_t if_addr;
ip_addr_t multi_addr;
LWIP_SOCKOPT_CHECK_OPTLEN_CONN_PCB_TYPE(sock, optlen, struct ip6_mreq, NETCONN_UDP);
inet6_addr_to_ip6addr(ip_2_ip6(&if_addr), &imr->ipv6mr_interface);
inet6_addr_to_ip6addr(ip_2_ip6(&multi_addr), &imr->ipv6mr_multiaddr);
if (optname == IPV6_ADD_MEMBERSHIP) {
if (!lwip_socket_register_membership(s, &if_addr, &multi_addr)) {
/* cannot track membership (out of memory) */
err = ENOMEM;
mld_err = ERR_OK;
} else {
mld_err = mld6_joingroup(ip_2_ip6(&if_addr), ip_2_ip6(&multi_addr));
}
} else {
mld_err = mld6_leavegroup(ip_2_ip6(&if_addr), ip_2_ip6(&multi_addr));
lwip_socket_unregister_membership(s, &if_addr, &multi_addr);
}
if (mld_err != ERR_OK) {
err = EADDRNOTAVAIL;
}
}
break;
#endif /* LWIP_IPV6_MLD && LWIP_MULTICAST_TX_OPTIONS */
default:
LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_setsockopt(%d, IPPROTO_IPV6, UNIMPL: optname=0x%x, ..)\n",
s, optname));
@ -3005,15 +3078,15 @@ lwip_fcntl(int s, int cmd, int val)
return ret;
}
#if LWIP_IGMP
/** Register a new IGMP membership. On socket close, the membership is dropped automatically.
#if LWIP_IGMP || (LWIP_IPV6_MLD && LWIP_MULTICAST_TX_OPTIONS)
/** Register a new multicast group membership. On socket close, the membership is dropped automatically.
*
* ATTENTION: this function is called from tcpip_thread (or under CORE_LOCK).
*
* @return 1 on success, 0 on failure
*/
static int
lwip_socket_register_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr)
lwip_socket_register_membership(int s, const ip_addr_t *if_addr, const ip_addr_t *multi_addr)
{
/* s+1 is stored in the array to prevent having to initialize the array
(default initialization is to 0) */
@ -3021,10 +3094,10 @@ lwip_socket_register_membership(int s, const ip4_addr_t *if_addr, const ip4_addr
int i;
for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) {
if (socket_ipv4_multicast_memberships[i].sa == 0) {
socket_ipv4_multicast_memberships[i].sa = sa;
ip4_addr_copy(socket_ipv4_multicast_memberships[i].if_addr, *if_addr);
ip4_addr_copy(socket_ipv4_multicast_memberships[i].multi_addr, *multi_addr);
if (socket_multicast_memberships[i].sa == 0) {
socket_multicast_memberships[i].sa = sa;
ip_addr_copy(socket_multicast_memberships[i].if_addr, *if_addr);
ip_addr_copy(socket_multicast_memberships[i].multi_addr, *multi_addr);
return 1;
}
}
@ -3037,7 +3110,7 @@ lwip_socket_register_membership(int s, const ip4_addr_t *if_addr, const ip4_addr
* ATTENTION: this function is called from tcpip_thread (or under CORE_LOCK).
*/
static void
lwip_socket_unregister_membership(int s, const ip4_addr_t *if_addr, const ip4_addr_t *multi_addr)
lwip_socket_unregister_membership(int s, const ip_addr_t *if_addr, const ip_addr_t *multi_addr)
{
/* s+1 is stored in the array to prevent having to initialize the array
(default initialization is to 0) */
@ -3045,12 +3118,12 @@ lwip_socket_unregister_membership(int s, const ip4_addr_t *if_addr, const ip4_ad
int i;
for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) {
if ((socket_ipv4_multicast_memberships[i].sa == sa) &&
ip4_addr_cmp(&socket_ipv4_multicast_memberships[i].if_addr, if_addr) &&
ip4_addr_cmp(&socket_ipv4_multicast_memberships[i].multi_addr, multi_addr)) {
socket_ipv4_multicast_memberships[i].sa = 0;
ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].if_addr);
ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].multi_addr);
if ((socket_multicast_memberships[i].sa == sa) &&
ip_addr_cmp(&socket_multicast_memberships[i].if_addr, if_addr) &&
ip_addr_cmp(&socket_multicast_memberships[i].multi_addr, multi_addr)) {
socket_multicast_memberships[i].sa = 0;
ip_addr_set_zero(&socket_multicast_memberships[i].if_addr);
ip_addr_set_zero(&socket_multicast_memberships[i].multi_addr);
return;
}
}
@ -3070,19 +3143,18 @@ static void lwip_socket_drop_registered_memberships(int s)
LWIP_ASSERT("socket has no netconn", sockets[s].conn != NULL);
for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) {
if (socket_ipv4_multicast_memberships[i].sa == sa) {
ip_addr_t multi_addr, if_addr;
ip_addr_copy_from_ip4(multi_addr, socket_ipv4_multicast_memberships[i].multi_addr);
ip_addr_copy_from_ip4(if_addr, socket_ipv4_multicast_memberships[i].if_addr);
socket_ipv4_multicast_memberships[i].sa = 0;
ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].if_addr);
ip4_addr_set_zero(&socket_ipv4_multicast_memberships[i].multi_addr);
netconn_join_leave_group(sockets[s].conn, &multi_addr, &if_addr, NETCONN_LEAVE);
if (socket_multicast_memberships[i].sa == sa) {
socket_multicast_memberships[i].sa = 0;
netconn_join_leave_group(sockets[s].conn,
&socket_multicast_memberships[i].multi_addr,
&socket_multicast_memberships[i].if_addr,
NETCONN_LEAVE);
ip_addr_set_zero(&socket_multicast_memberships[i].if_addr);
ip_addr_set_zero(&socket_multicast_memberships[i].multi_addr);
}
}
}
#endif /* LWIP_IGMP */
#endif /* LWIP_IGMP || (LWIP_IPV6_MLD && LWIP_MULTICAST_TX_OPTIONS) */
#if ESP_THREAD_SAFE

View file

@ -882,6 +882,11 @@ ip6_output_if_src(struct pbuf *p, const ip6_addr_t *src, const ip6_addr_t *dest,
}
}
}
#if LWIP_IPV6_MLD
if ((p->flags & PBUF_FLAG_MCASTLOOP) != 0) {
netif_loop_output(netif, p);
}
#endif /* LWIP_IPV6_MLD */
#endif /* ENABLE_LOOPBACK */
#if LWIP_IPV6_FRAG
/* don't fragment if interface has mtu set to 0 [loopif] */

View file

@ -542,6 +542,19 @@ udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip,
LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n"));
#if LWIP_IPV4 && LWIP_IPV6
/* Unwrap IPV4-mapped IPV6 addresses and convert to native IPV4 here */
if (IP_IS_V4MAPPEDV6(dst_ip)) {
ip_addr_t dest_ipv4;
ip_addr_ip4_from_mapped_ip6(&dest_ipv4, dst_ip);
#if LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UP
return udp_sendto_chksum(pcb, p, &dest_ipv4, dst_port, have_chksum, chksum);
#else
return udp_sendto(pcb, p, &dest_ipv4, dst_port);
#endif /* LWIP_CHECKSUM_ON_COPY && CHECKSUM_GEN_UP */
}
#endif /* LWIP_IPV4 && LWIP_IPV6 */
#if LWIP_IPV6 || (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS)
if (ip_addr_ismulticast(dst_ip_route)) {
#if LWIP_IPV6

View file

@ -68,13 +68,8 @@ extern "C" {
#define ANNOUNCE_NUM 2 /* (number of announcement packets) */
#define ANNOUNCE_INTERVAL 2 /* seconds (time between announcement packets) */
#define ANNOUNCE_WAIT 2 /* seconds (delay before announcing) */
#if CONFIG_MDNS
#define MAX_CONFLICTS 9 /* (max conflicts before rate limiting) */
#define RATE_LIMIT_INTERVAL 20 /* seconds (delay between successive attempts) */
#else
#define MAX_CONFLICTS 10 /* (max conflicts before rate limiting) */
#define RATE_LIMIT_INTERVAL 60 /* seconds (delay between successive attempts) */
#endif
#define MAX_CONFLICTS LWIP_AUTOIP_MAX_CONFLICTS /* (max conflicts before rate limiting) */
#define RATE_LIMIT_INTERVAL LWIP_AUTOIP_RATE_LIMIT_INTERVAL /* seconds (delay between successive attempts) */
#define DEFEND_INTERVAL 10 /* seconds (min. wait between defensive ARPs) */
/* AutoIP client states */

View file

@ -69,6 +69,10 @@ extern const ip_addr_t ip_addr_any_type;
#define IP_IS_V6_VAL(ipaddr) (IP_GET_TYPE(&ipaddr) == IPADDR_TYPE_V6)
#define IP_IS_V6(ipaddr) (((ipaddr) != NULL) && IP_IS_V6_VAL(*(ipaddr)))
#define IP_V6_EQ_PART(ipaddr, WORD, VAL) (ip_2_ip6(ipaddr)->addr[WORD] == htonl(VAL))
#define IP_IS_V4MAPPEDV6(ipaddr) (IP_IS_V6(ipaddr) && IP_V6_EQ_PART(ipaddr, 0, 0) && IP_V6_EQ_PART(ipaddr, 1, 0) && IP_V6_EQ_PART(ipaddr, 2, 0x0000FFFF))
#define IP_SET_TYPE_VAL(ipaddr, iptype) do { (ipaddr).type = (iptype); }while(0)
#define IP_SET_TYPE(ipaddr, iptype) do { if((ipaddr) != NULL) { IP_SET_TYPE_VAL(*(ipaddr), iptype); }}while(0)
#define IP_GET_TYPE(ipaddr) ((ipaddr)->type)
@ -156,6 +160,27 @@ extern const ip_addr_t ip_addr_any_type;
((IP_IS_V6(addr)) ? ip6addr_ntoa_r(ip_2_ip6(addr), buf, buflen) : ip4addr_ntoa_r(ip_2_ip4(addr), buf, buflen)))
int ipaddr_aton(const char *cp, ip_addr_t *addr);
/* Map an IPv4 ip_addr into an IPV6 ip_addr, using format
defined in RFC4291 2.5.5.2.
Safe to call when dest==src.
*/
#define ip_addr_make_ip4_mapped_ip6(dest, src) do { \
u32_t tmp = ip_2_ip4(src)->addr; \
IP_ADDR6((dest), 0x0, 0x0, htonl(0x0000FFFF), tmp); \
} while(0)
/* Convert an IPv4 mapped V6 address to an IPV4 address.
Check IP_IS_V4MAPPEDV6(src) before using this.
Safe to call when dest == src.
*/
#define ip_addr_ip4_from_mapped_ip6(dest, src) do { \
ip_2_ip4(dest)->addr = ip_2_ip6(src)->addr[3]; \
IP_SET_TYPE(dest, IPADDR_TYPE_V4); \
} while(0)
#else /* LWIP_IPV4 && LWIP_IPV6 */
#define IP_ADDR_PCB_VERSION_MATCH(addr, pcb) 1

View file

@ -823,6 +823,22 @@
#define LWIP_DHCP_AUTOIP_COOP_TRIES 9
#endif
/**
* LWIP_AUTOIP_MAX_CONFLICTS:
* Maximum number of AutoIP IP conflicts before rate limiting is enabled.
*/
#ifndef LWIP_AUTOIP_MAX_CONFLICTS
#define LWIP_AUTOIP_MAX_CONFLICTS 10
#endif
/**
* LWIP_AUTOIP_RATE_LIMIT_INTERVAL:
* Rate limited request interval, in seconds.
*/
#ifndef LWIP_AUTOIP_RATE_LIMIT_INTERVAL
#define LWIP_AUTOIP_RATE_LIMIT_INTERVAL 60
#endif
/*
----------------------------------
----- SNMP MIB2 support -----

View file

@ -262,6 +262,27 @@ struct linger {
*/
#define IPV6_CHECKSUM 7 /* RFC3542: calculate and insert the ICMPv6 checksum for raw sockets. */
#define IPV6_V6ONLY 27 /* RFC3493: boolean control to restrict AF_INET6 sockets to IPv6 communications only. */
#if LWIP_IPV6_MLD
/* Socket options for IPV6 multicast, uses the MLD interface to manage group memberships. RFC2133. */
#define IPV6_MULTICAST_IF 0x300
#define IPV6_MULTICAST_HOPS 0x301
#define IPV6_MULTICAST_LOOP 0x302
#define IPV6_ADD_MEMBERSHIP 0x303
#define IPV6_DROP_MEMBERSHIP 0x304
/* Structure used for IPV6_ADD/DROP_MEMBERSHIP */
typedef struct ip6_mreq {
struct in6_addr ipv6mr_multiaddr; /* IPv6 multicast addr */
struct in6_addr ipv6mr_interface; /* local IP address of interface */
} ip6_mreq;
/* Commonly used synonyms for these options */
#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP
#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP
#endif /* LWIP_IPV6_MLD */
#endif /* LWIP_IPV6 */
#if LWIP_UDP && LWIP_UDPLITE

View file

@ -173,6 +173,12 @@ void udp_init (void);
#define udp_get_multicast_netif_addr(pcb) ip_2_ip4(&(pcb)->multicast_ip)
#define udp_set_multicast_ttl(pcb, value) do { (pcb)->mcast_ttl = value; } while(0)
#define udp_get_multicast_ttl(pcb) ((pcb)->mcast_ttl)
#if LWIP_IPV6_MLD
#define udp_set_multicast_netif_ip6addr(pcb, ip6addr) ip_addr_copy_from_ip6((pcb)->multicast_ip, *(ip6addr))
#define udp_get_multicast_netif_ip6addr(pcb) ip_2_ip6(&(pcb)->multicast_ip)
#endif
#endif /* LWIP_MULTICAST_TX_OPTIONS */
#if UDP_DEBUG

View file

@ -221,10 +221,7 @@
---------- AUTOIP options ----------
------------------------------------
*/
#if CONFIG_MDNS
/**
* LWIP_AUTOIP==1: Enable AUTOIP module.
*/
#ifdef CONFIG_LWIP_AUTOIP
#define LWIP_AUTOIP 1
/**
@ -240,8 +237,13 @@
* be prepared to handle a changing IP address when DHCP overrides
* AutoIP.
*/
#define LWIP_DHCP_AUTOIP_COOP_TRIES 2
#endif
#define LWIP_DHCP_AUTOIP_COOP_TRIES CONFIG_LWIP_AUTOIP_TRIES
#define LWIP_AUTOIP_MAX_CONFLICTS CONFIG_LWIP_AUTOIP_MAX_CONFLICTS
#define LWIP_AUTOIP_RATE_LIMIT_INTERVAL CONFIG_LWIP_AUTOIP_RATE_LIMIT_INTERVAL
#endif /* CONFIG_LWIP_AUTOIP */
/*
----------------------------------
@ -367,7 +369,7 @@
---------- LOOPIF options ----------
------------------------------------
*/
#if CONFIG_MDNS
#ifdef CONFIG_LWIP_NETIF_LOOPBACK
/**
* LWIP_NETIF_LOOPBACK==1: Support sending packets with a destination IP
* address equal to the netif IP address, looping them back up the stack.
@ -378,7 +380,7 @@
* LWIP_LOOPBACK_MAX_PBUFS: Maximum number of pbufs on queue for loopback
* sending for each netif (0 = disabled)
*/
#define LWIP_LOOPBACK_MAX_PBUFS 8
#define LWIP_LOOPBACK_MAX_PBUFS CONFIG_LWIP_LOOPBACK_MAX_PBUFS
#endif
/*
@ -506,14 +508,12 @@
*/
#define SO_REUSE CONFIG_LWIP_SO_REUSE
#if CONFIG_MDNS
/**
* SO_REUSE_RXTOALL==1: Pass a copy of incoming broadcast/multicast packets
* to all local matches if SO_REUSEADDR is turned on.
* WARNING: Adds a memcpy for every packet if passing to more than one pcb!
*/
#define SO_REUSE_RXTOALL 1
#endif
#define SO_REUSE_RXTOALL CONFIG_LWIP_SO_REUSE_RXTOALL
/*
----------------------------------------

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := udp-multicast
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,66 @@
# UDP Multicast Example
This example shows how to use the IPV4 & IPV6 UDP multicast features via the BSD-style sockets interface.
## Behaviour
The behaviour of the example is:
* Listens to specified multicast addresses (one IPV4 and/or one IPV6).
* Print any UDP packets received as ASCII text.
* If no packets are received it will periodicially (after 2.5 seconds) send its own plaintext packet(s) to the multicast address(es).
## Configuration
The "Example Configuration" menu "make menuconfig" allows you to configure the details of the example:
* WiFi SSD & Password
* IP Mode: IPV4 & IPV6 dual, IPV4 only, or IPv6 only.
* Multicast addresses for IPV4 and/or IPV6.
* Enable multicast socket loopback (ie should the socket receive its own multicast transmissions.)
* Change the interface to add the multicast group on (default interface, or WiFi STA interface.) Both methods are valid.
## Implementation Details
In IPV4 & IPV6 dual mode, an IPV6 socket is created and the "dual mode" options described in [RFC4038](https://tools.ietf.org/html/rfc4038) are used to bind it to the default address for both IPV4 & IPV6 and join both the configured IPV4 & IPV6 multicast groups. Otherwise, a single socket of the appropriate type is created.
The socket is always bound to the default address, so it will also receive unicast packets. If you only want to receive multicast packets for a particular address, `bind()` to that multicast address instead.
## Host Tools
There are many host-side tools which can be used to interact with the UDP multicast example. One command line tool is [socat](http://www.dest-unreach.org/socat/) which can send and receive many kinds of packets.
### Send IPV4 multicast via socat
```
echo "Hi there, IPv4!" | socat STDIO UDP4-DATAGRAM:232.10.11.12:3333,ip-multicast-if=(host_ip_addr)
```
Replace `232.10.11.12:3333` with the IPV4 multicast address and port, and `(host_ip_addr)` with the host's IP address (used to find the interface to send the multicast packet on.)
### Receive IPV4 multicast via socat
```
socat STDIO UDP4-RECVFROM:3333,ip-add-membership=232.10.11.12:(host_ip_addr)
```
Replace `:3333` and `232.10.11.12` with the port and IPV4 multicast address, respectively. Replace `(host_ip_addr)` with the host IP address, used to find the interface to listen on.
(The `,ip-add-membership=...` clause may not be necessary, depending on your network configuration.)
### Send IPV6 multicast via socat
```
echo "Hi there, IPV6!" | socat STDIO UDP6-DATAGRAM:[ff02::fc]:3333
```
Replace `[ff02::fc]:3333` with the IPV6 multicast address and port, respectively.
### Receive IPV6 multicast via socat
At time of writing this is not possible without patching socat. Use a different tool or programming language to receive IPV6 multicast packets.
## About Examples
See the README.md file in the upper level 'examples' directory for general information about examples.

View file

@ -0,0 +1,91 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "myssid"
help
WiFi password (WPA or WPA2) for the example to use.
Can be left blank if the network has no security set.
choice EXAMPLE_IP_MODE
prompt "Multicast IP type"
help
Example can multicast IPV4, IPV6, or both.
config EXAMPLE_IPV4_V6
bool "IPV4 & IPV6"
select EXAMPLE_IPV4
select EXAMPLE_IPV6
config EXAMPLE_IPV4_ONLY
bool "IPV4"
select EXAMPLE_IPV4
config EXAMPLE_IPV6_ONLY
bool "IPV6"
select EXAMPLE_IPV6
endchoice
config EXAMPLE_IPV4
bool
config EXAMPLE_IPV6
bool
config EXAMPLE_MULTICAST_IPV4_ADDR
string "Multicast IPV4 Address (send & receive)"
default "232.10.11.12"
depends on EXAMPLE_IPV4
help
IPV4 multicast address. Example will both send to and listen to this address.
config EXAMPLE_MULTICAST_IPV6_ADDR
string "Multicast IPV6 Address (send & receive)"
default "FF02::FC"
depends on EXAMPLE_IPV6
help
IPV6 multicast address. Example will both send to and listen to this address.
The default FF02::FC address is a link-local multicast address. Consult IPV6 specifications or documentation for information about meaning of different IPV6 multicast ranges.
config EXAMPLE_PORT
int "Multicast port (send & receive)"
range 0 65535
default 333
help
Multicast port the example will both send & receive UDP packets on.
config EXAMPLE_LOOPBACK
bool "Multicast loopback"
help
Enables IP_MULTICAST_LOOP/IPV6_MULTICAST_LOOP options, meaning
that packets transmitted from the device are also received by the
device itself.
config EXAMPLE_MULTICAST_TTL
int "Multicast packet TTL"
range 1 255
help
Sets TTL field of multicast packets. Separate from uni- & broadcast TTL.
choice EXAMPLE_MULTICAST_IF
prompt "Multicast Interface"
help
Multicast socket can bind to default interface, or all interfaces.
config EXAMPLE_MULTICAST_LISTEN_DEFAULT_IF
bool "Default interface"
config EXAMPLE_MULTICAST_LISTEN_STA_IF
bool "WiFi STA interface"
endchoice
endmenu

View file

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View file

@ -0,0 +1,552 @@
/* UDP MultiCast Send/Receive Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
/* The examples use simple WiFi configuration that you can set via
'make menuconfig'.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
#define UDP_PORT CONFIG_EXAMPLE_PORT
#define MULTICAST_LOOPBACK CONFIG_EXAMPLE_LOOPBACK
#define MULTICAST_TTL CONFIG_EXAMPLE_MULTICAST_TTL
#define MULTICAST_IPV4_ADDR CONFIG_EXAMPLE_MULTICAST_IPV4_ADDR
#define MULTICAST_IPV6_ADDR CONFIG_EXAMPLE_MULTICAST_IPV6_ADDR
#define LISTEN_DEFAULT_IF CONFIG_EXAMPLE_LISTEN_DEFAULT_IF
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
/* The event group allows multiple bits for each event,
we use two - one for IPv4 "got ip", and
one for IPv6 "got ip". */
const int IPV4_GOTIP_BIT = BIT0;
const int IPV6_GOTIP_BIT = BIT1;
static const char *TAG = "multicast";
#ifdef CONFIG_EXAMPLE_IPV4
static const char *V4TAG = "mcast-ipv4";
#endif
#ifdef CONFIG_EXAMPLE_IPV6
static const char *V6TAG = "mcast-ipv6";
#endif
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_CONNECTED:
/* enable ipv6 */
tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA);
break;
case SYSTEM_EVENT_STA_GOT_IP:
xEventGroupSetBits(wifi_event_group, IPV4_GOTIP_BIT);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
/* This is a workaround as ESP32 WiFi libs don't currently
auto-reassociate. */
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, IPV4_GOTIP_BIT);
xEventGroupClearBits(wifi_event_group, IPV6_GOTIP_BIT);
break;
case SYSTEM_EVENT_AP_STA_GOT_IP6:
xEventGroupSetBits(wifi_event_group, IPV6_GOTIP_BIT);
default:
break;
}
return ESP_OK;
}
static void initialise_wifi(void)
{
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_WIFI_SSID,
.password = EXAMPLE_WIFI_PASS,
},
};
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
#ifdef CONFIG_EXAMPLE_IPV4
/* Add a socket, either IPV4-only or IPV6 dual mode, to the IPV4
multicast group */
static int socket_add_ipv4_multicast_group(int sock, bool assign_source_if)
{
struct ip_mreq imreq = { 0 };
struct in_addr iaddr = { 0 };
int err = 0;
// Configure source interface
#if USE_DEFAULT_IF
imreq.imr_interface.s_addr = IPADDR_ANY;
#else
tcpip_adapter_ip_info_t ip_info = { 0 };
err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);
if (err != ESP_OK) {
ESP_LOGE(V4TAG, "Failed to get IP address info. Error 0x%x", err);
goto err;
}
inet_addr_from_ipaddr(&iaddr, &ip_info.ip);
#endif
// Configure multicast address to listen to
err = inet_aton(MULTICAST_IPV4_ADDR, &imreq.imr_multiaddr.s_addr);
if (err != 1) {
ESP_LOGE(V4TAG, "Configured IPV4 multicast address '%s' is invalid.", MULTICAST_IPV4_ADDR);
goto err;
}
ESP_LOGI(TAG, "Configured IPV4 Multicast address %s", inet_ntoa(imreq.imr_multiaddr.s_addr));
if (!IP_MULTICAST(ntohl(imreq.imr_multiaddr.s_addr))) {
ESP_LOGW(V4TAG, "Configured IPV4 multicast address '%s' is not a valid multicast address. This will probably not work.", MULTICAST_IPV4_ADDR);
}
if (assign_source_if) {
// Assign the IPv4 multicast source interface, via its IP
// (only necessary if this socket is IPV4 only)
err = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &iaddr,
sizeof(struct in_addr));
if (err < 0) {
ESP_LOGE(V4TAG, "Failed to set IP_MULTICAST_IF. Error %d", errno);
goto err;
}
}
err = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&imreq, sizeof(struct ip_mreq));
if (err < 0) {
ESP_LOGE(V4TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
goto err;
}
err:
return err;
}
#endif /* CONFIG_EXAMPLE_IPV4 */
#ifdef CONFIG_EXAMPLE_IPV4_ONLY
static int create_multicast_ipv4_socket()
{
struct sockaddr_in saddr = { 0 };
int sock = -1;
int err = 0;
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock < 0) {
ESP_LOGE(V4TAG, "Failed to create socket. Error %d", errno);
return -1;
}
// Bind the socket to any address
saddr.sin_family = PF_INET;
saddr.sin_port = htons(UDP_PORT);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));
if (err < 0) {
ESP_LOGE(V4TAG, "Failed to bind socket. Error %d", errno);
goto err;
}
// Assign multicast TTL (set separately from normal interface TTL)
uint8_t ttl = MULTICAST_TTL;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(uint8_t));
if (err < 0) {
ESP_LOGE(V4TAG, "Failed to set IP_MULTICAST_TTL. Error %d", errno);
goto err;
}
#if MULTICAST_LOOPBACK
// select whether multicast traffic should be received by this device, too
// (if setsockopt() is not called, the default is no)
uint8_t loopback_val = MULTICAST_LOOPBACK;
err = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP,
&loopback_val, sizeof(uint8_t));
if (err < 0) {
ESP_LOGE(V4TAG, "Failed to set IP_MULTICAST_LOOP. Error %d", errno);
goto err;
}
#endif
// this is also a listening socket, so add it to the multicast
// group for listening...
err = socket_add_ipv4_multicast_group(sock, true);
if (err < 0) {
goto err;
}
// All set, socket is configured for sending and receiving
return sock;
err:
close(sock);
return -1;
}
#endif /* CONFIG_EXAMPLE_IPV4_ONLY */
#ifdef CONFIG_EXAMPLE_IPV6
static int create_multicast_ipv6_socket()
{
struct sockaddr_in6 saddr = { 0 };
struct in6_addr if_inaddr = { 0 };
struct ip6_addr if_ipaddr = { 0 };
struct ip6_mreq v6imreq = { 0 };
int sock = -1;
int err = 0;
sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IPV6);
if (sock < 0) {
ESP_LOGE(V6TAG, "Failed to create socket. Error %d", errno);
return -1;
}
// Bind the socket to any address
saddr.sin6_family = AF_INET6;
saddr.sin6_port = htons(UDP_PORT);
bzero(&saddr.sin6_addr.un, sizeof(saddr.sin6_addr.un));
err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6));
if (err < 0) {
ESP_LOGE(V6TAG, "Failed to bind socket. Error %d", errno);
goto err;
}
// Selct the interface to use as multicast source for this socket.
#if USE_DEFAULT_IF
bzero(&if_inaddr.un, sizeof(if_inaddr.un));
#else
// Read interface adapter link-local address and use it
// to bind the multicast IF to this socket.
//
// (Note the interface may have other non-LL IPV6 addresses as well,
// but it doesn't matter in this context as the address is only
// used to identify the interface.)
err = tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_STA, &if_ipaddr);
inet6_addr_from_ip6addr(&if_inaddr, &if_ipaddr);
if (err != ESP_OK) {
ESP_LOGE(V6TAG, "Failed to get IPV6 LL address. Error 0x%x", err);
goto err;
}
#endif
// Assign the multicast source interface, via its IP
err = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_inaddr,
sizeof(struct in6_addr));
if (err < 0) {
ESP_LOGE(V6TAG, "Failed to set IPV6_MULTICAST_IF. Error %d", errno);
goto err;
}
// Assign multicast TTL (set separately from normal interface TTL)
uint8_t ttl = MULTICAST_TTL;
setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(uint8_t));
if (err < 0) {
ESP_LOGE(V6TAG, "Failed to set IPV6_MULTICAST_HOPS. Error %d", errno);
goto err;
}
#if MULTICAST_LOOPBACK
// select whether multicast traffic should be received by this device, too
// (if setsockopt() is not called, the default is no)
uint8_t loopback_val = MULTICAST_LOOPBACK;
err = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
&loopback_val, sizeof(uint8_t));
if (err < 0) {
ESP_LOGE(V6TAG, "Failed to set IPV6_MULTICAST_LOOP. Error %d", errno);
goto err;
}
#endif
// this is also a listening socket, so add it to the multicast
// group for listening...
// Configure source interface
#if USE_DEFAULT_IF
v6imreq.imr_interface.s_addr = IPADDR_ANY;
#else
inet6_addr_from_ip6addr(&v6imreq.ipv6mr_interface, &if_ipaddr);
#endif
#ifdef CONFIG_EXAMPLE_IPV6
// Configure multicast address to listen to
err = inet6_aton(MULTICAST_IPV6_ADDR, &v6imreq.ipv6mr_multiaddr);
if (err != 1) {
ESP_LOGE(V6TAG, "Configured IPV6 multicast address '%s' is invalid.", MULTICAST_IPV6_ADDR);
goto err;
}
ESP_LOGI(TAG, "Configured IPV6 Multicast address %s", inet6_ntoa(v6imreq.ipv6mr_multiaddr));
ip6_addr_t multi_addr;
inet6_addr_to_ip6addr(&multi_addr, &v6imreq.ipv6mr_multiaddr);
if (!ip6_addr_ismulticast(&multi_addr)) {
ESP_LOGW(V6TAG, "Configured IPV6 multicast address '%s' is not a valid multicast address. This will probably not work.", MULTICAST_IPV6_ADDR);
}
err = setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
&v6imreq, sizeof(struct ip6_mreq));
if (err < 0) {
ESP_LOGE(V6TAG, "Failed to set IPV6_ADD_MEMBERSHIP. Error %d", errno);
goto err;
}
#endif
#if CONFIG_EXAMPLE_IPV4_V6
// Add the common IPV4 config options
err = socket_add_ipv4_multicast_group(sock, false);
if (err < 0) {
goto err;
}
#endif
#if CONFIG_EXAMPLE_IPV4_V6
int only = 0;
#else
int only = 1; /* IPV6-only socket */
#endif
err = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &only, sizeof(int));
if (err < 0) {
ESP_LOGE(V6TAG, "Failed to set IPV6_V6ONLY. Error %d", errno);
goto err;
}
ESP_LOGI(TAG, "Socket set IPV6-only");
// All set, socket is configured for sending and receiving
return sock;
err:
close(sock);
return -1;
}
#endif
static void mcast_example_task(void *pvParameters)
{
while (1) {
/* Wait for all the IPs we care about to be set
*/
uint32_t bits = 0;
#ifdef CONFIG_EXAMPLE_IPV4
bits |= IPV4_GOTIP_BIT;
#endif
#ifdef CONFIG_EXAMPLE_IPV6
bits |= IPV6_GOTIP_BIT;
#endif
ESP_LOGI(TAG, "Waiting for AP connection...");
xEventGroupWaitBits(wifi_event_group, bits, false, true, portMAX_DELAY);
ESP_LOGI(TAG, "Connected to AP");
int sock;
#ifdef CONFIG_EXAMPLE_IPV4_ONLY
sock = create_multicast_ipv4_socket();
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create IPv4 multicast socket");
}
#else
sock = create_multicast_ipv6_socket();
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create IPv6 multicast socket");
}
#endif
if (sock < 0) {
// Nothing to do!
vTaskDelay(5 / portTICK_PERIOD_MS);
continue;
}
#ifdef CONFIG_EXAMPLE_IPV4
// set destination multicast addresses for sending from these sockets
struct sockaddr_in sdestv4 = {
.sin_family = PF_INET,
.sin_port = htons(UDP_PORT),
};
// We know this inet_aton will pass because we did it above already
inet_aton(MULTICAST_IPV4_ADDR, &sdestv4.sin_addr.s_addr);
#endif
#ifdef CONFIG_EXAMPLE_IPV6
struct sockaddr_in6 sdestv6 = {
.sin6_family = PF_INET6,
.sin6_port = htons(UDP_PORT),
};
// We know this inet_aton will pass because we did it above already
inet6_aton(MULTICAST_IPV6_ADDR, &sdestv6.sin6_addr);
#endif
// Loop waiting for UDP received, and sending UDP packets if we don't
// see any.
int err = 1;
while (err > 0) {
struct timeval tv = {
.tv_sec = 2,
.tv_usec = 0,
};
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
int s = lwip_select(sock + 1, &rfds, NULL, NULL, &tv);
if (s < 0) {
ESP_LOGE(TAG, "Select failed: errno %d", errno);
err = -1;
break;
}
else if (s > 0) {
if (FD_ISSET(sock, &rfds)) {
// Incoming datagram received
char recvbuf[48];
char raddr_name[32] = { 0 };
struct sockaddr_in6 raddr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(raddr);
int len = recvfrom(sock, recvbuf, sizeof(recvbuf)-1, 0,
(struct sockaddr *)&raddr, &socklen);
if (len < 0) {
ESP_LOGE(TAG, "multicast recvfrom failed: errno %d", errno);
err = -1;
break;
}
// Get the sender's address as a string
#ifdef CONFIG_EXAMPLE_IPV4
if (raddr.sin6_family == PF_INET) {
inet_ntoa_r(((struct sockaddr_in *)&raddr)->sin_addr.s_addr,
raddr_name, sizeof(raddr_name)-1);
}
#endif
#ifdef CONFIG_EXAMPLE_IPV6
if (raddr.sin6_family == PF_INET6) {
inet6_ntoa_r(raddr.sin6_addr, raddr_name, sizeof(raddr_name)-1);
}
#endif
ESP_LOGI(TAG, "received %d bytes from %s:", len, raddr_name);
recvbuf[len] = 0; // Null-terminate whatever we received and treat like a string...
ESP_LOGI(TAG, "%s", recvbuf);
}
}
else { // s == 0
// Timeout passed with no incoming data, so send something!
static int send_count;
const char sendfmt[] = "Multicast #%d sent by ESP32\n";
char sendbuf[48];
char addrbuf[32] = { 0 };
int len = snprintf(sendbuf, sizeof(sendbuf), sendfmt, send_count++);
if (len > sizeof(sendbuf)) {
ESP_LOGE(TAG, "Overflowed multicast sendfmt buffer!!");
send_count = 0;
err = -1;
break;
}
struct addrinfo hints = {
.ai_flags = AI_PASSIVE,
.ai_socktype = SOCK_DGRAM,
};
struct addrinfo *res;
#ifdef CONFIG_EXAMPLE_IPV4 // Send an IPv4 multicast packet
#ifdef CONFIG_EXAMPLE_IPV4_ONLY
hints.ai_family = AF_INET; // For an IPv4 socket
#else
hints.ai_family = AF_INET6; // For an IPv4 socket with V4 mapped addresses
hints.ai_flags |= AI_V4MAPPED;
#endif
int err = getaddrinfo(CONFIG_EXAMPLE_MULTICAST_IPV4_ADDR,
NULL,
&hints,
&res);
if (err < 0) {
ESP_LOGE(TAG, "getaddrinfo() failed for IPV4 destination address. error: %d", err);
break;
}
#ifdef CONFIG_EXAMPLE_IPV4_ONLY
((struct sockaddr_in *)res->ai_addr)->sin_port = htons(UDP_PORT);
inet_ntoa_r(((struct sockaddr_in *)res->ai_addr)->sin_addr, addrbuf, sizeof(addrbuf)-1);
ESP_LOGI(TAG, "Sending to IPV4 multicast address %s...", addrbuf);
#else
((struct sockaddr_in6 *)res->ai_addr)->sin6_port = htons(UDP_PORT);
inet6_ntoa_r(((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, addrbuf, sizeof(addrbuf)-1);
ESP_LOGI(TAG, "Sending to IPV6 (V4 mapped) multicast address %s (%s)...", addrbuf, CONFIG_EXAMPLE_MULTICAST_IPV4_ADDR);
#endif
err = sendto(sock, sendbuf, len, 0, res->ai_addr, res->ai_addrlen);
if (err < 0) {
ESP_LOGE(TAG, "IPV4 sendto failed. errno: %d", errno);
break;
}
#endif
#ifdef CONFIG_EXAMPLE_IPV6
hints.ai_family = AF_INET6;
hints.ai_protocol = 0;
err = getaddrinfo(CONFIG_EXAMPLE_MULTICAST_IPV6_ADDR,
NULL,
&hints,
&res);
if (err < 0) {
ESP_LOGE(TAG, "getaddrinfo() failed for IPV6 destination address. error: %d", err);
break;
}
struct sockaddr_in6 *s6addr = (struct sockaddr_in6 *)res->ai_addr;
s6addr->sin6_port = htons(UDP_PORT);
inet6_ntoa_r(s6addr->sin6_addr, addrbuf, sizeof(addrbuf)-1);
ESP_LOGI(TAG, "Sending to IPV6 multicast address %s...", addrbuf);
err = sendto(sock, sendbuf, len, 0, res->ai_addr, res->ai_addrlen);
if (err < 0) {
ESP_LOGE(TAG, "IPV6 sendto failed. errno: %d", errno);
break;
}
#endif
}
}
ESP_LOGE(TAG, "Shutting down socket and restarting...");
shutdown(sock, 0);
close(sock);
}
}
void app_main()
{
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi();
xTaskCreate(&mcast_example_task, "mcast_task", 4096, NULL, 5, NULL);
}