blob: d8a43bcaa10851b43739ff3f8eaa0bd70109d638 [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
Charles Chan4bcd9562019-12-15 00:02:32 -080014class UserNAT(NAT):
15 """Disable NIC offloading such that this NAT can be used with user space OVS."""
16 def __init(self, name, *args, **kwargs):
17 super(UserNAT, self).__init__(name, *args, **kwargs)
18
19 def config(self, **kwargs):
20 super(UserNAT, self).config(**kwargs)
21 disable_offload(self, self.localIntf)
22 disable_offload(self, self.defaultIntf)
23
Jonathan Hartce97e5b2016-04-19 01:41:31 -070024class RoutedHost(Host):
25 """Host that can be configured with multiple IP addresses."""
26 def __init__(self, name, ips, gateway, *args, **kwargs):
27 super(RoutedHost, self).__init__(name, *args, **kwargs)
28
29 self.ips = ips
30 self.gateway = gateway
31
32 def config(self, **kwargs):
33 Host.config(self, **kwargs)
34
Charles Chan76128b62017-03-27 20:28:14 -070035 self.cmd('ip -4 addr flush dev %s' % self.defaultIntf())
Jonathan Hartce97e5b2016-04-19 01:41:31 -070036 for ip in self.ips:
37 self.cmd('ip addr add %s dev %s' % (ip, self.defaultIntf()))
38
39 self.cmd('ip route add default via %s' % self.gateway)
40
Yi Tseng45ee6922017-07-17 14:49:17 -070041class RoutedHost6(Host):
42 """Host that can be configured with multiple IP addresses."""
43 def __init__(self, name, ips, gateway, *args, **kwargs):
44 super(RoutedHost6, self).__init__(name, *args, **kwargs)
45
46 self.ips = ips
47 self.gateway = gateway
48
49 def config(self, **kwargs):
50 Host.config(self, **kwargs)
51
52 self.cmd('ip -6 addr flush dev %s' % self.defaultIntf())
53 for ip in self.ips:
54 self.cmd('ip -6 addr add %s dev %s' % (ip, self.defaultIntf()))
55
56 self.cmd('ip -6 route add default via %s' % self.gateway)
57
Jonathan Hartce97e5b2016-04-19 01:41:31 -070058class Router(Host):
Charles Chan76128b62017-03-27 20:28:14 -070059
Jonathan Hartce97e5b2016-04-19 01:41:31 -070060 """An L3 router.
61 Configures the Linux kernel for L3 forwarding and supports rich interface
62 configuration of IP addresses, MAC addresses and VLANs."""
Charles Chan76128b62017-03-27 20:28:14 -070063
Jonathan Hartce97e5b2016-04-19 01:41:31 -070064 def __init__(self, name, interfaces, *args, **kwargs):
65 super(Router, self).__init__(name, **kwargs)
66
67 self.interfaces = interfaces
Charles Chan76128b62017-03-27 20:28:14 -070068
Jonathan Hartce97e5b2016-04-19 01:41:31 -070069 def config(self, **kwargs):
70 super(Host, self).config(**kwargs)
Charles Chan76128b62017-03-27 20:28:14 -070071
Jonathan Hartce97e5b2016-04-19 01:41:31 -070072 self.cmd('sysctl net.ipv4.ip_forward=1')
73 self.cmd('sysctl net.ipv4.conf.all.rp_filter=0')
Charles Chan76128b62017-03-27 20:28:14 -070074 self.cmd('sysctl net.ipv6.conf.all.forwarding=1')
Jonathan Hartce97e5b2016-04-19 01:41:31 -070075
76 for intf, configs in self.interfaces.items():
Charles Chan76128b62017-03-27 20:28:14 -070077 self.cmd('ip -4 addr flush dev %s' % intf)
Jonathan Hartce97e5b2016-04-19 01:41:31 -070078 self.cmd( 'sysctl net.ipv4.conf.%s.rp_filter=0' % intf )
Charles Chan76128b62017-03-27 20:28:14 -070079
Jonathan Hartce97e5b2016-04-19 01:41:31 -070080 if not isinstance(configs, list):
81 configs = [configs]
Charles Chan76128b62017-03-27 20:28:14 -070082
Jonathan Hartce97e5b2016-04-19 01:41:31 -070083 for attrs in configs:
Charles Chan76128b62017-03-27 20:28:14 -070084 # Configure the vlan if there is one
Jonathan Hartce97e5b2016-04-19 01:41:31 -070085 if 'vlan' in attrs:
86 vlanName = '%s.%s' % (intf, attrs['vlan'])
Charles Chan76128b62017-03-27 20:28:14 -070087 self.cmd('ip link add link %s name %s type vlan id %s' %
Jonathan Hartce97e5b2016-04-19 01:41:31 -070088 (intf, vlanName, attrs['vlan']))
89 self.cmd('ip link set %s up' % vlanName)
90 addrIntf = vlanName
91 else:
92 addrIntf = intf
Charles Chan76128b62017-03-27 20:28:14 -070093
Jonathan Hartce97e5b2016-04-19 01:41:31 -070094 # Now configure the addresses on the vlan/native interface
95 if 'mac' in attrs:
96 self.cmd('ip link set %s down' % addrIntf)
97 self.cmd('ip link set %s address %s' % (addrIntf, attrs['mac']))
98 self.cmd('ip link set %s up' % addrIntf)
99 for addr in attrs['ipAddrs']:
100 self.cmd('ip addr add %s dev %s' % (addr, addrIntf))
101
102class QuaggaRouter(Router):
Charles Chan76128b62017-03-27 20:28:14 -0700103
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700104 """Runs Quagga to create a router that can speak routing protocols."""
Charles Chan76128b62017-03-27 20:28:14 -0700105
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700106 binDir = '/usr/lib/quagga'
Jonathan Hartd5d80872017-01-16 13:55:41 -0800107 logDir = '/var/log/quagga'
Charles Chan76128b62017-03-27 20:28:14 -0700108
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700109 def __init__(self, name, interfaces,
110 defaultRoute=None,
111 zebraConfFile=None,
112 protocols=[],
113 fpm=None,
114 runDir='/var/run/quagga', *args, **kwargs):
115 super(QuaggaRouter, self).__init__(name, interfaces, **kwargs)
Charles Chan76128b62017-03-27 20:28:14 -0700116
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700117 self.protocols = protocols
118 self.fpm = fpm
Charles Chan76128b62017-03-27 20:28:14 -0700119
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700120 for p in self.protocols:
121 p.setQuaggaRouter(self)
Charles Chan76128b62017-03-27 20:28:14 -0700122
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700123 self.runDir = runDir
124 self.defaultRoute = defaultRoute
Charles Chan76128b62017-03-27 20:28:14 -0700125
Jonathan Hartd5d80872017-01-16 13:55:41 -0800126 # Ensure required directories exist
127 try:
128 original_umask = os.umask(0)
129 if (not os.path.isdir(QuaggaRouter.logDir)):
130 os.makedirs(QuaggaRouter.logDir, 0777)
131 if (not os.path.isdir(self.runDir)):
132 os.makedirs(self.runDir, 0777)
133 finally:
134 os.umask(original_umask)
135
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700136 self.zebraConfFile = zebraConfFile
137 if (self.zebraConfFile is None):
138 self.zebraConfFile = '%s/zebrad%s.conf' % (self.runDir, self.name)
139 self.generateZebra()
Charles Chan76128b62017-03-27 20:28:14 -0700140
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700141 self.socket = '%s/zebra%s.api' % (self.runDir, self.name)
Charles Chan76128b62017-03-27 20:28:14 -0700142
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700143 self.zebraPidFile = '%s/zebra%s.pid' % (self.runDir, self.name)
144
145 def generateZebra(self):
146 configFile = open(self.zebraConfFile, 'w+')
Jonathan Hartd5d80872017-01-16 13:55:41 -0800147 configFile.write('log file %s/zebrad%s.log\n' % (QuaggaRouter.logDir, self.name))
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700148 configFile.write('hostname zebra-%s\n' % self.name)
Jonathan Harte6b897f2017-01-24 17:09:58 -0800149 configFile.write('password %s\n' % 'quagga')
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700150 if (self.fpm is not None):
151 configFile.write('fpm connection ip %s port 2620' % self.fpm)
152 configFile.close()
153
154 def config(self, **kwargs):
155 super(QuaggaRouter, self).config(**kwargs)
156
157 self.cmd('%s/zebra -d -f %s -z %s -i %s'
158 % (QuaggaRouter.binDir, self.zebraConfFile, self.socket, self.zebraPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700159
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700160 for p in self.protocols:
161 p.config(**kwargs)
Charles Chan76128b62017-03-27 20:28:14 -0700162
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700163 if self.defaultRoute:
164 self.cmd('ip route add default via %s' % self.defaultRoute)
Charles Chan76128b62017-03-27 20:28:14 -0700165
Charles Chane7e8cdb2019-10-22 23:38:39 -0700166 for interface in self.interfaces:
167 disable_offload(self, interface)
168
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700169 def terminate(self, **kwargs):
Charles Chan76128b62017-03-27 20:28:14 -0700170 self.cmd("ps ax | grep '%s' | awk '{print $1}' | xargs kill"
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700171 % (self.socket))
Charles Chan76128b62017-03-27 20:28:14 -0700172
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700173 for p in self.protocols:
174 p.terminate(**kwargs)
175
176 super(QuaggaRouter, self).terminate()
Charles Chan76128b62017-03-27 20:28:14 -0700177
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700178class Protocol(object):
Charles Chan76128b62017-03-27 20:28:14 -0700179
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700180 """Base abstraction of a protocol that the QuaggaRouter can run."""
Charles Chan76128b62017-03-27 20:28:14 -0700181
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700182 def setQuaggaRouter(self, qr):
183 self.qr = qr
Charles Chan76128b62017-03-27 20:28:14 -0700184
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700185 def config(self, **kwargs):
186 pass
Charles Chan76128b62017-03-27 20:28:14 -0700187
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700188 def terminate(self, **kwargs):
189 pass
Charles Chan76128b62017-03-27 20:28:14 -0700190
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700191class BgpProtocol(Protocol):
Charles Chan76128b62017-03-27 20:28:14 -0700192
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700193 """Configures and runs the BGP protocol in Quagga."""
Charles Chan76128b62017-03-27 20:28:14 -0700194
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700195 def __init__(self, configFile=None, asNum=None, neighbors=[], routes=[], *args, **kwargs):
196 self.configFile = configFile
Charles Chan76128b62017-03-27 20:28:14 -0700197
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700198 self.asNum = asNum
199 self.neighbors = neighbors
200 self.routes = routes
Charles Chan76128b62017-03-27 20:28:14 -0700201
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700202 def config(self, **kwargs):
203 if self.configFile is None:
204 self.configFile = '%s/bgpd%s.conf' % (self.qr.runDir, self.qr.name)
205 self.generateConfig()
Charles Chan76128b62017-03-27 20:28:14 -0700206
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700207 bgpdPidFile = '%s/bgpd%s.pid' % (self.qr.runDir, self.qr.name)
Charles Chan76128b62017-03-27 20:28:14 -0700208
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700209 self.qr.cmd('%s/bgpd -d -f %s -z %s -i %s'
210 % (QuaggaRouter.binDir, self.configFile, self.qr.socket, bgpdPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700211
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700212 def generateConfig(self):
213 conf = ConfigurationWriter(self.configFile)
Charles Chan76128b62017-03-27 20:28:14 -0700214
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700215 def getRouterId(interfaces):
216 intfAttributes = interfaces.itervalues().next()
217 print intfAttributes
218 if isinstance(intfAttributes, list):
219 # Try use the first set of attributes, but if using vlans they might not have addresses
220 intfAttributes = intfAttributes[1] if not intfAttributes[0]['ipAddrs'] else intfAttributes[0]
221 return intfAttributes['ipAddrs'][0].split('/')[0]
Charles Chan76128b62017-03-27 20:28:14 -0700222
Jonathan Hartd5d80872017-01-16 13:55:41 -0800223 conf.writeLine('log file %s/bgpd%s.log' % (QuaggaRouter.logDir, self.qr.name))
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700224 conf.writeLine('hostname bgp-%s' % self.qr.name);
Jonathan Harte6b897f2017-01-24 17:09:58 -0800225 conf.writeLine('password %s' % 'quagga')
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700226 conf.writeLine('!')
227 conf.writeLine('router bgp %s' % self.asNum)
Charles Chan76128b62017-03-27 20:28:14 -0700228
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700229 conf.indent()
Charles Chan76128b62017-03-27 20:28:14 -0700230
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700231 conf.writeLine('bgp router-id %s' % getRouterId(self.qr.interfaces))
232 conf.writeLine('timers bgp %s' % '3 9')
233 conf.writeLine('!')
Charles Chan76128b62017-03-27 20:28:14 -0700234
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700235 for neighbor in self.neighbors:
236 conf.writeLine('neighbor %s remote-as %s' % (neighbor['address'], neighbor['as']))
237 conf.writeLine('neighbor %s ebgp-multihop' % neighbor['address'])
238 conf.writeLine('neighbor %s timers connect %s' % (neighbor['address'], '5'))
239 conf.writeLine('neighbor %s advertisement-interval %s' % (neighbor['address'], '5'))
240 if 'port' in neighbor:
241 conf.writeLine('neighbor %s port %s' % (neighbor['address'], neighbor['port']))
242 conf.writeLine('!')
Charles Chan76128b62017-03-27 20:28:14 -0700243
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700244 for route in self.routes:
245 conf.writeLine('network %s' % route)
Charles Chan76128b62017-03-27 20:28:14 -0700246
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700247 conf.close()
Charles Chan76128b62017-03-27 20:28:14 -0700248
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700249class OspfProtocol(Protocol):
Charles Chan76128b62017-03-27 20:28:14 -0700250
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700251 """Configures and runs the OSPF protocol in Quagga."""
Charles Chan76128b62017-03-27 20:28:14 -0700252
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700253 def __init__(self, configFile=None, *args, **kwargs):
254 self.configFile = configFile
Charles Chan76128b62017-03-27 20:28:14 -0700255
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700256 def config(self, **kwargs):
257 if self.configFile is None:
258 self.configFile = '%s/ospfd%s.conf' % (self.qr.runDir, self.qr.name)
259 self.generateConfig()
Charles Chan76128b62017-03-27 20:28:14 -0700260
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700261 ospfPidFile = '%s/ospf%s.pid' % (self.qr.runDir, self.qr.name)
Charles Chan76128b62017-03-27 20:28:14 -0700262
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700263 self.qr.cmd('%s/ospfd -d -f %s -z %s -i %s'
264 % (QuaggaRouter.binDir, self.configFile, self.qr.socket, ospfPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700265
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700266 def generateConfig(self):
267 conf = ConfigurationWriter(self.configFile)
Charles Chan76128b62017-03-27 20:28:14 -0700268
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700269 def getRouterId(interfaces):
270 intfAttributes = interfaces.itervalues().next()
271 print intfAttributes
272 if isinstance(intfAttributes, list):
273 # Try use the first set of attributes, but if using vlans they might not have addresses
274 intfAttributes = intfAttributes[1] if not intfAttributes[0]['ipAddrs'] else intfAttributes[0]
275 return intfAttributes['ipAddrs'][0].split('/')[0]
Charles Chan76128b62017-03-27 20:28:14 -0700276
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700277 conf.writeLine('hostname ospf-%s' % self.qr.name);
278 conf.writeLine('password %s' % 'hello')
279 conf.writeLine('!')
280 conf.writeLine('router ospf')
Charles Chan76128b62017-03-27 20:28:14 -0700281
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700282 conf.indent()
Charles Chan76128b62017-03-27 20:28:14 -0700283
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700284 conf.writeLine('ospf router-id %s' % getRouterId(self.qr.interfaces))
285 conf.writeLine('!')
Charles Chan76128b62017-03-27 20:28:14 -0700286
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700287 for name, intf in self.qr.interfaces.items():
288 for ip in intf['ipAddrs']:
289 conf.writeLine('network %s area 0' % ip)
290 #if intf['ipAddrs'][0].startswith('192.168'):
291 # writeLine(1, 'passive-interface %s' % name)
Charles Chan76128b62017-03-27 20:28:14 -0700292
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700293 conf.close()
Charles Chan76128b62017-03-27 20:28:14 -0700294
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700295class PimProtocol(Protocol):
Charles Chan76128b62017-03-27 20:28:14 -0700296
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700297 """Configures and runs the PIM protcol in Quagga."""
Charles Chan76128b62017-03-27 20:28:14 -0700298
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700299 def __init__(self, configFile=None, *args, **kwargs):
300 self.configFile = configFile
Charles Chan76128b62017-03-27 20:28:14 -0700301
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700302 def config(self, **kwargs):
303 pimPidFile = '%s/pim%s.pid' % (self.qr.runDir, self.qr.name)
Charles Chan76128b62017-03-27 20:28:14 -0700304
Jonathan Hart09608592016-05-19 09:39:22 -0700305 self.qr.cmd('%s/pimd -Z -d -f %s -z %s -i %s'
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700306 % (QuaggaRouter.binDir, self.configFile, self.qr.socket, pimPidFile))
Charles Chan76128b62017-03-27 20:28:14 -0700307
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700308class ConfigurationWriter(object):
Charles Chan76128b62017-03-27 20:28:14 -0700309
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700310 """Utility class for writing a configuration file."""
Charles Chan76128b62017-03-27 20:28:14 -0700311
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700312 def __init__(self, filename):
313 self.filename = filename
314 self.indentValue = 0;
Charles Chan76128b62017-03-27 20:28:14 -0700315
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700316 self.configFile = open(self.filename, 'w+')
Charles Chan76128b62017-03-27 20:28:14 -0700317
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700318 def indent(self):
319 self.indentValue += 1
Charles Chan76128b62017-03-27 20:28:14 -0700320
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700321 def unindent(self):
322 if (self.indentValue > 0):
323 self.indentValue -= 1
Charles Chan76128b62017-03-27 20:28:14 -0700324
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700325 def write(self, string):
326 self.configFile.write(string)
Charles Chan76128b62017-03-27 20:28:14 -0700327
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700328 def writeLine(self, string):
329 intentStr = ''
330 for _ in range(0, self.indentValue):
331 intentStr += ' '
332 self.write('%s%s\n' % (intentStr, string))
Charles Chan76128b62017-03-27 20:28:14 -0700333
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700334 def close(self):
335 self.configFile.close()
336
337#Backward compatibility for BGP-only use case
338class BgpRouter(QuaggaRouter):
Charles Chan76128b62017-03-27 20:28:14 -0700339
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700340 """Quagga router running the BGP protocol."""
Charles Chan76128b62017-03-27 20:28:14 -0700341
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700342 def __init__(self, name, interfaces,
Charles Chandfa13bd2017-03-24 21:40:10 -0700343 asNum=0, neighbors=[], routes=[],
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700344 defaultRoute=None,
345 quaggaConfFile=None,
346 zebraConfFile=None,
347 *args, **kwargs):
348 bgp = BgpProtocol(configFile=quaggaConfFile, asNum=asNum, neighbors=neighbors, routes=routes)
Charles Chan76128b62017-03-27 20:28:14 -0700349
350 super(BgpRouter, self).__init__(name, interfaces,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700351 zebraConfFile=zebraConfFile,
352 defaultRoute=defaultRoute,
353 protocols=[bgp],
354 *args, **kwargs)
Charles Chane7e8cdb2019-10-22 23:38:39 -0700355 def config(self, **kwargs):
356 super(BgpRouter, self).config(**kwargs)
Charles Chan76128b62017-03-27 20:28:14 -0700357
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700358class RouterData(object):
Charles Chan76128b62017-03-27 20:28:14 -0700359
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700360 """Internal data structure storing information about a router."""
Charles Chan76128b62017-03-27 20:28:14 -0700361
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700362 def __init__(self, index):
363 self.index = index;
364 self.neighbors = []
365 self.interfaces = {}
366 self.switches = []
Charles Chan76128b62017-03-27 20:28:14 -0700367
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700368 def addNeighbor(self, theirAddress, theirAsNum):
369 self.neighbors.append({'address':theirAddress.ip, 'as':theirAsNum})
Charles Chan76128b62017-03-27 20:28:14 -0700370
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700371 def addInterface(self, intf, vlan, address):
372 if not intf in self.interfaces:
373 self.interfaces[intf] = InterfaceData(intf)
Charles Chan76128b62017-03-27 20:28:14 -0700374
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700375 self.interfaces[intf].addAddress(vlan, address)
Charles Chan76128b62017-03-27 20:28:14 -0700376
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700377 def setSwitch(self, switch):
378 self.switches.append(switch)
Charles Chan76128b62017-03-27 20:28:14 -0700379
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700380class InterfaceData(object):
Charles Chan76128b62017-03-27 20:28:14 -0700381
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700382 """Internal data structure storing information about an interface."""
Charles Chan76128b62017-03-27 20:28:14 -0700383
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700384 def __init__(self, number):
385 self.number = number
386 self.addressesByVlan = {}
Charles Chan76128b62017-03-27 20:28:14 -0700387
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700388 def addAddress(self, vlan, address):
389 if not vlan in self.addressesByVlan:
390 self.addressesByVlan[vlan] = []
Charles Chan76128b62017-03-27 20:28:14 -0700391
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700392 self.addressesByVlan[vlan].append(address.with_prefixlen)
Charles Chan76128b62017-03-27 20:28:14 -0700393
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700394class RoutedNetwork(object):
Charles Chan76128b62017-03-27 20:28:14 -0700395
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700396 """Creates a host behind a router. This is common boilerplate topology
397 segment in routed networks."""
Charles Chan76128b62017-03-27 20:28:14 -0700398
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700399 @staticmethod
400 def build(topology, router, hostName, networks):
401 # There's a convention that the router's addresses are already set up,
402 # and it has the last address in the network.
Charles Chan76128b62017-03-27 20:28:14 -0700403
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700404 def getFirstAddress(network):
405 return '%s/%s' % (network[1], network.prefixlen)
Charles Chan76128b62017-03-27 20:28:14 -0700406
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700407 defaultRoute = AutonomousSystem.getLastAddress(networks[0]).ip
Charles Chan76128b62017-03-27 20:28:14 -0700408
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700409 host = topology.addHost(hostName, cls=RoutedHost,
410 ips=[getFirstAddress(network) for network in networks],
411 gateway=defaultRoute)
412
413 topology.addLink(router, host)
414
415class AutonomousSystem(object):
Charles Chan76128b62017-03-27 20:28:14 -0700416
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700417 """Base abstraction of an autonomous system, which implies some internal
418 topology and connections to other topology elements (switches/other ASes)."""
Charles Chan76128b62017-03-27 20:28:14 -0700419
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700420 psIdx = 1
Charles Chan76128b62017-03-27 20:28:14 -0700421
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700422 def __init__(self, asNum, numRouters):
423 self.asNum = asNum
424 self.numRouters = numRouters
425 self.routers = {}
426 for i in range(1, numRouters + 1):
427 self.routers[i] = RouterData(i)
Charles Chan76128b62017-03-27 20:28:14 -0700428
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700429 self.routerNodes={}
Charles Chan76128b62017-03-27 20:28:14 -0700430
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700431 self.neighbors=[]
432 self.vlanAddresses={}
Charles Chan76128b62017-03-27 20:28:14 -0700433
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700434 def peerWith(self, myRouter, myAddress, theirAddress, theirAsNum, intf=1, vlan=None):
435 router = self.routers[myRouter]
Charles Chan76128b62017-03-27 20:28:14 -0700436
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700437 router.addInterface(intf, vlan, myAddress)
438 router.addNeighbor(theirAddress, theirAsNum)
439
440 def getRouter(self, i):
441 return self.routerNodes[i]
442
443 @staticmethod
444 def generatePeeringAddresses():
445 network = ip_network(u'10.0.%s.0/24' % AutonomousSystem.psIdx)
446 AutonomousSystem.psIdx += 1
Charles Chan76128b62017-03-27 20:28:14 -0700447
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700448 return ip_interface('%s/%s' % (network[1], network.prefixlen)), \
449 ip_interface('%s/%s' % (network[2], network.prefixlen))
Charles Chan76128b62017-03-27 20:28:14 -0700450
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700451 @staticmethod
452 def addPeering(as1, as2, router1=1, router2=1, intf1=1, intf2=1, address1=None, address2=None, useVlans=False):
453 vlan = AutonomousSystem.psIdx if useVlans else None
Charles Chan76128b62017-03-27 20:28:14 -0700454
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700455 if address1 is None or address2 is None:
456 (address1, address2) = AutonomousSystem.generatePeeringAddresses()
Charles Chan76128b62017-03-27 20:28:14 -0700457
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700458 as1.peerWith(router1, address1, address2, as2.asNum, intf=intf1, vlan=vlan)
459 as2.peerWith(router2, address2, address1, as1.asNum, intf=intf2, vlan=vlan)
Charles Chan76128b62017-03-27 20:28:14 -0700460
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700461 @staticmethod
462 def getLastAddress(network):
463 return ip_interface(network.network_address + network.num_addresses - 2)
Charles Chan76128b62017-03-27 20:28:14 -0700464
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700465 @staticmethod
466 def getIthAddress(network, i):
467 return ip_interface('%s/%s' % (network[i], network.prefixlen))
468
469class BasicAutonomousSystem(AutonomousSystem):
470
471 """Basic autonomous system containing one host and one or more routers
472 which peer with other ASes."""
473
474 def __init__(self, num, routes, numRouters=1):
475 super(BasicAutonomousSystem, self).__init__(65000+num, numRouters)
476 self.num = num
477 self.routes = routes
Charles Chan76128b62017-03-27 20:28:14 -0700478
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700479 def addLink(self, switch, router=1):
480 self.routers[router].setSwitch(switch)
481
482 def build(self, topology):
483 self.addRouterAndHost(topology)
484
485 def addRouterAndHost(self, topology):
Charles Chan76128b62017-03-27 20:28:14 -0700486
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700487 # TODO implementation is messy and needs to be cleaned up
Charles Chan76128b62017-03-27 20:28:14 -0700488
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700489 intfs = {}
Charles Chan76128b62017-03-27 20:28:14 -0700490
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700491 router = self.routers[1]
492 for i, router in self.routers.items():
Charles Chan76128b62017-03-27 20:28:14 -0700493
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700494 #routerName = 'r%i%i' % (self.num, i)
495 routerName = 'r%i' % self.num
496 if not i==1:
497 routerName += ('%i' % i)
Charles Chan76128b62017-03-27 20:28:14 -0700498
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700499 hostName = 'h%i' % self.num
Charles Chan76128b62017-03-27 20:28:14 -0700500
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700501 for j, interface in router.interfaces.items():
502 nativeAddresses = interface.addressesByVlan.pop(None, [])
503 peeringIntf = [{'mac' : '00:00:%02x:00:%02x:%02x' % (self.num, i, j),
504 'ipAddrs' : nativeAddresses}]
Charles Chan76128b62017-03-27 20:28:14 -0700505
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700506 for vlan, addresses in interface.addressesByVlan.items():
507 peeringIntf.append({'vlan':vlan,
508 'mac':'00:00:%02x:%02x:%02x:%02x' % (self.num, vlan, i, j),
509 'ipAddrs':addresses})
Charles Chan76128b62017-03-27 20:28:14 -0700510
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700511 intfs.update({'%s-eth%s' % (routerName, j-1) : peeringIntf})
Charles Chan76128b62017-03-27 20:28:14 -0700512
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700513 # Only add the host to the first router for now
514 if i==1:
515 internalAddresses=[]
516 for route in self.routes:
517 internalAddresses.append('%s/%s' % (AutonomousSystem.getLastAddress(route).ip, route.prefixlen))
Charles Chan76128b62017-03-27 20:28:14 -0700518
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700519 internalIntf = {'ipAddrs' : internalAddresses}
Charles Chan76128b62017-03-27 20:28:14 -0700520
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700521 # This is the configuration of the next interface after all the peering interfaces
522 intfs.update({'%s-eth%s' % (routerName, len(router.interfaces.keys())) : internalIntf})
Charles Chan76128b62017-03-27 20:28:14 -0700523
524 routerNode = topology.addHost(routerName,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700525 asNum=self.asNum, neighbors=router.neighbors,
526 routes=self.routes,
527 cls=BgpRouter, interfaces=intfs)
Charles Chan76128b62017-03-27 20:28:14 -0700528
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700529 self.routerNodes[i] = routerNode
530
531 for switch in router.switches:
532 topology.addLink(switch, routerNode)
533
534 # Only add the host to the first router for now
535 if i==1:
536 defaultRoute = internalAddresses[0].split('/')[0]
Charles Chan76128b62017-03-27 20:28:14 -0700537
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700538 host = topology.addHost(hostName, cls=RoutedHost,
539 ips=[self.getFirstAddress(route) for route in self.routes],
540 gateway=defaultRoute)
Charles Chan76128b62017-03-27 20:28:14 -0700541
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700542 topology.addLink(routerNode, host)
543
544 #def getLastAddress(self, network):
545 # return ip_address(network.network_address + network.num_addresses - 2)
Charles Chan76128b62017-03-27 20:28:14 -0700546
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700547 def getFirstAddress(self, network):
548 return '%s/%s' % (network[1], network.prefixlen)
549
550# TODO fix this AS - doesn't currently work
551class RouteServerAutonomousSystem(BasicAutonomousSystem):
552
553 def __init__(self, routerAddress, *args, **kwargs):
554 BasicAutonomousSystem.__init__(self, *args, **kwargs)
555
556 self.routerAddress = routerAddress
557
558 def build(self, topology, connectAtSwitch):
559
560 switch = topology.addSwitch('as%isw' % self.num, cls=OVSBridge)
561
562 self.addRouterAndHost(topology, self.routerAddress, switch)
563
564 rsName = 'rs%i' % self.num
565 routeServer = topology.addHost(rsName,
566 self.asnum, self.neighbors,
567 cls=BgpRouter,
568 interfaces={'%s-eth0' % rsName : {'ipAddrs':[self.peeringAddress]}})
569
570 topology.addLink(routeServer, switch)
571 topology.addLink(switch, connectAtSwitch)
Charles Chan76128b62017-03-27 20:28:14 -0700572
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700573class SdnAutonomousSystem(AutonomousSystem):
Charles Chan76128b62017-03-27 20:28:14 -0700574
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700575 """Runs the internal BGP speakers needed for ONOS routing apps like
576 SDN-IP."""
Charles Chan76128b62017-03-27 20:28:14 -0700577
Jonathan Harte6b897f2017-01-24 17:09:58 -0800578 routerIdx = 1
Charles Chan76128b62017-03-27 20:28:14 -0700579
Jonathan Harte6b897f2017-01-24 17:09:58 -0800580 def __init__(self, onosIps, num=1, numBgpSpeakers=1, asNum=65000, externalOnos=True,
Jonathan Hartfc0af772017-01-16 13:15:08 -0800581 peerIntfConfig=None, withFpm=False):
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700582 super(SdnAutonomousSystem, self).__init__(asNum, numBgpSpeakers)
583 self.onosIps = onosIps
Jonathan Harte6b897f2017-01-24 17:09:58 -0800584 self.num = num
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700585 self.numBgpSpeakers = numBgpSpeakers
586 self.peerIntfConfig = peerIntfConfig
Jonathan Hartfc0af772017-01-16 13:15:08 -0800587 self.withFpm = withFpm
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700588 self.externalOnos= externalOnos
589 self.internalPeeringSubnet = ip_network(u'1.1.1.0/24')
Charles Chan76128b62017-03-27 20:28:14 -0700590
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700591 for router in self.routers.values():
592 # Add iBGP sessions to ONOS nodes
593 for onosIp in onosIps:
594 router.neighbors.append({'address':onosIp, 'as':asNum, 'port':2000})
Charles Chan76128b62017-03-27 20:28:14 -0700595
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700596 # Add iBGP sessions to other BGP speakers
597 for i, router2 in self.routers.items():
598 if router == router2:
599 continue
Jonathan Harte6b897f2017-01-24 17:09:58 -0800600 cpIpBase = self.num*10
601 ip = AutonomousSystem.getIthAddress(self.internalPeeringSubnet, cpIpBase+i)
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700602 router.neighbors.append({'address':ip.ip, 'as':asNum})
Charles Chan76128b62017-03-27 20:28:14 -0700603
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700604 def build(self, topology, connectAtSwitch, controlSwitch):
Charles Chan76128b62017-03-27 20:28:14 -0700605
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700606 natIp = AutonomousSystem.getLastAddress(self.internalPeeringSubnet)
Charles Chan76128b62017-03-27 20:28:14 -0700607
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700608 for i, router in self.routers.items():
Jonathan Harte6b897f2017-01-24 17:09:58 -0800609 num = SdnAutonomousSystem.routerIdx
610 SdnAutonomousSystem.routerIdx += 1
611 name = 'bgp%s' % num
Charles Chan76128b62017-03-27 20:28:14 -0700612
Jonathan Harte6b897f2017-01-24 17:09:58 -0800613 cpIpBase = self.num*10
614 ip = AutonomousSystem.getIthAddress(self.internalPeeringSubnet, cpIpBase+i)
Charles Chan76128b62017-03-27 20:28:14 -0700615
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700616 eth0 = { 'ipAddrs' : [ str(ip) ] }
617 if self.peerIntfConfig is not None:
618 eth1 = self.peerIntfConfig
619 else:
620 nativeAddresses = router.interfaces[1].addressesByVlan.pop(None, [])
Charles Chan76128b62017-03-27 20:28:14 -0700621 eth1 = [{ 'mac':'00:00:00:00:00:%02x' % num,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700622 'ipAddrs' : nativeAddresses }]
Charles Chan76128b62017-03-27 20:28:14 -0700623
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700624 for vlan, addresses in router.interfaces[1].addressesByVlan.items():
625 eth1.append({'vlan':vlan,
Jonathan Harte6b897f2017-01-24 17:09:58 -0800626 'mac':'00:00:00:%02x:%02x:00' % (num, vlan),
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700627 'ipAddrs':addresses})
Charles Chan76128b62017-03-27 20:28:14 -0700628
629
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700630 intfs = { '%s-eth0' % name : eth0,
631 '%s-eth1' % name : eth1 }
Charles Chan76128b62017-03-27 20:28:14 -0700632
633 bgp = topology.addHost( name, cls=BgpRouter, asNum=self.asNum,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700634 neighbors=router.neighbors,
Charles Chan76128b62017-03-27 20:28:14 -0700635 interfaces=intfs,
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700636 defaultRoute=str(natIp.ip),
Jonathan Hartfc0af772017-01-16 13:15:08 -0800637 fpm=self.onosIps[0] if self.withFpm else None )
Charles Chan76128b62017-03-27 20:28:14 -0700638
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700639 topology.addLink( bgp, controlSwitch )
640 topology.addLink( bgp, connectAtSwitch )
Charles Chan76128b62017-03-27 20:28:14 -0700641
642
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700643 if self.externalOnos:
Charles Chan76128b62017-03-27 20:28:14 -0700644 nat = topology.addHost('nat', cls=NAT,
645 ip='%s/%s' % (natIp.ip, self.internalPeeringSubnet.prefixlen),
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700646 subnet=str(self.internalPeeringSubnet), inNamespace=False);
647 topology.addLink(controlSwitch, nat)
648
Charles Chan76128b62017-03-27 20:28:14 -0700649
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700650def generateRoutes(baseRange, numRoutes, subnetSize=None):
651 baseNetwork = ip_network(baseRange)
Charles Chan76128b62017-03-27 20:28:14 -0700652
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700653 # We need to get at least 2 addresses out of each subnet, so the biggest
654 # prefix length we can have is /30
655 maxPrefixLength = baseNetwork.max_prefixlen - 2
Charles Chan76128b62017-03-27 20:28:14 -0700656
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700657 if subnetSize is not None:
658 return list(baseNetwork.subnets(new_prefix=subnetSize))
Charles Chan76128b62017-03-27 20:28:14 -0700659
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700660 trySubnetSize = baseNetwork.prefixlen + 1
661 while trySubnetSize <= maxPrefixLength and \
662 len(list(baseNetwork.subnets(new_prefix=trySubnetSize))) < numRoutes:
663 trySubnetSize += 1
Charles Chan76128b62017-03-27 20:28:14 -0700664
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700665 if trySubnetSize > maxPrefixLength:
666 raise Exception("Can't get enough routes from input parameters")
Charles Chan76128b62017-03-27 20:28:14 -0700667
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700668 return list(baseNetwork.subnets(new_prefix=trySubnetSize))[:numRoutes]
Charles Chan76128b62017-03-27 20:28:14 -0700669
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700670class RoutingCli( CLI ):
Charles Chan76128b62017-03-27 20:28:14 -0700671
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700672 """CLI command that can bring a host up or down. Useful for simulating router failure."""
Charles Chan76128b62017-03-27 20:28:14 -0700673
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700674 def do_host( self, line ):
675 args = line.split()
676 if len(args) != 2:
677 error( 'invalid number of args: host <host name> {up, down}\n' )
678 return
Charles Chan76128b62017-03-27 20:28:14 -0700679
Jonathan Hartce97e5b2016-04-19 01:41:31 -0700680 host = args[ 0 ]
681 command = args[ 1 ]
682 if host not in self.mn or self.mn.get( host ) not in self.mn.hosts:
683 error( 'invalid host: %s\n' % args[ 1 ] )
684 else:
685 if command == 'up':
686 op = 'up'
687 elif command == 'down':
688 op = 'down'
689 else:
690 error( 'invalid command: host <host name> {up, down}\n' )
691 return
692
693 for intf in self.mn.get( host ).intfList( ):
694 intf.link.intf1.ifconfig( op )
695 intf.link.intf2.ifconfig( op )
Charles Chane7e8cdb2019-10-22 23:38:39 -0700696
697# Disable NIC offloading
698def disable_offload(host, intf):
699 for attr in ["rx", "tx", "sg"]:
700 cmd = "/sbin/ethtool --offload %s %s off" % (intf, attr)
701 host.cmd(cmd)