Skip to content

Instantly share code, notes, and snippets.

@austinmarton
Created June 3, 2012 07:55
Show Gist options
  • Save austinmarton/2862515 to your computer and use it in GitHub Desktop.
Save austinmarton/2862515 to your computer and use it in GitHub Desktop.
Receive raw Ethernet frames in Linux
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/ether.h>
#define DEST_MAC0 0x00
#define DEST_MAC1 0x00
#define DEST_MAC2 0x00
#define DEST_MAC3 0x00
#define DEST_MAC4 0x00
#define DEST_MAC5 0x00
#define ETHER_TYPE 0x0800
#define DEFAULT_IF "eth0"
#define BUF_SIZ 1024
int main(int argc, char *argv[])
{
char sender[INET6_ADDRSTRLEN];
int sockfd, ret, i;
int sockopt;
ssize_t numbytes;
struct ifreq ifopts; /* set promiscuous mode */
struct ifreq if_ip; /* get ip addr */
struct sockaddr_storage their_addr;
uint8_t buf[BUF_SIZ];
char ifName[IFNAMSIZ];
/* Get interface name */
if (argc > 1)
strcpy(ifName, argv[1]);
else
strcpy(ifName, DEFAULT_IF);
/* Header structures */
struct ether_header *eh = (struct ether_header *) buf;
struct iphdr *iph = (struct iphdr *) (buf + sizeof(struct ether_header));
struct udphdr *udph = (struct udphdr *) (buf + sizeof(struct iphdr) + sizeof(struct ether_header));
memset(&if_ip, 0, sizeof(struct ifreq));
/* Open PF_PACKET socket, listening for EtherType ETHER_TYPE */
if ((sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETHER_TYPE))) == -1) {
perror("listener: socket");
return -1;
}
/* Set interface to promiscuous mode - do we need to do this every time? */
strncpy(ifopts.ifr_name, ifName, IFNAMSIZ-1);
ioctl(sockfd, SIOCGIFFLAGS, &ifopts);
ifopts.ifr_flags |= IFF_PROMISC;
ioctl(sockfd, SIOCSIFFLAGS, &ifopts);
/* Allow the socket to be reused - incase connection is closed prematurely */
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof sockopt) == -1) {
perror("setsockopt");
close(sockfd);
exit(EXIT_FAILURE);
}
/* Bind to device */
if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, ifName, IFNAMSIZ-1) == -1) {
perror("SO_BINDTODEVICE");
close(sockfd);
exit(EXIT_FAILURE);
}
repeat: printf("listener: Waiting to recvfrom...\n");
numbytes = recvfrom(sockfd, buf, BUF_SIZ, 0, NULL, NULL);
printf("listener: got packet %lu bytes\n", numbytes);
/* Check the packet is for me */
if (eh->ether_dhost[0] == DEST_MAC0 &&
eh->ether_dhost[1] == DEST_MAC1 &&
eh->ether_dhost[2] == DEST_MAC2 &&
eh->ether_dhost[3] == DEST_MAC3 &&
eh->ether_dhost[4] == DEST_MAC4 &&
eh->ether_dhost[5] == DEST_MAC5) {
printf("Correct destination MAC address\n");
} else {
printf("Wrong destination MAC: %x:%x:%x:%x:%x:%x\n",
eh->ether_dhost[0],
eh->ether_dhost[1],
eh->ether_dhost[2],
eh->ether_dhost[3],
eh->ether_dhost[4],
eh->ether_dhost[5]);
ret = -1;
goto done;
}
/* Get source IP */
((struct sockaddr_in *)&their_addr)->sin_addr.s_addr = iph->saddr;
inet_ntop(AF_INET, &((struct sockaddr_in*)&their_addr)->sin_addr, sender, sizeof sender);
/* Look up my device IP addr if possible */
strncpy(if_ip.ifr_name, ifName, IFNAMSIZ-1);
if (ioctl(sockfd, SIOCGIFADDR, &if_ip) >= 0) { /* if we can't check then don't */
printf("Source IP: %s\n My IP: %s\n", sender,
inet_ntoa(((struct sockaddr_in *)&if_ip.ifr_addr)->sin_addr));
/* ignore if I sent it */
if (strcmp(sender, inet_ntoa(((struct sockaddr_in *)&if_ip.ifr_addr)->sin_addr)) == 0) {
printf("but I sent it :(\n");
ret = -1;
goto done;
}
}
/* UDP payload length */
ret = ntohs(udph->len) - sizeof(struct udphdr);
/* Print packet */
printf("\tData:");
for (i=0; i<numbytes; i++) printf("%02x:", buf[i]);
printf("\n");
done: goto repeat;
close(sockfd);
return ret;
}
Copy link

