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
mcast_init(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
mcast_reset(struct mcast_head * mcast_head)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
mcast_join(struct mcast_head * mcast_head,const ip_addr_t * group,struct ifdev * ifdev)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
mcast_free(struct mcast_member * mm,int leave_group)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
mcast_leave(struct mcast_head * mcast_head,const ip_addr_t * group,struct ifdev * ifdev)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
mcast_leave_all(struct mcast_head * mcast_head)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
mcast_clear(struct ifdev * ifdev)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