Capture Ethernet Frames with Linux Raw Sockets (PF_PACKET)
This tutorial explains how to use Linux raw sockets (PF_PACKET) to send and receive link‑layer Ethernet frames, covering socket creation, protocol selection, sockaddr_ll structure, interface binding, promiscuous mode, and provides a complete C example that prints MAC and IP information for each captured packet.
Overview
The article shows how to use Linux raw sockets not only to construct and transmit whole IP packets but also to directly send and receive Ethernet frames at the link layer. It demonstrates the required socket parameters, relevant structures, and a complete C program that prints MAC and IP details of captured packets.
Creating a Raw Socket for the Link Layer
Use the socket(PF_PACKET, type, protocol) call. The type argument can be SOCK_RAW (receives the full Ethernet frame, including the MAC header, so the application must build and parse the MAC header) or SOCK_DGRAM (the kernel strips the MAC header on receive and adds it on send). The protocol argument typically takes values such as ETH_P_IP, ETH_P_ARP, ETH_P_RARP or ETH_P_ALL. All possible values are defined in /usr/include/linux/if_ether.h.
sockaddr_ll Structure
The address structure used with PF_PACKET sockets is struct sockaddr_ll:
struct sockaddr_ll {
unsigned short sll_family; /* always AF_PACKET */
unsigned short sll_protocol; /* Ethernet protocol */
int sll_ifindex; /* interface index */
unsigned short sll_hatype; /* hardware type */
unsigned char sll_pkttype; /* packet type */
unsigned char sll_halen; /* address length */
unsigned char sll_addr[8];/* physical address */
};Key fields: sll_ifindex: set to 0 to listen on all interfaces, or to a specific interface index obtained via ioctl(SIOCGIFINDEX). sll_hatype: usually ARPHRD_ETHER for Ethernet. sll_pkttype: values such as PACKET_HOST, PACKET_BROADCAST, PACKET_MULTICAST, PACKET_OTHERHOST, PACKET_OUTGOING (relevant only for received packets). sll_addr and sll_halen: hold the hardware (MAC) address; length depends on the device.
Obtaining Interface Information
To get the index of an interface (e.g., eth0) and its MAC address:
struct ifreq ifr;
strcpy(ifr.ifr_name, "eth0");
ioctl(sockfd, SIOCGIFINDEX, &ifr);
int if_index = ifr.ifr_ifindex;
ioctl(sockfd, SIOCGIFHWADDR, &ifr);
unsigned char *mac = (unsigned char *)ifr.ifr_hwaddr.sa_data;Binding the Socket to an Interface
By default a PF_PACKET socket receives packets matching the specified protocol on all interfaces. Use bind() with a populated sockaddr_ll to restrict reception to a particular interface.
Example Packet‑Capture Program
The following C program creates a raw PF_PACKET socket, receives Ethernet frames, prints source and destination MAC addresses, extracts the IPv4 header (only when version==4 and ihl==5), and prints source and destination IP addresses.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, char **argv) {
int sock, n;
char buffer[2048];
struct ethhdr *eth;
struct iphdr *iph;
if ((sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0) {
perror("socket");
exit(1);
}
while (1) {
printf("=====================================
");
n = recvfrom(sock, buffer, sizeof(buffer), 0, NULL, NULL);
printf("%d bytes read
", n);
eth = (struct ethhdr *)buffer;
printf("Dest MAC addr:%02x:%02x:%02x:%02x:%02x:%02x
",
eth->h_dest[0], eth->h_dest[1], eth->h_dest[2],
eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]);
printf("Source MAC addr:%02x:%02x:%02x:%02x:%02x:%02x
",
eth->h_source[0], eth->h_source[1], eth->h_source[2],
eth->h_source[3], eth->h_source[4], eth->h_source[5]);
iph = (struct iphdr *)(buffer + sizeof(struct ethhdr));
if (iph->version == 4 && iph->ihl == 5) {
printf("Source host:%s
", inet_ntoa(*(struct in_addr *)&iph->saddr));
printf("Dest host:%s
", inet_ntoa(*(struct in_addr *)&iph->daddr));
}
}
return 0;
}Compile the program with gcc -o capture capture.c and run it as root.
Network‑Card Behavior and Promiscuous Mode
In normal (non‑promiscuous) mode a NIC only accepts frames whose destination MAC matches its own address. Exceptions are:
Broadcast frames (destination MAC ff:ff:ff:ff:ff:ff) are received by all NICs.
Multicast frames are received by NICs that have joined the corresponding multicast group.
When the NIC is placed in promiscuous mode, it receives every frame that traverses the link.
To enable promiscuous mode before the receive loop:
struct ifreq ethreq;
strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, ðreq) == -1) {
perror("ioctl");
close(sock);
exit(1);
}
ethreq.ifr_flags |= IFF_PROMISC;
if (ioctl(sock, SIOCSIFFLAGS, ðreq) == -1) {
perror("ioctl");
close(sock);
exit(1);
}After enabling promiscuous mode, the same capture loop will see all traffic on the selected interface.
Conclusion
The presented code forms a minimal packet‑capture tool based on raw PF_PACKET sockets. It can be extended with multithreading, protocol‑specific processing, or additional filtering to build more sophisticated network analysis utilities.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
