├── fibbingnode ├── misc │ ├── __init__.py │ ├── mininetlib │ │ ├── README.md │ │ ├── iptopo.py │ │ ├── __init__.py │ │ ├── cli.py │ │ ├── fibbingcontroller.py │ │ └── iprouter.py │ ├── utils.py │ ├── router.py │ ├── sjmp.py │ └── igp_graph.py ├── algorithms │ ├── __init__.py │ ├── cross_optimizer.py │ ├── ospf_simple.py │ ├── southbound_interface.py │ └── utils.py ├── southbound │ ├── __init__.py │ ├── __main__.py │ ├── lsdb │ │ ├── __init__.py │ │ └── lsa.py │ ├── namespaces.py │ ├── interface.py │ ├── link.py │ ├── main.py │ └── entities.py ├── __main__.py ├── res │ ├── templates │ │ ├── zebra.mako │ │ └── ospf.mako │ └── default.cfg └── __init__.py ├── setup.cfg ├── MANIFEST.in ├── .gitmodules ├── .gitignore ├── topologies ├── internet2.ntf └── weights-dist │ ├── 3967 │ ├── latencies.intra │ ├── weights.intra │ ├── 3967_pops_continent_inter_abr_pop.entf │ ├── 3967_pops_continent_inter_abr_backbone.entf │ └── 3967_pops_city_inter_abr_backbone.entf │ └── README.WEIGHTS-DIST ├── tests ├── test_pyaddress.py ├── test_simple.py ├── test_private_address_store.py ├── manual │ ├── sjmptest.py │ └── shapeshifterproxytest.py └── test_crossopt.py ├── setup.py ├── install.sh └── README.md /fibbingnode/misc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fibbingnode/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fibbingnode/southbound/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include fibbingnode/res * 2 | recursive-include fibbingnode/res/templates * 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Quagga"] 2 | path = Quagga 3 | url = https://github.com/Fibbing/Quagga.git 4 | -------------------------------------------------------------------------------- /fibbingnode/__main__.py: -------------------------------------------------------------------------------- 1 | import southbound.main as _m 2 | 3 | 4 | if __name__ == '__main__': 5 | _m.main() 6 | -------------------------------------------------------------------------------- /fibbingnode/southbound/__main__.py: -------------------------------------------------------------------------------- 1 | # Bootstrap to the main module 2 | if __name__ == '__main__': 3 | from main import main 4 | main() -------------------------------------------------------------------------------- /fibbingnode/southbound/lsdb/__init__.py: -------------------------------------------------------------------------------- 1 | from .lsdb import LSDB, PrivateAddressStore 2 | 3 | 4 | __all__ = [LSDB, PrivateAddressStore] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | *.iml 4 | build 5 | dist 6 | *.egg-info 7 | dynamips_* 8 | ilt_* 9 | *_rom 10 | *_rommon_vars 11 | *_slot* 12 | *_ssa 13 | *_lock 14 | *_log.txt 15 | *.ghost 16 | *.swp 17 | *.toc 18 | *.snm 19 | *.out 20 | *.nav 21 | lab_comment.pdf 22 | *.log 23 | *.fls 24 | *fdb_latexmk 25 | *.aux 26 | *.egg 27 | .cache 28 | .eggs 29 | tags 30 | -------------------------------------------------------------------------------- /topologies/internet2.ntf: -------------------------------------------------------------------------------- 1 | SEAT LOSA 1342 2 | LOSA SEAT 1342 3 | SEAT SALT 913 4 | SALT SEAT 913 5 | LOSA SALT 1303 6 | SALT LOSA 1303 7 | LOSA HOUS 1705 8 | HOUS LOSA 1705 9 | SALT KANS 1330 10 | KANS SALT 1330 11 | KANS CHIC 690 12 | CHIC KANS 690 13 | KANS HOUS 818 14 | HOUS KANS 818 15 | CHIC NEWY 1000 16 | NEWY CHIC 1000 17 | CHIC WASH 905 18 | WASH CHIC 905 19 | NEWY WASH 277 20 | WASH NEWY 277 21 | WASH ATLA 700 22 | ATLA WASH 700 23 | CHIC ATLA 1045 24 | ATLA CHIC 1045 25 | ATLA HOUS 1385 26 | HOUS ATLA 1385 27 | -------------------------------------------------------------------------------- /topologies/weights-dist/README.WEIGHTS-DIST: -------------------------------------------------------------------------------- 1 | 2 | 'tar zxvf weights-dist.tar.gz' yields this README.WEIGHTS-DIST file 3 | and 6 subdirectories corresponding to the six ISPs studied in the 4 | paper. Each subdirectory contains two files -- weights.intra and 5 | latencies.intra. Each file contains the backbone topology represented 6 | as a list of edges separated by newlines. The third field in each line 7 | is the corresponding metric -- inferred weight or latency -- of the 8 | edge. The latency estimated is computed by rounding up the geographic 9 | distance to the nearest millisecond. 10 | -------------------------------------------------------------------------------- /fibbingnode/misc/mininetlib/README.md: -------------------------------------------------------------------------------- 1 | # Fibbing Mininet extensions 2 | 3 | These are [Mininet](www.mininet.org) classes, and allow you to instantiate 4 | network with autoconfigured OSPF routers, as well as place Fibbing controllers 5 | around. 6 | 7 | ## Usage 8 | Using these classes requires a working mininet installation 9 | 10 | To install mininet: 11 | 12 | ```bash 13 | pip install git+git://github.com/mininet/mininet.git 14 | ``` 15 | 16 | Or visit www.mininet.org 17 | 18 | You can then use these classes by simply performing the following import 19 | in your topology files: 20 | ```python 21 | import fibbingnode.misc.mininetlib 22 | ``` 23 | 24 | You can find example topologies in 25 | [Fibbing/labs](https://github.com/Fibbing/labs) 26 | -------------------------------------------------------------------------------- /fibbingnode/res/templates/zebra.mako: -------------------------------------------------------------------------------- 1 | hostname ${node.hostname} 2 | password ${node.password} 3 | % if node.zebra.logfile: 4 | log file ${node.zebra.logfile} 5 | % endif 6 | % for section in node.zebra.debug: 7 | debug zebra section 8 | % endfor 9 | % for pl in node.zebra.prefixlists: 10 | ip prefix-list ${pl.name} ${pl.action} ${pl.prefix} ge ${pl.ge} 11 | % endfor 12 | ! 13 | % for rm in node.zebra.routemaps: 14 | route-map ${rm.name} ${rm.action} ${rm.prio} 15 | % for prefix in rm.prefix: 16 | match ip address prefix-list ${prefix} 17 | % endfor 18 | ! 19 | % for proto in rm.proto: 20 | ip protocol ${proto} route-map ${rm.name} 21 | % endfor 22 | % endfor 23 | ! 24 | % for prefix, via in node.zebra.static_routes: 25 | ip route ${prefix} via ${via} 26 | % endfor 27 | -------------------------------------------------------------------------------- /tests/test_pyaddress.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import ipaddress 3 | 4 | 5 | @pytest.mark.parametrize('address', [ 6 | '::/0', '0.0.0.0/0', '1.2.3.0/24', '2001:db8:1234::/48' 7 | ]) 8 | def test_nested_ip_networks(address): 9 | """This test ensures that we can build an IPvXNetwork from another one. 10 | If this breaks, need to grep through for ip_network calls as I removed the 11 | checks when instantiating these ... 12 | Test passing with py2-ipaddress (3.4.1)""" 13 | _N = ipaddress.ip_network 14 | n1 = _N(address) # Build an IPvXNetwork 15 | n2 = _N(n1) # Build a new one from the previous one 16 | assert (n1 == n2 and 17 | n1.with_prefixlen == address and 18 | n2.with_prefixlen == address and 19 | n1.max_prefixlen == n2.max_prefixlen) 20 | -------------------------------------------------------------------------------- /tests/test_simple.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import test_merger 3 | import fibbingnode.algorithms.ospf_simple as smpl 4 | 5 | 6 | class TestSimple(test_merger.MergerTestCase): 7 | def __init__(self, *args, **kwargs): 8 | super(TestSimple, self).__init__(*args, **kwargs) 9 | self.solver_provider = smpl.OSPFSimple 10 | 11 | def _test(self, igp_topo, fwd_dags, expected_lsa_count): 12 | solver = self.solver_provider() 13 | lsas = solver.solve(igp_topo, fwd_dags) 14 | test_merger.log.debug('solved reqs with LSAs: %s', lsas) 15 | self.assertTrue(test_merger.check_fwd_dags(fwd_dags, 16 | igp_topo, 17 | lsas, 18 | solver)) 19 | 20 | if __name__ == '__main__': 21 | unittest.main() 22 | -------------------------------------------------------------------------------- /fibbingnode/algorithms/cross_optimizer.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import fibbingnode 3 | import utils as _u 4 | 5 | 6 | log = fibbingnode.log 7 | 8 | 9 | class CrossOptimizer(object): 10 | def __init__(self, solver): 11 | self.solver = solver 12 | 13 | def solve(self, graph, requirements): 14 | lsas = self.solver.solve(graph, requirements) 15 | grouped_lsas = collections.defaultdict(list) 16 | # Group the LSAs by fakenodes 17 | for lsa in lsas: 18 | grouped_lsas[(lsa.node, lsa.nh)].append((lsa.cost, lsa.dest)) 19 | # Build and aggregate LSA from these groups 20 | reduced_lsas = [_u.ExtendedLSA(node, nh, 21 | [_u.ExtLSARoute(dest=d, cost=c) 22 | for c, d in dests]) 23 | for (node, nh), dests in grouped_lsas.iteritems()] 24 | log.info('CrossOptimizer reduced the LSA count to %s (from %s)', 25 | len(reduced_lsas), len(lsas)) 26 | log.debug(reduced_lsas) 27 | return reduced_lsas 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | "Setuptools params" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | VERSION = '0.1a' 8 | 9 | modname = distname = 'fibbingnode' 10 | 11 | setup( 12 | name=distname, 13 | version=VERSION, 14 | description='The set of scripts to manage a fibbing node', 15 | author='Olivier Tilmans', 16 | author_email='olivier.tilmans@uclouvain.be', 17 | packages=find_packages(), 18 | include_package_data = True, 19 | classifiers=[ 20 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", 21 | "Programming Language :: Python", 22 | "Development Status :: 2 - Pre-Alpha", 23 | "Intended Audience :: Developers", 24 | "Topic :: System :: Networking", 25 | ], 26 | keywords='networking OSPF fibbing', 27 | license='GPLv2', 28 | install_requires=[ 29 | 'setuptools', 30 | 'mako', 31 | 'networkx==1.11', 32 | 'py2-ipaddress' 33 | ], 34 | extras_require={ 35 | 'draw': ['matplotlib'], 36 | }, 37 | tests_require=['pytest'], 38 | setup_requires=['pytest-runner'] 39 | ) 40 | -------------------------------------------------------------------------------- /fibbingnode/misc/mininetlib/iptopo.py: -------------------------------------------------------------------------------- 1 | from mininet.topo import Topo 2 | 3 | 4 | class IPTopo(Topo): 5 | def __init__(self, *args, **kwargs): 6 | super(IPTopo, self).__init__(*args, **kwargs) 7 | 8 | def addController(self, name, **kwargs): 9 | return self.addNode(name, isController=True, **kwargs) 10 | 11 | def __isNodeType(self, n, x): 12 | try: 13 | return self.g.node[n].get(x, False) 14 | except KeyError: 15 | return False 16 | 17 | def isController(self, n): 18 | return self.__isNodeType(n, 'isController') 19 | 20 | def addRouter(self, name, **kwargs): 21 | return self.addNode(name, isRouter=True, **kwargs) 22 | 23 | def isRouter(self, n): 24 | return self.__isNodeType(n, 'isRouter') 25 | 26 | def hosts(self, sort=True): 27 | return [h for h in super(IPTopo, self).hosts(sort) 28 | if not (self.isRouter(h) or self.isController(h))] 29 | 30 | def routers(self, sort=True): 31 | return [r for r in self.nodes(sort) if self.isRouter(r)] 32 | 33 | def controllers(self, sort=True): 34 | return [c for c in self.nodes(sort) if self.isController(c)] 35 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $EUID -ne 0 ]]; then 4 | echo "This script might need root privileges!" 1>&2 5 | fi 6 | 7 | CWD=`dirname $0` 8 | SCRIPT=$(readlink -f $0) 9 | DIR=`dirname $SCRIPT` 10 | BIN=`(awk -F "=" '/quagga_path=/ { print $NF }' $DIR/fibbingnode/res/default.cfg)` 11 | 12 | quagga() { 13 | git submodule init || exit 1 14 | git submodule update || exit 1 15 | 16 | if ! getent group quagga ; then 17 | echo "Creating group quagga" 18 | groupadd quagga 19 | fi 20 | if ! getent passwd quagga ; then 21 | echo "Creating user quagga" 22 | useradd -g quagga quagga 23 | fi 24 | if ! id -nG quagga | grep -qw quagga ; then 25 | echo "Adding user quagga to the group quagga" 26 | usermod -a -G quagga quagga 27 | fi 28 | mkdir -p ${BIN} 29 | chown ${USER} ${BIN} 30 | QUAGGA=${DIR}/Quagga 31 | 32 | cd ${QUAGGA} 33 | # For some reasons (e.g. on Debian) autoreconf fails to copy ltmain.sh after the first run ... 34 | autoreconf -vfi 35 | # But succeeds after the second one. 36 | autoreconf -vfi 37 | ./configure --prefix=${BIN} --enable-multipath=0 --enable-vtysh --enable-withdraw 38 | make -j 4 39 | make install 40 | cd ${CWD} 41 | } 42 | 43 | fibbing() { 44 | cd ${DIR} 45 | pip install -e ".[draw, tests]" 46 | cd ${CWD} 47 | } 48 | 49 | quagga && fibbing 50 | -------------------------------------------------------------------------------- /fibbingnode/res/templates/ospf.mako: -------------------------------------------------------------------------------- 1 | hostname ${node.hostname} 2 | password ${node.password} 3 | % if node.ospf.logfile: 4 | log file ${node.ospf.logfile} 5 | % endif 6 | % for section in node.ospf.debug: 7 | debug ospf section 8 | % endfor 9 | ! 10 | % for intf in node.ospf.interfaces: 11 | interface ${intf.name} 12 | # ${intf.description} 13 | # Highiest priority routers will be DR 14 | ip ospf priority ${intf.ospf.priority} 15 | ip ospf cost ${intf.ospf.cost} 16 | # dead/hello intervals must be consistent across a broadcast domain 17 | ip ospf dead-interval ${intf.ospf.dead_int} 18 | ip ospf hello-interval ${intf.ospf.hello_int} 19 | ! 20 | % endfor 21 | router ospf 22 | router-id ${node.ospf.router_id} 23 | % if node.ospf.redistribute.fibbing: 24 | redistribute fibbing metric-type 1 25 | % endif 26 | % if node.ospf.redistribute.connected: 27 | redistribute connected metric-type 1 metric ${node.ospf.redistribute.connected} 28 | % endif 29 | % if node.ospf.redistribute.static: 30 | redistribute static metric-type 1 metric ${node.ospf.redistribute.static} 31 | % endif 32 | % for net in node.ospf.networks: 33 | network ${net.domain} area ${net.area} 34 | % endfor 35 | % for itf in node.ospf.passive_interfaces: 36 | passive-interface ${itf.name} 37 | % endfor 38 | % if node.ospf.throttling and node.ospf.lsa: 39 | timers throttle spf ${node.ospf.throttling.spf.delay} ${node.ospf.throttling.spf.initial_holdtime} ${node.ospf.throttling.spf.max_holdtime} 40 | timers throttle lsa all ${node.ospf.throttling.lsa_all.min_ls_interval} 41 | timers lsa arrival ${node.ospf.lsa.min_ls_arrival} 42 | % endif 43 | ! 44 | -------------------------------------------------------------------------------- /tests/test_private_address_store.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import pytest 4 | 5 | from fibbingnode.southbound.lsdb import PrivateAddressStore 6 | 7 | 8 | SIMPLE_TESTFILE = os.path.join(os.path.dirname(__file__), 9 | 'private_ipaddresses.json') 10 | 11 | 12 | @pytest.fixture(scope="function") 13 | def simple_address_file(request): 14 | d = {"10.127.255.252/30": 15 | {"192.168.239.254": ["10.127.255.254/30"], 16 | "192.168.251.254": ["10.127.255.253/30"]}, 17 | "10.191.255.252/30": 18 | {"192.168.251.253": ["10.191.255.253/30"], 19 | "192.168.251.254": ["10.191.255.254/30"]}, 20 | "10.223.255.252/30": 21 | {"192.168.239.254": ["10.223.255.254/30"], 22 | "192.168.255.253": ["10.223.255.253/30"]}, 23 | "10.255.255.252/30": 24 | {"192.168.251.253": ["10.255.255.254/30"], 25 | "192.168.255.253": ["10.255.255.253/30"]}} 26 | with open(SIMPLE_TESTFILE, 'w') as f: 27 | json.dump(d, f) 28 | 29 | def __teardown(): 30 | os.unlink(SIMPLE_TESTFILE) 31 | request.addfinalizer(__teardown) 32 | 33 | return PrivateAddressStore(SIMPLE_TESTFILE), d 34 | 35 | 36 | def same_lists(x, y): 37 | return len(x) == len(y) and sorted(y) == sorted(x) 38 | 39 | 40 | def test_simple(simple_address_file): 41 | store, d = simple_address_file 42 | # sample queries 43 | assert same_lists(store.targets_for('10.223.255.253/30'), 44 | ['192.168.239.254']) 45 | assert same_lists(store.addresses_of('192.168.239.254'), 46 | ['10.127.255.254/30', '10.223.255.254/30']) 47 | -------------------------------------------------------------------------------- /fibbingnode/misc/mininetlib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | As this module has an hard dependency against mininet and on the availability 3 | of some commands, perform the import at the top-level in order to make the 4 | relevant checks once, at import time. 5 | Furthermore, all these classes will be import anyway at some point when 6 | instantiating a Fibbing lab ... 7 | """ 8 | try: 9 | import mininet # noqa 10 | except ImportError as e: 11 | from fibbingnode import log 12 | import sys 13 | log.error('Failed to import mininet!') 14 | log.error('Using the mininetlib module requires mininet to be ' 15 | 'installed.\n' 16 | 'Visit www.mininet.org to learn how to do so.\n') 17 | sys.exit(1) 18 | 19 | 20 | PRIVATE_IP_KEY = '__fibbing_private_ips' 21 | CFG_KEY = '__fibbing_controller_config_key' 22 | BDOMAIN_KEY = '__fibbing_broadcast_domains' 23 | FIBBING_MIN_COST = 2 24 | FIBBING_DEFAULT_AREA = '0.0.0.0' 25 | DEBUG_FLAG = False 26 | 27 | 28 | def get_logger(): 29 | import mininet.log as l 30 | l.setLogLevel('info') 31 | return l.lg 32 | 33 | 34 | def otherIntf(intf): 35 | """"Get the interface on the other of a link""" 36 | l = intf.link 37 | return (l.intf1 if l.intf2 == intf else l.intf2) if l else None 38 | 39 | 40 | class L3Router(object): 41 | """Dumb class to enable for easy detection of node types through 42 | isinstance""" 43 | @staticmethod 44 | def is_l3router_intf(itf): 45 | """Returns whether an interface belongs to an L3Router 46 | (in the Mininet meaning: an intf with an associated node)""" 47 | return isinstance(itf.node, L3Router) 48 | 49 | 50 | def routers_in_bd(bd, cls=None): 51 | return list(filter(L3Router.is_l3router_intf if not cls else 52 | cls.is_l3router_intf, 53 | bd)) 54 | -------------------------------------------------------------------------------- /fibbingnode/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import ConfigParser 4 | import threading 5 | 6 | EXIT = threading.Event() 7 | 8 | # Path to the templates directory 9 | RES = os.path.join(os.path.dirname(__file__), 'res') 10 | TEMPLATES = os.path.join(RES, 'templates') 11 | 12 | 13 | def get_template_path(name): 14 | return os.path.realpath(os.path.join(TEMPLATES, name)) 15 | 16 | CFG = ConfigParser.ConfigParser() 17 | with open(os.path.join(RES, 'default.cfg'), 'r') as f: 18 | CFG.readfp(f) 19 | 20 | # Path to the directory containing the Quagga-Fibbing installation 21 | BIN = CFG.get(ConfigParser.DEFAULTSECT, 'quagga_path') 22 | 23 | # Warnings are orange 24 | logging.addLevelName(logging.WARNING, "\033[1;43m%s\033[1;0m" % 25 | logging.getLevelName(logging.WARNING)) 26 | # Errors are red 27 | logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % 28 | logging.getLevelName(logging.ERROR)) 29 | # Debug is green 30 | logging.addLevelName(logging.DEBUG, "\033[1;42m%s\033[1;0m" % 31 | logging.getLevelName(logging.DEBUG)) 32 | # Information messages are blue 33 | logging.addLevelName(logging.INFO, "\033[1;44m%s\033[1;0m" % 34 | logging.getLevelName(logging.INFO)) 35 | # Critical messages are violet 36 | logging.addLevelName(logging.CRITICAL, "\033[1;45m%s\033[1;0m" % 37 | logging.getLevelName(logging.CRITICAL)) 38 | 39 | log = logging.getLogger(__name__) 40 | fmt = logging.Formatter('%(asctime)s [%(levelname)20s] %(funcName)s: %(message)s') 41 | handler = logging.StreamHandler() 42 | handler.setFormatter(fmt) 43 | log.addHandler(handler) 44 | 45 | 46 | def log_to_file(filename, mode='a'): 47 | import datetime 48 | handler = logging.FileHandler(filename, mode) 49 | handler.setFormatter(fmt) 50 | log.addHandler(handler) 51 | now = datetime.datetime.now() 52 | log.info('==== Session start: %s', now.isoformat()) 53 | -------------------------------------------------------------------------------- /fibbingnode/misc/mininetlib/cli.py: -------------------------------------------------------------------------------- 1 | from ipaddress import ip_address 2 | from mininet.cli import CLI 3 | 4 | 5 | class FibbingCLI(CLI): 6 | def do_route(self, line=""): 7 | """route destination: Print all the routes towards that destination 8 | for every router in the network""" 9 | for r in self.mn.routers: 10 | self.default('%s ip route get %s' % (r.name, line)) 11 | 12 | def do_ip(self, line): 13 | """ip IP: return the node associated to IP""" 14 | try: 15 | print self.mn.node_for_ip(line) 16 | except KeyError: 17 | print 'No matching for for ip', line 18 | 19 | def do_traceroute(self, line): 20 | """traceroute SRC DEST: show the traceroute from SRC towards DEST 21 | SRC is a node name 22 | DEST is a node or an IP address""" 23 | try: 24 | src, dst = line.split(' ') 25 | s = self.mn[src] 26 | try: 27 | d = self.mn[dst].defaultIntf().ip 28 | except KeyError: 29 | d = ip_address(dst) 30 | result = s.cmd('traceroute', '-4n', '-w1', '-q1', str(d)) 31 | ips = ['%s (%s)' % (self.mn.ip_allocs.get(ip, 'Unknown'), ip) 32 | for ip in _parse_traceroute(result)] 33 | print '*** %s to %s (%s):\n%s' % (src, dst, d, '\n'.join(ips)) 34 | except (ValueError, KeyError) as e: 35 | print 'Missing argument(s): SRC DST [error was: %s]' % str(e) 36 | 37 | 38 | def _parse_traceroute(res): 39 | """Return an iterator over all hops in a traceroute result string""" 40 | for line in res.splitlines()[1:]: 41 | _, rest = _part_strip_split(line, ' ') 42 | hop, _ = _part_strip_split(rest, ' ') 43 | yield hop 44 | 45 | 46 | def _part_strip_split(line, sep): 47 | """Return the two members resulting from stripping both ends of the line 48 | from blank chars and then splitting at the first occurance of the 49 | separator""" 50 | return line.strip(' \r\n\t').split(sep, 1) 51 | -------------------------------------------------------------------------------- /tests/manual/sjmptest.py: -------------------------------------------------------------------------------- 1 | from cmd import Cmd 2 | import logging 3 | import time 4 | from threading import Thread 5 | from fibbingnode import log 6 | from fibbingnode.misc.sjmp import SJMPClient, SJMPServer, ProxyCloner 7 | 8 | H = 'localhost' 9 | P = 12345 10 | 11 | 12 | class EchoProxy(object): 13 | def echo(self, str): 14 | return str 15 | 16 | def sum(self, a, b): 17 | """ 18 | Docstring for the sum method 19 | :param a: Ideally an integer ... 20 | :param b: Same than a 21 | :return: a + b, might just crash as well if given garbage 22 | """ 23 | return int(a) + int(b) 24 | 25 | def some_func(self, d): 26 | return 'some_func %s' % d 27 | 28 | 29 | class TestCLI(Cmd): 30 | Cmd.prompt = '> ' 31 | 32 | def __init__(self, client, *args, **kwargs): 33 | Cmd.__init__(self, *args, **kwargs) 34 | self.client = client 35 | 36 | def do_echo(self, line): 37 | self.client.execute('echo', line) 38 | 39 | def do_sum(self, line): 40 | a, b = line.split(' ') 41 | # Invoke a sum method on the remote end, with 2 parameters, 42 | # named and unnamed 43 | self.client.execute('sum', a, b=b) 44 | 45 | def do_exit(self, line): 46 | return True 47 | 48 | def do_info(self, line): 49 | # Query the remopte end for the supported methods/docs/args 50 | self.client.ask_info() 51 | 52 | def default(self, line): 53 | items = line.split(' ') 54 | self.client.execute(items[0], *items[1:]) 55 | 56 | if __name__ == '__main__': 57 | log.setLevel(logging.DEBUG) 58 | s = SJMPServer(H, P, target=EchoProxy()) 59 | c = SJMPClient(H, P) 60 | a = ProxyCloner(EchoProxy, c) 61 | log.debug(dir(a)) 62 | st = Thread(target=s.communicate, name='server') 63 | st.daemon = True 64 | st.start() 65 | log.debug('Started server') 66 | ct = Thread(target=c.communicate, name='client') 67 | ct.daemon = True 68 | ct.start() 69 | log.debug('Started client') 70 | a.echo('hello world') 71 | a.sum(1, 2) 72 | TestCLI(c).cmdloop() 73 | c.stop() 74 | s.stop() 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FibbingNode 2 | This repository contains the code to run a fibbing controller 3 | that is able to control unmodified OSPF router to setup arbitrary paths in the network. 4 | 5 | The controller is currently compatible only with python 2. 6 | 7 | The controller code is split into 3 main parts: 8 | 9 | 1. The [Quagga](https://github.com/Fibbing/Quagga) directory, that contains a modified version of quagga that is able to craft 10 | and flood arbitrary Type-5 LSA which are used to inject the lies in the network. 11 | 2. The Southbound controller, the [fibbingnode](https://github.com/Fibbing/FibbingNode/tree/master/fibbingnode) python module, that will control the quagga ospfd instances 12 | and trigger the injection/removal of these LSAs, as well as infer the current network topology 13 | and decide whether the current instance of the controller is the 'master' one in case multiple controllers are 14 | present in the network. A critical file to tune is the config file, whose defaults are specified in [fibbingnode/res/default.cfg](https://github.com/Fibbing/FibbingNode/blob/master/fibbingnode/res/default.cfg).It can then be run via 15 | ```bash 16 | python2 -m fibbingnode 17 | ``` 18 | 3. The Northbound controller, [fibbingnode/algorithms](https://github.com/Fibbing/FibbingNode/tree/master/fibbingnode/algorithms) 19 | which implements the algorithms to compute the augmented topology and then communicates to the 20 | southern part through a json insterface. 21 | 22 | # Basic installation 23 | 24 | ```bash 25 | git clone --recursive https://github.com/Fibbing/FibbingNode.git 26 | ./install.sh 27 | ``` 28 | 29 | This will install the quagga distribution under /opt/fibbing/ and the fibbingnode python module 30 | 31 | # Demo 32 | 33 | [Sample labs are available in another repository](https://github.com/Fibbing/labs) 34 | 35 | # Virtual-Machine 36 | 37 | [Script to build a Virtual Box VM able to run the controller, make it interact with physical routers, or run mininet-based experiments is available in another repository](https://github.com/Fibbing/virtual-machine) 38 | 39 | # Documentation 40 | 41 | There is an ongoing work to document the inner-workings of the controller, its architecture, ... while not yet public, feel free to contact [@oliviertilmans](https://github.com/oliviertilmans) if you have questions. 42 | -------------------------------------------------------------------------------- /tests/manual/shapeshifterproxytest.py: -------------------------------------------------------------------------------- 1 | from ConfigParser import DEFAULTSECT 2 | from cmd import Cmd 3 | import logging 4 | from threading import Thread 5 | from fibbingnode import CFG, log 6 | from fibbingnode.misc.sjmp import SJMPClient, ProxyCloner 7 | from networkx import DiGraph 8 | from fibbingnode.southbound.interface import ShapeshifterProxy, FakeNodeProxy 9 | 10 | 11 | class ShapeshifterProxyTest(ShapeshifterProxy): 12 | def __init__(self): 13 | self.graph = DiGraph() 14 | 15 | def add_edge(self, source, destination, metric): 16 | log.info('Adding %s-%s @ %s', source, destination, metric) 17 | self.graph.add_edge(source, destination, cost=metric) 18 | 19 | def remove_edge(self, source, destination): 20 | log.info('Removing %s-%s', source, destination) 21 | self.graph.remove_edge(source, destination) 22 | 23 | def boostrap_graph(self, graph): 24 | log.info('Received graph: %s', graph) 25 | for u, v, m in graph: 26 | self.graph.add_edge(u, v, cost=m) 27 | 28 | 29 | class TestCLI(Cmd): 30 | Cmd.prompt = '> ' 31 | 32 | def __init__(self, client, *args, **kwargs): 33 | Cmd.__init__(self, *args, **kwargs) 34 | self.client = client 35 | 36 | def do_add(self, line=''): 37 | self.client.add(('192.168.14.1', '192.168.23.2', 1, '3.3.3.0/24')) 38 | self.client.add((None, '192.168.23.2', 1, '4.4.4.0/24')) 39 | self.client.add([(None, '192.168.23.2', 1, '5.5.5.0/24'), 40 | (None, '192.168.14.1', 1, '5.5.5.0/24')]) 41 | 42 | def do_remove(self, line=''): 43 | self.client.remove(('192.168.14.1', '192.168.23.2', '3.3.3.0/24')) 44 | self.client.remove((None, '192.168.23.2', '4.4.4.0/24')) 45 | self.client.remove([(None, '192.168.23.2', '5.5.5.0/24'), 46 | (None, '192.168.14.1', '5.5.5.0/24')]) 47 | 48 | def do_exit(self, line): 49 | return True 50 | 51 | if __name__ == '__main__': 52 | log.setLevel(logging.DEBUG) 53 | shapeshifter = ShapeshifterProxyTest() 54 | c = SJMPClient("localhost", 55 | CFG.getint(DEFAULTSECT, "json_port"), 56 | target=shapeshifter) 57 | fakenode = ProxyCloner(FakeNodeProxy, c) 58 | Thread(target=c.communicate, name='client').start() 59 | TestCLI(fakenode).cmdloop() 60 | c.stop() 61 | -------------------------------------------------------------------------------- /fibbingnode/southbound/namespaces.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import inspect 3 | import os 4 | from time import sleep 5 | from fibbingnode import log 6 | from fibbingnode.misc.utils import need_root 7 | 8 | NSDIR = '/var/run/netns' 9 | 10 | # Cannot play with net namespaces if we're not root 11 | need_root() 12 | 13 | 14 | def _netns(*args, **kwargs): 15 | cmd = ['ip', 'netns'] 16 | cmd.extend(args) 17 | log.debug(str(cmd)) 18 | return subprocess.call(cmd, **kwargs) 19 | 20 | 21 | class NetworkNamespace(object): 22 | ID = -1 23 | 24 | def __init__(self): 25 | NetworkNamespace.ID += 1 26 | self.id = NetworkNamespace.ID 27 | self.name = 'ns%d' % self.id 28 | self.create_ns() 29 | 30 | def create_ns(self): 31 | if os.path.exists(NSDIR) and ' %s ' % self.name in os.listdir(NSDIR): 32 | self.delete() 33 | err = _netns('add', self.name) 34 | if err != 0: 35 | log.error('Failed to create namespace %s', self.name) 36 | else: 37 | log.debug('Created namespace %s', self.name) 38 | 39 | def call(self, *args, **kwargs): 40 | return _netns('exec', self.name, *args, **kwargs) 41 | 42 | def pipe(self, *args, **kwargs): 43 | cmd = ['ip', 'netns', 'exec', self.name] 44 | cmd.extend(args) 45 | log.debug(str(cmd)) 46 | if 'stdin' not in kwargs: 47 | kwargs['stdin'] = subprocess.PIPE 48 | if 'stdout' not in kwargs: 49 | kwargs['stdout'] = subprocess.PIPE 50 | if 'stderr' not in kwargs: 51 | kwargs['stderr'] = subprocess.STDOUT 52 | return subprocess.Popen(cmd, **kwargs) 53 | 54 | def capture_port(self, port): 55 | log.debug('Moving port %s into namespace %s', port, self.name) 56 | return subprocess.call( 57 | ['ip', 'link', 'set', port.id, 'netns', self.name]) 58 | 59 | def delete(self): 60 | sleep(.2) 61 | log.debug('Removing namespace %s', self.name) 62 | _netns('delete', self.name) 63 | 64 | 65 | class RootNamespace(object): 66 | def __init__(self): 67 | self.name = 'root' 68 | # This namespace does nothing special 69 | for attr, _ in inspect.getmembers(NetworkNamespace, 70 | predicate=inspect.ismethod): 71 | setattr(self, attr, self._proxy) 72 | # Except the standard calls ... 73 | setattr(self, 'call', self._call) 74 | setattr(self, 'pipe', self._pipe) 75 | 76 | def _call(self, *args, **kwargs): 77 | log.debug('%s NS call data: %s // %s', self.name, args, kwargs) 78 | return subprocess.call(args, *kwargs) 79 | 80 | def _pipe(self, *args, **kwargs): 81 | if 'stdin' not in kwargs: 82 | kwargs['stdin'] = subprocess.PIPE 83 | if 'stdout' not in kwargs: 84 | kwargs['stdout'] = subprocess.PIPE 85 | if 'stderr' not in kwargs: 86 | kwargs['stderr'] = subprocess.STDOUT 87 | return subprocess.Popen(args, **kwargs) 88 | 89 | def _proxy(*args, **kwargs): 90 | pass 91 | -------------------------------------------------------------------------------- /fibbingnode/misc/mininetlib/fibbingcontroller.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import os 3 | import subprocess 4 | 5 | import ConfigParser as cparser 6 | 7 | import mininet.node as _node 8 | 9 | from fibbingnode.misc.mininetlib import get_logger, CFG_KEY, otherIntf,\ 10 | L3Router 11 | from fibbingnode.misc.utils import del_file, force 12 | 13 | 14 | log = get_logger() 15 | 16 | 17 | class FibbingController(_node.Host, L3Router): 18 | 19 | instance_count = 0 20 | 21 | def __init__(self, name, cfg_path=None, quiet=False, *args, **kwargs): 22 | super(FibbingController, self).__init__(name, *args, **kwargs) 23 | self.config_params = kwargs.get(CFG_KEY, {}) 24 | self.socket_path = "/tmp/%s.socket" % self.name 25 | self.cfg_path = "%s.cfg" % self.name if not cfg_path else cfg_path 26 | self.instance_number = FibbingController.instance_count 27 | self.quiet = quiet 28 | FibbingController.instance_count += 1 29 | 30 | def start(self): 31 | self.cmd('ip', 'link', 'set', 'dev', 'lo', 'up') 32 | itfs = self.dump_cfg_info() 33 | log.info('Starting southbound controller for ', self.name, '\n') 34 | args = ['python', '-m', 'fibbingnode', # '--nocli', 35 | '--cfg', self.cfg_path] 36 | args.extend(itfs) 37 | serr = sout = (None if not self.quiet else open(os.devnull, 'wb')) 38 | self.process = self.popen(args, 39 | stdin=subprocess.PIPE, 40 | stderr=serr, 41 | stdout=sout) 42 | 43 | def stop(self, *args, **kwargs): 44 | def _timeout(sig, frame): 45 | if not self.process.returncode: 46 | raise Exception 47 | 48 | # TODO figure out why calling process.send_signal(signal.SIGINT) 49 | # was not working properly ... (in conjunction with --nocli) 50 | signal.signal(signal.SIGALRM, _timeout) 51 | signal.alarm(2) 52 | force(self.process.communicate, 'exit\n') 53 | signal.alarm(0) 54 | super(FibbingController, self).stop(*args, **kwargs) 55 | 56 | def terminate(self, *args, **kwargs): 57 | force(self.process.terminate) 58 | del_file(self.socket_path) 59 | super(FibbingController, self).terminate(*args, **kwargs) 60 | 61 | def dump_cfg_info(self): 62 | cfg = cparser.ConfigParser() 63 | for key, val in self.config_params.iteritems(): 64 | cfg.set(cparser.DEFAULTSECT, key, val) 65 | cfg.set(cparser.DEFAULTSECT, 66 | 'json_hostname', 'unix://%s' % self.socket_path) 67 | cfg.set(cparser.DEFAULTSECT, 68 | 'controller_instance_number', 69 | self.instance_number) 70 | connected_intfs = [itf 71 | for itf in self.intfList() 72 | if L3Router.is_l3router_intf(otherIntf(itf)) and 73 | itf.name != 'lo'] 74 | for itf in connected_intfs: 75 | cfg.add_section(itf.name) 76 | n = otherIntf(itf).node 77 | cfg.set(itf.name, 'hello_interval', n.hello_interval) 78 | cfg.set(itf.name, 'dead_interval', n.dead_interval) 79 | with open(self.cfg_path, 'w') as f: 80 | cfg.write(f) 81 | return (itf.name for itf in connected_intfs) 82 | -------------------------------------------------------------------------------- /fibbingnode/res/default.cfg: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | ## Default OSPF values for the interfaces 3 | # ospf cost/metric 4 | cost=1 5 | # ospf dead interval timer 6 | dead_interval=40 7 | # ospf hello interval timer 8 | hello_interval=5 9 | # ospf area 10 | area=0.0.0.0 11 | # How long should the fake node be valid for if the controller dies? 12 | fake_lsa_ttl=300 13 | 14 | # LSA spf throttling (ms): Throttles SPF recomputation 15 | delay=0 16 | initial_holdtime=1 17 | max_holdtime=1 18 | 19 | # Configure architectural constants (msec) 20 | # LSA delay between transmissions 21 | min_ls_interval=100 22 | 23 | # Delay between sending LSAs 24 | min_ls_arrival=500 25 | 26 | 27 | ## Misc params 28 | # Path to the prefix of the fibbing-capable Quagga installation 29 | # Should be the same than the one passed as --prefix when running the 30 | # Quagga configure script. 31 | quagga_path=/opt/fibbing 32 | # Initial number of 'standby routers' hidden behind the fibbing node. 33 | # More will be created automatically if needed. 34 | # These provision the router ids needed to have multiple fake nodes 35 | # for a single destination prefix. 36 | initial_node_count=5 37 | # Used to derive the virtual network/router-ids on the fake node 38 | base_net=192.168.0.0/16 39 | # the prefix len of base_net to treat as controller id 40 | controller_prefixlen=24 41 | # To infer the private 'link-local' IP addresses to use in forwarding addresses 42 | # TODO -- get rid of this and directly use the IPs from the private bindings? 43 | # -- might be too costly though. 44 | private_net=10.0.0.0/8 45 | # Initially thought to control debug setting, apparently unused ... 46 | debug=0 47 | # The port on which we listen for the northbound controller: 48 | # The one running the Simple/Merger/... algorithm and choosing the placement 49 | # and cost of the fake nodes. 50 | #@ Also used by the northbound controller to choose the destination port! 51 | json_port=12345 52 | # The hostname on which we listen for the northbound controller 53 | #@ Also used by the northbound controller to choose the destination hostname! 54 | # Can also use unix domain socket: 55 | # unix:///path/to/socket 56 | json_hostname= 57 | # How many northbound controller are we accepting at once 58 | #@ Nothing is currently made to ensure that having multiple northbound 59 | # controller on the same southbound controller is safe (route duplication, 60 | # order of updates, ...) 61 | json_max_master=1 62 | # Whether to draw the graph of the inferred topology or not, see lsdb.py 63 | draw_graph=1 64 | # Where do we store the drawn graph 65 | graph_loc=/var/run/network.pdf 66 | # The file linking the private ip addresses to the router/link/other router 67 | # as these are advertized as stubnet (hence we cannot infer their counterpart 68 | # unless we use strict 69 | private_ips=./private_ip_binding.json 70 | # The controller instance number 71 | controller_instance_number=0 72 | 73 | # Specific settings for the routers of the fake node 74 | [fake] 75 | # We want a relatively fast convergence for the internal routers 76 | dead_interval=5 77 | hello_interval=1 78 | 79 | # Default settings for the connection(s) to the physical router(s) 80 | [physical] 81 | cost=10000 82 | 83 | # Example physical interface entry if you need per-interface configuration 84 | # [eth0] 85 | # We can also also use the sub-second timers from quagga, see 86 | # http://www.nongnu.org/quagga/docs/docs-info.html#OSPFv2 87 | # dead_interval=minimal hello-multiplier 5 88 | # hello_interval=10 89 | # cost=10 90 | # area=0.0.0.0 91 | -------------------------------------------------------------------------------- /fibbingnode/southbound/interface.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base interfaces for the communication between the fibbing node itself and 3 | the algorithmic part. 4 | Each proxy class exposes the methods available to the other side 5 | of the socket, e.g. via a call to an SJMPClient instance execute method. 6 | See tests/sjmptest.py for examples. 7 | """ 8 | from abc import abstractmethod, ABCMeta 9 | import fibbingnode 10 | 11 | 12 | class FakeNodeProxy(object): 13 | """The interface that a southbound controller implements""" 14 | 15 | __metaclass__ = ABCMeta 16 | 17 | @abstractmethod 18 | def add(self, points): 19 | """ 20 | Add a fibbing route 21 | :param points: a list of 4-tuple (source, fwd, metric, prefix) 22 | * source: The link source from which the forwarding address is 23 | defined, can be None 24 | * fwd: The forwarding address to use, either the loopback of 25 | that node if source is null, or the address 26 | of the interface on that node of the link source--fwd 27 | * metric: the metric associated with the route. If the metric 28 | is < 0, the controller will install a locally 29 | visible lie using abs(metric) to choose which private 30 | IP to use. 31 | * prefix: the network prefix corresponding to this route 32 | source and fwd are OSPF router id. 33 | """ 34 | 35 | @abstractmethod 36 | def remove(self, points): 37 | """ 38 | Remove (parts of) a fibbing route 39 | :param points: a list of 3-tuple (source, fwd, prefix) 40 | * source: The link source from which the forwarding address is 41 | defined, can be None 42 | * fwd: The forwarding address to use, either the loopback of 43 | that node if source is null, or the address 44 | of the interface on that node of the link source--fwd 45 | * prefix: the network prefix corresponding to this route 46 | """ 47 | 48 | @staticmethod 49 | def exit(): 50 | """Kill the Southbound controller""" 51 | fibbingnode.EXIT.set() 52 | 53 | 54 | class ShapeshifterProxy(object): 55 | """The interface that a Northbound controller application must implement""" 56 | 57 | __metaclass__ = ABCMeta 58 | 59 | @abstractmethod 60 | def add_edge(self, source, destination, properties={'metric': 1}): 61 | """ 62 | Add a new directed edge to the network graph 63 | :param source: The source node for that edge 64 | (possibly a new node altogether) 65 | :param destination: The destination node for that edge 66 | :param properties: The properties of that edge, 67 | e.g. metric for SPT computations 68 | """ 69 | 70 | @abstractmethod 71 | def remove_edge(self, source, destination): 72 | """ 73 | Remove a directed edge from the network graph 74 | :param source: The source node of the edge 75 | :param destination: The destination of the edge 76 | """ 77 | 78 | @abstractmethod 79 | def update_node_properties(self, **properties): 80 | """ 81 | Update the properties of nodes in the graph 82 | :param properties: a set of key-values where the keys are the node 83 | names and the values their property set. 84 | """ 85 | 86 | @abstractmethod 87 | def commit(self): 88 | """Signals that all updates have been pushed and that no more 89 | add_edge/remove_edge calls will happen""" 90 | 91 | @abstractmethod 92 | def bootstrap_graph(self, graph, node_properties): 93 | """ 94 | Instantiate an initial graph 95 | :param graph: a list of edges for that graph (router-id and/or 96 | prefixes) + associated properties 97 | :param node_properties: a dict of node: properties 98 | """ 99 | -------------------------------------------------------------------------------- /fibbingnode/algorithms/ospf_simple.py: -------------------------------------------------------------------------------- 1 | import utils as ssu 2 | from fibbingnode import log 3 | from fibbingnode.misc.igp_graph import ShortestPath 4 | 5 | 6 | def get_edge_multiplicity(dag, node, req_nh): 7 | try: 8 | return int(dag.get_edge_multiplicity(node, req_nh)) 9 | except AttributeError: # Not an IGPGraph 10 | return 1 11 | 12 | 13 | def is_fake(g, u, v): 14 | try: 15 | return g.is_fake_route(u, v) 16 | except AttributeError: 17 | return False 18 | 19 | 20 | class OSPFSimple(object): 21 | def __init__(self): 22 | self.new_edge_metric = int(10e4) 23 | 24 | def get_fake_lsas(self): 25 | return self.fake_ospf_lsas 26 | 27 | def nhs_for(self, node, dest, dag): 28 | req_nhs = dag.successors(node) 29 | if not req_nhs: 30 | log.debug('%s does not need a path towards %s', node, dest) 31 | return [] 32 | # compute the originals next-hops of the current node 33 | try: 34 | original_nhs = [ 35 | p[1] for p in self.igp_paths.default_path(node, dest)] 36 | except KeyError: 37 | log.debug("%s had no NH towards %s", node, dest) 38 | original_nhs = [] 39 | max_multiplicity = max( 40 | map(lambda v: get_edge_multiplicity(dag, node, v), req_nhs)) 41 | if (not set(req_nhs).symmetric_difference(original_nhs) and 42 | max_multiplicity == 1): 43 | log.debug("Same NH sets and no multiplicity from %s to %s", 44 | node, dest) 45 | return [] 46 | log.debug('Max multiplicity: %d // ' 47 | 'NHs sets: original(%s) - required(%s)', 48 | max_multiplicity, original_nhs, req_nhs) 49 | return req_nhs 50 | 51 | def solve(self, topo, requirement_dags): 52 | # a list of tuples with info on the node to be attracted, 53 | # the forwarding address, the cost to be set in the fake LSA, 54 | # and the respective destinations 55 | self.fake_ospf_lsas = [] 56 | self.reqs = requirement_dags 57 | self.igp_graph = topo 58 | self.igp_paths = ShortestPath(self.igp_graph) 59 | # process input forwarding DAGs, one at the time 60 | for dest, dag in requirement_dags.iteritems(): 61 | log.info('Solving DAG for dest %s', dest) 62 | self.dest, self.dag = dest, dag 63 | log.debug('Checking dest in dag') 64 | ssu.add_dest_to_graph(dest, dag) 65 | log.debug('Checking dest in igp graph') 66 | ssu.add_dest_to_graph(dest, topo, 67 | edges_src=dag.predecessors, 68 | spt=self.igp_paths, 69 | metric=self.new_edge_metric) 70 | ssu.complete_dag(dag, topo, dest, self.igp_paths, 71 | skip=self.reqs.keys()) 72 | # Add temporarily the destination to the igp graph and/or req dags 73 | if not ssu.solvable(dag, topo): 74 | log.warning('Skipping requirement for dest: %s', dest) 75 | continue 76 | for node in dag: 77 | nhs = self.nhs_for(node, dest, dag) 78 | if not nhs: 79 | continue 80 | for req_nh in nhs: 81 | log.debug('Placing a fake node for %s->%s', node, req_nh) 82 | for i in xrange(get_edge_multiplicity(dag, node, req_nh)): 83 | self.fake_ospf_lsas.append(ssu.LSA(node=node, 84 | nh=req_nh, 85 | cost=(-1 - i), 86 | dest=dest)) 87 | # Check whether we need to include one more fake node to handle 88 | # the case where we create a new route from scratch. 89 | for p in dag.predecessors_iter(dest): 90 | if not is_fake(topo, p, dest): 91 | continue 92 | log.debug('%s is a terminal node towards %s but had no prior ' 93 | 'route to it! Adding a synthetic route', p, dest) 94 | self.fake_ospf_lsas.append( 95 | ssu.GlobalLie(dest, self.new_edge_metric, p)) 96 | return self.fake_ospf_lsas 97 | -------------------------------------------------------------------------------- /fibbingnode/southbound/link.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | from fibbingnode import log, CFG 4 | 5 | 6 | class Port(object): 7 | """ 8 | A port on a node and its associated properties 9 | """ 10 | def __init__(self, node, link, id=None, cost=CFG.get('fake', 'cost'), dead_int=CFG.get('fake', 'dead_interval'), 11 | hello_int=CFG.get('fake', 'hello_interval'), area=CFG.get('fake', 'area')): 12 | """ 13 | :param node: The node owning this port 14 | :param link: The link in which this port belongs 15 | :param id: The id of this port, otherwise infer it from the node next available port number 16 | :param cost: The OSPF cost of that interface 17 | :param dead_int: The OSPF dead interval for that interface 18 | :param hello_int: The OSPF Hello interval 19 | """ 20 | self.node = node 21 | self.link = link 22 | self.id = '%s-eth%s' % (node.id, node.get_next_port()) if not id else id 23 | self.ip_interface = None 24 | self.ospf_area = area 25 | self.ospf_cost = cost 26 | self.ospf_dead_int = dead_int 27 | self.ospf_hello_int = hello_int 28 | 29 | def move_in_namespace(self): 30 | """ 31 | Move this port into its node's namespace 32 | """ 33 | self.node.add_port(self) 34 | self.node.call('ip', 'link', 'set', self.id, 'up') 35 | 36 | def set_ip(self, ip): 37 | """ 38 | Set this port's IP address 39 | :param ip: an IPV4Address 40 | """ 41 | if self.ip_interface: 42 | # Remove the previous address if any 43 | self.del_ip(self.ip_interface) 44 | self.ip_interface = ip 45 | log.debug('Assigning %s to %s', ip, self.id) 46 | self.node.call('ip', 'addr', 'add', ip.with_prefixlen, 'dev', self.id) 47 | 48 | def del_ip(self, ip): 49 | """ 50 | Remove an IP address from this port 51 | :param ip: an IPV4Address 52 | """ 53 | log.debug('Removing %s from %s ip''s', ip, self.id) 54 | self.node.call('ip', 'addr', 'delete', ip.with_prefixlen) 55 | 56 | def __str__(self): 57 | return '%s%s' % ( 58 | self.id, '' if not self.ip_interface 59 | else ('@%s' % self.ip_interface.with_prefixlen)) 60 | 61 | def delete(self): 62 | self.node.del_port(self) 63 | 64 | 65 | class Link(object): 66 | """ 67 | A Link between two nodes, virtual. 68 | """ 69 | def __init__(self, src, dst): 70 | """ 71 | :param src: source Node 72 | :param dst: destination Node 73 | """ 74 | # Create the port 75 | self.src = Port(src, self) 76 | self.dst = Port(dst, self) 77 | # Use their newly-made id to create the link 78 | self.create_link() 79 | # Move them into their node's namespace 80 | self.src.move_in_namespace() 81 | self.dst.move_in_namespace() 82 | 83 | def create_link(self): 84 | """ 85 | Create a veth link between the source and the destination ports 86 | """ 87 | cmd = ['ip', 'link', 'add', self.src.id, 'type', 'veth', 'peer', 'name', self.dst.id] 88 | log.debug('Creating link: %s', cmd) 89 | err = subprocess.call(cmd) 90 | if err != 0: 91 | log.error('Failed to create veth link: %s', cmd) 92 | sys.exit(1) 93 | 94 | def delete(self): 95 | """ 96 | Delete this link and its associate ports 97 | """ 98 | self.src.delete() 99 | self.dst.delete() 100 | # veth links are deleted as soon as one of their port is deleted 101 | subprocess.call(['ip', 'link', 'del', self.src.id]) 102 | 103 | def __str__(self): 104 | return 'Link %s--%s' % (self.src, self.dst) 105 | 106 | 107 | class PhysicalLink(object): 108 | """ 109 | A link to 'the outside world'. Has a single port associated to it 110 | """ 111 | def __init__(self, node, port_name, port_ip): 112 | """ 113 | :param node: The node owning this link 114 | :param port_name: The name of the only port visible on this link 115 | :param port_ip: The IPV4Address of that link 116 | """ 117 | section = port_name if CFG.has_section(port_name) else 'physical' 118 | self.src = Port(node, self, port_name, hello_int=CFG.get(section, 'hello_interval'), 119 | dead_int=CFG.get(section, 'dead_interval'), area=CFG.get(section, 'area'), 120 | cost=CFG.get(section, 'cost')) 121 | self.src.move_in_namespace() 122 | self.src.set_ip(port_ip) 123 | self.node = node 124 | self.name = port_name 125 | 126 | def move_to_root(self): 127 | self.node.call('ip', 'link', 'set', 'dev', self.name, 'netns', '1') 128 | 129 | def __str__(self): 130 | return 'Physical Link from %s' % self.src 131 | 132 | def delete(self): 133 | self.src.delete() 134 | -------------------------------------------------------------------------------- /fibbingnode/misc/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import collections 4 | import itertools 5 | import threading 6 | 7 | from time import sleep 8 | from fibbingnode import log 9 | 10 | 11 | def require_cmd(cmd, help_str=None): 12 | """ 13 | Ensures that a command is available in $PATH 14 | :param cmd: the command to test 15 | :param help_str: an optional help string to display if cmd is not found 16 | """ 17 | # Check if cmd is a valid absolute path 18 | if os.path.isfile(cmd): 19 | return 20 | # Try to find the cmd in each directory in $PATH 21 | for path in os.environ["PATH"].split(os.pathsep): 22 | path = path.strip('"') 23 | exe = os.path.join(path, cmd) 24 | if os.path.isfile(exe): 25 | return 26 | log.error('[%s] is not available in $PATH', cmd) 27 | if help_str: 28 | log.error(help_str) 29 | sys.exit(1) 30 | 31 | 32 | def need_root(): 33 | """ 34 | Ensures that the program is run as root 35 | """ 36 | if os.getuid() != 0: 37 | log.error('%s: Must be run as root!', sys.argv[0]) 38 | sys.exit(1) 39 | 40 | 41 | def post_delay(amount): 42 | """ 43 | Sleep some time after executing the function 44 | :param amount: the amount of seconds to wait 45 | :return: the return value of the function 46 | """ 47 | def inner(f): 48 | def inner_f(*args, **kwargs): 49 | r = f(*args, **kwargs) 50 | sleep(amount) 51 | return r 52 | return inner_f 53 | return inner 54 | 55 | 56 | def force(f, *args, **kwargs): 57 | """ 58 | Force the execution of function and log any exception 59 | :param f: the function to execute 60 | :param args: its arguments 61 | :param kwargs: its kw-arguments 62 | :return: the return value of f is any, or None 63 | """ 64 | try: 65 | return f(*args, **kwargs) 66 | except Exception as e: 67 | log.debug(e, exc_info=1) 68 | return None 69 | 70 | 71 | def dump_threads(): 72 | """ 73 | Shouldn't be used except for debugging purpose (e.g. find deadlocks) 74 | """ 75 | import traceback 76 | 77 | log.error("*** STACKTRACE - START ***") 78 | code = [] 79 | for threadId, stack in sys._current_frames().items(): 80 | code.append("\n# ThreadID: %s" % threadId) 81 | for filename, lineno, name, line in traceback.extract_stack(stack): 82 | code.append('File: "%s", line %d, in %s' % (filename, 83 | lineno, name)) 84 | if line: 85 | code.append(" %s" % (line.strip())) 86 | for line in code: 87 | log.error(line.strip('\n')) 88 | log.error("*** STACKTRACE - END ***") 89 | 90 | 91 | def read_pid(n): 92 | """ 93 | Extract a pid from a file 94 | :param n: path to a file 95 | :return: pid as a string 96 | """ 97 | try: 98 | with open(n, 'r') as f: 99 | return str(f.read()).strip(' \n\t') 100 | except: 101 | return None 102 | 103 | 104 | def del_file(f): 105 | force(os.remove, f) 106 | 107 | 108 | class ConfigDict(dict): 109 | """ 110 | A dictionary whose attributes are its keys 111 | """ 112 | 113 | def __init__(self, **kwargs): 114 | super(ConfigDict, self).__init__() 115 | for key, val in kwargs.iteritems(): 116 | self[key] = val 117 | 118 | def __getattr__(self, item): 119 | # so that self.item == self[item] 120 | try: 121 | # But preserve i.e. methods 122 | return super(ConfigDict, self).__getattr__(item) 123 | except: 124 | try: 125 | return self[item] 126 | except KeyError: 127 | return None 128 | 129 | def __setattr__(self, key, value): 130 | # so that self.key = value <==> self[key] = key 131 | self[key] = value 132 | 133 | 134 | def cmp_prefixlen(x, y): 135 | return x.prefixlen < y.prefixlen 136 | 137 | 138 | def extend_paths_list(paths, n): 139 | """Return and iterator on a new set of paths, 140 | built by copying the original paths 141 | and appending a new node at the end of it""" 142 | for p in paths: 143 | x = p[:] 144 | x.append(n) 145 | yield x 146 | 147 | 148 | def is_container(x): 149 | """Return whether x is a container (=iterable but not a string)""" 150 | return (isinstance(x, collections.Sequence) and 151 | not isinstance(x, basestring)) 152 | 153 | 154 | def flatten(l): 155 | """Flatten a list of list in a new one""" 156 | return list(itertools.chain.from_iterable(l)) 157 | 158 | 159 | def daemon_thread(target, name, *args, **kwargs): 160 | """Build a daemon thread""" 161 | t = threading.Thread(target=target, name=name, *args, **kwargs) 162 | t.setDaemon(True) 163 | return t 164 | 165 | 166 | def start_daemon_thread(target, name, *args, **kwargs): 167 | """Create a daemon thread and start it""" 168 | t = daemon_thread(target=target, name=name, *args, **kwargs) 169 | t.start() 170 | return t 171 | -------------------------------------------------------------------------------- /tests/test_crossopt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | 5 | import networkx as nx 6 | 7 | import fibbingnode.algorithms.cross_optimizer as crossopt 8 | import fibbingnode.algorithms.merger as merger 9 | import fibbingnode.algorithms.utils as ssu 10 | import fibbingnode as fibbing 11 | 12 | from test_merger import MergerTestCase 13 | 14 | log = fibbing.log 15 | fibbing.log_to_file('test_crossopt.log', 'w') 16 | 17 | # 18 | # Useful tip to selectively disable test: @unittest.skip('reason') 19 | # 20 | 21 | 22 | def check_fwd_dags(fwd_req, topo, lsas, solver): 23 | correct = True 24 | topo = topo.copy() 25 | # Check that the topology/dag contain the destinations, otherwise add it 26 | for dest, dag in fwd_req.iteritems(): 27 | dest_in_dag = dest in dag 28 | dest_in_graph = dest in topo 29 | if not dest_in_dag or not dest_in_graph: 30 | if not dest_in_dag: 31 | sinks = ssu.find_sink(dag) 32 | else: 33 | sinks = dag.predecessors(dest) 34 | for s in sinks: 35 | if not dest_in_dag: 36 | dag.add_edge(s, dest) 37 | if not dest_in_graph: 38 | topo.add_edge(s, dest, 39 | metric=solver.solver.new_edge_metric) 40 | fake_nodes = {} 41 | local_fake_nodes = {} 42 | f_ids = set() 43 | for lsa in lsas: 44 | for route in lsa.routes: 45 | if route.cost > 0: 46 | f_id = '__f_%s_%s_%s' % (lsa.node, lsa.nh, route.dest) 47 | f_ids.add(f_id) 48 | fake_nodes[(lsa.node, f_id, route.dest)] = lsa.nh 49 | cost = topo[lsa.node][lsa.nh]['metric'] 50 | topo.add_edge(lsa.node, f_id, metric=cost) 51 | topo.add_edge(f_id, route.dest, metric=route.cost - cost) 52 | log.debug('Added a globally-visible fake node: ' 53 | '%s - %s - %s - %s - %s [-> %s]', 54 | lsa.node, cost, f_id, route.cost - cost, 55 | route.dest, lsa.nh) 56 | else: 57 | local_fake_nodes[(lsa.node, route.dest)] = lsa.nh 58 | log.debug('Added a locally-visible fake node: %s -> %s', 59 | lsa.node, lsa.nh) 60 | 61 | spt = ssu.all_shortest_paths(topo, metric='metric') 62 | for dest, req_dag in fwd_req.iteritems(): 63 | log.info('Validating requirements for dest %s', dest) 64 | dag = nx.DiGraph() 65 | for n in filter(lambda n: n not in fwd_req, topo): 66 | if n in f_ids: 67 | continue 68 | log.debug('Checking paths of %s', n) 69 | for p in spt[n][0][dest]: 70 | log.debug('Reported path: %s', p) 71 | for u, v in zip(p[:-1], p[1:]): 72 | try: # Are we using a globally-visible fake node? 73 | nh = fake_nodes[(u, v, dest)] 74 | log.debug('%s uses the globally-visible fake node %s ' 75 | 'to get to %s', u, v, nh) 76 | dag.add_edge(u, nh) # Replace by correct next-hop 77 | break 78 | except KeyError: 79 | try: # Are we using a locally-visible one? 80 | nh = local_fake_nodes[(u, dest)] 81 | log.debug('%s uses a locally-visible fake node ' 82 | 'to get to %s', u, nh) 83 | dag.add_edge(u, nh) # Replace by true nh 84 | break 85 | except KeyError: 86 | dag.add_edge(u, v) # Otherwise follow the SP 87 | # Now that we have the current fwing dag, compare to the requirements 88 | for n in req_dag: 89 | successors = set(dag.successors(n)) 90 | req_succ = set(req_dag.successors(n)) 91 | if successors ^ req_succ: 92 | log.error('The successor sets for node %s differ, ' 93 | 'REQ: %s, CURRENT: %s', n, req_succ, successors) 94 | correct = False 95 | predecessors = set(dag.predecessors(n)) 96 | req_pred = set(req_dag.predecessors(n)) 97 | # Also requires to have a non-null successor sets to take into 98 | # account the fact that the destination will have new adjacencies 99 | # through fake nodes 100 | if predecessors ^ req_pred and successors: 101 | log.error('The predecessors sets for %s differ, ' 102 | 'REQ: %s, CURRENT: %s', n, req_pred, predecessors) 103 | correct = False 104 | if correct: 105 | log.info('All forwarding requirements are enforced!') 106 | return correct 107 | 108 | 109 | class CrossOptimizerTestCase(MergerTestCase): 110 | 111 | def _test(self, igp_topo, fwd_dags, expected_lsa_count): 112 | solver = crossopt.CrossOptimizer(solver=merger.PartialECMPMerger()) 113 | # Duplicating dag to show the effect of cross optimization 114 | for d, dag in fwd_dags.items(): 115 | fwd_dags['%s_copy' % d] = dag.copy() 116 | lsas = solver.solve(igp_topo, fwd_dags) 117 | self.assertTrue(check_fwd_dags(fwd_dags, igp_topo, lsas, solver)) 118 | self.assertTrue(len(lsas) == expected_lsa_count) 119 | 120 | 121 | if __name__ == '__main__': 122 | unittest.main() 123 | -------------------------------------------------------------------------------- /fibbingnode/misc/mininetlib/iprouter.py: -------------------------------------------------------------------------------- 1 | from mininet.node import Node 2 | 3 | from ipaddress import ip_interface 4 | 5 | from fibbingnode.misc.mininetlib import get_logger, PRIVATE_IP_KEY,\ 6 | FIBBING_MIN_COST, otherIntf,\ 7 | BDOMAIN_KEY, L3Router, routers_in_bd,\ 8 | FIBBING_DEFAULT_AREA 9 | import fibbingnode.misc.router 10 | from fibbingnode.misc.router import QuaggaRouter, RouterConfigDict 11 | from fibbingnode.misc.utils import ConfigDict 12 | from fibbingnode import CFG 13 | 14 | log = get_logger() 15 | fibbingnode.misc.router.log = log 16 | 17 | 18 | class MininetRouter(QuaggaRouter): 19 | def __init__(self, node, *args, **kwargs): 20 | super(MininetRouter, self).__init__(name=node.name, 21 | working_dir='/tmp', 22 | *args, **kwargs) 23 | self.mnode = node 24 | 25 | def call(self, *args, **kwargs): 26 | return self.mnode.cmd(*args, **kwargs) 27 | 28 | def pipe(self, *args, **kwargs): 29 | return self.mnode.popen(*args, **kwargs) 30 | 31 | def get_config_node(self): 32 | return MininetRouterConfig( 33 | self.mnode, 34 | debug_ospf=self.mnode.debug.get('ospf', ()), 35 | debug_zebra=self.mnode.debug.get('zebra', ())) 36 | 37 | 38 | class IPRouter(Node, L3Router): 39 | def __init__(self, name, private_net='10.0.0.0/8', 40 | routerid=None, static_routes=(), debug=None, 41 | subrouter=MininetRouter, 42 | **kwargs): 43 | """static_routes in the form of (prefix, via_node_id)* 44 | debug as a dict with the daemon name as key and the value 45 | is a list of quagga debug flags to set for that daemon""" 46 | self.private_net = str(private_net) 47 | self.debug = debug if debug else {} 48 | self.rid = routerid 49 | self.static_routes = static_routes 50 | self.hello_interval = '1' 51 | self.dead_interval = 'minimal hello-multiplier 5' 52 | super(IPRouter, self).__init__(name, **kwargs) 53 | self.router = subrouter(self) if subrouter else None 54 | 55 | def start(self): 56 | self.cmd('ip', 'link', 'set', 'dev', 'lo', 'up') 57 | for itf in self.intfList(): 58 | for ip in itf.params.get(PRIVATE_IP_KEY, ()): 59 | self.cmd('ip', 'address', 'add', ip, 60 | 'dev', itf.name) 61 | neighbor_to_intf = {otherIntf(itf).name: itf 62 | for itf in self.intfList()} 63 | self.static_routes = [(p, v if v not in neighbor_to_intf 64 | else neighbor_to_intf[v]) 65 | for p, v in self.static_routes] 66 | if self.router: 67 | self.router.start() 68 | 69 | def terminate(self): 70 | if self.router: 71 | self.router.delete() 72 | super(IPRouter, self).terminate() 73 | 74 | @staticmethod 75 | def is_l3router_intf(itf): 76 | # We override the instance check in order to not match Controllers 77 | return (isinstance(itf.node, IPRouter) and 78 | itf.params.get('cost', 1) >= 0) 79 | 80 | @property 81 | def id(self): 82 | return self.rid if self.rid else self.intfList()[0].ip 83 | 84 | def ospf_interfaces(self): 85 | # We will only 'configure' the interfaces belonging to a broadcast 86 | # domain where there is another OSPF router. 87 | # We will advertize the others through redistribute.connected 88 | def include_func(itf): 89 | return list(filter(lambda x: x.node != self, 90 | routers_in_bd(itf.params.get(BDOMAIN_KEY, ())))) 91 | 92 | return filter(include_func, self.intfList()) 93 | 94 | 95 | class MininetRouterConfig(RouterConfigDict): 96 | def __init__(self, router, *args, **kwargs): 97 | super(MininetRouterConfig, self).__init__(router, *args, **kwargs) 98 | self.ospf.redistribute.connected = 1000 99 | self.ospf.redistribute.static = 1000 100 | self.ospf.router_id = router.id 101 | 102 | # Parse LSA throttling parameters 103 | delay = CFG.get("DEFAULT", 'delay') 104 | initial_holdtime = CFG.get("DEFAULT", 'initial_holdtime') 105 | max_holdtime = CFG.get("DEFAULT", 'max_holdtime') 106 | 107 | # Parse minimum LS intervals 108 | min_ls_interval = CFG.get("DEFAULT", 'min_ls_interval') 109 | min_ls_arrival = CFG.get("DEFAULT", 'min_ls_arrival') 110 | 111 | self.ospf.throttling = ConfigDict(spf=ConfigDict(delay=delay, 112 | initial_holdtime=initial_holdtime, 113 | max_holdtime=max_holdtime), 114 | lsa_all=ConfigDict(min_ls_interval=min_ls_interval)) 115 | 116 | self.ospf.lsa = ConfigDict(min_ls_arrival=min_ls_arrival) 117 | 118 | def build_ospf(self, router): 119 | cfg = super(MininetRouterConfig, self).build_ospf(router) 120 | networks = [] 121 | for itf in router.ospf_interfaces(): 122 | c = itf.params.get('cost', FIBBING_MIN_COST) 123 | if c > 0: 124 | cfg.interfaces.append( 125 | ConfigDict(name=itf.name, 126 | description=str(itf.link), 127 | ospf=ConfigDict( 128 | cost=c, 129 | priority=10, 130 | dead_int=router.dead_interval, 131 | hello_int=router.hello_interval))) 132 | area = itf.params.get('area', FIBBING_DEFAULT_AREA) 133 | networks.append((ip_interface('%s/%s' % 134 | (itf.ip, itf.prefixLen)) 135 | .network, area)) 136 | # TODO figure out the private config knob so that the private 137 | # addresses dont create redundant OSPF session over the same 138 | # interface ... 139 | try: 140 | networks.extend((ip_interface(net).network, area) 141 | for net in itf.params[PRIVATE_IP_KEY]) 142 | except KeyError: 143 | pass # No private ip on that interface 144 | else: 145 | cfg.passive_interfaces.append(itf) 146 | for net, area in networks: 147 | cfg.networks.append(ConfigDict(domain=net.with_prefixlen, 148 | area=area)) 149 | return cfg 150 | 151 | def build_zebra(self, router): 152 | cfg = super(MininetRouterConfig, self).build_zebra(router) 153 | # Create route map to ignore 'private' addresses 154 | plen = int(router.private_net.split('/')[1]) 155 | cfg.prefixlists = [ConfigDict(name='PRIVATE', 156 | action='permit', 157 | prefix=router.private_net, 158 | ge=plen + 1)] 159 | cfg.routemaps = [ConfigDict(name='IMPORT', 160 | action='deny', 161 | prio='10', 162 | prefix=['PRIVATE'], 163 | proto=[]), 164 | ConfigDict(name='IMPORT', 165 | action='permit', 166 | prio='20', 167 | prefix=[], 168 | proto=['ospf'])] 169 | cfg.static_routes.extend(router.static_routes) 170 | return cfg 171 | -------------------------------------------------------------------------------- /fibbingnode/southbound/main.py: -------------------------------------------------------------------------------- 1 | from ConfigParser import DEFAULTSECT 2 | from cmd import Cmd 3 | import logging 4 | import sys 5 | import subprocess 6 | import argparse 7 | import datetime 8 | from fibbing import FibbingManager 9 | import fibbingnode 10 | from fibbingnode.misc.utils import dump_threads 11 | import signal 12 | 13 | log = fibbingnode.log 14 | CFG = fibbingnode.CFG 15 | 16 | 17 | class FibbingCLI(Cmd): 18 | Cmd.prompt = '> ' 19 | 20 | def __init__(self, mngr, *args, **kwargs): 21 | self.fibbing = mngr 22 | Cmd.__init__(self, *args, **kwargs) 23 | 24 | def do_add_node(self, line=''): 25 | """Add a new fibbing node""" 26 | self.fibbing.add_node() 27 | 28 | def do_show_lsdb(self, line=''): 29 | log.info(self.fibbing.root.lsdb) 30 | 31 | def do_draw_network(self, line): 32 | """Draw the network as pdf in the given file""" 33 | self.fibbing.root.lsdb.graph.draw(line) 34 | 35 | def do_print_graph(self, line=''): 36 | log.info('Current network graph: %s', 37 | self.fibbing.root.lsdb.graph.edges(data=True)) 38 | 39 | def do_print_net(self, line=''): 40 | """Print information about the fibbing network""" 41 | self.fibbing.print_net() 42 | 43 | def do_print_routes(self, line=''): 44 | """Print information about the fibbing routes""" 45 | self.fibbing.print_routes() 46 | 47 | def do_exit(self, line=''): 48 | """Exit the prompt""" 49 | return True 50 | 51 | def do_cfg(self, line=''): 52 | part = line.split(' ') 53 | val = part.pop() 54 | key = part.pop() 55 | sect = part.pop() if part else DEFAULTSECT 56 | CFG.set(sect, key, val) 57 | 58 | def do_call(self, line): 59 | """Execute a command on a node""" 60 | items = line.split(' ') 61 | try: 62 | node = self.fibbing[items[0]] 63 | node.call(*items[1:]) 64 | except KeyError: 65 | log.error('Unknown node %s', items[0]) 66 | 67 | def do_add_route(self, line=''): 68 | """Setup a fibbing route 69 | add_route network via1 metric1 via2 metric2 ...""" 70 | items = line.split(' ') 71 | if len(items) < 3: 72 | log.error('route only takes at least 3 arguments: ' 73 | 'network via_address metric') 74 | else: 75 | points = [] 76 | i = 2 77 | while i < len(items): 78 | points.append((items[i-1], items[i])) 79 | i += 2 80 | log.critical('Add route request at %s', 81 | datetime.datetime.now().strftime('%H.%M.%S.%f')) 82 | self.fibbing.install_route(items[0], points, True) 83 | 84 | def do_rm_route(self, line): 85 | """Remove a route or parts of a route""" 86 | items = line.split(' ') 87 | if len(items) == 1: 88 | ans = raw_input('Remove the WHOLE fibbing route for %s ? (y/N)' 89 | % line) 90 | if ans == 'y': 91 | self.fibbing.remove_route(line) 92 | else: 93 | self.fibbing.remove_route_part(items[0], *items[1:]) 94 | 95 | def default(self, line): 96 | """Pass the command to the shell""" 97 | args = line.split(' ') 98 | if args[0] in self.fibbing.nodes: 99 | self.do_call(' '.join(args)) 100 | else: 101 | try: 102 | log.info(subprocess.check_output(line, shell=True)) 103 | except Exception as e: 104 | log.info('Command %s failed', line) 105 | log.info(e.message) 106 | 107 | def eval(self, line): 108 | """Interpret the given line ...""" 109 | self.eval(line) 110 | 111 | def do_ospfd(self, line): 112 | """Connect to the ospfd daemon of the given node""" 113 | try: 114 | self.fibbing[line].call('telnet', 'localhost', '2604') 115 | except KeyError: 116 | log.error('Unknown node %s', line) 117 | 118 | def do_vtysh(self, line): 119 | """Execute a vtysh command on a node""" 120 | items = line.split(' ') 121 | try: 122 | node = self.fibbing[items[0]] 123 | result = node.vtysh(*items[1:], configure=False) 124 | log.info(result) 125 | except KeyError: 126 | log.error('Unknown node %s', items[0]) 127 | 128 | def do_configure(self, line): 129 | """Execute a vtysh configure command on a node""" 130 | items = line.split(' ') 131 | try: 132 | node = self.fibbing[items[0]] 133 | result = node.vtysh(*items[1:], configure=True) 134 | result = result.strip(' \n\t') 135 | if result: 136 | log.info(result) 137 | except KeyError: 138 | log.error('Unknown node %s', items[0]) 139 | 140 | def do_traceroute(self, line, max_ttl=10): 141 | """ 142 | Perform a simple traceroute between the source and an IP 143 | :param max_ttl: the maximal ttl to use 144 | """ 145 | items = line.split(' ') 146 | try: 147 | node = self.fibbing[items[0]] 148 | node.call('traceroute', '-q', '1', '-I', 149 | '-m', str(max_ttl), '-w', '.1', items[1]) 150 | except KeyError: 151 | log.error('Unknown node %s', items[0]) 152 | except ValueError: 153 | log.error('This command takes 2 arguments: ' 154 | 'source node and destination IP') 155 | 156 | def do_dump(self, line=''): 157 | dump_threads() 158 | 159 | 160 | def handle_args(): 161 | parser = argparse.ArgumentParser(description='Starts a fibbing node.') 162 | parser.add_argument('ports', metavar='IF', type=str, nargs='*', 163 | help='A physical interface to use') 164 | parser.add_argument('--debug', action='store_true', default=False, 165 | help='Debug (default: disabled)') 166 | parser.add_argument('--nocli', action='store_true', default=False, 167 | help='Disable the CLI') 168 | parser.add_argument('--cfg', help='Use specified config file', 169 | default=None) 170 | args = parser.parse_args() 171 | 172 | instance_count = CFG.getint(DEFAULTSECT, 'controller_instance_number') 173 | 174 | # Update default config 175 | if args.cfg: 176 | CFG.read(args.cfg) 177 | fibbingnode.BIN = CFG.get(DEFAULTSECT, 'quagga_path') 178 | # Check if we need to force debug mode 179 | if args.debug: 180 | CFG.set(DEFAULTSECT, 'debug', '1') 181 | if CFG.getboolean(DEFAULTSECT, 'debug'): 182 | log.setLevel(logging.DEBUG) 183 | else: 184 | log.setLevel(logging.INFO) 185 | # Check for any specified physical port to use both in config file 186 | # or in args 187 | ports = set(p for p in CFG.sections() 188 | if not (p == 'fake' or p == 'physical' or p == DEFAULTSECT)) 189 | ports.update(args.ports) 190 | if not ports: 191 | log.warning('The fibbing node will not be connected ' 192 | 'to any physical ports!') 193 | else: 194 | log.info('Using the physical ports: %s', ports) 195 | return ports, instance_count, not args.nocli 196 | 197 | 198 | def main(_CLI=FibbingCLI): 199 | phys_ports, name, cli = handle_args() 200 | if not cli: 201 | fibbingnode.log_to_file('%s.log' % name) 202 | mngr = FibbingManager(name) 203 | 204 | def sig_handler(sig, frame): 205 | mngr.cleanup() 206 | fibbingnode.EXIT.set() 207 | sys.exit() 208 | 209 | signal.signal(signal.SIGINT, sig_handler) 210 | signal.signal(signal.SIGTERM, sig_handler) 211 | 212 | try: 213 | mngr.start(phys_ports=phys_ports) 214 | if cli: 215 | cli = _CLI(mngr=mngr) 216 | cli.cmdloop() 217 | fibbingnode.EXIT.set() 218 | except Exception as e: 219 | log.exception(e) 220 | fibbingnode.EXIT.set() 221 | finally: 222 | fibbingnode.EXIT.wait() 223 | mngr.cleanup() 224 | 225 | 226 | if __name__ == '__main__': 227 | main() 228 | -------------------------------------------------------------------------------- /fibbingnode/algorithms/southbound_interface.py: -------------------------------------------------------------------------------- 1 | """This file defines Northbound application controller classes.""" 2 | 3 | import abc 4 | import copy 5 | from ConfigParser import DEFAULTSECT 6 | 7 | import networkx as nx 8 | 9 | from fibbingnode.southbound.interface import FakeNodeProxy, ShapeshifterProxy 10 | from fibbingnode.algorithms.ospf_simple import OSPFSimple 11 | from fibbingnode.misc.sjmp import SJMPClient, ProxyCloner 12 | from fibbingnode.misc.igp_graph import IGPGraph 13 | from fibbingnode import CFG 14 | from fibbingnode import log 15 | 16 | 17 | def sanitize_edge_data(d): 18 | """Because json.decode() does not set all types back ...""" 19 | try: 20 | d['metric'] = int(d['metric']) 21 | except KeyError: 22 | pass 23 | return d 24 | 25 | 26 | class SouthboundListener(ShapeshifterProxy): 27 | """This basic controller maintains a structure describing the IGP topology 28 | and listens for changes.""" 29 | 30 | def __init__(self, *args, **kwargs): 31 | super(SouthboundListener, self).__init__(*args, **kwargs) 32 | self.igp_graph = IGPGraph() 33 | self.dirty = False 34 | self.json_proxy = SJMPClient(hostname=CFG.get(DEFAULTSECT, 35 | 'json_hostname'), 36 | port=CFG.getint(DEFAULTSECT, 'json_port'), 37 | target=self) 38 | self.quagga_manager = ProxyCloner(FakeNodeProxy, self.json_proxy) 39 | 40 | def run(self): 41 | """Connect the the southbound controller. This call will not return 42 | unless the connection is halted.""" 43 | log.info('Connecting to server ...') 44 | self.json_proxy.communicate() 45 | 46 | def stop(self): 47 | """Stop the connection to the southbound controller""" 48 | self.json_proxy.stop() 49 | 50 | def bootstrap_graph(self, graph, node_properties): 51 | self.igp_graph.clear() 52 | self.igp_graph.add_edges_from(graph) 53 | for _, _, d in self.igp_graph.edges_iter(data=True): 54 | sanitize_edge_data(d) 55 | self.update_node_properties(**node_properties) 56 | log.debug('Bootstrapped graph with edges: %s and properties: %s', 57 | self.igp_graph.edges(data=True), node_properties) 58 | self.received_initial_graph() 59 | self.graph_changed() 60 | 61 | def received_initial_graph(self): 62 | """Called when the initial graph has been bootstrapped, before 63 | calling graph_changed""" 64 | pass 65 | 66 | def add_edge(self, source, destination, properties={'metric': 1}): 67 | properties = sanitize_edge_data(properties) 68 | # metric is added twice to support backward-compat. 69 | self.igp_graph.add_edge(source, destination, properties) 70 | log.debug('Added edge: %s-%s@%s', source, destination, properties) 71 | # Only trigger an update if the link is bidirectional 72 | self.dirty = self.igp_graph.has_edge(destination, source) 73 | 74 | def commit(self): 75 | log.debug('End of graph update') 76 | if self.dirty: 77 | self.dirty = False 78 | self.graph_changed() 79 | 80 | @abc.abstractmethod 81 | def graph_changed(self): 82 | """Called when the IGP graph has changed.""" 83 | 84 | def remove_edge(self, source, destination): 85 | # TODO: pay attention to re-add the symmetric edge if only one way 86 | # crashed 87 | try: 88 | self.igp_graph.remove_edge(source, destination) 89 | log.debug('Removed edge %s-%s', source, destination) 90 | self.igp_graph.remove_edge(destination, source) 91 | log.debug('Removed edge %s-%s', destination, source) 92 | except nx.NetworkXError: 93 | # This means that we had already removed both side of the edge 94 | # earlier or that the adjacency was not fully established before 95 | # going down 96 | pass 97 | else: 98 | self.dirty = True 99 | 100 | def update_node_properties(self, **properties): 101 | log.debug('Updating node propeties: %s', properties) 102 | for node, data in properties.iteritems(): 103 | self.igp_graph.node[node].update(data) 104 | self.dirty = self.dirty or properties 105 | 106 | 107 | class SouthboundController(SouthboundListener): 108 | """A simple northbound controller that monitors for changes in the IGP 109 | graph, and keeps track of advertized LSAs to remove them on exit""" 110 | def __init__(self, *args, **kwargs): 111 | super(SouthboundController, self).__init__(*args, **kwargs) 112 | self.advertized_lsa = set() 113 | 114 | def stop(self): 115 | self.remove_lsa(*self.advertized_lsa) 116 | super(SouthboundController, self).stop() 117 | 118 | @abc.abstractmethod 119 | def refresh_augmented_topo(self): 120 | """The IGP graph has changed, return the _set_ of LSAs that need to be 121 | advertized in the network (possibly just the previous one)""" 122 | 123 | def graph_changed(self): 124 | self.refresh_lsas() 125 | 126 | def advertize_lsa(self, *lsas): 127 | """Instructs the southbound controller to announce LSAs""" 128 | lsas = list(lsas) 129 | if lsas: 130 | self.quagga_manager.add(lsas) 131 | self.advertized_lsa.update(lsas) 132 | else: 133 | log.warning('Tried to advertize an empty list of LSA') 134 | 135 | def remove_lsa(self, *lsas): 136 | """Instructs the southbound controller to remove LSAs""" 137 | lsas = list(lsas) 138 | if lsas: 139 | self.quagga_manager.remove(lsas) 140 | self.advertized_lsa.difference_update(lsas) 141 | else: 142 | log.warning('Tried to remove an empty list of LSA') 143 | 144 | def _get_diff_lsas(self): 145 | new_lsas = self.refresh_augmented_topo() 146 | log.debug('New LSA set: %s', new_lsas) 147 | to_add = new_lsas.difference(self.advertized_lsa) 148 | to_rem = self.advertized_lsa.difference(new_lsas) 149 | log.debug('Removing LSA set: %s', to_rem) 150 | self.advertized_lsa = new_lsas 151 | return to_add, to_rem 152 | 153 | def refresh_lsas(self): 154 | """Refresh the set of LSAs that needs to be sent in the IGP, 155 | and instructs the southbound controller to update it if changed""" 156 | (to_add, to_rem) = self._get_diff_lsas() 157 | if not to_add and not to_rem: 158 | log.debug('Nothing to do for the current topology') 159 | return 160 | if to_rem: 161 | self.remove_lsa(*to_rem) 162 | if to_add: 163 | self.advertize_lsa(*to_add) 164 | 165 | 166 | class StaticPathManager(SouthboundController): 167 | """Dumb controller that will simply enforce static lsas""" 168 | def __init__(self, *args, **kwargs): 169 | super(StaticPathManager, self).__init__(*args, **kwargs) 170 | self.demands = set() 171 | 172 | def refresh_augmented_topo(self): 173 | return set(self.demands) 174 | 175 | def add_lie(self, *lies): 176 | """Add lies (LSA) to send in the network""" 177 | self.demands.update(lies) 178 | self.refresh_lsas() 179 | 180 | def remove_lie(self, *lies): 181 | """Remove lies (LSA) to send in the network""" 182 | self.demands.difference_update(lies) 183 | self.refresh_lsas() 184 | 185 | 186 | class SouthboundManager(SouthboundController): 187 | """A Northbound controller that will use a solver to implement path 188 | requirements expressed as forwarding DAGs""" 189 | def __init__(self, 190 | fwd_dags=None, 191 | optimizer=None, 192 | additional_routes=None, 193 | *args, **kwargs): 194 | self.additional_routes = additional_routes 195 | self.current_lsas = set([]) 196 | self.optimizer = optimizer if optimizer else OSPFSimple() 197 | self.fwd_dags = fwd_dags if fwd_dags else {} 198 | self.has_initial_topo = False 199 | super(SouthboundManager, self).__init__(*args, **kwargs) 200 | 201 | def refresh_augmented_topo(self): 202 | log.info('Solving topologies') 203 | if not self.json_proxy.alive() or not self.has_initial_topo: 204 | log.debug('Skipping as we do not yet have a topology') 205 | return self.advertized_lsa 206 | try: 207 | self.optimizer.solve(self.igp_graph.copy(), 208 | {p: dag.copy() 209 | for p, dag in self.fwd_dags.iteritems()}) 210 | except Exception as e: 211 | log.exception(e) 212 | return self.advertized_lsa 213 | else: 214 | return set(self.optimizer.get_fake_lsas()) 215 | 216 | def simple_path_requirement(self, prefix, path): 217 | """Add a path requirement for the given prefix. 218 | 219 | :param path: The ordered list of routerid composing the path. 220 | E.g. for path = [A, B, C], the following edges will be 221 | used as requirements: [](A, B), (B, C), (C, D)]""" 222 | self.fwd_dags[prefix] = IGPGraph( 223 | [(s, d) for s, d in zip(path[:-1], path[1:])]) 224 | self.refresh_lsas() 225 | 226 | def add_dag_requirement(self, prefix, dag): 227 | self.fwd_dags[prefix] = dag.copy() 228 | self.refresh_lsas() 229 | 230 | def add_dag_requirements_from(self, fw_dags): 231 | """ 232 | Adds a bunch of fw dag requirements 233 | :param fw_dags: dictionary prefix -> dag 234 | """ 235 | self.fwd_dags.update(copy.deepcopy(fw_dags)) 236 | self.refresh_lsas() 237 | 238 | def remove_dag_requirement(self, prefix): 239 | if prefix in self.fwd_dags.keys(): 240 | self.fwd_dags.pop(prefix) 241 | self.refresh_lsas() 242 | 243 | def remove_all_dag_requirements(self): 244 | self.fwd_dags.clear() 245 | self.refresh_lsas() 246 | 247 | def received_initial_graph(self): 248 | log.debug('Sending initial lsa''s') 249 | self.has_initial_topo = True 250 | if self.additional_routes: 251 | self.advertize_lsa(*self.additional_routes) 252 | -------------------------------------------------------------------------------- /fibbingnode/southbound/lsdb/lsa.py: -------------------------------------------------------------------------------- 1 | """This modules defines the methods to extract LSAs from Quagga instances log 2 | lines, as well the effect of these various LSAs on the network graph""" 3 | from abc import abstractmethod 4 | import functools 5 | 6 | from ipaddress import ip_interface, ip_address 7 | 8 | from fibbingnode import log 9 | 10 | # Keys that are used by Quagga/ospfd/ospf_dump.c 11 | FWD_ADDR = 'fwd_addr' 12 | LINK_DATA = 'link_data' 13 | LINKID = 'link_id' 14 | LINK_TYPE = 'link_type' 15 | LSAGE = "age" 16 | LSA_SEQNUM = 'seq_num' 17 | LSA_TYPE = 'lsa_type' 18 | MASK = 'link_mask' 19 | METRIC = 'link_metric' 20 | METRICTYPE = 'link_metrictype' 21 | OPAQUE = 'opaque_data' 22 | RID = 'rid' 23 | 24 | SEP_GROUP = ' ' 25 | SEP_INTRA_FIELD = ':' 26 | SEP_INTER_FIELD = ';' 27 | 28 | # Reference constant 29 | MAX_LS_AGE = 3600 # one hour 30 | 31 | 32 | class Link(object): 33 | TYPE = '0' 34 | 35 | def __init__(self, address=None, metric=0): 36 | self.address = address 37 | self.metric = metric 38 | 39 | @staticmethod 40 | def parse(lsa_prop): 41 | for subcls in Link.__subclasses__(): 42 | if subcls.TYPE == lsa_prop[LINK_TYPE]: 43 | return subcls(lsa_prop[LINKID], 44 | lsa_prop[LINK_DATA], 45 | lsa_prop[METRIC]) 46 | log.error('Couldn''t parse the link %s', lsa_prop) 47 | return None 48 | 49 | @abstractmethod 50 | def endpoints(self, lsdb): 51 | """ 52 | Give the list of endpoint IPS/router-id for that link 53 | :param graph: A IGPGraph of the network 54 | :param lsdb: an LSDB instance in order to resolve 55 | e.g. routerid or interface IPs 56 | :return: list of IPs or router-id 57 | """ 58 | 59 | def __str__(self): 60 | return '%s:%s' % (self.address, self.metric) 61 | 62 | 63 | class P2PLink(Link): 64 | TYPE = '1' 65 | 66 | def __init__(self, linkid, link_data, metric): 67 | super(P2PLink, self).__init__(address=link_data, metric=metric) 68 | self.other_routerid = linkid 69 | 70 | def endpoints(self, lsdb): 71 | return [self.other_routerid] 72 | 73 | 74 | class TransitLink(Link): 75 | TYPE = '2' 76 | 77 | def __init__(self, linkid, link_data, metric): 78 | super(TransitLink, self).__init__(address=link_data, metric=metric) 79 | self.dr_ip = linkid 80 | 81 | def endpoints(self, lsdb): 82 | other_routers = [] 83 | netdb = lsdb.lsdb(NetworkLSA) 84 | try: 85 | netlsa = netdb[self.dr_ip] 86 | except KeyError: 87 | log.debug('Cannot resolve network lsa for %s yet', self.dr_ip) 88 | else: 89 | other_routers.extend(netlsa.attached_routers) 90 | return other_routers 91 | 92 | 93 | class StubLink(Link): 94 | TYPE = '3' 95 | 96 | def __init__(self, linkid, link_data, metric): 97 | super(StubLink, self).__init__(address=linkid, metric=metric) 98 | self.mask = link_data 99 | 100 | @property 101 | def prefix(self): 102 | return ip_interface('%s/%s' % (self.address, self.mask)).with_prefixlen 103 | 104 | def endpoints(self, lsdb): 105 | # We don't want stub links on the graph 106 | return [] 107 | 108 | 109 | class VirtualLink(Link): 110 | TYPE = '4' 111 | 112 | def __init__(self, *args, **kwargs): 113 | log.debug('Ignoring virtual links') 114 | super(VirtualLink, self).__init__() 115 | 116 | def endpoints(self, lsdb): 117 | return [] 118 | 119 | 120 | class LSAHeader(object): 121 | def __init__(self, prop_dict): 122 | self.routerid = prop_dict[RID] 123 | self.linkid = prop_dict[LINKID] 124 | self.lsa_type = prop_dict[LSA_TYPE] 125 | self.mask = prop_dict.get(MASK, None) # Can be unset for some LSA 126 | self.age = int(prop_dict[LSAGE]) 127 | self.lsa_seqnum = int(prop_dict[LSA_SEQNUM]) 128 | 129 | 130 | class LSA(object): 131 | TYPE = '0' 132 | 133 | def __init__(self, hdr): 134 | self.seqnum = hdr.lsa_seqnum 135 | self.age = hdr.age 136 | 137 | @staticmethod 138 | def parse(lsa_header, lsa_prop): 139 | """ 140 | Create a new LSA based on the property dicts given 141 | :param lsa_header: an LSAHeader instance 142 | :param lsa_prop: a property dictionary 143 | :return: a new LSA instance 144 | """ 145 | for subcls in LSA.__subclasses__(): 146 | if subcls.TYPE == lsa_header.lsa_type: 147 | return subcls(lsa_header, lsa_prop) 148 | log.debug('Couldn''t parse the LSA type %s [%s]', 149 | lsa_header.lsa_type, 150 | lsa_prop) 151 | return UnusedLSA(lsa_header) 152 | 153 | @abstractmethod 154 | def key(self): 155 | """ 156 | What is the unique key identifying this LSA among 157 | all other LSA of that type 158 | :return: key 159 | """ 160 | 161 | @abstractmethod 162 | def apply(self, graph, lsdb): 163 | """ 164 | Apply this lsa on the graph, thus adding links/node as needed 165 | :param graph: The graph to manipulate 166 | :param lsdb: The LSDB instance that can be used 167 | to retrieve information from other LSAs 168 | """ 169 | 170 | 171 | class UnusedLSA(LSA): 172 | def key(self): 173 | return None 174 | 175 | def apply(self, graph, lsdb): 176 | pass 177 | 178 | 179 | class RouterLSA(LSA): 180 | TYPE = '1' 181 | 182 | def __init__(self, hdr, lsa_prop): 183 | super(RouterLSA, self).__init__(hdr) 184 | self.links = [Link.parse(part) for part in lsa_prop] 185 | self.routerid = hdr.routerid 186 | 187 | def key(self): 188 | return self.routerid 189 | 190 | def apply(self, graph, lsdb): 191 | graph.add_router(self.routerid) 192 | for link in self.links: 193 | # If the endpoints is not yet in the graph, its properties 194 | # will be set by later calls to add_xxx as inserting nodes 195 | # update their properties 196 | for endpoint in link.endpoints(lsdb): 197 | graph.add_edge(self.routerid, 198 | endpoint, 199 | metric=link.metric, 200 | src_address=link.address) 201 | 202 | def contract_graph(self, graph, private_ips): 203 | ips = [link.address for link in self.links 204 | if link.address != self.routerid] 205 | ips.extend(private_ips) 206 | graph.contract(self.routerid, ips) 207 | 208 | def __str__(self): 209 | return '[R]<%s: %s>' % (self.routerid, 210 | ', '.join([str(link) for link in self.links])) 211 | 212 | 213 | class NetworkLSA(LSA): 214 | TYPE = '2' 215 | 216 | def __init__(self, hdr, lsa_prop): 217 | super(NetworkLSA, self).__init__(hdr) 218 | self.mask = hdr.mask 219 | self.dr_ip = hdr.linkid 220 | self.attached_routers = [part[RID] for part in lsa_prop] 221 | 222 | def key(self): 223 | return self.dr_ip 224 | 225 | def apply(self, graph, lsdb): 226 | # Unused as the RouterLSA should have done the resolution for us 227 | pass 228 | 229 | def __str__(self): 230 | return '[N]<%s: %s>' % (self.dr_ip, ', '.join(self.attached_routers)) 231 | 232 | 233 | class ASExtRoute(object): 234 | def __init__(self, metric, fwd_addr): 235 | self.metric = metric 236 | self.fwd_addr = fwd_addr 237 | 238 | 239 | class ASExtLSA(LSA): 240 | TYPE = '5' 241 | 242 | def __init__(self, hdr, lsa_prop): 243 | super(ASExtLSA, self).__init__(hdr) 244 | self.routerid = hdr.routerid 245 | self.address = hdr.linkid 246 | self.mask = hdr.mask 247 | self.routes = [ASExtRoute(part[METRIC], part[FWD_ADDR]) 248 | for part in lsa_prop] 249 | self.interface = ip_interface('%s/%s' % (self.address, self.mask)) 250 | 251 | @property 252 | def prefix(self): 253 | return self.interface.with_prefixlen 254 | 255 | def key(self): 256 | return self.routerid, self.prefix 257 | 258 | def apply(self, graph, lsdb): 259 | for route in self.routes: 260 | fwd_addr = self.resolve_fwd_addr(route.fwd_addr) 261 | if ip_address(self.routerid) in lsdb.BASE_NET: 262 | try: 263 | targets = lsdb.private_addresses.targets_for(fwd_addr) 264 | method = functools.partial(graph.add_local_route, 265 | targets=targets) 266 | except KeyError: 267 | method = graph.add_fake_route 268 | else: 269 | method = graph.add_route 270 | method(fwd_addr, self.prefix, metric=route.metric) 271 | 272 | def resolve_fwd_addr(self, fwd_addr): 273 | return self.routerid if fwd_addr == '0.0.0.0' else fwd_addr 274 | 275 | def __str__(self): 276 | return '[E]<%s: %s>' % \ 277 | (self.prefix, 278 | ', '.join(('(%s, %s)' % (self.resolve_fwd_addr(route.fwd_addr), 279 | route.metric) 280 | for route in self.routes))) 281 | 282 | 283 | def is_newer_seqnum(a, b): 284 | """ 285 | As of OSPFv2, sequence numbers should simply be treated as signed 286 | integers ranging from the oldest sequence number possible 0x80000001 287 | (-N+1 in decimal) to the highest 0x7FFFFFFF (N-1 in decimal). 288 | """ 289 | return a > b 290 | 291 | 292 | def is_expired_lsa(lsa): 293 | """Return whether the LSA is too old to be considered valid""" 294 | return lsa.age >= MAX_LS_AGE 295 | 296 | 297 | def _extract_lsa_properties(lsa_part): 298 | d = {} 299 | for prop in lsa_part.split(SEP_INTER_FIELD): 300 | if not prop: 301 | continue 302 | key, val = prop.split(SEP_INTRA_FIELD) 303 | d[key] = val 304 | return d 305 | 306 | 307 | def parse_lsa(lsa_info): 308 | """Builds an lsa from the extracted lsa info""" 309 | lsa_parts = [_extract_lsa_properties(part) 310 | for part in lsa_info.split(SEP_GROUP) if part] 311 | return LSA.parse(LSAHeader(lsa_parts.pop(0)), lsa_parts) 312 | -------------------------------------------------------------------------------- /fibbingnode/misc/router.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import time 4 | from contextlib import closing 5 | from threading import Lock, Timer 6 | from mako import exceptions 7 | from mako.template import Template 8 | 9 | import fibbingnode 10 | from fibbingnode.misc.utils import read_pid, del_file, ConfigDict 11 | 12 | log = fibbingnode.log 13 | BIN = fibbingnode.BIN 14 | 15 | OSPF_CFG_TEMPLATE = fibbingnode.get_template_path('ospf.mako') 16 | ZEBRA_CFG_TEMPLATE = fibbingnode.get_template_path('zebra.mako') 17 | 18 | ZEBRA_EXEC = os.path.join(BIN, 'sbin/zebra') 19 | OSPFD_EXEC = os.path.join(BIN, 'sbin/ospfd') 20 | 21 | OSPFD_PORT = 2604 22 | OSPFD_PASSWORD = 'zebra' 23 | 24 | 25 | class QuaggaRouter(object): 26 | """ 27 | A wrapper around Quagga to start and configure an OSPF router 28 | """ 29 | ID = 0 30 | 31 | def __init__(self, 32 | name=None, 33 | ospf_priority=1, 34 | working_dir='.'): 35 | """ 36 | :param name: The router hostname, used for the temp files 37 | :param ospf_priority: The priority to apply to the interfaces for the 38 | DR elections. 39 | :param working_dir: The directory where all temp files should go. Must 40 | be rw for quagga/quagga! 41 | """ 42 | if not name: 43 | name = 'r%d' % QuaggaRouter.ID 44 | QuaggaRouter.ID += 1 45 | self.id = name 46 | self.ospf_priority = ospf_priority 47 | self.working_dir = working_dir 48 | self.zebra_pid = self._temp_path('zebra', 'pid') 49 | self.zebra_api = self._temp_path('zserv', 'api') 50 | self.ospfd_pid = self._temp_path('ospf', 'pid') 51 | self.ospf_cfg = self._temp_path('ospf', 'conf') 52 | self.zebra_cfg = self._temp_path('zebra', 'conf') 53 | self.vtysh = VTYSH('localhost', OSPFD_PORT, node=self) 54 | 55 | def _temp_path(self, name, ext): 56 | return '%s/%s_%s.%s' % (self.working_dir, name, self.id, ext) 57 | 58 | def delete(self): 59 | """ 60 | Delete this node and its associate resources 61 | """ 62 | self.call('sysctl', '-w', 'net.ipv4.ip_forward=0') 63 | self.call('sysctl', '-w', 'net.ipv4.icmp_errors_use_inbound_ifaddr=0') 64 | # Stop ospfd 65 | pid = read_pid(self.ospfd_pid) 66 | if pid: 67 | log.debug('Killing ospfd') 68 | self.call('kill', '-9', pid) 69 | del_file(self.ospf_cfg) 70 | del_file(self.ospfd_pid) 71 | # Stop zebra 72 | pid = read_pid(self.zebra_pid) 73 | if pid: 74 | log.debug('Killing zebra') 75 | self.call('kill', '-9', pid) 76 | del_file(self.zebra_cfg) 77 | del_file(self.zebra_pid) 78 | del_file(self.zebra_api) 79 | 80 | def start(self, *extra_args): 81 | """ 82 | Startup this router processes 83 | """ 84 | # Create a configuration node for this router 85 | cfg_node = self.get_config_node() 86 | # Generate ospf/zebra conf 87 | self.create_ospf_conf(cfg_node) 88 | self.create_zebra_conf(cfg_node) 89 | # Enable ipv4 forwarding 90 | self.call('sysctl', '-w', 'net.ipv4.ip_forward=1') 91 | self.call('sysctl', '-w', 'net.ipv4.icmp_errors_use_inbound_ifaddr=1') 92 | # Start zebra/ospf 93 | self.call(ZEBRA_EXEC, '-f', self.zebra_cfg, '-i', self.zebra_pid, 94 | '-z', self.zebra_api, '-d', '-k') 95 | time.sleep(.5) # Required to let zebra create its API socket ... 96 | self.call(OSPFD_EXEC, '-f', self.ospf_cfg, '-i', self.ospfd_pid, 97 | '-z', self.zebra_api, '-d', *extra_args) 98 | 99 | def get_config_node(self): 100 | return RouterConfigDict(self) 101 | 102 | def create_zebra_conf(self, confignode): 103 | self.render(ZEBRA_CFG_TEMPLATE, self.zebra_cfg, node=confignode) 104 | 105 | def create_ospf_conf(self, confignode): 106 | self.render(OSPF_CFG_TEMPLATE, self.ospf_cfg, node=confignode) 107 | 108 | @staticmethod 109 | def call(*args, **kwargs): 110 | return subprocess.call(args, **kwargs) 111 | 112 | @staticmethod 113 | def pipe(*args, **kwargs): 114 | return subprocess.Popen(args, 115 | stdin=subprocess.PIPE, 116 | stdout=subprocess.PIPE, 117 | stderr=subprocess.STDOUT, 118 | **kwargs) 119 | 120 | @staticmethod 121 | def render(template, dest, **kwargs): 122 | """ 123 | Render a Mako template passing it optional arguments 124 | """ 125 | log.debug('Generating %s\n' % dest) 126 | try: 127 | text = Template(filename=template).render(**kwargs) 128 | with closing(open(dest, 'w')) as f: 129 | f.write(text) 130 | except: 131 | # Display template errors in a less cryptic way 132 | log.error('Couldn''t render a config file (%s)', 133 | os.path.basename(template)) 134 | log.error(exceptions.text_error_template().render()) 135 | 136 | 137 | class VTYSH(object): 138 | """Proxy class to maintain a telnet session with a quagga daemon""" 139 | # How long should we keep the session open after inactivity? 140 | VTYSH_TIMEOUT = 5 141 | SHELL_INVITE = '>' 142 | ENABLED = '#' 143 | 144 | def __init__(self, hostname, port, node): 145 | """ 146 | :param hname: the hostname of the daemon 147 | :param port: the port number of the daemon 148 | :param id: the identifier to use for this vtysh in the logs 149 | """ 150 | self.hname = hostname 151 | self.port = port 152 | self.node = node 153 | self.id = node.id 154 | self.enabled_invite = '%s%s' % (self.id, self.ENABLED) 155 | self.shell_invite = '%s%s' % (self.id, self.SHELL_INVITE) 156 | # We currently don't connect to the daemon 157 | self.session = None 158 | # Handle synchronisation with expiration timer 159 | self.lock = Lock() 160 | 161 | def _open(self): 162 | self._debug('Opening VTYSH session') 163 | self.session = self.node.pipe('telnet', self.hname, str(self.port)) 164 | # First Quagga will ask us for the password 165 | self._read_until('Password:') 166 | self._enter(OSPFD_PASSWORD) 167 | self._read_until(self.shell_invite) 168 | # Enter privileged mode 169 | self._enter('enable') 170 | self._read_until(self.enabled_invite) 171 | # Start up the expiration timer for that session instance 172 | self._restart_timer() 173 | 174 | def __call__(self, *args, **kwargs): 175 | """ 176 | Execute a vtysh command on the corresponding daemon 177 | :param configure: Execute the command in a 178 | 'configure terminal' 'exit' block 179 | :param *args: list of command elements 180 | """ 181 | configure = kwargs.pop('configure', False) 182 | # We don't want the expiration timer to mess with us 183 | with self.lock: 184 | # Do we have a session already open? 185 | if not self.session: 186 | # create one otherwise 187 | self._open() 188 | # Cancel exp. timer 189 | self.reset_timer.cancel() 190 | if configure: 191 | self._enter('configure terminal') 192 | # Assemble and execute the command 193 | cmd_str = ' '.join(args) 194 | self._enter(cmd_str) 195 | if configure: 196 | self._enter('exit') 197 | out = self._read_until(self.enabled_invite) 198 | # Now that the session is idle again, restart the exp. timer 199 | self._restart_timer() 200 | return out[len(cmd_str) + 2:] 201 | 202 | def _enter(self, cmd): 203 | self._debug(cmd) 204 | self.session.stdin.write(cmd + '\n') 205 | 206 | def _restart_timer(self): 207 | self.reset_timer = Timer(interval=self.VTYSH_TIMEOUT, 208 | function=self._expire) 209 | self.reset_timer.start() 210 | 211 | def _read_until(self, delimiter): 212 | """Read and return the ouput of the shell until reaching 213 | the given delimiter (not included)""" 214 | # TODO optimize ... 215 | l = len(delimiter) 216 | b = self.session.stdout.read(l) 217 | while b[-l:] != delimiter: 218 | b += self.session.stdout.read(1) 219 | return b[:-l] 220 | 221 | def _expire(self): 222 | # If we don't own the lock this means the timer will be restarted 223 | if self.lock.acquire(True): 224 | self._debug('Closing VTYSH session') 225 | self._enter('exit') 226 | self.session.terminate() 227 | self.session = None 228 | self.lock.release() 229 | 230 | def _debug(self, message): 231 | log.debug('vtysh[%s]: %s', self.id, message) 232 | 233 | 234 | class RouterConfigDict(ConfigDict): 235 | def __init__(self, router, debug_ospf=(), debug_zebra=(), *args, **kwargs): 236 | super(RouterConfigDict, self).__init__(*args, **kwargs) 237 | self.hostname = router.name 238 | self.password = OSPFD_PASSWORD 239 | self.redistribute = ConfigDict() 240 | self.ospf = self.build_ospf(router) 241 | self.zebra = self.build_zebra(router) 242 | self.ospf.logfile = '/tmp/ospfd_%s.log' % router.name 243 | self.ospf.debug = debug_ospf 244 | self.zebra.logfile = '/tmp/zebra_%s.log' % router.name 245 | self.zebra.debug = debug_zebra 246 | 247 | def build_ospf(self, router, cfg=None): 248 | if not cfg: 249 | cfg = ConfigDict() 250 | if not cfg.redistribute: 251 | cfg.redistribute = ConfigDict() 252 | if not cfg.interfaces: 253 | cfg.interfaces = [] 254 | if not cfg.networks: 255 | cfg.networks = [] 256 | if not cfg.passive_interfaces: 257 | cfg.passive_interfaces = [] 258 | return cfg 259 | 260 | def build_zebra(self, router, cfg=None): 261 | if not cfg: 262 | cfg = ConfigDict() 263 | if not cfg.routemaps: 264 | cfg.routemaps = [] 265 | if not cfg.static_routes: 266 | cfg.static_routes = [] 267 | if not cfg.prefixlists: 268 | cfg.prefixlists = [] 269 | return cfg 270 | -------------------------------------------------------------------------------- /fibbingnode/misc/sjmp.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import cStringIO as StringIO 3 | import sys 4 | import os 5 | import json 6 | import socket 7 | import select 8 | import inspect 9 | import logging 10 | from urlparse import urlparse 11 | 12 | from .utils import start_daemon_thread 13 | 14 | log = logging.getLogger(__name__) 15 | 16 | 17 | def sock_readline(s): 18 | buf = '' 19 | data = True 20 | while data: 21 | data = s.recv(1) 22 | if data == '\n': 23 | yield buf 24 | buf = '' 25 | else: 26 | buf += data 27 | 28 | ARG_LIST = 'arg_list' 29 | ARG_DICT = 'arg_dict' 30 | CMD = 'cmd' 31 | CMD_ARG = 'cmd_arg' 32 | DISPLAY = 'display' 33 | EXEC = 'exec' 34 | EXCEPTION = 'exception' 35 | INFO = 'info' 36 | METHOD = 'method' 37 | PING = 'ping' 38 | PONG = 'pong' 39 | RESULT = 'result' 40 | 41 | 42 | class SimpleJSONMessagePassing(object): 43 | def __init__(self, socket, target=None, name='Unnamed JSON agent'): 44 | """ 45 | :param socket: A connected socket 46 | :param target: The object on which the commands should be executed 47 | (or self if None) 48 | :param name: The name of the JSON agent 49 | """ 50 | self.name = name 51 | self.s = socket 52 | self.stopped = True 53 | self.target = target if target else self 54 | self.hooks = { 55 | DISPLAY: self._json_display, 56 | EXEC: self._json_exec, 57 | EXCEPTION: self._json_exception, 58 | INFO: self._json_info, 59 | PING: self._json_ping, 60 | RESULT: self._json_result 61 | } 62 | 63 | def alive(self): 64 | return not self.stopped 65 | 66 | def communicate(self, timeout=5.0): 67 | """ 68 | Listen (until stop() is called) for incoming messages and execute the 69 | corresponding actions 70 | :param timeout: How long should we block at most before checking if we 71 | should stop listening 72 | """ 73 | self.stopped = False 74 | while not self.stopped: 75 | # Enforce read time out with select 76 | try: 77 | r, _, _ = select.select([self.s], [], [], timeout) 78 | except select.error: 79 | break 80 | else: 81 | if not r: 82 | # keepalive 83 | self._json_send(PING, {}) 84 | continue 85 | try: 86 | line = next(sock_readline(self.s)) 87 | except: 88 | log.debug('Socket is no longer readable, ' 89 | 'stopping communicate()') 90 | break 91 | else: 92 | try: 93 | decoded = json.loads(line, encoding='utf-8') 94 | except ValueError: 95 | log.debug('Malformed JSON message [%s] -- ignoring', 96 | line) 97 | # TODO - is it safe? 98 | continue 99 | else: 100 | for key, f in self.hooks.items(): 101 | if key == decoded[CMD]: 102 | f(decoded[CMD_ARG]) 103 | 104 | def stop(self): 105 | """ 106 | Stop listening for incoming messages 107 | """ 108 | log.debug('Stopped JSONMP agent %s', self.name) 109 | self.stopped = True 110 | 111 | def execute(self, method, *args, **kwargs): 112 | """ 113 | Execute a method on the remote end 114 | :param method: The method name 115 | :param args: the non-keywords arguments for that method 116 | :param kwargs: the keywords arguments for that method 117 | """ 118 | self._json_send(EXEC, { 119 | METHOD: method, 120 | ARG_LIST: args, 121 | ARG_DICT: kwargs 122 | }) 123 | 124 | def ask_info(self): 125 | """ 126 | Actively request the remote end to send us the descriptions 127 | of its exposed methods 128 | """ 129 | self._json_send(INFO, {}) 130 | 131 | def _json_exec(self, cmd_arg): 132 | """ 133 | Remote execute call 134 | """ 135 | try: 136 | method = getattr(self.target, cmd_arg[METHOD]) 137 | result = method(*cmd_arg.get(ARG_LIST, []), 138 | **cmd_arg.get(ARG_DICT, {})) 139 | except KeyError as e: 140 | self._send_exception(e, cmd_arg) 141 | except Exception as e: 142 | log.exception(e) 143 | self._send_exception(e, cmd_arg) 144 | else: 145 | # Send back command result if any 146 | if result: 147 | self._json_send(RESULT, result) 148 | 149 | @staticmethod 150 | def _json_exception(cmd_arg): 151 | """ 152 | Log remote exceptions 153 | """ 154 | log.error('%s generated a remote exception:\n\t%s', 155 | cmd_arg[CMD_ARG], cmd_arg[EXCEPTION]) 156 | 157 | @staticmethod 158 | def _json_result(cmd_arg): 159 | log.info('Remote result: %s', cmd_arg) 160 | 161 | @staticmethod 162 | def _json_display(cmd_arg): 163 | strs = [] 164 | for name, other in cmd_arg.items(): 165 | strs.append('\n%s: %s' 166 | % (name, ' '.join(filter(lambda x: not x == 'self', 167 | other['args'])))) 168 | if other['doc']: 169 | strs.append('%s' % other['doc']) 170 | log.info('%s', ''.join(strs)) 171 | 172 | def _json_info(self, cmd_arg): 173 | self._json_send(DISPLAY, { 174 | name: {'doc': m.__doc__, 'args': inspect.getargspec(m)[0]} 175 | for name, m in inspect.getmembers(self.target.__class__, 176 | predicate=inspect.ismethod) 177 | }) 178 | 179 | def _json_ping(self, cmd_arg): 180 | self._json_send(PONG, {}) 181 | 182 | def _send_exception(self, e, args): 183 | s = StringIO.StringIO() 184 | traceback.print_tb(sys.exc_info()[2], file=s) 185 | self._json_send(EXCEPTION, { 186 | CMD_ARG: args, 187 | EXCEPTION: '%s\n%s' % (str(e), s.getvalue()) 188 | }) 189 | s.close() 190 | 191 | def _json_send(self, cmd_name, cmd_dict): 192 | s = json.dumps({ 193 | CMD: cmd_name, 194 | CMD_ARG: cmd_dict 195 | }, encoding='utf-8') 196 | try: 197 | self.s.send(s) 198 | self.s.send('\n') 199 | except Exception as e: 200 | log.debug('Failed to send JSON data -- is the socket still alive? ' 201 | '(%s)', e) 202 | 203 | 204 | def _get_socket(hostname, port, unlink=False): 205 | url = urlparse(hostname) 206 | if url.scheme != 'unix': 207 | af = socket.AF_INET 208 | args = (hostname, port) 209 | else: 210 | af = socket.AF_UNIX 211 | args = url.path 212 | log.info('Listening on unix socket: %s', args) 213 | if unlink and os.path.exists(args): 214 | os.unlink(args) 215 | s = socket.socket(af, socket.SOCK_STREAM) 216 | return s, args 217 | 218 | 219 | class SJMPServer(): 220 | """ 221 | Sample Server that will accept one client per call to communicate 222 | """ 223 | def __init__(self, hostname, port, 224 | invoke=None, target=None, max_clients=5): 225 | """ 226 | :param hostname: Hostname on which to listen, '' to accept any origin 227 | :param port: The TCP port to listen on 228 | :param invoke: The method to call at a new client connection 229 | :param target: The object to expose, will fallback to self if None 230 | :param max_clients: The max number of concurrent connection 231 | """ 232 | s, pathspec = _get_socket(hostname, port, unlink=True) 233 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 234 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 235 | s.bind(pathspec) 236 | self.server_socket = s 237 | s.listen(max_clients) 238 | self.invoke = invoke 239 | self.target = target 240 | self.client_count = 0 241 | 242 | """This call never returns!""" 243 | def communicate(self, timeout=5.0, *args, **kwargs): 244 | while True: 245 | def accept(): 246 | r, _, _ = select.select([self.server_socket], [], [], timeout) 247 | if not r: 248 | return False 249 | return self.server_socket.accept()[0] 250 | try: 251 | client = False 252 | while not client: 253 | client = accept() 254 | except socket.error: 255 | return 256 | else: 257 | self.client_count += 1 258 | thread = start_daemon_thread( 259 | target=_new_server_client, 260 | args=(client, self.invoke, self.target), 261 | name='SJMPClient%s' % self.client_count) 262 | 263 | def stop(self): 264 | self.server_socket.close() 265 | 266 | 267 | def _new_server_client(sock, invoke, target): 268 | log.debug('New SJMP client') 269 | sjmp = SimpleJSONMessagePassing(sock, target=target) 270 | if invoke: 271 | invoke(sjmp) 272 | sjmp.communicate() 273 | sock.close() 274 | if invoke: 275 | invoke(sjmp) 276 | log.debug('Closed connection to an SJMP client') 277 | 278 | 279 | class SJMPClient(SimpleJSONMessagePassing): 280 | """ 281 | Sample Client that will connect to a server 282 | """ 283 | def __init__(self, hostname, port, target=None): 284 | """ 285 | :param hostname: The hostname of the server 286 | :param port: The TCP port it is listening on 287 | :param target: The object to expose, will fallback to self if None 288 | """ 289 | s, pathspec = _get_socket(hostname, port) 290 | s.connect(pathspec) 291 | super(SJMPClient, self).__init__(s, target=target, name='SJMPClient') 292 | 293 | def stop(self): 294 | super(SJMPClient, self).stop() 295 | self.s.close() 296 | 297 | 298 | class ProxyCloner(object): 299 | """ 300 | Class that will mimic an object methods but in fact 301 | send the calls over the networks 302 | """ 303 | def __init__(self, proxy_class, session): 304 | """ 305 | :param proxy_class: The class to mimic 306 | (e.g. one exposed in interface.py) 307 | :param session: An object who has an execute method 308 | (ideally an SimpleJSONMessagePassing instance) 309 | """ 310 | for name, _ in inspect.getmembers(proxy_class, 311 | predicate=inspect.ismethod): 312 | setattr(self, name, _ProxyMethod(name, session)) 313 | self.session = session 314 | 315 | 316 | class _ProxyMethod(object): 317 | def __init__(self, name, session): 318 | self.session = session 319 | self.name = name 320 | 321 | def __call__(self, *args, **kwargs): 322 | self.session.execute(self.name, *args, **kwargs) 323 | -------------------------------------------------------------------------------- /topologies/weights-dist/3967/latencies.intra: -------------------------------------------------------------------------------- 1 | San+Jose,+CA471 Santa+Clara,+CA444 2 2 | San+Jose,+CA471 Santa+Clara,+CA389 2 3 | San+Jose,+CA471 San+Jose,+CA472 1 4 | San+Jose,+CA471 Oak+Brook,+IL301 16 5 | San+Jose,+CA472 San+Jose,+CA471 1 6 | San+Jose,+CA472 Santa+Clara,+CA430 2 7 | San+Jose,+CA472 Santa+Clara,+CA389 2 8 | San+Jose,+CA472 Santa+Clara,+CA431 2 9 | Weehawken,+NJ543 New+York,+NY293 2 10 | Weehawken,+NJ543 Atlanta,+GA127 7 11 | Weehawken,+NJ543 New+York,+NY294 2 12 | Weehawken,+NJ543 Weehawken,+NJ544 1 13 | Weehawken,+NJ543 Weehawken,+NJ552 1 14 | Weehawken,+NJ543 Oak+Brook,+IL300 7 15 | Weehawken,+NJ543 Jersey+City,+NJ244 2 16 | Oak+Brook,+IL307 Oak+Brook,+IL315 1 17 | Oak+Brook,+IL307 Oak+Brook,+IL300 1 18 | Oak+Brook,+IL307 Oak+Brook,+IL301 1 19 | Herndon,+VA193 Herndon,+VA496 1 20 | Herndon,+VA193 Atlanta,+GA126 6 21 | Herndon,+VA193 Atlanta,+GA127 6 22 | Weehawken,+NJ544 Jersey+City,+NJ245 2 23 | Weehawken,+NJ544 London274 29 24 | Weehawken,+NJ544 London275 29 25 | Weehawken,+NJ544 London276 29 26 | Weehawken,+NJ544 Weehawken,+NJ543 1 27 | Weehawken,+NJ544 Weehawken,+NJ552 1 28 | Weehawken,+NJ544 New+York,+NY293 2 29 | Weehawken,+NJ544 Herndon,+VA495 3 30 | Oak+Brook,+IL308 Oak+Brook,+IL315 1 31 | Oak+Brook,+IL308 Oak+Brook,+IL300 1 32 | Oak+Brook,+IL308 Oak+Brook,+IL301 1 33 | Toronto,+Canada537 Waltham,+MA555 2 34 | Oak+Brook,+IL309 Oak+Brook,+IL300 1 35 | Oak+Brook,+IL309 Oak+Brook,+IL301 1 36 | Toronto,+Canada538 Oak+Brook,+IL300 8 37 | Palo+Alto,+CA317 Santa+Clara,+CA388 2 38 | Palo+Alto,+CA317 Palo+Alto,+CA104 1 39 | Palo+Alto,+CA317 Santa+Clara,+CA336 2 40 | Palo+Alto,+CA317 Palo+Alto,+CA318 1 41 | Palo+Alto,+CA318 Santa+Clara,+CA363 2 42 | Palo+Alto,+CA318 Santa+Clara,+CA364 2 43 | Palo+Alto,+CA318 Palo+Alto,+CA317 1 44 | Amsterdam119 London274 3 45 | Amsterdam119 London275 3 46 | Amsterdam119 Frankfurt185 3 47 | Tukwila,+WA508 Tukwila,+WA509 1 48 | Tukwila,+WA508 Santa+Clara,+CA429 7 49 | Tukwila,+WA508 Chicago,+IL155 15 50 | Oak+Brook,+IL310 Oak+Brook,+IL300 1 51 | Oak+Brook,+IL310 Oak+Brook,+IL301 1 52 | Tukwila,+WA509 Chicago,+IL155 15 53 | Tukwila,+WA509 Tukwila,+WA508 1 54 | Santa+Clara,+CA388 Santa+Clara,+CA430 1 55 | Santa+Clara,+CA388 Santa+Clara,+CA389 1 56 | Santa+Clara,+CA388 Palo+Alto,+CA317 2 57 | Santa+Clara,+CA388 San+Jose,+CA460 2 58 | Palo+Alto,+CA104 Santa+Clara,+CA444 2 59 | Palo+Alto,+CA104 Santa+Clara,+CA365 2 60 | Palo+Alto,+CA104 Palo+Alto,+CA317 1 61 | Palo+Alto,+CA104 Santa+Clara,+CA403 2 62 | Santa+Clara,+CA389 San+Jose,+CA471 2 63 | Santa+Clara,+CA389 Santa+Clara,+CA388 1 64 | Santa+Clara,+CA389 San+Jose,+CA472 2 65 | Santa+Clara,+CA389 Santa+Clara,+CA443 1 66 | Santa+Clara,+CA389 Santa+Clara,+CA403 1 67 | Oak+Brook,+IL315 Oak+Brook,+IL307 1 68 | Oak+Brook,+IL315 Oak+Brook,+IL300 1 69 | Oak+Brook,+IL315 Oak+Brook,+IL308 1 70 | Oak+Brook,+IL315 Oak+Brook,+IL301 1 71 | Weehawken,+NJ552 Weehawken,+NJ543 1 72 | Weehawken,+NJ552 Weehawken,+NJ544 1 73 | Weehawken,+NJ552 Oak+Brook,+IL300 7 74 | Chicago,+IL155 Tukwila,+WA509 15 75 | Chicago,+IL155 Oak+Brook,+IL301 2 76 | Chicago,+IL155 Chicago,+IL156 1 77 | Chicago,+IL155 Tukwila,+WA508 15 78 | Chicago,+IL156 Oak+Brook,+IL300 2 79 | Chicago,+IL156 Fort+Worth,+TX189 8 80 | Chicago,+IL156 Chicago,+IL155 1 81 | Fort+Worth,+TX189 Irvine,+CA228 11 82 | Fort+Worth,+TX189 Austin,+TX137 3 83 | Fort+Worth,+TX189 Fort+Worth,+TX190 1 84 | Fort+Worth,+TX189 Chicago,+IL156 8 85 | Fort+Worth,+TX189 Santa+Clara,+CA403 13 86 | Fort+Worth,+TX189 Fort+Worth,+TX191 1 87 | Herndon,+VA495 Herndon,+VA496 1 88 | Herndon,+VA495 Herndon,+VA208 1 89 | Herndon,+VA495 Weehawken,+NJ544 3 90 | Jersey+City,+NJ244 Jersey+City,+NJ261 1 91 | Jersey+City,+NJ244 Frankfurt184 32 92 | Jersey+City,+NJ244 Jersey+City,+NJ245 1 93 | Jersey+City,+NJ244 Weehawken,+NJ543 2 94 | Jersey+City,+NJ244 Waltham,+MA568 3 95 | Jersey+City,+NJ244 London277 29 96 | Jersey+City,+NJ244 Waltham,+MA569 3 97 | Herndon,+VA496 Santa+Clara,+CA429 21 98 | Herndon,+VA496 Herndon,+VA193 1 99 | Herndon,+VA496 Herndon,+VA495 1 100 | Irvine,+CA212 El+Segundo,+CA163 2 101 | Tokyo525 Santa+Clara,+CA404 43 102 | Tokyo525 Tokyo526 1 103 | Irvine,+CA213 Santa+Clara,+CA404 4 104 | Irvine,+CA213 Irvine,+CA228 1 105 | Irvine,+CA213 El+Segundo,+CA164 2 106 | Jersey+City,+NJ245 Jersey+City,+NJ261 1 107 | Jersey+City,+NJ245 London274 29 108 | Jersey+City,+NJ245 New+York,+NY294 2 109 | Jersey+City,+NJ245 London275 29 110 | Jersey+City,+NJ245 London276 29 111 | Jersey+City,+NJ245 Weehawken,+NJ544 2 112 | Jersey+City,+NJ245 Jersey+City,+NJ244 1 113 | Tokyo526 Tokyo525 1 114 | Tokyo526 Santa+Clara,+CA336 43 115 | Tokyo526 San+Jose,+CA460 44 116 | Waltham,+MA555 Waltham,+MA556 1 117 | Waltham,+MA555 Waltham,+MA568 1 118 | Waltham,+MA555 Oak+Brook,+IL300 8 119 | Waltham,+MA555 Toronto,+Canada537 2 120 | Waltham,+MA556 Oak+Brook,+IL300 8 121 | Waltham,+MA556 Waltham,+MA569 1 122 | Waltham,+MA556 Waltham,+MA555 1 123 | Herndon,+VA206 New+York,+NY293 3 124 | Herndon,+VA206 Herndon,+VA208 1 125 | Santa+Clara,+CA429 Herndon,+VA496 21 126 | Santa+Clara,+CA429 Santa+Clara,+CA430 1 127 | Santa+Clara,+CA429 Santa+Clara,+CA431 1 128 | Santa+Clara,+CA429 San+Jose,+CA459 2 129 | Santa+Clara,+CA429 Tukwila,+WA508 7 130 | Santa+Clara,+CA429 Santa+Clara,+CA403 1 131 | Herndon,+VA208 Herndon,+VA206 1 132 | Herndon,+VA208 New+York,+NY293 3 133 | Herndon,+VA208 Herndon,+VA495 1 134 | Atlanta,+GA126 Atlanta,+GA127 1 135 | Atlanta,+GA126 Herndon,+VA193 6 136 | Atlanta,+GA126 Atlanta,+GA133 1 137 | Miami,+FL285 New+York,+NY293 10 138 | Miami,+FL285 Miami,+FL286 1 139 | Atlanta,+GA127 Atlanta,+GA126 1 140 | Atlanta,+GA127 Miami,+FL286 6 141 | Atlanta,+GA127 Weehawken,+NJ543 7 142 | Atlanta,+GA127 Herndon,+VA193 6 143 | Atlanta,+GA127 Fort+Worth,+TX190 8 144 | Atlanta,+GA127 Atlanta,+GA133 1 145 | Miami,+FL286 Miami,+FL285 1 146 | Miami,+FL286 Atlanta,+GA127 6 147 | Fort+Worth,+TX190 Irvine,+CA228 11 148 | Fort+Worth,+TX190 Atlanta,+GA127 8 149 | Fort+Worth,+TX190 Irvine,+CA229 11 150 | Fort+Worth,+TX190 Austin,+TX136 3 151 | Fort+Worth,+TX190 Fort+Worth,+TX189 1 152 | Fort+Worth,+TX190 Fort+Worth,+TX191 1 153 | San+Jose,+CA459 Santa+Clara,+CA429 2 154 | San+Jose,+CA459 San+Jose,+CA460 1 155 | Fort+Worth,+TX191 Fort+Worth,+TX189 1 156 | Fort+Worth,+TX191 Fort+Worth,+TX190 1 157 | New+York,+NY293 Miami,+FL285 10 158 | New+York,+NY293 Herndon,+VA206 3 159 | New+York,+NY293 Herndon,+VA208 3 160 | New+York,+NY293 New+York,+NY294 1 161 | New+York,+NY293 Weehawken,+NJ543 2 162 | New+York,+NY293 Weehawken,+NJ544 2 163 | New+York,+NY294 Jersey+City,+NJ245 2 164 | New+York,+NY294 New+York,+NY293 1 165 | New+York,+NY294 Weehawken,+NJ543 2 166 | London274 Jersey+City,+NJ245 29 167 | London274 Amsterdam119 3 168 | London274 Weehawken,+NJ544 29 169 | London275 Jersey+City,+NJ245 29 170 | London275 Amsterdam119 3 171 | London275 Weehawken,+NJ544 29 172 | Santa+Clara,+CA430 Santa+Clara,+CA404 1 173 | Santa+Clara,+CA430 Santa+Clara,+CA364 1 174 | Santa+Clara,+CA430 Santa+Clara,+CA429 1 175 | Santa+Clara,+CA430 Santa+Clara,+CA388 1 176 | Santa+Clara,+CA430 San+Jose,+CA472 2 177 | Santa+Clara,+CA430 Santa+Clara,+CA336 1 178 | Santa+Clara,+CA430 Santa+Clara,+CA443 1 179 | London276 Jersey+City,+NJ245 29 180 | London276 Weehawken,+NJ544 29 181 | Santa+Clara,+CA431 Santa+Clara,+CA363 1 182 | Santa+Clara,+CA431 Santa+Clara,+CA429 1 183 | Santa+Clara,+CA431 Santa+Clara,+CA364 1 184 | Santa+Clara,+CA431 Santa+Clara,+CA365 1 185 | Santa+Clara,+CA431 San+Jose,+CA472 2 186 | Santa+Clara,+CA431 Santa+Clara,+CA443 1 187 | London277 Jersey+City,+NJ244 29 188 | Santa+Clara,+CA363 Santa+Clara,+CA431 1 189 | Santa+Clara,+CA363 Palo+Alto,+CA318 2 190 | Irvine,+CA228 Irvine,+CA213 1 191 | Irvine,+CA228 Irvine,+CA229 1 192 | Irvine,+CA228 Irvine,+CA237 1 193 | Irvine,+CA228 Irvine,+CA230 1 194 | Irvine,+CA228 Irvine,+CA231 1 195 | Irvine,+CA228 Fort+Worth,+TX189 11 196 | Irvine,+CA228 Fort+Worth,+TX190 11 197 | Santa+Clara,+CA364 Santa+Clara,+CA430 1 198 | Santa+Clara,+CA364 Santa+Clara,+CA431 1 199 | Santa+Clara,+CA364 Palo+Alto,+CA318 2 200 | Irvine,+CA229 Irvine,+CA228 1 201 | Irvine,+CA229 El+Segundo,+CA164 2 202 | Irvine,+CA229 Fort+Worth,+TX190 11 203 | Santa+Clara,+CA365 Palo+Alto,+CA104 2 204 | Santa+Clara,+CA365 Santa+Clara,+CA431 1 205 | Waltham,+MA568 Waltham,+MA569 1 206 | Waltham,+MA568 Jersey+City,+NJ244 3 207 | Waltham,+MA568 Waltham,+MA555 1 208 | Waltham,+MA569 Waltham,+MA556 1 209 | Waltham,+MA569 Waltham,+MA568 1 210 | Waltham,+MA569 Jersey+City,+NJ244 3 211 | Atlanta,+GA133 Atlanta,+GA126 1 212 | Atlanta,+GA133 Atlanta,+GA127 1 213 | San+Jose,+CA460 Tokyo526 44 214 | San+Jose,+CA460 Santa+Clara,+CA388 2 215 | San+Jose,+CA460 San+Jose,+CA459 1 216 | San+Jose,+CA460 Santa+Clara,+CA443 2 217 | El+Segundo,+CA163 Irvine,+CA212 2 218 | El+Segundo,+CA163 Santa+Clara,+CA404 4 219 | El+Segundo,+CA163 Santa+Clara,+CA405 4 220 | El+Segundo,+CA163 El+Segundo,+CA164 1 221 | El+Segundo,+CA163 Austin,+TX137 11 222 | Austin,+TX136 Fort+Worth,+TX190 3 223 | El+Segundo,+CA164 Irvine,+CA213 2 224 | El+Segundo,+CA164 Irvine,+CA229 2 225 | El+Segundo,+CA164 Irvine,+CA230 2 226 | El+Segundo,+CA164 Irvine,+CA231 2 227 | El+Segundo,+CA164 El+Segundo,+CA163 1 228 | El+Segundo,+CA164 Santa+Clara,+CA403 4 229 | El+Segundo,+CA164 Santa+Clara,+CA443 4 230 | Austin,+TX137 El+Segundo,+CA163 11 231 | Austin,+TX137 Fort+Worth,+TX189 3 232 | Santa+Clara,+CA403 Santa+Clara,+CA444 1 233 | Santa+Clara,+CA403 Santa+Clara,+CA404 1 234 | Santa+Clara,+CA403 Santa+Clara,+CA429 1 235 | Santa+Clara,+CA403 Santa+Clara,+CA405 1 236 | Santa+Clara,+CA403 Palo+Alto,+CA104 2 237 | Santa+Clara,+CA403 Santa+Clara,+CA389 1 238 | Santa+Clara,+CA403 Fort+Worth,+TX189 13 239 | Santa+Clara,+CA403 El+Segundo,+CA164 4 240 | Jersey+City,+NJ261 Jersey+City,+NJ245 1 241 | Jersey+City,+NJ261 Jersey+City,+NJ244 1 242 | Santa+Clara,+CA404 Santa+Clara,+CA444 1 243 | Santa+Clara,+CA404 Irvine,+CA213 4 244 | Santa+Clara,+CA404 Tokyo525 43 245 | Santa+Clara,+CA404 Santa+Clara,+CA430 1 246 | Santa+Clara,+CA404 El+Segundo,+CA163 4 247 | Santa+Clara,+CA404 Santa+Clara,+CA443 1 248 | Santa+Clara,+CA404 Santa+Clara,+CA403 1 249 | Santa+Clara,+CA405 El+Segundo,+CA163 4 250 | Santa+Clara,+CA405 Santa+Clara,+CA403 1 251 | Irvine,+CA230 Irvine,+CA228 1 252 | Irvine,+CA230 El+Segundo,+CA164 2 253 | Irvine,+CA231 Irvine,+CA228 1 254 | Irvine,+CA231 Irvine,+CA237 1 255 | Irvine,+CA231 El+Segundo,+CA164 2 256 | Santa+Clara,+CA336 Tokyo526 43 257 | Santa+Clara,+CA336 Santa+Clara,+CA430 1 258 | Santa+Clara,+CA336 Palo+Alto,+CA317 2 259 | Santa+Clara,+CA443 Santa+Clara,+CA444 1 260 | Santa+Clara,+CA443 Santa+Clara,+CA404 1 261 | Santa+Clara,+CA443 Santa+Clara,+CA430 1 262 | Santa+Clara,+CA443 Santa+Clara,+CA431 1 263 | Santa+Clara,+CA443 Santa+Clara,+CA389 1 264 | Santa+Clara,+CA443 El+Segundo,+CA164 4 265 | Santa+Clara,+CA443 San+Jose,+CA460 2 266 | Santa+Clara,+CA444 Santa+Clara,+CA404 1 267 | Santa+Clara,+CA444 San+Jose,+CA471 2 268 | Santa+Clara,+CA444 Palo+Alto,+CA104 2 269 | Santa+Clara,+CA444 Santa+Clara,+CA443 1 270 | Santa+Clara,+CA444 Santa+Clara,+CA403 1 271 | Frankfurt184 Jersey+City,+NJ244 32 272 | Irvine,+CA237 Irvine,+CA228 1 273 | Irvine,+CA237 Irvine,+CA231 1 274 | Frankfurt185 Amsterdam119 3 275 | Oak+Brook,+IL300 Weehawken,+NJ543 7 276 | Oak+Brook,+IL300 Oak+Brook,+IL307 1 277 | Oak+Brook,+IL300 Oak+Brook,+IL315 1 278 | Oak+Brook,+IL300 Weehawken,+NJ552 7 279 | Oak+Brook,+IL300 Oak+Brook,+IL308 1 280 | Oak+Brook,+IL300 Oak+Brook,+IL309 1 281 | Oak+Brook,+IL300 Chicago,+IL156 2 282 | Oak+Brook,+IL300 Toronto,+Canada538 8 283 | Oak+Brook,+IL300 Waltham,+MA555 8 284 | Oak+Brook,+IL300 Waltham,+MA556 8 285 | Oak+Brook,+IL300 Oak+Brook,+IL301 1 286 | Oak+Brook,+IL300 Oak+Brook,+IL310 1 287 | Oak+Brook,+IL301 San+Jose,+CA471 16 288 | Oak+Brook,+IL301 Oak+Brook,+IL307 1 289 | Oak+Brook,+IL301 Oak+Brook,+IL315 1 290 | Oak+Brook,+IL301 Oak+Brook,+IL308 1 291 | Oak+Brook,+IL301 Chicago,+IL155 2 292 | Oak+Brook,+IL301 Oak+Brook,+IL309 1 293 | Oak+Brook,+IL301 Oak+Brook,+IL300 1 294 | Oak+Brook,+IL301 Oak+Brook,+IL310 1 295 | -------------------------------------------------------------------------------- /topologies/weights-dist/3967/weights.intra: -------------------------------------------------------------------------------- 1 | San+Jose,+CA471 Santa+Clara,+CA444 4.5 2 | San+Jose,+CA471 Santa+Clara,+CA389 4 3 | San+Jose,+CA471 San+Jose,+CA472 3.5 4 | San+Jose,+CA471 Oak+Brook,+IL301 14.5 5 | San+Jose,+CA472 San+Jose,+CA471 3.5 6 | San+Jose,+CA472 Santa+Clara,+CA430 2 7 | San+Jose,+CA472 Santa+Clara,+CA389 3.5 8 | San+Jose,+CA472 Santa+Clara,+CA431 2.5 9 | Weehawken,+NJ543 New+York,+NY293 8 10 | Weehawken,+NJ543 Atlanta,+GA127 18.5 11 | Weehawken,+NJ543 New+York,+NY294 2.5 12 | Weehawken,+NJ543 Weehawken,+NJ544 2 13 | Weehawken,+NJ543 Weehawken,+NJ552 2 14 | Weehawken,+NJ543 Oak+Brook,+IL300 12 15 | Weehawken,+NJ543 Jersey+City,+NJ244 2 16 | Oak+Brook,+IL307 Oak+Brook,+IL315 2 17 | Oak+Brook,+IL307 Oak+Brook,+IL300 2 18 | Oak+Brook,+IL307 Oak+Brook,+IL301 2 19 | Herndon,+VA193 Herndon,+VA496 3 20 | Herndon,+VA193 Atlanta,+GA126 4.5 21 | Herndon,+VA193 Atlanta,+GA127 7.5 22 | Weehawken,+NJ544 Jersey+City,+NJ245 2 23 | Weehawken,+NJ544 London274 4 24 | Weehawken,+NJ544 London275 4.5 25 | Weehawken,+NJ544 London276 4 26 | Weehawken,+NJ544 Weehawken,+NJ543 2 27 | Weehawken,+NJ544 Weehawken,+NJ552 2 28 | Weehawken,+NJ544 New+York,+NY293 5 29 | Weehawken,+NJ544 Herndon,+VA495 4 30 | Oak+Brook,+IL308 Oak+Brook,+IL315 2 31 | Oak+Brook,+IL308 Oak+Brook,+IL300 2 32 | Oak+Brook,+IL308 Oak+Brook,+IL301 2 33 | Toronto,+Canada537 Waltham,+MA555 1 34 | Oak+Brook,+IL309 Oak+Brook,+IL300 2 35 | Oak+Brook,+IL309 Oak+Brook,+IL301 2.5 36 | Toronto,+Canada538 Oak+Brook,+IL300 1 37 | Palo+Alto,+CA317 Santa+Clara,+CA388 3 38 | Palo+Alto,+CA317 Palo+Alto,+CA104 2.5 39 | Palo+Alto,+CA317 Santa+Clara,+CA336 2.5 40 | Palo+Alto,+CA317 Palo+Alto,+CA318 2 41 | Palo+Alto,+CA318 Santa+Clara,+CA363 2 42 | Palo+Alto,+CA318 Santa+Clara,+CA364 2 43 | Palo+Alto,+CA318 Palo+Alto,+CA317 2 44 | Amsterdam119 London274 2 45 | Amsterdam119 London275 5.5 46 | Amsterdam119 Frankfurt185 1 47 | Tukwila,+WA508 Tukwila,+WA509 2 48 | Tukwila,+WA508 Santa+Clara,+CA429 7 49 | Tukwila,+WA508 Chicago,+IL155 9 50 | Oak+Brook,+IL310 Oak+Brook,+IL300 2 51 | Oak+Brook,+IL310 Oak+Brook,+IL301 3.5 52 | Tukwila,+WA509 Chicago,+IL155 6 53 | Tukwila,+WA509 Tukwila,+WA508 2 54 | Santa+Clara,+CA388 Santa+Clara,+CA430 2 55 | Santa+Clara,+CA388 Santa+Clara,+CA389 2.5 56 | Santa+Clara,+CA388 Palo+Alto,+CA317 3 57 | Santa+Clara,+CA388 San+Jose,+CA460 3 58 | Palo+Alto,+CA104 Santa+Clara,+CA444 2.5 59 | Palo+Alto,+CA104 Santa+Clara,+CA365 2 60 | Palo+Alto,+CA104 Palo+Alto,+CA317 2.5 61 | Palo+Alto,+CA104 Santa+Clara,+CA403 2 62 | Santa+Clara,+CA389 San+Jose,+CA471 4 63 | Santa+Clara,+CA389 Santa+Clara,+CA388 2.5 64 | Santa+Clara,+CA389 San+Jose,+CA472 3.5 65 | Santa+Clara,+CA389 Santa+Clara,+CA443 4 66 | Santa+Clara,+CA389 Santa+Clara,+CA403 3 67 | Oak+Brook,+IL315 Oak+Brook,+IL307 2 68 | Oak+Brook,+IL315 Oak+Brook,+IL300 2 69 | Oak+Brook,+IL315 Oak+Brook,+IL308 2 70 | Oak+Brook,+IL315 Oak+Brook,+IL301 2.5 71 | Weehawken,+NJ552 Weehawken,+NJ543 2 72 | Weehawken,+NJ552 Weehawken,+NJ544 2 73 | Weehawken,+NJ552 Oak+Brook,+IL300 13 74 | Chicago,+IL155 Tukwila,+WA509 6 75 | Chicago,+IL155 Oak+Brook,+IL301 2 76 | Chicago,+IL155 Chicago,+IL156 2.5 77 | Chicago,+IL155 Tukwila,+WA508 9 78 | Chicago,+IL156 Oak+Brook,+IL300 2 79 | Chicago,+IL156 Fort+Worth,+TX189 7 80 | Chicago,+IL156 Chicago,+IL155 2.5 81 | Fort+Worth,+TX189 Irvine,+CA228 17 82 | Fort+Worth,+TX189 Austin,+TX137 4 83 | Fort+Worth,+TX189 Fort+Worth,+TX190 2 84 | Fort+Worth,+TX189 Chicago,+IL156 7 85 | Fort+Worth,+TX189 Santa+Clara,+CA403 16 86 | Fort+Worth,+TX189 Fort+Worth,+TX191 3.5 87 | Herndon,+VA495 Herndon,+VA496 2 88 | Herndon,+VA495 Herndon,+VA208 5 89 | Herndon,+VA495 Weehawken,+NJ544 4 90 | Jersey+City,+NJ244 Jersey+City,+NJ261 4 91 | Jersey+City,+NJ244 Frankfurt184 1 92 | Jersey+City,+NJ244 Jersey+City,+NJ245 2 93 | Jersey+City,+NJ244 Weehawken,+NJ543 2 94 | Jersey+City,+NJ244 Waltham,+MA568 5 95 | Jersey+City,+NJ244 London277 1 96 | Jersey+City,+NJ244 Waltham,+MA569 2 97 | Herndon,+VA496 Santa+Clara,+CA429 22.5 98 | Herndon,+VA496 Herndon,+VA193 3 99 | Herndon,+VA496 Herndon,+VA495 2 100 | Irvine,+CA212 El+Segundo,+CA163 1 101 | Tokyo525 Santa+Clara,+CA404 2.5 102 | Tokyo525 Tokyo526 2 103 | Irvine,+CA213 Santa+Clara,+CA404 10 104 | Irvine,+CA213 Irvine,+CA228 4 105 | Irvine,+CA213 El+Segundo,+CA164 2 106 | Jersey+City,+NJ245 Jersey+City,+NJ261 3 107 | Jersey+City,+NJ245 London274 2 108 | Jersey+City,+NJ245 New+York,+NY294 2.5 109 | Jersey+City,+NJ245 London275 2.5 110 | Jersey+City,+NJ245 London276 2 111 | Jersey+City,+NJ245 Weehawken,+NJ544 2 112 | Jersey+City,+NJ245 Jersey+City,+NJ244 2 113 | Tokyo526 Tokyo525 2 114 | Tokyo526 Santa+Clara,+CA336 2.5 115 | Tokyo526 San+Jose,+CA460 2 116 | Waltham,+MA555 Waltham,+MA556 2 117 | Waltham,+MA555 Waltham,+MA568 2 118 | Waltham,+MA555 Oak+Brook,+IL300 13 119 | Waltham,+MA555 Toronto,+Canada537 1 120 | Waltham,+MA556 Oak+Brook,+IL300 10 121 | Waltham,+MA556 Waltham,+MA569 7 122 | Waltham,+MA556 Waltham,+MA555 2 123 | Herndon,+VA206 New+York,+NY293 2 124 | Herndon,+VA206 Herndon,+VA208 2 125 | Santa+Clara,+CA429 Herndon,+VA496 22.5 126 | Santa+Clara,+CA429 Santa+Clara,+CA430 2 127 | Santa+Clara,+CA429 Santa+Clara,+CA431 5.5 128 | Santa+Clara,+CA429 San+Jose,+CA459 2 129 | Santa+Clara,+CA429 Tukwila,+WA508 7 130 | Santa+Clara,+CA429 Santa+Clara,+CA403 4.5 131 | Herndon,+VA208 Herndon,+VA206 2 132 | Herndon,+VA208 New+York,+NY293 4 133 | Herndon,+VA208 Herndon,+VA495 5 134 | Atlanta,+GA126 Atlanta,+GA127 3 135 | Atlanta,+GA126 Herndon,+VA193 4.5 136 | Atlanta,+GA126 Atlanta,+GA133 4 137 | Miami,+FL285 New+York,+NY293 5 138 | Miami,+FL285 Miami,+FL286 8 139 | Atlanta,+GA127 Atlanta,+GA126 3 140 | Atlanta,+GA127 Miami,+FL286 2.5 141 | Atlanta,+GA127 Weehawken,+NJ543 18.5 142 | Atlanta,+GA127 Herndon,+VA193 7.5 143 | Atlanta,+GA127 Fort+Worth,+TX190 3.5 144 | Atlanta,+GA127 Atlanta,+GA133 2 145 | Miami,+FL286 Miami,+FL285 8 146 | Miami,+FL286 Atlanta,+GA127 2.5 147 | Fort+Worth,+TX190 Irvine,+CA228 14 148 | Fort+Worth,+TX190 Atlanta,+GA127 3.5 149 | Fort+Worth,+TX190 Irvine,+CA229 16 150 | Fort+Worth,+TX190 Austin,+TX136 1 151 | Fort+Worth,+TX190 Fort+Worth,+TX189 2 152 | Fort+Worth,+TX190 Fort+Worth,+TX191 2.5 153 | San+Jose,+CA459 Santa+Clara,+CA429 2 154 | San+Jose,+CA459 San+Jose,+CA460 6 155 | Fort+Worth,+TX191 Fort+Worth,+TX189 3.5 156 | Fort+Worth,+TX191 Fort+Worth,+TX190 2.5 157 | New+York,+NY293 Miami,+FL285 5 158 | New+York,+NY293 Herndon,+VA206 2 159 | New+York,+NY293 Herndon,+VA208 4 160 | New+York,+NY293 New+York,+NY294 5.5 161 | New+York,+NY293 Weehawken,+NJ543 8 162 | New+York,+NY293 Weehawken,+NJ544 5 163 | New+York,+NY294 Jersey+City,+NJ245 2.5 164 | New+York,+NY294 New+York,+NY293 5.5 165 | New+York,+NY294 Weehawken,+NJ543 2.5 166 | London274 Jersey+City,+NJ245 2 167 | London274 Amsterdam119 2 168 | London274 Weehawken,+NJ544 4 169 | London275 Jersey+City,+NJ245 2.5 170 | London275 Amsterdam119 5.5 171 | London275 Weehawken,+NJ544 4.5 172 | Santa+Clara,+CA430 Santa+Clara,+CA404 4.5 173 | Santa+Clara,+CA430 Santa+Clara,+CA364 5.5 174 | Santa+Clara,+CA430 Santa+Clara,+CA429 2 175 | Santa+Clara,+CA430 Santa+Clara,+CA388 2 176 | Santa+Clara,+CA430 San+Jose,+CA472 2 177 | Santa+Clara,+CA430 Santa+Clara,+CA336 2.5 178 | Santa+Clara,+CA430 Santa+Clara,+CA443 3.5 179 | London276 Jersey+City,+NJ245 2 180 | London276 Weehawken,+NJ544 4 181 | Santa+Clara,+CA431 Santa+Clara,+CA363 2 182 | Santa+Clara,+CA431 Santa+Clara,+CA429 5.5 183 | Santa+Clara,+CA431 Santa+Clara,+CA364 2 184 | Santa+Clara,+CA431 Santa+Clara,+CA365 4 185 | Santa+Clara,+CA431 San+Jose,+CA472 2.5 186 | Santa+Clara,+CA431 Santa+Clara,+CA443 4 187 | London277 Jersey+City,+NJ244 1 188 | Santa+Clara,+CA363 Santa+Clara,+CA431 2 189 | Santa+Clara,+CA363 Palo+Alto,+CA318 2 190 | Irvine,+CA228 Irvine,+CA213 4 191 | Irvine,+CA228 Irvine,+CA229 2 192 | Irvine,+CA228 Irvine,+CA237 2 193 | Irvine,+CA228 Irvine,+CA230 3 194 | Irvine,+CA228 Irvine,+CA231 2 195 | Irvine,+CA228 Fort+Worth,+TX189 17 196 | Irvine,+CA228 Fort+Worth,+TX190 14 197 | Santa+Clara,+CA364 Santa+Clara,+CA430 5.5 198 | Santa+Clara,+CA364 Santa+Clara,+CA431 2 199 | Santa+Clara,+CA364 Palo+Alto,+CA318 2 200 | Irvine,+CA229 Irvine,+CA228 2 201 | Irvine,+CA229 El+Segundo,+CA164 3 202 | Irvine,+CA229 Fort+Worth,+TX190 16 203 | Santa+Clara,+CA365 Palo+Alto,+CA104 2 204 | Santa+Clara,+CA365 Santa+Clara,+CA431 4 205 | Waltham,+MA568 Waltham,+MA569 2 206 | Waltham,+MA568 Jersey+City,+NJ244 5 207 | Waltham,+MA568 Waltham,+MA555 2 208 | Waltham,+MA569 Waltham,+MA556 7 209 | Waltham,+MA569 Waltham,+MA568 2 210 | Waltham,+MA569 Jersey+City,+NJ244 2 211 | Atlanta,+GA133 Atlanta,+GA126 4 212 | Atlanta,+GA133 Atlanta,+GA127 2 213 | San+Jose,+CA460 Tokyo526 2 214 | San+Jose,+CA460 Santa+Clara,+CA388 3 215 | San+Jose,+CA460 San+Jose,+CA459 6 216 | San+Jose,+CA460 Santa+Clara,+CA443 2.5 217 | El+Segundo,+CA163 Irvine,+CA212 1 218 | El+Segundo,+CA163 Santa+Clara,+CA404 5 219 | El+Segundo,+CA163 Santa+Clara,+CA405 3 220 | El+Segundo,+CA163 El+Segundo,+CA164 2 221 | El+Segundo,+CA163 Austin,+TX137 16 222 | Austin,+TX136 Fort+Worth,+TX190 1 223 | El+Segundo,+CA164 Irvine,+CA213 2 224 | El+Segundo,+CA164 Irvine,+CA229 3 225 | El+Segundo,+CA164 Irvine,+CA230 2 226 | El+Segundo,+CA164 Irvine,+CA231 3 227 | El+Segundo,+CA164 El+Segundo,+CA163 2 228 | El+Segundo,+CA164 Santa+Clara,+CA403 9 229 | El+Segundo,+CA164 Santa+Clara,+CA443 6 230 | Austin,+TX137 El+Segundo,+CA163 16 231 | Austin,+TX137 Fort+Worth,+TX189 4 232 | Santa+Clara,+CA403 Santa+Clara,+CA444 2.5 233 | Santa+Clara,+CA403 Santa+Clara,+CA404 2 234 | Santa+Clara,+CA403 Santa+Clara,+CA429 4.5 235 | Santa+Clara,+CA403 Santa+Clara,+CA405 5 236 | Santa+Clara,+CA403 Palo+Alto,+CA104 2 237 | Santa+Clara,+CA403 Santa+Clara,+CA389 3 238 | Santa+Clara,+CA403 Fort+Worth,+TX189 16 239 | Santa+Clara,+CA403 El+Segundo,+CA164 9 240 | Jersey+City,+NJ261 Jersey+City,+NJ245 3 241 | Jersey+City,+NJ261 Jersey+City,+NJ244 4 242 | Santa+Clara,+CA404 Santa+Clara,+CA444 4.5 243 | Santa+Clara,+CA404 Irvine,+CA213 10 244 | Santa+Clara,+CA404 Tokyo525 2.5 245 | Santa+Clara,+CA404 Santa+Clara,+CA430 4.5 246 | Santa+Clara,+CA404 El+Segundo,+CA163 5 247 | Santa+Clara,+CA404 Santa+Clara,+CA443 3 248 | Santa+Clara,+CA404 Santa+Clara,+CA403 2 249 | Santa+Clara,+CA405 El+Segundo,+CA163 3 250 | Santa+Clara,+CA405 Santa+Clara,+CA403 5 251 | Irvine,+CA230 Irvine,+CA228 3 252 | Irvine,+CA230 El+Segundo,+CA164 2 253 | Irvine,+CA231 Irvine,+CA228 2 254 | Irvine,+CA231 Irvine,+CA237 3 255 | Irvine,+CA231 El+Segundo,+CA164 3 256 | Santa+Clara,+CA336 Tokyo526 2.5 257 | Santa+Clara,+CA336 Santa+Clara,+CA430 2.5 258 | Santa+Clara,+CA336 Palo+Alto,+CA317 2.5 259 | Santa+Clara,+CA443 Santa+Clara,+CA444 2.5 260 | Santa+Clara,+CA443 Santa+Clara,+CA404 3 261 | Santa+Clara,+CA443 Santa+Clara,+CA430 3.5 262 | Santa+Clara,+CA443 Santa+Clara,+CA431 4 263 | Santa+Clara,+CA443 Santa+Clara,+CA389 4 264 | Santa+Clara,+CA443 El+Segundo,+CA164 6 265 | Santa+Clara,+CA443 San+Jose,+CA460 2.5 266 | Santa+Clara,+CA444 Santa+Clara,+CA404 4.5 267 | Santa+Clara,+CA444 San+Jose,+CA471 4.5 268 | Santa+Clara,+CA444 Palo+Alto,+CA104 2.5 269 | Santa+Clara,+CA444 Santa+Clara,+CA443 2.5 270 | Santa+Clara,+CA444 Santa+Clara,+CA403 2.5 271 | Frankfurt184 Jersey+City,+NJ244 1 272 | Irvine,+CA237 Irvine,+CA228 2 273 | Irvine,+CA237 Irvine,+CA231 3 274 | Frankfurt185 Amsterdam119 1 275 | Oak+Brook,+IL300 Weehawken,+NJ543 12 276 | Oak+Brook,+IL300 Oak+Brook,+IL307 2 277 | Oak+Brook,+IL300 Oak+Brook,+IL315 2 278 | Oak+Brook,+IL300 Weehawken,+NJ552 13 279 | Oak+Brook,+IL300 Oak+Brook,+IL308 2 280 | Oak+Brook,+IL300 Oak+Brook,+IL309 2 281 | Oak+Brook,+IL300 Chicago,+IL156 2 282 | Oak+Brook,+IL300 Toronto,+Canada538 1 283 | Oak+Brook,+IL300 Waltham,+MA555 13 284 | Oak+Brook,+IL300 Waltham,+MA556 10 285 | Oak+Brook,+IL300 Oak+Brook,+IL301 2.5 286 | Oak+Brook,+IL300 Oak+Brook,+IL310 2 287 | Oak+Brook,+IL301 San+Jose,+CA471 14.5 288 | Oak+Brook,+IL301 Oak+Brook,+IL307 2 289 | Oak+Brook,+IL301 Oak+Brook,+IL315 2.5 290 | Oak+Brook,+IL301 Oak+Brook,+IL308 2 291 | Oak+Brook,+IL301 Chicago,+IL155 2 292 | Oak+Brook,+IL301 Oak+Brook,+IL309 2.5 293 | Oak+Brook,+IL301 Oak+Brook,+IL300 2.5 294 | Oak+Brook,+IL301 Oak+Brook,+IL310 3.5 295 | -------------------------------------------------------------------------------- /fibbingnode/algorithms/utils.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | import sys 3 | import functools 4 | import collections 5 | from fibbingnode import log as log 6 | from fibbingnode.misc.utils import extend_paths_list, is_container 7 | import networkx as nx 8 | 9 | 10 | def find_sink(dag): 11 | """Find all sinks in a given DAG 12 | :type dag: DiGraph 13 | :return: list of nodes whose out degree is 0 aka sinks""" 14 | for n, deg in dag.out_degree_iter(): 15 | if deg == 0: 16 | yield n 17 | 18 | 19 | def add_separate_destination_to_sinks(destination, input_topo, dag, cost=1): 20 | if destination in input_topo: 21 | destination = "Dest_" + destination 22 | for node in find_sink(dag): 23 | log.debug("Connecting %s to %s with cost %d", 24 | destination, node, cost) 25 | input_topo.add_edge(node, destination, metric=cost) 26 | dag.add_edge(node, destination) 27 | return destination 28 | 29 | 30 | def all_shortest_paths(g, metric='metric'): 31 | """Return all shortest paths for all pairs of node in the graph 32 | :type g: DiGraph""" 33 | return {n: single_source_all_sp(g, n, metric=metric) for n in g} 34 | 35 | 36 | def single_source_all_sp(g, source, metric='metric'): 37 | """Return the list of all shortest paths originatig from src, 38 | and their associated costs. 39 | 40 | :type g: DiGraph 41 | :return: {dst: [[x y z], [a b c], ...]}, {dst: cost} 42 | 43 | Adapted from single_source_dijkstra in networkx. 44 | # Copyright (C) 2004-2010 by 45 | # Aric Hagberg 46 | # Dan Schult 47 | # Pieter Swart 48 | # All rights reserved. 49 | # BSD license. 50 | """ 51 | dist = {} # dictionary of final distances 52 | paths = {source: [[source]]} # dictionary of list of paths 53 | seen = {source: 0} 54 | fringe = [] # use heapq with (distance,label) tuples 55 | heapq.heappush(fringe, (0, source)) 56 | while fringe: 57 | (d, v) = heapq.heappop(fringe) 58 | if v in dist: 59 | continue # already searched this node. 60 | dist[v] = d 61 | for w, edgedata in g[v].items(): 62 | vw_dist = d + edgedata.get(metric, 1) 63 | seen_w = seen.get(w, sys.maxint) 64 | if vw_dist < dist.get(w, 0): 65 | raise ValueError('Contradictory paths found: ' 66 | 'negative "%s"?' % metric) 67 | elif vw_dist < seen_w: # vw is better than the old path 68 | seen[w] = vw_dist 69 | heapq.heappush(fringe, (vw_dist, w)) 70 | paths[w] = list(extend_paths_list(paths[v], w)) 71 | elif vw_dist == seen_w: # vw is ECMP 72 | paths[w].extend(extend_paths_list(paths[v], w)) 73 | return paths, dist 74 | 75 | 76 | def dag_paths_from_leaves(dag, target): 77 | paths = [] 78 | for leaf, _ in filter(lambda x: x[1] == 0, dag.in_degree_iter()): 79 | paths.extend(nx.all_simple_paths(dag, leaf, target)) 80 | return paths 81 | 82 | 83 | class MaxHeap(object): 84 | def __init__(self, initial_elem=()): 85 | self.pq = map(_ReverseCompare, initial_elem) 86 | heapq.heapify(self.pq) 87 | 88 | def push(self, *item): 89 | for i in item: 90 | heapq.heappush(self.pq, _ReverseCompare(i)) 91 | 92 | def pop(self): 93 | return heapq.heappop(self.pq).obj 94 | 95 | def is_empty(self): 96 | return len(self.pq) == 0 97 | 98 | def __repr__(self): 99 | return ', '.join(str(item) for item in self.pq) 100 | 101 | 102 | # http://stackoverflow.com/questions/12681772 103 | # CC BY-SA 3.0 104 | @functools.total_ordering 105 | class _ReverseCompare(object): 106 | def __init__(self, obj): 107 | self.obj = obj 108 | 109 | def __eq__(self, other): 110 | return self.obj == other.obj 111 | 112 | def __le__(self, other): 113 | return self.obj >= other.obj 114 | 115 | def __repr__(self): 116 | return repr(self.obj) 117 | 118 | 119 | """A tuple whose fields can be accessed by their names representing a LSA""" 120 | LSA = collections.namedtuple('LSA', 'node nh cost dest') 121 | 122 | 123 | def LocalLie(prefix, edge_src, edge_dst, ipindex=1): 124 | return LSA(edge_src, edge_dst, -ipindex, prefix) 125 | 126 | 127 | def GlobalLie(dest, cost, nh, node=None): 128 | return LSA(node, nh, cost, dest) 129 | 130 | 131 | class ExtendedLSA(object): 132 | def __init__(self, node, nh, routes): 133 | self.node = node 134 | self.nh = nh 135 | self.routes = routes 136 | 137 | def __repr__(self): 138 | return 'EXTLSA(node=%s, nh=%s, routes=%s)' % (self.node, 139 | self.nh, 140 | self.routes) 141 | 142 | 143 | ExtLSARoute = collections.namedtuple('ExtLSARoute', 'dest cost') 144 | 145 | 146 | def _add_fake_route(g, n, d, **kw): 147 | """Wrapper around IGPGraph.add_fake_route in case we have a normal 148 | DiGraph""" 149 | try: 150 | g.add_fake_route(n, d, **kw) 151 | except AttributeError: 152 | g.add_edge(n, d, **kw) 153 | 154 | 155 | def _is_fake_dest(g, d): 156 | """Test whether d is reachable through at least one non-fake link""" 157 | try: 158 | for p in g.predecessors_iter(d): 159 | if g.is_real_route(p, d): 160 | return False 161 | except AttributeError: 162 | return False 163 | return True 164 | 165 | 166 | def add_dest_to_graph(dest, graph, edges_src=None, spt=None, 167 | node_data_gen=None, **kw): 168 | """Add dest to the graph, possibly updating the shortest paths object 169 | 170 | :param dest: The destination node, will be set as a prefix 171 | :param graph: The graph to which dest must be added if not present 172 | :param edges_src: The source of edges to add in order to add dest, 173 | if None, defaults to the sinks in the graph, 174 | otherwise it is a function returning a list of edges 175 | and taking dest as argument 176 | :param spt: The ShortestPath object to update to account for the new node 177 | if applicable 178 | :param node_data_gen: A function that will generate data for the new node 179 | if needed 180 | :param kw: Extra parameters for the edges if any""" 181 | added = None 182 | if dest in graph and not _is_fake_dest(graph, dest): 183 | # Unless dest was only announced through fake links, we don't touch it 184 | log.debug('%s is already in the graph', dest) 185 | in_dag = True 186 | else: 187 | in_dag = False 188 | if not edges_src: 189 | added = [] 190 | sinks = find_sink(graph) 191 | if not sinks: 192 | log.info('No sinks found in the graph!') 193 | for node in sinks: 194 | if node == dest: 195 | continue 196 | log.info('Connected %s to %s in the graph', node, dest) 197 | _add_fake_route(graph, node, dest, **kw) 198 | added.append(node) 199 | else: 200 | added = edges_src(dest) 201 | log.info('Connecting edges sources %s to the graph to %s', 202 | dest, added) 203 | for s in added: 204 | _add_fake_route(graph, s, dest, **kw) 205 | graph.add_edges_from((s, dest) for s in added, **kw) 206 | ndata = {} if not node_data_gen else node_data_gen() 207 | # Only update the dest node if explicitely requested 208 | if node_data_gen or not in_dag: 209 | graph.add_node(dest, prefix=True, **ndata) 210 | if added and spt: 211 | log.info('Updating SPT') 212 | _update_paths_towards(spt, graph, dest, added) 213 | 214 | 215 | def _update_paths_towards(spt, g, dest, added_edges): 216 | """Update the shortest paths by adding some new edges towards a 217 | destination 218 | ! The destination should not be in the already existing SPT! 219 | :param g: The graph 220 | :param dest: The added destination 221 | :param added_edges: The source of the added edges""" 222 | __update_default_paths(spt, g, dest, added_edges) 223 | __update_fibbed_paths(spt, g, dest, added_edges) 224 | 225 | 226 | def __update_default_paths(spt, g, dest, added): 227 | spt._default_paths[dest] = {dest: [[dest]]} 228 | spt._default_dist[dest] = {dest: 0} 229 | for n in g.routers: 230 | paths = [] 231 | cost = sys.maxint 232 | for s in added: 233 | try: 234 | c = spt.default_cost(n, s) + g.metric(s, dest) 235 | except KeyError: # No path from n to s, skip 236 | continue 237 | p = spt.default_path(n, s) 238 | if c < cost: # new spt towards s is n-p-s 239 | paths = list(extend_paths_list(p, dest)) 240 | cost = c 241 | elif c == cost: # ecmp 242 | paths.extend(extend_paths_list(p, dest)) 243 | if paths: 244 | log.debug('Adding paths (cost: %s): %s', cost, paths) 245 | spt._default_paths[n][dest] = paths 246 | spt._default_dist[n][dest] = cost 247 | 248 | 249 | def __update_fibbed_paths(spt, g, dest, added): 250 | pass 251 | 252 | 253 | def complete_dag(dag, graph, dest, paths, skip=()): 254 | """Complete the DAG with all SPT from the graph towards 255 | destinations that are not yet in the dag 256 | 257 | :param dag: the dag to complete 258 | :param graph: the graph to explore 259 | :param dest: the destination to consider 260 | :param paths: a ShortestPath object 261 | :param skip: nodes that must not be considered""" 262 | for n in filter(lambda r: (r not in dag and r not in skip and 263 | graph.successors(r)), 264 | graph.routers): 265 | for p in paths.default_path(n, dest): 266 | for u, v in zip(p[:-1], p[1:]): 267 | v_in_dag = v in dag 268 | dag.add_edge(u, v) 269 | if v_in_dag: # we connected u to the new SPT 270 | break 271 | 272 | 273 | def solvable(dag, graph): 274 | """Check that the given DAG can be embedded in the graph""" 275 | for u, v in dag.edges_iter(): 276 | try: 277 | graph[u][v] 278 | except KeyError: 279 | log.error('Cannot satisfy the DAG ' 280 | ' as (%s, %s) is not in the IGP graph', 281 | u, v) 282 | log.error('Available edges: %s', graph.edges()) 283 | log.error('DAG: %s', dag.edges()) 284 | return False 285 | return True 286 | 287 | 288 | def DFS(generator, consumer, generate_from=None, *elems): 289 | """Perform a Depth First Search (DFS). 290 | 291 | :param generator: The function that will generate the next set of 292 | element to examinate from the current one 293 | :param consumer: The function that will consume one element. 294 | If it returns a single iterable, feed them to the 295 | generator 296 | If it returns a 2-tuple (x, y), feed x to the generator 297 | and yield y 298 | :param generate_from: A starting element to feed to the generator 299 | :param elems: Elements to add in the original set to visit""" 300 | visited = set() 301 | to_visit = set(elems) 302 | if generate_from: 303 | to_visit |= set(generator(generate_from)) 304 | while to_visit: 305 | n = to_visit.pop() 306 | if n in visited: 307 | continue 308 | visited.add(n) 309 | ret = consumer(n) 310 | try: 311 | remains, to_yield = ret 312 | if to_yield: 313 | yield to_yield 314 | except TypeError: 315 | remains = ret 316 | if remains: 317 | if not is_container(remains): 318 | remains = (remains,) 319 | to_visit |= set(*map(generator, remains)) 320 | -------------------------------------------------------------------------------- /fibbingnode/southbound/entities.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | import subprocess 3 | import sys 4 | import os 5 | 6 | import fibbingnode 7 | from lsdb import LSDB 8 | from fibbingnode.misc.utils import require_cmd, force, ConfigDict 9 | from fibbingnode.misc.router import QuaggaRouter, RouterConfigDict 10 | from namespaces import NetworkNamespace, RootNamespace 11 | 12 | log = fibbingnode.log 13 | 14 | # Ensures that we have a temp directory to store all configs, ... 15 | RUN = '/run/quagga' 16 | 17 | LSDB_LOG_PATH = os.path.join(RUN, 'lsdb.log') 18 | 19 | if not os.path.exists(RUN): 20 | import grp 21 | import pwd 22 | 23 | os.mkdir(RUN) 24 | os.chown(RUN, pwd.getpwnam('quagga').pw_uid, grp.getgrnam('quagga').gr_gid) 25 | 26 | 27 | class Node(object): 28 | """ 29 | Base network nodes: a dict of ports 30 | """ 31 | 32 | def __init__(self, id, prefix): 33 | """ 34 | :param id: The identifier for this node 35 | :param prefix: The namespace prefix 36 | """ 37 | if not prefix: 38 | raise Exception('Controller nodes require a prefix!') 39 | self.id = '%s_%s' % (prefix, id) 40 | self.next_port = -1 41 | self.interfaces = OrderedDict() 42 | 43 | def get_next_port(self): 44 | """ 45 | :return: the next available port number on this node 46 | """ 47 | self.next_port += 1 48 | return self.next_port 49 | 50 | def add_port(self, port): 51 | """ 52 | Associate the given port to this node 53 | :param port: The Port object to register 54 | """ 55 | self.interfaces[port] = port 56 | 57 | def del_port(self, port): 58 | """ 59 | Remove the given port from this device 60 | :param port: a Port object 61 | """ 62 | del self.interfaces[port] 63 | 64 | def call(self, *args, **kwargs): 65 | """ 66 | Execute a command on this node 67 | """ 68 | return subprocess.call(args, **kwargs) 69 | 70 | def pipe(self, *args, **kwargs): 71 | """ 72 | Execute a command on this node and return an object that 73 | has communicate() available for use with stdin/stdout 74 | """ 75 | return subprocess.Popen(args, 76 | stdin=subprocess.PIPE, 77 | stdout=subprocess.PIPE, 78 | stderr=subprocess.STDOUT, 79 | **kwargs) 80 | 81 | def start(self): 82 | self.call('ip', 'link', 'set', 'lo', 'up') 83 | 84 | 85 | class Bridge(Node): 86 | """ 87 | A Layer-2 hub 88 | """ 89 | 90 | def __init__(self, id='br0', *args, **kwargs): 91 | require_cmd('brctl', 92 | 'Look for package bridge-utils in your package manager') 93 | super(Bridge, self).__init__(id, *args, **kwargs) 94 | # Check that this bridge id is available 95 | if self.id in subprocess.check_output(['brctl', 'show']): 96 | # Otherwise destroy it 97 | self.delete() 98 | # Create the bridge 99 | self.brctl('addbr') 100 | self.call('ip', 'link', 'set', self.id, 'up') 101 | 102 | def brctl(self, cmd, *args): 103 | """ 104 | Wrapper around the brctl command 105 | """ 106 | cmd = ['brctl', cmd, self.id] 107 | cmd.extend(args) 108 | log.debug('Bridge command: %s', cmd) 109 | err = self.call(*cmd) 110 | if err != 0: 111 | log.error('%s has failed!', ' '.join(cmd)) 112 | sys.exit(1) 113 | 114 | def add_port(self, port): 115 | super(Bridge, self).add_port(port) 116 | log.debug('Adding interface %s to %s', port.id, self.id) 117 | return self.brctl('addif', port.id) 118 | 119 | def del_port(self, port): 120 | super(Bridge, self).del_port(port) 121 | log.debug('Removing interface %s from %s', port.id, self.id) 122 | return self.brctl('delif', port.id) 123 | 124 | def delete(self): 125 | """ 126 | Disable and delete this bridge 127 | """ 128 | self.call('ip', 'link', 'set', self.id, 'down') 129 | self.brctl('delbr') 130 | 131 | 132 | class FibbingRouter(QuaggaRouter): 133 | def __init__(self, node, *args, **kwargs): 134 | super(FibbingRouter, self).__init__(name=node.id, 135 | working_dir=RUN, 136 | *args, **kwargs) 137 | self.fibbingnode = node 138 | 139 | def call(self, *args, **kwargs): 140 | return self.fibbingnode.call(*args, **kwargs) 141 | 142 | def pipe(self, *args, **kwargs): 143 | return self.fibbingnode.pipe(*args, **kwargs) 144 | 145 | def get_config_node(self): 146 | return FibbingConfigNode(self.fibbingnode) 147 | 148 | 149 | class Router(Node): 150 | """ 151 | A fibbing router 152 | """ 153 | ID = 0 154 | 155 | def __init__(self, ospf_priority=1, id=None, namespaced=True, 156 | *args, **kwargs): 157 | """ 158 | :param id: Router id, set automatically if None 159 | :param namespaced: Run the router in a new network namespace? 160 | """ 161 | # Create new router id if needed 162 | if not id: 163 | Router.ID += 1 164 | id = 'r%d' % Router.ID 165 | super(Router, self).__init__(id, *args, **kwargs) 166 | # Basic routers have lowest priority 167 | self.ospf_priority = ospf_priority 168 | self.name = self.id 169 | self.ns = NetworkNamespace() if namespaced else RootNamespace() 170 | self.router = FibbingRouter(self) 171 | 172 | def delete(self): 173 | self.router.delete() 174 | self.ns.delete() 175 | 176 | def add_port(self, port): 177 | super(Router, self).add_port(port) 178 | # Move the port into our network namespace 179 | return self.ns.capture_port(port) 180 | 181 | def __str__(self): 182 | """ 183 | :return: nsname: port | port | port 184 | """ 185 | return '%s: %s' % (self.ns.name, 186 | ' | '.join([str(port) 187 | for port in self.interfaces.values()])) 188 | 189 | def start(self, *extra_args): 190 | """ 191 | Startup this router processes 192 | """ 193 | super(Router, self).start() 194 | self.router.start(*extra_args) 195 | 196 | def call(self, *args, **kwargs): 197 | # Redirect the call to happen inside this namespace 198 | return self.ns.call(*args, **kwargs) 199 | 200 | def pipe(self, *args, **kwargs): 201 | return self.ns.pipe(*args, **kwargs) 202 | 203 | def _fibbing(self, *args, **kwargs): 204 | cmd = ['ip', 'ospf', 'fibbing'] 205 | if kwargs.get('no', False): 206 | cmd.insert(0, 'no') 207 | cmd.extend(args) 208 | self.router.vtysh(*cmd, configure=True) 209 | 210 | def advertize(self, prefix, via, metric, ttl): 211 | self._fibbing(str(prefix), 'via', str(via), 'cost', str(metric), 212 | 'ttl', str(ttl)) 213 | 214 | def retract(self, prefix): 215 | self._fibbing(prefix, no=True) 216 | 217 | def vtysh(self, *args, **kwargs): 218 | log.debug('vtysh call: %s', ' '.join(args)) 219 | return self.router.vtysh(*args, **kwargs) 220 | 221 | 222 | class RootRouter(Router): 223 | """ 224 | The fibbing router that will interact with the real ones in the network 225 | """ 226 | 227 | def __init__(self, **kwargs): 228 | super(RootRouter, self).__init__(ospf_priority=2, **kwargs) 229 | # Register the physical ports apart 230 | self.physical_links = [] 231 | self.lsdb_log_file = None 232 | self.lsdb_log_file_name = '%s_%s' % (LSDB_LOG_PATH, self.id) 233 | if os.path.exists(self.lsdb_log_file_name): 234 | os.unlink(self.lsdb_log_file_name) 235 | os.mkfifo(self.lsdb_log_file_name) 236 | self.lsdb = LSDB() 237 | 238 | def add_physical_link(self, link): 239 | """ 240 | Add physical ports to its assigned ports 241 | :param link: A PhysicalLink to 'the outside world' 242 | """ 243 | self.physical_links.append(link) 244 | 245 | def __str__(self): 246 | top = ' | '.join([str(port) for port in self.physical_links]) 247 | return '%s\n%s\n%s%s\n%s' %\ 248 | (top, 249 | '-' * len(top), 250 | ' ' * ((len(top) - len(self.id)) / 2), 251 | self.id, 252 | ' | '.join([str(port) 253 | for port in self.interfaces.values() 254 | if port not in self.physical_links])) 255 | 256 | def start(self, *extra_args): 257 | super(RootRouter, self).start('--log_lsdb', 258 | self.lsdb_log_file_name, 259 | *extra_args) 260 | 261 | def delete(self): 262 | self.lsdb.stop() 263 | for p in self.physical_links: 264 | p.move_to_root() 265 | super(RootRouter, self).delete() 266 | if self.lsdb_log_file: 267 | force(self.lsdb_log_file.close) 268 | force(os.unlink, self.lsdb_log_file_name) 269 | 270 | def parse_lsdblog(self): 271 | def fifo_readline(f): 272 | buf = '' 273 | data = True 274 | while data: 275 | data = f.read(1) 276 | buf += data 277 | if data == '\n': 278 | yield buf 279 | buf = '' 280 | 281 | self.lsdb_log_file = open(self.lsdb_log_file_name, 'r') 282 | for line in fifo_readline(self.lsdb_log_file): 283 | try: 284 | self.lsdb.commit_change(line[:-1]) 285 | except Exception as e: 286 | # We do not want to crash the whole node ... 287 | # rather log the error 288 | # And stop parsing the LSDB 289 | log.error('Failed to parse LSDB update %s [%s]', line, str(e)) 290 | log.exception(e) 291 | log.debug('Stopped updating the LSDB') 292 | 293 | def send_lsdblog_to(self, listener): 294 | self.lsdb.register_change_listener(listener) 295 | 296 | def get_fwd_address(self, src, dst): 297 | """Return the list of forwarding address from src to dst""" 298 | fwd = self.lsdb.forwarding_address_of(src, dst) 299 | log.debug('fwding address of %s-%s is %s', src, dst, fwd) 300 | return fwd 301 | 302 | 303 | class FibbingConfigNode(RouterConfigDict): 304 | """ 305 | A router configuration node, 306 | Generates/extracts/formats the information needed by zebra/ospf from 307 | the router object 308 | """ 309 | 310 | def __init__(self, router): 311 | """ 312 | Create a configuration node reflecting the router 313 | :param router: The router object to analyze 314 | """ 315 | super(FibbingConfigNode, self).__init__(router) 316 | self.ospf.redistribute.fibbing = True 317 | 318 | def build_ospf(self, router): 319 | interfaces = [] 320 | networks = [] 321 | for intf in router.interfaces.values(): 322 | intf_dict = ConfigDict(name=intf.id, description=str(intf.link)) 323 | intf_dict.ospf = ConfigDict( 324 | cost=intf.ospf_cost, 325 | priority=router.ospf_priority, 326 | dead_int=intf.ospf_dead_int, 327 | hello_int=intf.ospf_hello_int 328 | ) 329 | interfaces.append(intf_dict) 330 | net_dict = ConfigDict(domain=intf.ip_interface 331 | .network.with_prefixlen, 332 | area=intf.ospf_area) 333 | networks.append(net_dict) 334 | return super(FibbingConfigNode, 335 | self).build_ospf(router, 336 | ConfigDict(interfaces=interfaces, 337 | # id is the first interface 338 | router_id=str(router 339 | .interfaces 340 | .values()[0] 341 | .ip_interface 342 | .ip), 343 | networks=networks)) 344 | -------------------------------------------------------------------------------- /topologies/weights-dist/3967/3967_pops_continent_inter_abr_pop.entf: -------------------------------------------------------------------------------- 1 | San+Jose,+CA471 Santa+Clara,+CA444 45 US 2 | San+Jose,+CA471 Santa+Clara,+CA389 40 US 3 | San+Jose,+CA471 San+Jose,+CA472 35 US 4 | San+Jose,+CA471 Oak+Brook,+IL301 145 US 5 | San+Jose,+CA472 San+Jose,+CA471 35 US 6 | San+Jose,+CA472 Santa+Clara,+CA430 20 US 7 | San+Jose,+CA472 Santa+Clara,+CA389 35 US 8 | San+Jose,+CA472 Santa+Clara,+CA431 25 US 9 | Weehawken,+NJ543 New+York,+NY293 80 US 10 | Weehawken,+NJ543 Atlanta,+GA127 185 US 11 | Weehawken,+NJ543 New+York,+NY294 25 US 12 | Weehawken,+NJ543 Weehawken,+NJ544 20 US 13 | Weehawken,+NJ543 Weehawken,+NJ552 20 US 14 | Weehawken,+NJ543 Oak+Brook,+IL300 120 US 15 | Weehawken,+NJ543 Jersey+City,+NJ244 20 US 16 | Oak+Brook,+IL307 Oak+Brook,+IL315 20 US 17 | Oak+Brook,+IL307 Oak+Brook,+IL300 20 US 18 | Oak+Brook,+IL307 Oak+Brook,+IL301 20 US 19 | Herndon,+VA193 Herndon,+VA496 30 US 20 | Herndon,+VA193 Atlanta,+GA126 45 US 21 | Herndon,+VA193 Atlanta,+GA127 75 US 22 | Weehawken,+NJ544 Jersey+City,+NJ245 20 US 23 | Weehawken,+NJ544 London274 40 BACKBONE 24 | Weehawken,+NJ544 London275 45 BACKBONE 25 | Weehawken,+NJ544 London276 40 BACKBONE 26 | Weehawken,+NJ544 Weehawken,+NJ543 20 US 27 | Weehawken,+NJ544 Weehawken,+NJ552 20 US 28 | Weehawken,+NJ544 New+York,+NY293 50 US 29 | Weehawken,+NJ544 Herndon,+VA495 40 US 30 | Oak+Brook,+IL308 Oak+Brook,+IL315 20 US 31 | Oak+Brook,+IL308 Oak+Brook,+IL300 20 US 32 | Oak+Brook,+IL308 Oak+Brook,+IL301 20 US 33 | Toronto,+Canada537 Waltham,+MA555 10 US 34 | Oak+Brook,+IL309 Oak+Brook,+IL300 20 US 35 | Oak+Brook,+IL309 Oak+Brook,+IL301 25 US 36 | Toronto,+Canada538 Oak+Brook,+IL300 10 US 37 | Palo+Alto,+CA317 Santa+Clara,+CA388 30 US 38 | Palo+Alto,+CA317 Palo+Alto,+CA104 25 US 39 | Palo+Alto,+CA317 Santa+Clara,+CA336 25 US 40 | Palo+Alto,+CA317 Palo+Alto,+CA318 20 US 41 | Palo+Alto,+CA318 Santa+Clara,+CA363 20 US 42 | Palo+Alto,+CA318 Santa+Clara,+CA364 20 US 43 | Palo+Alto,+CA318 Palo+Alto,+CA317 20 US 44 | Amsterdam119 London274 20 EU 45 | Amsterdam119 London275 55 EU 46 | Amsterdam119 Frankfurt185 10 EU 47 | Tukwila,+WA508 Tukwila,+WA509 20 US 48 | Tukwila,+WA508 Santa+Clara,+CA429 70 US 49 | Tukwila,+WA508 Chicago,+IL155 90 US 50 | Oak+Brook,+IL310 Oak+Brook,+IL300 20 US 51 | Oak+Brook,+IL310 Oak+Brook,+IL301 35 US 52 | Tukwila,+WA509 Chicago,+IL155 60 US 53 | Tukwila,+WA509 Tukwila,+WA508 20 US 54 | Santa+Clara,+CA388 Santa+Clara,+CA430 20 US 55 | Santa+Clara,+CA388 Santa+Clara,+CA389 25 US 56 | Santa+Clara,+CA388 Palo+Alto,+CA317 30 US 57 | Santa+Clara,+CA388 San+Jose,+CA460 30 US 58 | Palo+Alto,+CA104 Santa+Clara,+CA444 25 US 59 | Palo+Alto,+CA104 Santa+Clara,+CA365 20 US 60 | Palo+Alto,+CA104 Palo+Alto,+CA317 25 US 61 | Palo+Alto,+CA104 Santa+Clara,+CA403 20 US 62 | Santa+Clara,+CA389 San+Jose,+CA471 40 US 63 | Santa+Clara,+CA389 Santa+Clara,+CA388 25 US 64 | Santa+Clara,+CA389 San+Jose,+CA472 35 US 65 | Santa+Clara,+CA389 Santa+Clara,+CA443 40 US 66 | Santa+Clara,+CA389 Santa+Clara,+CA403 30 US 67 | Oak+Brook,+IL315 Oak+Brook,+IL307 20 US 68 | Oak+Brook,+IL315 Oak+Brook,+IL300 20 US 69 | Oak+Brook,+IL315 Oak+Brook,+IL308 20 US 70 | Oak+Brook,+IL315 Oak+Brook,+IL301 25 US 71 | Weehawken,+NJ552 Weehawken,+NJ543 20 US 72 | Weehawken,+NJ552 Weehawken,+NJ544 20 US 73 | Weehawken,+NJ552 Oak+Brook,+IL300 130 US 74 | Chicago,+IL155 Tukwila,+WA509 60 US 75 | Chicago,+IL155 Oak+Brook,+IL301 20 US 76 | Chicago,+IL155 Chicago,+IL156 25 US 77 | Chicago,+IL155 Tukwila,+WA508 90 US 78 | Chicago,+IL156 Oak+Brook,+IL300 20 US 79 | Chicago,+IL156 Fort+Worth,+TX189 70 US 80 | Chicago,+IL156 Chicago,+IL155 25 US 81 | Fort+Worth,+TX189 Irvine,+CA228 170 US 82 | Fort+Worth,+TX189 Austin,+TX137 40 US 83 | Fort+Worth,+TX189 Fort+Worth,+TX190 20 US 84 | Fort+Worth,+TX189 Chicago,+IL156 70 US 85 | Fort+Worth,+TX189 Santa+Clara,+CA403 160 US 86 | Fort+Worth,+TX189 Fort+Worth,+TX191 35 US 87 | Herndon,+VA495 Herndon,+VA496 20 US 88 | Herndon,+VA495 Herndon,+VA208 50 US 89 | Herndon,+VA495 Weehawken,+NJ544 40 US 90 | Jersey+City,+NJ244 Jersey+City,+NJ261 40 US 91 | Jersey+City,+NJ244 Frankfurt184 10 BACKBONE 92 | Jersey+City,+NJ244 Jersey+City,+NJ245 20 US 93 | Jersey+City,+NJ244 Weehawken,+NJ543 20 US 94 | Jersey+City,+NJ244 Waltham,+MA568 50 US 95 | Jersey+City,+NJ244 London277 10 BACKBONE 96 | Jersey+City,+NJ244 Waltham,+MA569 20 US 97 | Herndon,+VA496 Santa+Clara,+CA429 225 US 98 | Herndon,+VA496 Herndon,+VA193 30 US 99 | Herndon,+VA496 Herndon,+VA495 20 US 100 | Irvine,+CA212 El+Segundo,+CA163 10 US 101 | Tokyo525 Santa+Clara,+CA404 25 BACKBONE 102 | Tokyo525 Tokyo526 20 AS 103 | Irvine,+CA213 Santa+Clara,+CA404 100 US 104 | Irvine,+CA213 Irvine,+CA228 40 US 105 | Irvine,+CA213 El+Segundo,+CA164 20 US 106 | Jersey+City,+NJ245 Jersey+City,+NJ261 30 US 107 | Jersey+City,+NJ245 London274 20 BACKBONE 108 | Jersey+City,+NJ245 New+York,+NY294 25 US 109 | Jersey+City,+NJ245 London275 25 BACKBONE 110 | Jersey+City,+NJ245 London276 20 BACKBONE 111 | Jersey+City,+NJ245 Weehawken,+NJ544 20 US 112 | Jersey+City,+NJ245 Jersey+City,+NJ244 20 US 113 | Tokyo526 Tokyo525 20 AS 114 | Tokyo526 Santa+Clara,+CA336 25 BACKBONE 115 | Tokyo526 San+Jose,+CA460 20 BACKBONE 116 | Waltham,+MA555 Waltham,+MA556 20 US 117 | Waltham,+MA555 Waltham,+MA568 20 US 118 | Waltham,+MA555 Oak+Brook,+IL300 130 US 119 | Waltham,+MA555 Toronto,+Canada537 10 US 120 | Waltham,+MA556 Oak+Brook,+IL300 100 US 121 | Waltham,+MA556 Waltham,+MA569 70 US 122 | Waltham,+MA556 Waltham,+MA555 20 US 123 | Herndon,+VA206 New+York,+NY293 20 US 124 | Herndon,+VA206 Herndon,+VA208 20 US 125 | Santa+Clara,+CA429 Herndon,+VA496 225 US 126 | Santa+Clara,+CA429 Santa+Clara,+CA430 20 US 127 | Santa+Clara,+CA429 Santa+Clara,+CA431 55 US 128 | Santa+Clara,+CA429 San+Jose,+CA459 20 US 129 | Santa+Clara,+CA429 Tukwila,+WA508 70 US 130 | Santa+Clara,+CA429 Santa+Clara,+CA403 45 US 131 | Herndon,+VA208 Herndon,+VA206 20 US 132 | Herndon,+VA208 New+York,+NY293 40 US 133 | Herndon,+VA208 Herndon,+VA495 50 US 134 | Atlanta,+GA126 Atlanta,+GA127 30 US 135 | Atlanta,+GA126 Herndon,+VA193 45 US 136 | Atlanta,+GA126 Atlanta,+GA133 40 US 137 | Miami,+FL285 New+York,+NY293 50 US 138 | Miami,+FL285 Miami,+FL286 80 US 139 | Atlanta,+GA127 Atlanta,+GA126 30 US 140 | Atlanta,+GA127 Miami,+FL286 25 US 141 | Atlanta,+GA127 Weehawken,+NJ543 185 US 142 | Atlanta,+GA127 Herndon,+VA193 75 US 143 | Atlanta,+GA127 Fort+Worth,+TX190 35 US 144 | Atlanta,+GA127 Atlanta,+GA133 20 US 145 | Miami,+FL286 Miami,+FL285 80 US 146 | Miami,+FL286 Atlanta,+GA127 25 US 147 | Fort+Worth,+TX190 Irvine,+CA228 140 US 148 | Fort+Worth,+TX190 Atlanta,+GA127 35 US 149 | Fort+Worth,+TX190 Irvine,+CA229 160 US 150 | Fort+Worth,+TX190 Austin,+TX136 10 US 151 | Fort+Worth,+TX190 Fort+Worth,+TX189 20 US 152 | Fort+Worth,+TX190 Fort+Worth,+TX191 25 US 153 | San+Jose,+CA459 Santa+Clara,+CA429 20 US 154 | San+Jose,+CA459 San+Jose,+CA460 60 US 155 | Fort+Worth,+TX191 Fort+Worth,+TX189 35 US 156 | Fort+Worth,+TX191 Fort+Worth,+TX190 25 US 157 | New+York,+NY293 Miami,+FL285 50 US 158 | New+York,+NY293 Herndon,+VA206 20 US 159 | New+York,+NY293 Herndon,+VA208 40 US 160 | New+York,+NY293 New+York,+NY294 55 US 161 | New+York,+NY293 Weehawken,+NJ543 80 US 162 | New+York,+NY293 Weehawken,+NJ544 50 US 163 | New+York,+NY294 Jersey+City,+NJ245 25 US 164 | New+York,+NY294 New+York,+NY293 55 US 165 | New+York,+NY294 Weehawken,+NJ543 25 US 166 | London274 Jersey+City,+NJ245 20 BACKBONE 167 | London274 Amsterdam119 20 EU 168 | London274 Weehawken,+NJ544 40 BACKBONE 169 | London275 Jersey+City,+NJ245 25 BACKBONE 170 | London275 Amsterdam119 55 EU 171 | London275 Weehawken,+NJ544 45 BACKBONE 172 | Santa+Clara,+CA430 Santa+Clara,+CA404 45 US 173 | Santa+Clara,+CA430 Santa+Clara,+CA364 55 US 174 | Santa+Clara,+CA430 Santa+Clara,+CA429 20 US 175 | Santa+Clara,+CA430 Santa+Clara,+CA388 20 US 176 | Santa+Clara,+CA430 San+Jose,+CA472 20 US 177 | Santa+Clara,+CA430 Santa+Clara,+CA336 25 US 178 | Santa+Clara,+CA430 Santa+Clara,+CA443 35 US 179 | London276 Jersey+City,+NJ245 20 BACKBONE 180 | London276 Weehawken,+NJ544 40 BACKBONE 181 | Santa+Clara,+CA431 Santa+Clara,+CA363 20 US 182 | Santa+Clara,+CA431 Santa+Clara,+CA429 55 US 183 | Santa+Clara,+CA431 Santa+Clara,+CA364 20 US 184 | Santa+Clara,+CA431 Santa+Clara,+CA365 40 US 185 | Santa+Clara,+CA431 San+Jose,+CA472 25 US 186 | Santa+Clara,+CA431 Santa+Clara,+CA443 40 US 187 | London277 Jersey+City,+NJ244 10 BACKBONE 188 | Santa+Clara,+CA363 Santa+Clara,+CA431 20 US 189 | Santa+Clara,+CA363 Palo+Alto,+CA318 20 US 190 | Irvine,+CA228 Irvine,+CA213 40 US 191 | Irvine,+CA228 Irvine,+CA229 20 US 192 | Irvine,+CA228 Irvine,+CA237 20 US 193 | Irvine,+CA228 Irvine,+CA230 30 US 194 | Irvine,+CA228 Irvine,+CA231 20 US 195 | Irvine,+CA228 Fort+Worth,+TX189 170 US 196 | Irvine,+CA228 Fort+Worth,+TX190 140 US 197 | Santa+Clara,+CA364 Santa+Clara,+CA430 55 US 198 | Santa+Clara,+CA364 Santa+Clara,+CA431 20 US 199 | Santa+Clara,+CA364 Palo+Alto,+CA318 20 US 200 | Irvine,+CA229 Irvine,+CA228 20 US 201 | Irvine,+CA229 El+Segundo,+CA164 30 US 202 | Irvine,+CA229 Fort+Worth,+TX190 160 US 203 | Santa+Clara,+CA365 Palo+Alto,+CA104 20 US 204 | Santa+Clara,+CA365 Santa+Clara,+CA431 40 US 205 | Waltham,+MA568 Waltham,+MA569 20 US 206 | Waltham,+MA568 Jersey+City,+NJ244 50 US 207 | Waltham,+MA568 Waltham,+MA555 20 US 208 | Waltham,+MA569 Waltham,+MA556 70 US 209 | Waltham,+MA569 Waltham,+MA568 20 US 210 | Waltham,+MA569 Jersey+City,+NJ244 20 US 211 | Atlanta,+GA133 Atlanta,+GA126 40 US 212 | Atlanta,+GA133 Atlanta,+GA127 20 US 213 | San+Jose,+CA460 Tokyo526 20 BACKBONE 214 | San+Jose,+CA460 Santa+Clara,+CA388 30 US 215 | San+Jose,+CA460 San+Jose,+CA459 60 US 216 | San+Jose,+CA460 Santa+Clara,+CA443 25 US 217 | El+Segundo,+CA163 Irvine,+CA212 10 US 218 | El+Segundo,+CA163 Santa+Clara,+CA404 50 US 219 | El+Segundo,+CA163 Santa+Clara,+CA405 30 US 220 | El+Segundo,+CA163 El+Segundo,+CA164 20 US 221 | El+Segundo,+CA163 Austin,+TX137 160 US 222 | Austin,+TX136 Fort+Worth,+TX190 10 US 223 | El+Segundo,+CA164 Irvine,+CA213 20 US 224 | El+Segundo,+CA164 Irvine,+CA229 30 US 225 | El+Segundo,+CA164 Irvine,+CA230 20 US 226 | El+Segundo,+CA164 Irvine,+CA231 30 US 227 | El+Segundo,+CA164 El+Segundo,+CA163 20 US 228 | El+Segundo,+CA164 Santa+Clara,+CA403 90 US 229 | El+Segundo,+CA164 Santa+Clara,+CA443 60 US 230 | Austin,+TX137 El+Segundo,+CA163 160 US 231 | Austin,+TX137 Fort+Worth,+TX189 40 US 232 | Santa+Clara,+CA403 Santa+Clara,+CA444 25 US 233 | Santa+Clara,+CA403 Santa+Clara,+CA404 20 US 234 | Santa+Clara,+CA403 Santa+Clara,+CA429 45 US 235 | Santa+Clara,+CA403 Santa+Clara,+CA405 50 US 236 | Santa+Clara,+CA403 Palo+Alto,+CA104 20 US 237 | Santa+Clara,+CA403 Santa+Clara,+CA389 30 US 238 | Santa+Clara,+CA403 Fort+Worth,+TX189 160 US 239 | Santa+Clara,+CA403 El+Segundo,+CA164 90 US 240 | Jersey+City,+NJ261 Jersey+City,+NJ245 30 US 241 | Jersey+City,+NJ261 Jersey+City,+NJ244 40 US 242 | Santa+Clara,+CA404 Santa+Clara,+CA444 45 US 243 | Santa+Clara,+CA404 Irvine,+CA213 100 US 244 | Santa+Clara,+CA404 Tokyo525 25 BACKBONE 245 | Santa+Clara,+CA404 Santa+Clara,+CA430 45 US 246 | Santa+Clara,+CA404 El+Segundo,+CA163 50 US 247 | Santa+Clara,+CA404 Santa+Clara,+CA443 30 US 248 | Santa+Clara,+CA404 Santa+Clara,+CA403 20 US 249 | Santa+Clara,+CA405 El+Segundo,+CA163 30 US 250 | Santa+Clara,+CA405 Santa+Clara,+CA403 50 US 251 | Irvine,+CA230 Irvine,+CA228 30 US 252 | Irvine,+CA230 El+Segundo,+CA164 20 US 253 | Irvine,+CA231 Irvine,+CA228 20 US 254 | Irvine,+CA231 Irvine,+CA237 30 US 255 | Irvine,+CA231 El+Segundo,+CA164 30 US 256 | Santa+Clara,+CA336 Tokyo526 25 BACKBONE 257 | Santa+Clara,+CA336 Santa+Clara,+CA430 25 US 258 | Santa+Clara,+CA336 Palo+Alto,+CA317 25 US 259 | Santa+Clara,+CA443 Santa+Clara,+CA444 25 US 260 | Santa+Clara,+CA443 Santa+Clara,+CA404 30 US 261 | Santa+Clara,+CA443 Santa+Clara,+CA430 35 US 262 | Santa+Clara,+CA443 Santa+Clara,+CA431 40 US 263 | Santa+Clara,+CA443 Santa+Clara,+CA389 40 US 264 | Santa+Clara,+CA443 El+Segundo,+CA164 60 US 265 | Santa+Clara,+CA443 San+Jose,+CA460 25 US 266 | Santa+Clara,+CA444 Santa+Clara,+CA404 45 US 267 | Santa+Clara,+CA444 San+Jose,+CA471 45 US 268 | Santa+Clara,+CA444 Palo+Alto,+CA104 25 US 269 | Santa+Clara,+CA444 Santa+Clara,+CA443 25 US 270 | Santa+Clara,+CA444 Santa+Clara,+CA403 25 US 271 | Frankfurt184 Jersey+City,+NJ244 10 BACKBONE 272 | Irvine,+CA237 Irvine,+CA228 20 US 273 | Irvine,+CA237 Irvine,+CA231 30 US 274 | Frankfurt185 Amsterdam119 10 EU 275 | Oak+Brook,+IL300 Weehawken,+NJ543 120 US 276 | Oak+Brook,+IL300 Oak+Brook,+IL307 20 US 277 | Oak+Brook,+IL300 Oak+Brook,+IL315 20 US 278 | Oak+Brook,+IL300 Weehawken,+NJ552 130 US 279 | Oak+Brook,+IL300 Oak+Brook,+IL308 20 US 280 | Oak+Brook,+IL300 Oak+Brook,+IL309 20 US 281 | Oak+Brook,+IL300 Chicago,+IL156 20 US 282 | Oak+Brook,+IL300 Toronto,+Canada538 10 US 283 | Oak+Brook,+IL300 Waltham,+MA555 130 US 284 | Oak+Brook,+IL300 Waltham,+MA556 100 US 285 | Oak+Brook,+IL300 Oak+Brook,+IL301 25 US 286 | Oak+Brook,+IL300 Oak+Brook,+IL310 20 US 287 | Oak+Brook,+IL301 San+Jose,+CA471 145 US 288 | Oak+Brook,+IL301 Oak+Brook,+IL307 20 US 289 | Oak+Brook,+IL301 Oak+Brook,+IL315 25 US 290 | Oak+Brook,+IL301 Oak+Brook,+IL308 20 US 291 | Oak+Brook,+IL301 Chicago,+IL155 20 US 292 | Oak+Brook,+IL301 Oak+Brook,+IL309 25 US 293 | Oak+Brook,+IL301 Oak+Brook,+IL300 25 US 294 | Oak+Brook,+IL301 Oak+Brook,+IL310 35 US 295 | -------------------------------------------------------------------------------- /topologies/weights-dist/3967/3967_pops_continent_inter_abr_backbone.entf: -------------------------------------------------------------------------------- 1 | San+Jose,+CA471 Santa+Clara,+CA444 45 US 2 | San+Jose,+CA471 Santa+Clara,+CA389 40 US 3 | San+Jose,+CA471 San+Jose,+CA472 35 US 4 | San+Jose,+CA471 Oak+Brook,+IL301 145 US 5 | San+Jose,+CA472 San+Jose,+CA471 35 US 6 | San+Jose,+CA472 Santa+Clara,+CA430 20 US 7 | San+Jose,+CA472 Santa+Clara,+CA389 35 US 8 | San+Jose,+CA472 Santa+Clara,+CA431 25 US 9 | Weehawken,+NJ543 New+York,+NY293 80 US 10 | Weehawken,+NJ543 Atlanta,+GA127 185 US 11 | Weehawken,+NJ543 New+York,+NY294 25 US 12 | Weehawken,+NJ543 Weehawken,+NJ544 20 US 13 | Weehawken,+NJ543 Weehawken,+NJ552 20 US 14 | Weehawken,+NJ543 Oak+Brook,+IL300 120 US 15 | Weehawken,+NJ543 Jersey+City,+NJ244 20 US 16 | Oak+Brook,+IL307 Oak+Brook,+IL315 20 US 17 | Oak+Brook,+IL307 Oak+Brook,+IL300 20 US 18 | Oak+Brook,+IL307 Oak+Brook,+IL301 20 US 19 | Herndon,+VA193 Herndon,+VA496 30 US 20 | Herndon,+VA193 Atlanta,+GA126 45 US 21 | Herndon,+VA193 Atlanta,+GA127 75 US 22 | Weehawken,+NJ544 Jersey+City,+NJ245 20 BACKBONE 23 | Weehawken,+NJ544 London274 40 BACKBONE 24 | Weehawken,+NJ544 London275 45 BACKBONE 25 | Weehawken,+NJ544 London276 40 BACKBONE 26 | Weehawken,+NJ544 Weehawken,+NJ543 20 US 27 | Weehawken,+NJ544 Weehawken,+NJ552 20 US 28 | Weehawken,+NJ544 New+York,+NY293 50 US 29 | Weehawken,+NJ544 Herndon,+VA495 40 US 30 | Oak+Brook,+IL308 Oak+Brook,+IL315 20 US 31 | Oak+Brook,+IL308 Oak+Brook,+IL300 20 US 32 | Oak+Brook,+IL308 Oak+Brook,+IL301 20 US 33 | Toronto,+Canada537 Waltham,+MA555 10 US 34 | Oak+Brook,+IL309 Oak+Brook,+IL300 20 US 35 | Oak+Brook,+IL309 Oak+Brook,+IL301 25 US 36 | Toronto,+Canada538 Oak+Brook,+IL300 10 US 37 | Palo+Alto,+CA317 Santa+Clara,+CA388 30 US 38 | Palo+Alto,+CA317 Palo+Alto,+CA104 25 US 39 | Palo+Alto,+CA317 Santa+Clara,+CA336 25 US 40 | Palo+Alto,+CA317 Palo+Alto,+CA318 20 US 41 | Palo+Alto,+CA318 Santa+Clara,+CA363 20 US 42 | Palo+Alto,+CA318 Santa+Clara,+CA364 20 US 43 | Palo+Alto,+CA318 Palo+Alto,+CA317 20 US 44 | Amsterdam119 London274 20 EU 45 | Amsterdam119 London275 55 EU 46 | Amsterdam119 Frankfurt185 10 EU 47 | Tukwila,+WA508 Tukwila,+WA509 20 US 48 | Tukwila,+WA508 Santa+Clara,+CA429 70 US 49 | Tukwila,+WA508 Chicago,+IL155 90 US 50 | Oak+Brook,+IL310 Oak+Brook,+IL300 20 US 51 | Oak+Brook,+IL310 Oak+Brook,+IL301 35 US 52 | Tukwila,+WA509 Chicago,+IL155 60 US 53 | Tukwila,+WA509 Tukwila,+WA508 20 US 54 | Santa+Clara,+CA388 Santa+Clara,+CA430 20 US 55 | Santa+Clara,+CA388 Santa+Clara,+CA389 25 US 56 | Santa+Clara,+CA388 Palo+Alto,+CA317 30 US 57 | Santa+Clara,+CA388 San+Jose,+CA460 30 US 58 | Palo+Alto,+CA104 Santa+Clara,+CA444 25 US 59 | Palo+Alto,+CA104 Santa+Clara,+CA365 20 US 60 | Palo+Alto,+CA104 Palo+Alto,+CA317 25 US 61 | Palo+Alto,+CA104 Santa+Clara,+CA403 20 US 62 | Santa+Clara,+CA389 San+Jose,+CA471 40 US 63 | Santa+Clara,+CA389 Santa+Clara,+CA388 25 US 64 | Santa+Clara,+CA389 San+Jose,+CA472 35 US 65 | Santa+Clara,+CA389 Santa+Clara,+CA443 40 US 66 | Santa+Clara,+CA389 Santa+Clara,+CA403 30 US 67 | Oak+Brook,+IL315 Oak+Brook,+IL307 20 US 68 | Oak+Brook,+IL315 Oak+Brook,+IL300 20 US 69 | Oak+Brook,+IL315 Oak+Brook,+IL308 20 US 70 | Oak+Brook,+IL315 Oak+Brook,+IL301 25 US 71 | Weehawken,+NJ552 Weehawken,+NJ543 20 US 72 | Weehawken,+NJ552 Weehawken,+NJ544 20 US 73 | Weehawken,+NJ552 Oak+Brook,+IL300 130 US 74 | Chicago,+IL155 Tukwila,+WA509 60 US 75 | Chicago,+IL155 Oak+Brook,+IL301 20 US 76 | Chicago,+IL155 Chicago,+IL156 25 US 77 | Chicago,+IL155 Tukwila,+WA508 90 US 78 | Chicago,+IL156 Oak+Brook,+IL300 20 US 79 | Chicago,+IL156 Fort+Worth,+TX189 70 US 80 | Chicago,+IL156 Chicago,+IL155 25 US 81 | Fort+Worth,+TX189 Irvine,+CA228 170 US 82 | Fort+Worth,+TX189 Austin,+TX137 40 US 83 | Fort+Worth,+TX189 Fort+Worth,+TX190 20 US 84 | Fort+Worth,+TX189 Chicago,+IL156 70 US 85 | Fort+Worth,+TX189 Santa+Clara,+CA403 160 US 86 | Fort+Worth,+TX189 Fort+Worth,+TX191 35 US 87 | Herndon,+VA495 Herndon,+VA496 20 US 88 | Herndon,+VA495 Herndon,+VA208 50 US 89 | Herndon,+VA495 Weehawken,+NJ544 40 US 90 | Jersey+City,+NJ244 Jersey+City,+NJ261 40 US 91 | Jersey+City,+NJ244 Frankfurt184 10 BACKBONE 92 | Jersey+City,+NJ244 Jersey+City,+NJ245 20 BACKBONE 93 | Jersey+City,+NJ244 Weehawken,+NJ543 20 US 94 | Jersey+City,+NJ244 Waltham,+MA568 50 US 95 | Jersey+City,+NJ244 London277 10 BACKBONE 96 | Jersey+City,+NJ244 Waltham,+MA569 20 US 97 | Herndon,+VA496 Santa+Clara,+CA429 225 US 98 | Herndon,+VA496 Herndon,+VA193 30 US 99 | Herndon,+VA496 Herndon,+VA495 20 US 100 | Irvine,+CA212 El+Segundo,+CA163 10 US 101 | Tokyo525 Santa+Clara,+CA404 25 BACKBONE 102 | Tokyo525 Tokyo526 20 BACKBONE 103 | Irvine,+CA213 Santa+Clara,+CA404 100 US 104 | Irvine,+CA213 Irvine,+CA228 40 US 105 | Irvine,+CA213 El+Segundo,+CA164 20 US 106 | Jersey+City,+NJ245 Jersey+City,+NJ261 30 US 107 | Jersey+City,+NJ245 London274 20 BACKBONE 108 | Jersey+City,+NJ245 New+York,+NY294 25 US 109 | Jersey+City,+NJ245 London275 25 BACKBONE 110 | Jersey+City,+NJ245 London276 20 BACKBONE 111 | Jersey+City,+NJ245 Weehawken,+NJ544 20 BACKBONE 112 | Jersey+City,+NJ245 Jersey+City,+NJ244 20 BACKBONE 113 | Tokyo526 Tokyo525 20 BACKBONE 114 | Tokyo526 Santa+Clara,+CA336 25 BACKBONE 115 | Tokyo526 San+Jose,+CA460 20 BACKBONE 116 | Waltham,+MA555 Waltham,+MA556 20 US 117 | Waltham,+MA555 Waltham,+MA568 20 US 118 | Waltham,+MA555 Oak+Brook,+IL300 130 US 119 | Waltham,+MA555 Toronto,+Canada537 10 US 120 | Waltham,+MA556 Oak+Brook,+IL300 100 US 121 | Waltham,+MA556 Waltham,+MA569 70 US 122 | Waltham,+MA556 Waltham,+MA555 20 US 123 | Herndon,+VA206 New+York,+NY293 20 US 124 | Herndon,+VA206 Herndon,+VA208 20 US 125 | Santa+Clara,+CA429 Herndon,+VA496 225 US 126 | Santa+Clara,+CA429 Santa+Clara,+CA430 20 US 127 | Santa+Clara,+CA429 Santa+Clara,+CA431 55 US 128 | Santa+Clara,+CA429 San+Jose,+CA459 20 US 129 | Santa+Clara,+CA429 Tukwila,+WA508 70 US 130 | Santa+Clara,+CA429 Santa+Clara,+CA403 45 US 131 | Herndon,+VA208 Herndon,+VA206 20 US 132 | Herndon,+VA208 New+York,+NY293 40 US 133 | Herndon,+VA208 Herndon,+VA495 50 US 134 | Atlanta,+GA126 Atlanta,+GA127 30 US 135 | Atlanta,+GA126 Herndon,+VA193 45 US 136 | Atlanta,+GA126 Atlanta,+GA133 40 US 137 | Miami,+FL285 New+York,+NY293 50 US 138 | Miami,+FL285 Miami,+FL286 80 US 139 | Atlanta,+GA127 Atlanta,+GA126 30 US 140 | Atlanta,+GA127 Miami,+FL286 25 US 141 | Atlanta,+GA127 Weehawken,+NJ543 185 US 142 | Atlanta,+GA127 Herndon,+VA193 75 US 143 | Atlanta,+GA127 Fort+Worth,+TX190 35 US 144 | Atlanta,+GA127 Atlanta,+GA133 20 US 145 | Miami,+FL286 Miami,+FL285 80 US 146 | Miami,+FL286 Atlanta,+GA127 25 US 147 | Fort+Worth,+TX190 Irvine,+CA228 140 US 148 | Fort+Worth,+TX190 Atlanta,+GA127 35 US 149 | Fort+Worth,+TX190 Irvine,+CA229 160 US 150 | Fort+Worth,+TX190 Austin,+TX136 10 US 151 | Fort+Worth,+TX190 Fort+Worth,+TX189 20 US 152 | Fort+Worth,+TX190 Fort+Worth,+TX191 25 US 153 | San+Jose,+CA459 Santa+Clara,+CA429 20 US 154 | San+Jose,+CA459 San+Jose,+CA460 60 US 155 | Fort+Worth,+TX191 Fort+Worth,+TX189 35 US 156 | Fort+Worth,+TX191 Fort+Worth,+TX190 25 US 157 | New+York,+NY293 Miami,+FL285 50 US 158 | New+York,+NY293 Herndon,+VA206 20 US 159 | New+York,+NY293 Herndon,+VA208 40 US 160 | New+York,+NY293 New+York,+NY294 55 US 161 | New+York,+NY293 Weehawken,+NJ543 80 US 162 | New+York,+NY293 Weehawken,+NJ544 50 US 163 | New+York,+NY294 Jersey+City,+NJ245 25 US 164 | New+York,+NY294 New+York,+NY293 55 US 165 | New+York,+NY294 Weehawken,+NJ543 25 US 166 | London274 Jersey+City,+NJ245 20 BACKBONE 167 | London274 Amsterdam119 20 EU 168 | London274 Weehawken,+NJ544 40 BACKBONE 169 | London275 Jersey+City,+NJ245 25 BACKBONE 170 | London275 Amsterdam119 55 EU 171 | London275 Weehawken,+NJ544 45 BACKBONE 172 | Santa+Clara,+CA430 Santa+Clara,+CA404 45 US 173 | Santa+Clara,+CA430 Santa+Clara,+CA364 55 US 174 | Santa+Clara,+CA430 Santa+Clara,+CA429 20 US 175 | Santa+Clara,+CA430 Santa+Clara,+CA388 20 US 176 | Santa+Clara,+CA430 San+Jose,+CA472 20 US 177 | Santa+Clara,+CA430 Santa+Clara,+CA336 25 US 178 | Santa+Clara,+CA430 Santa+Clara,+CA443 35 US 179 | London276 Jersey+City,+NJ245 20 BACKBONE 180 | London276 Weehawken,+NJ544 40 BACKBONE 181 | Santa+Clara,+CA431 Santa+Clara,+CA363 20 US 182 | Santa+Clara,+CA431 Santa+Clara,+CA429 55 US 183 | Santa+Clara,+CA431 Santa+Clara,+CA364 20 US 184 | Santa+Clara,+CA431 Santa+Clara,+CA365 40 US 185 | Santa+Clara,+CA431 San+Jose,+CA472 25 US 186 | Santa+Clara,+CA431 Santa+Clara,+CA443 40 US 187 | London277 Jersey+City,+NJ244 10 BACKBONE 188 | Santa+Clara,+CA363 Santa+Clara,+CA431 20 US 189 | Santa+Clara,+CA363 Palo+Alto,+CA318 20 US 190 | Irvine,+CA228 Irvine,+CA213 40 US 191 | Irvine,+CA228 Irvine,+CA229 20 US 192 | Irvine,+CA228 Irvine,+CA237 20 US 193 | Irvine,+CA228 Irvine,+CA230 30 US 194 | Irvine,+CA228 Irvine,+CA231 20 US 195 | Irvine,+CA228 Fort+Worth,+TX189 170 US 196 | Irvine,+CA228 Fort+Worth,+TX190 140 US 197 | Santa+Clara,+CA364 Santa+Clara,+CA430 55 US 198 | Santa+Clara,+CA364 Santa+Clara,+CA431 20 US 199 | Santa+Clara,+CA364 Palo+Alto,+CA318 20 US 200 | Irvine,+CA229 Irvine,+CA228 20 US 201 | Irvine,+CA229 El+Segundo,+CA164 30 US 202 | Irvine,+CA229 Fort+Worth,+TX190 160 US 203 | Santa+Clara,+CA365 Palo+Alto,+CA104 20 US 204 | Santa+Clara,+CA365 Santa+Clara,+CA431 40 US 205 | Waltham,+MA568 Waltham,+MA569 20 US 206 | Waltham,+MA568 Jersey+City,+NJ244 50 US 207 | Waltham,+MA568 Waltham,+MA555 20 US 208 | Waltham,+MA569 Waltham,+MA556 70 US 209 | Waltham,+MA569 Waltham,+MA568 20 US 210 | Waltham,+MA569 Jersey+City,+NJ244 20 US 211 | Atlanta,+GA133 Atlanta,+GA126 40 US 212 | Atlanta,+GA133 Atlanta,+GA127 20 US 213 | San+Jose,+CA460 Tokyo526 20 BACKBONE 214 | San+Jose,+CA460 Santa+Clara,+CA388 30 US 215 | San+Jose,+CA460 San+Jose,+CA459 60 US 216 | San+Jose,+CA460 Santa+Clara,+CA443 25 US 217 | El+Segundo,+CA163 Irvine,+CA212 10 US 218 | El+Segundo,+CA163 Santa+Clara,+CA404 50 US 219 | El+Segundo,+CA163 Santa+Clara,+CA405 30 US 220 | El+Segundo,+CA163 El+Segundo,+CA164 20 US 221 | El+Segundo,+CA163 Austin,+TX137 160 US 222 | Austin,+TX136 Fort+Worth,+TX190 10 US 223 | El+Segundo,+CA164 Irvine,+CA213 20 US 224 | El+Segundo,+CA164 Irvine,+CA229 30 US 225 | El+Segundo,+CA164 Irvine,+CA230 20 US 226 | El+Segundo,+CA164 Irvine,+CA231 30 US 227 | El+Segundo,+CA164 El+Segundo,+CA163 20 US 228 | El+Segundo,+CA164 Santa+Clara,+CA403 90 US 229 | El+Segundo,+CA164 Santa+Clara,+CA443 60 US 230 | Austin,+TX137 El+Segundo,+CA163 160 US 231 | Austin,+TX137 Fort+Worth,+TX189 40 US 232 | Santa+Clara,+CA403 Santa+Clara,+CA444 25 US 233 | Santa+Clara,+CA403 Santa+Clara,+CA404 20 US 234 | Santa+Clara,+CA403 Santa+Clara,+CA429 45 US 235 | Santa+Clara,+CA403 Santa+Clara,+CA405 50 US 236 | Santa+Clara,+CA403 Palo+Alto,+CA104 20 US 237 | Santa+Clara,+CA403 Santa+Clara,+CA389 30 US 238 | Santa+Clara,+CA403 Fort+Worth,+TX189 160 US 239 | Santa+Clara,+CA403 El+Segundo,+CA164 90 US 240 | Jersey+City,+NJ261 Jersey+City,+NJ245 30 US 241 | Jersey+City,+NJ261 Jersey+City,+NJ244 40 US 242 | Santa+Clara,+CA404 Santa+Clara,+CA444 45 US 243 | Santa+Clara,+CA404 Irvine,+CA213 100 US 244 | Santa+Clara,+CA404 Tokyo525 25 BACKBONE 245 | Santa+Clara,+CA404 Santa+Clara,+CA430 45 US 246 | Santa+Clara,+CA404 El+Segundo,+CA163 50 US 247 | Santa+Clara,+CA404 Santa+Clara,+CA443 30 US 248 | Santa+Clara,+CA404 Santa+Clara,+CA403 20 US 249 | Santa+Clara,+CA405 El+Segundo,+CA163 30 US 250 | Santa+Clara,+CA405 Santa+Clara,+CA403 50 US 251 | Irvine,+CA230 Irvine,+CA228 30 US 252 | Irvine,+CA230 El+Segundo,+CA164 20 US 253 | Irvine,+CA231 Irvine,+CA228 20 US 254 | Irvine,+CA231 Irvine,+CA237 30 US 255 | Irvine,+CA231 El+Segundo,+CA164 30 US 256 | Santa+Clara,+CA336 Tokyo526 25 BACKBONE 257 | Santa+Clara,+CA336 Santa+Clara,+CA430 25 US 258 | Santa+Clara,+CA336 Palo+Alto,+CA317 25 US 259 | Santa+Clara,+CA443 Santa+Clara,+CA444 25 US 260 | Santa+Clara,+CA443 Santa+Clara,+CA404 30 US 261 | Santa+Clara,+CA443 Santa+Clara,+CA430 35 US 262 | Santa+Clara,+CA443 Santa+Clara,+CA431 40 US 263 | Santa+Clara,+CA443 Santa+Clara,+CA389 40 US 264 | Santa+Clara,+CA443 El+Segundo,+CA164 60 US 265 | Santa+Clara,+CA443 San+Jose,+CA460 25 US 266 | Santa+Clara,+CA444 Santa+Clara,+CA404 45 US 267 | Santa+Clara,+CA444 San+Jose,+CA471 45 US 268 | Santa+Clara,+CA444 Palo+Alto,+CA104 25 US 269 | Santa+Clara,+CA444 Santa+Clara,+CA443 25 US 270 | Santa+Clara,+CA444 Santa+Clara,+CA403 25 US 271 | Frankfurt184 Jersey+City,+NJ244 10 BACKBONE 272 | Irvine,+CA237 Irvine,+CA228 20 US 273 | Irvine,+CA237 Irvine,+CA231 30 US 274 | Frankfurt185 Amsterdam119 10 EU 275 | Oak+Brook,+IL300 Weehawken,+NJ543 120 US 276 | Oak+Brook,+IL300 Oak+Brook,+IL307 20 US 277 | Oak+Brook,+IL300 Oak+Brook,+IL315 20 US 278 | Oak+Brook,+IL300 Weehawken,+NJ552 130 US 279 | Oak+Brook,+IL300 Oak+Brook,+IL308 20 US 280 | Oak+Brook,+IL300 Oak+Brook,+IL309 20 US 281 | Oak+Brook,+IL300 Chicago,+IL156 20 US 282 | Oak+Brook,+IL300 Toronto,+Canada538 10 US 283 | Oak+Brook,+IL300 Waltham,+MA555 130 US 284 | Oak+Brook,+IL300 Waltham,+MA556 100 US 285 | Oak+Brook,+IL300 Oak+Brook,+IL301 25 US 286 | Oak+Brook,+IL300 Oak+Brook,+IL310 20 US 287 | Oak+Brook,+IL301 San+Jose,+CA471 145 US 288 | Oak+Brook,+IL301 Oak+Brook,+IL307 20 US 289 | Oak+Brook,+IL301 Oak+Brook,+IL315 25 US 290 | Oak+Brook,+IL301 Oak+Brook,+IL308 20 US 291 | Oak+Brook,+IL301 Chicago,+IL155 20 US 292 | Oak+Brook,+IL301 Oak+Brook,+IL309 25 US 293 | Oak+Brook,+IL301 Oak+Brook,+IL300 25 US 294 | Oak+Brook,+IL301 Oak+Brook,+IL310 35 US 295 | -------------------------------------------------------------------------------- /fibbingnode/misc/igp_graph.py: -------------------------------------------------------------------------------- 1 | """This module provides a structure to represent an IGP topology""" 2 | import os 3 | import sys 4 | import heapq 5 | import networkx as nx 6 | from itertools import count 7 | 8 | from fibbingnode import log 9 | import fibbingnode.algorithms.utils as ssu 10 | from fibbingnode.misc.utils import extend_paths_list, is_container 11 | 12 | # The draw_graph call will be remapped to 'nothing' if matplotlib (aka extra 13 | # packages) is not available 14 | try: 15 | import matplotlib 16 | matplotlib.use('PDF') 17 | import matplotlib.pyplot as plt 18 | except ImportError: 19 | log.warning('Missing packages to draw the network, disabling the fonction') 20 | def draw_graph(*_): 21 | """Can't draw without matplotlib""" 22 | pass 23 | else: 24 | def draw_graph(graph, output): 25 | """If matplotlib is available, draw the given graph to output file""" 26 | try: 27 | layout = nx.spring_layout(graph) 28 | metrics = { 29 | (src, dst): data['metric'] 30 | for src, dst, data in graph.edges_iter(data=True) 31 | } 32 | nx.draw_networkx_edge_labels(graph, layout, edge_labels=metrics) 33 | nx.draw(graph, layout, node_size=20) 34 | nx.draw_networkx_labels(graph, layout, 35 | labels={n: n for n in graph}) 36 | if os.path.exists(output): 37 | os.unlink(output) 38 | plt.savefig(output) 39 | plt.close() 40 | log.debug('Graph of %d nodes saved in %s', len(graph), output) 41 | except: 42 | pass 43 | 44 | 45 | METRIC = 'metric' 46 | FAKE = 'fake' 47 | LOCAL = 'target' 48 | MULTIPLICITY_KEY = 'multiplicity' 49 | 50 | 51 | class IGPGraph(nx.DiGraph): 52 | """This class represents an IGP graph, and defines a few useful bindings""" 53 | 54 | def __init__(self, *args, **kwargs): 55 | super(IGPGraph, self).__init__(*args, **kwargs) 56 | self._export_keys = (METRIC, LOCAL, FAKE) 57 | 58 | def draw(self, dest): 59 | """Draw this graph to dest""" 60 | draw_graph(self, dest) 61 | 62 | def difference(self, other): 63 | """Return the list of edges that other does not have wrt. this graph""" 64 | return [(u, v) for u, v in self.edges_iter() 65 | if not other.has_edge(u, v)] 66 | 67 | def _add_node(self, *names, **kw): 68 | for n in names: 69 | self.add_node(n, **kw) 70 | 71 | def add_controller(self, *names, **kw): 72 | """Add a controller node to the graph""" 73 | self._add_node(*names, controller=True, **kw) 74 | 75 | def add_router(self, *names, **kw): 76 | """Add a router node to the graph""" 77 | self._add_node(*names, router=True, **kw) 78 | 79 | def add_route(self, router, prefix, **kw): 80 | """Add routes to the graph""" 81 | self._add_node(prefix, prefix=True) 82 | self.add_edge(router, prefix, **kw) 83 | 84 | def add_fake_route(self, router, prefix, **kw): 85 | """Add a fake prefix node to the graph""" 86 | self.add_route(router, prefix, fake=True, **kw) 87 | 88 | def add_local_route(self, router, prefix, targets, **kw): 89 | """Add a fake local route available for specified targets""" 90 | if not is_container(targets): 91 | targets = [targets] 92 | self.add_fake_route(router, prefix, target=targets, **kw) 93 | 94 | def _is_x(self, n, x, val=True): 95 | try: 96 | return self.node[n][x] == val 97 | except KeyError: 98 | return False 99 | 100 | def _edge_is_x(self, u, v, x, val=True): 101 | try: 102 | return self[u][v][x] == val 103 | except KeyError: 104 | return False 105 | 106 | def is_router(self, n): 107 | """Return whether n is a router or not""" 108 | return self._is_x(n, 'router') 109 | 110 | def is_controller(self, n): 111 | """Return whether n is a controller or not""" 112 | return self._is_x(n, 'controller') 113 | 114 | def is_prefix(self, n): 115 | """Return whether n is a prefix or not""" 116 | return self._is_x(n, 'prefix') 117 | 118 | def is_router_link(self, u, v): 119 | """Return wether a given edge is a link between two routers""" 120 | return self.is_router(u) and self.is_router(v) and v in self[u] 121 | 122 | def is_route(self, _, v): 123 | """Return whether edge _,v is a route""" 124 | return self._is_x(v, 'prefix') 125 | 126 | def is_real_route(self, u, v): 127 | """Return whether u,v is an edge mapping to a real LSA""" 128 | return self.is_route(u, v) and not self._edge_is_x(u, v, 'fake') 129 | 130 | def is_fake_route(self, u, v): 131 | """Return whether edge u,v is a route from a fake LSA""" 132 | return self.is_route(u, v) and self._edge_is_x(u, v, 'fake') 133 | 134 | def is_global_lie(self, u, v): 135 | """Return wether u,v is a global lie""" 136 | return self.is_fake_route(u, v) and not self._edge_is_x(u, v, 'target') 137 | 138 | def is_local_lie(self, u, v, target=None): 139 | """Return wether u,v is a local lie, optionally check if it applies to 140 | the given target(s)""" 141 | isfake = self.is_fake_route(u, v) 142 | targets = self[u][v].get('target', False) 143 | return isfake and targets and (not target or target in targets) 144 | 145 | def local_lie_target(self, n): 146 | """Return the target node(s) as a list for that local lies""" 147 | try: 148 | return self.node[n]['target'] 149 | except KeyError: 150 | raise ValueError('%s is not a local lie!' % n) 151 | 152 | def _get_all(self, predicate): 153 | for n in self.nodes_iter(): 154 | if predicate(n): 155 | yield n 156 | 157 | def _get_all_edges(self, predicate): 158 | for u, v in self.edges_iter(): 159 | if predicate(u, v): 160 | yield u, v 161 | 162 | @property 163 | def routers(self): 164 | """Returns a generator over all routers in the graph 165 | Example: all_routers = list(graph.routers) 166 | """ 167 | return self._get_all(self.is_router) 168 | 169 | @property 170 | def controllers(self): 171 | """Returns a generator over all controllers in the graph 172 | Example: all_controllers = list(graph.controllers) 173 | """ 174 | return self._get_all(self.is_controller) 175 | 176 | @property 177 | def prefixes(self): 178 | """Returns a generator over all prefixes in the graph""" 179 | return self._get_all(self.is_prefix) 180 | 181 | @property 182 | def all_routes(self): 183 | """Returns a generator over all routes in the graph""" 184 | return self._get_all_edges(self.is_route) 185 | 186 | @property 187 | def real_routes(self): 188 | """Returns a generator over all real routes in the graph""" 189 | return self._get_all_edges(self.is_real_route) 190 | 191 | @property 192 | def fake_routes(self): 193 | """Returns a generator over all fake routes in the graph""" 194 | return self._get_all_edges(self.is_fake_route) 195 | 196 | @property 197 | def local_lies(self, target=False): 198 | """Returns a generator over all local lies in the graph, possibly 199 | return the target neighbours of it.""" 200 | for n in self._get_all_edges(self.is_local_lie): 201 | yield n if not target else (n, self.local_lie_target(n)) 202 | 203 | @property 204 | def global_lies(self): 205 | """Returns a generator over all global lies in the graph""" 206 | return self._get_all_edges(self.is_global_lie) 207 | 208 | @property 209 | def router_links(self): 210 | """Return a generator over all intra-router links""" 211 | return self._get_all_edges(self.is_router_link) 212 | 213 | def metric(self, u, v, m=None): 214 | """Return the link metric for link u->v, or set it if m is not None""" 215 | if m: 216 | self[u][v][METRIC] = m 217 | else: 218 | return self[u][v].get(METRIC, 1) 219 | 220 | def contract(self, into, nbunch): 221 | """Contract nodes from nbunch into a single node named into""" 222 | self.add_edges_from(((into, v, data) for _, v, data 223 | in self.edges_iter(nbunch, data=True))) 224 | self.remove_nodes_from(nbunch) 225 | 226 | def _filter_edge_data(self, data): 227 | return {n: data.get(n, False) for n in self._export_keys} 228 | 229 | def export_edge_data(self, u, v): 230 | """Return the exportable properties of an edge""" 231 | return self._filter_edge_data(self[u][v]) 232 | 233 | def export_edges(self): 234 | """Return a generator yielding a 3-tuple for all edges: 235 | src, dst, exportable properties""" 236 | for u, v, d in self.edges_iter(data=True): 237 | export_data = self._filter_edge_data(d) 238 | yield u, v, export_data 239 | 240 | def real_neighbors(self, n): 241 | """List the real (non dest) nodes in this graph""" 242 | return filter(self.is_router, self.neighbors_iter(n)) 243 | 244 | def set_edge_multiplicity(self, u, v, m): 245 | """Define the edge multiplicity, typically to encode splitting ratios 246 | in the graph. 247 | 248 | :param u, v: The edges end points 249 | :param m: The multiplicity value""" 250 | self[u][v][MULTIPLICITY_KEY] = m 251 | 252 | def get_edge_multiplicity(self, u, v): 253 | """Return the multiplicity of the edge u, v""" 254 | return self[u][v].get(MULTIPLICITY_KEY, 1) 255 | 256 | 257 | class ShortestPath(object): 258 | """A class storing shortest-path trees""" 259 | def __init__(self, graph): 260 | self._default_paths = {} 261 | self._default_dist = {} 262 | # Calculate non-fibbed Dijkstra 263 | for n in graph.nodes_iter(): 264 | (self._default_paths[n], 265 | self._default_dist[n]) = self.__default_spt_for_src(graph, n) 266 | # We do not Fib all destinations, re-use pre-computed ones 267 | fibbed_dst = set(v for _, v in graph.fake_routes) 268 | pure_dst = set(n for n in graph.nodes_iter() 269 | if n not in fibbed_dst) 270 | self._paths = {n: [p[:] for p in self._default_paths[n]] 271 | for n in pure_dst} 272 | self._dist = {n: self._default_dist[n] 273 | for n in pure_dst} 274 | # Compute the Fibbed paths 275 | for n in fibbed_dst: 276 | (self._paths[n], 277 | self._dist[n]) = self.__fibbed_spt_for_src(graph, n) 278 | 279 | @staticmethod 280 | def __default_spt_for_src(g, source): 281 | # Adapted from single_source_dijkstra in networkx 282 | dist = {} # dictionary of final distances 283 | paths = {source: [[source]]} # dictionary of list of paths 284 | seen = {source: 0} 285 | fringe = [] 286 | c = count() # We want to skip comparing node labels 287 | heapq.heappush(fringe, (0, next(c), source)) 288 | while fringe: 289 | (d, _, v) = heapq.heappop(fringe) 290 | if v in dist: 291 | continue # already searched this node. 292 | dist[v] = d 293 | for w, edgedata in g[v].iteritems(): 294 | if g.is_fake_route(v, w): 295 | # Deal with fake edges at a later stage 296 | continue 297 | vw_dist = d + edgedata.get(METRIC, 1) 298 | seen_w = seen.get(w, sys.maxint) 299 | if vw_dist < dist.get(w, 0): 300 | raise ValueError('Contradictory paths found: ' 301 | 'negative metric?') 302 | elif vw_dist < seen_w: # vw is better than the old path 303 | seen[w] = vw_dist 304 | heapq.heappush(fringe, (vw_dist, next(c), w)) 305 | paths[w] = list(extend_paths_list(paths[v], w)) 306 | elif vw_dist == seen_w: # vw is ECMP 307 | paths[w].extend(extend_paths_list(paths[v], w)) 308 | # else w is already pushed in the fringe and will pop later 309 | return paths, dist 310 | 311 | @staticmethod 312 | def __fibbed_spt_for_src(g, source): 313 | """Compute the actual used paths due to Fibbing. 314 | ! the router to which a fake edge is attached does not use it""" 315 | 316 | return None, None 317 | 318 | @staticmethod 319 | def _get(d, u, v=None): 320 | return d[u][v] if v else d[u] 321 | 322 | def fibbed_path(self, u, v=None): 323 | """Return the path, as seen by the routers, between u and v, 324 | or a dictionary of all shortest-paths starting at u if v is None""" 325 | return self._get(self._paths, u, v) 326 | 327 | def fibbed_cost(self, u, v=None): 328 | """Return the cost of the fibbed path between u and v, 329 | or a dict of cost of all shortest-paths starting at u""" 330 | return self._get(self._dist, u, v) 331 | 332 | def default_path(self, u, v=None): 333 | """Return the paths of the pure IGP shortest path if Fibbing was not in 334 | use on the current network, between u an v or a dict of paths if v is 335 | None""" 336 | try: 337 | return self._get(self._default_paths, u, v) 338 | except KeyError as e: 339 | log.debug('%s had no path to %s (lookup key: %s)', u, v, e) 340 | return [] 341 | 342 | def default_cost(self, u, v=None): 343 | """Return the cost of the pure IGP shortest path if Fibbing was not in 344 | use on the current network, between u and v or a dict of cost if v 345 | is None""" 346 | try: 347 | return self._get(self._default_dist, u, v) 348 | except KeyError as e: 349 | log.debug('%s had no path to %s (lookup key: %s)', u, v, e) 350 | return sys.maxint 351 | 352 | def __repr__(self): 353 | return '\n'.join('%s -> %s: %s' % (src, dst, p) 354 | for src, d in self._default_paths.iteritems() 355 | for dst, paths in d.iteritems() 356 | for p in paths) 357 | -------------------------------------------------------------------------------- /topologies/weights-dist/3967/3967_pops_city_inter_abr_backbone.entf: -------------------------------------------------------------------------------- 1 | San+Jose,+CA471 Santa+Clara,+CA444 45 BACKBONE 2 | San+Jose,+CA471 Santa+Clara,+CA389 40 BACKBONE 3 | San+Jose,+CA471 San+Jose,+CA472 35 BACKBONE 4 | San+Jose,+CA471 Oak+Brook,+IL301 145 BACKBONE 5 | San+Jose,+CA472 San+Jose,+CA471 35 BACKBONE 6 | San+Jose,+CA472 Santa+Clara,+CA430 20 BACKBONE 7 | San+Jose,+CA472 Santa+Clara,+CA389 35 BACKBONE 8 | San+Jose,+CA472 Santa+Clara,+CA431 25 BACKBONE 9 | Weehawken,+NJ543 New+York,+NY293 80 BACKBONE 10 | Weehawken,+NJ543 Atlanta,+GA127 185 BACKBONE 11 | Weehawken,+NJ543 New+York,+NY294 25 BACKBONE 12 | Weehawken,+NJ543 Weehawken,+NJ544 20 BACKBONE 13 | Weehawken,+NJ543 Weehawken,+NJ552 20 BACKBONE 14 | Weehawken,+NJ543 Oak+Brook,+IL300 120 BACKBONE 15 | Weehawken,+NJ543 Jersey+City,+NJ244 20 BACKBONE 16 | Oak+Brook,+IL307 Oak+Brook,+IL315 20 Oak+Brook 17 | Oak+Brook,+IL307 Oak+Brook,+IL300 20 Oak+Brook 18 | Oak+Brook,+IL307 Oak+Brook,+IL301 20 Oak+Brook 19 | Herndon,+VA193 Herndon,+VA496 30 BACKBONE 20 | Herndon,+VA193 Atlanta,+GA126 45 BACKBONE 21 | Herndon,+VA193 Atlanta,+GA127 75 BACKBONE 22 | Weehawken,+NJ544 Jersey+City,+NJ245 20 BACKBONE 23 | Weehawken,+NJ544 London274 40 BACKBONE 24 | Weehawken,+NJ544 London275 45 BACKBONE 25 | Weehawken,+NJ544 London276 40 BACKBONE 26 | Weehawken,+NJ544 Weehawken,+NJ543 20 BACKBONE 27 | Weehawken,+NJ544 Weehawken,+NJ552 20 BACKBONE 28 | Weehawken,+NJ544 New+York,+NY293 50 BACKBONE 29 | Weehawken,+NJ544 Herndon,+VA495 40 BACKBONE 30 | Oak+Brook,+IL308 Oak+Brook,+IL315 20 Oak+Brook 31 | Oak+Brook,+IL308 Oak+Brook,+IL300 20 Oak+Brook 32 | Oak+Brook,+IL308 Oak+Brook,+IL301 20 Oak+Brook 33 | Toronto,+Canada537 Waltham,+MA555 10 BACKBONE 34 | Oak+Brook,+IL309 Oak+Brook,+IL300 20 Oak+Brook 35 | Oak+Brook,+IL309 Oak+Brook,+IL301 25 Oak+Brook 36 | Toronto,+Canada538 Oak+Brook,+IL300 10 BACKBONE 37 | Palo+Alto,+CA317 Santa+Clara,+CA388 30 BACKBONE 38 | Palo+Alto,+CA317 Palo+Alto,+CA104 25 BACKBONE 39 | Palo+Alto,+CA317 Santa+Clara,+CA336 25 BACKBONE 40 | Palo+Alto,+CA317 Palo+Alto,+CA318 20 BACKBONE 41 | Palo+Alto,+CA318 Santa+Clara,+CA363 20 BACKBONE 42 | Palo+Alto,+CA318 Santa+Clara,+CA364 20 BACKBONE 43 | Palo+Alto,+CA318 Palo+Alto,+CA317 20 BACKBONE 44 | Amsterdam119 London274 20 BACKBONE 45 | Amsterdam119 London275 55 BACKBONE 46 | Amsterdam119 Frankfurt185 10 BACKBONE 47 | Tukwila,+WA508 Tukwila,+WA509 20 BACKBONE 48 | Tukwila,+WA508 Santa+Clara,+CA429 70 BACKBONE 49 | Tukwila,+WA508 Chicago,+IL155 90 BACKBONE 50 | Oak+Brook,+IL310 Oak+Brook,+IL300 20 Oak+Brook 51 | Oak+Brook,+IL310 Oak+Brook,+IL301 35 Oak+Brook 52 | Tukwila,+WA509 Chicago,+IL155 60 BACKBONE 53 | Tukwila,+WA509 Tukwila,+WA508 20 BACKBONE 54 | Santa+Clara,+CA388 Santa+Clara,+CA430 20 BACKBONE 55 | Santa+Clara,+CA388 Santa+Clara,+CA389 25 BACKBONE 56 | Santa+Clara,+CA388 Palo+Alto,+CA317 30 BACKBONE 57 | Santa+Clara,+CA388 San+Jose,+CA460 30 BACKBONE 58 | Palo+Alto,+CA104 Santa+Clara,+CA444 25 BACKBONE 59 | Palo+Alto,+CA104 Santa+Clara,+CA365 20 BACKBONE 60 | Palo+Alto,+CA104 Palo+Alto,+CA317 25 BACKBONE 61 | Palo+Alto,+CA104 Santa+Clara,+CA403 20 BACKBONE 62 | Santa+Clara,+CA389 San+Jose,+CA471 40 BACKBONE 63 | Santa+Clara,+CA389 Santa+Clara,+CA388 25 BACKBONE 64 | Santa+Clara,+CA389 San+Jose,+CA472 35 BACKBONE 65 | Santa+Clara,+CA389 Santa+Clara,+CA443 40 BACKBONE 66 | Santa+Clara,+CA389 Santa+Clara,+CA403 30 BACKBONE 67 | Oak+Brook,+IL315 Oak+Brook,+IL307 20 Oak+Brook 68 | Oak+Brook,+IL315 Oak+Brook,+IL300 20 Oak+Brook 69 | Oak+Brook,+IL315 Oak+Brook,+IL308 20 Oak+Brook 70 | Oak+Brook,+IL315 Oak+Brook,+IL301 25 Oak+Brook 71 | Weehawken,+NJ552 Weehawken,+NJ543 20 BACKBONE 72 | Weehawken,+NJ552 Weehawken,+NJ544 20 BACKBONE 73 | Weehawken,+NJ552 Oak+Brook,+IL300 130 BACKBONE 74 | Chicago,+IL155 Tukwila,+WA509 60 BACKBONE 75 | Chicago,+IL155 Oak+Brook,+IL301 20 BACKBONE 76 | Chicago,+IL155 Chicago,+IL156 25 BACKBONE 77 | Chicago,+IL155 Tukwila,+WA508 90 BACKBONE 78 | Chicago,+IL156 Oak+Brook,+IL300 20 BACKBONE 79 | Chicago,+IL156 Fort+Worth,+TX189 70 BACKBONE 80 | Chicago,+IL156 Chicago,+IL155 25 BACKBONE 81 | Fort+Worth,+TX189 Irvine,+CA228 170 BACKBONE 82 | Fort+Worth,+TX189 Austin,+TX137 40 BACKBONE 83 | Fort+Worth,+TX189 Fort+Worth,+TX190 20 BACKBONE 84 | Fort+Worth,+TX189 Chicago,+IL156 70 BACKBONE 85 | Fort+Worth,+TX189 Santa+Clara,+CA403 160 BACKBONE 86 | Fort+Worth,+TX189 Fort+Worth,+TX191 35 Fort+Worth 87 | Herndon,+VA495 Herndon,+VA496 20 BACKBONE 88 | Herndon,+VA495 Herndon,+VA208 50 BACKBONE 89 | Herndon,+VA495 Weehawken,+NJ544 40 BACKBONE 90 | Jersey+City,+NJ244 Jersey+City,+NJ261 40 Jersey+City 91 | Jersey+City,+NJ244 Frankfurt184 10 BACKBONE 92 | Jersey+City,+NJ244 Jersey+City,+NJ245 20 BACKBONE 93 | Jersey+City,+NJ244 Weehawken,+NJ543 20 BACKBONE 94 | Jersey+City,+NJ244 Waltham,+MA568 50 BACKBONE 95 | Jersey+City,+NJ244 London277 10 BACKBONE 96 | Jersey+City,+NJ244 Waltham,+MA569 20 BACKBONE 97 | Herndon,+VA496 Santa+Clara,+CA429 225 BACKBONE 98 | Herndon,+VA496 Herndon,+VA193 30 BACKBONE 99 | Herndon,+VA496 Herndon,+VA495 20 BACKBONE 100 | Irvine,+CA212 El+Segundo,+CA163 10 BACKBONE 101 | Tokyo525 Santa+Clara,+CA404 25 BACKBONE 102 | Tokyo525 Tokyo526 20 BACKBONE 103 | Irvine,+CA213 Santa+Clara,+CA404 100 BACKBONE 104 | Irvine,+CA213 Irvine,+CA228 40 BACKBONE 105 | Irvine,+CA213 El+Segundo,+CA164 20 BACKBONE 106 | Jersey+City,+NJ245 Jersey+City,+NJ261 30 Jersey+City 107 | Jersey+City,+NJ245 London274 20 BACKBONE 108 | Jersey+City,+NJ245 New+York,+NY294 25 BACKBONE 109 | Jersey+City,+NJ245 London275 25 BACKBONE 110 | Jersey+City,+NJ245 London276 20 BACKBONE 111 | Jersey+City,+NJ245 Weehawken,+NJ544 20 BACKBONE 112 | Jersey+City,+NJ245 Jersey+City,+NJ244 20 BACKBONE 113 | Tokyo526 Tokyo525 20 BACKBONE 114 | Tokyo526 Santa+Clara,+CA336 25 BACKBONE 115 | Tokyo526 San+Jose,+CA460 20 BACKBONE 116 | Waltham,+MA555 Waltham,+MA556 20 BACKBONE 117 | Waltham,+MA555 Waltham,+MA568 20 BACKBONE 118 | Waltham,+MA555 Oak+Brook,+IL300 130 BACKBONE 119 | Waltham,+MA555 Toronto,+Canada537 10 BACKBONE 120 | Waltham,+MA556 Oak+Brook,+IL300 100 BACKBONE 121 | Waltham,+MA556 Waltham,+MA569 70 BACKBONE 122 | Waltham,+MA556 Waltham,+MA555 20 BACKBONE 123 | Herndon,+VA206 New+York,+NY293 20 BACKBONE 124 | Herndon,+VA206 Herndon,+VA208 20 BACKBONE 125 | Santa+Clara,+CA429 Herndon,+VA496 225 BACKBONE 126 | Santa+Clara,+CA429 Santa+Clara,+CA430 20 BACKBONE 127 | Santa+Clara,+CA429 Santa+Clara,+CA431 55 BACKBONE 128 | Santa+Clara,+CA429 San+Jose,+CA459 20 BACKBONE 129 | Santa+Clara,+CA429 Tukwila,+WA508 70 BACKBONE 130 | Santa+Clara,+CA429 Santa+Clara,+CA403 45 BACKBONE 131 | Herndon,+VA208 Herndon,+VA206 20 BACKBONE 132 | Herndon,+VA208 New+York,+NY293 40 BACKBONE 133 | Herndon,+VA208 Herndon,+VA495 50 BACKBONE 134 | Atlanta,+GA126 Atlanta,+GA127 30 BACKBONE 135 | Atlanta,+GA126 Herndon,+VA193 45 BACKBONE 136 | Atlanta,+GA126 Atlanta,+GA133 40 Atlanta 137 | Miami,+FL285 New+York,+NY293 50 BACKBONE 138 | Miami,+FL285 Miami,+FL286 80 BACKBONE 139 | Atlanta,+GA127 Atlanta,+GA126 30 BACKBONE 140 | Atlanta,+GA127 Miami,+FL286 25 BACKBONE 141 | Atlanta,+GA127 Weehawken,+NJ543 185 BACKBONE 142 | Atlanta,+GA127 Herndon,+VA193 75 BACKBONE 143 | Atlanta,+GA127 Fort+Worth,+TX190 35 BACKBONE 144 | Atlanta,+GA127 Atlanta,+GA133 20 Atlanta 145 | Miami,+FL286 Miami,+FL285 80 BACKBONE 146 | Miami,+FL286 Atlanta,+GA127 25 BACKBONE 147 | Fort+Worth,+TX190 Irvine,+CA228 140 BACKBONE 148 | Fort+Worth,+TX190 Atlanta,+GA127 35 BACKBONE 149 | Fort+Worth,+TX190 Irvine,+CA229 160 BACKBONE 150 | Fort+Worth,+TX190 Austin,+TX136 10 BACKBONE 151 | Fort+Worth,+TX190 Fort+Worth,+TX189 20 BACKBONE 152 | Fort+Worth,+TX190 Fort+Worth,+TX191 25 Fort+Worth 153 | San+Jose,+CA459 Santa+Clara,+CA429 20 BACKBONE 154 | San+Jose,+CA459 San+Jose,+CA460 60 BACKBONE 155 | Fort+Worth,+TX191 Fort+Worth,+TX189 35 Fort+Worth 156 | Fort+Worth,+TX191 Fort+Worth,+TX190 25 Fort+Worth 157 | New+York,+NY293 Miami,+FL285 50 BACKBONE 158 | New+York,+NY293 Herndon,+VA206 20 BACKBONE 159 | New+York,+NY293 Herndon,+VA208 40 BACKBONE 160 | New+York,+NY293 New+York,+NY294 55 BACKBONE 161 | New+York,+NY293 Weehawken,+NJ543 80 BACKBONE 162 | New+York,+NY293 Weehawken,+NJ544 50 BACKBONE 163 | New+York,+NY294 Jersey+City,+NJ245 25 BACKBONE 164 | New+York,+NY294 New+York,+NY293 55 BACKBONE 165 | New+York,+NY294 Weehawken,+NJ543 25 BACKBONE 166 | London274 Jersey+City,+NJ245 20 BACKBONE 167 | London274 Amsterdam119 20 BACKBONE 168 | London274 Weehawken,+NJ544 40 BACKBONE 169 | London275 Jersey+City,+NJ245 25 BACKBONE 170 | London275 Amsterdam119 55 BACKBONE 171 | London275 Weehawken,+NJ544 45 BACKBONE 172 | Santa+Clara,+CA430 Santa+Clara,+CA404 45 BACKBONE 173 | Santa+Clara,+CA430 Santa+Clara,+CA364 55 BACKBONE 174 | Santa+Clara,+CA430 Santa+Clara,+CA429 20 BACKBONE 175 | Santa+Clara,+CA430 Santa+Clara,+CA388 20 BACKBONE 176 | Santa+Clara,+CA430 San+Jose,+CA472 20 BACKBONE 177 | Santa+Clara,+CA430 Santa+Clara,+CA336 25 BACKBONE 178 | Santa+Clara,+CA430 Santa+Clara,+CA443 35 BACKBONE 179 | London276 Jersey+City,+NJ245 20 BACKBONE 180 | London276 Weehawken,+NJ544 40 BACKBONE 181 | Santa+Clara,+CA431 Santa+Clara,+CA363 20 BACKBONE 182 | Santa+Clara,+CA431 Santa+Clara,+CA429 55 BACKBONE 183 | Santa+Clara,+CA431 Santa+Clara,+CA364 20 BACKBONE 184 | Santa+Clara,+CA431 Santa+Clara,+CA365 40 BACKBONE 185 | Santa+Clara,+CA431 San+Jose,+CA472 25 BACKBONE 186 | Santa+Clara,+CA431 Santa+Clara,+CA443 40 BACKBONE 187 | London277 Jersey+City,+NJ244 10 BACKBONE 188 | Santa+Clara,+CA363 Santa+Clara,+CA431 20 BACKBONE 189 | Santa+Clara,+CA363 Palo+Alto,+CA318 20 BACKBONE 190 | Irvine,+CA228 Irvine,+CA213 40 BACKBONE 191 | Irvine,+CA228 Irvine,+CA229 20 BACKBONE 192 | Irvine,+CA228 Irvine,+CA237 20 Irvine 193 | Irvine,+CA228 Irvine,+CA230 30 BACKBONE 194 | Irvine,+CA228 Irvine,+CA231 20 BACKBONE 195 | Irvine,+CA228 Fort+Worth,+TX189 170 BACKBONE 196 | Irvine,+CA228 Fort+Worth,+TX190 140 BACKBONE 197 | Santa+Clara,+CA364 Santa+Clara,+CA430 55 BACKBONE 198 | Santa+Clara,+CA364 Santa+Clara,+CA431 20 BACKBONE 199 | Santa+Clara,+CA364 Palo+Alto,+CA318 20 BACKBONE 200 | Irvine,+CA229 Irvine,+CA228 20 BACKBONE 201 | Irvine,+CA229 El+Segundo,+CA164 30 BACKBONE 202 | Irvine,+CA229 Fort+Worth,+TX190 160 BACKBONE 203 | Santa+Clara,+CA365 Palo+Alto,+CA104 20 BACKBONE 204 | Santa+Clara,+CA365 Santa+Clara,+CA431 40 BACKBONE 205 | Waltham,+MA568 Waltham,+MA569 20 BACKBONE 206 | Waltham,+MA568 Jersey+City,+NJ244 50 BACKBONE 207 | Waltham,+MA568 Waltham,+MA555 20 BACKBONE 208 | Waltham,+MA569 Waltham,+MA556 70 BACKBONE 209 | Waltham,+MA569 Waltham,+MA568 20 BACKBONE 210 | Waltham,+MA569 Jersey+City,+NJ244 20 BACKBONE 211 | Atlanta,+GA133 Atlanta,+GA126 40 Atlanta 212 | Atlanta,+GA133 Atlanta,+GA127 20 Atlanta 213 | San+Jose,+CA460 Tokyo526 20 BACKBONE 214 | San+Jose,+CA460 Santa+Clara,+CA388 30 BACKBONE 215 | San+Jose,+CA460 San+Jose,+CA459 60 BACKBONE 216 | San+Jose,+CA460 Santa+Clara,+CA443 25 BACKBONE 217 | El+Segundo,+CA163 Irvine,+CA212 10 BACKBONE 218 | El+Segundo,+CA163 Santa+Clara,+CA404 50 BACKBONE 219 | El+Segundo,+CA163 Santa+Clara,+CA405 30 BACKBONE 220 | El+Segundo,+CA163 El+Segundo,+CA164 20 BACKBONE 221 | El+Segundo,+CA163 Austin,+TX137 160 BACKBONE 222 | Austin,+TX136 Fort+Worth,+TX190 10 BACKBONE 223 | El+Segundo,+CA164 Irvine,+CA213 20 BACKBONE 224 | El+Segundo,+CA164 Irvine,+CA229 30 BACKBONE 225 | El+Segundo,+CA164 Irvine,+CA230 20 BACKBONE 226 | El+Segundo,+CA164 Irvine,+CA231 30 BACKBONE 227 | El+Segundo,+CA164 El+Segundo,+CA163 20 BACKBONE 228 | El+Segundo,+CA164 Santa+Clara,+CA403 90 BACKBONE 229 | El+Segundo,+CA164 Santa+Clara,+CA443 60 BACKBONE 230 | Austin,+TX137 El+Segundo,+CA163 160 BACKBONE 231 | Austin,+TX137 Fort+Worth,+TX189 40 BACKBONE 232 | Santa+Clara,+CA403 Santa+Clara,+CA444 25 BACKBONE 233 | Santa+Clara,+CA403 Santa+Clara,+CA404 20 BACKBONE 234 | Santa+Clara,+CA403 Santa+Clara,+CA429 45 BACKBONE 235 | Santa+Clara,+CA403 Santa+Clara,+CA405 50 BACKBONE 236 | Santa+Clara,+CA403 Palo+Alto,+CA104 20 BACKBONE 237 | Santa+Clara,+CA403 Santa+Clara,+CA389 30 BACKBONE 238 | Santa+Clara,+CA403 Fort+Worth,+TX189 160 BACKBONE 239 | Santa+Clara,+CA403 El+Segundo,+CA164 90 BACKBONE 240 | Jersey+City,+NJ261 Jersey+City,+NJ245 30 Jersey+City 241 | Jersey+City,+NJ261 Jersey+City,+NJ244 40 Jersey+City 242 | Santa+Clara,+CA404 Santa+Clara,+CA444 45 BACKBONE 243 | Santa+Clara,+CA404 Irvine,+CA213 100 BACKBONE 244 | Santa+Clara,+CA404 Tokyo525 25 BACKBONE 245 | Santa+Clara,+CA404 Santa+Clara,+CA430 45 BACKBONE 246 | Santa+Clara,+CA404 El+Segundo,+CA163 50 BACKBONE 247 | Santa+Clara,+CA404 Santa+Clara,+CA443 30 BACKBONE 248 | Santa+Clara,+CA404 Santa+Clara,+CA403 20 BACKBONE 249 | Santa+Clara,+CA405 El+Segundo,+CA163 30 BACKBONE 250 | Santa+Clara,+CA405 Santa+Clara,+CA403 50 BACKBONE 251 | Irvine,+CA230 Irvine,+CA228 30 BACKBONE 252 | Irvine,+CA230 El+Segundo,+CA164 20 BACKBONE 253 | Irvine,+CA231 Irvine,+CA228 20 BACKBONE 254 | Irvine,+CA231 Irvine,+CA237 30 Irvine 255 | Irvine,+CA231 El+Segundo,+CA164 30 BACKBONE 256 | Santa+Clara,+CA336 Tokyo526 25 BACKBONE 257 | Santa+Clara,+CA336 Santa+Clara,+CA430 25 BACKBONE 258 | Santa+Clara,+CA336 Palo+Alto,+CA317 25 BACKBONE 259 | Santa+Clara,+CA443 Santa+Clara,+CA444 25 BACKBONE 260 | Santa+Clara,+CA443 Santa+Clara,+CA404 30 BACKBONE 261 | Santa+Clara,+CA443 Santa+Clara,+CA430 35 BACKBONE 262 | Santa+Clara,+CA443 Santa+Clara,+CA431 40 BACKBONE 263 | Santa+Clara,+CA443 Santa+Clara,+CA389 40 BACKBONE 264 | Santa+Clara,+CA443 El+Segundo,+CA164 60 BACKBONE 265 | Santa+Clara,+CA443 San+Jose,+CA460 25 BACKBONE 266 | Santa+Clara,+CA444 Santa+Clara,+CA404 45 BACKBONE 267 | Santa+Clara,+CA444 San+Jose,+CA471 45 BACKBONE 268 | Santa+Clara,+CA444 Palo+Alto,+CA104 25 BACKBONE 269 | Santa+Clara,+CA444 Santa+Clara,+CA443 25 BACKBONE 270 | Santa+Clara,+CA444 Santa+Clara,+CA403 25 BACKBONE 271 | Frankfurt184 Jersey+City,+NJ244 10 BACKBONE 272 | Irvine,+CA237 Irvine,+CA228 20 Irvine 273 | Irvine,+CA237 Irvine,+CA231 30 Irvine 274 | Frankfurt185 Amsterdam119 10 BACKBONE 275 | Oak+Brook,+IL300 Weehawken,+NJ543 120 BACKBONE 276 | Oak+Brook,+IL300 Oak+Brook,+IL307 20 Oak+Brook 277 | Oak+Brook,+IL300 Oak+Brook,+IL315 20 Oak+Brook 278 | Oak+Brook,+IL300 Weehawken,+NJ552 130 BACKBONE 279 | Oak+Brook,+IL300 Oak+Brook,+IL308 20 Oak+Brook 280 | Oak+Brook,+IL300 Oak+Brook,+IL309 20 Oak+Brook 281 | Oak+Brook,+IL300 Chicago,+IL156 20 BACKBONE 282 | Oak+Brook,+IL300 Toronto,+Canada538 10 BACKBONE 283 | Oak+Brook,+IL300 Waltham,+MA555 130 BACKBONE 284 | Oak+Brook,+IL300 Waltham,+MA556 100 BACKBONE 285 | Oak+Brook,+IL300 Oak+Brook,+IL301 25 BACKBONE 286 | Oak+Brook,+IL300 Oak+Brook,+IL310 20 Oak+Brook 287 | Oak+Brook,+IL301 San+Jose,+CA471 145 BACKBONE 288 | Oak+Brook,+IL301 Oak+Brook,+IL307 20 Oak+Brook 289 | Oak+Brook,+IL301 Oak+Brook,+IL315 25 Oak+Brook 290 | Oak+Brook,+IL301 Oak+Brook,+IL308 20 Oak+Brook 291 | Oak+Brook,+IL301 Chicago,+IL155 20 BACKBONE 292 | Oak+Brook,+IL301 Oak+Brook,+IL309 25 Oak+Brook 293 | Oak+Brook,+IL301 Oak+Brook,+IL300 25 BACKBONE 294 | Oak+Brook,+IL301 Oak+Brook,+IL310 35 Oak+Brook 295 | --------------------------------------------------------------------------------