Revision d44a5843

View differences:

mininet/net.py
90 90

  
91 91
from mininet.cli import CLI
92 92
from mininet.log import info, error, debug
93
from mininet.node import Host, KernelSwitch, OVSKernelSwitch, Controller
93
from mininet.node import Host, UserSwitch, KernelSwitch, Controller
94 94
from mininet.node import ControllerParams
95 95
from mininet.util import quietRun, fixLimits
96
from mininet.util import createLink, macColonHex
96
from mininet.util import createLink, macColonHex, ipStr, ipParse
97 97
from mininet.xterm import cleanUpScreens, makeXterms
98 98

  
99 99
DATAPATHS = [ 'kernel' ] #[ 'user', 'kernel' ]
......
130 130
           xterms: if build now, spawn xterms?
131 131
           cleanup: if build now, cleanup before creating?
132 132
           inNamespace: spawn switches and controller in net namespaces?
133
           autoSetMacs: set MAC addrs to DPIDs?
133
           autoSetMacs: set MAC addrs from topo?
134 134
           autoStaticArp: set all-pairs static MAC addrs?"""
135 135
        self.switch = switch
136 136
        self.host = host
......
151 151
        self.dps = 0 # number of created kernel datapaths
152 152
        self.terms = [] # list of spawned xterm processes
153 153

  
154
        if topo and build:
155
            self.buildFromTopo( self.topo )
154
        if build:
155
            self.build()
156 156

  
157 157
    def addHost( self, name, mac=None, ip=None ):
158 158
        """Add host.
......
165 165
        self.nameToNode[ name ] = host
166 166
        return host
167 167

  
168
    def addSwitch( self, name, mac=None ):
168
    def addSwitch( self, name, mac=None, ip=None ):
169 169
        """Add switch.
170 170
           name: name of switch to add
171 171
           mac: default MAC address for kernel/OVS switch intf 0
172 172
           returns: added switch"""
173
        if self.switch is KernelSwitch or self.switch is OVSKernelSwitch:
174
            sw = self.switch( name, dp=self.dps, defaultMAC=mac )
175
            self.dps += 1
173
        if self.switch == UserSwitch:
174
            sw = self.switch( name, defaultMAC=mac, defaultIP=ip,
175
                inNamespace=self.inNamespace )
176 176
        else:
177
            sw = self.switch( name )
177
            sw = self.switch( name, defaultMAC=mac, defaultIP=ip, dp=self.dps,
178
                inNamespace=self.inNamespace )
179
        self.dps += 1
178 180
        self.switches.append( sw )
179 181
        self.nameToNode[ name ] = sw
180 182
        return sw
......
206 208
    #    useful for people who wish to simulate a separate control
207 209
    #    network (since real networks may need one!)
208 210

  
209
    def _configureControlNetwork( self ):
211
    def configureControlNetwork( self ):
210 212
        "Configure control network."
211
        self._configureRoutedControlNetwork()
213
        self.configureRoutedControlNetwork()
214

  
215
    # We still need to figure out the right way to pass
216
    # in the control network location.
212 217

  
213
    def _configureRoutedControlNetwork( self ):
218
    def configureRoutedControlNetwork( self, ip='192.168.123.1',
219
        prefixLen=16 ):
214 220
        """Configure a routed control network on controller and switches.
215 221
           For use with the user datapath only right now.
216
           TODO( brandonh ) test this code!
217 222
           """
218
        # params were: controller, switches, ips
219

  
220 223
        controller = self.controllers[ 0 ]
221
        info( '%s <-> ' % controller.name )
224
        info( controller.name + ' <->' )
225
        cip = ip
226
        snum = ipParse( ip )
222 227
        for switch in self.switches:
223
            info( '%s ' % switch.name )
224
            sip = switch.defaultIP
225
            sintf = switch.intfs[ 0 ]
226
            node, cintf = switch.connection[ sintf ]
227
            if node != controller:
228
                error( '*** Error: switch %s not connected to correct'
229
                         'controller' %
230
                         switch.name )
231
                exit( 1 )
232
            controller.setIP( cintf, self.cparams.ip, self.cparams.prefixLen )
233
            switch.setIP( sintf, sip, self.cparams.prefixLen )
228
            info( ' ' + switch.name )
229
            sintf, cintf = createLink( switch, controller )
230
            snum += 1
231
            while snum & 0xff in [ 0, 255 ]:
