ExaBGP and etcd – processing routes

In my last post – we took a look at how we could leverage etcd from Python. In this post, I want to propose a use for leveraging etcd as a sort of message bus for ExaBGP. We saw some pretty compelling features with etcd that I think can work nicely in our ExaBGP model. So without further blabbering – let’s start coding.

Note: I assume you have a local instance of etcd installed and it is currently empty. If it’s not empty – you’ll want to clear it all out using a command like this ETCDCTL_API=3 etcdctl del "" --from-key=true

If you recall – in our last post on ExaBGP we were at a point where the ExaBGP process was using two Python programs we wrote. One for processing received routes (exa_bgp_receive.py) and one for sending route updates (exa_bgp_send.py). My goal here it to remove a lot of the logic for static route processing from these two scripts and make them more about route processing. More specifically – I want to turn the two Python scripts that ExaBGP is running on our behalf into simple programs that read/write to to/from etcd. Once we have that working, we’ll start on another program that takes care of configuring the data plane (like adding the static routes) based on what’s in etcd. So to start with – let’s define how we want to store this information in etcd. I’m going to suggest that we store this information using the peer’s IP address as part of the key. So what if we started with something like this….

/peers/<peer IP address>

In theory – we should never have more than one peer with the same IP address – so that seems to work. But what we’re storing really isn’t the peer, it’s the information we receive from the peer. Specifically – it’s the destination prefix the peer is advertising to us. So what if we came up with a key that looked like this….

/peers/<peer IP address>/<received prefix>

Again – in theory – this should yield unique key values since we shouldn’t receive the same prefix more than once from the same peer. Then – for the value we’ll use our fancy JSON trick and jam all the information from the route update into the JSON key value. So really the only significant change here is that we’re going to be writing to etcd instead of adding the routes to the servers routing table. In addition, we’re going to add more parsing to capture most of the information that we’re receiving in the update so that we can store it in etcd. If you recall we left out exa_bgp_receive.py looking like this after our last post…

import sys
import json
from pyroute2 import IPRoute

ip = IPRoute()

for route in ip.get_routes():
    if route.get_attr('RTA_PRIORITY') == 123:
        prefix = route.get_attr('RTA_DST') + "/" + str(route["dst_len"])
        ip.route("delete", dst=prefix, gateway=route.get_attr('RTA_GATEWAY'), priority=route.get_attr('RTA_PRIORITY'))

while True:
    line = sys.stdin.readline().strip()
    json_line = json.loads(line)
    neighbor_ip = json_line['neighbor']['address']['peer']
    for msg_type, attributes in json_line['neighbor']['message']['update'].items():
        if msg_type == "announce":
            for next_hop_address, nlri in json_line['neighbor']['message']['update']['announce']['ipv4 unicast'].items():
                for prefix in nlri:
                    sys.stderr.write("Received a BGP update announce from " + neighbor_ip + '\n')
                    sys.stderr.write("For prefix  " + prefix['nlri'] + '\n')
                    sys.stderr.write("With a next hop address of " + next_hop_address + '\n')
                    sys.stderr.flush()
                    ip.route("add", dst=prefix['nlri'], gateway=next_hop_address, priority=123)
        elif msg_type == "withdraw":
            for route in json_line['neighbor']['message']['update']['withdraw']['ipv4 unicast']:
                for nlri, prefix in route.items():
                    message = "Received a BGP update withdraw for " + prefix + " from " + neighbor_ip
                    sys.stderr.write(message + '\n')
                    ip.route("delete", dst=prefix, priority=123)
                    sys.stderr.flush()

I’m going to propose we clean this up a little bit. First off – let’s take out any reference to actually modifying the routing table and just replace that with comments. Let’s also define a couple of functions. One for writing our log messages to STDERR and one for determining what kind of update we’re receiving. This should help clean things up a little bit….

import sys
import json

def print_to_stderr(message):
    sys.stderr.write(message + '\n')
    sys.stderr.flush()

def get_update_type(json_line):
    type = ""
    for key, value in json_line['neighbor']['message']['update'].items():
        if key == "announce":
            type = "announce"
        elif key == "withdraw":
            type = "withdraw"
    return type

