commented unused variables
[vsorcdistro/.git] / mininet / mininet / link.py
1 """
2 link.py: interface and link abstractions for mininet
3
4 It seems useful to bundle functionality for interfaces into a single
5 class.
6
7 Also it seems useful to enable the possibility of multiple flavors of
8 links, including:
9
10 - simple veth pairs
11 - tunneled links
12 - patchable links (which can be disconnected and reconnected via a patchbay)
13 - link simulators (e.g. wireless)
14
15 Basic division of labor:
16
17   Nodes: know how to execute commands
18   Intfs: know how to configure themselves
19   Links: know how to connect nodes together
20
21 Intf: basic interface object that can configure itself
22 TCIntf: interface with bandwidth limiting and delay via tc
23
24 Link: basic link class for creating veth pairs
25 """
26
27 from mininet.log import info, error, debug
28 from mininet.util import makeIntfPair
29 import re
30
31 class Intf( object ):
32
33     "Basic interface object that can configure itself."
34
35     def __init__( self, name, node=None, port=None, link=None,
36                   mac=None, **params ):
37         """name: interface name (e.g. h1-eth0)
38            node: owning node (where this intf most likely lives)
39            link: parent link if we're part of a link
40            other arguments are passed to config()"""
41         self.node = node
42         self.name = name
43         self.link = link
44         self.mac = mac
45         self.ip, self.prefixLen = None, None
46
47         # if interface is lo, we know the ip is 127.0.0.1.
48         # This saves an ifconfig command per node
49         if self.name == 'lo':
50             self.ip = '127.0.0.1'
51             self.prefixLen = 8
52         # Add to node (and move ourselves if necessary )
53         if node:
54             moveIntfFn = params.pop( 'moveIntfFn', None )
55             if moveIntfFn:
56                 node.addIntf( self, port=port, moveIntfFn=moveIntfFn )
57             else:
58                 node.addIntf( self, port=port )
59         # Save params for future reference
60         self.params = params
61         self.config( **params )
62
63     def cmd( self, *args, **kwargs ):
64         "Run a command in our owning node"
65         return self.node.cmd( *args, **kwargs )
66
67     def ifconfig( self, *args ):
68         "Configure ourselves using ifconfig"
69         return self.cmd( 'ifconfig', self.name, *args )
70
71     def setIP( self, ipstr, prefixLen=None ):
72         """Set our IP address"""
73         # This is a sign that we should perhaps rethink our prefix
74         # mechanism and/or the way we specify IP addresses
75         if '/' in ipstr:
76             self.ip, self.prefixLen = ipstr.split( '/' )
77             return self.ifconfig( ipstr, 'up' )
78         else:
79             if prefixLen is None:
80                 raise Exception( 'No prefix length set for IP address %s'
81                                  % ( ipstr, ) )
82             self.ip, self.prefixLen = ipstr, prefixLen
83             return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
84
85     def setMAC( self, macstr ):
86         """Set the MAC address for an interface.
87            macstr: MAC address as string"""
88         self.mac = macstr
89         return ( self.ifconfig( 'down' ) +
90                  self.ifconfig( 'hw', 'ether', macstr ) +
91                  self.ifconfig( 'up' ) )
92
93     _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
94     _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
95
96     def updateIP( self ):
97         "Return updated IP address based on ifconfig"
98         # use pexec instead of node.cmd so that we dont read
99         # backgrounded output from the cli.
100         ifconfig, _err, _exitCode = self.node.pexec(
101             'ifconfig %s' % self.name )
102         ips = self._ipMatchRegex.findall( ifconfig )
103         self.ip = ips[ 0 ] if ips else None
104         return self.ip
105
106     def updateMAC( self ):
107         "Return updated MAC address based on ifconfig"
108         ifconfig = self.ifconfig()
109         macs = self._macMatchRegex.findall( ifconfig )
110         self.mac = macs[ 0 ] if macs else None
111         return self.mac
112
113     # Instead of updating ip and mac separately,
114     # use one ifconfig call to do it simultaneously.
115     # This saves an ifconfig command, which improves performance.
116
117     def updateAddr( self ):
118         "Return IP address and MAC address based on ifconfig."
119         ifconfig = self.ifconfig()
120         ips = self._ipMatchRegex.findall( ifconfig )
121         macs = self._macMatchRegex.findall( ifconfig )
122         self.ip = ips[ 0 ] if ips else None
123         self.mac = macs[ 0 ] if macs else None
124         return self.ip, self.mac
125
126     def IP( self ):
127         "Return IP address"
128         return self.ip
129
130     def MAC( self ):
131         "Return MAC address"
132         return self.mac
133
134     def isUp( self, setUp=False ):
135         "Return whether interface is up"
136         if setUp:
137             cmdOutput = self.ifconfig( 'up' )
138             # no output indicates success
139             if cmdOutput:
140                 error( "Error setting %s up: %s " % ( self.name, cmdOutput ) )
141                 return False
142             else:
143                 return True
144         else:
145             return "UP" in self.ifconfig()
146
147     def rename( self, newname ):
148         "Rename interface"
149         if self.node and self.name in self.node.nameToIntf:
150             # rename intf in node's nameToIntf
151             self.node.nameToIntf[newname] = self.node.nameToIntf.pop(self.name)
152         self.ifconfig( 'down' )
153         result = self.cmd( 'ip link set', self.name, 'name', newname )
154         self.name = newname
155         self.ifconfig( 'up' )
156         return result
157
158     # The reason why we configure things in this way is so
159     # That the parameters can be listed and documented in
160     # the config method.
161     # Dealing with subclasses and superclasses is slightly
162     # annoying, but at least the information is there!
163
164     def setParam( self, results, method, **param ):
165         """Internal method: configure a *single* parameter
166            results: dict of results to update
167            method: config method name
168            param: arg=value (ignore if value=None)
169            value may also be list or dict"""
170         name, value = list( param.items() )[ 0 ]
171         f = getattr( self, method, None )
172         if not f or value is None:
173             return
174         if isinstance( value, list ):
175             result = f( *value )
176         elif isinstance( value, dict ):
177             result = f( **value )
178         else:
179             result = f( value )
180         results[ name ] = result
181         return result
182
183     def config( self, mac=None, ip=None, ifconfig=None,
184                 up=True, **_params ):
185         """Configure Node according to (optional) parameters:
186            mac: MAC address
187            ip: IP address
188            ifconfig: arbitrary interface configuration
189            Subclasses should override this method and call
190            the parent class's config(**params)"""
191         # If we were overriding this method, we would call
192         # the superclass config method here as follows:
193         # r = Parent.config( **params )
194         r = {}
195         self.setParam( r, 'setMAC', mac=mac )
196         self.setParam( r, 'setIP', ip=ip )
197         self.setParam( r, 'isUp', up=up )
198         self.setParam( r, 'ifconfig', ifconfig=ifconfig )
199         return r
200
201     def delete( self ):
202         "Delete interface"
203         self.cmd( 'ip link del ' + self.name )
204         # We used to do this, but it slows us down:
205         # if self.node.inNamespace:
206         # Link may have been dumped into root NS
207         # quietRun( 'ip link del ' + self.name )
208         self.node.delIntf( self )
209         self.link = None
210
211     def status( self ):
212         "Return intf status as a string"
213         links, _err, _result = self.node.pexec( 'ip link show' )
214         if self.name in links:
215             return "OK"
216         else:
217             return "MISSING"
218
219     def __repr__( self ):
220         return '<%s %s>' % ( self.__class__.__name__, self.name )
221
222     def __str__( self ):
223         return self.name
224
225
226 class TCIntf( Intf ):
227     """Interface customized by tc (traffic control) utility
228        Allows specification of bandwidth limits (various methods)
229        as well as delay, loss and max queue length"""
230
231     # The parameters we use seem to work reasonably up to 1 Gb/sec
232     # For higher data rates, we will probably need to change them.
233     bwParamMax = 1000
234
235     def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
236                 latency_ms=None, enable_ecn=False, enable_red=False ):
237         "Return tc commands to set bandwidth"
238
239         cmds, parent = [], ' root '
240
241         if bw and ( bw < 0 or bw > self.bwParamMax ):
242             error( 'Bandwidth limit', bw, 'is outside supported range 0..%d'
243                    % self.bwParamMax, '- ignoring\n' )
244         elif bw is not None:
245             # BL: this seems a bit brittle...
246             if ( speedup > 0 and
247                  self.node.name[0:1] == 's' ):
248                 bw = speedup
249             # This may not be correct - we should look more closely
250             # at the semantics of burst (and cburst) to make sure we
251             # are specifying the correct sizes. For now I have used
252             # the same settings we had in the mininet-hifi code.
253             if use_hfsc:
254                 cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1',
255                           '%s class add dev %s parent 5:0 classid 5:1 hfsc sc '
256                           + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
257             elif use_tbf:
258                 if latency_ms is None:
259                     latency_ms = 15.0 * 8 / bw
260                 cmds += [ '%s qdisc add dev %s root handle 5: tbf ' +
261                           'rate %fMbit burst 15000 latency %fms' %
262                           ( bw, latency_ms ) ]
263             else:
264                 cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1',
265                           '%s class add dev %s parent 5:0 classid 5:1 htb ' +
266                           'rate %fMbit burst 15k' % bw ]
267             parent = ' parent 5:1 '
268
269             # ECN or RED
270             if enable_ecn:
271                 cmds += [ '%s qdisc add dev %s' + parent +
272                           'handle 6: red limit 1000000 ' +
273                           'min 30000 max 35000 avpkt 1500 ' +
274                           'burst 20 ' +
275                           'bandwidth %fmbit probability 1 ecn' % bw ]
276                 parent = ' parent 6: '
277             elif enable_red:
278                 cmds += [ '%s qdisc add dev %s' + parent +
279                           'handle 6: red limit 1000000 ' +
280                           'min 30000 max 35000 avpkt 1500 ' +
281                           'burst 20 ' +
282                           'bandwidth %fmbit probability 1' % bw ]
283                 parent = ' parent 6: '
284         return cmds, parent
285
286     @staticmethod
287     def delayCmds( parent, delay=None, jitter=None,
288                    loss=None, max_queue_size=None ):
289         "Internal method: return tc commands for delay and loss"
290         cmds = []
291         if loss and ( loss < 0 or loss > 100 ):
292             error( 'Bad loss percentage', loss, '%%\n' )
293         else:
294             # Delay/jitter/loss/max queue size
295             netemargs = '%s%s%s%s' % (
296                 'delay %s ' % delay if delay is not None else '',
297                 '%s ' % jitter if jitter is not None else '',
298                 'loss %.5f ' % loss if loss is not None else '',
299                 'limit %d' % max_queue_size if max_queue_size is not None
300                 else '' )
301             if netemargs:
302                 cmds = [ '%s qdisc add dev %s ' + parent +
303                          ' handle 10: netem ' +
304                          netemargs ]
305                 parent = ' parent 10:1 '
306         return cmds, parent
307
308     def tc( self, cmd, tc='tc' ):
309         "Execute tc command for our interface"
310         c = cmd % (tc, self)  # Add in tc command and our name
311         debug(" *** executing command: %s\n" % c)
312         return self.cmd( c )
313
314     def config( self, bw=None, delay=None, jitter=None, loss=None,
315                 gro=False, txo=True, rxo=True,
316                 speedup=0, use_hfsc=False, use_tbf=False,
317                 latency_ms=None, enable_ecn=False, enable_red=False,
318                 max_queue_size=None, **params ):
319         """Configure the port and set its properties.
320            bw: bandwidth in b/s (e.g. '10m')
321            delay: transmit delay (e.g. '1ms' )
322            jitter: jitter (e.g. '1ms')
323            loss: loss (e.g. '1%' )
324            gro: enable GRO (False)
325            txo: enable transmit checksum offload (True)
326            rxo: enable receive checksum offload (True)
327            speedup: experimental switch-side bw option
328            use_hfsc: use HFSC scheduling
329            use_tbf: use TBF scheduling
330            latency_ms: TBF latency parameter
331            enable_ecn: enable ECN (False)
332            enable_red: enable RED (False)
333            max_queue_size: queue limit parameter for netem"""
334
335         # Support old names for parameters
336         gro = not params.pop( 'disable_gro', not gro )
337
338         result = Intf.config( self, **params)
339
340         def on( isOn ):
341             "Helper method: bool -> 'on'/'off'"
342             return 'on' if isOn else 'off'
343
344         # Set offload parameters with ethool
345         self.cmd( 'ethtool -K', self,
346                   'gro', on( gro ),
347                   'tx', on( txo ),
348                   'rx', on( rxo ) )
349
350         # Optimization: return if nothing else to configure
351         # Question: what happens if we want to reset things?
352         if ( bw is None and not delay and not loss
353              and max_queue_size is None ):
354             return
355
356         # Clear existing configuration
357         tcoutput = self.tc( '%s qdisc show dev %s' )
358         if "priomap" not in tcoutput and "noqueue" not in tcoutput:
359             cmds = [ '%s qdisc del dev %s root' ]
360         else:
361             cmds = []
362
363         # Bandwidth limits via various methods
364         bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
365                                       use_hfsc=use_hfsc, use_tbf=use_tbf,
366                                       latency_ms=latency_ms,
367                                       enable_ecn=enable_ecn,
368                                       enable_red=enable_red )
369         cmds += bwcmds
370
371         # Delay/jitter/loss/max_queue_size using netem
372         delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
373                                             loss=loss,
374                                             max_queue_size=max_queue_size,
375                                             parent=parent )
376         cmds += delaycmds
377
378         # Ugly but functional: display configuration info
379         stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
380                   ( [ '%s delay' % delay ] if delay is not None else [] ) +
381                   ( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
382                   ( ['%.5f%% loss' % loss ] if loss is not None else [] ) +
383                   ( [ 'ECN' ] if enable_ecn else [ 'RED' ]
384                     if enable_red else [] ) )
385         info( '(' + ' '.join( stuff ) + ') ' )
386
387         # Execute all the commands in our node
388         debug("at map stage w/cmds: %s\n" % cmds)
389         tcoutputs = [ self.tc(cmd) for cmd in cmds ]
390         for output in tcoutputs:
391             if output != '':
392                 error( "*** Error: %s" % output )
393         debug( "cmds:", cmds, '\n' )
394         debug( "outputs:", tcoutputs, '\n' )
395         result[ 'tcoutputs'] = tcoutputs
396         result[ 'parent' ] = parent
397
398         return result
399
400
401 class Link( object ):
402
403     """A basic link is just a veth pair.
404        Other types of links could be tunnels, link emulators, etc.."""
405
406     # pylint: disable=too-many-branches
407     def __init__( self, node1, node2, port1=None, port2=None,
408                   intfName1=None, intfName2=None, addr1=None, addr2=None,
409                   intf=Intf, cls1=None, cls2=None, params1=None,
410                   params2=None, fast=True, **params ):
411         """Create veth link to another node, making two new interfaces.
412            node1: first node
413            node2: second node
414            port1: node1 port number (optional)
415            port2: node2 port number (optional)
416            intf: default interface class/constructor
417            cls1, cls2: optional interface-specific constructors
418            intfName1: node1 interface name (optional)
419            intfName2: node2  interface name (optional)
420            params1: parameters for interface 1 (optional)
421            params2: parameters for interface 2 (optional)
422            **params: additional parameters for both interfaces"""
423
424         # This is a bit awkward; it seems that having everything in
425         # params is more orthogonal, but being able to specify
426         # in-line arguments is more convenient! So we support both.
427         params1 = dict( params1 ) if params1 else {}
428         params2 = dict( params2 ) if params2 else {}
429         if port1 is not None:
430             params1[ 'port' ] = port1
431         if port2 is not None:
432             params2[ 'port' ] = port2
433         if 'port' not in params1:
434             params1[ 'port' ] = node1.newPort()
435         if 'port' not in params2:
436             params2[ 'port' ] = node2.newPort()
437         if not intfName1:
438             intfName1 = self.intfName( node1, params1[ 'port' ] )
439         if not intfName2:
440             intfName2 = self.intfName( node2, params2[ 'port' ] )
441
442         # Update with remaining parameter list
443         params1.update( params )
444         params2.update( params )
445
446         self.fast = fast
447         if fast:
448             params1.setdefault( 'moveIntfFn', self._ignore )
449             params2.setdefault( 'moveIntfFn', self._ignore )
450             self.makeIntfPair( intfName1, intfName2, addr1, addr2,
451                                node1, node2, deleteIntfs=False )
452         else:
453             self.makeIntfPair( intfName1, intfName2, addr1, addr2 )
454
455         if not cls1:
456             cls1 = intf
457         if not cls2:
458             cls2 = intf
459
460         intf1 = cls1( name=intfName1, node=node1,
461                       link=self, mac=addr1, **params1  )
462         intf2 = cls2( name=intfName2, node=node2,
463                       link=self, mac=addr2, **params2 )
464
465         # All we are is dust in the wind, and our two interfaces
466         self.intf1, self.intf2 = intf1, intf2
467
468     # pylint: enable=too-many-branches
469
470     @staticmethod
471     def _ignore( *args, **kwargs ):
472         "Ignore any arguments"
473         pass
474
475     def intfName( self, node, n ):
476         "Construct a canonical interface name node-ethN for interface n."
477         # Leave this as an instance method for now
478         assert self
479         return node.name + '-eth' + repr( n )
480
481     @classmethod
482     def makeIntfPair( cls, intfname1, intfname2, addr1=None, addr2=None,
483                       node1=None, node2=None, deleteIntfs=True ):
484         """Create pair of interfaces
485            intfname1: name for interface 1
486            intfname2: name for interface 2
487            addr1: MAC address for interface 1 (optional)
488            addr2: MAC address for interface 2 (optional)
489            node1: home node for interface 1 (optional)
490            node2: home node for interface 2 (optional)
491            (override this method [and possibly delete()]
492            to change link type)"""
493         # Leave this as a class method for now
494         assert cls
495         return makeIntfPair( intfname1, intfname2, addr1, addr2, node1, node2,
496                              deleteIntfs=deleteIntfs )
497
498     def delete( self ):
499         "Delete this link"
500         self.intf1.delete()
501         self.intf1 = None
502         self.intf2.delete()
503         self.intf2 = None
504
505     def stop( self ):
506         "Override to stop and clean up link as needed"
507         self.delete()
508
509     def status( self ):
510         "Return link status as a string"
511         return "(%s %s)" % ( self.intf1.status(), self.intf2.status() )
512
513     def __str__( self ):
514         return '%s<->%s' % ( self.intf1, self.intf2 )
515
516
517 class OVSIntf( Intf ):
518     "Patch interface on an OVSSwitch"
519
520     def ifconfig( self, *args ):
521         cmd = ' '.join( args )
522         if cmd == 'up':
523             # OVSIntf is always up
524             return
525         else:
526             raise Exception( 'OVSIntf cannot do ifconfig ' + cmd )
527
528
529 class OVSLink( Link ):
530     """Link that makes patch links between OVSSwitches
531        Warning: in testing we have found that no more
532        than ~64 OVS patch links should be used in row."""
533
534     def __init__( self, node1, node2, **kwargs ):
535         "See Link.__init__() for options"
536         from mininet.node import OVSSwitch
537         self.isPatchLink = False
538         if ( isinstance( node1, OVSSwitch ) and
539              isinstance( node2, OVSSwitch ) ):
540             self.isPatchLink = True
541             kwargs.update( cls1=OVSIntf, cls2=OVSIntf )
542         Link.__init__( self, node1, node2, **kwargs )
543
544     def makeIntfPair( self, *args, **kwargs ):
545         "Usually delegated to OVSSwitch"
546         if self.isPatchLink:
547             return None, None
548         else:
549             return Link.makeIntfPair( *args, **kwargs )
550
551
552 class TCLink( Link ):
553     "Link with TC interfaces"
554     def __init__( self, *args, **kwargs):
555         kwargs.setdefault( 'cls1', TCIntf )
556         kwargs.setdefault( 'cls2', TCIntf )
557         Link.__init__( self, *args, **kwargs)
558
559
560 class TCULink( TCLink ):
561     """TCLink with default settings optimized for UserSwitch
562        (txo=rxo=0/False).  Unfortunately with recent Linux kernels,
563        enabling TX and RX checksum offload on veth pairs doesn't work
564        well with UserSwitch: either it gets terrible performance or
565        TCP packets with bad checksums are generated, forwarded, and
566        *dropped* due to having bad checksums! OVS and LinuxBridge seem
567        to cope with this somehow, but it is likely to be an issue with
568        many software Ethernet bridges."""
569
570     def __init__( self, *args, **kwargs ):
571         kwargs.update( txo=False, rxo=False )
572         TCLink.__init__( self, *args, **kwargs )