
This commit adds a new TCP/IP service to MINIX 3. As its core, the service uses the lwIP TCP/IP stack for maintenance reasons. The service aims to be compatible with NetBSD userland, including its low-level network management utilities. It also aims to support modern features such as IPv6. In summary, the new LWIP service has support for the following main features: - TCP, UDP, RAW sockets with mostly standard BSD API semantics; - IPv6 support: host mode (complete) and router mode (partial); - most of the standard BSD API socket options (SO_); - all of the standard BSD API message flags (MSG_); - the most used protocol-specific socket and control options; - a default loopback interface and the ability to create one more; - configuration-free ethernet interfaces and driver tracking; - queuing and multiple concurrent requests to each ethernet driver; - standard ioctl(2)-based BSD interface management; - radix tree backed, destination-based routing; - routing sockets for standard BSD route reporting and management; - multicast traffic and multicast group membership tracking; - Berkeley Packet Filter (BPF) devices; - standard and custom sysctl(7) nodes for many internals; - a slab allocation based, hybrid static/dynamic memory pool model. Many of its modules come with fairly elaborate comments that cover many aspects of what is going on. The service is primarily a socket driver built on top of the libsockdriver library, but for BPF devices it is at the same time also a character driver. Change-Id: Ib0c02736234b21143915e5fcc0fda8fe408f046f
284 lines
8.4 KiB
C
284 lines
8.4 KiB
C
/* 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*/);
|
|
}
|
|
}
|