Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 30 Oct 2014 05:05:45 +1100 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        net@freebsd.org
Subject:   inet_pton() broken
Message-ID:  <20141030024343.X1825@besplex.bde.org>

next in thread | raw e-mail | index | archive | help
inet_pton() has regressed to a primitive form that doesn't support hex or
octal, at least for ipv4.  This breaks at least gethostbyname() on hosts
in /etc/hosts.

/etc/hosts is still documented as supporting the general form and as
"using" [sic] inet_addr(3):

%    Network addresses are specified in the conventional ``.'' (dot) notation
%    using the inet_addr(3) routine from the Internet address manipulation
%    library, inet(3).  Host names may contain any printable character other
%    than a field delimiter, newline, or comment character.

This documentation hasn't changed since 4.4BSD.  4.4BSD doesn't even
have inet_pton().  It actually uses inet_aton() in gethostbyname(),
but inet_aton() is just a inet_addr(3) with a modified API.  FreeBSD
has used inet_pton() in gethostent() for a long time.  It used to work.

The format of inet_pton() is undocumented in FreeBSD, except to say that
it doesn't support shortened addresses:

%    The inet_pton() function converts a presentation format address (that is,
%    printable form as held in a character string) to network format (usually
%    a struct in_addr or some other internal binary representation, in network
%    byte order).  It returns 1 if the address was valid for the specified
%    address family, or 0 if the address was not parseable in the specified
%    address family, or -1 if some system error occurred (in which case errno
%    will have been set).  This function is presently valid for AF_INET and
%    AF_INET6.
% 
% STANDARDS
%    The inet_ntop() and inet_pton() functions conform to X/Open Networking
%    Services Issue 5.2 (``XNS5.2'').  Note that inet_pton() does not accept
%    1-, 2-, or 3-part dotted addresses; all four parts must be specified and
%    are interpreted only as decimal values.  This is a narrower input set
%    than that accepted by inet_aton().

POSIX.1-2001 specifies both in_addr() and inet_pton(), but not inet_aton().
Unlike the above, it says what the format for inet_pton() is -- for ipv4,
it is just decimal dotted with all 4 components required.

So the bug is in gethostbyname() still using inet_pton() after it regressed
to be broken to X/Open/POSIX spec.

Many programs don't have this bug.  The bug broke ifconfig on hosts by
name with the names in /etc/hosts (I use some small octal numbers there
to line up fields).  It was confusing for ifconfig to work for the
same addresses that it doesn't work on, then the addresses are given
on the command line instead of in /etc/hosts.

In /usr/src/*bin, there are now 61 references to inet_pton(), 28 to
inet_aton() and 18 to inet_ntoa().  In a 10 year old soure tree where
inet_pton() was not broken to spec, there were 35 to inet_pton(), 26 to
inet_aton() and 30 to inet_addr().  It looks like there are just a few
more ipv6 applications and most inet_aton()s are unchanged, but half
the inet_addr()s were changed to the incompatible inet_pton() instead
of to the compatible inet_aton().

Indeed, many of the new inet_pton()'s are in mountd, nfsd and rpc.statd.
This might break /etc/exports files like /etc/hosts.  The format of
addresses is more or less documented in exports(5) as being the
inet_addr(3) format for ipv4 but more restricted for ipv6:

%    The third component of a line specifies the host set to which the line
%    applies.  The set may be specified in three ways.  The first way is to
%    list the host name(s) separated by white space.  (Standard Internet
%    ``dot'' addresses may be used in place of names.)  The second way is to
% 
% EXAMPLES
%          ...
%          /u -maproot=bin: -network 131.104.48 -mask 255.255.255.0

Network addresses are special and use the reduced format.  This cannot
be parsed by inet_pton().  This probably still works, because there is
an RFC related to the bug I am reporting, and the code follows the RFC
to avoid regressions in the ipv4 case: mountd parses -network <addr>
by checking for a hex digit in <addr>; then it calls getaddrinfo(),
which has:

% 	switch (afd->a_af) {
% 	case AF_INET:
% 		/*
% 		 * RFC3493 requires getaddrinfo() to accept AF_INET formats
% 		 * that are accepted by inet_addr() and its family.  The
% 		 * accepted forms includes the "classful" one, which inet_pton
% 		 * does not accept.  So we need to separate the case for
% 		 * AF_INET.
% 		 */
% 		if (inet_aton(hostname, (struct in_addr *)pton) != 1)
% 			return 0;
% 		break;
% 	default:
% 		if (inet_pton(afd->a_af, hostname, pton) != 1)
% 			return 0;
% 		break;
% 	}

Old versions had the AF_INET case under #if 1 with the cryptic comment
/*X/Open Spec*/ (does that mean not broken to spec?).

% ...
%    The file system rooted at /a is also exported to the IPv6 network
%    3ffe:1ce1:1:fe80:: address, using the upper 64 bits as the prefix.  Note
%    that, unlike with IPv4 network addresses, the specified network address
%    must be complete, and not just contain the upper bits.  With IPv6
%    addresses, the -mask option must not be used.

This says that the short form is supposed to be accepted in the ipv4 case
only.  I doubt that it is, because mountd is not as careful as
getaddrinfo().  I debugged gethostname() as far as seeing gethostent()
call inet_pton().  Apparently, gethostent() also isn't as careful as
getaddrinfo().

The RFC is large, but doesn't cover gethostent() at all or gethostname()
except to say that it is inadequate for ipv6 so new functions
(getaddrinfo()?) are being invented.  I think that means ifconfig shouldn't
be using gethostname() and FreeBSD's gethostname() shouldn't try so hard
to support ipv6, or at least be more complicated so as not to change its
behaviour for ipv4.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20141030024343.X1825>