Statistics
| Branch: | Tag: | Revision:

mininet / mininet / link.py @ c273f490

History | View | Annotate | Download (17.2 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 c265deed Bob Lantz
    def __init__( self, name, node=None, port=None, link=None,
36
                  mac=None, srcNode=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 eba13f0c cody burkard
        self.mac = mac
45
        self.ip, self.prefixLen = None, None
46 42cdda38 cody burkard
        
47
        # if interface is lo, we know the ip is 127.0.0.1.
48
        # This saves an ifconfig command per node
49 eba13f0c cody burkard
        if self.name == 'lo':
50
            self.ip = '127.0.0.1'
51 84a91a14 Bob Lantz
        # Add to node (and move ourselves if necessary )
52 d7e5dfc5 Bob Lantz
        node.addIntf( self, port=port )
53 0b7c277e Bob Lantz
        # Save params for future reference
54
        self.params = params
55
        self.config( **params )
56 a6bcad8f Bob Lantz
57
    def cmd( self, *args, **kwargs ):
58 14ff3ad3 Bob Lantz
        "Run a command in our owning node"
59 84a91a14 Bob Lantz
        return self.node.cmd( *args, **kwargs )
60 a6bcad8f Bob Lantz
61
    def ifconfig( self, *args ):
62
        "Configure ourselves using ifconfig"
63
        return self.cmd( 'ifconfig', self.name, *args )
64
65 14ff3ad3 Bob Lantz
    def setIP( self, ipstr, prefixLen=None ):
66 a6bcad8f Bob Lantz
        """Set our IP address"""
67
        # This is a sign that we should perhaps rethink our prefix
68 14ff3ad3 Bob Lantz
        # mechanism and/or the way we specify IP addresses
69
        if '/' in ipstr:
70
            self.ip, self.prefixLen = ipstr.split( '/' )
71
            return self.ifconfig( ipstr, 'up' )
72
        else:
73 b7898bef backb1
            if prefixLen is None:
74
                raise Exception( 'No prefix length set for IP address %s' % ( ipstr, ) )
75 14ff3ad3 Bob Lantz
            self.ip, self.prefixLen = ipstr, prefixLen
76
            return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
77 a6bcad8f Bob Lantz
78
    def setMAC( self, macstr ):
79
        """Set the MAC address for an interface.
80
           macstr: MAC address as string"""
81
        self.mac = macstr
82 14ff3ad3 Bob Lantz
        return ( self.ifconfig( 'down' ) +
83 a6bcad8f Bob Lantz
                 self.ifconfig( 'hw', 'ether', macstr ) +
84
                 self.ifconfig( 'up' ) )
85
86
    _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
87
    _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
88
89
    def updateIP( self ):
90
        "Return updated IP address based on ifconfig"
91 f1123e71 cody burkard
        # use pexec instead of node.cmd so that we dont read
92
        # backgrounded output from the cli.
93
        ifconfig, _err, _exitCode = self.node.pexec( 'ifconfig %s' % self.name )
94 a6bcad8f Bob Lantz
        ips = self._ipMatchRegex.findall( ifconfig )
95
        self.ip = ips[ 0 ] if ips else None
96
        return self.ip
97
98 14ff3ad3 Bob Lantz
    def updateMAC( self ):
99 a6bcad8f Bob Lantz
        "Return updated MAC address based on ifconfig"
100
        ifconfig = self.ifconfig()
101
        macs = self._macMatchRegex.findall( ifconfig )
102
        self.mac = macs[ 0 ] if macs else None
103
        return self.mac
104 14ff3ad3 Bob Lantz
105 42cdda38 cody burkard
    # Instead of updating ip and mac separately,
106
    # use one ifconfig call to do it simultaneously.
107
    # This saves an ifconfig command, which improves performance.
108
109 4b65110e cody burkard
    def updateAddr( self ):
110 42cdda38 cody burkard
        "Return IP address and MAC address based on ifconfig."
111 4b65110e cody burkard
        ifconfig = self.ifconfig()
112
        ips = self._ipMatchRegex.findall( ifconfig )
113
        macs = self._macMatchRegex.findall( ifconfig )
114
        self.ip = ips[ 0 ] if ips else None
115
        self.mac = macs[ 0 ] if macs else None
116
        return self.ip, self.mac
117
118 a6bcad8f Bob Lantz
    def IP( self ):
119
        "Return IP address"
120
        return self.ip
121
122
    def MAC( self ):
123
        "Return MAC address"
124
        return self.mac
125
126 14ff3ad3 Bob Lantz
    def isUp( self, setUp=False ):
127 a6bcad8f Bob Lantz
        "Return whether interface is up"
128 14ff3ad3 Bob Lantz
        if setUp:
129 4b65110e cody burkard
            cmdOutput = self.ifconfig( 'up' )
130 41a54f05 cody burkard
            # no output indicates success
131 4b65110e cody burkard
            if cmdOutput:
132 a3d51b77 cody burkard
                error( "Error setting %s up: %s " % ( self.name, cmdOutput ) )
133 c1934706 cody burkard
                return False
134
            else:
135
                return True
136
        else:
137
            return "UP" in self.ifconfig()
138 a6bcad8f Bob Lantz
139 8856d284 Bob Lantz
    def rename( self, newname ):
140
        "Rename interface"
141
        self.ifconfig( 'down' )
142
        result = self.cmd( 'ip link set', self.name, 'name', newname )
143
        self.name = newname
144
        self.ifconfig( 'up' )
145
        return result
146
147 84a91a14 Bob Lantz
    # The reason why we configure things in this way is so
148
    # That the parameters can be listed and documented in
149
    # the config method.
150
    # Dealing with subclasses and superclasses is slightly
151
    # annoying, but at least the information is there!
152 a6bcad8f Bob Lantz
153 edf46e95 Bob Lantz
    def setParam( self, results, method, **param ):
154
        """Internal method: configure a *single* parameter
155
           results: dict of results to update
156
           method: config method name
157
           param: arg=value (ignore if value=None)
158
           value may also be list or dict"""
159 84a91a14 Bob Lantz
        name, value = param.items()[ 0 ]
160 edf46e95 Bob Lantz
        f = getattr( self, method, None )
161
        if not f or value is None:
162 84a91a14 Bob Lantz
            return
163 c273f490 Bob Lantz
        if isinstance( value, list ):
164 edf46e95 Bob Lantz
            result = f( *value )
165 c273f490 Bob Lantz
        elif isinstance( value, dict ):
166 edf46e95 Bob Lantz
            result = f( **value )
167 84a91a14 Bob Lantz
        else:
168 edf46e95 Bob Lantz
            result = f( value )
169
        results[ name ] = result
170
        return result
171 84a91a14 Bob Lantz
172 14ff3ad3 Bob Lantz
    def config( self, mac=None, ip=None, ifconfig=None,
173
                up=True, **_params ):
174 84a91a14 Bob Lantz
        """Configure Node according to (optional) parameters:
175
           mac: MAC address
176
           ip: IP address
177
           ifconfig: arbitrary interface configuration
178
           Subclasses should override this method and call
179
           the parent class's config(**params)"""
180
        # If we were overriding this method, we would call
181
        # the superclass config method here as follows:
182
        # r = Parent.config( **params )
183
        r = {}
184
        self.setParam( r, 'setMAC', mac=mac )
185
        self.setParam( r, 'setIP', ip=ip )
186 a49c85a6 Bob Lantz
        self.setParam( r, 'isUp', up=up )
187 84a91a14 Bob Lantz
        self.setParam( r, 'ifconfig', ifconfig=ifconfig )
188
        return r
189 a6bcad8f Bob Lantz
190
    def delete( self ):
191
        "Delete interface"
192
        self.cmd( 'ip link del ' + self.name )
193 10be691b Bob Lantz
        if self.node.inNamespace:
194
            # Link may have been dumped into root NS
195
            quietRun( 'ip link del ' + self.name )
196 a6bcad8f Bob Lantz
197 c265deed Bob Lantz
    def status( self ):
198
        "Return intf status as a string"
199
        links, err_, result_ = self.node.pexec( 'ip link show' )
200
        if self.name in links:
201
            return "OK"
202
        else:
203
            return "MISSING"
204
205 d7e5dfc5 Bob Lantz
    def __repr__( self ):
206
        return '<%s %s>' % ( self.__class__.__name__, self.name )
207
208 a6bcad8f Bob Lantz
    def __str__( self ):
209
        return self.name
210
211
212 a908fafa Bob Lantz
class TCIntf( Intf ):
213 14ff3ad3 Bob Lantz
    """Interface customized by tc (traffic control) utility
214
       Allows specification of bandwidth limits (various methods)
215
       as well as delay, loss and max queue length"""
216 a6bcad8f Bob Lantz
217 14ff3ad3 Bob Lantz
    def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
218 cec44763 Angad Singh
                latency_ms=None, enable_ecn=False, enable_red=False ):
219 14ff3ad3 Bob Lantz
        "Return tc commands to set bandwidth"
220 a6bcad8f Bob Lantz
221 14ff3ad3 Bob Lantz
        cmds, parent = [], ' root '
222 a6bcad8f Bob Lantz
223
        if bw and ( bw < 0 or bw > 1000 ):
224
            error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
225
226 14ff3ad3 Bob Lantz
        elif bw is not None:
227
            # BL: this seems a bit brittle...
228
            if ( speedup > 0 and
229 8139695d Bob Lantz
                 self.node.name[0:1] == 's' ):
230 d27a3c52 Bob Lantz
                bw = speedup
231 e5653fb6 Bob Lantz
            # This may not be correct - we should look more closely
232
            # at the semantics of burst (and cburst) to make sure we
233
            # are specifying the correct sizes. For now I have used
234
            # the same settings we had in the mininet-hifi code.
235 d27a3c52 Bob Lantz
            if use_hfsc:
236 e09254ee Andrew Ferguson
                cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1',
237
                          '%s class add dev %s parent 5:0 classid 5:1 hfsc sc '
238 14ff3ad3 Bob Lantz
                          + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
239 d27a3c52 Bob Lantz
            elif use_tbf:
240 cec44763 Angad Singh
                if latency_ms is None:
241
                    latency_ms = 15 * 8 / bw
242 e09254ee Andrew Ferguson
                cmds += [ '%s qdisc add dev %s root handle 5: tbf ' +
243 edf60032 Brandon Heller
                          'rate %fMbit burst 15000 latency %fms' %
244
                          ( bw, latency_ms ) ]
245 d27a3c52 Bob Lantz
            else:
246 e09254ee Andrew Ferguson
                cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1',
247
                          '%s class add dev %s parent 5:0 classid 5:1 htb ' +
248 edf60032 Brandon Heller
                          'rate %fMbit burst 15k' % bw ]
