pax_global_header00006660000000000000000000000064132374152240014515gustar00rootroot0000000000000052 comment=007c9af1ae532a165b75f90dc02993af4195ea75 canid-007c9af1ae532a165b75f90dc02993af4195ea75/000077500000000000000000000000001323741522400175135ustar00rootroot00000000000000canid-007c9af1ae532a165b75f90dc02993af4195ea75/.gitignore000066400000000000000000000004121323741522400215000ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof canid-007c9af1ae532a165b75f90dc02993af4195ea75/LICENSE000066400000000000000000000020571323741522400205240ustar00rootroot00000000000000MIT License Copyright (c) 2016 Brian Trammell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. canid-007c9af1ae532a165b75f90dc02993af4195ea75/README.md000066400000000000000000000050521323741522400207740ustar00rootroot00000000000000# canid(1) -- the caching additional network information daemon ## SYNOPSIS `canid` [-file _<cachefile>_] [-expiry _<sec>_] [-concurrency _<n>_] [-port _<port>_] ## DESCRIPTION Canid provides a simple web service for caching and simplifying information retrieved from other services (see [BACKENDS][]) about Internet hostnames and IP addresses. It is designed for use when looking up many addresses and/ On launch, Canid begins serving on the specified port. It shuts down cleanly on SIGINT (^C on the console). ## INSTALLING ``` $ go install github.com/britram/canid/canid ``` ## OPTIONS * `-file` _<cachefile>_ (default: no backing store) Use the given JSON file as a backing store for the cache. Loads the cache from this file on startup, and saves it on termination. * `-expiry` _<sec>_ (default: 86400, 1 day) Expire cache entries after _<sec>_ seconds. * `-concurrency` _<n>_ (default: 16) Allow at most _<n>_ simultaneous pending requests per backend. * `-port` _<port>_ (default: 8043) TCP port to listen on ## RESOURCES Canid provides three resources via HTTP: * `/` The root resource is an HTML page providing a simple web front-end to the service. * `/prefix.json?addr=` Look up information about the prefix associated with an address, and return it as a JSON object. This object presently contains a `Prefix` key with the routed prefix associated with the address, an `ASN` key with a BGP autonomous system number associated with the address, and a `CountryCode` key for an ISO 3166 country code associated with the address. * `/address.json?name=` Look up an Internet hostname via DNS, and return the IPv4 and IPv6 addresses associated with it as a JSON object. This object contains a `Name` key with the name looked up, and an `Addresses` key containing an array of IPv4 and/or IPv6 addresses as strings. Looking up an address for a name will cause prefix information for all addresses found to be cached, as well. All JSON resources also contain a `Cached` key, the time at which the data entry was put into the cache in [RFC3339][https://datatracker.ietf.org/doc/RFC3339] format. ## BACKENDS The `prefix.json` resource currently uses the Prefix Overview and Geolocation API entry points from [RIPEstat][https://stat.ripe.net]. The `address.json` resource uses DNS, as provided by the Go standard library's `net.LookupIP()` (i.e., the system resolver) ## AUTHOR Brian Trammell _<brian@trammell.ch>_ canid-007c9af1ae532a165b75f90dc02993af4195ea75/addresscache.go000066400000000000000000000041071323741522400224550ustar00rootroot00000000000000package canid import ( "encoding/json" "log" "net" "net/http" "sync" "time" ) type AddressInfo struct { Name string Addresses []net.IP Cached time.Time } type AddressCache struct { Data map[string]AddressInfo lock sync.RWMutex prefixes *PrefixCache expiry int backend_limiter chan struct{} } func NewAddressCache(expiry int, concurrency_limit int, prefixcache *PrefixCache) *AddressCache { c := new(AddressCache) c.Data = make(map[string]AddressInfo) c.expiry = expiry c.backend_limiter = make(chan struct{}, concurrency_limit) c.prefixes = prefixcache return c } func (cache *AddressCache) Lookup(name string) (out AddressInfo) { // Cache lookup var ok bool cache.lock.RLock() out, ok = cache.Data[name] cache.lock.RUnlock() if ok { // check for expiry if int(time.Since(out.Cached).Seconds()) > cache.expiry { log.Printf("entry expired for name %s", name) cache.lock.Lock() delete(cache.Data, name) cache.lock.Unlock() } else { log.Printf("cache hit for name %s", name) return } } // Cache miss. Lookup. out.Name = name cache.backend_limiter <- struct{}{} addrs, err := net.LookupIP(name) _ = <-cache.backend_limiter if err == nil { // we have addresses. precache prefix information. out.Addresses = addrs // precache prefixes, ignoring results if cache.prefixes != nil { for _, addr := range addrs { _, _ = cache.prefixes.Lookup(addr) } } } else { out.Addresses = make([]net.IP, 0) log.Printf("error looking up %s: %s", name, err.Error()) err = nil } // cache and return out.Cached = time.Now().UTC() cache.lock.Lock() cache.Data[out.Name] = out cache.lock.Unlock() log.Printf("cached name %s -> %v", out.Name, out) return } func (cache *AddressCache) LookupServer(w http.ResponseWriter, req *http.Request) { // TODO figure out how to duplicate less code here name := req.URL.Query().Get("name") if len(name) == 0 { w.WriteHeader(http.StatusBadRequest) return } addr_info := cache.Lookup(name) addr_body, _ := json.Marshal(addr_info) w.Write(addr_body) } canid-007c9af1ae532a165b75f90dc02993af4195ea75/canid/000077500000000000000000000000001323741522400205715ustar00rootroot00000000000000canid-007c9af1ae532a165b75f90dc02993af4195ea75/canid/main.go000066400000000000000000000173561323741522400220600ustar00rootroot00000000000000package main import ( "encoding/json" "flag" "io" "log" "net/http" "os" "os/signal" "strconv" "github.com/britram/canid" ) // WelcomePage contains the Canid welcome page, which explains what Canid is, // and gives a simple web interface to the service. const WelcomePage = ` Canid, the Caching Additional Network Information Daemon

