diff --git a/dns/server.go b/dns/server.go index 9b4b26b..857782a 100644 --- a/dns/server.go +++ b/dns/server.go @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" "net" - "net/http" "os" "path/filepath" "runtime" @@ -49,12 +48,7 @@ type ClientStats struct { LastSeen time.Time } -// IPGeolocation IP地理位置信息 -type IPGeolocation struct { - Country string `json:"country"` // 国家 - City string `json:"city"` // 城市 - Expiry time.Time `json:"expiry"` // 缓存过期时间 -} + // DNSAnswer DNS解析记录 type DNSAnswer struct { @@ -137,10 +131,7 @@ type Server struct { stopped bool // 服务器是否已经停止 stoppedMutex sync.Mutex // 保护stopped标志的互斥锁 - // IP地理位置缓存 - ipGeolocationCache map[string]*IPGeolocation // IP地址到地理位置的映射 - ipGeolocationCacheMutex sync.RWMutex // 保护IP地理位置缓存的互斥锁 - ipGeolocationCacheTTL time.Duration // 缓存有效期 + // DNS查询缓存 DnsCache *DNSCache // DNS响应缓存 @@ -214,9 +205,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie maxQueryLogs: 10000, // 最大保存10000条日志 saveDone: make(chan struct{}), stopped: false, // 初始化为未停止状态 - // IP地理位置缓存初始化 - ipGeolocationCache: make(map[string]*IPGeolocation), - ipGeolocationCacheTTL: 24 * time.Hour, // 缓存有效期24小时 + // DNS查询缓存初始化 DnsCache: NewDNSCache(cacheTTL), // 初始化域名DNSSEC状态映射表 @@ -269,8 +258,7 @@ func (s *Server) Start() error { // 启动自动保存功能 go s.startAutoSave() - // 启动IP地理位置缓存清理协程 - go s.startIPGeolocationCacheCleanup() + // 启动UDP服务 go func() { @@ -1136,147 +1124,7 @@ func (s *Server) forwardDNSRequestWithCache(r *dns.Msg, domain string) (*dns.Msg logger.Debug("合并所有响应返回", "domain", domain, "responseCount", len(validResponses)) } - case "loadbalance": - // 负载均衡模式 - 使用加权随机选择算法 - // 1. 尝试所有可用的服务器,直到找到一个能正常工作的 - var triedServers []string - for len(triedServers) < len(selectedUpstreamDNS) { - // 从剩余的服务器中选择一个加权随机服务器 - var availableServers []string - for _, server := range selectedUpstreamDNS { - found := false - for _, tried := range triedServers { - if server == tried { - found = true - break - } - } - if !found { - availableServers = append(availableServers, server) - } - } - selectedServer := s.selectWeightedRandomServer(availableServers) - if selectedServer == "" { - break - } - - triedServers = append(triedServers, selectedServer) - logger.Debug("在负载均衡模式下选择服务器", "domain", domain, "server", selectedServer, "triedServers", triedServers) - - // 使用带超时的方式执行Exchange - resultChan := make(chan struct { - response *dns.Msg - rtt time.Duration - err error - }, 1) - - go func() { - response, rtt, err := s.resolver.Exchange(r, normalizeDNSServerAddress(selectedServer)) - resultChan <- struct { - response *dns.Msg - rtt time.Duration - err error - }{response, rtt, err} - }() - - var response *dns.Msg - var rtt time.Duration - var err error - - // 直接获取结果,不使用上下文超时 - result := <-resultChan - response, rtt, err = result.response, result.rtt, result.err - - if err == nil && response != nil { - // 更新服务器统计信息 - s.updateServerStats(selectedServer, true, rtt) - - // 检查是否包含DNSSEC记录 - containsDNSSEC := s.hasDNSSECRecords(response) - - // 如果启用了DNSSEC且响应包含DNSSEC记录,验证DNSSEC签名 - // 但如果域名匹配不验证DNSSEC的模式,则跳过验证 - if s.config.EnableDNSSEC && containsDNSSEC && !noDNSSEC { - // 验证DNSSEC记录 - signatureValid := s.verifyDNSSEC(response) - - // 设置AD标志(Authenticated Data) - response.AuthenticatedData = signatureValid - - if signatureValid { - // 更新DNSSEC验证成功计数 - s.updateStats(func(stats *Stats) { - stats.DNSSECQueries++ - stats.DNSSECSuccess++ - }) - } else { - // 更新DNSSEC验证失败计数 - s.updateStats(func(stats *Stats) { - stats.DNSSECQueries++ - stats.DNSSECFailed++ - }) - } - } else if noDNSSEC { - // 对于不验证DNSSEC的域名,始终设置AD标志为false - response.AuthenticatedData = false - } - - // 如果响应成功或为NXDOMAIN,根据DNSSEC状态选择最佳响应 - if response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError { - if response.Rcode == dns.RcodeSuccess { - // 优先选择带有DNSSEC记录的响应 - if containsDNSSEC { - bestResponse = response - bestRtt = rtt - hasBestResponse = true - hasDNSSECResponse = true - usedDNSServer = selectedServer - // 如果当前使用的服务器是DNSSEC专用服务器,同时设置usedDNSSECServer - for _, dnssecServer := range dnssecServers { - if dnssecServer == selectedServer { - usedDNSSECServer = selectedServer - break - } - } - logger.Debug("找到带DNSSEC的最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt) - } else { - // 没有带DNSSEC的响应时,保存成功响应 - bestResponse = response - bestRtt = rtt - hasBestResponse = true - usedDNSServer = selectedServer - // 如果当前使用的服务器是DNSSEC专用服务器,同时设置usedDNSSECServer - for _, dnssecServer := range dnssecServers { - if dnssecServer == selectedServer { - usedDNSSECServer = selectedServer - break - } - } - logger.Debug("找到最佳响应", "domain", domain, "server", selectedServer, "rtt", rtt) - } - } else if response.Rcode == dns.RcodeNameError { - // 处理NXDOMAIN响应 - bestResponse = response - bestRtt = rtt - hasBestResponse = true - usedDNSServer = selectedServer - logger.Debug("找到NXDOMAIN响应", "domain", domain, "server", selectedServer, "rtt", rtt) - } - // 保存为备选响应 - if !hasBackup { - backupResponse = response - backupRtt = rtt - hasBackup = true - } - break // 找到有效响应,退出循环 - } - } else { - // 更新服务器统计信息(失败) - s.updateServerStats(selectedServer, false, 0) - logger.Debug("服务器请求失败,尝试下一个", "domain", domain, "server", selectedServer, "error", err) - } - } case "fastest-ip": // 最快的IP地址模式 - 使用TCP连接速度测量选择最快服务器 @@ -2403,14 +2251,11 @@ func (s *Server) updateStats(update func(*Stats)) { // addQueryLog 添加查询日志 func (s *Server) addQueryLog(clientIP, domain, queryType string, responseTime int64, result, blockRule, blockType string, fromCache, dnssec, edns bool, dnsServer, dnssecServer string, answers []DNSAnswer, responseCode int) { - // 获取IP地理位置 - location := s.getIpGeolocation(clientIP) - // 创建日志记录 log := QueryLog{ Timestamp: time.Now(), ClientIP: clientIP, - Location: location, + Location: "", // 客户端IP地理位置由前端处理 Domain: domain, QueryType: queryType, ResponseTime: responseTime, @@ -2799,82 +2644,7 @@ func isPrivateIP(ip string) bool { return false } -// getIpGeolocation 获取IP地址的地理位置信息 -func (s *Server) getIpGeolocation(ip string) string { - // 检查IP是否为本地或内网地址 - if isPrivateIP(ip) { - return "内网 内网" - } - // 先检查缓存 - s.ipGeolocationCacheMutex.RLock() - geo, exists := s.ipGeolocationCache[ip] - s.ipGeolocationCacheMutex.RUnlock() - - // 如果缓存存在且未过期,直接返回 - if exists && time.Now().Before(geo.Expiry) { - return fmt.Sprintf("%s %s", geo.Country, geo.City) - } - - // 缓存不存在或已过期,从API获取 - geoInfo, err := s.fetchIpGeolocationFromAPI(ip) - if err != nil { - logger.Error("获取IP地理位置失败", "ip", ip, "error", err) - return "未知 未知" - } - - // 保存到缓存 - s.ipGeolocationCacheMutex.Lock() - s.ipGeolocationCache[ip] = &IPGeolocation{ - Country: geoInfo["country"].(string), - City: geoInfo["city"].(string), - Expiry: time.Now().Add(s.ipGeolocationCacheTTL), - } - s.ipGeolocationCacheMutex.Unlock() - - // 返回格式化的地理位置 - return fmt.Sprintf("%s %s", geoInfo["country"].(string), geoInfo["city"].(string)) -} - -// fetchIpGeolocationFromAPI 从第三方API获取IP地理位置信息 -func (s *Server) fetchIpGeolocationFromAPI(ip string) (map[string]interface{}, error) { - // 使用ip-api.com获取IP地理位置信息 - url := fmt.Sprintf("http://ip-api.com/json/%s?fields=country,city", ip) - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - // 读取响应内容 - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - // 解析JSON响应 - var result map[string]interface{} - err = json.Unmarshal(body, &result) - if err != nil { - return nil, err - } - - // 检查API返回状态 - status, ok := result["status"].(string) - if !ok || status != "success" { - return nil, fmt.Errorf("API返回错误状态: %v", result) - } - - // 确保国家和城市字段存在 - if _, ok := result["country"]; !ok { - result["country"] = "未知" - } - if _, ok := result["city"]; !ok { - result["city"] = "未知" - } - - return result, nil -} // loadStatsData 从文件加载统计数据 func (s *Server) loadStatsData() { @@ -3138,39 +2908,9 @@ func (s *Server) startCpuUsageMonitor() { } } -// startIPGeolocationCacheCleanup 启动IP地理位置缓存清理协程 -func (s *Server) startIPGeolocationCacheCleanup() { - ticker := time.NewTicker(time.Hour) // 每小时清理一次 - defer ticker.Stop() - for { - select { - case <-ticker.C: - s.cleanupExpiredIPGeolocationCache() - case <-s.ctx.Done(): - return - } - } -} -// cleanupExpiredIPGeolocationCache 清理过期的IP地理位置缓存 -func (s *Server) cleanupExpiredIPGeolocationCache() { - now := time.Now() - s.ipGeolocationCacheMutex.Lock() - defer s.ipGeolocationCacheMutex.Unlock() - var deletedCount int - for ip, geo := range s.ipGeolocationCache { - if now.After(geo.Expiry) { - delete(s.ipGeolocationCache, ip) - deletedCount++ - } - } - - if deletedCount > 0 { - logger.Info("清理过期的IP地理位置缓存", "deleted", deletedCount, "remaining", len(s.ipGeolocationCache)) - } -} // getSystemCpuUsage 获取系统CPU使用率 func getSystemCpuUsage(prevIdle, prevTotal *uint64) (float64, error) { diff --git a/static/js/logs.js b/static/js/logs.js index 65b7968..4caaabb 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -14,6 +14,103 @@ let currentSortDirection = 'desc'; // 默认降序 let ipGeolocationCache = {}; const GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时 +// 获取IP地理位置信息 +async function getIpGeolocation(ip) { + // 检查是否为内网IP + if (isPrivateIP(ip)) { + return "内网 内网"; + } + + // 检查缓存 + const now = Date.now(); + if (ipGeolocationCache[ip] && (now - ipGeolocationCache[ip].timestamp) < GEOLOCATION_CACHE_EXPIRY) { + return ipGeolocationCache[ip].location; + } + + try { + // 使用whois.pconline.com.cn API获取IP地理位置 + const url = `https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`; + const response = await fetch(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + // 解析响应数据 + const data = await response.json(); + let location = "未知 未知"; + + if (data && data.country && data.city) { + location = `${data.country} ${data.city}`; + } + + // 保存到缓存 + ipGeolocationCache[ip] = { + location: location, + timestamp: now + }; + + return location; + } catch (error) { + console.error('获取IP地理位置失败:', error); + return "未知 未知"; + } +} + +// 检查是否为内网IP +function isPrivateIP(ip) { + const parts = ip.split('.'); + + // 检查IPv4内网地址 + if (parts.length === 4) { + const first = parseInt(parts[0]); + const second = parseInt(parts[1]); + + // 10.0.0.0/8 + if (first === 10) { + return true; + } + // 172.16.0.0/12 + if (first === 172 && second >= 16 && second <= 31) { + return true; + } + // 192.168.0.0/16 + if (first === 192 && second === 168) { + return true; + } + // 127.0.0.0/8 (localhost) + if (first === 127) { + return true; + } + // 169.254.0.0/16 (link-local) + if (first === 169 && second === 254) { + return true; + } + } + + // 检查IPv6内网地址 + if (ip.includes(':')) { + // ::1/128 (localhost) + if (ip === '::1' || ip.startsWith('0:0:0:0:0:0:0:1')) { + return true; + } + // fc00::/7 (unique local address) + if (ip.startsWith('fc') || ip.startsWith('fd')) { + return true; + } + // fe80::/10 (link-local) + if (ip.startsWith('fe80:')) { + return true; + } + } + + return false; +} + // 跟踪器数据库缓存 let trackersDatabase = null; let trackersLoaded = false; @@ -838,35 +935,44 @@ async function updateLogsTable(logs) { `; } else { // 桌面设备显示完整信息 - row.innerHTML = ` -