PDA

View Full Version : sockets programming and /etc/hosts configuration



r.stiltskin
April 10th, 2009, 06:10 AM
As my first attempt at any kind of network programming I modified a simple server demo from Beej's Guide to only accept a single connection & then exit. It works, but I don't really understand how it's selecting the ip address that it binds to the socket, and I'm wondering whether my hosts file is not configured correctly, or am I simply not understanding how getaddrinfo() is supposed to work?

In particular I don't understand the significance of 127.0.0.1 vs. 127.0.1.1, and how getaddrinfo() is selecting addresses.

I'm running the server and telnetting to it in separate console windows on the same machine.

When I get an address using

getaddrinfo( NULL, PORT, &hints, &servinfo )the address prints out as just "::" and the incoming connection is from "::ffff:127.0.0.1". The full output is

me@a2600:~/scrap/sockets$ ./server
binding 3
address: ::
server: waiting for connections...
server: got connection from ::ffff:127.0.0.1
Rec'd from ::ffff:127.0.0.1: goodbye


When I get an address using my hostname
getaddrinfo( "a2600", PORT, &hints, &servinfo )the address prints as "127.0.1.1", the incoming connection is from "127.0.1.1" and the full output is
me@a2600:~/scrap/sockets$ ./server
binding 3
address: 127.0.1.1
server: waiting for connections...
server: got connection from 127.0.1.1
Rec'd from 127.0.1.1: goodbye


If I specify my host's actual lan address in the call to getaddrinfo(), and telnet in from another machine, then the addresses in the output are the normal, correct ip addresses one would expect to see:
me@a2600:~/scrap/sockets$ ./server
binding 3
address: 192.168.1.21
server: waiting for connections...
server: got connection from 192.168.1.23
Rec'd from 192.168.1.23: goodbye

/etc/hosts looks like this:
127.0.0.1 localhost
192.168.1.21 a2600
#127.0.1.1 a2600
127.0.1.1 a2600.myhome.westell.com a2600
192.168.1.23 dd400

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
I added the two "192.168..." entries, and commented-out the second line. IIRC the rest was auto-generated by Ubuntu during installation.

Why is getaddrinfo() picking up "::" when I leave nodename NULL, and "127.0.1.1" when I provide "a2600" as nodename? Why does it pick up "192.168.1.21" ONLY if I explicitly supply that as nodename? Does my hosts file need modification?


Here's the code I'm running:

/*
** server.c -- a stream socket server demo
** adapted from Beej's Guide
** accepts only a single connection
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define PORT "3490" // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold

// get sockaddr, IPv4 or IPv6:
void *get_in_addr( struct sockaddr *sa )
{
if ( sa->sa_family == AF_INET ) {
return &((( struct sockaddr_in* )sa )->sin_addr );
}
return &((( struct sockaddr_in6* )sa )->sin6_addr );
}

int main( void )
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr; // connector's address information
socklen_t sin_size;

int yes=1;
char s[INET6_ADDRSTRLEN];
char in_buf[50];
int rv;
memset( &hints, 0, sizeof hints );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
if (( rv = getaddrinfo( NULL, PORT, &hints, &servinfo ) ) != 0 ) {
// if (( rv = getaddrinfo( "a2600", PORT, &hints, &servinfo ) ) != 0 ) {
// if (( rv = getaddrinfo( "192.168.1.21", PORT, &hints, &servinfo ) ) != 0 ) {
fprintf( stderr, "getaddrinfo: %s\n", gai_strerror( rv ) );
return 1;
}
// loop through all the results and bind to the first we can
for ( p = servinfo; p != NULL; p = p->ai_next ) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
inet_ntop( p->ai_family, &(ipv4->sin_addr), s, sizeof s );

if (( sockfd = socket( p->ai_family, p->ai_socktype,
p->ai_protocol ) ) == -1 ) {
perror( "server: socket" );
continue;
}
if ( setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof( int ) ) == -1 ) {
perror( "setsockopt" );
exit( 1 );
}
printf( "binding %d\n", sockfd );
printf( "address: %s\n", s );
if ( bind( sockfd, p->ai_addr, p->ai_addrlen ) == -1 ) {
close( sockfd );
perror( "server: bind" );
continue;
}
break;
}
if ( p == NULL ) {
fprintf( stderr, "server: failed to bind\n" );
return 2;

}
freeaddrinfo( servinfo ); // all done with this structure
if ( listen( sockfd, BACKLOG ) == -1 ) {
perror( "listen" );
exit( 1 );
}

printf( "server: waiting for connections...\n" );

sin_size = sizeof their_addr;
new_fd = accept( sockfd, ( struct sockaddr * )&their_addr, &sin_size );
if ( new_fd == -1 ) {
perror( "accept" );
}
inet_ntop( their_addr.ss_family,
get_in_addr(( struct sockaddr * )&their_addr ),
s, sizeof s );
printf( "server: got connection from %s\n", s );
send( new_fd, "Hello, world", 12, 0 );
recv( new_fd, in_buf, sizeof(in_buf), 0 );
printf( "Rec'd from %s: %s\n", s, in_buf );
close( sockfd );
close( new_fd );
return 0;
}

odyniec
April 10th, 2009, 10:53 AM
You might try modifying your code to display all the addresses that are returned by getaddrinfo() to get a better look at what is happening. Comment out the bind() call and the break statement in the for loop, and add an exit() call after the loop:

/*
printf( "binding %d\n", sockfd );
*/
printf( "address: %s\n", s );
/*
if ( bind( sockfd, p->ai_addr, p->ai_addrlen ) == -1 ) {
close( sockfd );
perror( "server: bind" );
continue;
}
break;
*/
}
exit(0);
When you call getaddrinfo() with these parameters:

