From d3d292ef3a1061b09cc3315488648c78e40b5f47 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Sun, 16 Aug 2026 08:24:35 +0100 Subject: [PATCH] feat: state the device's own address in origin.ip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ip PARAM, sourced from the same interface address HOSTNAME reports. A relay or NAT between device and collector rewrites what the collector observes; ip is what the device says about itself, which survives the hop. Flash +12,380 B (+392 on the previous stage) RAM +9,172 B (unchanged) Log stack +680 B (+8) Service +960 B (unchanged) ip is repeatable, so the library asks for a count and then one value per index rather than taking a string. This device has one address and returns one, and none before the interface has an address — a count of zero omits the PARAM rather than emitting an empty one. SyslogFields_IpAddress is now the single place that reads the address, and HOSTNAME formats the same string through it, so two fields that must agree cannot disagree. Four elements put the record at 260 octets. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 43 ++++++++++++++++---------------------- app/syslog/Syslog.c | 29 +++++++++++++++++++++++-- app/syslog/SyslogFields.c | 19 +++++++++++------ app/syslog/SyslogFields.h | 5 +++++ measurements/origin-ip.csv | 13 ++++++++++++ measurements/stages.tsv | 1 + run-report.md | 26 +++++++++++------------ 7 files changed, 90 insertions(+), 46 deletions(-) create mode 100644 measurements/origin-ip.csv 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) ```