ICMP ECHO Packet Structure
RFC 792 describes an ICMP ECHO packet as:
- Type:8
- Code:8
- Checksum:16
- Identifier:16
- Sequence Number:16
- Data1:8
- ...
- DataN:8
The number after the colon represents the number of bits in the field.
- The type field for ICMP ECHO is set to 8. The response (ICMP ECHO REPLY) has a value of 0.
- The code is 0.
- The checksum is a one's complement checksum that covers both the ICMP header and the data portion of the packet. An Erlang version looks like:
makesum(Hdr) -> 16#FFFF - checksum(Hdr). checksum(Hdr) -> lists:foldl(fun compl/2, 0, [ W || <<W:16>> <= Hdr ]). compl(N) when N =< 16#FFFF -> N; compl(N) -> (N band 16#FFFF) + (N bsr 16). compl(N,S) -> compl(N+S).
- The identifier and sequence number allow clients on a host to differentiate their packets, for example, if multiple ping's are running. The client will usually increment the sequence number for each ICMP ECHO packet sent.
- Data is the payload. Traditionally, it holds a struct timeval so the client can calculate the delay without having to maintain state, but any value can be used, such as the output of erlang:now/0. The remainder is padded with ASCII characters.
<<8:8, 0:8, Checksum:16, Id:16, Sequence:16, Payload/binary>>The ICMP ECHO reply is the same packet returned, with the type field set to 0 and an updated checksum:
<<0:8, 0:8, Checksum:16, Id:16, Sequence:16, Payload/binary>>
Opening a Socket
Sending out ICMP packets requires opening a raw socket. Aside from the issues of having the appropriate privileges, Erlang does not have native support for handling raw sockets. I used procket to handle the privileged socket operations and pass the file descriptor into Erlang. Once the socket is returned to Erlang, we can perform operations on it as an unprivileged user. Since there isn't a gen_icmp module, we need some way of calling sendto()/recvfrom() on the socket. gen_udp uses sendto(), so we can misuse it (with some quirks) for our icmp packets.% Get an ICMP raw socket {ok, FD} = procket:listen(0, [{protocol, icmp}]), % Use the file descriptor to create an Erlang socket structure {ok, S} = gen_udp:open(0, [binary, {fd, FD}]),The port is meaningless, so 0 is passed in as an argument. We create the packet payload twice: first with a zero'ed checksum, then with the results of the checksum.
make_packet(Id, Seq) -> {Mega,Sec,USec} = erlang:now(), Payload = list_to_binary(lists:seq(32, 75)), CS = makesum(<<?ICMP_ECHO:8, 0:8, 0:16, Id:16, Seq:16, Mega:32, Sec:32, USec:32, Payload/binary>>), << 8:8, % Type 0:8, % Code CS:16, % Checksum Id:16, % Id Seq:16, % Sequence Mega:32, Sec:32, USec:32, % Payload: time Payload/binary >>.The packet can be sent via the raw socket using gen_udp:send/4, with the port again set to 0.
ok = gen_udp:send(S, IP, 0, Packet)Since we're abusing gen_udp, we can wait for a message to be sent to the process:
receive {udp, S, _IP, _Port, <<_:20/bytes, Data/binary>>} -> {ICMP, <<Mega:32/integer, Sec:32/integer, Micro:32/integer, Payload/binary>>} = icmp(Data), error_logger:info_report([ {type, ICMP#icmp.type}, {code, ICMP#icmp.code}, {checksum, ICMP#icmp.checksum}, {id, ICMP#icmp.id}, {sequence, ICMP#icmp.sequence}, {payload, Payload}, {time, timer:now_diff(erlang:now(), {Mega, Sec, Micro})} ]), after 5000 -> error_logger:error_report([{noresponse, Packet}]) endIn the above code snippet, you may have noticed the first 20 bytes of the payload is stripped off. Comparing the ICMP packet we sent and the response handed to the process by gen_udp:
icmp: <<8,0,186,30,80,228,0,0,0,0,4,250,0,12,16,77,0,1,69,0,32,33,34,35,36, 37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58, 59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75>> response: <<69,0,0,84,101,155,64,0,64,1,154,44,192,168,220,187,192,168,220, 212,0,0,194,30,80,228,0,0,0,0,4,250,0,12,16,77,0,1,69,0,32,33, 34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54, 55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75>>While the process sent a 64 byte ICMP packet, gen_udp hands it an 84 byte packet which includes the 20 byte IPv4 header. An example of an Erlang ping is included with procket on github. The example will just print out the packets using error_logger:info_report/1:
1> icmp:ping("192.168.213.1"). =INFO REPORT==== 24-May-2010::16:21:37 === type: 0 code: 0 checksum: 52034 id: 14837 sequence: 0 payload: <<" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK">> time: 16790
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.