You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libiec61850/hal/ethernet/linux/ethernet_linux.c

363 lines
8.9 KiB
C

/*
* ethernet_linux.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of libIEC61850.
*
* libIEC61850 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libIEC61850 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <linux/filter.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include "lib_memory.h"
#include "hal_ethernet.h"
#ifndef DEBUG_SOCKET
#define DEBUG_SOCKET 0
#endif
struct sEthernetSocket {
int rawSocket;
bool isBind;
struct sockaddr_ll socketAddress;
};
struct sEthernetHandleSet {
struct pollfd* handles;
int nhandles;
};
EthernetHandleSet
EthernetHandleSet_new(void)
{
EthernetHandleSet result = (EthernetHandleSet) GLOBAL_MALLOC(sizeof(struct sEthernetHandleSet));
if (result != NULL)
{
result->handles = NULL;
result->nhandles = 0;
}
return result;
}
void
EthernetHandleSet_addSocket(EthernetHandleSet self, const EthernetSocket sock)
{
if (self != NULL && sock != NULL)
{
int i = self->nhandles++;
self->handles = realloc(self->handles, self->nhandles * sizeof(struct pollfd));
self->handles[i].fd = sock->rawSocket;
self->handles[i].events = POLLIN;
}
}
void
EthernetHandleSet_removeSocket(EthernetHandleSet self, const EthernetSocket sock)
{
if ((self != NULL) && (sock != NULL))
{
int i;
for (i = 0; i < self->nhandles; i++)
{
if (self->handles[i].fd == sock->rawSocket)
{
memmove(&self->handles[i], &self->handles[i+1], sizeof(struct pollfd) * (self->nhandles - i - 1));
self->nhandles--;
return;
}
}
}
}
int
EthernetHandleSet_waitReady(EthernetHandleSet self, unsigned int timeoutMs)
{
int result;
if ((self != NULL) && (self->nhandles >= 0)) {
result = poll(self->handles, self->nhandles, timeoutMs);
}
else {
result = -1;
}
return result;
}
void
EthernetHandleSet_destroy(EthernetHandleSet self)
{
if (self->nhandles)
free(self->handles);
GLOBAL_FREEMEM(self);
}
static int
getInterfaceIndex(int sock, const char* deviceName)
{
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, deviceName, IFNAMSIZ - 1);
if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Failed to get interface index");
return -1;
}
int interfaceIndex = ifr.ifr_ifindex;
return interfaceIndex;
}
void
Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr)
{
struct ifreq buffer;
int sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&buffer, 0x00, sizeof(buffer));
strncpy(buffer.ifr_name, interfaceId, IFNAMSIZ - 1);
ioctl(sock, SIOCGIFHWADDR, &buffer);
close(sock);
int i;
for (i = 0; i < 6; i++ )
{
addr[i] = (unsigned char)buffer.ifr_hwaddr.sa_data[i];
}
}
EthernetSocket
Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress)
{
EthernetSocket self = GLOBAL_CALLOC(1, sizeof(struct sEthernetSocket));
if (self)
{
self->rawSocket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (self->rawSocket == -1)
{
if (DEBUG_SOCKET)
printf("Error creating raw socket!\n");
GLOBAL_FREEMEM(self);
return NULL;
}
self->socketAddress.sll_family = PF_PACKET;
self->socketAddress.sll_protocol = htons(ETH_P_ALL);
int ifcIdx = getInterfaceIndex(self->rawSocket, interfaceId);
if (ifcIdx == -1)
{
Ethernet_destroySocket(self);
return NULL;
}
self->socketAddress.sll_ifindex = ifcIdx;
self->socketAddress.sll_hatype = ARPHRD_ETHER;
self->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST;
self->socketAddress.sll_halen = ETH_ALEN;
memset(self->socketAddress.sll_addr, 0, 8);
if (destAddress != NULL)
memcpy(self->socketAddress.sll_addr, destAddress, 6);
self->isBind = false;
Ethernet_setMode(self, ETHERNET_SOCKET_MODE_PROMISC);
}
return self;
}
void
Ethernet_setMode(EthernetSocket self, EthernetSocketMode mode)
{
if (self)
{
if (mode == ETHERNET_SOCKET_MODE_PROMISC)
{
struct ifreq ifr;
if (ioctl (self->rawSocket, SIOCGIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Problem getting device flags");
return;
}
ifr.ifr_flags |= IFF_PROMISC;
if (ioctl (self->rawSocket, SIOCSIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Setting device to promiscuous mode failed");
return;
}
}
else if (mode == ETHERNET_SOCKET_MODE_ALL_MULTICAST)
{
struct ifreq ifr;
if (ioctl (self->rawSocket, SIOCGIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Problem getting device flags");
return;
}
ifr.ifr_flags |= IFF_ALLMULTI;
if (ioctl (self->rawSocket, SIOCSIFFLAGS, &ifr) == -1)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Setting device to promiscuous mode failed");
return;
}
}
else if (mode == ETHERNET_SOCKET_MODE_HOST_ONLY)
{
self->socketAddress.sll_pkttype = PACKET_HOST;
}
else if (mode == ETHERNET_SOCKET_MODE_MULTICAST)
{
self->socketAddress.sll_pkttype = PACKET_HOST | PACKET_MULTICAST;
}
}
}
void
Ethernet_addMulticastAddress(EthernetSocket self, const uint8_t* multicastAddress)
{
struct packet_mreq mreq;
memset(&mreq, 0, sizeof(struct packet_mreq));
mreq.mr_ifindex = self->socketAddress.sll_ifindex;
mreq.mr_alen = ETH_ALEN;
mreq.mr_type = PACKET_MR_MULTICAST;
mreq.mr_address[0] = multicastAddress[0];
mreq.mr_address[1] = multicastAddress[1];
mreq.mr_address[2] = multicastAddress[2];
mreq.mr_address[3] = multicastAddress[3];
mreq.mr_address[4] = multicastAddress[4];
mreq.mr_address[5] = multicastAddress[5];
int res = setsockopt(self->rawSocket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
if (res != 0)
{
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Setting multicast address failed");
}
}
void
Ethernet_setProtocolFilter(EthernetSocket self, uint16_t etherType)
{
if (etherType == 0x88b8)
{
/* enable linux kernel filtering for GOOSE */
struct sock_fprog fprog;
struct sock_filter filter[] = {
/* sudo tcpdump -i enx88d7f6377223 '(ether proto 0x88b8)' -dd */
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 1, 0x000088b8 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 }
};
fprog.len = sizeof(filter) / sizeof(*filter);
fprog.filter = filter;
if (setsockopt(self->rawSocket, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) == -1)
if (DEBUG_SOCKET)
printf("ETHERNET_LINUX: Applying filter failed");
}
else
{
self->socketAddress.sll_protocol = htons(etherType);
}
}
/* non-blocking receive */
int
Ethernet_receivePacket(EthernetSocket self, uint8_t* buffer, int bufferSize)
{
if (self->isBind == false)
{
if (bind(self->rawSocket, (struct sockaddr*) &self->socketAddress, sizeof(self->socketAddress)) == 0)
self->isBind = true;
else
return 0;
}
return recvfrom(self->rawSocket, buffer, bufferSize, MSG_DONTWAIT, 0, 0);
}
void
Ethernet_sendPacket(EthernetSocket self, uint8_t* buffer, int packetSize)
{
sendto(self->rawSocket, buffer, packetSize,
0, (struct sockaddr*) &(self->socketAddress), sizeof(self->socketAddress));
}
void
Ethernet_destroySocket(EthernetSocket self)
{
if (self)
{
close(self->rawSocket);
GLOBAL_FREEMEM(self);
}
}
bool
Ethernet_isSupported()
{
return true;
}