232
                snum += 1
233
            sip = ipStr( snum )
234
            controller.setIP( cintf, cip, prefixLen )
235
            switch.setIP( sintf, sip, prefixLen )
234 236
            controller.setHostRoute( sip, cintf )
235
            switch.setHostRoute( self.cparams.ip, sintf )
237
            switch.setHostRoute( cip, sintf )
236 238
        info( '\n' )
237 239
        info( '*** Testing control network\n' )
238
        while not controller.intfIsUp( controller.intfs[ 0 ] ):
239
            info( '*** Waiting for %s to come up\n',
240
                controller.intfs[ 0 ] )
240
        while not controller.intfIsUp( cintf ):
241
            info( '*** Waiting for', cintf, 'to come up\n' )
241 242
            sleep( 1 )
242 243
        for switch in self.switches:
243
            while not switch.intfIsUp( switch.intfs[ 0 ] ):
244
                info( '*** Waiting for %s to come up\n' %
245
                    switch.intfs[ 0 ] )
244
            while not switch.intfIsUp( sintf ):
245
                info( '*** Waiting for', sintf, 'to come up\n' )
246 246
                sleep( 1 )
247 247
            if self.ping( hosts=[ switch, controller ] ) != 0:
248 248
                error( '*** Error: control network test failed\n' )
......
265 265
        """Build mininet from a topology object
266 266
           At the end of this function, everything should be connected
267 267
           and up."""
268

  
269
        def addNode( prefix, addMethod, nodeId ):
270
            "Add a host or a switch."
271
            name = prefix + topo.name( nodeId )
272
            mac = macColonHex( nodeId ) if self.setMacs else None
273
            ip = topo.ip( nodeId )
274
            node = addMethod( name, mac=mac, ip=ip )
275
            self.idToNode[ nodeId ] = node
276
            info( name + ' ' )
277

  
278
        # Possibly we should clean up here and/or validate
279
        # the topo
268 280
        if self.cleanup:
269
            pass # cleanup
270
        # validate topo?
281
            pass
282

  
271 283
        info( '*** Adding controller\n' )
272 284
        self.addController( self.controller )
273 285
        info( '*** Creating network\n' )
274 286
        info( '*** Adding hosts:\n' )
275 287
        for hostId in sorted( topo.hosts() ):
276
            name = 'h' + topo.name( hostId )
277
            mac = macColonHex( hostId ) if self.setMacs else None
278
            ip = topo.ip( hostId )
279
            host = self.addHost( name, ip=ip, mac=mac )
280
            self.idToNode[ hostId ] = host
281
            info( name + ' ' )
288
            addNode( 'h', self.addHost, hostId )
282 289
        info( '\n*** Adding switches:\n' )
283 290
        for switchId in sorted( topo.switches() ):
284
            name = 's' + topo.name( switchId )
285
            mac = macColonHex( switchId) if self.setMacs else None
286
            switch = self.addSwitch( name, mac=mac )
287
            self.idToNode[ switchId ] = switch
288
            info( name + ' ' )
291
            addNode( 's', self.addSwitch, switchId )
289 292
        info( '\n*** Adding edges:\n' )
290 293
        for srcId, dstId in sorted( topo.edges() ):
291 294
            src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
292 295
            srcPort, dstPort = topo.port( srcId, dstId )
293
            createLink( src, srcPort, dst, dstPort )
296
            createLink( src, dst, srcPort, dstPort )
294 297
            info( '(%s, %s) ' % ( src.name, dst.name ) )
295 298
        info( '\n' )
296 299

  
300
    def build( self ):
301
        "Build mininet."
302
        if self.topo:
303
            self.buildFromTopo( self.topo )
297 304
        if self.inNamespace:
298 305
            info( '*** Configuring control network\n' )
299
            self._configureControlNetwork()
300

  
306
            self.configureControlNetwork()
301 307
        info( '*** Configuring hosts\n' )
302 308
        self.configHosts()
303

  
304 309
        if self.xterms:
305 310
            self.startXterms()
306 311
        if self.autoSetMacs:
......
391 396
        """Ping between all specified hosts.
392 397
           hosts: list of hosts
393 398
           returns: ploss packet loss percentage"""
394
        #self.start()
395
        # check if running - only then, start?
399
        # should we check if running?
396 400
        packets = 0
397 401
        lost = 0
398 402
        ploss = None
