Skip to content

Commit

Permalink
Can advertise IPv4 routes
Browse files Browse the repository at this point in the history
Contributes to #6 and #7

Can sendIPv4 advertisements, still need to do withdrawals and then both
for IPv6
  • Loading branch information
samrussell committed May 13, 2018
1 parent 2498456 commit fc2a259
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 110 deletions.
3 changes: 3 additions & 0 deletions beeper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ routers:
peers:
- peer_ip: 172.25.0.111
peer_as: 65003
routes:
- prefix: "10.1.234.0/24"
next_hop: 172.25.0.1
- router_id: 172.25.0.1
local_as: 65002
local_address: "2001:db9:1::1"
Expand Down
19 changes: 17 additions & 2 deletions beeper/beeper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from copy import copy

from .stream_server import StreamServer

from .state_machine import StateMachine
from .peering import Peering
from .route import RouteAddition, RouteRemoval
from .ip import IPAddress, IPPrefix

DEFAULT_BGP_PORT = 179

Expand All @@ -21,12 +25,12 @@ def __init__(self, local_address, bgp_port, local_as,
self.peers = {}
self.peerings = []
self.stream_server = None
self.routes_to_advertise = []

if not self.bgp_port:
self.bgp_port = DEFAULT_BGP_PORT

def add_neighbor(self, connect_mode, peer_ip,
peer_as):
def add_neighbor(self, connect_mode, peer_ip, peer_as):
if connect_mode != "passive":
raise ValueError("Only passive BGP supported")
if peer_ip in self.peers:
Expand All @@ -37,6 +41,16 @@ def add_neighbor(self, connect_mode, peer_ip,
"peer_as": peer_as
}

def add_route(self, prefix, next_hop):
self.routes_to_advertise.append(
RouteAddition(
prefix=IPPrefix.from_string(prefix),
next_hop=IPAddress.from_string(next_hop),
as_path="",
origin="IGP"
)
)

def neighbor_states(self):
states = []
for peering in self.peerings:
Expand Down Expand Up @@ -69,6 +83,7 @@ def handle(self, socket, address):
local_address=self.local_address,
neighbor=peer["peer_ip"]
)
state_machine.routes_to_advertise = copy(self.routes_to_advertise)
peering = Peering(state_machine, address, socket, self.route_handler)
self.peerings.append(peering)
self.peer_up_handler(peer_ip, peer["peer_as"])
Expand Down
76 changes: 75 additions & 1 deletion beeper/bgp_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,44 @@ def parse_mp_unreach_nlri(packed_mp_reach_nlri):
15: "mp_unreach_nlri",
}

ORIGIN_NUMBERS = {
"IGP": 0,
"EGP": 1,
"INCOMPLETE": 2
}

def pack_origin(origin):
return struct.pack("!B", ORIGIN_NUMBERS[origin])

def pack_as_path(as_path):
# TODO actually do this
return b""

def pack_next_hop(next_hop):
return next_hop.address

attribute_packers = {
"origin": pack_origin,
"as_path": pack_as_path,
"next_hop": pack_next_hop
}

attribute_numbers = {
"origin" : 1,
"as_path" : 2,
"next_hop" : 3,
"mp_reach_nlri" : 14,
"mp_unreach_nlri" : 15,
}

attribute_flags = {
"origin" : 0x40,
"as_path" : 0x40,
"next_hop" : 0x40,
#"mp_reach_nlri" : 14,
#"mp_unreach_nlri" : 15,
}

def parse_path_attributes(serialised_path_attributes):
stream = BytesIO(serialised_path_attributes)
path_attributes = {}
Expand Down Expand Up @@ -278,7 +316,43 @@ def parse(cls, serialised_message):
return cls(withdrawn_routes, path_attributes, nlri)