while True:
    line = sys.stdin.readline().strip()
    json_line = json.loads(line)
    neighbor_ip = json_line['neighbor']['address']['peer']
    msg_type = get_update_type(json_line)
    if msg_type == "announce":
        for next_hop_address, nlri in json_line['neighbor']['message']['update']['announce'][
            'ipv4 unicast'].items():
            for prefix in nlri:
                print_to_stderr("Received Route " + prefix['nlri'] + " from " + neighbor_ip + " with next hop " + next_hop_address)
                # Removed the route add functionality
    elif msg_type == "withdraw":
        for route in json_line['neighbor']['message']['update']['withdraw']['ipv4 unicast']:
            for nlri, prefix in route.items():
                print_to_stderr("Received Route withdrawal for " + prefix + " from " + neighbor_ip)
                # Removed the route delete functionality

Ok – so that cleans things up a little bit. Since the goal of this code is to simply write and delete entries from etcd – it’s safe to assume that we want to write an entry when receiving a new route and delete an entry when receiving a route withdrawal. So our etcd logic will go where our comments are now. But before we start writing to etcd, let’s make sure we’ve captured all of the information from the update itself. Let’s look at what a typical update from ExaBGP looks like…

{
  "exabgp": "4.0.1",
  "time": 1551809316.4873936,
  "host": "bgp_peer",
  "pid": 26008,
  "ppid": 1,
  "counter": 1,
  "type": "update",
  "neighbor": {
    "address": {
      "local": "10.10.10.5",
      "peer": "10.10.10.6"
    },
    "asn": {
      "local": 65000,
      "peer": 65000
    },
    "direction": "receive",
    "message": {
      "update": {
        "attribute": {
          "origin": "igp",
          "as-path": [
            65001
          ],
          "confederation-path": [],
          "local-preference": 100
        },
        "announce": {
          "ipv4 unicast": {
            "10.10.10.6": [
              {
                "nlri": "10.20.100.0/24"
              },
              {
                "nlri": "9.9.9.9/32"
              },
              {
                "nlri": "10.10.10.12/30"
              }
            ]
          }
        }
      }
    }
  }
}

Ok – so there’s a lot of information here and since we’re just storing this all somewhere – I think it makes good sense to capture as much as we can. Let’s pull all of this information out and store it in variables as part of the announce type message parsing…

import sys
import json

def print_to_stderr(message):
    sys.stderr.write(message + '\n')
    sys.stderr.flush()

def get_update_type(json_line):
    type = ""
    for key, value in json_line['neighbor']['message']['update'].items():
        if key == "announce":
            type = "announce"
        elif key == "withdraw":
            type = "withdraw"
    return type

while True:
    line = sys.stdin.readline().strip()
    json_line = json.loads(line)
    neighbor_ip = json_line['neighbor']['address']['peer']
    msg_type = get_update_type(json_line)
    if msg_type == "announce":
        local_ip = json_line['neighbor']['address']['local']
        local_asn = json_line['neighbor']['asn']['local']
        remote_asn = json_line['neighbor']['asn']['peer']
        bgp_origin = json_line['neighbor']['message']['update']['attribute']['origin']
        as_path = json_line['neighbor']['message']['update']['attribute']['as-path']
        confed_path = json_line['neighbor']['message']['update']['attribute']['confederation-path']
        local_pref = json_line['neighbor']['message']['update']['attribute']['local-preference']
        for next_hop_address, nlri in json_line['neighbor']['message']['update']['announce'][
            'ipv4 unicast'].items():
            for prefix in nlri:
                print_to_stderr("Received Route " + prefix['nlri'] + " from " + neighbor_ip + " with next hop " + next_hop_address)
                # Removed the route add functionality
    elif msg_type == "withdraw":
        for route in json_line['neighbor']['message']['update']['withdraw']['ipv4 unicast']:
            for nlri, prefix in route.items():
                print_to_stderr("Received Route withdrawal for " + prefix + " from " + neighbor_ip)
                # Removed the route delete functionality

Ok cool. So in our section for processing a route update we added in logic to pull out all the other pieces from the update and store them in variables. Remember – what we want to do is store all this extra information as a JSON blob as the value of the key. To do this – we’ll first add all of this information into a dictionary and then convert it to a JSON object. We’ll add a new function to do this…

import sys
import json

