xref: /minix3/minix/net/lwip/mcast.c (revision ef8d499e2d2af900e9b2ab297171d7b088652482)
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