Skip to content

rana/ping

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ping

A simple ICMP Echo implementation in Rust.

Based on a Firezone interview challenge.

Intent

Create a solution that knocks it out of the park.

Implement a high quality solution that includes the more challenging optional bonus, all the requirements, and documentation.

Solution

Screencast

  • Completed optional bonus.
  • Handled ICMP packet IO asynchronously with non-blocking socket2 raw socket.
  • Created an async-friendly ICMP tokio socket IcmpSocketTokio.
  • Five second ping roundtrip timeouts.
  • Input parameter bounds checks.
  • Code documention.
  • Code comments.

Requirements

Some notes on the requirements.

  • Use Linux or Docker.
    • Linux selected.
      • Having an Ubuntu Linux desktop makes the choice simple.
  • Specify parameters:
    • IPv4.
    • ping count (min 1, max 10).
    • ping interval in milliseconds (min 1, max 1000). Requests should be sent at this interval.
  • Set ICMP Echo timout at 5 seconds.
  • Exit after all sent and all received, or timed out.
  • Use an async runtime.
    • Don't block the executor.
    • tokio runtime recommended.
    • Send, receive, and timeout are concurrent.
    • Choose how to coordinate concurrent commuication.
  • Bonus (optional)
    • Handle the packet IO asynchronously using the runtime without blocking calls, only waking the executor when the socket is ready. This will likely involve creating an async-friendly ICMP socket similar to tokio::net::UdpSocket on top of socket2 sockets.

  • Choose a crate for sending packets.
    • Understand the tradeoffs of crate selection.
      • GitHub repo rust-lang/socket2

      • socket2 has 49x more downloads than pnet.

      • socket2 is also cross-platform.

      • pnet is a popular choice for parsing and forming packets.

        Cross-platform, low level networking using the Rust programming language.

        1,678,261 crate downloads

        3rd party Rust crate

        GitHub: 1.9k stars

        Code changed 3 months ago

        Cross-platform

        High-level functions with convenience. Has nice Rust lanague constructs making it faster to develop with.

        Thread-safe

        Provides a cross-platform API for low level networking using Rust.

      • socket2 enables raw ICMP socket sending and recieving.

        Utilities for handling networking sockets with a maximal amount of configuration possible intended.

        82,798,377 crate downloads

        Official Rust crate

        GitHub: 519 stars

        Code changed last week

        Cross-platform: Linux, macOS, Windows

        Low-level functions; less convenience than pnet; more configurability than pnet.

        Possibly more perfofrmance than pnet? Due to low-level functions?

        Not thread-safe.

        It is up to the user to know how to use sockets when using this crate. If you don't know how to create a socket using libc/system calls then this crate is not for you. Most, if not all, functions directly relate to the equivalent system call with no error handling applied, so no handling errors such as EINTR. As a result using this crate can be a little wordy, but it should give you maximal flexibility over configuration of sockets.

Input

Input is passed to the first argument in a comma-delimited format:

# IPv4, request_count, interval_in_milliseconds
./ping "1.1.1.1,3,1000"

Output

Output is printed to STDOUT in a comma-delimited format:

# IPv4, icmp_sequence_number, elapsed_time_in_microseconds
1.1.1.1,0,7189

Example

$ echo "1.1.1.1,3,1000" | ping
1.1.1.1,0,7189
1.1.1.1,1,7750
1.1.1.1,2,6674

Research

Some resarch notes.

  • ICMP - Internet Control Message Protocol. OSI layer 3

    ICMP uses the basic support of IP as if it were a higher-level protocol, however, ICMP is actually an integral part of IP. Although ICMP messages are contained within standard IP packets, ICMP messages are usually processed as a special case, distinguished from normal IP processing.

  • ICMP RFC 792.

