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