Sunday, December 4, 2011

Sending raw Ethernet frames in 6 easy steps

Part of my work entails building a protocol stack in C that lives alongside TCP/IP and which is constructed entirely in user space, save for a few standard system calls. Hence, I need the ability to craft raw Ethernet frames and send them using only the facilities the operating system provides.

Fortunately, I have two things going for me. First, the implementation is all being done under Linux (in particular, a 2.6.32 kernel, though I believe any 2.6.x kernel will do). Second, a few other authors have gone before me in deciphering the man pages and system calls and have put together some great example codes. Most notably, Andreas Schaufler, whose writeup inspired my implementation as well as the code in this post.

So why write more on the subject? Because it takes time to understand all the moving parts and pieces, and most of us are operating under deadlines. What I hope to contribute is a one-stop, soup-to-nuts explanation with example code to save time for the next person. I'm going to assume you are somewhat comfortable with the C programming language and have seen or done some network programming before. However, my goal is to make this as painless as possible.

Starting point

First, let's try to make things easy for whomever is using this code. If they want to send a raw Ethernet frame, what things can we reasonably ask them to provide?
  • The destination MAC address would be nice, for sure. If they only know the IP, you can either have them look up the MAC with the arp command or code that lookup into your software. Let's assume you've done this if needed.
  • Since the frame needs to go out some particular Ethernet interface, we'll assume they know this already and can specify it by its "friendly" name, e.g., "eth0". The source MAC is needed as well, but we can look that up from the interface, assuming they're not trying to fake it. If they are, I will point out where to do this.
  • An Ethernet protocol number would also be good to know. This is the value that tells the receiving system which protocol is contained inside the frame. IP uses 0x0800, but perhaps you, like me, want to craft your own protocol that isn't processed by the TCP/IP stack.
  • Finally, the bytes you want to put into the message. This part is entirely up to you. Maybe you want to inject the contents of a captured packet, or some payload of your own creation. All we care about is that the bytes are stored somewhere (we'll say a C byte or "char" array) and you know the number of bytes to be sent. (We'll also assume you're staying within the number of bytes that fit in a frame, a.k.a. the Maximum Transmission Unit or MTU.)
Given these four pieces of information, let's walk through the code to send a frame. It will take six steps, listed below. Depending on your starting point, you may omit some of these steps.
  1. Defining our input and output data
  2. Building a raw "packet" socket
  3. Looking up the interface index and MAC address
  4. Filling in the packet contents
  5. Filling in the link layer socket address structure
  6. Sending the packet

Defining data

We need to define variables for the information described above, plus the actual frame format. Here I give hard-coded definitions to demonstrate what kind of data the rest of the code expects:

  char *iface = "eth0";
  unsigned char dest[]
           = { 0x00, 0x12, 0x34, 0x56, 0x78, 0x90 };
  unsigned short proto = 0x1234;
  unsigned char data[] = "hello world";
  unsigned short data_len = strlen(data);

As described above, the interface is given by its friendly name. We will derive all other necessary information about the interface inside this code. Destination is specified as a MAC address; the byte array dest corresponds to the address 00:12:34:56:78:90. The protocol field is 16 bits; we will come back to this shortly (haha, it's a pun! ... *ahem* okay, moving right along...). The frame data payload is whatever you want it to be, but eventually it will end up as a byte array also, so we'll assume you've already serialized your data somehow.

For your application, these values might be hand-jammed, come from command line arguments, or from another part of your program. And not to get parental, but don't forget to add sanity checks to all these inputs unless you really, really trust them!

At the end of the day, all these variables have to be crunched together as a byte array, because that's what the sending function will expect. One important difference from using "normal" sockets is that we have to specify the entire frame, including headers. The only things done for us (by hardware) are the preamble and CRC (error-checking). Let's create a data structure to make this easier later:

  union ethframe
  {
    struct
    {
      struct ethhdr    header;
      unsigned char    data[1500];
    } field;
    unsigned char    buffer[1514];
  };

The structs break out the components of the frame (struct ethhdr is provided in the include file <linux/if_ether.h>), and the union lets us treat the data as either separate fields or a single byte array. I won't go into the details of the Ethernet frame format; for our purposes, the "basic" frame consists of three header fields (source, destination, and protocol) and data. The protocol field is simply an unsigned short integer (two bytes) in network byte order. Source and destination are both MAC addresses, which are six bytes each. The overall frame length is limited to 1514 bytes, meaning the maximum data length is 1500 bytes (1514 - 2 - 6 - 6).

Wow, a lot of numbers to remember, right? The include file <linux/if_ether.h> defines all these and more as constants, so we don't have to remember them. Instead, we can define our structure like this:

  union ethframe
  {
    struct
    {
      struct ethhdr    header;
      unsigned char    data[ETH_DATA_LEN];
    } field;
    unsigned char    buffer[ETH_FRAME_LEN];
  };

Building the socket

Next we need to build the socket we will use to send our frame. We are doing this early in the code because we also need the socket to look up the index and MAC address of the interface (this will be explained below).

  int s;
  s = socket(AF_PACKET, SOCK_RAW, htons(proto));

If you are not familiar with creating network sockets, this is roughly the networking equivalent of opening a file to read or write. What makes this instance unusual are the particular arguments supplied. For TCP and UDP sockets, the first argument is normally AF_INET (address family Internet), and the second argument is normally SOCK_DGRAM for UDP datagrams and SOCK_STREAM for TCP streams. The combination of AF_PACKET and SOCK_RAW tells the operating system to create a socket beneath the level of the entire TCP/IP stack. Now is probably a good time to point out that in general, only the superuser (root) can do this. The third argument is the protocol field, converted to network byte order by htons (host-to-network-short). The only header needed for this part should be <sys/socket.h>, though some systems may require <netinet/in.h> for htons.

Assuming all went well, s is now set to a socket descriptor (value > 0). If its value is less than zero, an error occurred, possibly due to permissions. 

Looking up interface properties

The user has provided us with an interface name, maybe "eth0". But there is no Linux command to send packets from eth0; we have to look up an internal number used to identify the interface, referred to as the interface index. Since the user did not supply us with a source MAC address, we will also look this up.

Okay, this might be the least straightforward step, and I'm going to ask you to hit the "I Believe" button a bit here. Linux has an interface to many underlying system properties, called ioctl (Input-Output-Control). The inner workings of ioctl are by many considered to be dark and scary, but I will walk you through how to use it without dragging you through how it works.

Here is the code for looking up the interface index:

  struct ifreq buffer;
  int ifindex;
  memset(&buffer, 0x00, sizeof(buffer));
  strncpy(buffer.ifr_name, iface, IFNAMSIZ);
  ioctl(s, SIOCGIFINDEX, &buffer);
  ifindex = buffer.ifr_ifindex;

Briefly, ioctl is capable of querying properties of network interfaces and storing them in a buffer we supply as an argument. The include file <linux/if.h> provides a giant struct, ifreq, used for specifying these queries and storing the results. We first need to (well, ought to) zero out the contents of our struct and load it with the friendly name of the interface (memset and strncpy, respectively). The constant IFNAMSIZ defines the maximum length of friendly interface names for the operating system. We then invoke ioctl on an open socket (as far as I know, it doesn't matter what kind of open socket) with a constant specifying what property we want, and ioctl will load the struct with the corresponding result, which we store into ifindex.

Likewise, we can look up the source MAC address this way:

  unsigned char source[ETH_ALEN];
  ioctl(s, SIOCGIFHWADDR, &buffer)
  memcpy((void*)source, (void*)(buffer.ifr_hwaddr.sa_data), 
         ETH_ALEN);

I've skipped a couple key details about how ioctl and ifreq structs work. Make sure you copy the results of each ioctl before invoking it again, as previous results may get overwritten. Like socket, ioctl returns < 0 on error. There are many other interface properties that you can look up as well, including the MTU (SIOCGIFMTU) and timestamp of the last packet received on that specific socket (SIOCGSTAMP). See <bits/ioctls.h> and <sys/socket.h> for more.

Filling in the packet fields

We're in the home stretch! Just some data structures to populate and it's time to fire this packet off! Let's go ahead and fill in the packet fields next.

  union ethframe frame;
  memcpy(frame.field.header.h_dest, dest, ETH_ALEN);
  memcpy(frame.field.header.h_source, source, ETH_ALEN);
  frame.field.header.h_proto = htons(proto);
  memcpy(frame.field.data, data, data_len);

We'll create an instance of the union we defined earlier, but initially treat it as the internal struct so we can set fields individually without worrying about typecasting. Struct ethhdr defines the standard three-field Ethernet header, with an "h_" preceding the field names. Since the addresses are byte arrays, we use memset to copy them. Again, proto must be converted to network byte order (unless you did this earlier and stored it for later use... but that would be WAY too efficient for our sensibilities, right?). As for the payload data, we'll assume that everything fits within the allocated space, but a quick check that data_len < ETH_DATA_LEN wouldn't hurt either.

Filling in the sockaddr_ll struct

This should be a familiar step for anyone who has used the sendto system call. But wait, there's a twist! What is this _ll on the end? As it turns out, there is a variant of the sockaddr struct just for packet sockets. This is where ifindex comes in handy.

  struct sockaddr_ll saddrll;
  memset((void*)&saddrll, 0, sizeof(saddrll));
  saddrll.sll_family = PF_PACKET;   
  saddrll.sll_ifindex = ifindex;
  saddrll.sll_halen = ETH_ALEN;
  memcpy((void*)(saddrll.sll_addr), (void*)dest, ETH_ALEN);
 
Struct sockaddr_ll is defined in <linux/if_packet.h>. It contains roughly half a dozen fields, but we only need to worry about four of them, of which two are gimmes. Just to be safe, we start by zeroing it out. sll_family is simply a repetition that yes, this is in fact a packet socket. sll_halen confirms that Ethernet addresses are ETH_ALEN (i.e., 6) bytes long. Fields sll_ifindex and sll_addr specify the sending interface and destination address, respectively. (I'm not entirely clear why the destination address is both specified here and manually put into the Ethernet frame, but I suspect it is clear in the kernel code.) You'll notice the (void*) typecasting here; often these are not needed, but once in a while, depending on the type given to the struct fields, it gets rid of a compiler warning.

Sending the packet!

Congratulations! You have successfully created a raw socket, looked up properties of a network interface, filled in data structures, and you're all set to fire off your very own, customized Ethernet frame. Only one line of code stands between you and victory! So, let's have it:

  sendto(s, frame.buffer, frame_len, 0,
         (struct sockaddr*)&saddrll, sizeof(saddrll));
 
This is the same as a sendto for UDP or raw IP. Briefly, it takes as arguments the packet socket descriptor, the byte array containing the entire frame (headers included), length of the frame (data_len + ETH_HLEN), flags (if any), a pointer to the sockaddr_ll struct, and the size of the struct. It returns the number of bytes sent, or < 0 if an error occurred.

And that's it! A quick check with a packet analyzer like Wireshark can confirm that frames are making their way out of the operating system. After that, it's all up to the hardware.

Receiving packets is very similar; the socket is created the same way, and recvfrom populates a sockaddr_ll struct and a buffer containing the entire frame. One note: when sending very short frames, don't be surprised when the received frame appears to be longer than the sent frame. Ethernet defines a minimum payload length and will zero-pad the frame if needed. However, the length returned by recvfrom refers only to the legitimate data.

Included below is the full code along with minimal error-checking. Until next time, don't forget that it's all just bits!

The code, front to back (download ethersend.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>    /* Must precede if*.h */
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <sys/ioctl.h>

union ethframe
{
  struct
  {
    struct ethhdr    header;
    unsigned char    data[ETH_DATA_LEN];
  } field;
  unsigned char    buffer[ETH_FRAME_LEN];
};

int main(int argc, char **argv) {
  char *iface = "eth0";
  unsigned char dest[ETH_ALEN]
           = { 0x00, 0x12, 0x34, 0x56, 0x78, 0x90 };
  unsigned short proto = 0x1234;
  unsigned char *data = "hello world";
  unsigned short data_len = strlen(data);
 
  int s;
  if ((s = socket(AF_PACKET, SOCK_RAW, htons(proto))) < 0) {
    printf("Error: could not open socket\n");
    return -1;
  }
 
  struct ifreq buffer;
  int ifindex;
  memset(&buffer, 0x00, sizeof(buffer));
  strncpy(buffer.ifr_name, iface, IFNAMSIZ);
  if (ioctl(s, SIOCGIFINDEX, &buffer) < 0) {
    printf("Error: could not get interface index\n");
    close(s);
    return -1;
  }
  ifindex = buffer.ifr_ifindex;
 
  unsigned char source[ETH_ALEN];
  if (ioctl(s, SIOCGIFHWADDR, &buffer) < 0) {
    printf("Error: could not get interface address\n");
    close(s);
    return -1;
  }
  memcpy((void*)source, (void*)(buffer.ifr_hwaddr.sa_data),
         ETH_ALEN);
 
  union ethframe frame;
  memcpy(frame.field.header.h_dest, dest, ETH_ALEN);
  memcpy(frame.field.header.h_source, source, ETH_ALEN);
  frame.field.header.h_proto = htons(proto);
  memcpy(frame.field.data, data, data_len);
 
  unsigned int frame_len = data_len + ETH_HLEN;
 
  struct sockaddr_ll saddrll;
  memset((void*)&saddrll, 0, sizeof(saddrll));
  saddrll.sll_family = PF_PACKET;   
  saddrll.sll_ifindex = ifindex;
  saddrll.sll_halen = ETH_ALEN;
  memcpy((void*)(saddrll.sll_addr), (void*)dest, ETH_ALEN);
 
  if (sendto(s, frame.buffer, frame_len, 0,
             (struct sockaddr*)&saddrll, sizeof(saddrll)) > 0)
    printf("Success!\n");
  else
    printf("Error, could not send\n");
 
  close(s);
 
  return 0;
}

29 comments:

  1. Hey very cool. But Im working on an embedded system that has ethernet conectivity. I can see the frames out of this program . Thanks! exactly what I wanted, now how would we go about receiving the frames from the embedded system (non upper layer proto) its basically a dump of the data register out of ADC on a clock interrupt. Sorry I havent studied too much into OS as microcontrollers.

    Thanks a bunch for the code though

    ReplyDelete
  2. Hi Gabriel, sounds like an interesting application. I wasn't sure if you were asking how to receive frames using packet sockets or for something else, but thought I would include a quick synopsis just in case.

    Receiving is pretty similar to sending in terms of the variables and setup needed. The same sockaddr_ll struct used above to store the destination Ethernet (MAC) address is now used by recvfrom (the complement of sendto) to store information about the received frame. Just to be safe, I zero it out first:

    struct sockaddr_ll saddrll;
    memset((void*)&saddrll, 0, sizeof(saddrll));

    The recvfrom call needs a reference to this and to an integer containing its size:

    socklen_t sll_len = (socklen_t)sizeof(saddrll);

    Using the same socket built in step 2 above, a buffer to hold the received message, and an integer to store the result of recvfrom (will be either the number of bytes placed into the buffer or a negative number on error), we can perform the recvfrom:

    int recv_result;
    char buffer[ETH_FRAME_LEN];
    recv_result = recvfrom(sock, buffer, ETH_FRAME_LEN, 0,
    (struct sockaddr *)&saddrll, &sll_len);

    (See the manpage for recvfrom for details on what each argument means.)

    If a frame was received (recv_result > 0), then we can get some additional information about the frame from saddrll:

    if (recv_result > 0) {
    /* Interface index, as used in sending */
    int interface_index = saddrll.sll_ifindex;
    /* 0=unicast to me, 1=multicast, 2=broadcast, etc */
    unsigned short packet_type = saddrll.sll_pkttype;
    /* More fields for source address, protocol number, etc */
    }

    Other than that, you can use the same code structure, header files, et cetera from above to get you started. Type 'man 7 packet' and 'less /usr/include/linux/if_packet.h' at your Linux command prompt to get more details. Hope this helps!

    ReplyDelete
    Replies
    1. please send me the reciveEth code which recive the same same packet as send above

      Delete
  3. Mike,

    I have a question. Let's say we're going to prepare and send an ethernet frame via raw socket and we only have for ethernet payload data the following: an IPv4 header (20 octets) and UDP header with no UDP payload data (8 octets).

    We start our ethernet frame with the destination and source MACs and protocol, and then add-in our IPv4 header and UDP header. With that, we have 42 octets (6 + 6 + 2 + 20 + 8), which is short of the minimum of 60 (ignoring the frame check sequence field (FCS) which is 4 octets).

    Should we let the kernel pad the ethernet frame out to 60 or do it ourselves?

    The Linux kernel inserts the FCS and other junk anyway, but I wonder what would happen if we padded the frame out ourselves.

    Dave

    ReplyDelete
  4. Hi Dave, great questions!

    To my knowledge, padding is always done on your behalf when using packet sockets, as is the Ethernet preamble and FCS/CRC. I have confirmed this behavior on the system described in my writeup. It is not clear to me, however, whether in general padding is done by the kernel as you suggested or by the interface hardware.

    This behavior can be observed by sending the frame you describe above and capturing that frame at both the sending interface and at the remote, link-local interface to which it is destined. Padding bytes will not show up in the local capture but will in the remote capture.

    Your second question is more subtle. The short answer is that it doesn't functionally matter whether you or the underlying system pads out the frame; the end result will be the same. Suppose you padded your 42-byte frame (using null bytes) to 60 bytes. Then the receiving side will see a frame of length 60 with your 42 bytes followed by your padding, with the FCS appended to the end. Now suppose you don't add padding and let the underlying system do it for you. The receiving side will see precisely the same frame as in the first case: your 42 bytes followed by padding and finally the FCS.

    The interesting question is how the receiving side treats this padding. Unlike with C strings, null bytes in an Ethernet frame have no special meaning such as termination. This is necessary because legitimate frames may contain null bytes throughout their payloads. Further, Ethernet frame headers do not denote frame length. Thus, to the receiver there is no difference between a 42-byte frame with padding and a 60-byte frame that happens to contain null bytes in the payload. It is therefore typically up to the higher layers to implement PDU length fields and checks as needed. (As an aside, I learned the hard way that the receiving process must account for padding when checking the received frame length against an expected length. If the expected length is less than the minimum frame length, then the check should compare the received length against that minimum instead but use the expected length for extracting the higher layer PDU bytes.)

    Notice that the FCS must therefore also contain the same value whether you or the underlying system perform the padding; otherwise it would be required to check the FCS against all prefixes of the received frame that do not truncate any non-null bytes.

    The only reason I can think of why you might want pad a frame yourself is if it saves the underlying system from performing an expensive operation, such as copying the frame into a larger buffer with room for padding. However, this is not likely to be the way padding is implemented since short frames occur frequently. One of these days I'll have to look at the kernel implementation of packet sockets and confirm this. If you do so or find a reference for this, please share!

    Cheers,
    Mike

    ReplyDelete
  5. I should caveat that there is actually a second reason why one might pad a frame themselves, though I think it is specific to measurement and diagnostic applications. One might want to make explicit the exact string of bytes being emitted at the sender so as to obviate any implicit behavior of the underlying system. This would also mean that the sender and receiver could agree in advance (possibly out of band) on the exact string being transmitted. This might be useful when testing the handling of very specific frame contents, but less so in general applications.

    ReplyDelete
  6. Mike, thanks for that excellent answer!
    That's really great info.

    Thanks again,
    Dave

    ReplyDelete
  7. Hi Mike,

    First I would thank you for the clear and valuable tutorial you made.

    When constructing the Raw packet, you assumed that the destination address is already known. However, if one would make a complete implementation that fits all cases, one should use ARP.

    Actually, I want to do that but, as I am still beginner in Networking programmin, I don't know how to do it properly (what libraries to use, what fields to fill).

    I've already studied ARP protocol, but I have some points to check and I would be pleased if you could give me some guidelines.

    1/What data structure to implement and what header files to include (as Ethernet payload would be an ARP request)?
    2/Ethertype must be changed to ARP EtherType.
    3/ When filling destination address field "saddrll.sll_addr" one must put the Broadcast MAC address.
    4/ use "sendto" to send the ARP request.
    5/ Add a recvfrom() function to get the ARP response before proceeding to Data sending (so, recvfrom must be a blocking call in this case).
    6/ Fill Ethernet frame with the destination MAC address we got and send data.
    7/ I assume that it is not necessary to use two sockets for each: ARP and Ethernet data (one socket suffices for both).

    These are my assumptions and questions, please could you correct me if I was wrong.

    Thank you very much,
    Marcello

    ReplyDelete
  8. Hi Gabriel, implementing ARP from the ground up sounds like a fun project! I'll try to address your questions here, but I also recommend referencing Wright and Stevens' TCP/IP Illustrated Volume 2, which dedicates an entire chapter to ARP implementation.

    The headers you'll need should be the same as in my code above, presuming you don't want extra library support for constructing ARP messages. The ethertype for ARP will be 0x0806 (remember to put in network order using htons() or hard code as 0x0608 if developing for a little-endian machine). The "proto" field in socket() specifies which ethertype is received by that packet socket; you can specify htons(ETH_P_ALL) to receive all ethertypes, though you will then need to filter all other arriving Ethernet frames that you don't want. It may be easier to use two sockets to simplify this.

    In the sockaddr_ll struct for an outgoing ARP request, .sll_addr is indeed the broadcast MAC address. Other than that, you should only need to change .sll_protocol to the ARP ethertype. You will still use sendto() to send the request.

    You can receive ARP replies using recvfrom() as I described in a comment above. From there, you can use the .sll_addr or parse bytes 7-12 in the buffer you pass to recvfrom() to get the destination address for your frame. Again, you can either use a single socket and filter all unintended frames you receive or use two sockets.

    One last consideration is how to de-conflict with the existing ARP implementation on your system. Since you're not trying to process incoming ARP requests, there won't be any conflicting or duplicate responses between your implementation and the system's. However, you may receive replies to ARP requests sent by the system, in which case you'll need to parse the full ARP reply to determine whether it answers your request.

    Hope this helps, and I'd be excited to see what you develop!

    ReplyDelete
  9. Hi Mike,

    Thank you for your reply.

    I think that there are other aspects that should be taken into consideration rather than updating the Etherent header.

    In order to implement an ARP request, the ARP header should be included into in Ethernet payload field before sending the frame. ARP contains 9 fields (Hard type, proto type, operation , Dest Mac, Dest IP, ...) and these should be specified. Actually, that's why I was asking about what appropriate data structure to use in order to implement that.

    In truth, I want to make an embedded system be able to communicate with a host at Raw Ethernet level. That's why before starting to exchange data with the host, the embedded system must know what MAC address the host is actually using.

    Again thank you so much and Regards,
    Marcello

    ReplyDelete
  10. Hi Marcello,

    Indeed, there is work to be done in specifying the ARP payload. Since you are already familiar with the ARP message format, I will merely hint at a couple ways to implement an appropriate data structure. One way is to do as I did in my blog posted dated December 14, 2011 and create a struct such as this:

    struct my_arp {
    unsigned char dst[6];
    unsigned char src[6];
    unsigned short proto;
    unsigned short htype;
    unsigned short ptype;
    unsigned char hlen;
    ...
    };

    Notice that this includes the Ethernet header fields as well as the ARP message fields. You can actually embed an Ethernet header struct (provided in the standard Linux headers) as the first field of the above struct if you choose.

    Then create an instance of the struct (let's call this my_arp_instance), populate its fields (remembering again to convert multi-byte values into network order as needed), and re-cast as a byte (char) array when sending:

    int result = sendto(s, (char *)&my_arp_instance,
    sizeof(struct my_arp), 0,
    (struct sockaddr*)&saddrll, sizeof(saddrll));

    You can likewise re-cast received buffers (each a byte array) as a struct of this type. Another option that is sometimes handy is to use a union, which overlays two types of data in the same memory:

    union my_arp_union {
    unsigned char frame_bytes[1514];
    struct my_arp ma;
    };

    Then you can reference either member frame_bytes for the byte array or member ma for the struct without any need to re-cast.

    Since you are implementing this on an embedded system that does not run a standard OS with TCP/IP stack and ARP support, there are a number of other considerations such as how to process incoming ARP requests (generally necessary for other devices sending to you) and how the device knows the IP addresses it needs to resolve using ARP. However, that's much of the fun :)

    Best,
    Mike

    P.S. My apologies for mixing up your name when replying earlier; I had re-read the entire comment chain and got my wires crossed!

    ReplyDelete
  11. Hi Mike,

    Thank you, a clear explanation.

    >> how the device knows the IP addresses it needs to resolve using ARP.

    To solve this I imposed a specific IP address to be used on the host side. So, user have to configure the host IP address with the one I specified in order to be able to communicate with the device.

    >> how to process incoming ARP requests (generally necessary for other devices sending to you)

    That should be taken into consideration :)

    Also, as the system has to deal with only one host and it is not likely to have the host MAC address be changed, I suppose there is no need to implement an ARP cache. A character array of 6 bytes size would be sufficient to hold the MAC address of the host.


    Don't worry about mixing names :)

    Thanks and Regards,
    Marcello

    ReplyDelete
  12. Hi Mike!

    Thank you for the great post.

    I developed a receiver for your sender, but I have a question. Can I get only the message string for the recvfrom command?
    When I print the buffer all header comes together. Is it because of use of ethernet rather than UDP...?

    L u c h e o l (at) g m a i l

    Thanks

    lucio

    ReplyDelete
  13. Hi Lucio,

    You're absolutely correct. Unlike with higher-layer sockets, the C string produced by recvfrom() in this case is the full Ethernet frame, minus preamble and CRC (which I believe are removed prior to kernel processing).

    Supposing the char pointer you use in recvfrom() is s, the quick and dirty solution for printing would be something like:

    printf("%s", s+14);

    since the first 14 bytes are the Ethernet header. Assuming you aren't using any headers past that point, the remainder of the buffer is your payload. If you wanted to be more general with link-layer headers, try experimenting with the sll_hatype field of the sockaddr_ll that gets populated by recvfrom(). I suspect you can correspond each value (see linux/if_arp.h in your headers) with a header length.

    Best,
    Mike

    ReplyDelete
  14. i cannot open socket... returning value '-1'....what to do....??

    ReplyDelete
  15. Is the -1 being returned by the call to socket()? If so, try checking the variable errno after the call fails, using the error constants listed in the socket(2) manpage:

    ...
    if ((s = socket(AF_PACKET, SOCK_RAW, htons(proto))) < 0) {
    printf("Error: could not open socket");
    if (errno == EACCES) printf(" (EACCES)");
    if (errno == EAFNOSUPPORT) printf(" (EAFNOSUPPORT)");
    /* Add other error types as needed */
    printf("\n");
    return -1;
    }
    ...

    This may clarify the problem. For instance, if errno == EACCES then it is a permissions problem (this code must run as root, or with other special permissions, to create a link-layer socket).

    Hope this helps!

    ReplyDelete
  16. It worked mike....Great Bro...But now the first ioctl function is returning -1....what may be the problem...

    ReplyDelete
  17. You can use the same debugging approach with ioctl() and the other system calls. The manpage for each system call lists possible values for errno and corresponding explanations. Usually those will point you in the right direction. One hint for ioctl(): the two calls in the code above require a valid network interface name (e.g., eth0) for the system on which the program is being executed.

    ReplyDelete
  18. Thanks for the detailed info on sending raw ethernet frames.your article helped me understand more clearly. you might want to add the perror function within the failed system calls, since perror will give more of a detailed description of why it failed.

    ReplyDelete
  19. Could you please explain where have you assigned the value for "frame.buffer" in the program. From what i could understood in your program it is the data in frame.buffer[ETH_FRAME_LEN] that is getting sent but i could not find any reference to where the header info. and data has been copied to this array. Please explain. Thanks.

    ReplyDelete
  20. This comment has been removed by the author.

    ReplyDelete
  21. This is the code i wrote for receiving the data packets. But the code stops after fetching the socket descriptor. Please help.

    int main(int argc, char* argv[])
    {

    unsigned short int protocol_type = 0x1234;
    short int sock_desc=0, count=0;




    if ( (sock_desc = socket(AF_PACKET, SOCK_RAW, htons(protocol_type))) < 0)
    printf("\nError creating network socket\n");
    else
    {
    printf("\nSuccessfully created network socket with descriptor:%d\n", sock_desc);



    struct sockaddr_ll sock_addrll;
    unsigned char frame_buffer[ETH_FRAME_LEN];
    int data_rcvd=0;
    socklen_t sock_addrll_len = (socklen_t)sizeof(sock_addrll);
    data_rcvd = recvfrom(sock_desc, frame_buffer, ETH_FRAME_LEN, 0x00, (struct sockaddr*) &sock_addrll, &sock_addrll_len); // receive data from the source PC

    printf("\nData rcvd:%d\n", data_rcvd);



    }


    return 0;
    }

    ReplyDelete
  22. Hi Shreyas, thanks for the comments!

    Answering your first question: my code uses a C union (http://en.wikipedia.org/wiki/Union_type#C.2FC.2B.2B) between the embedded struct containing the Ethernet header and payload data, and a char array containing the entire "buffer." I set the bytes of the Ethernet frame by setting frame.header and frame.data, and then refer to those same bytes (but as a different data type) using frame.buffer. An alternative would be to not use a union, and instead define a struct that is recast as a char array when sending.

    Your code appears to work fine (assuming you #include the same files as in the sender, and that you run the code as root, i.e. using sudo). I modified the sender code to set iface to "lo" (the loopback interface) instead of "eth0" and set dest (the destination Ethernet address) to all 0xff's, and was able to receive the "hello world" message sent by my program using your code above.

    By the way, I also edited the printf call to show the contents of the payload (assuming it contains a human-readable string):

    printf("\nData rcvd (%d): %s\n", data_rcvd, frame_buffer+sizeof(struct ethhdr));

    Cheers!

    ReplyDelete
    Replies
    1. Hey Mike,
      Thanks a lot. I got it working. I am still trying to understand unions. How can i seperately declare a char array and copy the whole frame data to it?.

      Cheers!

      Delete
  23. I'm glad it worked out! You can copy the contents of the frame struct into a char array using another instance of memcpy (see the instance in the sender code for an example), but in general I would recommend recasting the structure instead because it saves a bit of computation. Here's a quick, untested example that you could incorporate into the sender code:

    struct my_frame {
    struct ethhdr header;
    unsigned char data[ETH_DATA_LEN];
    };

    struct my_frame fr;
    /* Set fr.header.h_source, fr.header.h_dest, fr.header.h_proto, fr.data, and frame_len similar to before */

    sendto(s, (unsigned char *)&fr, frame_len, 0, (struct sockaddr*)&saddrll, sizeof(saddrll));

    The "(unsigned char *)&fr" recasts &fr, which in effect is a pointer to the my_frame struct declared above, as a char array. In other words, it uses the same bytes in memory but causes those bytes to be interpreted as a different data type. The compiler treats this nearly the same as it treats the union, but perhaps this makes things more explicit :)

    Happy coding!

    ReplyDelete
  24. Hi Mike,
    Thanks a lot for your reply. I have got it working with a char array as well. This also helped me understand the concept of unions a bit more clearly. And yes the union method is implicit and reduces the typecasting and copying effort.

    Cheers!

    ReplyDelete
  25. Just wanted to say thanks for getting right to the point and not wasting my time. Good article.

    ReplyDelete
  26. Dear Mike, thanks so much for this post – really a fantastic article. As further reading, I know it's only tangentially related, but do you know of any good references on the inner workings of ioctl?

    ReplyDelete
  27. hii

    can you send the receeth code also

    ReplyDelete