Program Listing for File arp.h

Return to documentation for file (include/arp.h)

#pragma once

#include <arpa/inet.h>
#include <dpdk_wrappers.h>
#include <network_types.h>
#include <rte_arp.h>
#include <rte_cycles.h>
#include <rte_mbuf.h>
#include <stdint.h>

#include <chrono>
#include <iostream>
#include <optional>
#include <tuple>
#include <unordered_map>
#include <utility>

struct ArpPacket {
    struct rte_ether_hdr ether_hdr;
    struct rte_arp_hdr arp_hdr;
};

class Arp {
public:
    Arp(const in_addr_t own_ip, const rte_ether_addr own_mac)
        : own_ip(own_ip), own_mac(own_mac) { }

    enum class Error {
        ARP_OK = 0,
        INVALID_MESSAGE,
        UNKNOWN_PROTOCOL,
        UNKNOWN_OP,
        PACKET_CONSTRUCTION,
        IO_ERROR,
        NO_RESPONSE,
    };

    /*
     * @brief Converts an Error enum value to its string representation.
     *
     * @param e The Error.
     */
    static constexpr const char *ErrorToString(Error e) {
        switch (e) {
            case Error::ARP_OK:
                return "ARP OK";
            case Error::INVALID_MESSAGE:
                return "Invalid Message";
            case Error::UNKNOWN_PROTOCOL:
                return "Unknown protocol";
            case Error::UNKNOWN_OP:
                return "Unknown operation";
            case Error::PACKET_CONSTRUCTION:
                return "Packet construction";
            case Error::IO_ERROR:
                return "IO error";
            case Error::NO_RESPONSE:
                return "No response :( ";
            default:
                return "Unknown error";
        }
    }

    template <typename Send>
    requires std::invocable<Send, RTEMbufElement<DefaultPacket, MbufType::Pkt>>
    Error ReceivePacket(RTEMbuf<DefaultPacket> &pkt,
        RTEMempool<DefaultPacket, MbufType::Pkt> &mpool, Send send) {
        const auto &arp_pkt = pkt.data<ArpPacket>();

        // std::cout << std::hex << rte_be_to_cpu_16(arp_pkt.ether_hdr.ether_type) <<
        // std::dec << "\n";

        if (rte_be_to_cpu_16(arp_pkt.ether_hdr.ether_type) != RTE_ETHER_TYPE_ARP)
            return Error::UNKNOWN_PROTOCOL;

        if (rte_be_to_cpu_16(arp_pkt.arp_hdr.arp_hardware) != RTE_ARP_HRD_ETHER)
            return Error::UNKNOWN_PROTOCOL;

        if (arp_pkt.arp_hdr.arp_hlen != 6)
            return Error::INVALID_MESSAGE;

        if (rte_be_to_cpu_16(arp_pkt.arp_hdr.arp_protocol) != RTE_ETHER_TYPE_IPV4)
            return Error::UNKNOWN_PROTOCOL;

        if (arp_pkt.arp_hdr.arp_plen != 4)
            return Error::INVALID_MESSAGE;

        switch (rte_be_to_cpu_16(arp_pkt.arp_hdr.arp_opcode)) {
            case RTE_ARP_OP_REQUEST: {
                const auto sip = arp_pkt.arp_hdr.arp_data.arp_sip;
                const auto sha = arp_pkt.arp_hdr.arp_data.arp_sha;
                addr_map.insert(std::make_pair(sip, sha));

                if (arp_pkt.arp_hdr.arp_data.arp_tip != own_ip)
                    return Error::INVALID_MESSAGE;

                rte_arp_hdr arp_data;
                arp_data.arp_hardware = rte_cpu_to_be_16(RTE_ARP_HRD_ETHER);
                arp_data.arp_protocol = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
                arp_data.arp_opcode = rte_cpu_to_be_16(RTE_ARP_OP_REPLY);
                arp_data.arp_hlen = 6;
                arp_data.arp_plen = 4;

                rte_ether_addr_copy(&arp_pkt.arp_hdr.arp_data.arp_sha,
                    &arp_data.arp_data.arp_tha);
                rte_ether_addr_copy(&own_mac, &arp_data.arp_data.arp_sha);
                arp_data.arp_data.arp_sip = own_ip;
                arp_data.arp_data.arp_tip = arp_pkt.arp_hdr.arp_data.arp_sip;

                auto resp =
                    RTEMbufElement<DefaultPacket, MbufType::Pkt>::init(mpool);
                if (!resp)
                    return Error::PACKET_CONSTRUCTION;

                if (ConstructARPPacket_(resp->get(), arp_data) != Error::ARP_OK)
                    return Error::PACKET_CONSTRUCTION;

                const auto send_res = send(std::move(*resp));
                if (!send_res)
                    return Error::IO_ERROR;

                return Error::ARP_OK;
            }
            case RTE_ARP_OP_REPLY: {
                const auto sip = arp_pkt.arp_hdr.arp_data.arp_sip;
                const auto sha = arp_pkt.arp_hdr.arp_data.arp_sha;
                addr_map.insert(std::make_pair(sip, sha));
                return Error::ARP_OK;
            }
        }
        return Error::UNKNOWN_OP;
    }

