From ef844d88632c8456ed4ddb856bb88f05bcb9fe09 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 13 Dec 2019 12:30:18 +0100 Subject: [PATCH 1/6] vfs: provide function to get internal file information by fd --- sys/include/vfs.h | 15 +++++++++++++++ sys/vfs/vfs.c | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/sys/include/vfs.h b/sys/include/vfs.h index 2b547822e06a..9029e1508027 100644 --- a/sys/include/vfs.h +++ b/sys/include/vfs.h @@ -882,6 +882,21 @@ int vfs_normalize_path(char *buf, const char *path, size_t buflen); */ const vfs_mount_t *vfs_iterate_mounts(const vfs_mount_t *cur); +/** + * @brief Get information about the file for internal purposes + * + * @attention Not thread safe! Do not modify any of the fields in the returned + * struct. + * @note For file descriptor internal usage only. + * + * @internal + * @param[in] fd A file descriptor + * + * @return Pointer to the file information struct if a file with @p fd exists. + * @return NULL, when no file with file descriptor @p fd exists. + */ +const vfs_file_t *vfs_file_get(int fd); + #ifdef __cplusplus } #endif diff --git a/sys/vfs/vfs.c b/sys/vfs/vfs.c index 0938e67f86f3..6cf7232d600a 100644 --- a/sys/vfs/vfs.c +++ b/sys/vfs/vfs.c @@ -864,6 +864,16 @@ const vfs_mount_t *vfs_iterate_mounts(const vfs_mount_t *cur) return container_of(node, vfs_mount_t, list_entry); } +const vfs_file_t *vfs_file_get(int fd) +{ + if (_fd_is_valid(fd) == 0) { + return &_vfs_open_files[fd]; + } + else { + return NULL; + } +} + static inline int _allocate_fd(int fd) { if (fd < 0) { From c7dc0bb8de31e2ab8426b6bc8775518b00bfdd4c Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 13 Dec 2019 12:54:51 +0100 Subject: [PATCH 2/6] posix_sockets: use vfs_file_get() to resolve file descriptor --- sys/posix/sockets/posix_sockets.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sys/posix/sockets/posix_sockets.c b/sys/posix/sockets/posix_sockets.c index 5781e6389923..034bb1f91131 100644 --- a/sys/posix/sockets/posix_sockets.c +++ b/sys/posix/sockets/posix_sockets.c @@ -122,12 +122,19 @@ static socket_sock_t *_get_free_sock(void) static socket_t *_get_socket(int fd) { - for (int i = 0; i < _ACTUAL_SOCKET_POOL_SIZE; i++) { - if (_socket_pool[i].fd == fd) { - return &_socket_pool[i]; - } + const vfs_file_t *file = vfs_file_get(fd); + /* we know what to do with `socket`, so it's okay to discard the const */ + socket_t *socket = (file == NULL) + ? NULL + : file->private_data.ptr; + if ((socket >= &_socket_pool[0]) && + (socket <= &_socket_pool[_ACTUAL_SOCKET_POOL_SIZE - 1])) { + assert(socket->fd == fd); + return socket; + } + else { + return NULL; } - return NULL; } static int _get_sock_idx(socket_sock_t *sock) From 791069acf14e35c96474b55daf2f72584ee9c54e Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 13 Dec 2019 12:56:22 +0100 Subject: [PATCH 3/6] posix_sockets: provide function to check if file descriptor is a socket --- sys/posix/sockets/posix_sockets.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sys/posix/sockets/posix_sockets.c b/sys/posix/sockets/posix_sockets.c index 034bb1f91131..2ba1658f3721 100644 --- a/sys/posix/sockets/posix_sockets.c +++ b/sys/posix/sockets/posix_sockets.c @@ -1066,6 +1066,11 @@ int setsockopt(int socket, int level, int option_name, const void *option_value, #endif } +bool posix_socket_is(int fd) +{ + return IS_USED(MODULE_SOCK_ASYNC) && (_get_socket(fd) != NULL); +} + /** * @} */ From af24c539d0888d2b935a1223c819b9b2d792ea42 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 13 Dec 2019 12:58:35 +0100 Subject: [PATCH 4/6] posix_sockets: count available received messages asynchronously Utilizing `sock_async` --- sys/posix/sockets/posix_sockets.c | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/sys/posix/sockets/posix_sockets.c b/sys/posix/sockets/posix_sockets.c index 2ba1658f3721..5815d93707e4 100644 --- a/sys/posix/sockets/posix_sockets.c +++ b/sys/posix/sockets/posix_sockets.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,10 @@ #include "net/sock/udp.h" #include "net/sock/tcp.h" +#if IS_USED(MODULE_SOCK_ASYNC) +#include "net/sock/async.h" +#endif + /* enough to create sockets both with socket() and accept() */ #define _ACTUAL_SOCKET_POOL_SIZE (SOCKET_POOL_SIZE + \ (SOCKET_POOL_SIZE * SOCKET_TCP_QUEUE_SIZE)) @@ -78,6 +83,9 @@ typedef struct { #ifdef MODULE_SOCK_TCP sock_tcp_t *queue_array; unsigned queue_array_len; +#endif +#if IS_USED(MODULE_SOCK_ASYNC) + atomic_uint available; #endif sock_tcp_ep_t local; /* to store bind before connect/listen */ } socket_t; @@ -105,6 +113,9 @@ static socket_t *_get_free_socket(void) { for (int i = 0; i < _ACTUAL_SOCKET_POOL_SIZE; i++) { if (_socket_pool[i].domain == AF_UNSPEC) { +#if IS_USED(MODULE_SOCK_ASYNC) + atomic_init(&_socket_pool[i].available, 0U); +#endif return &_socket_pool[i]; } } @@ -338,6 +349,68 @@ static const vfs_file_ops_t socket_ops = { .write = socket_write, }; +#if IS_USED(MODULE_SOCK_ASYNC) +static void _async_cb(void *sock, sock_async_flags_t type, + void *arg) +{ + socket_t *socket = arg; + + (void)sock; + if (type & SOCK_ASYNC_MSG_RECV) { + atomic_fetch_add(&socket->available, 1); +#if IS_USED(MODULE_POSIX_SELECT) + thread_flags_set(sock->socket->selecting_thread, + POSIX_SELECT_THREAD_FLAG); +#endif + } +} + +static void _sock_set_cb(socket_t *socket) +{ + union { + void (*sock_pool)(void *, sock_async_flags_t, void *); +#ifdef MODULE_SOCK_IP + sock_ip_cb_t ip; +#endif +#ifdef MODULE_SOCK_TCP + sock_tcp_cb_t tcp; + sock_tcp_queue_cb_t tcp_queue; +#endif +#ifdef MODULE_SOCK_UDP + sock_udp_cb_t udp; +#endif + } callback = { .sock_pool = _async_cb }; + + switch (socket->type) { +#ifdef MODULE_SOCK_IP + case SOCK_RAW: + sock_ip_set_cb(&socket->sock.ip, callback.ip, socket); + break; +#endif +#ifdef MODULE_SOCK_TCP + case SOCK_STREAM: + /* is a TCP client socket */ + if (socket->queue_array == NULL) { + sock_tcp_set_cb(&socket->sock.tcp.sock, callback.tcp, socket); + } + /* is a TCP listening socket */ + else { + sock_tcp_queue_set_cb(&socket->sock.tcp.queue, + callback.tcp_queue, socket); + } + break; +#endif +#ifdef MODULE_SOCK_UDP + case SOCK_DGRAM: + sock_udp_set_cb(&socket->sock->udp, callback.udp, socket); + break; +#endif + default: + break; + } +} +#endif + int socket(int domain, int type, int protocol) { int res = 0; @@ -472,6 +545,9 @@ int accept(int socket, struct sockaddr *restrict address, new_s->queue_array = NULL; new_s->queue_array_len = 0; new_s->sock = (socket_sock_t *)sock; +#if IS_USED(MODULE_SOCK_ASYNC) + _sock_set_cb(new_s); +#endif memset(&s->local, 0, sizeof(sock_tcp_ep_t)); } break; @@ -605,6 +681,10 @@ static int _bind_connect(socket_t *s, const struct sockaddr *address, return -1; } s->sock = sock; +#if IS_USED(MODULE_SOCK_ASYNC) + _sock_set_cb(s); +#endif + return 0; } @@ -804,6 +884,9 @@ int listen(int socket, int backlog) } if (res == 0) { s->sock = sock; +#if IS_USED(MODULE_SOCK_ASYNC) + _sock_set_cb(s); +#endif } else { errno = -res; @@ -878,6 +961,9 @@ static ssize_t socket_recvfrom(socket_t *s, void *restrict buffer, break; } if ((res >= 0) && (address != NULL) && (address_len != NULL)) { +#ifdef MODULE_SOCK_ASYNC + atomic_fetch_sub(&s->available, 1); +#endif switch (s->type) { #ifdef MODULE_SOCK_TCP case SOCK_STREAM: @@ -1071,6 +1157,18 @@ bool posix_socket_is(int fd) return IS_USED(MODULE_SOCK_ASYNC) && (_get_socket(fd) != NULL); } +unsigned posix_socket_avail(int fd) +{ +#if IS_USED(MODULE_SOCK_ASYNC) + socket_t *socket = _get_socket(fd); + + return (socket != NULL) ? atomic_load(&socket->available) : 0U; +#else + (void)fd; + return 0U; +#endif +} + /** * @} */ From 6ba67402317ca9b73207c9f4bc53cd612f625d50 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 13 Dec 2019 13:01:30 +0100 Subject: [PATCH 5/6] posix_select: initial import of `select()` function --- Makefile.dep | 9 ++ sys/Makefile | 3 + sys/posix/doc.txt | 6 ++ sys/posix/include/sys/select.h | 172 ++++++++++++++++++++++++++++++ sys/posix/select/Makefile | 3 + sys/posix/select/posix_select.c | 143 +++++++++++++++++++++++++ sys/posix/sockets/posix_sockets.c | 42 +++++++- 7 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 sys/posix/include/sys/select.h create mode 100644 sys/posix/select/Makefile create mode 100644 sys/posix/select/posix_select.c diff --git a/Makefile.dep b/Makefile.dep index 65abf25c1145..bc8bb8929935 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -470,6 +470,15 @@ ifneq (,$(filter newlib,$(USEMODULE))) endif endif +ifneq (,$(filter posix_select,$(USEMODULE))) + ifneq (,$(filter posix_sockets,$(USEMODULE))) + USEMODULE += sock_async + endif + USEMODULE += core_thread_flags + USEMODULE += posix_headers + USEMODULE += xtimer +endif + ifneq (,$(filter posix_sockets,$(USEMODULE))) USEMODULE += bitfield USEMODULE += random diff --git a/sys/Makefile b/sys/Makefile index 19f289758e4f..f09cef8023d9 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -10,6 +10,9 @@ endif ifneq (,$(filter posix_inet,$(USEMODULE))) DIRS += posix/inet endif +ifneq (,$(filter posix_select,$(USEMODULE))) + DIRS += posix/select +endif ifneq (,$(filter posix_semaphore,$(USEMODULE))) DIRS += posix/semaphore endif diff --git a/sys/posix/doc.txt b/sys/posix/doc.txt index d6ce3a070115..a7e47bc5316f 100644 --- a/sys/posix/doc.txt +++ b/sys/posix/doc.txt @@ -14,3 +14,9 @@ * * @ingroup sys */ + +/** + * @defgroup config_posix POSIX wrapper compile configurations + * @ingroup posix + * @ingroup config + */ diff --git a/sys/posix/include/sys/select.h b/sys/posix/include/sys/select.h new file mode 100644 index 000000000000..6899e262cfda --- /dev/null +++ b/sys/posix/include/sys/select.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup posix_select POSIX select + * @ingroup posix + * @brief Select implementation for RIOT + * @see [The Open Group Base Specification Issue 7] + * (https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/) + * @todo Omitted from original specification for now: + * - Inclusion of ``; no POSIX signal handling implemented + * in RIOT yet + * - `pselect()` as it uses `sigset_t` from `` + * - handling of the `writefds` and `errorfds` parameters of `select()` + * @todo Currently, only [sockets](@ref posix_sockets) are supported + * @{ + * + * @file + * @brief Select types + * @see [The Open Group Base Specification Issue 7, 2018 edition, + * ](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/sys_select.h) + */ + +#ifndef SYS_SELECT_H +#define SYS_SELECT_H + +#include +/* prevent cyclic dependency with newlib's `sys/types.h` */ +#if defined(MODULE_NEWLIB) && !defined(CPU_ESP32) && !defined(CPU_ESP8266) +#include +#else +#include +#endif + +#include "bitfield.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup config_posix + * @{ + */ +/** + * @brief Maximum number of file descriptors in an `fd_set` structure. + * + * @note Should have at least the same value as VFS_MAX_OPEN_FILES. + * + * Config-exposed version + */ +#ifndef CONFIG_POSIX_FD_SET_SIZE +#define CONFIG_POSIX_FD_SET_SIZE (16) +#endif +/** @} */ + +/** + * @brief @ref core_thread_flags for POSIX select + */ +#define POSIX_SELECT_THREAD_FLAG (1U << 3) + +/* ESP's newlib has this already defined in `sys/types.h` */ +#if !defined(CPU_ESP32) && !defined(CPU_ESP8266) +/** + * @brief Maximum number of file descriptors in an `fd_set` structure. + * + * POSIX-compliant version. + */ +#define FD_SETSIZE (CONFIG_POSIX_FD_SET_SIZE) + +/** + * @brief The `fd_set` structure + */ +typedef struct { + BITFIELD(fds, FD_SETSIZE); /**< Bit-field to represent the set of file + * descriptors */ +} fd_set; + +/** + * @brief Removes a file descriptor from an `fd_set` if it is a member + * + * @param[in] fd A file descriptor + * @param[in] fdsetp An `fd_set` + */ +static inline void FD_CLR(int fd, fd_set *fdsetp) +{ + bf_unset(fdsetp->fds, fd); +} + +/** + * @brief Checks if a file descriptor is a member of an `fd_set` + * + * @param[in] fd A file descriptor + * @param[in] fdsetp An `fd_set` + * + * @return non-zero value, if @p fd is a member of @p fdsetp + * @return 0, if @p fd is not a member of @p fdsetp + */ +static inline int FD_ISSET(int fd, fd_set *fdsetp) +{ + return (int)bf_isset(fdsetp->fds, fd); +} + +/** + * @brief Adds a file descriptor from an `fd_set` if it is not already a + * member + * + * @param[in] fd A file descriptor + * @param[in] fdsetp An `fd_set` + */ +static inline void FD_SET(int fd, fd_set *fdsetp) +{ + bf_set(fdsetp->fds, fd); +} + +/** + * @brief Initializes the descriptor set as an empty set + * + * @param[in] fdsetp An `fd_set` + */ +static inline void FD_ZERO(fd_set *fdsetp) +{ + memset(fdsetp->fds, 0, sizeof(fdsetp->fds)); +} +#endif /* !defined(CPU_ESP32) && !defined(CPU_ESP8266) */ + +/** + * @brief Examines the given file descriptor sets if they are ready for their + * respective operation. + * + * @param[in] nfds The range of descriptors tested. The first @p nfds + * descriptors shall be checked in each set; that is, + * the descriptors from zero through @p nfds - 1 in the + * descriptor sets shall be examined. + * @param[in,out] readfds The set of file descriptors to be checked for being + * ready to read. Indicates on output which file + * descriptors are ready to read. May be NULL to check + * no file descriptors. + * @param[in,out] writefds The set of file descriptors to be checked for being + * ready to write. Indicates on output which file + * descriptors are ready to write. May be NULL to check + * no file descriptors. + * **As only sockets are supported for now, these will + * be ignored** + * @param[in,out] errorfds The set of file descriptors to be checked for being + * error conditions pending. Indicates on output which + * file descriptors have error conditions pending. May + * be NULL to check no file descriptors. + * **As only sockets are supported for now, these will + * be ignored** + * @param[in] timeout Timeout for select to block until one or more of the + * checked file descriptors is ready. Set timeout + * to all-zero to return immediately without blocking. + * May be NULL to block indefinitely. + * + * @return number of members added to the file descriptor sets on success. + * @return -1 on error, `errno` is set to indicate the error. + */ +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, + struct timeval *timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* SYS_SELECT_H */ +/** @} */ diff --git a/sys/posix/select/Makefile b/sys/posix/select/Makefile new file mode 100644 index 000000000000..f693b59b0517 --- /dev/null +++ b/sys/posix/select/Makefile @@ -0,0 +1,3 @@ +MODULE = posix_select + +include $(RIOTBASE)/Makefile.base diff --git a/sys/posix/select/posix_select.c b/sys/posix/select/posix_select.c new file mode 100644 index 000000000000..b8d56fa8dc9b --- /dev/null +++ b/sys/posix/select/posix_select.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * @file + * @author Martine S. Lenders + */ + +#include +#include +#include + +#include "thread_flags.h" +#include "vfs.h" +#include "xtimer.h" + +#if IS_USED(MODULE_POSIX_SOCKETS) +extern bool posix_socket_is(int fd); +extern unsigned posix_socket_avail(int fd); +extern void posix_socket_select(int fd); +#else /* MODULE_POSIX_SOCKETS */ +static inline bool posix_socket_is(int fd) +{ + (void)fd; + return false; +} + +static inline unsigned posix_socket_avail(int fd) +{ + (void)fd; + return 0; +} + +static inline void posix_socket_select(int fd) +{ + (void)fd; + return 0; +} +#endif /* IS_USED(MODULE_POSIX_SOCKETS) */ + +static int _set_timeout(xtimer_t *timeout_timer, struct timeval *timeout, + uint32_t offset, bool *wait) +{ + if (timeout != NULL) { + uint64_t t = ((uint64_t)(timeout->tv_sec * US_PER_SEC) + + timeout->tv_usec); + /* check for potential underflow before subtracting offset */ + if ((t == 0) || (offset > t)) { + *wait = false; + return 0; + } + t -= offset; + if (t > UINT32_MAX) { + errno = EINVAL; + /* don't have timer set yet so go to end */ + return -1; + } + else { + xtimer_set_timeout_flag(timeout_timer, (uint32_t)t); + } + } + return 0; +} + +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, + struct timeval *timeout) +{ + uint32_t start_time = xtimer_now_usec(); + fd_set ret_readfds; + xtimer_t timeout_timer; + int fds_set = 0; + bool wait = true; + + FD_ZERO(&ret_readfds); + /* TODO ignored writefds and errorfds for now since there is no point for + * them with sockets */ + if ((nfds >= FD_SETSIZE) || ((unsigned)nfds >= VFS_MAX_OPEN_FILES)) { + errno = EINVAL; + return -1; + } + for (int i = 0; i < nfds; i++) { + if ((readfds != NULL) && FD_ISSET(i, readfds)) { + if (!posix_socket_is(i)) { + errno = EBADF; + return -1; + } + if (posix_socket_avail(i) > 0) { + FD_SET(i, &ret_readfds); + fds_set++; + wait = false; + } + else { + posix_socket_select(i); + } + } + if ((writefds != NULL) && FD_ISSET(i, writefds) && + !posix_socket_is(i)) { + errno = EBADF; + return -1; + } + if ((errorfds != NULL) && FD_ISSET(i, errorfds) && + !posix_socket_is(i)) { + errno = EBADF; + return -1; + } + } + while (wait) { + if (_set_timeout(&timeout_timer, timeout, + xtimer_now_usec() - start_time, &wait) < 0) { + return -1; + } + if (!wait) { + errno = EINTR; + return -1; + } + thread_flags_t tflags = thread_flags_wait_any(POSIX_SELECT_THREAD_FLAG | + THREAD_FLAG_TIMEOUT); + if (tflags & POSIX_SELECT_THREAD_FLAG) { + for (int i = 0; i < nfds; i++) { + if (FD_ISSET(i, readfds)) { + if (posix_socket_avail(i) > 0) { + FD_SET(i, &ret_readfds); + fds_set++; + wait = false; + } + } + } + } + else if (tflags & THREAD_FLAG_TIMEOUT) { + errno = EINTR; + return -1; + } + xtimer_remove(&timeout_timer); + } + *readfds = ret_readfds; + return fds_set; +} diff --git a/sys/posix/sockets/posix_sockets.c b/sys/posix/sockets/posix_sockets.c index 5815d93707e4..27dffc1a985b 100644 --- a/sys/posix/sockets/posix_sockets.c +++ b/sys/posix/sockets/posix_sockets.c @@ -41,6 +41,12 @@ #if IS_USED(MODULE_SOCK_ASYNC) #include "net/sock/async.h" #endif +#if IS_USED(MODULE_POSIX_SELECT) +#include + +#include "thread.h" +#include "thread_flags.h" +#endif /* enough to create sockets both with socket() and accept() */ #define _ACTUAL_SOCKET_POOL_SIZE (SOCKET_POOL_SIZE + \ @@ -86,6 +92,9 @@ typedef struct { #endif #if IS_USED(MODULE_SOCK_ASYNC) atomic_uint available; +#endif +#if IS_USED(MODULE_POSIX_SELECT) + thread_t *selecting_thread; #endif sock_tcp_ep_t local; /* to store bind before connect/listen */ } socket_t; @@ -115,6 +124,9 @@ static socket_t *_get_free_socket(void) if (_socket_pool[i].domain == AF_UNSPEC) { #if IS_USED(MODULE_SOCK_ASYNC) atomic_init(&_socket_pool[i].available, 0U); +#endif +#if IS_USED(MODULE_POSIX_SELECT) + _socket_pool[i].selecting_thread = NULL; #endif return &_socket_pool[i]; } @@ -359,8 +371,10 @@ static void _async_cb(void *sock, sock_async_flags_t type, if (type & SOCK_ASYNC_MSG_RECV) { atomic_fetch_add(&socket->available, 1); #if IS_USED(MODULE_POSIX_SELECT) - thread_flags_set(sock->socket->selecting_thread, - POSIX_SELECT_THREAD_FLAG); + if (socket->selecting_thread) { + thread_flags_set(socket->selecting_thread, + POSIX_SELECT_THREAD_FLAG); + } #endif } } @@ -1169,6 +1183,30 @@ unsigned posix_socket_avail(int fd) #endif } +int posix_socket_select(int fd) +{ +#if IS_USED(MODULE_POSIX_SELECT) + socket_t *socket = _get_socket(fd); + + if (socket != NULL) { + if (socket->sock == NULL) { /* socket is not connected */ + int res; + + /* bind implicitly */ + if ((res = _bind_connect(socket, NULL, 0)) < 0) { + return res; + } + } + socket->selecting_thread = (thread_t *)sched_active_thread; + return 0; + } +#else + (void)fd; +#endif + errno = ENOTSUP; + return -1; +} + /** * @} */ From fa9371d177fddec88bd22ba0ab36ca0bb1a3cd39 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Tue, 17 Dec 2019 17:02:20 +0100 Subject: [PATCH 6/6] examples: add `posix_select` example --- examples/posix_select/Makefile | 32 +++++++ examples/posix_select/Makefile.ci | 27 ++++++ examples/posix_select/README.md | 83 ++++++++++++++++++ examples/posix_select/main.c | 141 ++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100644 examples/posix_select/Makefile create mode 100644 examples/posix_select/Makefile.ci create mode 100644 examples/posix_select/README.md create mode 100644 examples/posix_select/main.c diff --git a/examples/posix_select/Makefile b/examples/posix_select/Makefile new file mode 100644 index 000000000000..485ce451a83e --- /dev/null +++ b/examples/posix_select/Makefile @@ -0,0 +1,32 @@ +# name of your application +APPLICATION = posix_sockets_example + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules for socket communication via UDP +USEMODULE += gnrc_ipv6_default +# Add stack-specific implementations of sock modules +USEMODULE += gnrc_sock_async +USEMODULE += gnrc_sock_udp +# Add POSIX modules +USEMODULE += posix_select +USEMODULE += posix_sockets +USEMODULE += posix_inet + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/posix_select/Makefile.ci b/examples/posix_select/Makefile.ci new file mode 100644 index 000000000000..981a790ea3a7 --- /dev/null +++ b/examples/posix_select/Makefile.ci @@ -0,0 +1,27 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + chronos \ + i-nucleo-lrwan1 \ + msb-430 \ + msb-430h \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + telosb \ + waspmote-pro \ + wsn430-v1_3b \ + wsn430-v1_4 \ + z1 \ + # diff --git a/examples/posix_select/README.md b/examples/posix_select/README.md new file mode 100644 index 000000000000..5923af2cb870 --- /dev/null +++ b/examples/posix_select/README.md @@ -0,0 +1,83 @@ +examples/posix_sockets +====================== +This application is a showcase for RIOT's POSIX select support. To +keep things simple this application has only one-hop support and +no routing capabilities. + +Usage +===== + +Build, flash and start the application: +```sh +export BOARD=your_board +make +make flash +make term +``` + +The `term` make target starts a terminal emulator for your board. It +connects to a default port so you can interact with the shell, usually +that is `/dev/ttyUSB0`. If your port is named differently, the +`PORT=/dev/yourport` (not to be confused with the UDP port) variable can +be used to override this. + + +Example output +============== + +The application starts 4 UDP servers on a selection of different ports that wait +for input simultaneously: +``` +2019-12-17 16:36:45,559 # RIOT select example application +2019-12-17 16:36:45,561 # Started UDP server at [fe80::14ac:fb65:106b:1115]:1350 +2019-12-17 16:36:45,562 # Started UDP server at [fe80::14ac:fb65:106b:1115]:4973 +2019-12-17 16:36:45,562 # Started UDP server at [fe80::14ac:fb65:106b:1115]:6717 +2019-12-17 16:36:45,562 # Started UDP server at [fe80::14ac:fb65:106b:1115]:9673 +``` + +If you do not see any output you might need to reset the node. Either, by +pressing the hardware reset button on the board or by running +```sh +make reset +``` + +There is no shell in this application. You can use the [`posix_sockets` example] +from another board to send a packet to the node: + +``` +> udp send fe80::14ac:fb65:106b:1115 6717 "Hello World!" +2019-12-17 16:47:01,789 # udp send fe80::14ac:fb65:106b:1115%6 6717 "Hello World!" +2019-12-17 16:47:01,795 # Success: send 12 byte to fe80::14ac:fb65:106b:1115:6717 +``` + +On the board with the `posix_select` example you will see then something like +this: + +``` +2019-12-17 16:47:01,796 # Received data from [fe80::589d:9386:2208:6579]:192: +2019-12-17 16:47:01,796 # Hello World! +``` + +Alternatively, with `native` or if your host also can connect to the board, you +can also use [`netcat`][netcat] to send multiple packets simultaneously. E.g. +when the node is connected to the host via the interface `tapbr0`: + +```sh +echo -ne "Hello World!" | nc -6u "fe80::78b9:ecff:fe96:8279%tapbr0" 4973 & \ + echo -ne "Hello Space!" | nc -6u "fe80::78b9:ecff:fe96:8279%tapbr0" 1350 +killall nc +``` + +This is what the `native` node will then show: + +``` +Received data from [fe80::3ccc:8dff:fe9f:9991]:14279: +Hello World! + +Received data from [fe80::3ccc:8dff:fe9f:9991]:58817: +Hello Space! + +``` + +[`posix_sockets` example]: ../posix_sockets +[netcat]: https://www.unix.com/man-page/Linux/1/netcat/ diff --git a/examples/posix_select/main.c b/examples/posix_select/main.c new file mode 100644 index 000000000000..f78bb0143c7b --- /dev/null +++ b/examples/posix_select/main.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief Example application for demonstrating the RIOT's POSIX select() + * implementation + * + * @author Martine Lenders + * + * @} + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "od.h" +#include "net/gnrc/netif.h" +#include "thread.h" + +#define SERVER_BUFFER_SIZE (128U) +#define SERVER_PORTS { 1350U, 4973U, 6717U, 9673U } +#define SERVER_SOCKETS_NUM (4U) + +static char server_buffer[SERVER_BUFFER_SIZE]; +static char addr_str[IPV6_ADDR_MAX_STR_LEN]; + +static int _run_server(void *local_addr) +{ + struct sockaddr_in6 server_addr = { .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ANY_INIT }; + static const uint16_t ports[SERVER_SOCKETS_NUM] = SERVER_PORTS; + int server_sockets[SERVER_SOCKETS_NUM] = { 0 }; + int max_fd = -1; + int ret = 0; + + /* open SERVER_SOCKETS_NUM sockets with respective port */ + for (unsigned i = 0; i < SERVER_SOCKETS_NUM; i++) { + server_sockets[i] = socket(AF_INET6, SOCK_DGRAM, 0); + if (server_sockets[i] < 0) { + puts("error initializing socket"); + ret = 1; + goto end; + } + server_addr.sin6_port = htons(ports[i]); + if (bind(server_sockets[i], + (struct sockaddr *)&server_addr, + sizeof(server_addr)) < 0) { + puts("error binding socket"); + ret = 1; + goto end; + } + printf("Started UDP server at [%s]:%u\n", + inet_ntop(AF_INET6, local_addr, addr_str, sizeof(addr_str)), + ports[i]); + if (max_fd < server_sockets[i]) { + max_fd = server_sockets[i]; + } + } + + while (true) { + fd_set readfds; + + /* add bound sockets to set of file descriptors to read */ + FD_ZERO(&readfds); + for (unsigned i = 0; i < SERVER_SOCKETS_NUM; i++) { + FD_SET(server_sockets[i], &readfds); + } + /* wait for bound sockets to be notified for reading*/ + if (select(max_fd + 1, &readfds, NULL, NULL, NULL) < 0) { + puts("error on select"); + continue; + } + for (unsigned i = 0; i < SERVER_SOCKETS_NUM; i++) { + /* if socket is in set of file descriptors to check for reading */ + if (FD_ISSET(server_sockets[i], &readfds)) { + int res; + struct sockaddr_in6 src; + socklen_t src_len = sizeof(struct sockaddr_in6); + + /* receive data from socket */ + if ((res = recvfrom(server_sockets[i], server_buffer, + sizeof(server_buffer), 0, + (struct sockaddr *)&src, &src_len)) < 0) { + puts("Error on receive"); + } + else if (res == 0) { + puts("Peer did shut down"); + } + else { + printf("Received data from [%s]:%u:\n", + inet_ntop(AF_INET6, &src.sin6_addr, + addr_str, sizeof(addr_str)), + src.sin6_port); + res = ((unsigned)res < SERVER_BUFFER_SIZE) ? res : (res - 1); + /* terminate string */ + server_buffer[res] = '\0'; + printf("%s\n", server_buffer); + } + } + } + } + +end: + /* close all open sockets */ + for (unsigned i = 0; i < SERVER_SOCKETS_NUM; i++) { + if (server_sockets[i] > 0) { + close(server_sockets[i]); + } + } + return ret; +} + +int main(void) +{ + /* TODO: use TBD POSIX API to get link-local address */ + gnrc_netif_t *netif = gnrc_netif_iter(NULL); + ipv6_addr_t addr; + + puts("RIOT select example application"); + + /* get first address on the interface */ + if (gnrc_netif_ipv6_addrs_get(netif, &addr, sizeof(addr)) < 0) { + puts("Unable to get first address of the interface"); + return 1; + } + return _run_server(&addr); +}