I propose a correction to this error. The problem lies in the fact that upstream's source code makes a compile time decision on address family, and also makes an implicit assumption on dual stacked sockets which is not universally true.
The attached difference file is my suggestion which I also submit in the ITA-closing upload to mentors.debian.net. Observe that the three combinations GNU/Linux, net.ipv6.bindv6only = 1 GNU/kFreeBSD, net.inet6.ip6.v6only = 0 GNU/Linux, net.ipv6.bindv6only = 0 all put different demands on socket option handling. In fact the third of these forced me to make a second upload to mentors.debian.net. In crafting this patch, it has been written with my experience from OpenBSD in mind, but is has not yet been checked there. The technique is intended to catch Linux and BSD in all their variations. Best regards, Mats Erik Andersson, DM
Description: Rewrite socket management. The original source uses a compile time determined method of using IPv6. This can cause failures in prepackaged software on single stacked machines. The chosen solution is to open one listening socket for each address family, if needed. To prevent this new behaviour, two new options '-4' and '-6' are implemented for single socket listening. . Care is needed to arrive at single family sockets independently of 'net.ipv6.bindv6only'. . A further new option '-e' allows the strong elimination of a live file descriptor for STDERR. Author: Mats Erik Andersson <deb...@gisladisker.se> Forwarded: no Bug-Debian: http://bugs.debian.org/354778 http://bugs.debian.org/603110 Last-Update: 2011-03-25 --- micro-inetd-20050629.debian/micro_inetd.1 2011-03-24 11:59:06.000000000 +0100 +++ micro-inetd-20050629/micro_inetd.1 2011-03-24 18:35:05.000000000 +0100 @@ -2,7 +2,7 @@ .SH NAME micro-inetd - simple network service spawner .SH SYNOPSIS -micro-inetd port program [args ...] +micro-inetd [\-4|\-6|\-e] port program [args ...] .SH DESCRIPTION Like inetd, this program listens on the net for requests and spawns a server to handle them. @@ -25,6 +25,16 @@ micro-inetd only implements nowait. * Full inetd lets you specify a user-id to run the server as; micro-inetd doesn't try to switch user-ids. +.SH OPTIONS +.TP 5 +.B \-4, \-6 +Use a single address family for listening socket. The default, on a +dual stacked machine, is to open one socket for each address family +if at all possible. +.TP 5 +.B \-e +Do not pass \fISTDERR\fR to the client application. Instead the client +will see a file descriptor which is terminated in \fI/dev/null\fR. .SH "SEE ALSO" inetd(8), inetd.conf(5) .SH AUTHOR --- micro-inetd-20050629.debian/micro_inetd.c 2005-06-29 19:27:09.000000000 +0200 +++ micro-inetd-20050629/micro_inetd.c 2011-03-25 10:04:00.000000000 +0100 @@ -38,6 +38,8 @@ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> +#include <netdb.h> +#include <paths.h> #if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED) #define USE_IPV6 @@ -45,11 +47,12 @@ static void usage(); -static int initialize_listen_socket( int pf, int af, unsigned short port ); +static int initialize_listen_socket( int pf, int af, unsigned short port, int fd[] ); static void child_handler( int sig ); static char* argv0; +static int dostderr = 1; int @@ -57,37 +60,95 @@ main( int argc, char* argv[] ) { unsigned short port; char** child_argv; - int listen_fd, conn_fd; - struct sockaddr_in sin; - int sz; + int listen_fd[2], conn_fd; + int pf, af; + struct sockaddr_storage sin; + socklen_t sz; fd_set lfdset; - int maxfd; + int maxfd, numfd, j, pos = 1; +#ifdef USE_IPV6 + pf = PF_UNSPEC; + af = AF_UNSPEC; +#else /* !USE_IPV6 */ + pf = PF_INET; + af = AF_INET; +#endif argv0 = argv[0]; /* Get arguments. */ if ( argc < 3 ) usage(); - port = (unsigned short) atoi( argv[1] ); - child_argv = argv + 2; + + /* Simple parser of command line options. */ + while ( pos < argc && argv[pos][0] == '-' ) + { + switch ( argv[pos][1] ) + { + case '4': + pf = PF_INET, af = AF_INET; + break; + case '6': + pf = PF_INET6, af = AF_INET6; + break; + case 'e': + dostderr = 0; + break; + default: + usage(); + break; + } + /* Parsed option, proceed. */ + ++pos; + } + + /* Check arguments. */ + if ( argc < pos + 2 ) + usage(); + + port = (unsigned short) atoi( argv[pos] ); + child_argv = argv + pos + 1; /* Initialize listen socket. If we have v6 use that, since its sockets ** will accept v4 connections too. Otherwise just use v4. */ -#ifdef USE_IPV6 - listen_fd = initialize_listen_socket( PF_INET6, AF_INET6, port ); -#else /* USE_IPV6 */ - listen_fd = initialize_listen_socket( PF_INET, AF_INET, port ); -#endif /* USE_IPV6 */ + numfd = initialize_listen_socket( pf, af, port, listen_fd ); + if ( numfd <= 0 ) + { + fprintf( stderr, "No listening sockets. Aborting.\n"); + exit( 1 ); + } + + maxfd = listen_fd[0]; + if ( (numfd > 1) && (maxfd < listen_fd[1]) ) + maxfd = listen_fd[1]; /* Set up a signal handler for child reaping. */ (void) signal( SIGCHLD, child_handler ); for (;;) { + int n; + + FD_ZERO(&lfdset); + FD_SET(listen_fd[0], &lfdset); + if (numfd == 2) + FD_SET(listen_fd[1], &lfdset); + n = select( maxfd + 1, &lfdset, NULL, NULL, NULL ); + if (n <= 0) + continue; + + for ( j = 0; j < numfd; ++j ) + { + /* Indentation level is broken from + * this point and onwards. + */ + if ( !FD_ISSET(listen_fd[j], &lfdset) ) + continue; + /* Accept a new connection. */ sz = sizeof(sin); - conn_fd = accept( listen_fd, (struct sockaddr*) &sin, &sz ); + conn_fd = accept( listen_fd[j], (struct sockaddr*) &sin, &sz ); if ( conn_fd < 0 ) { if ( errno == EINTR ) /* because of SIGCHLD (or ptrace) */ @@ -103,11 +164,18 @@ main( int argc, char* argv[] ) (void) close( 0 ); (void) close( 1 ); (void) close( 2 ); - (void) close( listen_fd ); + (void) close( listen_fd[j] ); /* Dup the connection onto the standard descriptors. */ (void) dup2( conn_fd, 0 ); (void) dup2( conn_fd, 1 ); - (void) dup2( conn_fd, 2 ); + if ( dostderr ) + (void) dup2( conn_fd, STDERR_FILENO ); + else + { + int nullfd = open(_PATH_DEVNULL, O_WRONLY); + (void) dup2( nullfd, STDERR_FILENO ); + (void) close( nullfd ); + } (void) close( conn_fd ); /* Run the program. */ (void) execv( child_argv[0], child_argv ); @@ -117,7 +185,8 @@ main( int argc, char* argv[] ) } /* Parent process. */ (void) close( conn_fd ); - } + } /* for each possible listening socket */ + } /* Eternal server loop, waiting for clients. */ } @@ -125,7 +194,8 @@ main( int argc, char* argv[] ) static void usage() { - (void) fprintf( stderr, "usage: %s port program [args...]\n", argv0 ); + (void) fprintf( stderr, "usage: %s [-4|-6|-e] " + "port program [args...]\n", argv0 ); exit( 1 ); } @@ -163,58 +233,92 @@ child_handler( int sig ) static int -initialize_listen_socket( int pf, int af, unsigned short port ) +initialize_listen_socket( int pf, int af, unsigned short port, int fd[] ) { int listen_fd; - int on; -#ifdef USE_IPV6 - struct sockaddr_in6 sa; -#else /* USE_IPV6 */ - struct sockaddr_in sa; -#endif /* USE_IPV6 */ + int on, err, numfd = 0; + struct addrinfo hints, *res = NULL, *ai = NULL; + char portstr[10]; + + portstr[0] = '\0'; + snprintf( portstr, sizeof(portstr) - 1, "%u", port); + portstr[sizeof(portstr) - 1] = '\0'; + + memset( &hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif - /* Create socket. */ - listen_fd = socket( pf, SOCK_STREAM, 0 ); - if ( listen_fd < 0 ) + err = getaddrinfo( NULL, portstr, &hints, &res); + if ( err < 0 ) { - perror( "socket" ); + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); exit( 1 ); } + for ( ai = res; ai; ai = ai->ai_next ) + { + /* Indentation broken from this point onwards. */ + + /* Create socket. */ + listen_fd = socket( ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if ( listen_fd < 0) + continue; + /* Allow reuse of local addresses. */ on = 1; if ( setsockopt( listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &on, sizeof(on) ) < 0 ) { - perror( "setsockopt SO_REUSEADDR" ); - exit( 1 ); + close( listen_fd ); + listen_fd = -1; + continue; } - /* Set up the sockaddr. */ - (void) memset( (char*) &sa, 0, sizeof(sa) ); -#ifdef USE_IPV6 - sa.sin6_family = af; - sa.sin6_addr = in6addr_any; - sa.sin6_port = htons( port ); -#else /* USE_IPV6 */ - sa.sin_family = af; - sa.sin_addr.s_addr = htonl( INADDR_ANY ); - sa.sin_port = htons( port ); -#endif /* USE_IPV6 */ + /* Eliminate a dual socket. 'net.ipv6.bindv6only' gives problems. */ + if ( ai->ai_family == AF_INET6 ) + { + on = 1; + /* Ignore any failure. */ + (void) setsockopt( listen_fd, IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof(on) ); + } /* Bind it to the socket. */ - if ( bind( listen_fd, (struct sockaddr*) &sa, sizeof(sa) ) < 0 ) + if ( bind( listen_fd, ai->ai_addr, ai->ai_addrlen ) < 0 ) { - perror( "bind" ); - exit( 1 ); + close( listen_fd ); + listen_fd = -1; + continue; } /* Start a listen going. */ if ( listen( listen_fd, 1024 ) < 0 ) { - perror( "listen" ); - exit( 1 ); + close( listen_fd ); + listen_fd = -1; + continue; } - return listen_fd; + /* We have a listening socket. Accept it. */ + fd[numfd++] = listen_fd; + + /* At most two sockets are expected. */ + if ( numfd == 2 ) + break; + } /* for (ai = ai->ai_next) */ + + if ( res ) + freeaddrinfo( res ); + + if ( numfd == 0 ) + { + fprintf( stderr, "failed to get listening socket\n"); + exit( 1 ); + } + + return numfd; }
signature.asc
Description: Digital signature