Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
89c3b07
certs: update to also trusting ISRG ROOT X2
lws-team May 1, 2026
e54a2ee
Refresh of certs-update-to-trusting-isrg
lws-team May 1, 2026
8ce0999
cancel-fixes
lws-team May 2, 2026
d6a58c8
smd: fix build if disabled
lws-team May 2, 2026
2c26171
h2-let-encapsulated-ws-do-pings
lws-team May 5, 2026
f7ec832
dnessc-monitor: distribution
lws-team May 2, 2026
479bf00
debug daemon start
lws-team May 2, 2026
c6ba23d
dht: reduce log spew
lws-team May 5, 2026
5828d20
dht:sweep
lws-team May 5, 2026
8d04c81
Refresh of h2-let-encapsulated-ws-do
lws-team May 5, 2026
9f21aea
dht: increase limits
lws-team May 5, 2026
96a3f83
openssl: remove fixes teardown
lws-team May 5, 2026
bf3d6fd
logging-cleanup
lws-team May 5, 2026
c5ade8f
lws-proxy: ws proxy can handle duplex
lws-team May 5, 2026
4e75d52
ssstress-warmcat: improve valgrind
lws-team May 5, 2026
5b40241
meddles
lws-team May 6, 2026
b57dc0d
tls: ca pki apis
lws-team May 6, 2026
9616690
r4
lws-team May 6, 2026
3a46bc9
spawn: isolate plugins
lws-team May 6, 2026
943d5a4
dnssec-monitor: improvements
lws-team May 6, 2026
fd59f10
authentication digest: allow keepalive reuse
lws-team May 7, 2026
e3bb025
gc9a01a
lws-team May 7, 2026
322b685
rebound
lws-team May 7, 2026
86e1158
plugins: READMEs
lws-team May 8, 2026
e7acd59
CID 909762: NEGATIVE_RETURNS
lws-team May 8, 2026
b5f45e3
CID 909761: REVERSE_INULL
lws-team May 8, 2026
3c6e7e2
CID 909760: CHECKED_RETURN
lws-team May 8, 2026
41eaaa9
CID 909759: FORWARD_NULL
lws-team May 8, 2026
12a660b
CID 909756: DEADCODE
lws-team May 8, 2026
d368ebb
CID 909755: CHECKED_RETURN
lws-team May 8, 2026
9d37567
mqtt: qos2
lws-team May 8, 2026
c50361e
tls: mbedtls: append PEM chain certs to leaf so full chain is sent on…
precla May 13, 2026
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
6 changes: 6 additions & 0 deletions READMEs/README.coding.md
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,12 @@ LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS to add your certificate to
the SSL_CTX directly. The vhost SSL_CTX * is in the user parameter in that
callback.

@section tls_cleanup Process-wide TLS library cleanup

If you are using OpenSSL (>= 1.1.0) and you destroy the last `lws_context`, you may want to clean up the process-wide allocations made by the TLS library. You can call `lws_tls_cleanup_process()` to do this.

However, be aware that if you call this, you cannot re-initialize the OpenSSL library in the same process. So only call it if you are completely finished with `libwebsockets` and the TLS library. If you plan to create a new `lws_context` later in the same process lifecycle, you must not call this API.

@section clientasync Async nature of client connections

