2 link.py: interface and link abstractions for mininet
4 It seems useful to bundle functionality for interfaces into a single
7 Also it seems useful to enable the possibility of multiple flavors of
12 - patchable links (which can be disconnected and reconnected via a patchbay)
13 - link simulators (e.g. wireless)
15 Basic division of labor:
17 Nodes: know how to execute commands
18 Intfs: know how to configure themselves
19 Links: know how to connect nodes together
21 Intf: basic interface object that can configure itself
22 TCIntf: interface with bandwidth limiting and delay via tc
24 Link: basic link class for creating veth pairs
27 from mininet.log import info, error, debug
28 from mininet.util import makeIntfPair
33 "Basic interface object that can configure itself."
35 def __init__( self, name, node=None, port=None, link=None,
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()"""
45 self.ip, self.prefixLen = None, None
47 # if interface is lo, we know the ip is 127.0.0.1.
48 # This saves an ifconfig command per node
52 # Add to node (and move ourselves if necessary )
54 moveIntfFn = params.pop( 'moveIntfFn', None )
56 node.addIntf( self, port=port, moveIntfFn=moveIntfFn )
58 node.addIntf( self, port=port )
59 # Save params for future reference
61 self.config( **params )
63 def cmd( self, *args, **kwargs ):
64 "Run a command in our owning node"
65 return self.node.cmd( *args, **kwargs )
67 def ifconfig( self, *args ):
68 "Configure ourselves using ifconfig"
69 return self.cmd( 'ifconfig', self.name, *args )
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
76 self.ip, self.prefixLen = ipstr.split( '/' )
77 return self.ifconfig( ipstr, 'up' )
80 raise Exception( 'No prefix length set for IP address %s'
82 self.ip, self.prefixLen = ipstr, prefixLen
83 return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
85 def setMAC( self, macstr ):
86 """Set the MAC address for an interface.
87 macstr: MAC address as string"""
89 return ( self.ifconfig( 'down' ) +
90 self.ifconfig( 'hw', 'ether', macstr ) +
91 self.ifconfig( 'up' ) )
93 _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
94 _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
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
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
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.
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
134 def isUp( self, setUp=False ):
135 "Return whether interface is up"
137 cmdOutput = self.ifconfig( 'up' )
138 # no output indicates success
140 error( "Error setting %s up: %s " % ( self.name, cmdOutput ) )
145 return "UP" in self.ifconfig()
147 def rename( self, newname ):
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 )
155 self.ifconfig( 'up' )
158 # The reason why we configure things in this way is so
159 # That the parameters can be listed and documented in
161 # Dealing with subclasses and superclasses is slightly
162 # annoying, but at least the information is there!
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:
174 if isinstance( value, list ):
176 elif isinstance( value, dict ):
177 result = f( **value )
180 results[ name ] = result
183 def config( self, mac=None, ip=None, ifconfig=None,
184 up=True, **_params ):
185 """Configure Node according to (optional) parameters:
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 )
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 )
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 )
212 "Return intf status as a string"
213 links, _err, _result = self.node.pexec( 'ip link show' )
214 if self.name in links:
219 def __repr__( self ):
220 return '<%s %s>' % ( self.__class__.__name__, self.name )
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"""
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.
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"
239 cmds, parent = [], ' root '
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' )
245 # BL: this seems a bit brittle...
247 self.node.name[0:1] == 's' ):
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.
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 ) ]
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' %
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 '
271 cmds += [ '%s qdisc add dev %s' + parent +
272 'handle 6: red limit 1000000 ' +
273 'min 30000 max 35000 avpkt 1500 ' +
275 'bandwidth %fmbit probability 1 ecn' % bw ]
276 parent = ' parent 6: '
278 cmds += [ '%s qdisc add dev %s' + parent +
279 'handle 6: red limit 1000000 ' +
280 'min 30000 max 35000 avpkt 1500 ' +
282 'bandwidth %fmbit probability 1' % bw ]
283 parent = ' parent 6: '
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"
291 if loss and ( loss < 0 or loss > 100 ):
292 error( 'Bad loss percentage', loss, '%%\n' )
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
302 cmds = [ '%s qdisc add dev %s ' + parent +
303 ' handle 10: netem ' +
305 parent = ' parent 10:1 '
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)
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"""
335 # Support old names for parameters
336 gro = not params.pop( 'disable_gro', not gro )
338 result = Intf.config( self, **params)
341 "Helper method: bool -> 'on'/'off'"
342 return 'on' if isOn else 'off'
344 # Set offload parameters with ethool
345 self.cmd( 'ethtool -K', self,
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 ):
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' ]
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 )
371 # Delay/jitter/loss/max_queue_size using netem
372 delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
374 max_queue_size=max_queue_size,
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 ) + ') ' )
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:
392 error( "*** Error: %s" % output )
393 debug( "cmds:", cmds, '\n' )
394 debug( "outputs:", tcoutputs, '\n' )
395 result[ 'tcoutputs'] = tcoutputs
396 result[ 'parent' ] = parent
401 class Link( object ):
403 """A basic link is just a veth pair.
404 Other types of links could be tunnels, link emulators, etc.."""
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.
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"""
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()
438 intfName1 = self.intfName( node1, params1[ 'port' ] )
440 intfName2 = self.intfName( node2, params2[ 'port' ] )
442 # Update with remaining parameter list
443 params1.update( params )
444 params2.update( params )
448 params1.setdefault( 'moveIntfFn', self._ignore )
449 params2.setdefault( 'moveIntfFn', self._ignore )
450 self.makeIntfPair( intfName1, intfName2, addr1, addr2,
451 node1, node2, deleteIntfs=False )
453 self.makeIntfPair( intfName1, intfName2, addr1, addr2 )
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 )
465 # All we are is dust in the wind, and our two interfaces
466 self.intf1, self.intf2 = intf1, intf2
468 # pylint: enable=too-many-branches
471 def _ignore( *args, **kwargs ):
472 "Ignore any arguments"
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
479 return node.name + '-eth' + repr( n )
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
495 return makeIntfPair( intfname1, intfname2, addr1, addr2, node1, node2,
496 deleteIntfs=deleteIntfs )
506 "Override to stop and clean up link as needed"
510 "Return link status as a string"
511 return "(%s %s)" % ( self.intf1.status(), self.intf2.status() )
514 return '%s<->%s' % ( self.intf1, self.intf2 )
517 class OVSIntf( Intf ):
518 "Patch interface on an OVSSwitch"
520 def ifconfig( self, *args ):
521 cmd = ' '.join( args )
523 # OVSIntf is always up
526 raise Exception( 'OVSIntf cannot do ifconfig ' + cmd )
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."""
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 )
544 def makeIntfPair( self, *args, **kwargs ):
545 "Usually delegated to OVSSwitch"
549 return Link.makeIntfPair( *args, **kwargs )
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)
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."""
570 def __init__( self, *args, **kwargs ):
571 kwargs.update( txo=False, rxo=False )
572 TCLink.__init__( self, *args, **kwargs )