About
The Userspace Networking (usn) subsystem makes it possible to run a networking stack in userspace, while letting other programs use the standard socket interface. The stack registers a socket domain in the kernel. Calls for sockets that belong to this domain are then redirected to the userspace stack for processing.
How it works
In general:
- The program that implements the stack is started as a background service.
- It opens a reference to the kernel subsystem by opening /socket/usnet-init.
- It then registers a socket domain using an ioctl call.
- Other programs may now create and use sockets in the socket domain.
- Every call to the socket domain are now redirected to the program.
The redirection:
- The user calls one of many functions: socket, read, write, recv, send, recvfrom, sendto, etc.
- The call is immediately blocked by the kernel. That is, the calling thread is suspended.
- An internal object is made that describes a "job" to be done by the stack in order to handle that call.
- The handler returns and a context switch is made, allowing the stack to run.
- The stack retrieves the job from the kernel using recvfrom on the kernel interface reference that it first created.
- Processing of the request is done in the stack.
- Completion of the job is signaled to the kernel by a call to sendto.
- The kernel now unblocks the calling thread.
Benefits:
- If the network stack crashes or terminates, the rest of the system remains stable.
- In those cases, each reference to the domain is force closed and the domain is unregistered.
Functions to Implement
These are the functions that should be implemented in the user stack.
socket
int usn_handle_socket (int pid, int fd, int domain, int type, int protocol);
Input
- File descriptor and PID of socket: fd and pid
- Socket domain, will be the same as the one registered: domain
- Socket type: type
- Socket protocol: protocol
Output
- Return value: fd on success or error (negative errno)
Notes
- By the time this is called a file descriptor has already been allocated by the kernel. If the exact value of fd is not returned the kernel subsystem will assume that an error has occured and immediately free this file descriptor.
close
int usn_handle_close (int pid, int fd);
Input
- File descriptor and PID of socket: fd and pid
Output
- Return value: Success (0) or error (negative errno)
Notes
- Due to the very nature of close, do not assume that the caller will ever see the return value. This is especially true when the caller actually crashed. When this function is called the pid/fd pair is effectively "dead" and must not be used any more.
recvfrom
int usn_handle_recvfrom (unsigned char *buffer, int len,
struct usnet_queue_item_transfer *tr)
Input
- File descriptor and PID of socket: tr->fd and tr->pid
- Data buffer length: len
Output
- Data buffer: buffer
- Address option: tr->addrdat (must be casted)
- Address option length: tr->addrlen
- Return value: length of received data or error (negative errno)
sendto
int usn_handle_sendto (unsigned char *buffer, int len,
struct usnet_queue_item_transfer *tr)
Input
- File descriptor and PID of socket: tr->fd and tr->pid
- Data buffer: buffer
- Data length: len
- Address option: tr->addrdat (must be casted)
- Address option length: tr->addrlen
Output
- Return value: length of accepted data or error (negative errno)
bind
int usn_handle_bind (struct usnet_queue_item_transfer *tr, struct sockaddr_in *addr,
int len)
Input
- File descriptor and PID of socket: tr->fd and tr->pid
- Flags: tr->flags
- Address buffer: addr
- Address buffer length: len
Output
- Return value: Success (0) or error (negative errno)
listen
int usn_handle_listen (struct usnet_queue_item_transfer *tr, int backlog)
Input
- File descriptor and PID of socket: tr->fd and tr->pid
- Backlog argument: backlog
Output
- Return value: Success (0) or error (negative errno)
accept
int usn_handle_accept (struct usnet_queue_item_transfer *tr, int newfd,
struct sockaddr_in *addr, int *addrlen)
Input
- File descriptor and PID of listening socket: tr->fd and tr->pid
- File descriptor and PID of new socket: newfd and tr->pid
- Address size: *addrlen
Output
- Address that was connected: addr
- Length that was actually used: *addrlen
- Return value: New file descriptor (newfd)
connect
int usn_handle_connect (struct usnet_queue_item_transfer *tr,
struct sockaddr_in *addr, int addrlen)
Input
- File descriptor and PID of socket: tr->fd and tr->pid
- Address to connect to: addr
- Address size: addrlen
Output
- Return value: Success (0) or error (negative errno)