Canidbeta

Canid, the Caching Additional Network Information Daemon, provides a simple HTTP API for getting information about Internet names and numbers. See the GitHub repository for source code and more information

This landing page provides a browser-based interface to this instance of the cache, backed by RIPEstat. You can perform prefix lookups using the form below.


` const canidStorageVersion = 1 type canidStorage struct { Version int Prefixes *canid.PrefixCache Addresses *canid.AddressCache } func (storage *canidStorage) undump(in io.Reader) error { dec := json.NewDecoder(in) return dec.Decode(storage) } func (storage *canidStorage) dump(out io.Writer) error { enc := json.NewEncoder(out) return enc.Encode(*storage) } func newStorage(expiry int, limit int) *canidStorage { storage := new(canidStorage) storage.Version = canidStorageVersion storage.Prefixes = canid.NewPrefixCache(expiry, limit) storage.Addresses = canid.NewAddressCache(expiry, limit, storage.Prefixes) return storage } func welcomeServer(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/html") w.WriteHeader(http.StatusOK) w.Write([]byte(WelcomePage)) } func main() { fileflag := flag.String("file", "", "backing store for caches (JSON file)") expiryflag := flag.Int("expiry", 86400, "expire cache entries after n sec") limitflag := flag.Int("concurrency", 16, "simultaneous backend request limit") portflag := flag.Int("port", 8043, "port to listen on") // parse command line flag.Parse() // set up sigint handling interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) // allocate and link cache storage := newStorage(*expiryflag, *limitflag) // undump cache if filename given if len(*fileflag) > 0 { infile, ferr := os.Open(*fileflag) if ferr == nil { cerr := storage.undump(infile) infile.Close() if cerr != nil { log.Fatal(cerr) } log.Printf("loaded caches from %s", *fileflag) } else { log.Printf("unable to read cache file %s : %s", *fileflag, ferr.Error()) } } // check for cache version mismatch if storage.Version != canidStorageVersion { log.Fatalf("storage version mismatch for cache file %s: delete and try again", *fileflag) } go func() { http.HandleFunc("/", welcomeServer) http.HandleFunc("/prefix.json", storage.Prefixes.LookupServer) http.HandleFunc("/address.json", storage.Addresses.LookupServer) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*portflag), nil)) }() _ = <-interrupt log.Printf("terminating on interrupt") // dump cache if filename given if len(*fileflag) > 0 { outfile, ferr := os.Create(*fileflag) if ferr == nil { cerr := storage.dump(outfile) outfile.Close() if cerr != nil { log.Fatal(cerr) } log.Printf("dumped cache to %s", *fileflag) } else { log.Fatalf("unable to write backing file %s : %s", *fileflag, ferr.Error()) } } } canid-007c9af1ae532a165b75f90dc02993af4195ea75/canid/welcome.html000066400000000000000000000117201323741522400231130ustar00rootroot00000000000000 Canid, the Caching Additional Network Information Daemon

Canidbeta

Canid, the Caching Additional Network Information Daemon, provides a simple HTTP API for getting information about Internet names and numbers. See the GitHub repository for source code and more information

This landing page provides a browser-based interface to this instance of the cache, backed by RIPEstat. You can perform prefix lookups using the form below.


canid-007c9af1ae532a165b75f90dc02993af4195ea75/doc/000077500000000000000000000000001323741522400202605ustar00rootroot00000000000000canid-007c9af1ae532a165b75f90dc02993af4195ea75/doc/canid.1000066400000000000000000000074521323741522400214300ustar00rootroot00000000000000.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "CANID" "1" "February 2018" "" "" . .SH "NAME" \fBcanid\fR \- the caching additional network information daemon . .SH "SYNOPSIS" \fBcanid\fR [\-file \fIcachefile\fR] [\-expiry \fIsec\fR] [\-concurrency \fIn\fR] [\-port \fIport\fR] . .SH "DESCRIPTION" Canid provides a simple web service for caching and simplifying information retrieved from other services (see \fIBACKENDS\fR) about Internet hostnames and IP addresses\. It is designed for use when looking up many addresses and/ . .P On launch, Canid begins serving on the specified port\. It shuts down cleanly on SIGINT (^C on the console)\. . .SH "OPTIONS" . .IP "\(bu" 4 \fB\-file\fR \fIcachefile\fR (default: no backing store) Use the given JSON file as a backing store for the cache\. Loads the cache from this file on startup, and saves it on termination\. . .IP "\(bu" 4 \fB\-expiry\fR \fIsec\fR (default: 86400, 1 day) Expire cache entries after \fIsec\fR seconds\. . .IP "\(bu" 4 \fB\-concurrency\fR \fIn\fR (default: 16) Allow at most \fIn\fR simultaneous pending requests per backend\. . .IP "\(bu" 4 \fB\-port\fR \fIport\fR (default: 8043) TCP port to listen on . .IP "" 0 . .SH "RESOURCES" Canid provides three resources via HTTP: . .IP "\(bu" 4 \fB/\fR . .IP The root resource is an HTML page providing a simple web front\-end to the service\. . .IP "\(bu" 4 \fB/prefix\.json?addr=\fR . .IP Look up information about the prefix associated with an address, and return it as a JSON object\. This object presently contains a \fBPrefix\fR key with the routed prefix associated with the address, an \fBASN\fR key with a BGP autonomous system number associated with the address, and a \fBCountryCode\fR key for an ISO 3166 country code associated with the address\. . .IP "\(bu" 4 \fB/address\.json?name=\fR . .IP Look up an Internet hostname via DNS, and return the IPv4 and IPv6 addresses associated with it as a JSON object\. This object contains a \fBName\fR key with the name looked up, and an \fBAddresses\fR key containing an array of IPv4 and/or IPv6 addresses as strings\. Looking up an address for a name will cause prefix information for all addresses found to be cached, as well\. . .IP "" 0 . .P All JSON resources also contain a \fBCached\fR key, the time at which the data entry was put into the cache in [RFC3339][https://datatracker\.ietf\.org/doc/RFC3339] format\. . .SH "BACKENDS" The \fBprefix\.json\fR resource currently uses the Prefix Overview and Geolocation API entry points from [RIPEstat][https://stat\.ripe\.net]\. . .P The \fBaddress\.json\fR resource uses DNS, as provided by the Go standard library\'s \fBnet\.LookupIP()\fR (i\.e\., the system resolver) . .SH "AUTHOR" Brian Trammell \fIbrian@trammell\.ch\fR . .SH "LICENSE" Copyright (c) 2016 \- 2018 Brian Trammell . .P Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . .P The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software\. . .P THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE\. canid-007c9af1ae532a165b75f90dc02993af4195ea75/doc/canid.1.html000066400000000000000000000165301323741522400223700ustar00rootroot00000000000000 canid(1) - the caching additional network information daemon
  1. canid(1)
  2. canid(1)

NAME

canid - the caching additional network information daemon

SYNOPSIS

canid [-file cachefile] [-expiry sec] [-concurrency n] [-port port]

DESCRIPTION

Canid provides a simple web service for caching and simplifying information retrieved from other services (see BACKENDS) about Internet hostnames and IP addresses. It is designed for use when looking up many addresses and/

On launch, Canid begins serving on the specified port. It shuts down cleanly on SIGINT (^C on the console).

OPTIONS

RESOURCES

Canid provides three resources via HTTP:

All JSON resources also contain a Cached key, the time at which the data entry was put into the cache in [RFC3339][https://datatracker.ietf.org/doc/RFC3339] format.

BACKENDS

The prefix.json resource currently uses the Prefix Overview and Geolocation API entry points from [RIPEstat][https://stat.ripe.net].

The address.json resource uses DNS, as provided by the Go standard library's net.LookupIP() (i.e., the system resolver)

AUTHOR

Brian Trammell brian@trammell.ch

LICENSE

Copyright (c) 2016 - 2018 Brian Trammell

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

  1. February 2018
  2. canid(1)
canid-007c9af1ae532a165b75f90dc02993af4195ea75/doc/canid.1.md000066400000000000000000000067031323741522400220250ustar00rootroot00000000000000# canid(1) -- the caching additional network information daemon ## SYNOPSIS `canid` [-file ] [-expiry ] [-concurrency ] [-port ] ## DESCRIPTION Canid provides a simple web service for caching and simplifying information retrieved from other services (see [BACKENDS][]) about Internet hostnames and IP addresses. It is designed for use when looking up many addresses and/ On launch, Canid begins serving on the specified port. It shuts down cleanly on SIGINT (^C on the console). ## OPTIONS * `-file` (default: no backing store) Use the given JSON file as a backing store for the cache. Loads the cache from this file on startup, and saves it on termination. * `-expiry` (default: 86400, 1 day) Expire cache entries after seconds. * `-concurrency` (default: 16) Allow at most simultaneous pending requests per backend. * `-port` (default: 8043) TCP port to listen on ## RESOURCES Canid provides three resources via HTTP: * `/` The root resource is an HTML page providing a simple web front-end to the service. * `/prefix.json?addr=` Look up information about the prefix associated with an address, and return it as a JSON object. This object presently contains a `Prefix` key with the routed prefix associated with the address, an `ASN` key with a BGP autonomous system number associated with the address, and a `CountryCode` key for an ISO 3166 country code associated with the address. * `/address.json?name=` Look up an Internet hostname via DNS, and return the IPv4 and IPv6 addresses associated with it as a JSON object. This object contains a `Name` key with the name looked up, and an `Addresses` key containing an array of IPv4 and/or IPv6 addresses as strings. Looking up an address for a name will cause prefix information for all addresses found to be cached, as well. All JSON resources also contain a `Cached` key, the time at which the data entry was put into the cache in [RFC3339][https://datatracker.ietf.org/doc/RFC3339] format. ## BACKENDS The `prefix.json` resource currently uses the Prefix Overview and Geolocation API entry points from [RIPEstat][https://stat.ripe.net]. The `address.json` resource uses DNS, as provided by the Go standard library's `net.LookupIP()` (i.e., the system resolver) ## AUTHOR Brian Trammell ## LICENSE Copyright (c) 2016 - 2018 Brian Trammell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.canid-007c9af1ae532a165b75f90dc02993af4195ea75/prefixcache.go000066400000000000000000000045031323741522400223250ustar00rootroot00000000000000package canid import ( "encoding/json" "log" "net" "net/http" "strings" "sync" "time" ) // Prefix information type PrefixInfo struct { Prefix string ASN int CountryCode string Cached time.Time } type PrefixCache struct { Data map[string]PrefixInfo lock sync.RWMutex expiry int backend_limiter chan struct{} } func NewPrefixCache(expiry int, concurrency_limit int) *PrefixCache { c := new(PrefixCache) c.Data = make(map[string]PrefixInfo) c.expiry = expiry c.backend_limiter = make(chan struct{}, concurrency_limit) return c } func (cache *PrefixCache) Lookup(addr net.IP) (out PrefixInfo, err error) { // Determine starting prefix by guessing whether this is v6 or not var prefixlen, addrbits int if strings.Contains(addr.String(), ":") { prefixlen = 48 addrbits = 128 } else { prefixlen = 24 addrbits = 32 } // Iterate through prefixes looking for a match for i := prefixlen; i > 0; i-- { mask := net.CIDRMask(i, addrbits) net := net.IPNet{addr.Mask(mask), mask} prefix := net.String() cache.lock.RLock() out, ok := cache.Data[prefix] cache.lock.RUnlock() if ok { // check for expiry if int(time.Since(out.Cached).Seconds()) > cache.expiry { log.Printf("entry expired for prefix %s", prefix) cache.lock.Lock() delete(cache.Data, prefix) cache.lock.Unlock() break } else { log.Printf("cache hit! for prefix %s", prefix) return out, nil } } } // Cache miss, go ask RIPE cache.backend_limiter <- struct{}{} out, err = LookupRipestat(addr) _ = <-cache.backend_limiter if err != nil { return } // cache and return out.Cached = time.Now().UTC() cache.lock.Lock() cache.Data[out.Prefix] = out cache.lock.Unlock() log.Printf("cached prefix %s -> %v", out.Prefix, out) return } func (cache *PrefixCache) LookupServer(w http.ResponseWriter, req *http.Request) { ip := net.ParseIP(req.URL.Query().Get("addr")) if ip == nil { w.WriteHeader(http.StatusBadRequest) return } prefix_info, err := cache.Lookup(ip) if err != nil { w.WriteHeader(http.StatusInternalServerError) // FIXME not always a 500 error_struct := struct{ Error string }{err.Error()} error_body, _ := json.Marshal(error_struct) w.Write(error_body) return } prefix_body, _ := json.Marshal(prefix_info) w.Write(prefix_body) } canid-007c9af1ae532a165b75f90dc02993af4195ea75/ripestat.go000066400000000000000000000040321323741522400216740ustar00rootroot00000000000000package canid import ( "encoding/json" "errors" "log" "net" "net/http" "net/url" ) // Structure partially covering the output of RIPEstat's prefix overview and // geolocation API calls, for decoding JSON reponses from RIPEstat. type RipeStatResponse struct { Status string Data struct { Resource string Is_Less_Specific bool ASNs []struct { ASN int } Locations []struct { Country string } Block struct { Resource string } } } const ripeStatPrefixURL = "https://stat.ripe.net/data/prefix-overview/data.json" const ripeStatGeolocURL = "https://stat.ripe.net/data/geoloc/data.json" func callRipestat(apiurl string, addr net.IP, out *PrefixInfo) error { // construct a query string and add it to the URL v := make(url.Values) v.Add("resource", addr.String()) fullUrl, err := url.Parse(apiurl) if err != nil { return err } fullUrl.RawQuery = v.Encode() log.Printf("calling ripestat %s", fullUrl.String()) resp, err := http.Get(fullUrl.String()) if err != nil { return err } // and now we have a response, parse it var doc RipeStatResponse dec := json.NewDecoder(resp.Body) err = dec.Decode(&doc) if err != nil { return err } // don't even bother if the server told us to go away if doc.Status != "ok" { return errors.New("RIPEstat request failed with status " + doc.Status) } // store the prefix, if not already present if len(out.Prefix) == 0 { if doc.Data.Is_Less_Specific { out.Prefix = doc.Data.Resource } else { // if the resource isn't a prefix, look for the block out.Prefix = doc.Data.Block.Resource } } // get the first AS number, if present for _, asn := range doc.Data.ASNs { out.ASN = asn.ASN break } // get the first country code, if present for _, location := range doc.Data.Locations { out.CountryCode = location.Country break } return nil } func LookupRipestat(addr net.IP) (out PrefixInfo, err error) { err = callRipestat(ripeStatPrefixURL, addr, &out) if err == nil { callRipestat(ripeStatGeolocURL, addr, &out) } return } canid-007c9af1ae532a165b75f90dc02993af4195ea75/trie.go000066400000000000000000000032141323741522400210050ustar00rootroot00000000000000package canid import ( "net" ) // Trie for storing fast lookups of information by prefix. // Not yet tested or integrated with canid. type Trie struct { sub [2]*Trie data interface{} } // Return the prefix and data associated with a given IP address in the trie func (t *Trie) Find(addr net.IP) (pfx net.IPNet, data interface{}, ok bool) { addrmasks := [8]byte{0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01} netmask := make([]byte, len(addr)) current := t // and iterate for pfxlen := 0; pfxlen < (len(addr) * 8); pfxlen++ { // return data if the current trie node is a leaf if current.data != nil { cnetmask := net.IPMask(netmask) return net.IPNet{addr.Mask(cnetmask), cnetmask}, current.data, true } // otherwise determine whether to go right or left var branch int if addr[pfxlen/8]&addrmasks[pfxlen%8] == 0 { branch = 0 } else { branch = 1 } current = current.sub[branch] // stop searching if nil if current == nil { break } // and move to the next bit netmask[pfxlen/8] |= addrmasks[pfxlen%8] } return net.IPNet{}, nil, false } // Add a prefix to the trie and associate some data with it func (t *Trie) Add(pfx net.IPNet, data interface{}) { addrmasks := [8]byte{0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01} current := t subidx := 0 // first search to the bottom of the trie, creating nodes as necessary for i := 0; pfx.Mask[i/8]&addrmasks[i%8] > 0; i++ { if pfx.IP[i/8]&addrmasks[i%8] == 0 { subidx = 0 } else { subidx = 1 } if current.sub[subidx] == nil { current.sub[subidx] = new(Trie) } current = current.sub[subidx] } /* now add data */ current.data = data }