Compare commits

...

10 Commits

Author SHA1 Message Date
BodgeMaster 3f10ae6691 Add license 2025-06-27 01:53:15 +02:00
BodgeMaster 4e85a4efae README: add a note about netcat -l -p .... -_-
è_é RAAAAAGE
2025-02-10 18:50:53 +01:00
BodgeMaster 4e7adb9c53 fix a bug that prevented --help from being detected correctly 2025-02-10 16:34:51 +01:00
BodgeMaster b10a4158fa add build script for static binary 2025-02-10 14:26:17 +01:00
BodgeMaster 0c4ef661e2 README: add documentation 2025-02-06 19:32:29 +01:00
BodgeMaster d5547c3035 And now rewrite half of it bc the previous approach didn't actually work... 2025-02-06 17:30:48 +01:00
BodgeMaster da8543510a build.sh: show commands as they are run 2025-02-06 17:01:23 +01:00
BodgeMaster 2fac47f1c7 fix: exit when dup()ing stdin fails 2025-02-06 11:29:43 +01:00
BodgeMaster 1871098942 print strerror descriptions in error messages instead of errno 2025-02-06 11:28:49 +01:00
BodgeMaster 77542aa220 complete initial implementation
Never tested. Let's see how many bugs we find.
2025-02-05 23:06:16 +01:00
6 changed files with 187 additions and 18 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/ethercat
/ethercat-static

9
LICENSE.md Normal file
View File

@ -0,0 +1,9 @@
Copyright 2025 Interzero Product Cycle GmbH
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,3 +1,45 @@
```
|\ /|
| \______/ |
| ,,,,,,,, |
| |||||||| |
| |||||||| |
| ¯¯¯¯¯¯¯¯ |
|__________|
\- -/
```
# EtherCat
a tool to interface tap interfaces with stdin and stdout
A simple tool written in C that connects a Linux tap interface to stdin and stdout
## How to build
- Prerequisites: Linux, C compiler
- Might be easily portable to other systems that support tap interfaces, Idk.
- Run `./build.sh`
- Install by copying to `/usr/local/bin` or just leave it where it is
## How to use
Run as root:
```
ip tuntap add mode tap user USERNAME group GROUPNAME name tapN
ip link set tapN up
```
Run as user:
`ethercat tapN`
## Simple network tunnel:
Be warned: This will behave as if you had both sides connected with a (slow and less reliable) cable. This is more of a bridge than a regular VPN.
If you bridge both sides to a real NIC and you have DHCP servers on both networks, you will end up with two DHCP servers on the same network.
- Set up a bridge between a physical NIC and the tap interface on one or both sides
- Connect a port between the hosts, for example using SSH port forwarding
- Make two FIFOs on both hosts (mkfifo)
- Use netcat to connect the FIFOs between the hosts through the forwarded port
- `nc -l 10000 < fifo1 > fifo2` (with some implementations apparently `nc -l -p ...` -\_- )
- `nc localhost 10000 < fifo1 > fifo2`
- Start an instance of EtherCat with the other ends of the FIFOs on both hosts
- `./ethercat tapN < fifo2 > fifo1`

11
build-static.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
# 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
set -v
rm ethercat-static
gcc -Wall -Wextra -fPIE -pie -static -static-libgcc ethercat.c -o ethercat-static

View File

@ -1,3 +1,11 @@
#!/usr/bin/env bash
# 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
set -v
rm ethercat
gcc -Wall -Wextra ethercat.c -o ethercat

View File

@ -1,58 +1,156 @@
/*
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
#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))) {
} 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);
file_descriptor = open("/dev/net/tun", O_RDWR);
if (file_descriptor == -1) {
fprintf(stderr, "An error occurred while trying to open DEVICE_NODE_TUN: %i\n", errno);
return EXIT_RUNTIME;
}
if (ioctl(file_descriptor, TUNSETIFF, &ifr) == -1) {
fprintf(stderr, "An error occurred while trying to ioctl: %i\n", errno);
fprintf(stderr, "An error occurred while trying to ioctl: %s\n", strerror(errno));
close(file_descriptor);
return EXIT_RUNTIME;
}
//TODO:
// while input not EOF
// and the tap is still a valid device (it might go away)
// read from tap to stdout
// read from stdin to tap
polls_pls[0].fd = file_descriptor;
polls_pls[0].events = POLLIN;
//TODO: signal handler for SIGINT (and SIGHUP? maybe others?)
signal(SIGINT, stop);
signal(SIGTERM, stop);
if (fcntl(fd, F_GETFD) != -1 || errno != EBADF) {
close(file_descriptor);
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;
}