%%% @copyright 2010-2023 Michael Santos %%% All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% 1. Redistributions of source code must retain the above copyright notice, %%% this list of conditions and the following disclaimer. %%% %%% 2. Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in the %%% documentation and/or other materials provided with the distribution. %%% %%% 3. Neither the name of the copyright holder nor the names of its %%% contributors may be used to endorse or promote products derived from %%% this software without specific prior written permission. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS %%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT %%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR %%% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT %%% HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, %%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED %%% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR %%% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF %%% LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING %%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS %%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. %% @doc gen_icmp is an interface for using ICMP and ICMPv6 sockets %% %% gen_icmp uses raw sockets and abuses gen_udp for the socket %% handling. gen_icmp should work on Linux and BSDs. %% %% == Examples == %% %% ``` %% 1> gen_icmp:ping("hex.pm"). %% [{ok,"hex.pm", %% {35,241,14,39}, %% {35,241,14,39}, %% {61261,0,116,215}, %% <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}] %% ''' -module(gen_icmp). -behaviour(gen_server). -include_lib("kernel/include/inet.hrl"). -include_lib("pkt/include/pkt.hrl"). -define(PING_TIMEOUT, 5000). -export([ open/0, open/1, open/2, close/1, send/3, controlling_process/2, setopts/2, family/1, getfd/1, set_ttl/3, get_ttl/2, filter/1, filter/2, icmp6_filter_setpassall/0, icmp6_filter_setblockall/0, icmp6_filter_setpass/2, icmp6_filter_setblock/2, icmp6_filter_willpass/2, icmp6_filter_willblock/2 ]). -export([recv/2, recv/3]). -export([ping/1, ping/2, ping/3]). -export([ echo/3, echo/4, packet/2, packet/3, parse/1, parse/2, gettime/0, timediff/1, timediff/2 ]). -export([addr_list/3]). -export([start_link/2, start/2]). %% gen_server callbacks -export([ init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3 ]). -type uint8_t() :: 0..16#ff. -type uint16_t() :: 0..16#ffff. -type uint32_t() :: 0..16#ffffffff. -type int32_t() :: -16#7fffffff..16#7fffffff. -type fd() :: int32_t(). -type socket() :: pid(). -type icmp6_filter() :: <<_:256>>. % ping details -type id() :: uint16_t(). -type sequence() :: uint16_t(). -type ttlx() :: uint8_t(). % ICMP packet time to live (renamed to avoid conflict with ttl() defined in the pkt header file) -type elapsed() :: int32_t() | undefined. -type packet_option() :: {type, atom | uint8_t()} | {code, atom | uint8_t()} | {id, id()} | {sequence, sequence()} | {gateway, pkt:in_addr()} | {un, binary()} | {mtu, uint16_t()} | {pointer, uint8_t() | uint32_t()} | {ts_orig, uint32_t()} | {ts_recv, uint32_t()} | {ts_tx, uint32_t()} | {maxdelay, uint16_t()} | {saddr, pkt:in6_addr()} | {daddr, pkt:in6_addr()} | {len, non_neg_integer()} | {next, uint8_t()}. -export_type([ uint8_t/0, uint16_t/0, uint32_t/0, int32_t/0, fd/0, socket/0, icmp6_filter/0, id/0, sequence/0, ttlx/0, elapsed/0, packet_option/0 ]). -record(state, { % Protocol family (inet, inet6) family = inet :: inet | inet6, % caller PID pid :: pid(), % socket file descriptor fd :: fd(), % udp socket s :: gen_udp:socket() }). -record(ping_opt, { s, id, sequence, timeout, tref, timestamp = true }). -record(icmp6_pseudohdr, { saddr = {0, 0, 0, 0, 0, 0, 0, 0}, daddr = {0, 0, 0, 0, 0, 0, 0, 0}, len = 0, next = ?IPPROTO_ICMPV6, h = #icmp6{} }). %% @doc Open an ICMP socket. %% %% By default, the ICMP socket is opened in {active,false} mode. No %% packets will be received by the socket. setopts/2 can be used %% to place the socket in {active,true} mode. %% %% gen_icmp first attempts to natively open the socket and falls %% back to forking the setuid helper program if beam does not have %% the appropriate privileges. Privileges to open a raw socket can %% be given by, for example, running as root or, on Linux, granting %% the CAP_NET_RAW capability to beam: %% %% setcap cap_net_raw=ep /usr/local/lib/erlang/erts-5.8.3/bin/beam.smp %% %% Only the owning process will receive ICMP packets (see %% controlling_process/2 to change the owner). The process owning the %% raw socket will receive all ICMP packets sent to the host. %% %% Messages sent to the controlling process are: %% %% {icmp, Socket, Address, TTL, Packet} %% %% Where: %% %% * Socket is the pid of the gen_icmp process %% %% * Address is a tuple representing the IPv4 or IPv6 source address %% %% * TTL: IPv4: TTL taken from the IP header %% %% * TTL: IPv6: the socket's hop limit returned from %% getsockopt(IPV6_UNICAST_HOPS) (this is not the packet's %% TTL, it is the socket's max TTL) %% %% * Packet is the complete ICMP packet including the ICMP headers %% %% == Examples == %% %% ``` %% 1> gen_icmp:open(). %% {ok,<0.299.0>} %% ''' -spec open() -> {ok, socket()} | {error, system_limit | inet:posix()}. open() -> open([], []). %% @doc Open an ICMP socket with raw socket options. %% %% See the https://github.com/msantos/procket for the raw socket options %% and for instructions on setting up the setuid helper. %% %% == Examples == %% %% ``` %% 1> gen_icmp:open([{ttl, 1}, inet6]). %% {ok,<0.302.0>} %% ''' -spec open(proplists:proplist()) -> {ok, socket()} | {error, system_liimt | inet:posix()}. open(RawOpts) -> open(RawOpts, []). %% @doc Open an ICMP socket with options. %% %% == Examples == %% %% ``` %% 1> gen_icmp:open([{ttl, 1}, inet6], [list]). %% {ok,<0.302.0>} %% ''' -spec open(proplists:proplist(), [inet:inet_backend() | gen_udp:open_option()]) -> {ok, socket()} | {error, system_liimt | inet:posix()}. open(RawOpts, SockOpts) -> start_link(RawOpts, SockOpts). %% @doc Close the ICMP socket. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open(). %% {ok,<0.224.0>} %% 2> gen_icmp:close(S). %% ok %% ''' -spec close(socket()) -> ok. close(Socket) when is_pid(Socket) -> gen_server:call(Socket, close, infinity). %% @doc Send data via an ICMP socket. %% %% Like the gen_udp and gen_tcp modules, any process can send ICMP %% packets but only the owner will receive the responses. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open(). %% {ok,<0.224.0>} %% 2>gen_icmp:send(S, "google.com", gen_icmp:echo(inet, 0, 0)). %% ok %% 3> gen_icmp:recv(S, 1). %% {ok,{{142,251,32,78}, %% <<69,0,0,84,0,0,0,0,116,1,214,38,142,251,32,78,100,115, %% 92,198,0,0,19,125,0,...>>}} %% 4> gen_icmp:close(S). %% ok %% ''' -spec send( socket(), {inet:ip_address(), inet:port_number()} | inet:family_address() | socket:sockaddr_in() | socket:sockaddr_in6(), iodata() ) -> ok | {error, not_owner | inet:posix()}. send(Socket, Address, Packet) when is_pid(Socket) -> gen_server:call(Socket, {send, Address, Packet}, infinity). %% @doc Read data from an ICMP socket. %% %% This function receives a packet from a socket in passive mode. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open(). %% {ok,<0.224.0>} %% 2>gen_icmp:send(S, "google.com", gen_icmp:echo(inet, 0, 0)). %% ok %% 3> gen_icmp:recv(S, 1). %% {ok,{{142,251,32,78}, %% <<69,0,0,84,0,0,0,0,116,1,214,38,142,251,32,78,100,115, %% 92,198,0,0,19,125,0,...>>}} %% 4> gen_icmp:close(S). %% ok %% ''' -spec recv(socket(), non_neg_integer()) -> {ok, inet:ip_address() | inet:returned_non_ip_address(), string() | binary()} | {error, not_owner | timeout | inet:posix()}. recv(Socket, Length) -> recv(Socket, Length, infinity). %% @doc Read data from an ICMP socket with timeout. %% %% The optional Timeout parameter specifies a timeout in %% milliseconds. The default value is infinity. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open(). %% {ok,<0.299.0>} %% 2> gen_icmp:send(S, "google.com", gen_icmp:echo(inet, 0, 0)). %% ok %% 3> gen_icmp:recv(S, 1, 5000). %% {ok,{{142,251,41,78}, %% <<69,0,0,84,0,0,0,0,116,1,205,38,142,251,41,78,100,115, %% 92,198,0,0,100,77,0,...>>}} %% 4> gen_icmp:recv(S, 1, 5000). %% {error,timeout} %% 5> gen_icmp:close(S). %% ok %% ''' -spec recv(socket(), non_neg_integer(), timeout()) -> {ok, inet:ip_address() | inet:returned_non_ip_address(), string() | binary()} | {error, not_owner | timeout | inet:posix()}. recv(Socket, Length, Timeout) -> gen_server:call(Socket, {recv, Length, Timeout}, infinity). %% @doc Change the controlling process of the ICMP socket. %% %% Change the process owning the socket. Allows another process to %% receive the ICMP responses. -spec controlling_process(socket(), pid()) -> ok. controlling_process(Socket, Pid) when is_pid(Socket), is_pid(Pid) -> gen_server:call(Socket, {controlling_process, Pid}, infinity). %% @doc Set socket options. %% %% For options, see the inet man page. Simply calls inet:setopts/2 on %% the gen_udp socket. %% %% setopts/2 can be used to toggle the socket between passive and %% active mode. %% %% == Examples == %% %% ``` %% {ok, Socket} = gen_icmp:open(), % socket is {active,false} %% ok = gen_icmp:setopts(Socket, [{active, true}]), %% % do stuff with the socket %% ok = gen_icmp:setopts(Socket, [{active, false}]). %% ''' -spec setopts(socket(), [inet:socket_setopt()]) -> ok | {error, inet:posix()}. setopts(Socket, Options) when is_pid(Socket), is_list(Options) -> gen_server:call(Socket, {setopts, Options}, infinity). %% @doc Get socket family. %% %% Returns the socket family: `inet' (IPv4), `inet6' (IPv6) %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open([inet6]). %% {ok,<0.224.0>} %% 2> gen_icmp:family(S). %% inet6 %% ''' -spec family(socket()) -> inet | inet6. family(Socket) when is_pid(Socket) -> gen_server:call(Socket, family, infinity). %% @doc Get underlying file descriptor for socket. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open(). %% {ok,<0.221.0>} %% 2> gen_icmp:getfd(S). %% 20 %% ''' -spec getfd(socket()) -> fd(). getfd(Socket) when is_pid(Socket) -> gen_server:call(Socket, getfd, infinity). %% @doc Get ICMPv6 socket filter. %% %% Retrieves the ICMPv6 filter for a socket. For ICMPv4 %% sockets, the atom `unsupported' is returned. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open([inet6]). %% {ok,<0.299.0>} %% 2> gen_icmp:filter(S). %% {ok,<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, %% 0,0,...>>} %% ''' -spec filter(socket()) -> {ok, icmp6_filter()} | {error, unsupported | inet:posix()}. filter(Socket) when is_pid(Socket) -> gen_server:call(Socket, filter, infinity). %% @doc Set ICMPv6 socket filter. %% %% Sets an ICMPv6 filter on a socket. For ICMPv4 sockets, the atom %% `unsupported' is returned. %% %% Filters can be generated by using the icmp6_filter functions. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open([inet6]). %% {ok,<0.299.0>} %% 2> gen_icmp:filter(S, gen_icmp:icmp6_filter_setpassall()). %% ok %% ''' -spec filter(socket(), icmp6_filter()) -> ok | {error, unsupported | inet:posix()}. filter(Socket, Filter) when is_pid(Socket) -> gen_server:call(Socket, {filter, Filter}, infinity). %% @doc Send an ICMP ECHO_REQUEST. %% %% ping/1 is a convenience function to send a single ping %% packet. The argument to ping/1 can be either a hostname or a %% list of hostnames. %% %% == Examples == %% %% ``` %% 1> gen_icmp:ping("google.com"). %% [{ok,"google.com", %% {142,251,41,46}, %% {142,251,41,46}, %% {61261,0,116,84}, %% <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}] %% ''' %% %% @see ping/3 -spec ping(inet:socket_address() | inet:hostname() | [inet:socket_address() | inet:hostname()]) -> [ {ok, inet:socket_address() | inet:hostname(), inet:ip_address(), inet:ip_address(), {id(), sequence(), ttlx(), elapsed()}, binary()} | {error, unreach_host | timxceed_intrans, [inet:socket_address() | inet:hostname()], inet:ip_address(), inet:ip_address(), {id(), sequence(), ttlx(), elapsed()}, binary()} | {error, timeout | inet:posix(), [inet:socket_address() | inet:hostname()], inet:ip_address()} ]. ping(Host) -> ping(Host, []). %% @doc Send an ICMP ECHO_REQUEST with options. %% %% Ping a host or a list of hosts. %% %% == Examples == %% %% ``` %% 1> gen_icmp:ping("google.com", [inet6]). %% [{ok,"google.com", %% {9735,63664,16395,2052,0,0,0,8206}, %% {9735,63664,16395,2052,0,0,0,8206}, %% {61261,0,64,53}, %% <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}] %% ''' %% %% @see ping/3 -spec ping( inet:socket_address() | inet:hostname() | [inet:socket_address() | inet:hostname()], proplists:proplist() ) -> [ {ok, inet:socket_address() | inet:hostname(), inet:ip_address(), inet:ip_address(), {id(), sequence(), ttlx(), elapsed()}, binary()} | {error, unreach_host | timxceed_intrans, [inet:socket_address() | inet:hostname()], inet:ip_address(), inet:ip_address(), {id(), sequence(), ttlx(), elapsed()}, binary()} | {error, timeout | inet:posix(), [inet:socket_address() | inet:hostname()], inet:ip_address()} ]. ping(Host, Options) when is_tuple(Host) -> ping([Host], Options); ping([Char | _] = Host, Options) when is_integer(Char) -> ping([Host], Options); ping(Hosts, Options) -> {ok, Socket} = gen_icmp:open(Options), Res = ping(Socket, Hosts, Options), gen_icmp:close(Socket), Res. %% @doc Send an ICMP ECHO_REQUEST. %% %% To prevent the process mailbox from being flooded with ICMP %% messages, ping/3 will put the socket into `{active,false}' mode %% after completing. %% %% The ping/3 function blocks until either an ICMP ECHO REPLY is %% received from all hosts or Timeout is reached. %% %% Id and sequence are used to differentiate ping responses. Usually, %% the sequence is incremented for each ping in one run. %% %% A list of responses is returned. If the ping was successful, %% the elapsed time in milliseconds is included (calculated by %% subtracting the current time from the time we sent in the ICMP %% ECHO packet and returned to us in the ICMP ECHOREPLY payload) %% where: %% %% * Host: the provided hostname %% %% * Address: the resolved IPv4 or IPv6 network address represented %% as a 4 or 8-tuple used in the ICMP echo request %% %% * ReplyAddr: the IPv4 or IPv6 network address originating the %% ICMP echo reply %% %% The timeout is set for all ICMP packets and is set after all %% packets have been sent out. %% %% By default only one address per hostname is pinged. To %% enable pinging all addresses per hostname pass `{multi, true}' %% to options. %% %% A ping payload contains an 8 byte timestamp in microseconds. When %% creating a custom payload, the first 8 bytes of the ICMP echo %% reply payload will be used for calculating the elapsed time. To %% disable this behaviour, use the option {timestamp,false} (the %% elapsed time in the return value will be set to 0). %% %% The timeout defaults to 5 seconds. %% %% ICMPv6 sockets can restrict which ICMPv6 types are received by the %% socket using the filter option. The filter argument is a binary %% generated using the icmp6_filter functions described below. %% %% The default filter allows: ICMP6_ECHO_REPLY, ICMP6_DST_UNREACH, %% ICMP6_PACKET_TOO_BIG, ICMP6_TIME_EXCEEDED and ICMP6_PARAM_PROB. %% Note: ping/3 does not restore the original filter on the socket. %% %% == Examples == %% %% ``` %% 1> {ok, S} = gen_icmp:open([inet6]). %% {ok,<0.299.0>} %% 2> gen_icmp:ping(S, ["google.com"], []). %% [{ok,"google.com", %% {9735,63664,16395,2051,0,0,0,8206}, %% {9735,63664,16395,2051,0,0,0,8206}, %% {61261,0,64,110}, %% <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}] %% 3> gen_icmp:ping(S, ["2001:4860:4860::8888"], []). %% [{ok,"2001:4860:4860::8888", %% {8193,18528,18528,0,0,0,0,34952}, %% {8193,18528,18528,0,0,0,0,34952}, %% {61261,0,64,20}, %% <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}] %% 4> gen_icmp:ping(S, ["2001:4860:4860::8888", "google.com"], []). %% [{ok,"google.com", %% {9735,63664,16395,2051,0,0,0,8206}, %% {9735,63664,16395,2051,0,0,0,8206}, %% {61261,1,64,23}, %% <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}, %% {ok,"2001:4860:4860::8888", %% {8193,18528,18528,0,0,0,0,34952}, %% {8193,18528,18528,0,0,0,0,34952}, %% {61261,0,64,23}, %% <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}] %% 5> gen_icmp:close(S). %% ok %% ''' -spec ping(socket(), [inet:socket_address() | inet:hostname()], proplists:proplist()) -> [ {ok, inet:socket_address() | inet:hostname(), inet:ip_address(), inet:ip_address(), {id(), sequence(), ttlx(), elapsed()}, binary()} | {error, unreach_host | timxceed_intrans, [inet:socket_address() | inet:hostname()], inet:ip_address(), inet:ip_address(), {id(), sequence(), ttlx(), elapsed()}, binary()} | {error, timeout | inet:posix(), [inet:socket_address() | inet:hostname()], inet:ip_address()} ]. ping(Socket, Hosts, Options) when is_pid(Socket), is_list(Hosts), is_list(Options) -> ok = setopts(Socket, [{active, true}]), Family = family(Socket), Id = proplists:get_value(id, Options, erlang:phash2(self(), 16#FFFF)), Seq = proplists:get_value(sequence, Options, 0), Data = proplists:get_value(data, Options, payload(echo)), Timeout = proplists:get_value(timeout, Options, ?PING_TIMEOUT), Timestamp = proplists:get_value(timestamp, Options, true), Multi = proplists:get_value(multi, Options, false), ICMP6_filter = lists:foldl( fun(T, X) -> gen_icmp:icmp6_filter_setpass(T, X) end, gen_icmp:icmp6_filter_setblockall(), [ echo_reply, dst_unreach, packet_too_big, time_exceeded, param_prob ] ), Filter = proplists:get_value(filter, Options, ICMP6_filter), ok = case Family of inet6 -> filter(Socket, Filter); _ -> ok end, Hosts2 = addr_list(Family, Hosts, Multi), {Addresses, Errors, _} = lists:foldl( fun ({ok, Host, Addr}, {NHosts, Nerr, NSeq}) -> {[{ok, Host, Addr, NSeq} | NHosts], Nerr, NSeq + 1}; (Err, {NHosts, Nerr, NSeq}) -> {NHosts, [Err | Nerr], NSeq} end, {[], [], Seq}, Hosts2 ), Result = case Addresses of [] -> Errors; _ -> [ spawn(fun() -> gen_icmp:send(Socket, Addr, gen_icmp:echo(Family, Id, S, Data)) end) || {ok, _Host, Addr, S} <- Addresses ], {Timeouts, Replies} = ping_reply(Addresses, #ping_opt{ s = Socket, id = Id, timeout = Timeout, timestamp = Timestamp }), Errors ++ Timeouts ++ Replies end, ok = setopts(Socket, [{active, false}]), flush_events(Socket), Result. %%------------------------------------------------------------------------- %%% Callbacks %%------------------------------------------------------------------------- %% @private start_link(RawOpts, SockOpts) -> Pid = self(), gen_server:start_link(?MODULE, [Pid, RawOpts, SockOpts], []). %% @private start(RawOpts, SockOpts) -> Pid = self(), case gen_server:start(?MODULE, [Pid, RawOpts, SockOpts], []) of {ok, Socket} -> {ok, Socket}; {error, Error} -> Error end. %% @private init([Pid, RawOpts, SockOpts]) -> process_flag(trap_exit, true), {Protocol, Family} = case proplists:get_value(inet6, RawOpts, false) of false -> {icmp, inet}; true -> {'ipv6-icmp', inet6} end, Result = case procket:socket(Family, raw, Protocol) of {error, eperm} -> procket:open(0, RawOpts ++ [{protocol, Protocol}, {type, raw}, {family, Family}]); N -> N end, init_1(Pid, Family, RawOpts, SockOpts, Result). init_1(Pid, Family, RawOpts, SockOpts0, {ok, FD}) -> TTL = proplists:get_value(ttl, RawOpts), _ = case TTL of undefined -> ok; _ -> set_ttl(FD, Family, TTL) end, SockOpts = case proplists:is_defined(active, SockOpts0) of true -> SockOpts0; false -> SockOpts0 ++ [{active, false}] end, case gen_udp:open(0, SockOpts ++ [binary, {fd, FD}, Family]) of {ok, Socket} -> {ok, #state{ family = Family, pid = Pid, fd = FD, s = Socket }}; Error -> Error end; init_1(_Pid, _Family, _RawOpts, _SockOpts, Error) -> {stop, Error}. %% @private handle_call(close, {Pid, _}, #state{pid = Pid, s = Socket} = State) -> {stop, normal, gen_udp:close(Socket), State}; handle_call({send, IP, Packet}, _From, #state{s = Socket} = State) -> {reply, gen_udp:send(Socket, IP, 0, Packet), State}; handle_call({recv, Length, Timeout}, {Pid, _}, #state{pid = Pid, s = Socket} = State) -> Reply = case gen_udp:recv(Socket, Length, Timeout) of {ok, {Address, _Port, Packet}} -> {ok, {Address, Packet}}; N -> N end, {reply, Reply, State}; handle_call({controlling_process, Pid}, {Owner, _}, #state{pid = Owner} = State) -> {reply, ok, State#state{pid = Pid}}; handle_call({setopts, Options}, {Pid, _}, #state{pid = Pid, s = Socket} = State) -> {reply, inet:setopts(Socket, Options), State}; handle_call(family, _From, #state{family = Family} = State) -> {reply, Family, State}; handle_call(getfd, _From, #state{fd = Socket} = State) -> {reply, Socket, State}; handle_call(filter, _From, #state{family = inet6, fd = Socket} = State) -> Reply = procket:getsockopt(Socket, ?IPPROTO_ICMPV6, icmp6_filter(), <<0:256>>), {reply, Reply, State}; handle_call(filter, _From, State) -> {reply, unsupported, State}; handle_call({filter, Filter}, _From, #state{family = inet6, fd = Socket} = State) -> Reply = procket:setsockopt(Socket, ?IPPROTO_ICMPV6, icmp6_filter(), Filter), {reply, Reply, State}; handle_call({filter, _Filter}, _From, State) -> {reply, unsupported, State}; handle_call(Request, From, State) -> error_logger:info_report([{call, Request}, {from, From}, {state, State}]), {reply, error, State}. %% @private handle_cast(Msg, State) -> error_logger:info_report([{cast, Msg}, {state, State}]), {noreply, State}. %% @private % IPv4 ICMP handle_info( {udp, Socket, {_, _, _, _} = Saddr, 0, <<4:4, HL:4, _ToS:8, _Len:16, _Id:16, 0:1, _DF:1, _MF:1, _Off:13, TTL:8, ?IPPROTO_ICMP:8, _Sum:16, _SA1:8, _SA2:8, _SA3:8, _SA4:8, _DA1:8, _DA2:8, _DA3:8, _DA4:8, Data/binary>>}, #state{pid = Pid, s = Socket} = State ) -> N = (HL - 5) * 4, Opt = if N > 0 -> N; true -> 0 end, <<_:Opt/bits, Payload/bits>> = Data, Pid ! {icmp, self(), Saddr, TTL, Payload}, {noreply, State}; % IPv6 ICMP handle_info( {udp, Socket, {_, _, _, _, _, _, _, _} = Saddr, 0, Data}, #state{pid = Pid, fd = FD, s = Socket} = State ) -> {ok, TTL} = get_ttl(FD, inet6), Pid ! {icmp, self(), Saddr, TTL, Data}, {noreply, State}; handle_info({'EXIT', _, normal}, State) -> {noreply, State}; handle_info(Info, State) -> error_logger:info_report([{info, Info}, {state, State}]), {noreply, State}. %% @private terminate(_Reason, #state{fd = Socket}) -> procket:close(Socket), ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. %%------------------------------------------------------------------------- %%% Utility Functions %%------------------------------------------------------------------------- %% @doc Create an ICMP packet. %% %% Convenience function for creating arbitrary ICMP packets. This %% function will calculate the ICMP checksum and insert it into the %% packet. %% %% == Examples == %% %% ``` %% 1> rr("_build/default/lib/pkt/include/pkt_icmp.hrl"). %% [icmp] %% 2> gen_icmp:packet(#icmp{}, <<"abc">>). %% <<8,0,51,157,0,0,0,0,97,98,99>> %% ''' -spec packet(#icmp{} | #icmp6_pseudohdr{} | [packet_option()], binary()) -> binary(). packet(#icmp{} = Header, Payload) when is_binary(Payload) -> Sum = pkt:makesum( list_to_binary([ pkt:icmp(Header), Payload ]) ), list_to_binary([ pkt:icmp(Header#icmp{checksum = Sum}), Payload ]); packet( #icmp6_pseudohdr{ saddr = {SA1, SA2, SA3, SA4, SA5, SA6, SA7, SA8}, daddr = {DA1, DA2, DA3, DA4, DA5, DA6, DA7, DA8}, len = Len, next = Next, h = Header }, Payload ) when is_binary(Payload) -> Sum = pkt:makesum( list_to_binary([ <>, pkt:icmp6(Header), Payload ]) ), list_to_binary([ pkt:icmp6(Header#icmp6{checksum = Sum}), Payload ]); packet(Header, Payload) when is_list(Header) -> packet(inet, Header, Payload). %% @doc Construct an ICMP or ICMPv6 packet payload. %% %% == Examples == %% %% ``` %% 1> gen_icmp:packet(inet, [{type, dest_unreach}, {code, unreach_port}], <<"payload goes here">>). %% <<3,3,135,239,0,0,0,0,112,97,121,108,111,97,100,32,103, %% 111,101,115,32,104,101,114,101>> %% ''' -spec packet(inet | inet6, [packet_option()], binary()) -> binary(). packet(inet, Header, Payload) when is_list(Header), is_binary(Payload) -> Default = #icmp{}, Type = icmp_message:type_to_uint8( proplists:get_value(type, Header, Default#icmp.type) ), Code = icmp_message:code_to_uint8( proplists:get_value(code, Header, Default#icmp.code) ), Id = proplists:get_value(id, Header, Default#icmp.id), Seq = proplists:get_value(sequence, Header, Default#icmp.sequence), GW = proplists:get_value(gateway, Header, Default#icmp.gateway), UN = proplists:get_value(un, Header, Default#icmp.un), MTU = proplists:get_value(mtu, Header, Default#icmp.mtu), Pointer = proplists:get_value(pointer, Header, Default#icmp.pointer), TS_orig = proplists:get_value(ts_orig, Header, Default#icmp.ts_orig), TS_recv = proplists:get_value(ts_recv, Header, Default#icmp.ts_recv), TS_tx = proplists:get_value(ts_tx, Header, Default#icmp.ts_tx), ICMP = #icmp{ type = Type, code = Code, id = Id, sequence = Seq, gateway = GW, un = UN, mtu = MTU, pointer = Pointer, ts_orig = TS_orig, ts_recv = TS_recv, ts_tx = TS_tx }, packet(ICMP, Payload); % IPv6 ICMP packet packet(inet6, Header, Payload) when is_list(Header), is_binary(Payload) -> Default = #icmp6{}, Type = icmp6_message:type_to_uint8( proplists:get_value(type, Header, Default#icmp6.type) ), Code = icmp6_message:code_to_uint8( proplists:get_value(code, Header, Default#icmp6.code) ), Id = proplists:get_value(id, Header, Default#icmp6.id), Seq = proplists:get_value(sequence, Header, Default#icmp6.seq), UN = proplists:get_value(un, Header, Default#icmp6.un), MTU = proplists:get_value(mtu, Header, Default#icmp6.mtu), Pointer = proplists:get_value(pointer, Header, Default#icmp6.pptr), Maxdelay = proplists:get_value(maxdelay, Header, Default#icmp6.maxdelay), % IPv6 pseudoheader Saddr = proplists:get_value(saddr, Header, {0, 0, 0, 0, 0, 0, 0, 0}), Daddr = proplists:get_value(daddr, Header, {0, 0, 0, 0, 0, 0, 0, 0}), Len = proplists:get_value(len, Header, 0), Next = proplists:get_value(next, Header, ?IPPROTO_ICMPV6), Pseudo = #icmp6_pseudohdr{ saddr = Saddr, daddr = Daddr, len = Len, next = Next, h = #icmp6{ type = Type, code = Code, id = Id, seq = Seq, un = UN, mtu = MTU, pptr = Pointer, maxdelay = Maxdelay } }, packet(Pseudo, Payload). %% @doc Generate ICMP ECHO_REQUEST payload. %% %% Creates an ICMP echo packet with an 8 byte timestamp and a %% payload consisting of ASCII 32 to 79. %% %% == Examples == %% %% ``` %% 1> gen_icmp:echo(inet, 0, 0). %% <<8,0,166,49,0,0,0,0,255,253,243,182,71,2,233,209,32,33, %% 34,35,36,37,38,39,40,41,42,43,44,...>> %% ''' -spec echo(inet | inet6, id(), sequence()) -> binary(). echo(Family, Id, Seq) -> % Pad packet to 64 bytes echo(Family, Id, Seq, payload(echo)). %% @doc Generate ICMP ECHO_REQUEST with user specified payload. %% %% Creates an ICMP echo packet with the results of %% erlang:monotonic_time(micro_seconds) used as the timestamp and a user %% specified payload (padded to 64 bytes). %% %% == Examples == %% %% ``` %% 1> gen_icmp:echo(inet, 0, 0, <<"ping", 0:(60*8)>>). %% <<8,0,25,47,0,0,0,0,112,105,110,103,0,0,0,0,0,0,0,0,0,0,0, %% 0,0,0,0,0,0,...>> %% ''' -spec echo(inet | inet6, id(), sequence(), <<_:64>>) -> binary(). echo(Family, Id, Seq, Payload) when is_integer(Id), Id >= 0, Id < 16#FFFF, is_integer(Seq), Seq >= 0, Seq < 16#FFFF, is_binary(Payload) -> Echo = case Family of inet -> echo; inet6 -> echo_request end, packet( Family, [ {type, Echo}, {id, Id}, {sequence, Seq} ], Payload ). % Default ICMP echo payload payload(echo) -> USec = gettime(), <>. %% @doc Set the TTL on a file descriptor. %% %% == Examples == %% %% ``` %% 1> gen_icmp:set_ttl(gen_icmp:getfd(S), gen_icmp:family(S), 1). %% ok %% 2> gen_icmp:get_ttl(gen_icmp:getfd(S), gen_icmp:family(S)). %% {ok,1} %% ''' -spec set_ttl(fd(), inet | inet6, int32_t()) -> ok | {error, inet:posix()}. set_ttl(FD, inet, TTL) -> procket:setsockopt(FD, ?IPPROTO_IP, ip_ttl(), <>); set_ttl(FD, inet6, TTL) -> procket:setsockopt(FD, ?IPPROTO_IPV6, ipv6_unicast_hops(), <>). %% @doc Get the TTL for a file descriptor. %% %% == Examples == %% %% ``` %% 1> gen_icmp:get_ttl(gen_icmp:getfd(S), gen_icmp:family(S)). %% {ok,64} %% ''' -spec get_ttl(fd(), inet | inet6) -> {ok, int32_t()} | {error, inet:posix()}. get_ttl(FD, inet) -> case procket:getsockopt(FD, ?IPPROTO_IP, ip_ttl(), <<0:32>>) of {ok, <>} -> {ok, TTL}; Error -> Error end; get_ttl(FD, inet6) -> case procket:getsockopt(FD, ?IPPROTO_IPV6, ipv6_unicast_hops(), <<0:32>>) of {ok, <>} -> {ok, TTL}; Error -> Error end. %% @doc Resolve a host list. %% %% == Examples == %% %% ``` %% 1> gen_icmp:addr_list(inet, ["8.8.8.8", "google.com", "cloudflare.com", {8,8,8,8}, "8.8.8.8"], false). %% [{ok,"8.8.8.8",{8,8,8,8}}, %% {ok,"google.com",{172,217,165,14}}, %% {ok,"cloudflare.com",{104,16,133,229}}, %% {ok,{8,8,8,8},{8,8,8,8}}, %% {ok,"8.8.8.8",{8,8,8,8}}] %% %% 2> [{ok,"8.8.8.8",{8,8,8,8}}, %% {ok,"google.com",{172,217,165,14}}, %% {ok,"cloudflare.com",{104,16,132,229}}, %% {ok,"cloudflare.com",{104,16,133,229}}, %% {ok,{8,8,8,8},{8,8,8,8}}, %% {ok,"8.8.8.8",{8,8,8,8}}] %% ''' -spec addr_list(inet | inet6, [inet:socket_address() | inet:hostname()], boolean()) -> [ {ok, inet:hostname(), inet:socket_address()} | {error, inet:posix(), inet:hostname(), undefined} ]. addr_list(Family, Hosts, Multi) -> lists:flatmap( fun(Host) -> case parse(Family, Host) of {ok, IPs} when Multi == true -> [{ok, Host, IP} || IP <- IPs]; {ok, [IP | _]} -> [{ok, Host, IP}]; {error, Error} -> [{error, Error, Host, undefined}] end end, Hosts ). %% @doc Parse or resolve an IPv4 host identifier. %% %% == Examples == %% %% ``` %% 1> gen_icmp:parse("8.8.8.8"). %% {ok,[{8,8,8,8}]} %% %% 2> gen_icmp:parse({8,8,8,8}). %% {ok,[{8,8,8,8}]} %% %% 3> gen_icmp:parse("2001:4860:4860::8888"). %% {ok,[{8193,18528,18528,0,0,0,0,34952}]} %% %% 4> gen_icmp:parse("cloudflare.com"). %% {ok,[{104,16,132,229},{104,16,133,229}]} %% %% 5> gen_icmp:parse("foo"). %% {error,nxdomain} %% ''' -spec parse(inet:socket_address() | inet:hostname()) -> {ok, [inet:socket_address()]} | {error, inet:posix()}. parse(Addr) -> parse(inet, Addr). %% @doc Parse or resolve a host identifier. %% %% == Examples == %% %% ``` %% 1> gen_icmp:parse(inet, "cloudflare.com"). %% {ok,[{104,16,133,229},{104,16,132,229}]} %% %% 2> gen_icmp:parse(inet6, "cloudflare.com"). %% {ok,[{9734,18176,0,0,0,0,26640,34021}, %% {9734,18176,0,0,0,0,26640,34277}]} %% ''' -spec parse(inet | inet6, inet:socket_address() | inet:hostname()) -> {ok, [inet:socket_address()]} | {error, inet:posix()}. parse(Family, Addr) when is_list(Addr) -> parse_or_resolve(Family, Addr, inet_parse:address(Addr)); parse(_Family, Addr) when is_tuple(Addr) -> {ok, [Addr]}. %% @doc IPv6 ICMP filtering: allow all ICMP types. %% %% @see icmp6_filter_setblock/2 -spec icmp6_filter_setpassall() -> icmp6_filter(). icmp6_filter_setpassall() -> case os:type() of {unix, linux} -> % Linux reverses the RFC3542 macros <<0:256>>; {unix, _} -> <<16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff>> end. %% @doc ICMPv6 filtering: block all ICMP types. %% %% @see icmp6_filter_setpass/2 -spec icmp6_filter_setblockall() -> icmp6_filter(). icmp6_filter_setblockall() -> case os:type() of {unix, linux} -> % Linux reverses the RFC3542 macros <<16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff, 16#ff>>; {unix, _} -> <<0:256>> end. %% @doc Update a filter to allow an ICMPv6 type. %% %% == Examples == %% %% To generate a filter that allowed only ICMP6_ECHO_REPLY messages: %% %% ``` %% {ok, Socket} = gen_icmp:open([inet6]), %% Filter = gen_icmp:icmp6_filter_setpass(echo_reply, gen_icmp:icmp6_filter_setblockall()), %% ok = gen_icmp:filter(Socket, Filter). %% ''' %% %% @see filter/2 %#define ICMP6_FILTER_SETPASS(type, filterp) \ % (((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31))) -spec icmp6_filter_setpass(atom() | uint8_t(), icmp6_filter()) -> icmp6_filter(). icmp6_filter_setpass(Type0, <<_:256>> = Filter) -> Type = icmp6_message:type_to_uint8(Type0), Offset = Type bsr 5, Value = 1 bsl (Type band 31), Fun = case os:type() of {unix, linux} -> fun(N) -> N band bnot Value end; {unix, _} -> fun(N) -> N bor Value end end, array_set(Offset, Fun, Filter). %% @doc Update a filter to block an ICMPv6 type. %% %% == Examples == %% %% To generate a filter that allowed only ICMP6_PACKET_TOO_BIG messages: %% %% ``` %% 1> {ok, Socket} = gen_icmp:open([inet6]). %% {ok,<0.300.0>} %% 2> Filter = gen_icmp:icmp6_filter_setblock(packet_too_big, gen_icmp:icmp6_filter_setpassall()). %% <<4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, %% 0,...>> %% 3> gen_icmp:filter(Socket, Filter). %% ok %% ''' %% %% @see filter/2 %#define ICMP6_FILTER_SETBLOCK(type, filterp) \ % (((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31))) -spec icmp6_filter_setblock(atom() | uint8_t(), icmp6_filter()) -> icmp6_filter(). icmp6_filter_setblock(Type0, <<_:256>> = Filter) -> Type = icmp6_message:type_to_uint8(Type0), Offset = Type bsr 5, Value = 1 bsl (Type band 31), Fun = case os:type() of {unix, linux} -> fun(N) -> N bor Value end; {unix, _} -> fun(N) -> N band bnot Value end end, array_set(Offset, Fun, Filter). %% @doc Test if a filter allows an ICMPv6 type. %% %% == Examples == %% %% ``` %% 1> Filter = gen_icmp:icmp6_filter_setblock(packet_too_big, gen_icmp:icmp6_filter_setpassall()). %% <<4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, %% 0,...>> %% 2> gen_icmp:icmp6_filter_willpass(echo_reply, Filter). %% true %% 3> gen_icmp:icmp6_filter_willpass(packet_too_big, Filter). %% false %% ''' %#define ICMP6_FILTER_WILLPASS(type, filterp) \ % ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0) -spec icmp6_filter_willpass(atom() | uint8_t(), icmp6_filter()) -> boolean(). icmp6_filter_willpass(Type0, <<_:256>> = Filter) -> Type = icmp6_message:type_to_uint8(Type0), Offset = Type bsr 5, Value = 1 bsl (Type band 31), El = array_get(Offset, Filter), case os:type() of {unix, linux} -> El band Value =:= 0; {unix, _} -> El band Value =/= 0 end. %% @doc Test if a filter blocks an ICMPv6 type. %% %% == Examples == %% %% ``` %% 1> Filter = gen_icmp:icmp6_filter_setblock(packet_too_big, gen_icmp:icmp6_filter_setpassall()). %% <<4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, %% 0,...>> %% 2> gen_icmp:icmp6_filter_willblock(echo_reply, Filter). %% false %% 3> gen_icmp:icmp6_filter_willblock(packet_too_big, Filter). %% true %% ''' %#define ICMP6_FILTER_WILLBLOCK(type, filterp) \ % ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0) -spec icmp6_filter_willblock(atom() | uint8_t(), icmp6_filter()) -> boolean(). icmp6_filter_willblock(Type0, <<_:256>> = Filter) -> Type = icmp6_message:type_to_uint8(Type0), Offset = Type bsr 5, Value = 1 bsl (Type band 31), El = array_get(Offset, Filter), case os:type() of {unix, linux} -> El band Value =/= 0; {unix, _} -> El band Value =:= 0 end. %%------------------------------------------------------------------------- %%% Internal Functions %%------------------------------------------------------------------------- parse_or_resolve(_Family, _Addr, {ok, IP}) -> {ok, [IP]}; parse_or_resolve(Family, Addr, {error, einval}) -> case inet:gethostbyname(Addr, Family) of {ok, #hostent{h_addr_list = IPs}} -> {ok, IPs}; Error -> Error end. ping_reply(Hosts, #ping_opt{s = Socket, timeout = Timeout} = Opt) -> Pid = self(), TRef = case Timeout of infinity -> infinity; _ -> erlang:send_after(Timeout, Pid, {icmp, Socket, timeout}) end, ping_loop(Hosts, [], Opt#ping_opt{tref = TRef}). cancel_timeout(infinity) -> false; cancel_timeout(TRef) -> erlang:cancel_timer(TRef). ping_loop([], Acc, #ping_opt{tref = TRef}) -> _ = cancel_timeout(TRef), {[], Acc}; ping_loop( Hosts, Acc, #ping_opt{ tref = TRef, s = Socket, id = Id, timestamp = Timestamp } = Opt ) -> receive % IPv4 ICMP Echo Reply {icmp, Socket, {_, _, _, _} = Reply, TTL, <>} -> {Elapsed, Payload} = case Timestamp of true -> <> = Data, {timediff(USec) div 1000, Data1}; false -> {0, Data} end, {Hosts2, Result} = case lists:keytake(Seq, 4, Hosts) of {value, {ok, Addr, Address, Seq}, NHosts} -> {NHosts, [ {ok, Addr, Address, Reply, {Id, Seq, TTL, Elapsed}, Payload} | Acc ]}; false -> {Hosts, Acc} end, ping_loop(Hosts2, Result, Opt); % IPv4 ICMP Error {icmp, Socket, {_, _, _, _} = Reply, TTL, <> = Data} -> <<_ICMPHeader:8/bytes, Payload/binary>> = Data, DA = {DA1, DA2, DA3, DA4}, {Hosts2, Result} = case lists:keytake(Seq, 4, Hosts) of {value, {ok, Addr, DA, Seq}, NHosts} -> {NHosts, [ {error, icmp_message:code({Type, Code}), Addr, DA, Reply, {Id, Seq, TTL, undefined}, Payload} | Acc ]}; false -> {Hosts, Acc} end, ping_loop(Hosts2, Result, Opt); % IPv6 ICMP Echo Reply {icmp, Socket, {_, _, _, _, _, _, _, _} = Reply, TTL, <>} -> {Elapsed, Payload} = case Timestamp of true -> <> = Data, {timediff(USec) div 1000, Data1}; false -> {0, Data} end, {Hosts2, Result} = case lists:keytake(Seq, 4, Hosts) of {value, {ok, Addr, Address, Seq}, NHosts} -> {NHosts, [ {ok, Addr, Address, Reply, {Id, Seq, TTL, Elapsed}, Payload} | Acc ]}; false -> {Hosts, Acc} end, ping_loop(Hosts2, Result, Opt); % IPv6 ICMP Error {icmp, Socket, {_, _, _, _, _, _, _, _} = Reply, TTL, <> = Data} -> <<_ICMPHeader:8/bytes, Payload/binary>> = Data, DA = {DA1, DA2, DA3, DA4, DA5, DA6, DA7, DA8}, {value, {ok, Addr, DA, Seq}, Hosts2} = lists:keytake(Seq, 4, Hosts), {Hosts2, Result} = case lists:keytake(Seq, 4, Hosts) of {value, {ok, Addr, DA, Seq}, NHosts} -> {NHosts, [ {error, icmp_message:code({Type, Code}), Addr, DA, Reply, {Id, Seq, TTL, undefined}, Payload} | Acc ]}; false -> {Hosts, Acc} end, ping_loop(Hosts2, Result, Opt); % IPv4/IPv6 timeout on socket {icmp, Socket, timeout} -> _ = cancel_timeout(TRef), Timeouts = [{error, timeout, Addr, IP} || {ok, Addr, IP, _Seq} <- Hosts], {Timeouts, Acc} end. % TTL ip_ttl() -> case os:type() of {unix, linux} -> 2; {unix, _} -> 4 end. ipv6_unicast_hops() -> case os:type() of {unix, linux} -> 16; {unix, _} -> 4 end. icmp6_filter() -> case os:type() of {unix, linux} -> 1; {unix, _} -> 18 end. % Offset starts at 0 array_set(Offset, Fun, Bin) -> Array = array:from_list([N || <> <= Bin]), Value = Fun(array:get(Offset, Array)), << <> || N <- array:to_list(array:set(Offset, Value, Array)) >>. array_get(Offset, Bin) -> Array = array:from_list([N || <> <= Bin]), array:get(Offset, Array). flush_events(Socket) -> receive {icmp, Socket, _Addr, _TTL, _Data} -> flush_events(Socket) after 0 -> ok end. %% @private -spec gettime() -> integer(). gettime() -> try erlang:monotonic_time(micro_seconds) of N -> N catch error:undef -> timestamp_to_microseconds(os:timestamp()) end. %% @private -spec timediff(integer()) -> integer(). timediff(T) -> timediff(gettime(), T). %% @private -spec timediff(integer(), integer()) -> integer(). timediff(T1, T2) -> T1 - T2. timestamp_to_microseconds({MegaSecs, Secs, MicroSecs}) -> (MegaSecs * 1000000 + Secs) * 1000000 + MicroSecs.