This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Implement IPv6 support for GDB/gdbserver


This patch implements IPv6 support for both GDB and gdbserver.  Based
on my research, it is the fourth attempt to do that since 2006.  Since
I used ideas from all of the previous patches, I also added their
authors's names on the ChangeLogs as a way to recognize their
efforts.  For reference sake, you can find the previous attempts at:

  https://sourceware.org/ml/gdb-patches/2006-09/msg00192.html

  https://sourceware.org/ml/gdb-patches/2014-02/msg00248.html

  https://sourceware.org/ml/gdb-patches/2016-02/msg00226.html

The basic idea behind the patch is to start using the new
'getaddrinfo'/'getnameinfo' calls, which are responsible for
translating names and addresses in a protocol-independent way.  This
means that if we ever have an IPv8, we won't need to change the code
again.

The function 'getaddrinfo' returns a linked list of possible addresses
to connect to, so I modified ser-tcp.c:net_open's code to loop through
the linked list and try all the addresses until it finds a valid one.
The same rationale was used for gdbserver, but without the "retry"
mechanism that GDB has.

I also implemented some hostname parsing functions which are used to
help GDB and gdbserver to parse hostname strings provided by the user.
These new functions are living inside common/netstuff.[ch].  I've had
to do that since IPv6 introduces a new URL scheme, which defines that
square brackets can be used to enclose the host part and differentiate
it from the port (e.g., "[::1]:1234" means "host ::1, port 1234").  I
spent some time thinking about a reasonable way to interpret what the
user wants, and I came up with the following:

- If the user has provided a prefix that doesn't specify the protocol
  version (i.e., "tcp:" or "udp:"), or if the user has not provided
  any prefix, don't make any assumptions (i.e., assume AF_UNSPEC when
  dealing with 'getaddrinfo') *unless* the host starts with "[" (in
  which case, assume it's an IPv6 host).

- If the user has provided a prefix that does specify the protocol
  version (i.e., "tcp4:", "tcp6:", "udp4:" or "udp6:"), then respect
  that.

This method doesn't follow strictly what RFC 2732 proposes (that
literal IPv6 addresses should be provided enclosed in "[" and "]")
because IPv6 addresses still can be provided without square brackets
in our case, but since we have prefixes to specify protocol versions I
think this is not an issue.

Another thing worth mentioning is the new 'GDB_TEST_IPV6' testcase
parameter, which instructs GDB and gdbserver to use IPv6 for
connections.  This way, if you want to run IPv6 tests, you do:

  $ make check-gdb RUNTESTFLAGS='GDB_TEST_IPV6=1'

This required a few changes on the gdbserver-base.exp,
native-gdbserver.exp and native-extended-gdbserver.exp boards, and
also a minimal adjustment on gdb.server/run-without-local-binary.exp.

This patch has been regression-tested on BuildBot and locally, and
also built using a x86_64-w64-mingw32 GCC, and no problems were found.

gdb/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Jan Kratochvil  <jan.kratochvil@redhat.com>
	    Paul Fertser  <fercerpav@gmail.com>
	    Tsutomu Seki  <sekiriki@gmail.com>

	* Makefile.in (COMMON_SFILES): Add 'common/netstuff.c'.
	(HFILES_NO_SRCDIR): Add 'common/netstuff.h'.
	* NEWS (Changes since GDB 8.1): Mention IPv6 support.
	* common/netstuff.c: New file.
	* common/netstuff.h: New file.
	* ser-tcp.c: Include 'netstuff.h' and 'wspiapi.h'.
	(net_open): Handle IPv6-style hostnames; implement support for
	IPv6 connections.

gdb/gdbserver/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Jan Kratochvil  <jan.kratochvil@redhat.com>
	    Paul Fertser  <fercerpav@gmail.com>
	    Tsutomu Seki  <sekiriki@gmail.com>

	* Makefile.in (SFILES): Add '$(srcdir)/common/netstuff.c'.
	(OBS): Add 'common/netstuff.o'.
	* gdbreplay.c: Include 'wspiapi.h'.
	(remote_open): Implement support for IPv6
	connections.
	* remote-utils.c: Include 'netstuff.h', 'filestuff.h'
	and 'wspiapi.h'.
	(handle_accept_event): Accept connections from IPv6 sources.
	(remote_prepare): Handle IPv6-style hostnames; implement
	support for IPv6 connections.
	(remote_open): Implement support for printing connections from
	IPv6 sources.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Jan Kratochvil  <jan.kratochvil@redhat.com>
	    Paul Fertser  <fercerpav@gmail.com>
	    Tsutomu Seki  <sekiriki@gmail.com>

	* README (Testsuite Parameters): Mention new 'GDB_TEST_IPV6'
	parameter.
	* boards/gdbserver-base.exp (get_comm_port_localhost_ipv6):
	New procedure.
	* boards/native-extended-gdbserver.exp: Detect 'GDB_TEST_IPV6'
	and change board info accordingly.
	* boards/native-gdbserver.exp: Likewise.
	* gdb.server/run-without-local-binary.exp: Improve regexp used
	for detecting when a remote debugging connection succeeds.

gdb/doc/ChangeLog:
yyyy-mm-dd  Sergio Durigan Junior  <sergiodj@redhat.com>
	    Jan Kratochvil  <jan.kratochvil@redhat.com>
	    Paul Fertser  <fercerpav@gmail.com>
	    Tsutomu Seki  <sekiriki@gmail.com>

	* gdb.texinfo (Remote Connection Commands): Add explanation
	about new IPv6 support.  Add new connection prefixes.
---
 gdb/Makefile.in                                    |   2 +
 gdb/NEWS                                           |   4 +
 gdb/common/netstuff.c                              | 136 +++++++++++++
 gdb/common/netstuff.h                              |  52 +++++
 gdb/doc/gdb.texinfo                                |  48 ++++-
 gdb/gdbserver/Makefile.in                          |   2 +
 gdb/gdbserver/gdbreplay.c                          | 181 +++++++++++++----
 gdb/gdbserver/remote-utils.c                       | 119 +++++++----
 gdb/ser-tcp.c                                      | 217 ++++++++++-----------
 gdb/testsuite/README                               |   7 +
 gdb/testsuite/boards/gdbserver-base.exp            |   5 +
 gdb/testsuite/boards/native-extended-gdbserver.exp |   7 +-
 gdb/testsuite/boards/native-gdbserver.exp          |   7 +-
 .../gdb.server/run-without-local-binary.exp        |   2 +-
 14 files changed, 602 insertions(+), 187 deletions(-)
 create mode 100644 gdb/common/netstuff.c
 create mode 100644 gdb/common/netstuff.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index df6ebab851..06ce12a4ee 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -962,6 +962,7 @@ COMMON_SFILES = \
 	common/job-control.c \
 	common/gdb_tilde_expand.c \
 	common/gdb_vecs.c \
+	common/netstuff.c \
 	common/new-op.c \
 	common/pathstuff.c \
 	common/print-utils.c \
@@ -1443,6 +1444,7 @@ HFILES_NO_SRCDIR = \
 	common/gdb_vecs.h \
 	common/gdb_wait.h \
 	common/common-inferior.h \
+	common/netstuff.h \
 	common/host-defs.h \
 	common/pathstuff.h \
 	common/print-utils.h \
diff --git a/gdb/NEWS b/gdb/NEWS
index cef558039e..1f95ced912 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** Changes since GDB 8.1
 
+* GDB and GDBserver now support IPv6 connections.  IPv6 hostnames
+  can be passed using the '[ADDRESS]:PORT' notation, or the regular
+  'ADDRESS:PORT' method.
+
 * The commands 'info variables/functions/types' now show the source line
   numbers of symbol definitions when available.
 
diff --git a/gdb/common/netstuff.c b/gdb/common/netstuff.c
new file mode 100644
index 0000000000..cdf4b611db
--- /dev/null
+++ b/gdb/common/netstuff.c
@@ -0,0 +1,136 @@
+/* Operations on network stuff.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "common-defs.h"
+#include "netstuff.h"
+
+#ifdef USE_WIN32API
+#include <winsock2.h>
+#include <wspiapi.h>
+#else
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#endif
+
+/* See common/netstuff.h.  */
+
+scoped_free_addrinfo::scoped_free_addrinfo (struct addrinfo *a)
+{
+  m_res = a;
+}
+/* See common/netstuff.h.  */
+
+scoped_free_addrinfo::~scoped_free_addrinfo ()
+{
+  freeaddrinfo (m_res);
+}
+
+/* See common/netstuff.h.  */
+
+void
+parse_hostname_without_prefix (const char *hostname, std::string &host_str,
+			       std::string &port_str, struct addrinfo *hint)
+{
+  std::string strname (hostname);
+
+  if (hint->ai_family != AF_INET && strname[0] == '[')
+    {
+      /* IPv6 addresses can be written as '[ADDR]:PORT', and we
+	 support this notation.  */
+      size_t close_bracket_pos = strname.find_first_of (']');
+
+      if (close_bracket_pos == std::string::npos)
+	error (_("Missing close bracket in hostname '%s'"),
+	       strname.c_str ());
+
+      /* Erase both '[' and ']'.  */
+      strname.erase (0, 1);
+      strname.erase (close_bracket_pos - 1, 1);
+
+      hint->ai_family = AF_INET6;
+    }
+
+  /* The length of the hostname part.  */
+  size_t host_len;
+
+  size_t last_colon_pos = strname.find_last_of (':');
+
+  if (last_colon_pos != std::string::npos)
+    {
+      /* The user has provided a port.  */
+      host_len = last_colon_pos;
+      port_str = strname.substr (last_colon_pos + 1);
+    }
+  else
+    host_len = strname.size ();
+
+  host_str = strname.substr (0, host_len);
+
+  /* Default hostname is localhost.  */
+  if (host_str.empty ())
+    host_str = "localhost";
+}
+
+/* See common/netstuff.h.  */
+
+void
+parse_hostname (const char *hostname, std::string &host_str,
+		std::string &port_str, struct addrinfo *hint)
+{
+  /* Struct to hold the association between valid prefixes, their
+     family and socktype.  */
+  struct host_prefix
+    {
+      /* The prefix.  */
+      const char *prefix;
+
+      /* The 'ai_family'.  */
+      int family;
+
+      /* The 'ai_socktype'.  */
+      int socktype;
+    };
+  static const struct host_prefix prefixes[] =
+    {
+      { "udp:",  AF_UNSPEC, SOCK_DGRAM },
+      { "tcp:",  AF_UNSPEC, SOCK_STREAM },
+      { "udp4:", AF_INET,   SOCK_DGRAM },
+      { "tcp4:", AF_INET,   SOCK_STREAM },
+      { "udp6:", AF_INET6,  SOCK_DGRAM },
+      { "tcp6:", AF_INET6,  SOCK_STREAM },
+      { NULL, 0, 0 },
+    };
+
+  for (const struct host_prefix *prefix = prefixes;
+       prefix->prefix != NULL;
+       ++prefix)
+    if (startswith (hostname, prefix->prefix))
+      {
+	hostname += strlen (prefix->prefix);
+	hint->ai_family = prefix->family;
+	hint->ai_socktype = prefix->socktype;
+	hint->ai_protocol
+	  = hint->ai_socktype == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
+	break;
+      }
+
+  parse_hostname_without_prefix (hostname, host_str, port_str, hint);
+}
diff --git a/gdb/common/netstuff.h b/gdb/common/netstuff.h
new file mode 100644
index 0000000000..1ac2433f11
--- /dev/null
+++ b/gdb/common/netstuff.h
@@ -0,0 +1,52 @@
+/* Operations on network stuff.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef NETSTUFF_H
+#define NETSTUFF_H
+
+#include <string>
+
+/* Helper class to guarantee that we always call 'freeaddrinfo'.  */
+
+class scoped_free_addrinfo
+{
+public:
+  scoped_free_addrinfo (struct addrinfo *a);
+
+  ~scoped_free_addrinfo ();
+
+  DISABLE_COPY_AND_ASSIGN (scoped_free_addrinfo);
+
+private:
+  struct addrinfo *m_res;
+};
+
+/* Parse HOSTNAME (which is a string in the of "ADDR:PORT") and fill
+   in HOST_STR, PORT_STR and HINT accordingly.  */
+extern void parse_hostname_without_prefix (const char *hostname,
+					   std::string &host_str,
+					   std::string &port_str,
+					   struct addrinfo *hint);
+
+/* Parse HOSTNAME (which is a string in the form of
+   "[tcp[6]:|udp[6]:]ADDR:PORT") and fill in HOST_STR, PORT_STR and
+   HINT accordingly.  */
+extern void parse_hostname (const char *hostname, std::string &host_str,
+			    std::string &port_str, struct addrinfo *hint);
+
+#endif /* ! NETSTUFF_H */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 28f083f96e..7994204140 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -20496,16 +20496,27 @@ If you're using a serial line, you may want to give @value{GDBN} the
 @code{target} command.
 
 @item target remote @code{@var{host}:@var{port}}
