network/tun: rewritting tun driver and tun module for modern code base

Change-Id: Idf7ab6d2aa2b4f8cb4893053b531d2eae7418427
Reviewed-on: https://review.haiku-os.org/c/haiku/+/6608
Reviewed-by: Jérôme Duval <[email protected]>
Reviewed-by: Alex von Gluck IV <[email protected]>
This commit is contained in:
Swangeon
2023-11-17 16:58:45 +00:00
committed by Alex von Gluck IV
parent 35c4600e21
commit 47f43bccee
7 changed files with 773 additions and 432 deletions
-9
View File
@@ -14,15 +14,6 @@
#define NET_TUN_MODULE_NAME "network/devices/tun/v1" #define NET_TUN_MODULE_NAME "network/devices/tun/v1"
struct tun_device : net_device {
//queue
/*
int fd;
uint32 frame_size;*/
};
struct tun_module_info { struct tun_module_info {
struct net_device_module_info; struct net_device_module_info;
@@ -1,5 +1,6 @@
SubDir HAIKU_TOP src add-ons kernel drivers network ; SubDir HAIKU_TOP src add-ons kernel drivers network ;
HaikuSubInclude ether ; HaikuSubInclude ether ;
HaikuSubInclude tun ;
HaikuSubInclude wlan ; HaikuSubInclude wlan ;
HaikuSubInclude wwan ; HaikuSubInclude wwan ;
+10 -4
View File
@@ -1,9 +1,15 @@
SubDir HAIKU_TOP src add-ons kernel drivers network tun ; SubDir HAIKU_TOP src add-ons kernel drivers network tun ;
SubDirHdrs [ FDirName $(HAIKU_TOP) src add-ons kernel network protocols tcp ] ;
UsePrivateHeaders drivers kernel net ;
UsePrivateKernelHeaders ;
UsePrivateSystemHeaders ; UsePrivateSystemHeaders ;
# For net_tun.h
UsePrivateHeaders net ;
KernelAddon tun_config : KernelAddon tun_driver :
driver.c BufferQueue.cpp
driver.cpp
; ;
SEARCH on [ FGristFiles
BufferQueue.cpp
] = [ FDirName $(HAIKU_TOP) src add-ons kernel network protocols tcp ] ;
@@ -1,379 +0,0 @@
/*
* /dev/config/tun network tunnel driver for BeOS
* (c) 2003, mmu_man, [email protected]
* licenced under MIT licence.
*/
#include <Drivers.h>
#include <KernelExport.h>
#include <OS.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <fsproto.h>
#include "bone_tun.h"
const char * device_names[] = {TUN_DRIVER_NAME, NULL};
extern device_hooks tun_hooks;
int32 api_version = B_CUR_DRIVER_API_VERSION;
vint32 if_mod_ref_count = 0;
bone_tun_interface_info_t *gIfaceModule = NULL;
bone_util_info_t *gUtil = NULL;
status_t
init_hardware(void)
{
dprintf("tun:init_hardware()\n");
return B_OK;
}
status_t
init_driver(void)
{
dprintf("tun:init_driver()\n");
return B_OK;
}
void
uninit_driver(void)
{
dprintf("tun:uninit_driver()\n");
}
const char**
publish_devices()
{
return device_names;
}
device_hooks*
find_device(const char *name)
{
(void)name;
return &tun_hooks;
}
status_t
tun_open(const char *name, uint32 flags, cookie_t **cookie)
{
status_t err = B_OK;
(void)name; (void)flags;
/* XXX: add O_NONBLOCK + FIONBIO */
#if DEBUG > 1
dprintf("tun:open(%s, 0x%08lx,)\n", name, flags);
#endif
err = get_module(BONE_UTIL_MOD_NAME, (struct module_info **)&gUtil);
if (err < B_OK)
return err;
err = get_module(TUN_INTERFACE_MODULE, (struct module_info **)&gIfaceModule);
if (err < B_OK) {
put_module(BONE_UTIL_MOD_NAME);
return err;
}
/* XXX: FIXME, still not ok (rescan) */
if (atomic_add(&if_mod_ref_count, 1) < 1) /* force one more open to keep loaded */
get_module(TUN_INTERFACE_MODULE, (struct module_info **)&gIfaceModule);
*cookie = (void*)malloc(sizeof(cookie_t));
if (*cookie == NULL) {
dprintf("tun_open : error allocating cookie\n");
goto err0;
}
memset(*cookie, 0, sizeof(cookie_t));
(*cookie)->blocking_io = true;
return B_OK;
err1:
dprintf("tun_open : cleanup : will free cookie\n");
free(*cookie);
*cookie = NULL;
put_module(TUN_INTERFACE_MODULE);
put_module(BONE_UTIL_MOD_NAME);
err0:
return B_ERROR;
}
status_t
tun_close(void *cookie)
{
(void)cookie;
return B_OK;
}
status_t
tun_free(cookie_t *cookie)
{
status_t err = B_OK;
#if DEBUG > 1
dprintf("tun_close()\n");
#endif
if (cookie->iface)
err = gIfaceModule->tun_detach_driver(cookie->iface, true);
free(cookie);
atomic_add(&if_mod_ref_count, -1);
put_module(TUN_INTERFACE_MODULE);
put_module(BONE_UTIL_MOD_NAME);
return err;
}
status_t
tun_ioctl(cookie_t *cookie, uint32 op, void *data, size_t len)
{
ifreq_t *ifr;
bone_tun_if_interface_t *iface;
(void)cookie; (void)op; (void)data; (void)len;
iface = cookie->iface;
#if DEBUG > 1
dprintf("tun_ioctl(%d(0x%08lx), , %d)\n", op, op, len);
#endif
switch (op) {
case B_SET_NONBLOCKING_IO:
cookie->blocking_io = false;
return B_OK;
case B_SET_BLOCKING_IO:
cookie->blocking_io = true;
return B_OK;
case TUNSETNOCSUM:
return B_OK;//EOPNOTSUPP;
case TUNSETDEBUG:
return B_OK;//EOPNOTSUPP;
case TUNSETIFF:
if (data == NULL)
return EINVAL;
ifr = (ifreq_t *)data;
iface = gIfaceModule->tun_reuse_or_create(ifr, cookie);
if (iface != NULL) {
dprintf("tun: new tunnel created: %s, flags: 0x%08lx\n", ifr->ifr_name, iface->flags);
return B_OK;
} else
dprintf("tun: can't allocate a new tunnel!\n");
break;
case SIOCGIFHWADDR:
if (data == NULL)
return EINVAL;
ifr = (ifreq_t *)data;
if (iface == NULL)
return EINVAL;
if (strncmp(ifr->ifr_name, iface->ifn->if_name, IFNAMSIZ) != 0)
return EINVAL;
memcpy(ifr->ifr_hwaddr, iface->fakemac.octet, 6);
return B_OK;
case SIOCSIFHWADDR:
if (data == NULL)
return EINVAL;
ifr = (ifreq_t *)data;
if (iface == NULL)
return EINVAL;
if (strncmp(ifr->ifr_name, iface->ifn->if_name, IFNAMSIZ) != 0)
return EINVAL;
memcpy(iface->fakemac.octet, ifr->ifr_hwaddr, 6);
return B_OK;
}
return B_ERROR;
}
status_t
tun_read(cookie_t *cookie, off_t position, void *data, size_t *numbytes)
{
bone_data_t *bdata;
uint32 got;
ssize_t pktsize;
if (cookie->iface == NULL)
return EBUSY;
//if ((pktsize = gUtil->dequeue_fifo_data(cookie->iface->wfifo, &bdata, B_INFINITE_TIMEOUT, false)) < 0)
pktsize = gUtil->dequeue_fifo_data(cookie->iface->wfifo, &bdata, cookie->blocking_io?B_INFINITE_TIMEOUT:0LL, false);
if (pktsize < 0) {
*numbytes = 0;
return pktsize;
}
#if DEBUG > 2
dprintf("tun: dequeue = %ld, datalen = %ld, \n", pktsize, bdata?bdata->datalen:0);
#endif
pktsize = (ssize_t)bdata->datalen;
got = gUtil->copy_from_data(bdata, 0L, data, MIN((bdata->datalen), (*numbytes)));
//got = MIN((*numbytes), bdata->datalen);
if ((pktsize > *numbytes) && !(cookie->iface->flags & TUN_NO_PI))
((struct tun_pi *)data)->flags |= TUN_PKT_STRIP;
gUtil->delete_data(bdata);
#if DEBUG > 2
dprintf("tun: read() got %ld bytes, buf is %ld\n", got, *numbytes);
dump_data(bdata);
iovec_dump_data(bdata);
#endif
*numbytes = got;
return B_OK;
ERROR_EOF:
*numbytes = 0;
return B_OK;
}
status_t
tun_write(cookie_t *cookie, off_t position, const void *data, size_t *numbytes)
{
bone_data_t *bdata = NULL;
void *buf;
status_t err = B_NO_MEMORY;
(void)position;
/* XXX: max for *numbytes ? */
if (cookie->iface == NULL)
return EBUSY;
bdata = gUtil->new_data();
if (bdata == NULL)
return B_NO_MEMORY;
buf = gUtil->falloc(*numbytes);
if (buf == NULL)
goto ERROR_1;
memcpy(buf, data, *numbytes);
if (gUtil->prepend_data(bdata, buf, *numbytes, gUtil->free) < 0) {
gUtil->free(buf);
goto ERROR_1;
}
if ((err = gUtil->enqueue_fifo_data(cookie->iface->rfifo, bdata)) < B_OK)
goto ERROR_1;
return B_OK;
ERROR_1:
if (bdata)
gUtil->delete_data(bdata);
return err;
}
status_t
tun_select(cookie_t *cookie, uint8 event, uint32 ref, selectsync *sync)
{
status_t err = B_OK;
#if DEBUG > 1
dprintf("tun: select(, %d, %ld, )\n", event, ref);
#endif
if (cookie->iface == NULL)
return B_NO_INIT;
if (event > B_SELECT_EXCEPTION || !event)
return EINVAL;
err = gUtil->lock_benaphore(&cookie->iface->lock);
if (err < B_OK)
return err;
/* iface LOCKED */
if (cookie->iface->sel[event].sync)
err = EALREADY;
else {
bone_fifo_t *fifo = NULL;
switch (event) {
case B_SELECT_READ:
fifo = cookie->iface->wfifo;
break;
case B_SELECT_WRITE:
fifo = cookie->iface->rfifo;
break;
}
if (fifo) {
/* XXX: is it safe ??? shouldn't we dequeue(peek=true) ? */
err = gUtil->lock_benaphore(&fifo->lock);
if (err >= B_OK) {
bool avail;
switch (event) {
case B_SELECT_READ:
avail = (fifo->current_bytes > 1);
break;
case B_SELECT_WRITE:
avail = ((fifo->max_bytes - fifo->current_bytes) > cookie->iface->ifn->if_mtu);
break;
}
/* check if we don't already have the event */
if (avail) {
notify_select_event(sync, ref);
}
else {
cookie->iface->sel[event].sync = sync;
cookie->iface->sel[event].ref = ref;
}
gUtil->unlock_benaphore(&fifo->lock);
}
} else {
cookie->iface->sel[event].sync = sync;
cookie->iface->sel[event].ref = ref;
}
}
gUtil->unlock_benaphore(&cookie->iface->lock);
/* iface UNLOCKED */
return err;
}
status_t
tun_deselect(cookie_t *cookie, uint8 event, selectsync *sync)
{
status_t err = B_OK;
#if DEBUG > 1
dprintf("tun: deselect(, %d, )\n", event);
#endif
if (cookie->iface == NULL)
return B_NO_INIT;
if (event > B_SELECT_EXCEPTION || !event)
return EINVAL;
err = gUtil->lock_benaphore(&cookie->iface->lock);
if (err < B_OK)
return err;
cookie->iface->sel[event].sync = NULL;
cookie->iface->sel[event].ref = 0;
gUtil->unlock_benaphore(&cookie->iface->lock);
/* iface LOCKED */
return B_OK;
}
status_t
tun_readv(cookie_t *cookie, off_t position, const iovec *vec, size_t count, size_t *numBytes)
{
dprintf("tun: readv(, %lld, , %ld)\n", position, count);
return EOPNOTSUPP;
}
status_t
tun_writev(cookie_t *cookie, off_t position, const iovec *vec, size_t count, size_t *numBytes)
{
dprintf("tun: writev(, %lld, , %ld)\n", position, count);
return EOPNOTSUPP;
}
device_hooks tun_hooks = {
(device_open_hook)tun_open,
tun_close,
(device_free_hook)tun_free,
(device_control_hook)tun_ioctl,
(device_read_hook)tun_read,
(device_write_hook)tun_write,
(device_select_hook)tun_select,
(device_deselect_hook)tun_deselect,
(device_readv_hook)tun_readv,
(device_writev_hook)tun_writev
};
@@ -0,0 +1,446 @@
/*
* TUN/TAP Network Tunnel Driver for Haiku
* Copyright 2003 mmu_man, [email protected]
* Copyright 2023 Sean Brady, [email protected]
*
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include <Drivers.h>
#include <KernelExport.h>
#include <OS.h>
#include <Select.h>
#include "BufferQueue.h"
#include <net_buffer.h>
#include <condition_variable.h>
#include <fs/select_sync_pool.h>
#include <net/if_tun.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/types.h>
#include <unistd.h>
#include <util/AutoLock.h>
// #define TUN_DRIVER_NAME "tun/0"
#define TAP_DRIVER_NAME "tap/0"
#define NET_TUN_MODULE_NAME "network/devices/tun/v1"
#define BUFFER_QUEUE_MAX 30000
const char* device_names[] = {TAP_DRIVER_NAME, NULL};
typedef struct tun_struct {
uint32_t name[3];
unsigned long flags;
BufferQueue* sendQueue;
BufferQueue* recvQueue;
ConditionVariable* readWait;
mutex readLock;
union {
struct {
mutex selectLock;
} app;
};
} tun_struct;
int32 api_version = B_CUR_DRIVER_API_VERSION;
static BufferQueue gAppQueue(BUFFER_QUEUE_MAX);
static BufferQueue gIntQueue(BUFFER_QUEUE_MAX);
static ConditionVariable gIntWait;
static select_sync_pool* gSelectPool = NULL;
struct net_buffer_module_info* gBufferModule;
/**
* @brief Creates a filled net_buffer based on the given data and size.
*
* @param data the pointer to the data that will be copied to the net_buffer
* @param bytes the size of the data in bytes
*
* @return a net_buffer pointer containing the copied data, or NULL if creation fails
*/
static net_buffer*
create_filled_buffer(uint8* data, size_t bytes)
{
net_buffer* buffer = gBufferModule->create(256);
if (buffer == NULL)
return NULL;
status_t status = gBufferModule->append(buffer, data, bytes);
if (status != B_OK) {
gBufferModule->free(buffer);
return NULL;
}
return buffer;
}
/**
* @brief Retrieves a packet from the given buffer queue.
*
* @param queueToUse The buffer queue to retrieve the packet from.
* @param data A pointer to the memory location where the packet data will be copied.
* @param numbytes A pointer to the variable that will store the size of the retrieved packet.
*
* @return The status of the retrieval operation or B_OK if successful.
*/
static status_t
retrieve_packet(BufferQueue* queueToUse, void* data, size_t* numbytes)
{
net_buffer* buffer;
status_t status = B_OK;
if (queueToUse->Used() > 0) {
status_t status = queueToUse->Get(*numbytes, true, &buffer);
if (status != B_OK)
return status;
*numbytes = buffer->size;
status = gBufferModule->read(buffer, 0, data, *numbytes);
gBufferModule->free(buffer);
} else
*numbytes = 0;
return status;
}
/**
* A helper function to notify events based on the readability and writability
* of a file descriptor.
*
* @param readable A boolean indicating if the file descriptor is readable.
* @param writable A boolean indicating if the file descriptor is writable.
*
* @return void
*
* @throws None
*/
static void
notify_select_helper(bool readable, bool writable)
{
if (readable) {
notify_select_event_pool(gSelectPool, B_SELECT_READ);
}
if (writable) {
notify_select_event_pool(gSelectPool, B_SELECT_WRITE);
}
}
/**
* @brief Notifies the select event pool if the tun device is readable or writable.
*
* @param tun The tun_struct object representing the tun device.
*/
static void
tun_notify(tun_struct* tun)
{
/* This function is just for the APP side but both sides need to call it */
bool select_pool_check = gSelectPool != NULL;
if (select_pool_check) {
bool readable = tun->recvQueue->Used() > 0;
bool writable = tun->sendQueue->Used() < BUFFER_QUEUE_MAX;
if ((tun->flags & O_NONBLOCK) != 0) // APP Side
notify_select_helper(readable, writable);
else /* IFACE side switches since its queues are flipped from APP*/
notify_select_helper(writable, readable);
}
}
/**
* @brief First initialization step for the driver.
*
* @return the status of the initialization process.
*/
status_t
init_hardware(void)
{
/* No Hardware */
dprintf("tun:init_hardware()\n");
return B_NO_ERROR;
}
/**
* @brief Initializes the driver and the net buffer module for packet operations.
*
* @return the status of the initialization.
*/
status_t
init_driver(void)
{
/* Init driver */
dprintf("tun:init_driver()\n");
status_t status = get_module(NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule);
if (status != B_OK)
return status;
return B_OK;
}
/**
* @brief Uninitializes the driver.
*
* @return void
*/
void
uninit_driver(void)
{
dprintf("tun:uninit_driver()\n");
put_module(NET_BUFFER_MODULE_NAME);
}
/**
* @brief Sets the tun_struct for this session of open.
*
* @param name the name of the interface
* @param flags the flags for the interface
* @param cookie a pointer to store the cookie
*
* @return the status of the function
*/
status_t
tun_open(const char* name, uint32 flags, void** cookie)
{
/* Setup driver for interface here */
tun_struct* tun = new tun_struct();
memcpy(tun->name, "app", sizeof(tun->name));
tun->sendQueue = &gIntQueue;
tun->recvQueue = &gAppQueue;
tun->flags = 0;
tun->readWait = &gIntWait;
mutex_init(&tun->readLock, "read_avail");
mutex_init(&tun->app.selectLock, "select_lock");
*cookie = static_cast<void*>(tun);
return B_OK;
}
/**
* @brief Close the open instance.
*
* @param cookie a pointer to the tun_struct object to be used
*
* @return The status of the function
*/
status_t
tun_close(void* cookie)
{
/* Close interface here */
tun_struct* tun = static_cast<tun_struct*>(cookie);
if ((tun->flags & O_NONBLOCK) == 0) {
tun->readWait->NotifyAll(B_ERROR);
snooze(10); // Due to a lock timing issue, we sleep for 10ms
}
return B_OK;
}
/**
* @brief Frees the memory allocated for a tun_struct object.
*
* @param cookie a pointer to the tun_struct object to be freed
*
* @return a status_t indicating the result of the operation
*/
status_t
tun_free(void* cookie)
{
tun_struct* tun = static_cast<tun_struct*>(cookie);
mutex_destroy(&tun->readLock);
if ((tun->flags & O_NONBLOCK) != 0)
mutex_destroy(&tun->app.selectLock);
delete tun;
return B_OK;
}
/**
* @brief Updates the tun interface based on the given IOCTL operation and data.
*
* @param cookie a pointer to the tun_struct object
* @param op the IOCTL operation to perform
* @param data a pointer to the data to be used in the IOCTL operation
* @param len the size of the data in bytes
*
* @return the status of the IOCTL operation
*/
status_t
tun_ioctl(void* cookie, uint32 op, void* data, size_t len)
{
/* IOCTL for driver */
tun_struct* tun = static_cast<tun_struct*>(cookie);
switch (op) {
case TUNSETIFF: // Reconfigures tun_struct to interface settings
memcpy(tun->name, "int", sizeof(tun->name));
tun->sendQueue = &gAppQueue;
tun->recvQueue = &gIntQueue;
mutex_destroy(&tun->app.selectLock);
memset(&tun->app, 0, sizeof(tun->app));
return B_OK;
case B_SET_NONBLOCKING_IO:
tun->flags |= O_NONBLOCK;
return B_OK;
default:
return B_DEV_INVALID_IOCTL;
};
return B_OK;
}
/**
* @brief Reads data from the TUN/TAP device.
*
* @param cookie a pointer to the tun_struct
* @param position the position in the data stream to read from
* @param data a pointer to the buffer where the read data will be copied
* @param numbytes a pointer to the size to read, updated with the number of bytes read
*
* @return the status of the read operation
*/
status_t
tun_read(void* cookie, off_t position, void* data, size_t* numbytes)
{
tun_struct* tun = static_cast<tun_struct*>(cookie);
status_t status;
MutexLocker _(tun->readLock); // released on exit
/* IFACE side is blocking I/O */
if ((tun->flags & O_NONBLOCK) == 0) {
while (tun->recvQueue->Used() == 0) {
status = tun->readWait->Wait(&tun->readLock, B_CAN_INTERRUPT);
if (status != B_OK)
return status;
}
}
status = retrieve_packet(tun->recvQueue, data, numbytes);
tun_notify(tun);
return status;
}
/**
* @brief Writes data to the specified sending queue.
*
* @param cookie a pointer to the tun_struct
* @param position the position in the file to write to (NOT USED)
* @param data a pointer to the data to write
* @param numbytes a pointer to the number of bytes to write
*
* @return a status code indicating the result of the write operation
*/
status_t
tun_write(void* cookie, off_t position, const void* data, size_t* numbytes)
{
tun_struct* tun = static_cast<tun_struct*>(cookie);
size_t used = tun->sendQueue->Used();
// Buffer is full or will be so we have to drop the packet
if ((used + *numbytes) >= BUFFER_QUEUE_MAX)
return B_WOULD_BLOCK;
net_buffer* packet = create_filled_buffer((uint8*)data, *numbytes);
if (packet == NULL)
return B_ERROR;
tun->sendQueue->Add(packet);
if ((tun->flags & O_NONBLOCK) != 0)
tun->readWait->NotifyOne();
tun_notify(tun);
return B_OK;
}
/**
* @brief Handles the selection of read and write events on a TUN/TAP device.
*
* @param cookie a pointer to the tun_struct object
* @param event the event type (B_SELECT_READ or B_SELECT_WRITE are accepted)
* @param ref the reference number
* @param sync a pointer to the selectsync object
*
* @return the status of the select operation
*/
status_t
tun_select(void* cookie, uint8 event, uint32 ref, selectsync* sync)
{
tun_struct* tun = static_cast<tun_struct*>(cookie);
MutexLocker _(tun->app.selectLock);
bool isReadable = tun->recvQueue->Used() > 0;
bool isWritable = tun->sendQueue->Used() < BUFFER_QUEUE_MAX;
if (event != B_SELECT_READ && event != B_SELECT_WRITE)
return B_BAD_VALUE;
status_t status = add_select_sync_pool_entry(&gSelectPool, sync, event);
if (status != B_OK)
return B_BAD_VALUE;
if (event == B_SELECT_READ && isReadable)
notify_select_event(sync, event);
if (event == B_SELECT_WRITE && isWritable)
notify_select_event(sync, event);
return status;
}
/**
* @brief Deselects a event in the select pool.
*
* @param cookie a pointer to the tun_struct object representing the tun device
* @param event the event type to be deselected (B_SELECT_READ or B_SELECT_WRITE)
* @param sync a pointer to the selectsync object to be removed from the select pool
*
* @return the status code indicating the result of the deselect operation
*/
status_t
tun_deselect(void* cookie, uint8 event, selectsync* sync)
{
tun_struct* tun = static_cast<tun_struct*>(cookie);
MutexLocker _(tun->app.selectLock);
if (event != B_SELECT_READ && event != B_SELECT_WRITE)
return B_BAD_VALUE;
return remove_select_sync_pool_entry(&gSelectPool, sync, event);
}
/**
* @brief Publishes the driver names to devfs.
*
* @return A pointer to a constant character pointer representing the device names.
*/
const char**
publish_devices()
{
return device_names;
}
device_hooks tun_hooks = {
tun_open,
tun_close,
tun_free,
tun_ioctl,
tun_read,
tun_write,
tun_select,
tun_deselect,
NULL,
NULL
};
/**
* @brief Finds hooks for driver functions.
*
* @param name the name of the device.
*
* @return a pointer to the device hooks (all devices return the same hooks)
*/
device_hooks*
find_device(const char* name)
{
return &tun_hooks;
}
+314 -40
View File
@@ -1,126 +1,394 @@
/* /*
* Copyright 2006-2007, Haiku, Inc. All Rights Reserved. * Copyright 2023 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
* *
* Authors: * Authors:
* Axel Dörfler, [email protected] * Axel Dörfler, [email protected]
* Sean Brady, [email protected]
*/ */
#include <net_tun.h> #include <ethernet.h>
#include <net_buffer.h> #include <net_buffer.h>
#include <net_datalink.h>
#include <net_device.h> #include <net_device.h>
#include <net_stack.h> #include <net_stack.h>
#include <net_tun.h>
#include <ByteOrder.h>
#include <Drivers.h>
#include <lock.h>
#include <util/AutoLock.h>
#include <util/DoublyLinkedList.h>
#include <KernelExport.h> #include <KernelExport.h>
#include <NetBufferUtilities.h>
#include <debug.h>
#include <errno.h>
#include <net/if.h> #include <net/if.h>
#include <net/if_types.h>
#include <net/if_media.h> #include <net/if_media.h>
#include <net/if_tun.h>
#include <net/if_types.h>
#include <new> #include <new>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <util/Random.h>
struct tun_device : net_device, DoublyLinkedListLinkImpl<tun_device>
{
~tun_device()
{
free(read_buffer);
free(write_buffer);
}
int fd;
uint32 frame_size;
void *read_buffer, *write_buffer;
mutex read_buffer_lock, write_buffer_lock;
};
struct net_buffer_module_info* gBufferModule; struct net_buffer_module_info* gBufferModule;
static struct net_stack_module_info* sStackModule; static struct net_stack_module_info* sStackModule;
//static mutex sListLock;
//static DoublyLinkedList<ethernet_device> sCheckList;
// #pragma mark - // #pragma mark -
/**
* @brief Prepends an ethernet frame to the given net buffer packet.
*
* @param buffer pointer to the net buffer to prepend the ethernet frame to
*
* @return the status of the operation
*/
static status_t
prepend_ethernet_frame(net_buffer* buffer)
{
NetBufferPrepend<ether_header> bufferHeader(buffer);
if (bufferHeader.Status() != B_OK)
return bufferHeader.Status();
ether_header &header = bufferHeader.Data();
header.type = B_HOST_TO_BENDIAN_INT16(ETHER_TYPE_IP);
memset(header.source, 0, ETHER_ADDRESS_LENGTH);
memset(header.destination, 0, ETHER_ADDRESS_LENGTH);
bufferHeader.Sync();
return B_OK;
}
/**
* @brief Removes the ethernet header from the given network buffer.
*
* @param buffer Pointer to the network buffer.
*
* @return The status of the operation.
*/
static status_t
ethernet_header_deframe(net_buffer* buffer)
{
NetBufferHeaderRemover<ether_header> bufferHeader(buffer);
if (bufferHeader.Status() != B_OK)
return bufferHeader.Status();
return B_OK;
}
/**
* @brief Initializes a TUN/TAP interface with the given name.
*
* @param name The name of the TUN/TAP interface.
* @param _device A pointer to a net_device pointer for the newly created interface.
*
* @return The status of the initialization. Returns B_OK if successful, or an error code otherwise.
*/
status_t status_t
tun_init(const char* name, net_device** _device) tun_init(const char* name, net_device** _device)
{ {
tun_device* device;
if (strncmp(name, "tun", 3) if (strncmp(name, "tun", 3)
&& strncmp(name, "tap", 3) && strncmp(name, "tap", 3)
&& strncmp(name, "dns", 3)) /* iodine uses that */ && strncmp(name, "dns", 3)) /* iodine uses that */
return B_BAD_VALUE; return B_BAD_VALUE;
device = new (std::nothrow) tun_device; tun_device* device = new (std::nothrow) tun_device;
if (device == NULL) { if (device == NULL)
return B_NO_MEMORY; return B_NO_MEMORY;
}
memset(device, 0, sizeof(tun_device)); memset(device, 0, sizeof(tun_device));
strcpy(device->name, name); strcpy(device->name, name);
if (strncmp(name, "tun", 3) == 0) { if (strncmp(name, "tun", 3) == 0) {
device->flags = IFF_LOOPBACK | IFF_LINK; device->flags = IFF_POINTOPOINT | IFF_LINK;
device->type = IFT_TUN; device->type = IFT_ETHER;
} else if (strncmp(name, "tap", 3) == 0) { } else if (strncmp(name, "tap", 3) == 0) {
device->flags = IFF_BROADCAST | IFF_ALLMULTI | IFF_LINK; device->flags = IFF_BROADCAST | IFF_ALLMULTI | IFF_LINK;
device->type = IFT_ETHER; device->type = IFT_ETHER;
} else { /* Set the first two bits to prevent it from becoming a multicast address */
device->address.data[0] = 0x00;
device->address.data[1] = 0xFF;
for (int i = 2; i < ETHER_ADDRESS_LENGTH; i++) {
int val = random_value();
device->address.data[i] = val * 0x11;
}
device->address.length = ETHER_ADDRESS_LENGTH;
} else
return B_BAD_VALUE; return B_BAD_VALUE;
}
device->mtu = 1500; /* Almost all VPN MTU's are no more than 1500 bytes */ device->mtu = 1500;
device->media = IFM_ACTIVE; device->media = IFM_ACTIVE | IFM_ETHER;
device->frame_size = ETHER_MAX_FRAME_SIZE;
device->header_length = ETHER_HEADER_LENGTH;
device->fd = -1;
mutex_init(&device->read_buffer_lock, "tun read_buffer");
mutex_init(&device->write_buffer_lock, "tun write_buffer");
*_device = device; *_device = device;
return B_OK; return B_OK;
} }
/**
* @brief Uninitializes a TUN/TAP interface.
*
* @param _device the net_device to uninitialize
*
* @return the status of the uninitialization process
*/
status_t status_t
tun_uninit(net_device* _device) tun_uninit(net_device* _device)
{ {
tun_device* device = (tun_device*)_device; tun_device* device = (tun_device*)_device;
close(device->fd);
put_module(NET_STACK_MODULE_NAME); device->fd = -1;
put_module(NET_BUFFER_MODULE_NAME); mutex_destroy(&device->read_buffer_lock);
mutex_destroy(&device->write_buffer_lock);
delete device; delete device;
return B_OK; return B_OK;
} }
/**
* @brief Sets up the TUN/TAP network interface to be used.
*
* @param _device the network device to set up
*
* @return the status of the setup process
*/
status_t status_t
tun_up(net_device* device) tun_up(net_device* _device)
{ {
tun_device* device = (tun_device*)_device;
device->fd = open("/dev/tap/0", O_RDWR);
if (device->fd < 0)
return errno;
ioctl(device->fd, TUNSETIFF);
return B_OK; return B_OK;
} }
void /**
tun_down(net_device* device) * @brief Closes the TUN/TAP network interface to not be used.
*
* @param _device A pointer to the net_device structure representing the tun_device.
*
* @return void
*/
void tun_down(net_device* _device)
{ {
tun_device* device = (tun_device*)_device;
close(device->fd);
device->fd = -1;
} }
status_t status_t
tun_control(net_device* device, int32 op, void* argument, tun_control(net_device* device, int32 op, void* argument,
size_t length) size_t length)
{ {
return B_BAD_VALUE; return B_BAD_VALUE;
} }
/**
* @brief Writes data to the TUN/TAP network driver.
*
* @param _device the network device to get data from network stack
* @param buffer the buffer containing the data to write
*
* @return the status of the operation
*
* @throws ErrorType if an error occurs during the operation
*/
status_t status_t
tun_send_data(net_device* device, net_buffer* buffer) tun_send_data(net_device* _device, net_buffer* buffer)
{ {
return sStackModule->device_enqueue_buffer(device, buffer); tun_device* device = (tun_device*)_device;
status_t status;
if (strncmp(device->name, "tun", 3) == 0) {
status = ethernet_header_deframe(buffer);
if (status != B_OK)
return B_ERROR;
}
if (buffer->size > device->mtu)
return B_BAD_VALUE;
net_buffer* allocated = NULL;
net_buffer* original = buffer;
MutexLocker bufferLocker;
struct iovec iovec;
if (gBufferModule->count_iovecs(buffer) > 1) {
if (device->write_buffer != NULL) {
bufferLocker.SetTo(device->write_buffer_lock, false);
status_t status = gBufferModule->read(buffer, 0,
device->write_buffer, buffer->size);
if (status != B_OK)
return status;
iovec.iov_base = device->write_buffer;
iovec.iov_len = buffer->size;
} else {
// Fall back to creating a new buffer.
allocated = gBufferModule->duplicate(original);
if (allocated == NULL)
return ENOBUFS;
buffer = allocated;
if (gBufferModule->count_iovecs(allocated) > 1) {
dprintf("tun_send_data: no write buffer, cannot perform scatter I/O\n");
gBufferModule->free(allocated);
device->stats.send.errors++;
return B_NOT_SUPPORTED;
}
gBufferModule->get_iovecs(buffer, &iovec, 1);
}
} else {
gBufferModule->get_iovecs(buffer, &iovec, 1);
}
ssize_t bytesWritten = write(device->fd, iovec.iov_base, iovec.iov_len);
if (bytesWritten < 0) {
device->stats.send.errors++;
if (allocated)
gBufferModule->free(allocated);
return errno;
}
device->stats.send.packets++;
device->stats.send.bytes += bytesWritten;
gBufferModule->free(original);
if (allocated)
gBufferModule->free(allocated);
return B_OK;
} }
/**
* @brief Reads data from the TUN/TAP network driver.
*
* @param _device the network device to put the data into the network stack
* @param buffer the packet to be filled and sent to the network stack
*
* @return the status of the operation
*
* @throws ErrorType if an error occurs during the operation
*/
status_t
tun_receive_data(net_device* _device, net_buffer** _buffer)
{
/* Recieve Data */
tun_device* device = (tun_device*)_device;
if (device->fd == -1)
return B_FILE_ERROR;
// TODO: better header space
net_buffer* buffer = gBufferModule->create(256);
if (buffer == NULL)
return ENOBUFS;
MutexLocker bufferLocker;
struct iovec iovec;
size_t bytesRead;
status_t status;
if (device->read_buffer != NULL) {
bufferLocker.SetTo(device->read_buffer_lock, false);
iovec.iov_base = device->read_buffer;
iovec.iov_len = device->frame_size;
} else {
void* data;
status = gBufferModule->append_size(buffer, device->mtu, &data);
if (status == B_OK && data == NULL) {
dprintf("tun_receive_data: no read buffer, cannot perform scattered I/O!\n");
status = B_NOT_SUPPORTED;
}
if (status < B_OK) {
gBufferModule->free(buffer);
return status;
}
iovec.iov_base = data;
iovec.iov_len = device->frame_size;
}
bytesRead = read(device->fd, iovec.iov_base, iovec.iov_len);
if (bytesRead < 0 || iovec.iov_base == NULL) {
device->stats.receive.errors++;
status = errno;
gBufferModule->free(buffer);
return status;
}
if (strncmp(device->name, "tun", 3) == 0) {
status = prepend_ethernet_frame(buffer);
if (status != B_OK)
return status;
}
if (iovec.iov_base == device->read_buffer)
status = gBufferModule->append(buffer, iovec.iov_base, buffer->size);
else
status = gBufferModule->trim(buffer, buffer->size);
if (status < B_OK) {
device->stats.receive.dropped++;
gBufferModule->free(buffer);
return status;
}
device->stats.receive.bytes += bytesRead;
device->stats.receive.packets++;
*_buffer = buffer;
return B_OK;
}
/**
* @brief Sets the maximum transmission unit (MTU) for a network interface.
*
* @param device a pointer to the network interface
* @param mtu the desired MTU value
*
* @return the status of the operation (B_OK if successful, B_BAD_VALUE if the
* provided MTU is out of range)
*/
status_t status_t
tun_set_mtu(net_device* device, size_t mtu) tun_set_mtu(net_device* device, size_t mtu)
{ {
if (mtu > 65536 || mtu < 16) if (mtu > 65536 || mtu < 16)
return B_BAD_VALUE; return B_BAD_VALUE;
device->mtu = mtu; device->mtu = mtu;
return B_OK; return B_OK;
} }
status_t status_t
tun_set_promiscuous(net_device* device, bool promiscuous) tun_set_promiscuous(net_device* device, bool promiscuous)
{ {
@@ -138,6 +406,7 @@ tun_set_media(net_device* device, uint32 media)
status_t status_t
tun_add_multicast(net_device* device, const sockaddr* address) tun_add_multicast(net_device* device, const sockaddr* address)
{ {
/* Nothing to do for multicast filters as we always accept all frames. */
return B_OK; return B_OK;
} }
@@ -149,18 +418,24 @@ tun_remove_multicast(net_device* device, const sockaddr* address)
} }
/**
* @brief Performs various operations on the TUN/TAP network interface.
*
* @param op The operation to be performed.
* @param ... Additional arguments based on the operation.
*
* @return The status of the operation.
*/
static status_t static status_t
tun_std_ops(int32 op, ...) tun_std_ops(int32 op, ...)
{ {
switch (op) { switch (op) {
case B_MODULE_INIT: case B_MODULE_INIT:
{ {
status_t status = get_module(NET_STACK_MODULE_NAME, status_t status = get_module(NET_STACK_MODULE_NAME, (module_info**)&sStackModule);
(module_info**)&sStackModule);
if (status < B_OK) if (status < B_OK)
return status; return status;
status = get_module(NET_BUFFER_MODULE_NAME, status = get_module(NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule);
(module_info**)&gBufferModule);
if (status < B_OK) { if (status < B_OK) {
put_module(NET_STACK_MODULE_NAME); put_module(NET_STACK_MODULE_NAME);
return status; return status;
@@ -189,16 +464,15 @@ net_device_module_info sTunModule = {
tun_down, tun_down,
tun_control, tun_control,
tun_send_data, tun_send_data,
NULL, // receive_data tun_receive_data,
tun_set_mtu, tun_set_mtu,
tun_set_promiscuous, tun_set_promiscuous,
tun_set_media, tun_set_media,
tun_add_multicast, tun_add_multicast,
tun_remove_multicast, tun_remove_multicast,
}; };
module_info* modules[] = { module_info* modules[] = {
(module_info*)&sTunModule, (module_info*)&sTunModule,
NULL NULL};
};
@@ -831,6 +831,8 @@ init_stack()
scan_modules("network/datalink_protocols"); scan_modules("network/datalink_protocols");
// TODO: for now! // TODO: for now!
register_domain_datalink_protocols(AF_INET, IFT_TUN,
"network/datalink_protocols/ethernet_frame/v1", NULL);
register_domain_datalink_protocols(AF_INET, IFT_LOOP, register_domain_datalink_protocols(AF_INET, IFT_LOOP,
"network/datalink_protocols/loopback_frame/v1", NULL); "network/datalink_protocols/loopback_frame/v1", NULL);
#if 0 // PPP is not (currently) included in the build #if 0 // PPP is not (currently) included in the build