def print_to_stderr(message):
    sys.stderr.write(message + '\n')
    sys.stderr.flush()

def get_update_type(json_line):
    type = ""
    for key, value in json_line['neighbor']['message']['update'].items():
        if key == "announce":
            type = "announce"
        elif key == "withdraw":
            type = "withdraw"
    return type

def build_json_entry(neighbor, local_ip, dst_prefix, next_hop, local_asn, remote_asn,
                     bgp_origin, as_path, confed_path, local_pref):
  attributes = {"neighbor": neighbor, "dst_prefix": dst_prefix, "next_hop": next_hop,
                "local_ip": local_ip, "local_asn": local_asn, "remote_asn": remote_asn,
                "bgp_origin": bgp_origin, "as_path": as_path, "confed_path": confed_path,
                "local_pref": local_pref}
  return json.dumps(attributes)

while True:
    line = sys.stdin.readline().strip()
    json_line = json.loads(line)
    neighbor_ip = json_line['neighbor']['address']['peer']
    msg_type = get_update_type(json_line)
    if msg_type == "announce":
        local_ip = json_line['neighbor']['address']['local']
        local_asn = json_line['neighbor']['asn']['local']
        remote_asn = json_line['neighbor']['asn']['peer']
        bgp_origin = json_line['neighbor']['message']['update']['attribute']['origin']
        as_path = json_line['neighbor']['message']['update']['attribute']['as-path']
        confed_path = json_line['neighbor']['message']['update']['attribute']['confederation-path']
        local_pref = json_line['neighbor']['message']['update']['attribute']['local-preference']
        for next_hop_address, nlri in json_line['neighbor']['message']['update']['announce']['ipv4 unicast'].items():
            for prefix in nlri:
                print_to_stderr("Received Route " + prefix['nlri'] + " from " + neighbor_ip + " with next hop " + next_hop_address)
                json_object = build_json_entry(neighbor_ip, local_ip, prefix['nlri'], next_hop_address,
                                               local_asn, remote_asn, bgp_origin, as_path, confed_path, local_pref)
                # Removed the route add functionality
    elif msg_type == "withdraw":
        for route in json_line['neighbor']['message']['update']['withdraw']['ipv4 unicast']:
            for nlri, prefix in route.items():
                print_to_stderr("Received Route withdrawal for " + prefix + " from " + neighbor_ip)
                #Removed the route delete functionality

Alright – so at this point we have all the information we need to create the keys and the values in etcd. But before we do that – we need to talk a little bit about the structure of the key. Recall that we decided that we’d store things like this…

/peers/<peer IP address>/<received prefix>

The catch here is that a prefix can be like 10.10.10.0/24 which itself contains a /. Which would give us something like…

/peers/169.254.254.2/10.10.10.0/24

To keep things clear I’d like to change that so the key looks like this…

/peers/169.254.254.2/10.10.10.0-24

So let’s add a simple helper function to create the key and then add our etcd put operation…

import etcd3
import sys
import json

etcd = etcd3.client()

def print_to_stderr(message):
    sys.stderr.write(message + '\n')
    sys.stderr.flush()

def get_update_type(json_line):
    type = ""
    for key, value in json_line['neighbor']['message']['update'].items():
        if key == "announce":
            type = "announce"
        elif key == "withdraw":
            type = "withdraw"
    return type

def build_key(neighbor_ip, dst_prefix):
    return "/peers/" + neighbor_ip + "/" + dst_prefix.replace("/", "-")

def build_json_entry(neighbor, local_ip, dst_prefix, next_hop, local_asn, remote_asn,
                     bgp_origin, as_path, confed_path, local_pref):

  attributes = {"neighbor": neighbor, "dst_prefix": dst_prefix, "next_hop": next_hop,
                "local_ip": local_ip, "local_asn": local_asn, "remote_asn": remote_asn,
                "bgp_origin": bgp_origin, "as_path": as_path, "confed_path": confed_path,
                "local_pref": local_pref}
  return json.dumps(attributes)

