diff --git a/doc/dev/run.rst b/doc/dev/run.rst index bb7b3f605a..9501106ab6 100644 --- a/doc/dev/run.rst +++ b/doc/dev/run.rst @@ -182,6 +182,11 @@ The basic usage is ``./scion.sh ``. The main subcommands a Display help text, list all options + .. option:: -s, --seed + + Generate a seed emulation from the topology file. + For more Information check out the :ref:`generating-seed-emulation` section. + .. option:: run, start Start the local SCION topology. @@ -209,6 +214,62 @@ The basic usage is ``./scion.sh ``. The main subcommands a Describe all available subcommands +.. _generating-seed-emulation: + +generating seed emulation +========================= + +To generate a seed emulation from a topology file, you need this version of the +`seed-emulator `_ installed. + +The basic usage is ``./scion.sh topology -s -c /path/to/topology/file``. + +This will generate the following files in the gen directory: + +1. A seed emulation file called ``scion-seed.py``. This file contains the seed emulation + +2. A seed emulation dump file called ``scion-seed.bin``. + This is a binary dump of the seed emulation. + You will need this for example if you want to use the + `seed traffic generator `_. + +3. A folder called ``seed-compiled``. This folder contains the compiled seed emulation. + This folder is what you get when you run the ``scion-seed.py`` file. + Executing ``docker compose up --build`` in this folder will start the seed emulation. + +.. Note:: + seed currently only supports IPv4 therefore the -s flag is not available if the + topology file contains IPv6 underlay networks. + +.. option:: -xcn, --xconnect-network + + This flag allows you tho specify the subnet that the + seed cross connect networks will be created in. + The default is `10.3.0.0/16`. + +.. option:: -asn, --as-network + + This flag allows you tho specify the subnet that the + seed AS internal networks will be created in. + The default is `10.4.0.0/16`. + +.. option:: --features + + .. option:: SeedInternetMapDisable + + This will disable the internet map feature of the seed emulation. + + .. option:: SeedCompilerGraphviz + + This will enable the graphviz compiler of the seed compiler. + Instead of a docker compose file the ``gen/seed-compiled`` folder will contain a + graphviz file which can be used to visualize the seed emulation. + + .. option:: SeedSkipIPv6Check + + This will skip the IPv6 check and treat topology files with IPv6 underlay networks + as if the underlay networks were IPv4. + end2end_integration =================== @@ -222,3 +283,4 @@ The basic usage is ``./end2end_integration ``. Assume the SCION services are dockerized. Must be consistent with the last invocation of ``scion.sh topology``. + diff --git a/tools/topogen.py b/tools/topogen.py index 33bbb68c03..e3059fc536 100755 --- a/tools/topogen.py +++ b/tools/topogen.py @@ -37,7 +37,11 @@ def add_arguments(parser): parser.add_argument('-d', '--docker', action='store_true', help='Create a docker compose configuration') parser.add_argument('-n', '--network', - help='Network to create subnets in (E.g. "127.0.0.0/8"') + help='Network to create subnets in (E.g. "127.0.0.0/8")') + parser.add_argument("-xcn", "--xconnect-network", + help='/16 Network to create seed cross-connects subnets in (E.g. "10.3.0.0/16")') + parser.add_argument('-asn', "--as-network", + help='/16 Network to create seed AS subnets in (E.g. "10.4.0.0/16")') parser.add_argument('-o', '--output-dir', default=GEN_PATH, help='Output directory') parser.add_argument('--random-ifids', action='store_true', @@ -49,8 +53,11 @@ def add_arguments(parser): parser.add_argument('--sig', action='store_true', help='Generate a SIG per AS (only available with -d, the SIG image needs\ to be built manually e.g. when running acceptance tests)') + parser.add_argument('-s','--seed',action='store_true',help='Generate a seed file for the topology') parser.add_argument('--features', help='Feature flags to enable, a comma separated list\ - e.g. foo,bar enables foo and bar feature.') + e.g. foo,bar enables foo and bar feature.\n \ + Available features: SeedInternetMapDisable, SeedCompilerGraphviz, SeedSkipIPv6Check ') + return parser diff --git a/tools/topology/config.py b/tools/topology/config.py index fffff91be5..742f8273ef 100644 --- a/tools/topology/config.py +++ b/tools/topology/config.py @@ -38,6 +38,7 @@ from topology.cert import CertGenArgs, CertGenerator from topology.common import ArgsBase from topology.docker import DockerGenArgs, DockerGenerator +from topology.seed import SeedGenArgs, SeedGenerator from topology.go import GoGenArgs, GoGenerator from topology.net import ( NetworkDescription, @@ -110,6 +111,8 @@ def _ensure_uniq_ases(self): def _generate_with_topo(self, topo_dicts): self._generate_go(topo_dicts) + if self.args.seed: + self._generate_seed(topo_dicts) if self.args.docker: self._generate_docker(topo_dicts) else: @@ -160,6 +163,14 @@ def _generate_docker(self, topo_dicts): def _docker_args(self, topo_dicts): return DockerGenArgs(self.args, topo_dicts, self.all_networks) + def _generate_seed(self, topo_dicts): + args = self._seed_args(topo_dicts) + seed_gen = SeedGenerator(args) + seed_gen.generate() + + def _seed_args(self, topo_dicts): + return SeedGenArgs(self.args, topo_dicts, self.all_networks) + def _generate_monitoring_conf(self, topo_dicts): args = self._monitoring_args(topo_dicts) mon_gen = MonitoringGenerator(args) diff --git a/tools/topology/seed.py b/tools/topology/seed.py new file mode 100644 index 0000000000..b5c1664937 --- /dev/null +++ b/tools/topology/seed.py @@ -0,0 +1,621 @@ +# Copyright 2024 ETH Zürich, Lorin Urbantat +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Mapping, Set, Tuple, Optional, Dict, List, Union +import os +import subprocess +from ipaddress import IPv4Network + +from topology.util import write_file +from topology.common import ( + ArgsTopoDicts +) +from topology.net import NetworkDescription, IPNetwork +import yaml + +SEED_CONF = "scion-seed.py" + + +# class to manage seed arguments +class SeedGenArgs(ArgsTopoDicts): + def __init__(self, args, topo_dicts, + networks: Mapping[IPNetwork, NetworkDescription]): + """ + :param object args: Contains the passed command line arguments as named attributes. + :param dict topo_dicts: The generated topo dicts from TopoGenerator. + :param dict networks: The generated network descriptions from NetworkGenerator. + """ + super().__init__(args, topo_dicts) + self.networks = networks + print("SeedGenArgs\n") + print(args) + + +# copyright +# @lschulz -- seed-emulator/examples/scion/S05-scion-internet/scion-internet.py +# class to generate IP addresses for cross connect links + + +class ASNetworkAssigner: + def __init__(self, parentNet): + assert "/16" in parentNet, "Parent network must be a /16 network" + self.parentNet = ".".join(parentNet.split(".")[0:2]) + + def get_net_by_as(self, asn): + return f"{self.parentNet}.{asn}.0/24" + + +class CrossConnectNetAssigner: + def __init__(self, parentNet): + self.subnet_iter = IPv4Network(parentNet).subnets(new_prefix=29) + self.xc_nets = {} + + def next_addr(self, net): + if net not in self.xc_nets: + hosts = next(self.subnet_iter).hosts() + next(hosts) # Skip first IP (reserved for Docker) + self.xc_nets[net] = hosts + return "{}/29".format(next(self.xc_nets[net])) + + +class SeedGenerator(SeedGenArgs): + # define class variables + # dictionary containing the topo file parsed as yaml + _topo_file: \ + Dict[str, + Union[Dict[str, Dict[str, Union[bool, int, str]]], List[Dict[str, Union[str, int]]]]] + _args: SeedGenArgs + _out_file: str + _links: List[Dict[str, Union[Tuple[str, str, str], str, int]]] # list of parsed links + _br: Dict[str, List[str]] + _internetMapEnabled: bool = True + _SeedCompiler: str = "Docker" + _skipIPv6Check: bool = False + _parentNetwork: str = "10.3.0.0/16" + _as_network: str = "10.4.0.0/16" + # dict containing mapping from ISD_AS to list of border router properties + _brProperties: Dict[str, Dict[str, Dict]] + + def __init__(self, args): + """ + :param SeedGenArgs args: Contains the passed command line arguments and topo dicts. + + Generates a seed file for the SCION topology. + """ + self._args = args + + self._parseFeatures() + + with open(args.topo_config, "r") as f: + self._topo_file = yaml.load(f, Loader=yaml.SafeLoader) + + def _parseFeatures(self): + """ + parse cli feature flags and set class flags + """ + if "SeedInternetMapDisable" in self._args.features: + self._internetMapEnabled = False + if "SeedCompilerGraphviz" in self._args.features: + self._SeedCompiler = "Graphviz" + if "SeedSkipIPv6Check" in self._args.features: + self._skipIPv6Check = True + if self._args.xconnect_network: + self._parentNetwork = self._args.xconnect_network + if self._args.as_network: + self._as_network = self._args.as_network + + def generate(self): + """ + generate function called by ./config.py to generate seed file + """ + + # Seed does not support IPv6 thus throw error if IPv6 is used + if not self._skipIPv6Check: + self._check_IPv6() + + # write header of seed file + self._out_file = """\ +from seedemu.compiler import Docker, Graphviz +from seedemu.core import Emulator +from seedemu.layers import ScionBase, ScionRouting, ScionIsd, Scion, Ospf +from seedemu.layers.Scion import LinkType as ScLinkType + +# Initialize +emu = Emulator() +base = ScionBase() +routing = ScionRouting() +scion_isd = ScionIsd() +scion = Scion() +ospf = Ospf() + +""" + + # build appropriate link data structure + self._links = self._parse_links() + + self._brProperties = self._parse_borderRouterProperties() + + self._parse_borderRouter_interfaces() + + self._generate_addresses() + + self._out_file += self._create_ISD() + + self._out_file += self._create_AS() + + self._out_file += self._create_Routing() + + self._out_file += f""" +# Rendering +emu.addLayer(base) +emu.addLayer(routing) +emu.addLayer(scion_isd) +emu.addLayer(scion) +emu.addLayer(ospf) + +# dump seed emulation to file before rendering +emu.dump("{self._args.output_dir}/{SEED_CONF.replace('.py', '.bin')}") + + +emu.render() + +# Compilation +emu.compile({self._SeedCompiler}{f'(internetMapEnabled={self._internetMapEnabled},' + f'internetMapClientImage="bruol0/seedemu-client")' + if self._SeedCompiler == "Docker" else "()"}, \ +'./{self._args.output_dir}/seed-compiled') +""" + # write seed file + write_file(os.path.join(self._args.output_dir, SEED_CONF), self._out_file) + # generate simulation from seed file + print("\n\nRunning Seed Generation\n\n") + subprocess.run(["python", self._args.output_dir + "/" + SEED_CONF]) + + def _isd_Set(self) -> Set[str]: + """ + Generate a set of ISDs from the topo file + """ + isds = set() + for As in self._topo_file["ASes"]: + # get Id of every ISD + isd = As.split('-')[0] + isds.add(isd) + return isds + + def _create_ISD(self) -> str: + """ + Generate code for creating ISDs + """ + code = "# Create ISDs\n" + # get set of ISDs + isds = self._isd_Set() + # write code for each ISD + for isd in isds: + code += f"base.createIsolationDomain({isd})\n" + + code += "\n\n" + return code + + def _check_IPv6(self): + """ + Check if any network is IPv6 + """ + for network in self._args.networks: + if network._version == 6: + raise Exception(( + "Seed does not support IPv6. Please use IPv4 only. " + "If you want to try anyway use the feature flag SeedSkipIPv6Check.")) + + def _parse_AS_properties(self, As: str)\ + -> Tuple[str, str, bool, Optional[str], int, int, int, Optional[int], Optional[str]]: + """ + Read AS properties from topo file + """ + as_num = As.split(':')[2] + isd_num = As.split('-')[0] + + # handle optional properties + as_dict = self._topo_file["ASes"][As] + + is_core = as_dict['core'] if 'core' in as_dict else False + cert_issuer = as_dict['cert_issuer'].split(':')[2] if 'cert_issuer' in as_dict else None + as_int_bw = as_dict['bw'] if 'bw' in as_dict else 0 + as_int_lat = as_dict['latency'] if 'latency' in as_dict else 0 + as_int_drop = as_dict['drop'] if 'drop' in as_dict else 0 + as_int_mtu = as_dict['mtu'] if 'mtu' in as_dict else None + as_note = as_dict['note'] if 'note' in as_dict else None + + res = (as_num, + isd_num, + is_core, + cert_issuer, + as_int_bw, + as_int_lat, + as_int_drop, + as_int_mtu, + as_note) + + return res + + def _read_link_properties(self, link: Dict[str, Union[str, Optional[int], int]]) \ + -> Tuple[str, str, str, Optional[int], int, int, int]: + """ + Read link properties from topo file + """ + a = link['a'] + b = link['b'] + link_type = self._convert_link_type(link['linkAtoB']) + # read optional properties + if "mtu" in link: + mtu = link['mtu'] + else: + mtu = None + + if "bw" in link: + bandwidth = link['bw'] + else: + bandwidth = 0 # seed ignores value 0 + + if "latency" in link: + latency = link['latency'] + else: + latency = 0 # seed ignores value 0 + + if "drop" in link: + drop = link['drop'] + else: + drop = 0 # seed ignores value 0 + + return a, b, link_type, mtu, bandwidth, latency, drop + + def _convert_link_type(self, link_type: str) -> str: + """ + Convert link type from topo file to seed format + """ + if link_type == "CHILD": + return "ScLinkType.Transit" + elif link_type == "PEER": + return "ScLinkType.Peer" + elif link_type == "CORE": + return "ScLinkType.Core" + else: + raise Exception(f"Link type {link_type} not supported by Seed") + + def _parse_link_party(self, party: str) -> Tuple[str, str, str]: + """ + Parse link party from topo file + """ + isd_num = party.split('-')[0] + as_num = party.split(':')[2] + if "-" in as_num: + br_if = as_num.split('-')[1] + as_num = as_num.split('-')[0] + else: + br_if = as_num.split('#')[1] + as_num = as_num.split('#')[0] + return isd_num, as_num, br_if + + def _parse_links(self) -> List[Dict[str, Union[Tuple[str, str, str], str, int]]]: + """ + Parse links from topo file + """ + links = [] + for link in self._topo_file["links"]: + (a, b, link_type, mtu, bandwidth, latency, drop) = self._read_link_properties(link) + (a_isd, a_as, a_br_if) = self._parse_link_party(a) + (b_isd, b_as, b_br_if) = self._parse_link_party(b) + + data = { + "a": (a_isd, a_as, a_br_if), + "b": (b_isd, b_as, b_br_if), + "link_type": link_type, + "mtu": mtu, + "bandwidth": bandwidth, + "latency": latency, + "drop": drop + } + + links.append(data) + return links + + def _parse_interface(self, br_if, i, ia, a_b): + """ + :param br_if: bridge interface identifier (format A#1 or 1) + :param i: link index + :param ia: ISD_AS identifier + + Parse bridge interface and update bridges data structure + """ + # create set of bridges for per AS + if "#" in br_if: + br_id = br_if.split('#')[0] + # add bridge to list if not already in list + if (br_id not in self._br[ia]): + self._br[ia].append(br_id) + else: + # if bridge does not have an ID add new ID by prepending 'A' + last_id = "" if not self._br[ia] else self._br[ia][-1] + new_id = "A"+last_id + self._br[ia].append(new_id) + # also update link and brProperties data structure with new ID + isd = ia.split('_')[0] + as_num = ia.split('_')[1] + self._links[i][a_b] = (isd, as_num, new_id+"#"+br_if) + if ia in self._brProperties: + if br_if in self._brProperties[ia]: + self._brProperties[ia][new_id+"#"+br_if] = self._brProperties[ia][br_if] + del self._brProperties[ia][br_if] + + def _parse_borderRouterProperties(self) -> Dict[str, Dict[str, Dict]]: + """ + parse BorderRouter properties from topo file + """ + + brProperties = {} + + if "borderRouterProperties" not in self._topo_file: + return brProperties + + for br in self._topo_file["borderRouterProperties"]: + (isd, as_num, br_if) = self._parse_link_party(br) + if f"{isd}_{as_num}" not in brProperties: + brProperties[f"{isd}_{as_num}"] = {} + brProperties[f"{isd}_{as_num}"][br_if] = self._topo_file["borderRouterProperties"][br] + + return brProperties + + def _parse_borderRouter_interfaces(self): + """ + generate bridge_names from links + """ + + self._br = {} + + # initialize borderRouter datastructure + # by creating an empty list of BorderRouters for each AS + for As in self._topo_file["ASes"]: + isd_num = As.split('-')[0] + as_num = As.split(':')[2] + ia = f"{isd_num}_{as_num}" + self._br[ia] = [] + + # parse interfaces for each link + for i in range(0, len(self._links)): + link = self._links[i] + + a_isd, a_as, a_br = link['a'] + b_isd, b_as, b_br = link['b'] + + a_ia = f"{a_isd}_{a_as}" + b_ia = f"{b_isd}_{b_as}" + + self._parse_interface(a_br, i, a_ia, "a") + self._parse_interface(b_br, i, b_ia, "b") + + # generate border router names + for ia in self._br: + br_names = [] + i = 1 + for br in self._br[ia]: + br_names.append({br: f"br{i}"}) + i += 1 + self._br[ia] = br_names + + # replace border router interface names with border router names in links + for i in range(0, len(self._links)): + link = self._links[i] + + a_isd, a_as, a_br = link['a'] + b_isd, b_as, b_br = link['b'] + + a_br_id = a_br.split('#')[0] + b_br_id = b_br.split('#')[0] + + for br in self._br[f"{a_isd}_{a_as}"]: + if a_br_id in br: + a_br = br[a_br_id] + break + + for br in self._br[f"{b_isd}_{b_as}"]: + if b_br_id in br: + b_br = br[b_br_id] + break + + self._links[i]['a'] = (a_isd, a_as, a_br) + self._links[i]['b'] = (b_isd, b_as, b_br) + + # replace border router interface names with border router names in brProperties + + new_br_properties = {} + + for ia in self._brProperties: + new_br_properties[ia] = {} + for br_if in self._brProperties[ia]: + br_id = br_if.split('#')[0] + for br in self._br[ia]: + if br_id in br: + br_name = br[br_id] + new_br_properties[ia][br_name] = self._brProperties[ia][br_if] + + self._brProperties = new_br_properties + + def _generate_addresses(self): + """ + Generate IP addresses for cross connect links + """ + self._xc_nets = CrossConnectNetAssigner(self._parentNetwork) + + self.as_nets = ASNetworkAssigner(self._as_network) + + for i in range(0, len(self._links)): + link = self._links[i] + + a = link['a'] + b = link['b'] + + a_addr = self._xc_nets.next_addr((a, b)) + b_addr = self._xc_nets.next_addr((a, b)) + + self._links[i]['a_addr'] = a_addr + self._links[i]['b_addr'] = b_addr + + def _create_AS(self) -> str: + """ + Generate code for creating ASes + """ + + code = "# Ases \n" + + AS_template = """\ +# AS-{as_num} +as{as_num} = base.createAutonomousSystem({as_num}) +{set_note} +scion_isd.addIsdAs({isd_num},{as_num},is_core={is_core}) +{cert_issuer} +{set_link_properties} +as{as_num}.createControlService('cs_1').joinNetwork('net0') +{border_routers} + +""" + + for As in self._topo_file["ASes"]: + + (as_num, + isd_num, + is_core, + cert_issuer, + as_int_bw, + as_int_lat, + as_int_drop, + as_int_mtu, + as_note) = self._parse_AS_properties(As) + + set_note = f"as{as_num}.setNote('{as_note}')" if as_note else "" + + if cert_issuer: + cert_issuer = f"scion_isd.setCertIssuer(({isd_num},{as_num}),issuer={cert_issuer})" + else: + cert_issuer = "" + + if as_int_mtu: # default value 0 for latency, bandwidth, packetDrop will be ignored + set_link_properties = (f"as{as_num}.createNetwork('net0', " + f"prefix=\"{self.as_nets.get_net_by_as(as_num)}\")" + f".setDefaultLinkProperties(" + f"latency={as_int_lat}," + f"bandwidth={as_int_bw}," + f"packetDrop={as_int_drop}).setMtu({as_int_mtu})\n") + else: + set_link_properties = (f"as{as_num}.createNetwork('net0', " + f"prefix=\"{self.as_nets.get_net_by_as(as_num)}\")" + f".setDefaultLinkProperties(" + f"latency={as_int_lat}, " + f"bandwidth={as_int_bw}, " + f"packetDrop={as_int_drop})\n") + + border_routers = "" + + # iterate through border routers + for br in self._br[f"{isd_num}_{as_num}"]: + br_name = next(iter(br.values())) + border_routers += (f"as_{as_num}_{br_name} = as{as_num}" + f".createRouter('{br_name}')" + f".joinNetwork('net0')\n") + + # set border router properties + if f"{isd_num}_{as_num}" in self._brProperties \ + and br_name in self._brProperties[f"{isd_num}_{as_num}"]: + br_props = self._brProperties[f"{isd_num}_{as_num}"][br_name] + + if "geo" in br_props: + lat = br_props['geo']['latitude'] + lon = br_props['geo']['longitude'] + addr = br_props['geo']['address'] + border_routers += (f"as_{as_num}_{br_name}" + f".setGeo(Lat={lat}, " + f"Long={lon}, Address=\"\"\"{addr}\"\"\")\n") + if "note" in br_props: + border_routers += f"as_{as_num}_{br_name}.setNote('{br_props['note']}')\n" + + # create crosslinks for each border router + for link in self._links: + # check if link is connected to this AS + if link['a'][0] == isd_num and link['a'][1] == as_num: + # check if link is connected to this border router + if link['a'][2] == br_name: + b_br = link['b'][2] + b_as = link['b'][1] + a_addr = link['a_addr'] + # get link properties + latency = link['latency'] + bandwidth = link['bandwidth'] + packetDrop = link['drop'] + mtu = link['mtu'] + # generate code + if mtu: + border_routers += (f"as_{as_num}_{br_name}" + f".crossConnect({b_as},'{b_br}','{a_addr}'," + f"latency={latency},bandwidth={bandwidth}," + f"packetDrop={packetDrop},MTU={mtu})\n") + else: + border_routers += (f"as_{as_num}_{br_name}" + f".crossConnect({b_as},'{b_br}','{a_addr}'," + f"latency={latency},bandwidth={bandwidth}," + f"packetDrop={packetDrop})\n") + + if link['b'][0] == isd_num and link['b'][1] == as_num: + if link['b'][2] == br_name: + a_br = link['a'][2] + a_as = link['a'][1] + b_addr = link['b_addr'] + latency = link['latency'] + bandwidth = link['bandwidth'] + packetDrop = link['drop'] + mtu = link['mtu'] + if mtu: + border_routers += (f"as_{as_num}_{br_name}" + f".crossConnect({a_as},'{a_br}','{b_addr}'," + f"latency={latency},bandwidth={bandwidth}," + f"packetDrop={packetDrop},MTU={mtu})\n") + else: + border_routers += (f"as_{as_num}_{br_name}" + f".crossConnect({a_as},'{a_br}','{b_addr}'," + f"latency={latency},bandwidth={bandwidth}," + f"packetDrop={packetDrop})\n") + + code += AS_template.format(as_num=as_num, + isd_num=isd_num, + is_core=is_core, + cert_issuer=cert_issuer, + set_note=set_note, + set_link_properties=set_link_properties, + border_routers=border_routers).replace("\n\n", "\n") + + return code + + def _create_Routing(self) -> str: + """ + Generate code for creating routing + """ + code = "\n\n# Inter-AS routing\n" + + for link in self._links: + a = link['a'] + b = link['b'] + link_type = link['link_type'] + a_router = link['a'][2] + b_router = link['b'][2] + + code += (f"scion.addXcLink(({a[0]},{a[1]}),({b[0]},{b[1]})," + f"{link_type},a_router='{a_router}',b_router='{b_router}')\n") + return code diff --git a/tools/topology/topo.py b/tools/topology/topo.py index 9fb39e7ce0..dc7cbc190f 100644 --- a/tools/topology/topo.py +++ b/tools/topology/topo.py @@ -240,8 +240,10 @@ def _generate_as_topo(self, topo_id, as_conf): for attr in ['core']: if as_conf.get(attr, False): attributes.append(attr) + cert_issuer = as_conf.get('cert_issuer', None) self.topo_dicts[topo_id] = { 'attributes': attributes, + 'cert_issuer': cert_issuer, 'isd_as': str(topo_id), 'mtu': mtu, # XXX(JordiSubira): This key is used internally later on, to decide diff --git a/topology/README.md b/topology/README.md index 13c5662945..d25dd21164 100644 --- a/topology/README.md +++ b/topology/README.md @@ -2,10 +2,37 @@ Brief description of sections in .topo files +## Table of Contents + +- [ASes Section](#ases-section) +- [Links Section](#links-section) +- [borderRouterProperties Section](#border-router-properties-section) +- [Examples](#examples) + +## ASes Section + The 'ASes' section describes all the ASes in a topology. You can specify different attributes like Core, MTU, certificate issuer and number of services among other things. +**Supported attributes:** + +- "core" -- boolean, whether the AS is a core AS +- "voting" -- boolean +- "authoritative" -- boolean +- "issuing" -- boolean, whether the AS is an issuing AS +- "underlay" -- default is UDP/IPv4, can be set to UDP/IPv6, seed does not support IPv6 underlay +- "cert_issuer" -- string, the issuer IA of the CA. This attribute is necessary if AS is not core +- "MTU" -- integer, the internal MTU of the AS * +- "latency" -- integer, the internal latency in ms of the AS used by seed emulator +- "bw" -- integer, the internal bandwidth in bit/s of the AS used by seed emulator +- "drop" -- float, the internal drop rate (% in range(0.0,1.0)) of the AS used by seed emulator +- "note" -- string, a note for the AS seed emulator will include this in the beacons + +Fields marked with * are used by the seed emulator for setting link properties. + +## Links Section + The 'links' section describes the links between the BRs of different ASes. When defining the links in .topo files, we can specify whether the new interface @@ -29,3 +56,93 @@ In the example above, two links are defined resulting in: - BR 1-ff00:0:110 with a single interface - BR 1-ff00:0:120 with multiple interfaces - BR 1-ff00:0:130 with a single interface + +**Supported attributes:** + +- "a" -- string, mandatory, see above +- "b" -- string, mandatory, see above +- "linkAtoB" -- string, mandatory, the type of link, can be CORE, PEER, CHILD +- "mtu" -- integer, the MTU of the link * +- "underlay" -- default is UDP/IPv4, can be set to UDP/IPv6, seed doesn't support IPv6 +- "bw" -- integer, the bandwidth in bit/s of the link * +- "latency" -- integer, the latency in ms of the link * +- "drop" -- float, the drop rate (% in range(0.0,1.0)) of the link * + +Fields marked with * are used by the seed emulator for setting link properties. + +## Border Router Properties Section + +The **optional** 'borderRouterProperties' section describes properties of BRs such as Geolocation. +Entries in the 'borderRouterProperties' section are optional. +This means not every BR defined in the links section must appear in the +'borderRouterProperties' section. + +The same string identifiers as in the link section specify the key for a border router. +Though watch out as one border router can have several +SCION interfaces but there can only be one property section for each border router. + +Consider the following example from the *default.topo* file for clarification. +In the 'links' section these 6 scion interfaces were specified: + +```yaml +"1-ff00:0:120-A#6" +"1-ff00:0:120-A#1" +"1-ff00:0:120-B#2" +"1-ff00:0:120-B#3" +"1-ff00:0:120-B#4" +"1-ff00:0:120#5" +``` + +Notice though how the 6 scion interfaces are connected to only 3 BorderRouters. +Now in the 'borderRouterProperties' section we can specify properties for each one +of the three BorderRouters like this: + +```yaml +"1-ff00:0:120#5": + geo: + latitude: 48.858222 + longitude: 2.2945 + address: "Eiffel Tower\n7th arrondissement\nParis\nFrance" + note: "This is an arbitrary string" +"1-ff00:0:120-A#1": + geo: + latitude: 48.858222 + longitude: 2.2945 + address: "Eiffel Tower\n7th arrondissement\nParis\nFrance" + note: "This is an arbitrary string" +"1-ff00:0:120-B#2": + geo: + latitude: 48.858222 + longitude: 2.2945 + address: "Eiffel Tower\n7th arrondissement\nParis\nFrance" + note: "This is an arbitrary string" +``` + +Notice that instead of *"1-ff00:0:120-B#2"* +we could have also specified any other interface attached +to the same BorderRouter like *"1-ff00:0:120-B#3"* + +**Supported attributes:** + +- "geo" -- the geolocation of the Border Router. +geo has three arguments latitude, longitude and address. +This will be added to the staticInfoConfig.json by the seed emulator if set +- "note" -- a string that can contain any string. +This will be added as a note to the Border Router Node by the seed emulator + +## Examples + +This is a list of examples: + +- [tiny.topo](tiny.topo): A simple topology with 3 ASes and 2 links. +- [tiny4.topo](tiny4.topo): same topology as tiny.topo but using IPv4. +- [wide.topo](wide.topo) +- [default.topo](default.topo) +- [default-no-peers.topo](default-no-peers.topo) +- [peering-test.topo](peering-test.topo): example with one peering link +- [peering-test-multi.topo](peering-test-multi.topo): example with multiple peering links +- [tiny_borderRouterProperties.topo](tiny_borderRouterProperties.topo): +tiny.topo example file with an example of 'borderRouterProperties' Section +- [tiny4_link_properties.topo](tiny4_link_properties.topo): +tiny4.topo example file with an example of how to specify link properties for intra AS network +and inter AS links diff --git a/topology/tiny4_link_properties.topo b/topology/tiny4_link_properties.topo new file mode 100644 index 0000000000..95d0fbe614 --- /dev/null +++ b/topology/tiny4_link_properties.topo @@ -0,0 +1,19 @@ +--- # Tiny Topology with link properties, IPv4 Only +ASes: + "1-ff00:0:110": + core: true + voting: true + authoritative: true + issuing: true + mtu: 1400 + latency: 10 + bw: 1000 + drop: 0.1 + note: "This is a core AS" + "1-ff00:0:111": + cert_issuer: 1-ff00:0:110 + "1-ff00:0:112": + cert_issuer: 1-ff00:0:110 +links: + - {a: "1-ff00:0:110#1", b: "1-ff00:0:111#41", linkAtoB: CHILD, mtu: 1280} + - {a: "1-ff00:0:110#2", b: "1-ff00:0:112#1", linkAtoB: CHILD, bw: 500, latency: 30, drop: 0.1, mtu: 1100} diff --git a/topology/tiny_borderRouterProperties.topo b/topology/tiny_borderRouterProperties.topo new file mode 100644 index 0000000000..27915e2662 --- /dev/null +++ b/topology/tiny_borderRouterProperties.topo @@ -0,0 +1,41 @@ +--- # Tiny Topology with border router properties +ASes: + "1-ff00:0:110": + core: true + voting: true + authoritative: true + issuing: true + mtu: 1400 + "1-ff00:0:111": + cert_issuer: 1-ff00:0:110 + "1-ff00:0:112": + cert_issuer: 1-ff00:0:110 + underlay: UDP/IPv6 +links: + - {a: "1-ff00:0:110#1", b: "1-ff00:0:111#41", linkAtoB: CHILD, mtu: 1280} + - {a: "1-ff00:0:110#2", b: "1-ff00:0:112#1", linkAtoB: CHILD, bw: 500, underlay: UDP/IPv6} +borderRouterProperties: + "1-ff00:0:110#1": + geo: + latitude: 48.858222 + longitude: 2.2945 + address: "Eiffel Tower\n7th arrondissement\nParis\nFrance" + note: "Hello World" + "1-ff00:0:110#2": + geo: + latitude: 48.858222 + longitude: 2.2945 + address: "Eiffel Tower\n7th arrondissement\nParis\nFrance" + note: "Hello World" + "1-ff00:0:111#41": + geo: + latitude: 48.858222 + longitude: 2.2945 + address: "Eiffel Tower\n7th arrondissement\nParis\nFrance" + note: "Hello World" + "1-ff00:0:112#1": + geo: + latitude: 48.858222 + longitude: 2.2945 + address: "Eiffel Tower\n7th arrondissement\nParis\nFrance" + note: "Hello World" \ No newline at end of file