Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce socket service API #66758

Merged
merged 6 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/zephyr/linker/common-rom/common-rom-net.ld
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@
#if defined(CONFIG_NET_MGMT_EVENT)
ITERABLE_SECTION_ROM(net_mgmt_event_static_handler, 4)
#endif

#if defined(CONFIG_NET_SOCKETS_SERVICE)
ITERABLE_SECTION_RAM(net_socket_service_desc, 4)
#endif
246 changes: 246 additions & 0 deletions include/zephyr/net/socket_service.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/**
* @file
* @brief BSD Socket service API
*
* API can be used to install a k_work that is called
* if there is data received to a socket.
*/

/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_NET_SOCKET_SERVICE_H_
#define ZEPHYR_INCLUDE_NET_SOCKET_SERVICE_H_

/**
* @brief BSD socket service API
* @defgroup bsd_socket_service BSD socket service API
* @ingroup networking
* @{
*/

#include <sys/types.h>
#include <zephyr/types.h>
#include <zephyr/net/socket.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* This struct contains information which socket triggered
* calls to the callback function.
*/
struct net_socket_service_event {
/** k_work that is done when there is desired activity in file descriptor. */
struct k_work work;
/** Callback to be called for desired socket activity */
k_work_handler_t callback;
/** Socket information that triggered this event. */
struct zsock_pollfd event;
/** User data */
void *user_data;
/** Service back pointer */
struct net_socket_service_desc *svc;
};

/**
* Main structure holding socket service configuration information.
* The k_work item is created so that when there is data coming
* to those fds, the k_work callback is then called.
* The workqueue can be set NULL in which case system workqueue is used.
* The service descriptor should be created at built time, and then used
* as a parameter to register the sockets to be monitored.
* User should create needed sockets and then setup the poll struct and
* then register the sockets to be monitored at runtime.
*/
struct net_socket_service_desc {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we really need this static configuration for socket service? It seems a bit problematic, at least for my use case - DHCP server. Here, ideally, I'd need to have a single socket for each interface that wants to support the DHCP server functionality. But I don't think we have a way to calculate the number of interfaces in the system at build time to supply the static configuration macros. Purely runtime registration would be less cumbersome in such case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we really need this static configuration for socket service? It seems a bit problematic, at least for my use case - DHCP server. Here, ideally, I'd need to have a single socket for each interface that wants to support the DHCP server functionality. But I don't think we have a way to calculate the number of interfaces in the system at build time to supply the static configuration macros. Purely runtime registration would be less cumbersome in such case.

I originally did some experimentation with runtime configuration, but as the API requires some housekeeping activities, allocating them at runtime requires malloc etc. There is a call to malloc at this PR too but hopefully we could get rid of it.

We could place struct zsock_pollfd into DHCP config in network interface, that way the information this API needs, would already be part of network interface.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devicetree could help here, just sayin 🤷🏼‍♂️

jukkar marked this conversation as resolved.
Show resolved Hide resolved
#if CONFIG_NET_SOCKETS_LOG_LEVEL >= LOG_LEVEL_DBG
/**
* Owner name. This can be used in debugging to see who has
* registered this service.
*/
const char *owner;
#endif
/** Workqueue where the work is submitted. */
struct k_work_q *work_q;
/** Pointer to the list of services that we are listening */
struct net_socket_service_event *pev;
/** Length of the pollable socket array for this service. */
int pev_len;
/** Where are my pollfd entries in the global list */
int *idx;
};

#define __z_net_socket_svc_get_name(_svc_id) __z_net_socket_service_##_svc_id
#define __z_net_socket_svc_get_idx(_svc_id) __z_net_socket_service_idx##_svc_id
#define __z_net_socket_svc_get_owner __FILE__ ":" STRINGIFY(__LINE__)

extern void net_socket_service_callback(struct k_work *work);

#if CONFIG_NET_SOCKETS_LOG_LEVEL >= LOG_LEVEL_DBG
#define NET_SOCKET_SERVICE_OWNER .owner = __z_net_socket_svc_get_owner,
#else
#define NET_SOCKET_SERVICE_OWNER
#endif

#define NET_SOCKET_SERVICE_CALLBACK_MODE(_flag) \
IF_ENABLED(_flag, \
(.work = Z_WORK_INITIALIZER(net_socket_service_callback),))

