csocketsnetwork-programmingarp

Why does recvfrom() constantly output -1 in my code?


I'm practicing in networking programming. Currently I'm trying to make a program that crafts a single ARP packet and receives the response. Further, I want to expand it to handle the whole subnet.

But, I have a problem with the recvfrom() function. It always returns -1 and the error is: recvfrom: Resource temporarily unavailable. The code successfully sends the ARP packet, but never gets the response. However, responses do exist. I saw it with a packet sniffer. They just don't arrive to my program for some reason.

This code has a small config.txt file which looks like:

INTERFACE
MY MAC ADDRESS
BROADCAST MAC

I hardcoded "wlo1" in the code because if_nametoindex() constantly returns 0 instead of the index, but this is a question for another post.

I am a beginner, so don't throw too many rotten tomatoes, please.

main.c

#include "arpscanner.h"

int main(int argc, char* argv[])
{
    struct Config cfg;
    struct sockaddr_ll addr;
    struct ethhdr ehdr;
    struct myarphdr ahdr;
    unsigned char packet[PACKETLEN];

    ParseArg(argc, argv, &cfg);
    LoadConfig(&cfg, "config.txt");
    int sockfd = CreateRawSocket();
    BindRawSocket(sockfd, &addr, &cfg);
    CreateEthernetHeader(&ehdr, &cfg);
    CreateARPHeader(&ahdr, &cfg);
    ConstructPacket(packet, &ehdr, &ahdr);
    SendPacket(sockfd, packet, &addr);
    ListenPacket(sockfd, packet);
    close(sockfd);

    return 0;
}

arpscanner.h

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <fcntl.h>

#define IPV4_ADDR_LEN 4
#define PACKETLEN 42 //42 for ARP packet


struct Config {
    char interface[16];
    unsigned char src_mac[6];
    unsigned char dst_mac[6];
    uint32_t src_ip;
    uint32_t dst_ip;
};

#pragma pack(1)
struct myarphdr {
    uint16_t hardware_type;     // Hardware type (e.g., Ethernet)
    uint16_t protocol_type;     // Protocol type (e.g., IPv4)
    uint8_t hardware_size;      // Length of hardware address (6 for MAC)
    uint8_t protocol_size;      // Length of protocol address (4 for IPv4)
    uint16_t operation;          // Operation (1 for request, 2 for reply)
    uint8_t sender_mac[ETH_ALEN]; // Sender's MAC address
    uint32_t sender_ip;         // Sender's IP address
    uint8_t target_mac[ETH_ALEN]; // Target's MAC address
    uint32_t target_ip;
};

void ParseArg(int argc, char* argv[], struct Config* cfg);
void LoadConfig(struct Config* cfg, const char* filename);
int CreateRawSocket();
void BindRawSocket(int sockfd, struct sockaddr_ll* addr, struct Config* cfg);
void CreateEthernetHeader(struct ethhdr* ehdr, struct Config* cfg);
void CreateARPHeader(struct myarphdr* ahdr, struct Config* cfg);
uint32_t RetrieveLocalIP();    //used in CreateARPHeader()
void ConstructPacket(unsigned char* packet, struct ethhdr* ehdr, struct myarphdr* ahdr);
void SendPacket(int sockfd, unsigned char* packet, struct sockaddr_ll* addr);
void ListenPacket(int sockfd, unsigned char* packet);
void PrintMAC(unsigned char* address);  //Used in ListenPacket()
void PrintIP(uint32_t* address);

arpscanner.c

#include "arpscanner.h"

void ParseArg(int argc, char* argv[], struct Config* cfg) {
    if(argc != 2) {
        printf("Wrong input\n");
        exit(EXIT_FAILURE);
    }

    struct in_addr addr;
    inet_pton(AF_INET, argv[1], &addr);
    cfg->dst_ip = addr.s_addr;
}

void LoadConfig(struct Config* cfg, const char* filename) {
    FILE* file = fopen(filename, "r");
    if(!file) {
        perror("fopen");
        exit(EXIT_FAILURE);
    }

    if(fscanf(file, "%15s", cfg->interface) != 1) goto fail;
    if(fscanf(file, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &cfg->src_mac[0], &cfg->src_mac[1], &cfg->src_mac[2], &cfg->src_mac[3], &cfg->src_mac[4], &cfg->src_mac[5]) != 6) goto fail;
    if(fscanf(file, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &cfg->dst_mac[0], &cfg->dst_mac[1], &cfg->dst_mac[2], &cfg->dst_mac[3], &cfg->dst_mac[4], &cfg->dst_mac[5]) != 6) goto fail;
    fclose(file);
    return;

    fail:
        fclose(file);
        printf("Failed to parse the config file\n");
        exit(EXIT_FAILURE);
}

