mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-10-06 03:20:59 +02:00
471 lines
13 KiB
C
471 lines
13 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2009-2020 Roger Light <roger@atchoo.org>
|
|
*
|
|
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
|
*
|
|
* SPDX-FileContributor: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
|
*/
|
|
|
|
/*
|
|
All rights reserved. This program and the accompanying materials
|
|
are made available under the terms of the Eclipse Public License 2.0
|
|
and Eclipse Distribution License v1.0 which accompany this distribution.
|
|
|
|
The Eclipse Public License is available at
|
|
https://www.eclipse.org/legal/epl-2.0/
|
|
and the Eclipse Distribution License is available at
|
|
http://www.eclipse.org/org/documents/edl-v10.php.
|
|
|
|
Contributors:
|
|
Roger Light - initial implementation and documentation.
|
|
Espressif Systems (Shanghai) CO LTD - added support for ESP-TLS based connections
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
# include <arpa/inet.h>
|
|
# include <ifaddrs.h>
|
|
# include <netdb.h>
|
|
# include <netinet/tcp.h>
|
|
# include <strings.h>
|
|
# include <sys/socket.h>
|
|
# include <unistd.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_NETINET_IN_H
|
|
# include <netinet/in.h>
|
|
#endif
|
|
|
|
#include "mosquitto_broker_internal.h"
|
|
#include "mqtt_protocol.h"
|
|
#include "memory_mosq.h"
|
|
#include "misc_mosq.h"
|
|
#include "net_mosq.h"
|
|
#include "util_mosq.h"
|
|
#include "esp_tls.h"
|
|
|
|
#include "sys_tree.h"
|
|
|
|
#define MAX_CONNECTIONS (64)
|
|
|
|
struct esp_tls_context {
|
|
int sock;
|
|
esp_tls_t *tls;
|
|
};
|
|
|
|
static struct esp_tls_context tls_ctx[MAX_CONNECTIONS];
|
|
static esp_tls_cfg_server_t *tls_cfg;
|
|
|
|
void net__set_tls_config(esp_tls_cfg_server_t *config)
|
|
{
|
|
if (config) {
|
|
tls_cfg = mosquitto__calloc(1, sizeof(esp_tls_cfg_server_t));
|
|
if (tls_cfg) {
|
|
memcpy(tls_cfg, config, sizeof(esp_tls_cfg_server_t));
|
|
} else {
|
|
log__printf(NULL, MOSQ_LOG_ERR, "Unable to allocate ESP-TLS configuration structure, continuing with plain TCP transport only");
|
|
}
|
|
}
|
|
}
|
|
|
|
void net__broker_init(void)
|
|
{
|
|
for (int i = 0; i < MAX_CONNECTIONS; ++i) {
|
|
tls_ctx[i].sock = INVALID_SOCKET;
|
|
tls_ctx[i].tls = NULL;
|
|
}
|
|
net__init();
|
|
}
|
|
|
|
|
|
void net__broker_cleanup(void)
|
|
{
|
|
net__cleanup();
|
|
mosquitto__free(tls_cfg);
|
|
tls_cfg = NULL;
|
|
}
|
|
|
|
|
|
static void net__print_error(unsigned int log, const char *format_str)
|
|
{
|
|
char *buf;
|
|
buf = strerror(errno);
|
|
log__printf(NULL, log, format_str, buf);
|
|
}
|
|
|
|
|
|
struct mosquitto *net__socket_accept(struct mosquitto__listener_sock *listensock)
|
|
{
|
|
mosq_sock_t new_sock = INVALID_SOCKET;
|
|
struct mosquitto *new_context;
|
|
|
|
new_sock = accept(listensock->sock, NULL, 0);
|
|
if (new_sock == INVALID_SOCKET) {
|
|
if (errno == EAGAIN) { // mosquitto tries to accept() in a loop until EAGAIN is returned
|
|
return NULL;
|
|
}
|
|
log__printf(NULL, MOSQ_LOG_ERR,
|
|
"Unable to accept new connection, system socket count has been exceeded. Try increasing \"ulimit -n\" or equivalent.");
|
|
return NULL;
|
|
}
|
|
|
|
if (tls_cfg) {
|
|
// Finds first free spot in the context array
|
|
int ctx;
|
|
for (ctx = 0; ctx < MAX_CONNECTIONS; ++ctx) {
|
|
if (tls_ctx[ctx].sock == INVALID_SOCKET) {
|
|
tls_ctx[ctx].sock = new_sock;
|
|
tls_ctx[ctx].tls = esp_tls_init();
|
|
if (!tls_ctx[ctx].tls) {
|
|
log__printf(NULL, MOSQ_LOG_ERR, "Faled to create a new ESP-TLS context");
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (ctx >= MAX_CONNECTIONS) {
|
|
log__printf(NULL, MOSQ_LOG_ERR, "Unable to create new ESP-TLS connection. Try increasing \"MAX_CONNECTIONS\"");
|
|
return NULL;
|
|
}
|
|
int ret = esp_tls_server_session_create(tls_cfg, new_sock, tls_ctx[ctx].tls);
|
|
if (ret != 0) {
|
|
log__printf(NULL, MOSQ_LOG_ERR, "Unable to create new ESP-TLS session");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
G_SOCKET_CONNECTIONS_INC();
|
|
|
|
if (net__socket_nonblock(&new_sock)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (db.config->set_tcp_nodelay) {
|
|
int flag = 1;
|
|
if (setsockopt(new_sock, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)) != 0) {
|
|
log__printf(NULL, MOSQ_LOG_WARNING, "Warning: Unable to set TCP_NODELAY.");
|
|
}
|
|
}
|
|
|
|
new_context = context__init(new_sock);
|
|
if (!new_context) {
|
|
COMPAT_CLOSE(new_sock);
|
|
return NULL;
|
|
}
|
|
new_context->listener = listensock->listener;
|
|
if (!new_context->listener) {
|
|
context__cleanup(new_context, true);
|
|
return NULL;
|
|
}
|
|
new_context->listener->client_count++;
|
|
|
|
if (new_context->listener->max_connections > 0 && new_context->listener->client_count > new_context->listener->max_connections) {
|
|
if (db.config->connection_messages == true) {
|
|
log__printf(NULL, MOSQ_LOG_NOTICE, "Client connection from %s denied: max_connections exceeded.", new_context->address);
|
|
}
|
|
context__cleanup(new_context, true);
|
|
return NULL;
|
|
}
|
|
|
|
if (db.config->connection_messages == true) {
|
|
log__printf(NULL, MOSQ_LOG_NOTICE, "New connection from %s:%d on port %d.",
|
|
new_context->address, new_context->remote_port, new_context->listener->port);
|
|
}
|
|
|
|
return new_context;
|
|
}
|
|
|
|
int net__load_certificates(struct mosquitto__listener *listener)
|
|
{
|
|
return MOSQ_ERR_SUCCESS;
|
|
}
|
|
|
|
|
|
int net__tls_load_verify(struct mosquitto__listener *listener)
|
|
{
|
|
return net__load_certificates(listener);
|
|
}
|
|
|
|
|
|
static int net__socket_listen_tcp(struct mosquitto__listener *listener)
|
|
{
|
|
mosq_sock_t sock = INVALID_SOCKET;
|
|
struct addrinfo hints;
|
|
struct addrinfo *ainfo, *rp;
|
|
char service[10];
|
|
int rc;
|
|
int ss_opt = 1;
|
|
|
|
if (!listener) {
|
|
return MOSQ_ERR_INVAL;
|
|
}
|
|
|
|
snprintf(service, 10, "%d", listener->port);
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
if (listener->socket_domain) {
|
|
hints.ai_family = listener->socket_domain;
|
|
} else {
|
|
hints.ai_family = AF_UNSPEC;
|
|
}
|
|
hints.ai_flags = AI_PASSIVE;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
rc = getaddrinfo(listener->host, service, &hints, &ainfo);
|
|
if (rc) {
|
|
log__printf(NULL, MOSQ_LOG_ERR, "Error creating listener: %s.", gai_strerror(rc));
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
listener->sock_count = 0;
|
|
listener->socks = NULL;
|
|
|
|
for (rp = ainfo; rp; rp = rp->ai_next) {
|
|
if (rp->ai_family == AF_INET) {
|
|
log__printf(NULL, MOSQ_LOG_INFO, "Opening ipv4 listen socket on port %d.", ntohs(((struct sockaddr_in *)rp->ai_addr)->sin_port));
|
|
} else if (rp->ai_family == AF_INET6) {
|
|
log__printf(NULL, MOSQ_LOG_INFO, "Opening ipv6 listen socket on port %d.", ntohs(((struct sockaddr_in6 *)rp->ai_addr)->sin6_port));
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (sock == INVALID_SOCKET) {
|
|
net__print_error(MOSQ_LOG_WARNING, "Warning: %s");
|
|
continue;
|
|
}
|
|
listener->sock_count++;
|
|
listener->socks = mosquitto__realloc(listener->socks, sizeof(mosq_sock_t) * (size_t)listener->sock_count);
|
|
if (!listener->socks) {
|
|
log__printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory.");
|
|
freeaddrinfo(ainfo);
|
|
COMPAT_CLOSE(sock);
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
listener->socks[listener->sock_count - 1] = sock;
|
|
|
|
#ifndef WIN32
|
|
ss_opt = 1;
|
|
/* Unimportant if this fails */
|
|
(void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ss_opt, sizeof(ss_opt));
|
|
#endif
|
|
#ifdef IPV6_V6ONLY
|
|
ss_opt = 1;
|
|
(void)setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &ss_opt, sizeof(ss_opt));
|
|
#endif
|
|
|
|
if (net__socket_nonblock(&sock)) {
|
|
freeaddrinfo(ainfo);
|
|
mosquitto__free(listener->socks);
|
|
return 1;
|
|
}
|
|
|
|
if (listener->bind_interface) {
|
|
log__printf(NULL, MOSQ_LOG_ERR, "Error: listener->bind_interface is not supported");
|
|
return 1;
|
|
}
|
|
|
|
if (bind(sock, rp->ai_addr, rp->ai_addrlen) == -1) {
|
|
net__print_error(MOSQ_LOG_ERR, "Error: %s");
|
|
COMPAT_CLOSE(sock);
|
|
freeaddrinfo(ainfo);
|
|
mosquitto__free(listener->socks);
|
|
return 1;
|
|
}
|
|
|
|
if (listen(sock, 100) == -1) {
|
|
net__print_error(MOSQ_LOG_ERR, "Error: %s");
|
|
freeaddrinfo(ainfo);
|
|
COMPAT_CLOSE(sock);
|
|
mosquitto__free(listener->socks);
|
|
return 1;
|
|
}
|
|
}
|
|
freeaddrinfo(ainfo);
|
|
|
|
if (listener->bind_interface) {
|
|
mosquitto__free(listener->socks);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Creates a socket and listens on port 'port'.
|
|
* Returns 1 on failure
|
|
* Returns 0 on success.
|
|
*/
|
|
int net__socket_listen(struct mosquitto__listener *listener)
|
|
{
|
|
int rc;
|
|
|
|
if (!listener) {
|
|
return MOSQ_ERR_INVAL;
|
|
}
|
|
|
|
rc = net__socket_listen_tcp(listener);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
/* We need to have at least one working socket. */
|
|
if (listener->sock_count > 0) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int net__socket_get_address(mosq_sock_t sock, char *buf, size_t len, uint16_t *remote_port)
|
|
{
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen;
|
|
|
|
memset(&addr, 0, sizeof(struct sockaddr_storage));
|
|
addrlen = sizeof(addr);
|
|
if (!getpeername(sock, (struct sockaddr *)&addr, &addrlen)) {
|
|
if (addr.ss_family == AF_INET) {
|
|
if (remote_port) {
|
|
*remote_port = ntohs(((struct sockaddr_in *)&addr)->sin_port);
|
|
}
|
|
if (inet_ntop(AF_INET, &((struct sockaddr_in *)&addr)->sin_addr.s_addr, buf, (socklen_t)len)) {
|
|
return 0;
|
|
}
|
|
} else if (addr.ss_family == AF_INET6) {
|
|
if (remote_port) {
|
|
*remote_port = ntohs(((struct sockaddr_in6 *)&addr)->sin6_port);
|
|
}
|
|
if (inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr.s6_addr, buf, (socklen_t)len)) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// -----------------------------------
|
|
|
|
ssize_t net__read(struct mosquitto *mosq, void *buf, size_t count)
|
|
{
|
|
assert(mosq);
|
|
errno = 0;
|
|
for (int i = 0; i < MAX_CONNECTIONS; ++i) {
|
|
if (tls_ctx[i].sock == mosq->sock) {
|
|
return esp_tls_conn_read(tls_ctx[i].tls, buf, count);
|
|
}
|
|
}
|
|
return read(mosq->sock, buf, count);
|
|
}
|
|
|
|
ssize_t net__write(struct mosquitto *mosq, const void *buf, size_t count)
|
|
{
|
|
assert(mosq);
|
|
errno = 0;
|
|
for (int i = 0; i < MAX_CONNECTIONS; ++i) {
|
|
if (tls_ctx[i].sock == mosq->sock) {
|
|
return esp_tls_conn_write(tls_ctx[i].tls, buf, count);
|
|
}
|
|
}
|
|
return send(mosq->sock, buf, count, MSG_NOSIGNAL);
|
|
}
|
|
|
|
|
|
int net__socket_nonblock(mosq_sock_t *sock)
|
|
{
|
|
int opt;
|
|
/* Set non-blocking */
|
|
opt = fcntl(*sock, F_GETFL, 0);
|
|
if (opt == -1) {
|
|
COMPAT_CLOSE(*sock);
|
|
*sock = INVALID_SOCKET;
|
|
return MOSQ_ERR_ERRNO;
|
|
}
|
|
if (fcntl(*sock, F_SETFL, opt | O_NONBLOCK) == -1) {
|
|
/* If either fcntl fails, don't want to allow this client to connect. */
|
|
COMPAT_CLOSE(*sock);
|
|
*sock = INVALID_SOCKET;
|
|
return MOSQ_ERR_ERRNO;
|
|
}
|
|
return MOSQ_ERR_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Close a socket associated with a context and set it to -1.
|
|
* Returns 1 on failure (context is NULL)
|
|
* Returns 0 on success.
|
|
*/
|
|
int net__socket_close(struct mosquitto *mosq)
|
|
{
|
|
int rc = 0;
|
|
#ifdef WITH_BROKER
|
|
struct mosquitto *mosq_found;
|
|
#endif
|
|
|
|
assert(mosq);
|
|
#ifdef WITH_TLS
|
|
#ifdef WITH_WEBSOCKETS
|
|
if (!mosq->wsi)
|
|
#endif
|
|
{
|
|
if (mosq->ssl) {
|
|
if (!SSL_in_init(mosq->ssl)) {
|
|
SSL_shutdown(mosq->ssl);
|
|
}
|
|
SSL_free(mosq->ssl);
|
|
mosq->ssl = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef WITH_WEBSOCKETS
|
|
if (mosq->wsi) {
|
|
if (mosq->state != mosq_cs_disconnecting) {
|
|
mosquitto__set_state(mosq, mosq_cs_disconnect_ws);
|
|
}
|
|
lws_callback_on_writable(mosq->wsi);
|
|
} else
|
|
#endif
|
|
{
|
|
if (mosq->sock != INVALID_SOCKET) {
|
|
#ifdef WITH_BROKER
|
|
HASH_FIND(hh_sock, db.contexts_by_sock, &mosq->sock, sizeof(mosq->sock), mosq_found);
|
|
if (mosq_found) {
|
|
HASH_DELETE(hh_sock, db.contexts_by_sock, mosq_found);
|
|
}
|
|
#endif
|
|
rc = COMPAT_CLOSE(mosq->sock);
|
|
// Finds first free spot in the context array
|
|
for (int i = 0; i < MAX_CONNECTIONS; ++i) {
|
|
if (tls_ctx[i].sock == mosq->sock) {
|
|
tls_ctx[i].sock = INVALID_SOCKET;
|
|
esp_tls_server_session_delete(tls_ctx[i].tls);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mosq->sock = INVALID_SOCKET;
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_BROKER
|
|
if (mosq->listener) {
|
|
mosq->listener->client_count--;
|
|
mosq->listener = NULL;
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
int net__init(void)
|
|
{
|
|
return MOSQ_ERR_SUCCESS;
|
|
}
|
|
|
|
void net__cleanup(void)
|
|
{
|
|
}
|