EtherCat/ethercat.c

157 lines
4.3 KiB
C

/*
Copyright 2025 Interzero Product Cycle GmbH
This program is free software under the terms of the BSD-3-Clause license.
A copy of this license should have been provided with the code.
If not, see https://opensource.org/license/BSD-3-Clause or https://spdx.org/licenses/BSD-3-Clause.html
*/
#include <errno.h>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define DEVICE_NODE_TUN "/dev/net/tun"
// This number mus be larger than the MTU.
#define BUFFER_SIZE 0x1000
#define NUMBER_OF_POLLED_FILES 2
#define POLL_TIMEOUT 100
#define EXIT_SUCCESS 0
#define EXIT_USAGE 1
#define EXIT_RUNTIME 2
int file_descriptor;
int poll_result;
char buffer[BUFFER_SIZE];
struct ifreq ifr;
bool stop_now = false;
ssize_t read_size;
ssize_t write_size;
// 0 file_descriptor
// 1 stdin
struct pollfd polls_pls[NUMBER_OF_POLLED_FILES];
void stop() {
stop_now = true;
}
void yeet(int fd) {
if (fcntl(fd, F_GETFD) != -1) {
close(fd);
}
}
int main(int argc, char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s TAP\n", argv[0]);
return EXIT_USAGE;
} else if ((strlen(argv[1])==2 && strncmp(argv[1], "-h", 2)==0) || (strlen(argv[1])==6 && strncmp(argv[1], "--help", 6)==0)) {
fprintf(stdout, "Usage: %s TAP\n", argv[0]);
return EXIT_SUCCESS;
}
if (freopen(NULL, "rb", stdin) == NULL) {
fprintf(stderr, "Failed to set stdin to binary mode: %s\n", strerror(errno));
return EXIT_RUNTIME;
}
polls_pls[1].fd = fileno(stdin);
polls_pls[1].events = POLLIN;
if (freopen(NULL, "wb", stdout) == NULL) {
fprintf(stderr, "Failed to set stdout to binary mode: %s\n", strerror(errno));
return EXIT_RUNTIME;
}
file_descriptor = open(DEVICE_NODE_TUN, O_RDWR);
if (file_descriptor == -1) {
fprintf(stderr, "Failed to open %s: %s\n", DEVICE_NODE_TUN, strerror(errno));
return EXIT_RUNTIME;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, argv[1], IFNAMSIZ);
if (ioctl(file_descriptor, TUNSETIFF, &ifr) == -1) {
fprintf(stderr, "An error occurred while trying to ioctl: %s\n", strerror(errno));
close(file_descriptor);
return EXIT_RUNTIME;
}
polls_pls[0].fd = file_descriptor;
polls_pls[0].events = POLLIN;
signal(SIGINT, stop);
signal(SIGTERM, stop);
while (fcntl(file_descriptor, F_GETFD) != -1 && !stop_now) {
poll_result = poll(polls_pls, NUMBER_OF_POLLED_FILES, POLL_TIMEOUT);
if (poll_result == -1 && errno != EINTR) {
fprintf(stderr, "An error occurred while polling: %s\n", strerror(errno));
yeet(file_descriptor);
return EXIT_RUNTIME;
}
if (poll_result == 0) {
continue;
}
if (polls_pls[0].revents & POLLIN) {
read_size = read(file_descriptor, buffer, BUFFER_SIZE);
if (read_size == -1) {
fprintf(stderr, "Failed to read from tap: %s\n", strerror(errno));
yeet(file_descriptor);
return EXIT_RUNTIME;
}
for (
write_size = 0;
read_size > 0 && !stop_now;
read_size = read_size - write_size
) {
// using read_size as the amount of remaining bytes
write_size = write(fileno(stdout), buffer, read_size);
if (write_size == -1) {
fprintf(stderr, "Failed to write stdout: %s\n", strerror(errno));
yeet(file_descriptor);
return EXIT_RUNTIME;
}
}
}
if (polls_pls[1].revents & POLLIN) {
read_size = read(fileno(stdin), buffer, BUFFER_SIZE);
if (read_size == -1) {
fprintf(stderr, "Failed to read from stdin: %s\n", strerror(errno));
yeet(file_descriptor);
return EXIT_RUNTIME;
}
if (read_size == 0) {
//TODO: is this still how to check for eof with poll?
// or is there another way?
break;
}
//TODO: How to properly deal with partial reads?
// accepts exactly one complete packet (Ethernet Frame?) at a time
write_size = write(file_descriptor, buffer, read_size);
if (write_size == -1) {
fprintf(stderr, "Failed to write tap: %s\n", strerror(errno));
yeet(file_descriptor);
return EXIT_RUNTIME;
}
}
}
yeet(file_descriptor);
return EXIT_SUCCESS;
}