make DNS lookup asynchronous

true asynchronicity is actually fairly useless, as it's unlikely that
both Stores in a Channel use IMAP, and both host resolutions take
particularly long - the main objective is imposing the Timeout setting.
however, we can't just use setjmp()+alarm(), as longjmp()ing out of
getaddrinfo() is undefined, as it may for example free() just at the
wrong time. so we go for the real thing.

this implementation just fork()s out a process which uses getaddrinfo()
(or gethostbyname()) per lookup. this isn't particularly scalable, but
as we don't expect a lot of lookups, it seems adequate.
This commit is contained in:
Oswald Buddenhagen 2022-07-04 12:50:17 +02:00
parent ced20ad0d9
commit ec50c55c36
2 changed files with 202 additions and 99 deletions

View File

@ -28,6 +28,7 @@
#endif #endif
enum { enum {
SCK_RESOLVING,
SCK_CONNECTING, SCK_CONNECTING,
#ifdef HAVE_LIBSSL #ifdef HAVE_LIBSSL
SCK_STARTTLS, SCK_STARTTLS,
@ -415,6 +416,7 @@ static void socket_fd_cb( int, void * );
static void socket_fake_cb( void * ); static void socket_fake_cb( void * );
static void socket_timeout_cb( void * ); static void socket_timeout_cb( void * );
static void socket_resolve( conn_t * );
static void socket_connect_one( conn_t * ); static void socket_connect_one( conn_t * );
static void socket_connect_next( conn_t * ); static void socket_connect_next( conn_t * );
static void socket_connect_failed( conn_t * ); static void socket_connect_failed( conn_t * );
@ -422,15 +424,21 @@ static void socket_connected( conn_t * );
static void socket_connect_bail( conn_t * ); static void socket_connect_bail( conn_t * );
static void static void
socket_open_internal( conn_t *sock, int fd ) socket_register_internal( conn_t *sock, int fd )
{ {
sock->fd = fd; sock->fd = fd;
fcntl( fd, F_SETFL, O_NONBLOCK );
init_notifier( &sock->notify, fd, socket_fd_cb, sock ); init_notifier( &sock->notify, fd, socket_fd_cb, sock );
init_wakeup( &sock->fd_fake, socket_fake_cb, sock ); init_wakeup( &sock->fd_fake, socket_fake_cb, sock );
init_wakeup( &sock->fd_timeout, socket_timeout_cb, sock ); init_wakeup( &sock->fd_timeout, socket_timeout_cb, sock );
} }
static void
socket_open_internal( conn_t *sock, int fd )
{
fcntl( fd, F_SETFL, O_NONBLOCK );
socket_register_internal( sock, fd );
}
static void static void
socket_close_internal( conn_t *sock ) socket_close_internal( conn_t *sock )
{ {
@ -441,32 +449,6 @@ socket_close_internal( conn_t *sock )
sock->fd = -1; sock->fd = -1;
} }
#ifndef HAVE_IPV6
struct addr_info {
struct addr_info *ai_next;
struct sockaddr_in ai_addr[1];
};
#define freeaddrinfo(ai) free( ai )
static struct addr_info *
init_addrinfo( struct hostent *he )
{
uint naddr = 0;
for (char **addr = he->h_addr_list; *addr; addr++)
naddr++;
struct addr_info *caddr = nfzalloc( naddr * sizeof(struct addrinfo) );
struct addr_info *ret, **caddrp = &ret;
for (char **addr = he->h_addr_list; *addr; addr++, caddr++) {
caddr->ai_addr->sin_family = AF_INET;
memcpy( &caddr->ai_addr->sin_addr.s_addr, *addr, sizeof(struct in_addr) );
*caddrp = caddr;
caddrp = &caddr->ai_next;
}
return ret;
}
#endif
void void
socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) ) socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) )
{ {
@ -501,77 +483,202 @@ socket_connect( conn_t *sock, void (*cb)( int ok, void *aux ) )
info( "\vok\n" ); info( "\vok\n" );
socket_connected( sock ); socket_connected( sock );
} else { } else {
#ifdef HAVE_IPV6 socket_resolve( sock );
int gaierr;
struct addrinfo hints;
memset( &hints, 0, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
infon( "Resolving %s... ", conf->host );
if ((gaierr = getaddrinfo( conf->host, NULL, &hints, &sock->addrs ))) {
error( "Error: Cannot resolve server '%s': %s\n", conf->host, gai_strerror( gaierr ) );
socket_connect_bail( sock );
return;
}
info( "\vok\n" );
#else
struct hostent *he;
infon( "Resolving %s... ", conf->host );
he = gethostbyname( conf->host );
if (!he) {
error( "Error: Cannot resolve server '%s': %s\n", conf->host, hstrerror( h_errno ) );
socket_connect_bail( sock );
return;
}
info( "\vok\n" );
sock->addrs = init_addrinfo( he );
#endif
sock->curr_addr = sock->addrs;
socket_connect_one( sock );
} }
} }
static void
pipe_write( int fd, void *buf, int len )
{
do {
int wrote = write( fd, buf, len );
if (wrote < 0) {
perror( "write" );
_exit( 1 );
}
buf = ((char *)buf) + wrote;
len -= wrote;
} while (len);
}
static void
socket_resolve( conn_t *sock )
{
info( "Resolving %s...\n", sock->conf->host );
int pfd[2];
if (pipe( pfd )) {
perror( "pipe" );
exit( 1 );
}
switch (fork()) {
case -1:
perror( "fork" );
exit( 1 );
case 0:
break;
default:
close( pfd[1] );
socket_register_internal( sock, pfd[0] );
sock->state = SCK_RESOLVING;
conf_notifier( &sock->notify, 0, POLLIN );
socket_expect_activity( sock, 1 );
return;
}
#ifdef HAVE_IPV6
struct addrinfo *res, hints = { 0 };
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
int gaierr = getaddrinfo( sock->conf->host, NULL, &hints, &res );
pipe_write( pfd[1], &gaierr, sizeof(gaierr) );
if (gaierr)
_exit( 1 );
static_assert( sizeof(((struct addrinfo){ 0 }).ai_family) == sizeof(int), "unexpected size of ai_family" );
static_assert( sizeof(struct in_addr) % sizeof(int) == 0, "unexpected size of struct in_addr" );
static_assert( sizeof(struct in6_addr) % sizeof(int) == 0, "unexpected size of struct in6_addr" );
int nbytes = 0;
for (struct addrinfo *cres = res; cres; cres = cres->ai_next) {
if (cres->ai_family == AF_INET) {
nbytes += sizeof(int) + sizeof(struct in_addr);
} else {
assert( cres->ai_family == AF_INET6 );
nbytes += sizeof(int) + sizeof(struct in6_addr);
}
}
pipe_write( pfd[1], &nbytes, sizeof(nbytes) );
for (struct addrinfo *cres = res; cres; cres = cres->ai_next) {
pipe_write( pfd[1], &cres->ai_family, sizeof(int) );
if (cres->ai_family == AF_INET)
pipe_write( pfd[1], &((struct sockaddr_in *)cres->ai_addr)->sin_addr, sizeof(struct in_addr) );
else
pipe_write( pfd[1], &((struct sockaddr_in6 *)cres->ai_addr)->sin6_addr, sizeof(struct in6_addr) );
}
#else
struct hostent *he = gethostbyname( sock->conf->host );
int herrno = he ? 0 : h_errno;
pipe_write( pfd[1], &herrno, sizeof(herrno) );
if (!he)
_exit( 1 );
static_assert( sizeof(struct in_addr) % sizeof(int) == 0, "unexpected size of struct in_addr" );
int nbytes = 0;
for (char **addr = he->h_addr_list; *addr; addr++)
nbytes += sizeof(struct in_addr);
pipe_write( pfd[1], &nbytes, sizeof(nbytes) );
for (char **addr = he->h_addr_list; *addr; addr++)
pipe_write( pfd[1], *addr, sizeof(struct in_addr) );
#endif
_exit( 0 );
}
static void
pipe_read( int fd, void *buf, int len )
{
do {
int didrd = read( fd, buf, len );
if (didrd < 0) {
sys_error( "read" );
exit( 1 );
}
if (!didrd) {
error( "read: unexpected EOF\n" );
exit( 1 );
}
buf = ((char *)buf) + didrd;
len -= didrd;
} while (len);
}
static void
socket_resolve_finalize( conn_t *sock )
{
int errcode;
pipe_read( sock->fd, &errcode, sizeof(errcode) );
if (errcode) {
#ifdef HAVE_IPV6
const char *err = gai_strerror( errcode );
#else
const char *err = hstrerror( errcode );
#endif
error( "Error: Cannot resolve server '%s': %s\n", sock->conf->host, err );
socket_close_internal( sock );
socket_connect_bail( sock );
return;
}
int nbytes;
pipe_read( sock->fd, &nbytes, sizeof(nbytes) );
char *addrs = nfmalloc( nbytes );
pipe_read( sock->fd, addrs, nbytes );
sock->curr_addr = sock->addrs = addrs;
sock->addrs_end = addrs + nbytes;
socket_close_internal( sock ); // Get rid of the pipe
socket_connect_one( sock );
}
static void
socket_resolve_timeout( conn_t *sock )
{
error( "Error: Cannot resolve server '%s': timeout.\n", sock->conf->host );
socket_close_internal( sock );
socket_connect_bail( sock );
}
static void static void
socket_connect_one( conn_t *sock ) socket_connect_one( conn_t *sock )
{ {
int s; char *ai = sock->curr_addr;
#ifdef HAVE_IPV6 if (ai == sock->addrs_end) {
struct addrinfo *ai;
#else
struct addr_info *ai;
#endif
if (!(ai = sock->curr_addr)) {
error( "No working address found for %s\n", sock->conf->host ); error( "No working address found for %s\n", sock->conf->host );
socket_connect_bail( sock ); socket_connect_bail( sock );
return; return;
} }
union {
struct sockaddr any;
struct sockaddr_in ip4;
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
if (ai->ai_family == AF_INET6) { struct sockaddr_in6 ip6;
struct sockaddr_in6 *in6 = ((struct sockaddr_in6 *)ai->ai_addr); #endif
} addr;
#ifdef HAVE_IPV6
int fam = *(int *)ai;
ai += sizeof(int);
int addr_len;
if (fam == AF_INET6) {
addr_len = sizeof(addr.ip6);
addr.ip6.sin6_addr = *(struct in6_addr *)ai;
addr.ip6.sin6_flowinfo = 0;
addr.ip6.sin6_scope_id = 0;
ai += sizeof(struct in6_addr);
} else {
addr_len = sizeof(addr.ip4);
#else
const int fam = AF_INET;
const int addr_len = sizeof(addr.ip4);
{
#endif
addr.ip4.sin_addr = *(struct in_addr *)ai;
ai += sizeof(struct in_addr);
}
sock->curr_addr = ai;
#ifdef HAVE_IPV6
if (fam == AF_INET6) {
char sockname[64]; char sockname[64];
in6->sin6_port = htons( sock->conf->port ); inet_ntop( fam, &addr.ip6.sin6_addr, sockname, sizeof(sockname) );
nfasprintf( &sock->name, "%s ([%s]:%hu)", nfasprintf( &sock->name, "%s ([%s]:%hu)",
sock->conf->host, inet_ntop( AF_INET6, &in6->sin6_addr, sockname, sizeof(sockname) ), sock->conf->port ); sock->conf->host, sockname, sock->conf->port );
} else } else
#endif #endif
{ {
struct sockaddr_in *in = ((struct sockaddr_in *)ai->ai_addr);
in->sin_port = htons( sock->conf->port );
nfasprintf( &sock->name, "%s (%s:%hu)", nfasprintf( &sock->name, "%s (%s:%hu)",
sock->conf->host, inet_ntoa( in->sin_addr ), sock->conf->port ); sock->conf->host, inet_ntoa( addr.ip4.sin_addr ), sock->conf->port );
} }
#ifdef HAVE_IPV6 int s = socket( fam, SOCK_STREAM, 0 );
s = socket( ai->ai_family, SOCK_STREAM, 0 );
#else
s = socket( PF_INET, SOCK_STREAM, 0 );
#endif
if (s < 0) { if (s < 0) {
socket_connect_next( sock ); socket_connect_next( sock );
return; return;
@ -579,11 +686,9 @@ socket_connect_one( conn_t *sock )
socket_open_internal( sock, s ); socket_open_internal( sock, s );
infon( "Connecting to %s... ", sock->name ); infon( "Connecting to %s... ", sock->name );
#ifdef HAVE_IPV6 addr.any.sa_family = fam;
if (connect( s, ai->ai_addr, ai->ai_addrlen )) { addr.ip4.sin_port = htons( sock->conf->port ); // Aliased for ip6
#else if (connect( s, &addr.any, addr_len )) {
if (connect( s, ai->ai_addr, sizeof(*ai->ai_addr) )) {
#endif
if (errno != EINPROGRESS) { if (errno != EINPROGRESS) {
socket_connect_failed( sock ); socket_connect_failed( sock );
return; return;
@ -604,7 +709,6 @@ socket_connect_next( conn_t *conn )
sys_error( "Cannot connect to %s", conn->name ); sys_error( "Cannot connect to %s", conn->name );
free( conn->name ); free( conn->name );
conn->name = NULL; conn->name = NULL;
conn->curr_addr = conn->curr_addr->ai_next;
socket_connect_one( conn ); socket_connect_one( conn );
} }
@ -618,10 +722,8 @@ socket_connect_failed( conn_t *conn )
static void static void
socket_connected( conn_t *conn ) socket_connected( conn_t *conn )
{ {
if (conn->addrs) { free( conn->addrs );
freeaddrinfo( conn->addrs ); conn->addrs = NULL;
conn->addrs = NULL;
}
conf_notifier( &conn->notify, 0, POLLIN ); conf_notifier( &conn->notify, 0, POLLIN );
socket_expect_activity( conn, 0 ); socket_expect_activity( conn, 0 );
conn->state = SCK_READY; conn->state = SCK_READY;
@ -631,10 +733,8 @@ socket_connected( conn_t *conn )
static void static void
socket_cleanup_names( conn_t *conn ) socket_cleanup_names( conn_t *conn )
{ {
if (conn->addrs) { free( conn->addrs );
freeaddrinfo( conn->addrs ); conn->addrs = NULL;
conn->addrs = NULL;
}
free( conn->name ); free( conn->name );
conn->name = NULL; conn->name = NULL;
} }
@ -1106,6 +1206,11 @@ socket_fd_cb( int events, void *aux )
{ {
conn_t *conn = (conn_t *)aux; conn_t *conn = (conn_t *)aux;
if (conn->state == SCK_RESOLVING) {
socket_resolve_finalize( conn );
return;
}
if ((events & POLLERR) || conn->state == SCK_CONNECTING) { if ((events & POLLERR) || conn->state == SCK_CONNECTING) {
int soerr; int soerr;
socklen_t selen = sizeof(soerr); socklen_t selen = sizeof(soerr);
@ -1168,7 +1273,9 @@ socket_timeout_cb( void *aux )
{ {
conn_t *conn = (conn_t *)aux; conn_t *conn = (conn_t *)aux;
if (conn->state == SCK_CONNECTING) { if (conn->state == SCK_RESOLVING) {
socket_resolve_timeout( conn );
} else if (conn->state == SCK_CONNECTING) {
errno = ETIMEDOUT; errno = ETIMEDOUT;
socket_connect_failed( conn ); socket_connect_failed( conn );
} else { } else {

View File

@ -57,11 +57,7 @@ typedef struct {
int fd; int fd;
int state; int state;
const server_conf_t *conf; /* needed during connect */ const server_conf_t *conf; /* needed during connect */
#ifdef HAVE_IPV6 char *addrs, *addrs_end, *curr_addr; // needed during connect; assumed to be int-aligned
struct addrinfo *addrs, *curr_addr; /* needed during connect */
#else
struct addr_info *addrs, *curr_addr; /* needed during connect */
#endif
char *name; char *name;
#ifdef HAVE_LIBSSL #ifdef HAVE_LIBSSL
SSL *ssl; SSL *ssl;