blob: 970ce778d35a2aa1c11dcdc6b45eb0dcf5199c34 [file] [log] [blame]
Jonathan Hartce97e5b2016-04-19 01:41:31 -07001#!/usr/bin/python
2
3"""
4Libraries for creating L3 topologies with routing protocols.
5"""
6
7from mininet.node import Host, OVSBridge
8from mininet.nodelib import NAT
9from mininet.log import info, debug, error
10from mininet.cli import CLI
11from ipaddress import ip_network, ip_address, ip_interface
Jonathan Hartd5d80872017-01-16 13:55:41 -080012import os
Jonathan Hartce97e5b2016-04-19 01:41:31 -070013
14class RoutedHost(Host):
15 """Host that can be configured with multiple IP addresses."""
16 def __init__(self, name, ips, gateway, *args, **kwargs):
17 super(RoutedHost, self).__init__(name, *args, **kwargs)
18
19 self.ips = ips
20 self.gateway = gateway
21
22 def config(self, **kwargs):
23 Host.config(self, **kwargs)
24
Charles Chan76128b62017-03-27 20:28:14 -070025 self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
Jonathan Hartce97e5b2016-04-19 01:41:31 -070026 for ip in self.ips:
27 self.cmd('ip addr add %s dev %s' % (ip, self.defaultIntf()))
28
29 self.cmd('ip route add default via %s' % self.gateway)
30
Yi Tseng45ee6922017-07-17 14:49:17 -070031class RoutedHost6(Host):
32 """Host that can be configured with multiple IP addresses."""
33 def __init__(self, name, ips, gateway, *args, **kwargs):
34 super(RoutedHost6, self).__init__(name, *args, **kwargs)
35
36 self.ips = ips
37 self.gateway = gateway
38
39 def config(self, **kwargs):
40 Host.config(self, **kwargs)
41
42 self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
43 for ip in self.ips:
44 self.cmd('ip -6 addr add %s dev %s' % (ip, self.defaultIntf()))
45
46 self.cmd('ip -6 route add default via %s' % self.gateway)
47
Jonathan Hartce97e5b2016-04-19 01:41:31 -070048class Router(Host):
Charles Chan76128b62017-03-27 20:28:14 -070049
Jonathan Hartce97e5b2016-04-19 01:41:31 -070050 """An L3 router.
51 Configures the Linux kernel for L3 forwarding and supports rich interface
52 configuration of IP addresses, MAC addresses and VLANs."""
Charles Chan76128b62017-03-27 20:28:14 -070053
Jonathan Hartce97e5b2016-04-19 01:41:31 -070054 def __init__(self, name, interfaces, *args, **kwargs):
55 super(Router, self).__init__(name, **kwargs)
56
57 self.interfaces = interfaces
Charles Chan76128b62017-03-27 20:28:14 -070058
Jonathan Hartce97e5b2016-04-19 01:41:31 -070059 def config(self, **kwargs):
60 super(Host, self).config(**kwargs)
Charles Chan76128b62017-03-27 20:28:14 -070061
Jonathan Hartce97e5b2016-04-19 01:41:31 -070062 self.cmd('sysctl net.ipv4.ip_forward=1')
63 self.cmd('sysctl net.ipv4.conf.all.rp_filter=0')
Charles Chan76128b62017-03-27 20:28:14 -070064 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
Jonathan Hartce97e5b2016-04-19 01:41:31 -070065
66 for intf, configs in self.interfaces.items():
Charles Chan76128b62017-03-27 20:28:14 -070067 self.cmd('ip -4 addr flush dev %s' % intf)
Jonathan Hartce97e5b2016-04-19 01:41:31 -070068 self.cmd( 'sysctl net.ipv4.conf.%s.rp_filter=0' % intf )
Charles Chan76128b62017-03-27 20:28:14 -070069
Jonathan Hartce97e5b2016-04-19 01:41:31 -070070 if not isinstance(configs, list):
71 configs = [configs]
Charles Chan76128b62017-03-27 20:28:14 -070072
Jonathan Hartce97e5b2016-04-19 01:41:31 -070073 for attrs in configs:
Charles Chan76128b62017-03-27 20:28:14 -070074 # Configure the vlan if there is one
Jonathan Hartce97e5b2016-04-19 01:41:31 -070075 if 'vlan' in attrs:
76 vlanName = '%s.%s' % (intf, attrs['vlan'])
Charles Chan76128b62017-03-27 20:28:14 -070077 self.cmd('ip link add link %s name %s type vlan id %s' %
Jonathan Hartce97e5b2016-04-19 01:41:31 -070078 (intf, vlanName, attrs['vlan']))
79 self.cmd('ip link set %s up' % vlanName)
80 addrIntf = vlanName
81 else:
82 addrIntf = intf
Charles Chan76128b62017-03-27 20:28:14 -070083
Jonathan Hartce97e5b2016-04-19 01:41:31 -070084 # Now configure the addresses on the vlan/native interface
85 if 'mac' in attrs:
86 self.cmd('ip link set %s down' % addrIntf)
87 self.cmd('ip link set %s address %s' % (addrIntf, attrs['mac']))
88 self.cmd('ip link set %s up' % addrIntf)
89 for addr in attrs['ipAddrs']:
90 self.cmd('ip addr add %s dev %s' % (addr, addrIntf))
91
92class QuaggaRouter(Router):
Charles Chan76128b62017-03-27 20:28:14 -070093
Jonathan Hartce97e5b2016-04-19 01:41:31 -070094 """Runs Quagga to create a router that can speak routing protocols."""
Charles Chan76128b62017-03-27 20:28:14 -070095
Jonathan Hartce97e5b2016-04-19 01:41:31 -070096 binDir = '/usr/lib/quagga'
Jonathan Hartd5d80872017-01-16 13:55:41 -080097 logDir = '/var/log/quagga'
Charles Chan76128b62017-03-27 20:28:14 -070098
Jonathan Hartce97e5b2016-04-19 01:41:31 -070099 def __init__(self, name, interfaces,
100 defaultRoute=None,
101 zebraConfFile=None,
102 protocols=[],
103 fpm=None,
104 runDir='/var/run/quagga', *args, **kwargs):
105 super(QuaggaRouter, self).__init__(name, interfaces, **kwargs)
Charles Chan76128b62017-03-27 20:28:14 -0700106
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700107 self.protocols = protocols
108 self.fpm = fpm
Charles Chan76128b62017-03-27 20:28:14 -0700109
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700110 for p in self.protocols:
111 p.setQuaggaRouter(self)
Charles Chan76128b62017-03-27 20:28:14 -0700112
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700113 self.runDir = runDir
114 self.defaultRoute = defaultRoute
Charles Chan76128b62017-03-27 20:28:14 -0700115
Jonathan Hartd5d80872017-01-16 13:55:41 -0800116 # Ensure required directories exist
117 try:
118 original_umask = os.umask(0)
119 if (not os.path.isdir(QuaggaRouter.logDir)):
120 os.makedirs(QuaggaRouter.logDir, 0777)
121 if (not os.path.isdir(self.runDir)):
122 os.makedirs(self.runDir, 0777)
123 finally:
124 os.umask(original_umask)
125
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700126 self.zebraConfFile = zebraConfFile
127 if (self.zebraConfFile is None):
128 self.zebraConfFile = '%s/zebrad%s.conf' % (self.runDir, self.name)
129 self.generateZebra()
Charles Chan76128b62017-03-27 20:28:14 -0700130
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700131 self.socket = '%s/zebra%s.api' % (self.runDir, self.name)
Charles Chan76128b62017-03-27 20:28:14 -0700132
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700133 self.zebraPidFile = '%s/zebra%s.pid' % (self.runDir, self.name)
134
135 def generateZebra(self):
136 configFile = open(self.zebraConfFile, 'w+')
Jonathan Hartd5d80872017-01-16 13:55:41 -0800137 configFile.write('log file %s/zebrad%s.log\n' % (QuaggaRouter.logDir, self.name))
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700138 configFile.write('hostname zebra-%s\n' % self.name)
Jonathan Harte6b897f2017-01-24 17:09:58 -0800139 configFile.write('password %s\n' % 'quagga')
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700140 if (self.fpm is not None):
141 configFile.write('fpm connection ip %s port 2620' % self.fpm)
142 configFile.close()
143
144 def config(self, **kwargs):
145 super(QuaggaRouter, self).config(**kwargs)
146
147 self.cmd('%s/zebra -d -f %s -z %s -i %s'
148 % (QuaggaRouter.binDir, self.zebraConfFile, self.socket, self.zebraPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700149
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700150 for p in self.protocols:
151 p.config(**kwargs)
Charles Chan76128b62017-03-27 20:28:14 -0700152
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700153 if self.defaultRoute:
154 self.cmd('ip route add default via %s' % self.defaultRoute)
Charles Chan76128b62017-03-27 20:28:14 -0700155
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700156 def terminate(self, **kwargs):
Charles Chan76128b62017-03-27 20:28:14 -0700157 self.cmd("ps ax | grep '%s' | awk '{print $1}' | xargs kill"
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700158 % (self.socket))
Charles Chan76128b62017-03-27 20:28:14 -0700159
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700160 for p in self.protocols:
161 p.terminate(**kwargs)
162
163 super(QuaggaRouter, self).terminate()
Charles Chan76128b62017-03-27 20:28:14 -0700164
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700165class Protocol(object):
Charles Chan76128b62017-03-27 20:28:14 -0700166
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700167 """Base abstraction of a protocol that the QuaggaRouter can run."""
Charles Chan76128b62017-03-27 20:28:14 -0700168
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700169 def setQuaggaRouter(self, qr):
170 self.qr = qr
Charles Chan76128b62017-03-27 20:28:14 -0700171
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700172 def config(self, **kwargs):
173 pass
Charles Chan76128b62017-03-27 20:28:14 -0700174
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700175 def terminate(self, **kwargs):
176 pass
Charles Chan76128b62017-03-27 20:28:14 -0700177
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700178class BgpProtocol(Protocol):
Charles Chan76128b62017-03-27 20:28:14 -0700179
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700180 """Configures and runs the BGP protocol in Quagga."""
Charles Chan76128b62017-03-27 20:28:14 -0700181
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700182 def __init__(self, configFile=None, asNum=None, neighbors=[], routes=[], *args, **kwargs):
183 self.configFile = configFile
Charles Chan76128b62017-03-27 20:28:14 -0700184
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700185 self.asNum = asNum
186 self.neighbors = neighbors
187 self.routes = routes
Charles Chan76128b62017-03-27 20:28:14 -0700188
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700189 def config(self, **kwargs):
190 if self.configFile is None:
191 self.configFile = '%s/bgpd%s.conf' % (self.qr.runDir, self.qr.name)
192 self.generateConfig()
Charles Chan76128b62017-03-27 20:28:14 -0700193
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700194 bgpdPidFile = '%s/bgpd%s.pid' % (self.qr.runDir, self.qr.name)
Charles Chan76128b62017-03-27 20:28:14 -0700195
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700196 self.qr.cmd('%s/bgpd -d -f %s -z %s -i %s'
197 % (QuaggaRouter.binDir, self.configFile, self.qr.socket, bgpdPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700198
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700199 def generateConfig(self):
200 conf = ConfigurationWriter(self.configFile)
Charles Chan76128b62017-03-27 20:28:14 -0700201
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700202 def getRouterId(interfaces):
203 intfAttributes = interfaces.itervalues().next()
204 print intfAttributes
205 if isinstance(intfAttributes, list):
206 # Try use the first set of attributes, but if using vlans they might not have addresses
207 intfAttributes = intfAttributes[1] if not intfAttributes[0]['ipAddrs'] else intfAttributes[0]
208 return intfAttributes['ipAddrs'][0].split('/')[0]
Charles Chan76128b62017-03-27 20:28:14 -0700209
Jonathan Hartd5d80872017-01-16 13:55:41 -0800210 conf.writeLine('log file %s/bgpd%s.log' % (QuaggaRouter.logDir, self.qr.name))
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700211 conf.writeLine('hostname bgp-%s' % self.qr.name);
Jonathan Harte6b897f2017-01-24 17:09:58 -0800212 conf.writeLine('password %s' % 'quagga')
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700213 conf.writeLine('!')
214 conf.writeLine('router bgp %s' % self.asNum)
Charles Chan76128b62017-03-27 20:28:14 -0700215
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700216 conf.indent()
Charles Chan76128b62017-03-27 20:28:14 -0700217
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700218 conf.writeLine('bgp router-id %s' % getRouterId(self.qr.interfaces))
219 conf.writeLine('timers bgp %s' % '3 9')
220 conf.writeLine('!')
Charles Chan76128b62017-03-27 20:28:14 -0700221
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700222 for neighbor in self.neighbors:
223 conf.writeLine('neighbor %s remote-as %s' % (neighbor['address'], neighbor['as']))
224 conf.writeLine('neighbor %s ebgp-multihop' % neighbor['address'])
225 conf.writeLine('neighbor %s timers connect %s' % (neighbor['address'], '5'))
226 conf.writeLine('neighbor %s advertisement-interval %s' % (neighbor['address'], '5'))
227 if 'port' in neighbor:
228 conf.writeLine('neighbor %s port %s' % (neighbor['address'], neighbor['port']))
229 conf.writeLine('!')
Charles Chan76128b62017-03-27 20:28:14 -0700230
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700231 for route in self.routes:
232 conf.writeLine('network %s' % route)
Charles Chan76128b62017-03-27 20:28:14 -0700233
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700234 conf.close()
Charles Chan76128b62017-03-27 20:28:14 -0700235
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700236class OspfProtocol(Protocol):
Charles Chan76128b62017-03-27 20:28:14 -0700237
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700238 """Configures and runs the OSPF protocol in Quagga."""
Charles Chan76128b62017-03-27 20:28:14 -0700239
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700240 def __init__(self, configFile=None, *args, **kwargs):
241 self.configFile = configFile
Charles Chan76128b62017-03-27 20:28:14 -0700242
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700243 def config(self, **kwargs):
244 if self.configFile is None:
245 self.configFile = '%s/ospfd%s.conf' % (self.qr.runDir, self.qr.name)
246 self.generateConfig()
Charles Chan76128b62017-03-27 20:28:14 -0700247
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700248 ospfPidFile = '%s/ospf%s.pid' % (self.qr.runDir, self.qr.name)
Charles Chan76128b62017-03-27 20:28:14 -0700249
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700250 self.qr.cmd('%s/ospfd -d -f %s -z %s -i %s'
251 % (QuaggaRouter.binDir, self.configFile, self.qr.socket, ospfPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700252
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700253 def generateConfig(self):
254 conf = ConfigurationWriter(self.configFile)
Charles Chan76128b62017-03-27 20:28:14 -0700255
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700256 def getRouterId(interfaces):
257 intfAttributes = interfaces.itervalues().next()
258 print intfAttributes
259 if isinstance(intfAttributes, list):
260 # Try use the first set of attributes, but if using vlans they might not have addresses
261 intfAttributes = intfAttributes[1] if not intfAttributes[0]['ipAddrs'] else intfAttributes[0]
262 return intfAttributes['ipAddrs'][0].split('/')[0]
Charles Chan76128b62017-03-27 20:28:14 -0700263
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700264 conf.writeLine('hostname ospf-%s' % self.qr.name);
265 conf.writeLine('password %s' % 'hello')
266 conf.writeLine('!')
267 conf.writeLine('router ospf')
Charles Chan76128b62017-03-27 20:28:14 -0700268
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700269 conf.indent()
Charles Chan76128b62017-03-27 20:28:14 -0700270
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700271 conf.writeLine('ospf router-id %s' % getRouterId(self.qr.interfaces))
272 conf.writeLine('!')
Charles Chan76128b62017-03-27 20:28:14 -0700273
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700274 for name, intf in self.qr.interfaces.items():
275 for ip in intf['ipAddrs']:
276 conf.writeLine('network %s area 0' % ip)
277 #if intf['ipAddrs'][0].startswith('192.168'):
278 # writeLine(1, 'passive-interface %s' % name)
Charles Chan76128b62017-03-27 20:28:14 -0700279
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700280 conf.close()
Charles Chan76128b62017-03-27 20:28:14 -0700281
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700282class PimProtocol(Protocol):
Charles Chan76128b62017-03-27 20:28:14 -0700283
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700284 """Configures and runs the PIM protcol in Quagga."""
Charles Chan76128b62017-03-27 20:28:14 -0700285
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700286 def __init__(self, configFile=None, *args, **kwargs):
287 self.configFile = configFile
Charles Chan76128b62017-03-27 20:28:14 -0700288
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700289 def config(self, **kwargs):
290 pimPidFile = '%s/pim%s.pid' % (self.qr.runDir, self.qr.name)
Charles Chan76128b62017-03-27 20:28:14 -0700291
Jonathan Hart09608592016-05-19 09:39:22 -0700292 self.qr.cmd('%s/pimd -Z -d -f %s -z %s -i %s'
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700293 % (QuaggaRouter.binDir, self.configFile, self.qr.socket, pimPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700294
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700295class ConfigurationWriter(object):
Charles Chan76128b62017-03-27 20:28:14 -0700296
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700297 """Utility class for writing a configuration file."""
Charles Chan76128b62017-03-27 20:28:14 -0700298
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700299 def __init__(self, filename):
300 self.filename = filename
301 self.indentValue = 0;
Charles Chan76128b62017-03-27 20:28:14 -0700302
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700303 self.configFile = open(self.filename, 'w+')
Charles Chan76128b62017-03-27 20:28:14 -0700304
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700305 def indent(self):
306 self.indentValue += 1
Charles Chan76128b62017-03-27 20:28:14 -0700307
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700308 def unindent(self):
309 if (self.indentValue > 0):
310 self.indentValue -= 1
Charles Chan76128b62017-03-27 20:28:14 -0700311
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700312 def write(self, string):
313 self.configFile.write(string)
Charles Chan76128b62017-03-27 20:28:14 -0700314
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700315 def writeLine(self, string):
316 intentStr = ''
317 for _ in range(0, self.indentValue):
318 intentStr += ' '
319 self.write('%s%s\n' % (intentStr, string))
Charles Chan76128b62017-03-27 20:28:14 -0700320
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700321 def close(self):
322 self.configFile.close()
323
324#Backward compatibility for BGP-only use case
325class BgpRouter(QuaggaRouter):
Charles Chan76128b62017-03-27 20:28:14 -0700326
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700327 """Quagga router running the BGP protocol."""
Charles Chan76128b62017-03-27 20:28:14 -0700328
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700329 def __init__(self, name, interfaces,
Charles Chandfa13bd2017-03-24 21:40:10 -0700330 asNum=0, neighbors=[], routes=[],
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700331 defaultRoute=None,
332 quaggaConfFile=None,
333 zebraConfFile=None,
334 *args, **kwargs):
335 bgp = BgpProtocol(configFile=quaggaConfFile, asNum=asNum, neighbors=neighbors, routes=routes)
Charles Chan76128b62017-03-27 20:28:14 -0700336
337 super(BgpRouter, self).__init__(name, interfaces,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700338 zebraConfFile=zebraConfFile,
339 defaultRoute=defaultRoute,
340 protocols=[bgp],
341 *args, **kwargs)
Charles Chan76128b62017-03-27 20:28:14 -0700342
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700343class RouterData(object):
Charles Chan76128b62017-03-27 20:28:14 -0700344
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700345 """Internal data structure storing information about a router."""
Charles Chan76128b62017-03-27 20:28:14 -0700346
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700347 def __init__(self, index):
348 self.index = index;
349 self.neighbors = []
350 self.interfaces = {}
351 self.switches = []
Charles Chan76128b62017-03-27 20:28:14 -0700352
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700353 def addNeighbor(self, theirAddress, theirAsNum):
354 self.neighbors.append({'address':theirAddress.ip, 'as':theirAsNum})
Charles Chan76128b62017-03-27 20:28:14 -0700355
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700356 def addInterface(self, intf, vlan, address):
357 if not intf in self.interfaces:
358 self.interfaces[intf] = InterfaceData(intf)
Charles Chan76128b62017-03-27 20:28:14 -0700359
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700360 self.interfaces[intf].addAddress(vlan, address)
Charles Chan76128b62017-03-27 20:28:14 -0700361
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700362 def setSwitch(self, switch):
363 self.switches.append(switch)
Charles Chan76128b62017-03-27 20:28:14 -0700364
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700365class InterfaceData(object):
Charles Chan76128b62017-03-27 20:28:14 -0700366
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700367 """Internal data structure storing information about an interface."""
Charles Chan76128b62017-03-27 20:28:14 -0700368
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700369 def __init__(self, number):
370 self.number = number
371 self.addressesByVlan = {}
Charles Chan76128b62017-03-27 20:28:14 -0700372
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700373 def addAddress(self, vlan, address):
374 if not vlan in self.addressesByVlan:
375 self.addressesByVlan[vlan] = []
Charles Chan76128b62017-03-27 20:28:14 -0700376
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700377 self.addressesByVlan[vlan].append(address.with_prefixlen)
Charles Chan76128b62017-03-27 20:28:14 -0700378
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700379class RoutedNetwork(object):
Charles Chan76128b62017-03-27 20:28:14 -0700380
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700381 """Creates a host behind a router. This is common boilerplate topology
382 segment in routed networks."""
Charles Chan76128b62017-03-27 20:28:14 -0700383
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700384 @staticmethod
385 def build(topology, router, hostName, networks):
386 # There's a convention that the router's addresses are already set up,
387 # and it has the last address in the network.
Charles Chan76128b62017-03-27 20:28:14 -0700388
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700389 def getFirstAddress(network):
390 return '%s/%s' % (network[1], network.prefixlen)
Charles Chan76128b62017-03-27 20:28:14 -0700391
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700392 defaultRoute = AutonomousSystem.getLastAddress(networks[0]).ip
Charles Chan76128b62017-03-27 20:28:14 -0700393
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700394 host = topology.addHost(hostName, cls=RoutedHost,
395 ips=[getFirstAddress(network) for network in networks],
396 gateway=defaultRoute)
397
398 topology.addLink(router, host)
399
400class AutonomousSystem(object):
Charles Chan76128b62017-03-27 20:28:14 -0700401
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700402 """Base abstraction of an autonomous system, which implies some internal
403 topology and connections to other topology elements (switches/other ASes)."""
Charles Chan76128b62017-03-27 20:28:14 -0700404
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700405 psIdx = 1
Charles Chan76128b62017-03-27 20:28:14 -0700406
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700407 def __init__(self, asNum, numRouters):
408 self.asNum = asNum
409 self.numRouters = numRouters
410 self.routers = {}
411 for i in range(1, numRouters + 1):
412 self.routers[i] = RouterData(i)
Charles Chan76128b62017-03-27 20:28:14 -0700413
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700414 self.routerNodes={}
Charles Chan76128b62017-03-27 20:28:14 -0700415
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700416 self.neighbors=[]
417 self.vlanAddresses={}
Charles Chan76128b62017-03-27 20:28:14 -0700418
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700419 def peerWith(self, myRouter, myAddress, theirAddress, theirAsNum, intf=1, vlan=None):
420 router = self.routers[myRouter]
Charles Chan76128b62017-03-27 20:28:14 -0700421
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700422 router.addInterface(intf, vlan, myAddress)
423 router.addNeighbor(theirAddress, theirAsNum)
424
425 def getRouter(self, i):
426 return self.routerNodes[i]
427
428 @staticmethod
429 def generatePeeringAddresses():
430 network = ip_network(u'10.0.%s.0/24' % AutonomousSystem.psIdx)
431 AutonomousSystem.psIdx += 1
Charles Chan76128b62017-03-27 20:28:14 -0700432
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700433 return ip_interface('%s/%s' % (network[1], network.prefixlen)), \
434 ip_interface('%s/%s' % (network[2], network.prefixlen))
Charles Chan76128b62017-03-27 20:28:14 -0700435
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700436 @staticmethod
437 def addPeering(as1, as2, router1=1, router2=1, intf1=1, intf2=1, address1=None, address2=None, useVlans=False):
438 vlan = AutonomousSystem.psIdx if useVlans else None
Charles Chan76128b62017-03-27 20:28:14 -0700439
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700440 if address1 is None or address2 is None:
441 (address1, address2) = AutonomousSystem.generatePeeringAddresses()
Charles Chan76128b62017-03-27 20:28:14 -0700442
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700443 as1.peerWith(router1, address1, address2, as2.asNum, intf=intf1, vlan=vlan)
444 as2.peerWith(router2, address2, address1, as1.asNum, intf=intf2, vlan=vlan)
Charles Chan76128b62017-03-27 20:28:14 -0700445
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700446 @staticmethod
447 def getLastAddress(network):
448 return ip_interface(network.network_address + network.num_addresses - 2)
Charles Chan76128b62017-03-27 20:28:14 -0700449
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700450 @staticmethod
451 def getIthAddress(network, i):
452 return ip_interface('%s/%s' % (network[i], network.prefixlen))
453
454class BasicAutonomousSystem(AutonomousSystem):
455
456 """Basic autonomous system containing one host and one or more routers
457 which peer with other ASes."""
458
459 def __init__(self, num, routes, numRouters=1):
460 super(BasicAutonomousSystem, self).__init__(65000+num, numRouters)
461 self.num = num
462 self.routes = routes
Charles Chan76128b62017-03-27 20:28:14 -0700463
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700464 def addLink(self, switch, router=1):
465 self.routers[router].setSwitch(switch)
466
467 def build(self, topology):
468 self.addRouterAndHost(topology)
469
470 def addRouterAndHost(self, topology):
Charles Chan76128b62017-03-27 20:28:14 -0700471
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700472 # TODO implementation is messy and needs to be cleaned up
Charles Chan76128b62017-03-27 20:28:14 -0700473
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700474 intfs = {}
Charles Chan76128b62017-03-27 20:28:14 -0700475
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700476 router = self.routers[1]
477 for i, router in self.routers.items():
Charles Chan76128b62017-03-27 20:28:14 -0700478
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700479 #routerName = 'r%i%i' % (self.num, i)
480 routerName = 'r%i' % self.num
481 if not i==1:
482 routerName += ('%i' % i)
Charles Chan76128b62017-03-27 20:28:14 -0700483
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700484 hostName = 'h%i' % self.num
Charles Chan76128b62017-03-27 20:28:14 -0700485
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700486 for j, interface in router.interfaces.items():
487 nativeAddresses = interface.addressesByVlan.pop(None, [])
488 peeringIntf = [{'mac' : '00:00:%02x:00:%02x:%02x' % (self.num, i, j),
489 'ipAddrs' : nativeAddresses}]
Charles Chan76128b62017-03-27 20:28:14 -0700490
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700491 for vlan, addresses in interface.addressesByVlan.items():
492 peeringIntf.append({'vlan':vlan,
493 'mac':'00:00:%02x:%02x:%02x:%02x' % (self.num, vlan, i, j),
494 'ipAddrs':addresses})
Charles Chan76128b62017-03-27 20:28:14 -0700495
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700496 intfs.update({'%s-eth%s' % (routerName, j-1) : peeringIntf})
Charles Chan76128b62017-03-27 20:28:14 -0700497
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700498 # Only add the host to the first router for now
499 if i==1:
500 internalAddresses=[]
501 for route in self.routes:
502 internalAddresses.append('%s/%s' % (AutonomousSystem.getLastAddress(route).ip, route.prefixlen))
Charles Chan76128b62017-03-27 20:28:14 -0700503
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700504 internalIntf = {'ipAddrs' : internalAddresses}
Charles Chan76128b62017-03-27 20:28:14 -0700505
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700506 # This is the configuration of the next interface after all the peering interfaces
507 intfs.update({'%s-eth%s' % (routerName, len(router.interfaces.keys())) : internalIntf})
Charles Chan76128b62017-03-27 20:28:14 -0700508
509 routerNode = topology.addHost(routerName,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700510 asNum=self.asNum, neighbors=router.neighbors,
511 routes=self.routes,
512 cls=BgpRouter, interfaces=intfs)
Charles Chan76128b62017-03-27 20:28:14 -0700513
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700514 self.routerNodes[i] = routerNode
515
516 for switch in router.switches:
517 topology.addLink(switch, routerNode)
518
519 # Only add the host to the first router for now
520 if i==1:
521 defaultRoute = internalAddresses[0].split('/')[0]
Charles Chan76128b62017-03-27 20:28:14 -0700522
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700523 host = topology.addHost(hostName, cls=RoutedHost,
524 ips=[self.getFirstAddress(route) for route in self.routes],
525 gateway=defaultRoute)
Charles Chan76128b62017-03-27 20:28:14 -0700526
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700527 topology.addLink(routerNode, host)
528
529 #def getLastAddress(self, network):
530 # return ip_address(network.network_address + network.num_addresses - 2)
Charles Chan76128b62017-03-27 20:28:14 -0700531
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700532 def getFirstAddress(self, network):
533 return '%s/%s' % (network[1], network.prefixlen)
534
535# TODO fix this AS - doesn't currently work
536class RouteServerAutonomousSystem(BasicAutonomousSystem):
537
538 def __init__(self, routerAddress, *args, **kwargs):
539 BasicAutonomousSystem.__init__(self, *args, **kwargs)
540
541 self.routerAddress = routerAddress
542
543 def build(self, topology, connectAtSwitch):
544
545 switch = topology.addSwitch('as%isw' % self.num, cls=OVSBridge)
546
547 self.addRouterAndHost(topology, self.routerAddress, switch)
548
549 rsName = 'rs%i' % self.num
550 routeServer = topology.addHost(rsName,
551 self.asnum, self.neighbors,
552 cls=BgpRouter,
553 interfaces={'%s-eth0' % rsName : {'ipAddrs':[self.peeringAddress]}})
554
555 topology.addLink(routeServer, switch)
556 topology.addLink(switch, connectAtSwitch)
Charles Chan76128b62017-03-27 20:28:14 -0700557
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700558class SdnAutonomousSystem(AutonomousSystem):
Charles Chan76128b62017-03-27 20:28:14 -0700559
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700560 """Runs the internal BGP speakers needed for ONOS routing apps like
561 SDN-IP."""
Charles Chan76128b62017-03-27 20:28:14 -0700562
Jonathan Harte6b897f2017-01-24 17:09:58 -0800563 routerIdx = 1
Charles Chan76128b62017-03-27 20:28:14 -0700564
Jonathan Harte6b897f2017-01-24 17:09:58 -0800565 def __init__(self, onosIps, num=1, numBgpSpeakers=1, asNum=65000, externalOnos=True,
Jonathan Hartfc0af772017-01-16 13:15:08 -0800566 peerIntfConfig=None, withFpm=False):
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700567 super(SdnAutonomousSystem, self).__init__(asNum, numBgpSpeakers)
568 self.onosIps = onosIps
Jonathan Harte6b897f2017-01-24 17:09:58 -0800569 self.num = num
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700570 self.numBgpSpeakers = numBgpSpeakers
571 self.peerIntfConfig = peerIntfConfig
Jonathan Hartfc0af772017-01-16 13:15:08 -0800572 self.withFpm = withFpm
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700573 self.externalOnos= externalOnos
574 self.internalPeeringSubnet = ip_network(u'1.1.1.0/24')
Charles Chan76128b62017-03-27 20:28:14 -0700575
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700576 for router in self.routers.values():
577 # Add iBGP sessions to ONOS nodes
578 for onosIp in onosIps:
579 router.neighbors.append({'address':onosIp, 'as':asNum, 'port':2000})
Charles Chan76128b62017-03-27 20:28:14 -0700580
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700581 # Add iBGP sessions to other BGP speakers
582 for i, router2 in self.routers.items():
583 if router == router2:
584 continue
Jonathan Harte6b897f2017-01-24 17:09:58 -0800585 cpIpBase = self.num*10
586 ip = AutonomousSystem.getIthAddress(self.internalPeeringSubnet, cpIpBase+i)
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700587 router.neighbors.append({'address':ip.ip, 'as':asNum})
Charles Chan76128b62017-03-27 20:28:14 -0700588
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700589 def build(self, topology, connectAtSwitch, controlSwitch):
Charles Chan76128b62017-03-27 20:28:14 -0700590
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700591 natIp = AutonomousSystem.getLastAddress(self.internalPeeringSubnet)
Charles Chan76128b62017-03-27 20:28:14 -0700592
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700593 for i, router in self.routers.items():
Jonathan Harte6b897f2017-01-24 17:09:58 -0800594 num = SdnAutonomousSystem.routerIdx
595 SdnAutonomousSystem.routerIdx += 1
596 name = 'bgp%s' % num
Charles Chan76128b62017-03-27 20:28:14 -0700597
Jonathan Harte6b897f2017-01-24 17:09:58 -0800598 cpIpBase = self.num*10
599 ip = AutonomousSystem.getIthAddress(self.internalPeeringSubnet, cpIpBase+i)
Charles Chan76128b62017-03-27 20:28:14 -0700600
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700601 eth0 = { 'ipAddrs' : [ str(ip) ] }
602 if self.peerIntfConfig is not None:
603 eth1 = self.peerIntfConfig
604 else:
605 nativeAddresses = router.interfaces[1].addressesByVlan.pop(None, [])
Charles Chan76128b62017-03-27 20:28:14 -0700606 eth1 = [{ 'mac':'00:00:00:00:00:%02x' % num,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700607 'ipAddrs' : nativeAddresses }]
Charles Chan76128b62017-03-27 20:28:14 -0700608
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700609 for vlan, addresses in router.interfaces[1].addressesByVlan.items():
610 eth1.append({'vlan':vlan,
Jonathan Harte6b897f2017-01-24 17:09:58 -0800611 'mac':'00:00:00:%02x:%02x:00' % (num, vlan),
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700612 'ipAddrs':addresses})
Charles Chan76128b62017-03-27 20:28:14 -0700613
614
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700615 intfs = { '%s-eth0' % name : eth0,
616 '%s-eth1' % name : eth1 }
Charles Chan76128b62017-03-27 20:28:14 -0700617
618 bgp = topology.addHost( name, cls=BgpRouter, asNum=self.asNum,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700619 neighbors=router.neighbors,
Charles Chan76128b62017-03-27 20:28:14 -0700620 interfaces=intfs,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700621 defaultRoute=str(natIp.ip),
Jonathan Hartfc0af772017-01-16 13:15:08 -0800622 fpm=self.onosIps[0] if self.withFpm else None )
Charles Chan76128b62017-03-27 20:28:14 -0700623
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700624 topology.addLink( bgp, controlSwitch )
625 topology.addLink( bgp, connectAtSwitch )
Charles Chan76128b62017-03-27 20:28:14 -0700626
627
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700628 if self.externalOnos:
Charles Chan76128b62017-03-27 20:28:14 -0700629 nat = topology.addHost('nat', cls=NAT,
630 ip='%s/%s' % (natIp.ip, self.internalPeeringSubnet.prefixlen),
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700631 subnet=str(self.internalPeeringSubnet), inNamespace=False);
632 topology.addLink(controlSwitch, nat)
633
Charles Chan76128b62017-03-27 20:28:14 -0700634
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700635def generateRoutes(baseRange, numRoutes, subnetSize=None):
636 baseNetwork = ip_network(baseRange)
Charles Chan76128b62017-03-27 20:28:14 -0700637
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700638 # We need to get at least 2 addresses out of each subnet, so the biggest
639 # prefix length we can have is /30
640 maxPrefixLength = baseNetwork.max_prefixlen - 2
Charles Chan76128b62017-03-27 20:28:14 -0700641
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700642 if subnetSize is not None:
643 return list(baseNetwork.subnets(new_prefix=subnetSize))
Charles Chan76128b62017-03-27 20:28:14 -0700644
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700645 trySubnetSize = baseNetwork.prefixlen + 1
646 while trySubnetSize <= maxPrefixLength and \
647 len(list(baseNetwork.subnets(new_prefix=trySubnetSize))) < numRoutes:
648 trySubnetSize += 1
Charles Chan76128b62017-03-27 20:28:14 -0700649
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700650 if trySubnetSize > maxPrefixLength:
651 raise Exception("Can't get enough routes from input parameters")
Charles Chan76128b62017-03-27 20:28:14 -0700652
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700653 return list(baseNetwork.subnets(new_prefix=trySubnetSize))[:numRoutes]
Charles Chan76128b62017-03-27 20:28:14 -0700654
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700655class RoutingCli( CLI ):
Charles Chan76128b62017-03-27 20:28:14 -0700656
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700657 """CLI command that can bring a host up or down. Useful for simulating router failure."""
Charles Chan76128b62017-03-27 20:28:14 -0700658
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700659 def do_host( self, line ):
660 args = line.split()
661 if len(args) != 2:
662 error( 'invalid number of args: host <host name> {up, down}\n' )
663 return
Charles Chan76128b62017-03-27 20:28:14 -0700664
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700665 host = args[ 0 ]
666 command = args[ 1 ]
667 if host not in self.mn or self.mn.get( host ) not in self.mn.hosts:
668 error( 'invalid host: %s\n' % args[ 1 ] )
669 else:
670 if command == 'up':
671 op = 'up'
672 elif command == 'down':
673 op = 'down'
674 else:
675 error( 'invalid command: host <host name> {up, down}\n' )
676 return
677
678 for intf in self.mn.get( host ).intfList( ):
679 intf.link.intf1.ifconfig( op )
680 intf.link.intf2.ifconfig( op )