blob: 21e18ba9f63ae6d7ce97fca1b98291f7ab0f922a [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"""
2link.py: interface and link abstractions for mininet
3
4It seems useful to bundle functionality for interfaces into a single
5class.
6
7Also it seems useful to enable the possibility of multiple flavors of
8links, 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
15Basic 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
21Intf: basic interface object that can configure itself
22TCIntf: interface with bandwidth limiting and delay via tc
23
24Link: basic link class for creating veth pairs
25"""
26
27from mininet.log import info, error, debug
28from mininet.util import makeIntfPair
29from time import sleep
30import re
31
32class Intf( object ):
33
34 "Basic interface object that can configure itself."
35
36 def __init__( self, name, node=None, port=None, link=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, self.ip, self.prefixLen = None, None, None
45 # Add to node (and move ourselves if necessary )
46 node.addIntf( self, port=port )
47 # Save params for future reference
48 self.params = params
49 self.config( **params )
50
51 def cmd( self, *args, **kwargs ):
52 "Run a command in our owning node"
53 return self.node.cmd( *args, **kwargs )
54
55 def ifconfig( self, *args ):
56 "Configure ourselves using ifconfig"
57 return self.cmd( 'ifconfig', self.name, *args )
58
59 def setIP( self, ipstr, prefixLen=None ):
60 """Set our IP address"""
61 # This is a sign that we should perhaps rethink our prefix
62 # mechanism and/or the way we specify IP addresses
63 if '/' in ipstr:
64 self.ip, self.prefixLen = ipstr.split( '/' )
65 return self.ifconfig( ipstr, 'up' )
66 else:
67 self.ip, self.prefixLen = ipstr, prefixLen
68 return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
69
70 def setMAC( self, macstr ):
71 """Set the MAC address for an interface.
72 macstr: MAC address as string"""
73 self.mac = macstr
74 return ( self.ifconfig( 'down' ) +
75 self.ifconfig( 'hw', 'ether', macstr ) +
76 self.ifconfig( 'up' ) )
77
78 _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
79 _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
80
81 def updateIP( self ):
82 "Return updated IP address based on ifconfig"
83 ifconfig = self.ifconfig()
84 ips = self._ipMatchRegex.findall( ifconfig )
85 self.ip = ips[ 0 ] if ips else None
86 return self.ip
87
88 def updateMAC( self ):
89 "Return updated MAC address based on ifconfig"
90 ifconfig = self.ifconfig()
91 macs = self._macMatchRegex.findall( ifconfig )
92 self.mac = macs[ 0 ] if macs else None
93 return self.mac
94
95 def IP( self ):
96 "Return IP address"
97 return self.ip
98
99 def MAC( self ):
100 "Return MAC address"
101 return self.mac
102
103 def isUp( self, setUp=False ):
104 "Return whether interface is up"
105 if setUp:
106 self.ifconfig( 'up' )
107 return "UP" in self.ifconfig()
108
109 def rename( self, newname ):
110 "Rename interface"
111 self.ifconfig( 'down' )
112 result = self.cmd( 'ip link set', self.name, 'name', newname )
113 self.name = newname
114 self.ifconfig( 'up' )
115 return result
116
117 # The reason why we configure things in this way is so
118 # That the parameters can be listed and documented in
119 # the config method.
120 # Dealing with subclasses and superclasses is slightly
121 # annoying, but at least the information is there!
122
123 def setParam( self, results, method, **param ):
124 """Internal method: configure a *single* parameter
125 results: dict of results to update
126 method: config method name
127 param: arg=value (ignore if value=None)
128 value may also be list or dict"""
129 name, value = param.items()[ 0 ]
130 f = getattr( self, method, None )
131 if not f or value is None:
132 return
133 if type( value ) is list:
134 result = f( *value )
135 elif type( value ) is dict:
136 result = f( **value )
137 else:
138 result = f( value )
139 results[ name ] = result
140 return result
141
142 def config( self, mac=None, ip=None, ifconfig=None,
143 up=True, **_params ):
144 """Configure Node according to (optional) parameters:
145 mac: MAC address
146 ip: IP address
147 ifconfig: arbitrary interface configuration
148 Subclasses should override this method and call
149 the parent class's config(**params)"""
150 # If we were overriding this method, we would call
151 # the superclass config method here as follows:
152 # r = Parent.config( **params )
153 r = {}
154 self.setParam( r, 'setMAC', mac=mac )
155 self.setParam( r, 'setIP', ip=ip )
156 self.setParam( r, 'isUp', up=up )
157 self.setParam( r, 'ifconfig', ifconfig=ifconfig )
158 self.updateIP()
159 self.updateMAC()
160 return r
161
162 def delete( self ):
163 "Delete interface"
164 self.cmd( 'ip link del ' + self.name )
165 # Does it help to sleep to let things run?
166 sleep( 0.001 )
167
168 def __repr__( self ):
169 return '<%s %s>' % ( self.__class__.__name__, self.name )
170
171 def __str__( self ):
172 return self.name
173
174
175class TCIntf( Intf ):
176 """Interface customized by tc (traffic control) utility
177 Allows specification of bandwidth limits (various methods)
178 as well as delay, loss and max queue length"""
179
180 def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
181 latency_ms=None, enable_ecn=False, enable_red=False ):
182 "Return tc commands to set bandwidth"
183
184 cmds, parent = [], ' root '
185
186 if bw and ( bw < 0 or bw > 1000 ):
187 error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
188
189 elif bw is not None:
190 # BL: this seems a bit brittle...
191 if ( speedup > 0 and
192 self.node.name[0:1] == 's' ):
193 bw = speedup
194 # This may not be correct - we should look more closely
195 # at the semantics of burst (and cburst) to make sure we
196 # are specifying the correct sizes. For now I have used
197 # the same settings we had in the mininet-hifi code.
198 if use_hfsc:
199 cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1',
200 '%s class add dev %s parent 1:0 classid 1:1 hfsc sc '
201 + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
202 elif use_tbf:
203 if latency_ms is None:
204 latency_ms = 15 * 8 / bw
205 cmds += [ '%s qdisc add dev %s root handle 1: tbf ' +
206 'rate %fMbit burst 15000 latency %fms' %
207 ( bw, latency_ms ) ]
208 else:
209 cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1',
210 '%s class add dev %s parent 1:0 classid 1:1 htb ' +
211 'rate %fMbit burst 15k' % bw ]
212 parent = ' parent 1:1 '
213
214 # ECN or RED
215 if enable_ecn:
216 cmds += [ '%s qdisc add dev %s' + parent +
217 'handle 10: red limit 1000000 ' +
218 'min 30000 max 35000 avpkt 1500 ' +
219 'burst 20 ' +
220 'bandwidth %fmbit probability 1 ecn' % bw ]
221 parent = ' parent 10: '
222 elif enable_red:
223 cmds += [ '%s qdisc add dev %s' + parent +
224 'handle 10: red limit 1000000 ' +
225 'min 30000 max 35000 avpkt 1500 ' +
226 'burst 20 ' +
227 'bandwidth %fmbit probability 1' % bw ]
228 parent = ' parent 10: '
229 return cmds, parent
230
231 @staticmethod
232 def delayCmds( parent, delay=None, jitter=None,
233 loss=None, max_queue_size=None ):
234 "Internal method: return tc commands for delay and loss"
235 cmds = []
236 if delay and delay < 0:
237 error( 'Negative delay', delay, '\n' )
238 elif jitter and jitter < 0:
239 error( 'Negative jitter', jitter, '\n' )
240 elif loss and ( loss < 0 or loss > 100 ):
241 error( 'Bad loss percentage', loss, '%%\n' )
242 else:
243 # Delay/jitter/loss/max queue size
244 netemargs = '%s%s%s%s' % (
245 'delay %s ' % delay if delay is not None else '',
246 '%s ' % jitter if jitter is not None else '',
247 'loss %d ' % loss if loss is not None else '',
248 'limit %d' % max_queue_size if max_queue_size is not None
249 else '' )
250 if netemargs:
251 cmds = [ '%s qdisc add dev %s ' + parent +
252 ' handle 10: netem ' +
253 netemargs ]
254 return cmds
255
256 def tc( self, cmd, tc='tc' ):
257 "Execute tc command for our interface"
258 c = cmd % (tc, self) # Add in tc command and our name
259 debug(" *** executing command: %s\n" % c)
260 return self.cmd( c )
261
262 def config( self, bw=None, delay=None, jitter=None, loss=None,
263 disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False,
264 latency_ms=None, enable_ecn=False, enable_red=False,
265 max_queue_size=None, **params ):
266 "Configure the port and set its properties."
267
268 result = Intf.config( self, **params)
269
270 # Disable GRO
271 if disable_gro:
272 self.cmd( 'ethtool -K %s gro off' % self )
273
274 # Optimization: return if nothing else to configure
275 # Question: what happens if we want to reset things?
276 if ( bw is None and not delay and not loss
277 and max_queue_size is None ):
278 return
279
280 # Clear existing configuration
281 cmds = [ '%s qdisc del dev %s root' ]
282
283 # Bandwidth limits via various methods
284 bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
285 use_hfsc=use_hfsc, use_tbf=use_tbf,
286 latency_ms=latency_ms,
287 enable_ecn=enable_ecn,
288 enable_red=enable_red )
289 cmds += bwcmds
290
291 # Delay/jitter/loss/max_queue_size using netem
292 cmds += self.delayCmds( delay=delay, jitter=jitter, loss=loss,
293 max_queue_size=max_queue_size,
294 parent=parent )
295
296 # Ugly but functional: display configuration info
297 stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
298 ( [ '%s delay' % delay ] if delay is not None else [] ) +
299 ( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
300 ( ['%d%% loss' % loss ] if loss is not None else [] ) +
301 ( [ 'ECN' ] if enable_ecn else [ 'RED' ]
302 if enable_red else [] ) )
303 info( '(' + ' '.join( stuff ) + ') ' )
304
305 # Execute all the commands in our node
306 debug("at map stage w/cmds: %s\n" % cmds)
307 tcoutputs = [ self.tc(cmd) for cmd in cmds ]
308 debug( "cmds:", cmds, '\n' )
309 debug( "outputs:", tcoutputs, '\n' )
310 result[ 'tcoutputs'] = tcoutputs
311
312 return result
313
314
315class Link( object ):
316
317 """A basic link is just a veth pair.
318 Other types of links could be tunnels, link emulators, etc.."""
319
320 def __init__( self, node1, node2, port1=None, port2=None,
321 intfName1=None, intfName2=None,
322 intf=Intf, cls1=None, cls2=None, params1=None,
323 params2=None ):
324 """Create veth link to another node, making two new interfaces.
325 node1: first node
326 node2: second node
327 port1: node1 port number (optional)
328 port2: node2 port number (optional)
329 intf: default interface class/constructor
330 cls1, cls2: optional interface-specific constructors
331 intfName1: node1 interface name (optional)
332 intfName2: node2 interface name (optional)
333 params1: parameters for interface 1
334 params2: parameters for interface 2"""
335 # This is a bit awkward; it seems that having everything in
336 # params would be more orthogonal, but being able to specify
337 # in-line arguments is more convenient!
338 if port1 is None:
339 port1 = node1.newPort()
340 if port2 is None:
341 port2 = node2.newPort()
342 if not intfName1:
343 intfName1 = self.intfName( node1, port1 )
344 if not intfName2:
345 intfName2 = self.intfName( node2, port2 )
346
347 self.makeIntfPair( intfName1, intfName2 )
348
349 if not cls1:
350 cls1 = intf
351 if not cls2:
352 cls2 = intf
353 if not params1:
354 params1 = {}
355 if not params2:
356 params2 = {}
357
358 intf1 = cls1( name=intfName1, node=node1, port=port1,
359 link=self, **params1 )
360 intf2 = cls2( name=intfName2, node=node2, port=port2,
361 link=self, **params2 )
362
363 # All we are is dust in the wind, and our two interfaces
364 self.intf1, self.intf2 = intf1, intf2
365
366 @classmethod
367 def intfName( cls, node, n ):
368 "Construct a canonical interface name node-ethN for interface n."
369 return node.name + '-eth' + repr( n )
370
371 @classmethod
372 def makeIntfPair( cls, intf1, intf2 ):
373 """Create pair of interfaces
374 intf1: name of interface 1
375 intf2: name of interface 2
376 (override this class method [and possibly delete()]
377 to change link type)"""
378 makeIntfPair( intf1, intf2 )
379
380 def delete( self ):
381 "Delete this link"
382 self.intf1.delete()
383 self.intf2.delete()
384
385 def __str__( self ):
386 return '%s<->%s' % ( self.intf1, self.intf2 )
387
388class TCLink( Link ):
389 "Link with symmetric TC interfaces configured via opts"
390 def __init__( self, node1, node2, port1=None, port2=None,
391 intfName1=None, intfName2=None, **params ):
392 Link.__init__( self, node1, node2, port1=port1, port2=port2,
393 intfName1=intfName1, intfName2=intfName2,
394 cls1=TCIntf,
395 cls2=TCIntf,
396 params1=params,
397 params2=params)