hints.ai_family = AF_UNSPEC;
rv = getaddrinfo( NULL, PORT, &hints, &servinfo )
the address family is unspecified (AF_UNSPEC), so it can return both IPv4 and IPv6 addresses -- and it does, it actually returns two addresses: "::" and "0.0.0.0". The "0.0.0.0" is the IPv4 "any" address, meaning that if you assign it to a listening socket, you'll be able to connect to it on any of the network interfaces of the host. So, you'll be able to connect with a telnet session on the same machine, or on any host on your local network. "::" is IPv6 equivalent of "0.0.0.0", and getaddrinfo() returns it first, so it gets assigned to your socket. This is also explained in the getaddrinfo() man page.

Now, when you call it like this:

getaddrinfo( "a2600", PORT, &hints, &servinfo )
it returns the address corresponding to "a2600", which is the last address listed for "a2600" in /etc/hosts (you have two /etc/hosts entries with the same name, so the second entry replaces the first one).

Finally, with this call:

getaddrinfo( "192.168.1.21", PORT, &hints, &servinfo )
it doesn't really matter what you put in /etc/hosts -- it just returns the same IP address that was passed as the node argument.

r.stiltskin
April 10th, 2009, 06:50 PM
Thanks very much. That helps clear things up. I had forgotten that :: represented an abbreviated "0" IPv6 address and I guess I read too much too fast & overlooked that this acts as a wildcard.

I suppose that there was no reason for /etc/hosts to have a 192.168... entry for itself so I deleted that. I modified the program to print the details of the structs produced by getaddrinfo, which now produces this when called with node NULL
getaddrinfo results:
ai_flags: 1
ai_family: 10
ai_socktype: 1
ai_protocol: 6
ai_addrlen: 28
ai_addr->sa_family: 10
ai_addr->sa_data: 13 162 0 0 0 0 0 0 0 0 0 0 0 0
ai_canonname: (null)
getaddrinfo results:
ai_flags: 1
ai_family: 2
ai_socktype: 1
ai_protocol: 6
ai_addrlen: 16
ai_addr->sa_family: 2
ai_addr->sa_data: 13 162 0 0 0 0 0 0 0 0 0 0 0 0
ai_canonname: (null)
and with node a2600 produces
getaddrinfo results:
ai_flags: 1
ai_family: 2
ai_socktype: 1
ai_protocol: 6
ai_addrlen: 16
ai_addr->sa_family: 2
ai_addr->sa_data: 13 162 127 0 1 1 0 0 0 0 0 0 0 0
ai_canonname: (null)
I looked up all of the constants so that's all pretty clear now. I guess the "13 162" is just a "magic" prefix for the ip address?

I'm curious about why there are two loopback addresses 127.0.0.1 and 127.0.1.1 in /etc/hosts. Can you give me some intuition about that?

dwhitney67
April 10th, 2009, 07:05 PM
...

I'm curious about why there are two loopback addresses 127.0.0.1 and 127.0.1.1 in /etc/hosts. Can you give me some intuition about that?

I would like to know as well. My other distro, Fedora, does not use such a thing. Once when I commented out the 127.0.1.1 under Ubuntu, and the system did not behave that well (i.e. gnome had trouble).

odyniec
April 10th, 2009, 09:53 PM
I guess the "13 162" is just a "magic" prefix for the ip address?
No, this is your port number, represented by two bytes (13 * 256 + 162 = 3490). The ai_addr is a sockaddr_in structure -- take a look at /usr/include/netinet/in.h to see how it's defined.


I'm curious about why there are two loopback addresses 127.0.0.1 and 127.0.1.1 in /etc/hosts. Can you give me some intuition about that?
I guess the answer to this question lies in the Debian documentation: http://www.debian.org/doc/manuals/reference/ch-gateway.en.html#s-net-dns

dwhitney67
April 10th, 2009, 11:28 PM
...
I guess the answer to this question lies in the Debian documentation: http://www.debian.org/doc/manuals/reference/ch-gateway.en.html#s-net-dns

Thanks for that link; it makes sense now, and now I understand why gnome was unhappy when I commented that line out. The strange thing though, is that gnome under Fedora behaves normally without the 127.0.1.1. I wonder why?

r.stiltskin
April 11th, 2009, 04:29 AM
No, this is your port number, represented by two bytes (13 * 256 + 162 = 3490). The ai_addr is a sockaddr_in structure -- take a look at /usr/include/netinet/in.h to see how it's defined.


Thanks again.

Actually, struct addrinfo (defined in /usr/include/netdb.h) defines ai_addr as a struct sockaddr *, and struct sockaddr is defined in /usr/include/linux/socket.h as
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};

That's why I was printing out the last 14 bytes as a byte array. But clearly you're correct about the first two bytes representing the port number, and that the struct sockaddr is really the same as a struct sockaddr_in although the latter is defined rather cryptically as
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */

/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
This sockets interface is quite confusing with its various sets of "different yet equivalent" structures.

After reading that Debian DNS reference, I think that since this machine has a static address in my LAN, it would probably make more sense to delete the 127.0.1.1 entry from /etc/hosts and replace it this machine's 192.168... address and hostname. Do you agree?

dwhitney67
April 11th, 2009, 01:09 PM
...
After reading that Debian DNS reference, I think that since this machine has a static address in my LAN, it would probably make more sense to delete the 127.0.1.1 entry from /etc/hosts and replace it this machine's 192.168... address and hostname. Do you agree?

Yes; and even if your system remains behind your own router/hub where the system is always doled out the same DHCP IP address, then it would also work.

r.stiltskin
April 11th, 2009, 05:51 PM
I made that change last night & so far everything is working correctly.

odyniec
April 12th, 2009, 01:33 AM
Actually, struct addrinfo (defined in /usr/include/netdb.h) defines ai_addr as a struct sockaddr *, and struct sockaddr is defined in /usr/include/linux/socket.h as

It's actually both sockaddr and sockaddr_in at the same time. Sockaddr is a basic structure for any address family, while sockaddr_in is specific to IPv4 (the AF_INET family). This allows for a unified interface for socket functions -- they all expect the general sockaddr structure, and when you want to pass a specific one (eg. sockaddr_in or sockaddr_in6), you need to cast it to "struct sockaddr *".

There's an example of this in your program:

new_fd = accept( sockfd, ( struct sockaddr * )&their_addr, &sin_size );

"their_addr" is defined as "struct sockaddr_storage", but when it's passed to accept(), it gets casted to "struct sockaddr *".

r.stiltskin
April 13th, 2009, 03:03 AM
That's clear now. Thanks again.