diff --git a/Constants.h b/Constants.h new file mode 100644 index 0000000..149a711 --- /dev/null +++ b/Constants.h @@ -0,0 +1,9 @@ +#define IF_TAP 1 +#define IF_TUN 2 + +#define ETHERNET_MIN_FRAME_SIZE 14 +#define TUN_MIN_FRAME_SIZE 5 + +#define MTU_MIN 74 +#define MTU_MAX 1522 +#define MTU_DEFAULT 329 \ No newline at end of file diff --git a/KISS.c b/KISS.c index d948afc..ff72b1d 100644 --- a/KISS.c +++ b/KISS.c @@ -12,8 +12,20 @@ uint8_t kiss_command = CMD_UNKNOWN; uint8_t frame_buffer[MAX_PAYLOAD]; uint8_t write_buffer[MAX_PAYLOAD*2+3]; +extern bool verbose; +extern int attached_if; +extern void cleanup(void); + void kiss_frame_received(int frame_len) { - + if (verbose) printf("Got KISS frame\r\n"); + int written = write(attached_if, frame_buffer, frame_len); + if (written == -1) { + if (verbose) printf("Could not write received KISS frame to network interface, is the interface up?\r\n"); + } else if (written != frame_len) { + printf("Error: Could only write %d of %d bytes to interface", written, frame_len); + cleanup(); + exit(1); + } } void kiss_serial_read(uint8_t sbyte) { diff --git a/KISS.h b/KISS.h index 556388f..f609b06 100644 --- a/KISS.h +++ b/KISS.h @@ -1,3 +1,6 @@ +#include +#include "Constants.h" + #define FEND 0xC0 #define FESC 0xDB #define TFEND 0xDC @@ -12,7 +15,7 @@ #define CMD_FULLDUPLEX 0x05 #define CMD_SETHARDWARE 0x06 -#define MAX_PAYLOAD 1522 +#define MAX_PAYLOAD MTU_MAX void kiss_serial_read(uint8_t sbyte); int kiss_write_frame(int serial_port, uint8_t* buffer, int frame_len); \ No newline at end of file diff --git a/Serial.c b/Serial.c index 2b882e8..ef91c03 100644 --- a/Serial.c +++ b/Serial.c @@ -1,11 +1,15 @@ #include "Serial.h" +extern void cleanup(); + int open_port(char* port) { int fd; fd = open(port, O_RDWR | O_NOCTTY | O_SYNC | O_NDELAY); if (fd == -1) { perror("The serial port could not be opened"); + cleanup(); + exit(1); } else { fcntl(fd, F_SETFL, 0); } @@ -85,7 +89,9 @@ bool setup_port(int fd, int speed) { set_speed(&tty, B230400); break; default: - printf("Error: Invalid port speed %d specified", speed); + printf("Error: Invalid port speed %d specified\r\n", speed); + cleanup(); + exit(1); return false; } diff --git a/Serial.h b/Serial.h index 84f0d39..401df49 100644 --- a/Serial.h +++ b/Serial.h @@ -1,10 +1,12 @@ #include +#include #include #include #include #include #include #include +#include "Constants.h" int open_port(char* port); int close_port(int fd); diff --git a/TAP.c b/TAP.c index acdf302..1b6c2fd 100644 --- a/TAP.c +++ b/TAP.c @@ -1,3 +1,132 @@ #include "TAP.h" -char tap_name[IFNAMSIZ]; \ No newline at end of file +char tap_name[IFNAMSIZ]; + +extern bool verbose; +extern bool noipv6; +extern bool set_ipv4; +extern bool set_netmask; +extern bool noup; +extern int mtu; +extern int device_type; +extern char if_name[IFNAMSIZ]; +extern char* ipv4_addr; +extern char* netmask; +extern void cleanup(); + +int open_tap(void) { + struct ifreq ifr; + int fd = open("/dev/net/tun", O_RDWR); + + if (fd < 0) { + perror("Could not open clone device"); + exit(1); + } else { + memset(&ifr, 0, sizeof(ifr)); + // TODO: Enable PI header again? + + if (device_type == IF_TAP) { + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + } else if (device_type == IF_TUN) { + ifr.ifr_flags = IFF_TUN; + } else { + printf("Error: Unsupported interface type\r\n"); + cleanup(); + exit(1); + } + + strcpy(tap_name, "tnc%d"); + strncpy(ifr.ifr_name, tap_name, IFNAMSIZ); + + if (ioctl(fd, TUNSETIFF, &ifr) < 0) { + perror("Could not configure network interface"); + exit(1); + } else { + strcpy(if_name, ifr.ifr_name); + + int inet = socket(AF_INET, SOCK_DGRAM, 0); + if (inet == -1) { + perror("Could not open AF_INET socket\r\n"); + cleanup(); + exit(1); + } else { + if (ioctl(inet, SIOCGIFFLAGS, &ifr) < 0) { + perror("Could not get interface flags from kernel"); + close(inet); + cleanup(); + exit(1); + } else { + ifr.ifr_mtu = mtu; + if (ioctl(inet, SIOCSIFMTU, &ifr) < 0) { + perror("Could not configure interface MTU"); + close(inet); + cleanup(); + exit(1); + } + + if (!noup) { + ifr.ifr_flags |= IFF_UP | IFF_RUNNING; + if (ioctl(inet, SIOCSIFFLAGS, &ifr) < 0) { + perror("Could not bring up interface"); + close(inet); + cleanup(); + exit(1); + } else { + if (set_ipv4) { + struct ifreq a_ifr; + struct sockaddr_in addr, snm; + + memset(&a_ifr, 0, sizeof(a_ifr)); + memset(&addr, 0, sizeof(addr)); + memset(&snm, 0, sizeof(addr)); + strncpy(a_ifr.ifr_name, ifr.ifr_name, IFNAMSIZ); + addr.sin_family = AF_INET; + snm.sin_family = AF_INET; + + int addr_conversion = inet_pton(AF_INET, ipv4_addr, &(addr.sin_addr)); + if (addr_conversion != 1) { + printf("Error: Invalid IPv4 address specified\r\n"); + close(inet); + cleanup(); + exit(1); + } else { + a_ifr.ifr_addr = *(struct sockaddr*)&addr; + if (ioctl(inet, SIOCSIFADDR, &a_ifr) < 0) { + perror("Could not set IP-address"); + close(inet); + cleanup(); + exit(1); + } else { + if (set_netmask) { + int snm_conversion = inet_pton(AF_INET, netmask, &(snm.sin_addr)); + if (snm_conversion != 1) { + printf("Error: Invalid subnet mask specified\r\n"); + close(inet); + cleanup(); + exit(1); + } else { + a_ifr.ifr_addr = *(struct sockaddr*)&snm; + if (ioctl(inet, SIOCSIFNETMASK, &a_ifr) < 0) { + perror("Could not set subnet mask"); + close(inet); + cleanup(); + exit(1); + } + } + } + } + } + } + } + } + } + } + + return fd; + } + } +} + +int close_tap(int tap_fd) { + return close(tap_fd); +} \ No newline at end of file diff --git a/TAP.h b/TAP.h index e69de29..b095249 100644 --- a/TAP.h +++ b/TAP.h @@ -0,0 +1,16 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +#include +#include "Constants.h" + +int open_tap(void); +int close_tap(int tap_fd); \ No newline at end of file diff --git a/tncattach.c b/tncattach.c index e0e6ad2..7f30429 100644 --- a/tncattach.c +++ b/tncattach.c @@ -1,44 +1,404 @@ #include #include #include +#include +#include +#include "Constants.h" #include "Serial.h" #include "KISS.h" #include "TAP.h" +#define BAUDRATE_DEFAULT 0 #define SERIAL_BUFFER_SIZE 512 -int attached_port; -uint8_t serial_buffer[512]; +#define IF_FD_INDEX 0 +#define TNC_FD_INDEX 1 +#define N_FDS 2 + +struct pollfd fds[N_FDS]; + +int attached_tnc; +int attached_if; + +char if_name[IFNAMSIZ]; + +uint8_t serial_buffer[MTU_MAX]; +uint8_t if_buffer[MTU_MAX]; + +bool verbose = false; +bool noipv6 = false; +bool noup = false; +bool daemonize = false; +bool set_ipv4 = false; +bool set_netmask = false; +char* ipv4_addr; +char* netmask; +int mtu; +int device_type = IF_TUN; + +void cleanup(void) { + close_port(attached_tnc); + close_tap(attached_if); +} void signal_handler(int signal) { - printf("\r\nClosing serial port...\r\n"); - close_port(attached_port); + cleanup(); exit(0); } -void read_loop(tnc) { - bool should_continue = true; - while (should_continue) { - int len = read(attached_port, serial_buffer, sizeof(serial_buffer)); - if (len > 0) { - for (int i = 0; i < len; i++) { - kiss_serial_read(serial_buffer[i]); - } +bool is_ipv6(uint8_t* frame) { + if (device_type == IF_TAP) { + if (frame[12] == 0x86 && frame[13] == 0xdd) { + return true; } else { - printf("Error: Could not read from serial port, exiting now\r\n"); - close_port(attached_port); - exit(1); + return false; } + } else if (device_type == IF_TUN) { + if (frame[2] == 0x86 && frame[3] == 0xdd) { + return true; + } else { + return false; + } + } else { + printf("Error: Unsupported interface type\r\n"); + cleanup(); + exit(1); } } -int main() { +void read_loop(void) { + bool should_continue = true; + int min_frame_size; + if (device_type == IF_TAP) { + min_frame_size = ETHERNET_MIN_FRAME_SIZE; + } else if (device_type == IF_TUN) { + min_frame_size = TUN_MIN_FRAME_SIZE; + } else { + printf("Error: Unsupported interface type\r\n"); + cleanup(); + exit(1); + } + + while (should_continue) { + + int poll_result = poll(fds, 2, -1); + if (poll_result != -1) { + for (int fdi = 0; fdi < N_FDS; fdi++) { + if (fds[fdi].revents != 0) { + // Check for hangup event + if (fds[fdi].revents & POLLHUP) { + if (fdi == IF_FD_INDEX) { + printf("Received hangup from interface\r\n"); + cleanup(); + exit(1); + } + if (fdi == TNC_FD_INDEX) { + printf("Received hangup from TNC\r\n"); + cleanup(); + exit(1); + } + } + + // Check for error event + if (fds[fdi].revents & POLLERR) { + if (fdi == IF_FD_INDEX) { + perror("Received error event from interface\r\n"); + cleanup(); + exit(1); + } + if (fdi == TNC_FD_INDEX) { + perror("Received error event from TNC\r\n"); + cleanup(); + exit(1); + } + } + + // If data is ready, read it + if (fds[fdi].revents & POLLIN) { + if (fdi == IF_FD_INDEX) { + int if_len = read(attached_if, if_buffer, sizeof(if_buffer)); + if (if_len > 0) { + if (if_len >= min_frame_size) { + if (!noipv6 || (noipv6 && !is_ipv6(if_buffer))) { + kiss_write_frame(attached_tnc, if_buffer, if_len); + } + } + } else { + printf("Error: Could not read from network interface, exiting now\r\n"); + cleanup(); + exit(1); + } + } + + if (fdi == TNC_FD_INDEX) { + int tnc_len = read(attached_tnc, serial_buffer, sizeof(serial_buffer)); + if (tnc_len > 0) { + if (verbose) printf("Data from TNC: %d bytes.\r\n", tnc_len); + for (int i = 0; i < tnc_len; i++) { + kiss_serial_read(serial_buffer[i]); + } + } else { + printf("Error: Could not read from TNC, exiting now\r\n"); + cleanup(); + exit(1); + } + } + } + } + } + } else { + should_continue = false; + } + } + cleanup(); + exit(1); +} + +const char *argp_program_version = "tncattach 0.1.1"; +const char *argp_program_bug_address = ""; +static char doc[] = "\r\nAttach TNC devices as system network interfaces\vAs an example, to attach the TNC connected to /dev/ttyUSB0 as a full ethernet device with an MTU of 576 bytes and assign an IPv4 address, use the following command:\r\n\r\n\ttncattach /dev/ttyUSB0 115200 -m 576 -e --ipv4 10.0.0.1/24\r\n\r\nTo create an interface that doesn't use ethernet, but transports IP directly, and filters IPv6 packets out, a command like the following can be used:\r\n\r\n\ttncattach /dev/ttyUSB0 115200 --noipv6 --ipv4 10.0.0.1/24"; +static char args_doc[] = "port baudrate"; +static struct argp_option options[] = { + { "mtu", 'm', "MTU", 0, "Specify interface MTU"}, + { "daemon", 'd', 0, 0, "Run tncattach as a daemon"}, + { "ethernet", 'e', 0, 0, "Create a full ethernet device"}, + { "ipv4", 'i', "IP_ADDRESS", 0, "Configure an IPv4 address on interface"}, + { "noipv6", 'n', 0, 0, "Filter IPv6 traffic from reaching TNC"}, + { "noup", 1, 0, 0, "Only create interface, don't bring it up"}, + { "verbose", 'v', 0, 0, "Enable verbose output"}, + { 0 } +}; + +#define N_ARGS 2 +struct arguments { + char *args[N_ARGS]; + char *ipv4; + int baudrate; + int mtu; + bool tap; + bool daemon; + bool verbose; + bool set_ipv4; + bool set_netmask; + bool noipv6; + bool noup; +}; + +static error_t parse_opt(int key, char *arg, struct argp_state *state) { + struct arguments *arguments = state->input; + + switch (key) { + case 'v': + arguments->verbose = true; + break; + + case 'e': + arguments->tap = true; + break; + + case 'm': + arguments->mtu = atoi(arg); + if (arguments->mtu < MTU_MIN || arguments->mtu > MTU_MAX) { + printf("Error: Invalid MTU specified\r\n\r\n"); + argp_usage(state); + } + break; + + case 'i': + arguments->ipv4 = arg; + arguments->set_ipv4 = true; + + if (strchr(arg, '/')) { + char* net = strchr(arg, '/'); + int pos = net-arg; + ipv4_addr = (char*)malloc(pos+1); + int mask = atoi(net+1); + strncpy(ipv4_addr, arg, pos); + switch (mask) { + case 0: + netmask = "0.0.0.0"; + break; + case 1: + netmask = "128.0.0.0"; + break; + case 2: + netmask = "192.0.0.0"; + break; + case 3: + netmask = "224.0.0.0"; + break; + case 4: + netmask = "240.0.0.0"; + break; + case 5: + netmask = "248.0.0.0"; + break; + case 6: + netmask = "252.0.0.0"; + break; + case 7: + netmask = "254.0.0.0"; + break; + case 8: + netmask = "255.0.0.0"; + break; + case 9: + netmask = "255.128.0.0"; + break; + case 10: + netmask = "255.192.0.0"; + break; + case 11: + netmask = "255.224.0.0"; + break; + case 12: + netmask = "255.240.0.0"; + break; + case 13: + netmask = "255.248.0.0"; + break; + case 14: + netmask = "255.252.0.0"; + break; + case 15: + netmask = "255.254.0.0"; + break; + case 16: + netmask = "255.255.0.0"; + break; + case 17: + netmask = "255.255.128.0"; + break; + case 18: + netmask = "255.255.192.0"; + break; + case 19: + netmask = "255.255.224.0"; + break; + case 20: + netmask = "255.255.240.0"; + break; + case 21: + netmask = "255.255.248.0"; + break; + case 22: + netmask = "255.255.252.0"; + break; + case 23: + netmask = "255.255.254.0"; + break; + case 24: + netmask = "255.255.255.0"; + break; + case 25: + netmask = "255.255.255.128"; + break; + case 26: + netmask = "255.255.255.192"; + break; + case 27: + netmask = "255.255.255.224"; + break; + case 28: + netmask = "255.255.255.240"; + break; + case 29: + netmask = "255.255.255.248"; + break; + case 30: + netmask = "255.255.255.252"; + break; + case 31: + netmask = "255.255.255.254"; + break; + case 32: + netmask = "255.255.255.255"; + break; + + default: + printf("Error: Invalid subnet mask specified\r\n"); + cleanup(); + exit(1); + } + + arguments->set_netmask = true; + } else { + arguments->set_netmask = false; + ipv4_addr = (char*)malloc(strlen(arg)+1); + strcpy(ipv4_addr, arg); + } + + break; + + case 'n': + arguments->noipv6 = true; + break; + + case 'd': + arguments->daemon = true; + arguments->verbose = false; + break; + + case 1: + arguments->noup = true; + break; + + case ARGP_KEY_ARG: + // Check if there's now too many text arguments + if (state->arg_num >= N_ARGS) argp_usage(state); + + // If not add to args + arguments->args[state->arg_num] = arg; + break; + + case ARGP_KEY_END: + // Check if there's too few text arguments + if (state->arg_num < N_ARGS) argp_usage(state); + break; + + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp argp = {options, parse_opt, args_doc, doc}; +int main(int argc, char **argv) { + struct arguments arguments; signal(SIGINT, signal_handler); - attached_port = open_port("/dev/tty.usbserial-"); - if (setup_port(attached_port, 115200)) { - printf("Port open\r\n"); - read_loop(attached_port); + arguments.baudrate = BAUDRATE_DEFAULT; + arguments.mtu = MTU_DEFAULT; + arguments.tap = false; + arguments.verbose = false; + arguments.set_ipv4 = false; + arguments.set_netmask = false; + arguments.noipv6 = false; + arguments.daemon = false; + arguments.noup = false; + + argp_parse(&argp, argc, argv, 0, 0, &arguments); + arguments.baudrate = atoi(arguments.args[1]); + + if (arguments.daemon) daemonize = true; + if (arguments.verbose) verbose = true; + if (arguments.tap) device_type = IF_TAP; + if (arguments.noipv6) noipv6 = true; + if (arguments.set_ipv4) set_ipv4 = true; + if (arguments.set_netmask) set_netmask = true; + if (arguments.noup) noup = true; + mtu = arguments.mtu; + + attached_if = open_tap(); + attached_tnc = open_port(arguments.args[0]); + if (setup_port(attached_tnc, arguments.baudrate)) { + printf("TNC interface configured as %s\r\n", if_name); + fds[IF_FD_INDEX].fd = attached_if; + fds[IF_FD_INDEX].events = POLLIN; + fds[TNC_FD_INDEX].fd = attached_tnc; + fds[TNC_FD_INDEX].events = POLLIN; + read_loop(); } return 0;