Recipe 17.16 Writing a Multihomed Server

17.16.1 Problem

You want to write a server that knows that the machine it runs on has multiple IP addresses, and that it should possibly do different things for each address.

17.16.2 Solution

Don't bind your server to a particular address. Instead, bind to INADDR_ANY. Then once you've accepted a connection, use getsockname on the client socket to find out which address the client connected to:

use Socket;

socket(SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1);
bind(SERVER, sockaddr_in($server_port, INADDR_ANY))
    or die "Binding: $!\n";

# accept loop
while (accept(CLIENT, SERVER)) {
    $my_socket_address = getsockname(CLIENT);
    ($port, $myaddr)   = sockaddr_in($my_socket_address);
}

17.16.3 Discussion

Whereas getpeername (as discussed in Recipe 17.7) returns the address of the remote end of the socket, getsockname returns the address of the local end. When we've bound to INADDR_ANY, thus accepting connections on any address the machine has, we need to use getsockname to identify which address the client connected to.

If you're using IO::Socket::INET, your code will look like this:

$server = IO::Socket::INET->new(LocalPort => $server_port,
                                Type      => SOCK_STREAM,
                                Proto     => 'tcp',
                                Listen    => 10)
    or die "Can't create server socket: $@\n";

while ($client = $server->accept( )) {
    $my_socket_address = $client->sockname( );
    ($port, $myaddr)   = sockaddr_in($my_socket_address);
    # ...
}

If you don't specify a local port to IO::Socket::INET->new, your socket will be bound to INADDR_ANY.

If you want your server to listen only for a particular virtual host, don't use INADDR_ANY. Instead, bind to a specific host address:

use Socket;

$port = 4269;                       # port to bind to
$host = "specific.host.com";        # virtual host to listen on

socket(Server, PF_INET, SOCK_STREAM, getprotobyname("tcp"))
    or die "socket: $!";
bind(Server, sockaddr_in($port, inet_aton($host)))
    or die "bind: $!";
while ($client_address = accept(Client, Server)) {
    # ...
}

17.16.4 See Also

The getsockname function in Chapter 29 of Programming Perl and in perlfunc(1); the documentation for the standard Socket and IO::Socket modules; the section on "Sockets" in Chapter 16 of Programming Perl or perlipc(1)