Statistics
| Branch: | Tag: | Revision:

mininet / mininet / link.py @ 824afb84

History | View | Annotate | Download (14.8 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
        ifconfig = self.ifconfig()
90
        macs = self._macMatchRegex.findall( ifconfig )
91
        self.mac = macs[ 0 ] if macs else None
92
        return self.mac
93 14ff3ad3 Bob Lantz
94 a6bcad8f Bob Lantz
    def IP( self ):
95
        "Return IP address"
96
        return self.ip
97
98
    def MAC( self ):
99
        "Return MAC address"
100
        return self.mac
101
102 14ff3ad3 Bob Lantz
    def isUp( self, setUp=False ):
103 a6bcad8f Bob Lantz
        "Return whether interface is up"
104 14ff3ad3 Bob Lantz
        if setUp:
105 a49c85a6 Bob Lantz
            self.ifconfig( 'up' )
106 a6bcad8f Bob Lantz
        return "UP" in self.ifconfig()
107
108 8856d284 Bob Lantz
    def rename( self, newname ):
109
        "Rename interface"
110
        self.ifconfig( 'down' )
111
        result = self.cmd( 'ip link set', self.name, 'name', newname )
112
        self.name = newname
113
        self.ifconfig( 'up' )
114
        return result
115
116 84a91a14 Bob Lantz
    # The reason why we configure things in this way is so
117
    # That the parameters can be listed and documented in
118
    # the config method.
119
    # Dealing with subclasses and superclasses is slightly
120
    # annoying, but at least the information is there!
121 a6bcad8f Bob Lantz
122 edf46e95 Bob Lantz
    def setParam( self, results, method, **param ):
123
        """Internal method: configure a *single* parameter
124
           results: dict of results to update
125
           method: config method name
126
           param: arg=value (ignore if value=None)
127
           value may also be list or dict"""
128 84a91a14 Bob Lantz
        name, value = param.items()[ 0 ]
129 edf46e95 Bob Lantz
        f = getattr( self, method, None )
130
        if not f or value is None:
131 84a91a14 Bob Lantz
            return
132
        if type( value ) is list:
133 edf46e95 Bob Lantz
            result = f( *value )
134 84a91a14 Bob Lantz
        elif type( value ) is dict:
135 edf46e95 Bob Lantz
            result = f( **value )
136 84a91a14 Bob Lantz
        else:
137 edf46e95 Bob Lantz
            result = f( value )
138
        results[ name ] = result
139
        return result
140 84a91a14 Bob Lantz
141 14ff3ad3 Bob Lantz
    def config( self, mac=None, ip=None, ifconfig=None,
142
                up=True, **_params ):
143 84a91a14 Bob Lantz
        """Configure Node according to (optional) parameters:
144
           mac: MAC address
145
           ip: IP address
146
           ifconfig: arbitrary interface configuration
147
           Subclasses should override this method and call
148
           the parent class's config(**params)"""
149
        # If we were overriding this method, we would call
150
        # the superclass config method here as follows:
151
        # r = Parent.config( **params )
152
        r = {}
153
        self.setParam( r, 'setMAC', mac=mac )
154
        self.setParam( r, 'setIP', ip=ip )
155 a49c85a6 Bob Lantz
        self.setParam( r, 'isUp', up=up )
156 84a91a14 Bob Lantz
        self.setParam( r, 'ifconfig', ifconfig=ifconfig )
157 8856d284 Bob Lantz
        self.updateIP()
158
        self.updateMAC()
159 84a91a14 Bob Lantz
        return r
160 a6bcad8f Bob Lantz
161
    def delete( self ):
162
        "Delete interface"
163
        self.cmd( 'ip link del ' + self.name )
164 10be691b Bob Lantz
        if self.node.inNamespace:
165
            # Link may have been dumped into root NS
166
            quietRun( 'ip link del ' + self.name )
167 a6bcad8f Bob Lantz
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 cec44763 Angad Singh
                latency_ms=None, enable_ecn=False, enable_red=False ):
