summaryrefslogtreecommitdiff
path: root/minix/net/lwip/mcast.c
blob: 832dce1a42bae87b0676e5ac8401c318fa1d4e4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/* LWIP service - mcast.c - per-socket multicast membership tracking */
/*
 * Each socket has a linked list of multicast groups of which it is a member.
 * The linked list consists of 'mcast_member' elements.  There is both a global
 * limit (the number of elements in 'mcast_array') and a per-socket limit on
 * group membership.  Since multiple sockets may join the same multicast
 * groups, there is not a one-to-one relationship between our membership
 * structures and the lwIP IGMP/MLD membership structures.  Moreover, linking
 * to the latter structures directly is not intended by lwIP, so we have to
 * keep our own tracking independent, which in particular means that we have to
 * make a copy of the multicast group address.
 *
 * We currently put no effort into saving memory on storing that group address.
 * Optimization is complicated by the fact that we have to be able to remove
 * membership structures when their corresponding interface disappears, which
 * currently involves removal without knowing about the corresponding socket,
 * and therefore the socket's address family.  All of this can be changed.
 *
 * There is no function to test whether a particular socket is a member of a
 * multicast group.  The pktsock module currently makes the assumption that if
 * a socket has been joined to any multicast groups, or set any multicast
 * options, the application is multicast aware and therefore able to figure out
 * whether it is interested in particular packets, and so we do not filter
 * incoming packets against the receiving socket's multicast list.  This should
 * be more or less in line with what W. Richard Stevens say that the BSDs do.
 */

#include "lwip.h"
#include "mcast.h"

#include "lwip/igmp.h"
#include "lwip/mld6.h"

/*
 * The per-socket limit on group membership.  In theory, the limit should be
 * high enough that a single socket can join a particular multicast group on
 * all interfaces that support multicast.  In practice, we set it a bit lower
 * to prevent one socket from using up half of the entries per address family.
 * Setting it to IP_MAX_MEMBERSHIPS is definitely excessive right now..
 */
#define MAX_GROUPS_PER_SOCKET	8

static struct mcast_member {
	LIST_ENTRY(mcast_member) mm_next;	/* next in socket, free list */
	struct ifdev * mm_ifdev;		/* interface (NULL: free) */
	ip_addr_t mm_group;			/* group address */
} mcast_array[NR_IPV4_MCAST_GROUP + NR_IPV6_MCAST_GROUP];

static LIST_HEAD(, mcast_member) mcast_freelist;

/*
 * Initialize the per-socket multicast membership module.
 */
void
mcast_init(void)
{
	unsigned int slot;

	/* Initialize the list of free multicast membership entries. */
	LIST_INIT(&mcast_freelist);

	for (slot = 0; slot < __arraycount(mcast_array); slot++) {
		mcast_array[slot].mm_ifdev = NULL;

		LIST_INSERT_HEAD(&mcast_freelist, &mcast_array[slot], mm_next);
	}
}

/*
 * Reset the multicast head for a socket.  The socket must not have any
 * previous multicast group memberships.
 */
void
mcast_reset(struct mcast_head * mcast_head)
{

	LIST_INIT(&mcast_head->mh_list);
}

/*
 * Attempt to add a per-socket multicast membership association.  The given
 * 'mcast_head' pointer is part of a socket.  The 'group' parameter is the
 * multicast group to join.  It is a properly zoned address, but has not been
 * checked in any other way.  If 'ifdev' is not NULL, it is the interface for
 * the membership; if it is NULL, an interface will be selected using routing.
 * Return OK if the membership has been successfully removed, or a negative
 * error code otherwise.
 */
int
mcast_join(struct mcast_head * mcast_head, const ip_addr_t * group,
	struct ifdev * ifdev)
{
	struct mcast_member *mm;
	struct netif *netif;
	unsigned int count;
	err_t err;

	/*
	 * The callers of this function perform only checks that depend on the
	 * address family.  We check everything else here.
	 */
	if (!ip_addr_ismulticast(group))
		return EADDRNOTAVAIL;

	if (!addr_is_valid_multicast(group))
		return EINVAL;

	/*
	 * If no interface was specified, pick one with a routing query.  Note
	 * that scoped IPv6 addresses do require an interface to be specified.
	 */
	if (ifdev == NULL) {
		netif = ip_route(IP46_ADDR_ANY(IP_GET_TYPE(group)), group);

		if (netif == NULL)
			return EHOSTUNREACH;

		ifdev = netif_get_ifdev(netif);
	}

	assert(ifdev != NULL);
	assert(!IP_IS_V6(group) ||
	    !ip6_addr_lacks_zone(ip_2_ip6(group), IP6_MULTICAST));

	/* The interface must support multicast. */
	if (!(ifdev_get_ifflags(ifdev) & IFF_MULTICAST))
		return EADDRNOTAVAIL;

