commit ec50c55c36887b86b0143a265acae4b22d117fe9
Author: Oswald Buddenhagen <o...@users.sf.net>
Date:   Mon Jul 4 12:50:17 2022 +0200

    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.

 src/socket.c | 281 +++++++++++++++++++++++++++++++++++----------------
 src/socket.h |   6 +-
 2 files changed, 195 insertions(+), 92 deletions(-)

diff --git a/src/socket.c b/src/socket.c
index 8f41295..52cd7c2 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -28,6 +28,7 @@
 #endif
 
 enum {
+       SCK_RESOLVING,
        SCK_CONNECTING,
 #ifdef HAVE_LIBSSL
        SCK_STARTTLS,
@@ -415,6 +416,7 @@ static void socket_fd_cb( int, void * );
 static void socket_fake_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_next( 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_open_internal( conn_t *sock, int fd )
+socket_register_internal( conn_t *sock, int fd )
 {
        sock->fd = fd;
-       fcntl( fd, F_SETFL, O_NONBLOCK );
        init_notifier( &sock->notify, fd, socket_fd_cb, sock );
        init_wakeup( &sock->fd_fake, socket_fake_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
 socket_close_internal( conn_t *sock )
 {
@@ -441,32 +449,6 @@ socket_close_internal( conn_t *sock )
        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
 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" );
                socket_connected( sock );
        } else {
+               socket_resolve( 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
-               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;
+       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);
                }
-               info( "\vok\n" );
+       }
+       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;
+       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 );
+}
 
-               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;
+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 );
                }
-               info( "\vok\n" );
-
-               sock->addrs = init_addrinfo( he );
-#endif
-               sock->curr_addr = sock->addrs;
-               socket_connect_one( sock );
-       }
+               if (!didrd) {
+                       error( "read: unexpected EOF\n" );
+                       exit( 1 );
+               }
+               buf = ((char *)buf) + didrd;
+               len -= didrd;
+       } while (len);
 }
 
 static void
-socket_connect_one( conn_t *sock )
+socket_resolve_finalize( conn_t *sock )
 {
-       int s;
+       int errcode;
+       pipe_read( sock->fd, &errcode, sizeof(errcode) );
+       if (errcode) {
 #ifdef HAVE_IPV6
-       struct addrinfo *ai;
+               const char *err = gai_strerror( errcode );
 #else
-       struct addr_info *ai;
+               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;
+       }
 
-       if (!(ai = sock->curr_addr)) {
+       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
+socket_connect_one( conn_t *sock )
+{
+       char *ai = sock->curr_addr;
+       if (ai == sock->addrs_end) {
                error( "No working address found for %s\n", sock->conf->host );
                socket_connect_bail( sock );
                return;
        }
 
+       union {
+               struct sockaddr any;
+               struct sockaddr_in ip4;
+#ifdef HAVE_IPV6
+               struct sockaddr_in6 ip6;
+#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 (ai->ai_family == AF_INET6) {
-               struct sockaddr_in6 *in6 = ((struct sockaddr_in6 *)ai->ai_addr);
+       if (fam == AF_INET6) {
                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)",
-                           sock->conf->host, inet_ntop( AF_INET6, 
&in6->sin6_addr, sockname, sizeof(sockname) ), sock->conf->port );
+                           sock->conf->host, sockname, sock->conf->port );
        } else
 #endif
        {
-               struct sockaddr_in *in = ((struct sockaddr_in *)ai->ai_addr);
-               in->sin_port = htons( sock->conf->port );
                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
-       s = socket( ai->ai_family, SOCK_STREAM, 0 );
-#else
-       s = socket( PF_INET, SOCK_STREAM, 0 );
-#endif
+       int s = socket( fam, SOCK_STREAM, 0 );
        if (s < 0) {
                socket_connect_next( sock );
                return;
@@ -579,11 +686,9 @@ socket_connect_one( conn_t *sock )
        socket_open_internal( sock, s );
 
        infon( "Connecting to %s... ", sock->name );
-#ifdef HAVE_IPV6
-       if (connect( s, ai->ai_addr, ai->ai_addrlen )) {
-#else
-       if (connect( s, ai->ai_addr, sizeof(*ai->ai_addr) )) {
-#endif
+       addr.any.sa_family = fam;
+       addr.ip4.sin_port = htons( sock->conf->port );  // Aliased for ip6
+       if (connect( s, &addr.any, addr_len )) {
                if (errno != EINPROGRESS) {
                        socket_connect_failed( sock );
                        return;
@@ -604,7 +709,6 @@ socket_connect_next( conn_t *conn )
        sys_error( "Cannot connect to %s", conn->name );
        free( conn->name );
        conn->name = NULL;
-       conn->curr_addr = conn->curr_addr->ai_next;
        socket_connect_one( conn );
 }
 
@@ -618,10 +722,8 @@ socket_connect_failed( conn_t *conn )
 static void
 socket_connected( conn_t *conn )
 {
-       if (conn->addrs) {
-               freeaddrinfo( conn->addrs );
-               conn->addrs = NULL;
-       }
+       free( conn->addrs );
+       conn->addrs = NULL;
        conf_notifier( &conn->notify, 0, POLLIN );
        socket_expect_activity( conn, 0 );
        conn->state = SCK_READY;
@@ -631,10 +733,8 @@ socket_connected( conn_t *conn )
 static void
 socket_cleanup_names( conn_t *conn )
 {
-       if (conn->addrs) {
-               freeaddrinfo( conn->addrs );
-               conn->addrs = NULL;
-       }
+       free( conn->addrs );
+       conn->addrs = NULL;
        free( conn->name );
        conn->name = NULL;
 }
@@ -1106,6 +1206,11 @@ socket_fd_cb( int events, void *aux )
 {
        conn_t *conn = (conn_t *)aux;
 
+       if (conn->state == SCK_RESOLVING) {
+               socket_resolve_finalize( conn );
+               return;
+       }
+
        if ((events & POLLERR) || conn->state == SCK_CONNECTING) {
                int soerr;
                socklen_t selen = sizeof(soerr);
@@ -1168,7 +1273,9 @@ socket_timeout_cb( void *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;
                socket_connect_failed( conn );
        } else {
diff --git a/src/socket.h b/src/socket.h
index 0e40f48..5b538eb 100644
--- a/src/socket.h
+++ b/src/socket.h
@@ -57,11 +57,7 @@ typedef struct {
        int fd;
        int state;
        const server_conf_t *conf; /* needed during connect */
-#ifdef HAVE_IPV6
-       struct addrinfo *addrs, *curr_addr; /* needed during connect */
-#else
-       struct addr_info *addrs, *curr_addr; /* needed during connect */
-#endif
+       char *addrs, *addrs_end, *curr_addr;  // needed during connect; assumed 
to be int-aligned
        char *name;
 #ifdef HAVE_LIBSSL
        SSL *ssl;


_______________________________________________
isync-devel mailing list
isync-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/isync-devel

Reply via email to