blob: dbdde28cf3027da8d8d419b7ba5bae032b0948ed [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_echo_server_svc_sample, LOG_LEVEL_DBG);
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/posix/unistd.h>
#include <zephyr/posix/poll.h>
#include <zephyr/posix/arpa/inet.h>
#include <zephyr/posix/sys/socket.h>
#include <zephyr/net/socket_service.h>
#define MY_PORT 4242
static char addr_str[INET6_ADDRSTRLEN];
static struct pollfd sockfd_udp[1] = {
[0] = { .fd = -1 }, /* UDP socket */
};
static struct pollfd sockfd_tcp[1] = {
[0] = { .fd = -1 }, /* TCP socket */
};
#define MAX_SERVICES 1
static void receive_data(bool is_udp, struct net_socket_service_event *pev,
char *buf, size_t buflen);
static void tcp_service_handler(struct k_work *work)
{
struct net_socket_service_event *pev =
CONTAINER_OF(work, struct net_socket_service_event, work);
static char buf[1500];
/* Note that in this application we receive / send data from
* system work queue. In proper application the socket reading and data
* sending should be done so that the system work queue is not blocked.
* It is possible to create a socket service that uses own work queue.
*/
receive_data(false, pev, buf, sizeof(buf));
}
static void udp_service_handler(struct k_work *work)
{
struct net_socket_service_event *pev =
CONTAINER_OF(work, struct net_socket_service_event, work);
static char buf[1500];
receive_data(true, pev, buf, sizeof(buf));
}
/* In this example we create two services, one with async behavior and one with
* sync one. The async is for TCP and sync is for UDP (this is just an arbitrary
* choice).
* This is an artificial example, both UDP and TCP sockets could be served by the
* same service.
*/
NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(service_udp, NULL, udp_service_handler, MAX_SERVICES);
NET_SOCKET_SERVICE_ASYNC_DEFINE_STATIC(service_tcp, NULL, tcp_service_handler, MAX_SERVICES);
static void receive_data(bool is_udp, struct net_socket_service_event *pev,
char *buf, size_t buflen)
{
struct pollfd *pfd = &pev->event;
int client = pfd->fd;
struct sockaddr_in6 addr;
socklen_t addrlen = sizeof(addr);
int len, out_len;
char *p;
len = recvfrom(client, buf, buflen, 0,
(struct sockaddr *)&addr, &addrlen);
if (len <= 0) {
if (len < 0) {
LOG_ERR("recv: %d", -errno);
}
/* If the TCP socket is closed, mark it as non pollable */
if (!is_udp && sockfd_tcp[0].fd == client) {
sockfd_tcp[0].fd = -1;
/* Update the handler so that client connection is
* not monitored any more.
*/
(void)net_socket_service_register(&service_tcp, sockfd_tcp,
ARRAY_SIZE(sockfd_tcp), NULL);
close(client);
LOG_INF("Connection from %s closed", addr_str);
}
return;
}
p = buf;
do {
out_len = sendto(client, p, len, 0,
(struct sockaddr *)&addr, addrlen);
if (out_len < 0) {
LOG_ERR("sendto: %d", -errno);
break;
}
p += out_len;
len -= out_len;
} while (len);
}
static int setup_tcp_socket(struct sockaddr_in6 *addr)
{
socklen_t optlen = sizeof(int);
int ret, sock, opt;
sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
LOG_ERR("socket: %d", -errno);
return -errno;
}
ret = getsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, &optlen);
if (ret == 0 && opt) {
LOG_INF("IPV6_V6ONLY option is on, turning it off.");
opt = 0;
ret = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, optlen);
if (ret < 0) {
LOG_WRN("Cannot turn off IPV6_V6ONLY option");
} else {
LOG_INF("Sharing same socket between IPv6 and IPv4");
}
}
if (bind(sock, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
LOG_ERR("bind: %d", -errno);
return -errno;
}
if (listen(sock, 5) < 0) {
LOG_ERR("listen: %d", -errno);
return -errno;
}
return sock;
}
static int setup_udp_socket(struct sockaddr_in6 *addr)
{
socklen_t optlen = sizeof(int);
int ret, sock, opt;
sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
LOG_ERR("socket: %d", -errno);
return -errno;
}
ret = getsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, &optlen);
if (ret == 0 && opt) {
LOG_INF("IPV6_V6ONLY option is on, turning it off.");
opt = 0;
ret = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, optlen);
if (ret < 0) {
LOG_WRN("Cannot turn off IPV6_V6ONLY option");
} else {
LOG_INF("Sharing same socket between IPv6 and IPv4");
}
}
if (bind(sock, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
LOG_ERR("bind: %d", -errno);
return -errno;
}
return sock;
}
int main(void)
{
int tcp_sock, udp_sock, ret;
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_ANY_INIT,
.sin6_port = htons(MY_PORT),
};
static int counter;
tcp_sock = setup_tcp_socket(&addr);
if (tcp_sock < 0) {
return tcp_sock;
}
udp_sock = setup_udp_socket(&addr);
if (udp_sock < 0) {
return udp_sock;
}
sockfd_udp[0].fd = udp_sock;
sockfd_udp[0].events = POLLIN;
/* Register UDP socket to service handler */
ret = net_socket_service_register(&service_udp, sockfd_udp,
ARRAY_SIZE(sockfd_udp), NULL);
if (ret < 0) {
LOG_ERR("Cannot register socket service handler (%d)", ret);
}
LOG_INF("Single-threaded TCP/UDP echo server waits "
"for a connection on port %d", MY_PORT);
while (1) {
struct sockaddr_in6 client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client;
client = accept(tcp_sock, (struct sockaddr *)&client_addr,
&client_addr_len);
if (client < 0) {
LOG_ERR("accept: %d", -errno);
continue;
}
inet_ntop(client_addr.sin6_family, &client_addr.sin6_addr,
addr_str, sizeof(addr_str));
LOG_INF("Connection #%d from %s (%d)", counter++, addr_str, client);
sockfd_tcp[0].fd = client;
sockfd_tcp[0].events = POLLIN;
/* Register all the sockets to service handler */
ret = net_socket_service_register(&service_tcp, sockfd_tcp,
ARRAY_SIZE(sockfd_tcp), NULL);
if (ret < 0) {
LOG_ERR("Cannot register socket service handler (%d)",
ret);
break;
}
}
(void)net_socket_service_unregister(&service_tcp);
(void)net_socket_service_unregister(&service_udp);
close(tcp_sock);
close(udp_sock);
return 0;
}