четверг, 7 апреля 2011 г.

ICMP Ping in Erlang, part 2



ICMP Ping in Erlang, part 2

I've covered sending ICMP packets from Erlang using BSD raw sockets and Linux's PF_PACKET socket option.

gen_icmp tries to be a simple interface for ICMP sockets using the BSD raw socket interface for portability. It should work on both Linux and BSD's (I've tested on Ubuntu and Mac OS X).

SENDING PING'S

To ping a host:
1> gen_icmp:ping("erlang.org").
[{ok,{193,180,168,20},
     {{33786,0,129305},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}}]
The response is a list of 3-tuples. The third element is a 2-tuple holding the ICMP echo request ID, the sequence number, the elapsed time and the payload.

A bad response looks like:
2> gen_icmp:ping("192.168.213.4").
[{{error,host_unreachable},
  {192,168,213,4},
  {{34491,0},
   <<69,0,0,84,0,0,64,0,64,1,14,220,192,168,213,119,192,
     168,213,4,8,0,196,...>>}}]
The argument to gen_icmp:ping/1 takes either a string or a list of strings. For example, to ping every host on a /24 network:
1> gen_icmp:ping([ {192,168,213,N} || N <- lists:seq(1,254) ]).
[{{error,host_unreachable},
  {192,168,213,254},
  {{54370,0},
   <<69,0,0,84,0,0,64,0,64,1,13,226,192,168,213,119,192,
     168,213,254,8,0,82,...>>}},
 {{error,host_unreachable},
  {192,168,213,190},
  {{54370,0},
   <<69,0,0,84,0,0,64,0,64,1,14,34,192,168,213,119,192,168,
     213,190,8,0,...>>}},
gen_icmp:ping/1 takes care of opening and closing the raw socket. This operation is somewhat expensive because Erlang is spawning a setuid executable to get the socket. If you'll be doing a lot of ping's, it's better to keep the socket around and use ping/3:
1> {ok,Socket} = gen_icmp:open().
{ok,<0.308.0>}

2> gen_icmp:ping(Socket, ["www.yahoo.com", "erlang.org", {192,168,213,1}],
    [{id, 123}, {sequence, 0}, {timeout, 5000}]).
[{ok,{193,180,168,20},
     {{123,0,126270},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}},
     {ok,{69,147,125,65},
     {{123,0,29377},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}},
     {ok,{192,168,213,1},
     {{123,0,3586},
      <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">>}}]

3> gen_icmp:close(Socket).
ok

CREATING OTHER ICMP PACKET TYPES


ICMP destination unreachable and time exceeded packets return at least the first 64 bits of the header and payload of the original packet. Here is an example of generating an ICMP port unreachable for a fake TCP packet sent to port 80.
-module(icmperr).
-export([unreachable/3]).
-include("epcap_net.hrl").

-define(IPV4HDRLEN, 20).
-define(TCPHDRLEN, 20).

unreachable(Saddr, Daddr, Data) ->
    {ok, Socket} = gen_icmp:open(),

    IP = #ipv4{
        p = ?IPPROTO_TCP,
        len = ?IPV4HDRLEN + ?TCPHDRLEN + byte_size(Data),
        saddr = Daddr,
        daddr = Saddr
    },

    TCP = #tcp{
        dport = 80,
        sport = crypto:rand_uniform(0, 16#FFFF),
        seqno = crypto:rand_uniform(0, 16#FFFF),
        syn = 1
    },

    IPsum = epcap_net:makesum(IP),
    TCPsum = epcap_net:makesum([IP, TCP, Data]),

    Packet = <<
        (epcap_net:ipv4(IP#ipv4{sum = IPsum}))/bits,
        (epcap_net:tcp(TCP#tcp{sum = TCPsum}))/bits,
        Data/bits
        >>,

    ICMP = gen_icmp:packet([
        {type, ?ICMP_DEST_UNREACH},
        {code, ?ICMP_UNREACH_PORT}
    ], Packet),

    ok = gen_icmp:send(Socket, Daddr, ICMP),
    gen_icmp:close(Socket).

To create the IPv4 and TCP headers, we make the protocol records and use the epcap_net module functions to encode the headers with the proper checksums. For creating the ICMP packet, we use the gen_icmp:packet/2 function (which again simply calls epcap_net).


ICMP PING TUNNEL


We can tunnel any data we like in the payload of an ICMP packet. In this example, we'll use ICMP echo requests to tunnel an ssh connection between 2 hosts. The ICMP echo replies sent back by the peer OS ensure the data was received, like the ACK in a TCP connection.

The tunnel exports 2 functions:

  • ptun:server(ClientAddress, LocalPort) -> void()

    Types   ClientAddress = tuple()
                LocalPort = 0..65534
    
        ClientAddress is the IPv4 address of the peer represented as a tuple.
    
        The server listens on LocalPort for TCP connections and will close
        the port after a TCP client connects.  Data received on this port
        will be sent to the peer as the payload of the ICMP packets.
    

  • ptun:client(ServerAddress, LocalPort) -> void()

    Types   ServerAddress = tuple()
                LocalPort = 0..65534
    
        ServerAddress is the IPv4 address of the peer.
    
        When the client receives an ICMP echo request, the client opens a
        TCP connection to the LocalPort on localhost, proxying the data.
    
-module(ptun).
-include("epcap_net.hrl").
-export([client/2, server/2]).
-define(TIMEOUT, 5000).
-define(PORT, 8787).
-record(state, {
        addr,
        port,
        is,
        ts,
        id,
        seq = 1
    }).
server(Addr, Port) ->
    {ok, ICMP} = gen_icmp:open(),
    {ok, Socket} = gen_tcp:listen(Port, [
            binary,
            {packet, 0},
            {active, true},
            {reuseaddr, true},
            {ip, {127,0,0,1}}
        ]),
    accept(Addr, ICMP, Socket).
client(Addr, Port) ->
    {ok, ICMP} = gen_icmp:open(),
    State = #state{
        addr = Addr,
        port = Port,
        is = ICMP,
        id = crypto:rand_uniform(0, 16#FFFF)
    },
    proxy(State).
accept(Addr, ICMP, Listen) ->
    {ok, Socket} = gen_tcp:accept(Listen),
    gen_tcp:close(Listen),
    State = #state{
        addr = Addr,
        is = ICMP,
        ts = Socket,
        id = crypto:rand_uniform(0, 16#FFFF)
    },
    [{ok, Addr, _}] = gen_icmp:ping(ICMP, [Addr], [
            {id, State#state.id},
            {sequence, 0},
            {timeout, ?TIMEOUT}
        ]),
    proxy(State).
proxy(#state{
        is = IS,
        ts = TS,
        addr = Addr,
        port = Port
    } = State) ->
    receive
        % TCP socket events
        {tcp, TS, Data} ->
            Seq = send(Data, State),
            proxy(State#state{seq = Seq});
        {tcp_closed, TS} ->
            ok;
        {tcp_error, TS, Error} ->
            {error, Error};
        % ICMP socket events
        % client: open a connection on receiving the first ICMP ping
        {icmp, IS, Addr,
            <<?ICMP_ECHO:8, 0:8, _Checksum:16, _Id:16, Seq:16, _Data/binary>>}
            when TS == undefined, Seq == 0 ->
            {ok, Socket} = gen_tcp:connect("127.0.0.1", Port, [binary, {packet, 0}]),
            error_logger:info_report([{connect, {{127,0,0,1},Port}}]),
            proxy(State#state{ts = Socket});
        {icmp, IS, Addr,
            <<?ICMP_ECHO:8, 0:8, _Checksum:16, _Id:16, _Seq:16, Len:16, Data/binary>>} ->
            <<Data1:Len/bytes, _/binary>> = Data,
            ok = gen_tcp:send(TS, Data1),
            proxy(State#state{ts = TS});
        {icmp, IS, Addr, Packet} ->
            error_logger:info_report([{dropping, Packet},{address, Addr}]),
            proxy(State)
    end.
% To keep it simple, we use 64 byte packets
% 4 bytes header, 2 bytes type, 2 bytes code, 12 bytes timestamp, 2 bytes data length, 42 bytes data
send(<<Data:42/bytes, Rest/binary>>, #state{is = Socket, addr = Addr, id = Id, seq = Seq} = State) ->
    [{ok, Addr, _}] = gen_icmp:ping(Socket, [Addr], [
            {id, Id},
            {sequence, Seq},
            {timeout, ?TIMEOUT},
            {data, <<(byte_size(Data)):16, Data/bytes>>}
        ]),
    send(Rest, State#state{seq = Seq + 1});
send(Data, #state{is = Socket, addr = Addr, id = Id, seq = Seq}) ->
    Len = byte_size(Data),
    [{ok, Addr, _}] = gen_icmp:ping(Socket, [Addr], [
            {id, Id},
            {sequence, Seq},
            {timeout, ?TIMEOUT},
            {data, <<Len:16, Data/bytes, 0:((42-Len)*8)>>}
        ]),
    Seq+1.
view rawptun.erlThis Gist brought to you by GitHub.
To start the tunnel, you'll need 2 hosts. In this example, 192.168.213.7 is the client and 192.168.213.119 is the server. 192.168.213.7 forwards any tunnelled data it receives to a local SSH server. On 192.168.213.7:
1> ptun:client({192,168,213,119}, 22).
On 192.168.213.119:
1> ptun:server({192,168,213,7}, 8787).
Open another shell and start an SSH connection to port 8787 on localhost:
ssh -p 8787 127.0.0.1
<...>
$ ifconfig eth0 | awk '/inet addr/{print $2}'
addr:192.168.213.7
Оригинал 

Комментариев нет:

Отправить комментарий