From 1af4bbe1abeb01dcd29d49e60f97651e074bf9a8 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 7 Oct 2024 18:22:21 +0200 Subject: [PATCH 1/3] feat(mosq): Added support for TLS transport using ESP-TLS --- .github/workflows/mosq__build.yml | 1 + ci/check_copyright_config.yaml | 1 + components/mosquitto/CMakeLists.txt | 16 +- components/mosquitto/README.md | 8 +- components/mosquitto/api.md | 22 +- .../mosquitto/examples/broker/README.md | 15 +- .../examples/broker/main/CMakeLists.txt | 5 +- .../examples/broker/main/Kconfig.projbuild | 7 + .../mosquitto/examples/broker/main/cacert.pem | 19 + .../main/{broker.c => example_broker.c} | 33 +- .../examples/broker/main/servercert.pem | 17 + .../examples/broker/main/serverkey.pem | 28 ++ components/mosquitto/port/broker.c | 13 +- .../mosquitto/port/include/mosq_broker.h | 16 +- components/mosquitto/port/net__esp_tls.c | 467 ++++++++++++++++++ 15 files changed, 647 insertions(+), 21 deletions(-) create mode 100644 components/mosquitto/examples/broker/main/cacert.pem rename components/mosquitto/examples/broker/main/{broker.c => example_broker.c} (71%) create mode 100644 components/mosquitto/examples/broker/main/servercert.pem create mode 100644 components/mosquitto/examples/broker/main/serverkey.pem create mode 100644 components/mosquitto/port/net__esp_tls.c diff --git a/.github/workflows/mosq__build.yml b/.github/workflows/mosq__build.yml index 476d174d6d..abf9dbb4bc 100644 --- a/.github/workflows/mosq__build.yml +++ b/.github/workflows/mosq__build.yml @@ -64,6 +64,7 @@ jobs: - name: Run Test working-directory: ${{ env.TEST_DIR }} run: | + python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results unzip ci/artifacts.zip -d ci for dir in `ls -d ci/build_*`; do rm -rf build sdkconfig.defaults diff --git a/ci/check_copyright_config.yaml b/ci/check_copyright_config.yaml index 43f2ad7018..ce5eadf079 100644 --- a/ci/check_copyright_config.yaml +++ b/ci/check_copyright_config.yaml @@ -53,6 +53,7 @@ mosquitto_component: allowed_licenses: - EPL-2.0 - Apache-2.0 + - BSD-3-Clause slim_modem_examples: include: diff --git a/components/mosquitto/CMakeLists.txt b/components/mosquitto/CMakeLists.txt index ef43060db1..1db843ec19 100644 --- a/components/mosquitto/CMakeLists.txt +++ b/components/mosquitto/CMakeLists.txt @@ -7,7 +7,6 @@ set(m_deps_dir ${m_dir}/deps) set(m_srcs ${m_lib_dir}/memory_mosq.c ${m_lib_dir}/util_mosq.c - ${m_lib_dir}/net_mosq.c ${m_lib_dir}/will_mosq.c ${m_lib_dir}/alias_mosq.c ${m_lib_dir}/send_mosq.c @@ -46,7 +45,6 @@ set(m_srcs ${m_src_dir}/mux.c ${m_src_dir}/mux_epoll.c ${m_src_dir}/mux_poll.c - ${m_src_dir}/net.c ${m_src_dir}/password_mosq.c ${m_src_dir}/persist_read.c ${m_src_dir}/persist_read_v234.c @@ -73,20 +71,26 @@ set(m_srcs ${m_src_dir}/xtreport.c) idf_component_register(SRCS ${m_srcs} - port/callbacks.c port/config.c port/signals.c port/ifaddrs.c port/broker.c port/files.c + port/callbacks.c + port/config.c + port/signals.c + port/ifaddrs.c + port/broker.c + port/files.c + port/net__esp_tls.c PRIV_INCLUDE_DIRS port/priv_include port/priv_include/sys ${m_dir} ${m_src_dir} ${m_incl_dir} ${m_lib_dir} ${m_deps_dir} INCLUDE_DIRS ${m_incl_dir} port/include - PRIV_REQUIRES newlib + PRIV_REQUIRES newlib esp-tls ) target_compile_definitions(${COMPONENT_LIB} PRIVATE "WITH_BROKER") target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") -# Some mosquittos source unconditionally define `_GNU_SOURCE` which collides with IDF build system +# Some mosquitto source unconditionally define `_GNU_SOURCE` which collides with IDF build system # producing warning: "_GNU_SOURCE" redefined # This workarounds this issue by undefining the macro for the selected files -set(sources_that_define_gnu_source ${m_lib_dir}/net_mosq.c ${m_src_dir}/loop.c ${m_src_dir}/mux_poll.c) +set(sources_that_define_gnu_source ${m_src_dir}/loop.c ${m_src_dir}/mux_poll.c) foreach(offending_src ${sources_that_define_gnu_source}) set_source_files_properties(${offending_src} PROPERTIES COMPILE_OPTIONS "-U_GNU_SOURCE") endforeach() diff --git a/components/mosquitto/README.md b/components/mosquitto/README.md index 9199917ed4..a6408db29a 100644 --- a/components/mosquitto/README.md +++ b/components/mosquitto/README.md @@ -1,21 +1,21 @@ # ESP32 Mosquitto Port -This is a lightweight port of the Mosquitto broker designed to run on the ESP32. It currently supports a single listener and TCP transport only. +This is a lightweight port of the Mosquitto broker designed to run on the ESP32. It currently supports a single listener with TCP transport or TLS transport based on ESP-TLS library. ## Supported Options -The Espressif port supports a limited set of options (with plans to add more in future releases). These options can be configured through a structure passed to the `mosq_broker_start()` function. For detailed information on available configuration options, refer to the [API documentation](api.md). +The Espressif port supports a limited set of options (with plans to add more in future releases). These options can be configured through a structure passed to the `mosq_broker_run()` function. For detailed information on available configuration options, refer to the [API documentation](api.md). ## API ### Starting the Broker -To start the broker, call the `mosq_broker_start()` function with a properly configured settings structure. The broker operates in the context of the calling task and does not create a separate task. +To start the broker, call the `mosq_broker_run()` function with a properly configured settings structure. The broker operates in the context of the calling task and does not create a separate task. It's recommended to analyze the stack size needed for the task, but in general, the broker requires at least 4 kB of stack size. ```c -mosq_broker_start(&config); +mosq_broker_run(&config); ``` ## Memory Footprint Considerations diff --git a/components/mosquitto/api.md b/components/mosquitto/api.md index 39668bde34..f010cbb241 100644 --- a/components/mosquitto/api.md +++ b/components/mosquitto/api.md @@ -20,7 +20,8 @@ | Type | Name | | ---: | :--- | -| int | [**mosq\_broker\_start**](#function-mosq_broker_start) (struct [**mosq\_broker\_config**](#struct-mosq_broker_config) \*config)
_Start mosquitto broker._ | +| int | [**mosq\_broker\_run**](#function-mosq_broker_run) (struct [**mosq\_broker\_config**](#struct-mosq_broker_config) \*config)
_Start mosquitto broker._ | +| void | [**mosq\_broker\_stop**](#function-mosq_broker_stop) (void)
_Stops running broker._ | ## Structures and Types Documentation @@ -37,14 +38,16 @@ Variables: - int port
Port number of the broker to listen to +- esp\_tls\_cfg\_server\_t \* tls_cfg
ESP-TLS configuration (if TLS transport used) Please refer to the ESP-TLS official documentation for more details on configuring the TLS options. You can open the respective docs with this idf.py command: `idf.py docs -sp api-reference/protocols/esp_tls.html` + ## Functions Documentation -### function `mosq_broker_start` +### function `mosq_broker_run` _Start mosquitto broker._ ```c -int mosq_broker_start ( +int mosq_broker_run ( struct mosq_broker_config *config ) ``` @@ -63,3 +66,16 @@ This API runs the broker in the calling thread and blocks until the mosquitto ex **Returns:** int Exit code (0 on success) +### function `mosq_broker_stop` + +_Stops running broker._ +```c +void mosq_broker_stop ( + void +) +``` + + +**Note:** + +After calling this API, function mosq\_broker\_run() unblocks and returns. diff --git a/components/mosquitto/examples/broker/README.md b/components/mosquitto/examples/broker/README.md index 6d8545505c..c9043733df 100644 --- a/components/mosquitto/examples/broker/README.md +++ b/components/mosquitto/examples/broker/README.md @@ -2,7 +2,7 @@ ## Overview -This example runs a TCP broker on a specified host and port. +This example runs a broker on TLS or TCP transport, specified host and port. ### How to use this example @@ -13,6 +13,19 @@ If you enabled also the mqtt client, this example will connect to the local brok You can connect to the ESP32 mosquitto broker using some other client using the ESP32 IPv4 address and the port specified in the project configuration menu. +> [!IMPORTANT] +> The certificates and keys provided in this example are intended for testing purposes only. They are self-signed, single-use, and configured with a common name of "127.0.0.1". Do not reuse these credentials in any production or real-world applications, as they are not secure for such environments. + +For more information on setting up TLS configuration (including certificates and keys), please refer to the ESP-TLS documentation: +```bash +idf.py docs -sp api-reference/protocols/esp_tls.html +``` + +Configuring the TLS option for the broker is quite similar to setting it up for an HTTPS server, as both involve server-side security configuration. Refer to the HTTPS server documentation for details: +```bash +idf.py docs -sp api-reference/protocols/esp_https_server.html +``` + ### Test version This example is also used for testing on loopback interface only, disabling any actual connection, just using the local mqtt client to the loopback interface. diff --git a/components/mosquitto/examples/broker/main/CMakeLists.txt b/components/mosquitto/examples/broker/main/CMakeLists.txt index b76a5b5306..1f3299ddcc 100644 --- a/components/mosquitto/examples/broker/main/CMakeLists.txt +++ b/components/mosquitto/examples/broker/main/CMakeLists.txt @@ -1,2 +1,3 @@ -idf_component_register(SRCS "broker.c" - PRIV_REQUIRES newlib nvs_flash esp_netif esp_event mqtt) +idf_component_register(SRCS "example_broker.c" + PRIV_REQUIRES newlib nvs_flash esp_netif esp_event mqtt + EMBED_TXTFILES servercert.pem serverkey.pem cacert.pem) diff --git a/components/mosquitto/examples/broker/main/Kconfig.projbuild b/components/mosquitto/examples/broker/main/Kconfig.projbuild index 2e5efc912a..44c9e8f2d0 100644 --- a/components/mosquitto/examples/broker/main/Kconfig.projbuild +++ b/components/mosquitto/examples/broker/main/Kconfig.projbuild @@ -19,4 +19,11 @@ menu "Example Configuration" If enabled, it runs a local mqtt client connecting to the same endpoint ans the broker listens to + config EXAMPLE_BROKER_WITH_TLS + bool "Use TLS" + default y + help + If enabled, the broker (and the client too, if enabled) + uses TLS transport layer + endmenu diff --git a/components/mosquitto/examples/broker/main/cacert.pem b/components/mosquitto/examples/broker/main/cacert.pem new file mode 100644 index 0000000000..46e0eda8c0 --- /dev/null +++ b/components/mosquitto/examples/broker/main/cacert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIUXichctvCn6/6xXr0+UOBaqwkBMMwDQYJKoZIhvcNAQEL +BQAwITELMAkGA1UEBhMCQ1oxEjAQBgNVBAMMCUVzcHJlc3NpZjAeFw0yNDA3MDgx +NDE5NDNaFw0yNTA3MDgxNDE5NDNaMCExCzAJBgNVBAYTAkNaMRIwEAYDVQQDDAlF +c3ByZXNzaWYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqAGUZALUS +AwWkslBH0RJcgyTTYZ6Q3xadG9rubTGN0DNt8INlguElN9eUhj7VzQZeGxRtAk3A +b4r5MpTWAAC8maDgZU97TOmAaxA04h0P2MHTGG4i1vSm2/jebhh5Ydh8nKs9DdAO +YJWfbtt3XukBe5VJcmp7OICz88LFc/fArrAnBFdmrVX+0Y2l/5KDW6ItvcXhorpz +sO5hOnPXIs4Hq5TYOJbUw6h9E8O6bxUG4AXcSWqqbLJ6PzEFSBMBnjwBQn4HCWvM +GV6w2+I1QbtOTe6yNzBa7O3yqzSYeTcdpjv/FFngo4oRN1RMiCYc1Ae3hJiIhDlN +SRB1CHPi4MblAgMBAAGjUzBRMB0GA1UdDgQWBBTdlh8T2ze2K81IrZCpUv9yhZq2 +qjAfBgNVHSMEGDAWgBTdlh8T2ze2K81IrZCpUv9yhZq2qjAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBDTt9V3LnyBjHDi9pQa+Q8bjVYCMaOSBFE +LJj8GhkXxtfTzqO2u7vkvfz+2MaRDNpL2lePWDB0BwINT+mSYNWjD5bP2mdgJ2nK +BStzWT6MR4hiQ6u6hXy2Q8brqPN+dP4Pay8fXHe3JNadC/nSk4AC3EvVDpghCJJB +1W5az4YmJzK0F6S84AkKnXYdlYyb94RwWSevn7HYZM+xQjoJmBhQ+XnQ7o2uaEur +52igRRHQQ4xrF5JrbGAqfFVqfA8lJDYiAZCG/aNlV0VpgzyxpDxvPFvvlEYJoL63 +/asgSIzYoBknZjNZnPSKcsYGa+0Bjjh7tS50bV++5sN+aW/WDRLd +-----END CERTIFICATE----- diff --git a/components/mosquitto/examples/broker/main/broker.c b/components/mosquitto/examples/broker/main/example_broker.c similarity index 71% rename from components/mosquitto/examples/broker/main/broker.c rename to components/mosquitto/examples/broker/main/example_broker.c index 3e1d09465c..02632bb5f6 100644 --- a/components/mosquitto/examples/broker/main/broker.c +++ b/components/mosquitto/examples/broker/main/example_broker.c @@ -14,6 +14,16 @@ const static char *TAG = "mqtt_broker"; +#if CONFIG_EXAMPLE_BROKER_WITH_TLS +extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start"); +extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end"); +extern const unsigned char serverkey_start[] asm("_binary_serverkey_pem_start"); +extern const unsigned char serverkey_end[] asm("_binary_serverkey_pem_end"); +extern const char cacert_start[] asm("_binary_cacert_pem_start"); +extern const char cacert_end[] asm("_binary_cacert_pem_end"); +#endif + + #if CONFIG_EXAMPLE_BROKER_RUN_LOCAL_MQTT_CLIENT static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { @@ -63,7 +73,13 @@ static void mqtt_app_start(struct mosq_broker_config *config) { esp_mqtt_client_config_t mqtt_cfg = { .broker.address.hostname = "127.0.0.1", - .broker.address.transport = MQTT_TRANSPORT_OVER_TCP, // we support only TCP transport now +#if CONFIG_EXAMPLE_BROKER_WITH_TLS + .broker.address.transport = MQTT_TRANSPORT_OVER_SSL, + .broker.verification.certificate = cacert_start, + .broker.verification.certificate_len = cacert_end - cacert_start, +#else + .broker.address.transport = MQTT_TRANSPORT_OVER_TCP, +#endif .broker.address.port = config->port, }; esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); @@ -79,11 +95,22 @@ void app_main(void) ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(example_connect()); - struct mosq_broker_config config = { .host = CONFIG_EXAMPLE_BROKER_HOST, .port = CONFIG_EXAMPLE_BROKER_PORT }; + struct mosq_broker_config config = { .host = CONFIG_EXAMPLE_BROKER_HOST, .port = CONFIG_EXAMPLE_BROKER_PORT, .tls_cfg = NULL }; #if CONFIG_EXAMPLE_BROKER_RUN_LOCAL_MQTT_CLIENT mqtt_app_start(&config); #endif + +#if CONFIG_EXAMPLE_BROKER_WITH_TLS + esp_tls_cfg_server_t tls_cfg = { + .servercert_buf = servercert_start, + .servercert_bytes = servercert_end - servercert_start, + .serverkey_buf = serverkey_start, + .serverkey_bytes = serverkey_end - serverkey_start, + }; + config.tls_cfg = &tls_cfg; +#endif + // broker continues to run in this task - mosq_broker_start(&config); + mosq_broker_run(&config); } diff --git a/components/mosquitto/examples/broker/main/servercert.pem b/components/mosquitto/examples/broker/main/servercert.pem new file mode 100644 index 0000000000..8582b46e0d --- /dev/null +++ b/components/mosquitto/examples/broker/main/servercert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICvDCCAaQCFAo5BZTT6BC7rKLiWZA9mwGrEQYeMA0GCSqGSIb3DQEBCwUAMCEx +CzAJBgNVBAYTAkNaMRIwEAYDVQQDDAlFc3ByZXNzaWYwHhcNMjQwNzA4MTQxOTQ0 +WhcNMjUwNzA4MTQxOTQ0WjAUMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkCVA8MY/BMZ2e95s+oNMZ2cXEF7lbUL58 +I9OT5Y+0Mxy3mxsezEye0kKXgV7TqzXdWbu8VhDQONkKkeO4n/lPKhULmKQ7gIja +1aiL5Wt07S/jHBaYbMymbssCJaVcFpl+gclZ0oJJKDtJN9wYoUAAXeZkEE47rRWm +W5CZP92bn8TYar+3rjexHvZTtPhSKucsN/YoAtdC5ywRcf9lbhmjV4sMzUSAlPG4 +ZY8sG1mrul1AO2c7OI4lcm+iBo7WiIwtASmqvD/Ahhye5kY00jfAiQJvRl4Da65x +m6NVVUAk3pUsKjOHI/4FisnP0kIJrfMNaiiumroFApuMl3YxEN6tAgMBAAEwDQYJ +KoZIhvcNAQELBQADggEBAD+ML2Dp2GDomHoFxyTmu9msv+8YyZy4VhRGUWnG4k8f +XV+9cBoQkiV8VUDETwjcdp0lRVmyxy8w1x4ovJ/EO5udfXom8gxMS7lZVXw1Iv09 +vPHIpr9kQg2hxTpqoHSRKLRJv796kfYoPK+I43hYlhvewQko7+E8EEns46qXc4I3 +wqrwNOw1gjUzyj5DhW4RCJ9sBS/FaVyliCxICoDXRFhnSXWi0HaEjzq815muN6DG +lD5ENFExpPWpvjyVPQC6tNYRlRCAGKn5qbx/YetGNX3slHJHgHAO2dyPYdiXwkhG +GwQPqXJrvu0k83h5lTeW98wwgcqFEzVHQCJhREdW4uE= +-----END CERTIFICATE----- diff --git a/components/mosquitto/examples/broker/main/serverkey.pem b/components/mosquitto/examples/broker/main/serverkey.pem new file mode 100644 index 0000000000..02c08a1682 --- /dev/null +++ b/components/mosquitto/examples/broker/main/serverkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCkCVA8MY/BMZ2e +95s+oNMZ2cXEF7lbUL58I9OT5Y+0Mxy3mxsezEye0kKXgV7TqzXdWbu8VhDQONkK +keO4n/lPKhULmKQ7gIja1aiL5Wt07S/jHBaYbMymbssCJaVcFpl+gclZ0oJJKDtJ +N9wYoUAAXeZkEE47rRWmW5CZP92bn8TYar+3rjexHvZTtPhSKucsN/YoAtdC5ywR +cf9lbhmjV4sMzUSAlPG4ZY8sG1mrul1AO2c7OI4lcm+iBo7WiIwtASmqvD/Ahhye +5kY00jfAiQJvRl4Da65xm6NVVUAk3pUsKjOHI/4FisnP0kIJrfMNaiiumroFApuM +l3YxEN6tAgMBAAECggEBAIAmxohAMA6+tGV9C8vh2QpZGCgaYLT2X9qcIq9oqNwf +ElBe3NEyyqlJmrzCVVMIbwx/DiwVEQ2bW/TmBQI3+I3gUpC8r5HM2R4dzY99rHWs +17yWNRDf7wIXjIIg5w8KmOA8hRGnZCHDTI3nFgwn7dhbg6KpGnWEw2U0I8OWIYty +BX/CvsaWCtkOc1o1bUfnt91YlZlFm7VSK+jdxhnt+A5FJ06CllTaur9nf3u/sLO9 +4SKtd94S/pTlIWM4x+4565fNplNuKLsrnNJHV5eiPwiMb9OZzeFa48z26No2ebHc +zA5PlrzjtQRQaSiWausgZKfLp1+lA9kNNhi6gBPt4AECgYEA2mUZErdAmm0WS+Hl +/AEJWsXzoYLGmOvToVEyUqRNffsfkbTzSHajLGzntUd/ZnQQWh5NkvOPJ3BHTszb +vwVeGIQehpwqoCvF1Nz+XhI9yAGIoSzxq0FMMcmlW0XGdK8D30qbeygBEL2FJnK5 +mixWe3dZx3kujIFJ4VI8S4g/CjcCgYEAwEgT7FCk4xw6NClI3kAQ6NqqJ+qKDwFo +LQQKUep3juA7i84KwqRCVSoKVjJfkVw7MBb520MgnjAwnekyRql6ALDrSUqgXNB9 +R8Sa5b19mmmBDtvh+IrhD6gmAyzXVdGU9fxMnW3iW3sx5Anl/4lx8r7ITcrM9dgd +xY7pwzownDsCgYBkTuz+OKb2hsYn4kC0x3EZfTQSabN3x1EzlcysQoTJKU9tqBPZ +o4v8uqSOEaHFV+euzJ5KsY19ysclvVfs27VFQ2GV6CJ34MMDquE2KeCwfWvYw4DY +bKxnbbuCOYEWVNBNfcH+Bfi/TJzcdPMkidrK6J2WzeUAad2aHSBOfOyfbwKBgEkK +WD8ZdzkqXNW5pQt/7Kx3e9GD34PJtgf7k+wAFAB7H0OBNkcv3F67hIevxOvTzEv9 +PlZTDo3ool8p2UZMVKL0kbwalAYN0Lk1bt28eHzyfOrnDdS69Llc12u3Wekontw+ +ReA7gJPdnVsRg4PpcxaR8EbUtbzhppWILzZQ4WxHAoGALs0n0pDVQlkAuDtNhA/i +7/jIo1hd3fPpWbMfcKeP+TtlpXMu/BCsR5A/u+4iSfLMy9/Ggqad4jUsdd9+myvr +j/3BzbSx7OnD+gg8ao0K2FwO33ncM1iAw3G5QCKs1waHsVen43Oe3GtQxHxxi/G0 +Y4EIG5wkDz4YQOEXacvTWMo= +-----END PRIVATE KEY----- diff --git a/components/mosquitto/port/broker.c b/components/mosquitto/port/broker.c index 7742b55980..62cfb29bce 100644 --- a/components/mosquitto/port/broker.c +++ b/components/mosquitto/port/broker.c @@ -94,7 +94,14 @@ static void listeners__stop(void) mosquitto__free(listensock); } -int mosq_broker_start(struct mosq_broker_config *broker_config) +void net__set_tls_config(esp_tls_cfg_server_t *config); + +void mosq_broker_stop(void) +{ + run = 0; +} + +int mosq_broker_run(struct mosq_broker_config *broker_config) { struct mosquitto__config config; @@ -115,6 +122,10 @@ int mosq_broker_start(struct mosq_broker_config *broker_config) config__init(&config); + if (broker_config->tls_cfg) { + net__set_tls_config(broker_config->tls_cfg); + } + db.config = &config; rc = db__open(&config); diff --git a/components/mosquitto/port/include/mosq_broker.h b/components/mosquitto/port/include/mosq_broker.h index 88e2bc6975..362943557f 100644 --- a/components/mosquitto/port/include/mosq_broker.h +++ b/components/mosquitto/port/include/mosq_broker.h @@ -5,6 +5,7 @@ */ #pragma once #include "mosquitto.h" +#include "esp_tls.h" struct mosquitto__config; @@ -17,6 +18,12 @@ struct mosquitto__config; struct mosq_broker_config { char *host; /*!< Address on which the broker is listening for connections */ int port; /*!< Port number of the broker to listen to */ + esp_tls_cfg_server_t *tls_cfg; /*!< ESP-TLS configuration (if TLS transport used) + * Please refer to the ESP-TLS official documentation + * for more details on configuring the TLS options. + * You can open the respective docs with this idf.py command: + * `idf.py docs -sp api-reference/protocols/esp_tls.html` + */ }; /** @@ -28,4 +35,11 @@ struct mosq_broker_config { * @param config Mosquitto configuration structure * @return int Exit code (0 on success) */ -int mosq_broker_start(struct mosq_broker_config *config); +int mosq_broker_run(struct mosq_broker_config *config); + +/** + * @brief Stops running broker + * + * @note After calling this API, function mosq_broker_run() unblocks and returns. + */ +void mosq_broker_stop(void); diff --git a/components/mosquitto/port/net__esp_tls.c b/components/mosquitto/port/net__esp_tls.c new file mode 100644 index 0000000000..078a47f9d9 --- /dev/null +++ b/components/mosquitto/port/net__esp_tls.c @@ -0,0 +1,467 @@ +/* + * SPDX-FileCopyrightText: 2009-2020 Roger Light + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + * + * SPDX-FileContributor: 2024 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 +# include +# include +# include +# include +# include +# include + +#include +#include +#include +#include +#include + +#ifdef HAVE_NETINET_IN_H +# include +#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) { + 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) +{ +} From c2c4bf835a039fd3e5993a387ded420749230f26 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 11 Oct 2024 08:49:33 +0200 Subject: [PATCH 2/3] docs(mosq): Prepare mosquitto component for publishing --- .github/workflows/publish-docs-component.yml | 1 + README.md | 5 +++++ components/mosquitto/.cz.yaml | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-docs-component.yml b/.github/workflows/publish-docs-component.yml index 1cdb90d276..ea369670c9 100644 --- a/.github/workflows/publish-docs-component.yml +++ b/.github/workflows/publish-docs-component.yml @@ -100,5 +100,6 @@ jobs: components/console_cmd_wifi; components/esp_wifi_remote; components/mbedtls_cxx; + components/mosquitto; namespace: "espressif" api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/README.md b/README.md index 920ca34905..a896ab7fe9 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,8 @@ Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf) ### mbedtls_cxx * Brief introduction [README](components/mbedtls_cxx/README.md) + +### mosquitto + +* Brief introduction [README](components/mosquitto/README.md) +* API documentation [api.md](components/mosquitto/api.md) diff --git a/components/mosquitto/.cz.yaml b/components/mosquitto/.cz.yaml index eb00948490..c5881f7e6c 100644 --- a/components/mosquitto/.cz.yaml +++ b/components/mosquitto/.cz.yaml @@ -1,6 +1,6 @@ commitizen: bump_message: 'bump(mosq): $current_version -> $new_version' - pre_bump_hooks: python ../../ci/changelog.py mosq + pre_bump_hooks: python ../../ci/changelog.py mosquitto tag_format: mosq-v$version version: 2.0.27 version_files: From f613c70e00f22d49329e0d04369e15fac82d9152 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 11 Oct 2024 09:06:40 +0200 Subject: [PATCH 3/3] bump(mosq): Initial version v2.0.28~0 2.0.28~0 Features - Added support for TLS transport using ESP-TLS (1af4bbe1) - Add API docs, memory consideration and tests (a20c0c9d) - Add target tests with localhost broker-client (5c850cda) - Initial moquitto v2.0.18 port (TCP only) (de4531e8) Bug Fixes - Fix clean compilation addressing _GNU_SOURCE redefined (e2392c36) Updated - docs(mosq): Prepare mosquitto component for publishing (c2c4bf83) --- components/mosquitto/.cz.yaml | 5 +++-- components/mosquitto/CHANGELOG.md | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 components/mosquitto/CHANGELOG.md diff --git a/components/mosquitto/.cz.yaml b/components/mosquitto/.cz.yaml index c5881f7e6c..f9b741cd0b 100644 --- a/components/mosquitto/.cz.yaml +++ b/components/mosquitto/.cz.yaml @@ -1,7 +1,8 @@ +--- commitizen: bump_message: 'bump(mosq): $current_version -> $new_version' pre_bump_hooks: python ../../ci/changelog.py mosquitto tag_format: mosq-v$version - version: 2.0.27 + version: 2.0.28~0 version_files: - - idf_component.yml + - idf_component.yml diff --git a/components/mosquitto/CHANGELOG.md b/components/mosquitto/CHANGELOG.md new file mode 100644 index 0000000000..bcc1c9a553 --- /dev/null +++ b/components/mosquitto/CHANGELOG.md @@ -0,0 +1,16 @@ +## [2.0.28~0](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.28_0) + +### Features + +- Added support for TLS transport using ESP-TLS ([1af4bbe1](https://github.com/espressif/esp-protocols/commit/1af4bbe1)) +- Add API docs, memory consideration and tests ([a20c0c9d](https://github.com/espressif/esp-protocols/commit/a20c0c9d)) +- Add target tests with localhost broker-client ([5c850cda](https://github.com/espressif/esp-protocols/commit/5c850cda)) +- Initial moquitto v2.0.18 port (TCP only) ([de4531e8](https://github.com/espressif/esp-protocols/commit/de4531e8)) + +### Bug Fixes + +- Fix clean compilation addressing _GNU_SOURCE redefined ([e2392c36](https://github.com/espressif/esp-protocols/commit/e2392c36)) + +### Updated + +- docs(mosq): Prepare mosquitto component for publishing ([c2c4bf83](https://github.com/espressif/esp-protocols/commit/c2c4bf83))