while True:
    line = sys.stdin.readline().strip()
    json_line = json.loads(line)
    neighbor_ip = json_line['neighbor']['address']['peer']
    msg_type = get_update_type(json_line)
    if msg_type == "announce":
        local_ip = json_line['neighbor']['address']['local']
        local_asn = json_line['neighbor']['asn']['local']
        remote_asn = json_line['neighbor']['asn']['peer']
        bgp_origin = json_line['neighbor']['message']['update']['attribute']['origin']
        as_path = json_line['neighbor']['message']['update']['attribute']['as-path']
        confed_path = json_line['neighbor']['message']['update']['attribute']['confederation-path']
        local_pref = json_line['neighbor']['message']['update']['attribute']['local-preference']
        for next_hop_address, nlri in json_line['neighbor']['message']['update']['announce'][
            'ipv4 unicast'].items():
            for prefix in nlri:
                print_to_stderr("Received Route " + prefix['nlri'] + " from " + neighbor_ip + " with next hop " + next_hop_address)
                json_object = build_json_entry(neighbor_ip, local_ip, prefix['nlri'], next_hop_address,
                                               local_asn, remote_asn, bgp_origin, as_path, confed_path, local_pref)
                etcd.put(build_key(neighbor_ip, prefix['nlri']), json_object)
    elif msg_type == "withdraw":
        for route in json_line['neighbor']['message']['update']['withdraw']['ipv4 unicast']:
            for nlri, prefix in route.items():
                print_to_stderr("Received Route withdrawal for " + prefix + " from " + neighbor_ip)
                #Removed the route delete functionality

Cool! So at this point, if everything is working correctly, we should see entries loaded into etcd. To check, you can update the script on your server, and then restart the service…