182 14ff3ad3 Bob Lantz
        "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 e09254ee Andrew Ferguson
                cmds += [ '%s qdisc add dev %s root handle 5:0 hfsc default 1',
200
                          '%s class add dev %s parent 5:0 classid 5:1 hfsc sc '
201 14ff3ad3 Bob Lantz
                          + 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
202 d27a3c52 Bob Lantz
            elif use_tbf:
203 cec44763 Angad Singh
                if latency_ms is None:
204
                    latency_ms = 15 * 8 / bw
205 e09254ee Andrew Ferguson
                cmds += [ '%s qdisc add dev %s root handle 5: tbf ' +
206 edf60032 Brandon Heller
                          'rate %fMbit burst 15000 latency %fms' %
207
                          ( bw, latency_ms ) ]
208 d27a3c52 Bob Lantz
            else:
209 e09254ee Andrew Ferguson
                cmds += [ '%s qdisc add dev %s root handle 5:0 htb default 1',
210
                          '%s class add dev %s parent 5:0 classid 5:1 htb ' +
211 edf60032 Brandon Heller
                          'rate %fMbit burst 15k' % bw ]
212 e09254ee Andrew Ferguson
            parent = ' parent 5:1 '
213 d27a3c52 Bob Lantz
214
            # ECN or RED
215
            if enable_ecn:
216 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
217 e09254ee Andrew Ferguson
                          'handle 6: red limit 1000000 ' +
218 6bb5e123 Nikhil Handigol
                          'min 30000 max 35000 avpkt 1500 ' +
219 14ff3ad3 Bob Lantz
                          'burst 20 ' +
220 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1 ecn' % bw ]
221 e09254ee Andrew Ferguson
                parent = ' parent 6: '
222 d27a3c52 Bob Lantz
            elif enable_red:
223 ae2ede79 Nikhil Handigol
                cmds += [ '%s qdisc add dev %s' + parent +
224 e09254ee Andrew Ferguson
                          'handle 6: red limit 1000000 ' +
225 107785dd Nikhil Handigol
                          'min 30000 max 35000 avpkt 1500 ' +
226 14ff3ad3 Bob Lantz
                          'burst 20 ' +
227 d27a3c52 Bob Lantz
                          'bandwidth %fmbit probability 1' % bw ]
228 e09254ee Andrew Ferguson
                parent = ' parent 6: '
229 14ff3ad3 Bob Lantz
        return cmds, parent
230
231
    @staticmethod
232 cec44763 Angad Singh
    def delayCmds( parent, delay=None, jitter=None,
233
                   loss=None, max_queue_size=None ):
234 14ff3ad3 Bob Lantz
        "Internal method: return tc commands for delay and loss"
235
        cmds = []
236
        if delay and delay < 0:
237
            error( 'Negative delay', delay, '\n' )
238 cec44763 Angad Singh
        elif jitter and jitter < 0:
239
            error( 'Negative jitter', jitter, '\n' )
240 14ff3ad3 Bob Lantz
        elif loss and ( loss < 0 or loss > 100 ):
241
            error( 'Bad loss percentage', loss, '%%\n' )
242
        else:
243 cec44763 Angad Singh
            # Delay/jitter/loss/max queue size
244
            netemargs = '%s%s%s%s' % (
245 14ff3ad3 Bob Lantz
                'delay %s ' % delay if delay is not None else '',
246 cec44763 Angad Singh
                '%s ' % jitter if jitter is not None else '',
247 14ff3ad3 Bob Lantz
                'loss %d ' % loss if loss is not None else '',
248
                'limit %d' % max_queue_size if max_queue_size is not None
249 2e089b5e Brandon Heller
                else '' )
250 14ff3ad3 Bob Lantz
            if netemargs:
251 d776bd3a Bob Lantz
                cmds = [ '%s qdisc add dev %s ' + parent +
252
                         ' handle 10: netem ' +
253 2e089b5e Brandon Heller
                         netemargs ]
254 e09254ee Andrew Ferguson
                parent = ' parent 10:1 '
255
        return cmds, parent
256 14ff3ad3 Bob Lantz
257
    def tc( self, cmd, tc='tc' ):
258
        "Execute tc command for our interface"
259
        c = cmd % (tc, self)  # Add in tc command and our name
260
        debug(" *** executing command: %s\n" % c)
261
        return self.cmd( c )
262
263 cec44763 Angad Singh
    def config( self, bw=None, delay=None, jitter=None, loss=None,
264
                disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False,
265
                latency_ms=None, enable_ecn=False, enable_red=False,
266
                max_queue_size=None, **params ):
267 14ff3ad3 Bob Lantz
        "Configure the port and set its properties."
268
269
        result = Intf.config( self, **params)
270
271
        # Disable GRO
272
        if disable_gro:
273
            self.cmd( 'ethtool -K %s gro off' % self )
274
275
        # Optimization: return if nothing else to configure
276
        # Question: what happens if we want to reset things?
277
        if ( bw is None and not delay and not loss
278
             and max_queue_size is None ):
279
            return
280
281
        # Clear existing configuration
282
        cmds = [ '%s qdisc del dev %s root' ]
283
284
        # Bandwidth limits via various methods
285
        bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
286 edf60032 Brandon Heller
                                      use_hfsc=use_hfsc, use_tbf=use_tbf,
287
                                      latency_ms=latency_ms,
288
                                      enable_ecn=enable_ecn,
289
                                      enable_red=enable_red )
290 14ff3ad3 Bob Lantz
        cmds += bwcmds
291
292 cec44763 Angad Singh
        # Delay/jitter/loss/max_queue_size using netem
293 2485d57f Bob Lantz
        delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
294
                                loss=loss, max_queue_size=max_queue_size,
295 2e089b5e Brandon Heller
                                parent=parent )
296 e09254ee Andrew Ferguson
        cmds += delaycmds
297 14ff3ad3 Bob Lantz
298
        # Ugly but functional: display configuration info
299
        stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
300
                  ( [ '%s delay' % delay ] if delay is not None else [] ) +
