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
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,10 @@ dmod_link_modules(${DMOD_MODULE_NAME}

enable_testing()
add_subdirectory(tests)

# Application-type DMOD modules built as CLI tools around dmicmp's API.
# Added after dmod_add_library() above so ${DMOD_MODULE_NAME}_if already
# exists - tools/ping links it directly, the same way tests/CMakeLists.txt
# does, rather than fetching dmicmp's own headers back via
# dmod_link_modules() as if it were an external module.
add_subdirectory(tools)
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ dmicmp_register_echo_listener(my_identifier, my_echo_reply_handler);
dmicmp_v4_send_echo_request(&dst, my_identifier, 1, NULL, 0, DMARP_DEFAULT_TIMEOUT_MS);
```

## Tools

- **[tools/ping](tools/ping/README.md)** - CLI to send ICMPv4 Echo
Requests to a host and report the replies, built on the API below.
It's its own Application-type DMOD module (`ping`), released alongside
`dmicmp` itself in every tagged release.

## API

| Function | Description |
Expand Down Expand Up @@ -112,6 +119,9 @@ dmicmp/
├── tests/
│ ├── CMakeLists.txt
│ └── dmicmp_test.c
├── tools/
│ ├── CMakeLists.txt
│ └── ping/ # ping CLI - its own Application-type module, see tools/ping/README.md
├── CMakeLists.txt
├── Makefile
├── dmicmp.dmr
Expand Down
2 changes: 2 additions & 0 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Application-type DMOD modules that live under dmicmp/tools/.
add_subdirectory(ping)
56 changes: 56 additions & 0 deletions tools/ping/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Name of the module
set(DMOD_MODULE_NAME ping)

# Author (should be string)
set(DMOD_AUTHOR_NAME "Patryk Kubiak")

# Stack size for the module (should be integer)
set(DMOD_STACK_SIZE 1024)

# Priority for the thread of the module (should be integer)
set(DMOD_PRIORITY 0)

# Path to the .dmr file - enables release package creation in build/packages/
set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/ping.dmr)

#
# dmod_add_executable - create an executable module
# it has the same signature as add_executable
# and can be used in the same way after the creation
# (for example, to link libraries)
#
dmod_add_executable(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION}
src/ping.c
)

# dmicmp is this same repository's own top-level module, already built as
# part of this CMake configure by the time tools/ is added (see the root
# CMakeLists.txt) - link its dmicmp_if target directly, exactly like
# tests/CMakeLists.txt does, instead of fetching dmicmp's own published
# headers back via dmod_link_modules() as if it were an external module.
target_link_libraries(${DMOD_MODULE_NAME} dmicmp_if)

# dmip: dmicmp.h #includes dmip.h directly (dmip_addr_t, dmip_send()'s
# family) - needed for that header to resolve even though ping.c
# itself never calls a dmip_* function (same reasoning dmicmp's own
# CMakeLists.txt gives for linking dmroute/dmnetif header-only).
# dmroute: dmip.h needs dmroute_addr_t (re-exported as dmip_addr_t)
# directly - dmicmp_if alone doesn't carry dmroute's own include
# dir.
# dmnetif: dmip.h needs dmnetif_iface_t for dmip_protocol_handler_t: not
# called from ping.c, just part of the header chain dmicmp.h
# pulls in.
# dmarp: DMARP_DEFAULT_TIMEOUT_MS, the ARP-resolution timeout forwarded to
# dmicmp_v4_send_echo_request().
# dmosi: dmosi_semaphore_*() to block for the matching Echo Reply (with a
# timeout), dmosi_get_tick_count() for round-trip time, and
# dmosi_thread_sleep() for the delay between requests.
dmod_link_modules(${DMOD_MODULE_NAME}
dmip
dmroute
dmnetif
dmarp
dmosi
)

add_subdirectory(tests)
108 changes: 108 additions & 0 deletions tools/ping/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# ping

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)

`ping` DMOD application module - a CLI to send ICMPv4 Echo Requests to a
host and report the matching Echo Replies, built on top of
[dmicmp](../../)'s own Echo Request/Reply API. It builds/parses no ICMP
bytes itself, only calls `dmicmp_v4_send_echo_request()` and
`dmicmp_register_echo_listener()`/`_unregister_echo_listener()`.

## Usage

```
ping <address> Send 4 ICMPv4 Echo Requests to <address>
ping --help | -h Show this help

Options:
-c <count> Number of Echo Requests to send (default: 4)
-i <interval_ms> Delay between requests, in milliseconds (default: 1000)
-W <timeout_ms> Time to wait for a reply before it's lost, in milliseconds (default: 1000)
-s <size> Number of payload bytes to send, 0-1024 (default: 32)
```

`<address>` must be a literal dotted-decimal IPv4 address - dmicmp has no
hostname resolution, and no ICMPv6 send path yet (see
[include/dmicmp.h](../../include/dmicmp.h)'s top comment).

Example output:

```
$ ping 192.168.1.1
PING 192.168.1.1: 32 data bytes
32 bytes from 192.168.1.1: icmp_seq=0 time=3 ms
32 bytes from 192.168.1.1: icmp_seq=1 time=2 ms
32 bytes from 192.168.1.1: icmp_seq=2 time=2 ms
32 bytes from 192.168.1.1: icmp_seq=3 time=3 ms

--- 192.168.1.1 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
```

## Building

This module lives under `tools/ping` inside the `dmicmp` repository and is
built as part of the parent's CMake configure (the top-level
`CMakeLists.txt` calls `add_subdirectory(tools)`, whose own
`CMakeLists.txt` calls `add_subdirectory(ping)`) - it is not built
standalone.

```bash
mkdir -p build
cd build
cmake ..
cmake --build . --target ping
```

Pass `-DDMOD_DIR=/path/to/local/dmod` to build against a local dmod checkout
instead of fetching `develop` from GitHub.

It is also released as its own separate module-type application: any
tagged release of this repository packages `ping` alongside `dmicmp`
itself (see `build/packages/ping/` after a build with `DMOD_DMR_PATH` set,
and the repository's release workflow).

## Testing

`tests/ping_test.c` drives this module through `Dmod_RunModule("ping",
argc, argv)` (loads it, runs `main()`, unloads it again - see
`Dmod_RunModule` in `dmod.h`) rather than depending on `ping` as a linked
module: an Application-type module can't be loaded as another module's
dependency (only a Library-type module can), and `Dmod_RunModule`
sidesteps that entirely by loading/running/unloading it on demand instead
of holding it resident for the test's lifetime.

## Documentation

See the `docs/` directory:

- **[ping.md](docs/ping.md)** - Usage, exit codes, and how this module is
tested

View documentation using `dmf-man ping`.

## Project Structure

```
ping/
├── docs/
│ ├── README.md
│ └── ping.md
├── src/
│ └── ping.c
├── tests/
│ ├── CMakeLists.txt
│ └── ping_test.c
├── CMakeLists.txt
└── ping.dmr
```

LICENSE is shared with the rest of the `dmicmp` repository (`../../LICENSE`).

## Author

Patryk Kubiak

## License

MIT
27 changes: 27 additions & 0 deletions tools/ping/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ping Documentation

Welcome to the ping module documentation.

## Contents

- **[ping.md](ping.md)** - Usage, exit codes, and how this module is tested

## Quick Reference

```
ping <address> Send 4 ICMPv4 Echo Requests to <address>
ping --help | -h Show this help

Options:
-c <count> Number of Echo Requests to send (default: 4)
-i <interval_ms> Delay between requests, in milliseconds (default: 1000)
-W <timeout_ms> Time to wait for a reply before it's lost, in milliseconds (default: 1000)
-s <size> Number of payload bytes to send, 0-1024 (default: 32)
```

View documentation using `dmf-man`:

```bash
dmf-man ping # Main documentation
dmf-man ping ping # ping.md
```
114 changes: 114 additions & 0 deletions tools/ping/docs/ping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# ping

## Overview

`ping` is a CLI over [dmicmp](../../..)'s Echo Request/Reply API - it sends
one or more ICMPv4 Echo Requests to a literal IPv4 address and reports
each matching Echo Reply, or that the request was lost. It never builds
or parses an ICMP message itself; everything goes through
`dmicmp_v4_send_echo_request()` to send and
`dmicmp_register_echo_listener()`/`dmicmp_unregister_echo_listener()` to
learn about the reply (see `include/dmicmp.h`).

`<address>` must be a literal dotted-decimal IPv4 address (e.g.
`192.168.1.1`) - dmicmp has no hostname resolution, and no ICMPv6 send
path yet (see `dmicmp.h`'s top comment), so `ping` cannot resolve a name
or reach an IPv6 destination either.

## Usage

```
ping <address> Send 4 ICMPv4 Echo Requests to <address>
ping --help | -h Show this help

Options:
-c <count> Number of Echo Requests to send (default: 4)
-i <interval_ms> Delay between requests, in milliseconds (default: 1000)
-W <timeout_ms> Time to wait for a reply before it's lost, in milliseconds (default: 1000)
-s <size> Number of payload bytes to send, 0-1024 (default: 32)
```

Example output:

```
$ ping 192.168.1.1
PING 192.168.1.1: 32 data bytes
32 bytes from 192.168.1.1: icmp_seq=0 time=3 ms
32 bytes from 192.168.1.1: icmp_seq=1 time=2 ms
32 bytes from 192.168.1.1: icmp_seq=2 time=2 ms
32 bytes from 192.168.1.1: icmp_seq=3 time=3 ms

--- 192.168.1.1 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
```

`time=` is measured locally, from just before
`dmicmp_v4_send_echo_request()` is called to the moment the matching
reply's listener callback posts the semaphore `ping` is waiting on
(`dmosi_get_tick_count()` before/after) - it includes ARP resolution time
for the very first request to a given host, the same way a real ping's
first RTT sample often does.

## How a request/reply round works

Each Echo Request reuses the same ICMP identifier for the whole `ping`
run (derived from the tick count at startup, so two concurrent `ping`
invocations are unlikely to collide) but a fresh sequence number per
request. Before sending, `ping`:

1. Calls `dmicmp_register_echo_listener(identifier, echo_reply_handler)`.
2. Calls `dmicmp_v4_send_echo_request(...)`.
3. Blocks on a semaphore (created with `dmosi_semaphore_create(0, 1)`) for
up to `-W`'s timeout, via `dmosi_semaphore_wait()`.
4. Calls `dmicmp_unregister_echo_listener(identifier)` unconditionally,
whether or not a reply arrived in time.

`echo_reply_handler()` only posts the semaphore if the reply's sequence
number matches the request currently outstanding - a reply that arrives
late (after its own request already timed out and got unregistered) is
simply never delivered at all, since `dmicmp` drops a reply for an
identifier with no registered listener; the sequence check is an extra
guard against the much narrower race where a *new* registration for the
next request is already in place by the time a very late reply for the
*previous* one arrives.

## Exit codes

- `0` - at least one Echo Request got a matching reply, or `--help`/`-h`
was given
- `1` - bad arguments (missing/invalid address, unknown flag, an
out-of-range `-c`/`-W`/`-s` value), or every Echo Request was lost
(no reply within `-W`'s timeout, or `dmicmp_v4_send_echo_request()`
itself failed - e.g. no route to the destination)

## Testing

`tests/ping_test.c` drives this module through `Dmod_RunModule("ping",
argc, argv)` (loads it, runs `main()`, unloads it again - see
`Dmod_RunModule` in `dmod.h`) rather than depending on `ping` as a linked
module: an Application-type module can't be loaded as another module's
dependency (only a Library-type module can), and `Dmod_RunModule`
sidesteps that entirely by loading/running/unloading it on demand instead
of holding it resident for the test's lifetime.

A "/dev/null"-backed `dmnetif` fixture, with a `dmroute` route and a
`dmarp` cache entry pointed at it, lets a send be exercised up through
`dmip_send()` itself - same pattern `dmicmp`'s own
`tests/dmicmp_test.c` uses for `dmicmp_v4_send_error()`/
`_send_dest_unreachable()`. No real driver backs the fixture, so
`dmicmp_v4_send_echo_request()` still fails there (`dmnetif_send()` ->
`-EIO`) and no real Echo Reply can ever arrive - every test step expects
`ping` to report loss, not success; they only prove argument handling and
a full send attempt complete without crashing or hanging, and report the
honest outcome.

## Dependencies

- `dmicmp` - builds/sends the Echo Request and delivers the matching Echo
Reply (see the top-level [dmicmp](../../..) module)
- `dmroute` - the address type (`dmroute_addr_t`, re-exported by `dmicmp`
as `dmip_addr_t`) `ping` parses `<address>` into
- `dmarp` - `DMARP_DEFAULT_TIMEOUT_MS`, the ARP-resolution timeout
forwarded to `dmicmp_v4_send_echo_request()`
- `dmosi` - the semaphore `ping` waits for a reply on, the tick count used
to measure round-trip time, and the sleep between requests
45 changes: 45 additions & 0 deletions tools/ping/ping.dmr
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# DMOD Resource File for ping
# This file specifies where resources should be installed
#
# ping lives nested under dmicmp/tools/ping rather than being its own
# top-level repo, so ${repo_dir}/${build_dir} below (which always resolve
# to the dmicmp repo/build root, not this subdirectory) are prefixed with
# tools/ping - same convention dmnet/tools/ifconfig/ifconfig.dmr uses.

# === Core Module ===
# Main module file - always installed
dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf]

# Compressed module
dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc]

# Dependencies file (if exists)
dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd]

# Version information
version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt]

# === Documentation ===
# Module documentation in markdown format for dmf-man tool
docs=./docs => ${destination}/${module}/docs [origin=${repo_dir}/tools/ping/docs]

# README file
readme=./README.md => ${destination}/${module}/README.md [origin=${repo_dir}/tools/ping/README.md]

# === Header Files ===
# No committed include/ dir of its own (its API is dmicmp's, see the
# top-level dmicmp module) - just the generated module definitions header
# every DMOD module gets, so dmf-man/consumers can still see it.
# Destination must be a full *file* path (not a bare "include" dir) since
# there's no separate directory-origin here to tell the packaging tool to
# treat it as one - see dmicmp.dmr's inc= entry for the directory+file
# combined case.
inc=./include/${module}_defs.h => ${destination}/${module}/include/${module}_defs.h [origin=${build_dir}/tools/ping/${module}_defs.h]

# === License ===
# License file - shared at the dmicmp repo root (same "nested module
# shares the parent repo's LICENSE" convention as dmnet/tools/ifconfig)
license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE]

# Adding itself
dmr=./${module}.dmr => ${destination}/${module}/${module}.dmr [origin=${repo_dir}/tools/ping/${module}.dmr]
Loading
Loading