249 e09254ee Andrew Ferguson
            parent = ' parent 5:1 '
250 d27a3c52 Bob Lantz
251
            # ECN or RED
252
            if enable_ecn:
253 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
254 e09254ee Andrew Ferguson
                          'handle 6: red limit 1000000 ' +
255 6bb5e123 Nikhil Handigol
                          'min 30000 max 35000 avpkt 1500 ' +
256 14ff3ad3 Bob Lantz
                          'burst 20 ' +
257 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1 ecn' % bw ]
258 e09254ee Andrew Ferguson
                parent = ' parent 6: '
259 d27a3c52 Bob Lantz
            elif enable_red:
260 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
261 e09254ee Andrew Ferguson
                          'handle 6: red limit 1000000 ' +
262 107785dd Nikhil Handigol
                          'min 30000 max 35000 avpkt 1500 ' +
263 14ff3ad3 Bob Lantz
                          'burst 20 ' +
264 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1' % bw ]
265 e09254ee Andrew Ferguson
                parent = ' parent 6: '
266 14ff3ad3 Bob Lantz
        return cmds, parent
267
268
    @staticmethod
269 cec44763 Angad Singh
    def delayCmds( parent, delay=None, jitter=None,
270
                   loss=None, max_queue_size=None ):
271 14ff3ad3 Bob Lantz
        "Internal method: return tc commands for delay and loss"
