Statistics
| Branch: | Tag: | Revision:

mininet / mininet / link.py @ c1934706

History | View | Annotate | Download (15.4 KB)

1
"""
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
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
"""
26

    
27
from mininet.log import info, error, debug
28
from mininet.util import makeIntfPair, quietRun
29
import re
30

    
31
class Intf( object ):
32

    
33
    "Basic interface object that can configure itself."
34

    
35
    def __init__( self, name, node=None, port=None, link=None, **params ):
36
        """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
        self.node = node
41
        self.name = name
42
        self.link = link
43
        self.mac, self.ip, self.prefixLen = None, None, None
44
        # Add to node (and move ourselves if necessary )
45
        node.addIntf( self, port=port )
46
        # Save params for future reference
47
        self.params = params
48
        self.config( **params )
49

    
50
    def cmd( self, *args, **kwargs ):
51
        "Run a command in our owning node"
52
        return self.node.cmd( *args, **kwargs )
53

    
54
    def ifconfig( self, *args ):
55
        "Configure ourselves using ifconfig"
56
        return self.cmd( 'ifconfig', self.name, *args )
57

    
58
    def setIP( self, ipstr, prefixLen=None ):
59
        """Set our IP address"""
60
        # This is a sign that we should perhaps rethink our prefix
61
        # 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

    
69
    def setMAC( self, macstr ):
70
        """Set the MAC address for an interface.
71
           macstr: MAC address as string"""
72
        self.mac = macstr
73
        return ( self.ifconfig( 'down' ) +
74
                 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
    def updateMAC( self ):
88
        "Return updated MAC address based on ifconfig"
89
        #heres where we send the unecessary ifconfigs
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
            r = self.ifconfig( 'up' )
107
            if r:
108
                return False
109
            else:
110
                return True
111
        else:
112
            return "UP" in self.ifconfig()
113

    
114
    def rename( self, newname ):
115
        "Rename interface"
116
        self.ifconfig( 'down' )
117
        result = self.cmd( 'ip link set', self.name, 'name', newname )
118
        self.name = newname
119
        self.ifconfig( 'up' )
120
        return result
121

    
122
    # The reason why we configure things in this way is so
123
    # That the parameters can be listed and documented in
124
    # the config method.
125
    # Dealing with subclasses and superclasses is slightly
126
    # annoying, but at least the information is there!
127

    
128
    def setParam( self, results, method, **param ):
129
        """Internal method: configure a *single* parameter
130
           results: dict of results to update
131
           method: config method name
132
           param: arg=value (ignore if value=None)
133
           value may also be list or dict"""
134
        name, value = param.items()[ 0 ]
135
        f = getattr( self, method, None )
136
        if not f or value is None:
137
            return
138
        if type( value ) is list:
139
            result = f( *value )
140
        elif type( value ) is dict:
141
            result = f( **value )
142
        else:
143
            result = f( value )
144
        results[ name ] = result
145
        return result
146

    
147
    def updateAddr( self ):
148
        "instead of updating ip and mac separately, use one ifconfig call to do it simultaneously"
149
        ifconfig = self.ifconfig()
150
        print ifconfig
151
        ips = self._ipMatchRegex.findall( ifconfig )
152
        macs = self._macMatchRegex.findall( ifconfig )
153
        self.ip = ips[ 0 ] if ips else None
154
        self.mac = macs[ 0 ] if macs else None
155
        return self.ip, self.mac
156

    
157
    def config( self, mac=None, ip=None, ifconfig=None,
158
                up=True, **_params ):
159
        """Configure Node according to (optional) parameters:
160
           mac: MAC address
161
           ip: IP address
162
           ifconfig: arbitrary interface configuration
163
           Subclasses should override this method and call
164
           the parent class's config(**params)"""
165
        # If we were overriding this method, we would call
166
        # the superclass config method here as follows:
167
        # r = Parent.config( **params )
168
        r = {}
169
        self.setParam( r, 'setMAC', mac=mac )
170
        self.setParam( r, 'setIP', ip=ip )
171
        self.setParam( r, 'isUp', up=up )
172
        self.setParam( r, 'ifconfig', ifconfig=ifconfig )
173
        #should combine these next two operations into one. this is unecessary
174
        #self.updateAddr()
175
        #self.updateIP()
176
        #self.updateMAC()
177
        return r
178

    
179
    def delete( self ):
180
        "Delete interface"
181
        self.cmd( 'ip link del ' + self.name )
182
        if self.node.inNamespace:
183
            # Link may have been dumped into root NS
184
            quietRun( 'ip link del ' + self.name )
185

    
186
    def __repr__( self ):
