From 47f43bccee61e8a3b7545bfe6a71fa8afae5a036 Mon Sep 17 00:00:00 2001 From: Swangeon Date: Fri, 16 Jun 2023 15:18:19 -0500 Subject: [PATCH] network/tun: rewritting tun driver and tun module for modern code base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Idf7ab6d2aa2b4f8cb4893053b531d2eae7418427 Reviewed-on: https://review.haiku-os.org/c/haiku/+/6608 Reviewed-by: Jérôme Duval Reviewed-by: Alex von Gluck IV --- headers/private/net/net_tun.h | 9 - src/add-ons/kernel/drivers/network/Jamfile | 1 + .../kernel/drivers/network/tun/Jamfile | 14 +- .../kernel/drivers/network/tun/driver.c | 379 --------------- .../kernel/drivers/network/tun/driver.cpp | 446 ++++++++++++++++++ .../kernel/network/devices/tun/tun.cpp | 354 ++++++++++++-- src/add-ons/kernel/network/stack/stack.cpp | 2 + 7 files changed, 773 insertions(+), 432 deletions(-) delete mode 100644 src/add-ons/kernel/drivers/network/tun/driver.c create mode 100644 src/add-ons/kernel/drivers/network/tun/driver.cpp diff --git a/headers/private/net/net_tun.h b/headers/private/net/net_tun.h index e9d2eb848d..359371e85f 100644 --- a/headers/private/net/net_tun.h +++ b/headers/private/net/net_tun.h @@ -14,15 +14,6 @@ #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 net_device_module_info; diff --git a/src/add-ons/kernel/drivers/network/Jamfile b/src/add-ons/kernel/drivers/network/Jamfile index c4d07a4f57..dce7aa1a39 100644 --- a/src/add-ons/kernel/drivers/network/Jamfile +++ b/src/add-ons/kernel/drivers/network/Jamfile @@ -1,5 +1,6 @@ SubDir HAIKU_TOP src add-ons kernel drivers network ; HaikuSubInclude ether ; +HaikuSubInclude tun ; HaikuSubInclude wlan ; HaikuSubInclude wwan ; diff --git a/src/add-ons/kernel/drivers/network/tun/Jamfile b/src/add-ons/kernel/drivers/network/tun/Jamfile index 54dca5dc9c..1158560c0b 100644 --- a/src/add-ons/kernel/drivers/network/tun/Jamfile +++ b/src/add-ons/kernel/drivers/network/tun/Jamfile @@ -1,9 +1,15 @@ 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 ; -# For net_tun.h -UsePrivateHeaders net ; -KernelAddon tun_config : - driver.c +KernelAddon tun_driver : + BufferQueue.cpp + driver.cpp ; + +SEARCH on [ FGristFiles + BufferQueue.cpp + ] = [ FDirName $(HAIKU_TOP) src add-ons kernel network protocols tcp ] ; diff --git a/src/add-ons/kernel/drivers/network/tun/driver.c b/src/add-ons/kernel/drivers/network/tun/driver.c deleted file mode 100644 index 1ebf7dbc5c..0000000000 --- a/src/add-ons/kernel/drivers/network/tun/driver.c +++ /dev/null @@ -1,379 +0,0 @@ -/* - * /dev/config/tun network tunnel driver for BeOS - * (c) 2003, mmu_man, revol@free.fr - * licenced under MIT licence. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 -}; diff --git a/src/add-ons/kernel/drivers/network/tun/driver.cpp b/src/add-ons/kernel/drivers/network/tun/driver.cpp new file mode 100644 index 0000000000..f9fc33cbd4 --- /dev/null +++ b/src/add-ons/kernel/drivers/network/tun/driver.cpp @@ -0,0 +1,446 @@ +/* + * TUN/TAP Network Tunnel Driver for Haiku + * Copyright 2003 mmu_man, revol@free.fr + * Copyright 2023 Sean Brady, swangeon@gmail.com + * + * All rights reserved. Distributed under the terms of the MIT License. + */ +#include +#include +#include +#include + +#include "BufferQueue.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// #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(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(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(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(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(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(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(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(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; +} diff --git a/src/add-ons/kernel/network/devices/tun/tun.cpp b/src/add-ons/kernel/network/devices/tun/tun.cpp index af004ef238..26bc065a44 100644 --- a/src/add-ons/kernel/network/devices/tun/tun.cpp +++ b/src/add-ons/kernel/network/devices/tun/tun.cpp @@ -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. * * Authors: * Axel Dörfler, axeld@pinc-software.de + * Sean Brady, swangeon@gmail.com */ -#include - +#include #include +#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -#include #include +#include +#include #include #include #include +#include +#include +struct tun_device : net_device, DoublyLinkedListLinkImpl +{ + ~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; static struct net_stack_module_info* sStackModule; -//static mutex sListLock; -//static DoublyLinkedList sCheckList; - // #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 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 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 tun_init(const char* name, net_device** _device) { - tun_device* device; - if (strncmp(name, "tun", 3) && strncmp(name, "tap", 3) - && strncmp(name, "dns", 3)) /* iodine uses that */ + && strncmp(name, "dns", 3)) /* iodine uses that */ return B_BAD_VALUE; - device = new (std::nothrow) tun_device; - if (device == NULL) { + tun_device* device = new (std::nothrow) tun_device; + if (device == NULL) return B_NO_MEMORY; - } memset(device, 0, sizeof(tun_device)); - strcpy(device->name, name); if (strncmp(name, "tun", 3) == 0) { - device->flags = IFF_LOOPBACK | IFF_LINK; - device->type = IFT_TUN; + device->flags = IFF_POINTOPOINT | IFF_LINK; + device->type = IFT_ETHER; } else if (strncmp(name, "tap", 3) == 0) { device->flags = IFF_BROADCAST | IFF_ALLMULTI | IFF_LINK; 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; - } - device->mtu = 1500; /* Almost all VPN MTU's are no more than 1500 bytes */ - device->media = IFM_ACTIVE; + device->mtu = 1500; + 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; 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 tun_uninit(net_device* _device) { tun_device* device = (tun_device*)_device; - - put_module(NET_STACK_MODULE_NAME); - put_module(NET_BUFFER_MODULE_NAME); + close(device->fd); + device->fd = -1; + mutex_destroy(&device->read_buffer_lock); + mutex_destroy(&device->write_buffer_lock); delete device; - 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 -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; } -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 tun_control(net_device* device, int32 op, void* argument, - size_t length) + size_t length) { 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 -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 tun_set_mtu(net_device* device, size_t mtu) { if (mtu > 65536 || mtu < 16) return B_BAD_VALUE; - device->mtu = mtu; return B_OK; } - status_t tun_set_promiscuous(net_device* device, bool promiscuous) { @@ -138,6 +406,7 @@ tun_set_media(net_device* device, uint32 media) status_t tun_add_multicast(net_device* device, const sockaddr* address) { + /* Nothing to do for multicast filters as we always accept all frames. */ 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 tun_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: { - status_t status = get_module(NET_STACK_MODULE_NAME, - (module_info**)&sStackModule); + status_t status = get_module(NET_STACK_MODULE_NAME, (module_info**)&sStackModule); if (status < B_OK) return status; - status = get_module(NET_BUFFER_MODULE_NAME, - (module_info**)&gBufferModule); + status = get_module(NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule); if (status < B_OK) { put_module(NET_STACK_MODULE_NAME); return status; @@ -189,16 +464,15 @@ net_device_module_info sTunModule = { tun_down, tun_control, tun_send_data, - NULL, // receive_data + tun_receive_data, tun_set_mtu, tun_set_promiscuous, tun_set_media, tun_add_multicast, tun_remove_multicast, - }; + module_info* modules[] = { (module_info*)&sTunModule, - NULL -}; + NULL}; diff --git a/src/add-ons/kernel/network/stack/stack.cpp b/src/add-ons/kernel/network/stack/stack.cpp index 83a8e66d49..99014b69fe 100644 --- a/src/add-ons/kernel/network/stack/stack.cpp +++ b/src/add-ons/kernel/network/stack/stack.cpp @@ -831,6 +831,8 @@ init_stack() scan_modules("network/datalink_protocols"); // 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, "network/datalink_protocols/loopback_frame/v1", NULL); #if 0 // PPP is not (currently) included in the build