	/*
	 * First see if this socket is already joined to the given group, which
	 * is an error.  While looking, also count the number of groups the
	 * socket has joined already, to enforce the per-socket limit.
	 */
	count = 0;

	LIST_FOREACH(mm, &mcast_head->mh_list, mm_next) {
		if (mm->mm_ifdev == ifdev && ip_addr_cmp(&mm->mm_group, group))
			return EEXIST;

		count++;
	}

	if (count >= MAX_GROUPS_PER_SOCKET)
		return ENOBUFS;

	/* Do we have a free membership structure available? */
	if (LIST_EMPTY(&mcast_freelist))
		return ENOBUFS;

	/*
	 * Nothing can go wrong as far as we are concerned.  Ask lwIP to join
	 * the multicast group.  This may result in a multicast list update at
	 * the driver end.
	 */
	netif = ifdev_get_netif(ifdev);

	if (IP_IS_V6(group))
		err = mld6_joingroup_netif(netif, ip_2_ip6(group));
	else
		err = igmp_joingroup_netif(netif, ip_2_ip4(group));

	if (err != ERR_OK)
		return util_convert_err(err);

	/*
	 * Success.  Allocate, initialize, and attach a membership structure to
	 * the socket.
	 */
	mm = LIST_FIRST(&mcast_freelist);

	LIST_REMOVE(mm, mm_next);

	mm->mm_ifdev = ifdev;
	mm->mm_group = *group;

	LIST_INSERT_HEAD(&mcast_head->mh_list, mm, mm_next);

	return OK;
}

/*
 * Free the given per-socket multicast membership structure, which must
 * previously have been associated with a socket.  If 'leave_group' is set,
 * also tell lwIP to leave the corresponding multicast group.
 */
static void
mcast_free(struct mcast_member * mm, int leave_group)
{
	struct netif *netif;
	err_t err;

	assert(mm->mm_ifdev != NULL);

	if (leave_group) {
		netif = ifdev_get_netif(mm->mm_ifdev);

		if (IP_IS_V6(&mm->mm_group))
			err = mld6_leavegroup_netif(netif,
			    ip_2_ip6(&mm->mm_group));
		else
			err = igmp_leavegroup_netif(netif,
			    ip_2_ip4(&mm->mm_group));

		if (err != ERR_OK)
			panic("lwIP multicast membership desynchronization");
	}

	LIST_REMOVE(mm, mm_next);

	mm->mm_ifdev = NULL;

	LIST_INSERT_HEAD(&mcast_freelist, mm, mm_next);
}

/*
 * Attempt to remove a per-socket multicast membership association.  The given
 * 'mcast_head' pointer is part of a socket.  The 'group' parameter is the
 * multicast group to leave.  It is a properly zoned address, but has not been
 * checked in any other way.  If 'ifdev' is not NULL, it is the interface of
 * the membership; if it is NULL, a membership matching the address on any
 * interface will suffice.  As such, the parameter requirements mirror those of
 * mcast_join().  Return OK if the membership has been successfully removed, or
 * a negative error code otherwise.
 */
int
mcast_leave(struct mcast_head * mcast_head, const ip_addr_t * group,
	struct ifdev * ifdev)
{
	struct mcast_member *mm;

	/*
	 * Look up a matching entry.  The fact that we must find a match for
	 * the given address and interface, keeps us from having to perform
	 * various other checks, such as whether the given address is a
	 * multicast address at all.  The exact error codes are not specified.
	 */
	LIST_FOREACH(mm, &mcast_head->mh_list, mm_next) {
		if ((ifdev == NULL || mm->mm_ifdev == ifdev) &&
		    ip_addr_cmp(&mm->mm_group, group))
			break;
	}

	if (mm == NULL)
		return ESRCH;

	mcast_free(mm, TRUE /*leave_group*/);

	return OK;
}

/*
 * Remove all per-socket multicast membership associations of the given socket.
 * This function is called when the socket is closed.
 */
void
mcast_leave_all(struct mcast_head * mcast_head)
{
	struct mcast_member *mm;

	while (!LIST_EMPTY(&mcast_head->mh_list)) {
		mm = LIST_FIRST(&mcast_head->mh_list);

		mcast_free(mm, TRUE /*leave_group*/);
	}
}

/*
 * The given interface is about to disappear.  Remove and free any per-socket
 * multicast membership structures associated with the interface, without
 * leaving the multicast group itself (as that will happen a bit later anyway).
 */
void
mcast_clear(struct ifdev * ifdev)
{
	unsigned int slot;

	for (slot = 0; slot < __arraycount(mcast_array); slot++) {
		if (mcast_array[slot].mm_ifdev != ifdev)
			continue;

		mcast_free(&mcast_array[slot], FALSE /*leave_group*/);
	}
}