diff --git a/seedemu/core/Node.py b/seedemu/core/Node.py index b0dbd40e..1de3074e 100644 --- a/seedemu/core/Node.py +++ b/seedemu/core/Node.py @@ -381,6 +381,17 @@ def addHostName(self, name: str) -> Node: self.__host_names.append(name) return self + + + def getLocalIPAddress(self) -> str: + """! + @brief Get the IP address of the local interface for this node. + """ + for iface in self.getInterfaces(): + if iface.getNet().getType() == NetworkType.Local: + return iface.getAddress() + return '' + def getNameServers(self) -> List[str]: """! @brief get configured recursive name servers on this node. @@ -1030,6 +1041,7 @@ class Router(Node): def __init__(self, name: str, role: NodeRole, asn: int, scope: str = None): self.__is_border_router = False + self.__loopback_address=None super().__init__( name,role,asn,scope) def getRole(self) -> NodeRole: diff --git a/seedemu/core/ScionAutonomousSystem.py b/seedemu/core/ScionAutonomousSystem.py index ccfe3081..45eb74e9 100644 --- a/seedemu/core/ScionAutonomousSystem.py +++ b/seedemu/core/ScionAutonomousSystem.py @@ -35,7 +35,6 @@ class ScionAutonomousSystem(AutonomousSystem): # Origination, propagation, and registration intervals __beaconing_intervals: Tuple[Optional[str], Optional[str], Optional[str]] __beaconing_policy: Dict[str, Dict] - __next_ifid: int # Next IFID assigned to a link __note: str # optional free form parameter that contains interesting information about AS. This will be included in beacons if it is set __generateStaticInfoConfig: bool @@ -50,7 +49,6 @@ def __init__(self, asn: int, subnetTemplate: str = "10.{}.0.0/16"): self.__mtu = None self.__beaconing_intervals = (None, None, None) self.__beaconing_policy = {} - self.__next_ifid = 1 self.__note = None self.__generateStaticInfoConfig = False @@ -84,13 +82,6 @@ def configure(self, emulator: Emulator): base64.b64encode(os.urandom(16)).decode(), base64.b64encode(os.urandom(16)).decode()) - def getNextIfid(self) -> int: - """! - @brief Get next unused IFID. Called during configuration. - """ - ifid = self.__next_ifid - self.__next_ifid += 1 - return ifid def getSecretKeys(self) -> Tuple[str, str]: """! @@ -188,8 +179,11 @@ def getTopology(self, isd: int) -> Dict: for router in self.getBorderRouters(): rnode: ScionRouter = self.getRouter( router.getName() ) - border_routers[rnode.getName()] = { - "internal_addr": f"{rnode.getLoopbackAddress()}:30001", + listen_addr= rnode.getLocalIPAddress() if list([ ifn.getNet().isDirect() for ifn in rnode.getInterfaces() ]).count(True) ==1 else rnode.getLoopbackAddress() + #UDP address on which the router receives SCION packets + # from sibling routers and end hosts in this AS. + border_routers[rnode.getName()] = { + "internal_addr": f"{listen_addr }:30042", "interfaces": rnode.getScionInterfaces() } diff --git a/seedemu/layers/Routing.py b/seedemu/layers/Routing.py index 0322d2b9..34d04345 100644 --- a/seedemu/layers/Routing.py +++ b/seedemu/layers/Routing.py @@ -53,8 +53,8 @@ class Routing(Layer): protocols to use later and as router id. """ - __loopback_assigner: IPv4Network - __loopback_pos: int + _loopback_assigner: IPv4Network + _loopback_pos: int def __init__(self, loopback_range: str = '10.0.0.0/16'): """! @@ -64,14 +64,14 @@ def __init__(self, loopback_range: str = '10.0.0.0/16'): IP addresses. """ super().__init__() - self.__loopback_assigner = IPv4Network(loopback_range) - self.__loopback_pos = 1 + self._loopback_assigner = IPv4Network(loopback_range) + self._loopback_pos = 1 self.addDependency('Base', False, False) def getName(self) -> str: return "Routing" - def __installBird(self, node: Node): + def _installBird(self, node: Node): """! @brief Install bird on node, and handle the bug. """ @@ -84,68 +84,70 @@ def __installBird(self, node: Node): if not BaseSystem.doesAContainB(base,BaseSystem.SEEDEMU_ROUTER) and base !=BaseSystem.SEEDEMU_ROUTER: node.setBaseSystem(BaseSystem.SEEDEMU_ROUTER) - def configure(self, emulator: Emulator): - reg = emulator.getRegistry() - for ((scope, type, name), obj) in reg.getAll().items(): - if type == 'rs': - rs_node: Node = obj - self.__installBird(rs_node) - rs_node.appendStartCommand('[ ! -d /run/bird ] && mkdir /run/bird') - rs_node.appendStartCommand('bird -d', True) - self._log("Bootstrapping bird.conf for RS {}...".format(name)) + def _configure_rs(self, rs_node: Node): + rs_node.appendStartCommand('[ ! -d /run/bird ] && mkdir /run/bird') + rs_node.appendStartCommand('bird -d', True) + self._log("Bootstrapping bird.conf for RS {}...".format(rs_node.getName() )) - rs_ifaces = rs_node.getInterfaces() - assert len(rs_ifaces) == 1, "rs node {} has != 1 interfaces".format(rs_node.getName()) + rs_ifaces = rs_node.getInterfaces() + assert len(rs_ifaces) == 1, "rs node {} has != 1 interfaces".format(rs_node.getName()) - rs_iface = rs_ifaces[0] + rs_iface = rs_ifaces[0] - if not issubclass(rs_node.__class__, Router): - rs_node.__class__ = Router - rs_node.setBorderRouter(True) - rs_node.setFile("/etc/bird/bird.conf", RoutingFileTemplates["rs_bird"].format( + if not issubclass(rs_node.__class__, Router): + rs_node.__class__ = Router + rs_node.setBorderRouter(True) + rs_node.setFile("/etc/bird/bird.conf", RoutingFileTemplates["rs_bird"].format( routerId = rs_iface.getAddress() - )) + )) + + def _configure_bird_router(self, rnode: Router): + ifaces = '' + has_localnet = False + for iface in rnode.getInterfaces(): + net = iface.getNet() + if net.isDirect(): + has_localnet = True + ifaces += RoutingFileTemplates["rnode_bird_direct_interface"].format( + interfaceName = net.getName() + ) + rnode.setFile("/etc/bird/bird.conf", RoutingFileTemplates["rnode_bird"].format( + routerId = rnode.getLoopbackAddress() + )) + rnode.appendStartCommand('[ ! -d /run/bird ] && mkdir /run/bird') + rnode.appendStartCommand('bird -d', True) + if has_localnet: rnode.addProtocol('direct', 'local_nets', RoutingFileTemplates['rnode_bird_direct'].format(interfaces = ifaces)) + def configure(self, emulator: Emulator): + reg = emulator.getRegistry() + for ((scope, type, name), obj) in reg.getAll().items(): + if type == 'rs': + rs_node: Node = obj + self._installBird(rs_node) + self._configure_rs(rs_node) if type == 'rnode': rnode: Router = obj if not issubclass(rnode.__class__, Router): rnode.__class__ = Router self._log("Setting up loopback interface for AS{} Router {}...".format(scope, name)) - lbaddr = self.__loopback_assigner[self.__loopback_pos] + if rnode.getLoopbackAddress()==None: + lbaddr = self._loopback_assigner[self._loopback_pos] - rnode.appendStartCommand('ip li add dummy0 type dummy') - rnode.appendStartCommand('ip li set dummy0 up') - rnode.appendStartCommand('ip addr add {}/32 dev dummy0'.format(lbaddr)) - rnode.setLoopbackAddress(lbaddr) - self.__loopback_pos += 1 + rnode.appendStartCommand('ip li add dummy0 type dummy') + rnode.appendStartCommand('ip li set dummy0 up') + rnode.appendStartCommand('ip addr add {}/32 dev dummy0'.format(lbaddr)) + rnode.setLoopbackAddress(lbaddr) + self._loopback_pos += 1 self._log("Bootstrapping bird.conf for AS{} Router {}...".format(scope, name)) - self.__installBird(rnode) + self._installBird(rnode) r_ifaces = rnode.getInterfaces() assert len(r_ifaces) > 0, "router node {}/{} has no interfaces".format(rnode.getAsn(), rnode.getName()) - ifaces = '' - has_localnet = False - - for iface in r_ifaces: - net = iface.getNet() - if net.isDirect(): - has_localnet = True - ifaces += RoutingFileTemplates["rnode_bird_direct_interface"].format( - interfaceName = net.getName() - ) - - rnode.setFile("/etc/bird/bird.conf", RoutingFileTemplates["rnode_bird"].format( - routerId = rnode.getLoopbackAddress() - )) - - rnode.appendStartCommand('[ ! -d /run/bird ] && mkdir /run/bird') - rnode.appendStartCommand('bird -d', True) - - if has_localnet: rnode.addProtocol('direct', 'local_nets', RoutingFileTemplates['rnode_bird_direct'].format(interfaces = ifaces)) + self._configure_bird_router(rnode) def render(self, emulator: Emulator): reg = emulator.getRegistry() diff --git a/seedemu/layers/Scion.py b/seedemu/layers/Scion.py index 1a38c29c..2ffc555e 100644 --- a/seedemu/layers/Scion.py +++ b/seedemu/layers/Scion.py @@ -1,6 +1,6 @@ from __future__ import annotations from enum import Enum -from typing import Dict, Tuple, Union +from typing import Dict, Tuple, Union, Any from seedemu.core import (Emulator, Interface, Layer, Network, Registry, Router, ScionAutonomousSystem, ScionRouter, @@ -36,15 +36,22 @@ def to_topo_format(self) -> str: return "PEER" assert False, "invalid scion link type" - def to_json(self, a_to_b: bool) -> str: + def to_json(self, core_as: bool, is_parent: bool) -> str: + """ + a core AS has to have 'CHILD' as its 'link_to' attribute value, + for all interfaces!! + The child AS on the other end of the link will have 'PARENT' + """ if self.value == "Core": + assert core_as, 'non core AS cannot have core link' return "CORE" elif self.value == "Peer": return "PEER" elif self.value == "Transit": - if a_to_b: + if is_parent: return "CHILD" else: + assert not core_as, 'Logic error: Core ASes must only provide transit to customers, not receive it!' return "PARENT" @@ -56,9 +63,9 @@ class Scion(Layer, Graphable): alone do not uniquely identify a SCION AS (see ScionISD layer). """ - __links: Dict[Tuple[IA, IA, str, str, LinkType], int] - __ix_links: Dict[Tuple[int, IA, IA, str, str, LinkType], int] - + __links: Dict[Tuple[IA, IA, str, str, LinkType], int] + __ix_links: Dict[Tuple[int, IA, IA, str, str, LinkType], Dict[str,Any] ] + __if_ids_by_as = {} def __init__(self): """! @brief SCION layer constructor. @@ -71,6 +78,75 @@ def __init__(self): def getName(self) -> str: return "Scion" + @staticmethod + def _setIfId( ia: IA, ifid: int ): + ifs = set() + if ia in Scion.__if_ids_by_as: + ifs = Scion.__if_ids_by_as[ia] + + v = ifid in ifs + ifs.add(ifid) + Scion.__if_ids_by_as[ia] = ifs + return v + + @staticmethod + def getIfIds(ia: IA) : + ifs = set() + keys = Scion.__if_ids_by_as.keys() + if ia in keys: + ifs = Scion.__if_ids_by_as[ia] + return ifs + + @staticmethod + def peekNextIfId( ia: IA) -> int: + """get the next free IFID, but don't allocate it yet + subsequent calls return the same, if not interleaved with getNextIfId() or _setIfId() + """ + ifs = set() + if ia in Scion.__if_ids_by_as: + ifs = Scion.__if_ids_by_as[ia] + + if not ifs: + return 0 + + last = -1 + for i in ifs: + if i-last >1: + return last+1 + else: + last=i + + return last +1 + + @staticmethod + def getNextIfId( ia: IA ) -> int: + """ allocate the next free IFID + if call returned X, a subsequent call will return X+1 (or higher) + """ + + ifs = set() + if ia in Scion.__if_ids_by_as: + ifs = Scion.__if_ids_by_as[ia] + + + if not ifs: + ifs.add(1) + ifs.add(0) + Scion.__if_ids_by_as[ia] = ifs + + return 1 + + last = -1 + for i in ifs: + if i-last >1: + return last+1 + else: + last=i + + ifs.add(last+1) + Scion.__if_ids_by_as[ia] = ifs + return last+1 + def addXcLink(self, a: Union[IA, Tuple[int, int]], b: Union[IA, Tuple[int, int]], linkType: LinkType, count: int=1, a_router: str="", b_router: str="",) -> 'Scion': @@ -97,8 +173,10 @@ def addXcLink(self, a: Union[IA, Tuple[int, int]], b: Union[IA, Tuple[int, int]] return self +# additional arguments in 'kwargs': +# i.e. a_IF_ID and b_IF_ID if known (i.e. by a DataProvider) def addIxLink(self, ix: int, a: Union[IA, Tuple[int, int]], b: Union[IA, Tuple[int, int]], - linkType: LinkType, count: int=1, a_router: str="", b_router: str="") -> 'Scion': + linkType: LinkType, count: int=1, a_router: str="", b_router: str="", **kwargs ) -> 'Scion': """! @brief Create a private link between two ASes at an IX. @@ -106,6 +184,7 @@ def addIxLink(self, ix: int, a: Union[IA, Tuple[int, int]], b: Union[IA, Tuple[i @param a First AS (ISD and ASN). @param b Second AS (ISD and ASN). @param linkType Link type from a to b. + In case of Transit: A is parent @param count Number of parallel links. @param a_router router of AS a default is "" @param b_router router of AS b default is "" @@ -119,7 +198,22 @@ def addIxLink(self, ix: int, a: Union[IA, Tuple[int, int]], b: Union[IA, Tuple[i assert (a, b, a_router, b_router, linkType) not in self.__links, ( "Link between as{} and as{} of type {} at ix{} exists already.".format(a, b, linkType, ix)) - self.__ix_links[(ix, a, b, a_router, b_router, linkType)] = count + key = (ix, a, b, a_router, b_router, linkType) + + ids = [] + if 'if_ids' in kwargs: + ids = kwargs['if_ids'] + assert not Scion._setIfId(a, ids[0]), 'interface ID not unique' + assert not Scion._setIfId(b, ids[1]), 'interface ID not unique' + else: # auto assign next free IFIDs + ids = (Scion.getNextIfId(a), Scion.getNextIfId(b)) + + if key in self.__ix_links.keys(): + self.__ix_links[key]['count'] += count + else: + self.__ix_links[key] = {'count': count , 'if_ids': set()} + + self.__ix_links[key]['if_ids'].add( ids ) return self @@ -171,7 +265,10 @@ def _doCreateGraphs(self, emulator: Emulator) -> None: 'ISD{}'.format(a.isd), 'ISD{}'.format(b.isd), style= 'dashed') - for (ix, a, b, a_router, b_router, rel), count in self.__ix_links.items(): + for (ix, a, b, a_router, b_router, rel), d in self.__ix_links.items(): + count = d['count'] + ifids = d['if_ids'] + assert count == len(ifids) a_shape = 'doublecircle' if scionIsd_layer.isCoreAs(a.isd, a.asn) else 'circle' b_shape = 'doublecircle' if scionIsd_layer.isCoreAs(b.isd, b.asn) else 'circle' @@ -181,27 +278,33 @@ def _doCreateGraphs(self, emulator: Emulator) -> None: graph.addVertex('AS{}'.format(b.asn), 'ISD{}'.format(b.isd), b_shape) if rel == LinkType.Core: - for _ in range(count): + for ids in ifids: graph.addEdge('AS{}'.format(a.asn), 'AS{}'.format(b.asn), 'ISD{}'.format(a.isd), 'ISD{}'.format(b.isd), - label='IX{}'.format(ix), style= 'bold') - if rel == LinkType.Transit: - for _ in range(count): + label='IX{}'.format(ix), style= 'bold', + alabel=f'#{ids[0]}',blabel=f'#{ids[1]}' ) + elif rel == LinkType.Transit: + for ids in ifids: graph.addEdge('AS{}'.format(a.asn), 'AS{}'.format(b.asn), 'ISD{}'.format(a.isd), 'ISD{}'.format(b.isd), - label='IX{}'.format(ix), alabel='P', blabel='C') - if rel == LinkType.Peer: - for _ in range(count): + label='IX{}'.format(ix), + alabel=f'P #{ids[0]}', blabel=f'C #{ids[1]}') + elif rel == LinkType.Peer: + for ids in ifids: graph.addEdge('AS{}'.format(a.asn), 'AS{}'.format(b.asn), 'ISD{}'.format(a.isd), 'ISD{}'.format(b.isd), - 'IX{}'.format(ix), style= 'dashed') + 'IX{}'.format(ix), style= 'dashed', + alabel=f'#{ids[0]}',blabel=f'#{ids[1]}') + else: + raise RuntimeError("invalid LinkType") def print(self, indent: int = 0) -> str: out = ' ' * indent out += 'ScionLayer:\n' indent += 4 - for (ix, a, b, a_router, b_router, rel), count in self.__ix_links.items(): + for (ix, a, b, a_router, b_router, rel), d in self.__ix_links.items(): + count = d['count'] out += ' ' * indent if a_router == "": out += f'IX{ix}: AS{a} -({rel})-> ' @@ -268,7 +371,8 @@ def _configure_links(self, reg: Registry, base_layer: ScionBase) -> None: a_addr, b_addr, net, rel) # IX links - for (ix, a, b, a_router, b_router, rel), count in self.__ix_links.items(): + for (ix, a, b, a_router, b_router, rel), d in self.__ix_links.items(): + count = d['count'] ix_reg = ScopedRegistry('ix', reg) a_reg = ScopedRegistry(str(a.asn), reg) b_reg = ScopedRegistry(str(b.asn), reg) @@ -292,11 +396,19 @@ def _configure_links(self, reg: Registry, base_layer: ScionBase) -> None: b_ixrouter, b_ixif = self.__get_ix_port(b_routers, ix_net) except AssertionError: assert False, f"cannot resolve scion peering: as{a} not in ix{ix}" - - for _ in range(count): + if 'if_ids' in d: self._log(f"add scion IX link: {a_ixif.getAddress()} AS{a} -({rel})->" f"{b_ixif.getAddress()} AS{b}") - self.__create_link(a_ixrouter, b_ixrouter, a, b, a_as, b_as, + for ids in d['if_ids']: + self.__create_link(a_ixrouter, b_ixrouter, a, b, a_as, b_as, + str(a_ixif.getAddress()), str(b_ixif.getAddress()), + ix_net, rel, if_ids = ids ) + else: + for _ in range(count): + self._log(f"add scion IX link: {a_ixif.getAddress()} AS{a} -({rel})->" + f"{b_ixif.getAddress()} AS{b}") + + self.__create_link(a_ixrouter, b_ixrouter, a, b, a_as, b_as, str(a_ixif.getAddress()), str(b_ixif.getAddress()), ix_net, rel) @@ -326,32 +438,52 @@ def __create_link(self, a_ia: IA, b_ia: IA, a_as: ScionAutonomousSystem, b_as: ScionAutonomousSystem, a_addr: str, b_addr: str, - net: Network, rel: LinkType): - """Create a link between SCION BRs a and b.""" - a_ifid = a_as.getNextIfid() - b_ifid = b_as.getNextIfid() + net: Network, + rel: LinkType, + if_ids=None ): + """Create a link between SCION BRs a and b. + In case of LinkType Transit: A is parent of B + """ + + a_ifid = -1 + b_ifid = -1 + + if if_ids: + a_ifid = if_ids[0] + b_ifid = if_ids[1] + else: + a_ifid = Scion.getNextIfId(a_ia) + b_ifid = Scion.getNextIfId(b_ia) + a_port = a_router.getNextPort() b_port = b_router.getNextPort() + a_core = 'core' in a_as.getAsAttributes(a_ia.isd) + b_core = 'core' in b_as.getAsAttributes(b_ia.isd) + + if a_core and b_core: assert rel==LinkType.Core, f'Between Core ASes there can only be Core Links! {a_ia} -- {b_ia}' + a_iface = { "underlay": { - "public": f"{a_addr}:{a_port}", + "local": f"{a_addr}:{a_port}", "remote": f"{b_addr}:{b_port}", }, "isd_as": str(b_ia), - "link_to": rel.to_json(a_to_b=True), + "link_to": rel.to_json( a_core, True), "mtu": net.getMtu(), } + # TODO: additional settings according to config of 'as_a' b_iface = { "underlay": { - "public": f"{b_addr}:{b_port}", + "local": f"{b_addr}:{b_port}", "remote": f"{a_addr}:{a_port}", }, "isd_as": str(a_ia), - "link_to": rel.to_json(a_to_b=False), + "link_to": rel.to_json( b_core,False), "mtu": net.getMtu(), } + # TODO: additional settings according to config of 'as_b' # XXX(benthor): Remote interface id could probably be added # regardless of LinkType but might then undermine SCION's @@ -363,9 +495,9 @@ def __create_link(self, # supported in upstream SCION. if rel == LinkType.Peer: self._log("WARNING: As of February 2023 SCION peering links are not supported in upstream SCION") - a_iface["remote_interface_id"] = b_ifid - b_iface["remote_interface_id"] = a_ifid + a_iface["remote_interface_id"] = int(b_ifid) + b_iface["remote_interface_id"] = int(a_ifid) # Create interfaces in BRs - a_router.addScionInterface(a_ifid, a_iface) - b_router.addScionInterface(b_ifid, b_iface) + a_router.addScionInterface(int(a_ifid), a_iface) + b_router.addScionInterface(int(b_ifid), b_iface) diff --git a/seedemu/layers/ScionRouting.py b/seedemu/layers/ScionRouting.py index 9294de76..95631f9b 100644 --- a/seedemu/layers/ScionRouting.py +++ b/seedemu/layers/ScionRouting.py @@ -3,12 +3,15 @@ import os.path from typing import Dict, Tuple from ipaddress import IPv4Address +from typing import List import yaml -from seedemu.core import Emulator, Node, ScionAutonomousSystem, ScionRouter, Network +from seedemu.core import Emulator, Node, ScionAutonomousSystem, ScionRouter, Network, Router from seedemu.core.enums import NetworkType from seedemu.layers import Routing, ScionBase, ScionIsd +from seedemu.layers.Scion import Scion +from seedemu.core.ScionAutonomousSystem import IA _Templates: Dict[str, str] = {} @@ -72,13 +75,79 @@ class ScionRouting(Routing): During layer configuration Router nodes are replaced with ScionRouters which add methods for configuring SCION border router interfaces. """ + _static_routing: bool = True + + def __init__(self, loopback_range: str = '10.0.0.0/16', + static_routing: bool = True ): + """ + @param static_routing install and configure BIRD routing daemon only on routers + which are connected to more than one local-net (actual intra-domain routers). + Can be disabled to have BIRD on all routers, required or not. + """ + super().__init__(loopback_range) + + ScionRouting._static_routing = static_routing + + def configure_base(self, emulator: Emulator) -> List[Router]: + """ + returns list of routers which need routing daemon installed, + because it is connected to more than one local-net + """ + + actual_routers = [] + + reg = emulator.getRegistry() + has_bgp = False + try: + _ = emulator.getLayer('Ebgp') + has_bgp=True + except: + pass + for ((scope, type, name), obj) in reg.getAll().items(): + + if type == 'rs' : + if not has_bgp: + raise RuntimeError('SCION has no concept of Route Servers.') + else: + self._installBird(obj) + self._configure_rs(obj) + if type == 'rnode': + rnode: Router = obj + if not issubclass(rnode.__class__, Router): rnode.__class__ = Router + + assert rnode.getLoopbackAddress() == None + self._log("Setting up loopback interface for AS{} Router {}...".format(scope, name)) + + lbaddr = self._loopback_assigner[self._loopback_pos] + + rnode.appendStartCommand('ip li add dummy0 type dummy') + rnode.appendStartCommand('ip li set dummy0 up') + rnode.appendStartCommand('ip addr add {}/32 dev dummy0'.format(lbaddr)) + rnode.setLoopbackAddress(lbaddr) + self._loopback_pos += 1 + + r_ifaces = rnode.getInterfaces() + assert len(r_ifaces) > 0, "router node {}/{} has no interfaces".format(rnode.getAsn(), rnode.getName()) + localnet_count = list([ ifn.getNet().isDirect() for ifn in r_ifaces ]).count(True) + if localnet_count > 1: + actual_routers.append(rnode) + + return actual_routers + + def configure(self, emulator: Emulator): """! @brief Install SCION on router, control service and host nodes. """ - super().configure(emulator) - + if ScionRouting._static_routing: + # install BIRD routing daemon only where necessary + bird_routers=self.configure_base(emulator) + for br in bird_routers: + self._installBird(br) + self._configure_bird_router(br) + else: + super().configure(emulator) reg = emulator.getRegistry() for ((scope, type, name), obj) in reg.getAll().items(): # SCION inter-domain routing affects only border-routers @@ -145,11 +214,11 @@ def render(self, emulator: Emulator): as_topology = as_.getTopology(isds[0][0]) node.setFile("/etc/scion/topology.json", json.dumps(as_topology, indent=2)) - self.__provision_base_config(node) + self._provision_base_config(node,as_) if type == 'brdnode': rnode: ScionRouter = obj - self.__provision_router_config(rnode) + self._provision_router_config(rnode,as_) elif type == 'csnode': csnode: Node = obj self._provision_cs_config(csnode, as_) @@ -160,8 +229,7 @@ def render(self, emulator: Emulator): hnode: Node = obj self.__provision_dispatcher_config(hnode, isds[0][0], as_) - @staticmethod - def __provision_base_config(node: Node): + def _provision_base_config(self, node: Node,_as: AutonomousSystem): """Set configuration for sciond and dispatcher.""" node.addBuildCommand("mkdir -p /cache") @@ -194,7 +262,7 @@ def __provision_dispatcher_config(node: Node, isd: int, as_: ScionAutonomousSyst node.setFile("/etc/scion/dispatcher.toml", _Templates["dispatcher"].format(isd_as=isd_as, ip=ip)) @staticmethod - def __provision_router_config(router: ScionRouter): + def _provision_router_config(router: ScionRouter, _as: AutonomousSystem ): """Set border router configuration on router nodes.""" name = router.getName() @@ -247,10 +315,12 @@ def _get_internal_link_properties(interface : int, as_ : ScionAutonomousSystem) "Hops": {}, "Geo": {}, } + def hasGeo(as_ , br_name: str)-> bool: + return as_.getRouter(br_name).getGeo() != None # get Geo information for this interface if it exists - if as_.getRouter(this_br_name).getGeo(): - (lat,long,address) = as_.getRouter(this_br_name).getGeo() + if (br:=as_.getRouter(this_br_name)).getGeo(): + (lat,long,address) = br.getGeo() ifs["Geo"] = { "Latitude": lat, "Longitude": long, @@ -274,12 +344,17 @@ def _get_internal_link_properties(interface : int, as_ : ScionAutonomousSystem) else: net = ScionRouting._get_networks_from_router(this_br_name, br_str, as_) # get network between the two routers (Assume any two routers in AS are connected through a network) (latency, bandwidth, packetDrop) = net.getDefaultLinkProperties() - mtu = net.getMtu() - ifs["Latency"][str(other_if)] = f"{latency}ms" + if latency==0 and hasGeo(as_, this_br_name) and hasGeo(as_, br_str): + # compute lightspeed latency estimation from geodesic distance + (lat_1,long_1,_)= as_.getRouter(this_br_name).getGeo() + (lat_2,long_2,_)= as_.getRouter(br_str).getGeo() + + latency = ( geodesic( (lat_1,long_1),(lat_2,long_2) ).km *1000 /299792458) *1000 # [ms] + ifs["Latency"][str(other_if)] = f"{round(latency*1000)}us" if bandwidth != 0: # if bandwidth is not 0, add it ifs["Bandwidth"][str(other_if)] = int(bandwidth/1000) # convert bps to kbps ifs["packetDrop"][str(other_if)] = f"{packetDrop}" - ifs["MTU"][str(other_if)] = f"{mtu}" + ifs["MTU"][str(other_if)] = f"{net.getMtu()}" ifs["Hops"][str(other_if)] = 1 # NOTE: if interface is on different router, hops is 1 since we assume all routers are connected through a network @@ -292,8 +367,8 @@ def _get_xc_link_properties(interface : int, as_ : ScionAutonomousSystem) -> Tup """ this_br_name = ScionRouting._get_BR_from_interface(interface, as_) this_br = as_.getRouter(this_br_name) - - if_addr = this_br.getScionInterface(interface)['underlay']["public"].split(':')[0] + interface = this_br.getScionInterface(interface) + if_addr = interface['underlay']["local"].split(':')[0] xcs = this_br.getCrossConnects() @@ -310,7 +385,7 @@ def _get_ix_link_properties(interface : int, as_ : ScionAutonomousSystem) -> Tup this_br_name = ScionRouting._get_BR_from_interface(interface, as_) this_br = as_.getRouter(this_br_name) - if_addr = IPv4Address(this_br.getScionInterface(interface)['underlay']["public"].split(':')[0]) + if_addr = IPv4Address(this_br.getScionInterface(interface)['underlay']["local"].split(':')[0]) # get a list of all ix networks this Border Router is attached to ixs = [ifa.getNet() for ifa in this_br.getInterfaces() if ifa.getNet().getType() == NetworkType.InternetExchange] @@ -340,7 +415,7 @@ def _provision_staticInfo_config(node: Node, as_: ScionAutonomousSystem): } # iterate through all ScionInterfaces in AS - for interface in range(1,as_._ScionAutonomousSystem__next_ifid): + for interface in Scion.getIfIds(IA(1,as_.getAsn())): ifs = ScionRouting._get_internal_link_properties(interface, as_) xc_linkprops = ScionRouting._get_xc_link_properties(interface, as_) @@ -351,15 +426,23 @@ def _provision_staticInfo_config(node: Node, as_: ScionAutonomousSystem): # Add Latency + if not staticInfo["Latency"] or str(interface) not in staticInfo["Latency"]: # if no latencies have been added yet empty dict + staticInfo["Latency"][str(interface)] = {} + if lat != 0: # if latency is not 0, add it - if not staticInfo["Latency"]: # if no latencies have been added yet empty dict - staticInfo["Latency"][str(interface)] = {} - staticInfo["Latency"][str(interface)]["Inter"] = str(lat)+"ms" + us = round(lat * 1000 ) + staticInfo["Latency"][str(interface)]["Inter"] = str(us)+"us" + for _if in ifs["Latency"]: # add intra latency - if ifs["Latency"][_if] != "0ms": # omit 0ms latency - if not staticInfo["Latency"][str(interface)]["Intra"]: # if no intra latencies have been added yet empty dict + # don't omit 0ms latency, otherwise it won't be included in PCBs + if "Intra" not in staticInfo["Latency"][str(interface)]: # if no intra latencies have been added yet empty dict staticInfo["Latency"][str(interface)]["Intra"] = {} - staticInfo["Latency"][str(interface)]["Intra"][str(_if)] = ifs["Latency"][_if] + + dur = ifs["Latency"][_if] + if '.' not in dur: + staticInfo["Latency"][str(interface)]["Intra"][str(_if)] = dur + else: + raise ValueError("scion distributables can't parse floating point durations: pkg/private/util/duration.go")