网络连通性检查Hook

操作前自动检查网络连通性

一、网络连通性检查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秒),避免因网络检查本身造成长时间阻塞。同时应支持结果缓存机制,避免在短时间内重复检查同一目标。

检查执行流程

网络连通性检查遵循由浅入深、逐步诊断的执行流程,从最基础的连通性检查开始,逐步深入到具体的服务可达性和质量检测:

  1. 基础连通性检查:判断设备是否接入网络,能否访问外网
  2. DNS解析检查:验证域名能否正确解析为IP地址
  3. 关键服务可达性检查:检测目标服务是否正常运行
  4. 网络质量评估:测量延迟、丢包率等质量指标
  5. 全面诊断报告:当检测到问题时,生成详细的诊断报告
注意:网络检查本身也会消耗网络资源,需要合理设置检查频率和超时时间。建议对同一目标的检查间隔不少于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. 用户友好:所有告警和建议都以用户可理解的语言呈现,避免暴露底层技术细节,降低问题排查的门槛。