Initial commit
This commit is contained in:
commit
f945ec7913
8
README.md
Normal file
8
README.md
Normal 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.
|
||||||
|
|
20
contrib/lgregmapper.service
Normal file
20
contrib/lgregmapper.service
Normal 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
134
fakememcache.go
Normal 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
109
lgregmapper.go
Normal 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
201
regclient.go
Normal 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
|
Loading…
x
Reference in New Issue
Block a user