• Stars
    star
    100
  • Rank 340,703 (Top 7 %)
  • Language
    Erlang
  • License
    BSD 3-Clause "New...
  • Created almost 14 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Erlang interface to ICMP sockets

gen_icmp aspires to be a simple interface for using ICMP and ICMPv6 sockets in Erlang, just like gen_tcp and gen_udp do for their protocol types; incidentally messing up Google searches for whomever someday writes a proper gen_icmp module.

gen_icmp uses procket to get a raw socket and abuses gen_udp for the socket handling. gen_icmp should work on Linux and BSDs.

For a simple example of sending a ping, also see:

https://github.com/msantos/procket/blob/master/src/icmp.erl

EXPORTS

open() -> {ok, Socket}
open(SocketOptions) -> {ok, Socket}
open(RawOptions, SocketOptions) -> {ok, Socket}

    Types   Socket = pid()
            RawOptions = [ RawOption ]
            RawOption = options()
            SocketOptions = SocketOpt
            SocketOpt = [ {active, true} | {active, once} |
                {active, false} | inet | inet6 ]

    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 is the IP TTL
            * IPv4: TTL taken from the IP header
            * 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

close(Socket) -> ok | {error, Reason}

    Types   Socket = pid()
            Reason = posix()

    Close the ICMP socket.

send(Socket, Address, Packet) -> ok | {error, Reason}

    Types   Socket = pid()
            Address = tuple()
            Packet = binary()
            Reason = not_owner | posix()

    Like the gen_udp and gen_tcp modules, any process can send ICMP
    packets but only the owner will receive the responses.

recv(Socket, Length) -> {ok, {Address, Packet}} | {error, Reason}
recv(Socket, Length, Timeout) -> {ok, {Address, Packet}} | {error, Reason}

    Types   Socket = socket()
            Length = int()
            Address = ip_address()
            Packet = [char()] | binary()
            Timeout = int() | infinity
            Reason = not_owner | posix()

    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.

controlling_process(Socket, Pid) -> ok

    Types   Socket = pid()
            Pid = pid()

    Change the process owning the socket. Allows another process to
    receive the ICMP responses.

setopts(Socket, Options) ->

    Types   Socket = pid()
            Options = list()

    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}]).

ping(Host) -> Responses
ping(Host, Options) -> Responses
ping(Socket, Hosts, Options) -> Responses

    Types   Socket = pid()
            Host = Address | Hostname | Hosts
            Address = ReplyAddr = tuple()
            Hostname = string()
            Hosts = [ tuple() | string() ]
            Options = [ Option ]
            Option = {id, Id} | {sequence, Sequence} | {timeout, Timeout} | {data, Data} |
                {timestamp, boolean()} | {ttl, TTL} | {filter, Filter} | inet | inet6
            Id = uint16()
            Sequence = uint16()
            Timeout = non_neg_integer() | infinity
            TTL = uint8()
            Data = binary()
            Filter = binary()
            Responses = [ Response ]
            Response = {ok, Host, Address, ReplyAddr, Details, Payload}
                | {error, ICMPError, Host, Address, ReplyAddr, Details, Payload}
                | {error, Error, Host, Address}
            Details = {Id, Sequence, TTL, Elapsed}
            Elapsed = int() | undefined
            Payload = binary()
            ICMPError = unreach_host | timxceed_intrans
            Error = timeout | inet:posix()

    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.

echo(Id, Sequence) -> Packet

    Types   Id = uint16()
            Sequence = uint16()
            Packet = binary()

    Creates an ICMP echo packet with an 8 byte timestamp and a
    payload consisting of ASCII 32 to 79.

echo(Id, Sequence, Payload) -> Packet

    Types   Id = uint16()
            Sequence = uint16()
            Payload = binary()
            Packet = binary()

    Creates an ICMP echo packet with the results of erlang:now() used
    as the timestamp and a user specified payload (which should pad the
    packet to 64 bytes).


packet(Header, Payload) -> Packet

    Types   Header = [ #icmp{} | Options ]
            Options = [ Opts ]
            Opts = [{type, Type} | {code, Code} | {id, Id} | {sequence, Sequence} |
                        {gateway, Gateway} | {mtu, MTU} | {pointer, Pointer} |
                        {ts_orig, TS_orig} | {ts_recv, TS_recv} | {ts_tx, TS_tx} ]
            Type = uint8() | ICMP_type
            Code = uint8() | ICMP_code
            ICMP_type = echoreply | dest_unreach | source_quench | redirect | echo |
                time_exceeded | parameterprob | timestamp | timestampreply | info_request |
                info_reply | address | addressreply
            ICMP_code = unreach_net | unreach_host | unreach_protocol | unreach_port |
                unreach_needfrag | unreach_srcfail | redirect_net | redirect_host |
                redirect_tosnet | redirect_toshost | timxceed_intrans | timxceed_reass
            Id = uint16()
            Sequence = uint16()
            Payload = binary()
            Packet = binary()

    Convenience function for creating arbitrary ICMP packets. This
    function will calculate the ICMP checksum and insert it into the
    packet.

filter(Socket) -> {ok, Filter} | unsupported
filter(Socket, Filter) -> ok | unsupported

    Types   Socket = pid()
            Filter = binary()

    Sets or retrieves an ICMPv6 filter on a socket. For ICMPv4
    sockets, the atom 'unsupported' is returned.

    Filters can be generated by using the icmp6_filter functions.

icmp6_filter_setblockall() -> binary()
icmp6_filter_setpassall() -> binary()
icmp6_filter_setpass(Type, Filter) -> binary()
icmp6_filter_setblock(Type, Filter) -> binary()
icmp6_filter_willpass(Type, Filter) -> true | false
icmp6_filter_willblock(Type, Filter) -> true | false

    Types   Type = icmp_type()
            Filter = binary()

    Generate a ICMPv6 filter that can be set on a socket using
    filter/2.

    For example, 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).

Traceroute

tracert is an Erlang traceroute implementation built using gen_icmp.

