Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dmicmp.dmr)
# List of MAL interfaces implemented by this module (leave empty if none)
set(DMOD_MAL_IMPLS )

# List of DIF interfaces implemented by this module (leave empty if none)
set(DMOD_DIF_IMPLS )
# List of DIF interfaces implemented by this module (leave empty if none) -
# dmicmp implements dmip's protocol handler DIF (see src/dmicmp.c).
set(DMOD_DIF_IMPLS dmip)

#
# dmod_add_library - create a library module
Expand All @@ -76,15 +77,15 @@ target_include_directories(${DMOD_MODULE_NAME}_if INTERFACE
# ICMP message - dmicmp only builds/parses/checksums the ICMP
# message itself and hands it to dmip, plus dmip_checksum(),
# dmip_v4_parse_header() (to read the original IP header out of an
# unclaimed/undeliverable packet), and dmip_register_protocol()/
# _register_default_protocol() to receive completed IP packets
# (see src/dmicmp.c).
# unclaimed/undeliverable packet), and dmip's protocol handler DIF
# (dmip_protocol_receive()/_protocol_numbers()) to receive completed
# IP packets (see src/dmicmp.c and DMOD_DIF_IMPLS above).
# dmroute: header-only - dmip.h needs dmroute_addr_t (re-exported as
# dmip_addr_t) directly, even though dmicmp.c itself never calls
# a dmroute_* function (same reason dmudp/dmip link it - see
# their own CMakeLists.txt).
# dmnetif: dmnetif_iface_t, the iface parameter dmip_protocol_handler_t
# passes to dmicmp_handle_ip_packet() - dmicmp never calls a
# dmnetif: dmnetif_iface_t, the iface parameter dmip_protocol_receive()
# passes to dmicmp's implementation - dmicmp never calls a
# dmnetif_* function itself either.
# dmlist: backs the echo listener registry
# dmosi: mutex guarding that registry
Expand Down
36 changes: 19 additions & 17 deletions docs/dmicmp.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
## Overview

DMICMP builds and parses ICMP messages (RFC 792 for ICMPv4, RFC 4443 for
ICMPv6) and plugs into [dmip](../../dmip)'s protocol dispatch two ways at
once:

- `dmip_register_protocol(DMIP_PROTO_ICMP/_ICMPV6, ...)` - to receive
genuine ICMP messages. An incoming Echo Request is answered with an
Echo Reply synchronously, inline in that same callback.
- `dmip_register_default_protocol(...)` - to catch any IP packet whose
protocol nobody else claimed, replying with an ICMPv4 Destination
Unreachable. See
ICMPv6) and plugs into [dmip](../../dmip)'s protocol dispatch by
implementing dmip's protocol handler DIF, claiming three numbers at once
from one `dmip_protocol_numbers()` implementation:

