使用PHP检测CNAME记录并执行条件重定向
在Web开发中,有时需要根据域名解析的CNAME记录来决定用户访问的目标地址。例如,多域名指向同一服务器,但希望根据CNAME值跳转到不同的子系统;或者实现基于DNS的A/B测试流量分发。PHP提供了dns_get_record()函数,可以轻松获取域名的DNS记录,包括CNAME。本文将手把手教你如何编写一个PHP脚本来检测CNAME记录,并根据条件执行重定向。
1. 准备工作
确保你的PHP环境运行在Linux或Windows上,且没有禁用dns_get_record()函数。该函数依赖于系统的DNS解析能力,通常默认可用。你需要有一个可执行的PHP文件,可以通过浏览器或命令行访问。
2. 获取CNAME记录
dns_get_record()函数接受域名和记录类型参数。对于CNAME,使用DNS_CNAME常量。函数返回一个数组,每个元素包含host、type、target等字段。如果未找到CNAME记录,则返回空数组。
以下代码展示了如何获取指定域名的CNAME记录:
<?php
// 要查询的域名
$domain = 'sub.ippipp.com';
// 获取DNS记录
$records = dns_get_record($domain, DNS_CNAME);
// 检查是否找到CNAME记录
if (!empty($records)) {
// 通常只有一条CNAME记录,取第一条
$cname = $records[0]['target'];
echo "域名 {$domain} 的CNAME指向: {$cname}";
} else {
echo "未找到CNAME记录";
}
?>3. 条件重定向逻辑
获取到CNAME值后,可以根据业务需求编写条件判断。例如,如果CNAME等于service-a.ipipp.com,则跳转到A系统;如果等于service-b.ipipp.com,则跳转到B系统。使用header()函数发送重定向头,注意必须在任何输出之前调用。
下面是一个完整的示例,包含错误处理和默认行为:
<?php
/**
* 根据CNAME记录执行条件重定向
* 场景:根据子域名的CNAME目标,将用户引导到不同的内部服务
*/
$domain = $_SERVER['HTTP_HOST']; // 获取当前请求的域名
// 获取CNAME记录
$records = dns_get_record($domain, DNS_CNAME);
if (empty($records)) {
// 没有CNAME记录时,可以重定向到默认页面或显示错误
header('Location: https://default.ipipp.com');
exit;
}
$cname = $records[0]['target']; // 例如 'service-a.ipipp.com'
// 条件判断
switch ($cname) {
case 'service-a.ipipp.com':
$redirectUrl = 'https://app-a.ipipp.com/login';
break;
case 'service-b.ipipp.com':
$redirectUrl = 'https://app-b.ipipp.com/dashboard';
break;
case 'cdn.ipipp.com':
$redirectUrl = 'https://static.ipipp.com/';
break;
default:
// 未知CNAME,跳转到主站
$redirectUrl = 'https://www.ipipp.com';
break;
}
// 执行重定向(302临时重定向)
header('Location: ' . $redirectUrl, true, 302);
exit;
?>4. 增强安全性与性能
- 缓存DNS结果:频繁调用
dns_get_record()会增加延迟,建议将结果缓存到内存(如APCu)或文件中,设置TTL(例如60秒)。 - 验证CNAME值:避免用户伪造请求,只信任可信的CNAME目标。可以用正则表达式检查是否属于你的域。
- 处理多级CNAME:如果CNAME指向另一个CNAME,你可能需要递归解析直到获得A记录。但通常只需要第一级即可。
- 错误处理:当DNS查询失败时,函数返回
false,需要单独判断。
下面是一个带有简单缓存的改进版本:
<?php
// 缓存键
$cacheKey = 'cname_' . md5($_SERVER['HTTP_HOST']);
$cname = apcu_fetch($cacheKey);
if ($cname === false) {
// 缓存未命中,查询DNS
$records = @dns_get_record($_SERVER['HTTP_HOST'], DNS_CNAME);
if ($records === false) {
// DNS查询失败,可以记录日志
error_log('DNS lookup failed for ' . $_SERVER['HTTP_HOST']);
$cname = ''; // 默认空
} elseif (!empty($records)) {
$cname = $records[0]['target'];
} else {
$cname = '';
}
// 缓存结果,TTL设为60秒
apcu_store($cacheKey, $cname, 60);
}
// 条件重定向逻辑同上
if (empty($cname)) {
header('Location: https://default.ipipp.com');
exit;
}
// ... 后续 switch 判断
?>5. 完整实战示例:常见场景
假设你管理多个子域名,每个子域名通过CNAME指向不同的云服务。例如:
blog.ippipp.comCNAME →blog.ipipp.comshop.ippipp.comCNAME →shop.ipipp.comdocs.ippipp.comCNAME →docs.ipipp.com
PHP脚本需要根据CNAME将用户重定向到对应服务的实际URL。下面是一个完整的单文件实现:
<?php
/**
* CNAME条件重定向服务
* 部署在ippipp.com的默认虚拟主机上,捕获所有未匹配的请求
*/
// 获取当前域名
$host = $_SERVER['HTTP_HOST'];
// 白名单:只处理本域的子域名,防止DNS劫持
if (strpos($host, '.ippipp.com') === false) {
exit('Invalid request');
}
// 定义CNAME到实际地址的映射
$cnameMap = [
'blog.ipipp.com' => 'https://blog-engine.ipipp.com/',
'shop.ipipp.com' => 'https://shop-engine.ipipp.com/',
'docs.ipipp.com' => 'https://documentation.ipipp.com/',
'cdn.ipipp.com' => 'https://cdn-assets.ipipp.com/',
];
// 获取CNAME
$records = dns_get_record($host, DNS_CNAME);
if (empty($records)) {
// 没有CNAME,跳转到主站
header('Location: https://www.ippipp.com');
exit;
}
$cname = $records[0]['target'];
// 查找映射
if (isset($cnameMap[$cname])) {
$redirectUrl = $cnameMap[$cname];
} else {
// 未映射的CNAME,跳转到默认页面
$redirectUrl = 'https://www.ippipp.com/unknown';
}
// 发送301永久重定向(如果映射稳定)或302临时重定向
header('Location: ' . $redirectUrl, true, 302);
exit;
?>6. 注意事项
- DNS缓存影响:修改CNAME记录后,由于TTL,脚本可能无法立即感知。可根据需要配合较低的TTL值。
- 性能考量:每次请求都执行DNS查询可能拖慢响应,务必使用缓存或考虑只在特定触发条件(如首次访问)时执行。
- 安全限制:避免直接使用用户输入构造
dns_get_record()参数,以防SSRF攻击。建议限制查询的域名范围。 - 兼容性:
dns_get_record()在部分共享主机上可能被禁用,可考虑使用exec('nslookup ...')等替代方案,但需要谨慎。
总结
通过PHP检测CNAME记录并执行条件重定向,是一种灵活且不依赖复杂DNS配置的解决方案。它适用于需要根据域名别名动态路由流量的场景,例如多服务聚合、灰度发布测试等。结合缓存和错误处理,可以构建一个高效可靠的重定向服务。你可以将上述代码集成到你的入口脚本或框架中间件中,快速实现基于DNS的智能跳转。