Skip to content

ICMP Ping

AmirVoid12 edited this page Sep 18, 2026 · 1 revision

ICMP Ping

Platform support: Linux only for now. Windows and macOS are planned, see Roadmap.

The ping protocol sends ICMP echo requests using a native C addon (N-API). It does not spawn the ping command and it does not use a JavaScript raw-socket library.

Usage

import { Pingflux } from "pingflux";

const pf = new Pingflux();

pf.watch({ protocol: "ping", url: "1.1.1.1" });
pf.watch({ protocol: "ping", url: "2606:4700:4700::1111", interval: 3000 });

pf.on("up", (e) => console.log(`${e.target} ${e.latency}ms`));
pf.on("probe_error", (e) => console.error(`${e.target}: ${e.error}`));

Accepted targets

Format Example
IPv4 1.1.1.1
IPv4 with port (port is ignored) 1.1.1.1:80
IPv6 ::1
IPv6 in brackets [::1]
IPv6 in brackets with port (ignored) [::1]:80

Hostnames are not resolved. Pass a plain IP address. If you only have a hostname, resolve it first:

import { promises as dns } from "dns";

const { address } = await dns.lookup("example.com");
pf.watch({ protocol: "ping", url: address });

How it works

  1. pingProbe() extracts the IP from the target and detects IPv4 or IPv6.
  2. It calls the native pingIcmp() function. That function runs on the libuv threadpool through napi_async_work, so the event loop is never blocked.
  3. The addon opens an ICMP socket, sends one echo request, and waits for a reply that matches the packet ID and sequence number.
  4. The result is returned as a Promise and becomes an up, down, slow or probe_error event.
Pingflux.watch()
   └─ Monitor
       └─ pingProbe(url)              src/probes/ping.ts
           └─ rawPing(host, id, seq)  src/utils/probes/ping.ts
               └─ pingIcmp()          native/src/addon.c   (libuv threadpool)
                   ├─ icmp_socket_open / send / recv    socket.c
                   └─ icmp_build_packet / parse_reply   icmp.c

Permissions

The addon tries two socket types in this order:

Order Socket type Needs root?
1 SOCK_DGRAM + IPPROTO_ICMP No, when the kernel allows it
2 SOCK_RAW + IPPROTO_ICMP Yes, or the CAP_NET_RAW capability

In most cases it works as a normal user. The kernel setting behind option 1 is:

cat /proc/sys/net/ipv4/ping_group_range

If your group ID falls inside that range, unprivileged ping works. To allow all groups:

sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"

To keep it after a reboot:

echo "net.ipv4.ping_group_range = 0 2147483647" | sudo tee /etc/sysctl.d/99-ping.conf
sudo sysctl --system

Other options:

# run as root
sudo node app.js

# or give the capability to the node binary
sudo setcap cap_net_raw+ep $(which node)

Errors

Error Meaning
Cannot open ICMP socket — permission denied... Socket creation failed with EPERM or EACCES. See Permissions above.
ERR_TIMEOUT No matching reply within the timeout (default 2000 ms).
ERR_SEND_FAILED:<errno> The packet could not be sent, for example no route or network down.
Invalid IP — hostname resolution not supported The target is not an IP address.
ICMP (ping) is currently supported on Linux only You are on Windows or macOS.
Failed to load native ICMP addon: ... The compiled addon was not found. See Native-Addon.

Destination Unreachable and Time Exceeded replies from routers are also reported as errors, with the reason mapped by icmp_error_reason().

Native result fields

The native layer returns more information than the public event exposes:

Field Description
ok true when a matching echo reply arrived
latency Round-trip time in ms, null on failure
ttl TTL or hop limit of the reply
hops Estimated hop count, based on the nearest initial TTL of 64, 128 or 255
icmpType, icmpCode Raw ICMP type and code of the reply
error, errorReason Error code, null on success
stats Only when count > 1: sent, received, lossPercent, min, max, avg, jitter

ttl and hops depend on the socket mode. In SOCK_DGRAM mode for IPv4 the kernel removes the IP header before the data reaches the addon, so ttl is null. IPv6 gets the hop limit through ancillary data in both modes.

Reply matching

An echo reply is accepted only when all three match:

  • ICMP type is echo reply (0 for IPv4, 129 for IPv6)
  • identifier equals the one that was sent
  • sequence number equals the one that was sent

In SOCK_DGRAM mode the kernel assigns the identifier itself (it uses the local port). The addon reads it back with getsockname() and matches against that value.

Clone this wiki locally