dbooth has asked for the wisdom of the Perl Monks concerning the following question:
Given a target URI, how can I determine whether an HTTP GET of that URI would be making a request to the local machine?
Context: I have a mod_perl2 script that responds to HTTP requests. In the course of doing so, it sometimes needs to make an HTTP request to retrieve some data from a target URI, for which I am using WWW::Mechanize, though I'm sure I could use LWP::Simple or LWP instead. The problem is that, to avoid an infinite recursion of HTTP requests, I need to avoid making the HTTP request if the target URI would actually resolve to the current machine. This is to prevent users from accidentally shooting themselves in the foot. It is not intended as a security check.
The problem is not as simple as looking at the URI to see if the domain is "localhost", or its IP address is 127.0.0.1 (or 127.0.1.1 or 127.*), because: (a) the target URI might use a fully qualified domain name that resolves to an IP address on the current machine; and (b) a machine can have several IP addresses.
I would think that this would be a common problem, but I have been unable to find a straight-forward solution. I do not need a solution that is guaranteed to work in all possible cases, but it would be nice if it would catch most cases.
This post discusses the problem of trying to determine the local machine's IP address (or addresses, since it may have several). Maybe I could do that to determine the local machine's IP addresses, and then perhaps I could compare those IP addresses against the IP address in the target URI (or the IP address returned by gethostbyname of the URI's domain). Do I really need to do that? Are there problems with that approach? Is there a better way?
This post indicates that C# has a function HttpContext.Current.Request.IsLocal to do what I need, but I have been unable to find anything similar in perl.
Suggestions please?
UPDATE: Since I found no better solution I ended up implementing this as follows. After extracting the host name from the URI, which I did using the perl URI module, I used IO::Interface::Simple to get the list of local IP addresses, as suggested by @Anonymous_Monk:
#! /usr/bin/perl -w
use strict;
use Socket;
use IO::Interface::Simple;
print "127.0.1.1 is local\n" if &IsLocalHost("127.0.1.1");
print "google.com is local\n" if &IsLocalHost("google.com");
exit 0;
################ IsLocalHost #################
# Is the given host name, which may be either a domain name or
# an IP address, hosted on this local host machine?
# Results are cached in a hash for fast repeated lookup.
sub IsLocalHost
{
my $host = shift || return 0;
our %isLocal; # Cache
return $isLocal{$host} if exists($isLocal{$host});
my $packedIp = gethostbyname($host);
if (!$packedIp) {
$isLocal{$host} = 0;
return 0;
}
my $ip = inet_ntoa($packedIp) || "";
our %localIps; # Another cache
%localIps = map { ($_, 1) } &GetIps() if !%localIps;
my $isLocal = $localIps{$ip} || $ip =~ m/^127\./ || 0;
# TODO: Check for IPv6 loopback also. See:
# http://ipv6exchange.net/questions/16/what-is-the-loopback-127001-equivalent-ipv6-address
$isLocal{$host} = $isLocal;
return $isLocal;
}
################ GetIps #################
# Lookup IP addresses on this host.
sub GetIps
{
my @interfaces = IO::Interface::Simple->interfaces;
my @ips = grep {$_} map { $_->address } @interfaces;
return @ips;
}
|
|---|