/* 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 #include #include #include #include #include #include #include #include #include #include #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; }