mininet / mininet / link.py @ 824afb84
History | View | Annotate | Download (14.8 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 | 10be691b | Bob Lantz | from mininet.util import makeIntfPair, quietRun |
29 | a6bcad8f | Bob Lantz | import re |
30 | |||
31 | a908fafa | Bob Lantz | class Intf( object ): |
32 | a6bcad8f | Bob Lantz | |
33 | "Basic interface object that can configure itself."
|
||
34 | |||
35 | 0b7c277e | Bob Lantz | def __init__( self, name, node=None, port=None, link=None, **params ): |
36 | d27a3c52 | Bob Lantz | """name: interface name (e.g. h1-eth0)
|
37 | node: owning node (where this intf most likely lives)
|
||
38 | link: parent link if we're part of a link
|
||
39 | other arguments are passed to config()"""
|
||
40 | a6bcad8f | Bob Lantz | self.node = node
|
41 | self.name = name
|
||
42 | self.link = link
|
||
43 | 14ff3ad3 | Bob Lantz | self.mac, self.ip, self.prefixLen = None, None, None |
44 | 84a91a14 | Bob Lantz | # Add to node (and move ourselves if necessary )
|
45 | d7e5dfc5 | Bob Lantz | node.addIntf( self, port=port )
|
46 | 0b7c277e | Bob Lantz | # Save params for future reference
|
47 | self.params = params
|
||
48 | self.config( **params )
|
||
49 | a6bcad8f | Bob Lantz | |
50 | def cmd( self, *args, **kwargs ): |
||
51 | 14ff3ad3 | Bob Lantz | "Run a command in our owning node"
|
52 | 84a91a14 | Bob Lantz | return self.node.cmd( *args, **kwargs ) |
53 | a6bcad8f | Bob Lantz | |
54 | def ifconfig( self, *args ): |
||
55 | "Configure ourselves using ifconfig"
|
||
56 | return self.cmd( 'ifconfig', self.name, *args ) |
||
57 | |||
58 | 14ff3ad3 | Bob Lantz | def setIP( self, ipstr, prefixLen=None ): |
59 | a6bcad8f | Bob Lantz | """Set our IP address"""
|
60 | # This is a sign that we should perhaps rethink our prefix
|
||
61 | 14ff3ad3 | Bob Lantz | # mechanism and/or the way we specify IP addresses
|
62 | if '/' in ipstr: |
||
63 | self.ip, self.prefixLen = ipstr.split( '/' ) |
||
64 | return self.ifconfig( ipstr, 'up' ) |
||
65 | else:
|
||
66 | self.ip, self.prefixLen = ipstr, prefixLen |
||
67 | return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) ) |
||
68 | a6bcad8f | Bob Lantz | |
69 | def setMAC( self, macstr ): |
||
70 | """Set the MAC address for an interface.
|
||
71 | macstr: MAC address as string"""
|
||
72 | self.mac = macstr
|
||
73 | 14ff3ad3 | Bob Lantz | return ( self.ifconfig( 'down' ) + |
74 | a6bcad8f | Bob Lantz | self.ifconfig( 'hw', 'ether', macstr ) + |
75 | self.ifconfig( 'up' ) ) |
||
76 | |||
77 | _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
|
||
78 | _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
|
||
79 | |||
80 | def updateIP( self ): |
||
81 | "Return updated IP address based on ifconfig"
|
||
82 | ifconfig = self.ifconfig()
|
||
83 | ips = self._ipMatchRegex.findall( ifconfig )
|
||
84 | self.ip = ips[ 0 ] if ips else None |
||
85 | return self.ip |
||
86 | |||
87 | 14ff3ad3 | Bob Lantz | def updateMAC( self ): |
88 | a6bcad8f | Bob Lantz | "Return updated MAC address based on ifconfig"
|
89 | ifconfig = self.ifconfig()
|
||
90 | macs = self._macMatchRegex.findall( ifconfig )
|
||
91 | self.mac = macs[ 0 ] if macs else None |
||
92 | return self.mac |
||
93 | 14ff3ad3 | Bob Lantz | |
94 | a6bcad8f | Bob Lantz | def IP( self ): |
95 | "Return IP address"
|
||
96 | return self.ip |
||
97 | |||
98 | def MAC( self ): |
||
99 | "Return MAC address"
|
||
100 | return self.mac |
||
101 | |||
102 | 14ff3ad3 | Bob Lantz | def isUp( self, setUp=False ): |
103 | a6bcad8f | Bob Lantz | "Return whether interface is up"
|
104 | 14ff3ad3 | Bob Lantz | if setUp:
|
105 | a49c85a6 | Bob Lantz | self.ifconfig( 'up' ) |
106 | a6bcad8f | Bob Lantz | return "UP" in self.ifconfig() |
107 | |||
108 | 8856d284 | Bob Lantz | def rename( self, newname ): |
109 | "Rename interface"
|
||
110 | self.ifconfig( 'down' ) |
||
111 | result = self.cmd( 'ip link set', self.name, 'name', newname ) |
||
112 | self.name = newname
|
||
113 | self.ifconfig( 'up' ) |
||
114 | return result
|
||
115 | |||
116 | 84a91a14 | Bob Lantz | # The reason why we configure things in this way is so
|
117 | # That the parameters can be listed and documented in
|
||
118 | # the config method.
|
||
119 | # Dealing with subclasses and superclasses is slightly
|
||
120 | # annoying, but at least the information is there!
|
||
121 | a6bcad8f | Bob Lantz | |
122 | edf46e95 | Bob Lantz | def setParam( self, results, method, **param ): |
123 | """Internal method: configure a *single* parameter
|
||
124 | results: dict of results to update
|
||
125 | method: config method name
|
||
126 | param: arg=value (ignore if value=None)
|
||
127 | value may also be list or dict"""
|
||
128 | 84a91a14 | Bob Lantz | name, value = param.items()[ 0 ]
|
129 | edf46e95 | Bob Lantz | f = getattr( self, method, None ) |
130 | if not f or value is None: |
||
131 | 84a91a14 | Bob Lantz | return
|
132 | if type( value ) is list: |
||
133 | edf46e95 | Bob Lantz | result = f( *value ) |
134 | 84a91a14 | Bob Lantz | elif type( value ) is dict: |
135 | edf46e95 | Bob Lantz | result = f( **value ) |
136 | 84a91a14 | Bob Lantz | else:
|
137 | edf46e95 | Bob Lantz | result = f( value ) |
138 | results[ name ] = result |
||
139 | return result
|
||
140 | 84a91a14 | Bob Lantz | |
141 | 14ff3ad3 | Bob Lantz | def config( self, mac=None, ip=None, ifconfig=None, |
142 | up=True, **_params ):
|
||
143 | 84a91a14 | Bob Lantz | """Configure Node according to (optional) parameters:
|
144 | mac: MAC address
|
||
145 | ip: IP address
|
||
146 | ifconfig: arbitrary interface configuration
|
||
147 | Subclasses should override this method and call
|
||
148 | the parent class's config(**params)"""
|
||
149 | # If we were overriding this method, we would call
|
||
150 | # the superclass config method here as follows:
|
||
151 | # r = Parent.config( **params )
|
||
152 | r = {} |
||
153 | self.setParam( r, 'setMAC', mac=mac ) |
||
154 | self.setParam( r, 'setIP', ip=ip ) |
||
155 | a49c85a6 | Bob Lantz | self.setParam( r, 'isUp', up=up ) |
156 | 84a91a14 | Bob Lantz | self.setParam( r, 'ifconfig', ifconfig=ifconfig ) |
157 | 8856d284 | Bob Lantz | self.updateIP()
|
158 | self.updateMAC()
|
||
159 | 84a91a14 | Bob Lantz | return r
|
160 | a6bcad8f | Bob Lantz | |
161 | def delete( self ): |
||
162 | "Delete interface"
|
||
163 | self.cmd( 'ip link del ' + self.name ) |
||
164 | 10be691b | Bob Lantz | if self.node.inNamespace: |
165 | # Link may have been dumped into root NS
|
||
166 | quietRun( 'ip link del ' + self.name ) |
||
167 | a6bcad8f | Bob Lantz | |
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 | cec44763 | Angad Singh | latency_ms=None, enable_ecn=False, enable_red=False ): |
182 | 14ff3ad3 | Bob Lantz | "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 | e5653fb6 | Bob Lantz | # 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 | d27a3c52 | Bob Lantz | if use_hfsc:
|
199 | e09254ee | Andrew Ferguson | cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1',
|
200 | '%s class add dev %s parent 5:0 classid 5:1 hfsc sc '
|
||
201 | 14ff3ad3 | Bob Lantz | + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
|
202 | d27a3c52 | Bob Lantz | elif use_tbf:
|
203 | cec44763 | Angad Singh | if latency_ms is None: |
204 | latency_ms = 15 * 8 / bw |
||
205 | e09254ee | Andrew Ferguson | cmds += [ '%s qdisc add dev %s root handle 5: tbf ' +
|
206 | edf60032 | Brandon Heller | 'rate %fMbit burst 15000 latency %fms' %
|
207 | ( bw, latency_ms ) ] |
||
208 | d27a3c52 | Bob Lantz | else:
|
209 | e09254ee | Andrew Ferguson | cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1',
|
210 | '%s class add dev %s parent 5:0 classid 5:1 htb ' +
|
||
211 | edf60032 | Brandon Heller | 'rate %fMbit burst 15k' % bw ]
|
212 | e09254ee | Andrew Ferguson | parent = ' parent 5:1 '
|
213 | d27a3c52 | Bob Lantz | |
214 | # ECN or RED
|
||
215 | if enable_ecn:
|
||
216 | ae2ede79 | Nikhil Handigol | cmds += [ '%s qdisc add dev %s' + parent +
|
217 | e09254ee | Andrew Ferguson | 'handle 6: red limit 1000000 ' +
|
218 | 6bb5e123 | Nikhil Handigol | 'min 30000 max 35000 avpkt 1500 ' +
|
219 | 14ff3ad3 | Bob Lantz | 'burst 20 ' +
|
220 | d27a3c52 | Bob Lantz | 'bandwidth %fmbit probability 1 ecn' % bw ]
|
221 | e09254ee | Andrew Ferguson | parent = ' parent 6: '
|
222 | d27a3c52 | Bob Lantz | elif enable_red:
|
223 | ae2ede79 | Nikhil Handigol | cmds += [ '%s qdisc add dev %s' + parent +
|
224 | e09254ee | Andrew Ferguson | 'handle 6: red limit 1000000 ' +
|
225 | 107785dd | Nikhil Handigol | 'min 30000 max 35000 avpkt 1500 ' +
|
226 | 14ff3ad3 | Bob Lantz | 'burst 20 ' +
|
227 | d27a3c52 | Bob Lantz | 'bandwidth %fmbit probability 1' % bw ]
|
228 | e09254ee | Andrew Ferguson | parent = ' parent 6: '
|
229 | 14ff3ad3 | Bob Lantz | return cmds, parent
|
230 | |||
231 | @staticmethod
|
||
232 | cec44763 | Angad Singh | def delayCmds( parent, delay=None, jitter=None, |
233 | loss=None, max_queue_size=None ): |
||
234 | 14ff3ad3 | Bob Lantz | "Internal method: return tc commands for delay and loss"
|
235 | cmds = [] |
||
236 | if delay and delay < 0: |
||
237 | error( 'Negative delay', delay, '\n' ) |
||
238 | cec44763 | Angad Singh | elif jitter and jitter < 0: |
239 | error( 'Negative jitter', jitter, '\n' ) |
||
240 | 14ff3ad3 | Bob Lantz | elif loss and ( loss < 0 or loss > 100 ): |
241 | error( 'Bad loss percentage', loss, '%%\n' ) |
||
242 | else:
|
||
243 | cec44763 | Angad Singh | # Delay/jitter/loss/max queue size
|
244 | netemargs = '%s%s%s%s' % (
|
||
245 | 14ff3ad3 | Bob Lantz | 'delay %s ' % delay if delay is not None else '', |
246 | cec44763 | Angad Singh | '%s ' % jitter if jitter is not None else '', |
247 | 14ff3ad3 | Bob Lantz | 'loss %d ' % loss if loss is not None else '', |
248 | 'limit %d' % max_queue_size if max_queue_size is not None |
||
249 | 2e089b5e | Brandon Heller | else '' ) |
250 | 14ff3ad3 | Bob Lantz | if netemargs:
|
251 | d776bd3a | Bob Lantz | cmds = [ '%s qdisc add dev %s ' + parent +
|
252 | ' handle 10: netem ' +
|
||
253 | 2e089b5e | Brandon Heller | netemargs ] |
254 | e09254ee | Andrew Ferguson | parent = ' parent 10:1 '
|
255 | return cmds, parent
|
||
256 | 14ff3ad3 | Bob Lantz | |
257 | def tc( self, cmd, tc='tc' ): |
||
258 | "Execute tc command for our interface"
|
||
259 | c = cmd % (tc, self) # Add in tc command and our name |
||
260 | debug(" *** executing command: %s\n" % c)
|
||
261 | return self.cmd( c ) |
||
262 | |||
263 | cec44763 | Angad Singh | def config( self, bw=None, delay=None, jitter=None, loss=None, |
264 | disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False, |
||
265 | latency_ms=None, enable_ecn=False, enable_red=False, |
||
266 | max_queue_size=None, **params ):
|
||
267 | 14ff3ad3 | Bob Lantz | "Configure the port and set its properties."
|
268 | |||
269 | result = Intf.config( self, **params)
|
||
270 | |||
271 | # Disable GRO
|
||
272 | if disable_gro:
|
||
273 | self.cmd( 'ethtool -K %s gro off' % self ) |
||
274 | |||
275 | # Optimization: return if nothing else to configure
|
||
276 | # Question: what happens if we want to reset things?
|
||
277 | if ( bw is None and not delay and not loss |
||
278 | and max_queue_size is None ): |
||
279 | return
|
||
280 | |||
281 | # Clear existing configuration
|
||
282 | cmds = [ '%s qdisc del dev %s root' ]
|
||
283 | |||
284 | # Bandwidth limits via various methods
|
||
285 | bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
|
||
286 | edf60032 | Brandon Heller | use_hfsc=use_hfsc, use_tbf=use_tbf, |
287 | latency_ms=latency_ms, |
||
288 | enable_ecn=enable_ecn, |
||
289 | enable_red=enable_red ) |
||
290 | 14ff3ad3 | Bob Lantz | cmds += bwcmds |
291 | |||
292 | cec44763 | Angad Singh | # Delay/jitter/loss/max_queue_size using netem
|
293 | 2485d57f | Bob Lantz | delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
|
294 | loss=loss, max_queue_size=max_queue_size, |
||
295 | 2e089b5e | Brandon Heller | parent=parent ) |
296 | e09254ee | Andrew Ferguson | cmds += delaycmds |
297 | 14ff3ad3 | Bob Lantz | |
298 | # Ugly but functional: display configuration info
|
||
299 | stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) + |
||
300 | ( [ '%s delay' % delay ] if delay is not None else [] ) + |
||
301 | cec44763 | Angad Singh | ( [ '%s jitter' % jitter ] if jitter is not None else [] ) + |
302 | 14ff3ad3 | Bob Lantz | ( ['%d%% loss' % loss ] if loss is not None else [] ) + |
303 | 1052f8a0 | Brandon Heller | ( [ 'ECN' ] if enable_ecn else [ 'RED' ] |
304 | 14ff3ad3 | Bob Lantz | if enable_red else [] ) ) |
305 | info( '(' + ' '.join( stuff ) + ') ' ) |
||
306 | |||
307 | # Execute all the commands in our node
|
||
308 | a6bcad8f | Bob Lantz | debug("at map stage w/cmds: %s\n" % cmds)
|
309 | 14ff3ad3 | Bob Lantz | tcoutputs = [ self.tc(cmd) for cmd in cmds ] |
310 | 84a91a14 | Bob Lantz | debug( "cmds:", cmds, '\n' ) |
311 | debug( "outputs:", tcoutputs, '\n' ) |
||
312 | result[ 'tcoutputs'] = tcoutputs
|
||
313 | e09254ee | Andrew Ferguson | result[ 'parent' ] = parent
|
314 | 14ff3ad3 | Bob Lantz | |
315 | 84a91a14 | Bob Lantz | return result
|
316 | a6bcad8f | Bob Lantz | |
317 | |||
318 | class Link( object ): |
||
319 | 14ff3ad3 | Bob Lantz | |
320 | a6bcad8f | Bob Lantz | """A basic link is just a veth pair.
|
321 | Other types of links could be tunnels, link emulators, etc.."""
|
||
322 | |||
323 | 14ff3ad3 | Bob Lantz | def __init__( self, node1, node2, port1=None, port2=None, |
324 | intfName1=None, intfName2=None, |
||
325 | intf=Intf, cls1=None, cls2=None, params1=None, |
||
326 | params2=None ):
|
||
327 | a6bcad8f | Bob Lantz | """Create veth link to another node, making two new interfaces.
|
328 | node1: first node
|
||
329 | node2: second node
|
||
330 | port1: node1 port number (optional)
|
||
331 | port2: node2 port number (optional)
|
||
332 | 84a91a14 | Bob Lantz | intf: default interface class/constructor
|
333 | cls1, cls2: optional interface-specific constructors
|
||
334 | a6bcad8f | Bob Lantz | intfName1: node1 interface name (optional)
|
335 | 84a91a14 | Bob Lantz | intfName2: node2 interface name (optional)
|
336 | params1: parameters for interface 1
|
||
337 | params2: parameters for interface 2"""
|
||
338 | a6bcad8f | Bob Lantz | # This is a bit awkward; it seems that having everything in
|
339 | # params would be more orthogonal, but being able to specify
|
||
340 | # in-line arguments is more convenient!
|
||
341 | if port1 is None: |
||
342 | port1 = node1.newPort() |
||
343 | if port2 is None: |
||
344 | port2 = node2.newPort() |
||
345 | if not intfName1: |
||
346 | intfName1 = self.intfName( node1, port1 )
|
||
347 | if not intfName2: |
||
348 | intfName2 = self.intfName( node2, port2 )
|
||
349 | 14ff3ad3 | Bob Lantz | |
350 | a6bcad8f | Bob Lantz | self.makeIntfPair( intfName1, intfName2 )
|
351 | 14ff3ad3 | Bob Lantz | |
352 | 84a91a14 | Bob Lantz | if not cls1: |
353 | cls1 = intf |
||
354 | if not cls2: |
||
355 | cls2 = intf |
||
356 | 14ff3ad3 | Bob Lantz | if not params1: |
357 | params1 = {} |
||
358 | if not params2: |
||
359 | params2 = {} |
||
360 | |||
361 | d7e5dfc5 | Bob Lantz | intf1 = cls1( name=intfName1, node=node1, port=port1, |
362 | link=self, **params1 )
|
||
363 | intf2 = cls2( name=intfName2, node=node2, port=port2, |
||
364 | link=self, **params2 )
|
||
365 | 14ff3ad3 | Bob Lantz | |
366 | 84a91a14 | Bob Lantz | # All we are is dust in the wind, and our two interfaces
|
367 | a6bcad8f | Bob Lantz | self.intf1, self.intf2 = intf1, intf2 |
368 | |||
369 | @classmethod
|
||
370 | def intfName( cls, node, n ): |
||
371 | "Construct a canonical interface name node-ethN for interface n."
|
||
372 | return node.name + '-eth' + repr( n ) |
||
373 | |||
374 | @classmethod
|
||
375 | def makeIntfPair( cls, intf1, intf2 ): |
||
376 | """Create pair of interfaces
|
||
377 | intf1: name of interface 1
|
||
378 | intf2: name of interface 2
|
||
379 | 14ff3ad3 | Bob Lantz | (override this class method [and possibly delete()]
|
380 | to change link type)"""
|
||
381 | a6bcad8f | Bob Lantz | makeIntfPair( intf1, intf2 ) |
382 | |||
383 | def delete( self ): |
||
384 | "Delete this link"
|
||
385 | self.intf1.delete()
|
||
386 | self.intf2.delete()
|
||
387 | |||
388 | def __str__( self ): |
||
389 | return '%s<->%s' % ( self.intf1, self.intf2 ) |
||
390 | ff568819 | Bob Lantz | |
391 | class TCLink( Link ): |
||
392 | "Link with symmetric TC interfaces configured via opts"
|
||
393 | def __init__( self, node1, node2, port1=None, port2=None, |
||
394 | intfName1=None, intfName2=None, **params ): |
||
395 | 2ec866d2 | Bob Lantz | Link.__init__( self, node1, node2, port1=port1, port2=port2,
|
396 | intfName1=intfName1, intfName2=intfName2, |
||
397 | ff568819 | Bob Lantz | cls1=TCIntf, |
398 | cls2=TCIntf, |
||
399 | params1=params, |
||
400 | params2=params) |