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:
def deps do
[
{:raw_ping, "~> 0.2.0"}
]
end
Usage
# 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:
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:
$ 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:
sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"
β¦or grant the raw-socket path a privilege, as before:
Run as root (development/testing)
sudo mix run -e 'RawPing.ping("8.8.8.8") |> IO.inspect'Set CAP_NET_RAW capability (Linux)
sudo setcap cap_net_raw+ep /path/to/beam.smpContainer with NET_RAW (Docker/Kubernetes)
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
procketfor raw socket access - Abuses
gen_udpinternals 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
- Opens a raw ICMP socket via
:socket.open(:inet, :raw, :icmp) - Builds ICMP echo request packets with proper checksums
- Sends to target and receives replies with timeout handling
- Parses ICMP echo replies, filtering by ID/sequence to handle concurrent pings
Testing
# Run non-privileged tests (packet building/parsing)
mix test --exclude privileged
# Run all tests (requires sudo)
sudo mix test
License
MIT License - see LICENSE for details.