272
        cmds = []
273
        if delay and delay < 0:
274
            error( 'Negative delay', delay, '\n' )
275 cec44763 Angad Singh
        elif jitter and jitter < 0:
276
            error( 'Negative jitter', jitter, '\n' )
277 14ff3ad3 Bob Lantz
        elif loss and ( loss < 0 or loss > 100 ):
278
            error( 'Bad loss percentage', loss, '%%\n' )
279
        else:
280 cec44763 Angad Singh
            # Delay/jitter/loss/max queue size
281
            netemargs = '%s%s%s%s' % (
282 14ff3ad3 Bob Lantz
                'delay %s ' % delay if delay is not None else '',
283 cec44763 Angad Singh
                '%s ' % jitter if jitter is not None else '',
284 14ff3ad3 Bob Lantz
                'loss %d ' % loss if loss is not None else '',
285
                'limit %d' % max_queue_size if max_queue_size is not None
286 2e089b5e Brandon Heller
                else '' )
287 14ff3ad3 Bob Lantz
            if netemargs:
288 d776bd3a Bob Lantz
                cmds = [ '%s qdisc add dev %s ' + parent +
289
                         ' handle 10: netem ' +
290 2e089b5e Brandon Heller
                         netemargs ]
291 e09254ee Andrew Ferguson
                parent = ' parent 10:1 '