301 cec44763 Angad Singh
                  ( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
302 14ff3ad3 Bob Lantz
                  ( ['%d%% loss' % loss ] if loss is not None else [] ) +
303 1052f8a0 Brandon Heller
                  ( [ 'ECN' ] if enable_ecn else [ 'RED' ]
304 14ff3ad3 Bob Lantz
                    if enable_red else [] ) )
305
        info( '(' + ' '.join( stuff ) + ') ' )
306
307
        # Execute all the commands in our node
308 a6bcad8f Bob Lantz
        debug("at map stage w/cmds: %s\n" % cmds)
309 14ff3ad3 Bob Lantz
        tcoutputs = [ self.tc(cmd) for cmd in cmds ]
310 84a91a14 Bob Lantz
        debug( "cmds:", cmds, '\n' )
311
        debug( "outputs:", tcoutputs, '\n' )
312
        result[ 'tcoutputs'] = tcoutputs
313 e09254ee Andrew Ferguson
        result[ 'parent' ] = parent
314 14ff3ad3 Bob Lantz
315 84a91a14 Bob Lantz
        return result
316 a6bcad8f Bob Lantz
317
318
class Link( object ):
319 14ff3ad3 Bob Lantz
320 a6bcad8f Bob Lantz
    """A basic link is just a veth pair.
321
       Other types of links could be tunnels, link emulators, etc.."""
322
323 14ff3ad3 Bob Lantz
    def __init__( self, node1, node2, port1=None, port2=None,
324
                  intfName1=None, intfName2=None,
325
                  intf=Intf, cls1=None, cls2=None, params1=None,
326
                  params2=None ):
327 a6bcad8f Bob Lantz
        """Create veth link to another node, making two new interfaces.
328
           node1: first node
329
           node2: second node
330
           port1: node1 port number (optional)
331
           port2: node2 port number (optional)
332 84a91a14 Bob Lantz
           intf: default interface class/constructor
333
           cls1, cls2: optional interface-specific constructors
334 a6bcad8f Bob Lantz
           intfName1: node1 interface name (optional)
335 84a91a14 Bob Lantz
           intfName2: node2  interface name (optional)
336
           params1: parameters for interface 1
337
           params2: parameters for interface 2"""
338 a6bcad8f Bob Lantz
        # This is a bit awkward; it seems that having everything in
339
        # params would be more orthogonal, but being able to specify
340
        # in-line arguments is more convenient!
341
        if port1 is None:
342
            port1 = node1.newPort()
343
        if port2 is None:
344
            port2 = node2.newPort()
345
        if not intfName1:
346
            intfName1 = self.intfName( node1, port1 )
347
        if not intfName2:
348
            intfName2 = self.intfName( node2, port2 )
349 14ff3ad3 Bob Lantz
350 a6bcad8f Bob Lantz
        self.makeIntfPair( intfName1, intfName2 )
351 14ff3ad3 Bob Lantz
352 84a91a14 Bob Lantz
        if not cls1:
353
            cls1 = intf
354
        if not cls2:
355
            cls2 = intf
356 14ff3ad3 Bob Lantz
        if not params1:
357
            params1 = {}
358
        if not params2:
359
            params2 = {}
360
361 d7e5dfc5 Bob Lantz
        intf1 = cls1( name=intfName1, node=node1, port=port1,
362
                      link=self, **params1  )
363
        intf2 = cls2( name=intfName2, node=node2, port=port2,
364
                      link=self, **params2 )
365 14ff3ad3 Bob Lantz
366 84a91a14 Bob Lantz
        # All we are is dust in the wind, and our two interfaces
367 a6bcad8f Bob Lantz
        self.intf1, self.intf2 = intf1, intf2
368
369
    @classmethod
370
    def intfName( cls, node, n ):
371
        "Construct a canonical interface name node-ethN for interface n."
372
        return node.name + '-eth' + repr( n )
373
374
    @classmethod
375
    def makeIntfPair( cls, intf1, intf2 ):
376
        """Create pair of interfaces
377
           intf1: name of interface 1
378
           intf2: name of interface 2
379 14ff3ad3 Bob Lantz
           (override this class method [and possibly delete()]
380
           to change link type)"""
381 a6bcad8f Bob Lantz
        makeIntfPair( intf1, intf2  )
382
383
    def delete( self ):
384
        "Delete this link"
385
        self.intf1.delete()
386
        self.intf2.delete()
387
388
    def __str__( self ):
389
        return '%s<->%s' % ( self.intf1, self.intf2 )
390 ff568819 Bob Lantz
391
class TCLink( Link ):
392
    "Link with symmetric TC interfaces configured via opts"
393
    def __init__( self, node1, node2, port1=None, port2=None,
394
                  intfName1=None, intfName2=None, **params ):
395 2ec866d2 Bob Lantz
        Link.__init__( self, node1, node2, port1=port1, port2=port2,
396
                       intfName1=intfName1, intfName2=intfName2,
397 ff568819 Bob Lantz
                       cls1=TCIntf,
398
                       cls2=TCIntf,
399
                       params1=params,
400
                       params2=params)