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 }
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);