diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index c9c796f87..708a7bd14 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -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 diff --git a/components/lwip/api/api_msg.c b/components/lwip/api/api_msg.c index bfb07b876..bdfc0a984 100755 --- a/components/lwip/api/api_msg.c +++ b/components/lwip/api/api_msg.c @@ -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) { diff --git a/components/lwip/api/netdb.c b/components/lwip/api/netdb.c index 65510f55e..66f602d26 100755 --- a/components/lwip/api/netdb.c +++ b/components/lwip/api/netdb.c @@ -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); diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 33d4a97e7..0da123839 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -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 diff --git a/components/lwip/core/ipv6/ip6.c b/components/lwip/core/ipv6/ip6.c index 380bc290c..acd28fb5b 100755 --- a/components/lwip/core/ipv6/ip6.c +++ b/components/lwip/core/ipv6/ip6.c @@ -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] */ diff --git a/components/lwip/core/udp.c b/components/lwip/core/udp.c index 37ae2c179..779dd9ad7 100755 --- a/components/lwip/core/udp.c +++ b/components/lwip/core/udp.c @@ -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 diff --git a/components/lwip/include/lwip/lwip/autoip.h b/components/lwip/include/lwip/lwip/autoip.h index 3bb413f49..16eac510e 100755 --- a/components/lwip/include/lwip/lwip/autoip.h +++ b/components/lwip/include/lwip/lwip/autoip.h @@ -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 */ diff --git a/components/lwip/include/lwip/lwip/ip_addr.h b/components/lwip/include/lwip/lwip/ip_addr.h index 1e1ffb3f1..74897a9ee 100755 --- a/components/lwip/include/lwip/lwip/ip_addr.h +++ b/components/lwip/include/lwip/lwip/ip_addr.h @@ -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 diff --git a/components/lwip/include/lwip/lwip/opt.h b/components/lwip/include/lwip/lwip/opt.h index 4d8d6bf70..6ea556ac1 100755 --- a/components/lwip/include/lwip/lwip/opt.h +++ b/components/lwip/include/lwip/lwip/opt.h @@ -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 ----- diff --git a/components/lwip/include/lwip/lwip/sockets.h b/components/lwip/include/lwip/lwip/sockets.h index d9622ea03..cb458988a 100755 --- a/components/lwip/include/lwip/lwip/sockets.h +++ b/components/lwip/include/lwip/lwip/sockets.h @@ -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 diff --git a/components/lwip/include/lwip/lwip/udp.h b/components/lwip/include/lwip/lwip/udp.h index a370d3f9b..c2f6ed9df 100755 --- a/components/lwip/include/lwip/lwip/udp.h +++ b/components/lwip/include/lwip/lwip/udp.h @@ -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 diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 89d09d2d7..47b80ff61 100644 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -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 /* ---------------------------------------- diff --git a/examples/protocols/udp_multicast/Makefile b/examples/protocols/udp_multicast/Makefile new file mode 100644 index 000000000..60f3d59db --- /dev/null +++ b/examples/protocols/udp_multicast/Makefile @@ -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 + diff --git a/examples/protocols/udp_multicast/README.md b/examples/protocols/udp_multicast/README.md new file mode 100644 index 000000000..8a937afc1 --- /dev/null +++ b/examples/protocols/udp_multicast/README.md @@ -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. + diff --git a/examples/protocols/udp_multicast/main/Kconfig.projbuild b/examples/protocols/udp_multicast/main/Kconfig.projbuild new file mode 100644 index 000000000..72adc48ca --- /dev/null +++ b/examples/protocols/udp_multicast/main/Kconfig.projbuild @@ -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 diff --git a/examples/protocols/udp_multicast/main/component.mk b/examples/protocols/udp_multicast/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/protocols/udp_multicast/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/protocols/udp_multicast/main/udp_multicast_example_main.c b/examples/protocols/udp_multicast/main/udp_multicast_example_main.c new file mode 100644 index 000000000..830b00e07 --- /dev/null +++ b/examples/protocols/udp_multicast/main/udp_multicast_example_main.c @@ -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 +#include +#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 + +/* 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); +}