Initial commit

This commit is contained in:
Simon Marsh 2019-02-21 14:13:35 +00:00
commit f945ec7913
Signed by: burble
GPG Key ID: 7B9FE8780CFB6593
5 changed files with 472 additions and 0 deletions

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# lgregmapper
A micro server to interface between bird-lg and dn42regsrv and provide registry
data for the bird-lg bgpmap functionality.
The server works by querying the dn42regsrv API then providing a limited, memcached
like service that distributes the registry data to bird-lg.

View File

@ -0,0 +1,20 @@
##########################################################################
# lgregmapper systemd service file
##########################################################################
[Unit]
Description=Looking Glass Registry Mapper
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
User=lglass
Group=lglass
Type=simple
Restart=on-failure
ExecStart=/opt/lgregmapper/lgregmapper -b ":11211"
#########################################################################
# end of file

134
fakememcache.go Normal file
View File

@ -0,0 +1,134 @@
//////////////////////////////////////////////////////////////////////////
// lgregmapper, a small utility to provide DN42 registry data to bird-lg
//////////////////////////////////////////////////////////////////////////
package main
//////////////////////////////////////////////////////////////////////////
import (
// "fmt"
"bufio"
log "github.com/sirupsen/logrus"
"net"
"strings"
"sync"
)
//////////////////////////////////////////////////////////////////////////
type FakeMemcache struct {
done chan bool
wait *sync.WaitGroup
regClient *RegClient
}
//////////////////////////////////////////////////////////////////////////
// create a new fakememcache object
func NewFakeMemcache(regClient *RegClient) *FakeMemcache {
fmc := &FakeMemcache{
done: make(chan bool),
wait: &sync.WaitGroup{},
regClient: regClient,
}
fmc.wait.Add(1)
return fmc
}
//////////////////////////////////////////////////////////////////////////
// do the serving thing
func (fmc *FakeMemcache) Start(listener net.Listener) {
defer listener.Close()
defer fmc.wait.Done()
// listen and respond to connection or errors
go func() {
for {
conn, err := listener.Accept()
if err != nil {
// check if due to shutdown
select {
case <-fmc.done:
return
default:
}
// not shutdown, log an error and try again
log.WithFields(log.Fields{
"Error": err,
"addr": listener.Addr(),
}).Error("Listener accept error")
} else {
fmc.wait.Add(1)
go fmc.Serve(conn)
}
}
}()
// block waiting for done
<-fmc.done
}
//////////////////////////////////////////////////////////////////////////
// serve a single client
func (fmc *FakeMemcache) Serve(conn net.Conn) {
defer conn.Close()
defer fmc.wait.Done()
log.WithFields(log.Fields{
"Remote": conn.RemoteAddr(),
}).Debug("Accepted Connection")
// read lines from the client
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
command := strings.TrimSpace(scanner.Text())
// only respond to 'get' requests
if strings.HasPrefix(command, "get ") {
key := strings.TrimPrefix(command, "get ")
if data := fmc.regClient.ASN[key]; data == nil {
log.WithFields(log.Fields{
"command": command,
"remote": conn.RemoteAddr(),
}).Debug("Unknown key")
} else {
// return the cached data
conn.Write(data)
}
} else {
log.WithFields(log.Fields{
"command": command,
"remote": conn.RemoteAddr(),
}).Debug("Received unknown command")
}
}
log.WithFields(log.Fields{
"Remote": conn.RemoteAddr(),
}).Debug("Closing Connection")
}
//////////////////////////////////////////////////////////////////////////
// shutdown the server
func (fmc *FakeMemcache) Shutdown() {
// signal shutdown and wait to complete
close(fmc.done)
// fmc.wait.Wait()
log.Info("FakeMemcache shutdown")
}
//////////////////////////////////////////////////////////////////////////
// end of code

109
lgregmapper.go Normal file
View File

@ -0,0 +1,109 @@
//////////////////////////////////////////////////////////////////////////
// lgregmapper, a small utility to provide DN42 registry data to bird-lg
//////////////////////////////////////////////////////////////////////////
package main
//////////////////////////////////////////////////////////////////////////
import (
// "fmt"
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
"net"
"os"
"os/signal"
"time"
)
//////////////////////////////////////////////////////////////////////////
// utility function to set the log level
func setLogLevel(levelStr string) {
if level, err := log.ParseLevel(levelStr); err != nil {
// failed to set the level
// set a sensible default and, of course, log the error
log.SetLevel(log.InfoLevel)
log.WithFields(log.Fields{
"loglevel": levelStr,
"error": err,
}).Error("Failed to set requested log level")
} else {
// set the requested level
log.SetLevel(level)
}
}
//////////////////////////////////////////////////////////////////////////
// everything starts here
func main() {
// set a default log level, so that logging can be used immediately
// the level will be overidden later on once the command line
// options are loaded
log.SetLevel(log.InfoLevel)
log.Info("LG Reg Mapper Server Starting")
// declare cmd line options
var (
logLevel = flag.StringP("LogLevel", "l", "Info", "Log level")
refreshInterval = flag.StringP("Refresh", "i", "60m", "Refresh interval")
bindAddress = flag.StringP("BindAddress", "b", "localhost:11211",
"Server bind address")
apiAddress = flag.StringP("APIAddress", "a", "http://collector.dn42:8042",
"DN42 API server address")
)
flag.Parse()
// now initialise logging properly based on the cmd line options
setLogLevel(*logLevel)
// parse the refreshInterval and start data collection
interval, err := time.ParseDuration(*refreshInterval)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"interval": *refreshInterval,
}).Fatal("Unable to parse registry refresh interval")
}
// fetch initial registry data and start regular updates
regc := NewRegClient(*apiAddress)
regc.Update()
go regc.Start(interval)
listener, err := net.Listen("tcp", *bindAddress)
if err != nil {
log.WithFields(log.Fields{
"BindAddress": *bindAddress,
"Error": err,
}).Fatal("Failed to bind listener")
}
log.WithFields(log.Fields{
"BindAddress": *bindAddress,
}).Info("Listening")
// create a new server and start it working on the listen port
fmc := NewFakeMemcache(regc)
go fmc.Start(listener)
// sigint will perform a graceful exit
csig := make(chan os.Signal)
signal.Notify(csig, os.Interrupt)
// and block
<-csig
log.Info("Server shutting down")
regc.Shutdown()
fmc.Shutdown()
}
//////////////////////////////////////////////////////////////////////////
// end of code