mininet/node.py
49 49
from time import sleep
50 50

  
51 51
from mininet.log import info, error, debug
52
from mininet.util import quietRun, moveIntf
53

  
52
from mininet.util import quietRun, makeIntfPair, moveIntf
54 53

  
55 54
class Node( object ):
56 55
    """A virtual network node is simply a shell in a network namespace.
......
84 83
        self.outToNode[ self.stdout.fileno() ] = self
85 84
        self.inToNode[ self.stdin.fileno() ] = self
86 85
        self.pid = self.shell.pid
87
        self.intfCount = 0
88 86
        self.intfs = {} # dict of port numbers to interface names
89 87
        self.ports = {} # dict of interface names to port numbers
90 88
                        # replace with Port objects, eventually ?
......
206 204
        "Construct a canonical interface name node-ethN for interface n."
207 205
        return self.name + '-eth' + repr( n )
208 206

  
209
    def newIntf( self ):
210
        "Reserve and return a new interface name."
211
        intfName = self.intfName( self.intfCount )
212
        self.intfCount += 1
213
        return intfName
207
    def newPort( self ):
208
        "Return the next port number to allocate."
209
        if len( self.ports ) > 0:
210
            return max( self.ports.values() ) + 1
211
        return 0
214 212

  
215 213
    def addIntf( self, intf, port ):
216 214
        """Add an interface.
......
219 217
        self.intfs[ port ] = intf
220 218
        self.ports[ intf ] = port
221 219
        #info( '\n' )
222
        #info( 'added intf %s to node %x\n' % ( srcIntf, src ) )
220
        #info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
223 221
        if self.inNamespace:
224 222
            #info( 'moving w/inNamespace set\n' )
225 223
            moveIntf( intf, self )
226 224

  
227
    def connect( self, intf, dstNode, dstIntf ):
225
    def registerIntf( self, intf, dstNode, dstIntf ):
228 226
        "Register connection of intf to dstIntf on dstNode."
229 227
        self.connection[ intf ] = ( dstNode, dstIntf )
230 228

  
229
    # This is a symmetric operation, but it makes sense to put
230
    # the code here since it is tightly coupled to routines in
231
    # this class. For a more symmetric API, you can use
232
    # mininet.util.createLink()
233

  
234
    def linkTo( self, node2, port1=None, port2=None ):
235
        """Create link to another node, making two new interfaces.
236
           node2: Node to link us to
237
           port1: our port number (optional)
238
           port2: node2 port number (optional)
239
           returns: intf1 name, intf2 name"""
240
        node1 = self
241
        if port1 is None:
242
            port1 = node1.newPort()
243
        if port2 is None:
244
            port2 = node2.newPort()
245
        intf1 = node1.intfName( port1 )
246
        intf2 = node2.intfName( port2 )
247
        makeIntfPair( intf1, intf2 )
248
        node1.addIntf( intf1, port1 )
249
        node2.addIntf( intf2, port2 )
250
        node1.registerIntf( intf1, node2, intf2 )
251
        node2.registerIntf( intf2, node1, intf1 )
252
        return intf1, intf2
253

  
231 254
    def deleteIntfs( self ):
232 255
        "Delete all of our interfaces."
233 256
        # In theory the interfaces should go away after we shut down.
234 257
        # However, this takes time, so we're better off removing them
235 258
        # explicitly so that we won't get errors if we run before they
236
        # have been removed by the kernel. Unfortunately this is very slow.
259
        # have been removed by the kernel. Unfortunately this is very slow,
260
        # at least with Linux kernels before 2.6.33
237 261
        for intf in self.intfs.values():
238 262
            quietRun( 'ip link del ' + intf )
239 263
            info( '.' )
......
255 279
        result = self.cmd( [ 'arp', '-s', ip, mac ] )
256 280
        return result
257 281

  
258
    def setIP( self, intf, ip, prefixLen ):
282
    def setIP( self, intf, ip, prefixLen=8 ):
