%%% @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. -module(gen_icmp). -behaviour(gen_server). -include_lib("kernel/include/inet.hrl"). -include_lib("pkt/include/pkt.hrl"). -define(SERVER, ?MODULE). -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 ]). -record(state, { % Protocol family (inet, inet6) family = inet, % caller PID pid, % socket file descriptor fd, % udp socket s }). -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{} }). %%------------------------------------------------------------------------- %%% API %%------------------------------------------------------------------------- %% @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. %% %% See the procket README for the raw socket options and for %% instructions on setting up the setuid helper. %% %% 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 open() -> open([], []). open(RawOpts) -> open(RawOpts, []). open(RawOpts, SockOpts) -> start_link(RawOpts, SockOpts). %% @doc Close the ICMP socket close(Ref) when is_pid(Ref) -> gen_server:call(Ref, 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. send(Ref, Address, Packet) when is_pid(Ref) -> gen_server:call(Ref, {send, Address, Packet}, infinity). %% @doc Read data from an ICMP socket %% %% This function receives a packet from a socket in passive mode. %% %% The optional Timeout parameter specifies a timeout in %% milliseconds. The default value is infinity. recv(Ref, Length) -> recv(Ref, Length, infinity). recv(Ref, Length, Timeout) -> gen_server:call(Ref, {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. controlling_process(Ref, Pid) when is_pid(Ref), is_pid(Pid) -> gen_server:call(Ref, {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: %% %% ``` %% {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}]). %% ''' setopts(Ref, Options) when is_pid(Ref), is_list(Options) -> gen_server:call(Ref, {setopts, Options}, infinity). family(Ref) when is_pid(Ref) -> gen_server:call(Ref, family, infinity). getfd(Ref) when is_pid(Ref) -> gen_server:call(Ref, getfd, infinity). filter(Ref) when is_pid(Ref) -> gen_server:call(Ref, filter, infinity). filter(Ref, Filter) when is_pid(Ref) -> gen_server:call(Ref, {filter, Filter}, infinity). ping(Host) -> ping(Host, []). 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 %% %% 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. %% %% 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. %% %% ping/1 and ping/2 open and close an ICMP socket for the user. For %% best performance, ping/3 should be used instead, with the socket %% being maintained between runs. %% %% 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. 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 %%------------------------------------------------------------------------- start_link(RawOpts, SockOpts) -> Pid = self(), gen_server:start_link(?MODULE, [Pid, RawOpts, SockOpts], []). start(RawOpts, SockOpts) -> Pid = self(), case gen_server:start(?MODULE, [Pid, RawOpts, SockOpts], []) of {ok, Socket} -> {ok, Socket}; {error, Error} -> Error end. 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}. 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}. handle_cast(Msg, State) -> error_logger:info_report([{cast, Msg}, {state, State}]), {noreply, State}. % 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}. terminate(_Reason, #state{fd = Socket}) -> procket:close(Socket), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%------------------------------------------------------------------------- %%% Utility Functions %%------------------------------------------------------------------------- %% Create an ICMP packet 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) -> packet(inet, Header, Payload). % IPv4 ICMP packet 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). echo(Family, Id, Seq) -> % Pad packet to 64 bytes echo(Family, Id, Seq, payload(echo)). 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(), <>. % Set the TTL on a socket 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(), <>). % Get the socket TTL 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. %%------------------------------------------------------------------------- %%% Internal Functions %%------------------------------------------------------------------------- %% %% ping %% 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 ). parse(Addr) -> parse(inet, Addr). 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]}. 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 = erlang:send_after(Timeout, Pid, {icmp, Socket, timeout}), ping_loop(Hosts, [], Opt#ping_opt{tref = TRef}). ping_loop([], Acc, #ping_opt{tref = TRef}) -> erlang:cancel_timer(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} -> erlang:cancel_timer(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 erlang:system_info(os_type) of {unix, linux} -> 1; {unix, _} -> 18 end. % IPv6 ICMP filtering % % Linux reverses the meaining macros in RFC3542 icmp6_filter_setpassall() -> case erlang:system_info(os_type) of {unix, linux} -> <<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. icmp6_filter_setblockall() -> case erlang:system_info(os_type) of {unix, linux} -> <<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. %#define ICMP6_FILTER_SETPASS(type, filterp) \ % (((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31))) 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 erlang:system_info(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). %#define ICMP6_FILTER_SETBLOCK(type, filterp) \ % (((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31))) 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 erlang:system_info(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). %#define ICMP6_FILTER_WILLPASS(type, filterp) \ % ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0) 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 erlang:system_info(os_type) of {unix, linux} -> El band Value =:= 0; {unix, _} -> El band Value =/= 0 end. %#define ICMP6_FILTER_WILLBLOCK(type, filterp) \ % ((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0) 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 erlang:system_info(os_type) of {unix, linux} -> El band Value =/= 0; {unix, _} -> El band Value =:= 0 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(Ref) -> receive {icmp, Ref, _Addr, _TTL, _Data} -> flush_events(Ref) after 0 -> ok end. gettime() -> try erlang:monotonic_time(micro_seconds) of N -> N catch error:undef -> timestamp_to_microseconds(os:timestamp()) end. timediff(T) -> timediff(gettime(), T). timediff(T1, T2) -> T1 - T2. timestamp_to_microseconds({MegaSecs, Secs, MicroSecs}) -> (MegaSecs * 1000000 + Secs) * 1000000 + MicroSecs.