Statistics
| Branch: | Tag: | Revision:

mininet / mininet / link.py @ edf46e95

History | View | Annotate | Download (10.6 KB)

1
"""
2

3
link.py: interface and link abstractions for mininet
4

5
It seems useful to bundle functionality for interfaces into a single
6
class.
7

8
Also it seems useful to enable the possibility of multiple flavors of
9
links, including:
10

11
- simple veth pairs
12
- tunneled links
13
- patchable links (which can be disconnected and reconnected via a patchbay)
14
- link simulators (e.g. wireless)
15

16
Basic division of labor:
17

18
  Nodes: know how to execute commands
19
  Intfs: know how to configure themselves
20
  Links: know how to connect nodes together
21

22
"""
23

    
24
from mininet.log import info, error, debug
25
from mininet.util import makeIntfPair
26
from time import sleep
27
import re
28

    
29
class BasicIntf( object ):
30

    
31
    "Basic interface object that can configure itself."
32

    
33
    def __init__( self, node, name=None, link=None, **kwargs ):
34
        """node: owning node (where this intf most likely lives)
35
           name: interface name (e.g. h1-eth0)
36
           link: parent link if any
37
           other arguments are used to configure link parameters"""
38
        self.node = node
39
        self.name = name
40
        self.link = link
41
        self.mac, self.ip = None, None
42
        # Add to node (and move ourselves if necessary )
43
        node.addIntf( self )
44
        self.config( **kwargs )
45

    
46
    def cmd( self, *args, **kwargs ):
47
        return self.node.cmd( *args, **kwargs )
48

    
49
    def ifconfig( self, *args ):
50
        "Configure ourselves using ifconfig"
51
        return self.cmd( 'ifconfig', self.name, *args )
52

    
53
    def setIP( self, ipstr ):
54
        """Set our IP address"""
55
        # This is a sign that we should perhaps rethink our prefix
56
        # mechanism
57
        self.ip, self.prefixLen = ipstr.split( '/' )
58
        return self.ifconfig( ipstr, 'up' )
59

    
60
    def setMAC( self, macstr ):
61
        """Set the MAC address for an interface.
62
           macstr: MAC address as string"""
63
        self.mac = macstr
64
        return ( self.ifconfig( 'down' ) + 
65
                 self.ifconfig( 'hw', 'ether', macstr ) +
66
                 self.ifconfig( 'up' ) )
67

    
68
    _ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
69
    _macMatchRegex = re.compile( r'..:..:..:..:..:..' )
70

    
71
    def updateIP( self ):
72
        "Return updated IP address based on ifconfig"
73
        ifconfig = self.ifconfig()
74
        ips = self._ipMatchRegex.findall( ifconfig )
75
        self.ip = ips[ 0 ] if ips else None
76
        return self.ip
77

    
78
    def updateMAC( self, intf ):
79
        "Return updated MAC address based on ifconfig"
80
        ifconfig = self.ifconfig()
81
        macs = self._macMatchRegex.findall( ifconfig )
82
        self.mac = macs[ 0 ] if macs else None
83
        return self.mac
84
    
85
    def IP( self ):
86
        "Return IP address"
87
        return self.ip
88

    
89
    def MAC( self ):
90
        "Return MAC address"
91
        return self.mac
92

    
93
    def isUp( self, set=False ):
94
        "Return whether interface is up"
95
        return "UP" in self.ifconfig()
96

    
97
    # The reason why we configure things in this way is so
98
    # That the parameters can be listed and documented in
99
    # the config method.
100
    # Dealing with subclasses and superclasses is slightly
101
    # annoying, but at least the information is there!
102

    
103
    def setParam( self, results, method, **param ):
104
        """Internal method: configure a *single* parameter
105
           results: dict of results to update
106
           method: config method name
107
           param: arg=value (ignore if value=None)
108
           value may also be list or dict"""
109
        name, value = param.items()[ 0 ]