When you call `lws_client_connect_info(..)` and get a `wsi` back, it does not
Expand Down
46 changes: 46 additions & 0 deletions READMEs/README.lws_plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,49 @@ lws_plugins_destroy(struct lws_plugin **pplugin, each_plugin_cb_t each,
plugins and a pointer to its exported header object, so you can walk this
after loading.

## Protocol Plugin Best Practices

When writing a protocol plugin that utilizes `LWS_CALLBACK_PROTOCOL_INIT`, you must follow these requirements:

### 1. Ignore NULL `in` Parameters

During context creation or system initialization, `LWS_CALLBACK_PROTOCOL_INIT` may be called with a `NULL` `in` parameter (which normally carries the `lws_protocol_vhost_options`). Your plugin must safely ignore this and exit without error:

```c
case LWS_CALLBACK_PROTOCOL_INIT:
if (!in)
return 0;
```

### 2. Contextual Warning for PVO Errors

When parsing `lws_protocol_vhost_options` (PVOs) during `PROTOCOL_INIT`, if an error occurs (such as a missing or invalid value), you should use `lws_vhost_warn(lws_get_vhost(wsi), ...)` or `lws_vhost_err(...)` instead of generic logging. This ensures the user can understand *which* vhost is misconfigured, especially in multi-vhost setups.

### 3. Stub Process Isolation

Generic stub processes (such as `--lws-stub=dnssec-priv`) inherit the user's config and will attempt to initialize all plugins. To prevent resource contention (like conflicting UDP port bindings or duplicated threads), plugins must explicitly opt-in or opt-out of running inside stubs.

If your plugin **should never run** inside a stub process (which is the case for most application and UI plugins), you must inject this snippet at the top of your `PROTOCOL_INIT` block:

```c
case LWS_CALLBACK_PROTOCOL_INIT:
if (!in)
return 0;

/* Do not initialize in generic stub processes */
if (lws_cmdline_option_cx(lws_get_context(wsi), "--lws-stub"))
return 0;
```

If your plugin **is explicitly designed to run** inside a specific stub (e.g. `dnssec-monitor` running inside `dnssec-priv`), you must modify the snippet to ensure it only initializes for *that specific* stub, and ignores any others:

```c
case LWS_CALLBACK_PROTOCOL_INIT:
if (!in)
return 0;

/* Only initialize if we are running as the dnssec-priv stub */
const char *stub = lws_cmdline_option_cx(lws_get_context(wsi), "--lws-stub");
if (stub && strcmp(stub, "dnssec-priv"))
return 0;
```
2 changes: 2 additions & 0 deletions include/libwebsockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,9 @@ lws_fx_string(const lws_fx_t *a, char *buf, size_t size);
#include <libwebsockets/lws-dlo.h>
#include <libwebsockets/lws-ssd1306-i2c.h>
#include <libwebsockets/lws-ili9341-spi.h>
#include <libwebsockets/lws-gc9a01a-spi.h>
#include <libwebsockets/lws-spd1656-spi.h>
#include <libwebsockets/lws-ssd1675b-spi.h>
#include <libwebsockets/lws-uc8176-spi.h>
#include <libwebsockets/lws-ssd1675b-spi.h>
#include <libwebsockets/lws-settings.h>
Expand Down
1 change: 1 addition & 0 deletions include/libwebsockets/lws-async-dns.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ typedef enum {
#define LWS_ADNS_NOCACHE 0x40000 /* force network query, bypass cache */
#define LWS_ADNS_WANT_DNSSEC 0x80000 /* Explicitly set DO bit in EDNS0 OPT record */
#define LWS_ADNS_IGNORE_HOSTS_FILE 0x100000 /* Bypass checking /etc/hosts and force network DNS lookup */
#define LADNS_NO_WSI_BUT_OK ((struct lws *)(intptr_t)0x1)

struct addrinfo;

Expand Down
12 changes: 9 additions & 3 deletions include/libwebsockets/lws-callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -885,20 +885,26 @@ enum lws_callback_reasons {
* Return nonzero to close the wsi.
*/

LWS_CALLBACK_HTTP_INTERCEPTOR_CHECK = 213,
LWS_CALLBACK_MQTT_QOS2_RX_COMPLETE = 213,
/**< When a QoS2 message has fully completed the transaction (PUBREL
* received, PUBCOMP sent), this callback is generated.
* `in` will point to the `uint16_t` packet ID that completed.
*/

LWS_CALLBACK_HTTP_INTERCEPTOR_CHECK = 214,
/**< A mount has a interceptor_path enabled, this callback asks the
* protocol bound to that mount if it is OK for this request to
* proceed. If returning 0, the request proceeds to the original
* mount. If nonzero, the request is diverted to the interceptor_path
* mount.
*/

LWS_CALLBACK_GET_PSS_SIZE = 214,
LWS_CALLBACK_GET_PSS_SIZE = 215,
/**< Called when a protocol wants to specify its PSS size at runtime.
* If the protocol structure has per_session_data_size == 0, lws will
* call this to get the size to allocate for the session. */

LWS_CALLBACK_DHT_VERB_DISPATCH = 215,
LWS_CALLBACK_DHT_VERB_DISPATCH = 216,
/**< Sent to the user protocol handler callback when a DHT message
* carrying a registered verb has been matched by lws-dht.
* `in` is a pointer to `struct lws_dht_verb_dispatch_args` containing
Expand Down
16 changes: 16 additions & 0 deletions include/libwebsockets/lws-context-vhost.h
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,10 @@ struct lws_context_creation_info {
/**< CONTEXT: NULL, or interface name to bind outgoing WOL packet to */
#endif

const char *lws_stub;
/**< CONTEXT: if non-NULL, the name of the stub function requested
* via --lws-stub=... commandline switch. Filled in by
* lws_cmdline_option_handle_builtin(). */
int argc;
/**< CONTEXT: optionally pass the app commandline to the context, so we can use it
* as part of lws_cmdline_option_cx() */
Expand Down Expand Up @@ -1102,6 +1106,18 @@ lws_create_context(const struct lws_context_creation_info *info);
LWS_VISIBLE LWS_EXTERN void
lws_context_destroy(struct lws_context *context);

/**
* lws_tls_cleanup_process() - cleanup process-wide TLS allocations
*
* This function can be called after the last context is destroyed to
* cleanup process-wide TLS allocations. For example, for OpenSSL it
* may call OPENSSL_cleanup() if supported.
* You should only call this if you are absolutely sure you will not
* reinitialize libwebsockets or the TLS library in this process.
*/
LWS_VISIBLE LWS_EXTERN void
lws_tls_cleanup_process(void);

typedef int (*lws_reload_func)(void);

/**
Expand Down
2 changes: 1 addition & 1 deletion include/libwebsockets/lws-dht.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ lws_dht_register_verbs(struct lws_dht_ctx *ctx, const char **verbs, int count, c
* \return number of subscribers notified, or negative on error
*/
LWS_VISIBLE LWS_EXTERN int
lws_dht_notify_subscribers(struct lws_dht_ctx *ctx, const lws_dht_hash_t *hash, const uint8_t *sha256);
lws_dht_notify_subscribers(struct lws_dht_ctx *ctx, const lws_dht_hash_t *hash, const uint8_t *sha256, const uint8_t *payload, size_t payload_len);

/**
* lws_dht_blacklist_cb_t() - DHT blacklist check callback
Expand Down
54 changes: 54 additions & 0 deletions include/libwebsockets/lws-gc9a01a-spi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* lws abstract display implementation for gc9a01a on spi
*
* Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/

#if !defined(__LWS_DISPLAY_GC9A01A_SPI_H__)
#define __LWS_DISPLAY_GC9A01A_SPI_H__


typedef struct lws_display_gc9a01a {

lws_display_t disp; /* use lws_display_gc9a01a_ops to set */
const lws_spi_ops_t *spi; /* spi ops */

lws_display_completion_t cb;
const lws_gpio_ops_t *gpio; /* NULL or gpio ops */
_lws_plat_gpio_t reset_gpio; /* if gpio ops, nReset gpio # */

uint8_t spi_index; /* cs index starting from 0 */

} lws_display_gc9a01a_t;

int
lws_display_gc9a01a_spi_init(lws_display_state_t *lds);
int
lws_display_gc9a01a_spi_blit(lws_display_state_t *lds, const uint8_t *src,
lws_box_t *box, lws_dll2_owner_t *ids);
int
lws_display_gc9a01a_spi_power(lws_display_state_t *lds, int state);

#define lws_display_gc9a01a_ops \
.init = lws_display_gc9a01a_spi_init, \
.blit = lws_display_gc9a01a_spi_blit, \
.power = lws_display_gc9a01a_spi_power
#endif
22 changes: 22 additions & 0 deletions include/libwebsockets/lws-misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,28 @@ lws_get_child(const struct lws *wsi);
LWS_VISIBLE LWS_EXTERN void
lws_get_effective_uid_gid(struct lws_context *context, uid_t *uid, gid_t *gid);

/**
* lws_plat_user_to_uid() - Find uid from username
* \param username: string username, or string containing numeric uid
* \param puid: pointer to uid_t to receive result
*
* Returns 0 if found and *puid is set, else nonzero.
* If the string contains a number, it is parsed directly.
*/
LWS_VISIBLE LWS_EXTERN int
lws_plat_user_to_uid(const char *username, uid_t *puid);

/**
* lws_plat_group_to_gid() - Find gid from groupname
* \param groupname: string groupname, or string containing numeric gid
* \param pgid: pointer to gid_t to receive result
*
* Returns 0 if found and *pgid is set, else nonzero.
* If the string contains a number, it is parsed directly.
*/
LWS_VISIBLE LWS_EXTERN int
lws_plat_group_to_gid(const char *groupname, gid_t *pgid);

/**
* lws_get_udp() - get wsi's udp struct
*
Expand Down
20 changes: 19 additions & 1 deletion include/libwebsockets/lws-mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ typedef struct lws_mqtt_str_st lws_mqtt_str_t;
typedef enum {
QOS0,
QOS1,
QOS2, /* not supported */
QOS2,
RESERVED_QOS_LEVEL,
FAILURE_QOS_LEVEL = 0x80
} lws_mqtt_qos_levels_t;
Expand All @@ -88,6 +88,11 @@ typedef union {
uint8_t bits;
} lws_mqtt_fixed_hdr_t;

typedef struct lws_mqtt_qos2_state_ops {
int (*rx_add)(struct lws *wsi, const char *client_id, uint16_t pkt_id);
int (*rx_remove)(struct lws *wsi, const char *client_id, uint16_t pkt_id);
} lws_mqtt_qos2_state_ops_t;

/*
* MQTT connection parameters, passed into struct
* lws_client_connect_info to establish a connection using
Expand Down Expand Up @@ -122,6 +127,7 @@ typedef struct lws_mqtt_client_connect_param_s {
parameters */
const char *username;
const char *password;
const lws_mqtt_qos2_state_ops_t *qos2_state_ops;
uint8_t aws_iot;
} lws_mqtt_client_connect_param_t;

