1 /* LWIP service - mcast.c - per-socket multicast membership tracking */ 2 /* 3 * Each socket has a linked list of multicast groups of which it is a member. 4 * The linked list consists of 'mcast_member' elements. There is both a global 5 * limit (the number of elements in 'mcast_array') and a per-socket limit on 6 * group membership. Since multiple sockets may join the same multicast 7 * groups, there is not a one-to-one relationship between our membership 8 * structures and the lwIP IGMP/MLD membership structures. Moreover, linking 9 * to the latter structures directly is not intended by lwIP, so we have to 10 * keep our own tracking independent, which in particular means that we have to 11 * make a copy of the multicast group address. 12 * 13 * We currently put no effort into saving memory on storing that group address. 14 * Optimization is complicated by the fact that we have to be able to remove 15 * membership structures when their corresponding interface disappears, which 16 * currently involves removal without knowing about the corresponding socket, 17 * and therefore the socket's address family. All of this can be changed. 18 * 19 * There is no function to test whether a particular socket is a member of a 20 * multicast group. The pktsock module currently makes the assumption that if 21 * a socket has been joined to any multicast groups, or set any multicast 22 * options, the application is multicast aware and therefore able to figure out 23 * whether it is interested in particular packets, and so we do not filter 24 * incoming packets against the receiving socket's multicast list. This should 25 * be more or less in line with what W. Richard Stevens say that the BSDs do. 26 */ 27 28 #include "lwip.h" 29 #include "mcast.h" 30 31 #include "lwip/igmp.h" 32 #include "lwip/mld6.h" 33 34 /* 35 * The per-socket limit on group membership. In theory, the limit should be 36 * high enough that a single socket can join a particular multicast group on 37 * all interfaces that support multicast. In practice, we set it a bit lower 38 * to prevent one socket from using up half of the entries per address family. 39 * Setting it to IP_MAX_MEMBERSHIPS is definitely excessive right now.. 40 */ 41 #define MAX_GROUPS_PER_SOCKET 8 42 43 static struct mcast_member { 44 LIST_ENTRY(mcast_member) mm_next; /* next in socket, free list */ 45 struct ifdev * mm_ifdev; /* interface (NULL: free) */ 46 ip_addr_t mm_group; /* group address */ 47 } mcast_array[NR_IPV4_MCAST_GROUP + NR_IPV6_MCAST_GROUP]; 48 49 static LIST_HEAD(, mcast_member) mcast_freelist; 50 51 /* 52 * Initialize the per-socket multicast membership module. 53 */ 54 void 55 mcast_init(void) 56 { 57 unsigned int slot; 58 59 /* Initialize the list of free multicast membership entries. */ 60 LIST_INIT(&mcast_freelist); 61 62 for (slot = 0; slot < __arraycount(mcast_array); slot++) { 63 mcast_array[slot].mm_ifdev = NULL; 64 65 LIST_INSERT_HEAD(&mcast_freelist, &mcast_array[slot], mm_next); 66 } 67 } 68 69 /* 70 * Reset the multicast head for a socket. The socket must not have any 71 * previous multicast group memberships. 72 */ 73 void 74 mcast_reset(struct mcast_head * mcast_head) 75 { 76 77 LIST_INIT(&mcast_head->mh_list); 78 } 79 80 /* 81 * Attempt to add a per-socket multicast membership association. The given 82 * 'mcast_head' pointer is part of a socket. The 'group' parameter is the 83 * multicast group to join. It is a properly zoned address, but has not been 84 * checked in any other way. If 'ifdev' is not NULL, it is the interface for 85 * the membership; if it is NULL, an interface will be selected using routing. 86 * Return OK if the membership has been successfully removed, or a negative 87 * error code otherwise. 88 */ 89 int 90 mcast_join(struct mcast_head * mcast_head, const ip_addr_t * group, 91 struct ifdev * ifdev) 92 { 93 struct mcast_member *mm; 94 struct netif *netif; 95 unsigned int count; 96 err_t err; 97 98 /* 99 * The callers of this function perform only checks that depend on the 100 * address family. We check everything else here. 101 */ 102 if (!ip_addr_ismulticast(group)) 103 return EADDRNOTAVAIL; 104 105 if (!addr_is_valid_multicast(group)) 106 return EINVAL; 107 108 /* 109 * If no interface was specified, pick one with a routing query. Note 110 * that scoped IPv6 addresses do require an interface to be specified. 111 */ 112 if (ifdev == NULL) { 113 netif = ip_route(IP46_ADDR_ANY(IP_GET_TYPE(group)), group); 114 115 if (netif == NULL) 116 return EHOSTUNREACH; 117 118 ifdev = netif_get_ifdev(netif); 119 } 120 121 assert(ifdev != NULL); 122 assert(!IP_IS_V6(group) || 123 !ip6_addr_lacks_zone(ip_2_ip6(group), IP6_MULTICAST)); 124 125 /* The interface must support multicast. */ 126 if (!(ifdev_get_ifflags(ifdev) & IFF_MULTICAST)) 127 return EADDRNOTAVAIL; 128 129 /* 130 * First see if this socket is already joined to the given group, which 131 * is an error. While looking, also count the number of groups the 132 * socket has joined already, to enforce the per-socket limit. 133 */ 134 count = 0; 135 136 LIST_FOREACH(mm, &mcast_head->mh_list, mm_next) { 137 if (mm->mm_ifdev == ifdev && ip_addr_cmp(&mm->mm_group, group)) 138 return EEXIST; 139 140 count++; 141 } 142 143 if (count >= MAX_GROUPS_PER_SOCKET) 144 return ENOBUFS; 145 146 /* Do we have a free membership structure available? */ 147 if (LIST_EMPTY(&mcast_freelist)) 148 return ENOBUFS; 149 150 /* 151 * Nothing can go wrong as far as we are concerned. Ask lwIP to join 152 * the multicast group. This may result in a multicast list update at 153 * the driver end. 154 */ 155 netif = ifdev_get_netif(ifdev); 156 157 if (IP_IS_V6(group)) 158 err = mld6_joingroup_netif(netif, ip_2_ip6(group)); 159 else 160 err = igmp_joingroup_netif(netif, ip_2_ip4(group)); 161 162 if (err != ERR_OK) 163 return util_convert_err(err); 164 165 /* 166 * Success. Allocate, initialize, and attach a membership structure to 167 * the socket. 168 */ 169 mm = LIST_FIRST(&mcast_freelist); 170 171 LIST_REMOVE(mm, mm_next); 172 173 mm->mm_ifdev = ifdev; 174 mm->mm_group = *group; 175 176 LIST_INSERT_HEAD(&mcast_head->mh_list, mm, mm_next); 177 178 return OK; 179 } 180 181 /* 182 * Free the given per-socket multicast membership structure, which must 183 * previously have been associated with a socket. If 'leave_group' is set, 184 * also tell lwIP to leave the corresponding multicast group. 185 */ 186 static void 187 mcast_free(struct mcast_member * mm, int leave_group) 188 { 189 struct netif *netif; 190 err_t err; 191 192 assert(mm->mm_ifdev != NULL); 193 194 if (leave_group) { 195 netif = ifdev_get_netif(mm->mm_ifdev); 196 197 if (IP_IS_V6(&mm->mm_group)) 198 err = mld6_leavegroup_netif(netif, 199 ip_2_ip6(&mm->mm_group)); 200 else 201 err = igmp_leavegroup_netif(netif, 202 ip_2_ip4(&mm->mm_group)); 203 204 if (err != ERR_OK) 205 panic("lwIP multicast membership desynchronization"); 206 } 207 208 LIST_REMOVE(mm, mm_next); 209 210 mm->mm_ifdev = NULL; 211 212 LIST_INSERT_HEAD(&mcast_freelist, mm, mm_next); 213 } 214 215 /* 216 * Attempt to remove a per-socket multicast membership association. The given 217 * 'mcast_head' pointer is part of a socket. The 'group' parameter is the 218 * multicast group to leave. It is a properly zoned address, but has not been 219 * checked in any other way. If 'ifdev' is not NULL, it is the interface of 220 * the membership; if it is NULL, a membership matching the address on any 221 * interface will suffice. As such, the parameter requirements mirror those of 222 * mcast_join(). Return OK if the membership has been successfully removed, or 223 * a negative error code otherwise. 224 */ 225 int 226 mcast_leave(struct mcast_head * mcast_head, const ip_addr_t * group, 227 struct ifdev * ifdev) 228 { 229 struct mcast_member *mm; 230 231 /* 232 * Look up a matching entry. The fact that we must find a match for 233 * the given address and interface, keeps us from having to perform 234 * various other checks, such as whether the given address is a 235 * multicast address at all. The exact error codes are not specified. 236 */ 237 LIST_FOREACH(mm, &mcast_head->mh_list, mm_next) { 238 if ((ifdev == NULL || mm->mm_ifdev == ifdev) && 239 ip_addr_cmp(&mm->mm_group, group)) 240 break; 241 } 242 243 if (mm == NULL) 244 return ESRCH; 245 246 mcast_free(mm, TRUE /*leave_group*/); 247 248 return OK; 249 } 250 251 /* 252 * Remove all per-socket multicast membership associations of the given socket. 253 * This function is called when the socket is closed. 254 */ 255 void 256 mcast_leave_all(struct mcast_head * mcast_head) 257 { 258 struct mcast_member *mm; 259 260 while (!LIST_EMPTY(&mcast_head->mh_list)) { 261 mm = LIST_FIRST(&mcast_head->mh_list); 262 263 mcast_free(mm, TRUE /*leave_group*/); 264 } 265 } 266 267 /* 268 * The given interface is about to disappear. Remove and free any per-socket 269 * multicast membership structures associated with the interface, without 270 * leaving the multicast group itself (as that will happen a bit later anyway). 271 */ 272 void 273 mcast_clear(struct ifdev * ifdev) 274 { 275 unsigned int slot; 276 277 for (slot = 0; slot < __arraycount(mcast_array); slot++) { 278 if (mcast_array[slot].mm_ifdev != ifdev) 279 continue; 280 281 mcast_free(&mcast_array[slot], FALSE /*leave_group*/); 282 } 283 } 284