├── TCP.h ├── Constants.h ├── TAP.h ├── Serial.h ├── KISS.h ├── .gitignore ├── TCP.c ├── makefile ├── LICENSE ├── KISS.c ├── Serial.c ├── tncattach.8 ├── README.md ├── TAP.c └── tncattach.c /TCP.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int open_tcp(char* ip, int port); 9 | int close_tcp(int fd); -------------------------------------------------------------------------------- /Constants.h: -------------------------------------------------------------------------------- 1 | #define IF_TAP 1 2 | #define IF_TUN 2 3 | 4 | #define ETHERNET_MIN_FRAME_SIZE 14 5 | #define TUN_MIN_FRAME_SIZE 5 6 | 7 | #define MTU_MIN 74 8 | #define MTU_MAX 1522 9 | #define MTU_DEFAULT 329 10 | 11 | #define TXQUEUELEN 10 12 | 13 | // ARP timings, in seconds 14 | #define ARP_BASE_REACHABLE_TIME 300 15 | #define ARP_RETRANS_TIME 5 -------------------------------------------------------------------------------- /TAP.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "Constants.h" 13 | 14 | int open_tap(void); 15 | int close_tap(int tap_fd); -------------------------------------------------------------------------------- /Serial.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "Constants.h" 10 | 11 | int open_port(char* port); 12 | int close_port(int fd); 13 | bool setup_port(int fs, int speed); 14 | bool set_port_blocking(int fd, bool should_block); -------------------------------------------------------------------------------- /KISS.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Constants.h" 3 | 4 | #define FEND 0xC0 5 | #define FESC 0xDB 6 | #define TFEND 0xDC 7 | #define TFESC 0xDD 8 | 9 | #define CMD_UNKNOWN 0xFE 10 | #define CMD_DATA 0x00 11 | #define CMD_PREAMBLE 0x01 12 | #define CMD_P 0x02 13 | #define CMD_SLOTTIME 0x03 14 | #define CMD_TXTAIL 0x04 15 | #define CMD_FULLDUPLEX 0x05 16 | #define CMD_SETHARDWARE 0x06 17 | 18 | #define MAX_PAYLOAD MTU_MAX 19 | 20 | void kiss_serial_read(uint8_t sbyte); 21 | int kiss_write_frame(int serial_port, uint8_t* buffer, int frame_len); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tncattach 2 | dist 3 | # Prerequisites 4 | *.d 5 | 6 | # Object files 7 | *.o 8 | *.ko 9 | *.obj 10 | *.elf 11 | 12 | # Linker output 13 | *.ilk 14 | *.map 15 | *.exp 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Libraries 22 | *.lib 23 | *.a 24 | *.la 25 | *.lo 26 | 27 | # Shared objects (inc. Windows DLLs) 28 | *.dll 29 | *.so 30 | *.so.* 31 | *.dylib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | *.i*86 38 | *.x86_64 39 | *.hex 40 | 41 | # Debug files 42 | *.dSYM/ 43 | *.su 44 | *.idb 45 | *.pdb 46 | 47 | # Kernel Module Compile Results 48 | *.mod* 49 | *.cmd 50 | .tmp_versions/ 51 | modules.order 52 | Module.symvers 53 | Mkfile.old 54 | dkms.conf 55 | -------------------------------------------------------------------------------- /TCP.c: -------------------------------------------------------------------------------- 1 | #include "TCP.h" 2 | 3 | int open_tcp(char* ip, int port) { 4 | int sockfd = socket(AF_INET, SOCK_STREAM, 0); 5 | 6 | if (sockfd < 0) { 7 | perror("Could not open AF_INET socket"); 8 | exit(1); 9 | } 10 | 11 | struct hostent *server; 12 | struct sockaddr_in serv_addr; 13 | 14 | server = gethostbyname(ip); 15 | 16 | if (server == NULL) { 17 | perror("Error resolving host"); 18 | exit(1); 19 | } 20 | 21 | memset(&serv_addr, 0, sizeof(serv_addr)); 22 | serv_addr.sin_family = AF_INET; 23 | 24 | memcpy(server->h_addr, &serv_addr.sin_addr.s_addr, server->h_length); 25 | serv_addr.sin_port = htons(port); 26 | 27 | if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { 28 | perror("Could not connect TCP socket"); 29 | exit(1); 30 | } 31 | 32 | return sockfd; 33 | } 34 | 35 | int close_tcp(int fd) { 36 | return close(fd); 37 | } -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | .PHONY: all clean install uninstall tncattach 3 | 4 | compiler = gcc 5 | flags = -Wall -std=gnu11 -static-libgcc 6 | 7 | all: tncattach 8 | rebuild: clean all 9 | 10 | clean: 11 | @echo "Cleaning tncattach build..." 12 | @rm -f tncattach 13 | 14 | tncattach: 15 | @echo "Making tncattach..." 16 | @echo "Compiling with: ${compiler}" 17 | ${compiler} ${flags} tncattach.c Serial.c TCP.c KISS.c TAP.c -o tncattach -Wall 18 | 19 | install: 20 | @echo "Installing tncattach..." 21 | @chmod a+x tncattach 22 | cp ./tncattach /usr/local/sbin/ 23 | @echo "Installing man page..." 24 | @mkdir -p /usr/local/man/man8 25 | @install -m 644 -o root -g root tncattach.8 /usr/local/man/man8/tncattach.8 26 | @echo "Updating mandb..." 27 | @mandb -f /usr/local/man/man8/tncattach.8 2> /dev/null 1> /dev/null 28 | @echo "Done" 29 | 30 | uninstall: 31 | @echo "Uninstalling tncattach" 32 | rm /usr/local/sbin/tncattach 33 | rm /usr/local/man/man8/tncattach.8 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mark Qvist / unsigned.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /KISS.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "KISS.h" 5 | #include "Serial.h" 6 | 7 | int frame_len; 8 | bool IN_FRAME; 9 | bool ESCAPE; 10 | 11 | uint8_t kiss_command = CMD_UNKNOWN; 12 | uint8_t frame_buffer[MAX_PAYLOAD]; 13 | uint8_t write_buffer[MAX_PAYLOAD*2+3]; 14 | 15 | extern bool verbose; 16 | extern bool daemonize; 17 | extern int attached_if; 18 | extern int device_type; 19 | extern void cleanup(void); 20 | 21 | void kiss_frame_received(int frame_len) { 22 | if ( (device_type == IF_TUN && frame_len >= TUN_MIN_FRAME_SIZE) || (device_type == IF_TAP && frame_len >= ETHERNET_MIN_FRAME_SIZE) ) { 23 | int written = write(attached_if, frame_buffer, frame_len); 24 | if (written == -1) { 25 | if (verbose && !daemonize) printf("Could not write received KISS frame (%d bytes) to network interface, is the interface up?\r\n", frame_len); 26 | } else if (written != frame_len) { 27 | if (!daemonize) printf("Error: Could only write %d of %d bytes to interface", written, frame_len); 28 | cleanup(); 29 | exit(1); 30 | } 31 | if (verbose && !daemonize) printf("Got %d bytes from TNC, wrote %d bytes to interface\r\n", frame_len, written); 32 | } 33 | } 34 | 35 | void kiss_serial_read(uint8_t sbyte) { 36 | if (IN_FRAME && sbyte == FEND && kiss_command == CMD_DATA) { 37 | IN_FRAME = false; 38 | kiss_frame_received(frame_len); 39 | } else if (sbyte == FEND) { 40 | IN_FRAME = true; 41 | kiss_command = CMD_UNKNOWN; 42 | frame_len = 0; 43 | } else if (IN_FRAME && frame_len < MAX_PAYLOAD) { 44 | // Have a look at the command byte first 45 | if (frame_len == 0 && kiss_command == CMD_UNKNOWN) { 46 | // Strip of port nibble 47 | kiss_command = sbyte & 0x0F; 48 | } else if (kiss_command == CMD_DATA) { 49 | if (sbyte == FESC) { 50 | ESCAPE = true; 51 | } else { 52 | if (ESCAPE) { 53 | if (sbyte == TFEND) sbyte = FEND; 54 | if (sbyte == TFESC) sbyte = FESC; 55 | ESCAPE = false; 56 | } 57 | 58 | if (frame_len < MAX_PAYLOAD) { 59 | frame_buffer[frame_len++] = sbyte; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | int kiss_write_frame(int serial_port, uint8_t* buffer, int frame_len) { 67 | int write_len = 0; 68 | write_buffer[write_len++] = FEND; 69 | write_buffer[write_len++] = CMD_DATA; 70 | for (int i = 0; i < frame_len; i++) { 71 | uint8_t byte = buffer[i]; 72 | if (byte == FEND) { 73 | write_buffer[write_len++] = FESC; 74 | write_buffer[write_len++] = TFEND; 75 | } else if (byte == FESC) { 76 | write_buffer[write_len++] = FESC; 77 | write_buffer[write_len++] = TFESC; 78 | } else { 79 | write_buffer[write_len++] = byte; 80 | } 81 | } 82 | write_buffer[write_len++] = FEND; 83 | 84 | return write(serial_port, write_buffer, write_len); 85 | } -------------------------------------------------------------------------------- /Serial.c: -------------------------------------------------------------------------------- 1 | #include "Serial.h" 2 | 3 | extern void cleanup(); 4 | 5 | int open_port(char* port) { 6 | int fd; 7 | fd = open(port, O_RDWR | O_NOCTTY | O_SYNC | O_NDELAY); 8 | 9 | if (fd == -1) { 10 | perror("The serial port could not be opened"); 11 | cleanup(); 12 | exit(1); 13 | } else { 14 | fcntl(fd, F_SETFL, 0); 15 | } 16 | 17 | return fd; 18 | } 19 | 20 | int close_port(int fd) { 21 | return close(fd); 22 | } 23 | 24 | void set_speed(void *tty_s, int speed) { 25 | cfsetospeed(tty_s, speed); 26 | cfsetispeed(tty_s, speed); 27 | } 28 | 29 | bool setup_port(int fd, int speed) { 30 | struct termios tty; 31 | if (tcgetattr(fd, &tty) != 0) { 32 | perror("Error setting port speed, could not read port parameters"); 33 | return false; 34 | } 35 | 36 | switch (speed) { 37 | case 0: 38 | set_speed(&tty, B0); 39 | break; 40 | case 50: 41 | set_speed(&tty, B50); 42 | break; 43 | case 75: 44 | set_speed(&tty, B75); 45 | break; 46 | case 110: 47 | set_speed(&tty, B110); 48 | break; 49 | case 134: 50 | set_speed(&tty, B134); 51 | break; 52 | case 150: 53 | set_speed(&tty, B150); 54 | break; 55 | case 200: 56 | set_speed(&tty, B200); 57 | break; 58 | case 300: 59 | set_speed(&tty, B300); 60 | break; 61 | case 600: 62 | set_speed(&tty, B600); 63 | break; 64 | case 1200: 65 | set_speed(&tty, B1200); 66 | break; 67 | case 2400: 68 | set_speed(&tty, B2400); 69 | break; 70 | case 4800: 71 | set_speed(&tty, B4800); 72 | break; 73 | case 9600: 74 | set_speed(&tty, B9600); 75 | break; 76 | case 19200: 77 | set_speed(&tty, B19200); 78 | break; 79 | case 38400: 80 | set_speed(&tty, B38400); 81 | break; 82 | case 57600: 83 | set_speed(&tty, B57600); 84 | break; 85 | case 115200: 86 | set_speed(&tty, B115200); 87 | break; 88 | case 230400: 89 | set_speed(&tty, B230400); 90 | break; 91 | default: 92 | printf("Error: Invalid port speed %d specified\r\n", speed); 93 | cleanup(); 94 | exit(1); 95 | return false; 96 | } 97 | 98 | // Set 8-bit characters, no parity, one stop bit 99 | tty.c_cflag |= CS8; 100 | tty.c_cflag &= ~PARENB; 101 | tty.c_cflag &= ~CSTOPB; 102 | 103 | // Disable hardware flow control 104 | tty.c_cflag &= ~CRTSCTS; 105 | 106 | // Enable reading and ignore modem 107 | // control lines 108 | tty.c_cflag |= CREAD | CLOCAL; 109 | 110 | // Disable canonical mode, echo 111 | // and signal characters. 112 | tty.c_lflag &= ~ICANON; 113 | tty.c_lflag &= ~ECHO; 114 | tty.c_lflag &= ~ECHOE; 115 | tty.c_lflag &= ~ECHONL; 116 | tty.c_lflag &= ~ISIG; 117 | 118 | // Disable processing of input, 119 | // just pass the raw data. 120 | tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); 121 | 122 | // Disable XON/XOFF software flow control. 123 | tty.c_iflag &= ~(IXON | IXOFF | IXANY); 124 | 125 | // Disable processing output bytes 126 | // and new line conversions 127 | tty.c_oflag &= ~OPOST; 128 | tty.c_oflag &= ~ONLCR; 129 | 130 | // Block forever until at least one byte is read. 131 | tty.c_cc[VMIN] = 1; 132 | tty.c_cc[VTIME] = 0; 133 | 134 | // TODO: Check these 135 | // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX) 136 | // tty.c_oflag &= ~OXTABS; 137 | // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX) 138 | // tty.c_oflag &= ~ONOEOT; 139 | 140 | if (tcsetattr(fd, TCSANOW, &tty) != 0) { 141 | perror("Could not configure serial port parameters"); 142 | return false; 143 | } else { 144 | return true; 145 | } 146 | } 147 | 148 | bool set_port_blocking(int fd, bool should_block) { 149 | struct termios tty; 150 | memset(&tty, 0, sizeof tty); 151 | 152 | if (tcgetattr(fd, &tty) != 0) { 153 | perror("Error configuring port blocking behaviour, could not read port parameters"); 154 | return false; 155 | } else { 156 | // TODO: Implement this correctly 157 | if (should_block) { 158 | // Block forever until at least one byte is read. 159 | tty.c_cc[VMIN] = 1; 160 | tty.c_cc[VTIME] = 0; 161 | } else { 162 | // Never block, always return immediately with 163 | // whatever is available. 164 | tty.c_cc[VMIN] = 0; 165 | tty.c_cc[VTIME] = 0; 166 | } 167 | if (tcsetattr(fd, TCSANOW, &tty) != 0) { 168 | perror("Could not set port parameters while configuring blocking behaviour"); 169 | return false; 170 | } else { 171 | return true; 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /tncattach.8: -------------------------------------------------------------------------------- 1 | .TH tncattach 8 "September 12, 2020" 2 | 3 | .SH NAME 4 | . 5 | . 6 | tncattach \- Attach TNC devices as system network interfaces 7 | 8 | .SH SYNOPSIS 9 | . 10 | . 11 | \f[B]tncattach\f[R] [OPTION...] port baudrate 12 | 13 | .SH DESCRIPTION 14 | Attach KISS TNC devices as network interfaces in Linux. This program allows you to attach TNCs or any KISS-compatible device as a network interface. This program does not need any kernel modules, and has no external dependencies outside the standard Linux and GNU C libraries. 15 | 16 | .SH OPTIONS 17 | . 18 | . 19 | .TP 20 | .BI \-m, \-\-mtu=MTU 21 | . 22 | Specify interface MTU 23 | . 24 | . 25 | .TP 26 | .BI \-e, \-\-ethernet 27 | Create a full ethernet device 28 | . 29 | . 30 | .TP 31 | .BI \-i, \-\-ipv4=IP_ADDRESS 32 | Configure an IPv4 address on interface 33 | . 34 | . 35 | .TP 36 | .BI \-n, \-\-noipv6 37 | Filter IPv6 traffic from reaching TNC 38 | . 39 | . 40 | .TP 41 | .BI \-\-noup 42 | Only create interface, don't bring it up 43 | . 44 | . 45 | .TP 46 | .BI \-T, \-\-kisstcp 47 | Use KISS over TCP instead of serial port 48 | . 49 | . 50 | .TP 51 | .BI \-H, \-\-tcphost=TCP_HOST 52 | Host to connect to when using KISS over TCP 53 | . 54 | . 55 | .TP 56 | .BI \-P, \-\-tcpport=TCP_PORT 57 | TCP port when using KISS over TCP 58 | . 59 | . 60 | .TP 61 | .BI \-t, \-\-interval=SECONDS 62 | Maximum interval between station identifications 63 | . 64 | . 65 | .TP 66 | .B \-s, \-\-id=CALLSIGN 67 | Station identification data 68 | . 69 | . 70 | .TP 71 | .BI \-d, \-\-daemon 72 | Run tncattach as a daemon 73 | . 74 | . 75 | .TP 76 | .BI \-v, \-\-verbose 77 | Enable verbose output 78 | . 79 | . 80 | .TP 81 | .BI \-?, \-\-help 82 | Show help 83 | . 84 | . 85 | .TP 86 | .BI \-\-usage 87 | Give a short usage message 88 | . 89 | . 90 | .TP 91 | .BI \-V, \-\-version 92 | Print program version 93 | . 94 | . 95 | 96 | .SH USAGE 97 | The program supports attaching TNCs as point-to-point tunnel devices, or generic ethernet devices. The ethernet mode is suitable for point-to-multipoint setups, and can be enabled with the corresponding command line switch. If you only need point-to-point links, it is advisable to just use the standard point-to-point mode, since it doesn't incur the ethernet header overhead on each packet. 98 | .P 99 | If you want to connect to a virtual KISS TNC over a TCP connection, you can use the -T option, along with the -H and -P options to specify the host and port. 100 | .P 101 | Additionally, it is worth noting that tncattach can filter out IPv6 packets from reaching the TNC. Most operating systems attempts to autoconfigure IPv6 when an interface is brought up, which results in a substantial amount of IPv6 traffic generated by router solicitations and similar, which is usually unwanted for packet radio links and similar. 102 | .P 103 | If you intend to use tncattach on a system with mDNS services enabled (avahi-daemon, for example), you may want to consider modifying your mDNS setup to exclude TNC interfaces, or turning it off entirely, since it will generate a lot of traffic that might be unwanted. 104 | 105 | .SH STATION IDENTIFICATION 106 | 107 | You can configure tncattach to automatically transmit station identification beacons according to a given interval, by using the --id and --interval options. Identification will be transmitted as raw data frames with whatever content has been specified in the --id option. Useful for amateur radio use, or other areas where station identification is necessary. 108 | .P 109 | Identification beacons will be transmitted when: 110 | .P 111 | .IP 112 | There is outgoing data to send, and the specified interval has elapsed. 113 | .IP 114 | The specified interval elapses, and data has been sent since the last ID beacon. 115 | .IP 116 | The program exits, if any data frames have been transmitted since the last ID beacon. 117 | .P 118 | The above methodology should comply with station identification rules for amateur radio in most parts of the world, and complies with US Part 97 rules. 119 | 120 | .SH EXAMPLES 121 | . 122 | Create an ethernet device with a USB-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: 123 | .IP 124 | sudo tncattach /dev/ttyUSB0 115200 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 125 | .P 126 | Create an ethernet device with a TCP-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: 127 | .IP 128 | sudo tncattach -T -H localhost -P 8001 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 129 | .P 130 | You can interact with the interface like any other using the ip or ifconfig utilities. 131 | .p 132 | Check interface is running: 133 | .P 134 | # ifconfig 135 | .br 136 | tnc0: flags=4305 mtu 400 137 | .br 138 | inet 10.93.0.1 netmask 255.255.255.255 destination 10.93.0.2 139 | .br 140 | unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC) 141 | .br 142 | RX packets 0 bytes 0 (0.0 B) 143 | .br 144 | RX errors 0 dropped 0 overruns 0 frame 0 145 | .br 146 | TX packets 0 bytes 0 (0.0 B) 147 | .br 148 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 149 | .P 150 | .SH WORTH KNOWING ON RASPBIAN 151 | On some versions of Raspbian (and probably other operating systems), the DHCP client daemon dhcpcd interferes with TNC interfaces, by overriding their MTU and trying to auto-configure link-local addresses. You probably don't want this, and it can be disabled by editing the /etc/dhcpcd.conf file, adding a statement telling dhcpcd to ignore your TNC interface: 152 | .P 153 | # Add the following statement somewhere at the beginning 154 | .br 155 | # of /etc/dhcpcd.conf to prevent dhcpcd from changing MTU 156 | .br 157 | denyinterfaces tnc0 158 | 159 | .SH SEE ALSO 160 | 161 | rnodeconfigutil(8) 162 | 163 | .SH AUTHOR 164 | 165 | Mark Qvist 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TNC Attach 2 | ========== 3 | Attach KISS TNC devices as network interfaces in Linux. This program allows you to attach TNCs or any KISS-compatible device as a network interface. This program does not need any kernel modules, and has no external dependencies outside the standard Linux and GNU C libraries. 4 | 5 | ## Installation 6 | 7 | Currently it is recommended to compile and install __tncattach__ from source with the below commands. 8 | 9 | If that is not possible for you, precompiled __amd64__ and __armhf__ (Raspberry Pi and similar) binaries have been provided in the releases section. You can [download the latest release here](https://github.com/markqvist/tncattach/releases). 10 | 11 | ```sh 12 | # If you don't already have a compiler installed 13 | sudo apt install build-essential 14 | 15 | # Clone repository from GitHub 16 | git clone https://github.com/markqvist/tncattach.git 17 | 18 | # Move into source directory 19 | cd tncattach 20 | 21 | # Make program 22 | make 23 | 24 | # Install to system 25 | sudo make install 26 | ``` 27 | 28 | ## Using tncattach 29 | 30 | Using __tncattach__ is simple. Run the program from the command line, specifying which serial port the TNC is connected to, and the serial port baud-rate, and __tncattach__ takes care of the rest. In most cases, depending on what you intend to do, you probably want to use some of the options, though. See the examples section below for usage examples. 31 | 32 | ``` 33 | Usage: tncattach [OPTION...] port baudrate 34 | 35 | Attach TNC devices as system network interfaces 36 | 37 | -m, --mtu=MTU Specify interface MTU 38 | -e, --ethernet Create a full ethernet device 39 | -i, --ipv4=IP_ADDRESS Configure an IPv4 address on interface 40 | -n, --noipv6 Filter IPv6 traffic from reaching TNC 41 | --noup Only create interface, don't bring it up 42 | -T, --kisstcp Use KISS over TCP instead of serial port 43 | -H, --tcphost=TCP_HOST Host to connect to when using KISS over TCP 44 | -P, --tcpport=TCP_PORT TCP port when using KISS over TCP 45 | -t, --interval=SECONDS Maximum interval between station identifications 46 | -s, --id=CALLSIGN Station identification data 47 | -d, --daemon Run tncattach as a daemon 48 | -v, --verbose Enable verbose output 49 | -?, --help Give this help list 50 | --usage Give a short usage message 51 | -V, --version Print program version 52 | ``` 53 | 54 | The program supports attaching TNCs as point-to-point tunnel devices, or generic ethernet devices. The ethernet mode is suitable for point-to-multipoint setups, and can be enabled with the corresponding command line switch. If you only need point-to-point links, it is advisable to just use the standard point-to-point mode, since it doesn't incur the ethernet header overhead on each packet. 55 | 56 | If you want to connect to a virtual KISS TNC over a TCP connection, you can use the -T option, along with the -H and -P options to specify the host and port. 57 | 58 | Additionally, it is worth noting that __tncattach__ can filter out IPv6 packets from reaching the TNC. Most operating systems attempts to autoconfigure IPv6 when an interface is brought up, which results in a substantial amount of IPv6 traffic generated by router solicitations and similar, which is usually unwanted for packet radio links and similar. 59 | 60 | If you intend to use __tncattach__ on a system with mDNS services enabled (avahi-daemon, for example), you may want to consider modifying your mDNS setup to exclude TNC interfaces, or turning it off entirely, since it will generate a lot of traffic that might be unwanted. 61 | 62 | ## Station Identification 63 | 64 | You can configure tncattach to automatically transmit station identification beacons according to a given interval, by using the --id and --interval options. Identification will be transmitted as raw data frames with whatever content has been specified in the --id option. Useful for amateur radio use, or other areas where station identification is necessary. 65 | 66 | Identification beacons will be transmitted when: 67 | 68 | - There is outgoing data to send, and the specified interval has elapsed. 69 | - The specified interval elapses, and data has been sent since the last ID beacon. 70 | - The program exits, if any data frames have been transmitted since the last ID beacon. 71 | 72 | The above methodology should comply with station identification rules for amateur radio in most parts of the world, and complies with US Part 97 rules. 73 | 74 | ## Examples 75 | 76 | Create an ethernet device with a USB-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: 77 | 78 | ```sh 79 | # Attach interface 80 | sudo tncattach /dev/ttyUSB0 115200 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 81 | ``` 82 | 83 | Create an ethernet device with a TCP-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: 84 | 85 | ```sh 86 | # Attach interface 87 | sudo tncattach -T -H localhost -P 8001 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 88 | ``` 89 | 90 | You can interact with the interface like any other using the __ip__ or __ifconfig__ utilities: 91 | 92 | ```sh 93 | # Check interface is running 94 | ifconfig 95 | 96 | tnc0: flags=579 mtu 576 97 | inet 10.92.0.10 netmask 255.255.255.0 broadcast 10.92.0.255 98 | ether 02:56:ad:f2:40:33 txqueuelen 1000 (Ethernet) 99 | RX packets 0 bytes 0 (0.0 B) 100 | RX errors 0 dropped 0 overruns 0 frame 0 101 | TX packets 0 bytes 0 (0.0 B) 102 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 103 | ``` 104 | 105 | Create a point-to-point link: 106 | 107 | ```sh 108 | # Attach interface 109 | sudo tncattach /dev/ttyUSB0 115200 --mtu 400 --noipv6 --noup 110 | 111 | # Configure IP addresses for point-to-point link 112 | sudo ifconfig tnc0 10.93.0.1 pointopoint 10.93.0.2 113 | 114 | # Check interface 115 | ifconfig 116 | 117 | tnc0: flags=4305 mtu 400 118 | inet 10.93.0.1 netmask 255.255.255.255 destination 10.93.0.2 119 | unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC) 120 | RX packets 0 bytes 0 (0.0 B) 121 | RX errors 0 dropped 0 overruns 0 frame 0 122 | TX packets 0 bytes 0 (0.0 B) 123 | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 124 | 125 | ``` 126 | 127 | ## Worth Knowing on Raspbian 128 | 129 | On some versions of Raspbian (and probably other operating systems), the DHCP client daemon _dhcpcd_ interferes with TNC interfaces, by overriding their MTU and trying to auto-configure link-local addresses. You probably don't want this, and it can be disabled by editing the __/etc/dhcpcd.conf__ file, adding a statement telling _dhcpcd_ to ignore your TNC interface: 130 | 131 | ``` 132 | # Add the following statement somewhere at the beginning 133 | # of /etc/dhcpcd.conf to prevent dhcpcd from changing MTU 134 | 135 | denyinterfaces tnc0 136 | ``` 137 | 138 | ## Support tncattach development 139 | You can help support the continued development of open, free and private communications systems by donating via one of the following channels: 140 | 141 | - Ethereum: 0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a 142 | - Bitcoin: 3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq 143 | - Ko-Fi: https://ko-fi.com/markqvist 144 | -------------------------------------------------------------------------------- /TAP.c: -------------------------------------------------------------------------------- 1 | #include "TAP.h" 2 | 3 | char tap_name[IFNAMSIZ]; 4 | 5 | extern bool verbose; 6 | extern bool noipv6; 7 | extern bool set_ipv4; 8 | extern bool set_netmask; 9 | extern bool noup; 10 | extern int mtu; 11 | extern int device_type; 12 | extern char if_name[IFNAMSIZ]; 13 | extern char* ipv4_addr; 14 | extern char* netmask; 15 | extern void cleanup(); 16 | 17 | int open_tap(void) { 18 | struct ifreq ifr; 19 | int fd = open("/dev/net/tun", O_RDWR); 20 | 21 | if (fd < 0) { 22 | perror("Could not open clone device"); 23 | exit(1); 24 | } else { 25 | memset(&ifr, 0, sizeof(ifr)); 26 | // TODO: Enable PI header again? 27 | 28 | if (device_type == IF_TAP) { 29 | ifr.ifr_flags = IFF_TAP | IFF_NO_PI; 30 | } else if (device_type == IF_TUN) { 31 | ifr.ifr_flags = IFF_TUN; 32 | } else { 33 | printf("Error: Unsupported interface type\r\n"); 34 | cleanup(); 35 | exit(1); 36 | } 37 | 38 | strcpy(tap_name, "tnc%d"); 39 | strncpy(ifr.ifr_name, tap_name, IFNAMSIZ); 40 | 41 | if (ioctl(fd, TUNSETIFF, &ifr) < 0) { 42 | perror("Could not configure network interface"); 43 | exit(1); 44 | } else { 45 | strcpy(if_name, ifr.ifr_name); 46 | 47 | int inet = socket(AF_INET, SOCK_DGRAM, 0); 48 | if (inet == -1) { 49 | perror("Could not open AF_INET socket"); 50 | cleanup(); 51 | exit(1); 52 | } else { 53 | if (ioctl(inet, SIOCGIFMTU, &ifr) < 0) { 54 | perror("Could not get interface flags from kernel"); 55 | close(inet); 56 | cleanup(); 57 | exit(1); 58 | } else { 59 | ifr.ifr_mtu = mtu; 60 | if (ioctl(inet, SIOCSIFMTU, &ifr) < 0) { 61 | perror("Could not configure interface MTU"); 62 | close(inet); 63 | cleanup(); 64 | exit(1); 65 | } 66 | 67 | // Configure TX queue length 68 | if (ioctl(inet, SIOCGIFTXQLEN, &ifr) < 0) { 69 | perror("Could not get interface flags from kernel"); 70 | close(inet); 71 | cleanup(); 72 | exit(1); 73 | } else { 74 | ifr.ifr_qlen = TXQUEUELEN; 75 | if (ioctl(inet, SIOCSIFTXQLEN, &ifr) < 0) { 76 | perror("Could not set interface TX queue length"); 77 | close(inet); 78 | cleanup(); 79 | exit(1); 80 | } 81 | } 82 | 83 | // Configure ARP characteristics 84 | char path_buf[256]; 85 | if (device_type == IF_TAP) { 86 | snprintf(path_buf, sizeof(path_buf), "/proc/sys/net/ipv4/neigh/%s/base_reachable_time_ms", ifr.ifr_name); 87 | int arp_fd = open(path_buf, O_WRONLY); 88 | if (arp_fd < 0) { 89 | perror("Could not open proc entry for ARP parameters"); 90 | close(inet); 91 | cleanup(); 92 | exit(1); 93 | } else { 94 | if (dprintf(arp_fd, "%d", ARP_BASE_REACHABLE_TIME*1000) <= 0) { 95 | perror("Could not configure interface ARP parameter base_reachable_time_ms"); 96 | close(inet); 97 | close(arp_fd); 98 | cleanup(); 99 | exit(1); 100 | } else { 101 | close(arp_fd); 102 | } 103 | } 104 | 105 | snprintf(path_buf, sizeof(path_buf), "/proc/sys/net/ipv4/neigh/%s/retrans_time_ms", ifr.ifr_name); 106 | arp_fd = open(path_buf, O_WRONLY); 107 | if (arp_fd < 0) { 108 | perror("Could not open proc entry for ARP parameters"); 109 | close(inet); 110 | cleanup(); 111 | exit(1); 112 | } else { 113 | if (dprintf(arp_fd, "%d", ARP_RETRANS_TIME*1000) <= 0) { 114 | perror("Could not configure interface ARP parameter retrans_time_ms"); 115 | close(inet); 116 | close(arp_fd); 117 | cleanup(); 118 | exit(1); 119 | } else { 120 | close(arp_fd); 121 | } 122 | } 123 | } 124 | 125 | // Bring up if requested 126 | if (!noup) { 127 | if (ioctl(inet, SIOCGIFFLAGS, &ifr) < 0) { 128 | perror("Could not get interface flags from kernel"); 129 | close(inet); 130 | cleanup(); 131 | exit(1); 132 | } else { 133 | ifr.ifr_flags |= IFF_UP | IFF_RUNNING; 134 | if (ioctl(inet, SIOCSIFFLAGS, &ifr) < 0) { 135 | perror("Could not bring up interface"); 136 | close(inet); 137 | cleanup(); 138 | exit(1); 139 | } else { 140 | if (set_ipv4) { 141 | struct ifreq a_ifr; 142 | struct sockaddr_in addr, snm; 143 | 144 | memset(&a_ifr, 0, sizeof(a_ifr)); 145 | memset(&addr, 0, sizeof(addr)); 146 | memset(&snm, 0, sizeof(addr)); 147 | strncpy(a_ifr.ifr_name, ifr.ifr_name, IFNAMSIZ); 148 | addr.sin_family = AF_INET; 149 | snm.sin_family = AF_INET; 150 | 151 | int addr_conversion = inet_pton(AF_INET, ipv4_addr, &(addr.sin_addr)); 152 | if (addr_conversion != 1) { 153 | printf("Error: Invalid IPv4 address specified\r\n"); 154 | close(inet); 155 | cleanup(); 156 | exit(1); 157 | } else { 158 | a_ifr.ifr_addr = *(struct sockaddr*)&addr; 159 | if (ioctl(inet, SIOCSIFADDR, &a_ifr) < 0) { 160 | perror("Could not set IP-address"); 161 | close(inet); 162 | cleanup(); 163 | exit(1); 164 | } else { 165 | if (set_netmask) { 166 | int snm_conversion = inet_pton(AF_INET, netmask, &(snm.sin_addr)); 167 | if (snm_conversion != 1) { 168 | printf("Error: Invalid subnet mask specified\r\n"); 169 | close(inet); 170 | cleanup(); 171 | exit(1); 172 | } else { 173 | a_ifr.ifr_addr = *(struct sockaddr*)&snm; 174 | if (ioctl(inet, SIOCSIFNETMASK, &a_ifr) < 0) { 175 | perror("Could not set subnet mask"); 176 | close(inet); 177 | cleanup(); 178 | exit(1); 179 | } 180 | } 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | return fd; 192 | } 193 | } 194 | } 195 | 196 | int close_tap(int tap_fd) { 197 | return close(tap_fd); 198 | } -------------------------------------------------------------------------------- /tncattach.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "Constants.h" 10 | #include "Serial.h" 11 | #include "KISS.h" 12 | #include "TCP.h" 13 | #include "TAP.h" 14 | 15 | #define BAUDRATE_DEFAULT 0 16 | #define SERIAL_BUFFER_SIZE 512 17 | 18 | #define IF_FD_INDEX 0 19 | #define TNC_FD_INDEX 1 20 | #define N_FDS 2 21 | 22 | struct pollfd fds[N_FDS]; 23 | 24 | int attached_tnc; 25 | int attached_if; 26 | 27 | char if_name[IFNAMSIZ]; 28 | 29 | uint8_t serial_buffer[MTU_MAX]; 30 | uint8_t if_buffer[MTU_MAX]; 31 | 32 | bool verbose = false; 33 | bool noipv6 = false; 34 | bool noup = false; 35 | bool daemonize = false; 36 | bool set_ipv4 = false; 37 | bool set_netmask = false; 38 | bool kiss_over_tcp = false; 39 | char* ipv4_addr; 40 | char* netmask; 41 | 42 | char* tcp_host; 43 | int tcp_port; 44 | 45 | int mtu; 46 | int device_type = IF_TUN; 47 | 48 | char* id; 49 | int id_interval = -1; 50 | time_t last_id = 0; 51 | bool tx_since_last_id = false; 52 | 53 | void cleanup(void) { 54 | if (kiss_over_tcp) { 55 | close_tcp(attached_tnc); 56 | } else { 57 | close_port(attached_tnc); 58 | } 59 | close_tap(attached_if); 60 | } 61 | 62 | bool is_ipv6(uint8_t* frame) { 63 | if (device_type == IF_TAP) { 64 | if (frame[12] == 0x86 && frame[13] == 0xdd) { 65 | return true; 66 | } else { 67 | return false; 68 | } 69 | } else if (device_type == IF_TUN) { 70 | if (frame[2] == 0x86 && frame[3] == 0xdd) { 71 | return true; 72 | } else { 73 | return false; 74 | } 75 | } else { 76 | printf("Error: Unsupported interface type\r\n"); 77 | cleanup(); 78 | exit(1); 79 | } 80 | } 81 | 82 | time_t time_now(void) { 83 | time_t now = time(NULL); 84 | if (now == -1) { 85 | if (daemonize) { 86 | syslog(LOG_ERR, "Could not get system time, exiting now"); 87 | } else { 88 | printf("Error: Could not get system time, exiting now\r\n"); 89 | } 90 | cleanup(); 91 | exit(1); 92 | } else { 93 | return now; 94 | } 95 | } 96 | 97 | void transmit_id(void) { 98 | time_t now = time(NULL); 99 | int id_len = strlen(id); 100 | if (verbose) { 101 | if (!daemonize) { 102 | printf("Transmitting %d bytes of identification data on %s: %s\r\n", id_len, if_name, id); 103 | } 104 | } 105 | 106 | uint8_t* id_frame = malloc(strlen(id)); 107 | memcpy(id_frame, id, id_len); 108 | kiss_write_frame(attached_tnc, id_frame, id_len); 109 | last_id = now; 110 | tx_since_last_id = false; 111 | 112 | } 113 | 114 | bool should_id(void) { 115 | if (id_interval != -1) { 116 | time_t now = time_now(); 117 | return now > last_id + id_interval; 118 | } else { 119 | return false; 120 | } 121 | } 122 | 123 | void signal_handler(int signal) { 124 | if (daemonize) syslog(LOG_NOTICE, "tncattach daemon exiting"); 125 | 126 | // Transmit final ID if necessary 127 | if (id_interval != -1 && tx_since_last_id) transmit_id(); 128 | 129 | cleanup(); 130 | exit(0); 131 | } 132 | 133 | void read_loop(void) { 134 | bool should_continue = true; 135 | int min_frame_size; 136 | if (device_type == IF_TAP) { 137 | min_frame_size = ETHERNET_MIN_FRAME_SIZE; 138 | } else if (device_type == IF_TUN) { 139 | min_frame_size = TUN_MIN_FRAME_SIZE; 140 | } else { 141 | if (daemonize) { 142 | syslog(LOG_ERR, "Unsupported interface type"); 143 | } else { 144 | printf("Error: Unsupported interface type\r\n"); 145 | } 146 | 147 | cleanup(); 148 | exit(1); 149 | } 150 | 151 | int poll_timeout = 1000; 152 | while (should_continue) { 153 | int poll_result = poll(fds, 2, poll_timeout); 154 | if (poll_result != -1) { 155 | if (poll_result == 0) { 156 | // No resources are ready for reading, 157 | // run scheduled tasks instead. 158 | if (id_interval != -1 && tx_since_last_id) { 159 | time_t now = time_now(); 160 | if (now > last_id + id_interval) transmit_id(); 161 | } 162 | } else { 163 | for (int fdi = 0; fdi < N_FDS; fdi++) { 164 | if (fds[fdi].revents != 0) { 165 | // Check for hangup event 166 | if (fds[fdi].revents & POLLHUP) { 167 | if (fdi == IF_FD_INDEX) { 168 | if (daemonize) { 169 | syslog(LOG_ERR, "Received hangup from interface"); 170 | } else { 171 | printf("Received hangup from interface\r\n"); 172 | } 173 | cleanup(); 174 | exit(1); 175 | } 176 | if (fdi == TNC_FD_INDEX) { 177 | if (daemonize) { 178 | syslog(LOG_ERR, "Received hangup from TNC"); 179 | } else { 180 | printf("Received hangup from TNC\r\n"); 181 | } 182 | cleanup(); 183 | exit(1); 184 | } 185 | } 186 | 187 | // Check for error event 188 | if (fds[fdi].revents & POLLERR) { 189 | if (fdi == IF_FD_INDEX) { 190 | if (daemonize) { 191 | syslog(LOG_ERR, "Received error event from interface"); 192 | } else { 193 | perror("Received error event from interface\r\n"); 194 | } 195 | cleanup(); 196 | exit(1); 197 | } 198 | if (fdi == TNC_FD_INDEX) { 199 | if (daemonize) { 200 | syslog(LOG_ERR, "Received error event from TNC"); 201 | } else { 202 | perror("Received error event from TNC\r\n"); 203 | } 204 | cleanup(); 205 | exit(1); 206 | } 207 | } 208 | 209 | // If data is ready, read it 210 | if (fds[fdi].revents & POLLIN) { 211 | if (fdi == IF_FD_INDEX) { 212 | int if_len = read(attached_if, if_buffer, sizeof(if_buffer)); 213 | if (if_len > 0) { 214 | if (if_len >= min_frame_size) { 215 | if (!noipv6 || (noipv6 && !is_ipv6(if_buffer))) { 216 | 217 | int tnc_written = kiss_write_frame(attached_tnc, if_buffer, if_len); 218 | if (verbose && !daemonize) printf("Got %d bytes from interface, wrote %d bytes (KISS-framed and escaped) to TNC\r\n", if_len, tnc_written); 219 | tx_since_last_id = true; 220 | 221 | if (should_id()) transmit_id(); 222 | } 223 | } 224 | } else { 225 | if (daemonize) { 226 | syslog(LOG_ERR, "Could not read from network interface, exiting now"); 227 | } else { 228 | printf("Error: Could not read from network interface, exiting now\r\n"); 229 | } 230 | cleanup(); 231 | exit(1); 232 | } 233 | } 234 | 235 | if (fdi == TNC_FD_INDEX) { 236 | int tnc_len = read(attached_tnc, serial_buffer, sizeof(serial_buffer)); 237 | if (tnc_len > 0) { 238 | for (int i = 0; i < tnc_len; i++) { 239 | kiss_serial_read(serial_buffer[i]); 240 | } 241 | } else { 242 | if (daemonize) { 243 | syslog(LOG_ERR, "Could not read from TNC, exiting now"); 244 | } else { 245 | printf("Error: Could not read from TNC, exiting now\r\n"); 246 | } 247 | 248 | cleanup(); 249 | exit(1); 250 | } 251 | } 252 | } 253 | } 254 | } 255 | } 256 | } else { 257 | should_continue = false; 258 | } 259 | } 260 | cleanup(); 261 | exit(1); 262 | } 263 | 264 | const char *argp_program_version = "tncattach 0.1.9"; 265 | const char *argp_program_bug_address = ""; 266 | static char doc[] = "\r\nAttach TNC devices as system network interfaces\vTo attach the TNC connected to /dev/ttyUSB0 as an ethernet device with an MTU of 512 bytes and assign an IPv4 address, while filtering IPv6 traffic, use:\r\n\r\n\ttncattach /dev/ttyUSB0 115200 -m 512 -e --noipv6 --ipv4 10.0.0.1/24\r\n\r\nStation identification can be performed automatically to comply with Part 97 rules. See the README for a complete description. Use the --id and --interval options, which should commonly be set to your callsign, and 600 seconds."; 267 | static char args_doc[] = "port baudrate"; 268 | static struct argp_option options[] = { 269 | { "mtu", 'm', "MTU", 0, "Specify interface MTU", 1}, 270 | { "ethernet", 'e', 0, 0, "Create a full ethernet device", 2}, 271 | { "ipv4", 'i', "IP_ADDRESS", 0, "Configure an IPv4 address on interface", 3}, 272 | { "noipv6", 'n', 0, 0, "Filter IPv6 traffic from reaching TNC", 4}, 273 | { "noup", 1, 0, 0, "Only create interface, don't bring it up", 5}, 274 | { "kisstcp", 'T', 0, 0, "Use KISS over TCP instead of serial port", 6}, 275 | { "tcphost", 'H', "TCP_HOST", 0, "Host to connect to when using KISS over TCP", 7}, 276 | { "tcpport", 'P', "TCP_PORT", 0, "TCP port when using KISS over TCP", 8}, 277 | { "interval", 't', "SECONDS", 0, "Maximum interval between station identifications", 9}, 278 | { "id", 's', "CALLSIGN", 0, "Station identification data", 10}, 279 | { "daemon", 'd', 0, 0, "Run tncattach as a daemon", 11}, 280 | { "verbose", 'v', 0, 0, "Enable verbose output", 12}, 281 | { 0 } 282 | }; 283 | 284 | #define N_ARGS 2 285 | struct arguments { 286 | char *args[N_ARGS]; 287 | char *ipv4; 288 | char *id; 289 | bool valid_id; 290 | int id_interval; 291 | int baudrate; 292 | int tcpport; 293 | int mtu; 294 | bool tap; 295 | bool daemon; 296 | bool verbose; 297 | bool set_ipv4; 298 | bool set_netmask; 299 | bool noipv6; 300 | bool noup; 301 | bool kiss_over_tcp; 302 | bool set_tcp_host; 303 | bool set_tcp_port; 304 | }; 305 | 306 | static error_t parse_opt(int key, char *arg, struct argp_state *state) { 307 | struct arguments *arguments = state->input; 308 | 309 | switch (key) { 310 | case 'v': 311 | arguments->verbose = true; 312 | break; 313 | 314 | case 'e': 315 | arguments->tap = true; 316 | break; 317 | 318 | case 'm': 319 | arguments->mtu = atoi(arg); 320 | if (arguments->mtu < MTU_MIN || arguments->mtu > MTU_MAX) { 321 | printf("Error: Invalid MTU specified\r\n\r\n"); 322 | argp_usage(state); 323 | } 324 | break; 325 | 326 | case 't': 327 | arguments->id_interval = atoi(arg); 328 | if (arguments->id_interval < 0) { 329 | printf("Error: Invalid identification interval specified\r\n\r\n"); 330 | argp_usage(state); 331 | } 332 | break; 333 | 334 | case 's': 335 | arguments->id = arg; 336 | if (strlen(arg) < 1 || strlen(arg) > arguments->mtu) { 337 | printf("Error: Invalid identification string specified\r\n\r\n"); 338 | argp_usage(state); 339 | } else { 340 | arguments->valid_id = true; 341 | } 342 | break; 343 | 344 | case 'i': 345 | arguments->ipv4 = arg; 346 | arguments->set_ipv4 = true; 347 | 348 | if (strchr(arg, '/')) { 349 | char* net = strchr(arg, '/'); 350 | int pos = net-arg; 351 | ipv4_addr = (char*)malloc(pos+1); 352 | int mask = atoi(net+1); 353 | strncpy(ipv4_addr, arg, pos); 354 | switch (mask) { 355 | case 0: 356 | netmask = "0.0.0.0"; 357 | break; 358 | case 1: 359 | netmask = "128.0.0.0"; 360 | break; 361 | case 2: 362 | netmask = "192.0.0.0"; 363 | break; 364 | case 3: 365 | netmask = "224.0.0.0"; 366 | break; 367 | case 4: 368 | netmask = "240.0.0.0"; 369 | break; 370 | case 5: 371 | netmask = "248.0.0.0"; 372 | break; 373 | case 6: 374 | netmask = "252.0.0.0"; 375 | break; 376 | case 7: 377 | netmask = "254.0.0.0"; 378 | break; 379 | case 8: 380 | netmask = "255.0.0.0"; 381 | break; 382 | case 9: 383 | netmask = "255.128.0.0"; 384 | break; 385 | case 10: 386 | netmask = "255.192.0.0"; 387 | break; 388 | case 11: 389 | netmask = "255.224.0.0"; 390 | break; 391 | case 12: 392 | netmask = "255.240.0.0"; 393 | break; 394 | case 13: 395 | netmask = "255.248.0.0"; 396 | break; 397 | case 14: 398 | netmask = "255.252.0.0"; 399 | break; 400 | case 15: 401 | netmask = "255.254.0.0"; 402 | break; 403 | case 16: 404 | netmask = "255.255.0.0"; 405 | break; 406 | case 17: 407 | netmask = "255.255.128.0"; 408 | break; 409 | case 18: 410 | netmask = "255.255.192.0"; 411 | break; 412 | case 19: 413 | netmask = "255.255.224.0"; 414 | break; 415 | case 20: 416 | netmask = "255.255.240.0"; 417 | break; 418 | case 21: 419 | netmask = "255.255.248.0"; 420 | break; 421 | case 22: 422 | netmask = "255.255.252.0"; 423 | break; 424 | case 23: 425 | netmask = "255.255.254.0"; 426 | break; 427 | case 24: 428 | netmask = "255.255.255.0"; 429 | break; 430 | case 25: 431 | netmask = "255.255.255.128"; 432 | break; 433 | case 26: 434 | netmask = "255.255.255.192"; 435 | break; 436 | case 27: 437 | netmask = "255.255.255.224"; 438 | break; 439 | case 28: 440 | netmask = "255.255.255.240"; 441 | break; 442 | case 29: 443 | netmask = "255.255.255.248"; 444 | break; 445 | case 30: 446 | netmask = "255.255.255.252"; 447 | break; 448 | case 31: 449 | netmask = "255.255.255.254"; 450 | break; 451 | case 32: 452 | netmask = "255.255.255.255"; 453 | break; 454 | 455 | default: 456 | printf("Error: Invalid subnet mask specified\r\n"); 457 | cleanup(); 458 | exit(1); 459 | } 460 | 461 | arguments->set_netmask = true; 462 | } else { 463 | arguments->set_netmask = false; 464 | ipv4_addr = (char*)malloc(strlen(arg)+1); 465 | strcpy(ipv4_addr, arg); 466 | } 467 | 468 | break; 469 | 470 | case 'n': 471 | arguments->noipv6 = true; 472 | break; 473 | 474 | case 'd': 475 | arguments->daemon = true; 476 | arguments->verbose = false; 477 | break; 478 | 479 | case 'T': 480 | arguments->kiss_over_tcp = true; 481 | break; 482 | 483 | case 'H': 484 | arguments->set_tcp_host = true; 485 | tcp_host = (char*)malloc(strlen(arg)+1); 486 | strcpy(tcp_host, arg); 487 | break; 488 | 489 | case 'P': 490 | arguments->set_tcp_port = true; 491 | tcp_port = atoi(arg); 492 | break; 493 | 494 | case 1: 495 | arguments->noup = true; 496 | break; 497 | 498 | case ARGP_KEY_ARG: 499 | // Check if there's now too many text arguments 500 | if (state->arg_num >= N_ARGS) argp_usage(state); 501 | 502 | // If not add to args 503 | arguments->args[state->arg_num] = arg; 504 | break; 505 | 506 | case ARGP_KEY_END: 507 | // Check if there's too few text arguments 508 | if (!arguments->kiss_over_tcp && state->arg_num < N_ARGS) argp_usage(state); 509 | 510 | // Check if text arguments were given when 511 | // KISS over TCP was specified 512 | if (arguments->kiss_over_tcp && state->arg_num != 0) argp_usage(state); 513 | 514 | break; 515 | 516 | default: 517 | return ARGP_ERR_UNKNOWN; 518 | } 519 | 520 | return 0; 521 | } 522 | 523 | static void become_daemon() { 524 | pid_t pid; 525 | pid = fork(); 526 | 527 | if (pid < 0) { 528 | perror("Fork failed"); 529 | exit(EXIT_FAILURE); 530 | } 531 | 532 | if (pid > 0) { 533 | exit(0); 534 | } 535 | 536 | if (setsid() < 0) exit(1); 537 | 538 | signal(SIGCHLD, signal_handler); 539 | signal(SIGHUP, signal_handler); 540 | 541 | pid = fork(); 542 | if (pid < 0) exit(1); 543 | if (pid > 0) exit(0); 544 | 545 | umask(0); 546 | chdir("/"); 547 | 548 | openlog("tncattach", LOG_PID, LOG_DAEMON); 549 | } 550 | 551 | static struct argp argp = {options, parse_opt, args_doc, doc}; 552 | int main(int argc, char **argv) { 553 | struct arguments arguments; 554 | signal(SIGINT, signal_handler); 555 | 556 | arguments.baudrate = BAUDRATE_DEFAULT; 557 | arguments.mtu = MTU_DEFAULT; 558 | arguments.tap = false; 559 | arguments.verbose = false; 560 | arguments.set_ipv4 = false; 561 | arguments.set_netmask = false; 562 | arguments.noipv6 = false; 563 | arguments.daemon = false; 564 | arguments.noup = false; 565 | arguments.id_interval = -1; 566 | arguments.valid_id = false; 567 | arguments.kiss_over_tcp = false; 568 | 569 | argp_parse(&argp, argc, argv, 0, 0, &arguments); 570 | 571 | if (arguments.kiss_over_tcp) kiss_over_tcp = true; 572 | 573 | if (!kiss_over_tcp) { 574 | arguments.baudrate = atoi(arguments.args[1]); 575 | } else { 576 | if (!(arguments.set_tcp_host && arguments.set_tcp_port)) { 577 | if (!arguments.set_tcp_host) printf("Error: KISS over TCP was requested, but no host was specified\r\n"); 578 | if (!arguments.set_tcp_port) printf("Error: KISS over TCP was requested, but no port was specified\r\n"); 579 | exit(1); 580 | } 581 | } 582 | 583 | if (arguments.daemon) daemonize = true; 584 | if (arguments.verbose) verbose = true; 585 | if (arguments.tap) device_type = IF_TAP; 586 | if (arguments.noipv6) noipv6 = true; 587 | if (arguments.set_ipv4) set_ipv4 = true; 588 | if (arguments.set_netmask) set_netmask = true; 589 | if (arguments.noup) noup = true; 590 | mtu = arguments.mtu; 591 | 592 | if (arguments.id_interval >= 0) { 593 | if (!arguments.valid_id) { 594 | printf("Error: Periodic identification requested, but no valid indentification data specified\r\n"); 595 | cleanup(); 596 | exit(1); 597 | } else { 598 | id_interval = arguments.id_interval; 599 | id = malloc(strlen(arguments.id)); 600 | strcpy(id, arguments.id); 601 | } 602 | } else if (arguments.valid_id && arguments.id_interval == -1) { 603 | printf("Error: Periodic identification requested, but no indentification interval specified\r\n"); 604 | cleanup(); 605 | exit(1); 606 | } 607 | 608 | attached_if = open_tap(); 609 | 610 | if (!arguments.kiss_over_tcp) { 611 | attached_tnc = open_port(arguments.args[0]); 612 | if (!setup_port(attached_tnc, arguments.baudrate)) { 613 | printf("Error during serial port setup"); 614 | return 0; 615 | } 616 | } else { 617 | attached_tnc = open_tcp(tcp_host, tcp_port); 618 | } 619 | 620 | printf("TNC interface configured as %s\r\n", if_name); 621 | 622 | fds[IF_FD_INDEX].fd = attached_if; 623 | fds[IF_FD_INDEX].events = POLLIN; 624 | fds[TNC_FD_INDEX].fd = attached_tnc; 625 | fds[TNC_FD_INDEX].events = POLLIN; 626 | 627 | if (daemonize) { 628 | become_daemon(); 629 | syslog(LOG_NOTICE, "tncattach daemon running"); 630 | } 631 | 632 | read_loop(); 633 | 634 | return 0; 635 | } --------------------------------------------------------------------------------