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

OptionDefaultDescription
:timeout5000Timeout in milliseconds
:count1Number of pings (for ping_stats/2)
:payload_size56ICMP payload size in bytes
:max_concurrency50Max 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:

  1. Run as root (development/testing)

    sudo mix run -e 'RawPing.ping("8.8.8.8") |> IO.inspect'
  2. Set CAP_NET_RAW capability (Linux)

    sudo setcap cap_net_raw+ep /path/to/beam.smp
  3. Container 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:

LinuxmacOS/BSD
IP header on receivestrippedincluded
ICMP identifierrewritten by kernelpreserved
TTL availableno (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:

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

# 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.