Skip to content

Commit

Permalink
WIP: backporting existing code
Browse files Browse the repository at this point in the history
Signed-off-by: Olivier Tilmans <olivier.tilmans@uclouvain.be>
  • Loading branch information
oliviertilmans committed Mar 17, 2016
0 parents commit 44b661f
Show file tree
Hide file tree
Showing 12 changed files with 671 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.autoenv*
*.swp
*.pyc
build
dist
*.egg-info
*.egg
*.cache
339 changes: 339 additions & 0 deletions LICENSE.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
recursive-include fibbingnode/res *
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# IPMininet

This is a python library, extending [Mininet](http://mininet.org), in order
to support emulation of (complex) IP networks. As such it provides new classes,
such as Routers, auto-configures all properties not set by the user, such as
IP addresses or router configuration files, ...
30 changes: 30 additions & 0 deletions ipmininet/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from mininet.cli import CLI
from mininet.log import lg


class IPCLI(CLI):
def do_route(self, line=""):
"""route destination: Print all the routes towards that destination
for every router in the network"""
for r in self.mn.routers:
self.default('[%s] ip route get %s' % (r.name, line))

def do_ip(self, line):
"""ip IP1 IP2 ...: return the node associated to the given IP"""
for ip in line.split(' '):
try:
n = self.mn.node_for_ip(ip)
except KeyError:
n = 'unknown IP'
finally:
lg.info(ip, '|', n)

def do_ips(self, line):
"""ips n1 n2 ...: return the ips associated to the given node name"""
for n in line.split(' '):
try:
l = [itf.ip for itf in self.mn[n].intfList()]
except KeyError:
l = 'unknown node'
finally:
lg.info(n, '|', l)
39 changes: 39 additions & 0 deletions ipmininet/iptopo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""This module defines topology class that supports adding L3 routers"""
import ipmininet

from mininet.topo import Topo


class IPTopo(Topo):
"""A topology that supports L3 routers"""

def isNodeType(self, n, x):
"""Return wether node n has a key x set to True
:param n: node name
:param x: the key to check"""
try:
return self.g.node[n].get(x, False)
except KeyError: # node not found
return False

def addRouter(self, name, **kwargs):
"""Add a router to the topology
:param name: the name of the node"""
return self.addNode(name, isRouter=True, **kwargs)

def isRouter(self, n):
"""Check whether the given node is a router
:param n: node name"""
return self.isNodeType(n, 'isRouter')

def hosts(self, sort=True):
# The list si already sorted, simply filter out the routers
return [h for h in super(IPTopo, self).hosts(sort)
if not self.isRouter(h)]

def routers(self, sort=True):
"""Return a list of router node names"""
return filter(self.isRouter, self.nodes(sort))
29 changes: 29 additions & 0 deletions ipmininet/res/ospf.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
hostname ${node.hostname}
password ${node.password}
% if node.ospf.logfile:
log file ${node.ospf.logfile}
% endif
% for section in node.ospf.debug:
debug ospf section
% endfor
!
% for intf in node.ospf.interfaces:
interface ${intf.name}
# ${intf.description}
# Highiest priority routers will be DR
ip ospf priority ${intf.ospf.priority}
ip ospf cost ${intf.ospf.cost}
# dead/hello intervals must be consistent across a broadcast domain
ip ospf dead-interval ${intf.ospf.dead_int}
ip ospf hello-interval ${intf.ospf.hello_int}
!
% endfor
router ospf
router-id ${node.ospf.router_id}
% for type, prop in node.ospf.redistribute:
redistribute ${type} metric-type ${prop.metric_type} metric ${prop.metric}
% endfor
% for net in node.ospf.networks:
network ${net.domain.with_prefixlen} area ${net.area}
% endfor
!
26 changes: 26 additions & 0 deletions ipmininet/res/zebra.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
hostname ${node.hostname}
password ${node.password}
% if node.zebra.logfile:
log file ${node.zebra.logfile}
% endif
% for section in node.zebra.debug:
debug zebra section
% endfor
% for pl in node.zebra.prefixlists:
ip prefix-list ${pl.name} ${pl.action} ${pl.prefix} ${pl.condition}
% endfor
!
% for rm in node.zebra.routemaps:
route-map ${rm.name} ${rm.action} ${rm.prio}
% for prefix in rm.prefix:
match ip address prefix-list ${prefix}
% endfor
!
% for proto in rm.proto:
ip protocol ${proto} route-map ${rm.name}
% endfor
% endfor
!
% for prefix, via in node.zebra.static_routes:
ip route ${prefix} via ${via}
% endfor
136 changes: 136 additions & 0 deletions ipmininet/topologydb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""This module defines a data-store to help dealing wtih all (possibly)
auto-allocated properties of a topology: ip addresses, router ids, ..."""
import json
from ipaddress import ip_interface

from ipmininet import otherIntf, realIntfList

from mininet.log import lg


class TopologyDB(object):
"""A convenience store for auto-allocated mininet properties.
This is *NOT* to be used as IGP graph"""
def __init__(self, db=None, net=None, *args, **kwargs):
"""Either extract properties from a network or load a save file
:param db: a path towards a saved version of this class which will
be loaded
:param net: an IPNet instance which will be parsed in order to extract
useful properties
"""
super(TopologyDB, self).__init__(*args, **kwargs)
"""dict keyed by node name ->
dict keyed by - properties -> val
- neighbor -> interface properties"""
self._network = {}
if db:
self.load(db)
if net:
self.parse_net(net)
if not db and not net:
lg.warning('TopologyDB instantiated without any data')

def load(self, fpath):
"""Load a topology database
:param fpath: path towards the file to load"""
with open(fpath, 'r') as f:
self._network = json.load(f)

def save(self, fpath):
"""Save the topology database
:param fpath: the save file name"""
with open(fpath, 'w') as f:
json.dump(self._network, f)

def _node(self, x):
try:
return self._network[x]
except KeyError:
raise ValueError('No node named %s in the network' % x)

def _interface(self, x, y):
try:
return self._node(x)[y]
except KeyError:
raise ValueError('The link %s-%s does not exist' % (x, y))

def interface(self, x, y):
"""Return the ip address of the interface of x facing y
:param x: the node from which we want an IP address
:param y: the node on the other side of the link
:return: ip_interface-like object"""
return ip_interface(self._interface(x, y)['ip'])

def interface_bandwidth(self, x, y):
"""Return the bandwidth capacity of the interface on node x
facing node y.
:param x: node name
:param y: node name
:return: The bandwidth of link x-y, -1 if unlimited"""
try:
return self._interface(x, y)['bw']
except KeyError:
return -1

def subnet(self, x, y):
"""Return the subnet linking node x and y
:param x: node name
:param y: node name
:return: ip_network-like object"""
return self.interface(x, y).network

def routerid(self, x):
"""Return the router id of a node
:param x: router name
:return: the routerid"""
n = self._node(x)
if n['type'] != 'router':
raise TypeError('%s is not a router' % x)
return n['routerid']

def parse_net(self, net):
"""Stores the content of the given network
:param net: IPNet instance"""
for h in net.hosts:
self.add_host(h)
for s in net.switches:
self.add_switch(s)
for r in net.routers:
self.add_router(r)

def _add_node(self, n, props):
for itf in realIntfList(n):
nh = otherIntf(itf)
props[nh.node.name] = {
'ip': '%s/%s' % (itf.ip, itf.prefixLen),
'name': itf.name,
'bw': itf.params.get('bw', -1)
}
self._network[n.name] = props

def add_host(self, n):
"""Register an host
:param n: Host instance"""
self._add_node(n, {'type': 'host'})

def add_switch(self, n):
"""Register an switch
:param n: Switch instance"""
self._add_node(n, {'type': 'switch'})

def add_router(self, n):
"""Register an router
:param n: Router instance"""
self._add_node(n, {'type': 'router',
'routerid': n.id})
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[aliases]
test=pytest
35 changes: 35 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python

"Setuptools params"

from setuptools import setup, find_packages

VERSION = '0.1a'

modname = distname = 'ipmininet'

setup(
name=distname,
version=VERSION,
description='A mininet extension providing components to emulate IP networks',
author='Olivier Tilmans',
author_email='olivier.tilmans@uclouvain.be',
packages=find_packages(),
include_package_data = True,
classifiers=[
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"Topic :: System :: Networking",
],
keywords='networking OSPF IP mininet',
license='GPLv2',
install_requires=[
'setuptools',
'mako',
'py2-ipaddress'
],
tests_require=['pytest'],
setup_requires=['pytest-runner']
)
20 changes: 20 additions & 0 deletions tests/test_pyaddress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import pytest
import ipaddress


def test_nested_ip_networks():
"""This test ensures that we can build an IPvXNetwork from another one.
If this breaks, need to grep through for ip_network calls as I removed the
checks when instantiating these ...
Test passing with py2-ipaddress (3.4.1)"""
_N = ipaddress.ip_network
for p in ('::/0',
'0.0.0.0/0',
'1.2.3.0/24',
'2001:db8:1234::/48'):
n1 = _N(p) # Build an IPvXNetwork
n2 = _N(n1) # Build a new one from the previous one
assert (n1 == n2 and
n1.with_prefixlen == p and
n2.with_prefixlen == p and
n1.max_prefixlen == n2.max_prefixlen)

0 comments on commit 44b661f

Please sign in to comment.