clinuxsocketsraw-ethernet

Using sendmsg/sendmmsg with raw ethernet frames


I'm trying to use C to send raw ethernet packets via sendmsg(). This code successfully opens a raw packet socket, attempts to fill a struct iovec with a single array of bytes (char message[]), then fills a struct msghdr with the destination address, the address length, and a pointer to the struct iovec containing the message. sendmsg() returns EINVAL for every call but I have no idea which arguments are invalid. (I've deleted some perror() calls to make this code simpler to read; the output is Invalid argument.)

I haven't been able to find any examples of how sendmsg() would work with raw sockets, but similar code using sendto() works as expected. In that code, I form the Ethernet frame explicitly, including headers and protocol information, but to my understanding that's not necessary with a sendmsg() call? I've also attempted to have message.iov_base point to a buffer containing that explicitly-formed Ethernet frame including the 14-byte header, but sendmsg() also balks at that.

Can sendmsg() and sendmmsg() work with raw Ethernet frames? Is there something I'm missing about the iovec that is making it invalid?

 30 int main(void) {
 32         unsigned char   dest[ETH_ALEN] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; // destination MAC address
 33
 34         // Socket variables
 35         int             s;  
 36         unsigned short  protocol = htons(ETH_P_802_EX1); 
 38 
 39         // Message variables
 40         char            message[] = {"Test message. Test message. Test message!\n"};
 41         size_t          msg_len = strlen(message) + 1;          // Message length includes null terminator
 42         int             e;                                      // Error code
 43         struct msghdr   msg;
 44         struct iovec    msgvec;
 45 
 46         // Setup source-side socket
 47         s = socket(AF_PACKET, SOCK_RAW, protocol);
 48         if (s < 0) { printf("%d: %s\n", errno, strerror(errno)); return EXIT_FAILURE; }
 49 
 50         msgvec.iov_base = message;
 51         msgvec.iov_len = msg_len;
 52         memset(&msg, 0, sizeof(msg));
 53         msg.msg_name = dest;
 54         msg.msg_namelen = ETH_ALEN;
 55         msg.msg_control = NULL;
 56         msg.msg_controllen = 0;
 57         msg.msg_flags = 0;
 65         msg.msg_iov = &msgvec;
 66         msg.msg_iovlen = 1;
 67 
 68         for (int i=0; i<10; i++) {
 69                 e = sendmsg(s, &msg, 0);
 73         }
 79         close(s);
 80         return EXIT_SUCCESS;
 81 }

Solution

  • I got this code to work after making a few adjustments.

    Instead of sending the address as a string of bytes as implied by the man page for sendmsg(), I used a struct sockaddr_ll. I also pointed the iovec at a buffer containing the complete Ethernet frame including headers. Why the man page explicitly specifies a const struct sockaddr* in the sendto prototype but a void* in the msghdr definition remains unknown to me.

    I added this code after the call to socket():

     39         struct sockaddr_ll address;
     40         struct ifreq    buffer;                                 // To get information with ioctl()
     41         char            ifname[] = {"eth0"};
     42         int             ifindex;                                // Interface index, from ioctl() call
    
     57         memset(&buffer, 0, sizeof(buffer));                     // Getting interface index value
     58         strncpy(buffer.ifr_name, ifname, IFNAMSIZ);
     59         ioctl(s, SIOCGIFINDEX, &buffer);
     60         ifindex = buffer.ifr_ifindex;
     61
     62         // Setup sockaddr_ll address
     63         memset( (void*) &address, 0, sizeof(address) );
     64         address.sll_family = PF_PACKET;
     65         address.sll_ifindex = ifindex;
     66         address.sll_halen = ETH_ALEN;
     67         memcpy( (void*) (address.sll_addr), (void*) dest, ETH_ALEN );
    

    and replaced these lines of code for the msghdr struct assignment:

     81         msg.msg_name = &address;
     82         msg.msg_namelen = sizeof(address);