You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1015 lines
26 KiB
1015 lines
26 KiB
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/unistd.h>
|
|
#include <sys/socket.h>
|
|
|
|
#ifndef ESP_PLATFORM
|
|
|
|
/* Posix with libcsp */
|
|
|
|
#include <csp/arch/csp_thread.h>
|
|
#include <csp/arch/csp_semaphore.h>
|
|
#define ThreadHandle csp_thread_handle_t
|
|
#define Mutex csp_mutex_t
|
|
#define mutex_lock(mutex, timeout) csp_mutex_lock(&(mutex), (timeout))
|
|
#define mutex_unlock(mutex) csp_mutex_unlock(&(mutex))
|
|
#define mutex_remove(mutex) csp_mutex_remove(&(mutex))
|
|
#define DEFINE_TASK(name) CSP_DEFINE_TASK(name)
|
|
#define thread_exit() do { csp_thread_exit(); return NULL; } while(0)
|
|
#define port_calloc(a,b) csp_calloc((a),(b))
|
|
#define port_free(p) csp_free(p)
|
|
#define port_thread_create(routine, thread_name, stack_size, parameters, priority, return_handle) \
|
|
csp_thread_create(routine, thread_name, stack_size, parameters, priority, return_handle)
|
|
#define PORT_THREAD_CREATE_OK CSP_ERR_NONE
|
|
#define mutex_create_ok(handle) (CSP_SEMAPHORE_OK == csp_mutex_create(&(handle)))
|
|
|
|
#else
|
|
|
|
/* ESP_IDF (cspemu) */
|
|
|
|
#define ThreadHandle TaskHandle_t
|
|
#define Mutex QueueHandle_t
|
|
#define mutex_lock(mutex, timeout) xSemaphoreTake((mutex), (timeout))
|
|
#define mutex_unlock(mutex) xSemaphoreGive((mutex))
|
|
#define mutex_remove(mutex) vSemaphoreDelete((mutex))
|
|
#define DEFINE_TASK(name) void name(void *param)
|
|
#define thread_exit() do { vTaskDelete(NULL); return; } while(0)
|
|
#include <malloc.h>
|
|
#include "esp_log.h"
|
|
#define port_calloc(a,b) calloc((a),(b))
|
|
#define port_free(p) free(p)
|
|
#define port_thread_create(routine, thread_name, stack_size, parameters, priority, return_handle) \
|
|
xTaskCreate(routine, thread_name, stack_size, parameters, priority, return_handle)
|
|
#define PORT_THREAD_CREATE_OK pdPASS
|
|
#define mutex_create_ok(handle) (NULL != (handle = xSemaphoreCreateMutex()))
|
|
|
|
#endif
|
|
|
|
#include "socket_server.h"
|
|
|
|
/**
|
|
* Extra argument validations - can be turned off in production to slightly
|
|
* reduce code size and speed it up.
|
|
*/
|
|
#define TCPD_PARANOID_CHECKS 1
|
|
|
|
static const char *TAG = "tcpd";
|
|
|
|
#ifdef ESP_PLATFORM
|
|
#define TCPD_USE_RXLOCK 1
|
|
#include "esp_log.h"
|
|
#else
|
|
#define TCPD_USE_RXLOCK 0
|
|
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <sys/fcntl.h>
|
|
#include <csp/arch/csp_malloc.h>
|
|
|
|
/* compat with the original csp emu code */
|
|
|
|
// TODO disable spammy logging
|
|
#define ESP_LOGE(tag, format, ...) fprintf(stderr, "[error] %s: "format"\n", tag, ##__VA_ARGS__)
|
|
#define ESP_LOGW(tag, format, ...) fprintf(stderr, "[warn] %s: "format"\n", tag, ##__VA_ARGS__)
|
|
//#define ESP_LOGD(tag, format, ...) fprintf(stderr, "[debug] %s: "format"\n", tag, ##__VA_ARGS__)
|
|
//#define ESP_LOGI(tag, format, ...) fprintf(stderr, "[info] %s: "format"\n", tag, ##__VA_ARGS__)
|
|
#define ESP_LOGD(tag, format, ...) do {} while(0)
|
|
#define ESP_LOGI(tag, format, ...) do {} while(0)
|
|
|
|
#define portMAX_DELAY UINT32_MAX
|
|
#endif
|
|
|
|
static void close_client(TcpdClient_t client, bool mutex_locked);
|
|
|
|
struct sockd_client {
|
|
Tcpd_t serv; // backreference for easier API usage
|
|
int fd;
|
|
uint32_t usetime;
|
|
void *cctx;
|
|
uint32_t tag;
|
|
struct sockaddr_in addr;
|
|
// injected FDs are custom streams that can be used for reads, but can't be kicked due to LRU
|
|
// this allows including e.g. stdin in the select
|
|
bool injected;
|
|
};
|
|
|
|
struct sockd_server {
|
|
struct tcpd_config config;
|
|
int server_fd;
|
|
fd_set active_fd_set;
|
|
struct sockd_client *clients; // this is malloc'd
|
|
uint32_t rxcnt; // activity counter (for tracking LRU sockets)
|
|
ThreadHandle task;
|
|
#if TCPD_USE_RXLOCK
|
|
Mutex rxMutex;
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* Get client by FD. Returns NULL if not found.
|
|
*
|
|
* @param serv - server struct
|
|
* @param sockfd
|
|
* @return
|
|
*/
|
|
TcpdClient_t tcpd_client_by_fd(Tcpd_t serv, int sockfd)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "client_by_fd with no serv!");
|
|
return NULL;
|
|
}
|
|
|
|
if (!serv->clients) {
|
|
ESP_LOGE(TAG, "client_by_fd with no clients table!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
if (serv->clients[i].fd == sockfd) {
|
|
return &serv->clients[i];
|
|
}
|
|
}
|
|
|
|
ESP_LOGE(TAG, "Client for fd %d not found!", sockfd);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Get client by tag. Returns NULL if not found.
|
|
*
|
|
* @param serv - server struct
|
|
* @param sockfd
|
|
* @return
|
|
*/
|
|
TcpdClient_t tcpd_client_by_tag(Tcpd_t serv, uint32_t tag)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_client_by_tag with no serv!");
|
|
return NULL;
|
|
}
|
|
|
|
if (!serv->clients) {
|
|
ESP_LOGE(TAG, "tcpd_client_by_tag with no clients table!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
if (serv->clients[i].tag == tag && serv->clients[i].fd != FD_NONE) {
|
|
return &serv->clients[i];
|
|
}
|
|
}
|
|
|
|
ESP_LOGE(TAG, "Client for tag %x not found!", tag);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Update client's LRU time to indicate its activity
|
|
*
|
|
* @param client - client pointer
|
|
*/
|
|
static inline void record_client_activity(TcpdClient_t client)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "record_client_activity with no client!");
|
|
return;
|
|
}
|
|
|
|
if (client->fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "record_client_activity with no FD!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
client->usetime = ++client->serv->rxcnt;
|
|
ESP_LOGD(TAG, "fd %d used, time %d", client->fd, client->serv->rxcnt);
|
|
}
|
|
|
|
/**
|
|
* Mark client slot as free
|
|
*
|
|
* @param client - client pointer
|
|
*/
|
|
static inline void mark_client_free(TcpdClient_t client)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "mark_client_free with no client!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
client->fd = FD_NONE;
|
|
client->usetime = 0;
|
|
client->tag = 0;
|
|
client->cctx = NULL;
|
|
}
|
|
|
|
/**
|
|
* Create the main server socket
|
|
*/
|
|
static tcpd_err_t make_server_socket(int *pSock, uint16_t port)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!pSock) {
|
|
ESP_LOGE(TAG, "make_server_socket with no pSock!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
#endif
|
|
|
|
int sock;
|
|
struct sockaddr_in name;
|
|
|
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock < 0) {
|
|
ESP_LOGE(TAG, "err in socket()");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
#ifndef ESP_PLATFORM
|
|
// this should prevent failing to bind on POSIX when the program was recently hard-closed
|
|
int one = 1;
|
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&one, sizeof(one));
|
|
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&one, sizeof(one));
|
|
#endif
|
|
|
|
name.sin_family = AF_INET;
|
|
name.sin_port = htons(port);
|
|
name.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
if (bind(sock, (struct sockaddr *) &name, sizeof(name)) < 0) {
|
|
ESP_LOGE(TAG, "err in bind()");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
*pSock = sock;
|
|
return ESP_OK;
|
|
}
|
|
|
|
/**
|
|
* Get a client slot for a new incoming connection.
|
|
* Frees the LRU slot (closing its connection) when all slots are full (and LRU closing is enabled)
|
|
*
|
|
* The client is cleared
|
|
*
|
|
* @param serv - server struct
|
|
* @return client slot
|
|
*/
|
|
static TcpdClient_t get_new_client_slot(Tcpd_t serv)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "get_new_client_slot with no serv!");
|
|
return NULL;
|
|
}
|
|
|
|
if (!serv->clients) {
|
|
ESP_LOGE(TAG, "get_new_client_slot with no clients table!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
ESP_LOGD(TAG, "find free slot");
|
|
|
|
// find free or LRU slot
|
|
uint32_t min = UINT32_MAX;
|
|
TcpdClient_t client = NULL;
|
|
int min_slotnum = -1;
|
|
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
TcpdClient_t aClient = &serv->clients[i];
|
|
if (aClient->fd == FD_NONE) {
|
|
client = aClient;
|
|
ESP_LOGD(TAG, "-> Found empty slot #%d", i);
|
|
break;
|
|
}
|
|
|
|
// find LRU slot
|
|
if (aClient->usetime < min && !aClient->injected) {
|
|
client = aClient;
|
|
min = aClient->usetime;
|
|
min_slotnum = i;
|
|
}
|
|
}
|
|
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "get_new_client_slot no slot found!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
// close old connection
|
|
if (client->fd != FD_NONE) { // injected slot can never get here
|
|
assert(!client->injected);
|
|
|
|
if (serv->config.close_lru) {
|
|
ESP_LOGD(TAG, "Out of client sockets, closing oldest (fd %d, #%d)", client->fd, min_slotnum);
|
|
close(client->fd);
|
|
FD_CLR (client->fd, &serv->active_fd_set);
|
|
} else {
|
|
ESP_LOGW(TAG, "No client slots left.");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
client->serv = serv; // ensure the server reference is set
|
|
mark_client_free(client);
|
|
|
|
return client;
|
|
}
|
|
|
|
/* kick one client */
|
|
void tcpd_kick(TcpdClient_t client)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "tcpd_kick with no client!");
|
|
return;
|
|
}
|
|
|
|
if (client->fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "tcpd_kick with no FD!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
ESP_LOGW(TAG, "kicking client fd %d", client->fd);
|
|
close_client(client, false);
|
|
}
|
|
|
|
/* kick all clients */
|
|
void tcpd_kick_all(Tcpd_t serv, bool with_injected)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_kick_all with no server!");
|
|
return;
|
|
}
|
|
if (serv->server_fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "tcpd_kick_all with no server FD!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
TcpdClient_t client = &serv->clients[i];
|
|
if (client->fd != FD_NONE && (!client->injected || with_injected)) {
|
|
tcpd_kick(client);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* kick all clients */
|
|
int tcpd_kick_by_tag(Tcpd_t serv, uint32_t tag)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_kick_by_tag with no server!");
|
|
return -1;
|
|
}
|
|
if (serv->server_fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "tcpd_kick_by_tag with no server FD!");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int count = 0;
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
TcpdClient_t client = &serv->clients[i];
|
|
if ((client->fd != FD_NONE) && (client->tag == tag)) {
|
|
tcpd_kick(client);
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
/* kick clients with an IP */
|
|
int tcpd_kick_by_ip(Tcpd_t serv, const struct in_addr *addr)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_kick_by_ip with no server!");
|
|
return -1;
|
|
}
|
|
if (serv->server_fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "tcpd_kick_by_ip with no server FD!");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
ESP_LOGD(TAG, "Kick IP=%"PRIx32, addr->s_addr);
|
|
|
|
int count = 0;
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
TcpdClient_t client = &serv->clients[i];
|
|
if ((client->fd != FD_NONE)) {
|
|
ESP_LOGD(TAG, "client %"PRIu32, client->addr.sin_addr.s_addr);
|
|
if (client->addr.sin_addr.s_addr == addr->s_addr) {
|
|
tcpd_kick(client);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static bool fd_is_valid(int fd)
|
|
{
|
|
if (fd == FD_NONE) {
|
|
return false;
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
// always returns 0, but can set errno to EBADF or other
|
|
fcntl(fd, F_GETFD);
|
|
|
|
return errno == 0;
|
|
}
|
|
|
|
static tcpd_err_t read_from_client(TcpdClient_t client)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "read_from_client with no client!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (!client->serv) {
|
|
ESP_LOGE(TAG, "read_from_client with no client->serv!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (client->fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "read_from_client with no FD!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
#endif
|
|
|
|
if (client->serv->config.read_fn) {
|
|
return client->serv->config.read_fn(client->serv, client, client->fd);
|
|
} else {
|
|
static uint8_t scratch[16];
|
|
return read(client->fd, scratch, sizeof(scratch));
|
|
}
|
|
}
|
|
|
|
static void open_client(TcpdClient_t client, int fd, struct sockaddr_in *sockaddr)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "open_client with no client!");
|
|
return;
|
|
}
|
|
|
|
if (!sockaddr) {
|
|
ESP_LOGE(TAG, "open_client with no sockaddr!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
client->fd = fd;
|
|
memcpy(&client->addr, sockaddr, sizeof(struct sockaddr_in));
|
|
|
|
ESP_LOGD(TAG, "+ Client %s:%d joined (fd %d)",
|
|
inet_ntoa(sockaddr->sin_addr),
|
|
ntohs(sockaddr->sin_port),
|
|
client->fd);
|
|
|
|
record_client_activity(client);
|
|
FD_SET(client->fd, &client->serv->active_fd_set);
|
|
|
|
if (client->serv->config.open_fn) {
|
|
client->serv->config.open_fn(client->serv, client);
|
|
}
|
|
}
|
|
|
|
TcpdClient_t tcpd_inject_client(Tcpd_t server, int fd)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!server) {
|
|
ESP_LOGE(TAG, "tcpd_inject_client with no server!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
TcpdClient_t client = get_new_client_slot(server);
|
|
if (!client) { return NULL; }
|
|
|
|
client->injected = true;
|
|
client->fd = fd;
|
|
|
|
ESP_LOGI(TAG, "+ Client injected (fd %d)", client->fd);
|
|
FD_SET(fd, &server->active_fd_set);
|
|
record_client_activity(client);
|
|
|
|
if (server->config.open_fn) {
|
|
server->config.open_fn(server, client);
|
|
}
|
|
|
|
return client;
|
|
}
|
|
|
|
static void close_client(TcpdClient_t client, bool mutex_locked)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "close_client with no client!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (client->fd == FD_NONE) {
|
|
ESP_LOGW(TAG, "- Client became invalid while preparing to close!");
|
|
} else {
|
|
if (!client->serv) {
|
|
ESP_LOGE(TAG, "close_client with no serv!");
|
|
return;
|
|
}
|
|
|
|
#if TCPD_USE_RXLOCK
|
|
// We must guard this to prevent a lwip crash - https://github.com/espressif/esp-lwip/issues/13
|
|
if (!mutex_locked) {
|
|
mutex_lock(client->serv->rxMutex, portMAX_DELAY);
|
|
}
|
|
#endif
|
|
|
|
FD_CLR(client->fd, &client->serv->active_fd_set);
|
|
|
|
if (!client->injected) {
|
|
close(client->fd);
|
|
}
|
|
|
|
#if TCPD_USE_RXLOCK
|
|
if (!mutex_locked) {
|
|
mutex_unlock(client->serv->rxMutex);
|
|
}
|
|
#endif
|
|
|
|
ESP_LOGD(TAG, "- Closing client (fd %d)", client->fd);
|
|
}
|
|
|
|
if (client->serv->config.close_fn) {
|
|
client->serv->config.close_fn(client->serv, client);
|
|
}
|
|
mark_client_free(client);
|
|
}
|
|
|
|
/**
|
|
* TCP server's main thread
|
|
*
|
|
* @param arg
|
|
*/
|
|
DEFINE_TASK(socksrv_thread)
|
|
{
|
|
Tcpd_t serv = param;
|
|
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "socksrv_thread with no serv!");
|
|
thread_exit();
|
|
}
|
|
|
|
if (!serv->clients) {
|
|
ESP_LOGE(TAG, "socksrv_thread with no clients table!");
|
|
thread_exit();
|
|
}
|
|
|
|
if (0 == serv->config.port) {
|
|
ESP_LOGE(TAG, "socksrv_thread with zero port!");
|
|
thread_exit();
|
|
}
|
|
#endif
|
|
|
|
fd_set read_fd_set;
|
|
struct sockaddr_in clientname;
|
|
|
|
/* Create the socket and set it up to accept connections. */
|
|
tcpd_err_t rv = make_server_socket(&serv->server_fd, serv->config.port);
|
|
if (rv != ESP_OK) {
|
|
ESP_LOGE(TAG, "failed to start TCP server");
|
|
thread_exit();
|
|
}
|
|
|
|
if (listen(serv->server_fd, 4) < 0) {
|
|
ESP_LOGE(TAG, "err listen");
|
|
thread_exit();
|
|
}
|
|
|
|
ESP_LOGD(TAG, "Started TCP server on port %d", serv->config.port);
|
|
|
|
/* Initialize the set of active sockets. */
|
|
FD_ZERO (&serv->active_fd_set);
|
|
FD_SET (serv->server_fd, &serv->active_fd_set);
|
|
|
|
while (1) {
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_lock(serv->rxMutex, portMAX_DELAY);
|
|
#endif
|
|
|
|
/* Block until input arrives on one or more active sockets. */
|
|
struct timeval selectto = {.tv_usec = 100000};
|
|
read_fd_set = serv->active_fd_set;
|
|
if (select(FD_SETSIZE, &read_fd_set, NULL, NULL, &selectto) < 0) {
|
|
ESP_LOGE(TAG, "err in select()");
|
|
|
|
// try to clean up
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
TcpdClient_t client = &serv->clients[i];
|
|
if (client->fd != FD_NONE) {
|
|
if (!fd_is_valid(client->fd)) {
|
|
close_client(client, true);
|
|
FD_CLR(client->fd, &read_fd_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fd_is_valid(serv->server_fd)) {
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_unlock(serv->rxMutex);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Service all the sockets with input pending. */
|
|
for (int fd = 0; fd < FD_SETSIZE; ++fd) {
|
|
if (FD_ISSET (fd, &read_fd_set)) {
|
|
if (fd == serv->server_fd) {
|
|
/* Connection request on original socket. */
|
|
socklen_t size = sizeof(clientname);
|
|
int newfd = accept(serv->server_fd, (struct sockaddr *) &clientname, &size);
|
|
if (newfd < 0) {
|
|
ESP_LOGE(TAG, "err in accept()");
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_unlock(serv->rxMutex);
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
struct timeval sockto1 = {.tv_sec = 5};
|
|
setsockopt(newfd, SOL_SOCKET, SO_RCVTIMEO, (char *) &sockto1, sizeof(sockto1));
|
|
struct timeval sockto2 = {.tv_sec = 5};
|
|
setsockopt(newfd, SOL_SOCKET, SO_SNDTIMEO, (char *) &sockto2, sizeof(sockto2));
|
|
|
|
TcpdClient_t slot = get_new_client_slot(serv);
|
|
if (slot) {
|
|
open_client(slot, newfd, &clientname);
|
|
} else {
|
|
// reject the connection
|
|
close(newfd);
|
|
}
|
|
} else if (FD_ISSET(fd, &serv->active_fd_set)) {
|
|
/* Data arriving for an already connected client */
|
|
TcpdClient_t client = tcpd_client_by_fd(serv, fd);
|
|
if (client) {
|
|
if (ESP_OK != read_from_client(client)) {
|
|
close_client(client, true);
|
|
}
|
|
}
|
|
}
|
|
} /* end isset */
|
|
} /* end fd iteration */
|
|
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_unlock(serv->rxMutex);
|
|
#endif
|
|
} /* end server loop */
|
|
|
|
thread_exit();
|
|
}
|
|
|
|
tcpd_err_t tcpd_broadcast(Tcpd_t serv, const uint8_t *data, ssize_t len)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_broadcast with no serv!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (serv->server_fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "tcpd_broadcast with no server FD!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (!data) {
|
|
ESP_LOGE(TAG, "tcpd_send with no data!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
#endif
|
|
|
|
if (len < 0) { len = strlen((const char *) data); }
|
|
|
|
// send it to all clients
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
TcpdClient_t client = &serv->clients[i];
|
|
if (client->fd != FD_NONE) {
|
|
tcpd_send(client, data, len);
|
|
}
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
tcpd_err_t tcpd_send(TcpdClient_t client, const uint8_t *data, ssize_t len)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "tcpd_send with no client!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (!client->serv) {
|
|
ESP_LOGE(TAG, "tcpd_send with no client->serv!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (client->fd == FD_NONE) {
|
|
ESP_LOGE(TAG, "tcpd_send with no FD!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (!data) {
|
|
ESP_LOGE(TAG, "tcpd_send with no data!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
#endif
|
|
|
|
int fd = client->fd;
|
|
|
|
if (client->injected) {
|
|
if (fd == STDIN_FILENO) {
|
|
fd = STDOUT_FILENO;
|
|
} else if (O_RDONLY == fcntl(fd, F_GETFL)) {
|
|
// read-only file, do not try to write (maybe injected stdin)
|
|
return ESP_OK;
|
|
}
|
|
}
|
|
|
|
if (len < 0) { len = strlen((const char *) data); }
|
|
|
|
int rv = write(fd, data, (size_t) len);
|
|
if (rv <= 0) {
|
|
ESP_LOGE(TAG, "Client sock write failed");
|
|
close_client(client, false);
|
|
return ESP_FAIL;
|
|
} else {
|
|
record_client_activity(client);
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
void *tcpd_get_server_ctx(Tcpd_t serv)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_get_server_ctx on NULL!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
return serv->config.sctx;
|
|
}
|
|
|
|
void *tcpd_get_client_ctx(TcpdClient_t client)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "tcpd_get_client_ctx on NULL!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
return client->cctx;
|
|
}
|
|
|
|
void tcpd_set_client_ctx(TcpdClient_t client, void *cctx)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "tcpd_set_client_ctx on NULL!");
|
|
return;
|
|
}
|
|
#endif
|
|
client->cctx = cctx;
|
|
}
|
|
|
|
uint32_t tcpd_get_client_tag(TcpdClient_t client)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "tcpd_get_client_tag on NULL!");
|
|
return 0;
|
|
}
|
|
#endif
|
|
return client->tag;
|
|
}
|
|
|
|
const struct sockaddr_in * tcpd_get_client_addr(TcpdClient_t client)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "tcpd_get_client_addr on NULL!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
return &client->addr;
|
|
}
|
|
|
|
void tcpd_set_client_tag(TcpdClient_t client, uint32_t tag)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "tcpd_set_client_tag on NULL!");
|
|
return;
|
|
}
|
|
#endif
|
|
client->tag = tag;
|
|
}
|
|
|
|
int tcpd_get_client_fd(TcpdClient_t client)
|
|
{
|
|
return (client == NULL ? FD_NONE : client->fd);
|
|
}
|
|
|
|
tcpd_err_t tcpd_init(const tcpd_config_t *config, Tcpd_t *handle)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!config) {
|
|
ESP_LOGE(TAG, "tcpd_init with no config!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (!handle) {
|
|
ESP_LOGE(TAG, "tcpd_init with no handle ptr!");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
#endif
|
|
|
|
Tcpd_t serv = port_calloc(sizeof(struct sockd_server), 1);
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "server alloc failed");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
memcpy(&serv->config, config, sizeof(struct tcpd_config));
|
|
|
|
serv->clients = port_calloc(sizeof(struct sockd_client), config->max_clients);
|
|
if (!serv->clients) {
|
|
port_free(serv);
|
|
ESP_LOGE(TAG, "clients alloc failed");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
// gracefully shutdown all clients (this calls the close function to free user ctx)
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
mark_client_free(&serv->clients[i]);
|
|
}
|
|
|
|
#if TCPD_USE_RXLOCK
|
|
if (!mutex_create_ok(serv->rxMutex)) {
|
|
ESP_LOGE(TAG, "error creating mutex");
|
|
port_free(serv->clients);
|
|
port_free(serv);
|
|
return ESP_FAIL;
|
|
}
|
|
#endif
|
|
|
|
if (!config->start_immediately) {
|
|
tcpd_suspend(serv);
|
|
}
|
|
if (PORT_THREAD_CREATE_OK != port_thread_create(socksrv_thread,
|
|
config->task_name,
|
|
config->task_stack,
|
|
serv,
|
|
config->task_prio,
|
|
&serv->task)
|
|
) {
|
|
ESP_LOGE(TAG, "error creating server thread");
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_remove(serv->rxMutex);
|
|
#endif
|
|
port_free(serv->clients);
|
|
port_free(serv);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
*handle = serv;
|
|
return ESP_OK;
|
|
}
|
|
|
|
void tcpd_shutdown(Tcpd_t serv)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_shutdown with no handle!");
|
|
return;
|
|
}
|
|
#endif
|
|
close(serv->server_fd);
|
|
|
|
// this can fail if the server is already stopped, that's OK
|
|
tcpd_suspend(serv);
|
|
|
|
// gracefully shutdown all clients (this calls the close function to free user ctx)
|
|
for (int i = 0; i < serv->config.max_clients; i++) {
|
|
TcpdClient_t client = &serv->clients[i];
|
|
if (client->fd != FD_NONE && !client->injected) {
|
|
close_client(client, true);
|
|
}
|
|
}
|
|
|
|
if (serv->config.sctx_free_fn && serv->config.sctx) {
|
|
serv->config.sctx_free_fn(serv->config.sctx);
|
|
}
|
|
|
|
// XXX task kill is not implemented in CSP
|
|
#ifdef ESP_PLATFORM
|
|
vTaskDelete(serv->task);
|
|
#endif
|
|
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_remove(serv->rxMutex);
|
|
#endif
|
|
port_free(serv->clients);
|
|
port_free(serv);
|
|
}
|
|
|
|
void tcpd_suspend(Tcpd_t serv)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_stop with no handle!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Since these were implemented using the lock, they now don't work.
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_lock(serv->rxMutex, portMAX_DELAY);
|
|
#endif
|
|
}
|
|
|
|
void tcpd_resume(Tcpd_t serv)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_start with no handle!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if TCPD_USE_RXLOCK
|
|
mutex_unlock(&serv->rxMutex);
|
|
#endif
|
|
}
|
|
|
|
tcpd_err_t tcpd_iter_init(struct tcpd_client_iter *iter, Tcpd_t serv) {
|
|
if (!serv) {
|
|
ESP_LOGE(TAG, "tcpd_iter_init NULL server!");
|
|
return -1;
|
|
}
|
|
|
|
iter->server = serv;
|
|
iter->next = 0;
|
|
return 0;
|
|
}
|
|
|
|
TcpdClient_t tcpd_client_iter_next(struct tcpd_client_iter *iterator)
|
|
{
|
|
#if TCPD_PARANOID_CHECKS
|
|
if (!iterator) {
|
|
ESP_LOGE(TAG, "tcpd_client_iter_next NULL iterator!");
|
|
return NULL;
|
|
}
|
|
if (!iterator->server) {
|
|
ESP_LOGE(TAG, "tcpd_client_iter_next NULL server!");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
const uint16_t maxnum = iterator->server->config.max_clients;
|
|
struct sockd_client * const clients = iterator->server->clients;
|
|
|
|
for(; iterator->next < maxnum; iterator->next++) {
|
|
TcpdClient_t client = &clients[iterator->next];
|
|
if (client->fd != FD_NONE) {
|
|
iterator->next++;
|
|
return client;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|