ghost commented Apr 25, 2015

Thanks for sharing.

@workerNumber88
Copy link

Thanks for the software. This really helped to get input from the eth0 to my c program quick.

@rskrda
Copy link

rskrda commented Jun 16, 2016

perhaps #include <unistd.h> is missing as a compiler warning for implicit close definition occurs.

i've expanded this code significantly and joined it with the send example to relay packets. i'm adding thread support next for two-way relaying or a handy bridge.

one bug i've run into is that the kernel seems to re-assmble the TCP segments into large packets (>1500 bytes) requiring jumbo frames on the tx side. any comments on addressing this is appreciated.

@nnovzver
Copy link

nnovzver commented Jul 8, 2016

Label "done" should be on a next line.

@abrodkin
Copy link

Nice example.
I updated it a little-bit with addition of ability to set DST MAC in command line like ./sendRawEth enp0s29u1u1u2 2E:58:2F:73:73:40
If of any interest please find it here https://gist.github.com/abrodkin/8854874f3b34e92f71653a7fa35c9900

@Dambe
Copy link

Dambe commented Jan 3, 2017

Suggesting to use
#define ETHER_TYPE ETHERTYPE_IP
instead of
#define ETHER_TYPE 0x0800
ETHERTYPE_IP is already defined when including netinet/ether.h

@CurlyMoo
Copy link

You should set sockopt to 1 because at this moment it is undefined if SO_REUSEADDR is set or not.

@marian-pritsak
Copy link

SO_BINDTODEVICE works only with AF_INET

@AdrianTay
Copy link

HI. I am getting an error of "SO_BINDTODEVICE : NO SUCH DEVICE". Would deeply appreciate if I can get help from anybody here. I had tried googling the error but i still cant find a prominent solution.

@csBlueChip
Copy link

Thanks.
PS. I think your "done:" label is on the wrong line ;)

@sundeep95
Copy link

Can you please explain how to run these codes on both sender and receiver end using wlan0?

@ShooreshS
Copy link

ShooreshS commented Apr 12, 2020

Thanks a lot for sharing. It does not show 802.1q tags similar to another program that I got from here.
I can capture the complete ethernet frame including the VLAN tags using tcpdump. For some reason, these programs drop/exclude 802.1q tags.
Does anybody know the reason?

@Josh798
Copy link

Josh798 commented Oct 12, 2020

Warning: sockopt is undefined in this code. It probably should be initialized to 1.

@marco20240618
Copy link

marco20240618 commented Nov 4, 2022

I modify it a little bit, so that it only prints the packet with specified source or dest mac address.

#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <unistd.h>

// #define DEST_MAC0 0x00
// #define DEST_MAC1 0x00
// #define DEST_MAC2 0x00
// #define DEST_MAC3 0x00
// #define DEST_MAC4 0x00
// #define DEST_MAC5 0x00

#define DEST_MAC0 0x52
#define DEST_MAC1 0x54
#define DEST_MAC2 0x00
#define DEST_MAC3 0xcb
#define DEST_MAC4 0x4b
#define DEST_MAC5 0xca

#define ETHER_TYPE 0x0800

#define DEFAULT_IF "eth0"
#define BUF_SIZ 1024

