Statistics
| Branch: | Tag: | Revision:

mininet / mininet / link.py @ ae2ede79

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 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 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s root handle 1:0 hfsc default 1',
200 8139695d Bob Lantz
                          '%s class add dev %s parent 1:0 classid 1:1 hfsc sc '
201 14ff3ad3 Bob Lantz
                          + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
202 d27a3c52 Bob Lantz
            elif use_tbf:
203 e5653fb6 Bob Lantz
                latency_us = 10 * 1500 * 8 / bw
204 ae2ede79 Nikhil Handigol
                cmds += ['%s qdisc add dev %s root handle 1: tbf ' +
205 e5653fb6 Bob Lantz
                        'rate %fMbit burst 15000 latency %fus' %
206
                         ( bw, latency_us ) ]
207 d27a3c52 Bob Lantz
            else:
208 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s root handle 1:0 htb default 1',
209 d27a3c52 Bob Lantz
                         '%s class add dev %s parent 1:0 classid 1:1 htb ' +
210 e5653fb6 Bob Lantz
                         'rate %fMbit burst 15k' % bw ]
211 d27a3c52 Bob Lantz
            parent = ' parent 1:1 '
212
213
            # ECN or RED
214
            if enable_ecn:
215 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
216 14ff3ad3 Bob Lantz
                          'handle 10: red limit 1000000 ' +
217
                          'min 20000 max 25000 avpkt 1000 ' +
218
                          'burst 20 ' +
219 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1 ecn' % bw ]
220
                parent = ' parent 10: '
221
            elif enable_red:
222 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
223 14ff3ad3 Bob Lantz
                          'handle 10: red limit 1000000 ' +
224
                          'min 20000 max 25000 avpkt 1000 ' +
225
                          'burst 20 ' +
226 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1' % bw ]
227
                parent = ' parent 10: '
228 14ff3ad3 Bob Lantz
        return cmds, parent
229
230
    @staticmethod
231
    def delayCmds( parent, delay=None, loss=None,
232
                   max_queue_size=None ):
233
        "Internal method: return tc commands for delay and loss"
234
        cmds = []
235
        if delay and delay < 0:
236
            error( 'Negative delay', delay, '\n' )
237
        elif loss and ( loss < 0 or loss > 100 ):
238
            error( 'Bad loss percentage', loss, '%%\n' )
239
        else:
240
            # Delay/loss/max queue size
241
            netemargs = '%s%s%s' % (
242
                'delay %s ' % delay if delay is not None else '',
243
                'loss %d ' % loss if loss is not None else '',
244
                'limit %d' % max_queue_size if max_queue_size is not None
245
                 else '' )
246
            if netemargs:
247 d776bd3a Bob Lantz
                cmds = [ '%s qdisc add dev %s ' + parent +
248
                         ' handle 10: netem ' +
249 14ff3ad3 Bob Lantz
                          netemargs ]
250
        return cmds
251
252
    def tc( self, cmd, tc='tc' ):
253
        "Execute tc command for our interface"
254
        c = cmd % (tc, self)  # Add in tc command and our name
255
        debug(" *** executing command: %s\n" % c)
256
        return self.cmd( c )
257
258
    def config( self, bw=None, delay=None, loss=None, disable_gro=True,
259
                speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False,
260
                enable_red=False, max_queue_size=None, **params ):
261
        "Configure the port and set its properties."
262
263
        result = Intf.config( self, **params)
264
265
        # Disable GRO
266
        if disable_gro:
267
            self.cmd( 'ethtool -K %s gro off' % self )
268
269
        # Optimization: return if nothing else to configure
270
        # Question: what happens if we want to reset things?
271
        if ( bw is None and not delay and not loss
272
             and max_queue_size is None ):
273
            return
274
275
        # Clear existing configuration
276
        cmds = [ '%s qdisc del dev %s root' ]
277
278
        # Bandwidth limits via various methods
279
        bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
280
                                 use_hfsc=use_hfsc, use_tbf=use_tbf,
281
                                 enable_ecn=enable_ecn,
282
                                 enable_red=enable_red )
283
        cmds += bwcmds
284
285
        # Delay/loss/max_queue_size using netem
286
        cmds += self.delayCmds( delay=delay, loss=loss,
287
                                 max_queue_size=max_queue_size,
288
                                 parent=parent )
289
290
        # Ugly but functional: display configuration info
291
        stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
292
                  ( [ '%s delay' % delay ] if delay is not None else [] ) +
293
                  ( ['%d%% loss' % loss ] if loss is not None else [] ) +
294
                  ( [ 'ECN' ] if enable_ecn  else [ 'RED' ]
295
                    if enable_red else [] ) )
