diff --git a/CMakeLists.txt b/CMakeLists.txt index ef8bec3..1058527 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ set(LWIP_CONTRIB_FREERTOS_DIR "${LWIP_DIR}/contrib/ports/freertos") # link target — only the header-configured packs below do. # https://docs.cososo.co.uk/solid-syslog/getting-started/#path-a--cmake-consumer # Pinned to a commit until there is a release tag to pin to. -set(SOLIDSYSLOG_PLATFORMS "LwipRaw;StdAtomic;FreeRtos;FatFs" CACHE STRING "" FORCE) +set(SOLIDSYSLOG_PLATFORMS "LwipRaw;StdAtomic;FreeRtos;FatFs;MbedTls" CACHE STRING "" FORCE) include(FetchContent) FetchContent_Declare(SolidSyslog @@ -167,7 +167,7 @@ target_include_directories(baseline PRIVATE # library, or context struct sizes diverge between consumer and library. target_compile_definitions(baseline PRIVATE MBEDTLS_USER_CONFIG_FILE=${MBEDTLS_USER_CONFIG_HEADER}) -target_link_libraries(baseline PRIVATE mbedtls mbedx509 mbedcrypto SolidSyslog SolidSyslog::LwipRaw SolidSyslog::FreeRtos SolidSyslog::FatFs) +target_link_libraries(baseline PRIVATE mbedtls mbedx509 mbedcrypto SolidSyslog SolidSyslog::LwipRaw SolidSyslog::FreeRtos SolidSyslog::FatFs SolidSyslog::MbedTls) target_link_options(baseline PRIVATE -mcpu=cortex-m3 -mthumb diff --git a/README.md b/README.md index 8f6bb50..78a1bcd 100644 --- a/README.md +++ b/README.md @@ -10,39 +10,44 @@ It builds on a baseline that simulates the sort of device you might be adding th measures itself: see [docs/baseline.md](docs/baseline.md) for what the baseline is, how the figures are made, and how to run it. -## This stage — Origin address +## This stage — TLS -Add the `ip` PARAM to the origin element, sourced from the same interface address the HOSTNAME field -reports. +Wrap the byte stream in TLS, layered over the TCP stream from the previous stage. The device +verifies the collector against a trust anchor it already holds, so records can be read only by that +collector and cannot be altered in transit. The collector is authenticated to the device; the device +is not yet authenticated to the collector. ```c -struct SolidSyslogOriginSdConfig originConfig = { - /* ... as the previous stage ... */ - .GetIpCount = SyslogOriginIpCount, - .GetIpAt = SyslogOriginIpAt, +struct SolidSyslogMbedTlsStreamConfig tlsConfig = { + .Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig), + .Sleep = SyslogSleep, + .Rng = DeviceCertStore_Rng(), + .CaChain = DeviceCertStore_CaChain(), + .ServerName = SYSLOG_COLLECTOR_HOST, }; -``` -```text -... [origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started +.Stream = SolidSyslogMbedTlsStream_Create(&tlsConfig), ``` -A relay or NAT between the device and the collector rewrites the address the collector observes. -`ip` is what the device says about itself, and that survives the hop. +`ServerName` is checked against the collector's certificate, so it has to match a name that +certificate carries. The trust anchor and the DRBG are passed as handles rather than paths or PEM, +so the device's crypto is initialised before the scheduler starts. -The PARAM is repeatable, so the library asks for a count and then one value per index rather than -taking a single string. This device has one address and returns one, and returns none before the -interface has an address — a count of zero omits the PARAM rather than emitting an empty one. +A second concurrent session has to be paid for upstream. The mbedTLS allocator and the task that +carries the handshake both need sizing for it; both fail loudly when they are not, and neither can +be sized from the run that fails. -`SyslogFields_IpAddress` becomes the single place that reads the address, and HOSTNAME formats the -same string through it. Two fields that must agree now cannot disagree. +**When you need it.** If the log path crosses a network you do not control, or if someone reading +records in transit would learn something they should not. Also if the device needs to know it is +talking to the real collector rather than to whatever answered on that address. -**When you need it.** If anything sits between the device and the collector — a relay, a gateway, or -NAT — and the source address the collector sees can no longer be trusted to identify the device. +> If your device does not already run TLS, the library and its trust material will dominate +> everything on this page. This device already holds a TLS session for its own broker, so what this +> stage adds is the adapter and a second session. -**Cost above baseline: Flash +12,380 B, RAM +9,172 B.** +**Cost above baseline: Flash +13,084 B, RAM +37,452 B.** @@ -68,6 +73,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage. | File store | records that survive a failed send, spooled to disk with a checksum at rest | +11,584 | +9,128 | | Origin | the device named in the record itself, not inferred from the source address | +11,988 | +9,172 | | Origin address | the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite | +12,380 | +9,172 | +| TLS | a collector the device authenticates, and records no longer readable on the wire | +13,084 | +37,452 | *Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.* diff --git a/app/AppConfig.h b/app/AppConfig.h index 9b44086..5ef4388 100644 --- a/app/AppConfig.h +++ b/app/AppConfig.h @@ -15,7 +15,7 @@ * beneath, and neither fits the FreeRTOS floor. Sized generously here and * tightened against measured high-water marks once the pipeline is complete. */ #define LOG_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 4U) -#define SERVICE_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 4U) +#define SERVICE_TASK_STACK_WORDS (configMINIMAL_STACK_SIZE * 16U) #define LOG_TASK_PRIORITY (tskIDLE_PRIORITY + 1U) #define SERVICE_TASK_PRIORITY (tskIDLE_PRIORITY + 1U) @@ -31,6 +31,6 @@ * headroom, not spare capacity: buffer_alloc hands out contiguous space, so a * buffer only a little over the peak fails on fragmentation rather than on * capacity. Applied again wherever more is asked of mbedTLS. */ -#define SIMULATED_APP_MBEDTLS_HEAP_BYTES (32 * 1024) +#define SIMULATED_APP_MBEDTLS_HEAP_BYTES (53 * 1024) #endif /* APP_CONFIG_H */ diff --git a/app/main.c b/app/main.c index e42240a..a8fae3f 100644 --- a/app/main.c +++ b/app/main.c @@ -70,7 +70,8 @@ static void HarnessTask(void* parameters) * before the figures are taken. What arrived is the collector's word. */ bool logged = LogTask_EmitOnce(5000U); (void) printf("[device] first record logged: %s\n", logged ? "yes" : "FAILED"); - vTaskDelay(pdMS_TO_TICKS(500U)); + /* Long enough for the TLS negotiation, not just the send. */ + vTaskDelay(pdMS_TO_TICKS(3000U)); (void) Measure_Report(); diff --git a/app/syslog/Syslog.c b/app/syslog/Syslog.c index 82a70f5..7c55ca8 100644 --- a/app/syslog/Syslog.c +++ b/app/syslog/Syslog.c @@ -1,7 +1,7 @@ /* See Syslog.h. * - * A TCP stream over lwIP behind a circular buffer: Log enqueues and returns, and - * the service task drains and sends. The mutex is what makes those two sides + * A TLS stream over lwIP TCP behind a circular buffer: Log enqueues and returns, + * and the service task drains and sends. The mutex is what makes those two sides * safe on different tasks. * * Unlike a header field, an SD PARAM has no NILVALUE: an unset one is omitted @@ -9,6 +9,8 @@ #include "Syslog.h" +#include "DeviceCertStore.h" + #include "SolidSyslogBlockStore.h" #include "SolidSyslogCircularBuffer.h" #include "SolidSyslogConfig.h" @@ -23,6 +25,7 @@ #include "SolidSyslogLwipRawMarshal.h" #include "SolidSyslogLwipRawResolver.h" #include "SolidSyslogLwipRawTcpStream.h" +#include "SolidSyslogMbedTlsStream.h" #include "SolidSyslogMetaSd.h" #include "SolidSyslogOriginSd.h" #include "SolidSyslogSdValue.h" @@ -47,7 +50,7 @@ * the resolver numeric-only — no DNS, so no LWIP_DNS and no DNS resolver * component to compile. */ #define SYSLOG_COLLECTOR_HOST "10.0.2.2" -#define SYSLOG_COLLECTOR_PORT ((uint16_t) 5601U) +#define SYSLOG_COLLECTOR_PORT ((uint16_t) 6514U) /* Depth enough to absorb a burst while the sender is busy, without sizing for a * backlog the store is there to hold. */ @@ -133,11 +136,20 @@ void Syslog_Start(void) struct SolidSyslogLwipRawTcpStreamConfig tcpConfig = {.Sleep = SyslogSleep}; + /* ServerName must match the name in the collector's certificate. */ + struct SolidSyslogMbedTlsStreamConfig tlsConfig = { + .Transport = SolidSyslogLwipRawTcpStream_Create(&tcpConfig), + .Sleep = SyslogSleep, + .Rng = DeviceCertStore_Rng(), + .CaChain = DeviceCertStore_CaChain(), + .ServerName = SYSLOG_COLLECTOR_HOST, + }; + /* No EndpointVersion — this collector never moves, so the sender resolves * once and pins it. */ struct SolidSyslogStreamSenderConfig senderConfig = { .Resolver = SolidSyslogLwipRawResolver_Create(), - .Stream = SolidSyslogLwipRawTcpStream_Create(&tcpConfig), + .Stream = SolidSyslogMbedTlsStream_Create(&tlsConfig), .Address = SolidSyslogLwipRawAddress_Create(), .Endpoint = CollectorEndpoint, }; diff --git a/measurements/stages.tsv b/measurements/stages.tsv index f3918ec..a2583ca 100644 --- a/measurements/stages.tsv +++ b/measurements/stages.tsv @@ -22,3 +22,4 @@ time-quality Time quality a timestamp the collector knows how far to trust, and file-store File store records that survive a failed send, spooled to disk with a checksum at rest origin Origin the device named in the record itself, not inferred from the source address origin-ip Origin address the device's own address in the record, which a relay or NAT between it and the collector cannot rewrite +tls TLS a collector the device authenticates, and records no longer readable on the wire diff --git a/measurements/tls.csv b/measurements/tls.csv new file mode 100644 index 0000000..43a455d --- /dev/null +++ b/measurements/tls.csv @@ -0,0 +1,13 @@ +# tls figures (bytes) — captured by scripts/run.sh (CAPTURE=1). +# The device reads measurements/Baseline.csv as its frozen baseline and reports current-minus-Baseline. +flash_text,362744 +flash_data,648 +static_bss,147996 +heap_used,4440 +mbedtls_peak,35992 +mbedtls_free,18280 +lwip_mem_free,7576 +lwip_pbufs_free,14 +stack_log,800 +stack_service,3820 +stack_harness,2848 diff --git a/run-report.md b/run-report.md index 1775f31..c307334 100644 --- a/run-report.md +++ b/run-report.md @@ -1,4 +1,4 @@ -# solid-syslog-example — run (origin-ip) +# solid-syslog-example — run (tls) ## Device (self-measured) @@ -10,16 +10,16 @@ [device] first record logged: yes [report] --- SolidSyslog cost above baseline (simulated existing application) --- [report] key,current,baseline,used_above_baseline -[report] flash_text,362048,349992,12056 -[report] flash_data,640,316,324 -[report] static_bss,119724,110876,8848 +[report] flash_text,362744,349992,12752 +[report] flash_data,648,316,332 +[report] static_bss,147996,110876,37120 [report] heap_used,4440,4440,0 -[report] mbedtls_peak,21260,21332,-72 -[report] mbedtls_free,11508,11436,72 +[report] mbedtls_peak,36108,21332,14776 +[report] mbedtls_free,18164,11436,6728 [report] lwip_mem_free,7576,7576,0 [report] lwip_pbufs_free,13,14,-1 [report] stack_log,800,120,680 -[report] stack_service,1012,52,960 +[report] stack_service,3820,52,3768 [report] stack_harness,2848,2840,8 [report] --- end --- [device] ready @@ -29,7 +29,7 @@ ```text text data bss dec hex filename - 362040 648 119724 482412 75c6c /w/build/baseline-cross/baseline.elf + 362736 656 147996 511388 7cd9c /w/build/baseline-cross/baseline.elf ``` ## Listeners (proved before the device ran) @@ -47,23 +47,23 @@ ## Collector (syslog-ng) received ```text -wire <134>1 2026-08-16T07:24:12.430000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started -parsed PRIORITY=134 TIMESTAMP=2026-08-16T07:24:12+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="243"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] MSG=device started +wire <134>1 2026-08-16T09:14:51.440000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="244"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started +parsed PRIORITY=134 TIMESTAMP=2026-08-16T09:14:51+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="244"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] MSG=device started ``` -## Self-check (vs measurements/origin-ip.csv) +## Self-check (vs measurements/tls.csv) ```text - OK flash_text: 362048 (expected 362048, Δ0) - OK flash_data: 640 (expected 640, Δ0) - OK static_bss: 119724 (expected 119724, Δ0) + OK flash_text: 362744 (expected 362744, Δ0) + OK flash_data: 648 (expected 648, Δ0) + OK static_bss: 147996 (expected 147996, Δ0) OK heap_used: 4440 (expected 4440, Δ0) - OK mbedtls_peak: 21260 (expected 21256, Δ4) - OK mbedtls_free: 11508 (expected 11512, Δ4) + OK mbedtls_peak: 36108 (expected 35992, Δ116) + OK mbedtls_free: 18164 (expected 18280, Δ116) OK lwip_mem_free: 7576 (expected 7576, Δ0) - OK lwip_pbufs_free: 13 (expected 13, Δ0) + OK lwip_pbufs_free: 13 (expected 14, Δ1) OK stack_log: 800 (expected 800, Δ0) - OK stack_service: 1012 (expected 1012, Δ0) + OK stack_service: 3820 (expected 3820, Δ0) OK stack_harness: 2848 (expected 2848, Δ0) ```