Sending raw Ethernet packets from a specific interface in C on Linux

2015/12/19 posted in  Network

Lately I’ve been writing some code to send packets to a specific MAC address from a specific interface. I’m sure this will come in handy again so here is how it goes:

Includes

    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <net/if.h>
    #include <netinet/ip.h>
    #include <netinet/udp.h>
    #include <netinet/ether.h>
    #include <linux/if_packet.h>

Open the raw socket:

    int sockfd;
    ...
    /* Open RAW socket to send on */
    if ((sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) == -1) {
        perror("socket");
    }

Get the index of the interface to send on:

    struct ifreq if_idx;
    ...
    memset(&if_idx, 0, sizeof(struct ifreq));
    strncpy(if_idx.ifr_name, "eth0", IFNAMSIZ-1);
    if (ioctl(sock, SIOCGIFINDEX, &if_idx) < 0)
        perror("SIOCGIFINDEX");  

Get the MAC address of the interface to send on:

    struct ifreq if_mac;
    ...
    memset(&if_mac, 0, sizeof(struct ifreq));
    strncpy(if_mac.ifr_name, "eth0", IFNAMSIZ-1);
    if (ioctl(sock, SIOCGIFHWADDR, &if_mac) < 0)
        perror("SIOCGIFHWADDR");

Get the IP address of the interface to send on:

    struct ifreq if_ip;
    ...
    memset(&if_ip, 0, sizeof(struct ifreq));
    strncpy(if_ip.ifr_name, "eth0", IFNAMSIZ-1);
    if (ioctl(sock, SIOCGIFADDR, &if_ip) < 0)
        perror("SIOCGIFADDR");

Construct the Ethernet header:

    int tx_len = 0;
    char sendbuf[1024];
    struct ether_header *eh = (struct ether_header *) sendbuf;
    ...
    memset(sendbuf, 0, 1024);
    /* Ethernet header */
    eh->ether_shost[0] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[0];
    eh->ether_shost[1] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[1];
    eh->ether_shost[2] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[2];
    eh->ether_shost[3] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[3];
    eh->ether_shost[4] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[4];
    eh->ether_shost[5] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[5];
    eh->ether_dhost[0] = MY_DEST_MAC0;
    eh->ether_dhost[1] = MY_DEST_MAC1;
    eh->ether_dhost[2] = MY_DEST_MAC2;
    eh->ether_dhost[3] = MY_DEST_MAC3;
    eh->ether_dhost[4] = MY_DEST_MAC4;
    eh->ether_dhost[5] = MY_DEST_MAC5;
    eh->ether_type = htons(ETH_P_IP);
    tx_len += sizeof(struct ether_header);  

Construct the IP header:

    struct iphdr *iph = (struct iphdr *) (sendbuf + sizeof(struct ether_header));
    ...
    /* IP Header */
    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 16; // Low delay
    iph->id = htons(54321);
    iph->ttl = ttl; // hops
    iph->protocol = 17; // UDP
    /* Source IP address, can be spoofed */
    iph->saddr = inet_addr(inet_ntoa(((struct sockaddr_in *)&if_ip.ifr_addr)->sin_addr));
    // iph->saddr = inet_addr("192.168.0.112");
    /* Destination IP address */
    iph->daddr = inet_addr("192.168.0.111");
    tx_len += sizeof(struct iphdr);  

Construct the UDP header:

    struct udphdr *udph = (struct udphdr *) (sendbuf + sizeof(struct iphdr) + sizeof(struct ether_header));
    ...
    /* UDP Header */
    udph->source = htons(3423);
    udph->dest = htons(5342);
    udph->check = 0; // skip
    tx_len += sizeof(struct udphdr);  

Fill in UDP payload:

    /* Packet data */
    sendbuf[tx_len++] = 0xde;
    sendbuf[tx_len++] = 0xad;
    sendbuf[tx_len++] = 0xbe;
    sendbuf[tx_len++] = 0xef;  

Fill in remaining header info:

    unsigned short csum(unsigned short *buf, int nwords)
    {
        unsigned long sum;
        for(sum=0; nwords>0; nwords--)
            sum += *buf++;
        sum = (sum >> 16) + (sum &0xffff);
        sum += (sum >> 16);
        return (unsigned short)(~sum);
    }
    ...
    /* Length of UDP payload and header */
    udph->len = htons(tx_len - sizeof(struct ether_header) - sizeof(struct iphdr));
    /* Length of IP payload and header */
    iph->tot_len = htons(tx_len - sizeof(struct ether_header));
    /* Calculate IP checksum on completed header */
    iph->check = csum((unsigned short *)(sendbuf+sizeof(struct ether_header)), sizeof(struct iphdr)/2);  

Send the raw Ethernet packet:

    /* Destination address */
    struct sockaddr_ll socket_address;
    ...
    /* Index of the network device */
    socket_address.sll_ifindex = if_idx.ifr_ifindex;
    /* Address length*/
    socket_address.sll_halen = ETH_ALEN;
    /* Destination MAC */
    socket_address.sll_addr[0] = MY_DEST_MAC0;
    socket_address.sll_addr[1] = MY_DEST_MAC1;
    socket_address.sll_addr[2] = MY_DEST_MAC2;
    socket_address.sll_addr[3] = MY_DEST_MAC3;
    socket_address.sll_addr[4] = MY_DEST_MAC4;
    socket_address.sll_addr[5] = MY_DEST_MAC5;
    /* Send packet */
    if (sendto(sock, sendbuf, tx_len, 0, (struct sockaddr*)&socket_address, sizeof(struct sockaddr_ll)) < 0)
        printf("Send failed\n");  

Code available here