292
        return cmds, parent
293 14ff3ad3 Bob Lantz
294
    def tc( self, cmd, tc='tc' ):
295
        "Execute tc command for our interface"
296
        c = cmd % (tc, self)  # Add in tc command and our name
297
        debug(" *** executing command: %s\n" % c)
298
        return self.cmd( c )
299
300 cec44763 Angad Singh
    def config( self, bw=None, delay=None, jitter=None, loss=None,
301
                disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False,
302
                latency_ms=None, enable_ecn=False, enable_red=False,
303
                max_queue_size=None, **params ):
304 14ff3ad3 Bob Lantz
        "Configure the port and set its properties."
305
306
        result = Intf.config( self, **params)
307
308
        # Disable GRO
309
        if disable_gro:
310
            self.cmd( 'ethtool -K %s gro off' % self )
311
312
        # Optimization: return if nothing else to configure
313
        # Question: what happens if we want to reset things?
314
        if ( bw is None and not delay and not loss
315
             and max_queue_size is None ):
316
            return
317
318
        # Clear existing configuration
319 4550fff1 Darshan Thaker
        tcoutput = self.tc( '%s qdisc show dev %s' )
320
        if "priomap" not in tcoutput:
321
            cmds = [ '%s qdisc del dev %s root' ]
322
        else:
323
            cmds = []
324 14ff3ad3 Bob Lantz
325
        # Bandwidth limits via various methods
326
        bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
327 edf60032 Brandon Heller
                                      use_hfsc=use_hfsc, use_tbf=use_tbf,
328
                                      latency_ms=latency_ms,
329
                                      enable_ecn=enable_ecn,
330
                                      enable_red=enable_red )
331 14ff3ad3 Bob Lantz
        cmds += bwcmds
332
333 cec44763 Angad Singh
        # Delay/jitter/loss/max_queue_size using netem
334 2485d57f Bob Lantz
        delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
335
                                loss=loss, max_queue_size=max_queue_size,
336 2e089b5e Brandon Heller
                                parent=parent )
337 e09254ee Andrew Ferguson
        cmds += delaycmds
338 14ff3ad3 Bob Lantz
339
        # Ugly but functional: display configuration info
340
        stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
341
                  ( [ '%s delay' % delay ] if delay is not None else [] ) +
342 cec44763 Angad Singh
                  ( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
343 14ff3ad3 Bob Lantz
                  ( ['%d%% loss' % loss ] if loss is not None else [] ) +
344 1052f8a0 Brandon Heller
                  ( [ 'ECN' ] if enable_ecn else [ 'RED' ]
345 14ff3ad3 Bob Lantz
                    if enable_red else [] ) )
346
        info( '(' + ' '.join( stuff ) + ') ' )
347
348
        # Execute all the commands in our node
349 a6bcad8f Bob Lantz
        debug("at map stage w/cmds: %s\n" % cmds)
350 14ff3ad3 Bob Lantz
        tcoutputs = [ self.tc(cmd) for cmd in cmds ]
351 4550fff1 Darshan Thaker
        for output in tcoutputs:
352
            if output != '':
353
                error( "*** Error: %s" % output )
354 84a91a14 Bob Lantz
        debug( "cmds:", cmds, '\n' )
355
        debug( "outputs:", tcoutputs, '\n' )
356
        result[ 'tcoutputs'] = tcoutputs
357 e09254ee Andrew Ferguson
        result[ 'parent' ] = parent
358 14ff3ad3 Bob Lantz
359 84a91a14 Bob Lantz
        return result
360 a6bcad8f Bob Lantz
361
362
class Link( object ):
363 14ff3ad3 Bob Lantz
364 a6bcad8f Bob Lantz
    """A basic link is just a veth pair.
365
       Other types of links could be tunnels, link emulators, etc.."""
366
367 14ff3ad3 Bob Lantz
    def __init__( self, node1, node2, port1=None, port2=None,
368 eba13f0c cody burkard
                  intfName1=None, intfName2=None, addr1=None, addr2=None,
369 14ff3ad3 Bob Lantz
                  intf=Intf, cls1=None, cls2=None, params1=None,
370
                  params2=None ):
371 a6bcad8f Bob Lantz
        """Create veth link to another node, making two new interfaces.
372
           node1: first node
373
           node2: second node
374
           port1: node1 port number (optional)
375
           port2: node2 port number (optional)
376 84a91a14 Bob Lantz
           intf: default interface class/constructor
377
           cls1, cls2: optional interface-specific constructors
378 a6bcad8f Bob Lantz
           intfName1: node1 interface name (optional)
379 84a91a14 Bob Lantz
           intfName2: node2  interface name (optional)
380
           params1: parameters for interface 1
381
           params2: parameters for interface 2"""
