Сокеты Linux: почему Ethernet MTU является пределом даже при передаче по 802.11?
При написании программы с использованием RAW и SOCK_DRAM
UDP-сокеты, я заметил, что Ethernet Maximum Transmission Unit всегда представляет ограничение для данных, которые я могу отправить без фрагментации, даже если сокет строго привязан к беспроводному интерфейсу (используя bind()
) и пакеты всегда передаются в эфире с использованием Wi-Fi (802.11), без какого-либо сегмента проводной сети между ними.
Я знал, что 802.11 MTU равен 2346 B (это правильно?), Что больше 1500 B. Но если я пытаюсь передать больше, чем размер Ethernet MTU (1500 B), я получаю фрагментацию при использовании UDP-сокетов и " сообщение слишком большое "ошибка (errno 90
, EMSGSIZE
) когда используешь sendto()
на RAW розетках.
Связано ли это с тем, что с точки зрения пользовательских приложений пакеты 802.11 рассматриваются как пакеты 802.3, которые затем преобразуются и управляются внутри ядра и аппаратных устройств? Почему применяется этот лимит, даже если я могу передавать большие кадры с помощью "Wi-Fi"?
Изменить: пример кода для воспроизведения проблемы для сокетов RAW
Вот пример кода, извлеченный из исходного кода, который вы можете скомпилировать gcc
и использовать для воспроизведения проблемы, которую я описал ранее.
При использовании RAW-сокетов сообщение свыше 1500 B явно отклоняется, настройка errno
, даже если розетка связана с беспроводным интерфейсом.
Вот код:
#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;
}
Перед компиляцией вы должны установить DEVNAME
к имени интерфейса, к которому должен привязываться пример программы DESTINATIONMAC_INITIALIZER
на устройство назначения, на которое вы хотите попытаться отправить пакеты.
Я действительно получил этот вывод (после запуска программы с sudo
), показывающий, как пакет свыше 1500 B отклоняется:
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.
Пакеты "ok" при трехкратном запуске примера программы были правильно получены устройством-получателем, а все "неправильные" - нет:
Заранее большое спасибо.
0 ответов
В конце концов, вопрос был решен пользователем LinuxQuestions.
Я на самом деле путал понятие MTU протокола с MTU интерфейса.
То, что ограничивало размер кадров, передаваемых через Wi-Fi, на самом деле было MTU, установленным для соответствующего интерфейса, который можно изменить, используя, как сообщается в LinuxQuestions, ip link set <dev> mtu <mtu>
. Эта команда может быть успешной, если оборудование действительно поддерживает указанный MTU, или завершиться ошибкой, если оборудование не поддерживает его.
Мне удалось успешно передавать более крупные кадры по Wi-Fi, увеличив MTU, установленный для беспроводного интерфейса, до максимального значения 2304 B.
Если бы я попытался вместо этого установить еще больший MTU интерфейса, я получил бы следующую ошибку, которая в некотором роде ожидалась:
$ sudo ip link set wlp1s0 mtu 2305
Error: mtu greater than device maximum.
В качестве последнего примечания, можно было проверить текущий установленный MTU интерфейса для каждого активного интерфейса, используя, например:
ifconfig | grep -i MTU