Expand Down Expand Up @@ -383,4 +389,16 @@ LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT
lws_mqtt_client_send_unsubcribe(struct lws *wsi,
const lws_mqtt_subscribe_param_t *unsub);

/**
* lws_mqtt_client_qos2_rx_add() - inject a saved QoS2 packet ID into the rx list
*
* \param wsi: the mqtt child wsi
* \param pkt_id: the packet ID to inject
*
* This allows the application to repopulate the unacknowledged QoS2 receives
* from persistent storage when a session is resumed.
*/
LWS_VISIBLE LWS_EXTERN int
lws_mqtt_client_qos2_rx_add(struct lws *wsi, uint16_t pkt_id);

#endif /* _LWS_MQTT_H */
31 changes: 31 additions & 0 deletions include/libwebsockets/lws-x509.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,37 @@ lws_x509_create_self_signed(struct lws_context *context,
uint8_t **key_buf, size_t *key_len,
const char *san, int key_bits);

struct lws_x509_cert_gen_info {
const char *san; /* Subject Alt Name / CN */
const char *ca_cert_pem; /* Optional CA cert to sign with */
const char *ca_key_pem; /* Optional CA key to sign with */
const char *curve_name; /* e.g., "P-521" or "P-384" for ECDSA */
int key_bits; /* If curve_name is NULL, use RSA with these bits */
int is_ca; /* 1 = CA:TRUE (Basic Constraints) */
int is_server; /* 1 = serverAuth, 0 = clientAuth */
};

