hauleth

hauleth

Gen_icmp - PoC wrapper over `socket` to be able to send ICMP requests (ping)

Some people was already asking how to implement ping in their Erlang/Elixir applications. Previously it wasn’t really possible, because there was no access to the BSD sockets API without using external NIFs. With OTP 21 we got a built-in NIF with access for these, and with later updates we became allowed to use any type/protocol combination, not only these “allowed” by the Erlang team.

With all that work I wanted to present an example implementation of gen_tcp/gen_udp-like server that would provide higher-level API for one of the available protocols. That is how gen_icmp was born, which, obviously, implement Internet Control Message Protocol which is used by tools like ping.

It is currently not available on Hex, as I want to have tests first, however that may be a little bit complex, as it is not commonly accessible protocol (it requires raw sockets which are accessible only by root, with exception for Linux machines and Darwin, where it can be used on top of datagram sockets).

Usage is quite simple, to send simple ECHOREQ message (also known as PING) and receive response from 1.1.1.1 (CloudFlare DNS servers):

{:ok, socket} = :gen_icmp.open() # require macOS or Linux with user being in group that is within `net.ipv4.ping_group_range`

:ok = :gen_icmp.echoreq(socket, {1, 1, 1, 1}, <<1,2,3,4>>)

receive do
  {:icmp, socket, {1,1,1,1}, {echorep, %{data: <<1,2,3,4>>}}} ->
    IO.puts("Echo received")
end

For some reason the message need to be padded to 16 bits (it has to have even number of bytes in the message and I am trying to find out why and how to mitigate that).

Most Liked

hauleth

hauleth

You can achieve that right now. Just set seq and id in the request.

gen_icmp do not care about timeouts, because there is no need to. It is quite low level library, that do not manage whole ping on its own, and instead make it up to the user. There is no timeouts handled automatically by this library, it just sends and receives packets, without any additional magic.

As I said earlier, it is very “low level” library, so I do not think I should do any filtering in the library (unless there are some flags that can be set to the socket, but I am not aware of such). If you want to filter unwanted messages in your process, then just discard them in the controlling process.

If you have an example (can be in any language) how that does work, then please share. I can look into it, but no promises, as I can be limited to the capabilities of the socket module.

the_wildgoose

the_wildgoose

I REALLY like the direction you are going with this. Just a couple of days ago I was about to implement something similar for my own needs. However, you have knocked off about 80% of what I need! Thanks! (Thanks also for implementing my request to bind to a specific interface!)

The only thing I am not sure that I like is that lack of negative acknowledgements (ie ping NOT received in time), especially as it potentially affects the API. Now I think in the past your thought was to leave this up to an outer wrapper, but as I see that, this means creating another process to filter through, which seems inefficient compared with implementing inside the library?

My use case is:

  • I have a very slow (iridium satellite) connection to monitor.
  • I need to decide if this is up/down to certain destinations
  • However, because it’s so slow, queues can develop on the interfaces, 5+second ping times are not uncommon in light use, you could have huge peaks
  • So it would be desirable to have a) multiple pings in flight (ie set a sequence number) b) to set quite a long timeout as some of these may turn up really quite a long time later (I don’t mind if they take 60 seconds to come home)
  • It would also remove a bunch of complexity to have the library report that packets are overdue
  • I’m undecided if it would be useful to report “arrived late” events though? Probably someone would find it useful? Seems to drop out of the code for free on linux at least?
  • I would also prefer that in most modes I only hear about replies to my own pings, ie on OSX I don’t also get all the icmp data. However, clearly you also desire to implement an “all icmp” mode as well?

The way I would see this working is that echorequest() takes an optional timeout. If this is given then internally we maintain a queue of requests in flight and their expected latest arrival time. In handle(:continue) we would need to set a wakeup timer for the earliest expiry time. If we arrive in the timer event then not reply was received, so we can remove the item from the queue and send a message. Obviously if a reply arrives we need to dequeue the request and cancel/update the timer.

I guess this would also imply that opening the socket needs to take a param as to whether you want all events or only icmpecho (I’m mostly thinking because this affects how you open the socket on linux? We can use a different socket type if we want the kernel to do the reply processing for us?)