- `DMIP_PROTO_ICMP` / `DMIP_PROTO_ICMPV6` - genuine ICMP messages. An
incoming Echo Request is answered with an Echo Reply synchronously,
inline in the same `dmip_protocol_receive()` call.
- `DMIP_PROTO_DEFAULT` - to catch any IP packet whose protocol nobody
else claimed, replying with an ICMPv4 Destination Unreachable. See
[dmip.md](../../dmip/docs/dmip.md#protocol-dispatch) for why this
fallback exists.

Expand All @@ -24,23 +24,25 @@ once:
├──────────────────────────────────────────────┤
│ DMIP │
│ dmip_send(), dmip_checksum(), │
│ protocol registration │
│ protocol handler DIF │
├──────────────────────────────────────────────┤
│ DMNETBRIDGE / DMROUTE / DMNETIF / DMARP │
└──────────────────────────────────────────────┘
```

## No extra thread needed to answer a ping

`dmip_register_protocol()`'s callback already runs on whatever thread is
pumping the interface a packet arrived on (see
`dmip_protocol_handler_t` in `dmip.h`) - the same thread
`dmip_protocol_receive()`'s implementation already runs on whatever
thread is pumping the interface a packet arrived on (see dmip.h's
"Protocol handler DIF" section) - the same thread
`dmnetbridge_handle_netif_rx()` uses. Answering an Echo Request is
therefore just "build an Echo Reply and call `dmip_send()`, right here,
right now" - no receive queue, no worker thread, no listener registry
needed for this direction. That's also true of the default-handler path:
an unclaimed protocol's Destination Unreachable is sent inline from the
same callback.
needed for this direction. That's also true of the fallback path: an
unclaimed protocol's Destination Unreachable is sent inline from the same
call, after re-parsing the packet's own protocol field to tell the two
cases apart (dmicmp implements a single `dmip_protocol_receive()`,
serving both claims - see `src/dmicmp.c`).

## Sending our own Echo Request needs a different shape

Expand Down Expand Up @@ -104,7 +106,7 @@ shape almost exactly, just with `DMIP_PROTO_ICMPV6` in place of

There is no `dmip_v6_send()` yet (blocked on a missing NDP module, RFC
4861 - the same boundary `dmarp.h`/`dmudp.h` document for their own IPv6
gaps). dmicmp still registers for `DMIP_PROTO_ICMPV6` and validates/
gaps). dmicmp still claims `DMIP_PROTO_ICMPV6` and validates/
parses incoming ICMPv6 messages correctly - there's real value in
rejecting a malformed or spoofed message even if nothing can be sent back
yet - but an incoming ICMPv6 Echo Request, or an unclaimed IPv6 protocol
Expand Down
48 changes: 24 additions & 24 deletions include/dmicmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,29 @@ extern "C" {
* @brief DMOD ICMP - Public API
*
* dmicmp builds/parses ICMP messages (RFC 792 for ICMPv4, RFC 4443 for
* ICMPv6) and plugs into dmip's protocol dispatch two ways at once
* (registered in dmod_init(), see src/dmicmp.c):
*
* - dmip_register_protocol(DMIP_PROTO_ICMP/_ICMPV6, ...) - to receive
* genuine ICMP messages. An incoming Echo Request is answered with an
* Echo Reply synchronously, inline in that same callback - it already
* runs on whatever thread is pumping the interface (see
* dmip_protocol_handler_t in dmip.h), so there's no need for an extra
* thread or queue just to answer a ping.
*
* - dmip_register_default_protocol(...) - to catch any IP packet whose
* protocol nobody else claimed, replying with an ICMPv4 Destination
* Unreachable (Protocol Unreachable). See dmip.h's "Protocol
* registration" section and docs/dmip.md for why this fallback exists.
* ICMPv6) and plugs into dmip's protocol dispatch by implementing dmip's
* protocol handler DIF (see src/dmicmp.c and dmip.h's "Protocol handler
* DIF" section), claiming three numbers at once from one
* dmip_protocol_numbers() implementation:
*
* - DMIP_PROTO_ICMP / DMIP_PROTO_ICMPV6 - genuine ICMP messages. An
* incoming Echo Request is answered with an Echo Reply synchronously,
* inline in the same dmip_protocol_receive() call - it already runs on
* whatever thread is pumping the interface, so there's no need for an
* extra thread or queue just to answer a ping.
*
* - DMIP_PROTO_DEFAULT - to catch any IP packet whose protocol nobody
* else claimed, replying with an ICMPv4 Destination Unreachable
* (Protocol Unreachable). See dmip.h's "Protocol handler DIF" section
* and docs/dmip.md for why this fallback exists.
*
* There is no dmip_v6_send() yet (blocked on a missing NDP module, the
* same gap dmudp already lives with for sending) - dmicmp still
* registers for DMIP_PROTO_ICMPV6 and validates/parses incoming ICMPv6
* messages correctly, but cannot reply to an ICMPv6 Echo Request or send
* an ICMPv6 error yet; those paths log a warning and drop instead of
* silently doing nothing. This is a deliberate, temporary limitation -
* see docs/dmicmp.md.
* same gap dmudp already lives with for sending) - dmicmp still claims
* DMIP_PROTO_ICMPV6 and validates/parses incoming ICMPv6 messages
* correctly, but cannot reply to an ICMPv6 Echo Request or send an ICMPv6
* error yet; those paths log a warning and drop instead of silently doing
* nothing. This is a deliberate, temporary limitation - see
* docs/dmicmp.md.
*
* Sending our OWN Echo Request (a "ping") is a separate, caller-driven
* feature: dmicmp_v4_send_echo_request() just sends, and
Expand Down Expand Up @@ -280,7 +281,7 @@ dmod_dmicmp_api(1.0, int, _v4_send_dest_unreachable, ( dmicmp_v4_dest_unreachabl
* receive one matching Echo Reply
*
* Called from whatever thread is pumping the interface the reply
* arrived on (same delivery context as dmip_protocol_handler_t - see
* arrived on (same delivery context as dmip_protocol_receive - see
* dmip.h) - `payload` is only valid for the duration of the call, copy
* it out if you need it afterward. Fires at most once per
* dmicmp_register_echo_listener() call - register again (with a fresh
Expand All @@ -300,9 +301,8 @@ typedef void (*dmicmp_echo_reply_handler_t)( const dmip_addr_t* src, uint16_t id
* @brief Register `handler` to be called the next time an Echo Reply
* carrying `identifier` arrives (either family)
*
* One registration per identifier, like dmip_register_protocol() is one
* registration per protocol number - register a fresh one per
* outstanding ping, not a long-lived subscription. An Echo Reply for an
* One registration per identifier - register a fresh one per outstanding
* ping, not a long-lived subscription. An Echo Reply for an
* identifier with no registered listener is silently dropped, the same
* tiered fallback shape dmip's own protocol dispatch uses (specific
* match -> default -> drop) - here there is no "default" tier, just
Expand Down
149 changes: 89 additions & 60 deletions src/dmicmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@
* primitive), dmicmp_v4_send_dest_unreachable(), and
* dmicmp_v4_send_echo_request() are all thin wrappers over it.
*
* - Receiving: dmicmp registers with dmip two ways at once (see
* dmod_init()) - dmip_register_protocol() for DMIP_PROTO_ICMP/
* _ICMPV6, to answer an incoming Echo Request inline and deliver an
* incoming Echo Reply to whichever dmicmp_register_echo_listener()
* call matches its identifier (a small dmlist-backed table, guarded
* by g_echo_mutex - the same shape dmip.c's own protocol dispatch
* table uses, just keyed by identifier instead of protocol number);
* and dmip_register_default_protocol(), to answer any IP packet whose
* protocol nobody claimed with an ICMPv4 Destination Unreachable.
* - Receiving: dmicmp implements dmip's protocol handler DIF (see
* dmip.h) to claim DMIP_PROTO_ICMP/_ICMPV6 (to answer an incoming Echo
* Request inline and deliver an incoming Echo Reply to whichever
* dmicmp_register_echo_listener() call matches its identifier - a
* small dmlist-backed table, guarded by g_echo_mutex) and
* DMIP_PROTO_DEFAULT (to answer any IP packet whose protocol nobody
* else claimed with an ICMPv4 Destination Unreachable) - all three from
* the single dmip_protocol_numbers() implementation below, so
* dmicmp_handle_ip_packet() (dmip_protocol_receive()'s implementation)
* re-parses the enclosing IP header's own protocol field to tell the
* two cases apart, rather than being told which one applies.
*
* There is no dmip_v6_send() yet - an incoming ICMPv6 Echo Request or an
* unclaimed IPv6 protocol can be parsed and checksum-validated correctly,
Expand Down Expand Up @@ -366,7 +368,7 @@ static void deliver_echo_reply(const dmip_addr_t* src, uint16_t identifier, uint
*
* An Echo Request is answered inline, right here - this function is
* already running on whatever thread is pumping the interface (see
* dmip_protocol_handler_t in dmip.h), so there's no need for an extra
* dmip_protocol_receive in dmip.h), so there's no need for an extra
* thread or queue just to answer a ping. An Echo Reply is delivered to
* whichever dmicmp_register_echo_listener() call matches its identifier,
* if any. Anything else (Destination Unreachable, any other type/code)
Expand Down Expand Up @@ -425,19 +427,13 @@ static void handle_v6_icmp_message(const dmip_addr_t* src, const dmip_addr_t* ds
}

/**
* @brief dmip_protocol_handler_t registered for DMIP_PROTO_ICMP and
* DMIP_PROTO_ICMPV6 - see dmod_init()
*
* Parses `packet`'s IP header to locate the ICMP message and read
* `src`/`dst`, then hands off to handle_v4_icmp_message()/
* _v6_icmp_message(). `packet` is borrowed (see dmip_protocol_handler_t's
* own doc comment in dmip.h) - nothing here keeps a pointer into it past
* the call.
* @brief Handle a packet whose own protocol is DMIP_PROTO_ICMP/_ICMPV6 -
* parses `packet`'s IP header to locate the ICMP message and read
* `src`/`dst`, then hands off to handle_v4_icmp_message()/
* _v6_icmp_message()
*/
static void dmicmp_handle_ip_packet(dmip_family_t family, dmnetif_iface_t iface, const uint8_t* packet, size_t packet_len)
static void handle_icmp_protocol(dmip_family_t family, const uint8_t* packet, size_t packet_len)
{
(void)iface;

if (family == dmip_family_v4)
{
dmip_v4_header_t ip_header = { 0 };
Expand All @@ -458,19 +454,13 @@ static void dmicmp_handle_ip_packet(dmip_family_t family, dmnetif_iface_t iface,
}

/**
* @brief dmip_protocol_handler_t registered via
* dmip_register_default_protocol() - see dmod_init()
*
* Called for any IP packet whose protocol nobody claimed. Never actually
* reached for a genuine ICMP packet - dmicmp itself claims both
* DMIP_PROTO_ICMP and DMIP_PROTO_ICMPV6 via dmip_register_protocol(), so
* dmip's dispatcher routes those to dmicmp_handle_ip_packet() instead,
* never here.
* @brief Handle a packet whose own protocol is neither DMIP_PROTO_ICMP
* nor _ICMPV6 - reached only via the DMIP_PROTO_DEFAULT fallback
* claim (see dmicmp_protocol_numbers() below), i.e. some protocol
* nobody else claimed either
*/
static void dmicmp_handle_unclaimed_protocol(dmip_family_t family, dmnetif_iface_t iface, const uint8_t* packet, size_t packet_len)
static void handle_unclaimed_protocol(dmip_family_t family, const uint8_t* packet, size_t packet_len)
{
(void)iface;

if (family == dmip_family_v4)
{
dmicmp_v4_send_dest_unreachable(dmicmp_v4_dest_unreachable_protocol, packet, packet_len, DMICMP_DEFAULT_ARP_TIMEOUT_MS);
Expand All @@ -485,11 +475,75 @@ static void dmicmp_handle_unclaimed_protocol(dmip_family_t family, dmnetif_iface
}
}

/**
* @brief Implementation of dmip's dmip_protocol_receive DIF (see dmip.h) -
* the single entry point for both of dmicmp_protocol_numbers()'s
* claims (ICMP/ICMPv6 specifically, and DMIP_PROTO_DEFAULT as the
* fallback for everything else)
*
* Re-parses just enough of `packet`'s own IP header to read its protocol
* number and tell the two cases apart - unlike the old two-callback
* registration (dmip_register_protocol() for ICMP/ICMPv6,
* dmip_register_default_protocol() for the fallback), a DIF implementation
* is one function per module, so dmicmp can no longer rely on dmip having
* already made that distinction before calling in. `packet` is borrowed
* (see dmip_protocol_receive()'s own doc comment in dmip.h) - nothing here
* keeps a pointer into it past the call.
*/
dmod_dmip_dif_api_declaration(1.0, dmicmp, void, _protocol_receive, ( dmip_family_t family, dmnetif_iface_t iface, const uint8_t* packet, size_t packet_len ))
{
(void)iface;

uint8_t protocol;
if (family == dmip_family_v4)
{
dmip_v4_header_t ip_header = { 0 };
size_t header_len = 0;
if (dmip_v4_parse_header(packet, packet_len, &ip_header, &header_len) != 0)
return;
protocol = ip_header.protocol;
}
else /* dmip_family_v6 */
{
dmip_v6_header_t ip_header = { 0 };
if (dmip_v6_parse_header(packet, packet_len, &ip_header) != 0)
return;
protocol = ip_header.next_header;
}

if (protocol == DMIP_PROTO_ICMP || protocol == DMIP_PROTO_ICMPV6)
handle_icmp_protocol(family, packet, packet_len);
else
handle_unclaimed_protocol(family, packet, packet_len);
}

/**
* @brief Implementation of dmip's dmip_protocol_numbers DIF (see dmip.h) -
* dmicmp claims DMIP_PROTO_ICMP, DMIP_PROTO_ICMPV6, and
* DMIP_PROTO_DEFAULT, all unconditionally
*/
dmod_dmip_dif_api_declaration(1.0, dmicmp, size_t, _protocol_numbers, ( uint16_t* out_protocols, size_t max_protocols ))
{
static const uint16_t claimed[] = { DMIP_PROTO_ICMP, DMIP_PROTO_ICMPV6, DMIP_PROTO_DEFAULT };
size_t claimed_count = sizeof(claimed) / sizeof(claimed[0]);
size_t count = (max_protocols < claimed_count) ? max_protocols : claimed_count;

for (size_t i = 0; i < count; i++)
out_protocols[i] = claimed[i];

return count;
}

/* ---- DMOD lifecycle ---- */

/**
* @brief Module initialization - allocates the echo listener table and
* its guarding mutex, then registers with dmip
* its guarding mutex
*
* No registration with dmip needed anymore - dmicmp_protocol_receive()/
* _protocol_numbers() (above) are discovered by dmip via DIF, on demand,
* for as long as this module stays loaded and enabled (see dmip.h's
* "Protocol handler DIF" section).
*/
int dmod_init(const Dmod_Config_t *Config)
{
Expand All @@ -510,41 +564,16 @@ int dmod_init(const Dmod_Config_t *Config)
return -1;
}

int result = dmip_register_protocol(DMIP_PROTO_ICMP, dmicmp_handle_ip_packet);
if (result != 0)
{
DMOD_LOG_ERROR("dmicmp: cannot register as the ICMP protocol handler (%d)\n", result);
return -1;
}

result = dmip_register_protocol(DMIP_PROTO_ICMPV6, dmicmp_handle_ip_packet);
if (result != 0)
{
DMOD_LOG_ERROR("dmicmp: cannot register as the ICMPv6 protocol handler (%d)\n", result);
return -1;
}

result = dmip_register_default_protocol(dmicmp_handle_unclaimed_protocol);
if (result != 0)
{
DMOD_LOG_ERROR("dmicmp: cannot register as dmip's default protocol handler (%d)\n", result);
return -1;
}

DMOD_LOG_INFO("DMICMP initialized\n");
return 0;
}

/**
* @brief Module deinitialization - unregisters from dmip, frees every
* remaining echo listener registration, then the table and mutex
* @brief Module deinitialization - frees every remaining echo listener
* registration, then the table and mutex
*/
int dmod_deinit(void)
{
dmip_unregister_default_protocol();
dmip_unregister_protocol(DMIP_PROTO_ICMPV6);
dmip_unregister_protocol(DMIP_PROTO_ICMP);

size_t count = dmlist_size(g_echo_listeners);
for (size_t i = 0; i < count; i++)
{
Expand Down
Loading