Пакетные сокеты, не получающие данные для пользовательского идентификатора протокола

Я пытаюсь отправить и получить пакеты типа SOCK_RAW поверх PF_SOCKET, используя свой собственный идентификатор протокола на том же компьютере. Вот мой пример кода отправителя и получателя -

sender.c

#include<sys/socket.h>
#include<linux/if_packet.h>
#include<linux/if_ether.h>
#include<linux/if_arp.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define CUSTOM_PROTO 0xB588

int main ()
{
    int sockfd = -1;
    struct sockaddr_ll dest_addr = {0}, src_addr={0};
    char *buffer = NULL;
    struct ethhdr *eh;

    sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) );

    if ( sockfd == -1 )
    {
        perror("socket");
        return -1;
    }
    buffer = malloc(1518);
    eh = (struct ethhdr *)buffer;

    dest_addr.sll_ifindex  = if_nametoindex("eth0");
    dest_addr.sll_addr[0]  = 0x0;
    dest_addr.sll_addr[1]  = 0xc;
    dest_addr.sll_addr[2]  = 0x29;
    dest_addr.sll_addr[3]  = 0x49;
    dest_addr.sll_addr[4]  = 0x3f;
    dest_addr.sll_addr[5]  = 0x5b;
    dest_addr.sll_addr[6]  = 0x0;
    dest_addr.sll_addr[7]  = 0x0;

    //other host MAC address
    unsigned char dest_mac[6] = {0x0, 0xc, 0x29, 0x49, 0x3f, 0x5b};

    /*set the frame header*/
    memcpy((void*)buffer, (void*)dest_mac, ETH_ALEN);
    memcpy((void*)(buffer+ETH_ALEN), (void*)dest_mac, ETH_ALEN);

    eh->h_proto = htons(PAVAN_PROTO);

    memcpy((void*)(buffer+ETH_ALEN+ETH_ALEN + 2), "Pavan", 6 );

    int send = sendto(sockfd, buffer, 1514, 0, (struct sockaddr*)&dest_addr,
                      sizeof(dest_addr) );
    if ( send == -1 )
    {
        perror("sendto");
        return -1;
    }
    return 0;
}

receiver.c

#include<sys/socket.h>
#include<linux/if_packet.h>
#include<linux/if_ether.h>
#include<linux/if_arp.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define CUSTOM_PROTO 0xB588

int main ()
{
    int sockfd = -1;
    struct sockaddr_ll dest_addr = {0}, src_addr={0};
    char *recvbuf = malloc(1514);

    sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) );

    if ( sockfd == -1 )
    {
        perror("socket");
        return -1;
    }
    int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL);
    printf("I received: \n");

    return 0;
}

Оба отправителя и получателя работают в Ubuntu Virtualbox. Проблема заключается в том, что приемник находится в recvfrom. Но в receiver.c, если я изменяю htons(CUSTOM_PROTO) на htons(ETH_P_ALL), приемник работает отлично.

Почему ядро ​​не доставляет пакет с моим собственным идентификатором протокола в свой собственный идентификатор протокола ID?

Я проверил в GDB, что заголовок Ethernet сформирован правильно, когда я получаю пакет с htons(ETH_P_ALL)

Обновление: Вместо интерфейса eth0 и его соответствующего MAC, если я выбираю локальный loopback lo и MAC-адрес 00:00:00:00:00:00, CUSTOM_PROTO работает просто отлично!

Обновление 2 CUSTOM_PROTO отлично работает, если отправитель и получатель находятся на разных машинах. Это открытие и предварительное обновление заставили меня подозревать, что пакеты, отправленные на eth0, не принимаются одним и тем же компьютером. Но тот факт, что ETH_P_ALL работает на одной машине, опровергает мои подозрения.

Ответы

Ответ 1

ETH_P_ALL и любой другой протокол

Протокол ETH_P_ALL имеет особую роль для сбора исходящих пакетов.

Сокет приемника с любым протоколом, который не равен ETH_P_ALL, принимает пакеты этого протокола, которые поступают от драйвера устройства.

