如果您执行 gethostbyname() 并且域名后面没有尾部点并且无法解析,此域名最终将被 nslookup 附加到服务器 FQDN。
因此,如果您查找不存在的域名 nonexistentdomainname.be,您的服务器可能会返回 nonexistentdomainname.be.yourhostname.com 的 IP,即服务器 IP。
要避免这种行为,只需在域名后面添加一个尾部点;例如 gethostbyname('nonexistentdomainname.be.')
(PHP 4, PHP 5, PHP 7, PHP 8)
gethostbyname — 获取与给定互联网主机名对应的 IPv4 地址
hostname
主机名。
返回 IPv4 地址,或在失败时返回包含未修改的 hostname
的字符串。
示例 #1 一个简单的 gethostbyname() 示例
<?php
$ip = gethostbyname('www.example.com');
echo $ip;
?>
如果您执行 gethostbyname() 并且域名后面没有尾部点并且无法解析,此域名最终将被 nslookup 附加到服务器 FQDN。
因此,如果您查找不存在的域名 nonexistentdomainname.be,您的服务器可能会返回 nonexistentdomainname.be.yourhostname.com 的 IP,即服务器 IP。
要避免这种行为,只需在域名后面添加一个尾部点;例如 gethostbyname('nonexistentdomainname.be.')
此函数说“返回 IPv4 地址,或在失败时返回包含未修改的主机名的字符串。
这并不完全正确,任何在其中包含空字节的主机名都只返回空字节之前的字符。
<?php
$hostname = "foo\0bar";
var_dump($hostname );
var_dump(gethostbyname($hostname ));
?>
结果
string 'foo�bar' (length=7)
string 'foo' (length=3)
重要提示:您应该避免在生产中使用它。
DNS 解析可能需要 0.5 到 4 秒的时间,在此期间您的脚本不会被执行。
您的客户可能认为服务器很慢,但实际上它只是在等待 DNS 解析响应。
您可以使用它,但如果您想要性能,您应该避免使用它,或者将其安排到某个 CRON 脚本中……
可以使用 RES_OPTIONS 环境变量提供底层解析器函数的选项。(至少在 Linux 下,请参见 man resolv.conf)
将超时和重试设置为 1,使 DNS 查找的最大执行时间为 1 秒
<?php
putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
gethostbyname($something);
?>
您还应该使用以点结尾的完全限定域名。这可以防止解析器遍历所有搜索域并尝试在后面附加搜索域的域名。
对于使用此函数进行基本 RBL(实时黑名单)查找,请执行以下操作
<?php
$host = '64.53.200.156';
$rbl = 'sbl-xbl.spamhaus.org';
// 有效的查询格式为:156.200.53.64.sbl-xbl.spamhaus.org
$rev = array_reverse(explode('.', $host));
$lookup = implode('.', $rev) . '.' . $rbl;
if ($lookup != gethostbyname($lookup)) {
echo "ip: $host is listed in $rbl\n";
} else {
echo "ip: $host NOT listed in $rbl\n";
}
?>
Tomas V.V.Cox
gethostbyname 和 gethostbynamel 不会请求 AAAA 记录。我编写了两个函数来实现这一点。gethostbyname6 和 gethostbynamel6。我不相信这个问题已经得到解决。
它们是为了替换 gethostbyname[l],如果 $try_a 为真,如果无法获取 AAAA 记录,它将尝试获取 A 记录。
随意更正任何错误,我意识到它正在请求 *两个* A 和 AAAA 记录,这意味着两次 DNS 调用。如果它在进行查询之前检查 $try_a 可能会更有效率,但这对我来说有效,所以我将把这个留给其他人来在他们自己的工作中实现。提示已经出来了。
以下是代码
function gethostbyname6($host, $try_a = false) {
// 获取 $host 的 AAAA 记录
// 如果 $try_a 为真,如果 AAAA 失败,它会尝试获取 A
// 返回找到的第一个匹配项
// 否则返回 false
$dns = gethostbynamel6($host, $try_a);
if ($dns == false) { return false; }
else { return $dns[0]; }
}
function gethostbynamel6($host, $try_a = false) {
// 获取 $host 的 AAAA 记录,
// 如果 $try_a 为真,如果 AAAA 失败,它会尝试获取 A
// 结果以匹配类型的 IP 数组的形式返回
// 否则返回 false
$dns6 = dns_get_record($host, DNS_AAAA);
if ($try_a == true) {
$dns4 = dns_get_record($host, DNS_A);
$dns = array_merge($dns4, $dns6);
}
else { $dns = $dns6; }
$ip6 = array();
$ip4 = array();
foreach ($dns as $record) {
if ($record["type"] == "A") {
$ip4[] = $record["ip"];
}
if ($record["type"] == "AAAA") {
$ip6[] = $record["ipv6"];
}
}
if (count($ip6) < 1) {
if ($try_a == true) {
if (count($ip4) < 1) {
return false;
}
else {
return $ip4;
}
}
else {
return false;
}
}
else {
return $ip6;
}
}
自 2015 年以来,主机名参数被任意限制为 255 个字符,以解决 CVE-2015-0235,可能是为了反映 RFC1035 对域名中八位字节的限制。
我在 OpenBSD 3.2 和 Apache 下使用 gethostbyname 时遇到了困难,直到我发现默认的 Apache chroot 导致了问题。
要使 PHP 的 gethostbyname 正常工作,您需要 resolv.conf(可能还有 hosts)位于 /var/www/etc(假设默认安装目录)。
获取多个主机的 IP
<?php
function getAddrByHost($hosts, $timeout = 3) {
$returnString = '';
foreach ($hosts as $host) {
$query = `nslookup -timeout=$timeout -retry=1 $host`;
if (preg_match('/\nAddress: (.*)\n/', $query, $matches))
$returnString .= trim($matches[1]) . '<br>';
$returnString .= $host . '<br>';
}
return $returnString;
}
$hostArray[] = 'www.domain1.com';
$hostArray[] = 'www.domain2.com';
//$hostArray[] = 'www.domain3.com';
//$hostArray[] = 'www.domain4.com';
$returnString = getAddrByHost($hostArray);
echo $returnString;
?>
在 PHP4 中,您可以使用 gethostbyname(),但我发现当对返回私有网络上的 A 记录的条目进行查找时,它不可靠。PHP5 有一个更好的例程 - dns_get_record()。如果您仍然使用 PHP4 或者不想升级,您可以使用 dig
<?php
$ip = `/usr/bin/dig $host A +short`;
?>
mmucklo 提出了一个有效的观点,但是要使用的函数在最简单的情况下是 getmxrr(),或者如果您愿意,还可以使用 checkdnsrr();getdnsrr() 不存在。
另一方面,当使用 while 循环迭代时(例如),gethostbyname() 在失败时返回主机名而不是 FALSE,这可能会很麻烦。您可以将其封装在一个简单的用户函数中,以“纠正”此问题
<?php
function fixed_gethostbyname ($host) {
// 尝试正常执行查找...
$ip = gethostbyname($host);
// ...但如果失败,返回 FALSE 而不是未解析的主机
if ($ip != $host) { return $ip; } else return false;
}
?>
<?php
// 用于计时 DNS 传播的脚本
//(上面脚本略作修改以显示微秒时间)
// 似乎非常快.. 我得到的最差情况坏主机时间是 .0055 秒。
// 一个已知的良好 DNS 名称(我自己的)
$nametotest = "fuzzygroup.com";
// 调用地址测试函数
$time_start = getmicrotime();
testipaddress($nametotest);
$time_end = getmicrotime();
$time = $time_end - $time_start;
echo "Good Host Search took $time seconds<br><br>";
// 一个已知的错误名称(相信我)
$nametotest = "providence.mascot.com";
$time_start = getmicrotime();
testipaddress($nametotest);
$time_end = getmicrotime();
$time = $time_end - $time_start;
echo "Bad Host Search took $time seconds<br>";
function getmicrotime(){
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
}
// ip 地址检查函数
// 为了实际使用应该有一个返回值,但这是一个示例代码
function testipaddress ($nametotest) {
$ipaddress = $nametotest;
$ipaddress = gethostbyname($nametotest);
if ($ipaddress == $nametotest) {
echo "No ip address for host<br>";
}
else {
echo "good hostname, $nametotest ipaddress = $ipaddress<br>";
}
}
// 建议用于 SQL 应用程序的修复:
// 将 URL 存储到临时表中
// 定期运行第二个进程来
// 检查 URL 并更新主表
?>
引用 ralphbolton at mail2sexy dot com 的评论
(至少在 5.2.0 + djbdns-dnscache 中)gethostbyname 似乎并没有真正缓存条目。如果有人注意到在第二次查找相同域后速度加快了 - 那很可能是您的 DNS 缓存本身,而不是 PHP 内部的一些 DNS 缓存。
它确实在您的 /etc/resolv.conf 中缓存条目(例如使用哪个 DNS),所以我同意他的说法,停止和启动 Apache 将重新加载 resolv.conf。
另外,PHP(5.0.4,但可能还有其他版本)可以缓存 gethostbyname 信息。
简而言之,PHP 一旦查找了一个地址,它可能不会像您预期的那样真正执行另一个查找。在我的特定情况下(我认为)问题是 resolv.conf 的更改在 PHP 内部没有生效(尽管 nslookup/ping 等工作正常)。停止/启动 Apache 解决了这个问题(尽管简单的“重启”(kill -HUP)没有解决)。
简而言之,如果您更改了 resolv.conf,请停止并重启 Apache。
我正在对一组 URL 使用 file_get_contents。其中一些 URL 无效(它的结构没问题,但 DNS 主机无法解析它们),我一直收到烦人的警告。我想以某种方式检查 DNS,但是 PHP 中现有的检查 DNS 函数在 Windows 上没有,而一个人提供的一个函数在大多数情况下无法正常工作。
相反,使用此函数尝试解析主机。这不会抛出任何警告,您只需要检查输出。您将使用 fopen 和 fsockopen 遇到相同的警告。
提醒一下。我在我的网站上使用此函数来验证电子邮件主机地址。我认为一切都很好,直到一个潜在的客户联系我说他们无法正确注册。他们有一个有效的电子邮件地址和域名,但是这个函数失败了。
祝您好运。
这是我想到的将任何主机名解析为 IP 地址的最佳方法,它快速可靠并且支持超时!无效地址(例如 unicode 字符串)将在 4~ 秒后返回,而不是 gethostbyname 的 8~ 秒!不过它仅适用于 Unix。
<?php
function getAddrByHost($host, $timeout = 3) {
$query = `nslookup -timeout=$timeout -retry=1 $host`;
if(preg_match('/\nAddress: (.*)\n/', $query, $matches))
return trim($matches[1]);
return $host;
}
?>
对于“dig”解决方案,这里有一个更好的解决方案
<?php
$IP = `/usr/bin/dig $host. A +short | /usr/bin/tail -n1`
?>
使用 tail 因为 dig,即使在 +short 形式下,也可能先返回 CNAME 条目,并且可能返回很多条目(参见 "dig google.com")。
在主机名末尾使用点以避免执行域名搜索。
当 $host 已经是 IP 地址时,此方法同样有效。
如果解析失败(无效主机名),将返回空字符串。
此逻辑解决了检查 DNS 反向名称解析的问题。
<?php
$ip = gethostbyname($host);
if(ip2long($ip) == -1 || ($ip == gethostbyaddr($ip) && preg_match("/.*\.[a-zA-Z]{2,3}$/",$host) == 0) ) {
echo '错误,主机名或 IP 地址不正确';
}
else {
echo '成功';
}
?>
一种自动发现您的 IP 地址的方法。
<?php
// 需要使用 trim() 因为空格会干扰名称查找
$myIP = gethostbyname(trim(`hostname`));
echo $myIP;
?>
当同时使用 gethostbynamel() 和 gethostbyname() 时,必须先执行 gethostbynamel(),否则它将始终从缓存中返回一个 IP(或没有),而不是返回完整的查找结果。
<?php
// 脚本用于查看主机是否存在于互联网上
// 针对上述关于主机名检查和 SQL 超时的要点进行补充,运行此测试脚本
// 并查看第二次主机名检查失败需要多长时间
// 注意 - 这不是 PHP 的问题 - 这是 DNS 的特性
// 已知良好的 DNS 名字(我自己网站)
$nametotest = "fuzzygroup.com";
// 调用地址测试函数
testipaddress($nametotest);
// 已知错误的名字(相信我)
$nametotest = "providence.mascot.com";
// 调用地址测试函数
testipaddress($nametotest);
// IP 地址检查函数
// 实际使用应该有返回值,但这里只是示例代码
function testipaddress ($nametotest) {
$ipaddress = $nametotest;
$ipaddress = gethostbyname($nametotest);
if ($ipaddress == $nametotest) {
echo "主机没有 IP 地址,因此主机目前无法在 DNS 中访问,可能已离线一段时间<BR>";
}
else {
echo "有效的域名,IP 地址 = $ipaddress<BR>";
}
}
// 建议的 SQL 应用修复方法:
// 将 URL 保存到临时表中
// 定期运行第二个进程来
// 检查 URL 并更新主表
?>
始终在末尾添加根域名 ".",否则您的服务器可能会添加自己的后缀。
例如:abc.com 可能会变成 abc.com.yourcompany.com,如果您没有使用 abc.com。
我还发现这个函数有点局限性,因为它无法告诉您是否解析失败。
为了解决所有这些问题,我使用了
function host2ip($host)
{
$host=trim($host.'.'); // 清理并添加根域名 "."
$ip= gethostbyname($host);
if($ip==$host) $ip='';// 如果没有 IP 地址,则 IP 为空
return $ip;
}
在 RedHat Linux 上使用 PHP 和 Apache 在 chroot 环境中时,我发现我需要绑定挂载 /var/run/nscd 才能使它正常工作。显然,该目录中的套接字对于所有 DNS 操作都是必需的。
关于使用 gethostbyname() 检查电子邮件地址域名的一个注意事项
如果名称无法解析,请使用 getdnsrr() 并确保它们没有 MX 记录,然后再返回错误。
一个域名可能没有 A 记录,但仍然有 MX 记录。
我将它放在我所有网站的前置脚本中,以减少来自自动化脚本的滥用量。
<?PHP
$blacklists = array('web.sorbs.net');
$parts = explode('.', $_SERVER['REMOTE_ADDR']);
$ip = implode('.', array_reverse($parts)) . '.';
foreach($blacklists as $bl) {
$check = $ip . $bl;
if ($check != gethostbyname($check)) {
error_log('PHP 安全性: [DNSBL] - ' . $_SERVER['REMOTE_ADDR'] . ' - ' . $bl);
die('在此处放置详细的错误信息,以便客户端知道他们被阻止的原因');
}
}
?>
如果使用 apache2、mod_chroot 和 php5 时名称解析失败,请在 mod_chroot 配置中添加
LoadFile /lib/libnss_dns.so.2
。
函数返回布尔值。
<?php
function isDomainResolves($domain)
{
return gethostbyname($domain) != $domain;
}
?>