/**
* ( C ) 2007 - 21 - ntop . org and contributors
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not see see < http : //www.gnu.org/licenses/>
*
*/
# include "n2n.h"
/* *************************************************** */
/** maximum length of command line arguments */
# define MAX_CMDLINE_BUFFER_LENGTH 4096
/** maximum length of a line in the configuration file */
# define MAX_CONFFILE_LINE_LENGTH 1024
/* ***************************************************** */
# ifdef HAVE_LIBCAP
# include <sys/capability.h>
# include <sys/prctl.h>
# include "network_traffic_filter.h"
static cap_value_t cap_values [ ] = {
//CAP_NET_RAW, /* Use RAW and PACKET sockets */
CAP_NET_ADMIN /* Needed to performs routes cleanup at exit */
} ;
int num_cap = sizeof ( cap_values ) / sizeof ( cap_value_t ) ;
# endif
// forward declaration for use in main()
void send_register_super ( n2n_edge_t * eee ) ;
void send_query_peer ( n2n_edge_t * eee , const n2n_mac_t dst_mac ) ;
int supernode_connect ( n2n_edge_t * eee ) ;
int supernode_disconnect ( n2n_edge_t * eee ) ;
int fetch_and_eventually_process_data ( n2n_edge_t * eee , SOCKET sock ,
uint8_t * pktbuf , uint16_t * expected , uint16_t * position ,
time_t now ) ;
int resolve_create_thread ( n2n_resolve_parameter_t * * param , struct peer_info * sn_list ) ;
int resolve_check ( n2n_resolve_parameter_t * param , time_t now ) ;
/* ***************************************************** */
/** Find the address and IP mode for the tuntap device.
*
* s is of the form :
*
* [ " static " | " dhcp " , " : " ] ( < host > | < ip > ) [ / < cidr subnet mask > ]
*
* for example static : 192.168 .8 .5 / 24
*
* Fill the parts of the string into the fileds , ip_mode only if
* present . All strings are NULL terminated .
*
* return 0 on success and - 1 on error
*/
static int scan_address ( char * ip_addr , size_t addr_size ,
char * netmask , size_t netmask_size ,
char * ip_mode , size_t mode_size ,
char * s ) {
int retval = - 1 ;
char * start ;
char * end ;
int bitlen = N2N_EDGE_DEFAULT_CIDR_NM ;
if ( ( NULL = = s ) | | ( NULL = = ip_addr ) | | ( NULL = = netmask ) ) {
return - 1 ;
}
memset ( ip_addr , 0 , addr_size ) ;
memset ( netmask , 0 , netmask_size ) ;
start = s ;
end = strpbrk ( s , " : " ) ;
if ( end ) {
// colon is present
if ( ip_mode ) {
memset ( ip_mode , 0 , mode_size ) ;
strncpy ( ip_mode , start , ( size_t ) MIN ( end - start , mode_size - 1 ) ) ;
}
start = end + 1 ;
} else {
// colon is not present
}
// start now points to first address character
retval = 0 ; // we have got an address
end = strpbrk ( start , " / " ) ;
if ( ! end )
// no slash present -- default end
end = s + strlen ( s ) ;
strncpy ( ip_addr , start , ( size_t ) MIN ( end - start , addr_size - 1 ) ) ; // ensure NULL term
if ( end ) {
// slash is present
// now, handle the sub-network address
sscanf ( end + 1 , " %u " , & bitlen ) ;
bitlen = htobe32 ( bitlen2mask ( bitlen ) ) ;
inet_ntop ( AF_INET , & bitlen , netmask , netmask_size ) ;
}
return retval ;
}
/* *************************************************** */
static void help ( int level ) {
if ( level = = 0 ) return ; /* no help required */
printf ( " \n " ) ;
print_n2n_version ( ) ;
if ( level = = 1 ) /* short help */ {
printf ( " basic usage: edge <config file> (see edge.conf) \n "
" \n "
" or edge "
" -c <community name> "
" \n "
" -l <supernode host>:<port> "
" \n "
" [-a <tap IP address>] "
" \n "
# if defined(N2N_CAN_NAME_IFACE)
" [-d <tap device name>] "
" \n "
# endif
" [-k <encryption key>] "
" \n "
" \n -h shows a quick reference including all available options "
" \n --help gives a detailed parameter description "
" \n man files for n2n, edge, and superndode contain in-depth information "
" \n \n " ) ;
} else if ( level = = 2 ) /* quick reference */ {
printf ( " general usage: edge <config file> (see edge.conf) \n "
" \n "
" or edge "
" -c <community name> "
" -l <supernode host:port> "
" \n "
" [-p <local port>] "
# ifdef __linux__
" [-T <type of service>] "
# endif
# ifndef __APPLE__
" [-D] "
# endif
" \n options for under- "
" [-i <registration interval>] "
" [-L <registration ttl>] "
" \n lying connection "
" [-k <key>] "
" [-A<cipher>] "
" [-H] "
" [-z<compression>] "
" \n "
" [-S<level of solitude>] "
" \n \n tap device and "
" [-a [static:|dhcp:]<tap IP address>[/<cidr suffix>]] "
" \n overlay network "
" [-m <tap MAC address>] "
# if defined(N2N_CAN_NAME_IFACE)
" [-d <tap device name>] "
# endif
" \n configuration "
" [-M <tap MTU>] "
" [-r] "
" [-E] "
" [-I <edge description>] "
" \n "
" [-J <password>] "
" [-P <public key>] "
" [-R <rule string>] "
# ifdef WIN32
" \n "
" [-x <metric>] "
# endif
" \n \n local options "
# ifndef WIN32
" [-f] "
# endif
" [-t <management port>] "
" [-v] "
" [-n <cidr:gateway>] "
# ifndef WIN32
" \n "
" [-u <numerical user id>] "
" [-g <numerical group id>] "
# endif
" \n \n environment "
" N2N_KEY instead of [-k <key>] "
" \n variables "
" \n "
" \n meaning of the "
# ifndef __APPLE__
" [-D] enable PMTU discovery "
# endif
" \n flag options [-H] enable header encryption "
" \n [-r] enable packet forwarding through n2n community "
" \n [-E] accept multicast MAC addresses "
# ifndef WIN32
" \n [-f] do not fork but run in foreground "
# endif
" \n [-v] make more verbose, repeat as required "
" \n "
" \n -h shows this quick reference including all available options "
" \n --help gives a detailed parameter description "
" \n man files for n2n, edge, and superndode contain in-depth information "
" \n \n " ) ;
} else /* long help */ {
printf ( " general usage: edge <config file> (see edge.conf) \n "
" \n "
" or edge -c <community name> -l <supernode host:port> \n "
" [further optional command line parameters] \n \n "
) ;
printf ( " OPTIONS FOR THE UNDERLYING NETWORK CONNECTION \n " ) ;
printf ( " --------------------------------------------- \n \n " ) ;
printf ( " -c <community> | n2n community name the edge belongs to \n " ) ;
printf ( " -l <host:port> | supernode ip address or name, and port \n " ) ;
printf ( " -p <local port> | fixed local UDP port \n " ) ;
# ifdef __linux__
printf ( " -T <tos> | TOS for packets, e.g. 0x48 for SSH like priority \n " ) ;
# endif
# ifndef __APPLE__
printf ( " -D | enable PMTU discovery, it can reduce fragmentation but \n "
" | causes connections to stall if not properly supported \n " ) ;
# endif
printf ( " -S1 ... -S2 | do not connect p2p, always use the supernode, \n "
" | -S1 = via UDP "
# ifdef N2N_HAVE_TCP
" , -S2 = via TCP "
# endif
" \n " ) ;
printf ( " -i <reg_interval> | registration interval, for NAT hole punching (default \n "
" | %u seconds) \n " , REGISTER_SUPER_INTERVAL_DFL ) ;
printf ( " -L <reg_ttl> | TTL for registration packet for NAT hole punching through \n "
" | supernode (default 0 for not set) \n " ) ;
printf ( " -k <key> | encryption key (ASCII) - also N2N_KEY=<key> \n " ) ;
printf ( " -A1 | disable payload encryption, do not use with key, defaults \n "
" | to AES then \n " ) ;
printf ( " -A2 ... -A5 | choose a cipher for payload encryption, requires a key, \n "
" | -A2 = Twofish, -A3 = AES (default if key provided), \n "
" | -A4 = ChaCha20, -A5 = Speck-CTR \n " ) ;
printf ( " -H | use header encryption, supernode needs fixed community \n " ) ;
printf ( " -z1 ... -z2 | compress outgoing data packets, -z1 = lzo1x, \n "
" | "
# ifdef N2N_HAVE_ZSTD
" -z2 = zstd, "
# endif
" disabled by default \n " ) ;
printf ( " \n " ) ;
printf ( " TAP DEVICE AND OVERLAY NETWORK CONFIGURATION \n " ) ;
printf ( " -------------------------------------------- \n \n " ) ;
printf ( " -a [mode]<ip>[/n] | interface address and optional CIDR subnet, default '/24', \n "
" | mode = [static|dhcp]:, for DHCP use '-r -a dhcp:0.0.0.0', \n "
" | edge draws IP address from supernode if no '-a ...' given \n " ) ;
printf ( " -m <mac> | fixed MAC address for the TAP interface, e.g. \n "
" | '-m 10:20:30:40:50:60', random otherwise \n " ) ;
# if defined(N2N_CAN_NAME_IFACE)
printf ( " -d <device> | TAP device name \n " ) ;
# endif
printf ( " -M <mtu> | specify n2n MTU of TAP interface, default %d \n " , DEFAULT_MTU ) ;
printf ( " -r | enable packet forwarding through n2n community \n " ) ;
printf ( " -E | accept multicast MAC addresses, drop by default \n " ) ;
printf ( " -I <description> | annotate the edge's description used for easier \n "
" | identification in management port output or username \n " ) ;
printf ( " -J <password> | password for user-password edge authentication \n " ) ;
printf ( " -P <public key> | federation public key for user-password authentication \n " ) ;
printf ( " -R <rule> | drop or accept packets by rules, can be set multiple times \n " ) ;
printf ( " | rule format: 'src_ip/n:[s_port,e_port],... \n "
" | |on same| ...dst_ip/n:[s_port,e_port],... \n "
" | | line | ...TCP+/-,UDP+/-,ICMP+/-' \n " ) ;
# ifdef WIN32
printf ( " -x <metric> | set TAP interface metric, defaults to 0 (auto), \n "
" | e.g. set to 1 for better multiplayer game detection \n " ) ;
# endif
printf ( " \n " ) ;
printf ( " LOCAL OPTIONS \n " ) ;
printf ( " ------------- \n \n " ) ;
# ifndef WIN32
printf ( " -f | do not fork and run as a daemon, rather run in foreground \n " ) ;
# endif
printf ( " -t <port> | management UDP port, for multiple edges on a machine, \n "
" | defaults to %u \n " , N2N_EDGE_MGMT_PORT ) ;
printf ( " -v | make more verbose, repeat as required \n " ) ;
printf ( " -n <cidr:gateway> | route an IPv4 network via the gateway, use 0.0.0.0/0 for \n "
" | the default gateway, can be set multiple times \n " ) ;
# ifndef WIN32
printf ( " -u <UID> | numeric user ID to use when privileges are dropped \n " ) ;
printf ( " -g <GID> | numeric group ID to use when privileges are dropped \n " ) ;
# endif
printf ( " \n " ) ;
printf ( " ENVIRONMENT VARIABLES \n " ) ;
printf ( " --------------------- \n \n " ) ;
printf ( " N2N_KEY | encryption key (ASCII), not with '-k ...' \n " ) ;
# ifdef WIN32
printf ( " \n " ) ;
printf ( " AVAILABLE TAP ADAPTERS \n " ) ;
printf ( " ---------------------- \n \n " ) ;
win_print_available_adapters ( ) ;
# endif
printf ( " \n "
" \n -h shows a quick reference including all available options "
" \n --help gives this detailed parameter description "
" \n man files for n2n, edge, and superndode contain in-depth information "
" \n \n " ) ;
}
exit ( 0 ) ;
}
/* *************************************************** */
static void setPayloadCompression ( n2n_edge_conf_t * conf , int compression ) {
/* even though 'compression' and 'conf->compression' share the same encoding scheme,
* a switch - statement under conditional compilation is used to sort out the
* unsupported optarguments */
switch ( compression ) {
case 1 : {
conf - > compression = N2N_COMPRESSION_ID_LZO ;
break ;
}
# ifdef N2N_HAVE_ZSTD
case 2 : {
conf - > compression = N2N_COMPRESSION_ID_ZSTD ;
break ;
}
# endif
default : {
conf - > compression = N2N_COMPRESSION_ID_NONE ;
// internal comrpession scheme numbering differs from cli counting by one, hence plus one
// (internal: 0 == invalid, 1 == none, 2 == lzo, 3 == zstd)
traceEvent ( TRACE_NORMAL , " the %s compression given by -z_ option is not supported in this version. " , compression_str ( compression + 1 ) ) ;
exit ( 1 ) ; // to make the user aware
}
}
}
/* *************************************************** */
static void setPayloadEncryption ( n2n_edge_conf_t * conf , int cipher ) {
/* even though 'cipher' and 'conf->transop_id' share the same encoding scheme,
* a switch - statement under conditional compilation is used to sort out the
* unsupported ciphers */
switch ( cipher ) {
case 1 : {
conf - > transop_id = N2N_TRANSFORM_ID_NULL ;
break ;
}
case 2 : {
conf - > transop_id = N2N_TRANSFORM_ID_TWOFISH ;
break ;
}
case 3 : {
conf - > transop_id = N2N_TRANSFORM_ID_AES ;
break ;
}
case 4 : {
conf - > transop_id = N2N_TRANSFORM_ID_CHACHA20 ;
break ;
}
case 5 : {
conf - > transop_id = N2N_TRANSFORM_ID_SPECK ;
break ;
}
default : {
conf - > transop_id = N2N_TRANSFORM_ID_INVAL ;
traceEvent ( TRACE_NORMAL , " the %s cipher given by -A_ option is not supported in this version. " , transop_str ( cipher ) ) ;
exit ( 1 ) ;
}
}
}
/* *************************************************** */
static int setOption ( int optkey , char * optargument , n2n_tuntap_priv_config_t * ec , n2n_edge_conf_t * conf ) {
/* traceEvent(TRACE_NORMAL, "Option %c = %s", optkey, optargument ? optargument : ""); */
switch ( optkey ) {
case ' a ' : /* IP address and mode of TUNTAP interface */ {
scan_address ( ec - > ip_addr , N2N_NETMASK_STR_SIZE ,
ec - > netmask , N2N_NETMASK_STR_SIZE ,
ec - > ip_mode , N2N_IF_MODE_SIZE ,
optargument ) ;
break ;
}
case ' c ' : /* community as a string */ {
memset ( conf - > community_name , 0 , N2N_COMMUNITY_SIZE ) ;
strncpy ( ( char * ) conf - > community_name , optargument , N2N_COMMUNITY_SIZE ) ;
conf - > community_name [ N2N_COMMUNITY_SIZE - 1 ] = ' \0 ' ;
break ;
}
case ' E ' : /* multicast ethernet addresses accepted. */ {
conf - > drop_multicast = 0 ;
traceEvent ( TRACE_INFO , " Enabling ethernet multicast traffic " ) ;
break ;
}
# ifndef WIN32
case ' u ' : /* unprivileged uid */ {
ec - > userid = atoi ( optargument ) ;
break ;
}
case ' g ' : /* unprivileged uid */ {
ec - > groupid = atoi ( optargument ) ;
break ;
}
# endif
# ifndef WIN32
case ' f ' : /* do not fork as daemon */ {
ec - > daemon = 0 ;
break ;
}
# endif /* #ifndef WIN32 */
case ' m ' : /* TUNTAP MAC address */ {
strncpy ( ec - > device_mac , optargument , N2N_MACNAMSIZ ) ;
ec - > device_mac [ N2N_MACNAMSIZ - 1 ] = ' \0 ' ;
break ;
}
case ' M ' : /* TUNTAP MTU */ {
ec - > mtu = atoi ( optargument ) ;
break ;
}
# ifndef __APPLE__
case ' D ' : /* enable PMTU discovery */ {
conf - > disable_pmtu_discovery = 0 ;
break ;
}
# endif
case ' k ' : /* encrypt key */ {
if ( conf - > encrypt_key ) free ( conf - > encrypt_key ) ;
conf - > encrypt_key = strdup ( optargument ) ;
traceEvent ( TRACE_DEBUG , " encrypt_key = '%s' \n " , conf - > encrypt_key ) ;
break ;
}
case ' r ' : /* enable packet routing across n2n endpoints */ {
conf - > allow_routing = 1 ;
break ;
}
case ' A ' : {
int cipher ;
if ( optargument ) {
cipher = atoi ( optargument ) ;
} else {
traceEvent ( TRACE_WARNING , " the use of the solitary -A switch is deprecated and will not be supported in future versions. "
" please use -A3 instead to choose the AES cipher for payload encryption. " ) ;
cipher = N2N_TRANSFORM_ID_AES ; // default, if '-A' only
}
setPayloadEncryption ( conf , cipher ) ;
break ;
}
case ' H ' : /* indicate header encryption */ {
/* we cannot be sure if this gets parsed before the community name is set.
* so , only an indicator is set , action is taken later */
conf - > header_encryption = HEADER_ENCRYPTION_ENABLED ;
break ;
}
case ' z ' : {
int compression ;
if ( optargument ) {
compression = atoi ( optargument ) ;
} else {
traceEvent ( TRACE_WARNING , " the use of the solitary -z switch is deprecated and will not be supported in future versions. "
" please use -z1 instead to choose the LZO1X algorithm for payload compression. " ) ;
compression = 1 ; // default, if '-z' only, equals -z1
}
setPayloadCompression ( conf , compression ) ;
break ;
}
case ' l ' : /* supernode-list */ {
if ( optargument ) {
if ( edge_conf_add_supernode ( conf , optargument ) ! = 0 ) {
traceEvent ( TRACE_WARNING , " Failed to add supernode '%s' " , optargument ) ;
}
}
break ;
}
case ' i ' : /* supernode registration interval */
conf - > register_interval = atoi ( optargument ) ;
break ;
case ' L ' : /* supernode registration interval */
conf - > register_ttl = atoi ( optarg ) ;
break ;
# if defined(N2N_CAN_NAME_IFACE)
case ' d ' : /* TUNTAP name */ {
strncpy ( ec - > tuntap_dev_name , optargument , N2N_IFNAMSIZ ) ;
ec - > tuntap_dev_name [ N2N_IFNAMSIZ - 1 ] = ' \0 ' ;
break ;
}
# endif
case ' I ' : /* Device Description (hint) or username */ {
memset ( conf - > dev_desc , 0 , N2N_DESC_SIZE ) ;
/* reserve possible last char as null terminator. */
strncpy ( ( char * ) conf - > dev_desc , optargument , N2N_DESC_SIZE - 1 ) ;
break ;
}
case ' J ' : /* password for user-password authentication */ {
conf - > shared_secret = calloc ( 1 , sizeof ( n2n_private_public_key_t ) ) ;
if ( conf - > shared_secret )
generate_private_key ( * ( conf - > shared_secret ) , optargument ) ;
// the hash of the username (-I) gets xored into this key later,
// we can't be sure to already have it at this point
// also, the complete shared secret will be calculated then as we
// might still be missing the federation public key as well
break ;
}
case ' P ' : /* federation public key for user-password authentication */ {
if ( strlen ( optargument ) < ( ( N2N_PRIVATE_PUBLIC_KEY_SIZE * 8 + 5 ) / 6 + 1 ) ) {
conf - > federation_public_key = calloc ( 1 , sizeof ( n2n_private_public_key_t ) ) ;
if ( conf - > federation_public_key ) {
ascii_to_bin ( * ( conf - > federation_public_key ) , optargument ) ;
}
} else {
traceEvent ( TRACE_WARNING , " Public key too long. " ) ;
return 2 ;
}
break ;
}
case ' p ' : {
conf - > local_port = atoi ( optargument ) ;
if ( conf - > local_port = = 0 ) {
traceEvent ( TRACE_WARNING , " Bad local port format, using OS assigned port. " ) ;
break ;
}
break ;
}
case ' t ' : {
conf - > mgmt_port = atoi ( optargument ) ;
break ;
}
# ifdef __linux__
case ' T ' : {
if ( ( optargument [ 0 ] = = ' 0 ' ) & & ( optargument [ 1 ] = = ' x ' ) )
conf - > tos = strtol ( & optargument [ 2 ] , NULL , 16 ) ;
else
conf - > tos = atoi ( optargument ) ;
break ;
}
# endif
case ' n ' : {
char cidr_net [ 64 ] , gateway [ 64 ] ;
n2n_route_t route ;
if ( sscanf ( optargument , " %63[^/]/%hhd:%63s " , cidr_net , & route . net_bitlen , gateway ) ! = 3 ) {
traceEvent ( TRACE_WARNING , " Bad cidr/gateway format '%d'. " , optargument ) ;
return 2 ;
}
route . net_addr = inet_addr ( cidr_net ) ;
route . gateway = inet_addr ( gateway ) ;
if ( ( route . net_bitlen < 0 ) | | ( route . net_bitlen > 32 ) ) {
traceEvent ( TRACE_WARNING , " Bad prefix '%d' in '%s' " , route . net_bitlen , optargument ) ;
return 2 ;
}
if ( route . net_addr = = INADDR_NONE ) {
traceEvent ( TRACE_WARNING , " Bad network '%s' in '%s' " , cidr_net , optargument ) ;
return 2 ;
}
if ( route . gateway = = INADDR_NONE ) {
traceEvent ( TRACE_WARNING , " Bad gateway '%s' in '%s' " , gateway , optargument ) ;
return 2 ;
}
traceEvent ( TRACE_NORMAL , " Adding %s/%d via %s " , cidr_net , route . net_bitlen , gateway ) ;
conf - > routes = realloc ( conf - > routes , sizeof ( struct n2n_route ) * ( conf - > num_routes + 1 ) ) ;
conf - > routes [ conf - > num_routes ] = route ;
conf - > num_routes + + ;
break ;
}
case ' S ' : {
int solitude ;
if ( optargument ) {
solitude = atoi ( optargument ) ;
} else {
traceEvent ( TRACE_WARNING , " the use of the solitary -S switch is deprecated and will not be supported in future versions. "
" please use -S1 instead to choose supernode-only connection via UDP. " ) ;
solitude = 1 ;
}
// set the level
if ( solitude > = 1 )
conf - > allow_p2p = 0 ;
# ifdef N2N_HAVE_TCP
if ( solitude = = 2 )
conf - > connect_tcp = 1 ;
# endif
break ;
}
case ' h ' : /* quick reference */ {
return 2 ;
}
case ' @ ' : /* long help */ {
return 3 ;
}
case ' v ' : /* verbose */
setTraceLevel ( getTraceLevel ( ) + 1 ) ;
break ;
case ' R ' : /* network traffic filter */ {
filter_rule_t * new_rule = malloc ( sizeof ( filter_rule_t ) ) ;
memset ( new_rule , 0 , sizeof ( filter_rule_t ) ) ;
if ( process_traffic_filter_rule_str ( optargument , new_rule ) ) {
HASH_ADD ( hh , conf - > network_traffic_filter_rules , key , sizeof ( filter_rule_key_t ) , new_rule ) ;
} else {
free ( new_rule ) ;
traceEvent ( TRACE_WARNING , " Invalid filter rule: %s " , optargument ) ;
return 2 ;
}
break ;
}
# ifdef WIN32
case ' x ' : {
conf - > metric = atoi ( optargument ) ;
ec - > metric = atoi ( optargument ) ;
break ;
}
# endif
default : {
traceEvent ( TRACE_WARNING , " Unknown option -%c " , ( char ) optkey ) ;
return 2 ;
}
}
return 0 ;
}
/* *********************************************** */
static const struct option long_options [ ] =
{
{ " community " , required_argument , NULL , ' c ' } ,
{ " supernode-list " , required_argument , NULL , ' l ' } ,
{ " tap-device " , required_argument , NULL , ' d ' } ,
{ " euid " , required_argument , NULL , ' u ' } ,
{ " egid " , required_argument , NULL , ' g ' } ,
{ " help " , no_argument , NULL , ' @ ' } , /* special character '@' to identify long help case */
{ " verbose " , no_argument , NULL , ' v ' } ,
{ NULL , 0 , NULL , 0 }
} ;
/* *************************************************** */
/* read command line options */
static int loadFromCLI ( int argc , char * argv [ ] , n2n_edge_conf_t * conf , n2n_tuntap_priv_config_t * ec ) {
u_char c ;
while ( ( c = getopt_long ( argc , argv ,
" k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:I:J:P:S::DL:z::A::Hn:R: "
# ifdef __linux__
" T: "
# endif
# ifdef WIN32
" x: "
# endif
,
long_options , NULL ) ) ! = ' ? ' ) {
if ( c = = 255 ) break ;
help ( setOption ( c , optarg , ec , conf ) ) ;
}
return 0 ;
}
/* *************************************************** */
static char * trim ( char * s ) {
char * end ;
while ( isspace ( s [ 0 ] ) | | ( s [ 0 ] = = ' " ' ) | | ( s [ 0 ] = = ' \' ' ) ) s + + ;
if ( s [ 0 ] = = 0 ) return s ;
end = & s [ strlen ( s ) - 1 ] ;
while ( end > s
& & ( isspace ( end [ 0 ] ) | | ( end [ 0 ] = = ' " ' ) | | ( end [ 0 ] = = ' \' ' ) ) )
end - - ;
end [ 1 ] = 0 ;
return s ;
}
/* *************************************************** */
/* parse the configuration file */
static int loadFromFile ( const char * path , n2n_edge_conf_t * conf , n2n_tuntap_priv_config_t * ec ) {
char buffer [ 4096 ] , * line ;
char * line_vec [ 3 ] ;
int tmp ;
FILE * fd ;
fd = fopen ( path , " r " ) ;
if ( fd = = NULL ) {
traceEvent ( TRACE_WARNING , " Config file %s not found " , path ) ;
return - 1 ;
}
// we mess around with optind, better save it
tmp = optind ;
while ( ( line = fgets ( buffer , sizeof ( buffer ) , fd ) ) ! = NULL ) {
line = trim ( line ) ;
if ( strlen ( line ) < 2 | | line [ 0 ] = = ' # ' )
continue ;
// executable, cannot be omitted, content can be anything
line_vec [ 0 ] = line ;
// first token, e.g. `-p` or `-A3', eventually followed by a whitespace or '=' delimiter
line_vec [ 1 ] = strtok ( line , " \t = " ) ;
// separate parameter option, if present
line_vec [ 2 ] = strtok ( NULL , " " ) ;
if ( line_vec [ 2 ] )
line_vec [ 2 ] = trim ( line_vec [ 2 ] ) ;
// not to duplicate the option parser code, call loadFromCLI and pretend we have no option read yet at all
optind = 0 ;
// if second token present (optional argument, not part of first), then announce 3 vector members
loadFromCLI ( line_vec [ 2 ] ? 3 : 2 , line_vec , conf , ec ) ;
}
fclose ( fd ) ;
optind = tmp ;
return 0 ;
}
/* ************************************** */
static void daemonize ( ) {
# ifndef WIN32
int childpid ;
traceEvent ( TRACE_NORMAL , " Parent process is exiting (this is normal) " ) ;
signal ( SIGPIPE , SIG_IGN ) ;
signal ( SIGHUP , SIG_IGN ) ;
signal ( SIGCHLD , SIG_IGN ) ;
signal ( SIGQUIT , SIG_IGN ) ;
if ( ( childpid = fork ( ) ) < 0 )
traceEvent ( TRACE_ERROR , " Occurred while daemonizing (errno=%d) " ,
errno ) ;
else {
if ( ! childpid ) { /* child */
int rc ;
//traceEvent(TRACE_NORMAL, "Bye bye: I'm becoming a daemon...");
rc = chdir ( " / " ) ;
if ( rc ! = 0 )
traceEvent ( TRACE_ERROR , " Error while moving to / directory " ) ;
setsid ( ) ; /* detach from the terminal */
fclose ( stdin ) ;
fclose ( stdout ) ;
/* fclose(stderr); */
/*
* clear any inherited file mode creation mask
*/
//umask(0);
/*
* Use line buffered stdout
*/
/* setlinebuf (stdout); */
setvbuf ( stdout , ( char * ) NULL , _IOLBF , 0 ) ;
} else /* father */
exit ( 0 ) ;
}
# endif
}
/* *************************************************** */
static int keep_on_running ;
# if defined(__linux__) || defined(WIN32)
# ifdef WIN32
BOOL WINAPI term_handler ( DWORD sig )
# else
static void term_handler ( int sig )
# endif
{
static int called = 0 ;
if ( called ) {
traceEvent ( TRACE_NORMAL , " Ok I am leaving now " ) ;
_exit ( 0 ) ;
} else {
traceEvent ( TRACE_NORMAL , " Shutting down... " ) ;
called = 1 ;
}
keep_on_running = 0 ;
# ifdef WIN32
return ( TRUE ) ;
# endif
}
# endif /* defined(__linux__) || defined(WIN32) */
/* *************************************************** */
/** Entry point to program from kernel. */
int main ( int argc , char * argv [ ] ) {
int rc ;
tuntap_dev tuntap ; /* a tuntap device */
n2n_edge_t * eee ; /* single instance for this program */
n2n_edge_conf_t conf ; /* generic N2N edge config */
n2n_tuntap_priv_config_t ec ; /* config used for standalone program execution */
uint8_t runlevel = 0 ; /* bootstrap: runlevel */
uint8_t seek_answer = 1 ; /* expecting answer from supernode */
time_t now , last_action = 0 ; /* timeout */
macstr_t mac_buf ; /* output mac address */
fd_set socket_mask ; /* for supernode answer */
struct timeval wait_time ; /* timeout for sn answer */
peer_info_t * scan , * scan_tmp ; /* supernode iteration */
uint16_t expected = sizeof ( uint16_t ) ;
uint16_t position = 0 ;
uint8_t pktbuf [ N2N_SN_PKTBUF_SIZE + sizeof ( uint16_t ) ] ; /* buffer + prepended buffer length in case of tcp */
# ifndef WIN32
struct passwd * pw = NULL ;
# endif
# ifdef HAVE_LIBCAP
cap_t caps ;
# endif
# ifdef WIN32
initWin32 ( ) ;
# endif
/* Defaults */
edge_init_conf_defaults ( & conf ) ;
memset ( & ec , 0 , sizeof ( ec ) ) ;
ec . mtu = DEFAULT_MTU ;
ec . daemon = 1 ; /* By default run in daemon mode. */
# ifndef WIN32
if ( ( ( pw = getpwnam ( " n2n " ) ) ! = NULL ) | |
( ( pw = getpwnam ( " nobody " ) ) ! = NULL ) ) {
ec . userid = pw - > pw_uid ;
ec . groupid = pw - > pw_gid ;
}
# endif
# ifdef WIN32
ec . tuntap_dev_name [ 0 ] = ' \0 ' ;
ec . metric = 0 ;
# else
snprintf ( ec . tuntap_dev_name , sizeof ( ec . tuntap_dev_name ) , N2N_EDGE_DEFAULT_DEV_NAME ) ;
# endif
snprintf ( ec . netmask , sizeof ( ec . netmask ) , N2N_EDGE_DEFAULT_NETMASK ) ;
if ( ( argc > = 2 ) & & ( argv [ 1 ] [ 0 ] ! = ' - ' ) ) {
rc = loadFromFile ( argv [ 1 ] , & conf , & ec ) ;
if ( argc > 2 )
rc = loadFromCLI ( argc , argv , & conf , & ec ) ;
} else if ( argc > 1 )
rc = loadFromCLI ( argc , argv , & conf , & ec ) ;
else
# ifdef WIN32
// load from current directory
rc = loadFromFile ( " edge.conf " , & conf , & ec ) ;
# else
rc = - 1 ;
# endif
// --- additional crypto setup; REVISIT: move to edge_init()?
// payload
if ( conf . transop_id = = N2N_TRANSFORM_ID_NULL ) {
if ( conf . encrypt_key ) {
// make sure that AES is default cipher if key only (and no cipher) is specified
traceEvent ( TRACE_WARNING , " Switching to AES as key was provided. " ) ;
conf . transop_id = N2N_TRANSFORM_ID_AES ;
}
}
// user auth
if ( conf . shared_secret /* containing private key only so far*/ ) {
// if user-password auth and no federation public key provided, use default
if ( ! conf . federation_public_key ) {
conf . federation_public_key = calloc ( 1 , sizeof ( n2n_private_public_key_t ) ) ;
if ( conf . federation_public_key ) {
traceEvent ( TRACE_WARNING , " Using default federation public key. FOR TESTING ONLY, usage of a custom federation name and key (-P) is highly recommended! " ) ;
generate_private_key ( * ( conf . federation_public_key ) , FEDERATION_NAME + 1 ) ;
generate_public_key ( * ( conf . federation_public_key ) , * ( conf . federation_public_key ) ) ;
}
}
// calculate public key and shared secret
if ( conf . federation_public_key ) {
traceEvent ( TRACE_NORMAL , " Using username and password for edge authentication. " ) ;
bind_private_key_to_username ( * ( conf . shared_secret ) , conf . dev_desc ) ;
conf . public_key = calloc ( 1 , sizeof ( n2n_private_public_key_t ) ) ;
if ( conf . public_key )
generate_public_key ( * conf . public_key , * ( conf . shared_secret ) ) ;
generate_shared_secret ( * ( conf . shared_secret ) , * ( conf . shared_secret ) , * ( conf . federation_public_key ) ) ;
// prepare (first 128 bit) for use as key
conf . shared_secret_ctx = ( he_context_t * ) calloc ( 1 , sizeof ( speck_context_t ) ) ;
speck_init ( ( speck_context_t * * ) & ( conf . shared_secret_ctx ) , * ( conf . shared_secret ) , 128 ) ;
}
// force header encryption
if ( conf . header_encryption ! = HEADER_ENCRYPTION_ENABLED ) {
traceEvent ( TRACE_NORMAL , " Enabling header encryption for edge authentication. " ) ;
conf . header_encryption = HEADER_ENCRYPTION_ENABLED ;
}
}
if ( rc < 0 )
help ( 1 ) ; /* short help */
if ( edge_verify_conf ( & conf ) ! = 0 )
help ( 1 ) ; /* short help */
traceEvent ( TRACE_NORMAL , " Starting n2n edge %s %s " , PACKAGE_VERSION , PACKAGE_BUILDDATE ) ;
# if defined(HAVE_OPENSSL_1_1)
traceEvent ( TRACE_NORMAL , " Using %s " , OpenSSL_version ( 0 ) ) ;
# endif
traceEvent ( TRACE_NORMAL , " Using compression: %s. " , compression_str ( conf . compression ) ) ;
traceEvent ( TRACE_NORMAL , " Using %s cipher. " , transop_str ( conf . transop_id ) ) ;
/* Random seed */
n2n_srand ( n2n_seed ( ) ) ;
# ifndef WIN32
/* If running suid root then we need to setuid before using the force. */
if ( setuid ( 0 ) ! = 0 )
traceEvent ( TRACE_ERROR , " Unable to become root [%u/%s] " , errno , strerror ( errno ) ) ;
/* setgid(0); */
# endif
if ( conf . encrypt_key & & ! strcmp ( ( char * ) conf . community_name , conf . encrypt_key ) )
traceEvent ( TRACE_WARNING , " Community and encryption key must differ, otherwise security will be compromised " ) ;
if ( ( eee = edge_init ( & conf , & rc ) ) = = NULL ) {
traceEvent ( TRACE_ERROR , " Failed in edge_init " ) ;
exit ( 1 ) ;
}
if ( resolve_create_thread ( & ( eee - > resolve_parameter ) , eee - > conf . supernodes ) = = 0 ) {
traceEvent ( TRACE_NORMAL , " Successfully created resolver thread " ) ;
}
memcpy ( & ( eee - > tuntap_priv_conf ) , & ec , sizeof ( ec ) ) ;
if ( ( 0 = = strcmp ( " static " , eee - > tuntap_priv_conf . ip_mode ) ) | |
( ( eee - > tuntap_priv_conf . ip_mode [ 0 ] = = ' \0 ' ) & & ( eee - > tuntap_priv_conf . ip_addr [ 0 ] ! = ' \0 ' ) ) ) {
traceEvent ( TRACE_NORMAL , " Use manually set IP address. " ) ;
eee - > conf . tuntap_ip_mode = TUNTAP_IP_MODE_STATIC ;
} else if ( 0 = = strcmp ( " dhcp " , eee - > tuntap_priv_conf . ip_mode ) ) {
traceEvent ( TRACE_NORMAL , " Obtain IP from other edge DHCP services. " ) ;
eee - > conf . tuntap_ip_mode = TUNTAP_IP_MODE_DHCP ;
} else {
traceEvent ( TRACE_NORMAL , " Automatically assign IP address by supernode. " ) ;
eee - > conf . tuntap_ip_mode = TUNTAP_IP_MODE_SN_ASSIGN ;
}
// mini main loop for bootstrap, not using main loop code because some of its mechanisms do not fit in here
// for the sake of quickly establishing connection. REVISIT when a more elegant way to re-use main loop code
// is found
// find at least one supernode alive to faster establish connection
// exceptions:
if ( ( HASH_COUNT ( eee - > conf . supernodes ) < = 1 ) | | ( eee - > conf . connect_tcp ) | | ( eee - > conf . shared_secret ) ) {
// skip the initial supernode ping
traceEvent ( TRACE_DEBUG , " Skip PING to supernode. " ) ;
runlevel = 2 ;
}
eee - > last_sup = 0 ; /* if it wasn't zero yet */
eee - > curr_sn = eee - > conf . supernodes ;
supernode_connect ( eee ) ;
while ( runlevel < 5 ) {
now = time ( NULL ) ;
// we do not use switch-case because we also check for 'greater than'
if ( runlevel = = 0 ) { /* PING to all known supernodes */
last_action = now ;
eee - > sn_pong = 0 ;
// (re-)initialize the number of max concurrent pings (decreases by calling send_query_peer)
eee - > conf . number_max_sn_pings = NUMBER_SN_PINGS_INITIAL ;
send_query_peer ( eee , null_mac ) ;
traceEvent ( TRACE_NORMAL , " Send PING to supernodes. " ) ;
runlevel + + ;
}
if ( runlevel = = 1 ) { /* PING has been sent to all known supernodes */
if ( eee - > sn_pong ) {
// first answer
eee - > sn_pong = 0 ;
sn_selection_sort ( & ( eee - > conf . supernodes ) ) ;
eee - > curr_sn = eee - > conf . supernodes ;
supernode_connect ( eee ) ;
traceEvent ( TRACE_NORMAL , " Received first PONG from supernode [%s]. " , eee - > curr_sn - > ip_addr ) ;
runlevel + + ;
} else if ( last_action < = ( now - BOOTSTRAP_TIMEOUT ) ) {
// timeout
runlevel - - ;
// skip waiting for answer to direcly go to send PING again
seek_answer = 0 ;
traceEvent ( TRACE_DEBUG , " PONG timeout. " ) ;
}
}
// by the way, have every later PONG cause the remaining (!) list to be sorted because the entries
// before have already been tried; as opposed to initial PONG, do not change curr_sn
if ( runlevel > 1 ) {
if ( eee - > sn_pong ) {
eee - > sn_pong = 0 ;
if ( eee - > curr_sn - > hh . next ) {
sn_selection_sort ( ( peer_info_t * * ) & ( eee - > curr_sn - > hh . next ) ) ;
traceEvent ( TRACE_DEBUG , " Received additional PONG from supernode. " ) ;
// here, it is hard to detemine from which one, so no details to output
}
}
}
if ( runlevel = = 2 ) { /* send REGISTER_SUPER to get auto ip address from a supernode */
if ( eee - > conf . tuntap_ip_mode = = TUNTAP_IP_MODE_SN_ASSIGN ) {
last_action = now ;
eee - > sn_wait = 1 ;
send_register_super ( eee ) ;
runlevel + + ;
traceEvent ( TRACE_NORMAL , " Send REGISTER_SUPER to supernode [%s] asking for IP address. " ,
eee - > curr_sn - > ip_addr ) ;
} else {
runlevel + = 2 ; /* skip waiting for TUNTAP IP address */
traceEvent ( TRACE_DEBUG , " Skip auto IP address asignment. " ) ;
}
}
if ( runlevel = = 3 ) { /* REGISTER_SUPER to get auto ip address from a sn has been sent */
if ( ! eee - > sn_wait ) { /* TUNTAP IP address received */
runlevel + + ;
traceEvent ( TRACE_NORMAL , " Received REGISTER_SUPER_ACK from supernode for IP address asignment. " ) ;
// it should be from curr_sn, but we can't determine definitely here, so no details to output
} else if ( last_action < = ( now - BOOTSTRAP_TIMEOUT ) ) {
// timeout, so try next supernode
if ( eee - > curr_sn - > hh . next )
eee - > curr_sn = eee - > curr_sn - > hh . next ;
else
eee - > curr_sn = eee - > conf . supernodes ;
supernode_connect ( eee ) ;
runlevel - - ;
// skip waiting for answer to direcly go to send REGISTER_SUPER again
seek_answer = 0 ;
traceEvent ( TRACE_DEBUG , " REGISTER_SUPER_ACK timeout. " ) ;
}
}
if ( runlevel = = 4 ) { /* configure the TUNTAP device */
if ( tuntap_open ( & tuntap , eee - > tuntap_priv_conf . tuntap_dev_name , eee - > tuntap_priv_conf . ip_mode ,
eee - > tuntap_priv_conf . ip_addr , eee - > tuntap_priv_conf . netmask ,
eee - > tuntap_priv_conf . device_mac , eee - > tuntap_priv_conf . mtu
# ifdef WIN32
, eee - > tuntap_priv_conf . metric
# endif
) < 0 )
exit ( 1 ) ;
memcpy ( & eee - > device , & tuntap , sizeof ( tuntap ) ) ;
traceEvent ( TRACE_NORMAL , " Created local tap device IP: %s, Mask: %s, MAC: %s " ,
eee - > tuntap_priv_conf . ip_addr ,
eee - > tuntap_priv_conf . netmask ,
macaddr_str ( mac_buf , eee - > device . mac_addr ) ) ;
runlevel = 5 ;
// no more answers required
seek_answer = 0 ;
}
// we usually wait for some answer, there however are exceptions when going back to a previous runlevel
if ( seek_answer ) {
FD_ZERO ( & socket_mask ) ;
FD_SET ( eee - > sock , & socket_mask ) ;
wait_time . tv_sec = BOOTSTRAP_TIMEOUT ;
wait_time . tv_usec = 0 ;
if ( select ( eee - > sock + 1 , & socket_mask , NULL , NULL , & wait_time ) > 0 ) {
if ( FD_ISSET ( eee - > sock , & socket_mask ) ) {
fetch_and_eventually_process_data ( eee , eee - > sock ,
pktbuf , & expected , & position ,
now ) ;
}
}
}
seek_answer = 1 ;
resolve_check ( eee - > resolve_parameter , now ) ;
}
// allow a higher number of pings for first regular round of ping
// to quicker get an inital 'supernode selection criterion overview'
eee - > conf . number_max_sn_pings = NUMBER_SN_PINGS_INITIAL ;
// shape supernode list; make current one the first on the list
HASH_ITER ( hh , eee - > conf . supernodes , scan , scan_tmp ) {
if ( scan = = eee - > curr_sn )
sn_selection_criterion_good ( & ( scan - > selection_criterion ) ) ;
else
sn_selection_criterion_default ( & ( scan - > selection_criterion ) ) ;
}
sn_selection_sort ( & ( eee - > conf . supernodes ) ) ;
// do not immediately ping again, allow some time
eee - > last_sweep = now - SWEEP_TIME + 2 * BOOTSTRAP_TIMEOUT ;
eee - > sn_wait = 1 ;
eee - > last_register_req = 0 ;
# ifndef WIN32
if ( eee - > tuntap_priv_conf . daemon ) {
setUseSyslog ( 1 ) ; /* traceEvent output now goes to syslog. */
daemonize ( ) ;
}
# endif /* #ifndef WIN32 */
# ifndef WIN32
# ifdef HAVE_LIBCAP
/* Before dropping the privileges, retain capabilities to regain them in future. */
caps = cap_get_proc ( ) ;
cap_set_flag ( caps , CAP_PERMITTED , num_cap , cap_values , CAP_SET ) ;
cap_set_flag ( caps , CAP_EFFECTIVE , num_cap , cap_values , CAP_SET ) ;
if ( ( cap_set_proc ( caps ) ! = 0 ) | | ( prctl ( PR_SET_KEEPCAPS , 1 , 0 , 0 , 0 ) ! = 0 ) )
traceEvent ( TRACE_WARNING , " Unable to retain permitted capabilities [%s] \n " , strerror ( errno ) ) ;
# else
# ifndef __APPLE__
traceEvent ( TRACE_WARNING , " n2n has not been compiled with libcap-dev. Some commands may fail. " ) ;
# endif
# endif /* HAVE_LIBCAP */
if ( ( eee - > tuntap_priv_conf . userid ! = 0 ) | | ( eee - > tuntap_priv_conf . groupid ! = 0 ) ) {
traceEvent ( TRACE_NORMAL , " Dropping privileges to uid=%d, gid=%d " ,
( signed int ) eee - > tuntap_priv_conf . userid , ( signed int ) eee - > tuntap_priv_conf . groupid ) ;
/* Finished with the need for root privileges. Drop to unprivileged user. */
if ( ( setgid ( eee - > tuntap_priv_conf . groupid ) ! = 0 )
| | ( setuid ( eee - > tuntap_priv_conf . userid ) ! = 0 ) ) {
traceEvent ( TRACE_ERROR , " Unable to drop privileges [%u/%s] " , errno , strerror ( errno ) ) ;
exit ( 1 ) ;
}
}
if ( ( getuid ( ) = = 0 ) | | ( getgid ( ) = = 0 ) )
traceEvent ( TRACE_WARNING , " Running as root is discouraged, check out the -u/-g options " ) ;
# endif
# ifdef __linux__
signal ( SIGPIPE , SIG_IGN ) ;
signal ( SIGTERM , term_handler ) ;
signal ( SIGINT , term_handler ) ;
# endif
# ifdef WIN32
SetConsoleCtrlHandler ( term_handler , TRUE ) ;
# endif
keep_on_running = 1 ;
traceEvent ( TRACE_NORMAL , " edge started " ) ;
rc = run_edge_loop ( eee , & keep_on_running ) ;
print_edge_stats ( eee ) ;
# ifdef HAVE_LIBCAP
/* Before completing the cleanup, regain the capabilities as some
* cleanup tasks require them ( e . g . routes cleanup ) . */
cap_set_flag ( caps , CAP_EFFECTIVE , num_cap , cap_values , CAP_SET ) ;
if ( cap_set_proc ( caps ) ! = 0 )
traceEvent ( TRACE_WARNING , " Could not regain the capabilities [%s] \n " , strerror ( errno ) ) ;
cap_free ( caps ) ;
# endif
/* Cleanup */
edge_term_conf ( & eee - > conf ) ;
tuntap_close ( & eee - > device ) ;
edge_term ( eee ) ;
# ifdef WIN32
destroyWin32 ( ) ;
# endif
return ( rc ) ;
}
/* ************************************** */