2020-05-25 08:31:55 -06:00
# include <stdlib.h>
# include <stdbool.h>
# include <signal.h>
2020-05-26 09:59:56 -06:00
# include <poll.h>
# include <argp.h>
2020-05-27 02:30:02 -06:00
# include <syslog.h>
# include <sys/stat.h>
2020-05-27 06:30:05 -06:00
# include <time.h>
2020-05-26 09:59:56 -06:00
# include "Constants.h"
2020-05-25 08:31:55 -06:00
# include "Serial.h"
2020-05-25 05:29:33 -06:00
# include "KISS.h"
2020-06-24 06:15:49 -06:00
# include "TCP.h"
2020-05-25 08:31:55 -06:00
# include "TAP.h"
2020-05-26 09:59:56 -06:00
# define BAUDRATE_DEFAULT 0
2020-05-25 08:31:55 -06:00
# define SERIAL_BUFFER_SIZE 512
2020-05-26 09:59:56 -06:00
# define IF_FD_INDEX 0
# define TNC_FD_INDEX 1
# define N_FDS 2
struct pollfd fds [ N_FDS ] ;
int attached_tnc ;
int attached_if ;
char if_name [ IFNAMSIZ ] ;
uint8_t serial_buffer [ MTU_MAX ] ;
uint8_t if_buffer [ MTU_MAX ] ;
bool verbose = false ;
bool noipv6 = false ;
bool noup = false ;
bool daemonize = false ;
bool set_ipv4 = false ;
bool set_netmask = false ;
2020-06-24 06:15:49 -06:00
bool kiss_over_tcp = false ;
2020-05-26 09:59:56 -06:00
char * ipv4_addr ;
char * netmask ;
2020-06-24 06:15:49 -06:00
char * tcp_host ;
int tcp_port ;
2020-05-26 09:59:56 -06:00
int mtu ;
int device_type = IF_TUN ;
2020-05-27 06:30:05 -06:00
char * id ;
int id_interval = - 1 ;
time_t last_id = 0 ;
2020-05-28 05:27:06 -06:00
bool tx_since_last_id = false ;
2020-05-27 06:30:05 -06:00
2020-05-26 09:59:56 -06:00
void cleanup ( void ) {
2020-06-24 06:22:42 -06:00
if ( kiss_over_tcp ) {
close_tcp ( attached_tnc ) ;
} else {
close_port ( attached_tnc ) ;
}
close_tap ( attached_if ) ;
2020-05-26 09:59:56 -06:00
}
2020-05-25 08:31:55 -06:00
2020-05-26 09:59:56 -06:00
bool is_ipv6 ( uint8_t * frame ) {
2020-06-24 06:22:42 -06:00
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 ) ;
}
2020-05-26 09:59:56 -06:00
}
2020-05-28 05:27:06 -06:00
time_t time_now ( void ) {
2020-06-24 06:22:42 -06:00
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 ;
}
2020-05-28 05:27:06 -06:00
}
void transmit_id ( void ) {
2020-06-24 06:22:42 -06:00
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 ;
2020-05-28 05:42:08 -06:00
2020-05-28 05:27:06 -06:00
}
bool should_id ( void ) {
2020-06-24 06:22:42 -06:00
if ( id_interval ! = - 1 ) {
time_t now = time_now ( ) ;
return now > last_id + id_interval ;
} else {
return false ;
}
2020-05-28 05:27:06 -06:00
}
void signal_handler ( int signal ) {
2020-06-24 06:22:42 -06:00
if ( daemonize ) syslog ( LOG_NOTICE , " tncattach daemon exiting " ) ;
2020-05-28 05:27:06 -06:00
2020-06-24 06:22:42 -06:00
// Transmit final ID if necessary
if ( id_interval ! = - 1 & & tx_since_last_id ) transmit_id ( ) ;
2020-05-28 05:27:06 -06:00
2020-06-24 06:22:42 -06:00
cleanup ( ) ;
exit ( 0 ) ;
2020-05-28 05:27:06 -06:00
}
2020-05-26 09:59:56 -06:00
void read_loop ( void ) {
2020-06-24 06:22:42 -06:00
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 ) ;
}
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 ) ;
}
}
// 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 ) ) ) {
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 ) ;
2020-05-26 09:59:56 -06:00
}
2020-09-28 03:39:28 -06:00
const char * argp_program_version = " tncattach 0.1.9 " ;
2020-05-26 09:59:56 -06:00
const char * argp_program_bug_address = " <mark@unsigned.io> " ;
2020-05-28 05:27:06 -06:00
static char doc [ ] = " \r \n Attach TNC devices as system network interfaces \v To 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 \t tncattach /dev/ttyUSB0 115200 -m 512 -e --noipv6 --ipv4 10.0.0.1/24 \r \n \r \n Station 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. " ;
2020-06-24 06:15:49 -06:00
static char args_doc [ ] = " port baudrate " ;
2020-06-16 13:55:25 -06:00
static struct argp_option options [ ] = {
2020-06-24 06:22:42 -06:00
{ " 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 } ,
{ " noipv6 " , ' n ' , 0 , 0 , " Filter IPv6 traffic from reaching TNC " , 4 } ,
{ " noup " , 1 , 0 , 0 , " Only create interface, don't bring it up " , 5 } ,
{ " kisstcp " , ' T ' , 0 , 0 , " Use KISS over TCP instead of serial port " , 6 } ,
{ " tcphost " , ' H ' , " TCP_HOST " , 0 , " Host to connect to when using KISS over TCP " , 7 } ,
{ " tcpport " , ' P ' , " TCP_PORT " , 0 , " TCP port when using KISS over TCP " , 8 } ,
{ " interval " , ' t ' , " SECONDS " , 0 , " Maximum interval between station identifications " , 9 } ,
{ " id " , ' s ' , " CALLSIGN " , 0 , " Station identification data " , 10 } ,
{ " daemon " , ' d ' , 0 , 0 , " Run tncattach as a daemon " , 11 } ,
{ " verbose " , ' v ' , 0 , 0 , " Enable verbose output " , 12 } ,
{ 0 }
2020-05-26 09:59:56 -06:00
} ;
# define N_ARGS 2
struct arguments {
2020-06-24 06:22:42 -06:00
char * args [ N_ARGS ] ;
char * ipv4 ;
char * id ;
bool valid_id ;
int id_interval ;
int baudrate ;
2020-06-24 06:15:49 -06:00
int tcpport ;
2020-06-24 06:22:42 -06:00
int mtu ;
bool tap ;
bool daemon ;
bool verbose ;
bool set_ipv4 ;
bool set_netmask ;
bool noipv6 ;
bool noup ;
bool kiss_over_tcp ;
2020-06-24 06:15:49 -06:00
bool set_tcp_host ;
bool set_tcp_port ;
2020-05-26 09:59:56 -06:00
} ;
static error_t parse_opt ( int key , char * arg , struct argp_state * state ) {
2020-06-24 06:22:42 -06:00
struct arguments * arguments = state - > input ;
switch ( key ) {
case ' v ' :
arguments - > verbose = true ;
break ;
case ' e ' :
arguments - > tap = true ;
break ;
case ' m ' :
arguments - > mtu = atoi ( arg ) ;
if ( arguments - > mtu < MTU_MIN | | arguments - > mtu > MTU_MAX ) {
printf ( " Error: Invalid MTU specified \r \n \r \n " ) ;
argp_usage ( state ) ;
}
break ;
case ' 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 ;
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 ' i ' :
arguments - > ipv4 = arg ;
arguments - > set_ipv4 = true ;
if ( strchr ( arg , ' / ' ) ) {
char * net = strchr ( arg , ' / ' ) ;
int pos = net - arg ;
ipv4_addr = ( char * ) malloc ( pos + 1 ) ;
int mask = atoi ( net + 1 ) ;
strncpy ( ipv4_addr , arg , pos ) ;
switch ( mask ) {
case 0 :
netmask = " 0.0.0.0 " ;
break ;
case 1 :
netmask = " 128.0.0.0 " ;
break ;
case 2 :
netmask = " 192.0.0.0 " ;
break ;
case 3 :
netmask = " 224.0.0.0 " ;
break ;
case 4 :
netmask = " 240.0.0.0 " ;
break ;
case 5 :
netmask = " 248.0.0.0 " ;
break ;
case 6 :
netmask = " 252.0.0.0 " ;
break ;
case 7 :
netmask = " 254.0.0.0 " ;
break ;
case 8 :
netmask = " 255.0.0.0 " ;
break ;
case 9 :
netmask = " 255.128.0.0 " ;
break ;
case 10 :
netmask = " 255.192.0.0 " ;
break ;
case 11 :
netmask = " 255.224.0.0 " ;
break ;
case 12 :
netmask = " 255.240.0.0 " ;
break ;
case 13 :
netmask = " 255.248.0.0 " ;
break ;
case 14 :
netmask = " 255.252.0.0 " ;
break ;
case 15 :
netmask = " 255.254.0.0 " ;
break ;
case 16 :
netmask = " 255.255.0.0 " ;
break ;
case 17 :
netmask = " 255.255.128.0 " ;
break ;
case 18 :
netmask = " 255.255.192.0 " ;
break ;
case 19 :
netmask = " 255.255.224.0 " ;
break ;
case 20 :
netmask = " 255.255.240.0 " ;
break ;
case 21 :
netmask = " 255.255.248.0 " ;
break ;
case 22 :
netmask = " 255.255.252.0 " ;
break ;
case 23 :
netmask = " 255.255.254.0 " ;
break ;
case 24 :
netmask = " 255.255.255.0 " ;
break ;
case 25 :
netmask = " 255.255.255.128 " ;
break ;
case 26 :
netmask = " 255.255.255.192 " ;
break ;
case 27 :
netmask = " 255.255.255.224 " ;
break ;
case 28 :
netmask = " 255.255.255.240 " ;
break ;
case 29 :
netmask = " 255.255.255.248 " ;
break ;
case 30 :
netmask = " 255.255.255.252 " ;
break ;
case 31 :
netmask = " 255.255.255.254 " ;
break ;
case 32 :
netmask = " 255.255.255.255 " ;
break ;
default :
printf ( " Error: Invalid subnet mask specified \r \n " ) ;
cleanup ( ) ;
exit ( 1 ) ;
}
arguments - > set_netmask = true ;
} else {
arguments - > set_netmask = false ;
ipv4_addr = ( char * ) malloc ( strlen ( arg ) + 1 ) ;
strcpy ( ipv4_addr , arg ) ;
}
break ;
case ' n ' :
arguments - > noipv6 = true ;
break ;
case ' d ' :
arguments - > daemon = true ;
arguments - > verbose = false ;
break ;
case ' T ' :
arguments - > kiss_over_tcp = true ;
break ;
2020-06-24 06:15:49 -06:00
case ' H ' :
arguments - > set_tcp_host = true ;
tcp_host = ( char * ) malloc ( strlen ( arg ) + 1 ) ;
strcpy ( tcp_host , arg ) ;
2020-06-16 13:55:25 -06:00
break ;
2020-06-24 06:15:49 -06:00
case ' P ' :
arguments - > set_tcp_port = true ;
tcp_port = atoi ( arg ) ;
2020-06-16 13:55:25 -06:00
break ;
2020-06-24 06:22:42 -06:00
case 1 :
arguments - > noup = true ;
break ;
2020-06-16 13:55:25 -06:00
2020-06-24 06:22:42 -06:00
case ARGP_KEY_ARG :
// Check if there's now too many text arguments
if ( state - > arg_num > = N_ARGS ) argp_usage ( state ) ;
2020-06-16 13:55:25 -06:00
2020-06-24 06:22:42 -06:00
// If not add to args
arguments - > args [ state - > arg_num ] = arg ;
break ;
2020-06-16 13:55:25 -06:00
2020-06-24 06:22:42 -06:00
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 ) ;
2020-06-16 13:55:25 -06:00
2020-06-24 06:15:49 -06:00
// Check if text arguments were given when
// KISS over TCP was specified
if ( arguments - > kiss_over_tcp & & state - > arg_num ! = 0 ) argp_usage ( state ) ;
2020-06-16 13:55:25 -06:00
2020-06-24 06:22:42 -06:00
break ;
2020-06-16 13:55:25 -06:00
2020-06-24 06:22:42 -06:00
default :
return ARGP_ERR_UNKNOWN ;
}
2020-06-16 13:55:25 -06:00
2020-06-24 06:22:42 -06:00
return 0 ;
2020-05-25 08:31:55 -06:00
}
2020-05-25 05:29:33 -06:00
2020-05-27 02:30:02 -06:00
static void become_daemon ( ) {
2020-06-24 06:22:42 -06:00
pid_t pid ;
pid = fork ( ) ;
2020-05-27 02:30:02 -06:00
2020-06-24 06:22:42 -06:00
if ( pid < 0 ) {
perror ( " Fork failed " ) ;
exit ( EXIT_FAILURE ) ;
}
2020-05-27 02:30:02 -06:00
2020-06-24 06:22:42 -06:00
if ( pid > 0 ) {
exit ( 0 ) ;
}
2020-05-27 02:30:02 -06:00
2020-06-24 06:22:42 -06:00
if ( setsid ( ) < 0 ) exit ( 1 ) ;
2020-05-27 02:30:02 -06:00
2020-06-24 06:22:42 -06:00
signal ( SIGCHLD , signal_handler ) ;
signal ( SIGHUP , signal_handler ) ;
2020-05-27 02:30:02 -06:00
2020-06-24 06:22:42 -06:00
pid = fork ( ) ;
if ( pid < 0 ) exit ( 1 ) ;
if ( pid > 0 ) exit ( 0 ) ;
2020-05-27 02:30:02 -06:00
2020-06-24 06:22:42 -06:00
umask ( 0 ) ;
chdir ( " / " ) ;
2020-05-27 02:30:02 -06:00
2020-06-24 06:22:42 -06:00
openlog ( " tncattach " , LOG_PID , LOG_DAEMON ) ;
2020-05-27 02:30:02 -06:00
}
2020-05-26 09:59:56 -06:00
static struct argp argp = { options , parse_opt , args_doc , doc } ;
int main ( int argc , char * * argv ) {
2020-06-24 06:22:42 -06:00
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 . id_interval = - 1 ;
arguments . valid_id = false ;
2020-09-28 03:18:02 -06:00
arguments . kiss_over_tcp = false ;
2020-06-24 06:22:42 -06:00
argp_parse ( & argp , argc , argv , 0 , 0 , & arguments ) ;
2020-06-24 06:15:49 -06:00
if ( arguments . kiss_over_tcp ) kiss_over_tcp = true ;
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 " ) ;
2020-06-16 13:55:25 -06:00
exit ( 1 ) ;
}
}
2020-06-24 06:22:42 -06:00
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 . 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 ] ) ;
2020-06-24 06:15:49 -06:00
if ( ! setup_port ( attached_tnc , arguments . baudrate ) ) {
2020-06-16 13:55:25 -06:00
printf ( " Error during serial port setup " ) ;
return 0 ;
}
2020-06-24 06:22:42 -06:00
} else {
2020-06-24 06:15:49 -06:00
attached_tnc = open_tcp ( tcp_host , tcp_port ) ;
2020-06-24 06:22:42 -06:00
}
2020-06-24 06:15:49 -06:00
2020-06-24 06:22:42 -06:00
printf ( " TNC interface configured as %s \r \n " , if_name ) ;
2020-06-24 06:15:49 -06:00
2020-06-24 06:22:42 -06:00
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 ;
2020-06-16 13:55:25 -06:00
if ( daemonize ) {
2020-06-24 06:22:42 -06:00
become_daemon ( ) ;
syslog ( LOG_NOTICE , " tncattach daemon running " ) ;
}
2020-06-24 06:15:49 -06:00
2020-06-24 06:22:42 -06:00
read_loop ( ) ;
2020-06-16 13:55:25 -06:00
2020-06-24 06:22:42 -06:00
return 0 ;
}