第342篇:DHCP 服务故障与排障实战案例
关键词
DHCP、DHCP Snooping、DHCP Relay、IP地址冲突、地址池耗尽、Giaddr、Option 82、中继
一、案例背景
1.1 故障现象
某企业园区网 DHCP 故障:
时间:周一早上 8:30(上班高峰期) 影响:新接入用户无法获取 IP 地址
现象:
新员工接入网络: └─ 网卡显示 "正在获取 IP 地址" └─ 等待 30 秒后显示 "受限" └─ 手动配置静态 IP 可以上网 已有用户: └─ 在线用户不受影响 └─ 续约正常 DHCP 服务器状态: └─ CPU 正常(15%) └─ 地址池显示有空闲地址 └─ 日志无错误
1.2 网络拓扑
园区 DHCP 组网:
┌──────────────────────┐
│ DHCP Server │
│ (Windows/Linux) │
└──────────┬───────────┘
│
┌──────────┴───────────┐
│ 核心交换机 Core │
│ (DHCP Relay) │
└──────────┬───────────┘
│
┌──────────┴───────────┐
│ 汇聚交换机 Agg │
└──────────┬───────────┘
│
┌──────────┴───────────┐
│ 接入交换机 Access │
│ (DHCP Snooping) │
└──────────┬───────────┘
│
┌────┴────┐
│ PC │
└─────────┘
二、故障排查过程
2.1 初步排查
排查路线:
第一步:检查 DHCP 服务器
┌──────────────────────────────────────────┐
│ # 查看 DHCP 地址池状态 │
│ > netsh dhcp server scope 10.1.1.0 │
│ show clients │
│ > 地址池:10.1.1.0/24 │
│ > 已分配:220/254 地址 │
│ > 空闲:34 个地址 │
│ > 排除:10 个地址 │
│ │
│ 结论:地址池正常,还有空闲地址 │
└──────────────────────────────────────────┘
第二步:抓包分析(在接入交换机端口抓包)
┌──────────────────────────────────────────┐
│ 客户端 → DHCP Discover(广播) │
│ DHCP Discover │
│ src: 0.0.0.0 │
│ dst: 255.255.255.255 │
│ chaddr: xx:xx:xx:xx:xx:xx │
│ │
│ ✅ 客户端成功发出 Discover │
│ ❌ 客户端未收到 Offer │
│ ❌ 未看到中继转发到服务器的包 │
│ │
│ 问题:DHCP Discover 未到达服务器 │
└──────────────────────────────────────────┘
2.2 深入排查
第三步:检查 DHCP Snooping
┌──────────────────────────────────────────┐
│ # 查看 DHCP Snooping 配置 │
│ display dhcp snooping configuration │
│ │
│ DHCP Snooping 配置: │
│ ┌──────────────────────────────────────┐ │
│ │ dhcp snooping enable │ │
│ │ dhcp snooping trusted interface │ │
│ │ GigabitEthernet0/0/24 (上联口) │ │
│ │ dhcp snooping trusted interface │ │
│ │ GigabitEthernet0/0/23 (上联口) │ │
│ └──────────────────────────────────────┘ │
│ │
│ # 查看 DHCP Snooping 绑定表 │
│ display dhcp snooping user-table all │
│ ┌──────────────────────────────────────┐ │
│ │ MAC IP VLAN Port │ │
│ │ xx:xx:xx:01 10.1.1.10 100 0/0/1 │ │
│ │ xx:xx:xx:02 10.1.1.11 100 0/0/2 │ │
│ │ ... │ │
│ │ 绑定表有 220 条记录 │ │
│ └──────────────────────────────────────┘ │
│ │
│ 初步看配置正常... │
└──────────────────────────────────────────┘
第四步:检查 DHCP Relay(核心交换机发现关键问题)
┌──────────────────────────────────────────┐
│ # 查看 DHCP Relay 配置 │
│ display dhcp relay configuration │
│ │
│ interface Vlanif100 │
│ dhcp relay server 10.1.0.5 │
│ │
│ # 查看 DHCP Relay 统计 │
│ display dhcp relay statistics │
│ ┌──────────────────────────────────────┐ │
│ │ Received from client: 1500 packets │ │
│ │ Sent to server: 0 packets │ │ ← 问题!
│ │ Received from server: 0 packets │ │
│ │ Sent to client: 0 packets │ │
│ └──────────────────────────────────────┘ │
│ │
│ 发现:Relay 收到客户端包,但没发给服务器 │
│ 根因:Relay 配置的服务器 IP 无法到达 │
└──────────────────────────────────────────┘
2.3 根因定位
最终根因分析:
问题链路追踪:
┌──────────────────────────────────────────┐
│ Core 到 DHCP Server 路径: │
│ │
│ Core ── 防火墙 ── DHCP Server │
│ 10.1.0.1 10.1.0.2 10.1.0.5 │
│ │
│ # 在 Core 上测试连通性 │
│ ping 10.1.0.5 │
│ └─ 不通! │
│ │
│ # 检查路由表 │
│ display ip routing-table 10.1.0.5 │
│ └─ 路由指向防火墙 10.1.0.2 │
│ │
│ # 检查防火墙策略 │
│ └─ 防火墙管理员说昨天做了策略变更 │
│ └─ 添加了新的安全域隔离规则 │
│ └─ DHCP 中继流量(UDP 67/68)被阻断 │
│ │
│ 根因:防火墙策略变更导致 DHCP Relay │
│ 流量被阻断 │
└──────────────────────────────────────────┘
三、解决方案
3.1 紧急恢复
紧急恢复措施:
方案一:临时放通防火墙策略
┌──────────────────────────────────────────┐
│ # 防火墙放通 DHCP 中继流量 │
│ security-policy │
│ rule name permit_dhcp_relay │
│ source-zone trust │
│ destination-zone dmz │
│ source-address 10.1.0.1 24 │
│ destination-address 10.1.0.5 32 │
│ service udp_dhcp │ # UDP 67/68
│ action permit │
│ # │
│ # 效果:客户端立即获取到 IP │
└──────────────────────────────────────────┘
方案二:配置 DHCP 多跳 Relay(跨三层)
┌──────────────────────────────────────────┐
│ # 如果无法放通防火墙(安全要求高) │
│ # 可在接入交换机直接配置 Relay │
│ │
│ interface Vlanif100 │
│ dhcp relay server 10.1.0.5 │
│ dhcp relay source-interface LoopBack0 │
│ # 确保接入交换机到服务器的路由可达 │
└──────────────────────────────────────────┘
3.2 长期优化
长期优化方案:
方案一:DHCP Server 双机热备
┌──────────────────────────────────────────┐
│ DHCP Server-1 (Active) │
│ DHCP Server-2 (Standby) │
│ │
│ 配置: │
│ └─ 地址池自动同步 │
│ └─ 健康检查:每 30 秒检查 │
│ └─ 故障切换:< 1 分钟 │
│ │
│ Relay 配置双服务器: │
│ dhcp relay server 10.1.0.5 │
│ dhcp relay server 10.1.0.6 │
└──────────────────────────────────────────┘
方案二:DHCP Snooping 安全加固
┌──────────────────────────────────────────┐
│ # 限制端口 DHCP 请求速率 │
│ interface GigabitEthernet0/0/1 │
│ dhcp snooping check dhcp-rate enable │
│ dhcp snooping dhcp-rate 10 │
│ # 每端口最多 10 包/秒 │
│ │
│ # 启用 Option 82 插入 │
│ dhcp snooping option82 enable │
│ dhcp snooping option82 insert │
│ # 便于定位 DHCP 请求来源 │
│ │
│ # 启用 MAC 地址校验 │
│ dhcp snooping check mac-address │
└──────────────────────────────────────────┘
四、自动化监控脚本
#!/usr/bin/env python3
"""
DHCP 服务健康监控脚本
检查 DHCP 服务器可达性、地址池水位、Relay 统计
"""
import subprocess
import re
import time
import json
from datetime import datetime
class DHCPHealthMonitor:
"""DHCP 健康监控器"""
def __init__(self, relay_ip, server_ips, pool_subnets):
self.relay_ip = relay_ip
self.server_ips = server_ips
self.pool_subnets = pool_subnets
self.status = {}
def check_server_reachability(self, ip):
"""检查 DHCP 服务器可达性"""
try:
result = subprocess.run(
["ping", "-n", "2", ip],
capture_output=True,
text=True,
timeout=10
)
return result.returncode == 0
except subprocess.TimeoutExpired:
return False
def check_relay_statistics(self):
"""检查 DHCP Relay 统计(模拟)"""
# 实际场景通过 SSH/Netconf 采集
stats = {
"received_from_client": 1500,
"sent_to_server": 1500, # 正常应非零
"received_from_server": 1500,
"sent_to_client": 1500,
"discard_packets": 0,
"error_packets": 0
}
return stats
def check_pool_utilization(self, subnet):
"""检查地址池利用率(模拟)"""
total = 254
used = 220
reserved = 10
free = total - used - reserved
return {
"subnet": subnet,
"total": total,
"used": used,
"free": free,
"utilization_pct": round(used / total * 100, 1)
}
def run_health_check(self):
"""执行完整健康检查"""
timestamp = datetime.now().isoformat()
results = {
"timestamp": timestamp,
"server_status": {},
"relay_status": {},
"pool_status": [],
"overall_status": "HEALTHY"
}
# 检查服务器
all_servers_ok = True
for server_ip in self.server_ips:
reachable = self.check_server_reachability(server_ip)
results["server_status"][server_ip] = {
"reachable": reachable
}
if not reachable:
all_servers_ok = False
# 检查 Relay
relay_stats = self.check_relay_statistics()
relay_ok = (
relay_stats["sent_to_server"] > 0
and relay_stats["sent_to_client"] > 0
)
results["relay_status"] = {
"stats": relay_stats,
"healthy": relay_ok
}
# 检查地址池
pool_warning = False
for subnet in self.pool_subnets:
pool = self.check_pool_utilization(subnet)
results["pool_status"].append(pool)
if pool["utilization_pct"] > 90:
pool_warning = True
# 综合状态
if not all_servers_ok:
results["overall_status"] = "CRITICAL"
results["alert"] = "DHCP 服务器不可达!"
elif not relay_ok:
results["overall_status"] = "CRITICAL"
results["alert"] = "DHCP Relay 异常!"
elif pool_warning:
results["overall_status"] = "WARNING"
results["alert"] = "地址池使用率超过 90%!"
else:
results["overall_status"] = "HEALTHY"
self.status = results
return results
def print_report(self):
"""打印检查报告"""
if not self.status:
self.run_health_check()
status = self.status
print(f"\nDHCP 健康检查报告")
print(f"时间: {status['timestamp']}")
print(f"状态: {status['overall_status']}")
print("=" * 50)
if "alert" in status:
print(f"⚠ 告警: {status['alert']}\n")
print("DHCP 服务器状态:")
for ip, info in status["server_status"].items():
symbol = "✅" if info["reachable"] else "❌"
print(f" {symbol} {ip}: {'可达' if info['reachable'] else '不可达'}")
print("\nDHCP Relay 状态:")
relay = status["relay_status"]
print(f" {'✅' if relay['healthy'] else '❌'} 转发正常")
print(f" 收到客户端: {relay['stats']['received_from_client']}")
print(f" 发往服务器: {relay['stats']['sent_to_server']}")
print(f" 收到服务器: {relay['stats']['received_from_server']}")
print("\n地址池状态:")
for pool in status["pool_status"]:
bar = "█" * int(pool["utilization_pct"] / 5)
print(f" {pool['subnet']}: [{bar:<20}] {pool['utilization_pct']}%")
print(f" 已用: {pool['used']}, 空闲: {pool['free']}, 总: {pool['total']}")
def main():
"""主函数"""
monitor = DHCPHealthMonitor(
relay_ip="10.1.0.1",
server_ips=["10.1.0.5", "10.1.0.6"],
pool_subnets=["10.1.1.0/24", "10.1.2.0/24", "10.1.3.0/24"]
)
# 运行检查
monitor.run_health_check()
monitor.print_report()
# 持续监控示例
print("\n\n持续监控模式(每 60 秒检查一次)...")
print("按 Ctrl+C 退出")
try:
while True:
monitor.run_health_check()
if monitor.status["overall_status"] != "HEALTHY":
print(f"\n[{datetime.now().isoformat()}] "
f"告警: {monitor.status.get('alert', '未知')}")
time.sleep(60)
except KeyboardInterrupt:
print("\n监控已停止")
if __name__ == "__main__":
main()
五、DHCP 排障常见场景
常见 DHCP 故障场景速查表:
场景 可能原因 排查方向 Discover 无响应 Relay 故障/防火墙 检查 Relay 统计 获取到 169.254.x.x DHCP Server 不可达 检查服务器连通 IP 地址冲突 Snooping 表过期 清除绑定表 获取慢(>10秒) Option 82 处理慢 优化 Relay 部分用户获取不到 地址池耗尽 增加地址池 续约失败 租期太短 调整租期 收到非法 DHCP Server Snooping 未配置 启用 Snooping VLAN 间获取不到 Relay 未配置 VLANIF 配置 Relay
六、总结
DHCP 故障排查要点:
1. 分层排查
└─ 客户端 → 接入交换机 → 汇聚 → Core → 服务器
└─ 逐段确认 DHCP 报文是否正常转发
2. 关键检查点
└─ DHCP Snooping 绑定表(检查是否满)
└─ DHCP Relay 统计(确认转发正常)
└─ 防火墙策略(UDP 67/68 是否放通)
└─ 地址池利用率(避免耗尽)
3. 预防措施
└─ 部署双 DHCP Server 冗余
└─ 配置 DHCP Snooping 安全功能
└─ 定期检查地址池水位
└─ 变更防火墙策略前评估 DHCP 影响
└─ 监控 Relay 统计,发现异常及时处理
下篇预告:第343篇《防火墙策略冲突与安全域隔离故障案例》——通过防火墙策略冲突案例,掌握安全域划分和策略优化的实战技能。
下篇预告:第343篇《防火墙策略冲突与安全域隔离故障案例》——通过防火墙策略冲突案例,掌握安全域划分和策略优化的实战技能。