mininet / mininet / link.py @ e5653fb6
History | View | Annotate | Download (14.1 KB)
1 | a6bcad8f | Bob Lantz | """
|
---|---|---|---|
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 | a908fafa | Bob Lantz | 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 | a6bcad8f | Bob Lantz | """
|
26 | |||
27 | from mininet.log import info, error, debug |
||
28 | from mininet.util import makeIntfPair |
||
29 | from time import sleep |
||
30 | import re |
||
31 | |||
32 | a908fafa | Bob Lantz | class Intf( object ): |
33 | a6bcad8f | Bob Lantz | |
34 | "Basic interface object that can configure itself."
|
||
35 | |||
36 | 0b7c277e | Bob Lantz | def __init__( self, name, node=None, port=None, link=None, **params ): |
37 | d27a3c52 | Bob Lantz | """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 | a6bcad8f | Bob Lantz | self.node = node
|
42 | self.name = name
|
||
43 | self.link = link
|
||
44 | 14ff3ad3 | Bob Lantz | self.mac, self.ip, self.prefixLen = None, None, None |
45 | 84a91a14 | Bob Lantz | # Add to node (and move ourselves if necessary )
|
46 | d7e5dfc5 | Bob Lantz | node.addIntf( self, port=port )
|
47 | 0b7c277e | Bob Lantz | # Save params for future reference
|
48 | self.params = params
|
||
49 | self.config( **params )
|
||
50 | a6bcad8f | Bob Lantz | |
51 | def cmd( self, *args, **kwargs ): |
||
52 | 14ff3ad3 | Bob Lantz | "Run a command in our owning node"
|
53 | 84a91a14 | Bob Lantz | return self.node.cmd( *args, **kwargs ) |
54 | a6bcad8f | Bob Lantz | |
55 | def ifconfig( self, *args ): |
||
56 | "Configure ourselves using ifconfig"
|
||
57 | return self.cmd( 'ifconfig', self.name, *args ) |
||
58 | |||
59 | 14ff3ad3 | Bob Lantz | def setIP( self, ipstr, prefixLen=None ): |
60 | a6bcad8f | Bob Lantz | """Set our IP address"""
|
61 | # This is a sign that we should perhaps rethink our prefix
|
||
62 | 14ff3ad3 | Bob Lantz | # 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 | a6bcad8f | Bob Lantz | |
70 | def setMAC( self, macstr ): |
||
71 | """Set the MAC address for an interface.
|
||
72 | macstr: MAC address as string"""
|
||
73 | self.mac = macstr
|
||
74 | 14ff3ad3 | Bob Lantz | return ( self.ifconfig( 'down' ) + |
75 | a6bcad8f | Bob Lantz | 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 | 14ff3ad3 | Bob Lantz | def updateMAC( self ): |
89 | a6bcad8f | Bob Lantz | "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 | 14ff3ad3 | Bob Lantz | |
95 | a6bcad8f | Bob Lantz | 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 | 14ff3ad3 | Bob Lantz | def isUp( self, setUp=False ): |
104 | a6bcad8f | Bob Lantz | "Return whether interface is up"
|
105 | 14ff3ad3 | Bob Lantz | if setUp:
|
106 | a49c85a6 | Bob Lantz | self.ifconfig( 'up' ) |
107 | a6bcad8f | Bob Lantz | return "UP" in self.ifconfig() |
108 | |||
109 | 8856d284 | Bob Lantz | 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 | 84a91a14 | Bob Lantz | # 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 | a6bcad8f | Bob Lantz | |
123 | edf46e95 | Bob Lantz | 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 | 84a91a14 | Bob Lantz | name, value = param.items()[ 0 ]
|
130 | edf46e95 | Bob Lantz | f = getattr( self, method, None ) |
131 | if not f or value is None: |
||
132 | 84a91a14 | Bob Lantz | return
|
133 | if type( value ) is list: |
||
134 | edf46e95 | Bob Lantz | result = f( *value ) |
135 | 84a91a14 | Bob Lantz | elif type( value ) is dict: |
136 | edf46e95 | Bob Lantz | result = f( **value ) |
137 | 84a91a14 | Bob Lantz | else:
|
138 | edf46e95 | Bob Lantz | result = f( value ) |
139 | results[ name ] = result |
||
140 | return result
|
||
141 | 84a91a14 | Bob Lantz | |
142 | 14ff3ad3 | Bob Lantz | def config( self, mac=None, ip=None, ifconfig=None, |
143 | up=True, **_params ):
|
||
144 | 84a91a14 | Bob Lantz | """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 | a49c85a6 | Bob Lantz | self.setParam( r, 'isUp', up=up ) |
157 | 84a91a14 | Bob Lantz | self.setParam( r, 'ifconfig', ifconfig=ifconfig ) |
158 | 8856d284 | Bob Lantz | self.updateIP()
|
159 | self.updateMAC()
|
||
160 | 84a91a14 | Bob Lantz | return r
|
161 | a6bcad8f | Bob Lantz | |
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 | d7e5dfc5 | Bob Lantz | def __repr__( self ): |
169 | return '<%s %s>' % ( self.__class__.__name__, self.name ) |
||
170 | |||
171 | a6bcad8f | Bob Lantz | def __str__( self ): |
172 | return self.name |
||
173 | |||
174 | |||
175 | a908fafa | Bob Lantz | class TCIntf( Intf ): |
176 | 14ff3ad3 | Bob Lantz | """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 | a6bcad8f | Bob Lantz | |
180 | 14ff3ad3 | Bob Lantz | def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False, |
181 | enable_ecn=False, enable_red=False ): |
||
182 | "Return tc commands to set bandwidth"
|
||
183 | a6bcad8f | Bob Lantz | |
184 | 14ff3ad3 | Bob Lantz | cmds, parent = [], ' root '
|
185 | a6bcad8f | Bob Lantz | |
186 | if bw and ( bw < 0 or bw > 1000 ): |
||
187 | error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' ) |
||
188 | |||
189 | 14ff3ad3 | Bob Lantz | elif bw is not None: |
190 | # BL: this seems a bit brittle...
|
||
191 | if ( speedup > 0 and |
||
192 | 8139695d | Bob Lantz | self.node.name[0:1] == 's' ): |
193 | d27a3c52 | Bob Lantz | bw = speedup |
194 | 8139695d | Bob Lantz | burst = bw * 1e6 / 8 * .001 |
195 | e5653fb6 | Bob Lantz | # This may not be correct - we should look more closely
|
196 | # at the semantics of burst (and cburst) to make sure we
|
||
197 | # are specifying the correct sizes. For now I have used
|
||
198 | # the same settings we had in the mininet-hifi code.
|
||
199 | d27a3c52 | Bob Lantz | if use_hfsc:
|
200 | 14ff3ad3 | Bob Lantz | cmds = [ '%s qdisc add dev %s root handle 1:0 hfsc default 1',
|
201 | 8139695d | Bob Lantz | '%s class add dev %s parent 1:0 classid 1:1 hfsc sc '
|
202 | 14ff3ad3 | Bob Lantz | + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
|
203 | d27a3c52 | Bob Lantz | elif use_tbf:
|
204 | e5653fb6 | Bob Lantz | latency_us = 10 * 1500 * 8 / bw |
205 | 14ff3ad3 | Bob Lantz | cmds = ['%s qdisc add dev %s root handle 1: tbf ' +
|
206 | e5653fb6 | Bob Lantz | 'rate %fMbit burst 15000 latency %fus' %
|
207 | ( bw, latency_us ) ] |
||
208 | d27a3c52 | Bob Lantz | else:
|
209 | 14ff3ad3 | Bob Lantz | cmds = [ '%s qdisc add dev %s root handle 1:0 htb default 1',
|
210 | d27a3c52 | Bob Lantz | '%s class add dev %s parent 1:0 classid 1:1 htb ' +
|
211 | e5653fb6 | Bob Lantz | 'rate %fMbit burst 15k' % bw ]
|
212 | d27a3c52 | Bob Lantz | parent = ' parent 1:1 '
|
213 | |||
214 | # ECN or RED
|
||
215 | if enable_ecn:
|
||
216 | 14ff3ad3 | Bob Lantz | cmds = [ '%s qdisc add dev %s' + parent +
|
217 | 'handle 10: red limit 1000000 ' +
|
||
218 | 'min 20000 max 25000 avpkt 1000 ' +
|
||
219 | 'burst 20 ' +
|
||
220 | d27a3c52 | Bob Lantz | 'bandwidth %fmbit probability 1 ecn' % bw ]
|
221 | parent = ' parent 10: '
|
||
222 | elif enable_red:
|
||
223 | 14ff3ad3 | Bob Lantz | cmds = [ '%s qdisc add dev %s' + parent +
|
224 | 'handle 10: red limit 1000000 ' +
|
||
225 | 'min 20000 max 25000 avpkt 1000 ' +
|
||
226 | 'burst 20 ' +
|
||
227 | d27a3c52 | Bob Lantz | 'bandwidth %fmbit probability 1' % bw ]
|
228 | parent = ' parent 10: '
|
||
229 | 14ff3ad3 | Bob Lantz | |
230 | return cmds, parent
|
||
231 | |||
232 | @staticmethod
|
||
233 | def delayCmds( parent, delay=None, loss=None, |
||
234 | max_queue_size=None ):
|
||
235 | "Internal method: return tc commands for delay and loss"
|
||
236 | cmds = [] |
||
237 | if delay and delay < 0: |
||
238 | error( 'Negative delay', delay, '\n' ) |
||
239 | elif loss and ( loss < 0 or loss > 100 ): |
||
240 | error( 'Bad loss percentage', loss, '%%\n' ) |
||
241 | else:
|
||
242 | # Delay/loss/max queue size
|
||
243 | netemargs = '%s%s%s' % (
|
||
244 | 'delay %s ' % delay if delay is not None else '', |
||
245 | 'loss %d ' % loss if loss is not None else '', |
||
246 | 'limit %d' % max_queue_size if max_queue_size is not None |
||
247 | else '' ) |
||
248 | if netemargs:
|
||
249 | cmds = [ '%s qdisc add dev %s ' + parent + ' netem ' + |
||
250 | netemargs ] |
||
251 | return cmds
|
||
252 | |||
253 | def tc( self, cmd, tc='tc' ): |
||
254 | "Execute tc command for our interface"
|
||
255 | c = cmd % (tc, self) # Add in tc command and our name |
||
256 | debug(" *** executing command: %s\n" % c)
|
||
257 | return self.cmd( c ) |
||
258 | |||
259 | def config( self, bw=None, delay=None, loss=None, disable_gro=True, |
||
260 | speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False, |
||
261 | enable_red=False, max_queue_size=None, **params ): |
||
262 | "Configure the port and set its properties."
|
||
263 | |||
264 | result = Intf.config( self, **params)
|
||
265 | |||
266 | # Disable GRO
|
||
267 | if disable_gro:
|
||
268 | self.cmd( 'ethtool -K %s gro off' % self ) |
||
269 | |||
270 | # Optimization: return if nothing else to configure
|
||
271 | # Question: what happens if we want to reset things?
|
||
272 | if ( bw is None and not delay and not loss |
||
273 | and max_queue_size is None ): |
||
274 | return
|
||
275 | |||
276 | # Clear existing configuration
|
||
277 | cmds = [ '%s qdisc del dev %s root' ]
|
||
278 | |||
279 | # Bandwidth limits via various methods
|
||
280 | bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
|
||
281 | use_hfsc=use_hfsc, use_tbf=use_tbf, |
||
282 | enable_ecn=enable_ecn, |
||
283 | enable_red=enable_red ) |
||
284 | cmds += bwcmds |
||
285 | |||
286 | # Delay/loss/max_queue_size using netem
|
||
287 | cmds += self.delayCmds( delay=delay, loss=loss,
|
||
288 | max_queue_size=max_queue_size, |
||
289 | parent=parent ) |
||
290 | |||
291 | # Ugly but functional: display configuration info
|
||
292 | stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + |
||
293 | ( [ '%s delay' % delay ] if delay is not None else [] ) + |
||
294 | ( ['%d%% loss' % loss ] if loss is not None else [] ) + |
||
295 | ( [ 'ECN' ] if enable_ecn else [ 'RED' ] |
||
296 | if enable_red else [] ) ) |
||
297 | info( '(' + ' '.join( stuff ) + ') ' ) |
||
298 | |||
299 | # Execute all the commands in our node
|
||
300 | a6bcad8f | Bob Lantz | debug("at map stage w/cmds: %s\n" % cmds)
|
301 | 14ff3ad3 | Bob Lantz | tcoutputs = [ self.tc(cmd) for cmd in cmds ] |
302 | 84a91a14 | Bob Lantz | debug( "cmds:", cmds, '\n' ) |
303 | debug( "outputs:", tcoutputs, '\n' ) |
||
304 | result[ 'tcoutputs'] = tcoutputs
|
||
305 | 14ff3ad3 | Bob Lantz | |
306 | 84a91a14 | Bob Lantz | return result
|
307 | a6bcad8f | Bob Lantz | |
308 | |||
309 | class Link( object ): |
||
310 | 14ff3ad3 | Bob Lantz | |
311 | a6bcad8f | Bob Lantz | """A basic link is just a veth pair.
|
312 | Other types of links could be tunnels, link emulators, etc.."""
|
||
313 | |||
314 | 14ff3ad3 | Bob Lantz | def __init__( self, node1, node2, port1=None, port2=None, |
315 | intfName1=None, intfName2=None, |
||
316 | intf=Intf, cls1=None, cls2=None, params1=None, |
||
317 | params2=None ):
|
||
318 | a6bcad8f | Bob Lantz | """Create veth link to another node, making two new interfaces.
|
319 | node1: first node
|
||
320 | node2: second node
|
||
321 | port1: node1 port number (optional)
|
||
322 | port2: node2 port number (optional)
|
||
323 | 84a91a14 | Bob Lantz | intf: default interface class/constructor
|
324 | cls1, cls2: optional interface-specific constructors
|
||
325 | a6bcad8f | Bob Lantz | intfName1: node1 interface name (optional)
|
326 | 84a91a14 | Bob Lantz | intfName2: node2 interface name (optional)
|
327 | params1: parameters for interface 1
|
||
328 | params2: parameters for interface 2"""
|
||
329 | a6bcad8f | Bob Lantz | # This is a bit awkward; it seems that having everything in
|
330 | # params would be more orthogonal, but being able to specify
|
||
331 | # in-line arguments is more convenient!
|
||
332 | if port1 is None: |
||
333 | port1 = node1.newPort() |
||
334 | if port2 is None: |
||
335 | port2 = node2.newPort() |
||
336 | if not intfName1: |
||
337 | intfName1 = self.intfName( node1, port1 )
|
||
338 | if not intfName2: |
||
339 | intfName2 = self.intfName( node2, port2 )
|
||
340 | 14ff3ad3 | Bob Lantz | |
341 | a6bcad8f | Bob Lantz | self.makeIntfPair( intfName1, intfName2 )
|
342 | 14ff3ad3 | Bob Lantz | |
343 | 84a91a14 | Bob Lantz | if not cls1: |
344 | cls1 = intf |
||
345 | if not cls2: |
||
346 | cls2 = intf |
||
347 | 14ff3ad3 | Bob Lantz | if not params1: |
348 | params1 = {} |
||
349 | if not params2: |
||
350 | params2 = {} |
||
351 | |||
352 | d7e5dfc5 | Bob Lantz | intf1 = cls1( name=intfName1, node=node1, port=port1, |
353 | link=self, **params1 )
|
||
354 | intf2 = cls2( name=intfName2, node=node2, port=port2, |
||
355 | link=self, **params2 )
|
||
356 | 14ff3ad3 | Bob Lantz | |
357 | 84a91a14 | Bob Lantz | # All we are is dust in the wind, and our two interfaces
|
358 | a6bcad8f | Bob Lantz | self.intf1, self.intf2 = intf1, intf2 |
359 | |||
360 | @classmethod
|
||
361 | def intfName( cls, node, n ): |
||
362 | "Construct a canonical interface name node-ethN for interface n."
|
||
363 | return node.name + '-eth' + repr( n ) |
||
364 | |||
365 | @classmethod
|
||
366 | def makeIntfPair( cls, intf1, intf2 ): |
||
367 | """Create pair of interfaces
|
||
368 | intf1: name of interface 1
|
||
369 | intf2: name of interface 2
|
||
370 | 14ff3ad3 | Bob Lantz | (override this class method [and possibly delete()]
|
371 | to change link type)"""
|
||
372 | a6bcad8f | Bob Lantz | makeIntfPair( intf1, intf2 ) |
373 | |||
374 | def delete( self ): |
||
375 | "Delete this link"
|
||
376 | self.intf1.delete()
|
||
377 | self.intf2.delete()
|
||
378 | |||
379 | def __str__( self ): |
||
380 | return '%s<->%s' % ( self.intf1, self.intf2 ) |
||
381 | ff568819 | Bob Lantz | |
382 | class TCLink( Link ): |
||
383 | "Link with symmetric TC interfaces configured via opts"
|
||
384 | def __init__( self, node1, node2, port1=None, port2=None, |
||
385 | intfName1=None, intfName2=None, **params ): |
||
386 | 2ec866d2 | Bob Lantz | Link.__init__( self, node1, node2, port1=port1, port2=port2,
|
387 | intfName1=intfName1, intfName2=intfName2, |
||
388 | ff568819 | Bob Lantz | cls1=TCIntf, |
389 | cls2=TCIntf, |
||
390 | params1=params, |
||
391 | params2=params) |