diff --git a/README.md b/README.md index 40c7ffb..8f6bb50 100644 --- a/README.md +++ b/README.md @@ -10,47 +10,39 @@ 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 +## This stage — Origin address -Name the device in the record with `SolidSyslogOriginSd` — the software, its version, and the -enterprise number. +Add the `ip` PARAM to the origin element, sourced from the same interface address the HOSTNAME field +reports. ```c -#define SYSLOG_SOFTWARE "solid-syslog-example" -#define SYSLOG_SW_VERSION "0.1.0" - struct SolidSyslogOriginSdConfig originConfig = { - .Software = SYSLOG_SOFTWARE, - .SwVersion = SYSLOG_SW_VERSION, - .EnterpriseId = SYSLOG_ENTERPRISE_ID, + /* ... as the previous stage ... */ + .GetIpCount = SyslogOriginIpCount, + .GetIpAt = SyslogOriginIpAt, }; -sd[2] = SolidSyslogOriginSd_Create(&originConfig); ``` ```text -... [origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473"] device started +... [origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473" ip="10.0.2.15"] device started ``` -This lands after the store rather than before it. While records went straight out, the answer to -"who sent this" was implied by the connection they arrived on. Once records can replay hours later -that is no longer so, and the record has to carry it. - -The `ip` PARAM is left out here. The address the collector sees is still the address that reached -it; the next stage takes that assumption away. +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. -`SYSLOG_ENTERPRISE_ID` is defined in its own header rather than beside the element that carries it, -because the number identifies the vendor rather than the logger — anything else this product puts -its own name on wants the same one. +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. -> Enterprise number 32473 is reserved for documentation and testing by RFC 5612. A shipping product -> uses its own, registered with IANA. +`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 records will be correlated across devices, replayed after a delay, or -relayed through anything. +**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. -**Cost above baseline: Flash +11,988 B, RAM +9,172 B.** +**Cost above baseline: Flash +12,380 B, RAM +9,172 B.** @@ -75,6 +67,7 @@ committed as [`run-report.md`](run-report.md), and rewritten by every stage. | Time quality | a timestamp the collector knows how far to trust, and an uptime that tells a reboot from a counter wrap | +7,628 | +7,692 | | 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 | *Deltas are bytes above the baseline, which is itself Flash 350,308 B, RAM 111,192 B.* diff --git a/app/syslog/Syslog.c b/app/syslog/Syslog.c index 95da3ef..82a70f5 100644 --- a/app/syslog/Syslog.c +++ b/app/syslog/Syslog.c @@ -25,6 +25,7 @@ #include "SolidSyslogLwipRawTcpStream.h" #include "SolidSyslogMetaSd.h" #include "SolidSyslogOriginSd.h" +#include "SolidSyslogSdValue.h" #include "SolidSyslogStdAtomicCounter.h" #include "SolidSyslogStreamSender.h" #include "SolidSyslogTimeQuality.h" @@ -32,6 +33,7 @@ #include "SyslogEnterprise.h" #include "SyslogFields.h" +#include "lwip/ip4_addr.h" #include "lwip/tcpip.h" #include "FreeRTOS.h" @@ -73,6 +75,29 @@ static void SyslogTimeQuality(struct SolidSyslogTimeQuality* timeQuality) timeQuality->IsSynced = false; } +/* The device's own view of its address, which a relay or NAT between it and the + * collector would otherwise replace. */ +static size_t SyslogOriginIpCount(void* context) +{ + (void) context; + + char address[IP4ADDR_STRLEN_MAX] = {0}; + + SyslogFields_IpAddress(address, sizeof(address)); + return (address[0] != '\0') ? 1U : 0U; +} + +static void SyslogOriginIpAt(struct SolidSyslogSdValue* value, void* context, size_t index) +{ + (void) context; + (void) index; + + char address[IP4ADDR_STRLEN_MAX] = {0}; + + SyslogFields_IpAddress(address, sizeof(address)); + SolidSyslogSdValue_String(value, address); +} + /* Bounds the connect spin so it yields instead of busy-waiting. */ static void SyslogSleep(int milliseconds) { @@ -127,12 +152,12 @@ void Syslog_Start(void) s_sd[0] = SolidSyslogMetaSd_Create(&metaConfig); s_sd[1] = SolidSyslogTimeQualitySd_Create(SyslogTimeQuality); - /* No ip: the address the collector sees is the one that reached it, until a - * relay makes that untrue. */ struct SolidSyslogOriginSdConfig originConfig = { .Software = SYSLOG_SOFTWARE, .SwVersion = SYSLOG_SW_VERSION, .EnterpriseId = SYSLOG_ENTERPRISE_ID, + .GetIpCount = SyslogOriginIpCount, + .GetIpAt = SyslogOriginIpAt, }; s_sd[2] = SolidSyslogOriginSd_Create(&originConfig); diff --git a/app/syslog/SyslogFields.c b/app/syslog/SyslogFields.c index dd3b191..cf837c1 100644 --- a/app/syslog/SyslogFields.c +++ b/app/syslog/SyslogFields.c @@ -39,20 +39,27 @@ void SyslogFields_Clock(struct SolidSyslogTimestamp* timestamp) } } -void SyslogFields_Hostname(struct SolidSyslogHeaderField* field, void* context) +void SyslogFields_IpAddress(char* out, size_t size) { - (void) context; - - char address[IP4ADDR_STRLEN_MAX] = {0}; + out[0] = '\0'; /* netif state belongs to the lwIP core, so read and format under its lock. * ip4addr_ntoa_r, not ip4addr_ntoa: the latter shares one static buffer. */ LOCK_TCPIP_CORE(); - if (netif_default != NULL) + if ((netif_default != NULL) && !ip4_addr_isany_val(*netif_ip4_addr(netif_default))) { - (void) ip4addr_ntoa_r(netif_ip4_addr(netif_default), address, (int) sizeof(address)); + (void) ip4addr_ntoa_r(netif_ip4_addr(netif_default), out, (int) size); } UNLOCK_TCPIP_CORE(); +} + +void SyslogFields_Hostname(struct SolidSyslogHeaderField* field, void* context) +{ + (void) context; + + char address[IP4ADDR_STRLEN_MAX] = {0}; + + SyslogFields_IpAddress(address, sizeof(address)); if (address[0] != '\0') { diff --git a/app/syslog/SyslogFields.h b/app/syslog/SyslogFields.h index d329392..57e26b3 100644 --- a/app/syslog/SyslogFields.h +++ b/app/syslog/SyslogFields.h @@ -4,12 +4,17 @@ #ifndef SYSLOG_FIELDS_H #define SYSLOG_FIELDS_H +#include + struct SolidSyslogTimestamp; struct SolidSyslogHeaderField; /** SolidSyslogClockFunction. */ void SyslogFields_Clock(struct SolidSyslogTimestamp* timestamp); +/** The default interface's IPv4 address, or an empty string when unavailable. */ +void SyslogFields_IpAddress(char* out, size_t size); + /** HOSTNAME as the interface's IPv4 address — RFC 5424 section 6.2.4 allows an * address where a device has no resolvable name. */ void SyslogFields_Hostname(struct SolidSyslogHeaderField* field, void* context); diff --git a/measurements/origin-ip.csv b/measurements/origin-ip.csv new file mode 100644 index 0000000..1c73498 --- /dev/null +++ b/measurements/origin-ip.csv @@ -0,0 +1,13 @@ +# origin-ip 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,362048 +flash_data,640 +static_bss,119724 +heap_used,4440 +mbedtls_peak,21256 +mbedtls_free,11512 +lwip_mem_free,7576 +lwip_pbufs_free,13 +stack_log,800 +stack_service,1012 +stack_harness,2848 diff --git a/measurements/stages.tsv b/measurements/stages.tsv index 7e5f5fa..f3918ec 100644 --- a/measurements/stages.tsv +++ b/measurements/stages.tsv @@ -21,3 +21,4 @@ tcp TCP records the network retransmits instead of dropping, and a send that fai time-quality Time quality a timestamp the collector knows how far to trust, and an uptime that tells a reboot from a counter wrap 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 diff --git a/run-report.md b/run-report.md index ec06d4a..1775f31 100644 --- a/run-report.md +++ b/run-report.md @@ -1,4 +1,4 @@ -# solid-syslog-example — run (origin) +# solid-syslog-example — run (origin-ip) ## Device (self-measured) @@ -10,15 +10,15 @@ [device] first record logged: yes [report] --- SolidSyslog cost above baseline (simulated existing application) --- [report] key,current,baseline,used_above_baseline -[report] flash_text,361656,349992,11664 +[report] flash_text,362048,349992,12056 [report] flash_data,640,316,324 [report] static_bss,119724,110876,8848 [report] heap_used,4440,4440,0 -[report] mbedtls_peak,21240,21332,-92 -[report] mbedtls_free,11528,11436,92 +[report] mbedtls_peak,21260,21332,-72 +[report] mbedtls_free,11508,11436,72 [report] lwip_mem_free,7576,7576,0 [report] lwip_pbufs_free,13,14,-1 -[report] stack_log,792,120,672 +[report] stack_log,800,120,680 [report] stack_service,1012,52,960 [report] stack_harness,2848,2840,8 [report] --- end --- @@ -29,7 +29,7 @@ ```text text data bss dec hex filename - 361648 648 119724 482020 75ae4 /w/build/baseline-cross/baseline.elf + 362040 648 119724 482412 75c6c /w/build/baseline-cross/baseline.elf ``` ## Listeners (proved before the device ran) @@ -47,22 +47,22 @@ ## Collector (syslog-ng) received ```text -wire <134>1 2026-08-16T06:31:59.830000Z 10.0.2.15 solid-syslog-example - BOOT [meta sequenceId="1" sysUpTime="283"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473"] device started -parsed PRIORITY=134 TIMESTAMP=2026-08-16T06:31:59+00:00 HOSTNAME=10.0.2.15 APP_NAME=solid-syslog-example PROCID= MSGID=BOOT STRUCTURED_DATA=[meta sequenceId="1" sysUpTime="283"][timeQuality tzKnown="1" isSynced="0"][origin software="solid-syslog-example" swVersion="0.1.0" enterpriseId="32473"] MSG=device started +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 ``` -## Self-check (vs measurements/origin.csv) +## Self-check (vs measurements/origin-ip.csv) ```text - OK flash_text: 361656 (expected 361656, Δ0) + OK flash_text: 362048 (expected 362048, Δ0) OK flash_data: 640 (expected 640, Δ0) OK static_bss: 119724 (expected 119724, Δ0) OK heap_used: 4440 (expected 4440, Δ0) - OK mbedtls_peak: 21240 (expected 21332, Δ92) - OK mbedtls_free: 11528 (expected 11436, Δ92) + OK mbedtls_peak: 21260 (expected 21256, Δ4) + OK mbedtls_free: 11508 (expected 11512, Δ4) OK lwip_mem_free: 7576 (expected 7576, Δ0) OK lwip_pbufs_free: 13 (expected 13, Δ0) - OK stack_log: 792 (expected 792, Δ0) + OK stack_log: 800 (expected 800, Δ0) OK stack_service: 1012 (expected 1012, Δ0) OK stack_harness: 2848 (expected 2848, Δ0) ```