host(Host) -> Path
host(Host, Options) -> Path
host(Socket, Host, Options) -> Path

    Types   Socket = pid()
            Host = Address | Hostname
            Address = tuple()
            Hostname = string()
            Options = [ Option ]
            Option = {protocol, Protocol}
                | {max_hops, uint()}
                | {timeout, uint()}
                | {setuid, bool()}
                | {saddr, Address}
                | {sport, uint16()}
            Protocol = icmp | udp
            Path = [ {Address, MicroSeconds, {Protocol, binary()}
                | timeout ]

    Perform a traceroute to a destination. ICMP and UDP probes are
    supported. ICMP probes are the default.

    max_hops is the maximum TTL (default: 30)

    Set the time in milliseconds to wait for a response using the
    timeout option (default: 1000 ms).  WARNING: if the response
    arrives after the timeout, tracert will insert spurious entries
    into the path.

    tracert will not spawn the setuid helper if the {setuid, false}
    option is used. In this case, beam must either be running as
    root or have the cap_net_raw privileges under Linux.

    The {sport, Port} option sets the initial source port for UDP
    probes. The port will be incremented by 1 for each subsequent
    probe (default: random high port).  For ICMP probes, the ICMP
    ID field will be set to this value.

    The return value is an ordered list of tuples:

        Address: the source address responding to the probe

        MicroSeconds: time elapsed between the probe and receiving
        the response

        Protocol: icmp or udp

        Protocol data: a binary representing the received packet
        contents

path(Path) -> Reasons

    Types   Path = [ {Address, MicroSeconds, {Protocol, binary()} ]
            Reasons = [ {Address, MicroSeconds, Reason} ]
            Reason = ICMP | UDP | timeout
            ICMP = timxceed_intrans | echo_reply | ...
            UDP = unreach_port | ...

    Convert the list of binaries returned by host/1,2,3 to atoms
    representing the ICMP response codes and UDP errors.

COMPILING

rebar3 do clean, compile, ct

Also see the README for procket for additional setup (the procket executable needs superuser privileges).

EXAMPLE

Simple ping interface

1> gen_icmp:ping("www.google.com").
[{ok,"www.google.com",
     {173,194,64,99},
     {173,194,64,99},
     18411,0,50,
     <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}]

2> gen_icmp:ping(["www.google.com", {192,168,213,4}, "193.180.168.20", {192,0,32,10}]).
[{error,timeout,"193.180.168.20",{193,180,168,20}},
 {error,unreach_host,
        {192,168,213,4},
        {192,168,213,4},
        {192,168,213,54},
        {18411,2,undefined},
        <<69,0,0,84,0,0,64,0,64,1,15,29,192,168,213,54,192,168,
          213,4,...>>},
 {ok,{192,0,32,10},
     {192,0,32,10},
     {192,0,32,10},
     {18411,1,103},
     <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>},
 {ok,"www.google.com",
     {173,194,77,99},
     {173,194,77,99},
     {18411,0,50},
     <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}]

IPv6

1> gen_icmp:ping("google.com", [inet6]).
[{ok,"google.com",
     {9735,63664,16395,2054,0,0,0,4098},
     {9735,63664,16395,2054,0,0,0,4098},
     {18411,0,64,62},
     <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO">>}]

2> tracert:host("google.com", [inet6]).

Re-using the ICMP ping socket

Keeping the ICMP socket around between runs is more efficient:

{ok, Socket} = gen_icmp:open(),
P1 = gen_icmp:ping(Socket, [{10,1,1,1}, "www.google.com"], []),
P2 = gen_icmp:ping(Socket, [{10,2,2,2}, "www.yahoo.com"], []),
gen_icmp:close(Socket).

Working with ICMP sockets

{ok, Socket} = gen_icmp:open().

% By default, the ICMP socket is in passive mode
ok = gen_icmp:setopts(Socket, [{active, true}]),

% ICMP host unreachable, empty payload (should contain an IPv4 header
% and the first 8 bytes of the packet data)
Packet = gen_icmp:packet([{type, 3}, {code, 0}], <<0:160, 0:64>>).

gen_icmp:send(Socket, {127,0,0,1}, Packet),

% Put the socket back into passive mode
ok = gen_icmp:setopts(Socket, [{active, false}]).

Setting Up an ICMP Ping Tunnel

ptun is an example of using gen_icmp to tunnel TCP over ICMP.

To compile ptun:

make eg

Host1 (1.1.1.1) listens for TCP on port 8787 and forwards the data over ICMP:

erl -noshell -pa ebin deps/*/ebin -eval 'ptun:server({2,2,2,2},8787)' -s erlang halt

Host2 (2.2.2.2) receives ICMP echo requests and opens a TCP connection to 127.0.0.1:22:

erl -noshell -pa ebin deps/*/ebin -eval 'ptun:client({1,1,1,1},22)' -s erlang halt

To use the proxy on host1:

ssh -p 8787 127.0.0.1

Traceroute

  • ICMP traceroute

      1> Path = tracert:host({8,8,8,8}).
      [{{216,239,46,191},
       36149,
       {icmp,<<11,0,111,150,0,0,0,0,69,128,0,84,0,0,64,...>>}},
       {{216,239,47,189},
       51459,
       {icmp,<<11,0,111,150,0,0,0,0,69,128,0,84,0,0,...>>}},
       {{8,8,8,8},
       34946,
       {icmp,<<0,0,170,0,219,104,0,0,32,33,34,35,36,...>>}}]
    
      2> tracert:path(Path).
      [{{216,239,46,191},62815,timxceed_intrans},
       {{216,239,47,189},44244,timxceed_intrans},
       {{8,8,8,8},34825,echoreply}]
    
  • UDP traceroute

      1> Path = tracert:host({8,8,8,8}, [{protocol, udp}]).
    
  • IPv6 traceroute

      1> Path = tracert:host("google.com", [inet6]).
    

TODO

  • tests: do not depend on list order

  • handle rfc 4884 (Extended ICMP to Support Multi-Part Messages)

  • handle ICMP router renumbering messages

  • IPv6: handle socket ancillary data (RFC 3542)

    • retrieve the packet TTL rather than using the IPV6_UNICAST_HOPS

More Repositories

1

procket

Erlang interface to low level socket operations
C
285
star
2

epcap

Erlang packet capture interface using pcap
C
179
star
3

pkt

Erlang network protocol library
Erlang
151
star
4

evum

["Linux VM", ["Erlang Process", ["Erlang VM"]]].
Erlang
87
star
5

tunctl

Erlang TUN/TAP interface
Erlang
77
star
6

sods

Socket over DNS tunnel
C
69
star
7

verx

Erlang implementation of the libvirtd remote protocol
Erlang
60
star
8

srly

Native Erlang Unix serial interface
Erlang
57
star
9

alcove

Control plane for system processes
C
48
star
10

prx

an Erlang library for interacting with Unix processes
Erlang
36
star
11

erlang-libvirt

Erlang binding to libvirt virtualization API
Erlang
36
star
12

libkeepalive

LD_PRELOAD library for enabling TCP keepalive socket options
C
34
star
13

emdns

Erlang multicast DNS and DNS-SD (DNS Service Discovery)
Erlang
33
star
14

ewpcap

Portable native Erlang raw socket interface using pcap
Erlang
33
star
15

gen_unix

Erlang Unix socket interface
Erlang
30
star
16

wierl

Erlang interface for manipulating 802.11 wireless devices
Erlang
24
star
17

libproxyproto

Proxy protocol v1 and v2 support via an LD_PRELOAD library
C
23
star
18

xmppipe

stdio over XMPP
C
23
star
19

erlxc

Simple, safe erlang interface for managing Linux Containers
Erlang
22
star
20

stk500

Enough of the STK500 protocol in Erlang to control the Arduino boot loader
Erlang
21
star
21

inert

An Erlang library for notification of events on file descriptors
Erlang
20
star
22

seds

Erlang socket over DNS tunnel server
Erlang
19
star
23

perc

Erlang interface for controlling Unix processes
Erlang
16
star
24

epcap_compile

Compile pcap-filter(7) expressions to BPF programs
Erlang
15
star
25

reuseport

SO_REUSEPORT socket load distribution using LD_PRELOAD
C
15
star
26

spoofed

Spoof the Erlang Distribution Protocol!
Erlang
14
star
27

herp

Erlang user space bridge
Erlang
14
star
28

execve

Go package for fexecve(3) and execveat(2)
Go
13
star
29

islet

Simple, safe isolation using Erlang
Erlang
13
star
30

erpcgen

RPC/XDR protocol compiler (from jungerl)
Erlang
13
star
31

tuntap

Erlang "Universal TUN/TAP device" driver from Jungerl
C
12
star
32

crypt

Erlang NIF wrapping Unix crypt(3)
Erlang
12
star
33

sut

Six (IPv6 in IPv4) Userlspace Tunnel
Erlang
11
star
34

erlang-notify-osd

Erlang NIF interface for sending desktop notifications
Erlang
11
star
35

farp

Poison the ARPs, courtesy of Erlang
Erlang
10
star
36

perv

Media captured from the ether for your viewing pleasure
Erlang
9
star
37

runcron

simple, safe, container-friendly cron alternative
C
8
star
38

cerck

Password quality checks for Erlang
Erlang
8
star
39

spood

The spoofing DNS proxy with a vaguely obscene name
Erlang
8
star
40

totp.c

simple, standalone TOTP without dependencies
C
7
star
41

drench

Makes things go ... down
C
7
star
42

libenospace

Process-based disk usage limits
C
7
star
43

sredird

RFC 2217 network serial port redirector
C
6
star
44

everl

Async socket notifications for Erlang using libev
C
6
star
45

runlet

Event stream query and flow control
Elixir
6
star
46

ampule

An elixir library for linux containers
Elixir
6
star
47

trep

Selectively stream stdin to stdout/stderr based on regular expressions
C
5
star
48

librlimit

rlimit sandbox for any process
C
5
star
49

rst

Think of it as peer to peer QoS
C
5
star
50

cm17a

Erlang X10 Firecracker (CM17A) Interface
Erlang
4
star
51

wat

A simple example of an Erlang NIF for creating mutable variables
C
4
star
52

embedexe

Run an executable embedded in a Go binary
Go
4
star
53

pseudocron

sleep(1) using a cron expression
C
3
star
54

libsockfilter

Connection filtering for dynamically linked applications
C
3
star
55

stdio

Reliably reap, restrict and isolate system tasks: Stdio is a control plane for processes
Elixir
3
star
56

runlet_sh

Generate runlets from containerized Unix processes
Elixir
3
star
57

wwallo

Tag cloud for your geo location
JavaScript
2
star
58

libnoexec

Prevent dynamically linked executables from calling exec(3)
C
2
star
59

collectd-prv

stdout to collectd notification
C
1
star
60

tscat

Timestamp stdin to stdout/stderr
C
1
star
61

runhash-go

runhash: command line interface for distributed node selection
Go
1
star
62

noprivexec

noprivexec: disable setuid privileges
C
1
star
63

pdeathsigexec

signal process when parent exits
C
1
star
64

tcpexec-rs

tcpexec: a minimal, UCSPI inetd
Rust
1
star
65

unixexec

attach stdin/stdout of a command to a Unix socket
C
1
star
66

pipewatch

pipewatch: supervise pipelines of processes
C
1
star
67

fchmodexec

fchmod(2) inherited file descriptors before exec(3)'ing a command
C
1
star
68

prv

pressure relief valve for Unix process pipelines
C
1
star
69

closefrom

close(2) a range of file descriptors before exec(2)
C
1
star
70

xmppipe-go

stdio over XMPP
Go
1
star
71

runlimit

restart intensity limits for supervised Unix processes
C
1
star
72

tcpexec

tcpexec: a minimal, UCSPI inetd
C
1
star
73

logsurfer-

Rules based log file monitoring and alerting tool
C
1
star
74

imappipe

poll IMAP mailbox to stdout
Go
1
star
75

hexlog

Hexdump stdin and/or stdout to stderr
C
1
star
76

runlock

rate limit command invocation based on the last successful run time
C
1
star
77

eventbot

A bot for generating and interacting with event streams using XMPP
Elixir
1
star
78

goreap

User init to supervise and terminate subprocesses
Go
1
star
79

dnsup

Publish an IP address using the Cloudflare API
Go
1
star
80

genlb-ptrace

connect(2) load balancer for Unix processes
C
1
star
81

runlet_net

Miscellaneous network related commands for runlets
Erlang
1
star
82

rotatee

tee(1) with file rotation
Go
1
star