/**
* lws_x509_create_cert() - Create a certificate (self-signed or CA-signed)
*
* \param context: lws_context
* \param cert_buf: pointer to pointer to be set to allocated DER cert
* \param cert_len: pointer to size_t to be set to length of allocated cert
* \param key_buf: pointer to pointer to be set to allocated DER private key
* \param key_len: pointer to size_t to be set to length of allocated key
* \param info: struct containing generation parameters
*
* Creates a certificate and private key in memory (DER format).
* The caller is responsible for freeing *cert_buf and *key_buf using lws_free().
*
* Returns 0 on success.
*/
LWS_VISIBLE LWS_EXTERN int
lws_x509_create_cert(struct lws_context *context,
uint8_t **cert_buf, size_t *cert_len,
uint8_t **key_buf, size_t *key_len,
const struct lws_x509_cert_gen_info *info);

/**
* lws_x509_parse_from_pem() - Read one or more x509 certs in PEM format from memory
*
Expand Down
7 changes: 5 additions & 2 deletions lib/core-net/txpacer.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@ lws_txpacer_thread(void *d)
} else {
/* Calculate sleep time */
struct timespec ts;
lws_usec_t target_us = lws_now_usecs() + txp->txp_info.interval_us;
ts.tv_sec = (time_t)(target_us / 1000000);
struct timeval tv;
gettimeofday(&tv, NULL);

