From 601227a20fd415ecacf39765f19cdf72acc689f4 Mon Sep 17 00:00:00 2001 From: eltrac Date: Thu, 5 Nov 2026 11:20:32 +0800 Subject: asn: move asn/ to pkg/asn/; add lookup.goq only export Lookup() and IsDatacenter() --- pkg/asn/lookup.go | 18 +++++++++ pkg/asn/mapping.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 pkg/asn/lookup.go create mode 100644 pkg/asn/mapping.go (limited to 'pkg') diff --git a/pkg/asn/lookup.go b/pkg/asn/lookup.go new file mode 100644 index 0000000..4851a6f --- /dev/null +++ b/pkg/asn/lookup.go @@ -0,0 +1,18 @@ +package asn + +var AsnTable AsnMap + +// Lookup return metadata of given AS +func Lookup(ASN int) AsnEntry { + if len(AsnTable) == 0 { + AsnTable = getMapping() + } + + return AsnTable[ASN] +} + +// IsDatacenter return bool value whether an AS is datajjij center or not +func IsDatacenter(ASN int) bool { + // TODO + return false +} diff --git a/pkg/asn/mapping.go b/pkg/asn/mapping.go new file mode 100644 index 0000000..14272de --- /dev/null +++ b/pkg/asn/mapping.go @@ -0,0 +1,105 @@ +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 +} -- cgit v1.2.3