/**
* ( 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/>
*
*/
/* Supernode for n2n-2.x */
# include "n2n.h"
# include "header_encryption.h"
# define HASH_FIND_COMMUNITY(head, name, out) HASH_FIND_STR(head, name, out)
static n2n_sn_t sss_node ;
void close_tcp_connection ( n2n_sn_t * sss , n2n_tcp_connection_t * conn ) ;
void calculate_shared_secrets ( n2n_sn_t * sss ) ;
int load_allowed_sn_community ( n2n_sn_t * sss ) ;
int resolve_create_thread ( n2n_resolve_parameter_t * * param , struct peer_info * sn_list ) ;
/** Help message to print if the command line arguments are not valid. */
static void help ( int level ) {
if ( level = = 0 ) /* no help required */
return ;
printf ( " \n " ) ;
print_n2n_version ( ) ;
if ( level = = 1 ) /* short help */ {
printf ( " basic usage: supernode <config file> (see supernode.conf) \n "
" \n "
" or supernode "
" [optional parameters, at least one] "
" \n "
" \n technically, all parameters are optional, but the supernode executable "
" \n requires at least one parameter to run, .e.g. -v or -f, as otherwise this "
" \n short help text is displayed "
" \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: supernode <config file> (see supernode.conf) \n "
" \n "
" or supernode "
" [-p <local port>] "
" \n "
" [-F <federation name>] "
" \n options for under- "
" [-l <supernode host:port>] "
" \n lying connection "
# ifdef SN_MANUAL_MAC
" [-m <mac address>] "
# endif
" [-M] "
" [-V <version text>] "
" \n \n overlay network "
" [-c <community list file>] "
" \n configuration "
" [-a <net ip>-<net ip>/<cidr suffix>] "
" \n \n local options "
# if defined(N2N_HAVE_DAEMON)
" [-f] "
# endif
" [-t <management port>] "
" [-v] "
# ifndef WIN32
" \n "
" [-u <numerical user id>] "
" [-g <numerical group id>] "
# endif
" \n \n meaning of the "
" [-M] disable MAC and IP address spoofing protection "
" \n flag options "
# if defined(N2N_HAVE_DAEMON)
" [-f] do not fork but run in foreground "
" \n "
# endif
" [-v] make more verbose, repeat as required "
" \n "
" \n technically, all parameters are optional, but the supernode executable "
" \n requires at least one parameter to run, .e.g. -v or -f, as otherwise a "
" \n short help text is displayed "
" \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: supernode <config file> (see supernode.conf) \n "
" \n "
" or supernode [optional parameters, at least one] \n \n "
) ;
printf ( " OPTIONS FOR THE UNDERLYING NETWORK CONNECTION \n " ) ;
printf ( " --------------------------------------------- \n \n " ) ;
printf ( " -p <local port> | fixed local UDP port, defaults to %u \n " , N2N_SN_LPORT_DEFAULT ) ;
printf ( " -F <fed name> | name of the supernode's federation, defaults to \n "
" | '%s' \n " , ( char * ) FEDERATION_NAME ) ;
printf ( " -l <host:port> | ip address or name, and port of known supernode \n " ) ;
# ifdef SN_MANUAL_MAC
printf ( " -m <mac> | fixed MAC address for the supernode, e.g. \n "
" | '-m 10:20:30:40:50:60', random otherwise \n " ) ;
# endif
printf ( " -M | disable MAC and IP address spoofing protection for all \n "
" | non-username-password-authenticating communities \n " ) ;
printf ( " -V <version text> | sends a custom supernode version string of max 19 letters \n "
" | length to edges, visible in their management port output \n " ) ;
printf ( " \n " ) ;
printf ( " TAP DEVICE AND OVERLAY NETWORK CONFIGURATION \n " ) ;
printf ( " -------------------------------------------- \n \n " ) ;
printf ( " -c <path> | file containing the allowed communities \n " ) ;
printf ( " -a <net-net/n> | subnet range for auto ip address service, e.g. \n "
" | '-a 192.168.0.0-192.168.255.0/24', defaults \n "
" | to '10.128.255.0-10.255.255.0/24' \n " ) ;
printf ( " \n " ) ;
printf ( " LOCAL OPTIONS \n " ) ;
printf ( " ------------- \n \n " ) ;
# if defined(N2N_HAVE_DAEMON)
printf ( " -f | do not fork and run as a daemon, rather run in foreground \n " ) ;
# endif
printf ( " -t <port> | management UDP port, for multiple supernodes on a machine, \n "
" | defaults to %u \n " , N2N_SN_MGMT_PORT ) ;
printf ( " -v | make more verbose, repeat as required \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 technically, all parameters are optional, but the supernode executable "
" \n requires at least one parameter to run, .e.g. -v or -f, as otherwise a "
" \n short help text is displayed "
" \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 int setOption ( int optkey , char * _optarg , n2n_sn_t * sss ) {
//traceEvent(TRACE_NORMAL, "Option %c = %s", optkey, _optarg ? _optarg : "");
switch ( optkey ) {
case ' p ' : /* local-port */
sss - > lport = atoi ( _optarg ) ;
if ( sss - > lport = = 0 )
traceEvent ( TRACE_WARNING , " bad local port format, defaulting to %u " , N2N_SN_LPORT_DEFAULT ) ;
// default is made sure in sn_init()
break ;
case ' t ' : /* mgmt-port */
sss - > mport = atoi ( _optarg ) ;
if ( sss - > mport = = 0 )
traceEvent ( TRACE_WARNING , " bad management port format, defaulting to %u " , N2N_SN_MGMT_PORT ) ;
// default is made sure in sn_init()
break ;
case ' l ' : { /* supernode:port */
n2n_sock_t * socket ;
struct peer_info * anchor_sn ;
size_t length ;
int rv = - 1 ;
int skip_add ;
char * double_column = strchr ( _optarg , ' : ' ) ;
length = strlen ( _optarg ) ;
if ( length > = N2N_EDGE_SN_HOST_SIZE ) {
traceEvent ( TRACE_WARNING , " size of -l argument too long: %zu; maximum size is %d " , length , N2N_EDGE_SN_HOST_SIZE ) ;
return 1 ;
}
if ( ! double_column ) {
traceEvent ( TRACE_WARNING , " invalid -l format, missing port " ) ;
return 1 ;
}
socket = ( n2n_sock_t * ) calloc ( 1 , sizeof ( n2n_sock_t ) ) ;
rv = supernode2sock ( socket , _optarg ) ;
if ( rv < - 2 ) { /* we accept resolver failure as it might resolve later */
traceEvent ( TRACE_WARNING , " invalid supernode parameter " ) ;
free ( socket ) ;
return 1 ;
}
if ( sss - > federation ! = NULL ) {
skip_add = SN_ADD ;
anchor_sn = add_sn_to_list_by_mac_or_sock ( & ( sss - > federation - > edges ) , socket , null_mac , & skip_add ) ;
if ( anchor_sn ! = NULL ) {
anchor_sn - > ip_addr = calloc ( 1 , N2N_EDGE_SN_HOST_SIZE ) ;
if ( anchor_sn - > ip_addr ) {
strncpy ( anchor_sn - > ip_addr , _optarg , N2N_EDGE_SN_HOST_SIZE - 1 ) ;
memcpy ( & ( anchor_sn - > sock ) , socket , sizeof ( n2n_sock_t ) ) ;
memcpy ( anchor_sn - > mac_addr , null_mac , sizeof ( n2n_mac_t ) ) ;
anchor_sn - > purgeable = SN_UNPURGEABLE ;
anchor_sn - > last_valid_time_stamp = initial_time_stamp ( ) ;
}
}
}
free ( socket ) ;
break ;
}
case ' a ' : {
dec_ip_str_t ip_min_str = { ' \0 ' } ;
dec_ip_str_t ip_max_str = { ' \0 ' } ;
in_addr_t net_min , net_max ;
uint8_t bitlen ;
uint32_t mask ;
if ( sscanf ( _optarg , " %15[^ \\ -]-%15[^/]/%hhu " , ip_min_str , ip_max_str , & bitlen ) ! = 3 ) {
traceEvent ( TRACE_WARNING , " bad net-net/bit format '%s'. " , _optarg ) ;
return 2 ;
}
net_min = inet_addr ( ip_min_str ) ;
net_max = inet_addr ( ip_max_str ) ;
mask = bitlen2mask ( bitlen ) ;
if ( ( net_min = = ( in_addr_t ) ( - 1 ) ) | | ( net_min = = INADDR_NONE ) | | ( net_min = = INADDR_ANY )
| | ( net_max = = ( in_addr_t ) ( - 1 ) ) | | ( net_max = = INADDR_NONE ) | | ( net_max = = INADDR_ANY )
| | ( ntohl ( net_min ) > ntohl ( net_max ) )
| | ( ( ntohl ( net_min ) & ~ mask ) ! = 0 ) | | ( ( ntohl ( net_max ) & ~ mask ) ! = 0 ) ) {
traceEvent ( TRACE_WARNING , " bad network range '%s...%s/%u' in '%s', defaulting to '%s...%s/%d' " ,
ip_min_str , ip_max_str , bitlen , _optarg ,
N2N_SN_MIN_AUTO_IP_NET_DEFAULT , N2N_SN_MAX_AUTO_IP_NET_DEFAULT , N2N_SN_AUTO_IP_NET_BIT_DEFAULT ) ;
return 2 ;
}
if ( ( bitlen > 30 ) | | ( bitlen = = 0 ) ) {
traceEvent ( TRACE_WARNING , " bad prefix '%hhu' in '%s', defaulting to '%s...%s/%d' " ,
bitlen , _optarg ,
N2N_SN_MIN_AUTO_IP_NET_DEFAULT , N2N_SN_MAX_AUTO_IP_NET_DEFAULT , N2N_SN_AUTO_IP_NET_BIT_DEFAULT ) ;
return 2 ;
}
traceEvent ( TRACE_NORMAL , " the network range for community ip address service is '%s...%s/%hhu' " , ip_min_str , ip_max_str , bitlen ) ;
sss - > min_auto_ip_net . net_addr = ntohl ( net_min ) ;
sss - > min_auto_ip_net . net_bitlen = bitlen ;
sss - > max_auto_ip_net . net_addr = ntohl ( net_max ) ;
sss - > max_auto_ip_net . net_bitlen = bitlen ;
break ;
}
# ifndef WIN32
case ' u ' : /* unprivileged uid */
sss - > userid = atoi ( _optarg ) ;
break ;
case ' g ' : /* unprivileged uid */
sss - > groupid = atoi ( _optarg ) ;
break ;
# endif
case ' F ' : { /* federation name */
snprintf ( sss - > federation - > community , N2N_COMMUNITY_SIZE - 1 , " *%s " , _optarg ) ;
sss - > federation - > community [ N2N_COMMUNITY_SIZE - 1 ] = ' \0 ' ;
break ;
}
# ifdef SN_MANUAL_MAC
case ' m ' : { /* MAC address */
str2mac ( sss - > mac_addr , _optarg ) ;
break ;
}
# endif
case ' M ' : /* override spoofing protection */
sss - > override_spoofing_protection = 1 ;
break ;
case ' V ' : /* community file */
strncpy ( sss - > version , _optarg , sizeof ( n2n_version_t ) - 1 ) ; /* mind the \0 terminator */
case ' c ' : /* community file */
sss - > community_file = calloc ( 1 , strlen ( _optarg ) + 1 ) ;
if ( sss - > community_file )
strcpy ( sss - > community_file , _optarg ) ;
break ;
# if defined(N2N_HAVE_DAEMON)
case ' f ' : /* foreground */
sss - > daemon = 0 ;
break ;
# endif
case ' h ' : /* quick reference */
return 2 ;
case ' @ ' : /* long help */
return 3 ;
case ' v ' : /* verbose */
setTraceLevel ( getTraceLevel ( ) + 1 ) ;
break ;
default :
traceEvent ( TRACE_WARNING , " unknown option -%c: " , ( char ) optkey ) ;
return 2 ;
}
return 0 ;
}
/* *********************************************** */
static const struct option long_options [ ] = {
{ " communities " , required_argument , NULL , ' c ' } ,
# if defined(N2N_HAVE_DAEMON)
{ " foreground " , no_argument , NULL , ' f ' } ,
# endif
{ " local-port " , required_argument , NULL , ' p ' } ,
{ " mgmt-port " , required_argument , NULL , ' t ' } ,
{ " autoip " , required_argument , NULL , ' a ' } ,
{ " 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 * const argv [ ] , n2n_sn_t * sss ) {
u_char c ;
while ( ( c = getopt_long ( argc , argv ,
" p:l:t:a:c:F:vhMV: "
# ifdef SN_MANUAL_MAC
" m: "
# endif
# if defined(N2N_HAVE_DAEMON)
" f "
# endif
# ifndef WIN32
" u:g: "
# endif
,
long_options , NULL ) ) ! = ' ? ' ) {
if ( c = = 255 ) {
break ;
}
help ( setOption ( c , optarg , sss ) ) ;
}
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_sn_t * sss ) {
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`, eventually followed by a whitespace or '=' delimiter
line_vec [ 1 ] = strtok ( line , " \t = " ) ;
// separate parameter option, if present
line_vec [ 2 ] = strtok ( NULL , " \t " ) ;
// not to duplicate the option parser code, call loadFromCLI and pretend we have no option read yet
optind = 0 ;
// if separate second token present (optional argument, not part of first), then announce 3 vector members
loadFromCLI ( line_vec [ 2 ] ? 3 : 2 , line_vec , sss ) ;
}
fclose ( fd ) ;
optind = tmp ;
return 0 ;
}
/* *************************************************** */
/* Add the federation to the communities list of a supernode */
static int add_federation_to_communities ( n2n_sn_t * sss ) {
uint32_t num_communities = 0 ;
if ( sss - > federation ! = NULL ) {
HASH_ADD_STR ( sss - > communities , community , sss - > federation ) ;
num_communities = HASH_COUNT ( sss - > communities ) ;
traceEvent ( TRACE_INFO , " added federation '%s' to the list of communities [total: %u] " ,
( char * ) sss - > federation - > community , num_communities ) ;
}
return 0 ;
}
/* *************************************************** */
# ifdef __linux__
static void dump_registrations ( int signo ) {
struct sn_community * comm , * ctmp ;
struct peer_info * list , * tmp ;
char buf [ 32 ] ;
time_t now = time ( NULL ) ;
u_int num = 0 ;
traceEvent ( TRACE_NORMAL , " ==================================== " ) ;
HASH_ITER ( hh , sss_node . communities , comm , ctmp ) {
traceEvent ( TRACE_NORMAL , " dumping community: %s " , comm - > community ) ;
HASH_ITER ( hh , comm - > edges , list , tmp ) {
if ( list - > sock . family = = AF_INET ) {
traceEvent ( TRACE_NORMAL , " [id: %u][MAC: %s][edge: %u.%u.%u.%u:%u][last seen: %u sec ago] " ,
+ + num , macaddr_str ( buf , list - > mac_addr ) ,
list - > sock . addr . v4 [ 0 ] , list - > sock . addr . v4 [ 1 ] , list - > sock . addr . v4 [ 2 ] , list - > sock . addr . v4 [ 3 ] ,
list - > sock . port ,
now - list - > last_seen ) ;
} else {
traceEvent ( TRACE_NORMAL , " [id: %u][MAC: %s][edge: IPv6:%u][last seen: %u sec ago] " ,
+ + num , macaddr_str ( buf , list - > mac_addr ) , list - > sock . port ,
now - list - > last_seen ) ;
}
}
}
traceEvent ( TRACE_NORMAL , " ==================================== " ) ;
}
# endif
/* *************************************************** */
static int keep_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_running = 0 ;
# ifdef WIN32
return ( TRUE ) ;
# endif
}
# endif /* defined(__linux__) || defined(WIN32) */
/* *************************************************** */
/** Main program entry point from kernel. */
int main ( int argc , char * const argv [ ] ) {
int rc ;
# ifndef WIN32
struct passwd * pw = NULL ;
# endif
struct peer_info * scan , * tmp ;
sn_init_defaults ( & sss_node ) ;
add_federation_to_communities ( & sss_node ) ;
if ( ( argc > = 2 ) & & ( argv [ 1 ] [ 0 ] ! = ' - ' ) ) {
rc = loadFromFile ( argv [ 1 ] , & sss_node ) ;
if ( argc > 2 ) {
rc = loadFromCLI ( argc , argv , & sss_node ) ;
}
} else if ( argc > 1 ) {
rc = loadFromCLI ( argc , argv , & sss_node ) ;
} else
# ifdef WIN32
// load from current directory
rc = loadFromFile ( " supernode.conf " , & sss_node ) ;
# else
rc = - 1 ;
# endif
if ( rc < 0 ) {
help ( 1 ) ; /* short help */
}
if ( sss_node . community_file )
load_allowed_sn_community ( & sss_node ) ;
# if defined(N2N_HAVE_DAEMON)
if ( sss_node . daemon ) {
setUseSyslog ( 1 ) ; /* traceEvent output now goes to syslog. */
if ( - 1 = = daemon ( 0 , 0 ) ) {
traceEvent ( TRACE_ERROR , " failed to become daemon " ) ;
exit ( - 5 ) ;
}
}
# endif /* #if defined(N2N_HAVE_DAEMON) */
// warn on default federation name
if ( ! strcmp ( sss_node . federation - > community , FEDERATION_NAME ) ) {
traceEvent ( TRACE_WARNING , " using default federation name; FOR TESTING ONLY, usage of a custom federation name (-F) is highly recommended! " ) ;
}
if ( sss_node . override_spoofing_protection ) {
traceEvent ( TRACE_WARNING , " disabled MAC and IP address spoofing protection; FOR TESTING ONLY, usage of user-password authentication (-I, -J, -P) recommended instead! " ) ;
}
calculate_shared_secrets ( & sss_node ) ;
traceEvent ( TRACE_DEBUG , " traceLevel is %d " , getTraceLevel ( ) ) ;
sss_node . sock = open_socket ( sss_node . lport , INADDR_ANY , 0 /* UDP */ ) ;
if ( - 1 = = sss_node . sock ) {
traceEvent ( TRACE_ERROR , " failed to open main socket. %s " , strerror ( errno ) ) ;
exit ( - 2 ) ;
} else {
traceEvent ( TRACE_NORMAL , " supernode is listening on UDP %u (main) " , sss_node . lport ) ;
}
# ifdef N2N_HAVE_TCP
sss_node . tcp_sock = open_socket ( sss_node . lport , INADDR_ANY , 1 /* TCP */ ) ;
if ( - 1 = = sss_node . tcp_sock ) {
traceEvent ( TRACE_ERROR , " failed to open auxiliary TCP socket, %s " , strerror ( errno ) ) ;
exit ( - 2 ) ;
} else {
traceEvent ( TRACE_NORMAL , " supernode opened TCP %u (aux) " , sss_node . lport ) ;
}
if ( - 1 = = listen ( sss_node . tcp_sock , N2N_TCP_BACKLOG_QUEUE_SIZE ) ) {
traceEvent ( TRACE_ERROR , " failed to listen on auxiliary TCP socket, %s " , strerror ( errno ) ) ;
exit ( - 2 ) ;
} else {
traceEvent ( TRACE_NORMAL , " supernode is listening on TCP %u (aux) " , sss_node . lport ) ;
}
# endif
sss_node . mgmt_sock = open_socket ( sss_node . mport , INADDR_LOOPBACK , 0 /* UDP */ ) ;
if ( - 1 = = sss_node . mgmt_sock ) {
traceEvent ( TRACE_ERROR , " failed to open management socket, %s " , strerror ( errno ) ) ;
exit ( - 2 ) ;
} else {
traceEvent ( TRACE_NORMAL , " supernode is listening on UDP %u (management) " , sss_node . mport ) ;
}
HASH_ITER ( hh , sss_node . federation - > edges , scan , tmp )
scan - > socket_fd = sss_node . sock ;
# ifndef WIN32
if ( ( ( pw = getpwnam ( " n2n " ) ) ! = NULL ) | | ( ( pw = getpwnam ( " nobody " ) ) ! = NULL ) ) {
sss_node . userid = sss_node . userid = = 0 ? pw - > pw_uid : 0 ;
sss_node . groupid = sss_node . groupid = = 0 ? pw - > pw_gid : 0 ;
}
if ( ( sss_node . userid ! = 0 ) | | ( sss_node . groupid ! = 0 ) ) {
traceEvent ( TRACE_NORMAL , " dropping privileges to uid=%d, gid=%d " ,
( signed int ) sss_node . userid , ( signed int ) sss_node . groupid ) ;
/* Finished with the need for root privileges. Drop to unprivileged user. */
if ( ( setgid ( sss_node . groupid ) ! = 0 )
| | ( setuid ( sss_node . 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
sn_init ( & sss_node ) ;
traceEvent ( TRACE_NORMAL , " supernode started " ) ;
# ifdef __linux__
signal ( SIGPIPE , SIG_IGN ) ;
signal ( SIGTERM , term_handler ) ;
signal ( SIGINT , term_handler ) ;
signal ( SIGHUP , dump_registrations ) ;
# endif
# ifdef WIN32
SetConsoleCtrlHandler ( term_handler , TRUE ) ;
# endif
keep_running = 1 ;
return run_sn_loop ( & sss_node , & keep_running ) ;
}