C++, ping and traceroute

*First post, please be loyal, sound criticism is welcome

background

Having studied network programming and having a portfolio of several C++ projects related to network programming, I decided to write something that would have a real practical application.

The first thing that came to my mind was the ping utility.

Ping is a utility for checking the integrity and quality of connections in networks based on TCP / IP, as well as the common name for the request itself.

I thought that after reading the docs: https://www.rfc-editor.org/rfc/rfc792I can write my own implementation.

Ping

In principle, the algorithm is simple and understandable: you send a packet and note the time until the answer.

A few days later, an acceptable ping option, which is on Github.

To understand how traceroute works, you need to understand how ping works, so it doesn’t hurt to break down some lines of code.

pid_t ppid = getppid();

In this line, we get the identifier of the stream of our ping-a, to which a field is allocated in the ICMP protocol.

Next, the ICMP header structure is created:

struct icmpHeader {
    uint8_t type;
    uint8_t code;
    uint16_t checksum;

    union {
        struct {
            uint16_t identifier;
            uint16_t sequence;
            uint64_t payload;
        } echo;

        struct ICMP_PACKET_POINTER_HEADER {
            uint8_t pointer;
        } pointer;

        struct ICMP_PACKET_REDIRECT_HEADER {
            uint32_t gatewayAddress;
        } redirect;
    } meta;
};

The fields type, code, checksum are required. In ping, we used only echo, a structure of 7 – 11 lines, but other structures are fragments of the ICMP implementation, which, in principle, could be removed. In the future, the same structures will be used in traceroute.

Next comes the function for generating an Internet checksum:

uint16_t checksum(const void *data, size_t len) {
    auto p = reinterpret_cast<const uint16_t *>(data);

    uint32_t sum = 0;

    if (len & 1) {
        sum = reinterpret_cast<const uint8_t *>(p)[len - 1];
    }

    len /= 2;

    while (len--) {
        sum += *p++;
        if (sum & 0xffff0000) {
            sum = (sum >> 16) + (sum & 0xffff);
        }
    }

    return static_cast<uint16_t>(~sum);
}

After sending the packet, we need to detect the time that the packet went from us to the target and back:

 long int send_flag = sendto(sock, &icmpPacket, sizeof(icmpPacket), 0, 
                             (struct sockaddr *) &in_addr,
                             socklen_t(sizeof(in_addr)));

        sent++;

        uint64_t ms_before = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();


        if (send_flag < 0) {
            perror("send error");
            return;
        }

        char buf[1024];

        auto *icmpResponseHeader = (struct icmpHeader *) buf;

        struct timeval tv;
        tv.tv_sec = response_timeout;
        tv.tv_usec = 0;

        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));


        int data_length_byte = recv(sock, icmpResponseHeader, sizeof(buf), 0);

        if(data_length_byte == -1) {
            cout << "\033[1;31m" << "Host unreachable or response timeout." << "\033[0m" << "   ";
            cout << "Sequence: " << "\033[1;35m" << i << "\033[0m" << "    ";
            cout << "Process id: " << "\033[1;35m" << ppid << "\033[0m" << endl;
            continue;
        }

        uint64_t ms_after = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();

The lines for creating a package and assigning it to value fields are omitted, as they do not contain critical information. It is only worth noting that ICMP allocates a field for binary data, which in the code is filled with a set of ones and zeros.

traceroute

From Wikipedia:

Traceroute is a utility computer program designed to determine the routes of data in TCP/IP networks.

First, I analyzed the packets of the original Linux traceroute with Wireshark.

Because sending raw packets requires root privileges, traceroute uses UDP to send packets with incrementing TTLs to a random port on the target and waits for a closed port response.

Still, I thought that it would be easier to write to ICMP, although root would be needed to run it.

Traceroute sends echo packets with increasing TTL. TTL – Time To Live, the packet lifetime and by default it is 30. The packet lifetime decreases after passing through each node in the network. Let’s say we want to find a route to 1.1.1.1:

If you send a packet with TTL 1, then it will hit the first node (usually your router 192.168.0.1 or subnet) and it will return the answer: TTL exceeded, which means “time to live has expired”. From his response, you can pull out ip_src and thus find out the IP address of the first node. Taking TTL 2, you can find out the IP address of the second node, etc.

You can see it clearly on the website. https://www.ip-lookup.org/visual/traceroute

Traceroute is also on Github

The packet type changes (8) and TTL increases every iteration of the loop.

icmp_packet.type = 8;
icmp_packet.code = 0;
icmp_packet.checksum = 0;
icmp_packet.meta.echo.identifier = ppid;
icmp_packet.meta.echo.sequence = i;
icmp_packet.meta.echo.payload = 0b101101010110100101; // random binary data
icmp_packet.checksum = checksum(&icmp_packet, sizeof(icmp_packet));
int ttl = i + 1;

setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));

In the following code, we check whether the response came from the target or not. If from the goal, then we interrupt the cycle and show the results.

if (strcmp(inet_ntoa(src_addr.sin_addr), ip) == 0) {
    cout << endl << "\033[1;35m" << ttl << "\033[0m" << " hops between you and " << ip << endl;
    break;
}

I believe that traceroute and ping are tools that will enhance the portfolio and help you delve deeper into network programming. In any case, for general development, I recommend reading https://www.rfc-editor.org/rfc/rfc791 (about the IP protocol).

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *