package main import ( "bufio" "fmt" "net" "net/http" "strings" ) const ipverseBaseURL = "https://raw.githubusercontent.com/ipverse/country-ip-blocks/master/country" // IPverseProvider fetches GeoIP data from the ipverse/country-ip-blocks GitHub repository. // Data is sourced from Regional Internet Registries (RIRs) and aggregated by ipverse. // Repository: https://github.com/ipverse/country-ip-blocks type IPverseProvider struct{} func (p *IPverseProvider) Name() string { return "ipverse" } // FetchCIDRs downloads IPv4 CIDR blocks for a country from ipverse. // URL format: https://raw.githubusercontent.com/ipverse/country-ip-blocks/master/country/{cc}/ipv4-aggregated.txt func (p *IPverseProvider) FetchCIDRs(countryCode string) ([]string, error) { url := fmt.Sprintf("%s/%s/ipv4-aggregated.txt", ipverseBaseURL, strings.ToLower(countryCode)) return downloadAndParseCIDRs(url) } // downloadAndParseCIDRs is a shared helper that downloads a URL containing // one CIDR per line and returns validated CIDR strings. func downloadAndParseCIDRs(url string) ([]string, error) { resp, err := http.Get(url) if err != nil { return nil, fmt.Errorf("http get error on url %q: %v", url, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected http status %d from %q", resp.StatusCode, url) } scanner := bufio.NewScanner(resp.Body) scanner.Split(bufio.ScanLines) var cidrs []string for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // Skip empty lines and comments if line == "" || strings.HasPrefix(line, "#") { continue } _, ipnet, err := net.ParseCIDR(line) if err != nil { // Skip unparseable lines rather than failing the whole country continue } cidrs = append(cidrs, ipnet.String()) } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading response body from %q: %v", url, err) } return cidrs, nil }