Do the proper hack for the (Open)BSD getaddrinfo quirk.

From evutil.c:

   Some older BSDs (like OpenBSD up to 4.6) used to believe that
   giving a numeric port without giving an ai_socktype was verboten.
   We test for this so we can apply an appropriate workaround.  If it
   turns out that the bug is present, then:

    - If nodename==NULL and servname is numeric, we build an answer
      ourselves using evutil_getaddrinfo_common().

    - If nodename!=NULL and servname is numeric, then we set
      servname=NULL when calling getaddrinfo, and post-process the
      result to set the ports on it.

   We test for this bug at runtime, since otherwise we can't have the
   same binary run on multiple BSD versions.

svn:r1550
This commit is contained in:
Nick Mathewson 2009-11-18 23:18:55 +00:00
parent 07ce7f9991
commit f070a4aed2

186
evutil.c
View File

@ -490,6 +490,18 @@ evutil_addrinfo_append(struct evutil_addrinfo *first,
return first; return first;
} }
static int
parse_numeric_servname(const char *servname)
{
int n;
char *endptr=NULL;
n = (int) strtol(servname, &endptr, 10);
if (n>=0 && n <= 65535 && servname[0] && endptr && !endptr[0])
return n;
else
return -1;
}
/** Parse a service name in 'servname', which can be a decimal port. /** Parse a service name in 'servname', which can be a decimal port.
* Return the port number, or -1 on error. * Return the port number, or -1 on error.
*/ */
@ -497,10 +509,8 @@ static int
evutil_parse_servname(const char *servname, const char *protocol, evutil_parse_servname(const char *servname, const char *protocol,
const struct evutil_addrinfo *hints) const struct evutil_addrinfo *hints)
{ {
int n; int n = parse_numeric_servname(servname);
char *endptr=NULL; if (n>=0)
n = (int) strtol(servname, &endptr, 10);
if (n>=0 && n <= 65535 && servname[0] && endptr && !endptr[0])
return n; return n;
#if defined(_EVENT_HAVE_GETSERVBYNAME) || defined(WIN32) #if defined(_EVENT_HAVE_GETSERVBYNAME) || defined(WIN32)
if (!(hints->ai_flags & EVUTIL_AI_NUMERICSERV)) { if (!(hints->ai_flags & EVUTIL_AI_NUMERICSERV)) {
@ -826,59 +836,146 @@ evutil_adjust_hints_for_addrconfig(struct evutil_addrinfo *hints)
} }
} }
#ifdef USE_NATIVE_GETADDRINFO
/* Some older BSDs (like OpenBSD up to 4.6) used to believe that
giving a numeric port without giving an ai_socktype was verboten.
We test for this so we can apply an appropriate workaround. If it
turns out that the bug is present, then:
- If nodename==NULL and servname is numeric, we build an answer
ourselves using evutil_getaddrinfo_common().
- If nodename!=NULL and servname is numeric, then we set
servname=NULL when calling getaddrinfo, and post-process the
result to set the ports on it.
We test for this bug at runtime, since otherwise we can't have the
same binary run on multiple BSD versions.
*/
static int
need_numeric_port_hack(void)
{
static int tested=0;
static int need_hack=0;
int r, r2;
struct evutil_addrinfo *ai=NULL, *ai2=NULL;
struct evutil_addrinfo hints;
if (tested)
return need_hack;
memset(&hints,0,sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_flags =
#ifdef AI_NUMERICHOST
AI_NUMERICHOST |
#endif
#ifdef AI_NUMERICSERV
AI_NUMERICSERV |
#endif
0;
r = getaddrinfo("1.2.3.4", "80", &hints, &ai);
hints.ai_socktype = SOCK_STREAM;
r2 = getaddrinfo("1.2.3.4", "80", &hints, &ai2);
if (r2 == 0 && r != 0) {
need_hack=1;
}
if (ai)
freeaddrinfo(ai);
if (ai2)
freeaddrinfo(ai2);
tested = 1;
return need_hack;
}
static void
apply_numeric_port_hack(int port, struct evutil_addrinfo **ai)
{
/* Now we run through the list and set the ports on all of the
* results where ports */
for ( ; *ai; ai = &(*ai)->ai_next) {
struct sockaddr *sa = (*ai)->ai_addr;
if (sa && sa->sa_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in*)sa;
sin->sin_port = htons(port);
} else if (sa && sa->sa_family == AF_INET6) {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)sa;
sin6->sin6_port = htons(port);
} else {
/* A numeric port makes no sense here; remove this one
* from the list. */
struct evutil_addrinfo *victim = *ai;
*ai = victim->ai_next;
victim->ai_next = NULL;
freeaddrinfo(victim);
}
}
}
#endif
int int
evutil_getaddrinfo(const char *nodename, const char *servname, evutil_getaddrinfo(const char *nodename, const char *servname,
const struct evutil_addrinfo *hints_in, struct evutil_addrinfo **res) const struct evutil_addrinfo *hints_in, struct evutil_addrinfo **res)
{ {
#ifdef USE_NATIVE_GETADDRINFO #ifdef USE_NATIVE_GETADDRINFO
struct evutil_addrinfo hints; struct evutil_addrinfo hints;
int portnum=-1, need_np_hack, err;
if (hints_in) { if (hints_in) {
memcpy(&hints, hints_in, sizeof(hints)); memcpy(&hints, hints_in, sizeof(hints));
} else {
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
}
#ifndef AI_ADDRCONFIG #ifndef AI_ADDRCONFIG
/* Not every system has AI_ADDRCONFIG, so fake it. */ /* Not every system has AI_ADDRCONFIG, so fake it. */
if (hints.ai_family == PF_UNSPEC && if (hints.ai_family == PF_UNSPEC &&
(hints.ai_flags & EVUTIL_AI_ADDRCONFIG)) { (hints.ai_flags & EVUTIL_AI_ADDRCONFIG)) {
evutil_adjust_hints_for_addrconfig(&hints); evutil_adjust_hints_for_addrconfig(&hints);
} }
#endif #endif
#if 0 #ifndef AI_NUMERICSERV
/* XXXX This is now done by the call below. */ /* Not every system has AI_NUMERICSERV, so fake it. */
/* Not every system has AI_NUMERICSERV, so fake it. */ if (hints.ai_flags & EVUTIL_AI_NUMERICSERV) {
if (hints.ai_flags & EVUTIL_AI_NUMERICSERV) { if (servname && parse_numeric_servname(servname)<0)
if (evutil_parse_servname(servname, return EVUTIL_EAI_NONAME;
NULL, &hints) < 0) }
return EVUTIL_EAI_NONAME;
}
#endif #endif
/* Enough operating systems handle enough common non-resolve /* Enough operating systems handle enough common non-resolve
* cases here weirdly enough that we are better off just * cases here weirdly enough that we are better off just
* overriding them. For example: * overriding them. For example:
* - Some older BSDs used to believe that giving a numeric *
* port without giving an ai_socktype was verboten. * - Windows doesn't like to infer the protocol from the
* (XXX we don't yet handle the general case of this.) * socket type, or fill in socket or protocol types much at
* * all. It also seems to do its own broken implicit
* - Windows doesn't like to infer the protocol from the * always-on version of AI_ADDRCONFIG that keeps it from
* socket type, or fill in socket or protocol types much at * ever resolving even a literal IPv6 address when
* all. It also seems to do its own broken implicit * ai_addrtype is PF_UNSPEC.
* always-on version of AI_ADDRCONFIG that keeps it from */
* ever resolving even a literal IPv6 address when #ifdef WIN32
* ai_addrtype is PF_UNSPEC. {
*/ int tmp_port;
{ err = evutil_getaddrinfo_common(nodename,servname,&hints,
int err, port; res, &tmp_port);
err = evutil_getaddrinfo_common(nodename,servname,&hints, if (err == 0 ||
res, &port); err == EVUTIL_EAI_MEMORY ||
if (err == 0 || err == EVUTIL_EAI_NONAME)
err == EVUTIL_EAI_MEMORY || return err;
err == EVUTIL_EAI_NONAME) /* If we make it here, the system getaddrinfo can
return err; * have a crack at it. */
/* If we make it here, the system getaddrinfo can }
* have a crack at it. */ #endif
}
/* See documentation for need_numeric_port_hack above.*/
need_np_hack = need_numeric_port_hack() && servname && !hints.ai_socktype
&& ((portnum=parse_numeric_servname(servname)) >= 0);
if (need_np_hack) {
if (!nodename)
return evutil_getaddrinfo_common(
NULL,servname,&hints, res, &portnum);
servname = NULL;
} }
/* Make sure that we didn't actually steal any AI_FLAGS values that /* Make sure that we didn't actually steal any AI_FLAGS values that
@ -893,7 +990,10 @@ evutil_getaddrinfo(const char *nodename, const char *servname,
/* Clear any flags that only libevent understands. */ /* Clear any flags that only libevent understands. */
hints.ai_flags &= ~ALL_NONNATIVE_AI_FLAGS; hints.ai_flags &= ~ALL_NONNATIVE_AI_FLAGS;
return getaddrinfo(nodename, servname, &hints, res); err = getaddrinfo(nodename, servname, &hints, res);
if (need_np_hack)
apply_numeric_port_hack(portnum, res);
return err;
#else #else
int port=0, err; int port=0, err;
struct hostent *ent = NULL; struct hostent *ent = NULL;