def pack(self):
return b""
# TODO pack withdrawn routes
packed_withdrawn_routes = b""
packed_withdrawn_routes_length = struct.pack("!H", len(packed_withdrawn_routes))
packed_path_attributes = self.pack_path_attributes()
packed_path_attributes_length = struct.pack("!H", len(packed_path_attributes))
packed_nlri = self.pack_nlri()
return packed_withdrawn_routes_length + \
packed_withdrawn_routes + \
packed_path_attributes_length + \
packed_path_attributes + \
packed_nlri

def pack_path_attributes(self):
packed_path_attributes = []
sorted_attribute_pairs = sorted(self.path_attributes.items(), key=lambda x: attribute_numbers[x[0]])
for name, path_attribute in sorted_attribute_pairs:
packed_entry = attribute_packers[name](path_attribute)
packed_header = struct.pack(
"!BBB",
attribute_flags[name],
attribute_numbers[name],
len(packed_entry)
)
packed_path_attribute = packed_header + packed_entry
packed_path_attributes.append(packed_path_attribute)

return b"".join(packed_path_attributes)

def pack_nlri(self):
packed_nlri = []

for prefix in self.nlri:
# TODO this feels like it should be on IP4Prefix
packed_prefix = struct.pack("!B", prefix.length) + pack_prefix(prefix.prefix, prefix.length)
packed_nlri.append(packed_prefix)

return b"".join(packed_nlri)

def __str__(self):
return "BgpUpdateMessage: Widthdrawn routes: %s, Path attributes: %s, NLRI: %s" % (
Expand Down
12 changes: 10 additions & 2 deletions beeper/ip.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .ip4 import IP4Address
from .ip6 import IP6Address
from .ip4 import IP4Address, IP4Prefix
from .ip6 import IP6Address, IP6Prefix

class IPAddress:
@staticmethod
Expand All @@ -8,3 +8,11 @@ def from_string(string):
return IP6Address.from_string(string)
else:
return IP4Address.from_string(string)

class IPPrefix:
@staticmethod
def from_string(string):
if ":" in string:
return IP6Prefix.from_string(string)
else:
return IP4Prefix.from_string(string)
3 changes: 3 additions & 0 deletions beeper/ip4.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def __repr__(self):
def __eq__(self, other):
return self.address == other.address

def __hash__(self):
return hash(self.address)

class IP4Prefix:
def __init__(self, prefix, length):
self.prefix = prefix
Expand Down
30 changes: 29 additions & 1 deletion beeper/state_machine.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from eventlet.queue import Queue

from .event import Event
from .bgp_message import BgpMessage, BgpOpenMessage
from .bgp_message import BgpMessage, BgpOpenMessage, BgpUpdateMessage
from .bgp_message import BgpKeepaliveMessage, BgpNotificationMessage
from .route import RouteAddition, RouteRemoval
from .ip import IPAddress
Expand All @@ -22,6 +22,7 @@ def __init__(self, local_as, peer_as, router_id, local_address, neighbor, hold_t
self.keepalive_time = hold_time // 3
self.output_messages = Queue()
self.route_updates = Queue()
self.routes_to_advertise = []

self.timers = {
"hold": None,
Expand Down Expand Up @@ -93,6 +94,8 @@ def handle_message_active_state(self, message, tick):

def handle_message_open_confirm_state(self, message, tick):
if message.type == BgpMessage.KEEPALIVE_MESSAGE:
for message in self.build_update_messages():
self.output_messages.put(message)
self.timers["hold"] = tick
self.state = "established"
elif message.type == BgpMessage.NOTIFICATION_MESSAGE:
Expand Down Expand Up @@ -150,3 +153,28 @@ def process_route_update(self, update_message):
withdrawal
)
self.route_updates.put(route)

def build_update_messages(self):
update_messages = []

# TODO handle withdrawals
route_additions = filter(lambda x: isinstance(x, RouteAddition), self.routes_to_advertise)
nlri_by_path = {}
for route_addition in route_additions:
# TODO we're assuming IPv4 here
path_attributes = {
"next_hop": route_addition.next_hop,
"as_path": route_addition.as_path,
"origin": route_addition.origin
}
path_key = tuple(path_attributes.items())

if path_key not in nlri_by_path:
nlri_by_path[path_key] = []

nlri_by_path[path_key].append(route_addition.prefix)

for path_attributes, nlri in nlri_by_path.items():
update_messages.append(BgpUpdateMessage([], dict(path_attributes), nlri))

return update_messages
6 changes: 6 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ def run(self):
peer["peer_ip"],
peer["peer_as"],
)
if "routes" in router:
for route in router["routes"]:
beeper.add_route(
route["prefix"],
route["next_hop"]
)
self.beepers.append(beeper)
pool.spawn_n(beeper.run)
pool.waitall()
Expand Down
29 changes: 27 additions & 2 deletions test/test_bgp_message.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from beeper.bgp_message import BgpMessage, parse_bgp_message, BgpOpenMessage, BgpNotificationMessage, BgpKeepaliveMessage
from beeper.bgp_message import BgpMessage, parse_bgp_message, BgpOpenMessage
from beeper.bgp_message import BgpUpdateMessage, BgpNotificationMessage, BgpKeepaliveMessage
from beeper.ip4 import IP4Prefix, IP4Address
from beeper.ip6 import IP6Prefix, IP6Address
import socket
Expand Down Expand Up @@ -49,11 +50,26 @@ def test_notification_message_packs(self):
self.assertEqual(serialised_message, expected_serialised_message)

def test_update_message_new_routes_parses(self):
serialised_message = build_byte_string("0000002740010101400200400304c0a800218004040000000040050400000064c00808fe0901f4fe090258080a")
serialised_message = build_byte_string("0000000e40010101400200400304c0a80021080a")
message = parse_bgp_message(BgpMessage.UPDATE_MESSAGE, serialised_message)
self.assertEqual(message.nlri[0], IP4Prefix.from_string("10.0.0.0/8"))
self.assertEqual(message.path_attributes["next_hop"], IP4Address.from_string("192.168.0.33"))
self.assertEqual(message.path_attributes["origin"], "EGP")
self.assertEqual(message.path_attributes["as_path"], "")

def test_update_message_new_routes_packs(self):
expected_serialised_message = build_byte_string("0000000e40010101400200400304c0a80021080a17c0a840")
nlri = [
IP4Prefix.from_string("10.0.0.0/8"),
IP4Prefix.from_string("192.168.64.0/23")
]
path_attributes = {
"next_hop": IP4Address.from_string("192.168.0.33"),
"origin": "EGP",
"as_path": ""
}
message = BgpUpdateMessage([], path_attributes, nlri)
self.assertEquals(message.pack(), expected_serialised_message)

def test_update_message_withdrawn_routes_parses(self):
serialised_message = build_byte_string("0004180a01010000")
Expand Down Expand Up @@ -82,3 +98,12 @@ def test_update_v6_message_withdrawn_routes_parses(self):

self.assertEqual(message.path_attributes["mp_unreach_nlri"]["withdrawn_routes"], expected_withdrawn_routes)

# def test_update_v6_message_new_routes_packs(self):
# expected_serialised_message = build_byte_string("0000004b400101004002040201fdeb800e3d0002012020010db80001000000000242ac110002fe800000000000000042acfffe110002007f20010db40000000000000000000000002f20010db30000")
# message = parse_bgp_message(BgpMessage.UPDATE_MESSAGE, serialised_message)
# origin = "IGP"
# self.assertEqual(message.path_attributes["mp_reach_nlri"]["next_hop"]["afi"], IP6Address.from_string("2001:db8:1::242:ac11:2"))
# self.assertEqual(message.path_attributes["mp_reach_nlri"]["next_hop"]["safi"], IP6Address.from_string("fe80::42:acff:fe11:2"))
# self.assertEqual(message.path_attributes["mp_reach_nlri"]["nlri"][0], IP6Prefix.from_string("2001:db4::/127"))
# self.assertEqual(message.path_attributes["mp_reach_nlri"]["nlri"][1], IP6Prefix.from_string("2001:db3::/47"))

Loading

0 comments on commit fc2a259

Please sign in to comment.