For the loopback driver provided in the previous article, we write a simple RAW socket program to send a packet directly to the virtual network driver. The code is provided below for reference.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#define BUF_SIZE 1500
int main(int argc, char *argv[])
{
int sockfd;
struct ifreq ifr;
struct sockaddr_ll socket_address;
unsigned char sendbuf[BUF_SIZE];
unsigned char recvbuf[BUF_SIZE];
int ifindex;
ssize_t numbytes;
if (argc != 2) {
printf("Usage: %s <interface>\n", argv[0]);
return 1;
}
/* Create raw socket */
sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd < 0) {
perror("socket");
return 1;
}
/* Get interface index */
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1) {
perror("SIOCGIFINDEX");
close(sockfd);
return 1;
}
ifindex = ifr.ifr_ifindex;
/* Get interface MAC */
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1) {
perror("SIOCGIFHWADDR");
close(sockfd);
return 1;
}
unsigned char src_mac[6];
memcpy(src_mac, ifr.ifr_hwaddr.sa_data, 6);
/* Construct Ethernet frame */
memset(sendbuf, 0, BUF_SIZE);
struct ethhdr *eth = (struct ethhdr *)sendbuf;
/* Destination MAC = same as source (loopback) */
memcpy(eth->h_dest, src_mac, 6);
memcpy(eth->h_source, src_mac, 6);
eth->h_proto = htons(ETH_P_ALL);
char *payload = (char *)(sendbuf + sizeof(struct ethhdr));
strcpy(payload, "Vivek sent a packet!");
int frame_len = sizeof(struct ethhdr) + strlen(payload) + 1;
/* Prepare socket address */
memset(&socket_address, 0, sizeof(socket_address));
socket_address.sll_ifindex = ifindex;
socket_address.sll_halen = ETH_ALEN;
memcpy(socket_address.sll_addr, src_mac, 6);
printf("Sending packet...\n");
if (sendto(sockfd, sendbuf, frame_len, 0,
(struct sockaddr *)&socket_address,
sizeof(socket_address)) < 0) {
perror("sendto");
close(sockfd);
return 1;
}
printf("Waiting to receive echoed packet...\n");
numbytes = recv(sockfd, recvbuf, BUF_SIZE, 0);
if (numbytes < 0) {
perror("recv");
close(sockfd);
return 1;
}
struct ethhdr *reth = (struct ethhdr *)recvbuf;
char *recv_payload = (char *)(recvbuf + sizeof(struct ethhdr));
printf("Received packet (%ld bytes)\n", numbytes);
printf("Payload: %s\n", recv_payload);
close(sockfd);
return 0;
}
The above code is pretty much self explanatory. However, a short description is provided below
- A raw socket is created
- The interface name is a parameter that is passed to the application
- From the interface name, the interface index and the MAC address that was provided for that interface is extracted
- The core networking subsystem ioctl handling code usually at /net/core/dev_ioctl.c handles the SIOCGIFINDEX and SIOCGIFHWADDR ioctl calls
- packet is formulated
- packet is sent to the loopback networking driver
- The loopback networking driver processes the packet and sends it back up the stack
- The application is waiting on the socket to receive the packet from the networking driver
- It prints out the received packet
The loopback driver load and application output is shown below

In the next article, we will add more functionality to the networking driver as we progress in providing this skeleton driver various capabilities starting with the ability to handle IOCTLs. We will understand IOCTLs, their history and why they are being slowly replaced with netlink.