187
        return '<%s %s>' % ( self.__class__.__name__, self.name )
188

    
189
    def __str__( self ):
190
        return self.name
191

    
192

    
193
class TCIntf( Intf ):
194
    """Interface customized by tc (traffic control) utility
195
       Allows specification of bandwidth limits (various methods)
196
       as well as delay, loss and max queue length"""
197

    
198
    def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
199
                latency_ms=None, enable_ecn=False, enable_red=False ):
200
        "Return tc commands to set bandwidth"
201

    
202
        cmds, parent = [], ' root '
203

    
204
        if bw and ( bw < 0 or bw > 1000 ):
205
            error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
206

    
207
        elif bw is not None:
208
            # BL: this seems a bit brittle...
209
            if ( speedup > 0 and
210
                 self.node.name[0:1] == 's' ):
211
                bw = speedup
212
            # This may not be correct - we should look more closely
213
            # at the semantics of burst (and cburst) to make sure we
214
            # are specifying the correct sizes. For now I have used
215
            # the same settings we had in the mininet-hifi code.
216
            if use_hfsc:
217
                cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1',
218
                          '%s class add dev %s parent 5:0 classid 5:1 hfsc sc '
219
                          + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
220
            elif use_tbf:
221
                if latency_ms is None:
222
                    latency_ms = 15 * 8 / bw
223
                cmds += [ '%s qdisc add dev %s root handle 5: tbf ' +
224
                          'rate %fMbit burst 15000 latency %fms' %
225
                          ( bw, latency_ms ) ]
226
            else:
227
                cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1',
228
                          '%s class add dev %s parent 5:0 classid 5:1 htb ' +
229
                          'rate %fMbit burst 15k' % bw ]
230
            parent = ' parent 5:1 '
231

    
232
            # ECN or RED
233
            if enable_ecn:
234
                cmds += [ '%s qdisc add dev %s' + parent +
235
                          'handle 6: red limit 1000000 ' +
236
                          'min 30000 max 35000 avpkt 1500 ' +
237
                          'burst 20 ' +
238
                          'bandwidth %fmbit probability 1 ecn' % bw ]
239
                parent = ' parent 6: '
240
            elif enable_red:
241
                cmds += [ '%s qdisc add dev %s' + parent +
242
                          'handle 6: red limit 1000000 ' +
243
                          'min 30000 max 35000 avpkt 1500 ' +
244
                          'burst 20 ' +
245
                          'bandwidth %fmbit probability 1' % bw ]
246
                parent = ' parent 6: '
247
        return cmds, parent
248

    
249
    @staticmethod
250
    def delayCmds( parent, delay=None, jitter=None,
251
                   loss=None, max_queue_size=None ):
252
        "Internal method: return tc commands for delay and loss"
253
        cmds = []
254
        if delay and delay < 0:
255
            error( 'Negative delay', delay, '\n' )
256
        elif jitter and jitter < 0:
257
            error( 'Negative jitter', jitter, '\n' )
258
        elif loss and ( loss < 0 or loss > 100 ):
259
            error( 'Bad loss percentage', loss, '%%\n' )
260
        else:
261
            # Delay/jitter/loss/max queue size
262
            netemargs = '%s%s%s%s' % (
263
                'delay %s ' % delay if delay is not None else '',
264
                '%s ' % jitter if jitter is not None else '',
265
                'loss %d ' % loss if loss is not None else '',
266
                'limit %d' % max_queue_size if max_queue_size is not None
267
                else '' )
268
            if netemargs:
269
                cmds = [ '%s qdisc add dev %s ' + parent +
270
                         ' handle 10: netem ' +
271
                         netemargs ]
272
                parent = ' parent 10:1 '
273
        return cmds, parent
274

    
275
    def tc( self, cmd, tc='tc' ):
276
        "Execute tc command for our interface"
277
        c = cmd % (tc, self)  # Add in tc command and our name
278
        debug(" *** executing command: %s\n" % c)
279
        return self.cmd( c )
280

    
281
    def config( self, bw=None, delay=None, jitter=None, loss=None,
282
                disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False,
283
                latency_ms=None, enable_ecn=False, enable_red=False,
284
                max_queue_size=None, **params ):
285
        "Configure the port and set its properties."
286

    
287
        result = Intf.config( self, **params)
288

    
289
        # Disable GRO
290
        if disable_gro:
291
            self.cmd( 'ethtool -K %s gro off' % self )
292

    
293
        # Optimization: return if nothing else to configure
294
        # Question: what happens if we want to reset things?
295
        if ( bw is None and not delay and not loss
296
             and max_queue_size is None ):
297
            return
298

    
299
        # Clear existing configuration
