Сокеты Linux: почему MTU Ethernet является ограничением даже при передаче по 802.11?

При написании программы с использованием сокетов RAW и SOCK_DRAM UDP я заметил, что максимальная единица передачи Ethernet всегда представляет собой ограничение данных, которые я могу отправить, не подвергаясь фрагментации, даже если сокет строго привязан к беспроводному интерфейсу (используя bind()) и пакеты всегда передаются в эфир с использованием Wi-Fi (802.11) без какого-либо промежуточного сегмента проводной сети.

Я знал, что MTU 802.11 составляет 2346 Б (правильно ли это?), что больше, чем 1500 Б. Но если я попытаюсь передать больше, чем размер MTU Ethernet (1500 Б), я получу фрагментацию при использовании сокетов UDP и " сообщение слишком большое» ошибка (errno 90, EMSGSIZE) при использовании sendto() на сокетах RAW.

Это связано с тем, что с точки зрения пользовательских приложений пакеты 802.11 рассматриваются как пакеты 802.3, которые затем преобразуются и управляются внутри ядра и аппаратных устройств? Почему это ограничение применяется, даже если я могу передавать большие кадры с помощью «Wi-Fi»?

Изменить: пример кода для воспроизведения проблемы с сокетами RAW

Вот пример кода, извлеченный из исходного кода, который вы можете скомпилировать с помощью gcc и использовать для воспроизведения описанной выше проблемы.

При использовании сокетов RAW сообщение размером более 1500 байт явно отклоняется, устанавливая 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,&etherHeader,sizeof(struct ether_header));
    memcpy(packetok+sizeof(struct ether_header),bufferok,sizeok_final);

    // Packet WRONG
    memcpy(packetwrong,&etherHeader,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 байт:

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" при трехкратном запуске программы-образца были правильно приняты устройством-получателем, а все "неправильные" - нет: Снимок экрана Wireshark

Заранее большое спасибо.


person es483    schedule 30.01.2019    source источник
comment
Раньше я поддерживал программное обеспечение, которое много раз генерировало/вводило кадры 802.11 в Linux, и никогда не сталкивалось с этой проблемой. Могу ли я увидеть соответствующий код?   -  person 735Tesla    schedule 14.02.2019
comment
@735Tesla Конечно! Я только что обновил исходный вопрос образцом кода, извлеченным из исходного, который вы можете скомпилировать с помощью gcc и который должен воспроизвести проблему для необработанных сокетов. Что касается SOCK_DGRAM, то вместо этого пакет отправляется всегда, но фрагментируется, когда размер превышает 1500 байт (это видно не по прямой ошибке, а например при использовании Wireshark). Заранее большое спасибо.   -  person es483    schedule 17.02.2019


Ответы (1)


В конце концов, вопрос был решен пользователем по адресу LinuxQuestions.

На самом деле я перепутал концепцию MTU протокола с концепцией MTU интерфейса.

То, что ограничивало размер кадров, передаваемых через Wi-Fi, на самом деле было MTU, установленным для соответствующего интерфейса, который можно изменить с помощью, как сообщается в LinuxQuestions, ip link set <dev> mtu <mtu>. Эта команда может выполниться успешно, если аппаратное обеспечение действительно поддерживает указанный MTU, или завершиться ошибкой, если аппаратное обеспечение его не поддерживает.

Я смог успешно передавать большие кадры по Wi-Fi, увеличив MTU, установленный для беспроводного интерфейса, до максимального значения 2304 Б.

Если бы я вместо этого попытался установить еще больший MTU интерфейса, я бы получил следующую ошибку, которую в некотором роде ожидал:

$ sudo ip link set wlp1s0 mtu 2305
Error: mtu greater than device maximum.

В качестве последнего замечания можно было проверить текущий установленный MTU интерфейса для каждого активного интерфейса, используя, например:

ifconfig | grep -i MTU
person es483    schedule 23.07.2019