110
        f = getattr( self, method, None )
111
        if not f or value is None:
112
            return
113
        if type( value ) is list:
114
            result = f( *value )
115
        elif type( value ) is dict:
116
            result = f( **value )
117
        else:
118
            result = f( value )
119
        results[ name ] = result
120
        return result
121

    
122
    def config( self, mac=None, ip=None, ifconfig=None, 
123
                defaultRoute=None, **params):
124
        """Configure Node according to (optional) parameters:
125
           mac: MAC address
126
           ip: IP address
127
           ifconfig: arbitrary interface configuration
128
           Subclasses should override this method and call
129
           the parent class's config(**params)"""
130
        # If we were overriding this method, we would call
131
        # the superclass config method here as follows:
132
        # r = Parent.config( **params )
133
        r = {}
134
        self.setParam( r, 'setMAC', mac=mac )
135
        self.setParam( r, 'setIP', ip=ip )
136
        self.setParam( r, 'ifconfig', ifconfig=ifconfig )
137
        return r
138

    
139
    def delete( self ):
140
        "Delete interface"
141
        self.cmd( 'ip link del ' + self.name )
142
        # Does it help to sleep to let things run?
143
        sleep( 0.001 )
144

    
145
    def __str__( self ):
146
        return self.name
147

    
148

    
149
class TCIntf( BasicIntf ):
150
    "Interface customized by tc (traffic control) utility"  
151

    
152
    def config( self, bw=None, delay=None, loss=0, disable_gro=True,
153
                speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False,
154
                enable_red=False, max_queue_size=1000, **params ):
155
        "Configure the port and set its properties."
156

    
157
        result = BasicIntf.config( self, **params)
158

    
159
        # disable GRO
160
        if disable_gro:
161
            self.cmd( 'ethtool -K %s gro off' % self )
162
        
163
        if bw is None and not delay and not loss:
164
            return
165

    
166
        if bw and ( bw < 0 or bw > 1000 ):
167
            error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
168
            return
169
            
170
        if delay and delay < 0:
171
            error( 'Negative delay', delay, '\n' )
172
            return
173

    
174
        if loss and ( loss < 0 or loss > 100 ):
175
            error( 'Bad loss percentage', loss, '%%\n' )
176
            return
177
        
178
        if delay is None:
179
            delay = '0ms'
180
        
181
        if bw is not None and delay is not None:
182
            info( self, '(bw %.2fMbit, delay %s, loss %d%%) ' % 
183
                 ( bw, delay, loss ) )
184
        
185
        # BL: hmm... what exactly is this???
186
        # This seems kind of brittle
187
        if speedup > 0 and self.node.name[0:2] == 'sw':
188
            bw = speedup
189

    
190
        tc = 'tc' # was getCmd( 'tc' )
191

    
192
        # Bandwidth control algorithms
193
        if use_hfsc:
194
            cmds = [ '%s qdisc del dev %s root',
195
                     '%s qdisc add dev %s root handle 1:0 hfsc default 1' ]
196
            if bw is not None:
197
                cmds.append( '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' +
198
                            'rate %fMbit ul rate %fMbit' % ( bw, bw ) )
199
        elif use_tbf:
200
            latency_us = 10 * 1500 * 8 / bw
201
            cmds = ['%s qdisc del dev %s root',
202
                    '%s qdisc add dev %s root handle 1: tbf ' +
203
                    'rate %fMbit burst 15000 latency %fus' % (bw, latency_us) ]
204
        else:
205
            cmds = [ '%s qdisc del dev %s root',
206
                     '%s qdisc add dev %s root handle 1:0 htb default 1',
207
                     '%s class add dev %s parent 1:0 classid 1:1 htb ' +
208
                     'rate %fMbit burst 15k' % bw ]
209

    
210
        # ECN or RED
211
        if enable_ecn:
212
            info( 'Enabling ECN\n' )