382 a6bcad8f Bob Lantz
        # This is a bit awkward; it seems that having everything in
383 c265deed Bob Lantz
        # params is more orthogonal, but being able to specify
384
        # in-line arguments is more convenient! So we support both.
385
        if params1 is None:
386
            params1 = {}
387
        if params2 is None:
388
            params2 = {}
389
        # Allow passing in params1=params2
390
        if params2 is params1:
391
            params2 = dict( params1 )
392
        if port1 is not None:
393
            params1[ 'port' ] = port1
394
        if port2 is not None:
395
            params2[ 'port' ] = port2
396
        if 'port' not in params1:
397
            params1[ 'port' ] = node1.newPort()
398
        if 'port' not in params2:
399
            params2[ 'port' ] = node2.newPort()
400 a6bcad8f Bob Lantz
        if not intfName1:
401 c265deed Bob Lantz
            intfName1 = self.intfName( node1, params1[ 'port' ] )
402 a6bcad8f Bob Lantz
        if not intfName2:
403 c265deed Bob Lantz
            intfName2 = self.intfName( node2, params2[ 'port' ] )
404 14ff3ad3 Bob Lantz
405 eba13f0c cody burkard
        self.makeIntfPair( intfName1, intfName2, addr1, addr2 )
406 14ff3ad3 Bob Lantz
407 84a91a14 Bob Lantz
        if not cls1:
408
            cls1 = intf
409
        if not cls2:
410
            cls2 = intf
411 14ff3ad3 Bob Lantz
412 c265deed Bob Lantz
        intf1 = cls1( name=intfName1, node=node1,
413 eba13f0c cody burkard
                      link=self, mac=addr1, **params1  )
414 c265deed Bob Lantz
        intf2 = cls2( name=intfName2, node=node2,
415 eba13f0c cody burkard
                      link=self, mac=addr2, **params2 )
416 14ff3ad3 Bob Lantz
417 84a91a14 Bob Lantz
        # All we are is dust in the wind, and our two interfaces
418 a6bcad8f Bob Lantz
        self.intf1, self.intf2 = intf1, intf2
419
420 c265deed Bob Lantz
    def intfName( _self, node, n ):
421 a6bcad8f Bob Lantz
        "Construct a canonical interface name node-ethN for interface n."
422
        return node.name + '-eth' + repr( n )
423
424
    @classmethod
425 c265deed Bob Lantz
    def makeIntfPair( _cls, intfname1, intfname2, addr1=None, addr2=None ):
426 a6bcad8f Bob Lantz
        """Create pair of interfaces
427 c265deed Bob Lantz
           intfname1: name of interface 1
428
           intfname2: name of interface 2
429
           (override this method [and possibly delete()]
430 14ff3ad3 Bob Lantz
           to change link type)"""
431 c265deed Bob Lantz
        return makeIntfPair( intfname1, intfname2, addr1, addr2 )
432 a6bcad8f Bob Lantz
433
    def delete( self ):
434
        "Delete this link"
435
        self.intf1.delete()
436
        self.intf2.delete()
437
438 c265deed Bob Lantz
    def stop( self ):
439
        "Override to stop and clean up link as needed"
440
        pass
441
442
    def status( self ):
443
        "Return link status as a string"
444
        return "(%s %s)" % ( self.intf1.status(), self.intf2.status() )
445
446 a6bcad8f Bob Lantz
    def __str__( self ):
447
        return '%s<->%s' % ( self.intf1, self.intf2 )
448 ff568819 Bob Lantz
449
class TCLink( Link ):
450
    "Link with symmetric TC interfaces configured via opts"
451
    def __init__( self, node1, node2, port1=None, port2=None,
452 af4c9719 cody burkard
                  intfName1=None, intfName2=None,
453
                  addr1=None, addr2=None, **params ):
454 2ec866d2 Bob Lantz
        Link.__init__( self, node1, node2, port1=port1, port2=port2,
455
                       intfName1=intfName1, intfName2=intfName2,
456 ff568819 Bob Lantz
                       cls1=TCIntf,
457
                       cls2=TCIntf,
458 af4c9719 cody burkard
                       addr1=addr1, addr2=addr2,
459 ff568819 Bob Lantz
                       params1=params,
460 c265deed Bob Lantz
                       params2=params )