Bug 189268 - 3 getaddrinfo(3) - hostanme="localhost", but it returns IN_ADDR_ANY (0.0.0.0)
Summary: 3 getaddrinfo(3) - hostanme="localhost", but it returns IN_ADDR_ANY (0.0.0.0)
Status: Closed Works As Intended
Alias: None
Product: Documentation
Classification: Unclassified
Component: Books & Articles (show other bugs)
Version: Latest
Hardware: Any Any
: Normal Affects Only Me
Assignee: freebsd-doc (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2014-05-02 23:00 UTC by dreamcat4
Modified: 2014-07-15 20:54 UTC (History)
3 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description dreamcat4 2014-05-02 23:00:00 UTC
* Inside a jail, no lo0 loopback interface.
* Calling this function returns the ip "0.0.0.0" (also known as "IN_ADDR_ANY").
* But we asked for the hostname "localhost" - which in our /etc/hosts file is set to "127.0.0.1".

The proof:
Example 'C' test program "test.c" is attached.
getaddrinfo ~/ root~# gcc test.c -o test && ./test
0.0.0.0

This affects the gSOAP library, it's function soap_bind(), which is called by VirtualBox's SOAP webservice ("vboxwebsrv").

These applications seem to do everything correctly. But the result is a bug. Please see test script "test.c" included.

The real-life bug occurs in this code where:

vboxweb.cpp:858:
    SOAP_SOCKET m, s; // master and slave sockets
    m = soap_bind(&soap,
                  g_pcszBindToHost ? g_pcszBindToHost : "localhost",    // safe default host
                  g_uBindToPort,    // port
                  g_uBacklog);      // backlog = max queue size for requests
    if (m < 0)
        WebLogSoapError(&soap);


and:

stdsoap2.cpp:4143:
  err = getaddrinfo(host, soap_int2s(soap, port), &hints, &addrinfo);

Is passing in "localhost", is told "0.0.0.0". Then later on in soap_bind(), the returned address "0.0.0.0" is passed into bind(). And bind() then binds to ALL interfaces (not just only the localhost interface, or should error out in the jail where lo0 has no IP address).

I looked hard in these applications but the bug isn't in there - it seems to be in the result returned be "getaddrinfo()" C library call.

I confirmed this was the case with "test.c" example program (below). Which was run in the same jail.

The test jail was created like this:

$ qjail create -4 192.168.1.203 getaddrinfo
$ qjail config -k getaddrinfo
$ qjail start getaddrinfo

Fix: 

I suspect this function (getaddrinfo) shouldn't be returning IN_ADDR_ANY. It should either fail, or return "127.0.0.1" as per "/etc/hosts".
How-To-Repeat: getaddrinfo ~/ root~# ifconfig
re0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=8209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC,LINKSTATE>
	ether 00:1e:ec:d7:3a:1f
	inet 192.168.1.203 netmask 0xffffffff broadcast 192.168.1.203
	media: Ethernet autoselect (1000baseT <full-duplex>)
	status: active
ipfw0: flags=8801<UP,SIMPLEX,MULTICAST> metric 0 mtu 65536
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
	options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6>
getaddrinfo ~/ root~# ping -c 1 "localhost"
PING localhost (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.168 ms

--- localhost ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.168/0.168/0.168/0.000 ms
getaddrinfo ~/ root~# cat /etc/hosts | grep localhost
::1			localhost localhost.my.domain
127.0.0.1		localhost localhost.my.domain
getaddrinfo ~/ root~# cat test.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>


/* man getaddrinfo */

int main()
{
  struct addrinfo *addrinfo = NULL;
  struct addrinfo hints;
  struct addrinfo res;
  int err;

  memset((void*)&hints, 0, sizeof(hints));
  hints.ai_family   = PF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags    = AI_PASSIVE;

  err = getaddrinfo("localhost", "18083", &hints, &addrinfo);

  if (addrinfo)
  { 
    res = *addrinfo;
    printf("%s\n", inet_ntoa( ((struct sockaddr_in*)addrinfo->ai_addr)->sin_addr ));

    freeaddrinfo(addrinfo);
    return(0);
  }

  if (err || !addrinfo)
  {
    printf("getaddrinfo failed with code %i.\n",err);
    return(1);
  }

  printf("end_main()\n");
  return (0);

}

getaddrinfo ~/ root~# gcc test.c -o test && ./test
0.0.0.0
getaddrinfo ~/ root~# exit
logout
Comment 1 Mark Linimon freebsd_committer freebsd_triage 2014-05-03 03:54:22 UTC
Responsible Changed
From-To: freebsd-bugs->freebsd-doc

reclassify.
Comment 2 Allan Jude 2014-05-03 05:13:05 UTC
for me, the 0.0.0.0 results go away when I remove the ipv6 ::1 entry
from /etc/hosts in my jail

However, I get the same results both inside and outside of a jail.

-- 
Allan Jude
Comment 3 dreamcat4 2014-05-03 08:29:44 UTC
Ah,
I have researched a little more. It is my impression that the expected
result aught to be "::1" if the protocol family was PF_UNSPEC or PF_INET6.

Now that Allan has mentioned it. Commenting out the ::1 in /etc/hosts does
change the result. However that isn't something the gSOAP library which is
making that API call has any control over.

Also:

This bug occurs when the protocol family being requested is set to
"PF_UNSPEC" (unspecified). OR "PF_INET6". With the default "/etc/hosts"
file (where that ::1 localhost entry exists).

Wheras setting:

hints.ai_family   = PF_INET;

returns 127.0.0.1 regardless of the ::1 in /etc/hosts file.

Setting:

hints.ai_family   = PF_INET6;

also gives such incorrect "0.0.0.0", except if commenting out ::1 in
/etc/hosts as Alan says.

Then it gives:

getaddrinfo ~/ root~# gcc test.c -o test && ./test
getaddrinfo failed with code 8.

Which would be correct to error out since no corresponding ip6 address
found.


The gSOAP library seems to choose PF_UNSPEC because they want to support
both ipv4 and ipv6 protocols simultaneously (as many others also do). I'm
not sure if they could fix downstream the issue by avoiding PF_UNSPEC
because the bug will still occur on PF_INET6.


Anyway. Heres is the gSOAP code where getaddrinfo() is being called in
"soap_bind()":

http://sourceforge.net/p/gsoap2/code/HEAD/tree/gsoap/stdsoap2.cpp#l4789

http://sourceforge.net/p/gsoap2/code/HEAD/tree/gsoap/stdsoap2.cpp#l4791

Then later on in soap_bind() the incorrect address (0.0.0.0), is used are
the parameter being passed into bind()

http://sourceforge.net/p/gsoap2/code/HEAD/tree/gsoap/stdsoap2.cpp#l4876
Comment 4 dreamcat4 2014-05-03 08:44:57 UTC
getaddrinfo() returns a list of results. So I have updated my test program
to walk the entire list. It now prints two results for PF_UNSPEC

getaddrinfo ~/ root~# gcc test.c -o test && ./test
0.0.0.0
127.0.0.1
getaddrinfo ~/ root~#

Where the first result in the list is supposed to be the ipv6's "::1" (but
isn't).

Anyway heres the new version of the test.c

getaddrinfo ~/ root~# cat test.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>


/* man getaddrinfo */

int main()
{
  struct addrinfo *addrinfo = NULL;
  struct addrinfo *rp = NULL;
  struct addrinfo hints;
  struct addrinfo res;
  int err;

  memset((void*)&hints, 0, sizeof(hints));
  hints.ai_family   = PF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags    = AI_PASSIVE;

  err = getaddrinfo("localhost", "18083", &hints, &addrinfo);

  if (addrinfo)
  {
    for (rp = addrinfo; rp != NULL; rp = rp->ai_next)
    {
      printf("%s\n", inet_ntoa( ((struct
sockaddr_in*)rp->ai_addr)->sin_addr ));
    }

    freeaddrinfo(addrinfo);
    return(0);
  }

  if (err || !addrinfo)
  {
    printf("getaddrinfo failed with code %i.\n",err);
    return(1);
  }

  printf("end_main()\n");
  return (0);

}

getaddrinfo ~/ root~# gcc test.c -o test && ./test
0.0.0.0
127.0.0.1
getaddrinfo ~/ root~#
Comment 5 Allan Jude freebsd_committer freebsd_triage 2014-05-19 19:26:44 UTC
So do you still think there is an issue here? or can I close this PR?

-- 
Allan Jude
Comment 6 dreamcat4 2014-06-22 16:15:56 UTC
(In reply to Allan Jude from comment #5)
> So do you still think there is an issue here? or can I close this PR?
> Allan Jude

Yeah I believe this one should stay open because it was discovered as a bug when such a query was being executed by a real piece of software (virtualbox).

Rationale: The virtualbox source code running this query was multi platform. If their developers can only test their work on one platform (for example linux), and result the other platform works correctly... they kindda need to be able to assume it work's gonna the same way on FreeBSD too.

Problem Recap.

In short. The problem is that: running a "localhost" DNS query should never be returning "0.0.0.0" anywhere in it's results list. As "0.0.0.0" != "localhost".

This bug occurs if the protocol family being requested is set to
"PF_UNSPEC" (unspecified). OR "PF_INET6". With the default "/etc/hosts"
file (with a line that says "::1 localhost").

The bug is more likely to occur on new "IPV4+IPV6" code. The bug may not be evident on older IP4-only code, if the DNS query's "protocol family" struct field was set to "PF_INET4".
Comment 7 dreamcat4 2014-07-15 09:09:18 UTC
Should this PR really be in 'Documentation' ? Or 'Base System' ?
Comment 8 Benjamin Kaduk freebsd_committer freebsd_triage 2014-07-15 14:22:10 UTC
I do not see why it is believed to be correct behavior to blindly cast addrinfo->ai_addr to type 'struct sockaddr_in *' without first checking that addrinfo->ai_family is PF_INET (or that the sockaddr's sa_family is AFS_INET); such a cast is expected to yield "nonsense" results when ai_family is PF_INET6.
Comment 9 dreamcat4 2014-07-15 15:17:12 UTC
(In reply to Benjamin Kaduk from comment #8)
> I do not see why it is believed to be correct behavior to blindly cast
> addrinfo->ai_addr to type 'struct sockaddr_in *' without first checking that
> addrinfo->ai_family is PF_INET (or that the sockaddr's sa_family is
> AFS_INET); such a cast is expected to yield "nonsense" results when
> ai_family is PF_INET6.

I made the change you suggested.

https://gist.github.com/dreamcat4/86706bba25c468fc0ecc

For PF_INET6 hints, the returned structure says it is of type ai_family '0x1C' (which is '28' in decimal base r10).

If we look in the header file '/usr/include/sys/socket.h', we see that returned type is: AF_ISDN

:/

Maybe that aught to be a returned value of type '30' (0x1E) for PF_INET6. Since that was the type we actually requested?


#define pseudo_AF_PIP	25		/* Help Identify PIP packets */
#ifdef __APPLE__
/*define pseudo_AF_BLUE	26	   Identify packets for Blue Box - Not used */
#define AF_NDRV		27		/* Network Driver 'raw' access */
#endif
#define	AF_ISDN		28		/* Integrated Services Digital Network*/
#define	AF_E164		AF_ISDN		/* CCITT E.164 recommendation */
#define	pseudo_AF_KEY	29		/* Internal key-management function */
#endif	/* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
#define	AF_INET6	30		/* IPv6 */
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
#define	AF_NATM		31		/* native ATM access */
#ifdef __APPLE__
#define AF_SYSTEM	32		/* Kernel event messages */
Comment 10 Benjamin Kaduk freebsd_committer freebsd_triage 2014-07-15 15:44:18 UTC
(In reply to dreamcat4 from comment #9)
> For PF_INET6 hints, the returned structure says it is of type ai_family
> '0x1C' (which is '28' in decimal base r10).
> 
> If we look in the header file '/usr/include/sys/socket.h', we see that
> returned type is: AF_ISDN
> 
> :/
> 
> Maybe that aught to be a returned value of type '30' (0x1E) for PF_INET6.
> Since that was the type we actually requested?
> 
> 
> #define pseudo_AF_PIP	25		/* Help Identify PIP packets */
> #ifdef __APPLE__
> /*define pseudo_AF_BLUE	26	   Identify packets for Blue Box - Not used */
> #define AF_NDRV		27		/* Network Driver 'raw' access */
> #endif
> #define	AF_ISDN		28		/* Integrated Services Digital Network*/
> #define	AF_E164		AF_ISDN		/* CCITT E.164 recommendation */
> #define	pseudo_AF_KEY	29		/* Internal key-management function */
> #endif	/* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
> #define	AF_INET6	30		/* IPv6 */
> #if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
> #define	AF_NATM		31		/* native ATM access */
> #ifdef __APPLE__
> #define AF_SYSTEM	32		/* Kernel event messages */

I think you are looking at the /usr/include/sys/socket.h from an OS X machine, not a FreeBSD machine.  On my FreeBSD machine, AF_INET6 is decimal 28, matching the 0x1c you see.
Comment 11 dreamcat4 2014-07-15 16:06:48 UTC
(In reply to Benjamin Kaduk from comment #10)
> I think you are looking at the /usr/include/sys/socket.h from an OS X
> machine, not a FreeBSD machine.  On my FreeBSD machine, AF_INET6 is decimal
> 28, matching the 0x1c you see.

Yes. Oh sorry! Been looking in the wrong window. Silly me.

And my test program should be:

  if ( (addrinfo) && (addrinfo->ai_family == hints.ai_family) )
                                             ^^^^^^^^^^^^^^^ 
in order to filter on the same requested / returned type.                                        

Gist link is updated to reflect that,
https://gist.github.com/dreamcat4/86706bba25c468fc0ecc


BTW

This bug I have observed on FreeBSD 9.2-RELEASE. It is also suspected to occur on 9.3, 10.0, and high. I just don't have any newer FreeBSD servers to test it on. Can check on them by running this program 'test.c'.
Comment 12 Benjamin Kaduk freebsd_committer freebsd_triage 2014-07-15 16:47:05 UTC
(In reply to dreamcat4 from comment #11)
> 
> Gist link is updated to reflect that,
> https://gist.github.com/dreamcat4/86706bba25c468fc0ecc
> 

No, this is still incorrect.  The point I was making, is that the ai_addr field must be cast to the type 'struct sockaddr_in6 *' (note the '6') when it is in the INET6 family.  Your code is still using 'struct sockaddr_in *', which is incorrect.
Comment 13 dreamcat4 2014-07-15 17:34:50 UTC
(In reply to Benjamin Kaduk from comment #12)
> (In reply to dreamcat4 from comment #11)
> No, this is still incorrect.  The point I was making, is that the ai_addr
> field must be cast to the type 'struct sockaddr_in6 *' (note the '6') when
> it is in the INET6 family.  Your code is still using 'struct sockaddr_in *',
> which is incorrect.

Thanks Ben! I am pleased to say that with that change, the new version of 'test.c' and test result you can see here:

https://gist.github.com/dreamcat4/86706bba25c468fc0ecc

So this proves that there is no bug in getaddrinfo. My bug report is invalid. This also proves there is definitely some bug in GSOAP library (which the previous version of this 'test.c' program was exactly reproducing).

Many thanks.

Bug is on GSOAP. As previously stated in my very 1st comment:


stdsoap2.cpp:4143:
  err = getaddrinfo(host, soap_int2s(soap, port), &hints, &addrinfo);

Is passing in "localhost", is told "0.0.0.0". Then later on in soap_bind(), the returned address "0.0.0.0" is passed into bind(). And bind() then binds to ALL interfaces. 

We can see as of today, comparing before and after, the '0.0.0.0' is occur because incorrect cast of structure members. When the INET6 structure actually contain '::1' ipv6 address for localhost.

Appreciate this. It clarifies the issue totally.
Comment 14 dreamcat4 2014-07-15 20:54:58 UTC
Just for completeness (and not to forget it), here is link to IPV4 tutorial page. Which was the most helpful to improve / fix today this parts of broken networking code (where error occurred).

http://long.ccaba.upc.edu/long/045Guidelines/eva/ipv6.html

Many thanks.