/* {{{ php_network_getaddresses
* Returns number of addresses, 0 for none/error
*/
-static int php_network_getaddresses(const char *host, struct sockaddr ***sal TSRMLS_DC)
+static int php_network_getaddresses(const char *host, int socktype, struct sockaddr ***sal TSRMLS_DC)
{
struct sockaddr **sap;
int n;
+#ifdef HAVE_GETADDRINFO
+ static int ipv6_borked = -1; /* the way this is used *is* thread safe */
+ struct addrinfo hints, *res, *sai;
+#else
+ struct hostent *host_info;
+ struct in_addr in;
+#endif
if (host == NULL) {
return 0;
}
- {
#ifdef HAVE_GETADDRINFO
- struct addrinfo hints, *res, *sai;
-
- memset(&hints, '\0', sizeof(hints));
-# ifdef HAVE_IPV6
- hints.ai_family = AF_UNSPEC;
-# else
- hints.ai_family = AF_INET;
-# endif
- if ((n = getaddrinfo(host, NULL, &hints, &res))) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: getaddrinfo failed: %s"
-# ifdef HAVE_IPV6
- " (is your IPV6 configuration correct? If this error happens all the time, try reconfiguring PHP using --disable-ipv6 option to configure)"
-#endif
- , PHP_GAI_STRERROR(n));
- return 0;
- } else if (res == NULL) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: getaddrinfo failed (null result pointer)");
- return 0;
+ memset(&hints, '\0', sizeof(hints));
+
+ hints.ai_family = AF_INET; /* default to regular inet (see below) */
+ hints.ai_socktype = socktype;
+
+# ifdef HAVE_IPV6
+ /* probe for a working IPv6 stack; even if detected as having v6 at compile
+ * time, at runtime some stacks are slow to resolve or have other issues
+ * if they are not correctly configured.
+ * static variable use is safe here since simple store or fetch operations
+ * are atomic and because the actual probe process is not in danger of
+ * collisions or race conditions. */
+ if (ipv6_borked == -1) {
+ int s;
+
+ s = socket(PF_INET6, SOCK_DGRAM, 0);
+ if (s == SOCK_ERR) {
+ ipv6_borked = 1;
+ } else {
+ ipv6_borked = 0;
+ closesocket(s);
}
+ }
+ hints.ai_family = ipv6_borked ? AF_INET : AF_UNSPEC;
+# endif
+
+ if ((n = getaddrinfo(host, NULL, &hints, &res))) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: getaddrinfo failed: %s", PHP_GAI_STRERROR(n));
+ return 0;
+ } else if (res == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: getaddrinfo failed (null result pointer)");
+ return 0;
+ }
- sai = res;
- for (n = 1; (sai = sai->ai_next) != NULL; n++);
- *sal = safe_emalloc((n + 1), sizeof(*sal), 0);
- sai = res;
- sap = *sal;
- do {
- switch (sai->ai_family) {
-# ifdef HAVE_IPV6
- case AF_INET6:
- *sap = emalloc(sizeof(struct sockaddr_in6));
- *(struct sockaddr_in6 *)*sap =
- *((struct sockaddr_in6 *)sai->ai_addr);
- sap++;
- break;
-# endif
- case AF_INET:
- *sap = emalloc(sizeof(struct sockaddr_in));
- *(struct sockaddr_in *)*sap =
- *((struct sockaddr_in *)sai->ai_addr);
- sap++;
- break;
- }
- } while ((sai = sai->ai_next) != NULL);
- freeaddrinfo(res);
+ sai = res;
+ for (n = 1; (sai = sai->ai_next) != NULL; n++)
+ ;
+
+ *sal = safe_emalloc((n + 1), sizeof(*sal), 0);
+ sai = res;
+ sap = *sal;
+
+ do {
+ *sap = emalloc(sai->ai_addrlen);
+ memcpy(*sap, sai->ai_addr, sai->ai_addrlen);
+ sap++;
+ } while ((sai = sai->ai_next) != NULL);
+
+ freeaddrinfo(res);
#else
- struct hostent *host_info;
- struct in_addr in;
-
- if (!inet_aton(host, &in)) {
- /* XXX NOT THREAD SAFE */
- host_info = gethostbyname(host);
- if (host_info == NULL) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: gethostbyname failed");
- return 0;
- }
- in = *((struct in_addr *) host_info->h_addr);
+ if (!inet_aton(host, &in)) {
+ /* XXX NOT THREAD SAFE (is safe under win32) */
+ host_info = gethostbyname(host);
+ if (host_info == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "php_network_getaddresses: gethostbyname failed");
+ return 0;
}
+ in = *((struct in_addr *) host_info->h_addr);
+ }
- *sal = emalloc(2 * sizeof(*sal));
- sap = *sal;
- *sap = emalloc(sizeof(struct sockaddr_in));
- (*sap)->sa_family = AF_INET;
- ((struct sockaddr_in *)*sap)->sin_addr = in;
- sap++;
- n = 1;
+ *sal = emalloc(2 * sizeof(*sal));
+ sap = *sal;
+ *sap = emalloc(sizeof(struct sockaddr_in));
+ (*sap)->sa_family = AF_INET;
+ ((struct sockaddr_in *)*sap)->sin_addr = in;
+ sap++;
+ n = 1;
#endif
- }
+
*sap = NULL;
return n;
}
goto ok;
}
+#ifdef __linux__
+retry_again:
+#endif
+
FD_ZERO(&rset);
FD_ZERO(&eset);
FD_SET(sockfd, &rset);
if ((n = select(sockfd + 1, &rset, &wset, &eset, timeout)) == 0) {
error = ETIMEDOUT;
- }
-
- if(FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
+ } else if ((FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset))) {
len = sizeof(error);
/*
BSD-derived systems set errno correctly
} else {
/* whoops: sockfd has disappeared */
ret = -1;
+ error = errno;
}
+#ifdef __linux__
+ /* this is a linux specific hack that only works since linux updates
+ * the timeout struct to reflect the time remaining from the original
+ * timeout value. One day, we should record the start time and calculate
+ * the remaining time ourselves for portability */
+ if (ret == -1 && error == EINPROGRESS) {
+ error = 0;
+ goto retry_again;
+ }
+#endif
+
ok:
fcntl(sockfd, F_SETFL, flags);
- if(error) {
+ if (error) {
errno = error;
ret = -1;
}
int set_timeout = 0;
int err = 0;
- n = php_network_getaddresses(host, &sal TSRMLS_CC);
+ n = php_network_getaddresses(host, socktype, &sal TSRMLS_CC);
if (n == 0)
return -1;