Summary of Message Types
    0  Echo Reply
    3  Destination Unreachable
    4  Source Quench
    5  Redirect
    8  Echo
   11  Time Exceeded
   12  Parameter Problem
   13  Timestamp
   14  Timestamp Reply
   15  Information Request
   16  Information Reply
  • UDP - User Datagram Protocol. OSI layer 4

  • Internet layer - OSI layer 3

  • Transport layer - OSI layer 4

  • Cloudflare article "What is the Internet Control Message Protocol (ICMP)?".

    ICMP is not associated with a transport layer protocol such as TCP or UDP. This makes ICMP a connectionless protocol: one device does not need to open a connection with another device before sending an ICMP message.

  • GitHub repo fastping-rs

    • Uses pnet
      • pnet::packet::icmp
  • Library documentation "pnet::packet::icmp".

  • GitHub repo "libpnet"

  • pnet: Ethernet echo server

  • tokio: crate, website, tutorial, api

    Tokio is designed for IO-bound applications where each individual task spends most of its time waiting for IO.

    Tokio has a lot of functionality (TCP, UDP, Unix sockets, timers, sync utilities, multiple scheduler types, etc).

    The current_thread runtime flavor is a lightweight, single-threaded runtime. It is a good choice when only spawning a few tasks and opening a handful of sockets. For example, this option works well when providing a synchronous API bridge on top of an asynchronous client library.

  • tokio::io::Stdout

  • tokio::net

  • Example Linux ping output.

> ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=16.1 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=118 time=14.7 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=118 time=14.4 ms
  • tokio Echo server

  • tokio-rs/mio

    • Mio – Metal I/O

    Mio is a fast, low-level I/O library for Rust focusing on non-blocking APIs and event notification for building high performance I/O apps with as little overhead as possible over the OS abstractions.

    • Non-blocking TCP, UDP

    • I/O event queue backed by epoll, kqueue, and IOCP

    • Zero allocations at runtime

    • Platform specific extensions

    • Platforms: Android (API level 21), DragonFly BSD, FreeBSD, Linux, NetBSD, OpenBSD, Windows, iOS, macOS

    • API docs

    • mio::net::UdpSocket - UDP Echo example program

    • Linux man page: ip - Linux IPv4 protocol implementation

      • Searching for ICMP usage
    • Linux man page: icmp - Linux IPv4 ICMP kernel module.

      This kernel protocol module implements the Internet Control Message Protocol defined in RFC 792. It is used to signal error conditions and for diagnosis. The user doesn't interact directly with this module; instead it communicates with the other protocols in the kernel and these pass the ICMP errors to the application layers. The kernel ICMP module also answers ICMP requests.

      A user protocol may receive ICMP packets for all local sockets by opening a raw socket with the protocol IPPROTO_ICMP. See raw(7) for more information. The types of ICMP packets passed to the socket can be filtered using the ICMP_FILTER socket option. ICMP packets are always processed by the kernel too, even when passed to a user socket.

  • zaphar/icmp-socket, api docs

  • lib.rs: #icmp

  • tokio::select

  • Article "Implementing ICMP in Rust"

  • sys/socket.h "api docs", src.

