如何以编程方式确定URI、主机名或IP地址是否属于本地主机?

4
给定目标URI,如何以编程方式确定对该URI进行的HTTP GET请求是否会向本地机器发出请求?
背景:有两个原因我需要这样做。一是我有一个mod_perl2应用程序响应HTTP请求。在这样做时,它有时需要进行HTTP请求以从目标URI检索一些数据。为了避免HTTP请求的无限递归,如果目标URI实际上会解析到当前机器,则需要避免进行HTTP请求。这是为了防止用户意外自己危害自己而不是作为安全检查。第二个原因是,如果我的应用程序接收到HTTP请求,我需要使用请求URI作为键查找一些元数据。问题是可能已经使用了几个URI同义词作为创建元数据时的键,因此我需要一种解决同义词的方法,但仅适用于本地主机上的URI。
问题并不像简单地查看URI是否为“localhost”,或其IP地址为127.0.0.1(或127.0.1.1或127.*)那样简单,因为:(a)目标URI可能使用解析到当前计算机上的IP地址的完全限定域名(例如foo.example.com);以及(b)一台计算机可以有几个IP地址。
操作系统必须拥有需要解决此问题的信息,因为它必须知道它所侦听的IP地址和端口。 这篇文章 讨论了尝试确定本地机器的IP地址(或地址,因为它可能有几个)的问题。也许我可以通过这种方式来确定本地机器的IP地址,然后或许我可以将这些IP地址与目标URI中的IP地址(或URI的域的gethostbyname返回的IP地址)进行比较。我真的需要这样做吗?这种方法存在问题吗?是否有更好的方法?

这篇帖子表明C#有一个函数HttpContext.Current.Request.IsLocal可以完成我需要的功能,但是我无法在perl中找到类似的东西。

之前我在perlmonks.org上问过这个问题(因为我在使用perl),但没有找到令人满意的答案。如果在Linux上普遍可用的其他编程语言(如C,bash或python)中有可用的解决方案,则也可以。我不需要保证在每种情况下都能正常工作的解决方案,但如果它能在大多数情况下工作,那就太好了。


它也可以指向负载均衡器的IP地址,该负载均衡器将重写数据包以指向本地机器。 - derobert
第一个案例的随机建议:在您的应用程序生成的请求中设置一些自定义HTTP标头。在接收请求时检查它,如果存在则返回错误。请记住,您可以在自定义标头中放置尽可能多的跟踪信息(例如,请求经过的所有节点 - 如果node2收到node1的请求,则发送带有node2和node1的标头。Node3将是OK,但是node1或node2会说“不”)。 - derobert
在/robots.txt中添加一个包含唯一字符串(例如系统hostid或CPUID的md5哈希)的注释。通过http检索它并将其与本地文件系统中的robots.txt进行比较。 - Mark Plotnick
到目前为止,HTTP头的想法似乎是最好的。/robots.txt的想法类似于需要额外的HTTP请求。但理想情况下,即使在负载均衡器存在的情况下也希望完全避免额外的HTTP请求。 - DavidBooth
.NET HttpRequest.IsLocal "IsLocal属性返回true,如果请求发起者的IP地址为127.0.0.1或者请求的IP地址与服务器的IP地址相同。" - miracle173
3个回答

2

由于我找不到更好的解决方案,最终我几乎完全按照 @EightBitTony 和 perlmonks 上其他人的建议实现了它。在从URI中获取主机名之后,可以使用perl URI模块,以下是我用来确定主机是否为本地的perl代码:

#! /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;
}

1
有一个朴素的解决方案,描述如下:
  1. 从相关的URI中提取完整合格的域名、主机名或IP地址。
  2. 将其解析为IP地址
  3. 与当前主机上的IP地址列表进行比较
  4. 如果匹配,则此URI指向此主机
只要满足以下条件,这种方法就有效:
  1. URI未解析到重定向到其他主机的情况
  2. URI未解析到负载平衡器平衡到该主机的情况
  3. 主机未使用代理来处理请求(缓存代理)或链中的其他设备。
但是,我认为您的问题太过宽泛,最好分为两个问题:
  1. 如何从URI中提取IP地址、主机名或完整合格的域名(并在编程网站上询问)
  2. 如何枚举单个主机上的所有IP地址(如果该主机是Linux服务器,则在此处发表问题)。
这不算真正的答案,但长度超过了评论限制,我怀疑您的问题会被关闭。

-2
start cmd: # ip route get 192.168.1.2
local 192.168.1.2 dev lo  src 192.168.1.2 
    cache <local>

我不理解你的回答。它是用编程语言写的吗?如果是,用的是哪种语言? - DavidBooth
1
@DavidBooth 这是一个命令。你需要运行 ip route get 1.2.3.4。@HaukeLaging 可以稍微解释一下,比如说这个命令来自于IProute2,你应该寻找 local 等信息。 - phemmer
@DavidBooth 我猜如果你不理解我的回答(并期望得到一些“真正的编程”),那么你在这个网站上是错误的。你应该去Stackoverflow上问问。 - Hauke Laging
我猜这不是Patrick所问的那种信息。 - miracle173
@miracle173,很高兴被一个在unix.sx上谈论.NET的人评价。现在让我们把这个OT的话题从这里拿开。 - Hauke Laging
@Patrick,谢谢,这正是我需要解释Hauke Laging答案的上下文。看起来我仍然需要分两步实现:(a)将名称解析为IP地址;和(b)检查该IP地址是否为本地地址。第一步可以通过perl函数gethostbyname完成。第二步可以使用ip命令,如Hauke Laging建议的那样,也可以使用perl模块IO::Interface::Simple获取所有本地IP地址列表,如perlmonks上所建议的那样。 - DavidBooth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接