int main(int argc, char *argv[])
{
	char sender[INET6_ADDRSTRLEN];
	int sockfd, ret, i;
	int sockopt;
	ssize_t numbytes;
	struct ifreq ifopts; /* set promiscuous mode */
	struct sockaddr_storage their_addr;
	uint8_t buf[BUF_SIZ];
	char ifName[IFNAMSIZ];

	/* Get interface name */
	if (argc > 1)
		strcpy(ifName, argv[1]);
	else
		strcpy(ifName, DEFAULT_IF);

	/* Header structures */
	struct ether_header *eh = (struct ether_header *)buf;

	/* Open PF_PACKET socket, listening for EtherType ETHER_TYPE */
	if ((sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETHER_TYPE))) == -1)
	{
		perror("listener: socket");
		return -1;
	}

	/* Set interface to promiscuous mode - do we need to do this every time? */
	strncpy(ifopts.ifr_name, ifName, IFNAMSIZ - 1);
	ioctl(sockfd, SIOCGIFFLAGS, &ifopts);
	ifopts.ifr_flags |= IFF_PROMISC;
	ioctl(sockfd, SIOCSIFFLAGS, &ifopts);
	/* Allow the socket to be reused - incase connection is closed prematurely */
	if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof sockopt) == -1)
	{
		perror("setsockopt");
		close(sockfd);
		exit(EXIT_FAILURE);
	}
	/* Bind to device */
	if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, ifName, IFNAMSIZ - 1) == -1)
	{
		perror("SO_BINDTODEVICE");
		close(sockfd);
		exit(EXIT_FAILURE);
	}

	while (1)
	{
		numbytes = recvfrom(sockfd, buf, BUF_SIZ, 0, NULL, NULL);
		if (
			// eh->ether_dhost[0] == DEST_MAC0 &&
			// eh->ether_dhost[1] == DEST_MAC1 &&
			// eh->ether_dhost[2] == DEST_MAC2 &&
			// eh->ether_dhost[3] == DEST_MAC3 &&
			// eh->ether_dhost[4] == DEST_MAC4 &&
			// eh->ether_dhost[5] == DEST_MAC5 &&
			eh->ether_shost[0] == 0x52 &&
			eh->ether_shost[1] == 0x54 &&
			eh->ether_shost[2] == 0x00 &&
			eh->ether_shost[3] == 0xb7 &&
			eh->ether_shost[4] == 0xd4 &&
			eh->ether_shost[5] == 0x39)
		{
			printf("source mac: %x:%x:%x:%x:%x:%x\n",
				   eh->ether_shost[0],
				   eh->ether_shost[1],
				   eh->ether_shost[2],
				   eh->ether_shost[3],
				   eh->ether_shost[4],
				   eh->ether_shost[5]);
			printf("destination mac: %x:%x:%x:%x:%x:%x\n",
				   eh->ether_dhost[0],
				   eh->ether_dhost[1],
				   eh->ether_dhost[2],
				   eh->ether_dhost[3],
				   eh->ether_dhost[4],
				   eh->ether_dhost[5]);

			printf("Data: ");
			for (i = 0; i < numbytes; i++)
				printf("%02x ", buf[i]);
			printf("\n\n");
		}
	}
}

@slothfull
Copy link

slothfull commented Jun 21, 2024

thanks for these two send/recv progam, really helps for a greener like me!
hello, currently Im trying to chain the send & recv program together:

i've got eth0 & docker0 bridge, try send raw pkts using the sender via docker0 to eth0:

$ ./send docker0   # set the pkt dest mac as eth0's mac

try recv raw pkts by receiver via eth0:

$ ./recv eth0      # set pkt dest mac as eth0's mac

however, nothing shown.

try capture the pkts on eth0 using tcp, got ya:

$ tcpdump -nettti eth0 '(ether dst host fa:16:xx:xx:xx:xx)' -vvnnXX -l | grep beef
         0x0010:  beef                                     ..

actually the pkt got send, but not captured by recv program.
so any way to point me out there, some thing missing?

Actually I forget about the iptables,, the answer is that: raw pkt are simply layer2,
the iptables rules get some filter on route from docker0 <-> eth0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment