From c8581e4cd99c3c32c7d833c100dc2102d1a3b7bd Mon Sep 17 00:00:00 2001 From: sg Date: Wed, 25 Feb 2015 21:30:22 +0100 Subject: [PATCH] fixed bug #38165 (socket with mulicast): ensure igmp membership are dropped when socket (not netconn!) is closed. --- CHANGELOG | 4 ++ src/api/sockets.c | 114 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 672e3ee2..76839cd4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -202,6 +202,10 @@ HISTORY ++ Bugfixes: + 2015-02-25: Simon Goldschmidt + * sockets.c: fixed bug #38165 (socket with mulicast): ensure igmp membership + are dropped when socket (not netconn!) is closed. + 2015-02-25: Simon Goldschmidt * ip4.h/.c, udp.c: fixed bug #38061 (wrong multicast routing in IPv4) by adding an optional default netif for multicast routing diff --git a/src/api/sockets.c b/src/api/sockets.c index 1e349212..71cc346e 100644 --- a/src/api/sockets.c +++ b/src/api/sockets.c @@ -237,6 +237,29 @@ union sockaddr_aligned { struct sockaddr_in sin; }; +#if LWIP_IGMP +/* Define the number of IPv4 multicast memberships, default is one per socket */ +#ifndef LWIP_SOCKET_MAX_MEMBERSHIPS +#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 */ +struct lwip_socket_multicast_pair { + /** the socket (+1 to not require initialization) */ + int sa; + /** the interface address */ + ip_addr_t if_addr; + /** the group address */ + ip_addr_t multi_addr; +}; + +struct lwip_socket_multicast_pair socket_ipv4_multicast_memberships[LWIP_SOCKET_MAX_MEMBERSHIPS]; + +static int lwip_socket_register_membership(int s, ip_addr_t *if_addr, ip_addr_t *multi_addr); +static void lwip_socket_unregister_membership(int s, ip_addr_t *if_addr, ip_addr_t *multi_addr); +static void lwip_socket_drop_registered_memberships(int s); +#endif /* LWIP_IGMP */ /** The global array of available sockets */ static struct lwip_sock sockets[NUM_SOCKETS]; @@ -577,6 +600,11 @@ lwip_close(int s) LWIP_ASSERT("sock->lastdata == NULL", sock->lastdata == NULL); } +#if LWIP_IGMP + /* drop all possibly joined IGMP memberships */ + lwip_socket_drop_registered_memberships(s); +#endif /* LWIP_IGMP */ + err = netconn_delete(sock->conn); if (err != ERR_OK) { sock_set_errno(sock, err_to_errno(err)); @@ -2162,10 +2190,17 @@ lwip_setsockopt_impl(int s, int level, int optname, const void *optval, socklen_ 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); - if(optname == IP_ADD_MEMBERSHIP){ - igmp_err = igmp_joingroup(&if_addr, &multi_addr); + 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); + } } else { igmp_err = igmp_leavegroup(&if_addr, &multi_addr); + lwip_socket_unregister_membership(s, &if_addr, &multi_addr); } if (igmp_err != ERR_OK) { err = EADDRNOTAVAIL; @@ -2454,4 +2489,79 @@ 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. + * + * 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, ip_addr_t *if_addr, ip_addr_t *multi_addr) +{ + /* s+1 is stored in the array to prevent having to initialize the array + (default initialization is to 0) */ + int sa = s + 1; + 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; + ip_addr_copy(socket_ipv4_multicast_memberships[i].if_addr, *if_addr); + ip_addr_copy(socket_ipv4_multicast_memberships[i].multi_addr, *multi_addr); + return 1; + } + } + return 0; +} + +/** Unregister a previously registered membership. This prevents dropping the membership + * on socket close. + * + * ATTENTION: this function is called from tcpip_thread (or under CORE_LOCK). + */ +static void +lwip_socket_unregister_membership(int s, ip_addr_t *if_addr, ip_addr_t *multi_addr) +{ + /* s+1 is stored in the array to prevent having to initialize the array + (default initialization is to 0) */ + int sa = s + 1; + int i; + + for (i = 0; i < LWIP_SOCKET_MAX_MEMBERSHIPS; i++) { + if ((socket_ipv4_multicast_memberships[i].sa == sa) && + ip_addr_cmp(&socket_ipv4_multicast_memberships[i].if_addr, if_addr) && + ip_addr_cmp(&socket_ipv4_multicast_memberships[i].multi_addr, multi_addr)) { + socket_ipv4_multicast_memberships[i].sa = 0; + ip_addr_set_zero(&socket_ipv4_multicast_memberships[i].if_addr); + ip_addr_set_zero(&socket_ipv4_multicast_memberships[i].multi_addr); + return; + } + } +} + +/** Drop all memberships of a socket that were not dropped explicitly via setsockopt. + * + * ATTENTION: this function is called NOT called from tcpip_thread (or under CORE_LOCK). + */ +static void lwip_socket_drop_registered_memberships(int s) +{ + /* s+1 is stored in the array to prevent having to initialize the array + (default initialization is to 0) */ + int sa = s + 1; + int i; + + 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) { + netconn_join_leave_group(sockets[s].conn, &socket_ipv4_multicast_memberships[i].if_addr, + &socket_ipv4_multicast_memberships[i].multi_addr, NETCONN_LEAVE); + socket_ipv4_multicast_memberships[i].sa = 0; + ip_addr_set_zero(&socket_ipv4_multicast_memberships[i].if_addr); + ip_addr_set_zero(&socket_ipv4_multicast_memberships[i].multi_addr); + } + } +} +#endif /* LWIP_IGMP */ #endif /* LWIP_SOCKET */