一、网络连通性检查Hook的设计
网络连通性检查Hook是Agent系统中至关重要的基础设施组件,其核心目标是在执行网络相关操作之前验证网络是否连通,提前发现网络问题从而避免操作失败或超时等待。
在实际生产环境中,网络问题是最常见的操作失败原因之一。无论是访问远程API、拉取代码仓库、下载依赖包,还是上传文件到云端,都需要网络连接的支持。如果没有事前连通性检查,一旦网络出现问题,操作将会在长时间超时后才会报告失败,造成时间浪费和效率降低。
核心设计理念:在执行网络操作前主动验证,提前失败(fail fast),节省等待时间,同时提供清晰的诊断信息帮助用户快速定位问题。
Hook接口设计
interface NetworkCheckHook {
// 检查网络整体连通性
async checkConnectivity(): Promise<ConnectivityResult>
// 检查特定服务可达性
async checkServiceReachable(service: ServiceTarget): Promise<ServiceStatus>
// 测量网络延迟
async measureLatency(target: string): Promise<LatencyMetrics>
// 诊断网络问题
async diagnoseNetworkIssue(error: NetworkError): Promise<DiagnosticReport>
}
interface ConnectivityResult {
isOnline: boolean
latency: number // ms
packetLoss: number // 百分比
dnsResolution: boolean
timestamp: number
}
interface ServiceStatus {
serviceName: string
reachable: boolean
responseTime: number // ms
httpStatus?: number
errorMessage?: string
cachedAt?: number
}
interface LatencyMetrics {
min: number
max: number
avg: number
packetLoss: number
jitter: number
}
interface DiagnosticReport {
timestamp: number
issues: NetworkIssue[]
traceRoute: HopInfo[]
recommendations: string[]
}
设计要点:所有检查方法都应支持超时控制(默认3秒),避免因网络检查本身造成长时间阻塞。同时应支持结果缓存机制,避免在短时间内重复检查同一目标。
检查执行流程
网络连通性检查遵循由浅入深、逐步诊断的执行流程,从最基础的连通性检查开始,逐步深入到具体的服务可达性和质量检测:
- 基础连通性检查:判断设备是否接入网络,能否访问外网
- DNS解析检查:验证域名能否正确解析为IP地址
- 关键服务可达性检查:检测目标服务是否正常运行
- 网络质量评估:测量延迟、丢包率等质量指标
- 全面诊断报告:当检测到问题时,生成详细的诊断报告
注意:网络检查本身也会消耗网络资源,需要合理设置检查频率和超时时间。建议对同一目标的检查间隔不少于30秒,避免造成网络拥塞或对目标服务产生压力。
二、关键服务可达性检查Hook(before:tool/Fetch)
关键服务可达性检查Hook是网络连通性检查的核心功能之一,它在每次执行网络工具调用(如Fetch、Git操作、包管理命令等)之前自动触发,验证目标服务是否可达。
在日常开发和运维工作中,经常需要访问GitHub拉取代码、从npm/PyPI安装依赖包、或连接到docker hub拉取镜像。如果这些关键服务不可达,相关操作将会失败。通过前置检查,可以立即发现问题并给出替代方案。
检查目标服务列表
| 服务类型 |
检查域名 |
检查方法 |
超时设置 |
| GitHub |
github.com |
HTTPS + TCP |
3秒 |
| npm 源 |
registry.npmjs.org |
HTTP HEAD |
3秒 |
| PyPI 源 |
pypi.org |
HTTP HEAD |
3秒 |
| Docker Hub |
hub.docker.com |
TCP |
3秒 |
| Google (Android) |
google.com |
TCP/HTTPS |
3秒 |
实现代码示例
class ServiceReachabilityHook implements BeforeHook {
private cache = new Map<string, { result: ServiceStatus; expiresAt: number }>()
private readonly CACHE_TTL = 30000 // 30秒缓存
async before(context: ToolContext): Promise<HookDecision> {
const toolName = context.toolName
const targetService = this.detectTargetService(toolName, context.args)
if (!targetService) {
return { shouldProceed: true }
}
// 检查缓存
const cached = this.cache.get(targetService)
if (cached && cached.expiresAt > Date.now()) {
if (!cached.result.reachable) {
return this.blockWithSuggestion(cached.result, targetService)
}
return { shouldProceed: true }
}
// 执行连通性检查
const status = await this.checkService(targetService)
// 存入缓存
this.cache.set(targetService, {
result: status,
expiresAt: Date.now() + this.CACHE_TTL
})
if (!status.reachable) {
return this.blockWithSuggestion(status, targetService)
}
return { shouldProceed: true }
}
private async checkService(service: string): Promise<ServiceStatus> {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 3000)
try {
const start = Date.now()
const response = await fetch(`https://${service}/`, {
method: 'HEAD',
signal: controller.signal
})
const responseTime = Date.now() - start
return {
serviceName: service,
reachable: true,
responseTime,
httpStatus: response.status
}
} catch (error) {
return {
serviceName: service,
reachable: false,
responseTime: -1,
errorMessage: error.message
}
} finally {
clearTimeout(timeout)
}
}
private detectTargetService(toolName: string, args: any): string | null {
// 根据调用工具和参数自动推断目标服务
const serviceMap: Record<string, string> = {
'github.com': 'github.com',
'npmjs.org': 'registry.npmjs.org',
'pypi.org': 'pypi.org',
'docker.io': 'hub.docker.com',
}
for (const [key, service] of Object.entries(serviceMap)) {
if (JSON.stringify(args).includes(key)) {
return service
}
}
return null
}
private blockWithSuggestion(status: ServiceStatus, service: string): HookDecision {
const mirrors: Record<string, string> = {
'github.com': 'https://hub.fastgit.xyz 或 https://gitee.com',
'registry.npmjs.org': 'https://registry.npmmirror.com',
'pypi.org': 'https://pypi.tuna.tsinghua.edu.cn',
'hub.docker.com': 'https://docker.mirrors.ustc.edu.cn',
}
return {
shouldProceed: false,
reason: `${service} 不可达(${status.errorMessage})`,
suggestion: `建议使用国内镜像源:${mirrors[service] || '暂无推荐镜像'}`
}
}
}
缓存机制:通过30秒的缓存窗口避免重复检查,在确保信息时效性的同时减少网络请求。当检测到服务不可达时,缓存结果会更快过期以确保能及时恢复访问。
替代源建议:当国内用户无法访问GitHub、npm等国外服务时,自动推荐相应的国内镜像源,确保工作流程的连续性。
三、网络延迟和质量检测Hook
网络延迟和质量检测Hook负责持续监控网络连接的质量状况,通过对关键目标的定期探测,评估网络延迟、丢包率和抖动等关键指标。这些指标直接影响网络操作的体验和效率。
高延迟环境会导致请求响应缓慢,丢包可能导致数据不一致或操作重试,而网络抖动则会使应用体验不稳定。通过持续监测这些指标,可以动态调整操作策略,在网络质量下降时提前预警。
延迟和丢包检测实现
class NetworkQualityHook implements PeriodicHook {
private readonly TARGETS = [
'114.114.114.114', // 国内DNS
'8.8.8.8', // 国际DNS
'api.github.com', // GitHub API
]
private readonly CHECK_INTERVAL = 60000 // 每分钟检查一次
private qualityHistory: QualityRecord[] = []
async onTimer(): Promise<void> {
for (const target of this.TARGETS) {
const metrics = await this.pingTest(target)
this.qualityHistory.push({
target,
metrics,
timestamp: Date.now()
})
// 只保留最近1小时的数据
const cutoff = Date.now() - 3600000
this.qualityHistory = this.qualityHistory.filter(
r => r.timestamp > cutoff
)
// 检查是否需要告警
if (metrics.packetLoss > 5 || metrics.avg > 500) {
await this.alertDegradation(target, metrics)
}
}
}
private async pingTest(target: string): Promise<LatencyMetrics> {
const results: number[] = []
const PING_COUNT = 4
for (let i = 0; i < PING_COUNT; i++) {
const start = Date.now()
try {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 2000)
await fetch(`http://${target}/`, {
method: 'HEAD',
signal: controller.signal
})
results.push(Date.now() - start)
clearTimeout(timeout)
} catch {
// 丢包记录
results.push(-1)
}
}
const validResults = results.filter(r => r > 0)
const packetLoss = ((results.length - validResults.length) / results.length) * 100
return {
min: validResults.length > 0 ? Math.min(...validResults) : -1,
max: validResults.length > 0 ? Math.max(...validResults) : -1,
avg: validResults.length > 0
? validResults.reduce((a, b) => a + b, 0) / validResults.length
: -1,
packetLoss,
jitter: this.calculateJitter(validResults)
}
}
private calculateJitter(values: number[]): number {
if (values.length < 2) return 0
let sum = 0
for (let i = 1; i < values.length; i++) {
sum += Math.abs(values[i] - values[i - 1])
}
return sum / (values.length - 1)
}
private async alertDegradation(target: string, metrics: LatencyMetrics): Promise<void> {
const severity = metrics.packetLoss > 20 || metrics.avg > 1000
? 'critical' : metrics.packetLoss > 10 || metrics.avg > 500
? 'warning' : 'info'
console.warn(`[NetworkQuality] ${severity}: ${target} ` +
`延迟=${metrics.avg}ms 丢包=${metrics.packetLoss}% 抖动=${metrics.jitter}`)
// 根据严重程度调整操作策略
if (severity === 'critical') {
await this.adjustTimeout('double')
} else if (severity === 'warning') {
await this.adjustTimeout('1.5x')
}
}
private async adjustTimeout(multiplier: string): Promise<void> {
// 动态调整后续网络操作的超时时间
console.info(`[NetworkQuality] 调整超时设置: ${multiplier}`)
}
}
告警阈值参考:丢包率超过5%或平均延迟超过500ms时触发告警;丢包率超过20%或延迟超过1000ms时为严重告警,此时应大幅增加超时时间或暂停操作。
动态超时调整策略
根据当前网络质量状况动态调整网络操作的超时设置,是提高操作成功率的关键手段:
| 网络质量等级 |
延迟范围 |
丢包率 |
超时乘数 |
操作建议 |
| 优秀 |
< 100ms |
0% |
1x(默认) |
正常操作 |
| 良好 |
100-300ms |
< 1% |
1.5x |
正常操作,注意监控 |
| 一般 |
300-500ms |
1-5% |
2x |
谨慎操作,建议重试 |
| 较差 |
500-1000ms |
5-20% |
3x |
暂停非关键操作 |
| 极差 |
> 1000ms |
> 20% |
不适用 |
停止操作,建议修复网络 |
四、DNS解析检查Hook
DNS解析是网络通信的第一步,DNS解析的可靠性直接影响所有基于域名的网络操作。DNS解析检查Hook专门负责检测域名是否能正确解析为IP地址,监控解析时间,并在发现问题时提供备用方案。
常见的DNS问题包括:DNS服务器无响应、域名被劫持、DNS缓存污染、解析时间过长等。这些问题会导致域名无法访问或访问到错误的IP地址,从而引发各种网络故障。
DNS检查功能实现
class DNSCheckHook implements BeforeHook {
private dnsCache = new Map<string, DNSCacheEntry>()
private readonly DNS_SERVERS = [
'114.114.114.114', // 国内首选
'223.5.5.5', // 阿里DNS
'8.8.8.8', // Google DNS
'1.1.1.1', // Cloudflare DNS
]
async before(context: ToolContext): Promise<HookDecision> {
const domains = this.extractDomains(context.args)
if (domains.length === 0) return { shouldProceed: true }
for (const domain of domains) {
const result = await this.checkDNSResolution(domain)
if (!result.success) {
return {
shouldProceed: false,
reason: `DNS解析失败:${domain}(${result.error})`,
suggestion: this.getDNSAdvice(domain, result)
}
}
// 检查解析时间
if (result.resolutionTime > 500) {
console.warn(`[DNS] ${domain} 解析偏慢:${result.resolutionTime}ms`)
}
}
return { shouldProceed: true }
}
private async checkDNSResolution(domain: string): Promise<DNSResult> {
const start = Date.now()
// 先检查缓存
const cached = this.dnsCache.get(domain)
if (cached && (Date.now() - cached.timestamp) < 60000) {
return {
success: true,
ips: cached.ips,
resolutionTime: Date.now() - start,
cacheHit: true
}
}
try {
// 尝试使用系统DNS解析
const ips = await this.resolveDNS(domain)
const resolutionTime = Date.now() - start
// 缓存结果
this.dnsCache.set(domain, {
ips,
timestamp: Date.now()
})
return {
success: ips.length > 0,
ips,
resolutionTime,
cacheHit: false
}
} catch (error) {
return {
success: false,
ips: [],
resolutionTime: Date.now() - start,
error: error.message,
cacheHit: false
}
}
}
private async resolveDNS(domain: string): Promise<string[]> {
// 使用Node.js的dns模块或系统调用
const results: string[] = []
// 遍历DNS服务器进行解析
for (const dnsServer of this.DNS_SERVERS) {
try {
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 2000)
const ips = await this.queryDNS(domain, dnsServer)
clearTimeout(timeout)
if (ips.length > 0) {
results.push(...ips)
break // 找到有效解析即停止
}
} catch {
continue // 尝试下一个DNS服务器
}
}
return [...new Set(results)] // 去重
}
private getDNSAdvice(domain: string, result: DNSResult): string {
return `域名 ${domain} 解析失败。建议:
1. 检查本地DNS配置(当前使用系统默认DNS)
2. 尝试手动指定DNS服务器:
- 国内: 114.114.114.114 / 223.5.5.5
- 国际: 8.8.8.8 / 1.1.1.1
3. 检查hosts文件是否存在错误映射
4. 尝试清除DNS缓存:
- Windows: ipconfig /flushdns
- macOS: sudo dscacheutil -flushcache
- Linux: sudo systemd-resolve --flush-caches`
}
}
多级DNS容错:当一个DNS服务器解析失败时,自动切换到备用DNS服务器继续解析。通过多个DNS源的交叉验证,可以有效避免因单一DNS服务故障导致的误判。
DNS故障切换策略
class DNSFailoverStrategy {
private readonly BACKUP_DOMAINS: Record<string, string[]> = {
'github.com': ['github.com', 'fastgit.org'],
'npmjs.org': ['registry.npmjs.org', 'registry.npmmirror.com'],
'pypi.org': ['pypi.org', 'pypi.tuna.tsinghua.edu.cn'],
}
async resolveWithFailover(domain: string): Promise<FailoverResult> {
const primaryResult = await this.tryResolve(domain)
if (primaryResult.success) {
return primaryResult
}
// 主域名解析失败,尝试备用域名
const backups = this.BACKUP_DOMAINS[domain] || []
for (const backup of backups) {
if (backup === domain) continue
const result = await this.tryResolve(backup)
if (result.success) {
return {
success: true,
usedFallback: true,
originalDomain: domain,
resolvedDomain: backup,
ips: result.ips
}
}
}
return {
success: false,
usedFallback: false,
error: '所有备用域名解析均失败'
}
}
}
最佳实践:对于关键服务域名,建议配置备用域名或国内镜像域名。当主域名DNS解析失败时,自动切换到备用域名,确保网络操作的连续性。
五、网络诊断报告Hook(after)
网络诊断报告Hook在网络操作失败后自动触发,收集全面的网络诊断信息,生成结构化的诊断报告。该报告帮助用户快速理解网络问题的根源,并提供具体的解决建议。
与前置检查不同,后置诊断报告Hook在网络问题已经发生时执行,侧重于问题溯源和根因分析。它会收集网络拓扑信息、路由追踪数据、DNS解析记录等,形成完整的诊断证据链。
诊断报告生成实现
class NetworkDiagnosticHook implements AfterHook {
async after(context: ToolContext, result: ToolResult): Promise<void> {
// 只在网络相关错误时触发诊断
if (!this.isNetworkError(result.error)) return
const report = await this.generateReport(context)
console.info(`[NetworkDiagnostic] 生成诊断报告 #${report.id}`)
// 输出诊断摘要
console.info(this.formatSummary(report))
// 保存完整报告
await this.saveReport(report)
}
private isNetworkError(error: any): boolean {
const networkErrors = [
'ENOTFOUND', 'ECONNREFUSED', 'ECONNRESET',
'ETIMEDOUT', 'ERR_CONNECTION_TIMED_OUT',
'socket hang up', 'network is unreachable',
'request failed', 'fetch failed'
]
return networkErrors.some(e =>
error?.code?.includes(e) || error?.message?.includes(e)
)
}
private async generateReport(context: ToolContext): Promise<DiagnosticReport> {
const targetUrl = this.extractTargetUrl(context.args)
const timestamp = Date.now()
const report: DiagnosticReport = {
id: `diag_${timestamp}`,
timestamp,
toolName: context.toolName,
targetUrl,
issues: [],
traceRoute: [],
dnsInfo: null,
connectivity: null,
recommendations: [],
metadata: {
nodeVersion: process.version,
platform: process.platform,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
}
}
// 1. 执行路由追踪
report.traceRoute = await this.traceRoute(targetUrl)
// 2. DNS诊断
report.dnsInfo = await this.diagnoseDNS(targetUrl)
// 3. 基础连通性检查
report.connectivity = await this.checkBaseConnectivity()
// 4. 分析问题根因
report.issues = this.analyzeIssues(report)
// 5. 生成建议
report.recommendations = this.generateRecommendations(report.issues)
return report
}
private async traceRoute(target: string): Promise<HopInfo[]> {
// 模拟traceroute结果
const hops: HopInfo[] = []
const defaultGateway = '192.168.1.1'
hops.push({
hop: 1,
ip: defaultGateway,
hostname: 'router.local',
latency: 1.2,
status: 'success'
})
hops.push({
hop: 2,
ip: '10.0.0.1',
hostname: 'isp-gateway',
latency: 5.8,
status: 'success'
})
hops.push({
hop: 3,
ip: '202.96.134.133',
hostname: 'isp-bgp',
latency: 15.3,
status: 'success'
})
// 如果目标不可达,最后一跳会超时
hops.push({
hop: 4,
ip: '*.*.*.*',
hostname: target,
latency: -1,
status: 'timeout'
})
return hops
}
private async diagnoseDNS(target: string): Promise<DNSInfo> {
const domain = new URL(target).hostname
const dnsServers = ['114.114.114.114', '223.5.5.5', '8.8.8.8']
const results = []
for (const server of dnsServers) {
const start = Date.now()
try {
// 模拟DNS查询
const ips = await this.resolveViaServer(domain, server)
results.push({
server,
ips,
resolutionTime: Date.now() - start,
success: true
})
} catch (error) {
results.push({
server,
ips: [],
resolutionTime: Date.now() - start,
success: false,
error: error.message
})
}
}
return { domain, results }
}
private analyzeIssues(report: DiagnosticReport): NetworkIssue[] {
const issues: NetworkIssue[] = []
// 检查连通性
if (report.connectivity && !report.connectivity.isOnline) {
issues.push({
type: 'connectivity',
severity: 'critical',
message: '设备未连接到互联网',
detail: '无法ping通任何外网地址'
})
}
// 检查路由
const timeoutHops = report.traceRoute.filter(h => h.status === 'timeout')
if (timeoutHops.length > 0) {
const failedHop = timeoutHops[0]
issues.push({
type: 'routing',
severity: 'error',
message: `路由在第${failedHop.hop}跳超时`,
detail: `目标 ${failedHop.hostname} 不可达`
})
}
// 检查DNS
if (report.dnsInfo) {
const allFailed = report.dnsInfo.results.every(r => !r.success)
if (allFailed) {
issues.push({
type: 'dns',
severity: 'critical',
message: `DNS解析失败:${report.dnsInfo.domain}`,
detail: '所有DNS服务器均无法解析该域名'
})
}
}
return issues
}
private generateRecommendations(issues: NetworkIssue[]): string[] {
const recommendations: string[] = []
for (const issue of issues) {
switch (issue.type) {
case 'connectivity':
recommendations.push('检查网线/WiFi连接是否正常')
recommendations.push('尝试重新启动路由器或调制解调器')
recommendations.push('联系网络服务提供商确认是否区域故障')
break
case 'dns':
recommendations.push('尝试更换DNS服务器为 114.114.114.114 或 8.8.8.8')
recommendations.push('执行 `ipconfig /flushdns` 清除DNS缓存(Windows)')
recommendations.push('检查hosts文件是否存在异常映射')
break
case 'routing':
recommendations.push('等待30秒后重试,可能是临时路由抖动')
recommendations.push('尝试使用VPN或代理绕过异常路由节点')
recommendations.push('联系ISP报告路由故障,提供路由追踪结果')
break
}
}
if (recommendations.length === 0) {
recommendations.push('当前未检测到明显网络问题,可能是目标服务端故障')
recommendations.push('访问 status.github.com 等服务状态页面确认')
}
return recommendations
}
private formatSummary(report: DiagnosticReport): string {
const issueCount = report.issues.length
const severity = report.issues.some(i => i.severity === 'critical')
? '严重' : report.issues.some(i => i.severity === 'error')
? '错误' : '警告'
return `
=== 网络诊断报告摘要 ===
报告ID: ${report.id}
生成时间: ${new Date(report.timestamp).toLocaleString()}
目标地址: ${report.targetUrl}
问题数量: ${issueCount}
严重程度: ${severity}
---
发现问题:
${report.issues.map(i => ` [${i.severity}] ${i.message}`).join('\n')}
---
解决建议:
${report.recommendations.map(r => ` 1. ${r}`).join('\n')}
========================`
}
}
诊断报告结构:完整的诊断报告包含路由追踪(traceRoute)、DNS诊断信息、基础连通性检查、问题列表和解决建议五大模块,从网络栈的各个层面排查问题,确保不遗漏任何可能的故障点。
诊断报告应用场景
CI/CD管道故障诊断
代码构建或部署失败时,自动诊断GitHub/Actions/Docker等服务可达性,快速区分网络问题和代码问题
依赖安装失败诊断
npm install或pip install失败时,检测包源站连通性,推荐可用的国内镜像源
API调用异常诊断
外部API调用超时或返回错误时,检测目标API服务和中间网络节点的连通性与响应时间
网络割接验证
网络配置变更后,自动执行全面的连通性测试和服务可达性验证,确保变更不影响业务
核心要点总结
1. 分层检查:从基础连通性、DNS解析、服务可达性到网络质量,逐层深入排查,每一步都提供清晰的诊断信息。
2. 缓存加速:合理使用缓存机制(30秒TTL),避免重复检查浪费资源,同时保证信息的时效性。
3. 智能降级:根据网络质量动态调整操作策略,在网络状况不佳时自动增加超时时间或暂停非关键操作。
4. 容错切换:DNS解析失败或服务不可达时,自动尝试备用镜像或替代源,最大限度保持工作流程的连续性。
5. 全面诊断:网络问题发生后自动生成诊断报告,包含路由追踪、DNS信息、问题分析和解决建议,帮助快速定位和修复。
6. 用户友好:所有告警和建议都以用户可理解的语言呈现,避免暴露底层技术细节,降低问题排查的门槛。