mininet / mininet / node.py @ 60d9ead6
History | View | Annotate | Download (13.3 KB)
1 |
#!/usr/bin/env python
|
---|---|
2 |
'''Node objects for Mininet.'''
|
3 |
|
4 |
from subprocess import Popen, PIPE, STDOUT |
5 |
import os, signal, sys, select |
6 |
|
7 |
flush = sys.stdout.flush |
8 |
|
9 |
from mininet.logging_mod import lg |
10 |
from mininet.util import quietRun, macColonHex, ipStr |
11 |
|
12 |
class Node(object): |
13 |
'''A virtual network node is simply a shell in a network namespace.
|
14 |
We communicate with it using pipes.'''
|
15 |
inToNode = {} |
16 |
outToNode = {} |
17 |
|
18 |
def __init__(self, name, inNamespace = True): |
19 |
self.name = name
|
20 |
closeFds = False # speed vs. memory use |
21 |
# xpg_echo is needed so we can echo our sentinel in sendCmd
|
22 |
cmd = ['/bin/bash', '-O', 'xpg_echo'] |
23 |
self.inNamespace = inNamespace
|
24 |
if self.inNamespace: |
25 |
cmd = ['netns'] + cmd
|
26 |
self.shell = Popen(cmd, stdin = PIPE, stdout = PIPE, stderr = STDOUT,
|
27 |
close_fds = closeFds) |
28 |
self.stdin = self.shell.stdin |
29 |
self.stdout = self.shell.stdout |
30 |
self.pollOut = select.poll()
|
31 |
self.pollOut.register(self.stdout) |
32 |
# Maintain mapping between file descriptors and nodes
|
33 |
# This could be useful for monitoring multiple nodes
|
34 |
# using select.poll()
|
35 |
self.outToNode[self.stdout.fileno()] = self |
36 |
self.inToNode[self.stdin.fileno()] = self |
37 |
self.pid = self.shell.pid |
38 |
self.intfCount = 0 |
39 |
self.intfs = [] # list of interface names, as strings |
40 |
self.ips = {} # dict of interfaces to ip addresses as strings |
41 |
self.connection = {}
|
42 |
self.waiting = False |
43 |
self.execed = False |
44 |
|
45 |
def fdToNode(self, f): |
46 |
'''Insert docstring.
|
47 |
|
48 |
@param f unknown
|
49 |
@return bool unknown
|
50 |
'''
|
51 |
node = self.outToNode.get(f)
|
52 |
return node or self.inToNode.get(f) |
53 |
|
54 |
def cleanup(self): |
55 |
'''Help python collect its garbage.'''
|
56 |
self.shell = None |
57 |
|
58 |
# Subshell I/O, commands and control
|
59 |
def read(self, fileno_max): |
60 |
'''Insert docstring.
|
61 |
|
62 |
@param fileno_max unknown
|
63 |
'''
|
64 |
return os.read(self.stdout.fileno(), fileno_max) |
65 |
|
66 |
def write(self, data): |
67 |
'''Write data to node.
|
68 |
|
69 |
@param data string
|
70 |
'''
|
71 |
os.write(self.stdin.fileno(), data)
|
72 |
|
73 |
def terminate(self): |
74 |
'''Send kill signal to Node and cleanup after it.'''
|
75 |
os.kill(self.pid, signal.SIGKILL)
|
76 |
self.cleanup()
|
77 |
|
78 |
def stop(self): |
79 |
'''Stop node.'''
|
80 |
self.terminate()
|
81 |
|
82 |
def waitReadable(self): |
83 |
'''Poll on node.'''
|
84 |
self.pollOut.poll()
|
85 |
|
86 |
def sendCmd(self, cmd): |
87 |
'''Send a command, followed by a command to echo a sentinel,
|
88 |
and return without waiting for the command to complete.'''
|
89 |
assert not self.waiting |
90 |
if cmd[-1] == '&': |
91 |
separator = '&'
|
92 |
cmd = cmd[:-1]
|
93 |
else:
|
94 |
separator = ';'
|
95 |
if isinstance(cmd, list): |
96 |
cmd = ' '.join(cmd)
|
97 |
self.write(cmd + separator + ' echo -n "\\0177" \n') |
98 |
self.waiting = True |
99 |
|
100 |
def monitor(self): |
101 |
'''Monitor the output of a command, returning (done, data).'''
|
102 |
assert self.waiting |
103 |
self.waitReadable()
|
104 |
data = self.read(1024) |
105 |
if len(data) > 0 and data[-1] == chr(0177): |
106 |
self.waiting = False |
107 |
return True, data[:-1] |
108 |
else:
|
109 |
return False, data |
110 |
|
111 |
def sendInt(self): |
112 |
'''Send ^C, hopefully interrupting a running subprocess.'''
|
113 |
self.write(chr(3)) |
114 |
|
115 |
def waitOutput(self): |
116 |
'''Wait for a command to complete.
|
117 |
|
118 |
Completion is signaled by a sentinel character, ASCII(127) appearing in
|
119 |
the output stream. Wait for the sentinel and return the output,
|
120 |
including trailing newline.
|
121 |
'''
|
122 |
assert self.waiting |
123 |
output = ''
|
124 |
while True: |
125 |
self.waitReadable()
|
126 |
data = self.read(1024) |
127 |
if len(data) > 0 and data[-1] == chr(0177): |
128 |
output += data[:-1]
|
129 |
break
|
130 |
else: output += data
|
131 |
self.waiting = False |
132 |
return output
|
133 |
|
134 |
def cmd(self, cmd): |
135 |
'''Send a command, wait for output, and return it.
|
136 |
|
137 |
@param cmd string
|
138 |
'''
|
139 |
self.sendCmd(cmd)
|
140 |
return self.waitOutput() |
141 |
|
142 |
def cmdPrint(self, cmd): |
143 |
'''Call cmd and printing its output
|
144 |
|
145 |
@param cmd string
|
146 |
'''
|
147 |
#lg.info('*** %s : %s', self.name, cmd)
|
148 |
result = self.cmd(cmd)
|
149 |
#lg.info('%s\n', result)
|
150 |
return result
|
151 |
|
152 |
# Interface management, configuration, and routing
|
153 |
def intfName(self, n): |
154 |
'''Construct a canonical interface name node-intf for interface N.'''
|
155 |
return self.name + '-eth' + repr(n) |
156 |
|
157 |
def newIntf(self): |
158 |
'''Reserve and return a new interface name.'''
|
159 |
intfName = self.intfName(self.intfCount) |
160 |
self.intfCount += 1 |
161 |
self.intfs += [intfName]
|
162 |
return intfName
|
163 |
|
164 |
def setMAC(self, intf, mac): |
165 |
'''Set the MAC address for an interface.
|
166 |
|
167 |
@param mac MAC address as unsigned int
|
168 |
'''
|
169 |
mac_str = macColonHex(mac) |
170 |
result = self.cmd(['ifconfig', intf, 'down']) |
171 |
result += self.cmd(['ifconfig', intf, 'hw', 'ether', mac_str]) |
172 |
result += self.cmd(['ifconfig', intf, 'up']) |
173 |
return result
|
174 |
|
175 |
def setARP(self, ip, mac): |
176 |
'''Add an ARP entry.
|
177 |
|
178 |
@param ip IP address as unsigned int
|
179 |
@param mac MAC address as unsigned int
|
180 |
'''
|
181 |
ip_str = ipStr(ip) |
182 |
mac_str = macColonHex(mac) |
183 |
result = self.cmd(['arp', '-s', ip_str, mac_str]) |
184 |
return result
|
185 |
|
186 |
def setIP(self, intf, ip, bits): |
187 |
'''Set the IP address for an interface.
|
188 |
|
189 |
@param intf string, interface name
|
190 |
@param ip IP address as a string
|
191 |
@param bits
|
192 |
'''
|
193 |
result = self.cmd(['ifconfig', intf, ip + bits, 'up']) |
194 |
self.ips[intf] = ip
|
195 |
return result
|
196 |
|
197 |
def setHostRoute(self, ip, intf): |
198 |
'''Add route to host.
|
199 |
|
200 |
@param ip IP address as dotted decimal
|
201 |
@param intf string, interface name
|
202 |
'''
|
203 |
return self.cmd('route add -host ' + ip + ' dev ' + intf) |
204 |
|
205 |
def setDefaultRoute(self, intf): |
206 |
'''Set the default route to go through intf.
|
207 |
|
208 |
@param intf string, interface name
|
209 |
'''
|
210 |
self.cmd('ip route flush') |
211 |
return self.cmd('route add default ' + intf) |
212 |
|
213 |
def IP(self): |
214 |
'''Return IP address of first interface'''
|
215 |
if len(self.intfs) > 0: |
216 |
return self.ips.get(self.intfs[ 0 ], None) |
217 |
|
218 |
def intfIsUp(self): |
219 |
'''Check if one of our interfaces is up.'''
|
220 |
return 'UP' in self.cmd('ifconfig ' + self.intfs[0]) |
221 |
|
222 |
# Other methods
|
223 |
def __str__(self): |
224 |
result = self.name + ':' |
225 |
if self.IP(): |
226 |
result += ' IP=' + self.IP() |
227 |
result += ' intfs=' + ','.join(self.intfs) |
228 |
result += ' waiting=' + repr(self.waiting) |
229 |
return result
|
230 |
|
231 |
|
232 |
class Host(Node): |
233 |
'''A host is simply a Node.'''
|
234 |
pass
|
235 |
|
236 |
|
237 |
class Switch(Node): |
238 |
'''A Switch is a Node that is running (or has execed)
|
239 |
an OpenFlow switch.'''
|
240 |
|
241 |
def sendCmd(self, cmd): |
242 |
'''Send command to Node.
|
243 |
|
244 |
@param cmd string
|
245 |
'''
|
246 |
if not self.execed: |
247 |
return Node.sendCmd(self, cmd) |
248 |
else:
|
249 |
lg.error('*** Error: %s has execed and cannot accept commands' %
|
250 |
self.name)
|
251 |
|
252 |
def monitor(self): |
253 |
'''Monitor node.'''
|
254 |
if not self.execed: |
255 |
return Node.monitor(self) |
256 |
else:
|
257 |
return True, '' |
258 |
|
259 |
class UserSwitch(Switch): |
260 |
|
261 |
def __init__(self, name): |
262 |
'''Init.
|
263 |
|
264 |
@param name
|
265 |
'''
|
266 |
Node.__init__(self, name, inNamespace = True) |
267 |
|
268 |
def start(self, controllers): |
269 |
'''Start OpenFlow reference user datapath.
|
270 |
|
271 |
Log to /tmp/sN-{ofd,ofp}.log.
|
272 |
|
273 |
@param controllers dict of controller names to objects
|
274 |
'''
|
275 |
if 'c0' not in controller: |
276 |
raise Exception('User datapath start() requires controller c0') |
277 |
controller = controllers['c0']
|
278 |
ofdlog = '/tmp/' + self.name + '-ofd.log' |
279 |
ofplog = '/tmp/' + self.name + '-ofp.log' |
280 |
self.cmd('ifconfig lo up') |
281 |
intfs = self.intfs[1:] # 0 is mgmt interface |
282 |
self.cmdPrint('ofdatapath -i ' + ','.join(intfs) + |
283 |
' ptcp: 1> ' + ofdlog + ' 2> ' + ofdlog + ' &') |
284 |
self.cmdPrint('ofprotocol tcp:' + controller.IP() + |
285 |
' tcp:localhost --fail=closed 1> ' + ofplog + ' 2>' + |
286 |
ofplog + ' &')
|
287 |
|
288 |
def stop(self): |
289 |
'''Stop OpenFlow reference user datapath.'''
|
290 |
self.cmd('kill %ofdatapath') |
291 |
self.cmd('kill %ofprotocol') |
292 |
|
293 |
|
294 |
class KernelSwitch(Switch): |
295 |
|
296 |
def __init__(self, name, dp = None, dpid = None): |
297 |
'''Init.
|
298 |
|
299 |
@param name
|
300 |
@param dp netlink id (0, 1, 2, ...)
|
301 |
@param dpid datapath ID as unsigned int; random value if None
|
302 |
'''
|
303 |
Node.__init__(self, name, inNamespace = False) |
304 |
self.dp = dp
|
305 |
self.dpid = dpid
|
306 |
|
307 |
def start(self, controllers): |
308 |
'''Start up reference kernel datapath.'''
|
309 |
ofplog = '/tmp/' + self.name + '-ofp.log' |
310 |
quietRun('ifconfig lo up')
|
311 |
# Delete local datapath if it exists;
|
312 |
# then create a new one monitoring the given interfaces
|
313 |
quietRun('dpctl deldp nl:%i' % self.dp) |
314 |
self.cmdPrint('dpctl adddp nl:%i' % self.dp) |
315 |
if self.dpid: |
316 |
intf = 'of%i' % self.dp |
317 |
mac_str = macColonHex(self.dpid)
|
318 |
self.cmd(['ifconfig', intf, 'hw', 'ether', mac_str]) |
319 |
self.cmdPrint('dpctl addif nl:' + str(self.dp) + ' ' + |
320 |
' '.join(self.intfs)) |
321 |
# Run protocol daemon
|
322 |
self.cmdPrint('ofprotocol nl:' + str(self.dp) + ' tcp:' + |
323 |
controllers['c0'].IP()+':'+str(controllers['c0'].port) + |
324 |
' --fail=closed 1> ' + ofplog + ' 2>' + ofplog + ' &') |
325 |
self.execed = False # XXX until I fix it |
326 |
|
327 |
def stop(self): |
328 |
'''Terminate reference kernel datapath.'''
|
329 |
quietRun('dpctl deldp nl:%i' % self.dp) |
330 |
# In theory the interfaces should go away after we shut down.
|
331 |
# However, this takes time, so we're better off to remove them
|
332 |
# explicitly so that we won't get errors if we run before they
|
333 |
# have been removed by the kernel. Unfortunately this is very slow.
|
334 |
self.cmd('kill %ofprotocol') |
335 |
for intf in self.intfs: |
336 |
quietRun('ip link del ' + intf)
|
337 |
lg.info('.')
|
338 |
|
339 |
|
340 |
class Controller(Node): |
341 |
'''A Controller is a Node that is running (or has execed) an
|
342 |
OpenFlow controller.'''
|
343 |
|
344 |
def __init__(self, name, inNamespace = False, controller = 'controller', |
345 |
cargs = '-v ptcp:', cdir = None, ip_address="127.0.0.1", |
346 |
port = 6633):
|
347 |
self.controller = controller
|
348 |
self.cargs = cargs
|
349 |
self.cdir = cdir
|
350 |
self.ip_address = ip_address
|
351 |
self.port = port
|
352 |
Node.__init__(self, name, inNamespace = inNamespace)
|
353 |
|
354 |
def start(self): |
355 |
'''Start <controller> <args> on controller.
|
356 |
|
357 |
Log to /tmp/cN.log
|
358 |
'''
|
359 |
cout = '/tmp/' + self.name + '.log' |
360 |
if self.cdir is not None: |
361 |
self.cmdPrint('cd ' + self.cdir) |
362 |
self.cmdPrint(self.controller + ' ' + self.cargs + |
363 |
' 1> ' + cout + ' 2> ' + cout + ' &') |
364 |
self.execed = False # XXX Until I fix it |
365 |
|
366 |
def stop(self): |
367 |
'''Stop controller.'''
|
368 |
self.cmd('kill %' + self.controller) |
369 |
self.terminate()
|
370 |
|
371 |
def IP(self): |
372 |
'''Return IP address of the Controller'''
|
373 |
return self.ip_address |
374 |
|
375 |
|
376 |
class ControllerParams(object): |
377 |
'''Container for controller IP parameters.'''
|
378 |
def __init__(self, ip, subnet_size): |
379 |
'''Init.
|
380 |
|
381 |
@param ip integer, controller IP
|
382 |
@param subnet_size integer, ex 8 for slash-8, covering 17M
|
383 |
'''
|
384 |
self.ip = ip
|
385 |
self.subnet_size = subnet_size
|
386 |
|
387 |
|
388 |
class NOX(Controller): |
389 |
'''Controller to run a NOX application.'''
|
390 |
def __init__(self, name, inNamespace = False, nox_args = None, **kwargs): |
391 |
'''Init.
|
392 |
|
393 |
@param name name to give controller
|
394 |
@param nox_args list of args, or single arg, to pass to NOX
|
395 |
'''
|
396 |
if type(nox_args) != list: |
397 |
nox_args = [nox_args] |
398 |
if not nox_args: |
399 |
nox_args = ['packetdump']
|
400 |
nox_core_dir = os.environ['NOX_CORE_DIR']
|
401 |
if not nox_core_dir: |
402 |
raise Exception('please set NOX_CORE_DIR env var\n') |
403 |
Controller.__init__(self, name,
|
404 |
controller = nox_core_dir + '/nox_core',
|
405 |
cargs = '--libdir=/usr/local/lib -v -i ptcp: ' + \
|
406 |
' '.join(nox_args),
|
407 |
cdir = nox_core_dir, **kwargs) |
408 |
|
409 |
class RemoteController(Controller): |
410 |
'''Controller running remotely.'''
|
411 |
def __init__(self, name, inNamespace = False, ip_address = None, port = 6633): |
412 |
'''Init.
|
413 |
|
414 |
@param name name to give controller
|
415 |
@param ip_address the IP address where the remote controller is
|
416 |
listening
|
417 |
@param port the port where the remote controller is listening
|
418 |
'''
|
419 |
if not ip_address: |
420 |
raise Exception('please set ip_address\n') |
421 |
Controller.__init__(self, name, ip_address = ip_address, port = port)
|
422 |
|
423 |
def start(self): |
424 |
'''Overridden to do nothing.'''
|
425 |
return
|
426 |
|
427 |
def stop(self): |
428 |
'''Overridden to do nothing.'''
|
429 |
return
|