package asn import ( "fmt" "io" "log" "net/http" "os" "path/filepath" "github.com/adrg/xdg" "github.com/gocarina/gocsv" ) const SOURCE string = "https://cdn.jsdelivr.net/gh/ipverse/as-metadata@master/as.csv" type AsnEntry struct { ASN int `csv:"asn"` Handle string `csv:"handle"` Description string `csv:"description"` CountryCode string `csv:"country-code"` } type AsnMap map[int]AsnEntry // localPath returns the destination of local copy of asn metadata func localPath() string { return filepath.Join(xdg.DataHome, "/audidi/as.csv") } // downloadAsnSource downloads ASN metadata source to disk func downloadAsnSource() error { resp, err := http.Get(SOURCE) if err != nil { return fmt.Errorf("error fetching ASN metadata: %s", err) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("error fetching ASN metadata: http %d", resp.StatusCode) } defer resp.Body.Close() file, err := os.Create(localPath()) if err != nil { return fmt.Errorf("can't open file to write ASN metadata: %s", err) } if _, err := io.Copy(file, resp.Body); err != nil { return fmt.Errorf("can't copy ASN metadata to file: %s", err) } return nil } // useAsnSource returns either a local cached version of // ASN source bytes or download from source func useAsnSource() ([]byte, error) { path := localPath() os.MkdirAll(filepath.Dir(path), os.ModePerm) if info, err := os.Stat(path); os.IsNotExist(err) || info.Size() == 0 { log.Println("downloading ASN metadata...") if err := downloadAsnSource(); err != nil { return nil, err } log.Println("ASN metadata downloaded") } bytes, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("can't open file to read ASN metadata: %s", err) } return bytes, nil } // parseMetadata parse raw bytes of ASN metadata into readable entries func parseMetadata(bytes []byte) ([]AsnEntry, error) { var entries []AsnEntry if err := gocsv.UnmarshalBytes(bytes, &entries); err != nil { return nil, fmt.Errorf("can't parse ASN metadata: %s", err) } return entries, nil } // GetMapping returns a pointer to ASN-metadata mappings func getMapping() AsnMap { bytes, err := useAsnSource() if err != nil { log.Fatalln("Failed to load ASN metadata: ", err) } entries, err := parseMetadata(bytes) if err != nil { log.Fatalln("Failed to parse ASN metadata (Delete data and retry): ", err) } mapping := make(AsnMap) for _, entry := range entries { mapping[entry.ASN] = entry } return mapping }