Statistics
| Branch: | Tag: | Revision:

mininet / mininet / link.py @ c1934706

History | View | Annotate | Download (15.4 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 c1934706 cody burkard
        #heres where we send the unecessary ifconfigs
90 a6bcad8f Bob Lantz
        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 c1934706 cody burkard
            r = self.ifconfig( 'up' )
107
            if r:
108
                return False
109
            else:
110
                return True
111
        else:
112
            return "UP" in self.ifconfig()
113 a6bcad8f Bob Lantz
114 8856d284 Bob Lantz
    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 84a91a14 Bob Lantz
    # 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 a6bcad8f Bob Lantz
128 edf46e95 Bob Lantz
    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 84a91a14 Bob Lantz
        name, value = param.items()[ 0 ]
135 edf46e95 Bob Lantz
        f = getattr( self, method, None )
136
        if not f or value is None:
137 84a91a14 Bob Lantz
            return
138
        if type( value ) is list:
139 edf46e95 Bob Lantz
            result = f( *value )
140 84a91a14 Bob Lantz
        elif type( value ) is dict:
141 edf46e95 Bob Lantz
            result = f( **value )
142 84a91a14 Bob Lantz
        else:
143 edf46e95 Bob Lantz
            result = f( value )
144
        results[ name ] = result
145
        return result
146 84a91a14 Bob Lantz
147 c1934706 cody burkard
    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 14ff3ad3 Bob Lantz
    def config( self, mac=None, ip=None, ifconfig=None,
158
                up=True, **_params ):
159 84a91a14 Bob Lantz
        """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 a49c85a6 Bob Lantz
        self.setParam( r, 'isUp', up=up )
172 84a91a14 Bob Lantz
        self.setParam( r, 'ifconfig', ifconfig=ifconfig )
173 c1934706 cody burkard
        #should combine these next two operations into one. this is unecessary
174
        #self.updateAddr()
175
        #self.updateIP()
176
        #self.updateMAC()
177 84a91a14 Bob Lantz
        return r
178 a6bcad8f Bob Lantz
179
    def delete( self ):
180
        "Delete interface"
181
        self.cmd( 'ip link del ' + self.name )
182 10be691b Bob Lantz
        if self.node.inNamespace:
183
            # Link may have been dumped into root NS
184
            quietRun( 'ip link del ' + self.name )
185 a6bcad8f Bob Lantz
186 d7e5dfc5 Bob Lantz
    def __repr__( self ):
187
        return '<%s %s>' % ( self.__class__.__name__, self.name )
188
189 a6bcad8f Bob Lantz
    def __str__( self ):
190
        return self.name
191
192
193 a908fafa Bob Lantz
class TCIntf( Intf ):
194 14ff3ad3 Bob Lantz
    """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 a6bcad8f Bob Lantz
198 14ff3ad3 Bob Lantz
    def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
199 cec44763 Angad Singh
                latency_ms=None, enable_ecn=False, enable_red=False ):
200 14ff3ad3 Bob Lantz
        "Return tc commands to set bandwidth"
201 a6bcad8f Bob Lantz
202 14ff3ad3 Bob Lantz
        cmds, parent = [], ' root '
203 a6bcad8f Bob Lantz
204
        if bw and ( bw < 0 or bw > 1000 ):
205
            error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
206
207 14ff3ad3 Bob Lantz
        elif bw is not None:
208
            # BL: this seems a bit brittle...
209
            if ( speedup > 0 and
210 8139695d Bob Lantz
                 self.node.name[0:1] == 's' ):
211 d27a3c52 Bob Lantz
                bw = speedup
212 e5653fb6 Bob Lantz
            # 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 d27a3c52 Bob Lantz
            if use_hfsc:
217 e09254ee Andrew Ferguson
                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 14ff3ad3 Bob Lantz
                          + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
220 d27a3c52 Bob Lantz
            elif use_tbf:
221 cec44763 Angad Singh
                if latency_ms is None:
222
                    latency_ms = 15 * 8 / bw
223 e09254ee Andrew Ferguson
                cmds += [ '%s qdisc add dev %s root handle 5: tbf ' +
224 edf60032 Brandon Heller
                          'rate %fMbit burst 15000 latency %fms' %
225
                          ( bw, latency_ms ) ]
226 d27a3c52 Bob Lantz
            else:
227 e09254ee Andrew Ferguson
                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 edf60032 Brandon Heller
                          'rate %fMbit burst 15k' % bw ]
230 e09254ee Andrew Ferguson
            parent = ' parent 5:1 '
231 d27a3c52 Bob Lantz
232
            # ECN or RED
233
            if enable_ecn:
234 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
235 e09254ee Andrew Ferguson
                          'handle 6: red limit 1000000 ' +
236 6bb5e123 Nikhil Handigol
                          'min 30000 max 35000 avpkt 1500 ' +
237 14ff3ad3 Bob Lantz
                          'burst 20 ' +
238 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1 ecn' % bw ]
239 e09254ee Andrew Ferguson
                parent = ' parent 6: '
240 d27a3c52 Bob Lantz
            elif enable_red:
241 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
242 e09254ee Andrew Ferguson
                          'handle 6: red limit 1000000 ' +
243 107785dd Nikhil Handigol
                          'min 30000 max 35000 avpkt 1500 ' +
244 14ff3ad3 Bob Lantz
                          'burst 20 ' +
245 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1' % bw ]
246 e09254ee Andrew Ferguson
                parent = ' parent 6: '
247 14ff3ad3 Bob Lantz
        return cmds, parent
248
249
    @staticmethod
