]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
tcp: rewrite tcp_connect to try more IP addresses, cleanups, fixes #4552
authorJaroslav Kysela <perex@perex.cz>
Sun, 27 Aug 2017 17:48:11 +0000 (19:48 +0200)
committerJaroslav Kysela <perex@perex.cz>
Thu, 31 Aug 2017 13:56:38 +0000 (15:56 +0200)
src/tcp.c

index 8085a5edee99394d6e60c93afadc58f93aa5800d..04c1998b001acf705de750f9bdec739a14f34460 100644 (file)
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -77,27 +77,55 @@ int
 tcp_connect(const char *hostname, int port, const char *bindaddr,
             char *errbuf, size_t errbufsize, int timeout)
 {
-  int fd, r, res, err;
-  struct addrinfo *ai, hints;
+  int fd = -1, r, res, err;
+  struct addrinfo *ai, *rai = NULL, hints;
+  struct sockaddr_storage bindip;
   char portstr[6];
   socklen_t errlen = sizeof(err);
 
+  errbuf[0] = '\0';
+
+  bindip.ss_family = AF_UNSPEC;
+  if (bindaddr && bindaddr[0] != '\0') {
+    if (tcp_get_ip_from_str(bindaddr, &bindip) == NULL) {
+      snprintf(errbuf, errbufsize, "Cannot bind to addr '%s'", bindaddr);
+      return -1;
+    }
+  }
+
   snprintf(portstr, 6, "%u", port);
   memset(&hints, 0, sizeof(struct addrinfo));
   hints.ai_family = AF_UNSPEC;
   res = getaddrinfo(hostname, portstr, &hints, &ai);
-  
   if (res != 0) {
     snprintf(errbuf, errbufsize, "%s", gai_strerror(res));
     return -1;
   }
 
-  fd = tvh_socket(ai->ai_family, SOCK_STREAM, 0);
+again:
+  if (fd >= 0) {
+    close(fd);
+    fd = -1;
+  }
+  rai = rai == NULL ? ai : rai->ai_next;
+  if (rai == NULL) {
+    if (errbuf[0] == '\0')
+      snprintf(errbuf, errbufsize, "Invalid or unresolved hostname '%s'", hostname);
+    goto error;
+  }
+
+  if (bindip.ss_family == AF_UNSPEC) {
+    if (rai->ai_family != AF_INET && rai->ai_family != AF_INET6)
+      goto again;
+  } else if (rai->ai_family != bindip.ss_family) {
+    goto again;
+  }
+
+  fd = tvh_socket(rai->ai_family, SOCK_STREAM, 0);
   if(fd < 0) {
     snprintf(errbuf, errbufsize, "Unable to create socket: %s",
             strerror(errno));
-    freeaddrinfo(ai);
-    return -1;
+    goto again;
   }
 
   /**
@@ -105,30 +133,16 @@ tcp_connect(const char *hostname, int port, const char *bindaddr,
    */
   fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
 
-  if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
-    if (bindaddr && bindaddr[0] != '\0') {
-      struct sockaddr_storage ip;
-      memset(&ip, 0, sizeof(ip));
-      ip.ss_family = ai->ai_family;
-      if (inet_pton(AF_INET, bindaddr, IP_IN_ADDR(ip)) <= 0 ||
-          bind(fd, (struct sockaddr *)&ip, IP_IN_ADDRLEN(ip)) < 0) {
-        snprintf(errbuf, errbufsize, "Cannot bind to IPv%s addr '%s'",
-                                     ai->ai_family == AF_INET6 ? "6" : "4",
-                                     bindaddr);
-        freeaddrinfo(ai);
-        close(fd);
-        return -1;
-      }
+  if (bindip.ss_family != AF_UNSPEC) {
+    if (bind(fd, (struct sockaddr *)&bindip, IP_IN_ADDRLEN(bindip)) < 0) {
+      snprintf(errbuf, errbufsize, "Cannot bind to IPv%s addr '%s'",
+                                   bindip.ss_family == AF_INET6 ? "6" : "4",
+                                   bindaddr);
+      goto error;
     }
-  } else {
-    snprintf(errbuf, errbufsize, "Invalid protocol family");
-    freeaddrinfo(ai);
-    close(fd);
-    return -1;
   }
 
-  r = connect(fd, ai->ai_addr, ai->ai_addrlen);
-  freeaddrinfo(ai);
+  r = connect(fd, rai->ai_addr, rai->ai_addrlen);
 
   if(r < 0) {
     /* timeout < 0 - do not wait at all */
@@ -147,14 +161,13 @@ tcp_connect(const char *hostname, int port, const char *bindaddr,
 
       /* minimal timeout is one second */
       if (timeout < 1)
-        timeout = 0;
+        timeout = 1;
 
       while (1) {
         if (!tvheadend_is_running()) {
           errbuf[0] = '\0';
           tvhpoll_destroy(efd);
-          close(fd);
-          return -1;
+          goto error;
         }
 
         r = tvhpoll_wait(efd, &ev, 1, timeout * 1000);
@@ -162,17 +175,15 @@ tcp_connect(const char *hostname, int port, const char *bindaddr,
           break;
         
         if (r == 0) { /* Timeout */
-          snprintf(errbuf, errbufsize, "Connection attempt timed out");
+          snprintf(errbuf, errbufsize, "Connection attempt to '%s' timed out", hostname);
           tvhpoll_destroy(efd);
-          close(fd);
-          return -1;
+          goto again;
         }
       
         if (!ERRNO_AGAIN(errno)) {
           snprintf(errbuf, errbufsize, "poll() error: %s", strerror(errno));
           tvhpoll_destroy(efd);
-          close(fd);
-          return -1;
+          goto error;
         }
       }
       
@@ -187,18 +198,23 @@ tcp_connect(const char *hostname, int port, const char *bindaddr,
 
   if(err != 0) {
     snprintf(errbuf, errbufsize, "%s", strerror(err));
-    close(fd);
-    return -1;
+    goto again;
   }
   
   fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
 
-
   /* Set the keep-alive active */
   err = 1;
   setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&err, errlen);
 
+  freeaddrinfo(ai);
   return fd;
+
+error:
+  if (fd >= 0)
+    close(fd);
+  freeaddrinfo(ai);
+  return -1;
 }