uint64_t target_us = (uint64_t)tv.tv_usec + txp->txp_info.interval_us;
ts.tv_sec = tv.tv_sec + (time_t)(target_us / 1000000);
ts.tv_nsec = (long)((target_us % 1000000) * 1000);

/* Wait for signal (new packet) or timeout (next pacing tick) */
Expand Down
13 changes: 8 additions & 5 deletions lib/core-net/wsi-timeout.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,13 @@ lws_validity_cb(lws_sorted_usec_list_t *sul)
/* one of either the ping or hangup validity threshold was crossed */

if (wsi->validity_hup) {
lwsl_notice("%s: VALIDITY TIMEOUT EXPIRED ON WSI %p! Server is closing connection. (ping=%d, hangup=%d)\n",
__func__, wsi, rbo ? rbo->secs_since_valid_ping : 0, rbo ? rbo->secs_since_valid_hangup : 0);
lwsl_wsi_err(wsi, "validity too old");
char buf[128];
buf[0] = '\0';
lws_get_peer_simple(wsi, buf, sizeof(buf));

lwsl_wsi_notice(wsi, "VALIDITY TIMEOUT EXPIRED ON (protocol %s, peer %s)! Server is closing connection. (ping=%d, hangup=%d)\n",
wsi->a.protocol ? wsi->a.protocol->name : "none", buf,
rbo ? rbo->secs_since_valid_ping : 0, rbo ? rbo->secs_since_valid_hangup : 0);
struct lws_context *cx = wsi->a.context;
struct lws_context_per_thread *pt = &cx->pt[(int)wsi->tsi];

Expand Down Expand Up @@ -308,8 +312,7 @@ lws_validity_confirmed(struct lws *wsi)
* to the role to figure out who actually needs to understand their
* validity was confirmed.
*/
if (!wsi->h2_stream_carries_ws && /* only if not encapsulated */
wsi->role_ops &&
if (wsi->role_ops &&
lws_rops_fidx(wsi->role_ops, LWS_ROPS_issue_keepalive))
lws_rops_func_fidx(wsi->role_ops, LWS_ROPS_issue_keepalive).
issue_keepalive(wsi, 1);
Expand Down
4 changes: 2 additions & 2 deletions lib/core-net/wsi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1270,8 +1270,8 @@ void lws_http_close_immortal(struct lws *wsi) {
* since we closed the only immortal stream on this nwsi, we
* need to reapply a normal timeout regime to the nwsi
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
lws_wsi_keepalive_timeout_eff(wsi));
lws_set_timeout(nwsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
lws_wsi_keepalive_timeout_eff(nwsi));
}

void lws_mux_mark_immortal(struct lws *wsi) {
Expand Down
1 change: 1 addition & 0 deletions lib/core/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ lws_create_context(const struct lws_context_creation_info *info)
context->event_loop_ops = plev->ops;
context->us_wait_resolution = us_wait_resolution;
context->wol_if = info->wol_if;
context->lws_stub = info->lws_stub;
#if defined(LWS_WITH_TLS_JIT_TRUST)
{
struct lws_cache_creation_info ci;
Expand Down
Loading