# `RawPing`
[🔗](https://github.com/awksedgreep/raw_ping/blob/v0.3.1/lib/raw_ping.ex#L1)

Pure Erlang/OTP ICMP ping library using the modern `:socket` API.

No NIFs, no external dependencies. Requires Elixir 1.17+ (OTP 25+).

## Usage

    # Single ping
    {:ok, rtt_ms} = RawPing.ping("8.8.8.8")
    {:ok, rtt_ms} = RawPing.ping({8, 8, 8, 8})

    # With options
    {:ok, rtt_ms} = RawPing.ping("8.8.8.8", timeout: 2000)

    # Multiple pings with stats
    {:ok, stats} = RawPing.ping_stats("8.8.8.8", count: 5)
    # => {:ok, %{min: 10.5, max: 15.2, avg: 12.3, success_rate: 1.0, ...}}

    # Batch ping multiple hosts
    results = RawPing.ping_batch(["8.8.8.8", "1.1.1.1", "192.168.1.1"])
    # => %{"8.8.8.8" => {:ok, 12.5}, "1.1.1.1" => {:ok, 8.2}, ...}

## Privileges

Most hosts need none. By default an unprivileged ICMP datagram socket is used,
falling back to a raw socket only where that is unavailable:

    RawPing.socket_mode()
    #=> {:ok, :dgram}   # no privileges required
    #=> {:ok, :raw}     # fell back; needed root or CAP_NET_RAW

On Linux the datagram path is gated by `net.ipv4.ping_group_range`, which must
include the process's GID; many distributions ship it wide open. Where it is
restricted, the raw path still needs root, `CAP_NET_RAW` on the BEAM
(`setcap cap_net_raw+ep /path/to/beam.smp`), or a container granted `NET_RAW`.

See `RawPing.Socket` for the trade-offs and the platform differences.

## How It Works

Uses Erlang's `:socket` module to open an ICMP socket, builds ICMP echo
request packets manually, sends them, and parses the echo replies to calculate
round-trip time.

# `ip_address`

```elixir
@type ip_address() :: String.t() | :inet.ip_address() | [integer()]
```

# `ping_result`

```elixir
@type ping_result() :: {:ok, float()} | {:error, term()}
```

# `ping_stats`

```elixir
@type ping_stats() :: %{
  min: float() | nil,
  max: float() | nil,
  avg: float() | nil,
  success_rate: float(),
  success_count: non_neg_integer(),
  failure_count: non_neg_integer(),
  rtts: [float()]
}
```

# `ping`

```elixir
@spec ping(
  ip_address(),
  keyword()
) :: ping_result()
```

Ping a host and return the round-trip time in milliseconds.

## Options

  * `:timeout` - Timeout in milliseconds (default: 5000)
  * `:payload_size` - Size of ICMP payload in bytes (default: 56)
  * `:mode` - Socket mode, `:auto` (default), `:dgram`, or `:raw`. See
    `RawPing.Socket` for what each requires.

## Examples

    {:ok, rtt} = RawPing.ping("8.8.8.8")
    {:ok, rtt} = RawPing.ping({8, 8, 8, 8}, timeout: 1000)
    {:error, :timeout} = RawPing.ping("10.255.255.1", timeout: 100)

# `ping_batch`

```elixir
@spec ping_batch(
  [ip_address()],
  keyword()
) :: %{required(String.t()) =&gt; ping_result()}
```

Ping multiple hosts concurrently.

Returns a map of host => result.

## Options

  * `:timeout` - Timeout per ping in milliseconds (default: 5000)
  * `:max_concurrency` - Maximum concurrent pings (default: 50)

## Examples

    results = RawPing.ping_batch(["8.8.8.8", "1.1.1.1"])
    # %{"8.8.8.8" => {:ok, 12.5}, "1.1.1.1" => {:ok, 8.2}}

# `ping_stats`

```elixir
@spec ping_stats(
  ip_address(),
  keyword()
) :: {:ok, ping_stats()} | {:error, term()}
```

Ping a host multiple times and return statistics.

## Options

  * `:count` - Number of pings to send (default: 1)
  * `:timeout` - Timeout per ping in milliseconds (default: 5000)
  * `:payload_size` - Size of ICMP payload in bytes (default: 56)
  * `:mode` - Socket mode, `:auto` (default), `:dgram`, or `:raw`. See
    `RawPing.Socket` for what each requires.

## Examples

    {:ok, stats} = RawPing.ping_stats("8.8.8.8", count: 5)
    # %{min: 10.2, max: 15.8, avg: 12.5, success_rate: 1.0, ...}

# `socket_mode`

```elixir
@spec socket_mode(keyword()) :: {:ok, RawPing.Socket.mode()} | {:error, term()}
```

Report which socket mode is available to this process.

Opens a socket using the same negotiation as `ping/2` and closes it
immediately. `:dgram` means ICMP works with no elevated privileges; `:raw`
means it fell back to a raw socket, which required root or `CAP_NET_RAW`.

Useful for confirming a deployment is running unprivileged.

    {:ok, :dgram} = RawPing.socket_mode()

---

*Consult [api-reference.md](api-reference.md) for complete listing*
