1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-03-21 14:18:38 -05:00

Reorganise sk_namelookup (on both platforms).

I just tried to trace through the Windows version's control flow in
response to a confusing bug report, and found that the control flow
itself was so confusing I couldn't make sense of it. Why are we
choosing between getaddrinfo and gethostbyname via #ifndef NO_IPV6,
then re-converging control flow and diverging a second time to report
the error?

So I rewrote the whole thing to have completely separate sections of
code dealing with the three resolution strategies, each with its own
dedicated error reporting system. And then I checked the Unix version
and found it was about as confusing, so I rewrote that too in the same
style. Now the two are mostly the same, except for details: Unix has
an override at the top for a Unix socket pathname, Windows has to cope
with getaddrinfo maybe not being found at run time (so the other cases
aren't in the #else clause), and Windows uses the same error reporting
for both lookup functions whereas Unix has to use the appropriate
gai_strerror or hstrerror.
This commit is contained in:
Simon Tatham 2022-04-29 08:05:39 +01:00
parent 67204ffd0b
commit e22df74545
2 changed files with 148 additions and 185 deletions

View File

@ -184,103 +184,89 @@ void sk_cleanup(void)
} }
} }
SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family) SockAddr *sk_namelookup(const char *host, char **canonicalname,
int address_family)
{ {
*canonicalname = NULL;
if (host[0] == '/') { if (host[0] == '/') {
*canonicalname = dupstr(host); *canonicalname = dupstr(host);
return unix_sock_addr(host); return unix_sock_addr(host);
} }
SockAddr *ret = snew(SockAddr); SockAddr *addr = snew(SockAddr);
memset(addr, 0, sizeof(SockAddr));
addr->superfamily = UNRESOLVED;
addr->refcount = 1;
#ifndef NO_IPV6 #ifndef NO_IPV6
/*
* Use getaddrinfo, as long as it's available. This should handle
* both IPv4 and IPv6 address literals, and hostnames, in one
* unified API.
*/
{
struct addrinfo hints; struct addrinfo hints;
int err; memset(&hints, 0, sizeof(hints));
#else
unsigned long a;
struct hostent *h = NULL;
int n;
#endif
strbuf *realhost = strbuf_new();
/* Clear the structure and default to IPv4. */
memset(ret, 0, sizeof(SockAddr));
ret->superfamily = UNRESOLVED;
ret->error = NULL;
ret->refcount = 1;
#ifndef NO_IPV6
hints.ai_flags = AI_CANONNAME;
hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
address_family == ADDRTYPE_IPV6 ? AF_INET6 : address_family == ADDRTYPE_IPV6 ? AF_INET6 :
AF_UNSPEC); AF_UNSPEC);
hints.ai_flags = AI_CANONNAME;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_addrlen = 0;
hints.ai_addr = NULL;
hints.ai_canonname = NULL;
hints.ai_next = NULL;
{
char *trimmed_host = host_strduptrim(host); /* strip [] on literals */
err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais);
sfree(trimmed_host);
}
if (err != 0) {
ret->error = gai_strerror(err);
strbuf_free(realhost);
return ret;
}
ret->superfamily = IP;
if (ret->ais->ai_canonname != NULL) /* strip [] on IPv6 address literals */
put_fmt(realhost, "%s", ret->ais->ai_canonname); char *trimmed_host = host_strduptrim(host);
int err = getaddrinfo(trimmed_host, NULL, &hints, &addr->ais);
sfree(trimmed_host);
if (addr->ais) {
addr->superfamily = IP;
if (addr->ais->ai_canonname)
*canonicalname = dupstr(addr->ais->ai_canonname);
else else
put_fmt(realhost, "%s", host); *canonicalname = dupstr(host);
#else
if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) {
/*
* Otherwise use the IPv4-only gethostbyname... (NOTE:
* we don't use gethostbyname as a fallback!)
*/
if (ret->superfamily == UNRESOLVED) {
/*debug("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host); */
if ( (h = gethostbyname(host)) )
ret->superfamily = IP;
}
if (ret->superfamily == UNRESOLVED) {
ret->error = (h_errno == HOST_NOT_FOUND ||
h_errno == NO_DATA ||
h_errno == NO_ADDRESS ? "Host does not exist" :
h_errno == TRY_AGAIN ?
"Temporary name service failure" :
"gethostbyname: unknown error");
strbuf_free(realhost);
return ret;
}
/* This way we are always sure the h->h_name is valid :) */
strbuf_clear(realhost);
put_fmt(realhost, "%s", h->h_name);
for (n = 0; h->h_addr_list[n]; n++);
ret->addresses = snewn(n, unsigned long);
ret->naddresses = n;
for (n = 0; n < ret->naddresses; n++) {
memcpy(&a, h->h_addr_list[n], sizeof(a));
ret->addresses[n] = ntohl(a);
}
} else { } else {
/* addr->error = gai_strerror(err);
* This must be a numeric IPv4 address because it caused a
* success return from inet_addr.
*/
ret->superfamily = IP;
strbuf_clear(realhost);
put_fmt(realhost, "%s", host);
ret->addresses = snew(unsigned long);
ret->naddresses = 1;
ret->addresses[0] = ntohl(a);
} }
return addr;
}
#else
/*
* Failing that (if IPv6 support was not compiled in), try the
* old-fashioned approach, which is to start by manually checking
* for an IPv4 literal and then use gethostbyname.
*/
unsigned long a = inet_addr(host);
if (a != (unsigned long) INADDR_NONE) {
addr->addresses = snew(unsigned long);
addr->naddresses = 1;
addr->addresses[0] = ntohl(a);
addr->superfamily = IP;
*canonicalname = dupstr(host);
return addr;
}
struct hostent *h = gethostbyname(host);
if (h) {
addr->superfamily = IP;
size_t n;
for (n = 0; h->h_addr_list[n]; n++);
addr->addresses = snewn(n, unsigned long);
addr->naddresses = n;
for (n = 0; n < addr->naddresses; n++) {
uint32_t a;
memcpy(&a, h->h_addr_list[n], sizeof(a));
addr->addresses[n] = ntohl(a);
}
*canonicalname = dupstr(h->h_name);
} else {
addr->error = hstrerror(h_errno);
}
return addr;
#endif #endif
*canonicalname = strbuf_to_str(realhost);
return ret;
} }
SockAddr *sk_nonamelookup(const char *host) SockAddr *sk_nonamelookup(const char *host)

