package main import ( "bufio" "fmt" "io" "log" "net/http" "strings" "sync" "time" ) const ( // 数据源 URL urlChnrouteIPv4 = "https://raw.githubusercontent.com/mayaxcn/china-ip-list/master/chnroute.txt" urlChnrouteIPv6 = "https://raw.githubusercontent.com/mayaxcn/china-ip-list/master/chnroute_v6.txt" // 缓存时间(1小时) cacheDuration = 1 * time.Hour ) // 缓存结构 type Cache struct { data string timestamp time.Time mu sync.RWMutex } var ( cacheIPv4 = &Cache{} cacheIPv6 = &Cache{} ) // 从 URL 获取数据 func fetchData(url string) (string, error) { resp, err := http.Get(url) if err != nil { return "", fmt.Errorf("fetch error: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("HTTP %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read error: %w", err) } return string(body), nil } // 转换为 RouterOS RSC 格式 func convertToRSC(cidrList string, listName string, ipVersion string) string { var sb strings.Builder scanner := bufio.NewScanner(strings.NewReader(cidrList)) // 添加头部注释 sb.WriteString(fmt.Sprintf("# China IP List - RouterOS Script\n")) sb.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().Format("2006-01-02 15:04:05"))) sb.WriteString(fmt.Sprintf("# Source: https://github.com/mayaxcn/china-ip-list\n\n")) // 根据 IP 版本选择命令前缀 prefix := "/ip firewall address-list" if ipVersion == "6" { prefix = "/ipv6 firewall address-list" } count := 0 for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } sb.WriteString(fmt.Sprintf("%s add list=%s address=%s\n", prefix, listName, line)) count++ } sb.WriteString(fmt.Sprintf("\n# Total: %d entries\n", count)) return sb.String() } // 获取或更新缓存 func getOrUpdateCache(cache *Cache, url string, listName string, ipVersion string) (string, error) { cache.mu.RLock() if time.Since(cache.timestamp) < cacheDuration && cache.data != "" { defer cache.mu.RUnlock() return cache.data, nil } cache.mu.RUnlock() // 需要更新缓存 cache.mu.Lock() defer cache.mu.Unlock() // 双重检查 if time.Since(cache.timestamp) < cacheDuration && cache.data != "" { return cache.data, nil } log.Printf("Fetching data from %s...", url) cidrData, err := fetchData(url) if err != nil { return "", err } rscData := convertToRSC(cidrData, listName, ipVersion) cache.data = rscData cache.timestamp = time.Now() log.Printf("Cache updated: %d bytes", len(rscData)) return rscData, nil } // HTTP 处理器 func handleIPv4(w http.ResponseWriter, r *http.Request) { data, err := getOrUpdateCache(cacheIPv4, urlChnrouteIPv4, "chnroute", "4") if err != nil { http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusInternalServerError) log.Printf("IPv4 error: %v", err) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Disposition", "attachment; filename=chnroute.rsc") w.Write([]byte(data)) log.Printf("IPv4 request served: %s", r.RemoteAddr) } func handleIPv6(w http.ResponseWriter, r *http.Request) { data, err := getOrUpdateCache(cacheIPv6, urlChnrouteIPv6, "chnroute6", "6") if err != nil { http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusInternalServerError) log.Printf("IPv6 error: %v", err) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Disposition", "attachment; filename=chnroute_v6.rsc") w.Write([]byte(data)) log.Printf("IPv6 request served: %s", r.RemoteAddr) } func handleHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"ok","cache_age_ipv4":"%s","cache_age_ipv6":"%s"}`, time.Since(cacheIPv4.timestamp).Round(time.Second), time.Since(cacheIPv6.timestamp).Round(time.Second)) } func handleRoot(w http.ResponseWriter, r *http.Request) { // 获取缓存状态 cacheIPv4.mu.RLock() cacheAgeIPv4 := time.Since(cacheIPv4.timestamp).Round(time.Second) cacheIPv4.mu.RUnlock() cacheIPv6.mu.RLock() cacheAgeIPv6 := time.Since(cacheIPv6.timestamp).Round(time.Second) cacheIPv6.mu.RUnlock() // 判断状态 statusIPv4 := "🟢" if cacheAgeIPv4 > cacheDuration { statusIPv4 = "🔴" } else if cacheAgeIPv4 > cacheDuration/2 { statusIPv4 = "🟡" } statusIPv6 := "🟢" if cacheAgeIPv6 > cacheDuration { statusIPv6 = "🔴" } else if cacheAgeIPv6 > cacheDuration/2 { statusIPv6 = "🟡" } // 🔥 自动检测访问协议和地址 protocol := "http" if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { protocol = "https" } host := r.Host if host == "" { host = "localhost:8080" } baseURL := fmt.Sprintf("%s://%s", protocol, host) fetchMode := protocol // 根据协议生成不同的提示和脚本 var protocolNote string var protocolBadge string if protocol == "https" { protocolNote = "您正在使用 HTTPS 安全连接,适合外网环境" protocolBadge = `🔒 HTTPS` } else { protocolNote = "您正在使用 HTTP 连接,适合内网环境" protocolBadge = `🌐 HTTP` } html := fmt.Sprintf(`
自动转换 IP 列表为 RouterOS 可导入格式
# SSH 登录 RouterOS 后执行 /tool fetch url="%s/chnroute.rsc" mode=%s /import chnroute.rsc # 验证导入 /ip firewall address-list print count-only where list=chnroute
# 创建更新脚本
/system script add name=update-chnroute source={
:log info "开始更新 CHNRoute"
/file remove [find name="chnroute.rsc"]
/tool fetch url="%s/chnroute.rsc" mode=%s
:delay 3s
/ip firewall address-list remove [find list=chnroute]
/import chnroute.rsc
:local count [/ip firewall address-list print count-only where list=chnroute]
:log info ("CHNRoute 更新完成,共 " . $count . " 条")
}
# 设置每周自动执行(每周日凌晨3点)
/system scheduler add name=auto-update-chnroute \\
interval=7d \\
on-event="/system script run update-chnroute" \\
start-time=03:00:00
# IPv6 路由列表导入 /tool fetch url="%s/chnroute_v6.rsc" mode=%s /import chnroute_v6.rsc # 验证导入 /ipv6 firewall address-list print count-only where list=chnroute6