#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.