import netaddr
import networkx as nx
from base_objects import Switch
from misc.networkx import NxEdge, convert_nx_to_fw
from linux.ubuntu2204 import Ubuntu2204Server
from generic_vm_objects import GenericRouter
from firewheel.lib.utilities import strtobool
from firewheel.control.experiment_graph import Vertex, AbstractPlugin
[docs]
class Plugin(AbstractPlugin):
"""
This plugin provides an example of how to convert a NetworkX
based graph into a FIREWHEEL experiment.
Specifically, it leverages the :py:func:`nx.random_internet_as_graph`
topology which creates a random undirected graph resembling the Internet AS network.
"""
[docs]
def run(self, num_nodes="100", del_edges="False"):
"""
Run method documentation which takes the NetworkX graph and
converts it to FIREWHEEL.
Args:
num_nodes (str): Signifies the number of nodes in the network.
This should be convertible to an :py:data:`int`.
del_edges (str): Whether to delete any :py:class:`Edges <Edges>`
that are decorated with :py:class:`NxEdge`. This is particularly
useful for visualizing the graph structure.
This should be convertible to an :py:data:`bool`.
Raises:
RuntimeError: If the input parameters are improperly formatted.
"""
# Catch any issues with input parameters
try:
num_nodes = int(num_nodes)
except (TypeError, ValueError) as exc:
raise RuntimeError("The number of nodes should be an integer") from exc
del_edges = strtobool(del_edges)
# Create the random graph with the specified number of nodes
nx_inet = nx.random_internet_as_graph(num_nodes)
# Convert the NetworkX graph to FIREWHEEL Vertices/Edges
convert_nx_to_fw(nx_inet, self.g)
# Create an iterator with AS numbers which can be used with the BGP routers.
as_nums = iter(range(1, self.g.g.number_of_nodes() + 1))
# Each node models an autonomous system, with an attribute 'type'
# specifying its kind; tier-1 (T), mid-level (M), customer (C) or
# content-provider (CP).
# Let's rename the Vertices based on their type
tier_prefix = "Tier1"
mid_prefix = "mid-level"
cus_prefix = "customer"
for node in self.g.get_vertices():
# For each node, rename it and decorate it with the correct
# VM object
if node.nx_data["type"] == "T":
node.name = f"{tier_prefix}-{node.graph_id}"
node.decorate(GenericRouter)
# Setting the AS number for this router
node.set_bgp_as(next(as_nums))
if node.nx_data["type"] == "M":
node.name = f"{mid_prefix}-{node.graph_id}"
node.decorate(GenericRouter)
# Setting the AS number for this router
node.set_bgp_as(next(as_nums))
if node.nx_data["type"] == "C" or node.nx_data["type"] == "CP":
node.name = f"{cus_prefix}-{node.graph_id}"
node.decorate(Ubuntu2204Server)
# Create different networks for each layer-3 connection
# Internet networks, creates 65536 different networks
inet_networks = netaddr.IPNetwork("1.0.0.0/8").subnet(24)
# Customer networks, creates 65536 different networks
customer_networks = netaddr.IPNetwork("2.0.0.0/8").subnet(24)
# We cannot add new edges while iterating over the existing edges
# so we will keep track of any edges that need to be added.
router_edges = []
# For connections to customers, there should be a single switch (for the network)
# Therefore, let's use a dictionary for the router to hold all associated customers.
customer_edges = {}
# Search of edges that should be modified
for edge in self.g.get_edges():
# Ignore non NetworkX edges
if not edge.is_decorated_by(NxEdge):
continue
# Check if both the source and destination are routers
if edge.source.is_decorated_by(GenericRouter):
if edge.destination.is_decorated_by(GenericRouter):
router_edges.append(edge)
else:
# If the destination is not a router, it must be a customer
cust_list = customer_edges.get(edge.source, [])
cust_list.append(edge.destination)
customer_edges[edge.source] = cust_list
# If the source is Ubuntu, see if it's peer is a router
elif edge.source.is_decorated_by(Ubuntu2204Server):
if edge.destination.is_decorated_by(GenericRouter):
cust_list = customer_edges.get(edge.destination, [])
cust_list.append(edge.source)
customer_edges[edge.source] = cust_list
# Now we should iterate over all of the edges we should add/modify
for edge in router_edges:
vert = edge.source
neighbor = edge.destination
# A Switch is needed for this layer-3 edge
switch = Vertex(self.g, f"switch-{vert.name}-{neighbor.name}")
switch.decorate(Switch)
# Get the next available network
network = next(inet_networks)
ips = network.iter_hosts()
# Connect the routers to the switch and link them via BGP
vert.connect(switch, next(ips), network.netmask)
neighbor.connect(switch, next(ips), network.netmask)
vert.link_bgp(neighbor, switch)
# Iterate over all the routers with customers
for rtr, neighbor_list in customer_edges.items():
# A Switch is needed for this layer-3 edge
switch = Vertex(self.g, f"switch-{rtr.name}-customer")
switch.decorate(Switch)
# Get the next available network
network = next(customer_networks)
ips = network.iter_hosts()
# Connect the router to the switch and advertise
# this network using BGP
rtr.connect(switch, next(ips), network.netmask)
rtr.add_bgp_network(network)
# Connect each of the customers to the switch
for neighbor in neighbor_list:
neighbor.connect(switch, next(ips), network.netmask)
# If we want to remove all NxEdges to better help visualize the graph
# we can do that now
if del_edges:
rm_list = []
for edge in self.g.get_edges():
if edge.is_decorated_by(NxEdge):
rm_list.append(edge)
for edge in rm_list:
edge.delete()