2002-02-18 16:25:19 +00:00
/*
net_socket . c - - Handle various kinds of sockets .
2006-04-26 13:52:58 +00:00
Copyright ( C ) 1998 - 2005 Ivo Timmermans ,
2014-02-07 19:38:48 +00:00
2000 - 2014 Guus Sliepen < guus @ tinc - vpn . org >
2009-09-25 19:14:56 +00:00
2006 Scott Lamb < slamb @ slamb . org >
2009-09-24 21:29:46 +00:00
2009 Florian Forster < octo @ verplant . org >
2002-02-18 16:25:19 +00:00
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 2 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 .
2009-09-24 22:01:00 +00:00
You should have received a copy of the GNU General Public License along
with this program ; if not , write to the Free Software Foundation , Inc . ,
51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
2002-02-18 16:25:19 +00:00
*/
2003-07-17 15:06:27 +00:00
# include "system.h"
2002-02-18 16:25:19 +00:00
# include "conf.h"
# include "connection.h"
2013-01-17 17:12:55 +00:00
# include "control_common.h"
2012-10-09 11:28:09 +00:00
# include "list.h"
2003-07-17 15:06:27 +00:00
# include "logger.h"
2002-02-18 16:25:19 +00:00
# include "meta.h"
2013-01-17 15:39:02 +00:00
# include "names.h"
2002-02-18 16:25:19 +00:00
# include "net.h"
# include "netutl.h"
# include "protocol.h"
2003-07-17 15:06:27 +00:00
# include "utils.h"
# include "xalloc.h"
2002-04-18 20:09:05 +00:00
2006-01-13 11:21:59 +00:00
/* Needed on Mac OS/X */
# ifndef SOL_TCP
# define SOL_TCP IPPROTO_TCP
# endif
2003-06-11 19:27:35 +00:00
int addressfamily = AF_UNSPEC ;
2002-02-18 16:25:19 +00:00
int maxtimeout = 900 ;
int seconds_till_retry = 5 ;
2010-11-13 18:05:49 +00:00
int udp_rcvbuf = 0 ;
int udp_sndbuf = 0 ;
2013-07-11 21:38:38 +00:00
int max_connection_burst = 100 ;
2002-02-18 16:25:19 +00:00
2002-03-18 22:47:20 +00:00
listen_socket_t listen_socket [ MAXSOCKETS ] ;
2002-06-13 16:12:40 +00:00
int listen_sockets ;
2013-01-17 17:12:55 +00:00
# ifndef HAVE_MINGW
io_t unix_socket ;
# endif
2009-01-20 12:12:41 +00:00
list_t * outgoing_list = NULL ;
2002-02-26 23:26:41 +00:00
2002-02-18 16:25:19 +00:00
/* Setup sockets */
2007-05-18 10:00:00 +00:00
static void configure_tcp ( connection_t * c ) {
2006-01-13 11:21:59 +00:00
int option ;
# ifdef O_NONBLOCK
2006-01-19 17:13:18 +00:00
int flags = fcntl ( c - > socket , F_GETFL ) ;
2006-01-13 11:21:59 +00:00
2006-01-19 17:13:18 +00:00
if ( fcntl ( c - > socket , F_SETFL , flags | O_NONBLOCK ) < 0 ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " fcntl for %s: %s " , c - > hostname , strerror ( errno ) ) ;
2006-01-13 11:21:59 +00:00
}
2007-05-17 19:15:48 +00:00
# elif defined(WIN32)
unsigned long arg = 1 ;
if ( ioctlsocket ( c - > socket , FIONBIO , & arg ) ! = 0 ) {
2012-10-14 17:21:13 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " ioctlsocket for %s: %s " , c - > hostname , sockstrerror ( sockerrno ) ) ;
2007-05-17 19:15:48 +00:00
}
2006-01-13 11:21:59 +00:00
# endif
# if defined(SOL_TCP) && defined(TCP_NODELAY)
option = 1 ;
2010-11-12 15:15:29 +00:00
setsockopt ( c - > socket , SOL_TCP , TCP_NODELAY , ( void * ) & option , sizeof option ) ;
2006-01-13 11:21:59 +00:00
# endif
# if defined(SOL_IP) && defined(IP_TOS) && defined(IPTOS_LOWDELAY)
option = IPTOS_LOWDELAY ;
2010-11-12 15:15:29 +00:00
setsockopt ( c - > socket , SOL_IP , IP_TOS , ( void * ) & option , sizeof option ) ;
2006-01-13 11:21:59 +00:00
# endif
}
2009-09-24 22:14:03 +00:00
static bool bind_to_interface ( int sd ) {
2009-05-27 12:20:24 +00:00
char * iface ;
# if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
struct ifreq ifr ;
int status ;
# endif /* defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) */
if ( ! get_config_string ( lookup_config ( config_tree , " BindToInterface " ) , & iface ) )
return true ;
# if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
memset ( & ifr , 0 , sizeof ( ifr ) ) ;
strncpy ( ifr . ifr_ifrn . ifrn_name , iface , IFNAMSIZ ) ;
ifr . ifr_ifrn . ifrn_name [ IFNAMSIZ - 1 ] = 0 ;
2010-05-01 13:39:59 +00:00
status = setsockopt ( sd , SOL_SOCKET , SO_BINDTODEVICE , ( void * ) & ifr , sizeof ( ifr ) ) ;
2009-05-27 12:20:24 +00:00
if ( status ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Can't bind to interface %s: %s " , iface ,
2014-06-26 19:42:40 +00:00
sockstrerror ( sockerrno ) ) ;
2009-05-27 12:20:24 +00:00
return false ;
}
# else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_WARNING , " %s not supported on this platform " , " BindToInterface " ) ;
2009-05-27 12:20:24 +00:00
# endif
return true ;
2009-09-24 22:14:03 +00:00
}
2009-05-27 12:20:24 +00:00
2013-08-18 21:55:40 +00:00
static bool bind_to_address ( connection_t * c ) {
int s = - 1 ;
2014-01-20 20:19:13 +00:00
for ( int i = 0 ; i < listen_sockets & & listen_socket [ i ] . bindto ; i + + ) {
2013-08-18 21:55:40 +00:00
if ( listen_socket [ i ] . sa . sa . sa_family ! = c - > address . sa . sa_family )
continue ;
if ( s > = 0 )
return false ;
s = i ;
}
if ( s < 0 )
return false ;
sockaddr_t sa = listen_socket [ s ] . sa ;
if ( sa . sa . sa_family = = AF_INET )
sa . in . sin_port = 0 ;
else if ( sa . sa . sa_family = = AF_INET6 )
sa . in6 . sin6_port = 0 ;
if ( bind ( c - > socket , & sa . sa , SALEN ( sa . sa ) ) ) {
2014-06-26 19:42:40 +00:00
logger ( DEBUG_CONNECTIONS , LOG_WARNING , " Can't bind outgoing socket: %s " , sockstrerror ( sockerrno ) ) ;
2013-08-18 21:55:40 +00:00
return false ;
}
return true ;
}
2009-09-24 22:14:03 +00:00
int setup_listen_socket ( const sockaddr_t * sa ) {
2003-12-20 19:47:53 +00:00
int nfd ;
2002-09-09 21:25:28 +00:00
char * addrstr ;
int option ;
2003-07-18 13:45:06 +00:00
char * iface ;
2002-09-09 19:40:12 +00:00
2002-09-09 21:25:28 +00:00
nfd = socket ( sa - > sa . sa_family , SOCK_STREAM , IPPROTO_TCP ) ;
2002-02-18 16:25:19 +00:00
2002-09-09 21:25:28 +00:00
if ( nfd < 0 ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_STATUS , LOG_ERR , " Creating metasocket failed: %s " , sockstrerror ( sockerrno ) ) ;
2002-09-09 21:25:28 +00:00
return - 1 ;
}
2012-02-17 15:13:38 +00:00
# ifdef FD_CLOEXEC
fcntl ( nfd , F_SETFD , FD_CLOEXEC ) ;
# endif
2002-09-09 21:25:28 +00:00
/* Optimize TCP settings */
2002-02-18 16:25:19 +00:00
2002-09-09 21:25:28 +00:00
option = 1 ;
2010-11-12 15:15:29 +00:00
setsockopt ( nfd , SOL_SOCKET , SO_REUSEADDR , ( void * ) & option , sizeof option ) ;
2002-03-01 15:14:29 +00:00
2008-12-11 20:49:14 +00:00
# if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
if ( sa - > sa . sa_family = = AF_INET6 )
2010-05-01 13:39:59 +00:00
setsockopt ( nfd , SOL_IPV6 , IPV6_V6ONLY , ( void * ) & option , sizeof option ) ;
2008-12-11 20:49:14 +00:00
# endif
2002-09-09 21:25:28 +00:00
if ( get_config_string
2003-07-18 13:45:06 +00:00
( lookup_config ( config_tree , " BindToInterface " ) , & iface ) ) {
2002-03-01 15:14:29 +00:00
# if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
2003-12-20 19:47:53 +00:00
struct ifreq ifr ;
2008-12-11 15:56:18 +00:00
memset ( & ifr , 0 , sizeof ifr ) ;
2003-07-18 13:45:06 +00:00
strncpy ( ifr . ifr_ifrn . ifrn_name , iface , IFNAMSIZ ) ;
2002-09-09 21:25:28 +00:00
2010-11-12 15:15:29 +00:00
if ( setsockopt ( nfd , SOL_SOCKET , SO_BINDTODEVICE , ( void * ) & ifr , sizeof ifr ) ) {
2003-07-29 22:59:01 +00:00
closesocket ( nfd ) ;
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Can't bind to interface %s: %s " , iface ,
2014-06-26 19:42:40 +00:00
sockstrerror ( sockerrno ) ) ;
2002-09-09 21:25:28 +00:00
return - 1 ;
}
2002-03-01 15:14:29 +00:00
# else
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_WARNING , " %s not supported on this platform " , " BindToInterface " ) ;
2002-02-18 16:25:19 +00:00
# endif
2002-09-09 21:25:28 +00:00
}
if ( bind ( nfd , & sa - > sa , SALEN ( sa - > sa ) ) ) {
2003-07-29 22:59:01 +00:00
closesocket ( nfd ) ;
2002-09-09 21:25:28 +00:00
addrstr = sockaddr2hostname ( sa ) ;
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Can't bind to %s/tcp: %s " , addrstr , sockstrerror ( sockerrno ) ) ;
2002-09-09 21:25:28 +00:00
free ( addrstr ) ;
return - 1 ;
}
if ( listen ( nfd , 3 ) ) {
2003-07-29 22:59:01 +00:00
closesocket ( nfd ) ;
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " System call `%s' failed: %s " , " listen " , sockstrerror ( sockerrno ) ) ;
2002-09-09 21:25:28 +00:00
return - 1 ;
}
return nfd ;
2002-02-18 16:25:19 +00:00
}
2007-05-18 10:00:00 +00:00
int setup_vpn_in_socket ( const sockaddr_t * sa ) {
2003-12-20 19:47:53 +00:00
int nfd ;
2002-09-09 21:25:28 +00:00
char * addrstr ;
int option ;
nfd = socket ( sa - > sa . sa_family , SOCK_DGRAM , IPPROTO_UDP ) ;
if ( nfd < 0 ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Creating UDP socket failed: %s " , sockstrerror ( sockerrno ) ) ;
2002-09-09 21:25:28 +00:00
return - 1 ;
}
2012-02-17 15:13:38 +00:00
# ifdef FD_CLOEXEC
fcntl ( nfd , F_SETFD , FD_CLOEXEC ) ;
# endif
2003-07-28 22:06:09 +00:00
# ifdef O_NONBLOCK
2003-12-20 19:47:53 +00:00
{
int flags = fcntl ( nfd , F_GETFL ) ;
if ( fcntl ( nfd , F_SETFL , flags | O_NONBLOCK ) < 0 ) {
closesocket ( nfd ) ;
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " System call `%s' failed: %s " , " fcntl " ,
2003-12-20 19:47:53 +00:00
strerror ( errno ) ) ;
return - 1 ;
}
2002-09-09 21:25:28 +00:00
}
2007-05-17 19:15:48 +00:00
# elif defined(WIN32)
{
unsigned long arg = 1 ;
if ( ioctlsocket ( nfd , FIONBIO , & arg ) ! = 0 ) {
closesocket ( nfd ) ;
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Call to `%s' failed: %s " , " ioctlsocket " , sockstrerror ( sockerrno ) ) ;
2007-05-17 19:15:48 +00:00
return - 1 ;
}
}
2003-07-28 22:06:09 +00:00
# endif
2002-09-09 21:25:28 +00:00
option = 1 ;
2010-11-12 15:15:29 +00:00
setsockopt ( nfd , SOL_SOCKET , SO_REUSEADDR , ( void * ) & option , sizeof option ) ;
2012-02-23 12:26:01 +00:00
setsockopt ( nfd , SOL_SOCKET , SO_BROADCAST , ( void * ) & option , sizeof option ) ;
2002-03-01 15:14:29 +00:00
2010-11-13 18:05:49 +00:00
if ( udp_rcvbuf & & setsockopt ( nfd , SOL_SOCKET , SO_RCVBUF , ( void * ) & udp_rcvbuf , sizeof ( udp_rcvbuf ) ) )
2014-06-26 19:42:40 +00:00
logger ( DEBUG_ALWAYS , LOG_WARNING , " Can't set UDP SO_RCVBUF to %i: %s " , udp_rcvbuf , sockstrerror ( sockerrno ) ) ;
2010-11-13 18:05:49 +00:00
if ( udp_sndbuf & & setsockopt ( nfd , SOL_SOCKET , SO_SNDBUF , ( void * ) & udp_sndbuf , sizeof ( udp_sndbuf ) ) )
2014-06-26 19:42:40 +00:00
logger ( DEBUG_ALWAYS , LOG_WARNING , " Can't set UDP SO_SNDBUF to %i: %s " , udp_sndbuf , sockstrerror ( sockerrno ) ) ;
2010-11-13 18:05:49 +00:00
2010-02-02 21:22:27 +00:00
# if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
2008-12-11 20:49:14 +00:00
if ( sa - > sa . sa_family = = AF_INET6 )
2010-05-01 13:39:59 +00:00
setsockopt ( nfd , IPPROTO_IPV6 , IPV6_V6ONLY , ( void * ) & option , sizeof option ) ;
2010-02-02 21:22:27 +00:00
# endif
# if defined(IP_DONTFRAG) && !defined(IP_DONTFRAGMENT)
# define IP_DONTFRAGMENT IP_DONTFRAG
2008-12-11 20:49:14 +00:00
# endif
2003-12-20 19:47:53 +00:00
# if defined(SOL_IP) && defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
2009-03-09 12:48:54 +00:00
if ( myself - > options & OPTION_PMTU_DISCOVERY ) {
option = IP_PMTUDISC_DO ;
2010-05-01 13:39:59 +00:00
setsockopt ( nfd , SOL_IP , IP_MTU_DISCOVER , ( void * ) & option , sizeof ( option ) ) ;
2003-12-20 21:09:33 +00:00
}
2009-10-24 20:32:35 +00:00
# elif defined(IPPROTO_IP) && defined(IP_DONTFRAGMENT)
if ( myself - > options & OPTION_PMTU_DISCOVERY ) {
option = 1 ;
2010-05-01 13:39:59 +00:00
setsockopt ( nfd , IPPROTO_IP , IP_DONTFRAGMENT , ( void * ) & option , sizeof ( option ) ) ;
2009-10-24 20:32:35 +00:00
}
2003-12-20 21:09:33 +00:00
# endif
# if defined(SOL_IPV6) && defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO)
2009-03-09 12:48:54 +00:00
if ( myself - > options & OPTION_PMTU_DISCOVERY ) {
option = IPV6_PMTUDISC_DO ;
2010-05-01 13:39:59 +00:00
setsockopt ( nfd , SOL_IPV6 , IPV6_MTU_DISCOVER , ( void * ) & option , sizeof ( option ) ) ;
2003-12-20 19:47:53 +00:00
}
2010-02-02 21:22:27 +00:00
# elif defined(IPPROTO_IPV6) && defined(IPV6_DONTFRAG)
if ( myself - > options & OPTION_PMTU_DISCOVERY ) {
option = 1 ;
2010-05-01 13:39:59 +00:00
setsockopt ( nfd , IPPROTO_IPV6 , IPV6_DONTFRAG , ( void * ) & option , sizeof ( option ) ) ;
2010-02-02 21:22:27 +00:00
}
2003-12-20 19:47:53 +00:00
# endif
2002-09-09 21:25:28 +00:00
2009-05-27 12:20:24 +00:00
if ( ! bind_to_interface ( nfd ) ) {
closesocket ( nfd ) ;
return - 1 ;
2002-03-01 11:18:34 +00:00
}
2002-02-18 16:25:19 +00:00
2002-09-09 21:25:28 +00:00
if ( bind ( nfd , & sa - > sa , SALEN ( sa - > sa ) ) ) {
2003-07-29 22:59:01 +00:00
closesocket ( nfd ) ;
2002-09-09 21:25:28 +00:00
addrstr = sockaddr2hostname ( sa ) ;
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Can't bind to %s/udp: %s " , addrstr , sockstrerror ( sockerrno ) ) ;
2002-09-09 21:25:28 +00:00
free ( addrstr ) ;
return - 1 ;
}
return nfd ;
2009-05-27 12:20:24 +00:00
} /* int setup_vpn_in_socket */
2002-02-18 16:25:19 +00:00
2012-11-29 11:28:23 +00:00
static void retry_outgoing_handler ( void * data ) {
2007-05-19 13:34:32 +00:00
setup_outgoing_connection ( data ) ;
2007-05-17 22:09:55 +00:00
}
2002-09-09 21:25:28 +00:00
2007-05-17 22:09:55 +00:00
void retry_outgoing ( outgoing_t * outgoing ) {
2002-09-09 21:25:28 +00:00
outgoing - > timeout + = 5 ;
if ( outgoing - > timeout > maxtimeout )
outgoing - > timeout = maxtimeout ;
2012-11-29 11:28:23 +00:00
timeout_add ( & outgoing - > ev , retry_outgoing_handler , outgoing , & ( struct timeval ) { outgoing - > timeout , rand ( ) % 100000 } ) ;
2002-09-09 21:25:28 +00:00
2012-11-29 11:28:23 +00:00
logger ( DEBUG_CONNECTIONS , LOG_NOTICE , " Trying to re-establish outgoing connection in %d seconds " , outgoing - > timeout ) ;
2002-02-18 16:25:19 +00:00
}
2007-05-18 10:00:00 +00:00
void finish_connecting ( connection_t * c ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_CONNECTIONS , LOG_INFO , " Connected to %s (%s) " , c - > name , c - > hostname ) ;
2002-02-18 16:25:19 +00:00
2013-03-08 13:11:15 +00:00
c - > last_ping_time = now . tv_sec ;
2007-05-17 23:04:02 +00:00
c - > status . connecting = false ;
2002-02-18 16:25:19 +00:00
2002-09-09 21:25:28 +00:00
send_id ( c ) ;
2002-02-18 16:25:19 +00:00
}
2012-04-19 13:18:31 +00:00
static void do_outgoing_pipe ( connection_t * c , char * command ) {
# ifndef HAVE_MINGW
int fd [ 2 ] ;
if ( socketpair ( AF_UNIX , SOCK_STREAM , 0 , fd ) ) {
2014-06-26 19:42:40 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Could not create socketpair: %s " , sockstrerror ( sockerrno ) ) ;
2012-04-19 13:18:31 +00:00
return ;
}
if ( fork ( ) ) {
c - > socket = fd [ 0 ] ;
close ( fd [ 1 ] ) ;
2012-06-26 11:24:20 +00:00
logger ( DEBUG_CONNECTIONS , LOG_DEBUG , " Using proxy %s " , command ) ;
2012-04-19 13:18:31 +00:00
return ;
}
close ( 0 ) ;
close ( 1 ) ;
close ( fd [ 0 ] ) ;
dup2 ( fd [ 1 ] , 0 ) ;
dup2 ( fd [ 1 ] , 1 ) ;
close ( fd [ 1 ] ) ;
// Other filedescriptors should be closed automatically by CLOEXEC
char * host = NULL ;
char * port = NULL ;
sockaddr2str ( & c - > address , & host , & port ) ;
setenv ( " REMOTEADDRESS " , host , true ) ;
setenv ( " REMOTEPORT " , port , true ) ;
setenv ( " NODE " , c - > name , true ) ;
setenv ( " NAME " , myself - > name , true ) ;
if ( netname )
setenv ( " NETNAME " , netname , true ) ;
int result = system ( command ) ;
if ( result < 0 )
2012-09-04 12:21:50 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Could not execute %s: %s " , command , strerror ( errno ) ) ;
2012-04-19 13:18:31 +00:00
else if ( result )
2012-06-26 11:24:20 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " %s exited with non-zero status %d " , command , result ) ;
2012-04-19 13:18:31 +00:00
exit ( result ) ;
# else
2012-06-26 11:24:20 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Proxy type exec not supported on this platform! " ) ;
2012-06-25 13:00:24 +00:00
return ;
2012-04-19 13:18:31 +00:00
# endif
}
2012-11-29 11:28:23 +00:00
static void handle_meta_write ( connection_t * c ) {
2013-03-01 16:15:26 +00:00
if ( c - > outbuf . len < = c - > outbuf . offset )
return ;
2012-10-07 19:02:40 +00:00
ssize_t outlen = send ( c - > socket , c - > outbuf . data + c - > outbuf . offset , c - > outbuf . len - c - > outbuf . offset , 0 ) ;
if ( outlen < = 0 ) {
2014-06-26 19:42:40 +00:00
if ( ! sockerrno | | sockerrno = = EPIPE ) {
2012-10-07 19:02:40 +00:00
logger ( DEBUG_CONNECTIONS , LOG_NOTICE , " Connection closed by %s (%s) " , c - > name , c - > hostname ) ;
} else if ( sockwouldblock ( sockerrno ) ) {
logger ( DEBUG_CONNECTIONS , LOG_DEBUG , " Sending %d bytes to %s (%s) would block " , c - > outbuf . len - c - > outbuf . offset , c - > name , c - > hostname ) ;
return ;
} else {
2014-06-26 19:42:40 +00:00
logger ( DEBUG_CONNECTIONS , LOG_ERR , " Could not send %d bytes of data to %s (%s): %s " , c - > outbuf . len - c - > outbuf . offset , c - > name , c - > hostname , sockstrerror ( sockerrno ) ) ;
2012-10-07 19:02:40 +00:00
}
terminate_connection ( c , c - > status . active ) ;
return ;
}
buffer_read ( & c - > outbuf , outlen ) ;
2012-11-29 11:28:23 +00:00
if ( ! c - > outbuf . len )
io_set ( & c - > io , IO_READ ) ;
2012-10-07 19:02:40 +00:00
}
2012-11-29 11:28:23 +00:00
static void handle_meta_io ( void * data , int flags ) {
2013-02-06 14:12:53 +00:00
connection_t * c = data ;
if ( c - > status . connecting ) {
Fix connection event error handling.
Commit 86a99c6b999671ed444711139db1937617e802a0 changed the way we
handle connection events to protect against spurious event loop
callbacks. Unfortunately, it turns out that calling connect() twice on
the same socket results in different behaviors depending on the platform
(even though it seems well defined in POSIX). On Windows this resulted
in the connection handling code being unable to react to connection
errors (such as connection refused), always hitting the timeout; on
Linux this resulted in spurious error messages about connect() returning
success.
In POSIX and on Linux, using connect() on a socket where the previous
attempt failed will attempt to connect again, resulting in unnecessary
network activity. Using getsockopt(SO_ERROR) before connect() solves
that, but introduces a race condition if a connection failure happens
between the two calls.
For this reason, this commit switches from connect() to a zero-sized
send() call, which is more consistent (though not completely, see the
truth table in the comments) and simpler to use for that purpose. Note
that Windows explictly support empty send() calls; POSIX says nothing
on the subject, but testing shows it works at least on Linux.
(Surprisingly enough, Windows seems more POSIX-compliant than Linux on
this one!)
2014-06-28 10:13:29 +00:00
/*
The event loop does not protect against spurious events . Verify that we are actually connected
by issuing an empty send ( ) call .
Note that the behavior of send ( ) on potentially unconnected sockets differ between platforms :
+ - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - +
| Event | POSIX | Linux | Windows |
+ - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - +
| Spurious | ENOTCONN | EWOULDBLOCK | ENOTCONN |
| Failed | ENOTCONN | ( cause ) | ENOTCONN |
| Successful | ( success ) | ( success ) | ( success ) |
+ - - - - - - - - - - - - + - - - - - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - +
*/
if ( send ( c - > socket , NULL , 0 , 0 ) ! = 0 ) {
if ( sockwouldblock ( sockerrno ) )
return ;
int socket_error ;
if ( ! socknotconn ( sockerrno ) )
socket_error = sockerrno ;
else {
int len = sizeof socket_error ;
getsockopt ( c - > socket , SOL_SOCKET , SO_ERROR , ( void * ) & socket_error , & len ) ;
}
if ( socket_error ) {
logger ( DEBUG_CONNECTIONS , LOG_DEBUG , " Error while connecting to %s (%s): %s " , c - > name , c - > hostname , sockstrerror ( socket_error ) ) ;
2014-06-27 18:33:31 +00:00
terminate_connection ( c , false ) ;
}
return ;
}
2013-02-06 14:12:53 +00:00
c - > status . connecting = false ;
Fix connection event error handling.
Commit 86a99c6b999671ed444711139db1937617e802a0 changed the way we
handle connection events to protect against spurious event loop
callbacks. Unfortunately, it turns out that calling connect() twice on
the same socket results in different behaviors depending on the platform
(even though it seems well defined in POSIX). On Windows this resulted
in the connection handling code being unable to react to connection
errors (such as connection refused), always hitting the timeout; on
Linux this resulted in spurious error messages about connect() returning
success.
In POSIX and on Linux, using connect() on a socket where the previous
attempt failed will attempt to connect again, resulting in unnecessary
network activity. Using getsockopt(SO_ERROR) before connect() solves
that, but introduces a race condition if a connection failure happens
between the two calls.
For this reason, this commit switches from connect() to a zero-sized
send() call, which is more consistent (though not completely, see the
truth table in the comments) and simpler to use for that purpose. Note
that Windows explictly support empty send() calls; POSIX says nothing
on the subject, but testing shows it works at least on Linux.
(Surprisingly enough, Windows seems more POSIX-compliant than Linux on
this one!)
2014-06-28 10:13:29 +00:00
finish_connecting ( c ) ;
2013-02-06 14:12:53 +00:00
}
2012-11-29 11:28:23 +00:00
if ( flags & IO_WRITE )
2013-02-06 14:12:53 +00:00
handle_meta_write ( c ) ;
2012-11-29 11:28:23 +00:00
else
2013-02-06 14:12:53 +00:00
handle_meta_connection_data ( c ) ;
2012-11-29 11:28:23 +00:00
}
2012-10-07 19:02:40 +00:00
bool do_outgoing_connection ( outgoing_t * outgoing ) {
2009-12-23 18:49:38 +00:00
char * address , * port , * space ;
2012-06-25 17:01:51 +00:00
struct addrinfo * proxyai = NULL ;
2006-08-08 13:44:19 +00:00
int result ;
2002-02-18 16:25:19 +00:00
2002-09-09 21:25:28 +00:00
begin :
2012-10-07 19:02:40 +00:00
if ( ! outgoing - > ai ) {
if ( ! outgoing - > cfg ) {
logger ( DEBUG_CONNECTIONS , LOG_ERR , " Could not set up a meta connection to %s " , outgoing - > name ) ;
retry_outgoing ( outgoing ) ;
2011-05-29 19:53:21 +00:00
return false ;
2002-09-09 21:25:28 +00:00
}
2012-10-07 19:02:40 +00:00
get_config_string ( outgoing - > cfg , & address ) ;
2002-09-09 21:25:28 +00:00
2009-12-23 18:49:38 +00:00
space = strchr ( address , ' ' ) ;
if ( space ) {
port = xstrdup ( space + 1 ) ;
* space = 0 ;
} else {
2012-10-07 19:02:40 +00:00
if ( ! get_config_string ( lookup_config ( outgoing - > config_tree , " Port " ) , & port ) )
2009-12-23 18:49:38 +00:00
port = xstrdup ( " 655 " ) ;
}
2002-09-09 21:25:28 +00:00
2012-10-07 19:02:40 +00:00
outgoing - > ai = str2addrinfo ( address , port , SOCK_STREAM ) ;
2002-09-09 21:25:28 +00:00
free ( address ) ;
free ( port ) ;
2012-10-07 19:02:40 +00:00
outgoing - > aip = outgoing - > ai ;
outgoing - > cfg = lookup_config_next ( outgoing - > config_tree , outgoing - > cfg ) ;
2002-09-09 21:25:28 +00:00
}
2002-02-18 16:25:19 +00:00
2012-10-07 19:02:40 +00:00
if ( ! outgoing - > aip ) {
if ( outgoing - > ai )
freeaddrinfo ( outgoing - > ai ) ;
outgoing - > ai = NULL ;
2002-09-09 21:25:28 +00:00
goto begin ;
}
2002-02-18 16:25:19 +00:00
2012-10-07 19:02:40 +00:00
connection_t * c = new_connection ( ) ;
c - > outgoing = outgoing ;
2002-02-18 16:25:19 +00:00
2012-10-07 19:02:40 +00:00
memcpy ( & c - > address , outgoing - > aip - > ai_addr , outgoing - > aip - > ai_addrlen ) ;
outgoing - > aip = outgoing - > aip - > ai_next ;
2002-02-18 16:25:19 +00:00
2002-09-09 21:25:28 +00:00
c - > hostname = sockaddr2hostname ( & c - > address ) ;
2002-02-18 16:25:19 +00:00
2012-10-07 19:02:40 +00:00
logger ( DEBUG_CONNECTIONS , LOG_INFO , " Trying to connect to %s (%s) " , outgoing - > name , c - > hostname ) ;
2002-02-18 16:25:19 +00:00
2012-04-18 21:19:40 +00:00
if ( ! proxytype ) {
c - > socket = socket ( c - > address . sa . sa_family , SOCK_STREAM , IPPROTO_TCP ) ;
2012-04-19 13:18:31 +00:00
configure_tcp ( c ) ;
2012-06-25 13:00:24 +00:00
} else if ( proxytype = = PROXY_EXEC ) {
2012-04-19 13:18:31 +00:00
do_outgoing_pipe ( c , proxyhost ) ;
2012-06-25 13:00:24 +00:00
} else {
2012-04-18 21:19:40 +00:00
proxyai = str2addrinfo ( proxyhost , proxyport , SOCK_STREAM ) ;
2012-10-07 19:02:40 +00:00
if ( ! proxyai ) {
free_connection ( c ) ;
2012-04-18 21:19:40 +00:00
goto begin ;
2012-10-07 19:02:40 +00:00
}
2012-06-26 11:24:20 +00:00
logger ( DEBUG_CONNECTIONS , LOG_INFO , " Using proxy at %s port %s " , proxyhost , proxyport ) ;
2012-04-18 21:19:40 +00:00
c - > socket = socket ( proxyai - > ai_family , SOCK_STREAM , IPPROTO_TCP ) ;
2013-02-07 13:22:28 +00:00
configure_tcp ( c ) ;
2012-04-18 21:19:40 +00:00
}
2012-02-17 15:13:38 +00:00
2002-09-09 21:25:28 +00:00
if ( c - > socket = = - 1 ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_CONNECTIONS , LOG_ERR , " Creating socket for %s failed: %s " , c - > hostname , sockstrerror ( sockerrno ) ) ;
2012-10-07 19:02:40 +00:00
free_connection ( c ) ;
2002-09-09 21:25:28 +00:00
goto begin ;
}
2002-02-18 16:25:19 +00:00
2012-04-18 21:19:40 +00:00
# ifdef FD_CLOEXEC
fcntl ( c - > socket , F_SETFD , FD_CLOEXEC ) ;
2008-12-11 20:49:14 +00:00
# endif
2012-04-19 13:18:31 +00:00
if ( proxytype ! = PROXY_EXEC ) {
2008-12-22 19:40:40 +00:00
# if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
2012-04-19 13:18:31 +00:00
int option = 1 ;
if ( c - > address . sa . sa_family = = AF_INET6 )
setsockopt ( c - > socket , SOL_IPV6 , IPV6_V6ONLY , ( void * ) & option , sizeof option ) ;
2008-12-22 19:40:40 +00:00
# endif
2002-02-18 16:25:19 +00:00
2012-04-19 13:18:31 +00:00
bind_to_interface ( c - > socket ) ;
2013-08-18 21:55:40 +00:00
bind_to_address ( c ) ;
2012-04-19 13:18:31 +00:00
}
2002-02-18 16:25:19 +00:00
2002-09-09 21:25:28 +00:00
/* Connect */
2002-02-18 16:25:19 +00:00
2012-04-18 21:19:40 +00:00
if ( ! proxytype ) {
result = connect ( c - > socket , & c - > address . sa , SALEN ( c - > address . sa ) ) ;
2012-04-19 13:18:31 +00:00
} else if ( proxytype = = PROXY_EXEC ) {
result = 0 ;
2012-04-18 21:19:40 +00:00
} else {
result = connect ( c - > socket , proxyai - > ai_addr , proxyai - > ai_addrlen ) ;
freeaddrinfo ( proxyai ) ;
}
2002-02-18 16:25:19 +00:00
2012-10-07 19:02:40 +00:00
if ( result = = - 1 & & ! sockinprogress ( sockerrno ) ) {
logger ( DEBUG_CONNECTIONS , LOG_ERR , " Could not connect to %s (%s): %s " , outgoing - > name , c - > hostname , sockstrerror ( sockerrno ) ) ;
free_connection ( c ) ;
2002-09-09 21:25:28 +00:00
goto begin ;
}
2002-02-18 16:25:19 +00:00
2012-10-07 19:02:40 +00:00
/* Now that there is a working socket, fill in the rest and register this connection. */
2002-02-18 16:25:19 +00:00
2012-10-07 19:02:40 +00:00
c - > status . connecting = true ;
c - > name = xstrdup ( outgoing - > name ) ;
c - > outcipher = myself - > connection - > outcipher ;
c - > outdigest = myself - > connection - > outdigest ;
c - > outmaclength = myself - > connection - > outmaclength ;
c - > outcompression = myself - > connection - > outcompression ;
2013-03-08 13:11:15 +00:00
c - > last_ping_time = now . tv_sec ;
2011-05-14 17:20:56 +00:00
2012-10-07 19:02:40 +00:00
connection_add ( c ) ;
2012-10-07 09:45:54 +00:00
2013-02-06 14:12:53 +00:00
io_add ( & c - > io , handle_meta_io , c , c - > socket , IO_READ | IO_WRITE ) ;
2011-05-14 17:20:56 +00:00
2012-10-07 19:02:40 +00:00
return true ;
2007-05-19 22:23:02 +00:00
}
2014-01-30 16:10:30 +00:00
// Find edges pointing to this node, and use them to build a list of unique, known addresses.
static struct addrinfo * get_known_addresses ( node_t * n ) {
struct addrinfo * ai = NULL ;
for splay_each ( edge_t , e , n - > edge_tree ) {
if ( ! e - > reverse )
continue ;
bool found = false ;
for ( struct addrinfo * aip = ai ; aip ; aip = aip - > ai_next ) {
if ( ! sockaddrcmp ( & e - > reverse - > address , ( sockaddr_t * ) aip - > ai_addr ) ) {
found = true ;
break ;
}
}
if ( found )
continue ;
struct addrinfo * nai = xzalloc ( sizeof * nai ) ;
if ( ai )
ai - > ai_next = nai ;
ai = nai ;
ai - > ai_family = e - > reverse - > address . sa . sa_family ;
ai - > ai_socktype = SOCK_STREAM ;
ai - > ai_protocol = IPPROTO_TCP ;
ai - > ai_addrlen = SALEN ( e - > reverse - > address . sa ) ;
ai - > ai_addr = xmalloc ( ai - > ai_addrlen ) ;
memcpy ( ai - > ai_addr , & e - > reverse - > address , ai - > ai_addrlen ) ;
}
return ai ;
}
2007-05-18 10:00:00 +00:00
void setup_outgoing_connection ( outgoing_t * outgoing ) {
2012-11-29 11:28:23 +00:00
timeout_del ( & outgoing - > ev ) ;
2009-10-20 20:14:47 +00:00
2012-10-07 19:02:40 +00:00
node_t * n = lookup_node ( outgoing - > name ) ;
2002-09-09 21:25:28 +00:00
2012-10-07 19:02:40 +00:00
if ( n & & n - > connection ) {
logger ( DEBUG_CONNECTIONS , LOG_INFO , " Already connected to %s " , outgoing - > name ) ;
2002-09-09 21:25:28 +00:00
2012-10-07 19:02:40 +00:00
n - > connection - > outgoing = outgoing ;
return ;
}
2002-09-09 21:25:28 +00:00
2012-10-07 19:02:40 +00:00
init_configuration ( & outgoing - > config_tree ) ;
read_host_config ( outgoing - > config_tree , outgoing - > name ) ;
outgoing - > cfg = lookup_config ( outgoing - > config_tree , " Address " ) ;
2002-09-09 21:25:28 +00:00
if ( ! outgoing - > cfg ) {
2014-01-30 16:10:30 +00:00
if ( n )
outgoing - > aip = outgoing - > ai = get_known_addresses ( n ) ;
if ( ! outgoing - > ai ) {
logger ( DEBUG_ALWAYS , LOG_ERR , " No address known for %s " , outgoing - > name ) ;
return ;
}
2002-09-09 21:25:28 +00:00
}
2012-10-07 19:02:40 +00:00
do_outgoing_connection ( outgoing ) ;
2002-02-18 16:25:19 +00:00
}
/*
accept a new tcp connect and create a
new connection
*/
2012-11-29 11:28:23 +00:00
void handle_new_meta_connection ( void * data , int flags ) {
listen_socket_t * l = data ;
2002-09-09 21:25:28 +00:00
connection_t * c ;
sockaddr_t sa ;
2006-03-19 13:06:21 +00:00
int fd ;
2008-12-11 15:56:18 +00:00
socklen_t len = sizeof sa ;
2002-09-09 21:25:28 +00:00
2012-11-29 11:28:23 +00:00
fd = accept ( l - > tcp . fd , & sa . sa , & len ) ;
2002-09-09 21:25:28 +00:00
if ( fd < 0 ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR , " Accepting a new connection failed: %s " , sockstrerror ( sockerrno ) ) ;
2007-05-17 20:20:10 +00:00
return ;
2002-09-09 21:25:28 +00:00
}
sockaddrunmap ( & sa ) ;
2013-07-11 21:38:38 +00:00
// Check if we get many connections from the same host
static sockaddr_t prev_sa ;
static int tarpit = - 1 ;
if ( tarpit > = 0 ) {
closesocket ( tarpit ) ;
tarpit = - 1 ;
}
2013-09-01 22:11:04 +00:00
if ( ! sockaddrcmp_noport ( & sa , & prev_sa ) ) {
static int samehost_burst ;
static int samehost_burst_time ;
if ( now . tv_sec - samehost_burst_time > samehost_burst )
samehost_burst = 0 ;
else
samehost_burst - = now . tv_sec - samehost_burst_time ;
samehost_burst_time = now . tv_sec ;
samehost_burst + + ;
if ( samehost_burst > max_connection_burst ) {
tarpit = fd ;
return ;
}
2013-07-11 21:38:38 +00:00
}
memcpy ( & prev_sa , & sa , sizeof sa ) ;
// Check if we get many connections from different hosts
static int connection_burst ;
static int connection_burst_time ;
if ( now . tv_sec - connection_burst_time > connection_burst )
connection_burst = 0 ;
else
connection_burst - = now . tv_sec - connection_burst_time ;
connection_burst_time = now . tv_sec ;
connection_burst + + ;
if ( connection_burst > = max_connection_burst ) {
connection_burst = max_connection_burst ;
tarpit = fd ;
return ;
}
// Accept the new connection
2002-09-09 21:25:28 +00:00
c = new_connection ( ) ;
2007-05-17 19:15:48 +00:00
c - > name = xstrdup ( " <unknown> " ) ;
2002-09-09 21:25:28 +00:00
c - > outcipher = myself - > connection - > outcipher ;
c - > outdigest = myself - > connection - > outdigest ;
c - > outmaclength = myself - > connection - > outmaclength ;
c - > outcompression = myself - > connection - > outcompression ;
c - > address = sa ;
c - > hostname = sockaddr2hostname ( & sa ) ;
c - > socket = fd ;
2013-03-08 13:11:15 +00:00
c - > last_ping_time = now . tv_sec ;
2002-09-09 21:25:28 +00:00
2012-02-26 17:37:36 +00:00
logger ( DEBUG_CONNECTIONS , LOG_NOTICE , " Connection from %s " , c - > hostname ) ;
2002-09-09 21:25:28 +00:00
2012-11-29 11:28:23 +00:00
io_add ( & c - > io , handle_meta_io , c , c - > socket , IO_READ ) ;
2012-10-10 15:17:49 +00:00
2006-01-13 11:21:59 +00:00
configure_tcp ( c ) ;
2004-11-10 21:56:31 +00:00
2002-09-09 21:25:28 +00:00
connection_add ( c ) ;
c - > allow_request = ID ;
send_id ( c ) ;
2002-02-18 16:25:19 +00:00
}
2013-01-17 17:12:55 +00:00
# ifndef HAVE_MINGW
/*
accept a new UNIX socket connection
*/
void handle_new_unix_connection ( void * data , int flags ) {
io_t * io = data ;
connection_t * c ;
sockaddr_t sa ;
int fd ;
socklen_t len = sizeof sa ;
fd = accept ( io - > fd , & sa . sa , & len ) ;
if ( fd < 0 ) {
logger ( DEBUG_ALWAYS , LOG_ERR , " Accepting a new connection failed: %s " , sockstrerror ( sockerrno ) ) ;
return ;
}
sockaddrunmap ( & sa ) ;
c = new_connection ( ) ;
c - > name = xstrdup ( " <control> " ) ;
c - > address = sa ;
c - > hostname = xstrdup ( " localhost port unix " ) ;
c - > socket = fd ;
2013-03-08 13:11:15 +00:00
c - > last_ping_time = now . tv_sec ;
2013-01-17 17:12:55 +00:00
logger ( DEBUG_CONNECTIONS , LOG_NOTICE , " Connection from %s " , c - > hostname ) ;
io_add ( & c - > io , handle_meta_io , c , c - > socket , IO_READ ) ;
connection_add ( c ) ;
c - > allow_request = ID ;
send_id ( c ) ;
}
# endif
2011-05-28 01:56:06 +00:00
static void free_outgoing ( outgoing_t * outgoing ) {
2012-11-29 11:28:23 +00:00
timeout_del ( & outgoing - > ev ) ;
2012-09-28 15:05:01 +00:00
2009-01-20 12:12:41 +00:00
if ( outgoing - > ai )
freeaddrinfo ( outgoing - > ai ) ;
2012-10-09 14:27:28 +00:00
if ( outgoing - > config_tree )
exit_configuration ( & outgoing - > config_tree ) ;
2009-01-20 12:12:41 +00:00
if ( outgoing - > name )
free ( outgoing - > name ) ;
free ( outgoing ) ;
}
2009-09-24 22:14:03 +00:00
void try_outgoing_connections ( void ) {
2012-09-28 15:05:01 +00:00
/* If there is no outgoing list yet, create one. Otherwise, mark all outgoings as deleted. */
if ( ! outgoing_list ) {
outgoing_list = list_alloc ( ( list_action_t ) free_outgoing ) ;
} else {
2012-10-07 22:35:38 +00:00
for list_each ( outgoing_t , outgoing , outgoing_list )
2012-09-28 15:05:01 +00:00
outgoing - > timeout = - 1 ;
}
/* Make sure there is one outgoing_t in the list for each ConnectTo. */
2012-10-07 22:35:38 +00:00
for ( config_t * cfg = lookup_config ( config_tree , " ConnectTo " ) ; cfg ; cfg = lookup_config_next ( config_tree , cfg ) ) {
char * name ;
2002-09-09 21:25:28 +00:00
get_config_string ( cfg , & name ) ;
2003-07-22 20:55:21 +00:00
if ( ! check_id ( name ) ) {
2012-02-26 17:37:36 +00:00
logger ( DEBUG_ALWAYS , LOG_ERR ,
2009-09-24 22:54:07 +00:00
" Invalid name for outgoing connection in %s line %d " ,
2002-09-09 21:25:28 +00:00
cfg - > file , cfg - > line ) ;
free ( name ) ;
continue ;
}
2012-09-28 15:05:01 +00:00
bool found = false ;
2012-10-07 22:35:38 +00:00
for list_each ( outgoing_t , outgoing , outgoing_list ) {
2012-09-28 15:05:01 +00:00
if ( ! strcmp ( outgoing - > name , name ) ) {
found = true ;
outgoing - > timeout = 0 ;
break ;
}
}
if ( ! found ) {
2013-05-01 15:31:33 +00:00
outgoing_t * outgoing = xzalloc ( sizeof * outgoing ) ;
2012-09-28 15:05:01 +00:00
outgoing - > name = name ;
list_insert_tail ( outgoing_list , outgoing ) ;
setup_outgoing_connection ( outgoing ) ;
}
}
/* Terminate any connections whose outgoing_t is to be deleted. */
2012-10-07 22:35:38 +00:00
for list_each ( connection_t , c , connection_list ) {
2012-09-28 15:05:01 +00:00
if ( c - > outgoing & & c - > outgoing - > timeout = = - 1 ) {
c - > outgoing = NULL ;
2012-10-07 19:02:40 +00:00
logger ( DEBUG_CONNECTIONS , LOG_INFO , " No more outgoing connection to %s " , c - > name ) ;
2012-09-28 15:05:01 +00:00
terminate_connection ( c , c - > status . active ) ;
}
}
/* Delete outgoing_ts for which there is no ConnectTo. */
2012-10-07 22:35:38 +00:00
for list_each ( outgoing_t , outgoing , outgoing_list )
2012-09-28 15:05:01 +00:00
if ( outgoing - > timeout = = - 1 )
2012-10-07 19:59:53 +00:00
list_delete_node ( outgoing_list , node ) ;
2002-02-18 16:25:19 +00:00
}