pax_global_header 0000666 0000000 0000000 00000000064 13237415224 0014515 g ustar 00root root 0000000 0000000 52 comment=007c9af1ae532a165b75f90dc02993af4195ea75
canid-007c9af1ae532a165b75f90dc02993af4195ea75/ 0000775 0000000 0000000 00000000000 13237415224 0017513 5 ustar 00root root 0000000 0000000 canid-007c9af1ae532a165b75f90dc02993af4195ea75/.gitignore 0000664 0000000 0000000 00000000412 13237415224 0021500 0 ustar 00root root 0000000 0000000 # 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/LICENSE 0000664 0000000 0000000 00000002057 13237415224 0020524 0 ustar 00root root 0000000 0000000 MIT 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.md 0000664 0000000 0000000 00000005052 13237415224 0020774 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000004107 13237415224 0022455 0 ustar 00root root 0000000 0000000 package 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/ 0000775 0000000 0000000 00000000000 13237415224 0020571 5 ustar 00root root 0000000 0000000 canid-007c9af1ae532a165b75f90dc02993af4195ea75/canid/main.go 0000664 0000000 0000000 00000017356 13237415224 0022060 0 ustar 00root root 0000000 0000000 package 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.html 0000664 0000000 0000000 00000011720 13237415224 0023113 0 ustar 00root root 0000000 0000000
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/ 0000775 0000000 0000000 00000000000 13237415224 0020260 5 ustar 00root root 0000000 0000000 canid-007c9af1ae532a165b75f90dc02993af4195ea75/doc/canid.1 0000664 0000000 0000000 00000007452 13237415224 0021430 0 ustar 00root root 0000000 0000000 .\" 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.html 0000664 0000000 0000000 00000016530 13237415224 0022370 0 ustar 00root root 0000000 0000000
canid(1) - the caching additional network information daemon
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
-filecachefile (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.
-concurrencyn (default: 16)
Allow at most n simultaneous pending requests per backend.
-portport (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)
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.
February 2018
canid(1)
canid-007c9af1ae532a165b75f90dc02993af4195ea75/doc/canid.1.md 0000664 0000000 0000000 00000006703 13237415224 0022025 0 ustar 00root root 0000000 0000000 # 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.go 0000664 0000000 0000000 00000004503 13237415224 0022325 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000004032 13237415224 0021674 0 ustar 00root root 0000000 0000000 package 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.go 0000664 0000000 0000000 00000003214 13237415224 0021005 0 ustar 00root root 0000000 0000000 package 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
}