213
            cmds += [ '%s qdisc add dev %s parent 1:1 '+
214
                      'handle 10: red limit 1000000 '+
215
                      'min 20000 max 25000 avpkt 1000 '+
216
                      'burst 20 '+
217
                      'bandwidth %fmbit probability 1 ecn' % bw ]
218
        elif enable_red:
219
            info( 'Enabling RED\n' )
220
            cmds += [ '%s qdisc add dev %s parent 1:1 '+
221
                      'handle 10: red limit 1000000 '+
222
                      'min 20000 max 25000 avpkt 1000 '+
223
                      'burst 20 '+
224
                      'bandwidth %fmbit probability 1' % bw ]
225
        else:
226
            cmds += [ '%s qdisc add dev %s parent 1:1 handle 10:0 netem ' +
227
                     'delay ' + '%s' % delay + ' loss ' + '%d' % loss + 
228
                     ' limit %d' % (max_queue_size) ]
229
        
230
        # Execute all the commands in the container
231
        debug("at map stage w/cmds: %s\n" % cmds)
232
        
233
        def doConfigPort(s):
234
            c = s % (tc, self)
235
            debug(" *** executing command: %s\n" % c)
236
            return self.cmd(c)
237
        
238
        tcoutputs = [ doConfigPort(cmd) for cmd in cmds ]
239
        debug( "cmds:", cmds, '\n' )
240
        debug( "outputs:", tcoutputs, '\n' )
241
        result[ 'tcoutputs'] = tcoutputs
242
        return result
243

    
244
Intf = TCIntf
245

    
246
class Link( object ):
247
    
248
    """A basic link is just a veth pair.
249
       Other types of links could be tunnels, link emulators, etc.."""
250

    
251
    def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None,
252
                  intf=Intf, cls1=None, cls2=None, params1={}, params2={} ):
253
        """Create veth link to another node, making two new interfaces.
254
           node1: first node
255
           node2: second node
256
           port1: node1 port number (optional)
257
           port2: node2 port number (optional)
258
           intf: default interface class/constructor
259
           cls1, cls2: optional interface-specific constructors
260
           intfName1: node1 interface name (optional)
261
           intfName2: node2  interface name (optional)
262
           params1: parameters for interface 1
263
           params2: parameters for interface 2"""
264
        # This is a bit awkward; it seems that having everything in
265
        # params would be more orthogonal, but being able to specify
266
        # in-line arguments is more convenient!
267
        if port1 is None:
268
            port1 = node1.newPort()
269
        if port2 is None:
270
            port2 = node2.newPort()
271
        if not intfName1:
272
            intfName1 = self.intfName( node1, port1 )
273
        if not intfName2:
274
            intfName2 = self.intfName( node2, port2 )
275
        self.makeIntfPair( intfName1, intfName2 )
276
        if not cls1:
277
            cls1 = intf
278
        if not cls2:
279
            cls2 = intf
280
        intf1 = cls1( name=intfName1, node=node1, link=self, **params1  )
281
        intf2 = cls2( name=intfName2, node=node2, link=self, **params2 )
282
        # All we are is dust in the wind, and our two interfaces
283
        self.intf1, self.intf2 = intf1, intf2
284

    
285
    @classmethod
286
    def intfName( cls, node, n ):
287
        "Construct a canonical interface name node-ethN for interface n."
288
        return node.name + '-eth' + repr( n )
289

    
290
    @classmethod
291
    def makeIntfPair( cls, intf1, intf2 ):
292
        """Create pair of interfaces
293
           intf1: name of interface 1
294
           intf2: name of interface 2
295
           (override this class method [and possibly delete()] to change link type)"""
296
        makeIntfPair( intf1, intf2  )
297

    
298
    def delete( self ):
299
        "Delete this link"
300
        self.intf1.delete()
301
        self.intf2.delete()
302

    
303
    def __str__( self ):
304
        return '%s<->%s' % ( self.intf1, self.intf2 )