201
regclient.go Normal file
View File

@ -0,0 +1,201 @@
//////////////////////////////////////////////////////////////////////////
// lgregmapper, a small utility to provide DN42 registry data to bird-lg
//////////////////////////////////////////////////////////////////////////
package main
//////////////////////////////////////////////////////////////////////////
import (
"fmt"
log "github.com/sirupsen/logrus"
"net/http"
"strings"
"sync"
// "net"
"encoding/json"
// "io/ioutil"
"time"
)
//////////////////////////////////////////////////////////////////////////
type RegClient struct {
done chan bool
wait *sync.WaitGroup
endpoint string
client *http.Client
ASN map[string][]byte
}
type APIAttributeResponse map[string]map[string][]string
type ASNData struct {
mntner []string
descr string
prefixes []string
}
//////////////////////////////////////////////////////////////////////////
// create a new RegClient object
func NewRegClient(apiAddr string) *RegClient {
rc := &RegClient{
done: make(chan bool),
wait: &sync.WaitGroup{},
client: &http.Client{Timeout: 20 * time.Second},
endpoint: apiAddr,
ASN: nil,
}
rc.wait.Add(1)
return rc
}
//////////////////////////////////////////////////////////////////////////
// regularly update the registry
func (rc *RegClient) Start(interval time.Duration) {
defer rc.wait.Done()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-rc.done:
return
case <-ticker.C:
rc.Update()
}
}
}
//////////////////////////////////////////////////////////////////////////
// shutdown the client
func (rc *RegClient) Shutdown() {
close(rc.done)
rc.wait.Wait()
log.Info("Registry client shutdown")
}
//////////////////////////////////////////////////////////////////////////
// fetch data from the registry API
func (rc *RegClient) queryAPI(query string, target interface{}) error {
query = rc.endpoint + "/api/registry/" + query
log.WithFields(log.Fields{
"Query": query,
}).Debug("Querying API")
response, err := rc.client.Get(query)
if err != nil {
log.WithFields(log.Fields{
"Error": err,
"Query": query,
}).Error("Unable to query API")
return err
}
defer response.Body.Close()
if err := json.NewDecoder(response.Body).Decode(target); err != nil {
log.WithFields(log.Fields{
"Error": err,
}).Error("Failed to decode JSON")
return err
}
return nil
}
//////////////////////////////////////////////////////////////////////////
// perform an update
func (rc *RegClient) Update() {
// query the API to collect ASN maintainers
mnts := make(APIAttributeResponse)
if err := rc.queryAPI("aut-num/*/mnt-by?raw", &mnts); err != nil {
return
}
// and ASN descriptions
descr := make(APIAttributeResponse)
if err := rc.queryAPI("aut-num/*/descr?raw", &descr); err != nil {
return
}
// and query again to collect origin data
origins := make(APIAttributeResponse)
if err := rc.queryAPI("*route/*/origin?raw", &origins); err != nil {
return
}
// normalise the returned data in to a struct
asnData := make(map[string]*ASNData)
for asnpath, adata := range mnts {
asn := strings.TrimPrefix(asnpath, "aut-num/")
var description string
dmap := descr[asnpath]
if dmap != nil && dmap["descr"] != nil && len(dmap["descr"]) > 0 {
description = dmap["descr"][0]
}
asnData[asn] = &ASNData{
mntner: adata["mnt-by"],
descr: description,
prefixes: nil,
}
}
// and add the origin data
for prefix, odata := range origins {
ix := strings.IndexRune(prefix, '/')
prefix = strings.Replace(prefix[ix+1:], "_", "/", 1)
for _, origin := range odata["origin"] {
if asnd := asnData[origin]; asnd != nil {
asnd.prefixes = append(asnd.prefixes, prefix)
}
}
}
// finally, turn the collated data in to a string suitable
// for returning from the fake memcache server
asns := make(map[string][]byte)
for asn, adata := range asnData {
asn = "lg_" + strings.TrimPrefix(asn, "AS")
plist := strings.Join(adata.prefixes, "\r")
if plist != "" {
// add spacer if not empty
plist = "\r" + plist
}
if adata.descr != "" {
adata.descr += "\r"
}
data := fmt.Sprintf("%s\r%s%s",
strings.Join(adata.mntner, "\r"),
adata.descr, plist)
asns[asn] = []byte(fmt.Sprintf("VALUE %s 0 %d\r\n%s\r\nEND\r\n",
asn, len(data), data))
}
log.WithFields(log.Fields{
"ASN Count": len(asns),
}).Debug("Updated ASN data")
rc.ASN = asns
}
//////////////////////////////////////////////////////////////////////////
// end of code