diff --git a/Constants.h b/Constants.h index 149a711..fd366ee 100644 --- a/Constants.h +++ b/Constants.h @@ -6,4 +6,10 @@ #define MTU_MIN 74 #define MTU_MAX 1522 -#define MTU_DEFAULT 329 \ No newline at end of file +#define MTU_DEFAULT 329 + +#define TXQUEUELEN 10 + +// ARP timings, in seconds +#define ARP_BASE_REACHABLE_TIME 300 +#define ARP_RETRANS_TIME 5 \ No newline at end of file diff --git a/KISS.c b/KISS.c index ff72b1d..40fc495 100644 --- a/KISS.c +++ b/KISS.c @@ -13,69 +13,73 @@ uint8_t frame_buffer[MAX_PAYLOAD]; uint8_t write_buffer[MAX_PAYLOAD*2+3]; extern bool verbose; +extern bool daemonize; extern int attached_if; +extern int device_type; 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); - } + if ( (device_type == IF_TUN && frame_len >= TUN_MIN_FRAME_SIZE) || (device_type == IF_TAP && frame_len >= ETHERNET_MIN_FRAME_SIZE) ) { + int written = write(attached_if, frame_buffer, frame_len); + if (written == -1) { + if (verbose && !daemonize) printf("Could not write received KISS frame (%d bytes) to network interface, is the interface up?\r\n", frame_len); + } else if (written != frame_len) { + if (!daemonize) printf("Error: Could only write %d of %d bytes to interface", written, frame_len); + cleanup(); + exit(1); + } + if (verbose && !daemonize) printf("Got %d bytes from TNC, wrote %d bytes to interface\r\n", frame_len, written); + } } void kiss_serial_read(uint8_t sbyte) { - if (IN_FRAME && sbyte == FEND && kiss_command == CMD_DATA) { - IN_FRAME = false; - kiss_frame_received(frame_len); - } else if (sbyte == FEND) { - IN_FRAME = true; - kiss_command = CMD_UNKNOWN; - frame_len = 0; - } else if (IN_FRAME && frame_len < MAX_PAYLOAD) { - // Have a look at the command byte first - if (frame_len == 0 && kiss_command == CMD_UNKNOWN) { - // Strip of port nibble - kiss_command = sbyte & 0x0F; - } else if (kiss_command == CMD_DATA) { - if (sbyte == FESC) { - ESCAPE = true; - } else { - if (ESCAPE) { - if (sbyte == TFEND) sbyte = FEND; - if (sbyte == TFESC) sbyte = FESC; - ESCAPE = false; - } + if (IN_FRAME && sbyte == FEND && kiss_command == CMD_DATA) { + IN_FRAME = false; + kiss_frame_received(frame_len); + } else if (sbyte == FEND) { + IN_FRAME = true; + kiss_command = CMD_UNKNOWN; + frame_len = 0; + } else if (IN_FRAME && frame_len < MAX_PAYLOAD) { + // Have a look at the command byte first + if (frame_len == 0 && kiss_command == CMD_UNKNOWN) { + // Strip of port nibble + kiss_command = sbyte & 0x0F; + } else if (kiss_command == CMD_DATA) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } - if (frame_len < MAX_PAYLOAD) { - frame_buffer[frame_len++] = sbyte; - } - } - } - } + if (frame_len < MAX_PAYLOAD) { + frame_buffer[frame_len++] = sbyte; + } + } + } + } } int kiss_write_frame(int serial_port, uint8_t* buffer, int frame_len) { - int write_len = 0; - write_buffer[write_len++] = FEND; - write_buffer[write_len++] = CMD_DATA; - for (int i = 0; i < frame_len; i++) { - uint8_t byte = buffer[i]; - if (byte == FEND) { - write_buffer[write_len++] = FESC; - write_buffer[write_len++] = TFEND; - } else if (byte == FESC) { - write_buffer[write_len++] = FESC; - write_buffer[write_len++] = TFESC; - } else { - write_buffer[write_len++] = byte; - } - } - write_buffer[write_len++] = FEND; + int write_len = 0; + write_buffer[write_len++] = FEND; + write_buffer[write_len++] = CMD_DATA; + for (int i = 0; i < frame_len; i++) { + uint8_t byte = buffer[i]; + if (byte == FEND) { + write_buffer[write_len++] = FESC; + write_buffer[write_len++] = TFEND; + } else if (byte == FESC) { + write_buffer[write_len++] = FESC; + write_buffer[write_len++] = TFESC; + } else { + write_buffer[write_len++] = byte; + } + } + write_buffer[write_len++] = FEND; - return write(serial_port, write_buffer, write_len); + return write(serial_port, write_buffer, write_len); } \ No newline at end of file diff --git a/README.md b/README.md index 17b0b7d..943d8ed 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Attach KISS TNC devices as network interfaces in Linux. This program allows you ## Installation -Currently it is recommended to compile and install __tncattach__ from source with the below commands. If that is not possible for you, precompiled __amd64__ and __armhf__ (Raspberry Pi) binaries exist in the releases section. +Currently it is recommended to compile and install __tncattach__ from source with the below commands. + +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). ```sh # If you don't already have a compiler installed @@ -32,12 +34,17 @@ Usage: tncattach [OPTION...] port baudrate Attach TNC devices as system network interfaces - -d, --daemon Run tncattach as a daemon + -m, --mtu=MTU Specify interface MTU -e, --ethernet Create a full ethernet device -i, --ipv4=IP_ADDRESS Configure an IPv4 address on interface - -m, --mtu=MTU Specify interface MTU -n, --noipv6 Filter IPv6 traffic from reaching TNC --noup Only create interface, don't bring it up + -T, --kisstcp Use KISS over TCP instead of serial port + -H, --tcphost=TCP_HOST Host to connect to when using KISS over TCP + -P, --tcpport=TCP_PORT TCP port when using KISS over TCP + -t, --interval=SECONDS Maximum interval between station identifications + -s, --id=CALLSIGN Station identification data + -d, --daemon Run tncattach as a daemon -v, --verbose Enable verbose output -?, --help Give this help list --usage Give a short usage message @@ -46,10 +53,24 @@ Attach TNC devices as system network interfaces 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. +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. + 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. 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. +## Station Identification + +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. + +Identification beacons will be transmitted when: + + - There is outgoing data to send, and the specified interval has elapsed. + - The specified interval elapses, and data has been sent since the last ID beacon. + - The program exits, if any data frames have been transmitted since the last ID beacon. + +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. + ## Examples Create an ethernet device with a USB-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: @@ -59,6 +80,13 @@ Create an ethernet device with a USB-connected TNC, set the MTU, filter IPv6 tra sudo tncattach /dev/ttyUSB0 115200 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 ``` +Create an ethernet device with a TCP-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: + +```sh +# Attach interface +sudo tncattach -T -H localhost -P 8001 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 +``` + You can interact with the interface like any other using the __ip__ or __ifconfig__ utilities: ```sh @@ -95,3 +123,21 @@ tnc0: flags=4305 mtu 400 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 ``` + +## Worth Knowing on Raspbian + +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: + +``` +# Add the following statement somewhere at the beginning +# of /etc/dhcpcd.conf to prevent dhcpcd from changing MTU + +denyinterfaces tnc0 +``` + +## Support tncattach development +You can help support the continued development of open, free and private communications systems by donating via one of the following channels: + +- Ethereum: 0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a +- Bitcoin: 3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq +- Ko-Fi: https://ko-fi.com/markqvist diff --git a/Serial.c b/Serial.c index ef91c03..09b0fa8 100644 --- a/Serial.c +++ b/Serial.c @@ -3,172 +3,172 @@ extern void cleanup(); int open_port(char* port) { - int fd; - fd = open(port, O_RDWR | O_NOCTTY | O_SYNC | O_NDELAY); + 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); - } + if (fd == -1) { + perror("The serial port could not be opened"); + cleanup(); + exit(1); + } else { + fcntl(fd, F_SETFL, 0); + } - return fd; + return fd; } int close_port(int fd) { - return close(fd); + return close(fd); } void set_speed(void *tty_s, int speed) { - cfsetospeed(tty_s, speed); - cfsetispeed(tty_s, speed); + cfsetospeed(tty_s, speed); + cfsetispeed(tty_s, speed); } bool setup_port(int fd, int speed) { - struct termios tty; - if (tcgetattr(fd, &tty) != 0) { - perror("Error setting port speed, could not read port parameters"); - return false; - } + struct termios tty; + if (tcgetattr(fd, &tty) != 0) { + perror("Error setting port speed, could not read port parameters"); + return false; + } - switch (speed) { - case 0: - set_speed(&tty, B0); - break; - case 50: - set_speed(&tty, B50); - break; - case 75: - set_speed(&tty, B75); - break; - case 110: - set_speed(&tty, B110); - break; - case 134: - set_speed(&tty, B134); - break; - case 150: - set_speed(&tty, B150); - break; - case 200: - set_speed(&tty, B200); - break; - case 300: - set_speed(&tty, B300); - break; - case 600: - set_speed(&tty, B600); - break; - case 1200: - set_speed(&tty, B1200); - break; - case 2400: - set_speed(&tty, B2400); - break; - case 4800: - set_speed(&tty, B4800); - break; - case 9600: - set_speed(&tty, B9600); - break; - case 19200: - set_speed(&tty, B19200); - break; - case 38400: - set_speed(&tty, B38400); - break; - case 57600: - set_speed(&tty, B57600); - break; - case 115200: - set_speed(&tty, B115200); - break; - case 230400: - set_speed(&tty, B230400); - break; - default: - printf("Error: Invalid port speed %d specified\r\n", speed); - cleanup(); - exit(1); - return false; - } + switch (speed) { + case 0: + set_speed(&tty, B0); + break; + case 50: + set_speed(&tty, B50); + break; + case 75: + set_speed(&tty, B75); + break; + case 110: + set_speed(&tty, B110); + break; + case 134: + set_speed(&tty, B134); + break; + case 150: + set_speed(&tty, B150); + break; + case 200: + set_speed(&tty, B200); + break; + case 300: + set_speed(&tty, B300); + break; + case 600: + set_speed(&tty, B600); + break; + case 1200: + set_speed(&tty, B1200); + break; + case 2400: + set_speed(&tty, B2400); + break; + case 4800: + set_speed(&tty, B4800); + break; + case 9600: + set_speed(&tty, B9600); + break; + case 19200: + set_speed(&tty, B19200); + break; + case 38400: + set_speed(&tty, B38400); + break; + case 57600: + set_speed(&tty, B57600); + break; + case 115200: + set_speed(&tty, B115200); + break; + case 230400: + set_speed(&tty, B230400); + break; + default: + printf("Error: Invalid port speed %d specified\r\n", speed); + cleanup(); + exit(1); + return false; + } - // Set 8-bit characters, no parity, one stop bit - tty.c_cflag |= CS8; - tty.c_cflag &= ~PARENB; - tty.c_cflag &= ~CSTOPB; + // Set 8-bit characters, no parity, one stop bit + tty.c_cflag |= CS8; + tty.c_cflag &= ~PARENB; + tty.c_cflag &= ~CSTOPB; - // Disable hardware flow control - tty.c_cflag &= ~CRTSCTS; + // Disable hardware flow control + tty.c_cflag &= ~CRTSCTS; - // Enable reading and ignore modem - // control lines - tty.c_cflag |= CREAD | CLOCAL; + // Enable reading and ignore modem + // control lines + tty.c_cflag |= CREAD | CLOCAL; - // Disable canonical mode, echo - // and signal characters. - tty.c_lflag &= ~ICANON; - tty.c_lflag &= ~ECHO; - tty.c_lflag &= ~ECHOE; - tty.c_lflag &= ~ECHONL; - tty.c_lflag &= ~ISIG; + // Disable canonical mode, echo + // and signal characters. + tty.c_lflag &= ~ICANON; + tty.c_lflag &= ~ECHO; + tty.c_lflag &= ~ECHOE; + tty.c_lflag &= ~ECHONL; + tty.c_lflag &= ~ISIG; - // Disable processing of input, - // just pass the raw data. - tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); + // Disable processing of input, + // just pass the raw data. + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); - // Disable XON/XOFF software flow control. - tty.c_iflag &= ~(IXON | IXOFF | IXANY); + // Disable XON/XOFF software flow control. + tty.c_iflag &= ~(IXON | IXOFF | IXANY); - // Disable processing output bytes - // and new line conversions - tty.c_oflag &= ~OPOST; - tty.c_oflag &= ~ONLCR; + // Disable processing output bytes + // and new line conversions + tty.c_oflag &= ~OPOST; + tty.c_oflag &= ~ONLCR; - // Block forever until at least one byte is read. - tty.c_cc[VMIN] = 1; - tty.c_cc[VTIME] = 0; + // Block forever until at least one byte is read. + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; - // TODO: Check these - // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX) - // tty.c_oflag &= ~OXTABS; - // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX) - // tty.c_oflag &= ~ONOEOT; + // TODO: Check these + // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX) + // tty.c_oflag &= ~OXTABS; + // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX) + // tty.c_oflag &= ~ONOEOT; - if (tcsetattr(fd, TCSANOW, &tty) != 0) { - perror("Could not configure serial port parameters"); - return false; - } else { - return true; - } + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + perror("Could not configure serial port parameters"); + return false; + } else { + return true; + } } bool set_port_blocking(int fd, bool should_block) { - struct termios tty; - memset(&tty, 0, sizeof tty); + struct termios tty; + memset(&tty, 0, sizeof tty); - if (tcgetattr(fd, &tty) != 0) { - perror("Error configuring port blocking behaviour, could not read port parameters"); - return false; - } else { - // TODO: Implement this correctly - if (should_block) { - // Block forever until at least one byte is read. - tty.c_cc[VMIN] = 1; - tty.c_cc[VTIME] = 0; - } else { - // Never block, always return immediately with - // whatever is available. - tty.c_cc[VMIN] = 0; - tty.c_cc[VTIME] = 0; - } - if (tcsetattr(fd, TCSANOW, &tty) != 0) { - perror("Could not set port parameters while configuring blocking behaviour"); - return false; - } else { - return true; - } - } + if (tcgetattr(fd, &tty) != 0) { + perror("Error configuring port blocking behaviour, could not read port parameters"); + return false; + } else { + // TODO: Implement this correctly + if (should_block) { + // Block forever until at least one byte is read. + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + } else { + // Never block, always return immediately with + // whatever is available. + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 0; + } + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + perror("Could not set port parameters while configuring blocking behaviour"); + return false; + } else { + return true; + } + } } \ No newline at end of file diff --git a/TAP.c b/TAP.c index 1b6c2fd..289fd76 100644 --- a/TAP.c +++ b/TAP.c @@ -5,128 +5,300 @@ char tap_name[IFNAMSIZ]; extern bool verbose; extern bool noipv6; extern bool set_ipv4; +extern bool set_ipv6; +extern bool set_linklocal; 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* ipv6_addr; +extern long ipv6_prefixLen; extern char* netmask; extern void cleanup(); + +void trySixSet +( + int interfaceIndex, + struct in6_addr address, + int prefixLen +) +{ + char ip_str[INET6_ADDRSTRLEN+1]; + inet_ntop(AF_INET6, &address, ip_str, INET6_ADDRSTRLEN+1); + + printf + ( + "Adding IPv6 address of '%s/%d' to interface at if_index %d\n", + ip_str, + prefixLen, + interfaceIndex + ); + + int inet6 = socket(AF_INET6, SOCK_DGRAM, 0); + if(inet6 < 0) + { + printf("Error opening control socket for adding IPv6 address to interface\n"); + cleanup(); + exit(1); + } + + struct in6_ifreq paramReq; + memset(¶mReq, 0, sizeof(struct in6_ifreq)); + paramReq.ifr6_ifindex = interfaceIndex; + paramReq.ifr6_prefixlen = prefixLen; + paramReq.ifr6_addr = address; + + + // Try add the address + if(ioctl(inet6, SIOCSIFADDR, ¶mReq) < 0) + { + printf + ( + "There was an errror assigning address '%s/%d' to if_index %d\n", + ip_str, + prefixLen, + interfaceIndex + ); + cleanup(); + close(inet6); + exit(1); + } + + printf("Address '%s/%d' added\n", ip_str, prefixLen); + close(inet6); +} + int open_tap(void) { - struct ifreq ifr; - int fd = open("/dev/net/tun", O_RDWR); + 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 (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); - } + 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); + 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 (ioctl(fd, TUNSETIFF, &ifr) < 0) { + perror("Could not configure network interface"); + exit(1); + } else { + strcpy(if_name, ifr.ifr_name); - 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; + + int inet = socket(AF_INET, SOCK_DGRAM, 0); + if (inet == -1) { + perror("Could not open control socket"); + cleanup(); + exit(1); + } else { + if (ioctl(inet, SIOCGIFMTU, &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); + } - 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; + // Configure TX queue length + if (ioctl(inet, SIOCGIFTXQLEN, &ifr) < 0) { + perror("Could not get interface flags from kernel"); + close(inet); + cleanup(); + exit(1); + } else { + ifr.ifr_qlen = TXQUEUELEN; + if (ioctl(inet, SIOCSIFTXQLEN, &ifr) < 0) { + perror("Could not set interface TX queue length"); + close(inet); + cleanup(); + exit(1); + } + } - 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); - } - } - } - } - } - } - } - } - } - } + // Configure ARP characteristics + char path_buf[256]; + if (device_type == IF_TAP) { + snprintf(path_buf, sizeof(path_buf), "/proc/sys/net/ipv4/neigh/%s/base_reachable_time_ms", ifr.ifr_name); + int arp_fd = open(path_buf, O_WRONLY); + if (arp_fd < 0) { + perror("Could not open proc entry for ARP parameters"); + close(inet); + cleanup(); + exit(1); + } else { + if (dprintf(arp_fd, "%d", ARP_BASE_REACHABLE_TIME*1000) <= 0) { + perror("Could not configure interface ARP parameter base_reachable_time_ms"); + close(inet); + close(arp_fd); + cleanup(); + exit(1); + } else { + close(arp_fd); + } + } - return fd; - } - } + snprintf(path_buf, sizeof(path_buf), "/proc/sys/net/ipv4/neigh/%s/retrans_time_ms", ifr.ifr_name); + arp_fd = open(path_buf, O_WRONLY); + if (arp_fd < 0) { + perror("Could not open proc entry for ARP parameters"); + close(inet); + cleanup(); + exit(1); + } else { + if (dprintf(arp_fd, "%d", ARP_RETRANS_TIME*1000) <= 0) { + perror("Could not configure interface ARP parameter retrans_time_ms"); + close(inet); + close(arp_fd); + cleanup(); + exit(1); + } else { + close(arp_fd); + } + } + } + + // Bring up if requested + if (!noup) { + if (ioctl(inet, SIOCGIFFLAGS, &ifr) < 0) { + perror("Could not get interface flags from kernel"); + close(inet); + cleanup(); + exit(1); + } else { + 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); + } + } + } + } + } + } + + if(set_ipv6 || set_linklocal) + { + // Firstly, obtain the interface index by `ifr_name` + int inet6 = socket(AF_INET6, SOCK_DGRAM, 0); + if(inet6 < 0) + { + printf("Error opening control socket for adding IPv6 address to interface\n"); + cleanup(); + exit(1); + } + + if(ioctl(inet6, SIOCGIFINDEX, &ifr) < 0) + { + printf("Could not get interface index for interface '%s'\n", ifr.ifr_name); + close(inet6); + cleanup(); + exit(1); + } + + // if link-local was NOT requested and interface + // has been up'd -> then kernel would have added + // a link-local already, this removes it + if(!set_linklocal & !noup) + { + // TODO: Get all addresses that start with fe80 + } + // Else it could have been no-up; hence you will have to remove + // the link-local yourself + // Other else is link-local was requested, then we don't care (whether + // up'd or not as it will inevitably be added by the kernel) + + // Convert ASCII IPv6 address to ABI structure + struct in6_addr six_addr_itself; + memset(&six_addr_itself, 0, sizeof(struct in6_addr)); + if(inet_pton(AF_INET6, ipv6_addr, &six_addr_itself) < 0) + { + printf("Error parsing IPv6 address '%s'\n", ipv6_addr); + close(inet6); + cleanup(); + exit(1); + } + + // Add user's requested address + trySixSet(ifr.ifr_ifindex, six_addr_itself, ipv6_prefixLen); + + close(inet6); + } + } + } + } + } + } + + return fd; + } + } } int close_tap(int tap_fd) { - return close(tap_fd); -} \ No newline at end of file + return close(tap_fd); +} diff --git a/TAP.h b/TAP.h index eeb6a14..07f003a 100644 --- a/TAP.h +++ b/TAP.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "Constants.h" int open_tap(void); diff --git a/TCP.c b/TCP.c new file mode 100644 index 0000000..c8e3d52 --- /dev/null +++ b/TCP.c @@ -0,0 +1,37 @@ +#include "TCP.h" + +int open_tcp(char* ip, int port) { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (sockfd < 0) { + perror("Could not open AF_INET socket"); + exit(1); + } + + struct hostent *server; + struct sockaddr_in serv_addr; + + server = gethostbyname(ip); + + if (server == NULL) { + perror("Error resolving host"); + exit(1); + } + + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + + memcpy(server->h_addr, &serv_addr.sin_addr.s_addr, server->h_length); + serv_addr.sin_port = htons(port); + + if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + perror("Could not connect TCP socket"); + exit(1); + } + + return sockfd; +} + +int close_tcp(int fd) { + return close(fd); +} \ No newline at end of file diff --git a/TCP.h b/TCP.h new file mode 100644 index 0000000..d386b59 --- /dev/null +++ b/TCP.h @@ -0,0 +1,9 @@ +#include +#include +#include +#include +#include +#include + +int open_tcp(char* ip, int port); +int close_tcp(int fd); \ No newline at end of file diff --git a/makefile b/makefile index f373c4f..8d6ba66 100644 --- a/makefile +++ b/makefile @@ -1,26 +1,35 @@ .DEFAULT_GOAL := all .PHONY: all clean install uninstall tncattach -compiler = gcc -flags = -std=gnu11 -lm +RM ?= rm +INSTALL ?= install +CC ?= gcc +CFLAGS ?= -Wall -std=gnu11 -static-libgcc +LDFLAGS ?= +PREFIX ?= /usr/local all: tncattach rebuild: clean all clean: @echo "Cleaning tncattach build..." - @rm -f tncattach + $(RM) -f tncattach tncattach: @echo "Making tncattach..." - @echo "Compiling with: ${compiler}" - ${compiler} ${flags} tncattach.c Serial.c KISS.c TAP.c -o tncattach -Wall + @echo "Compiling with: $(CC)" + $(CC) $(CFLAGS) $(LDFLAGS) tncattach.c Serial.c TCP.c KISS.c TAP.c -o tncattach install: @echo "Installing tncattach..." - chmod a+x tncattach - cp ./tncattach /usr/local/sbin/ + $(INSTALL) -d $(DESTDIR)/$(PREFIX)/bin + $(INSTALL) -Dm755 tncattach $(DESTDIR)/$(PREFIX)/bin/tncattach + @echo "Installing man page..." + gzip -9 tncattach.8 + $(INSTALL) -d $(DESTDIR)/$(PREFIX)/share/man/man8 + $(INSTALL) -Dm644 tncattach.8.gz $(DESTDIR)/$(PREFIX)/share/man/man8/tncattach.8.gz uninstall: @echo "Uninstalling tncattach" - rm /usr/local/sbin/tncattach \ No newline at end of file + $(RM) $(DESTDIR)/$(PREFIX)/bin/tncattach + $(RM) $(DESTDIR)/$(PREFIX)/share/man/man8/tncattach.8.gz diff --git a/tncattach.8 b/tncattach.8 new file mode 100644 index 0000000..f41390f --- /dev/null +++ b/tncattach.8 @@ -0,0 +1,165 @@ +.TH tncattach 8 "September 12, 2020" + +.SH NAME +. +. +tncattach \- Attach TNC devices as system network interfaces + +.SH SYNOPSIS +. +. +\f[B]tncattach\f[R] [OPTION...] port baudrate + +.SH DESCRIPTION +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. + +.SH OPTIONS +. +. +.TP +.BI \-m, \-\-mtu=MTU +. +Specify interface MTU +. +. +.TP +.BI \-e, \-\-ethernet +Create a full ethernet device +. +. +.TP +.BI \-i, \-\-ipv4=IP_ADDRESS +Configure an IPv4 address on interface +. +. +.TP +.BI \-n, \-\-noipv6 +Filter IPv6 traffic from reaching TNC +. +. +.TP +.BI \-\-noup +Only create interface, don't bring it up +. +. +.TP +.BI \-T, \-\-kisstcp +Use KISS over TCP instead of serial port +. +. +.TP +.BI \-H, \-\-tcphost=TCP_HOST +Host to connect to when using KISS over TCP +. +. +.TP +.BI \-P, \-\-tcpport=TCP_PORT +TCP port when using KISS over TCP +. +. +.TP +.BI \-t, \-\-interval=SECONDS +Maximum interval between station identifications +. +. +.TP +.B \-s, \-\-id=CALLSIGN +Station identification data +. +. +.TP +.BI \-d, \-\-daemon +Run tncattach as a daemon +. +. +.TP +.BI \-v, \-\-verbose +Enable verbose output +. +. +.TP +.BI \-?, \-\-help +Show help +. +. +.TP +.BI \-\-usage +Give a short usage message +. +. +.TP +.BI \-V, \-\-version +Print program version +. +. + +.SH USAGE +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. +.P +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. +.P +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. +.P +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. + +.SH STATION IDENTIFICATION + +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. +.P +Identification beacons will be transmitted when: +.P +.IP +There is outgoing data to send, and the specified interval has elapsed. +.IP +The specified interval elapses, and data has been sent since the last ID beacon. +.IP +The program exits, if any data frames have been transmitted since the last ID beacon. +.P +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. + +.SH EXAMPLES +. +Create an ethernet device with a USB-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: +.IP +sudo tncattach /dev/ttyUSB0 115200 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 +.P +Create an ethernet device with a TCP-connected TNC, set the MTU, filter IPv6 traffic, and set an IPv4 address: +.IP +sudo tncattach -T -H localhost -P 8001 --ethernet --mtu 576 --noipv6 --ipv4 10.92.0.10/24 +.P +You can interact with the interface like any other using the ip or ifconfig utilities. +.p +Check interface is running: +.P +# ifconfig +.br +tnc0: flags=4305 mtu 400 +.br + inet 10.93.0.1 netmask 255.255.255.255 destination 10.93.0.2 +.br + unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 500 (UNSPEC) +.br + RX packets 0 bytes 0 (0.0 B) +.br + RX errors 0 dropped 0 overruns 0 frame 0 +.br + TX packets 0 bytes 0 (0.0 B) +.br + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 +.P +.SH WORTH KNOWING ON RASPBIAN +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: +.P +# Add the following statement somewhere at the beginning +.br +# of /etc/dhcpcd.conf to prevent dhcpcd from changing MTU +.br +denyinterfaces tnc0 + +.SH SEE ALSO + +rnodeconfigutil(8) + +.SH AUTHOR + +Mark Qvist diff --git a/tncattach.c b/tncattach.c index 1c83782..7d825b9 100644 --- a/tncattach.c +++ b/tncattach.c @@ -5,9 +5,11 @@ #include #include #include +#include #include "Constants.h" #include "Serial.h" #include "KISS.h" +#include "TCP.h" #include "TAP.h" #define BAUDRATE_DEFAULT 0 @@ -32,373 +34,569 @@ bool noipv6 = false; bool noup = false; bool daemonize = false; bool set_ipv4 = false; +bool set_ipv6 = false; +bool set_linklocal = false; bool set_netmask = false; +bool kiss_over_tcp = false; char* ipv4_addr; char* netmask; + +char* ipv6_addr; +long ipv6_prefixLen; + +char* tcp_host; +int tcp_port; + int mtu; int device_type = IF_TUN; -void cleanup(void) { - close_port(attached_tnc); - close_tap(attached_if); -} +char* id; +int id_interval = -1; +time_t last_id = 0; +bool tx_since_last_id = false; -void signal_handler(int signal) { - if (daemonize) { - cleanup(); - syslog(LOG_NOTICE, "tncattach daemon exiting"); - exit(0); - } else { - cleanup(); - exit(0); - } +void cleanup(void) { + if (kiss_over_tcp) { + close_tcp(attached_tnc); + } else { + close_port(attached_tnc); + } + close_tap(attached_if); } bool is_ipv6(uint8_t* frame) { - if (device_type == IF_TAP) { - if (frame[12] == 0x86 && frame[13] == 0xdd) { - return true; - } else { - 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); - } + if (device_type == IF_TAP) { + if (frame[12] == 0x86 && frame[13] == 0xdd) { + return true; + } else { + 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); + } +} + +time_t time_now(void) { + time_t now = time(NULL); + if (now == -1) { + if (daemonize) { + syslog(LOG_ERR, "Could not get system time, exiting now"); + } else { + printf("Error: Could not get system time, exiting now\r\n"); + } + cleanup(); + exit(1); + } else { + return now; + } +} + +void transmit_id(void) { + time_t now = time(NULL); + int id_len = strlen(id); + if (verbose) { + if (!daemonize) { + printf("Transmitting %d bytes of identification data on %s: %s\r\n", id_len, if_name, id); + } + } + + uint8_t* id_frame = malloc(strlen(id)); + memcpy(id_frame, id, id_len); + kiss_write_frame(attached_tnc, id_frame, id_len); + last_id = now; + tx_since_last_id = false; + +} + +bool should_id(void) { + if (id_interval != -1) { + time_t now = time_now(); + return now > last_id + id_interval; + } else { + return false; + } +} + +void signal_handler(int signal) { + if (daemonize) syslog(LOG_NOTICE, "tncattach daemon exiting"); + + // Transmit final ID if necessary + if (id_interval != -1 && tx_since_last_id) transmit_id(); + + cleanup(); + exit(0); } 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 { - if (daemonize) { - syslog(LOG_ERR, "Unsupported interface type"); - } else { - printf("Error: Unsupported interface type\r\n"); - } - - cleanup(); - exit(1); - } + 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 { + if (daemonize) { + syslog(LOG_ERR, "Unsupported interface type"); + } else { + printf("Error: Unsupported interface type\r\n"); + } - while (should_continue) { + cleanup(); + exit(1); + } - 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) { - if (daemonize) { - syslog(LOG_ERR, "Received hangup from interface"); - } else { - printf("Received hangup from interface\r\n"); - } - cleanup(); - exit(1); - } - if (fdi == TNC_FD_INDEX) { - if (daemonize) { - syslog(LOG_ERR, "Received hangup from TNC"); - } else { - printf("Received hangup from TNC\r\n"); - } - cleanup(); - exit(1); - } - } + int poll_timeout = 1000; + while (should_continue) { + int poll_result = poll(fds, 2, poll_timeout); + if (poll_result != -1) { + if (poll_result == 0) { + // No resources are ready for reading, + // run scheduled tasks instead. + if (id_interval != -1 && tx_since_last_id) { + time_t now = time_now(); + if (now > last_id + id_interval) transmit_id(); + } + } else { + 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) { + if (daemonize) { + syslog(LOG_ERR, "Received hangup from interface"); + } else { + printf("Received hangup from interface\r\n"); + } + cleanup(); + exit(1); + } + if (fdi == TNC_FD_INDEX) { + if (daemonize) { + syslog(LOG_ERR, "Received hangup from TNC"); + } else { + printf("Received hangup from TNC\r\n"); + } + cleanup(); + exit(1); + } + } - // Check for error event - if (fds[fdi].revents & POLLERR) { - if (fdi == IF_FD_INDEX) { - if (daemonize) { - syslog(LOG_ERR, "Received error event from interface"); - } else { - perror("Received error event from interface\r\n"); - } - cleanup(); - exit(1); - } - if (fdi == TNC_FD_INDEX) { - if (daemonize) { - syslog(LOG_ERR, "Received error event from TNC"); - } else { - perror("Received error event from TNC\r\n"); - } - cleanup(); - exit(1); - } - } + // Check for error event + if (fds[fdi].revents & POLLERR) { + if (fdi == IF_FD_INDEX) { + if (daemonize) { + syslog(LOG_ERR, "Received error event from interface"); + } else { + perror("Received error event from interface\r\n"); + } + cleanup(); + exit(1); + } + if (fdi == TNC_FD_INDEX) { + if (daemonize) { + syslog(LOG_ERR, "Received error event from TNC"); + } else { + 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 { - if (daemonize) { - syslog(LOG_ERR, "Could not read from network interface, exiting now"); - } else { - printf("Error: Could not read from network interface, exiting now\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))) { - 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 { - if (daemonize) { - syslog(LOG_ERR, "Could not read from TNC, exiting now"); - } else { - printf("Error: Could not read from TNC, exiting now\r\n"); - } - - cleanup(); - exit(1); - } - } - } - } - } - } else { - should_continue = false; - } - } - cleanup(); - exit(1); + int tnc_written = kiss_write_frame(attached_tnc, if_buffer, if_len); + if (verbose && !daemonize) printf("Got %d bytes from interface, wrote %d bytes (KISS-framed and escaped) to TNC\r\n", if_len, tnc_written); + tx_since_last_id = true; + + if (should_id()) transmit_id(); + } + } + } else { + if (daemonize) { + syslog(LOG_ERR, "Could not read from network interface, exiting now"); + } 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) { + for (int i = 0; i < tnc_len; i++) { + kiss_serial_read(serial_buffer[i]); + } + } else { + if (daemonize) { + syslog(LOG_ERR, "Could not read from TNC, exiting now"); + } 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.2"; +const char *argp_program_version = "tncattach 0.1.9"; 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 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."; 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 } +static struct argp_option options[] = { + { "mtu", 'm', "MTU", 0, "Specify interface MTU", 1}, + { "ethernet", 'e', 0, 0, "Create a full ethernet device", 2}, + { "ipv4", 'i', "IP_ADDRESS", 0, "Configure an IPv4 address on interface", 3}, + { "ipv6", '6', "IP6_ADDRESS", 0, "Configure an IPv6 address on interface", 4}, + { "ll", 'l', 0, 0, "Add a link-local Ipv6 address", 5}, + { "noipv6", 'n', 0, 0, "Filter IPv6 traffic from reaching TNC", 6}, + { "noup", 1, 0, 0, "Only create interface, don't bring it up", 7}, + { "kisstcp", 'T', 0, 0, "Use KISS over TCP instead of serial port", 8}, + { "tcphost", 'H', "TCP_HOST", 0, "Host to connect to when using KISS over TCP", 9}, + { "tcpport", 'P', "TCP_PORT", 0, "TCP port when using KISS over TCP", 10}, + { "interval", 't', "SECONDS", 0, "Maximum interval between station identifications", 11}, + { "id", 's', "CALLSIGN", 0, "Station identification data", 12}, + { "daemon", 'd', 0, 0, "Run tncattach as a daemon", 13}, + { "verbose", 'v', 0, 0, "Enable verbose output", 14}, + { 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; + char *args[N_ARGS]; + char *ipv4; + char *ipv6; + char *id; + bool valid_id; + int id_interval; + int baudrate; + int tcpport; + int mtu; + bool tap; + bool daemon; + bool verbose; + bool set_ipv4; + bool set_netmask; + bool set_ipv6; + bool link_local_v6; + bool set_netmask_v6; + bool noipv6; + bool noup; + bool kiss_over_tcp; + bool set_tcp_host; + bool set_tcp_port; }; static error_t parse_opt(int key, char *arg, struct argp_state *state) { - struct arguments *arguments = state->input; + struct arguments *arguments = state->input; - switch (key) { - case 'v': - arguments->verbose = true; - break; + switch (key) { + case 'v': + arguments->verbose = true; + break; - case 'e': - arguments->tap = 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 '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); + } - case 'i': - arguments->ipv4 = arg; - arguments->set_ipv4 = true; + if((arguments->set_ipv6 || arguments->link_local_v6) && arguments->mtu < 1280) + { + printf("IPv6 and/or link-local IPv6 was requested, but the MTU provided is lower than 1280\n"); + exit(EXIT_FAILURE); + } - 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); - } + break; - arguments->set_netmask = true; - } else { - arguments->set_netmask = false; - ipv4_addr = (char*)malloc(strlen(arg)+1); - strcpy(ipv4_addr, arg); - } + case 't': + arguments->id_interval = atoi(arg); + if (arguments->id_interval < 0) { + printf("Error: Invalid identification interval specified\r\n\r\n"); + argp_usage(state); + } + break; - break; + case 's': + arguments->id = arg; + if (strlen(arg) < 1 || strlen(arg) > arguments->mtu) { + printf("Error: Invalid identification string specified\r\n\r\n"); + argp_usage(state); + } else { + arguments->valid_id = true; + } + break; - case 'n': - arguments->noipv6 = true; - break; + case 'i': + arguments->ipv4 = arg; + arguments->set_ipv4 = true; - case 'd': - arguments->daemon = true; - arguments->verbose = false; - break; + 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; - case 1: - arguments->noup = true; - break; + default: + printf("Error: Invalid subnet mask specified\r\n"); + cleanup(); + exit(1); + } - case ARGP_KEY_ARG: - // Check if there's now too many text arguments - if (state->arg_num >= N_ARGS) argp_usage(state); + arguments->set_netmask = true; + } else { + arguments->set_netmask = false; + ipv4_addr = (char*)malloc(strlen(arg)+1); + strcpy(ipv4_addr, arg); + } - // If not add to args - arguments->args[state->arg_num] = arg; - break; + break; + case '6': + if(arguments->noipv6) + { + perror("Sorry, but you had noipv6 set yet want to use ipv6?\n"); + exit(EXIT_FAILURE); + } - case ARGP_KEY_END: - // Check if there's too few text arguments - if (state->arg_num < N_ARGS) argp_usage(state); - break; + char* ipPart_s = strtok(arg, "/"); + char* prefixPart_s = strtok(NULL, "/"); + printf("ipPart_s: %s\n", ipPart_s); - default: - return ARGP_ERR_UNKNOWN; - } + if(!prefixPart_s) + { + printf("No prefix length was provided\n"); + exit(1); + } + printf("prefixPart_s: %s\n", prefixPart_s); - return 0; + long prefixLen_l = strtol(prefixPart_s, NULL, 10); // TODO: Add handling here for errors (using errno) + + if(prefixLen_l == 0) { + printf("Prefix length '%s' is not numeric\n", prefixPart_s); + exit(EXIT_FAILURE); + } + else if(!(prefixLen_l >= 0 && prefixLen_l <= 128)) + { + printf("Prefix length '%s' is not within valid range of 0-128\n", prefixPart_s); + exit(EXIT_FAILURE); + } + + arguments->ipv6 = ipPart_s; + + arguments->set_ipv6 = true; + + // Copy across global IPv6 address + ipv6_addr = malloc(strlen(arguments->ipv6)+1); + strcpy(ipv6_addr, arguments->ipv6); + + // Set global IPv6 prefix length + ipv6_prefixLen = prefixLen_l; + + printf("MTU was %d, setting to minimum of %d as is required for IPv6\n", arguments->mtu, 1280); + arguments->mtu = 1280; + break; + + case 'l': + if(arguments->noipv6) + { + perror("Sorry, but you had noipv6 set yet want to use ipv6 link-local?\n"); + exit(EXIT_FAILURE); + } + arguments->link_local_v6 = true; + + printf("MTU was %d, setting to minimum of %d as is required for IPv6\n", arguments->mtu, 1280); + arguments->mtu = 1280; + break; + + case 'n': + arguments->noipv6 = true; + if(arguments->set_ipv6) + { + printf("Requested no IPv6 yet you have set the IPv6 to '%s'\n", arguments->ipv6); + exit(1); + } + break; + + case 'd': + arguments->daemon = true; + arguments->verbose = false; + break; + + case 'T': + arguments->kiss_over_tcp = true; + break; + + case 'H': + arguments->set_tcp_host = true; + tcp_host = (char*)malloc(strlen(arg)+1); + strcpy(tcp_host, arg); + break; + + case 'P': + arguments->set_tcp_port = true; + tcp_port = atoi(arg); + 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 (!arguments->kiss_over_tcp && state->arg_num < N_ARGS) argp_usage(state); + + // Check if text arguments were given when + // KISS over TCP was specified + if (arguments->kiss_over_tcp && state->arg_num != 0) argp_usage(state); + + break; + + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; } static void become_daemon() { @@ -406,8 +604,8 @@ static void become_daemon() { pid = fork(); if (pid < 0) { - perror("Fork failed"); - exit(EXIT_FAILURE); + perror("Fork failed"); + exit(EXIT_FAILURE); } if (pid > 0) { @@ -431,45 +629,90 @@ static void become_daemon() { static struct argp argp = {options, parse_opt, args_doc, doc}; int main(int argc, char **argv) { - struct arguments arguments; - signal(SIGINT, signal_handler); + struct arguments arguments; + signal(SIGINT, signal_handler); - 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; + arguments.baudrate = BAUDRATE_DEFAULT; + arguments.mtu = MTU_DEFAULT; + arguments.tap = false; + arguments.verbose = false; + arguments.set_ipv4 = false; + arguments.set_netmask = false; + arguments.set_ipv6 = false; + arguments.link_local_v6 = false; + arguments.set_netmask_v6 = false; + arguments.noipv6 = false; + arguments.daemon = false; + arguments.noup = false; + arguments.id_interval = -1; + arguments.valid_id = false; + arguments.kiss_over_tcp = false; - argp_parse(&argp, argc, argv, 0, 0, &arguments); - arguments.baudrate = atoi(arguments.args[1]); + argp_parse(&argp, argc, argv, 0, 0, &arguments); - 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; + if (arguments.kiss_over_tcp) kiss_over_tcp = true; - 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; - if (daemonize) { - become_daemon(); - syslog(LOG_NOTICE, "tncattach daemon running"); - } - read_loop(); - } - - return 0; -} \ No newline at end of file + if (!kiss_over_tcp) { + arguments.baudrate = atoi(arguments.args[1]); + } else { + if (!(arguments.set_tcp_host && arguments.set_tcp_port)) { + if (!arguments.set_tcp_host) printf("Error: KISS over TCP was requested, but no host was specified\r\n"); + if (!arguments.set_tcp_port) printf("Error: KISS over TCP was requested, but no port was specified\r\n"); + exit(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.set_ipv6) set_ipv6 = true; + if (arguments.noup) noup = true; + mtu = arguments.mtu; + + if (arguments.id_interval >= 0) { + if (!arguments.valid_id) { + printf("Error: Periodic identification requested, but no valid indentification data specified\r\n"); + cleanup(); + exit(1); + } else { + id_interval = arguments.id_interval; + id = malloc(strlen(arguments.id)); + strcpy(id, arguments.id); + } + } else if (arguments.valid_id && arguments.id_interval == -1) { + printf("Error: Periodic identification requested, but no indentification interval specified\r\n"); + cleanup(); + exit(1); + } + + attached_if = open_tap(); + + if (!arguments.kiss_over_tcp) { + attached_tnc = open_port(arguments.args[0]); + if (!setup_port(attached_tnc, arguments.baudrate)) { + printf("Error during serial port setup"); + return 0; + } + } else { + attached_tnc = open_tcp(tcp_host, tcp_port); + } + + 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; + + if (daemonize) { + become_daemon(); + syslog(LOG_NOTICE, "tncattach daemon running"); + } + + read_loop(); + + return 0; +}