250 cec44763 Angad Singh
    def delayCmds( parent, delay=None, jitter=None,
251
                   loss=None, max_queue_size=None ):
252 14ff3ad3 Bob Lantz
        "Internal method: return tc commands for delay and loss"
253
        cmds = []
254
        if delay and delay < 0:
255
            error( 'Negative delay', delay, '\n' )
256 cec44763 Angad Singh
        elif jitter and jitter < 0:
257
            error( 'Negative jitter', jitter, '\n' )
258 14ff3ad3 Bob Lantz
        elif loss and ( loss < 0 or loss > 100 ):
259
            error( 'Bad loss percentage', loss, '%%\n' )
260
        else:
261 cec44763 Angad Singh
            # Delay/jitter/loss/max queue size
262
            netemargs = '%s%s%s%s' % (
263 14ff3ad3 Bob Lantz
                'delay %s ' % delay if delay is not None else '',
264 cec44763 Angad Singh
                '%s ' % jitter if jitter is not None else '',
265 14ff3ad3 Bob Lantz
                'loss %d ' % loss if loss is not None else '',
266
                'limit %d' % max_queue_size if max_queue_size is not None
267 2e089b5e Brandon Heller
                else '' )
268 14ff3ad3 Bob Lantz
            if netemargs:
269 d776bd3a Bob Lantz
                cmds = [ '%s qdisc add dev %s ' + parent +
270
                         ' handle 10: netem ' +
271 2e089b5e Brandon Heller
                         netemargs ]
272 e09254ee Andrew Ferguson
                parent = ' parent 10:1 '
273
        return cmds, parent
274 14ff3ad3 Bob Lantz
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 cec44763 Angad Singh
    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 14ff3ad3 Bob Lantz
        "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 edf60032 Brandon Heller
                                      use_hfsc=use_hfsc, use_tbf=use_tbf,
305
                                      latency_ms=latency_ms,
306
                                      enable_ecn=enable_ecn,
307
                                      enable_red=enable_red )
308 14ff3ad3 Bob Lantz
        cmds += bwcmds
309
310 cec44763 Angad Singh
        # Delay/jitter/loss/max_queue_size using netem
311 2485d57f Bob Lantz
        delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
312
                                loss=loss, max_queue_size=max_queue_size,
313 2e089b5e Brandon Heller
                                parent=parent )
314 e09254ee Andrew Ferguson
        cmds += delaycmds
315 14ff3ad3 Bob Lantz
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 cec44763 Angad Singh
                  ( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
320 14ff3ad3 Bob Lantz
                  ( ['%d%% loss' % loss ] if loss is not None else [] ) +
321 1052f8a0 Brandon Heller
                  ( [ 'ECN' ] if enable_ecn else [ 'RED' ]
322 14ff3ad3 Bob Lantz
                    if enable_red else [] ) )
323
        info( '(' + ' '.join( stuff ) + ') ' )
324
325
        # Execute all the commands in our node
326 a6bcad8f Bob Lantz
        debug("at map stage w/cmds: %s\n" % cmds)
327 14ff3ad3 Bob Lantz
        tcoutputs = [ self.tc(cmd) for cmd in cmds ]
328 84a91a14 Bob Lantz
        debug( "cmds:", cmds, '\n' )
329
        debug( "outputs:", tcoutputs, '\n' )
330
        result[ 'tcoutputs'] = tcoutputs
331 e09254ee Andrew Ferguson
        result[ 'parent' ] = parent
332 14ff3ad3 Bob Lantz
333 84a91a14 Bob Lantz
        return result
334 a6bcad8f Bob Lantz
335
336
class Link( object ):
337 14ff3ad3 Bob Lantz
338 a6bcad8f Bob Lantz
    """A basic link is just a veth pair.
339
       Other types of links could be tunnels, link emulators, etc.."""
340
341 14ff3ad3 Bob Lantz
    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 a6bcad8f Bob Lantz
        """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 84a91a14 Bob Lantz
           intf: default interface class/constructor
351
           cls1, cls2: optional interface-specific constructors
352 a6bcad8f Bob Lantz
           intfName1: node1 interface name (optional)
353 84a91a14 Bob Lantz
           intfName2: node2  interface name (optional)
354
           params1: parameters for interface 1
355
           params2: parameters for interface 2"""
356 a6bcad8f Bob Lantz
        # 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 14ff3ad3 Bob Lantz
368 a6bcad8f Bob Lantz
        self.makeIntfPair( intfName1, intfName2 )
369 14ff3ad3 Bob Lantz
370 84a91a14 Bob Lantz
        if not cls1:
371
            cls1 = intf
372
        if not cls2:
373
            cls2 = intf
374 14ff3ad3 Bob Lantz
        if not params1:
375
            params1 = {}
376
        if not params2:
377
            params2 = {}
378
379 d7e5dfc5 Bob Lantz
        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 14ff3ad3 Bob Lantz
384 84a91a14 Bob Lantz
        # All we are is dust in the wind, and our two interfaces
385 a6bcad8f Bob Lantz
        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 14ff3ad3 Bob Lantz
           (override this class method [and possibly delete()]
398
           to change link type)"""
399 a6bcad8f Bob Lantz
        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 ff568819 Bob Lantz
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 2ec866d2 Bob Lantz
        Link.__init__( self, node1, node2, port1=port1, port2=port2,
414
                       intfName1=intfName1, intfName2=intfName2,
415 ff568819 Bob Lantz
                       cls1=TCIntf,
416
                       cls2=TCIntf,
417
                       params1=params,
418
                       params2=params)