#define AF_INET		2	/* Internet IP Protocol 	*/
  • SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message boundaries, and delivers messages in the order that they were sent.

    • ICMP is not connection-oriented. Do not use SOCK_SEQPACKET for ICMP.
  • SOCK_SEQPACKET - Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.

  • SOCK_RAW - Provides raw network protocol access.

  • Linux manual "socket - create an endpoint for communication".

  • Linux manual "protocols - protocols definition file".

  • Linux manual "icmp - Linux IPv4 ICMP kernel module".

    A user protocol may receive ICMP packets for all local sockets by opening a raw socket with the protocol IPPROTO_ICMP.

  • Linux manual "raw - Linux IPv4 raw sockets".

    raw sockets are usually needed only for new protocols or protocols with no user interface (like ICMP).

    For sending and receiving datagrams (sendto(2), recvfrom(2), and similar), raw sockets use the standard sockaddr_in address structure defined in ip(7).

    Errors originating from the network are passed to the user only when the socket is connected or the IP_RECVERR flag is enabled.

    If you want to receive all ICMP packets for a datagram socket, it is often better to use IP_RECVERR on that particular socket; see ip(7).

  • Linux manual "ip - Linux IPv4 protocol implementation".

    IP_HDRINCL - If enabled, the user supplies an IP header in front of the user data. Valid only for SOCK_RAW sockets

  • Linux manual "send, sendto, sendmsg - send a message on a socket".

  • Linux header "linux/icmp.h".

  • Linux manual "recv, recvfrom, recvmsg - receive a message from a socket".

    If no messages are available at the socket, the receive calls wait for a message to arrive, unless the socket is nonblocking (see fcntl(2)), in which case the value -1 is returned and errno is set to EAGAIN or EWOULDBLOCK. The receive calls normally return any data available, up to the requested amount, rather than waiting for receipt of the full amount requested.

    An application can use select(2), poll(2), or epoll(7) to determine when more data arrives on a socket.

  • Linux manual "select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing"

    All modern applications should instead use poll(2) or epoll(7), which do not suffer this limitation.

  • Linux manual "poll, ppoll - wait for some event on a file descriptor".

    poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O. The Linux-specific epoll(7) API performs a similar task, but offers features beyond those found in poll().

  • Linux manual "epoll - I/O event notification facility".

    The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of watched file descriptors.

    The central concept of the epoll API is the epoll instance, an in-kernel data structure which, from a user-space perspective, can be considered as a container for two lists:

    • The interest list (sometimes also called the epoll set): the set of file descriptors that the process has registered an interest in monitoring.

    • The ready list: the set of file descriptors that are "ready" for I/O. The ready list is a subset of (or, more precisely, a set of references to) the file descriptors in the interest list. The ready list is dynamically populated by the kernel as a result of I/O activity on those file descriptors.

  • Module mio::guide

    • Getting started guide.
    • Uses with socket2, OS file descriptors
  • Struct mio::net::UdpSocket

    • Example of UDP echo send/recv with mio polling.
  • Article "An introduction to using tcpdump at the Linux command line".

  • Linux manual "tcpdump - dump traffic on a network".

> tcpdump --list-interfaces
1.wlp3s0 [Up, Running, Wireless, Associated]
2.any (Pseudo-device that captures on all interfaces) [Up, Running]
3.lo [Up, Running, Loopback]
4.enp112s0 [Up, Disconnected]
5.eno1 [Up, Disconnected]
6.bluetooth0 (Bluetooth adapter number 0) [Wireless, Association status unknown]
7.bluetooth-monitor (Bluetooth Linux Monitor) [Wireless]
8.nflog (Linux netfilter log (NFLOG) interface) [none]
9.nfqueue (Linux netfilter queue (NFQUEUE) interface) [none]
10.dbus-system (D-Bus system bus) [none]
11.dbus-session (D-Bus session bus) [none]
# sudo required
> sudo tcpdump --interface wlp3s0
14:42:51.534190 ARP, Request who-has wiz_71b920.lan tell wiz_71b920.lan, length 28
14:42:51.638107 IP aum.lan.54962 > _gateway.domain: 31829+ [1au] PTR? 24.86.168.192.in-addr.arpa. (55)
14:42:51.642568 IP _gateway.domain > aum.lan.54962: 31829* 1/0/1 PTR wiz_71b920.lan. (83)

^C
64 packets captured
64 packets received by filter
0 packets dropped by kernel
> ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=58 time=14.7 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=58 time=14.6 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=58 time=14.9 ms
^C
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 14.648/14.766/14.905/0.105 ms

> sudo tcpdump --interface wlp3s0 -c5 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on wlp3s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:50:42.961326 IP aum.lan > one.one.one.one: ICMP echo request, id 1, seq 1, length 64
14:50:42.976064 IP one.one.one.one > aum.lan: ICMP echo reply, id 1, seq 1, length 64
14:50:43.963206 IP aum.lan > one.one.one.one: ICMP echo request, id 1, seq 2, length 64
14:50:43.977841 IP one.one.one.one > aum.lan: ICMP echo reply, id 1, seq 2, length 64
14:50:44.965062 IP aum.lan > one.one.one.one: ICMP echo request, id 1, seq 3, length 64
5 packets captured
6 packets received by filter
0 packets dropped by kernel
>

About

Ping written in Rust.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages