From patchwork Wed May 23 18:57:19 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sergio Durigan Junior X-Patchwork-Id: 27469 Received: (qmail 12586 invoked by alias); 23 May 2018 18:57:35 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 12568 invoked by uid 89); 23 May 2018 18:57:33 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-25.7 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=listen, 36, Connection X-HELO: mx1.redhat.com Received: from mx3-rdu2.redhat.com (HELO mx1.redhat.com) (66.187.233.73) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Wed, 23 May 2018 18:57:28 +0000 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 680647C6A9; Wed, 23 May 2018 18:57:27 +0000 (UTC) Received: from psique.yyz.redhat.com (unused-10-15-17-196.yyz.redhat.com [10.15.17.196]) by smtp.corp.redhat.com (Postfix) with ESMTP id EF0ED6F9D9; Wed, 23 May 2018 18:57:21 +0000 (UTC) From: Sergio Durigan Junior To: GDB Patches Cc: Pedro Alves , Eli Zaretskii , Jan Kratochvil , Paul Fertser , Tsutomu Seki , Sergio Durigan Junior Subject: [PATCH] Implement IPv6 support for GDB/gdbserver Date: Wed, 23 May 2018 14:57:19 -0400 Message-Id: <20180523185719.22832-1-sergiodj@redhat.com> X-IsSubscribed: yes 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 Jan Kratochvil Paul Fertser Tsutomu Seki * 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 Jan Kratochvil Paul Fertser Tsutomu Seki * 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 Jan Kratochvil Paul Fertser Tsutomu Seki * 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 Jan Kratochvil Paul Fertser Tsutomu Seki * 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 . */ + +#include "common-defs.h" +#include "netstuff.h" + +#ifdef USE_WIN32API +#include +#include +#else +#include +#include +#include +#include +#include +#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 . */ + +#ifndef NETSTUFF_H +#define NETSTUFF_H + +#include + +/* 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 +#include #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 #if HAVE_SYS_IOCTL_H #include @@ -63,6 +65,7 @@ #if USE_WIN32API #include +#include #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 @@ -39,6 +40,7 @@ #ifdef USE_WIN32API #include +#include #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" \