+@itemx target remote @code{@var{@r{[}host@r{]}}:@var{port}}
 @itemx target remote @code{tcp:@var{host}:@var{port}}
+@itemx target remote @code{tcp:@var{@r{[}host@r{]}}:@var{port}}
+@itemx target remote @code{tcp4:@var{host}:@var{port}}
+@itemx target remote @code{tcp6:@var{host}:@var{port}}
+@itemx target remote @code{tcp6:@var{@r{[}host@r{]}}:@var{port}}
 @itemx target extended-remote @code{@var{host}:@var{port}}
+@itemx target extended-remote @code{@var{@r{[}host@r{]}}:@var{port}}
 @itemx target extended-remote @code{tcp:@var{host}:@var{port}}
+@itemx target extended-remote @code{tcp:@var{@r{[}host@r{]}}:@var{port}}
+@itemx target extended-remote @code{tcp4:@var{host}:@var{port}}
+@itemx target extended-remote @code{tcp6:@var{host}:@var{port}}
+@itemx target extended-remote @code{tcp6:@var{@r{[}host@r{]}}:@var{port}}
 @cindex @acronym{TCP} port, @code{target remote}
 Debug using a @acronym{TCP} connection to @var{port} on @var{host}.
-The @var{host} may be either a host name or a numeric @acronym{IP}
-address; @var{port} must be a decimal number.  The @var{host} could be
-the target machine itself, if it is directly connected to the net, or
-it might be a terminal server which in turn has a serial line to the
-target.
+The @var{host} may be either a host name, a numeric @acronym{IPv4}
+address, or a numeric @acronym{IPv6} address (with or without the
+square brackets to separate the address from the port); @var{port}
+must be a decimal number.  The @var{host} could be the target machine
+itself, if it is directly connected to the net, or it might be a
+terminal server which in turn has a serial line to the target.
 
 For example, to connect to port 2828 on a terminal server named
 @code{manyfarms}:
@@ -20514,6 +20525,24 @@ For example, to connect to port 2828 on a terminal server named
 target remote manyfarms:2828
 @end smallexample
 
+To connect to port 2828 on a terminal server whose address is
+@code{2001::f8ff::67cf}, you can either use the square bracket syntax:
+
+@smallexample
+target remote [2001::f8ff::67cf]:2828
+@end smallexample
+
+Or explicitly specify the @acronym{IPv6} protocol:
+
+@smallexample
+target remote tcp6:2001::f8ff::67cf:2828
+@end smallexample
+
+This last example may be confusing to the reader, because there is no
+visible separation between the hostname and the port number.
+Therefore, we recommend the user to provide @acronym{IPv6} addresses
+using square brackets for clarity.
+
 If your remote target is actually running on the same machine as your
 debugger session (e.g.@: a simulator for your target running on the
 same host), you can omit the hostname.  For example, to connect to
@@ -20527,7 +20556,16 @@ target remote :1234
 Note that the colon is still required here.
 
 @item target remote @code{udp:@var{host}:@var{port}}
+@itemx target remote @code{udp:@var{host}:@var{port}}
+@itemx target remote @code{udp:@var{@r{[}host@r{]}}:@var{port}}
+@itemx target remote @code{udp4:@var{host}:@var{port}}
+@itemx target remote @code{udp6:@var{@r{[}host@r{]}}:@var{port}}
+@itemx target extended-remote @code{udp:@var{host}:@var{port}}
 @itemx target extended-remote @code{udp:@var{host}:@var{port}}
+@itemx target extended-remote @code{udp:@var{@r{[}host@r{]}}:@var{port}}
+@itemx target extended-remote @code{udp4:@var{host}:@var{port}}
+@itemx target extended-remote @code{udp6:@var{host}:@var{port}}
+@itemx target extended-remote @code{udp6:@var{@r{[}host@r{]}}:@var{port}}
 @cindex @acronym{UDP} port, @code{target remote}
 Debug using @acronym{UDP} packets to @var{port} on @var{host}.  For example, to
 connect to @acronym{UDP} port 2828 on a terminal server named @code{manyfarms}:
diff --git a/gdb/gdbserver/Makefile.in b/gdb/gdbserver/Makefile.in
index 675faa4364..d65346a357 100644
--- a/gdb/gdbserver/Makefile.in
+++ b/gdb/gdbserver/Makefile.in
@@ -211,6 +211,7 @@ SFILES = \
 	$(srcdir)/common/job-control.c \
 	$(srcdir)/common/gdb_tilde_expand.c \
 	$(srcdir)/common/gdb_vecs.c \
+	$(srcdir)/common/netstuff.c \
 	$(srcdir)/common/new-op.c \
 	$(srcdir)/common/pathstuff.c \
 	$(srcdir)/common/print-utils.c \
@@ -253,6 +254,7 @@ OBS = \
 	common/format.o \
 	common/gdb_tilde_expand.o \
 	common/gdb_vecs.o \
+	common/netstuff.o \
 	common/new-op.o \
 	common/pathstuff.o \
 	common/print-utils.o \
diff --git a/gdb/gdbserver/gdbreplay.c b/gdb/gdbserver/gdbreplay.c
index c1a639069a..01b70d49f4 100644
--- a/gdb/gdbserver/gdbreplay.c
+++ b/gdb/gdbserver/gdbreplay.c
@@ -53,6 +53,7 @@
 
 #if USE_WIN32API
 #include <winsock2.h>
+#include <wspiapi.h>
 #endif
 
 #ifndef HAVE_SOCKLEN_T
@@ -175,56 +176,159 @@ remote_close (void)
 static void
 remote_open (char *name)
 {
-  if (!strchr (name, ':'))
+  char *last_colon = strrchr (name, ':');
+
+  if (last_colon == NULL)
     {
       fprintf (stderr, "%s: Must specify tcp connection as host:addr\n", name);
       fflush (stderr);
       exit (1);
     }
-  else
-    {
+
 #ifdef USE_WIN32API
-      static int winsock_initialized;
+  static int winsock_initialized;
 #endif
-      char *port_str;
-      int port;
-      struct sockaddr_in sockaddr;
-      socklen_t tmp;
-      int tmp_desc;
+  char *port_str;
+  int tmp;
+  int tmp_desc;
+  struct addrinfo hint;
+  struct addrinfo *ainfo;
+  char *orig_name = strdup (name);
+
+  struct prefix
+  {
+    /* The prefix to be parsed.  */
+    const char *str;
+
+    /* The address family.  */
+    int ai_family;
+
+    /* The socktype.  */
+    int ai_socktype;
+  };
+  static const struct prefix prefixes[]
+    = { { "udp:",  AF_UNSPEC, SOCK_DGRAM },
+	{ "tcp:",  AF_UNSPEC, SOCK_STREAM },
+	{ "udp4:", AF_INET,   SOCK_DGRAM },
+	{ "tcp4:", AF_INET,   SOCK_STREAM },
+	{ "udp6:", AF_INET6,  SOCK_DGRAM },
+	{ "tcp6:", AF_INET6,  SOCK_STREAM },
+	{ NULL, 0, 0 } };
+
+  memset (&hint, 0, sizeof (hint));
+  /* Assume no prefix will be passed, therefore we should use
+     AF_UNSPEC.  */
+  hint.ai_family = AF_UNSPEC;
+  hint.ai_socktype = SOCK_STREAM;
+  hint.ai_protocol = IPPROTO_TCP;
+
+  for (const struct prefix *p = prefixes; p->str != NULL; ++p)
+    if (strncmp (name, p->str, strlen (p->str)) == 0)
+      {
+	name += strlen (p->str);
+	hint.ai_family = p->ai_family;
+	hint.ai_socktype = p->ai_socktype;
+	hint.ai_protocol
+	  = p->ai_socktype == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
+	break;
+      }
 
-      port_str = strchr (name, ':');
+  if (hint.ai_family != AF_INET && *name == '[')
+    {
+      ++name;
+      port_str = strchr (name, ']');
+      if (port_str == NULL)
+	{
+	  fprintf (stderr, "Missing closing bracket on hostname: %s\n",
+		   orig_name);
+	  exit (1);
+	}
+      /* Skip closing bracket.  */
+      *port_str = '\0';
+      ++port_str;
+      if (*port_str != ':')
+	{
+	  fprintf (stderr, "Missing port number on hostname: %s\n",
+		   orig_name);
+	  exit (1);
+	}
+      hint.ai_family = AF_INET6;
+    }
+  else
+    port_str = last_colon;
 
-      port = atoi (port_str + 1);
+  /* Skip the colon.  */
+  *port_str = '\0';
+  ++port_str;
 
 #ifdef USE_WIN32API
-      if (!winsock_initialized)
-	{
-	  WSADATA wsad;
+  if (!winsock_initialized)
+    {
+      WSADATA wsad;
 
-	  WSAStartup (MAKEWORD (1, 0), &wsad);
-	  winsock_initialized = 1;
-	}
+      WSAStartup (MAKEWORD (1, 0), &wsad);
+      winsock_initialized = 1;
+    }
 #endif
 
-      tmp_desc = socket (PF_INET, SOCK_STREAM, 0);
-      if (tmp_desc == -1)
-	perror_with_name ("Can't open socket");
+  int r = getaddrinfo (name, port_str, &hint, &ainfo);
 
-      /* Allow rapid reuse of this port. */
-      tmp = 1;
-      setsockopt (tmp_desc, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp,
-		  sizeof (tmp));
+  if (r != 0)
+    {
+      fprintf (stderr, "%s:%s: cannot resolve name: %s\n",
+	       name, port_str, gai_strerror (r));
+      fflush (stderr);
+      exit (1);
+    }
 
-      sockaddr.sin_family = PF_INET;
-      sockaddr.sin_port = htons (port);
-      sockaddr.sin_addr.s_addr = INADDR_ANY;
+  struct addrinfo *p;
 
-      if (bind (tmp_desc, (struct sockaddr *) &sockaddr, sizeof (sockaddr))
-	  || listen (tmp_desc, 1))
-	perror_with_name ("Can't bind address");
+  for (p = ainfo; p != NULL; p = p->ai_next)
+    {
+      tmp_desc = socket (p->ai_family, p->ai_socktype, p->ai_protocol);
+
+      if (tmp_desc >= 0)
+	break;
+    }
+
+  if (p == NULL)
+    perror_with_name ("Cannot open socket");
+
+  /* Allow rapid reuse of this port. */
+  tmp = 1;
+  setsockopt (tmp_desc, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp,
+	      sizeof (tmp));
+
+  switch (p->ai_family)
+    {
+    case AF_INET:
+      ((struct sockaddr_in *) p->ai_addr)->sin_addr.s_addr = INADDR_ANY;
+      break;
+    case AF_INET6:
+      ((struct sockaddr_in6 *) p->ai_addr)->sin6_addr = in6addr_any;
+      break;
+    default:
+      fprintf (stderr, "Invalid 'ai_family' %d\n", p->ai_family);
+      exit (1);
+    }
+
+  if (bind (tmp_desc, p->ai_addr, p->ai_addrlen) != 0)
+    perror_with_name ("Can't bind address");
+
+  if (p->ai_socktype == SOCK_DGRAM)
+    remote_desc = tmp_desc;
+  else
+    {
+      struct sockaddr_storage sockaddr;
+      socklen_t sockaddrsize = sizeof (sockaddr);
+      char orig_host[64], orig_port[16];
+
+      if (listen (tmp_desc, 1) != 0)
+	perror_with_name ("Can't listen on socket");
+
+      remote_desc = accept (tmp_desc, (struct sockaddr *) &sockaddr,
+			    &sockaddrsize);
 
-      tmp = sizeof (sockaddr);
-      remote_desc = accept (tmp_desc, (struct sockaddr *) &sockaddr, &tmp);
       if (remote_desc == -1)
 	perror_with_name ("Accept failed");
 
@@ -239,6 +343,16 @@ remote_open (char *name)
       setsockopt (remote_desc, IPPROTO_TCP, TCP_NODELAY,
 		  (char *) &tmp, sizeof (tmp));
 
+      if (getnameinfo ((struct sockaddr *) &sockaddr, sockaddrsize,
+		       orig_host, sizeof (orig_host),
+		       orig_port, sizeof (orig_port),
+		       NI_NUMERICHOST | NI_NUMERICSERV) == 0)
+	{
+	  fprintf (stderr, "Remote debugging from host %s, port %s\n",
+		   orig_host, orig_port);
+	  fflush (stderr);
+	}
+
 #ifndef USE_WIN32API
       close (tmp_desc);		/* No longer need this */
 
@@ -254,8 +368,9 @@ remote_open (char *name)
   fcntl (remote_desc, F_SETFL, FASYNC);
 #endif
 
-  fprintf (stderr, "Replay logfile using %s\n", name);
+  fprintf (stderr, "Replay logfile using %s\n", orig_name);
   fflush (stderr);
+  free (orig_name);
 }
 
 static int
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index 3b5a459ae4..c8b5dcdbba 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -26,6 +26,8 @@
 #include "dll.h"
 #include "rsp-low.h"
 #include "gdbthread.h"
+#include "netstuff.h"
+#include "filestuff.h"
 #include <ctype.h>
 #if HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
@@ -63,6 +65,7 @@
 
 #if USE_WIN32API
 #include <winsock2.h>
+#include <wspiapi.h>
 #endif
 
 #if __QNX__
@@ -156,19 +159,18 @@ enable_async_notification (int fd)
 static int
 handle_accept_event (int err, gdb_client_data client_data)
 {
-  struct sockaddr_in sockaddr;
-  socklen_t tmp;
+  struct sockaddr_storage sockaddr;
+  socklen_t len = sizeof (sockaddr);
 
   if (debug_threads)
     debug_printf ("handling possible accept event\n");
 
-  tmp = sizeof (sockaddr);
-  remote_desc = accept (listen_desc, (struct sockaddr *) &sockaddr, &tmp);
+  remote_desc = accept (listen_desc, (struct sockaddr *) &sockaddr, &len);
   if (remote_desc == -1)
     perror_with_name ("Accept failed");
 
   /* Enable TCP keep alive process. */
-  tmp = 1;
+  socklen_t tmp = 1;
   setsockopt (remote_desc, SOL_SOCKET, SO_KEEPALIVE,
 	      (char *) &tmp, sizeof (tmp));
 
@@ -197,8 +199,19 @@ handle_accept_event (int err, gdb_client_data client_data)
   delete_file_handler (listen_desc);
 
   /* Convert IP address to string.  */
-  fprintf (stderr, "Remote debugging from host %s\n",
-	   inet_ntoa (sockaddr.sin_addr));
+  char orig_host[64], orig_port[16];
+
+  int r = getnameinfo ((struct sockaddr *) &sockaddr, len,
+		       orig_host, sizeof (orig_host),
+		       orig_port, sizeof (orig_port),
+		       NI_NUMERICHOST | NI_NUMERICSERV);
+
+  if (r != 0)
+    fprintf (stderr, _("Could not obtain remote address: %s\n"),
+	     gai_strerror (r));
+  else
+    fprintf (stderr, "Remote debugging from host %s, port %s\n", orig_host,
+	     orig_port);
 
   enable_async_notification (remote_desc);
 
@@ -222,14 +235,10 @@ handle_accept_event (int err, gdb_client_data client_data)
 void
 remote_prepare (const char *name)
 {
-  const char *port_str;
 #ifdef USE_WIN32API
   static int winsock_initialized;
 #endif
-  int port;
-  struct sockaddr_in sockaddr;
   socklen_t tmp;
-  char *port_end;
 
   remote_is_stdio = 0;
   if (strcmp (name, STDIO_CONNECTION_NAME) == 0)
@@ -242,17 +251,25 @@ remote_prepare (const char *name)
       return;
     }
 
-  port_str = strchr (name, ':');
-  if (port_str == NULL)
+  struct addrinfo hint;
+  struct addrinfo *ainfo;
+  std::string host_str, port_str;
+
+  memset (&hint, 0, sizeof (hint));
+  /* Assume no prefix will be passed, therefore we should use
+     AF_UNSPEC.  */
+  hint.ai_family = AF_UNSPEC;
+  hint.ai_socktype = SOCK_STREAM;
+  hint.ai_protocol = IPPROTO_TCP;
+
+  parse_hostname_without_prefix (name, host_str, port_str, &hint);
+
+  if (port_str.empty ())
     {
       transport_is_reliable = 0;
       return;
     }
 
-  port = strtoul (port_str + 1, &port_end, 10);
-  if (port_str[1] == '\0' || *port_end != '\0')
-    error ("Bad port argument: %s", name);
-
 #ifdef USE_WIN32API
   if (!winsock_initialized)
     {
@@ -263,8 +280,25 @@ remote_prepare (const char *name)
     }
 #endif
 
-  listen_desc = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
-  if (listen_desc == -1)
+  int r = getaddrinfo (host_str.c_str (), port_str.c_str (), &hint, &ainfo);
+
+  if (r != 0)
+    error (_("%s: cannot resolve name: %s"), name, gai_strerror (r));
+
+  scoped_free_addrinfo freeaddrinfo (ainfo);
+
+  struct addrinfo *iter;
+
+  for (iter = ainfo; iter != NULL; iter = iter->ai_next)
+    {
+      listen_desc = gdb_socket_cloexec (iter->ai_family, iter->ai_socktype,
+					iter->ai_protocol);
+
+      if (listen_desc >= 0)
+	break;
+    }
+
+  if (iter == NULL)
     perror_with_name ("Can't open socket");
 
   /* Allow rapid reuse of this port. */
@@ -272,14 +306,25 @@ remote_prepare (const char *name)
   setsockopt (listen_desc, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp,
 	      sizeof (tmp));
 
-  sockaddr.sin_family = PF_INET;
-  sockaddr.sin_port = htons (port);
-  sockaddr.sin_addr.s_addr = INADDR_ANY;
+  switch (iter->ai_family)
+    {
+    case AF_INET:
+      ((struct sockaddr_in *) iter->ai_addr)->sin_addr.s_addr = INADDR_ANY;
+      break;
+    case AF_INET6:
+      ((struct sockaddr_in6 *) iter->ai_addr)->sin6_addr = in6addr_any;
+      break;
+    default:
+      internal_error (__FILE__, __LINE__,
+		      _("Invalid 'ai_family' %d\n"), iter->ai_family);
+    }
 
-  if (bind (listen_desc, (struct sockaddr *) &sockaddr, sizeof (sockaddr))
-      || listen (listen_desc, 1))
+  if (bind (listen_desc, iter->ai_addr, iter->ai_addrlen) != 0)
     perror_with_name ("Can't bind address");
 
+  if (listen (listen_desc, 1) != 0)
+    perror_with_name ("Can't listen on socket");
+
   transport_is_reliable = 1;
 }
 
@@ -354,18 +399,24 @@ remote_open (const char *name)
 #endif /* USE_WIN32API */
   else
     {
-      int port;
-      socklen_t len;
-      struct sockaddr_in sockaddr;
-
-      len = sizeof (sockaddr);
-      if (getsockname (listen_desc,
-		       (struct sockaddr *) &sockaddr, &len) < 0
-	  || len < sizeof (sockaddr))
+      char listen_port[16];
+      struct sockaddr_storage sockaddr;
+      socklen_t len = sizeof (sockaddr);
+
+      if (getsockname (listen_desc, (struct sockaddr *) &sockaddr, &len) < 0)
 	perror_with_name ("Can't determine port");
-      port = ntohs (sockaddr.sin_port);
 
-      fprintf (stderr, "Listening on port %d\n", port);
+      int r = getnameinfo ((struct sockaddr *) &sockaddr, len,
+			   NULL, 0,
+			   listen_port, sizeof (listen_port),
+			   NI_NUMERICSERV);
+
+      if (r != 0)
+	fprintf (stderr, _("Can't obtain port where we are listening: %s"),
+		 gai_strerror (r));
+      else
+	fprintf (stderr, "Listening on port %s\n", listen_port);
+
       fflush (stderr);
 
       /* Register the event loop handler.  */
diff --git a/gdb/ser-tcp.c b/gdb/ser-tcp.c
index 23ef3b04b8..3d9fbd866f 100644
--- a/gdb/ser-tcp.c
+++ b/gdb/ser-tcp.c
@@ -25,6 +25,7 @@
 #include "cli/cli-decode.h"
 #include "cli/cli-setshow.h"
 #include "filestuff.h"
+#include "netstuff.h"
 
 #include <sys/types.h>
 
@@ -39,6 +40,7 @@
 
 #ifdef USE_WIN32API
 #include <winsock2.h>
+#include <wspiapi.h>
 #ifndef ETIMEDOUT
 #define ETIMEDOUT WSAETIMEDOUT
 #endif
@@ -158,166 +160,157 @@ wait_for_connect (struct serial *scb, unsigned int *polls)
 int
 net_open (struct serial *scb, const char *name)
 {
-  char hostname[100];
-  const char *port_str;
-  int n, port, tmp;
-  int use_udp;
-  struct hostent *hostent;
-  struct sockaddr_in sockaddr;
+  int n;
+  bool use_udp;
 #ifdef USE_WIN32API
   u_long ioarg;
 #else
   int ioarg;
 #endif
   unsigned int polls = 0;
+  struct addrinfo hint;
+  struct addrinfo *ainfo;
+  std::string host_str, port_str;
 
-  use_udp = 0;
-  if (startswith (name, "udp:"))
-    {
-      use_udp = 1;
-      name = name + 4;
-    }
-  else if (startswith (name, "tcp:"))
-    name = name + 4;
-
-  port_str = strchr (name, ':');
+  memset (&hint, 0, sizeof (hint));
+  /* Assume no prefix will be passed, therefore we should use
+     AF_UNSPEC.  */
+  hint.ai_family = AF_INET;
+  hint.ai_socktype = SOCK_STREAM;
+  hint.ai_protocol = IPPROTO_TCP;
 
-  if (!port_str)
-    error (_("net_open: No colon in host name!"));  /* Shouldn't ever
-						       happen.  */
+  parse_hostname (name, host_str, port_str, &hint);
 
-  tmp = std::min (port_str - name, (ptrdiff_t) sizeof hostname - 1);
-  strncpy (hostname, name, tmp);	/* Don't want colon.  */
-  hostname[tmp] = '\000';	/* Tie off host name.  */
-  port = atoi (port_str + 1);
+  if (port_str.empty ())
+    error (_("Missing port on hostname '%s'"), name);
 
-  /* Default hostname is localhost.  */
-  if (!hostname[0])
-    strcpy (hostname, "localhost");
+  int r = getaddrinfo (host_str.c_str (), port_str.c_str (), &hint, &ainfo);
 
-  hostent = gethostbyname (hostname);
-  if (!hostent)
+  if (r != 0)
     {
-      fprintf_unfiltered (gdb_stderr, "%s: unknown host\n", hostname);
+      fprintf_unfiltered (gdb_stderr, _("%s: cannot resolve name: %s\n"),
+			  name, gai_strerror (r));
       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));
+  scoped_free_addrinfo free_ainfo (ainfo);
 
- retry:
+  struct addrinfo *cur_ainfo;
 
-  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 (cur_ainfo = ainfo; cur_ainfo != NULL; cur_ainfo = cur_ainfo->ai_next)
+    {
+retry:
+      scb->fd = gdb_socket_cloexec (cur_ainfo->ai_family,
+				    cur_ainfo->ai_socktype,
+				    cur_ainfo->ai_protocol);
 
-  if (scb->fd == -1)
-    return -1;
-  
-  /* Set socket nonblocking.  */
-  ioarg = 1;
-  ioctl (scb->fd, FIONBIO, &ioarg);
+      if (scb->fd < 0)
+	continue;
 
-  /* Use Non-blocking connect.  connect() will return 0 if connected
-     already.  */
-  n = connect (scb->fd, (struct sockaddr *) &sockaddr, sizeof (sockaddr));
+      /* Set socket nonblocking.  */
+      ioarg = 1;
+      ioctl (scb->fd, FIONBIO, &ioarg);
 
-  if (n < 0)
-    {
+      /* Use Non-blocking connect.  connect() will return 0 if connected
+	 already.  */
+      n = connect (scb->fd, cur_ainfo->ai_addr, cur_ainfo->ai_addrlen);
+
+      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);
+	      goto retry;
+	    }
 
-      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;
+	      )
+	    {
+	      errno = err;
+	      continue;
+	    }
+
+	  /* Looks like we need to wait for the connect.  */
+	  do 
+	    {
+	      n = wait_for_connect (scb, &polls);
+	    } 
+	  while (n == 0);
+	  if (n < 0)
+	    continue;
 	}
 
-      /* 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 "char *"
-       is OK everywhere, since in C++ any data pointer type can be
-       implicitly converted to "void *".  */
-    res = getsockopt (scb->fd, SOL_SOCKET, SO_ERROR, (char *) &err, &len);
-    if (res < 0 || err)
+      /* 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 = sizeof (err);
+
+	/* On Windows, the fourth parameter to getsockopt is a "char *";
+	   on UNIX systems it is generally "void *".  The cast to "char *"
+	   is OK everywhere, since in C++ any data pointer type can be
+	   implicitly converted to "void *".  */
+	res = getsockopt (scb->fd, SOL_SOCKET, SO_ERROR, (char *) &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;
+		&& wait_for_connect (NULL, &polls) >= 0)
+	      {
+		close (scb->fd);
+		goto retry;
+	      }
+	    if (err)
+	      errno = err;
+	    continue;
 	  }
