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