This purpose of this article is to explain how to send a raw Ethernet packet using C# on a Microsoft platform. A raw Ethernet packet is the complete Layer 2 network frame that is sent to the physical wire. Sending a frame like this allows you to manipulate the target and source MAC addresses and the Layer 3 protocol fields.
You may be thinking, "Why would anyone want to do this?". Well, I was trying to create an application (using C#) that would make a typical Windows computer with 2 NICs act as a Layer 2 Network device. My goal was to listen for packets on a network interface and send the exact same packet out of the opposite interface, basically a packet repeater. To do this, I needed to be able to read a raw Ethernet packet (easy) and then write that same raw Ethernet packet (difficult). The sent packet needed to be exactly like the read packet, Ethernet header and all. I did a great deal of research online, and did not find a whole lot of info, just a few hints here and there.
The first problem was that Windows does not include a way to programmatically send a raw Ethernet packet. After some research, I realized that I needed to create a NDIS Protocol Driver (PassThru and Intermediate drivers will also work) to interface with the network adapters at a very low level. Luckily, the Windows Driver Development Kits (DDKs) included samples that would accomplish this for me. Great, the hard part down right......yeah, that is what I thought too. Now I had to interface with the driver from managed C# code.
Well, enough of the background.....on to the code.....
Part 1 - NDIS Protocol Driver
So, like I said, the DDK provides a suitable NDIS driver for sending raw packets. I compiled this, creating the .inf and .sys files for the driver (I have included the compiled driver, altered to fit my needs, in the attached zip file). After running a few test, I found that I could:
- Only receive packets destined for me and
- I could only send packets with a source address of my adapter.
Well, this was not acceptable. I needed to receive any packets on my LAN segment, and send those same packets regardless of the source address. So after looking through the driver code, I figured out how to accomplish that.
To receive any packets, the driver had to be set to Promiscuous mode. The following code segment was what was altered to accomplish this.
// line 177
// Add NDIS_PACKET_TYPE_PROMISCUOUS to support promiscuous mode reading
#define NUIOO_PACKET_FILTER (NDIS_PACKET_TYPE_DIRECTED| \
NDIS_PACKET_TYPE_PROMISCUOUS) // **Added**
To send any packets, the following code segment had to be commented out
// line 136
// Comment out to support sending packets from any MAC source address
// To prevent applications from sending packets with spoofed
// mac address, we will do the following check to make sure the source
// address in the packet is same as the current MAC address of the NIC.
if ((pIrp->RequestorMode == UserMode) &&
DEBUGP(DL_WARN, ("Write: Failing with invalid Source address"));
NtStatus = STATUS_INVALID_PARAMETER;
Once those changes were made, the NDIS Driver performed perfect for what I needed.
Part 2 - C# RawEthernet Application
The code for the RawEthernet application is commented fairly well, so I am not going to go into a lot of detail on the code here. I am just going to highlight the important steps in the code.
Writing information to a device driver is somewhat similar to writing to a file. We open the driver file by calling the
CreateFile API. This returns a handle that we can use to write to and read from the driver. Next, we can bind the driver handle to a specific adapter by using the
DeviceIoControl API. Binding the adapter lets us access the NDIS Driver on a specific network adapter. After all this, the writing is simple. We use the
WriteFile API. The
ReadFile API can be used in a similar manner to read incoming network data as well.
To send a packet, we have to create a byte representation on the packet that we want to send. The following shows the Ethernet header (first 14 bytes of packet) in byte format
DD DD DD DD DD DD SS SS SS SS SS SS PP PP <data follows>
- D = Destination MAC Address
- S = Source MAC Address
- P = Next Layer Protocol (0800 = IP)
You can use a packet sniffer (Ethereal, Snoop, EtherPeeks) to verify that you are sending a raw data packet on the network medium. The packet that this application currently sends is a very simple data packet that served no purpose other than to show the concept. This can easily be changed to reflect a real packet, such as a ping or anything else that you can think of.
Running the sample
You can install the NDIS Driver by opening your network adapter properties and clicking the "Install" button, selecting "Protocol", and then choosing "Have Disk". Then browse to the .inf file and click "OK". This will then load the driver onto every adapter that you have in your system.
Important - Make sure that it is enabled, there should be a check in the box next to "Raw Packet NDIS Protocol Driver".
Important - Open a command prompt and type "net start ndisprot" to start the driver service.
Note - The beauty of having this driver is that you can disable every other protocol in the Adapter's protocol list (i.e. Internet Protocol) and you will still be able to send and receive packets. Your machine will not even have an address, but because we are working at Layer 2, you don't need one. (This driver will work even if you keep all of the other protocols enabled)
The zip file contains the source and compiled binary for the RawEthernet application. Once the driver is installed and enabled, simply run the EXE to see the packets being sent.
I have been working on getting this to work asynchronously so that I can send and receive on the same adapter at the same time. When I get some more time, I will put out an article on how async file works.
That is about it for now...if you have any questions or comments, feel free to contact me.