259 283
        """Set the IP address for an interface.
260 284
           intf: interface name
261 285
           ip: IP address as a string
......
277 301
        self.cmd( 'ip route flush' )
278 302
        return self.cmd( 'route add default ' + intf )
279 303

  
280
    def IP( self ):
281
        "Return IP address of interface 0"
282
        return self.ips.get( self.intfs.get( 0 , None ), None )
283

  
284
    def MAC( self ):
285
        "Return MAC address of interface 0"
286
        ifconfig = self.cmd( 'ifconfig ' + self.intfs[ 0 ] )
304
    def IP( self, intf=None ):
305
        "Return IP address of a node or specific interface."
306
        if len( self.ips ) == 1:
307
            return self.ips.values()[ 0 ]
308
        if intf:
309
            return self.ips.get( intf, None )
310

  
311
    def MAC( self, intf=None ):
312
        "Return MAC address of a node or specific interface."
313
        if intf is None and len( self.intfs ) == 1:
314
            intf = self.intfs.values()[ 0 ]
315
        ifconfig = self.cmd( 'ifconfig ' + intf )
287 316
        macs = re.findall( '..:..:..:..:..:..', ifconfig )
288 317
        if len( macs ) > 0:
289 318
            return macs[ 0 ]
290
        else:
291
            return None
292 319

  
293
    def intfIsUp( self, port ):
294
        """Check if interface for a given port number is up.
295
           port: port number"""
296
        return 'UP' in self.cmd( 'ifconfig ' + self.intfs[ port ] )
320
    def intfIsUp( self, intf ):
321
        "Check if an interface is up."
322
        return 'UP' in self.cmd( 'ifconfig ' + intf )
297 323

  
298 324
    # Other methods
299 325
    def __str__( self ):
......
331 357

  
332 358

  
333 359
class UserSwitch( Switch ):
334
    """User-space switch.
335
       Currently only works in the root namespace."""
360
    "User-space switch."
336 361

  
337 362
    def __init__( self, name, *args, **kwargs ):
338 363
        """Init.
339 364
           name: name for the switch"""
340
        Switch.__init__( self, name, inNamespace=False, **kwargs )
365
        Switch.__init__( self, name, **kwargs )
341 366

  
342 367
    def start( self, controllers ):
343 368
        """Start OpenFlow reference user datapath.
......
348 373
        ofplog = '/tmp/' + self.name + '-ofp.log'
349 374
        self.cmd( 'ifconfig lo up' )
350 375
        intfs = sorted( self.intfs.values() )
351

  
376
        if self.inNamespace:
377
            intfs = intfs[ :-1 ]
352 378
        self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
353 379
            ' punix:/tmp/' + self.name +
354 380
            ' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
......
364 390

  
365 391
class KernelSwitch( Switch ):
366 392
    """Kernel-space switch.
367
       Currently only works in the root namespace."""
393
       Currently only works in root namespace."""
368 394

  
369 395
    def __init__( self, name, dp=None, **kwargs ):
370 396
        """Init.
371 397
           name:
372 398
           dp: netlink id (0, 1, 2, ...)
373 399
           defaultMAC: default MAC as string; random value if None"""
374
        Switch.__init__( self, name, inNamespace=False, **kwargs )
400
        Switch.__init__( self, name, **kwargs )
375 401
        self.dp = dp
402
        if self.inNamespace:
403
            error( "KernelSwitch currently only works"
404
                " in the root namespace." )
405
            exit( 1 )
376 406

  
377 407
    def start( self, controllers ):
378 408
        "Start up reference kernel datapath."
......
385 415
        if self.defaultMAC:
386 416
            intf = 'of%i' % self.dp
387 417
            self.cmd( [ 'ifconfig', intf, 'hw', 'ether', self.defaultMAC ] )
388

  
389 418
        if len( self.intfs ) != max( self.intfs ) + 1:
390 419
            raise Exception( 'only contiguous, zero-indexed port ranges'
391 420
                            'supported: %s' % self.intfs )
......
412 441

  
413 442
    def __init__( self, name, dp=None, **kwargs ):
414 443
        """Init.
415
           name:
444
           name: name of switch
416 445
           dp: netlink id (0, 1, 2, ...)
417
           dpid: datapath ID as unsigned int; random value if None"""
418
        Switch.__init__( self, name, inNamespace=False, **kwargs )
446
           defaultMAC: default MAC as unsigned int; random value if None"""
447
        Switch.__init__( self, name, **kwargs )
419 448
        self.dp = dp
449
        if self.inNamespace:
450
            error( "OVSKernelSwitch currently only works"
451
                " in the root namespace." )
452
            exit( 1 )
420 453

  
421 454
    def start( self, controllers ):
422 455
        "Start up kernel datapath."
......
480 513
        self.cmd( 'kill %' + self.controller )
