Processing and Storing BGP Messages in MongoDB
Now that you know how to advertise prefixes to BGP peers with ExaBGP and are familiar with how to use this to influence traffic in your network, let's change gears and look at processing BGP messages between ExaBGP and its peers. Since ExaBGP uses JSON for message data, I figured it would be a good opportunity to use MongoDB so the message information can easily be stored into a database for data collection and analysis.
In the ExaBGP configuration file you can specify the types of messages you want to receive on STDOUT and also the encoding of the messages (text or JSON). Here's an example of configuring ExaBGP to send all BGP messages to the script running in the process section:
group test {
router-id 172.16.2.1;
local-as 65000;
local-address 172.16.2.1;
process syslog {
run /usr/bin/python path/to/syslog.py;
encoder json;
receive {
parsed;
update;
neighbor-changes;
}
}
neighbor 172.16.2.10 {
peer-as 65000;
}
}
This should look real similar to previous ExaBGP config files, but note the new receive
section and encoder
directive under process syslog
. This tells ExaBGP to output neighbor changes, updates, and all other parsed BGP messages in JSON format to STDOUT. There is also an option to output messages that ExaBGP sends to peers which you can read about here, but is outside the scope of this post.
Now that ExaBGP is outputting these messages we'll look at how to use the python script specified in the process syslog
section to parse the data. Here's an example of the messages in JSON format we receive when ExaBGP peers with a router, receives a prefix, and then the peering is shutdown:
// ExaBGP connects with peer 172.16.2.10
{
"exabgp": "3.4.8",
"time": 1435450105,
"host": "packetgeek.local",
"pid": "93361",
"ppid": "92918",
"counter": 14,
"type": "state",
"neighbor": {
"ip": "172.16.2.10",
"address": {
"local": "172.16.2.1",
"peer": "172.16.2.10"
},
"asn": {
"local": "65000",
"peer": "65000"
},
"state": "up"
}
}
// UPDATE message sent from peer with attributes and accompanying prefix(es)
{
"exabgp": "3.4.8",
"time": 1435450105,
"host": "packetgeek.local",
"pid": "93361",
"ppid": "92918",
"counter": 15,
"type": "update",
"neighbor": {
"ip": "172.16.2.10",
"address": {
"local": "172.16.2.1",
"peer": "172.16.2.10"
},
"asn": {
"local": "65000",
"peer": "65000"
},
"message": {
"update": {
"attribute": {
"origin": "igp",
"med": 0,
"local-preference": 100
},
"announce": {
"ipv4 unicast": {
"172.16.2.10": {
"1.1.1.0/24": {},
"10.10.0.0/24": {},
"100.10.10.0/24": {}
}
}
}
}
}
}
}
// Peer sends EoR (End of RIB) to notify ExaBGP has received all prefixes
{
"exabgp": "3.4.8",
"time": 1435450105,
"host": "packetgeek.local",
"pid": "93361",
"ppid": "92918",
"counter": 16,
"type": "update",
"neighbor": {
"ip": "172.16.2.10",
"address": {
"local": "172.16.2.1",
"peer": "172.16.2.10"
},
"asn": {
"local": "65000",
"peer": "65000"
},
"message": {
"eor": {
"afi": "ipv4",
"safi": "unicast"
}
}
}
}
// Peer withdraws the 100.10.10.0/24 prefix
{
"exabgp": "3.4.8",
"time": 1435450170,
"host": "packetgeek.local",
"pid": "93361",
"ppid": "92918",
"counter": 17,
"type": "update",
"neighbor": {
"ip": "172.16.2.10",
"address": {
"local": "172.16.2.1",
"peer": "172.16.2.10"
},
"asn": {
"local": "65000",
"peer": "65000"
},
"message": {
"update": {
"withdraw": {
"ipv4 unicast": {
"100.10.10.0/24": {}
}
}
}
}
}
}
// Connection with peer 172.16.2.10 is torn down
{
"exabgp": "3.4.8",
"time": 1435450190,
"host": "packetgeek.local",
"pid": "93361",
"ppid": "92918",
"counter": 18,
"type": "state",
"neighbor": {
"ip": "172.16.2.10",
"address": {
"local": "172.16.2.1",
"peer": "172.16.2.10"
},
"asn": {
"local": "65000",
"peer": "65000"
},
"state": "down",
"reason": "out loop, peer reset, message [closing connection] error[the TCP connection was closed by the remote end]"
}
}
As you can see, there's plenty of useful information provided in these messages in a very easy to consume JSON format. Here's a summary of the values in messages:
- Details about ExaBGP are contained in each message (Version, localhost, process ID)
- BGP Message type: State (related to OPEN), Notification, Update, and Keepalive
- ABGP neighbor section to show peer associated with the message and the message content
- This section will contain the route announcement/withdraw/EoR info
Just to show the process of storing these messages in our MongoDB database, let's pretend we're working on an app that will monitor the status of ExaBGP peers and send alerts if a peer connection is shutdown or keepalives haven't been received in a certain amount of time. For this app, we will only need to hold onto the state and keepalive messages. Go ahead and check out this example syslog script in the ExaBGP repo that reads from STDIN, as I will be using it as a base for this next example.
Instead of storing the entire JSON message in MongoDB, I will create a summarized version with just the info needed for the app (type, peer, time, and state info). This example also converts the timestamp to a python datetime object so it's a little easier to work with in our app. Here's the python example to do this using the syslog-1.py
script mentioned earlier as a base (I've preserved the original comments to help):
#!/usr/bin/env python
import json
import os
from sys import stdin, stdout
from pymongo import MongoClient
### DB Setup ###
client = MongoClient()
db = client.exabgp_db
updates = db.bgp_updates
def message_parser(line):
# Parse JSON string to dictionary
temp_message = json.loads(line)
# Convert Unix timestamp to python datetime
timestamp = datetime.fromtimestamp(temp_message['time'])
if temp_message['type'] == 'state':
message = {
'type': 'state',
'time': timestamp,
'peer': temp_message['neighbor']['ip'],
'state': temp_message['neighbor']['state'],
}
return message
if temp_message['type'] == 'keepalive':
message = {
'type': 'keepalive',
'time': timestamp,
'peer': temp_message['neighbor']['ip'],
}
return message
# If message is a different type, ignore
return None
counter = 0
while True:
try:
line = stdin.readline().strip()
# When the parent dies we are seeing continual newlines, so we only access so many before stopping
if line == "":
counter += 1
if counter > 100:
break
continue
counter = 0
# Parse message, and if it's the correct type, store in the database
message = message_parser(line)
if message:
updates.insert_one(message)
except KeyboardInterrupt:
pass
except IOError:
# most likely a signal during readline
pass
- This example assumes you have MongoDB and pymongo installed and are running it on the same host as ExaBGP with the default port.
So now if we were to connect to this database from the frontend app that will monitor BGP peer status, we can use the pymongo library to check for keepalives in the last 5 minutes from peer 172.16.2.10 like this:
#!/usr/bin/env python
from datetime import datetime, timedelta
from pymongo import MongoClient, DESCENDING
### DB Setup ###
client = MongoClient()
db = client.exabgp_db
updates = db.bgp_updates
def peer_is_up(peer_ip):
# Find keepalives in last 5 minutes, True or False based on count
has_keepalives = bool(updates.find({'peer': peer_ip, 'time': {'$gt': datetime.now() - timedelta(minutes=5)}}).count())
# Check latest state message for this peer
state_message = list(updates.find({'type': 'state', 'peer': peer_ip}).sort('time', DESCENDING).limit(1))
state = state_message[0]['state']
if has_keepalives and state == 'up':
return True
else:
return False
peer = '172.16.2.10'
peer_status = peer_is_up(peer)
if peer_status:
print 'Peer %s is up.' % peer
else:
print 'Peer %s is down.' % peer
This was just an introduction to working with the JSON messages from ExaBGP and storing them in a MongoDB for querying purposes. There's plenty of potential with access to the UPDATE messages from neighbors. Thanks for reading!