user@bgp_peer:~$ sudo systemctl restart exabgp
user@bgp_peer:~$ ETCDCTL_API=3 etcdctl get "" --from-key=true
/peers/10.10.10.6/10.10.10.12-30
{"neighbor": "10.10.10.6", "dst_prefix": "10.10.10.12/30", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.100.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.100.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/9.9.9.9-32
{"neighbor": "10.10.10.6", "dst_prefix": "9.9.9.9/32", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
user@bgp_peer:~$ 

Nice! So the last thing we need to tackle is the route withdrawal. Since we now have all the information pulled out, this is as easy as just adding a delete for the given key…

import etcd3
import sys
import json

etcd = etcd3.client()

def print_to_stderr(message):
    sys.stderr.write(message + '\n')
    sys.stderr.flush()

def get_update_type(json_line):
    type = ""
    for key, value in json_line['neighbor']['message']['update'].items():
        if key == "announce":
            type = "announce"
        elif key == "withdraw":
            type = "withdraw"
    return type

def build_key(neighbor_ip, dst_prefix):
    return "/peers/" + neighbor_ip + "/" + dst_prefix.replace("/", "-")

def build_json_entry(neighbor, local_ip, dst_prefix, next_hop, local_asn, remote_asn,
                     bgp_origin, as_path, confed_path, local_pref):

  attributes = {"neighbor": neighbor, "dst_prefix": dst_prefix, "next_hop": next_hop,
                "local_ip": local_ip, "local_asn": local_asn, "remote_asn": remote_asn,
                "bgp_origin": bgp_origin, "as_path": as_path, "confed_path": confed_path,
                "local_pref": local_pref}
  return json.dumps(attributes)

while True:
    line = sys.stdin.readline().strip()
    json_line = json.loads(line)
    neighbor_ip = json_line['neighbor']['address']['peer']
    msg_type = get_update_type(json_line)
    if msg_type == "announce":
        local_ip = json_line['neighbor']['address']['local']
        local_asn = json_line['neighbor']['asn']['local']
        remote_asn = json_line['neighbor']['asn']['peer']
        bgp_origin = json_line['neighbor']['message']['update']['attribute']['origin']
        as_path = json_line['neighbor']['message']['update']['attribute']['as-path']
        confed_path = json_line['neighbor']['message']['update']['attribute']['confederation-path']
        local_pref = json_line['neighbor']['message']['update']['attribute']['local-preference']
        for next_hop_address, nlri in json_line['neighbor']['message']['update']['announce'][
            'ipv4 unicast'].items():
            for prefix in nlri:
                print_to_stderr("Received Route " + prefix['nlri'] + " from " + neighbor_ip + " with next hop " + next_hop_address)
                json_object = build_json_entry(neighbor_ip, local_ip, prefix['nlri'], next_hop_address,
                                               local_asn, remote_asn, bgp_origin, as_path, confed_path, local_pref)
                etcd.put(build_key(neighbor_ip, prefix['nlri']), json_object)
    elif msg_type == "withdraw":
        for route in json_line['neighbor']['message']['update']['withdraw']['ipv4 unicast']:
            for nlri, prefix in route.items():
                print_to_stderr("Received Route withdrawal for " + prefix + " from " + neighbor_ip)
                etcd.delete(build_key(neighbor_ip, prefix))

Ok – so now let’s once again update the script and then reload the service…

user@bgp_peer:~$ sudo systemctl restart exabgp
user@bgp_peer:~$ ETCDCTL_API=3 etcdctl get "" --from-key=true
/peers/10.10.10.6/10.10.10.12-30
{"neighbor": "10.10.10.6", "dst_prefix": "10.10.10.12/30", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.100.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.100.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/9.9.9.9-32
{"neighbor": "10.10.10.6", "dst_prefix": "9.9.9.9/32", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
user@bgp_peer:~$ 

Ok – so far so good – now let’s delete one of the static routes on our vMX-2 box to see if it clears out of etcd…

admin@vmx2.lab# delete routing-options static route 9.9.9.9/32 next-hop 10.10.10.14 

[edit]
admin@vmx2.lab# commit 
commit complete

[edit]
admin@vmx2.lab#

Now if we check etcd again…

user@bgp_peer:~$ ETCDCTL_API=3 etcdctl get "" --from-key=true
/peers/10.10.10.6/10.10.10.12-30
{"neighbor": "10.10.10.6", "dst_prefix": "10.10.10.12/30", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.100.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.100.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
user@bgp_peer:~$ 

Nice! So now let’s do a bulk add and delete and make sure things work as expected…

admin@vmx2.lab# set routing-options static route 10.20.10.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.20.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.30.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.40.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.50.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.60.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.70.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.80.0/24 discard 

[edit]
admin@vmx2.lab# set routing-options static route 10.20.90.0/24 discard 

[edit]
admin@vmx2.lab# commit 
commit complete

Now check etcd…

user@bgp_peer:~$ ETCDCTL_API=3 etcdctl get "" --from-key=true
/peers/10.10.10.6/10.10.10.12-30
{"neighbor": "10.10.10.6", "dst_prefix": "10.10.10.12/30", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.10.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.10.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.100.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.100.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.20.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.20.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.30.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.30.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.40.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.40.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.50.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.50.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.60.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.60.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.70.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.70.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.80.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.80.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.90.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.90.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
user@bgp_peer:~$ 

Now let’s delete them and check again…

admin@vmx2.lab# delete routing-options static route 10.20.10.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.20.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.30.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.40.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.50.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.60.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.70.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.80.0/24 discard 

[edit]
admin@vmx2.lab# delete routing-options static route 10.20.90.0/24 discard 

[edit]
admin@vmx2.lab# commit 
commit complete

[edit]
admin@vmx2.lab# 
user@bgp_peer:~$ ETCDCTL_API=3 etcdctl get "" --from-key=true
/peers/10.10.10.6/10.10.10.12-30
{"neighbor": "10.10.10.6", "dst_prefix": "10.10.10.12/30", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
/peers/10.10.10.6/10.20.100.0-24
{"neighbor": "10.10.10.6", "dst_prefix": "10.20.100.0/24", "next_hop": "10.10.10.6", "local_ip": "10.10.10.5", "local_asn": 65000, "remote_asn": 65000, "bgp_origin": "igp", "as_path": [65001], "confed_path": [], "local_pref": 100}
user@bgp_peer:~$ 

Nice! So at this point we’re now successfully using ExaBGP to write/delete route updates into etcd. In the next post, we’ll tackle the next piece of this which is making the exa_bgp_send.py script also work with etcd.

Stay tuned!

1 thought on “ExaBGP and etcd – processing routes

  1. DP

    I’ve read all of your 4 posts on exabgp. I would like to say thank you very much to make the knowledge so concise and precise.
    Your technique of technology writing is of one of the best I’ve ever seen. Reading your articles feels not only helpful but full of pleasure.
    Really look forward to your next post on this topic!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *