From 289790235fb0f556d87a84fc960b25ba9bf0f73e Mon Sep 17 00:00:00 2001 From: eltrac Date: Thu, 5 Nov 2026 10:41:31 +0800 Subject: asn: download and map ASN metadata use jsdelivr CDN --- asn/mapping.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 4 +++ 3 files changed, 111 insertions(+) create mode 100644 asn/mapping.go diff --git a/asn/mapping.go b/asn/mapping.go new file mode 100644 index 0000000..79372a2 --- /dev/null +++ b/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 +} diff --git a/go.mod b/go.mod index 7507e58..6413654 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module src.eltr.ac/eltrac/audidi go 1.26.5 require ( + github.com/adrg/xdg v0.5.3 // indirect github.com/gocarina/gocsv v0.0.0-20261101205459-d0dd027dcbfa // indirect github.com/ohler55/ojg v1.28.9 // indirect github.com/x-way/crawlerdetect v0.2.32 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index b882144..29a7f15 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,10 @@ +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/gocarina/gocsv v0.0.0-20261101205459-d0dd027dcbfa h1:u6dQLh7NdeVIBn4fE4YL9PiHFephNp7t7rUSU+fulro= github.com/gocarina/gocsv v0.0.0-20261101205459-d0dd027dcbfa/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/ohler55/ojg v1.28.9 h1:VL2vdN1CjniaNABNOFjorAW+/o7eBQDc4z5jO49jkHM= github.com/ohler55/ojg v1.28.9/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= github.com/x-way/crawlerdetect v0.2.32 h1:kmYHC8lk5T91PDETQ3h9pF35GUyrdzmi3KX64jPvHt4= github.com/x-way/crawlerdetect v0.2.32/go.mod h1:BPHLsB3FOuiwoWyhAvnqeiUSAEKd34O7BcsTCcxHRj4= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -- cgit v1.2.3