int CreateRawSocket() {
    int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
    if(sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    return sockfd;
}

void BindRawSocket(int sockfd, struct sockaddr_ll* addr, struct Config* cfg) {
    //fill sockaddr_ll;
    addr->sll_family = AF_PACKET;
    addr->sll_protocol = htons(ETH_P_ARP);
    addr->sll_ifindex = if_nametoindex("wlo1");
    if(addr->sll_ifindex == 0) {
        perror("if_nametoindex");
        exit(EXIT_FAILURE);
    }
    addr->sll_hatype = htons(1);
    addr->sll_pkttype = 0;
    addr->sll_halen = ETH_ALEN;
    memcpy(addr->sll_addr, cfg->src_mac, ETH_ALEN);

    //bind raw socket
    if(bind(sockfd, (struct sockaddr*)addr, sizeof(struct sockaddr_ll)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }
}

void CreateEthernetHeader(struct ethhdr* ehdr, struct Config* cfg) {
    memcpy(ehdr->h_dest, cfg->dst_mac, ETH_ALEN);
    memcpy(ehdr->h_source, cfg->src_mac, ETH_ALEN);
    ehdr->h_proto = htons(ETH_P_ARP);
}

void CreateARPHeader(struct myarphdr* ahdr, struct Config* cfg) {
    ahdr->hardware_type = htons(1); // 1 for ethernet
    ahdr->protocol_type = htons(ETH_P_IP);
    ahdr->hardware_size = ETH_ALEN;
    ahdr->protocol_size = IPV4_ADDR_LEN;
    ahdr->operation = htons(1); //1 for request
    memcpy(ahdr->sender_mac, cfg->src_mac, ETH_ALEN);
    ahdr->sender_ip = RetrieveLocalIP();
    memcpy(ahdr->target_mac, cfg->dst_mac, ETH_ALEN);
    ahdr->target_ip = cfg->dst_ip;
}

//used in CreateARPHeader()
uint32_t RetrieveLocalIP() {
    struct sockaddr_in* addr;
    struct ifaddrs *ifaddr, *ifa;
    if(getifaddrs(&ifaddr) == -1) {
        perror("getifaddrs");
        exit(EXIT_FAILURE);
    }

    for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if(ifa->ifa_addr->sa_family == AF_INET) {
            addr = (struct sockaddr_in*)ifa->ifa_addr;
        }
    }

    freeifaddrs(ifaddr);
    return addr->sin_addr.s_addr;
}

void ConstructPacket(unsigned char* packet, struct ethhdr* ehdr, struct myarphdr* ahdr) {
    memset(packet, 0, PACKETLEN);
    memcpy(packet, ehdr, sizeof(struct ethhdr));
    memcpy(packet + sizeof(struct ethhdr), ahdr, sizeof(struct myarphdr));
}

void SendPacket(int sockfd, unsigned char* packet, struct sockaddr_ll* addr) {
    if(sendto(sockfd, packet, PACKETLEN, 0, (struct sockaddr*)addr, sizeof(*addr)) == -1) {
        perror("sendto");
        exit(EXIT_FAILURE);
    } else {
        printf("Packet sent successfully\n");
    }
}
void ListenPacket(int sockfd, unsigned char* packet) {
    struct sockaddr raddr = {0};
    socklen_t addr_len = sizeof(raddr);

    struct timeval timeout = { .tv_sec = 3, .tv_usec = 0 };
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    while(1) {
        ssize_t num_bytes = recvfrom(sockfd, packet, PACKETLEN, 0, (struct sockaddr*)&raddr, &addr_len);
        if(num_bytes == -1) {
            perror("recvfrom");
            exit(EXIT_FAILURE);
        }

        if(num_bytes != PACKETLEN) continue;

        struct ethhdr* eth_hdr = (struct ethhdr*)packet;
        if(eth_hdr->h_proto != ETH_P_ARP) {
            continue;
        }

        struct myarphdr* arp_hdr = (struct myarphdr*)(packet + sizeof(struct ethhdr));
        if(arp_hdr->operation != 2) {
            continue;
        }

        PrintMAC(arp_hdr->sender_mac);
        PrintMAC(arp_hdr->target_mac);
        PrintIP(&arp_hdr->sender_ip);
        PrintIP(&arp_hdr->target_ip);

    }
}

//Used in ListenPacket()
void PrintMAC(unsigned char* address) {
    struct ether_addr addr;
    memcpy(&addr.ether_addr_octet, address, ETH_ALEN);
    char* mac = ether_ntoa(&addr);
    printf("%s\n", mac);
}

//Used in LIstenPacket()
void PrintIP(uint32_t* address) {
    char ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, address, ip, INET_ADDRSTRLEN);
    printf("%s\n", ip);
}

The recvfrom: Resource temporarily unavailable error typically arises with non-blocking sockets, but sockets are typically blocking by default. I double-checked it in my code.


Solution

  • I believe the problem might be the following:

    You indeed receive ARP replies, but fail to compare the packet types due to endianess mismatch here:

    You should wrap htons() around ETH_P_ARP and 2 for the operation (or use a define like ARPOP_REPLY).

    This means you consumed all reply packets and then ran into the timeout you set with SO_RCVTIMEO .