Statistics
| Branch: | Tag: | Revision:

mininet / mininet / link.py @ edf60032

History | View | Annotate | Download (14.6 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
29
from time import sleep
30
import re
31

    
32
class 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

    
175
class 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

    
315
class 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

    
388
class 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)