296
        info( '(' + ' '.join( stuff ) + ') ' )
297
298
        # Execute all the commands in our node
299 a6bcad8f Bob Lantz
        debug("at map stage w/cmds: %s\n" % cmds)
300 14ff3ad3 Bob Lantz
        tcoutputs = [ self.tc(cmd) for cmd in cmds ]
301 84a91a14 Bob Lantz
        debug( "cmds:", cmds, '\n' )
302
        debug( "outputs:", tcoutputs, '\n' )
303
        result[ 'tcoutputs'] = tcoutputs
304 14ff3ad3 Bob Lantz
305 84a91a14 Bob Lantz
        return result
306 a6bcad8f Bob Lantz
307
308
class Link( object ):
309 14ff3ad3 Bob Lantz
310 a6bcad8f Bob Lantz
    """A basic link is just a veth pair.
311
       Other types of links could be tunnels, link emulators, etc.."""
312
313 14ff3ad3 Bob Lantz
    def __init__( self, node1, node2, port1=None, port2=None,
314
                  intfName1=None, intfName2=None,
315
                  intf=Intf, cls1=None, cls2=None, params1=None,
316
                  params2=None ):
317 a6bcad8f Bob Lantz
        """Create veth link to another node, making two new interfaces.
318
           node1: first node
319
           node2: second node
320
           port1: node1 port number (optional)
321
           port2: node2 port number (optional)
322 84a91a14 Bob Lantz
           intf: default interface class/constructor
323
           cls1, cls2: optional interface-specific constructors
324 a6bcad8f Bob Lantz
           intfName1: node1 interface name (optional)
325 84a91a14 Bob Lantz
           intfName2: node2  interface name (optional)
326
           params1: parameters for interface 1
327
           params2: parameters for interface 2"""
328 a6bcad8f Bob Lantz
        # This is a bit awkward; it seems that having everything in
329
        # params would be more orthogonal, but being able to specify
330
        # in-line arguments is more convenient!
331
        if port1 is None:
332
            port1 = node1.newPort()
333
        if port2 is None:
334
            port2 = node2.newPort()
335
        if not intfName1:
336
            intfName1 = self.intfName( node1, port1 )
337
        if not intfName2:
338
            intfName2 = self.intfName( node2, port2 )
339 14ff3ad3 Bob Lantz
340 a6bcad8f Bob Lantz
        self.makeIntfPair( intfName1, intfName2 )
341 14ff3ad3 Bob Lantz
342 84a91a14 Bob Lantz
        if not cls1:
343
            cls1 = intf
344
        if not cls2:
345
            cls2 = intf
346 14ff3ad3 Bob Lantz
        if not params1:
347
            params1 = {}
348
        if not params2:
349
            params2 = {}
350
351 d7e5dfc5 Bob Lantz
        intf1 = cls1( name=intfName1, node=node1, port=port1,
352
                      link=self, **params1  )
353
        intf2 = cls2( name=intfName2, node=node2, port=port2,
354
                      link=self, **params2 )
355 14ff3ad3 Bob Lantz
356 84a91a14 Bob Lantz
        # All we are is dust in the wind, and our two interfaces
357 a6bcad8f Bob Lantz
        self.intf1, self.intf2 = intf1, intf2
358
359
    @classmethod
360
    def intfName( cls, node, n ):
361
        "Construct a canonical interface name node-ethN for interface n."
362
        return node.name + '-eth' + repr( n )
363
364
    @classmethod
365
    def makeIntfPair( cls, intf1, intf2 ):
366
        """Create pair of interfaces
367
           intf1: name of interface 1
368
           intf2: name of interface 2
369 14ff3ad3 Bob Lantz
           (override this class method [and possibly delete()]
370
           to change link type)"""
371 a6bcad8f Bob Lantz
        makeIntfPair( intf1, intf2  )
372
373
    def delete( self ):
374
        "Delete this link"
375
        self.intf1.delete()
376
        self.intf2.delete()
377
378
    def __str__( self ):
379
        return '%s<->%s' % ( self.intf1, self.intf2 )
380 ff568819 Bob Lantz
381
class TCLink( Link ):
382
    "Link with symmetric TC interfaces configured via opts"
383
    def __init__( self, node1, node2, port1=None, port2=None,
384
                  intfName1=None, intfName2=None, **params ):
385 2ec866d2 Bob Lantz
        Link.__init__( self, node1, node2, port1=port1, port2=port2,
386
                       intfName1=intfName1, intfName2=intfName2,
387 ff568819 Bob Lantz
                       cls1=TCIntf,
388
                       cls2=TCIntf,
389
                       params1=params,
390
                       params2=params)