-	if (err)
-	  errno = err;
-	net_close (scb);
-	return -1;
       }
-  } 
+      break;
+    }
+
+  if (cur_ainfo == NULL)
+    {
+      net_close (scb);
+      return -1;
+    }
 
   /* Turn off nonblocking.  */
   ioarg = 0;
   ioctl (scb->fd, FIONBIO, &ioarg);
 
-  if (use_udp == 0)
+  if (cur_ainfo->ai_socktype == IPPROTO_TCP)
     {
       /* Disable Nagle algorithm.  Needed in some cases.  */
-      tmp = 1;
+      int tmp = 1;
+
       setsockopt (scb->fd, IPPROTO_TCP, TCP_NODELAY,
-		  (char *)&tmp, sizeof (tmp));
+		  (char *) &tmp, sizeof (tmp));
     }
 
 #ifdef SIGPIPE
diff --git a/gdb/testsuite/README b/gdb/testsuite/README
index 4475ac21a9..37f676d252 100644
--- a/gdb/testsuite/README
+++ b/gdb/testsuite/README
@@ -259,6 +259,13 @@ This make (not runtest) variable is used to specify whether the
 testsuite preloads the read1.so library into expect.  Any non-empty
 value means true.  See "Race detection" below.
 
+GDB_TEST_IPV6
+
+This variable makes the tests related to GDBserver to run with IPv6
+local addresses, instead of IPv4.  This is useful to test the IPv6
+support, and only makes sense for the native-gdbserver and the
+native-extended-gdbserver boards.
+
 Race detection
 **************
 
