Current section
Files
Jump to
Current section
Files
README.md
# RawPing
Pure Erlang/OTP ICMP ping library using the modern `:socket` API.
No NIFs, no external dependencies, no debug trace memory leaks. Requires Elixir 1.17+ (OTP 25+).
## Installation
Add `raw_ping` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:raw_ping, "~> 0.2.0"}
]
end
```
## Usage
```elixir
# Single ping - returns RTT in milliseconds
{:ok, rtt} = RawPing.ping("8.8.8.8")
{:ok, rtt} = RawPing.ping({8, 8, 8, 8})
{:ok, rtt} = RawPing.ping([8, 8, 8, 8]) # list format also works
# With options
{:ok, rtt} = RawPing.ping("8.8.8.8", timeout: 2000)
{:error, :timeout} = RawPing.ping("192.0.2.1", timeout: 100)
# Multiple pings with statistics
{:ok, stats} = RawPing.ping_stats("8.8.8.8", count: 5)
# %{min: 10.2, max: 15.8, avg: 12.5, success_rate: 1.0, success_count: 5, failure_count: 0, rtts: [...]}
# Batch ping multiple hosts concurrently
results = RawPing.ping_batch(["8.8.8.8", "1.1.1.1", "192.168.1.1"], timeout: 1000)
# %{"8.8.8.8" => {:ok, 12.5}, "1.1.1.1" => {:ok, 8.2}, "192.168.1.1" => {:error, :timeout}}
```
## Options
| Option | Default | Description |
|--------|---------|-------------|
| `:timeout` | 5000 | Timeout in milliseconds |
| `:count` | 1 | Number of pings (for `ping_stats/2`) |
| `:payload_size` | 56 | ICMP payload size in bytes |
| `:max_concurrency` | 50 | Max concurrent pings (for `ping_batch/2`) |
## Privileges
**Most hosts need none.** By default RawPing opens an unprivileged ICMP datagram
socket (`SOCK_DGRAM`/`IPPROTO_ICMP`), falling back to a raw socket only if that
is unavailable.
Check which interface you got:
```elixir
RawPing.socket_mode()
#=> {:ok, :dgram} # unprivileged
#=> {:ok, :raw} # fell back; needed root or CAP_NET_RAW
```
On Linux, datagram ICMP is gated by `net.ipv4.ping_group_range`, which must
include the running process's GID. Many distributions already ship it wide open:
```bash
$ cat /proc/sys/net/ipv4/ping_group_range
0 2147483647 # any GID may use unprivileged ICMP
```
If it is restrictive, either widen it — no runtime capability required:
```bash
sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"
```
…or grant the raw-socket path a privilege, as before:
1. **Run as root** (development/testing)
```bash
sudo mix run -e 'RawPing.ping("8.8.8.8") |> IO.inspect'
```
2. **Set CAP_NET_RAW capability** (Linux)
```bash
sudo setcap cap_net_raw+ep /path/to/beam.smp
```
3. **Container with NET_RAW** (Docker/Kubernetes)
```yaml
securityContext:
capabilities:
add: ["NET_RAW"]
```
Note that a **rootless container sharing the host network namespace cannot
obtain `CAP_NET_RAW` at all** — that namespace is owned by the initial user
namespace, so the capability has no force there. In that deployment shape the
datagram path is the only way ICMP works.
### Platform differences
The two platforms behave differently on datagram sockets. RawPing handles both
by inspecting the reply rather than assuming a format:
| | Linux | macOS/BSD |
|---|---|---|
| IP header on receive | stripped | included |
| ICMP identifier | rewritten by kernel | preserved |
| TTL available | no (`nil`) | yes |
Because Linux rewrites the identifier, replies from a datagram socket are
matched on **sequence**. Raw sockets keep the stricter id-and-sequence match,
since they see every ICMP packet on the host.
## Why Not gen_icmp?
This library was created as an alternative to `gen_icmp` which:
- Uses NIFs via `procket` for raw socket access
- Abuses `gen_udp` internals in ways that can trigger debug traces
- Can cause severe memory leaks (20GB+) when pinging unreachable hosts at scale
`RawPing` uses Erlang/OTP's native `:socket` API (available since OTP 22) which provides clean, safe access to raw sockets without any of these issues.
## How It Works
1. Opens a raw ICMP socket via `:socket.open(:inet, :raw, :icmp)`
2. Builds ICMP echo request packets with proper checksums
3. Sends to target and receives replies with timeout handling
4. Parses ICMP echo replies, filtering by ID/sequence to handle concurrent pings
## Testing
```bash
# Run non-privileged tests (packet building/parsing)
mix test --exclude privileged
# Run all tests (requires sudo)
sudo mix test
```
## License
MIT License - see [LICENSE](LICENSE) for details.