David van Moolenbroek ef8d499e2d Add lwip: a new lwIP-based TCP/IP service
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
2017-04-30 13:16:03 +00:00

383 lines
9.2 KiB
C

/* LWIP service - lwip.c - main program and dispatch code */
#include "lwip.h"
#include "tcpisn.h"
#include "mcast.h"
#include "ethif.h"
#include "rtsock.h"
#include "route.h"
#include "bpfdev.h"
#include "lwip/init.h"
#include "lwip/sys.h"
#include "lwip/timeouts.h"
#include "arch/cc.h"
static int running, recheck_timer;
static minix_timer_t lwip_timer;
static void expire_lwip_timer(int);
/*
* Return the system uptime in milliseconds. Also remember that lwIP retrieved
* the system uptime during this call, so that we know to check for timer
* updates at the end of the current iteration of the message loop.
*/
uint32_t
sys_now(void)
{
recheck_timer = TRUE;
/* TODO: avoid 64-bit arithmetic if possible. */
return (uint32_t)(((uint64_t)getticks() * 1000) / sys_hz());
}
/*
* Check if and when lwIP has its next timeout, and set or cancel our timer
* accordingly.
*/
static void
set_lwip_timer(void)
{
uint32_t next_timeout;
clock_t ticks;
/* Ask lwIP when the next alarm is supposed to go off, if any. */
next_timeout = sys_timeouts_sleeptime();
/*
* Set or update the lwIP timer. We rely on set_timer() asking the
* kernel for an alarm only if the timeout is different from the one we
* gave it last time (if at all). However, due to conversions between
* absolute and relative times, and the fact that we cannot guarantee
* that the uptime itself does not change while executing these
* routines, set_timer() will sometimes be issuing a kernel call even
* if the alarm has not changed. Not a huge deal, but fixing this will
* require a different interface to lwIP and/or the timers library.
*/
if (next_timeout != (uint32_t)-1) {
/*
* Round up the next timeout (which is in milliseconds) to the
* number of clock ticks to add to the current time. Avoid any
* potential for overflows, no matter how unrealistic..
*/
if (next_timeout > TMRDIFF_MAX / sys_hz())
ticks = TMRDIFF_MAX;
else
ticks = (next_timeout * sys_hz() + 999) / 1000;
set_timer(&lwip_timer, ticks, expire_lwip_timer, 0 /*unused*/);
} else
cancel_timer(&lwip_timer); /* not really needed.. */
}
/*
* The timer for lwIP timeouts has gone off. Check timeouts, and possibly set
* a new timer.
*/
static void
expire_lwip_timer(int arg __unused)
{
/* Let lwIP do its work. */
sys_check_timeouts();
/*
* See if we have to update our timer for the next lwIP timer. Doing
* this here, rather than from the main loop, avoids one kernel call.
*/
set_lwip_timer();
recheck_timer = FALSE;
}
/*
* Check whether we should adjust our local timer based on a change in the next
* lwIP timeout.
*/
static void
check_lwip_timer(void)
{
/*
* We make the assumption that whenever lwIP starts a timer, it will
* need to retrieve the current time. Thus, whenever sys_now() is
* called, we set the 'recheck_timer' flag. Here, we check whether to
* (re)set our lwIP timer only if the flag is set. As a result, we do
* not have to mess with timers for literally every incoming message.
*
* When lwIP stops a timer, it does not call sys_now(), and thus, we
* may miss such updates. However, timers being stopped should be rare
* and getting too many alarm messages is not a big deal.
*/
if (!recheck_timer)
return;
set_lwip_timer();
/* Reset the flag for the next message loop iteration. */
recheck_timer = FALSE;
}
/*
* Return a random number, for use by lwIP.
*/
uint32_t
lwip_hook_rand(void)
{
/*
* The current known uses of this hook are for selection of initial
* TCP/UDP port numbers and for multicast-related timer randomness.
* The former case exists only to avoid picking the same starting port
* numbers after a reboot. After that, simple sequential iteration of
* the port numbers is used. The latter case varies the response time
* for sending multicast messages. Thus, none of the current uses of
* this function require proper randomness, and so we use the simplest
* approach, with time-based initialization to cover the reboot case.
* The sequential port number selection could be improved upon, but
* such an extension would probably bypass this hook anyway.
*/
return lrand48();
}
/*
* Create a new socket, with the given domain, type, and protocol, for the user
* process identified by 'user_endpt'. On success, return the new socket's
* identifier, with the libsockevent socket stored in 'sock' and an operations
* table stored in 'ops'. On failure, return a negative error code.
*/
static sockid_t
alloc_socket(int domain, int type, int protocol, endpoint_t user_endpt,
struct sock ** sock, const struct sockevent_ops **ops)
{
switch (domain) {
case PF_INET:
#ifdef INET6
case PF_INET6:
#endif /* INET6 */
switch (type) {
case SOCK_STREAM:
return tcpsock_socket(domain, protocol, sock, ops);
case SOCK_DGRAM:
return udpsock_socket(domain, protocol, sock, ops);
case SOCK_RAW:
if (!util_is_root(user_endpt))
return EACCES;
return rawsock_socket(domain, protocol, sock, ops);
default:
return EPROTOTYPE;
}
case PF_ROUTE:
return rtsock_socket(type, protocol, sock, ops);
case PF_LINK:
return lnksock_socket(type, protocol, sock, ops);
default:
/* This means that the service has been misconfigured. */
printf("socket() with unsupported domain %d\n", domain);
return EAFNOSUPPORT;
}
}
/*
* Initialize the service.
*/
static int
init(int type __unused, sef_init_info_t * init __unused)
{
/*
* Initialize the random number seed. See the lwip_hook_rand() comment
* on why this weak random number source is currently sufficient.
*/
srand48(clock_time(NULL));
/* Initialize the lwIP library. */
lwip_init();
/* Initialize the socket events library. */
sockevent_init(alloc_socket);
/* Initialize various helper modules. */
mempool_init();
tcpisn_init();
mcast_init();
/* Initialize the high-level socket modules. */
ipsock_init();
tcpsock_init();
udpsock_init();
rawsock_init();
/* Initialize the various network interface modules. */
ifdev_init();
loopif_init();
ethif_init();
/* Initialize the network device driver module. */
ndev_init();
/* Initialize the low-level socket modules. */
rtsock_init();
lnksock_init();
/* Initialize the routing module. */
route_init();
/* Initialize other device modules. */
bpfdev_init();
/*
* Initialize the MIB module, after all other modules have registered
* their subtrees with this module.
*/
mibtree_init();
/*
* After everything else has been initialized, set up the default
* configuration - in particular, a loopback interface.
*/
ifconf_init();
/*
* Initialize the master timer for all the lwIP timers. Just in case
* lwIP starts a timer right away, perform a first check upon entry of
* the message loop.
*/
init_timer(&lwip_timer);
recheck_timer = TRUE;
running = TRUE;
return OK;
}
/*
* Perform initialization using the System Event Framework (SEF).
*/
static void
startup(void)
{
sef_setcb_init_fresh(init);
/*
* This service requires stateless restarts, in that several parts of
* the system (including VFS and drivers) expect that if restarted,
* this service comes back up with a new endpoint. Therefore, do not
* set a _restart callback here.
*
* TODO: support for live update.
*
* TODO: support for immediate shutdown if no sockets are in use, as
* also done by UDS. For now, we never shut down immediately, giving
* other processes the opportunity to close sockets on system shutdown.
*/
sef_startup();
}
/*
* The lwIP-based TCP/IP sockets driver.
*/
int
main(void)
{
message m;
int r, ipc_status;
startup();
while (running) {
/*
* For various reasons, the loopback interface does not pass
* packets back into the stack right away. Instead, it queues
* them up for later processing. We do that processing here.
*/
ifdev_poll();
/*
* Unfortunately, lwIP does not tell us when it starts or stops
* timers. This means that we have to check ourselves every
* time we have called into lwIP. For simplicity, we perform
* the check here.
*/
check_lwip_timer();
if ((r = sef_receive_status(ANY, &m, &ipc_status)) != OK) {
if (r == EINTR)
continue; /* sef_cancel() was called */
panic("sef_receive_status failed: %d", r);
}
/* Process the received message. */
if (is_ipc_notify(ipc_status)) {
switch (m.m_source) {
case CLOCK:
expire_timers(m.m_notify.timestamp);
break;
case DS_PROC_NR:
/* Network drivers went up and/or down. */
ndev_check();
break;
default:
printf("unexpected notify from %d\n",
m.m_source);
}
continue;
}
switch (m.m_source) {
case MIB_PROC_NR:
rmib_process(&m, ipc_status);
break;
case VFS_PROC_NR:
/* Is this a socket device request? */
if (IS_SDEV_RQ(m.m_type)) {
sockevent_process(&m, ipc_status);
break;
}
/* Is this a character (or block) device request? */
if (IS_CDEV_RQ(m.m_type) || IS_BDEV_RQ(m.m_type)) {
bpfdev_process(&m, ipc_status);
break;
}
/* FALLTHROUGH */
default:
/* Is this a network device driver response? */
if (IS_NDEV_RS(m.m_type)) {
ndev_process(&m, ipc_status);
break;
}
printf("unexpected message %d from %d\n",
m.m_type, m.m_source);
}
}
return 0;
}