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.
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:
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;
}
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
.