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.
https://github.com/hauleth/gen_icmp
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
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
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
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?
Popular in Announcing
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









