diff --git a/CMakeLists.txt b/CMakeLists.txt index 94bc607..5ee896d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -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 diff --git a/docs/dmicmp.md b/docs/dmicmp.md index cb1dd24..974ebd5 100644 --- a/docs/dmicmp.md +++ b/docs/dmicmp.md @@ -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. @@ -24,7 +24,7 @@ once: ├──────────────────────────────────────────────┤ │ DMIP │ │ dmip_send(), dmip_checksum(), │ -│ protocol registration │ +│ protocol handler DIF │ ├──────────────────────────────────────────────┤ │ DMNETBRIDGE / DMROUTE / DMNETIF / DMARP │ └──────────────────────────────────────────────┘ @@ -32,15 +32,17 @@ once: ## 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 @@ -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 diff --git a/include/dmicmp.h b/include/dmicmp.h index 3efbaa9..776d05c 100644 --- a/include/dmicmp.h +++ b/include/dmicmp.h @@ -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 @@ -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 @@ -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 diff --git a/src/dmicmp.c b/src/dmicmp.c index eb875b8..9a62385 100644 --- a/src/dmicmp.c +++ b/src/dmicmp.c @@ -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, @@ -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) @@ -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 }; @@ -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); @@ -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) { @@ -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++) {