300
        cmds = [ '%s qdisc del dev %s root' ]
301

    
302
        # Bandwidth limits via various methods
303
        bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
304
                                      use_hfsc=use_hfsc, use_tbf=use_tbf,
305
                                      latency_ms=latency_ms,
306
                                      enable_ecn=enable_ecn,
307
                                      enable_red=enable_red )
308
        cmds += bwcmds
309

    
310
        # Delay/jitter/loss/max_queue_size using netem
311
        delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
312
                                loss=loss, max_queue_size=max_queue_size,
313
                                parent=parent )
314
        cmds += delaycmds
315

    
316
        # Ugly but functional: display configuration info
317
        stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
318
                  ( [ '%s delay' % delay ] if delay is not None else [] ) +
319
                  ( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
320
                  ( ['%d%% loss' % loss ] if loss is not None else [] ) +
321
                  ( [ 'ECN' ] if enable_ecn else [ 'RED' ]
322
                    if enable_red else [] ) )
323
        info( '(' + ' '.join( stuff ) + ') ' )
324

    
325
        # Execute all the commands in our node
326
        debug("at map stage w/cmds: %s\n" % cmds)
327
        tcoutputs = [ self.tc(cmd) for cmd in cmds ]
328
        debug( "cmds:", cmds, '\n' )
329
        debug( "outputs:", tcoutputs, '\n' )
330
        result[ 'tcoutputs'] = tcoutputs
331
        result[ 'parent' ] = parent
332

    
333
        return result
334

    
335

    
336
class Link( object ):
337

    
338
    """A basic link is just a veth pair.
339
       Other types of links could be tunnels, link emulators, etc.."""
340

    
341
    def __init__( self, node1, node2, port1=None, port2=None,
342
                  intfName1=None, intfName2=None,
343
                  intf=Intf, cls1=None, cls2=None, params1=None,
344
                  params2=None ):
345
        """Create veth link to another node, making two new interfaces.
346
           node1: first node
347
           node2: second node
348
           port1: node1 port number (optional)
349
           port2: node2 port number (optional)
350
           intf: default interface class/constructor
351
           cls1, cls2: optional interface-specific constructors
352
           intfName1: node1 interface name (optional)
353
           intfName2: node2  interface name (optional)
354
           params1: parameters for interface 1
355
           params2: parameters for interface 2"""
356
        # This is a bit awkward; it seems that having everything in
357
        # params would be more orthogonal, but being able to specify
358
        # in-line arguments is more convenient!
359
        if port1 is None:
360
            port1 = node1.newPort()
361
        if port2 is None:
362
            port2 = node2.newPort()
363
        if not intfName1:
364
            intfName1 = self.intfName( node1, port1 )
365
        if not intfName2:
366
            intfName2 = self.intfName( node2, port2 )
367

    
368
        self.makeIntfPair( intfName1, intfName2 )
369

    
370
        if not cls1:
371
            cls1 = intf
372
        if not cls2:
373
            cls2 = intf
374
        if not params1:
375
            params1 = {}
376
        if not params2:
377
            params2 = {}
378

    
379
        intf1 = cls1( name=intfName1, node=node1, port=port1,
380
                      link=self, **params1  )
381
        intf2 = cls2( name=intfName2, node=node2, port=port2,
382
                      link=self, **params2 )
383

    
384
        # All we are is dust in the wind, and our two interfaces
385
        self.intf1, self.intf2 = intf1, intf2
386

    
387
    @classmethod
388
    def intfName( cls, node, n ):
389
        "Construct a canonical interface name node-ethN for interface n."
390
        return node.name + '-eth' + repr( n )
391

    
392
    @classmethod
393
    def makeIntfPair( cls, intf1, intf2 ):
394
        """Create pair of interfaces
395
           intf1: name of interface 1
396
           intf2: name of interface 2
397
           (override this class method [and possibly delete()]
398
           to change link type)"""
399
        makeIntfPair( intf1, intf2  )
400

    
401
    def delete( self ):
402
        "Delete this link"
403
        self.intf1.delete()
404
        self.intf2.delete()
405

    
406
    def __str__( self ):
407
        return '%s<->%s' % ( self.intf1, self.intf2 )
408

    
409
class TCLink( Link ):
410
    "Link with symmetric TC interfaces configured via opts"
411
    def __init__( self, node1, node2, port1=None, port2=None,
412
                  intfName1=None, intfName2=None, **params ):
413
        Link.__init__( self, node1, node2, port1=port1, port2=port2,
414
                       intfName1=intfName1, intfName2=intfName2,
415
                       cls1=TCIntf,
416
                       cls2=TCIntf,
417
                       params1=params,
418
                       params2=params)