Исходные сокеты Linux дублируют полученный фрейм

#c #linux #sockets

#c #linux #сокеты

Вопрос:

Я разработал модуль, который позволяет обмениваться необработанными сокетами через устройства Ethernet. Это файл заголовка и исходный код модуля:

linux_socket_raw.h

 #ifndef LINUX_RAW_SOCKET_H
#define LINUX_RAW_SOCKET_H

/**
 * @ingroup linux
 * @{
 * @defgroup raw_socket Raw Ethernet sockets
 * @{
 */

#include <inttypes.h>

#define LINUX_RAW_SOCKET_MIN_FRAME_SIZE 60U

#define LINUX_RAW_SOCKET_MAX_DATA_SIZE  1500

#define LINUX_RAW_SOCKET_MAX_FRAME_SIZE 1536

#define MAC_ADDR_SIZE   6U

#define MAC_STRING_LENGTH 18U

#define INTERFACE_NAME_SIZE 16U

typedef char mac_addr_t[MAC_ADDR_SIZE];
typedef char mac_addr_string_t[MAC_STRING_LENGTH];
typedef char iface_name_t[INTERFACE_NAME_SIZE];

/**
 * @brief Raw ethernet socket pair.
 * 
 */
typedef struct 
{
  /**
   * Descriptor of the socket configured for reception.
   * This socket is used by the "receiver" thread to receive data to be sent
   * to the incoming data queue.
   */
  int32_t recv_sock_descr;

  /**
   * Descriptor of the socket configured for transmission.
   * This socket is used by the "transmitter" thread to send data received
   * from the outgoing data queue.
   */
  int32_t send_sock_descr;

  /**
   * Index of the local ethernet device
   */
  int32_t interface_index;

  /**
   * MAC address of the local Ethernet device
   */
  mac_addr_t src_mac;

  /**
   * Destination MAC address of frames sent via raw socket
   */
  mac_addr_t dest_mac;
}
linux_raw_socket_t;

void linux_raw_socket_init(
        linux_raw_socket_t * const      raw_sockets,
        const iface_name_t              interface_name,
        const mac_addr_string_t             destination_mac);


int32_t linux_raw_socket_send(
        linux_raw_socket_t * const      raw_sockets,
        const uint8_t * const           sendbuffer,
        uint16_t                        size);

int32_t linux_raw_socket_recv(
        linux_raw_socket_t * const      raw_sockets,
        uint8_t * const                 recvbuffer,
        uint16_t                        size);

void linux_raw_socket_close(linux_raw_socket_t * const raw_sockets);

/**
 * @}
 * @}
 */

#endif
  

linux_socket_raw.c

 #include "linux_socket_raw.h"
#include <assert.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <stddef.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <errno.h>
#include <net/if.h>

static inline void raw_sock_create_send_sock(linux_raw_socket_t * const sock)
{
  assert(sock != NULL);
  sock->send_sock_descr = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  assert (-1 != sock->send_sock_descr);
}

