While writing a program using RAW and SOCK_DRAM
UDP sockets, I noticed that the Ethernet Maximum Transmission Unit is always representing a limit to the data I can send without incurring into fragmentation, even if the socket is strictly bound to a wireless interface (using bind()
) and the packets are always transmitted on the air using Wi-Fi (802.11), without any wired network segment in between.
I knew the 802.11 MTU to be 2346 B (is it correct?), which is larger than 1500 B. But if I try to transmit more than the Ethernet MTU size (1500 B), I get fragmentation when using UDP sockets and a "message too big" error (errno 90
, EMSGSIZE
) when using sendto()
on RAW sockets.
Is this due to the fact that, from the point of view of the user applications, 802.11 packets are seen as 802.3 packets which are then converted and managed inside the kernel and hardware devices? Why is this limit applying even if I could transmit larger frames with "Wi-Fi"?
Edit: sample code to reproduce the problem for RAW sockets
Here is a sample code, extracted from the original one, that you can compile with gcc
and use to reproduce the problem I described before.
When using RAW sockets a message over 1500 B is explicitely rejected, setting errno
, even when the socket is bound to a wireless interface.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/wireless.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/ioctl.h>
#define DEVNAME "wlp2s0"
#define DESTINATIONMAC_INITIALIZER {0x9C,0xD2,0x1E,0x20,0x91,0xE5}
#define SIZEOK 1500
#define SIZEWRONG 1501
int main (int argc, char **argv) {
// wlanLookup() variables, for interface name, source MAC address and return value
char devname[]=DEVNAME;
int ifindex;
int descriptor; // Socket descriptor
struct sockaddr_ll addrll; // sockaddr_ll address structure
struct ifreq wifireq;
struct ether_header etherHeader;
unsigned char bufferok[SIZEOK];
unsigned char bufferwrong[SIZEWRONG];
unsigned char *packetok=NULL;
unsigned char *packetwrong=NULL;
// Source and destination MAC addresses
unsigned char macsrc[6];
unsigned char macdst[6]=DESTINATIONMAC_INITIALIZER;
// Size variables
int sentbytes;
size_t sizeok_final=sizeof(struct ether_header)+SIZEOK; // Size of the ok buffer + size of struct ether_header
size_t sizewrong_final=sizeof(struct ether_header)+SIZEWRONG; // Size of the ok buffer + size of struct ether_header
if(SIZEWRONG<SIZEOK) {
fprintf(stderr,"Error: in this sample code, SIZEWRONG (%d) must be >= than SIZEOK (%d)\n",SIZEWRONG,SIZEOK);
exit(EXIT_FAILURE);
}
descriptor=socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
if(descriptor==-1) {
perror("socket() error");
exit(EXIT_FAILURE);
}
// Get interface index of the interface
strncpy(wifireq.ifr_name,devname,IFNAMSIZ);
if(ioctl(descriptor,SIOCGIFINDEX,&wifireq)!=-1) {
ifindex=wifireq.ifr_ifindex;
} else {
perror("ioctl() error");
close(descriptor);
exit(EXIT_FAILURE);
}
fprintf(stdout,"Using interface: %s (index: %x)\n",DEVNAME,ifindex);
// Prepare sockaddr_ll structure
memset(&addrll,0,sizeof(addrll));
addrll.sll_ifindex=ifindex;
addrll.sll_family=AF_PACKET;
addrll.sll_protocol=htons(ETH_P_ALL);
// Bind to the wireless interface
if(bind(descriptor,(struct sockaddr *) &addrll,sizeof(addrll))<0) {
perror("Cannot bind to interface: bind() error");
close(descriptor);
exit(EXIT_FAILURE);
}
fprintf(stdout,"Bound to interface: %s (index: %x)\n",DEVNAME,ifindex);
// Populate both buffers with some data
for(int i=0;i<SIZEWRONG;i++) {
if(i<SIZEOK) {
bufferok[i]=(unsigned char) (i & 15); // Fill each byte with a cyclic sequence 0x00, 0x01, 0x02, ... 0x0F, 0x00, 0x01, ...
}
bufferwrong[i]=(unsigned char) (i & 15);
}
// Get source MAC address
strncpy(wifireq.ifr_name,devname,IFNAMSIZ);
if(ioctl(descriptor,SIOCGIFHWADDR,&wifireq)!=-1) {
memcpy(macsrc,wifireq.ifr_hwaddr.sa_data,6);
} else {
perror("Cannot get source MAC address: ioctl() error");
close(descriptor);
}
fprintf(stdout,"Source MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",macsrc[0],macsrc[1],macsrc[2],macsrc[3],macsrc[4],macsrc[5]);
fprintf(stdout,"Destination MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",macdst[0],macdst[1],macdst[2],macdst[3],macdst[4],macdst[5]);
// Fill struct ether_header
memcpy(etherHeader.ether_dhost,macdst,ETHER_ADDR_LEN);
memcpy(etherHeader.ether_shost,macsrc,ETHER_ADDR_LEN);
// Using local experimental ethertype for the sake of this sample code, but the effect is the same for any other EtherType
etherHeader.ether_type=htons(0x88B5);
packetok=malloc(sizeok_final*sizeof(unsigned char));
if(!packetok) {
perror("malloc() error");
close(descriptor);
exit(EXIT_FAILURE);
}
packetwrong=malloc(sizewrong_final*sizeof(unsigned char));
if(!packetwrong) {
perror("malloc() error");
free(packetok);
close(descriptor);
exit(EXIT_FAILURE);
}
// Generate the complete packet buffers
// Packet OK
memcpy(packetok,ðerHeader,sizeof(struct ether_header));
memcpy(packetok+sizeof(struct ether_header),bufferok,sizeok_final);
// Packet WRONG
memcpy(packetwrong,ðerHeader,sizeof(struct ether_header));
memcpy(packetwrong+sizeof(struct ether_header),bufferwrong,sizewrong_final);
sentbytes=sendto(descriptor,packetok,sizeok_final,0,(struct sockaddr *)&addrll,sizeof(struct sockaddr_ll));
perror("Packet OK errors (if any)");
fprintf(stdout,"Packet OK: sent %d bytes.\n",sentbytes);
sentbytes=sendto(descriptor,packetwrong,sizewrong_final,0,(struct sockaddr *)&addrll,sizeof(struct sockaddr_ll));
perror("Packet WRONG errors (if any)");
fprintf(stdout,"Packet WRONG: sent %d bytes.\n",sentbytes);
close(descriptor);
return 0;
}
Before compiling, you should set DEVNAME
to the name of the interface the sample program should bind to and DESTINATIONMAC_INITIALIZER
to the destination device you want to try to send packets to.
I actually got this output (after running the program with sudo
), showing how a packet over 1500 B is rejected:
Using interface: wlp2s0 (index: 3)
Bound to interface: wlp2s0 (index: 3)
Source MAC address: 00:16:ea:4a:bd:7e
Destination MAC address: 9c:d2:1e:20:91:e5
Packet OK errors (if any): Success
Packet OK: sent 1514 bytes.
Packet WRONG errors (if any): Message too long
Packet WRONG: sent -1 bytes.
The "ok" packets, when launching the sample program for three times, were correctly received by the destination device, while all the "wrong" ones were not:
Thank you very much in advance.
The question has been solved, in the end, by a user at LinuxQuestions.
I was actually confusing the concept of protocol MTU with the one of interface MTU.
What was limiting the size of the frames transmitted through Wi-Fi was actually the MTU set for the corresponding interface, which can be changed by using, as reported at LinuxQuestions, ip link set <dev> mtu <mtu>
. This command can have success if the hardware actually supports the specified MTU or fail if the hardware does not support it.
I was able to successfully transmit bigger frames over Wi-Fi by increasing the MTU set for the wireless interface, until the maximum value of 2304 B.
If I tried, instead, to set an even bigger interface MTU, I got the following error, which was in some way expected:
$ sudo ip link set wlp1s0 mtu 2305
Error: mtu greater than device maximum.
As a final note, it was possible to check the currently set interface MTU, for each active interface, by using, for instance:
ifconfig | grep -i MTU