Socket с протоколом ETH_P_ALL получает все пакеты перед отправкой исходящих пакетов в драйвер устройства и все входящие пакеты, полученные от драйвера устройства.

Устройство Loopback vs Ethernet device

Пакеты, отправленные на устройство loopback, выходят из этого устройства, а затем одни и те же пакеты принимаются с устройства как входящие. Таким образом, когда CUSTOM_PROTO используется с loopback, сокет захватывает пакеты с настраиваемым протоколом как входящие.

Обратите внимание, что если ETH_P_ALL используется с устройством loopback, каждый пакет принимается дважды. После того, как он будет захвачен как исходящий, а второй - как входящий.

В случае eth0 пакет передается с устройства. Таким образом, такие пакеты идут в драйвер устройства, а затем их можно увидеть на другой стороне физического Ethernet-порта. Например, с сетевым адаптером VirtualBox "Только для хоста" эти пакеты могут быть захвачены некоторым снифером в хост-системе.

Однако пакеты, переданные на физический порт (или его эмуляция), не перенаправляются обратно на этот порт. Таким образом, они не принимаются как входящие с устройства. Вот почему такие пакеты могут быть записаны только в ETH_P_ALL в исходящем направлении, и они не могут быть видны с помощью CUSTOM_PROTO во входящем направлении.

Технически необходимо подготовить специальную настройку для выполнения внешней петли пакета (пакеты с порта устройства должны быть отправлены обратно в этот порт). В этом случае поведение должно быть аналогично устройству loopback.

Реализация ядра

Смотрите файл ядра net/core/dev.c. Существует два разных списка:

struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
struct list_head ptype_all __read_mostly;   /* Taps */

Список ptype_all предназначен для обработчиков сокетов с протоколом ETH_P_ALL. Список ptype_base предназначен для обработчиков с обычными протоколами.

Существует крючок для исходящих пакетов в xmit_one(), вызванный из dev_hard_start_xmit():

    if (!list_empty(&ptype_all))
        dev_queue_xmit_nit(skb, dev);

Для исходящих пакетов функция dev_queue_xmit_nit() вызывается для ETH_P_ALL обработки каждого элемента ptype_all. Наконец, сокеты типа AF_SOCKET с протоколом ETH_P_ALL фиксируют этот исходящий пакет.


Таким образом, наблюдаемое поведение не связано с каким-либо обычным протоколом. Такое же поведение наблюдается при ETH_P_IP. В этом случае приемник способен захватывать все входящие IP-пакеты, однако он не может захватывать IP-пакеты из sender.c, которые отправляют с "eth0" на MAC-адрес устройства "eth0".

Это также можно увидеть на tcpdump. Пакеты, отправленные отправителем, не записываются, если вызывается tcpdump с возможностью захвата только входящих пакетов (разные версии tcpdump используют другой аргумент командной строки для включения такой фильтрации).


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

while (1) {
    int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL);

    if (ntohs(*(uint16_t*)(recvbuf + ETH_ALEN + ETH_ALEN)) == CUSTOM_PROTO) {
        printf("I received: \n");
        break;
    }
}

Полезная ссылка "kernel_flow" с хорошей диаграммой http://www.linuxfoundation.org/images/1/1c/Network_data_flow_through_kernel.png

Он основан на ядре 2.6.20, однако в современных ядрах ETH_P_ALL обрабатывается одинаково.

Ответ 2

Когда пакеты с одним и тем же исходным и конечным MAC-адресами передаются из реального сетевого устройства eспасибо и физически возвращаются обратно.

Если ETH_P_ALL протокол ETH_P_ALL, пакет захватывается дважды:

  • первый пакет с socket_address.sll_pkttype - это PACKET_OUTGOING
  • и второй пакет с socket_address.sll_pkttype является PACKET_HOST

Если указан конкретный протокол CUSTOM_PROTO, пакет захватывается один раз:

  • в случае обычного пакета: socket_address.sll_pkttype - это PACKET_HOST.
  • в случае пакета VLAN: socket_address.sll_pkttype имеет значение PACKET_OTHERHOST.