[PATCH v3] Add IPv6 support for outgoing remote TCP connections

Paul Fertser
2014-02-11T19:48:38Z

This patch implements target host lookup the modern way,
see ``man 3 getaddrinfo'' for details; as a result, both IPv4 and IPv6
are transparently supported.

Changes since v1:

* Use gnulib to remain compatible with old systems lacking this
function.
* Compile-time tested with MinGW-w64 (cross).

Changes since v2:

* Do not use gnulib, handle windows case separately.

gdb:

2014-02-10 Paul Fertser <fercerpav@gmail.com>

* ser-tcp.c : Additionally include ws2tcpip.h for windows.
* ser-tcp.c (net-open): Use last semicolon as port separator.
* ser-tcp.c (net-open): Use getaddrinfo for host lookup.
* configure.ac : Add check for getaddrinfo.
* configure : Regenerated.
---

Interestingly enough, this version continues to try connecting for
about 12 seconds (cycling IPv6 and IPv4 loopback addresess) when run as

./gdb -ex "tar ext :3333"

and nobody's listening.

The same cross-compiled and run with Wine stops trying immediately
(but connects ok if there's a listener). I haven't tried vanilla
version and haven't tried on native windows, so it's hard to tell if
that's a wine artifact.

In this patch I was trying to change current behaviour as little as
possible.

gdb/configure | 58 +++++++++++++++++
gdb/configure.ac | 3 +
gdb/ser-tcp.c | 191 +++++++++++++++++++++++++++++--------------------------
3 files changed, 162 insertions(+), 90 deletions(-)

diff --git a/gdb/configure b/gdb/configure
index 8ae2e09..ea7a9db 100755
--- a/gdb/configure
+++ b/gdb/configure
@@ -6509,6 +6509,64 @@ if test "$ac_res" != no; then :
fi

+# Borrow from gnulib, Solaris might additionally need nsl
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing getaddrinfo" >&5
+$as_echo_n "checking for library containing getaddrinfo... " >&6; }
+if test "${ac_cv_search_getaddrinfo+set}" = set; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char getaddrinfo ();
+int
+main ()
+{
+return getaddrinfo ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' socket network net; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib -lnsl $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_getaddrinfo=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if test "${ac_cv_search_getaddrinfo+set}" = set; then :
+ break
+fi
+done
+if test "${ac_cv_search_getaddrinfo+set}" = set; then :
+
+else
+ ac_cv_search_getaddrinfo=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_getaddrinfo" >&5
+$as_echo "$ac_cv_search_getaddrinfo" >&6; }
+ac_res=$ac_cv_search_getaddrinfo
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+
# Some systems (e.g. Solaris) have `socketpair' in libsocket.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing socketpair" >&5
$as_echo_n "checking for library containing socketpair... " >&6; }
diff --git a/gdb/configure.ac b/gdb/configure.ac
index feb28f3..fb14c4b 100644
--- a/gdb/configure.ac
+++ b/gdb/configure.ac
@@ -512,6 +512,9 @@ AC_CHECK_FUNC(wctype, [],
# Some systems (e.g. Solaris) have `gethostbyname' in libnsl.
AC_SEARCH_LIBS(gethostbyname, nsl)

+# Borrow from gnulib, Solaris might additionally need nsl
+AC_SEARCH_LIBS(getaddrinfo, [socket network net], [], [], [-lnsl])
+
# Some systems (e.g. Solaris) have `socketpair' in libsocket.
AC_SEARCH_LIBS(socketpair, socket)

diff --git a/gdb/ser-tcp.c b/gdb/ser-tcp.c
index c288ab4..15f7648 100644
--- a/gdb/ser-tcp.c
+++ b/gdb/ser-tcp.c
@@ -39,6 +39,7 @@

#ifdef USE_WIN32API
#include <winsock2.h>
+#include <ws2tcpip.h>
#ifndef ETIMEDOUT
#define ETIMEDOUT WSAETIMEDOUT
#endif
@@ -157,9 +158,10 @@ int
net_open (struct serial *scb, const char *name)
{
char *port_str, hostname[100];
- int n, port, tmp;
+ int n, tmp;
int use_udp;
- struct hostent *hostent;
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
struct sockaddr_in sockaddr;
#ifdef USE_WIN32API
u_long ioarg;
@@ -177,7 +179,7 @@ net_open (struct serial *scb, const char *name)
else if (strncmp (name, "tcp:", 4) == 0)
name = name + 4;

- port_str = strchr (name, ':');
+ port_str = strrchr (name, ':');

if (!port_str)
error (_("net_open: No colon in host name!")); /* Shouldn't ever
@@ -186,124 +188,133 @@ net_open (struct serial *scb, const char *name)
tmp = min (port_str - name, (int) sizeof hostname - 1);
strncpy (hostname, name, tmp); /* Don't want colon. */
hostname[tmp] = '\000'; /* Tie off host name. */
- port = atoi (port_str + 1);
+ port_str++;

/* Default hostname is localhost. */
if (!hostname[0])
strcpy (hostname, "localhost");

- hostent = gethostbyname (hostname);
- if (!hostent)
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ if (use_udp)
+ hints.ai_socktype = SOCK_DGRAM;
+ else
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0;
+
+ tmp = getaddrinfo (hostname, port_str, &hints, &result);
+ if (tmp)
{
fprintf_unfiltered (gdb_stderr, "%s: unknown host\n", hostname);
errno = ENOENT;
return -1;
}

- sockaddr.sin_family = PF_INET;
- sockaddr.sin_port = htons (port);
- memcpy (&sockaddr.sin_addr.s_addr, hostent->h_addr,
- sizeof (struct in_addr));
-
- retry:
-
- if (use_udp)
- scb->fd = gdb_socket_cloexec (PF_INET, SOCK_DGRAM, 0);
- else
- scb->fd = gdb_socket_cloexec (PF_INET, SOCK_STREAM, 0);
+ for (rp = result; ; rp = rp->ai_next ? rp->ai_next : result)
+ {
+ scb->fd = gdb_socket_cloexec (rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);

- if (scb->fd == -1)
- return -1;
+ if (scb->fd == -1)
+ continue;

- /* Set socket nonblocking. */
- ioarg = 1;
- ioctl (scb->fd, FIONBIO, &ioarg);
+ /* Set socket nonblocking. */
+ ioarg = 1;
+ ioctl (scb->fd, FIONBIO, &ioarg);

- /* Use Non-blocking connect. connect() will return 0 if connected
- already. */
- n = connect (scb->fd, (struct sockaddr *) &sockaddr, sizeof (sockaddr));
+ /* Use Non-blocking connect. connect() will return 0 if connected
+ already. */
+ n = connect (scb->fd, rp->ai_addr, rp->ai_addrlen);

- if (n < 0)
- {
+ if (n < 0)
+ {
#ifdef USE_WIN32API
- int err = WSAGetLastError();
+ int err = WSAGetLastError();
#else
- int err = errno;
+ int err = errno;
#endif

- /* Maybe we're waiting for the remote target to become ready to
- accept connections. */
- if (tcp_auto_retry
+ /* Maybe we're waiting for the remote target to become ready to
+ accept connections. */
+ if (tcp_auto_retry
#ifdef USE_WIN32API
- && err == WSAECONNREFUSED
+ && err == WSAECONNREFUSED
#else
- && err == ECONNREFUSED
+ && err == ECONNREFUSED
#endif
- && wait_for_connect (NULL, &polls) >= 0)
- {
- close (scb->fd);
- goto retry;
- }
+ && wait_for_connect (NULL, &polls) >= 0)
+ {
+ close (scb->fd);
+ continue;
+ }

- if (
+ if (
#ifdef USE_WIN32API
- /* Under Windows, calling "connect" with a non-blocking socket
- results in WSAEWOULDBLOCK, not WSAEINPROGRESS. */
- err != WSAEWOULDBLOCK
+ /* Under Windows, calling "connect" with a non-blocking socket
+ results in WSAEWOULDBLOCK, not WSAEINPROGRESS. */
+ err != WSAEWOULDBLOCK
#else
- err != EINPROGRESS
+ err != EINPROGRESS
#endif
- )
- {
- errno = err;
- net_close (scb);
- return -1;
- }
-
- /* Looks like we need to wait for the connect. */
- do
- {
- n = wait_for_connect (scb, &polls);
- }
- while (n == 0);
- if (n < 0)
- {
- net_close (scb);
- return -1;
- }
- }
-
- /* Got something. Is it an error? */
- {
- int res, err;
- socklen_t len;
-
- len = sizeof (err);
- /* On Windows, the fourth parameter to getsockopt is a "char *";
- on UNIX systems it is generally "void *". The cast to "void *"
- is OK everywhere, since in C "void *" can be implicitly
- converted to any pointer type. */
- res = getsockopt (scb->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len);
- if (res < 0 || err)
+ )
+ {
+ errno = err;
+ net_close (scb);
+ freeaddrinfo (result);
+ return -1;
+ }
+
+ /* Looks like we need to wait for the connect. */
+ do
+ {
+ n = wait_for_connect (scb, &polls);
+ }
+ while (n == 0);
+ if (n < 0)
+ {
+ net_close (scb);
+ freeaddrinfo (result);
+ return -1;
+ }
+ }
+
+ /* Got something. Is it an error? */
{
- /* Maybe the target still isn't ready to accept the connection. */
- if (tcp_auto_retry
+ int res, err;
+ socklen_t len;
+
+ len = sizeof (err);
+ /* On Windows, the fourth parameter to getsockopt is a "char *";
+ on UNIX systems it is generally "void *". The cast to "void *"
+ is OK everywhere, since in C "void *" can be implicitly
+ converted to any pointer type. */
+ res = getsockopt (scb->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len);
+ if (res < 0 || err)
+ {
+ /* Maybe the target still isn't ready to accept the connection. */
+ if (tcp_auto_retry
#ifdef USE_WIN32API
- && err == WSAECONNREFUSED
+ && err == WSAECONNREFUSED
#else
- && err == ECONNREFUSED
+ && err == ECONNREFUSED
#endif
- && wait_for_connect (NULL, &polls) >= 0)
- {
- close (scb->fd);
- goto retry;
- }
- if (err)
- errno = err;
- net_close (scb);
- return -1;
+ && wait_for_connect (NULL, &polls) >= 0)
+ {
+ close (scb->fd);
+ continue;
+ }
+ if (err)
+ errno = err;
+ net_close (scb);
+ freeaddrinfo (result);
+ return -1;
+ }
+ break;
}
- }
+ }
+
+ freeaddrinfo (result);

/* Turn off nonblocking. */
ioarg = 0;
--
1.8.3.2