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