If I’m honest, I find it hard work to speak Erlang, which makes it somewhat tricky for me to contribute code here. If you liked the idea above, would you accept a donation for an implementation!? PM me.

the_wildgoose

the_wildgoose

Answering the easiest first:

I think (untested) filtering happens automatically on linux if we open the socket in dgram, icmp mode? I haven’t retested your library, but I thought that was how you were opening the socket on linux? So in that mode the socket won’t receive random other ICMP noticed on the network. I think this is helpful for many use cases

So regarding the low level library aspect. Yes, I agree. However, do you not agree that a frequent use case is also the absence of receiving a message within a time period? (Doesn’t mean it won’t arrive later of course)

Now, it’s not hard to wrap your library in another genserver, which simply wraps it, starts a timer and relays all the messages from your genserver through our genserver and onto the user. However, I think if you consider that it leads to a moderately amount of duplication just to pass through the same API and means another genserver which is simply relaying messages. Hence the request, would you consider adding within your genserver the additional parts to set timers and report an extra message in the absence of the icmp message arriving?

I could make a similar argument for the usefulness of filtering in the library. There could be a lot of icmp messages in some setups, so the further forward we could push the filtering the better. It’s even worth pondering how to end up at active:once in the library?

What do you think?

hauleth

hauleth

In that case it is supported already. gen_icmp:open/1 accepts raw option to use raw sockets, by default datagram sockets are used. So it is working right now.

In my opinion it would be infeasible to provide such functionality as gen_icmp:echoreq/3 is asynchronous. It would make sense to have timeout if that would be synchronous command (similarly to gen_server:call). So due to that I do not think that there is a reasonable way to implement that. Also as I said, it is meant to be very low-level interface, and adding such complexity there would be unneeded maintenance burden, especially that it isn’t that hard to implement on the user-side (it is mostly Process.send_after/3 with some map for storing references). It would also mean that I would need to modify the data sent in the ICMP request to contain reference ID and stuff like that. I do not want to complicate the implementation for something like that.

Yes, but making such option in the library could make the implementation quite significantly more complex, as if I would add the filtering on message type, then someone would like filtering on sender IP, message content, etc. As there is a lot of different messages, it would make the implementation more and more complex with each message type. And even if I would add new option to pass filtering function, then I do not think that would be much of the improvement about performance there.

It may me an option. As it started as PoC I created minimal implementation, but I could add active option as well as gen_icmp:recv/{1,2} functions, to manually handle incoming messages. That shouldn’t be too hard to implement.

Where Next?

Popular in Libraries Top

scohen
Lexical Lexical is a next-generation language server for the Elixir programming language. Features Context aware code completion As-you...
New
Qqwy
TypeCheck: Fast and flexible runtime type-checking for your Elixir projects. Core ideas Type- and function specifications are const...
336 13801 100
New
sasajuric
I’d like to announce a small library called boundaries. This is an experimental project which explores the idea of enforcing boundaries ...
New
OvermindDL1
Been making an MLElixir thing (not released yet…) for fun in spare time in the past day. I’m just trying to see how much I can get an ML...
132 13487 106
New
josevalim
EDIT: since Ecto 3.0 final version is out, this post was amended to use the final versions in the instructions below. Hi everyone, We a...
New
blatyo
https://www.conduitframework.com/ The best overview for how things are tied together is this presentation. Modules and functions are pre...
New
danschultzer
In short Plug n’ play OAuth 2.0 provider library. Just set up a resource owner schema with Ecto (your user schema), install the dependen...
New
wfgilman
I’ve cleaned up and open sourced three financial libraries I was using for my company. They are bindings for the APIs of these three comp...
New
aditya7iyengar
Rummage.Ecto and Rummage.Phoenix provide ways to perform Searching, Sorting and Pagination over Ecto queries and Phoenix collections. Fo...
New
mtrudel
Bandit is an HTTP server for Plug and WebSock apps. Bandit is written entirely in Elixir and is built atop Thousand Island. It can serve...
New

Other popular topics Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New

Sub Categories:

We're in Beta

About us Mission Statement