481 514
        self.terminate()
482 515

  
483
    def IP( self ):
516
    def IP( self, intf=None ):
484 517
        "Return IP address of the Controller"
485
        return self.defaultIP
486

  
518
        ip = Node.IP( self, intf=intf )
519
        if ip is None:
520
            ip = self.defaultIP
521
        return ip
487 522

  
488 523
class ControllerParams( object ):
489 524
    "Container for controller IP parameters."
mininet/test/test_nets.py
20 20
        "Ping test with both datapaths on minimal topology"
21 21
        init()
22 22
        for switch in SWITCHES.values():
23
            controllerParams = ControllerParams( 0x0a000000, 8 ) # 10.0.0.0/8
23
            controllerParams = ControllerParams( '10.0.0.0', 8 )
24 24
            mn = Mininet( SingleSwitchTopo(), switch, Host, Controller,
25 25
                         controllerParams )
26 26
            dropped = mn.run( 'ping' )
......
30 30
        "Ping test with both datapaths on 5-host single-switch topology"
31 31
        init()
32 32
        for switch in SWITCHES.values():
33
            controllerParams = ControllerParams( 0x0a000000, 8 ) # 10.0.0.0/8
33
            controllerParams = ControllerParams( '10.0.0.0', 8 )
34 34
            mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller,
35 35
                         controllerParams )
36 36
            dropped = mn.run( 'ping' )
......
44 44
        "Ping test with both datapaths on a 5-switch topology"
45 45
        init()
46 46
        for switch in SWITCHES.values():
47
            controllerParams = ControllerParams( 0x0a000000, 8 ) # 10.0.0.0/8
47
            controllerParams = ControllerParams( '10.0.0.0', 8 )
48 48
            mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller,
49 49
                         controllerParams )
50 50
            dropped = mn.run( 'ping' )
mininet/util.py
101 101
       printError: if true, print error"""
102 102
    retry( retries, delaySecs, moveIntfNoRetry, intf, node, printError )
103 103

  
104
def createLink( node1, port1, node2, port2 ):
104
def createLink( node1, node2, port1=None, port2=None ):
105 105
    """Create a link between nodes, making an interface for each.
106 106
       node1: Node object
107
       port1: node1 port number
108 107
       node2: Node object
109
       port2: node2 port number
108
       port1: node1 port number (optional)
109
       port2: node2 port number (optional)
110 110
       returns: intf1 name, intf2 name"""
111
    intf1 = node1.intfName( port1 )
112
    intf2 = node2.intfName( port2 )
113
    makeIntfPair( intf1, intf2 )
114
    node1.addIntf( intf1, port1 )
115
    node2.addIntf( intf2, port2 )
116
    node1.connect( intf1, node2, intf2 )
117
    node2.connect( intf2, node1, intf1 )
118
    return intf1, intf2
111
    return node1.linkTo( node2, port1, port2 )
119 112

  
120 113
def fixLimits():
121 114
    "Fix ridiculously small resource limits."
......
141 134
    return _colonHex( mac, 6 )
142 135

  
143 136
def ipStr( ip ):
144
    """Generate IP address string from an unsigned int
145
       ip: unsigned int of form x << 16 | y << 8 | z
146
       returns: ip address string 10.x.y.z """
147
    hi = ( ip & 0xff0000 ) >> 16
148
    mid = ( ip & 0xff00 ) >> 8
149
    lo = ip & 0xff
150
    return "10.%i.%i.%i" % ( hi, mid, lo )
137
    """Generate IP address string from an unsigned int.
138
       ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
139
       returns: ip address string w.x.y.z, or 10.x.y.z if w==0"""
140
    w = ( ip & 0xff000000 ) >> 24
141
    w = 10 if w == 0 else w
142
    x = ( ip & 0xff0000 ) >> 16
143
    y = ( ip & 0xff00 ) >> 8
144
    z = ip & 0xff
145
    return "%i.%i.%i.%i" % ( w, x, y, z )
146

  
147
def ipNum( w, x, y, z ):
148
    """Generate unsigned int from components ofIP address
149
       returns: w << 24 | x << 16 | y << 8 | z"""
150
    return  ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z
151

  
152
def ipParse( ip ):
153
    "Parse an IP address and return an unsigned int."
154
    args = [ int( arg ) for arg in ip.split( '.' ) ]
155
    return ipNum( *args )

Also available in: Unified diff