diff --git a/gdb/testsuite/boards/gdbserver-base.exp b/gdb/testsuite/boards/gdbserver-base.exp
index 52ad698b3f..f738c90e8e 100644
--- a/gdb/testsuite/boards/gdbserver-base.exp
+++ b/gdb/testsuite/boards/gdbserver-base.exp
@@ -32,3 +32,8 @@ set_board_info gdb,nofileio 1
 set_board_info gdb,predefined_tsv "\\\$trace_timestamp"
 
 set GDBFLAGS "${GDBFLAGS} -ex \"set auto-connect-native-target off\""
+
+# Helper function that returns a local IPv6 address to connect to.
+proc get_comm_port_localhost_ipv6 { port } {
+    return "\\\[::1\\\]:${port}"
+}
diff --git a/gdb/testsuite/boards/native-extended-gdbserver.exp b/gdb/testsuite/boards/native-extended-gdbserver.exp
index df949994fd..9ec053c9d6 100644
--- a/gdb/testsuite/boards/native-extended-gdbserver.exp
+++ b/gdb/testsuite/boards/native-extended-gdbserver.exp
@@ -24,7 +24,12 @@ load_generic_config "extended-gdbserver"
 load_board_description "gdbserver-base"
 load_board_description "local-board"
 
-set_board_info sockethost "localhost:"
+if { [info exists GDB_TEST_IPV6] } {
+    set_board_info sockethost "tcp6:\[::1\]:"
+    set_board_info gdbserver,get_comm_port get_comm_port_localhost_ipv6
+} else {
+    set_board_info sockethost "localhost:"
+}
 
 # We will be using the extended GDB remote protocol.
 set_board_info gdb_protocol "extended-remote"