#define __z_net_socket_service_define(_name, _work_q, _cb, _count, _async, ...) \
static int __z_net_socket_svc_get_idx(_name); \
static struct net_socket_service_event \
__z_net_socket_svc_get_name(_name)[_count] = { \
[0 ... ((_count) - 1)] = { \
.event.fd = -1, /* Invalid socket */ \
NET_SOCKET_SERVICE_CALLBACK_MODE(_async) \
.callback = _cb, \
} \
}; \
COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (), __VA_ARGS__) \
const STRUCT_SECTION_ITERABLE(net_socket_service_desc, _name) = { \
NET_SOCKET_SERVICE_OWNER \
.work_q = (_work_q), \
.pev = __z_net_socket_svc_get_name(_name), \
.pev_len = (_count), \
.idx = &__z_net_socket_svc_get_idx(_name), \
}

/**
* @brief Statically define a network socket service.
* The user callback is called asynchronously for this service meaning that
* the service API will not wait until the user callback returns before continuing
* with next socket service.
*
* The socket service can be accessed outside the module where it is defined using:
*
* @code extern struct net_socket_service_desc <name>; @endcode
*
* @note This macro cannot be used together with a static keyword.
* If such a use-case is desired, use NET_SOCKET_SERVICE_ASYNC_DEFINE_STATIC
* instead.
*
* @param name Name of the service.
* @param work_q Pointer to workqueue where the work is done. Can be null in which case
* system workqueue is used.
* @param cb Callback function that is called for socket activity.
* @param count How many pollable sockets is needed for this service.
*/
#define NET_SOCKET_SERVICE_ASYNC_DEFINE(name, work_q, cb, count) \
__z_net_socket_service_define(name, work_q, cb, count, 1)

/**
* @brief Statically define a network socket service in a private (static) scope.
* The user callback is called asynchronously for this service meaning that
* the service API will not wait until the user callback returns before continuing
* with next socket service.
*
* @param name Name of the service.
* @param work_q Pointer to workqueue where the work is done. Can be null in which case
* system workqueue is used.
* @param cb Callback function that is called for socket activity.
* @param count How many pollable sockets is needed for this service.
*/
#define NET_SOCKET_SERVICE_ASYNC_DEFINE_STATIC(name, work_q, cb, count) \
__z_net_socket_service_define(name, work_q, cb, count, 1, static)

/**
* @brief Statically define a network socket service.
* The user callback is called synchronously for this service meaning that
* the service API will wait until the user callback returns before continuing
* with next socket service.
*
* The socket service can be accessed outside the module where it is defined using:
*
* @code extern struct net_socket_service_desc <name>; @endcode
*
* @note This macro cannot be used together with a static keyword.
* If such a use-case is desired, use NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC
* instead.
*
* @param name Name of the service.
* @param work_q Pointer to workqueue where the work is done. Can be null in which case
* system workqueue is used.
* @param cb Callback function that is called for socket activity.
* @param count How many pollable sockets is needed for this service.
*/
#define NET_SOCKET_SERVICE_SYNC_DEFINE(name, work_q, cb, count) \
__z_net_socket_service_define(name, work_q, cb, count, 0)

/**
* @brief Statically define a network socket service in a private (static) scope.
* The user callback is called synchronously for this service meaning that
* the service API will wait until the user callback returns before continuing
* with next socket service.
*
* @param name Name of the service.
* @param work_q Pointer to workqueue where the work is done. Can be null in which case
* system workqueue is used.
* @param cb Callback function that is called for socket activity.
* @param count How many pollable sockets is needed for this service.
*/
#define NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(name, work_q, cb, count) \
__z_net_socket_service_define(name, work_q, cb, count, 0, static)

/**
* @brief Register pollable sockets.
*
* @param service Pointer to a service description.
* @param fds Socket array to poll.
* @param len Length of the socket array.
* @param user_data User specific data.
*
* @retval 0 No error
* @retval -ENOENT Service is not found.
* @retval -ENINVAL Invalid parameter.
*/
__syscall int net_socket_service_register(const struct net_socket_service_desc *service,
struct zsock_pollfd *fds, int len, void *user_data);

/**
* @brief Unregister pollable sockets.
*
* @param service Pointer to a service description.
*
* @retval 0 No error
* @retval -ENOENT Service is not found.
* @retval -ENINVAL Invalid parameter.
*/
static inline int net_socket_service_unregister(const struct net_socket_service_desc *service)
{
return net_socket_service_register(service, NULL, 0, NULL);
}