View File

@ -452,118 +452,95 @@ const char *winsock_error_string(int error)
return win_strerror(error); return win_strerror(error);
} }
static inline const char *namelookup_strerror(DWORD err)
{
/* PuTTY has traditionally translated a few of the likely error
* messages into more concise strings than the standard Windows ones */
return (err == WSAENETDOWN ? "Network is down" :
err == WSAHOST_NOT_FOUND ? "Host does not exist" :
err == WSATRY_AGAIN ? "Host not found" :
win_strerror(err));
}
SockAddr *sk_namelookup(const char *host, char **canonicalname, SockAddr *sk_namelookup(const char *host, char **canonicalname,
int address_family) int address_family)
{ {
SockAddr *ret = snew(SockAddr); *canonicalname = NULL;
unsigned long a;
char realhost[8192];
int hint_family;
/* Default to IPv4. */ SockAddr *addr = snew(SockAddr);
hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : memset(addr, 0, sizeof(SockAddr));
#ifndef NO_IPV6 addr->superfamily = UNRESOLVED;
address_family == ADDRTYPE_IPV6 ? AF_INET6 : addr->refcount = 1;
#endif
AF_UNSPEC);
/* Clear the structure and default to IPv4. */
memset(ret, 0, sizeof(SockAddr));
#ifndef NO_IPV6
ret->ais = NULL;
#endif
ret->addresses = NULL;
ret->superfamily = UNRESOLVED;
ret->refcount = 1;
*realhost = '\0';
if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) {
struct hostent *h = NULL;
int err = 0;
#ifndef NO_IPV6 #ifndef NO_IPV6
/* /*
* Use getaddrinfo when it's available * Use getaddrinfo, as long as it's available. This should handle
* both IPv4 and IPv6 address literals, and hostnames, in one
* unified API.
*/ */
if (p_getaddrinfo) { if (p_getaddrinfo) {
struct addrinfo hints; struct addrinfo hints;
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
hints.ai_family = hint_family; hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
address_family == ADDRTYPE_IPV6 ? AF_INET6 :
AF_UNSPEC);
hints.ai_flags = AI_CANONNAME; hints.ai_flags = AI_CANONNAME;
{ hints.ai_socktype = SOCK_STREAM;
/* strip [] on IPv6 address literals */ /* strip [] on IPv6 address literals */
char *trimmed_host = host_strduptrim(host); char *trimmed_host = host_strduptrim(host);
err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); int err = p_getaddrinfo(trimmed_host, NULL, &hints, &addr->ais);
sfree(trimmed_host); sfree(trimmed_host);
}
if (err == 0)
ret->superfamily = IP;
} else
#endif
{
/*
* Otherwise use the IPv4-only gethostbyname...
* (NOTE: we don't use gethostbyname as a fallback!)
*/
if ( (h = p_gethostbyname(host)) )
ret->superfamily = IP;
else
err = p_WSAGetLastError();
}
if (ret->superfamily != IP) { if (addr->ais) {
ret->error = (err == WSAENETDOWN ? "Network is down" : addr->superfamily = IP;
err == WSAHOST_NOT_FOUND ? "Host does not exist" : if (addr->ais->ai_canonname)
err == WSATRY_AGAIN ? "Host not found" : *canonicalname = dupstr(addr->ais->ai_canonname);
win_strerror(err)); else
*canonicalname = dupstr(host);
} else { } else {
ret->error = NULL; addr->error = namelookup_strerror(err);
}
#ifndef NO_IPV6 return addr;
/* If we got an address info use that... */
if (ret->ais) {
/* Are we in IPv4 fallback mode? */
/* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */
if (ret->ais->ai_family == AF_INET)
memcpy(&a,
(char *) &((SOCKADDR_IN *) ret->ais->
ai_addr)->sin_addr, sizeof(a));
if (ret->ais->ai_canonname)
strncpy(realhost, ret->ais->ai_canonname, lenof(realhost));
else
strncpy(realhost, host, lenof(realhost));
} }
/* We used the IPv4-only gethostbyname()... */
else
#endif #endif
{
int n; /*
* Failing that (if IPv6 support was not compiled in, or if
* getaddrinfo turned out to be unavailable at run time), try the
* old-fashioned approach, which is to start by manually checking
* for an IPv4 literal and then use gethostbyname.
*/
unsigned long a = p_inet_addr(host);
if (a != (unsigned long) INADDR_NONE) {
addr->addresses = snew(unsigned long);
addr->naddresses = 1;
addr->addresses[0] = p_ntohl(a);
addr->superfamily = IP;
*canonicalname = dupstr(host);
return addr;
}
struct hostent *h = p_gethostbyname(host);
if (h) {
addr->superfamily = IP;
size_t n;
for (n = 0; h->h_addr_list[n]; n++); for (n = 0; h->h_addr_list[n]; n++);
ret->addresses = snewn(n, unsigned long); addr->addresses = snewn(n, unsigned long);
ret->naddresses = n; addr->naddresses = n;
for (n = 0; n < ret->naddresses; n++) { for (n = 0; n < addr->naddresses; n++) {
uint32_t a;
memcpy(&a, h->h_addr_list[n], sizeof(a)); memcpy(&a, h->h_addr_list[n], sizeof(a));
ret->addresses[n] = p_ntohl(a); addr->addresses[n] = p_ntohl(a);
}
memcpy(&a, h->h_addr, sizeof(a));
/* This way we are always sure the h->h_name is valid :) */
strncpy(realhost, h->h_name, sizeof(realhost));
}
} }
*canonicalname = dupstr(h->h_name);
} else { } else {
/* DWORD err = p_WSAGetLastError();
* This must be a numeric IPv4 address because it caused a addr->error = namelookup_strerror(err);
* success return from inet_addr.
*/
ret->addresses = snewn(1, unsigned long);
ret->naddresses = 1;
ret->addresses[0] = p_ntohl(a);
ret->superfamily = IP;
strncpy(realhost, host, sizeof(realhost));
} }
realhost[lenof(realhost)-1] = '\0'; return addr;
*canonicalname = dupstr(realhost);
return ret;
} }
static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name) static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name)