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