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

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:
(might not need all of these)

#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");

Update:
As in the comments, I’ve written a working example that can be found here: https://gist.github.com/1922600

Change the destination MAC address (e.g. 00:11:22:33:44:55) and compile:

gcc sendRawEth.c -o sendRawEth

In one terminal run tcpdump to observe the packets:

sudo tcpdump -nettti eth0 '(ether dst host 00:11:22:33:44:55)'

And in another run the program as root:

sudo ./sendRawEth eth0

References:
http://aschauf.landshut.org/fh/linux/udp_vs_raw/ch01s03.html
http://www.tenouk.com/Module43a.html
http://linux.die.net/man/3/sendto

About these ads

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

    • Hi Jon,

      I’m stoked someone else has found it useful! Those snippits should include everything you need but I’ll try put a full compiling example up on github for you soon.

      Cheers,
      Austin.

  1. I have it working :-)

    I’m sending 8 UDP broadcast packets each with a different IP address from a single ethernet card. Its being used to test an audio streaming application (it emulates 8 stream sources at once). Linux will let you have multiple IP addresses per card, but it seems will not let you elect to UDP broadcast from a specific one. Many thanks, great work, Jon

    • Hi Awais,

      Of course it is possible to send a raw Ethernet packet in C! Just ignore the section of the above code that does the UDP and IP headers.

      I’ve written a little example here:
      https://gist.github.com/1922600

      If you compile and run that app as root you can view the packets going out on the specified interface using wireshark.

      Cheers,
      Austin.

  2. Hi Austin,
    I have tried your example code https://gist.github.com/1922600 . Its compilation is fine with no errors but I’m not getting packets going out of the interface using wireshark……..Just for confirmation…. Have you run that code or you have just provided the code???….And guide me if there are more steps involved which I am not completing….And one thing more At run time what arguements is it needed..??
    Thanks

    • I have compiled and run the code on Linux:
      gcc sendRawEth.c -o sendRawEth
      You will need to run the executable as root, or using “sudo”:
      sudo ./sendRawEth eth0
      Check that you are watching on the same interface as the packet is being sent.

      Also, because I have used the IP EtherType (0×0800) and then just put random data in, it will come up as “Malformed Packet” in Wireshark. Unlikely but possible your NIC would reject it on that basis but you should see it in Wireshark at least.

      Cheers,
      Austin.

      • Sorry I shouldn’t have said “random” data, I was referring to the bytes that I had hard coded as the packet data (0xde, 0xad, 0xbe, 0xef – see line 81 of the gist).

        You can easily add your own payload by copying the bytes into ‘sendbuf’ and incrementing ‘tx_len’.

        I could add some code to parse packet data from a command line argument if needed.

      • Where exactly would you expect to see the data if viewing packets in Wireshark?? What I’m trying to do is send raw data to a wireless transceiver that tells a PIC chip to turn on a SSR. Any ideas? YThanks for the help!

    • I too witnessed “packet not going out” which had me going for some time. I was using an Asus laptop with its built-in NIC set up with six VLANs. All the VLANs were basically working in that I could ping devices on all different VLANs, and then look at the arp table and see the expected enteries against each sub-interface, but packets generated using this code were not going out – not according to tcpdump, at least.

      The same software was working on another machine in the same version of Fedora, so I was pretty sure the software was correct.

      In the end, I added a second NIC (elderly 100Mbit/s Belkin, USB-attached), reconfigured my interfaces to run over that NIC and everything started working correctly.

      My tentative conclusion is that the drivers or the hardware of the built-in NIC were not handling RAW sockets correctly over sub-interfaces.
      The suspect NIC reports as “Tigon3 [partno(BCM95764m) rev 5784100]” in a dmesg report, and its MAC is interpreted as Wistron by Wireshark. Kernel version is “2.6.35.14-106″

  3. Hi Austin,
    I am a student, currently trying to develop an Ethernet communication link using a research protocol. But I need first to send raw ethernet packets from a machine to an other using MAC addresses. Since I’m a newbie in the field, I would need some help if you don’t mind.
    I downloaded your file but I think I need the “receive” file to run on the other machine. Can you provide me with this?

    Cheers,
    Mehdi

      • Hi Mehdi,
        Don’t worry I haven’t forgotten about this. I will try find some time to put up the receive code soon.
        Cheers,
        Austin.

  4. Actually, I’m trying to learn by myself how to code with RAW sockets. Do you have any kind of tutorial for newbies or some step–by-step guide? Thank you for your help :)

  5. Hi Austin

    I tried your code from the github gist posting and it compiles fine but I can’t see any packet going out on eth0.

    I changed MY_DEST_MAC0 to my destination MAC in the same LAN using the format 0×001122334455.

    Then I setup tcpdump -nettti eth0 |grep 44:55 to find a packet going to that mac address but nothing comes out. I also tried without the grep but with a bpf to filter out the noise on heavily used ports but still no packet.

    So what am I doing wrong here?

    • I observed the same problem when using grep on tcpdump, but when I ran tcpdump by itself I can see the packets (on a separate interface that isn’t flooded with other traffic).

      In short – you need to use tcpdump filters instead of grep, the following command will show packets with destination MAC address 00:11:22:33:44:55 on interface eth0:
      sudo tcpdump -nettti eth0 '(ether dst host 00:11:22:33:44:55)'

      Cheers,
      Austin.

      • You’re right I do faintly remember being told never to grep tcpdump, thanks for showing how to filter out mac addresses.

        However, this time I tried it with a filter setting of ether dst host 11:22:33… on a LAN and still I see no packets. I tried pinging the same LAN host that I stole the destination mac address from and sure enough tcpdump shows an icmp echo request on that mac.

      • Not sure what’s different with your set up. I modified the code from github like so:
        #define MY_DEST_MAC0 0x00
        #define MY_DEST_MAC1 0x11
        #define MY_DEST_MAC2 0x22
        #define MY_DEST_MAC3 0x33
        #define MY_DEST_MAC4 0x44
        #define MY_DEST_MAC5 0x55

        recompiled:
        gcc sendRawEth.c -o sendRawEth
        then ran tcpdump:
        sudo tcpdump -nettti eth0 '(ether dst host 00:11:22:33:44:55)'
        then ran the program:
        sudo sendRawEth
        and in tcpdump I can see the packets.

        Tested on Ubuntu 10.04 and 11.10.

      • Oh I see Austin! This is exactly why I specified the format I used on the mac address, because I wasn’t sure it was correct. You’ve shown me now that the correct format is to use all 6 macros.

        And now it works! You’re the best Austin. <3

      • Jenius! Austin! You save me. haha. For me, I use this example for wireless (e.g. ad-hoc), the both wireless cards have a same essid & channel. And run : sudo tcpdump -nettti wlan0 ‘(ether dst host 00:11:22:33:44:55)’ On the other card: sudo ./sendRawEth wlan0 everything goes well…..

      • That’s very cool! I hadn’t thought about using it for ad-hoc wifi networks. Thanks for the info. Austin.

  6. This code is fantastic, thanks so much for posting it. Tried out the linked github source and it worked flawlessly the first time. After beating my head against networking code for several hours, this made my day.

  7. Hi Austin,

    thanks for your code. Especially for the gist. I thought it is bad to compile the MAC address in the program, so I forked your gist and wrote the bits to specify the MAC address with a command line argument.

    Kind regards,
    Thomas

  8. Wow. You are a LIFESAVER! I have been searching for a clear example of using raw sockets online for weeks, but everyone always says “Don’t use them!” and provides no information. This really saves my bacon. Thanks!

  9. Hi great post and very useful, using this ive been able to send Ethernet packets over the wireless interface (802.11) just needed to know what kind of sorcery is this :P and how its even possible as ethernet (wired) and Wlan (wireless) follow different L2 protocol structures

    However Thanks for this great post!

  10. hey ,
    i need this code urgent ” Sending raw Ethernet packets from a specific interface in C on Linux” can u please send it to my email
    thanks

  11. Hi all,
    i have raw IPSEC protected packet which i have to send to my Ethernet interface,

    Dst Addr : my IP , MAC address
    Src Addr : Tunnel’s other end point host ip and MAC,

    1) Whether it will receive in the application running on my host ?

    2) Will it go through ip(ipsec) layer of my host for decrypting the packet and pass to the application ?

    While Trying this in wireshark, IPSEC packet can be captured but application is not received the packet…

    Please help me.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s