    template <typename Send, typename Recv>
    requires std::invocable<Send, RTEMbufElement<DefaultPacket, MbufType::Pkt>> &&
             std::invocable<Recv>
    Error RequestAddr(const in_addr_t addr, RTEMempool<DefaultPacket, MbufType::Pkt> &mpool,
        Send &send, Recv &recv) {
        // First erase the address if it already exists in the map
        EraseAddr(addr);

        size_t max_retry = 20;
        const size_t timeout_ms = 500;

        bool found = false;

        // Loop until the correct MAC has been found or until the max number of retries has
        // been reached
        while (max_retry-- && !found) {
            // First construct and send the ARP request
            auto req_buf = RTEMbufElement<DefaultPacket, MbufType::Pkt>::init(mpool);
            if (!req_buf)
                return Error::PACKET_CONSTRUCTION;

            const auto gen_res = GenAddrRequest_(addr, req_buf->get());
            if (gen_res != Error::ARP_OK)
                return gen_res;

            const auto send_res = send(std::move(*req_buf));
            if (!send_res)
                return Error::IO_ERROR;

            // Record current time as a reference for the loop
            auto start = std::chrono::steady_clock::now();
            size_t duration_ms = 0;

            // Loop for timeout_ms, receive packets continuously
            do {
                auto recvd = recv();

                for (auto &pkt : recvd) {
                    // Do not handle erroneous packets, we are only interested
                    // in valid ARP packets
                    ReceivePacket(pkt, mpool, send);

                    // Check if the requested address has already been inserted
                    // into map
                    if (GetEtherAddr(addr)) {
                        found = true;
                        break;
                    }
                }

                auto now = std::chrono::steady_clock::now();
                duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                    now - start)
                                  .count();

                // Stay in the loop when the timeout has not been reached yet and
                // the correct packet has not been found yet
            } while (duration_ms < timeout_ms && !found);
        }

        return found ? Error::ARP_OK : Error::NO_RESPONSE;
    }

    std::optional<rte_ether_addr> GetEtherAddr(const in_addr_t addr);

    void InsertAddr(const in_addr_t ip, const rte_ether_addr mac);

    size_t EraseAddr(const in_addr_t ip);

private:
    Error ConstructARPPacket_(RTEMbuf<DefaultPacket> &msg, rte_arp_hdr arp_data);

    Error GenAddrRequest_(const in_addr_t addr, RTEMbuf<DefaultPacket> &resp);

    std::unordered_map<in_addr_t, rte_ether_addr> addr_map;
    const in_addr_t own_ip;
    const rte_ether_addr own_mac;
};