lwIP (Raw API) setup¶
Wiring the lwIP adapters so a sender delivers over UDP or TCP.
lwIP (Raw API) covers what the adapters fill and what your
lwipopts.h must enable; the config fields are documented on the structs
themselves. This page is the wiring, and the one thing that is easy to get
wrong.
What to link¶
set(SOLIDSYSLOG_PLATFORMS "LwipRaw;<OsPrimitives>")
target_link_libraries(my_app PRIVATE SolidSyslog SolidSyslog::LwipRaw)
The placeholders are whichever platforms the capability matrix says fill the rest of what your build needs. See naming your platforms for how the list is read.
The marshal¶
Every Raw API call the adapters make is routed through a single hop, installed once at boot:
The marshal must run its callback synchronously, before it returns. The adapter reads results the moment the hop returns. One global slot serves the process, because there is one lwIP instance to protect.
Which marshal you install depends only on how lwIP is built.
NO_SYS=1 - bare metal¶
Install nothing. There is one execution context and no core to protect, so the default direct call is correct.
The trap is the Sleep callback. The TCP stream's Open is synchronous over an
asynchronous tcp_connect, so it spins and sleeps while waiting for the
connection callback - and on bare metal nothing else is driving lwIP during that
sleep. Your Sleep must keep the stack running:
void MyLwipSleep(uint32_t milliseconds)
{
/* Elapsed rather than a deadline, so the loop is correct across a
* timebase wrap; unsigned subtraction wraps with it. */
uint32_t start = MyTimebase_NowMs();
while ((MyTimebase_NowMs() - start) < milliseconds)
{
sys_check_timeouts();
MyNetif_DrivePolledRx(); /* your board's receive pump */
}
}
A Sleep that merely busy-waits leaves lwIP unable to advance its state
machine, so the connection callback never fires and Open times out with
nothing visibly wrong.
Call SolidSyslog_Service from the same loop that calls sys_check_timeouts.
NO_SYS=0 - an lwIP thread¶
A dedicated thread owns lwIP's state, so every call has to reach it. With core locking compiled in, take the lock around the hop:
void MyCoreLockMarshal(SolidSyslogLwipRawCallback callback, void* context)
{
LOCK_TCPIP_CORE();
callback(context);
UNLOCK_TCPIP_CORE();
}
Core locking is the route to prefer, and it is what the reference target uses. It runs the callback in your own task under the lock, so it is synchronous by construction, independent of task priority, and costs no mailbox message.
Without core locking you must post to lwIP's mailbox - and then wait for the
callback to run, which the post alone does not do. tcpip_callback_with_block
blocks until the mailbox accepts the message, not until the tcpip thread
executes it, so returning at that point would break the contract and leave the
hop's stack frame dangling under the callback. Carry a semaphore:
struct MarshalHop
{
SolidSyslogLwipRawCallback callback;
void* context;
sys_sem_t done;
};
static void RunHop(void* ctx)
{
struct MarshalHop* hop = ctx;
hop->callback(hop->context);
sys_sem_signal(&hop->done);
}
void MyTcpipMarshal(SolidSyslogLwipRawCallback callback, void* context)
{
if (MyPort_CurrentTaskIsTcpipThread()) /* else this deadlocks */
{
callback(context);
return;
}
struct MarshalHop hop = {callback, context, {0}};
if (sys_sem_new(&hop.done, 0) == ERR_OK)
{
if (tcpip_callback_with_block(RunHop, &hop, 1) == ERR_OK)
{
sys_arch_sem_wait(&hop.done, 0);
}
sys_sem_free(&hop.done);
}
}
The err_t matters: a failed post means RunHop never runs, so waiting on the
semaphore would hang and returning without waiting would let the adapter read
result state nothing wrote.
Two more things to check in your port: the mailbox must be sized for a blocking
post, and lwIP exposes no portable "am I on the lwIP thread?" predicate, so
most ports compare the current task handle against the one given to
tcpip_init. Without that guard, an adapter call made from inside a callback
lwIP itself invoked will deadlock.
Here Sleep does not drive lwIP - the lwIP thread is running concurrently - but
it must still wait at least the time asked, as
SolidSyslogSleep.h requires: the connect
and resolve deadlines are counted in the sleeps requested.
Marshal at the individual lwIP call, which is what this seam gives you. Wrapping
SolidSyslog_Service instead puts file I/O, crypto, buffer locking and record
formatting on the lwIP thread, none of which touch lwIP, and starves its timer
and receive path under load.
Resolving by name¶
The DNS resolver bridges lwIP's asynchronous dns_gethostbyname to the
synchronous resolve contract the same way the TCP stream bridges connect: a
cache or hostlist hit returns immediately, and anything else spins on your
thread - never lwIP's - sleeping via your Sleep until the answer arrives or
the deadline passes. It needs LWIP_DNS=1 and a Sleep; without one it falls
back to the Null resolver.
Where there is no DNS server, DNS_LOCAL_HOSTLIST maps names statically and
resolves entirely on-device:
#define LWIP_DNS 1
#define DNS_LOCAL_HOSTLIST 1
#define DNS_LOCAL_HOSTLIST_INIT \
{ DNS_LOCAL_HOSTLIST_ELEM("collector", IPADDR4_INIT_BYTES(10, 0, 2, 2)) }
Wire whichever resolver your deployment needs:
SolidSyslogLwipRawResolver for an
endpoint given as an address, or
SolidSyslogLwipRawDnsResolver
for one that may be a name. Each says which fits. The pool sizes and timeouts are
in Adding it to your build.
Limits¶
The pack is IPv4. Both resolvers refuse an IPv6 answer rather than store a
destination the datagram and the stream cannot send to, and report through the
error handler when they do. Neither the datagram nor the TCP stream
selects an output interface - lwIP's routing table decides - and the datagram
cannot discover the path maximum transmission unit, so it answers the payload
limit its config states, or SOLIDSYSLOG_UDP_IPV4_PAYLOAD_LIMIT where none is
stated.
When it does not work¶
Install an error handler before you start; error severity says what each level is telling you.
- A lookup that does not answer raises nothing from the numeric resolver,
and
WARNINGfrom the DNS resolver only when its deadline passes. An unparseable literal, an unknown name or a rejected query fails the resolve and reports no event; what you see is the delivery failure the sender raises once records stop getting through. - A lookup refused for its address family raises
ERROR. The destination cannot be served as asked, and retrying will not change that.