diff --git a/gdb/testsuite/boards/native-gdbserver.exp b/gdb/testsuite/boards/native-gdbserver.exp
index ef9316007e..d491aa451a 100644
--- a/gdb/testsuite/boards/native-gdbserver.exp
+++ b/gdb/testsuite/boards/native-gdbserver.exp
@@ -30,7 +30,12 @@ set_board_info gdb,do_reload_on_run 1
 # There's no support for argument-passing (yet).
 set_board_info noargs 1
 
-set_board_info sockethost "localhost:"
+if { [info exists GDB_TEST_IPV6] } {
+    set_board_info sockethost "tcp6:\[::1\]:"
+    set_board_info gdbserver,get_comm_port get_comm_port_localhost_ipv6
+} else {
+    set_board_info sockethost "localhost:"
+}
 set_board_info use_gdb_stub 1
 set_board_info exit_is_reliable 1
 
diff --git a/gdb/testsuite/gdb.server/run-without-local-binary.exp b/gdb/testsuite/gdb.server/run-without-local-binary.exp
index 1665ca9912..6ba3e711d9 100644
--- a/gdb/testsuite/gdb.server/run-without-local-binary.exp
+++ b/gdb/testsuite/gdb.server/run-without-local-binary.exp
@@ -53,7 +53,7 @@ save_vars { GDBFLAGS } {
     set use_gdb_stub 0
 
     gdb_test "target ${gdbserver_protocol} ${gdbserver_gdbport}" \
-	"Remote debugging using $gdbserver_gdbport" \
+	"Remote debugging using [string_to_regexp $gdbserver_gdbport]" \
 	"connect to gdbserver"
 
     gdb_test "run" \
-- 
2.14.3


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]