blob: 73bfda3b788303612cc1384e831619930a7c723b [file] [log] [blame]
You Wang7d14d642019-01-23 15:10:08 -08001#!/usr/bin/python
2import os
3import re
4from optparse import OptionParser
5
6from ipaddress import ip_network
7from mininet.node import RemoteController, OVSBridge, Host, OVSSwitch
8from mininet.link import TCLink
9from mininet.log import setLogLevel
10from mininet.net import Mininet
11from mininet.topo import Topo
12from mininet.nodelib import NAT
13from mininet.cli import CLI
14
15from routinglib import BgpRouter
16from trellislib import TrellisHost, DhcpRelay
17from functools import partial
18
19# Parse command line options and dump results
20def parseOptions():
21 "Parse command line options"
22 parser = OptionParser()
23 parser.add_option( '--spine', dest='spine', type='int', default=2,
24 help='number of spine switches, default=2' )
25 parser.add_option( '--leaf', dest='leaf', type='int', default=2,
26 help='number of leaf switches, default=2' )
27 parser.add_option( '--fanout', dest='fanout', type='int', default=2,
28 help='number of hosts per leaf switch, default=2' )
29 parser.add_option( '--onos-ip', dest='onosIp', type='str', default='',
30 help='IP address list of ONOS instances, separated by comma(,). Overrides --onos option' )
31 parser.add_option( '--ipv6', action="store_true", dest='ipv6',
32 help='hosts are capable to use also ipv6' )
33 parser.add_option( '--dual-homed', action="store_true", dest='dualhomed', default=False,
34 help='True if the topology is dual-homed, default=False' )
35 parser.add_option( '--vlan', dest='vlan', type='str', default='',
36 help='list of vlan id for hosts, separated by comma(,).'
37 'Empty or id with 0 will be unconfigured.' )
38 parser.add_option( '--dhcp-client', action="store_true", dest='dhcpClient', default=False,
39 help='Set hosts as DhcpClient if True' )
40 parser.add_option( '--dhcp-relay', action="store_true", dest='dhcpRelay', default=False,
41 help='Connect half of the hosts to switch indirectly (via DHCP relay) if True' )
42 parser.add_option( '--multiple-dhcp-server', action="store_true", dest='multipleServer', default=False,
43 help='Use another DHCP server for indirectly connected DHCP clients if True' )
44 parser.add_option( '--remote-dhcp-server', action="store_true", dest='remoteServer', default=False,
45 help='Connect DHCP server indirectly (via gateway) if True' )
46 ( options, args ) = parser.parse_args()
47 return options, args
48
49
50opts, args = parseOptions()
51
52IP6_SUBNET_CLASS = 120
53IP4_SUBNET_CLASS = 24
54
55# TODO: DHCP support
56class IpHost( Host ):
57
58 def __init__( self, name, *args, **kwargs ):
59 super( IpHost, self ).__init__( name, *args, **kwargs )
60 gateway = re.split( '\.|/', kwargs[ 'ip' ] )
61 gateway[ 3 ] = '254'
62 self.gateway = '.'.join( gateway[ 0:4 ] )
63
64 def config( self, **kwargs ):
65 Host.config( self, **kwargs )
66 mtu = "ifconfig " + self.name + "-eth0 mtu 1490"
67 self.cmd( mtu )
68 self.cmd( 'ip route add default via %s' % self.gateway )
69
70class DualHomedIpHost(IpHost):
71 def __init__(self, name, *args, **kwargs):
72 super(DualHomedIpHost, self).__init__(name, **kwargs)
73 self.bond0 = None
74
75 def config(self, **kwargs):
76 super(DualHomedIpHost, self).config(**kwargs)
77 intf0 = self.intfs[0].name
78 intf1 = self.intfs[1].name
79 self.bond0 = "%s-bond0" % self.name
80 self.cmd('modprobe bonding')
81 self.cmd('ip link add %s type bond' % self.bond0)
82 self.cmd('ip link set %s down' % intf0)
83 self.cmd('ip link set %s down' % intf1)
84 self.cmd('ip link set %s master %s' % (intf0, self.bond0))
85 self.cmd('ip link set %s master %s' % (intf1, self.bond0))
86 self.cmd('ip addr flush dev %s' % intf0)
87 self.cmd('ip addr flush dev %s' % intf1)
88 self.cmd('ip link set %s up' % self.bond0)
89
90 def terminate(self, **kwargs):
91 self.cmd('ip link set %s down' % self.bond0)
92 self.cmd('ip link delete %s' % self.bond0)
93 self.cmd('kill -9 `cat %s`' % self.pidFile)
94 self.cmd('rm -rf %s' % self.pidFile)
95 super(DualHomedIpHost, self).terminate()
96
97
98# TODO: Implement IPv6 support
99class DualHomedLeafSpineFabric (Topo) :
100 def __init__(self, spine = 2, leaf = 2, fanout = 2, vlan_id = [], ipv6 = False,
101 dhcp_client = False, dhcp_relay = False,
102 multiple_server = False, remote_server = False, **opts):
103 # TODO: add support to dhcp_relay, multiple_server and remote_server
104 Topo.__init__(self, **opts)
105 spines = dict()
106 leafs = dict()
107
108 # leaf should be 2 or 4
109
110 # calculate the subnets to use and set options
111 linkopts = dict( bw=100 )
112 # Create spine switches
113 for s in range(spine):
114 spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
115
116 # Create leaf switches
117 for ls in range(leaf):
118 leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
119
120 # Connect leaf to all spines with dual link
121 for s in range( spine ):
122 switch = spines[ s ]
123 self.addLink(leafs[ls], switch, **linkopts)
124 self.addLink(leafs[ls], switch, **linkopts)
125
126 # Add hosts after paired ToR switches are added.
127 if ls % 2 == 0:
128 continue
129
130 # Add leaf-leaf link
131 self.addLink(leafs[ls], leafs[ls-1])
132
133 dual_ls = ls / 2
134 # Add hosts
135 for f in range(fanout):
136 name = 'h%s%s' % (dual_ls * fanout + f + 1, "v6" if ipv6 else "")
137 if ipv6:
138 ips = ['2000::%d0%d/%d' % (dual_ls+2, f+1, IP6_SUBNET_CLASS)]
139 gateway = '2000::%dff' % (dual_ls+2)
140 mac = '00:bb:00:00:00:%02x' % (dual_ls * fanout + f + 1)
141 else:
142 ips = ['10.0.%d.%d/%d' % (dual_ls+2, f+1, IP4_SUBNET_CLASS)]
143 gateway = '10.0.%d.254' % (dual_ls+2)
144 mac = '00:aa:00:00:00:%02x' % (dual_ls * fanout + f + 1)
145 host = self.addHost( name=name, cls=TrellisHost, ips=ips, gateway=gateway, mac=mac,
146 vlan=vlan_id[ dual_ls*fanout + f ] if vlan_id[dual_ls * fanout + f] != 0 else None,
147 dhcpClient=dhcp_client, ipv6=ipv6, dualHomed=True )
148 self.addLink(host, leafs[ls], **linkopts)
149 self.addLink(host, leafs[ls-1], **linkopts)
150
151 last_ls = leafs[leaf-2]
152 last_paired_ls = leafs[leaf-1]
153 # Create common components
154 # Control plane switch (for DHCP servers)
155 cs1 = self.addSwitch('cs1', cls=OVSBridge)
156 self.addLink(cs1, last_ls)
157
158 # Control plane switch (for quagga fpm)
159 cs0 = self.addSwitch('cs0', cls=OVSBridge)
160
161 # Control plane NAT (for quagga fpm)
162 nat = self.addHost('nat', cls=NAT,
163 ip='172.16.0.1/12',
164 subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
165 self.addLink(cs0, nat)
166
167 # Internal Quagga bgp1
168 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:03'},
169 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
170 bgp1 = self.addHost('bgp1', cls=BgpRouter,
171 interfaces=intfs,
172 quaggaConfFile='./bgpdbgp1.conf',
173 zebraConfFile='./zebradbgp1.conf')
174 self.addLink(bgp1, last_ls)
175 self.addLink(bgp1, cs0)
176
177 # Internal Quagga bgp2
178 intfs = {'bgp2-eth0': [{'ipAddrs': ['10.0.5.2/24', '2000::502/120'], 'mac': '00:88:00:00:00:04', 'vlan': '150'},
179 {'ipAddrs': ['10.0.6.2/24', '2000::602/120'], 'mac': '00:88:00:00:00:04', 'vlan': '160'}],
180 'bgp2-eth1': {'ipAddrs': ['172.16.0.4/12']}}
181 bgp2 = self.addHost('bgp2', cls=BgpRouter,
182 interfaces=intfs,
183 quaggaConfFile='./bgpdbgp2.conf',
184 zebraConfFile='./zebradbgp2.conf')
185 self.addLink(bgp2, last_paired_ls)
186 self.addLink(bgp2, cs0)
187
188 # External Quagga r1
189 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
190 'r1-eth1': {'ipAddrs': ['10.0.5.1/24', '2000::501/120'], 'mac': '00:88:00:00:00:11'},
191 'r1-eth2': {'ipAddrs': ['10.0.99.1/16']}}
192 r1 = self.addHost('r1', cls=BgpRouter,
193 interfaces=intfs,
194 quaggaConfFile='./bgpdr1.conf')
195 self.addLink(r1, last_ls)
196 self.addLink(r1, last_paired_ls)
197
198 # External IPv4 Host behind r1
199 rh1 = self.addHost('rh1', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
200 self.addLink(r1, rh1)
201
202 # External Quagga r2
203 intfs = {'r2-eth0': {'ipAddrs': ['10.0.6.1/24', '2000::601/120'], 'mac': '00:88:00:00:00:02'},
204 'r2-eth1': {'ipAddrs': ['10.0.7.1/24', '2000::701/120'], 'mac': '00:88:00:00:00:22'},
205 'r2-eth2': {'ipAddrs': ['10.0.99.1/16']}}
206 r2 = self.addHost('r2', cls=BgpRouter,
207 interfaces=intfs,
208 quaggaConfFile='./bgpdr2.conf')
209 self.addLink(r2, last_ls)
210 self.addLink(r2, last_paired_ls)
211
212 # External IPv4 Host behind r2
213 rh2 = self.addHost('rh2', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
214 self.addLink(r2, rh2)
215
216 # DHCP server
217 if ipv6:
218 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
219 ips=['2000::3fd/120'], gateway='2000::3ff',
220 dhcpServer=True, ipv6=True)
221 self.addLink(dhcp, cs1)
222 else:
223 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
224 ips=['10.0.3.253/24'], gateway='10.0.3.254',
225 dhcpServer=True)
226 self.addLink(dhcp, cs1)
227
228
229class LeafSpineFabric (Topo) :
230 def __init__(self, spine = 2, leaf = 2, fanout = 2, vlan_id = [], ipv6 = False,
231 dhcp_client = False, dhcp_relay = False,
232 multiple_server = False, remote_server = False, **opts):
233 Topo.__init__(self, **opts)
234 spines = dict()
235 leafs = dict()
236
237 # TODO: support IPv6 hosts
238 linkopts = dict( bw=100 )
239
240 # Create spine switches
241 for s in range(spine):
242 spines[s] = self.addSwitch('spine10%s' % (s + 1), dpid = "00000000010%s" % (s + 1) )
243
244 # Create leaf switches
245 for ls in range(leaf):
246 leafs[ls] = self.addSwitch('leaf%s' % (ls + 1), dpid = "00000000000%s" % ( ls + 1) )
247
248 # Connect leaf to all spines
249 for s in range( spine ):
250 switch = spines[ s ]
251 self.addLink( leafs[ ls ], switch, **linkopts )
252
253 # If dual-homed ToR, add hosts only when adding second switch at each edge-pair
254 # When the number of leaf switches is odd, leave the last switch as a single ToR
255
256 # Add hosts
257 for f in range(fanout):
258 name = 'h%s%s' % (ls * fanout + f + 1, "v6" if ipv6 else "")
259 if ipv6:
260 ips = ['2000::%d0%d/%d' % (ls+2, f+1, IP6_SUBNET_CLASS)]
261 gateway = '2000::%dff' % (ls+2)
262 mac = '00:bb:00:00:00:%02x' % (ls * fanout + f + 1)
263 else:
264 ips = ['10.0.%d.%d/%d' % (ls+2, f+1, IP4_SUBNET_CLASS)]
265 gateway = '10.0.%d.254' % (ls+2)
266 mac = '00:aa:00:00:00:%02x' % (ls * fanout + f + 1)
267 host = self.addHost( name=name, cls=TrellisHost, ips=ips, gateway=gateway, mac=mac,
268 vlan=vlan_id[ ls*fanout + f ] if vlan_id[ls * fanout + f] != 0 else None,
269 dhcpClient=dhcp_client, ipv6=ipv6 )
270 if dhcp_relay and f % 2:
271 relayIndex = ls * fanout + f + 1
272 if ipv6:
273 intfs = {
274 'relay%s-eth0' % relayIndex: { 'ipAddrs': ['2000::%dff/%d' % (leaf + ls + 2, IP6_SUBNET_CLASS)] },
275 'relay%s-eth1' % relayIndex: { 'ipAddrs': ['2000::%d5%d/%d' % (ls + 2, f, IP6_SUBNET_CLASS)] }
276 }
277 if remote_server:
278 serverIp = '2000::99fd'
279 elif multiple_server:
280 serverIp = '2000::3fc'
281 else:
282 serverIp = '2000::3fd'
283 dhcpRelay = self.addHost(name='relay%s' % relayIndex, cls=DhcpRelay, serverIp=serverIp,
284 gateway='2000::%dff' % (ls+2), interfaces=intfs)
285 else:
286 intfs = {
287 'relay%s-eth0' % relayIndex: { 'ipAddrs': ['10.0.%d.254/%d' % (leaf + ls + 2, IP4_SUBNET_CLASS)] },
288 'relay%s-eth1' % relayIndex: { 'ipAddrs': ['10.0.%d.%d/%d' % (ls + 2, f + 99, IP4_SUBNET_CLASS)] }
289 }
290 if remote_server:
291 serverIp = '10.0.99.3'
292 elif multiple_server:
293 serverIp = '10.0.3.252'
294 else:
295 serverIp = '10.0.3.253'
296 dhcpRelay = self.addHost(name='relay%s' % relayIndex, cls=DhcpRelay, serverIp=serverIp,
297 gateway='10.0.%d.254' % (ls+2), interfaces=intfs)
298 self.addLink(host, dhcpRelay, **linkopts)
299 self.addLink(dhcpRelay, leafs[ls], **linkopts)
300 else:
301 self.addLink(host, leafs[ls], **linkopts)
302
303 last_ls = leafs[leaf-1]
304 # Create common components
305 # Control plane switch (for DHCP servers)
306 cs1 = self.addSwitch('cs1', cls=OVSBridge)
307 self.addLink(cs1, last_ls)
308
309 # Control plane switch (for quagga fpm)
310 cs0 = self.addSwitch('cs0', cls=OVSBridge)
311
312 # Control plane NAT (for quagga fpm)
313 nat = self.addHost('nat', cls=NAT,
314 ip='172.16.0.1/12',
315 subnet=str(ip_network(u'172.16.0.0/12')), inNamespace=False)
316 self.addLink(cs0, nat)
317
318 # Internal Quagga bgp1
319 intfs = {'bgp1-eth0': {'ipAddrs': ['10.0.1.2/24', '2000::102/120'], 'mac': '00:88:00:00:00:02'},
320 'bgp1-eth1': {'ipAddrs': ['172.16.0.2/12']}}
321 bgp1 = self.addHost('bgp1', cls=BgpRouter,
322 interfaces=intfs,
323 quaggaConfFile='./bgpdbgp1.conf',
324 zebraConfFile='./zebradbgp1.conf')
325 self.addLink(bgp1, last_ls)
326 self.addLink(bgp1, cs0)
327
328 # External Quagga r1
329 intfs = {'r1-eth0': {'ipAddrs': ['10.0.1.1/24', '2000::101/120'], 'mac': '00:88:00:00:00:01'},
330 'r1-eth1': {'ipAddrs': ['10.0.99.1/16']},
331 'r1-eth2': {'ipAddrs': ['2000::9901/120']}}
332 r1 = self.addHost('r1', cls=BgpRouter,
333 interfaces=intfs,
334 quaggaConfFile='./bgpdr1.conf')
335 self.addLink(r1, last_ls)
336
337 # External switch behind r1
338 rs0 = self.addSwitch('rs0', cls=OVSBridge)
339 self.addLink(r1, rs0)
340
341 # External IPv4 Host behind r1
342 rh1 = self.addHost('rh1', cls=TrellisHost, ips=['10.0.99.2/24'], gateway='10.0.99.1')
343 self.addLink(r1, rh1)
344
345 # External IPv6 Host behind r1
346 rh1v6 = self.addHost('rh1v6', cls=TrellisHost, ips=['2000::9902/120'], gateway='2000::9901')
347 self.addLink(r1, rh1v6)
348
349 # DHCP server
350 if ipv6:
351 if remote_server:
352 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
353 ips=['2000::99fd/120'], gateway='2000::9901',
354 dhcpServer=True, ipv6=True)
355 self.addLink(rs0, dhcp)
356 else:
357 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
358 ips=['2000::3fd/120'], gateway='2000::3ff',
359 dhcpServer=True, ipv6=True)
360 self.addLink(dhcp, cs1)
361 if multiple_server:
362 dhcp2 = self.addHost('dhcp2', cls=TrellisHost, mac='00:99:00:00:00:02',
363 ips=['2000::3fc/120'], gateway='2000::3ff',
364 dhcpServer=True, ipv6=True)
365 self.addLink(dhcp2, cs1)
366 else:
367 if remote_server:
368 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
369 ips=['10.0.99.3/24'], gateway='10.0.99.1',
370 dhcpServer=True)
371 self.addLink(rs0, dhcp)
372 else:
373 dhcp = self.addHost('dhcp', cls=TrellisHost, mac='00:99:00:00:00:01',
374 ips=['10.0.3.253/24'], gateway='10.0.3.254',
375 dhcpServer=True)
376 self.addLink(dhcp, cs1)
377 if multiple_server:
378 dhcp2 = self.addHost('dhcp2', cls=TrellisHost, mac='00:99:00:00:00:02',
379 ips=['10.0.3.252/24'], gateway='10.0.3.254',
380 dhcpServer=True)
381 self.addLink(dhcp2, cs1)
382
383def config( opts ):
384 spine = opts.spine
385 leaf = opts.leaf
386 fanout = opts.fanout
387 dualhomed = opts.dualhomed
388 if opts.vlan == '':
389 vlan = [0] * (((leaf / 2) if dualhomed else leaf) * fanout)
390 else:
391 vlan = [int(vlan_id) if vlan_id != '' else 0 for vlan_id in opts.vlan.split(',')]
392
393 if opts.onosIp != '':
394 controllers = opts.onosIp.split( ',' )
395 else:
396 controllers = ['127.0.0.1']
397
398 if len(vlan) != ((leaf / 2) if dualhomed else leaf ) * fanout:
399 print "Invalid vlan configuration is given."
400 return
401
402 if dualhomed:
403 if leaf % 2 == 1 or leaf == 0:
404 print "Even number of leaf switches (at least two) are needed to build dual-homed topology."
405 return
406 else:
407 topo = DualHomedLeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan,
408 ipv6=opts.ipv6,
409 dhcp_client=opts.dhcpClient,
410 dhcp_relay=opts.dhcpRelay,
411 multiple_server=opts.multipleServer,
412 remote_server=opts.remoteServer)
413 else:
414 topo = LeafSpineFabric(spine=spine, leaf=leaf, fanout=fanout, vlan_id=vlan, ipv6=opts.ipv6,
415 dhcp_client=opts.dhcpClient,
416 dhcp_relay=opts.dhcpRelay,
417 multiple_server=opts.multipleServer,
418 remote_server=opts.remoteServer)
419
420 net = Mininet( topo=topo, link=TCLink, build=False,
421 controller=None, autoSetMacs=True )
422 i = 0
423 for ip in controllers:
424 net.addController( "c%s" % ( i ), controller=RemoteController, ip=ip )
425 i += 1
426 net.build()
427 net.start()
428 CLI( net )
429 net.stop()
430
431if __name__ == '__main__':
432 setLogLevel('info')
433 config(opts)
434 os.system('sudo mn -c')