/**
* @typedef net_socket_service_cb_t
* @brief Callback used while iterating over socket services.
*
* @param svc Pointer to current socket service.
* @param user_data A valid pointer to user data or NULL
*/
typedef void (*net_socket_service_cb_t)(const struct net_socket_service_desc *svc,
void *user_data);

/**
* @brief Go through all the socket services and call callback for each service.
*
* @param cb User-supplied callback function to call
* @param user_data User specified data
*/
void net_socket_service_foreach(net_socket_service_cb_t cb, void *user_data);
jukkar marked this conversation as resolved.
Show resolved Hide resolved

#ifdef __cplusplus
}
#endif

#include <syscalls/socket_service.h>

/**
* @}
*/

#endif /* ZEPHYR_INCLUDE_NET_SOCKET_SERVICE_H_ */
11 changes: 11 additions & 0 deletions samples/net/sockets/echo_service/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(sockets_service_echo)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

include(${ZEPHYR_BASE}/samples/net/common/common.cmake)
51 changes: 51 additions & 0 deletions samples/net/sockets/echo_service/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.. zephyr:code-sample:: sockets-service-echo
:name: Echo server (service)
:relevant-api: bsd_sockets

Implements a simple IPv4/IPv6 TCP echo server using BSD sockets and socket service API.

Overview
********

The sockets/echo_service sample application for Zephyr implements a TCP echo
server supporting both IPv4 and IPv6 and using a BSD Sockets compatible API.

The purpose of this sample is to show how to use socket service API.
The socket service is a concept where many blocking sockets can be listened by
one thread, and which can then trigger a callback if there is activity in the set
of sockets. This saves memory as only one thread needs to be created in the
system.

The application supports IPv4 and IPv6, and both UDP and TCP are also supported.
The source code for this sample application can be found at:
:zephyr_file:`samples/net/sockets/echo_service`.

Requirements
************

- :ref:`networking_with_host`
- or, a board with hardware networking

Building and Running
********************

Build the Zephyr version of the sockets/echo_service application like this:

.. zephyr-app-commands::
:zephyr-app: samples/net/sockets/echo_service
:board: <board_to_use>
:goals: build
:compact:

After the sample starts, it expects connections at 192.0.2.1, or 2001:db8::1
and port 4242.
The easiest way to connect is:

.. code-block:: console

$ telnet 192.0.2.1 4242

After a connection is made, the application will echo back any line sent
to it. The application implements a single-threaded server using blocking
sockets, and currently is only implemented to serve only one client connection
at time. After the current client disconnects, the next connection can proceed.
6 changes: 6 additions & 0 deletions samples/net/sockets/echo_service/overlay-e1000.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Overlay for experimental TCP as qemu_x86 with E1000

CONFIG_PCIE=y

CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_QEMU_ETHERNET=y
38 changes: 38 additions & 0 deletions samples/net/sockets/echo_service/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# General config
# The async method used in the sample needs more stack for the workqueue
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1500
CONFIG_POSIX_API=y

# Networking config
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=y
CONFIG_NET_TCP=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_NET_IPV4_MAPPING_TO_IPV6=y
CONFIG_POSIX_MAX_FDS=10
CONFIG_NET_MAX_CONN=5
CONFIG_NET_SOCKETS_SERVICE=y
CONFIG_NET_SOCKETS_POLL_MAX=20

# Network driver config
CONFIG_TEST_RANDOM_GENERATOR=y

# Network address config
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
pdgendt marked this conversation as resolved.
Show resolved Hide resolved
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"

# Network buffers
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_BUF_RX_COUNT=64
CONFIG_NET_BUF_TX_COUNT=64
CONFIG_NET_CONTEXT_NET_PKT_POOL=y

CONFIG_NET_SHELL=y
16 changes: 16 additions & 0 deletions samples/net/sockets/echo_service/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
sample:
description: echo server using socket service API
name: socket_service_echo
common:
harness: net
depends_on: netif
filter: CONFIG_FULL_LIBC_SUPPORTED and not CONFIG_NATIVE_LIBC
# eventfd does not work properly with native_posix so exclude it here
platform_exclude:
- native_posix
- native_posix_64
tests:
sample.net.sockets.service.echo:
tags:
- net
- socket
Loading
Loading