# Zing, basic Elixir ICMP ('ping') server using Zig NIF

**URL:** <https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681>\
**Category:** Discussions\
**Tags:** nifs, zig\
**Created:** [May 21, 2020, 4:52am UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681 "2020-05-21T04:52:38Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![ityonemo](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ityonemo/32/11341_2.png) [@ityonemo](https://forum.elixirforum.com/u/ityonemo)\
**Post date:** [May 21, 2020, 4:52am UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/1 "2020-05-21T04:52:38Z")

</div>

This is a basic demonstration of how to write an ICMP (‘ping’) server using a NIF in about 100 lines of elixir and 100 lines of Zig.

This might be able to be deployed as a ‘real’ library. Would anyone be interested in using this? I am very happy to put more work into making this a quality release (modulo zig itself being still experimental) if people are interested, as there really isn’t a satisfactory ICMP solution in the BEAM (and the :socket library maintainers have suggested that ICMP is not going to happen)

> **[GitHub - ityonemo/zing: ICMP ping server for elixir](https://github.com/ityonemo/zing/)**
>
> ICMP ping server for elixir

---

<div class="post-metadata">

**Author:** ![hauleth](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/hauleth/32/18942_2.png) [@hauleth](https://forum.elixirforum.com/u/hauleth)\
**Post date:** [May 21, 2020, 10:42am UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/2 "2020-05-21T10:42:59Z")

</div>

Actually since [OTP 22.3](https://github.com/erlang/otp/pull/2496/commits/f420b197076284cab02ea4757bc9a68888e63a6b) you can use `socket` module to have ICMP socket. If you want to use `raw` socket however you will need to be superuser, but Darwin and Linux supports ICMP over `dgram` sockets.

---

<div class="post-metadata">

**Author:** ![hauleth](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/hauleth/32/18942_2.png) [@hauleth](https://forum.elixirforum.com/u/hauleth)\
**Post date:** [May 21, 2020, 11:49am UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/3 "2020-05-21T11:49:07Z")

</div>

To prove that it is possible:

```elixir
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:

```elixir
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.

---

<div class="post-metadata">

**Author:** ![ityonemo](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ityonemo/32/11341_2.png) [@ityonemo](https://forum.elixirforum.com/u/ityonemo)\
**Post date:** [May 21, 2020, 4:13pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/4 "2020-05-21T16:13:07Z")

</div>

Lol, I tried this two months ago and support for the :icmp mode wasn’t there!! I think I was on 22.2, and I asked Peter dimitrov at codebeam and he said they weren’t going to do icmp 😃

---

<div class="post-metadata">

**Author:** ![hauleth](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/hauleth/32/18942_2.png) [@hauleth](https://forum.elixirforum.com/u/hauleth)\
**Post date:** [May 21, 2020, 4:38pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/5 "2020-05-21T16:38:32Z")

</div>

I was trying to implement `gen_icmp` earlier via `socket` and encountered that problem. So instead I created PR to the core that fixed checks for `{type, proto}` pairs and now it is possible to create such socket. I think that he meant that they will not implement ICMP helper function directly in OTP, but manual implementation via `socket` should be possible (on supporting OSes, as traditionally ICMP sockets needed to be created as raw sockets which can be created only by superuser).

---

<div class="post-metadata">

**Author:** ![ityonemo](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ityonemo/32/11341_2.png) [@ityonemo](https://forum.elixirforum.com/u/ityonemo)\
**Post date:** [May 21, 2020, 4:46pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/6 "2020-05-21T16:46:29Z")

</div>

Yeah. We must have been in the same place because I remember looking through the :socket nif code and scratching my head about some dead ends. However I’m not brave enough to figure out how to do full otp/erlang build to make PRs to core. Maybe I should work on that.

I think he must have misinterpreted my question. It was over the vtc after all.

Do you think your code above should be a library?

---

<div class="post-metadata">

**Author:** ![hauleth](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/hauleth/32/18942_2.png) [@hauleth](https://forum.elixirforum.com/u/hauleth)\
**Post date:** [May 21, 2020, 5:36pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/7 "2020-05-21T17:36:05Z")

</div>

I can clean it up and make it into library later.

---

<div class="post-metadata">

**Author:** ![ityonemo](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ityonemo/32/11341_2.png) [@ityonemo](https://forum.elixirforum.com/u/ityonemo)\
**Post date:** [May 21, 2020, 5:48pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/8 "2020-05-21T17:48:44Z")

</div>

Happy to help if you’d like some!

---

<div class="post-metadata">

**Author:** ![hauleth](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/hauleth/32/18942_2.png) [@hauleth](https://forum.elixirforum.com/u/hauleth)\
**Post date:** [May 26, 2020, 1:00pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/9 "2020-05-26T13:00:50Z")

</div>

[I have implemented ICMP](https://github.com/hauleth/gen_icmp), but ICMPv6 is still not done, if you want then I would be glad for PR. It uses slightly different API from what You are using as I wanted something “more basic” so users could build larger functionalities on top of this.

---

<div class="post-metadata">

**Author:** ![lkuty](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lkuty/32/34879_2.png) [@lkuty](https://forum.elixirforum.com/u/lkuty)\
**Post date:** [June 19, 2021, 5:24am UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/10 "2021-06-19T05:24:06Z")

</div>

From different tests I made on macOS and Linux, it looks like macOS needs us to compute the checksum for the echo request and then give us back the full IP packet of the echo reply, hence your `_::size(160)` to skip 20 bytes of IP header. On Linux we do not need to compute the checksum or set an id because the OS will dot it for us and we also only get the ICMP echo reply, not the full IP packet.

This is confirmed by the [`ping` source code](https://opensource.apple.com/source/network_cmds/network_cmds-433/ping.tproj/ping.c.auto.html) on MacOS. I guess the behaviour of the library regarding a socket created with `socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)` is quite OS dependent.

---

<div class="post-metadata">

**Author:** ![ityonemo](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ityonemo/32/11341_2.png) [@ityonemo](https://forum.elixirforum.com/u/ityonemo)\
**Post date:** [June 19, 2021, 4:45pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/11 "2021-06-19T16:45:43Z")

</div>

You might want to try this: [API Reference — icmp v0.1.0](https://hexdocs.pm/icmp/api-reference.html)

---

<div class="post-metadata">

**Author:** ![hauleth](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/hauleth/32/18942_2.png) [@hauleth](https://forum.elixirforum.com/u/hauleth)\
**Post date:** [June 19, 2021, 4:53pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/12 "2021-06-19T16:53:14Z")

</div>

I have tested my library on macOS and Linux and both works. If you look into `inet_icmp.erl` then you will see that there is special case for handling macOS return value.

> [@lkuty](#):
>
> I guess the behaviour of the library regarding a socket created with `socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)` is quite OS dependent.

Yes, it is. Not all OSes even allow such socket. That is why my library provides a way to use either datagram or raw sockets (datagram sockets are default).

---

<div class="post-metadata">

**Author:** ![lkuty](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/lkuty/32/34879_2.png) [@lkuty](https://forum.elixirforum.com/u/lkuty)\
**Post date:** [June 20, 2021, 5:15am UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/13 "2021-06-20T05:15:44Z")

</div>

Yes. I was just mentioning the different behaviors without referring explicitly to your code which I read btw, even if I overlooked `decode(<<4:4, IHL:4, Rest/binary>>)` because I was mostly interested in `gen_icmp_server.erl` and its nice use of `handle_continue`.

---

<div class="post-metadata">

**Author:** ![the\_wildgoose](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/t/3ab097/32.png) [@the\_wildgoose](https://forum.elixirforum.com/u/the_wildgoose)\
**Post date:** [June 20, 2021, 7:48pm UTC](https://forum.elixirforum.com/t/zing-basic-elixir-icmp-ping-server-using-zig-nif/31681/14 "2021-06-20T19:48:00Z")

</div>

Hi, could I persuade you to explain the code a little more here please?

I see the line referenced  
`decode(<<4:4, IHL:4, Rest/binary>>)`  
But I’m really not clear why that can’t match an unfortunate (or carefully constructed) ping response

Phrasing the question another way, is there a tight binary match for an IPv4 header that couldn’t match the ICMP body response (on Linux)?

I wonder why you don’t check the OS during init and then match on that? (Stash it in the state perhaps?)

Agreed that the continue handling is very neat!
