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;
}
/**
*/
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 */
/* 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);
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;
}
}
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;
}