static inline void raw_sock_create_recv_sock(
      linux_raw_socket_t * const sock,
      const iface_name_t    const interface_name)
{
  assert(sock != NULL);
  sock->recv_sock_descr = socket(   AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  assert(-1 != sock->recv_sock_descr);
  int32_t sockopt = 1;
  assert(-1 != setsockopt(
          sock->recv_sock_descr, 
          SOL_SOCKET, 
          SO_REUSEADDR, 
          amp;sockopt, 
          sizeof(sockopt)));
  assert(-1 != setsockopt(
          sock->recv_sock_descr, 
          SOL_SOCKET, 
          SO_BINDTODEVICE, 
          interface_name, 
          INTERFACE_NAME_SIZE-1));
}

static void raw_sock_get_src_addr(
      linux_raw_socket_t *  const   sock,
      const iface_name_t    const   interface_name)
{

  struct ifreq if_mac, * if_mac_ptr = amp;if_mac;
  memset(amp;if_mac, 0, sizeof(struct ifreq));
  
  // NOTE The copy of interface name into the ifr_name field of the if_idx
  // must be made manually, byte by byte.
  int8_t ifn_count = 0;
  do
  {
    // NOTE the cast to char is oerformed for MISRA-compliance
    if_mac.ifr_name[ifn_count] = (char) interface_name[ifn_count];
    ifn_count  ;
  }
  while ((ifn_count < 16) amp;amp; (interface_name[ifn_count - 1] != 0));
  assert(   0 == ioctl(sock->send_sock_descr, SIOCGIFHWADDR, if_mac_ptr));
  for (uint8_t counter = 0; counter < MAC_ADDR_SIZE; counter  )
  {
    sock->src_mac[counter] = 
              (uint8_t) if_mac.ifr_hwaddr.sa_data[counter];
  }
}

static void raw_sock_get_iface_index(
      linux_raw_socket_t *  const   sock,
      const iface_name_t    const   interface_name)
{
  struct ifreq if_idx, * if_idx_ptr = amp;if_idx;
  memset(amp;if_idx, 0, sizeof(struct ifreq));
  
  // NOTE The copy of interface name into the ifr_name field of the if_idx
  // must be made manually, byte by byte.
  int8_t ifn_count = 0;
  do
  {
    // NOTE the cast to char is oerformed for MISRA-compliance
    if_idx.ifr_name[ifn_count] = (char) interface_name[ifn_count];
    ifn_count  ;
  }
  while ((ifn_count < 16) amp;amp; (interface_name[ifn_count - 1] != 0));
  assert(0 == ioctl(    sock->send_sock_descr, SIOCGIFINDEX, if_idx_ptr));
  sock->interface_index = if_idx.ifr_ifindex;
}

static void raw_sock_build_frame_header(
      const mac_addr_t      const   src_mac,
      const mac_addr_t      const   dest_mac,
      const uint16_t                        payload_size,
      uint8_t * const                   frame_buffer)
{
  for (uint8_t counter = 0; counter < MAC_ADDR_SIZE; counter  )
  {
    frame_buffer[counter] = dest_mac[counter];
    frame_buffer[counter   MAC_ADDR_SIZE] = 
                          src_mac[counter];
  }

  frame_buffer[12] = (uint8_t) (payload_size amp; 0x00FFU);
  frame_buffer[13] = (uint8_t) ((payload_size amp; 0xFF00U) >> 8);
}

static void raw_sock_convert_mac(
             const int8_t * mac_address,
             int8_t separator, 
             uint8_t * mac_address_array)
{   
  assert(mac_address != NULL);
  assert(mac_address_array != NULL);

  int8_t mac_tmp[MAC_STRING_LENGTH];
  memset(mac_tmp, 0, MAC_STRING_LENGTH);
  assert(mac_tmp == memcpy(mac_tmp, mac_address, MAC_STRING_LENGTH));

  int8_t * mac_tmp_ptr = amp;mac_tmp[0];

  errno = 0;

    for (uint32_t counter = 0; counter < MAC_ADDR_SIZE; counter  ) {

    // MISRA C 2012 Rule 22.9: The value of errno shall be tested against 
    // zero after calling an errno-setting-function. 'errno' is not checked 
    // for error conditions. For more secure code:
    //  - Set 'errno' to zero before call to 'strtoul'. 
    //  - Test return value of 'strtoul' for ULONG_MAX and test 'errno' for 
    //    ERANGE after the call.
    errno = 0;
    uint64_t tmp = strtoul((const char*)mac_tmp_ptr, NULL, 16);
         assert(    tmp != ULONG_MAX);
       assert(  errno != ERANGE);
       assert(  errno == 0);
    assert( tmp <= 255);
        mac_address_array[counter] = (uint8_t) tmp;  

        mac_tmp_ptr = (int8_t*) strchr((const char*)mac_tmp_ptr, separator);                                
        if ( (mac_tmp_ptr == NULL) || (*mac_tmp_ptr == (int8_t)'') ) 
    {
            break;                                                      
        }
        mac_tmp_ptr  ;                                                  
    }
}

void linux_raw_socket_init(
        linux_raw_socket_t * const      raw_sockets,
        const iface_name_t              interface_name,
        const mac_addr_string_t             destination_mac)
{
  int8_t mac_tmp[MAC_STRING_LENGTH];
  memset(mac_tmp, 0, MAC_STRING_LENGTH);
  assert(mac_tmp == memcpy( (void*)mac_tmp, 
                (const void*)destination_mac, 
                MAC_STRING_LENGTH));

  uint8_t mac_array[MAC_ADDR_SIZE];
  memset(mac_array, 0, MAC_ADDR_SIZE);
  int8_t separator = (int8_t)':';

  raw_sock_convert_mac(mac_tmp, separator, mac_array);

  assert(raw_sockets->dest_mac == memcpy(
        (void*)raw_sockets->dest_mac,
        (const void*)mac_array, 
        MAC_ADDR_SIZE));

  raw_sock_create_send_sock(raw_sockets);
  raw_sock_create_recv_sock(raw_sockets, interface_name);
  raw_sock_get_iface_index(raw_sockets, interface_name);
  raw_sock_get_src_addr(raw_sockets, interface_name);
}


int32_t linux_raw_socket_send(
        linux_raw_socket_t * const      raw_sockets,
        const uint8_t * const           sendbuffer,
        uint16_t                        size)
{
  assert(size <= LINUX_RAW_SOCKET_MAX_DATA_SIZE);
  uint8_t buffer[LINUX_RAW_SOCKET_MAX_FRAME_SIZE];

  memset(buffer, 0, LINUX_RAW_SOCKET_MAX_FRAME_SIZE);


  struct sockaddr_ll socket_address;
  socket_address.sll_ifindex = raw_sockets->interface_index;
  socket_address.sll_halen = ETH_ALEN;
  for (uint8_t counter = 0; counter < MAC_ADDR_SIZE; counter  )
  {
    socket_address.sll_addr[counter] = 
              raw_sockets->dest_mac[counter];
  }
  
  raw_sock_build_frame_header(
        raw_sockets->src_mac,
        raw_sockets->dest_mac,
        (uint16_t) size,
        buffer);

  memcpy(amp;buffer[14], sendbuffer, size);
  uint16_t tx_len = 14U   size;
  if (tx_len < LINUX_RAW_SOCKET_MIN_FRAME_SIZE)
  {
    tx_len = LINUX_RAW_SOCKET_MIN_FRAME_SIZE;
  }

  // The send operation is performed in non-blocking manner. When the
  // MSG_DONTWAIT is used, if the data-packet does not fit into the
  // send buffer of the socket, the send it will fail with the error
  // EAGAIN or EWOULDBLOCK.
  int32_t sent = sendto(    raw_sockets->send_sock_descr,
              buffer,
              tx_len,
              MSG_DONTWAIT,
              (struct sockaddr*)amp;socket_address,
              sizeof(struct sockaddr_ll));
  
  return (sent > 0 ? sent - 14 : 0);
}

int32_t linux_raw_socket_recv(
        linux_raw_socket_t * const      raw_sockets,
        uint8_t * const                 recvbuffer,
        uint16_t                        size)
{
  assert(size <= LINUX_RAW_SOCKET_MAX_DATA_SIZE);
  uint8_t buffer[LINUX_RAW_SOCKET_MAX_FRAME_SIZE];

  memset(buffer, 0, LINUX_RAW_SOCKET_MAX_FRAME_SIZE);
  
  // The receive operatio is performed in non-blocking manner. When the
  // MSG_DONTWAIT is used, if mo data-packet are available into the
  // socket, the recv it will fail with. In this case the value -1 is
  // returned
  ssize_t received_data = recvfrom( raw_sockets->recv_sock_descr, 
                    buffer,
                    LINUX_RAW_SOCKET_MAX_FRAME_SIZE,
                    MSG_DONTWAIT, 
                    NULL,
                    NULL);

  //slogger(severity_note,"Hai ricevuto %d byten",1,received_data);

  if (received_data > 0)
  {
    if (0 == memcmp(buffer, raw_sockets->src_mac, MAC_ADDR_SIZE))
    {
      //slogger(severity_note,"Pacchetto letto da recvfromn",0);
      memcpy(recvbuffer, amp;buffer[14], size);
      return received_data - 14;
    }
    return 0;
  }
  return 0;
}

void linux_raw_socket_close(linux_raw_socket_t * const raw_sockets)
{
  assert(raw_sockets != NULL);
  assert(0 == close(raw_sockets->recv_sock_descr));
  assert(0 == close(raw_sockets->send_sock_descr));
}
  

Чтобы доказать это, я создал два виртуальных устройства Ethernet с помощью bash-скрипта, который использует ip link команду, и написал простое пользовательское приложение на c:

setup_ethernet.sh

 #!/bin/bash

RED='33[0;31m'
GREEN='33[0;32m'
NC='33[0m' # No Color

SOURCE_INTERFACE_NAME="source_raw"
SOURCE_INTERFACE_ADDR="aa:aa:aa:aa:aa:aa"

DEST_INTERFACE_NAME="dest_raw"
DEST_INTERFACE_ADDR="aa:bb:cc:dd:ee:ff"

error_exit()
{
    echo -e "${RED}$1${NC}" 1>amp;2
    exit 1
}

create_dummy_interfaces()
{
    ip link add "${SOURCE_INTERFACE_NAME}" type veth peer name "${DEST_INTERFACE_NAME}" amp;amp; ip link set "${SOURCE_INTERFACE_NAME}" address "${SOURCE_INTERFACE_ADDR}" amp;amp; ip link set "${DEST_INTERFACE_NAME}" address "${DEST_INTERFACE_ADDR}" amp;amp; ip link set "${SOURCE_INTERFACE_NAME}" up amp;amp; ip link set "${DEST_INTERFACE_NAME}" up
}

if (($EUID != 0)); then
    error_exit "Ethernet Setup script must be executed with superuser rights."
fi


if create_dummy_interfaces;
then
    echo "Interfaces created correctly!"
    exit 0;
else
    error_exit "Error creating virtual ethernet interfaces!"
fi
  

main.c

 #include <stdio.h>
#include "linux_socket_raw.h"

#define SOURCE_ETH_IFACE_NAME "source_raw"
#define SOURCE_ETH_MAC_ADDR   "AA:AA:AA:AA:AA:AA"

#define DEST_ETH_IFACE_NAME "dest_raw"
#define DEST_ETH_MAC_ADDR   "AA:BB:CC:DD:EE:FF"

#define NUM_OF_TRANSMISSIONS 30U
#define NUM_OF_RECEPTIONS    10U

int main(void)
{
  linux_raw_socket_t source_sock;
  linux_raw_socket_t dest_sock;

  linux_raw_socket_init(amp;source_sock,SOURCE_ETH_IFACE_NAME,DEST_ETH_MAC_ADDR);
  linux_raw_socket_init(amp;dest_sock,DEST_ETH_IFACE_NAME,SOURCE_ETH_MAC_ADDR);

  for(uint32_t i = 0; i < NUM_OF_TRANSMISSIONS; i  )
  {
    int32_t sent = linux_raw_socket_send(amp;source_sock,(uint8_t *)amp;i,sizeof(uint32_t));

    printf("Sent %d bytes on source_eth; payload = %dn",sent,i);
    
    for(uint32_t j = 0; j < NUM_OF_RECEPTIONS; j  )
    {
      uint32_t recv_buffer = 0;
      int32_t recvd = linux_raw_socket_recv(amp;dest_sock,(uint8_t *)amp;recv_buffer,sizeof(uint32_t));
      
      printf("Received %d bytes on dest_eth; payload = %dn",recvd,recv_buffer);
    }
  }

  return 0;
}
  

However, the output shows that for a single transmission there are always two receptions of the same frame:

 Sent 46 bytes on source_eth; payload = 0
Received 46 bytes on dest_eth; payload = 0
Received 46 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Sent 46 bytes on source_eth; payload = 1
Received 46 bytes on dest_eth; payload = 1
Received 46 bytes on dest_eth; payload = 1
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Sent 46 bytes on source_eth; payload = 2
Received 46 bytes on dest_eth; payload = 2
Received 46 bytes on dest_eth; payload = 2
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
Received 0 bytes on dest_eth; payload = 0
[...]
  

Я проверил эффективные операции двух veth с помощью tcpdump, нет никакой формы дублирования:

tcpdump для dest_raw

 11:47:03.908364 aa:aa:aa:aa:aa:aa (oui Unknown) > aa:bb:cc:dd:ee:ff (oui Unknown) Null Information, send seq 0, rcv seq 0, Flags [Command], length 46
    0x0000:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
11:47:03.908493 aa:aa:aa:aa:aa:aa (oui Unknown) > aa:bb:cc:dd:ee:ff (oui Unknown) Null Information, send seq 0, rcv seq 0, Flags [Command], length 46
    0x0000:  0100 0000 0000 0000 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
11:47:03.908523 aa:aa:aa:aa:aa:aa (oui Unknown) Null > aa:bb:cc:dd:ee:ff (oui Unknown) 802.1B I Information, send seq 0, rcv seq 0, Flags [Command], length 46
    0x0000:  0200 0000 0000 0000 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
[...]
  

tcp_dump для source_raw

 11:47:03.908353 aa:aa:aa:aa:aa:aa (oui Unknown) > aa:bb:cc:dd:ee:ff (oui Unknown) Null Information, send seq 0, rcv seq 0, Flags [Command], length 46
    0x0000:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
11:47:03.908483 aa:aa:aa:aa:aa:aa (oui Unknown) > aa:bb:cc:dd:ee:ff (oui Unknown) Null Information, send seq 0, rcv seq 0, Flags [Command], length 46
    0x0000:  0100 0000 0000 0000 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
11:47:03.908521 aa:aa:aa:aa:aa:aa (oui Unknown) Null > aa:bb:cc:dd:ee:ff (oui Unknown) 802.1B I Information, send seq 0, rcv seq 0, Flags [Command], length 46
    0x0000:  0200 0000 0000 0000 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
11:47:03.908550 aa:aa:aa:aa:aa:aa (oui Unknown) Null > aa:bb:cc:dd:ee:ff (oui Unknown) 802.1B I Information, send seq 0, rcv seq 0, Flags [Command], length 46
    0x0000:  0300 0000 0000 0000 0000 0000 0000 0000  ................
    0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
    0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
  

Я не эксперт по необработанным сокетам, но я думаю, что мне не хватает некоторых параметров конфигурации. Кроме того, я хочу уточнить, что в настоящее время я работаю на виртуальной машине Ubuntu 18.04.