To prove that it is possible:
defmodule GenICMP do
@data <<0xdeadbeef::size(32)>>
def ping(addr) do
{:ok, s} = open()
data = @data
req_echo(s, addr, data: data)
case recv_echo(s) do
{:ok, %{data: ^data}} = resp -> resp
{:ok, other} -> {:error, other}
_ -> {:error, :invalid_resp}
end
end
def open, do: :socket.open(:inet, :dgram, :icmp)
def req_echo(socket, addr, opts \\ []) do
data = Keyword.get(opts, :data, @data)
id = Keyword.get(opts, :id, 0)
seq = Keyword.get(opts, :seq, 0)
sum = checksum(<<8, 0, 0::size(16), id, seq, data::binary>>)
msg = <<8, 0, sum::binary, id, seq, data::binary>>
:socket.sendto(socket, msg, %{family: :inet, port: 1, addr: addr})
end
def recv_echo(socket, timeout \\ 5000) do
{:ok, data} = :socket.recv(socket, 0, [], timeout)
<<_::size(160), pong::binary>> = data
case pong do
<<0, 0, _::size(16), id, seq, data::binary>> ->
{:ok, %{
id: id, seq: seq, data: data
}}
_ -> {:error, pong}
end
end
defp checksum(bin), do: checksum(bin, 0)
defp checksum(<<x::integer-size(16), rest::binary>>, sum), do: checksum(rest, sum + x)
defp checksum(<<x>>, sum), do: checksum(<<>>, sum + x)
defp checksum(<<>>, sum) do
<<x::size(16), y::size(16)>> = <<sum::size(32)>>
res = :erlang.bnot(x + y)
<<res::big-size(16)>>
end
end
API is simple:
GenICMP.ping({127, 0, 0, 1})
Tested on macOS, but should work on Linux as well. I am not sure about BSDs.
EDIT:
Just in case, this project seems as a good example of how to use Zigler to write Erlang NIFs, and it is great. However sending ICMP echo requests via socket
is absolutely possible.