blob: 4c790f086a56844e22400a82e9c424add001b27d [file] [log] [blame]
Guido van Rossume3cafbe1992-12-14 23:25:04 +00001# Implement (a subset of) Sun RPC, version 2 -- RFC1057.
2
Guido van Rossumc4698fb1992-12-17 17:12:17 +00003# XXX There should be separate exceptions for the various reasons why
4# XXX an RPC can fail, rather than using RuntimeError for everything
5
6# XXX The UDP version of the protocol resends requests when it does
7# XXX not receive a timely reply -- use only for idempotent calls!
8
Guido van Rossume3cafbe1992-12-14 23:25:04 +00009import xdr
10import socket
11import os
12
13RPCVERSION = 2
14
15CALL = 0
16REPLY = 1
17
18AUTH_NULL = 0
19AUTH_UNIX = 1
20AUTH_SHORT = 2
21AUTH_DES = 3
22
23MSG_ACCEPTED = 0
24MSG_DENIED = 1
25
26SUCCESS = 0 # RPC executed successfully
27PROG_UNAVAIL = 1 # remote hasn't exported program
28PROG_MISMATCH = 2 # remote can't support version #
29PROC_UNAVAIL = 3 # program can't support procedure
30GARBAGE_ARGS = 4 # procedure can't decode params
31
32RPC_MISMATCH = 0 # RPC version number != 2
33AUTH_ERROR = 1 # remote can't authenticate caller
34
35AUTH_BADCRED = 1 # bad credentials (seal broken)
36AUTH_REJECTEDCRED = 2 # client must begin new session
37AUTH_BADVERF = 3 # bad verifier (seal broken)
38AUTH_REJECTEDVERF = 4 # verifier expired or replayed
39AUTH_TOOWEAK = 5 # rejected for security reasons
40
41
42class Packer(xdr.Packer):
43
44 def pack_auth(self, auth):
45 flavor, stuff = auth
46 self.pack_enum(flavor)
47 self.pack_opaque(stuff)
48
49 def pack_auth_unix(self, stamp, machinename, uid, gid, gids):
50 self.pack_uint(stamp)
51 self.pack_string(machinename)
52 self.pack_uint(uid)
53 self.pack_uint(gid)
54 self.pack_uint(len(gids))
55 for i in gids:
56 self.pack_uint(i)
57
58 def pack_callheader(self, xid, prog, vers, proc, cred, verf):
59 self.pack_uint(xid)
60 self.pack_enum(CALL)
61 self.pack_uint(RPCVERSION)
62 self.pack_uint(prog)
63 self.pack_uint(vers)
64 self.pack_uint(proc)
65 self.pack_auth(cred)
66 self.pack_auth(verf)
67 # Caller must add procedure-specific part of call
68
69 def pack_replyheader(self, xid, verf):
70 self.pack_uint(xid)
71 self.pack_enum(REPLY)
72 self.pack_uint(MSG_ACCEPTED)
73 self.pack_auth(verf)
74 self.pack_enum(SUCCESS)
75 # Caller must add procedure-specific part of reply
76
77
Guido van Rossum424c6731992-12-19 00:05:55 +000078# Exceptions
79BadRPCFormat = 'rpc.BadRPCFormat'
80BadRPCVersion = 'rpc.BadRPCVersion'
81GarbageArgs = 'rpc.GarbageArgs'
82
Guido van Rossume3cafbe1992-12-14 23:25:04 +000083class Unpacker(xdr.Unpacker):
84
85 def unpack_auth(self):
86 flavor = self.unpack_enum()
87 stuff = self.unpack_opaque()
88 return (flavor, stuff)
89
Guido van Rossum424c6731992-12-19 00:05:55 +000090 def unpack_callheader(self):
91 xid = self.unpack_uint(xid)
92 temp = self.unpack_enum()
93 if temp <> CALL:
94 raise BadRPCFormat, 'no CALL but ' + `temp`
95 temp = self.unpack_uint()
96 if temp <> RPCVERSION:
97 raise BadRPCVerspion, 'bad RPC version ' + `temp`
98 prog = self.unpack_uint()
99 vers = self.unpack_uint()
100 proc = self.unpack_uint()
101 cred = self.unpack_auth()
102 verf = self.unpack_auth()
103 return xid, prog, vers, proc, cred, verf
104 # Caller must add procedure-specific part of call
105
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000106 def unpack_replyheader(self):
107 xid = self.unpack_uint()
108 mtype = self.unpack_enum()
109 if mtype <> REPLY:
Guido van Rossumc4698fb1992-12-17 17:12:17 +0000110 raise RuntimeError, 'no REPLY but ' + `mtype`
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000111 stat = self.unpack_enum()
Guido van Rossumc4698fb1992-12-17 17:12:17 +0000112 if stat == MSG_DENIED:
113 stat = self.unpack_enum()
114 if stat == RPC_MISMATCH:
115 low = self.unpack_uint()
116 high = self.unpack_uint()
117 raise RuntimeError, \
118 'MSG_DENIED: RPC_MISMATCH: ' + `low, high`
119 if stat == AUTH_ERROR:
120 stat = self.unpack_uint()
121 raise RuntimeError, \
122 'MSG_DENIED: AUTH_ERROR: ' + `stat`
123 raise RuntimeError, 'MSG_DENIED: ' + `stat`
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000124 if stat <> MSG_ACCEPTED:
Guido van Rossumc4698fb1992-12-17 17:12:17 +0000125 raise RuntimeError, \
126 'Neither MSG_DENIED nor MSG_ACCEPTED: ' + `stat`
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000127 verf = self.unpack_auth()
128 stat = self.unpack_enum()
Guido van Rossum424c6731992-12-19 00:05:55 +0000129 if stat == PROG_UNAVAIL:
130 raise RuntimeError, 'call failed: PROG_UNAVAIL'
Guido van Rossumc4698fb1992-12-17 17:12:17 +0000131 if stat == PROG_MISMATCH:
132 low = self.unpack_uint()
133 high = self.unpack_uint()
134 raise RuntimeError, \
135 'call failed: PROG_MISMATCH: ' + `low, high`
Guido van Rossum424c6731992-12-19 00:05:55 +0000136 if stat == PROC_UNAVAIL:
137 raise RuntimeError, 'call failed: PROC_UNAVAIL'
138 if stat == GARBAGE_ARGS:
139 raise RuntimeError, 'call failed: GARBAGE_ARGS'
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000140 if stat <> SUCCESS:
Guido van Rossumc4698fb1992-12-17 17:12:17 +0000141 raise RuntimeError, 'call failed: ' + `stat`
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000142 return xid, verf
143 # Caller must get procedure-specific part of reply
144
145
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000146# Subroutines to create opaque authentication objects
147
148def make_auth_null():
149 return ''
150
151def make_auth_unix(seed, host, uid, gid, groups):
152 p = Packer().init()
153 p.pack_auth_unix(seed, host, uid, gid, groups)
154 return p.get_buf()
155
156def make_auth_unix_default():
Guido van Rossuma5854441992-12-17 17:31:58 +0000157 try:
158 from os import getuid, getgid
159 uid = getuid()
160 gid = getgid()
161 except ImportError:
162 uid = gid = 0
163 return make_auth_unix(0, socket.gethostname(), uid, gid, [])
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000164
165
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000166# Common base class for clients
167
168class Client:
169
170 def init(self, host, prog, vers, port, type):
171 self.host = host
172 self.prog = prog
173 self.vers = vers
174 self.port = port
175 self.type = type
176 self.sock = socket.socket(socket.AF_INET, type)
177 self.sock.connect((host, port))
178 self.lastxid = 0
179 self.addpackers()
180 self.cred = None
181 self.verf = None
182 return self
183
184 def Null(self): # Procedure 0 is always like this
185 self.start_call(0)
186 self.do_call(0)
187 self.end_call()
188
189 def close(self):
190 self.sock.close()
191
192 # Functions that may be overridden by specific derived classes
193
194 def addpackers(self):
195 self.packer = Packer().init()
196 self.unpacker = Unpacker().init('')
197
198 def mkcred(self, proc):
199 if self.cred == None:
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000200 self.cred = (AUTH_NULL, make_auth_null())
201 return self.cred
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000202
203 def mkverf(self, proc):
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000204 if self.verf == None:
205 self.verf = (AUTH_NULL, make_auth_null())
206 return self.verf
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000207
208
209# Record-Marking standard support
210
211def sendfrag(sock, last, frag):
212 x = len(frag)
213 if last: x = x | 0x80000000L
214 header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
215 chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
216 sock.send(header + frag)
217
218def sendrecord(sock, record):
219 sendfrag(sock, 1, record)
220
221def recvfrag(sock):
222 header = sock.recv(4)
Guido van Rossum424c6731992-12-19 00:05:55 +0000223 if len(header) < 4:
224 raise EOFError
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000225 x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \
226 ord(header[2])<<8 | ord(header[3])
227 last = ((x & 0x80000000) != 0)
228 n = int(x & 0x7fffffff)
229 frag = ''
230 while n > 0:
231 buf = sock.recv(n)
232 if not buf: raise EOFError
233 n = n - len(buf)
234 frag = frag + buf
235 return last, frag
236
237def recvrecord(sock):
238 record = ''
239 last = 0
240 while not last:
241 last, frag = recvfrag(sock)
242 record = record + frag
243 return record
244
245
246# Raw TCP-based client
247
248class RawTCPClient(Client):
249
250 def init(self, host, prog, vers, port):
251 return Client.init(self, host, prog, vers, port, \
252 socket.SOCK_STREAM)
253
254 def start_call(self, proc):
255 self.lastxid = xid = self.lastxid + 1
256 cred = self.mkcred(proc)
257 verf = self.mkverf(proc)
258 p = self.packer
259 p.reset()
260 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
261
262 def do_call(self, *rest):
263 # rest is used for UDP buffer size; ignored for TCP
264 call = self.packer.get_buf()
265 sendrecord(self.sock, call)
266 reply = recvrecord(self.sock)
267 u = self.unpacker
268 u.reset(reply)
269 xid, verf = u.unpack_replyheader()
270 if xid <> self.lastxid:
271 # Can't really happen since this is TCP...
272 raise RuntimeError, 'wrong xid in reply ' + `xid` + \
273 ' instead of ' + `self.lastxid`
274
275 def end_call(self):
276 self.unpacker.done()
277
278
279# Raw UDP-based client
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000280
281class RawUDPClient(Client):
282
283 def init(self, host, prog, vers, port):
284 return Client.init(self, host, prog, vers, port, \
285 socket.SOCK_DGRAM)
286
287 def start_call(self, proc):
288 self.lastxid = xid = self.lastxid + 1
289 cred = self.mkcred(proc)
290 verf = self.mkverf(proc)
291 p = self.packer
292 p.reset()
293 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
294
295 def do_call(self, *rest):
Guido van Rossuma5854441992-12-17 17:31:58 +0000296 try:
297 from select import select
298 except ImportError:
299 print 'WARNING: select not found, RPC may hang'
300 select = None
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000301 if len(rest) == 0:
302 bufsize = 8192
303 elif len(rest) > 1:
304 raise TypeError, 'too many args'
305 else:
306 bufsize = rest[0] + 512
307 call = self.packer.get_buf()
Guido van Rossum16b22191992-12-15 21:44:31 +0000308 timeout = 1
309 count = 5
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000310 self.sock.send(call)
Guido van Rossum16b22191992-12-15 21:44:31 +0000311 while 1:
Guido van Rossuma5854441992-12-17 17:31:58 +0000312 r, w, x = [self.sock], [], []
313 if select:
314 r, w, x = select(r, w, x, timeout)
Guido van Rossum16b22191992-12-15 21:44:31 +0000315 if self.sock not in r:
316 count = count - 1
317 if count < 0: raise RuntimeError, 'timeout'
318 if timeout < 25: timeout = timeout *2
319 print 'RESEND', timeout, count
320 self.sock.send(call)
321 continue
322 reply = self.sock.recv(bufsize)
323 u = self.unpacker
324 u.reset(reply)
325 xid, verf = u.unpack_replyheader()
326 if xid <> self.lastxid:
327 print 'BAD xid'
328 continue
329 break
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000330
331 def end_call(self):
332 self.unpacker.done()
333
334
335# Port mapper interface
336
337PMAP_PORT = 111
338PMAP_PROG = 100000
339PMAP_VERS = 2
340PMAPPROC_NULL = 0 # (void) -> void
341PMAPPROC_SET = 1 # (mapping) -> bool
342PMAPPROC_UNSET = 2 # (mapping) -> bool
343PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int
344PMAPPROC_DUMP = 4 # (void) -> pmaplist
345PMAPPROC_CALLIT = 5 # (call_args) -> call_result
346
347# A mapping is (prog, vers, prot, port) and prot is one of:
348
349IPPROTO_TCP = 6
350IPPROTO_UDP = 17
351
352# A pmaplist is a variable-length list of mappings, as follows:
353# either (1, mapping, pmaplist) or (0).
354
355# A call_args is (prog, vers, proc, args) where args is opaque;
356# a call_result is (port, res) where res is opaque.
357
358
359class PortMapperPacker(Packer):
360
361 def pack_mapping(self, mapping):
362 prog, vers, prot, port = mapping
363 self.pack_uint(prog)
364 self.pack_uint(vers)
365 self.pack_uint(prot)
366 self.pack_uint(port)
367
368 def pack_pmaplist(self, list):
369 self.pack_list(list, self.pack_mapping)
370
371
372class PortMapperUnpacker(Unpacker):
373
374 def unpack_mapping(self):
375 prog = self.unpack_uint()
376 vers = self.unpack_uint()
377 prot = self.unpack_uint()
378 port = self.unpack_uint()
379 return prog, vers, prot, port
380
381 def unpack_pmaplist(self):
382 return self.unpack_list(self.unpack_mapping)
383
384
385class PartialPortMapperClient:
386
387 def addpackers(self):
388 self.packer = PortMapperPacker().init()
389 self.unpacker = PortMapperUnpacker().init('')
390
Guido van Rossum424c6731992-12-19 00:05:55 +0000391 def Set(self, mapping):
392 self.start_call(PMAPPROC_SET)
393 self.packer.pack_mapping(mapping)
394 self.do_call()
395 res = self.unpacker.unpack_uint()
396 self.end_call()
397 return res
398
399 def Unset(self, mapping):
400 self.start_call(PMAPPROC_UNSET)
401 self.packer.pack_mapping(mapping)
402 self.do_call()
403 res = self.unpacker.unpack_uint()
404 self.end_call()
405 return res
406
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000407 def Getport(self, mapping):
408 self.start_call(PMAPPROC_GETPORT)
409 self.packer.pack_mapping(mapping)
410 self.do_call(4)
411 port = self.unpacker.unpack_uint()
412 self.end_call()
413 return port
414
415 def Dump(self):
416 self.start_call(PMAPPROC_DUMP)
417 self.do_call(8192-512)
418 list = self.unpacker.unpack_pmaplist()
419 self.end_call()
420 return list
421
422
423class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
424
425 def init(self, host):
426 return RawTCPClient.init(self, \
427 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
428
429
430class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
431
432 def init(self, host):
433 return RawUDPClient.init(self, \
434 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
435
436
437class TCPClient(RawTCPClient):
438
439 def init(self, host, prog, vers):
440 pmap = TCPPortMapperClient().init(host)
441 port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
Guido van Rossum424c6731992-12-19 00:05:55 +0000442 if port == 0:
443 raise RuntimeError, 'program not registered'
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000444 pmap.close()
445 return RawTCPClient.init(self, host, prog, vers, port)
446
447
448class UDPClient(RawUDPClient):
449
450 def init(self, host, prog, vers):
451 pmap = UDPPortMapperClient().init(host)
452 port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
453 pmap.close()
454 return RawUDPClient.init(self, host, prog, vers, port)
455
456
Guido van Rossum424c6731992-12-19 00:05:55 +0000457# Server classes
458
459class Server:
460
461 def init(self, host, prog, vers, port, type):
462 self.host = host # Should normally be '' for default interface
463 self.prog = prog
464 self.vers = vers
465 self.port = port # Should normally be 0 for random port
466 self.type = type # SOCK_STREAM or SOCK_DGRAM
467 self.sock = socket.socket(socket.AF_INET, type)
468 self.sock.bind((host, port))
469 self.host, self.port = self.sock.getsockname()
470 self.addpackers()
471 return self
472
473 def register(self):
474 if self.type == socket.SOCK_STREAM:
475 type = IPPROTO_TCP
476 elif self.type == socket.SOCK_DGRAM:
477 type = IPPROTO_UDP
478 else:
479 raise ValueError, 'unknown protocol type'
480 mapping = self.prog, self.vers, type, self.port
481 p = TCPPortMapperClient().init(self.host)
482 if not p.Set(mapping):
483 raise RuntimeError, 'register failed'
484
485 def unregister(self):
486 if self.type == socket.SOCK_STREAM:
487 type = IPPROTO_TCP
488 elif self.type == socket.SOCK_DGRAM:
489 type = IPPROTO_UDP
490 else:
491 raise ValueError, 'unknown protocol type'
492 mapping = self.prog, self.vers, type, self.port
493 p = TCPPortMapperClient().init(self.host)
494 if not p.Unset(mapping):
495 raise RuntimeError, 'unregister failed'
496
497 def handle(self, call):
498 # Don't use unpack_header but parse the header piecewise
499 # XXX I have no idea if I am using the right error responses!
500 self.unpacker.reset(call)
501 self.packer.reset()
502 xid = self.unpacker.unpack_uint()
503 self.packer.pack_uint(xid)
504 temp = self.unpacker.unpack_enum()
505 if temp <> CALL:
506 return None # Not worthy of a reply
507 self.packer.pack_uint(REPLY)
508 temp = self.unpacker.unpack_uint()
509 if temp <> RPCVERSION:
510 self.packer.pack_uint(MSG_DENIED)
511 self.packer.pack_uint(RPC_MISMATCH)
512 self.packer.pack_uint(RPCVERSION)
513 self.packer.pack_uint(RPCVERSION)
514 return self.packer.get_buf()
515 self.packer.pack_uint(MSG_ACCEPTED)
516 self.packer.pack_auth((AUTH_NULL, make_auth_null()))
517 prog = self.unpacker.unpack_uint()
518 if prog <> self.prog:
519 self.packer.pack_uint(PROG_UNAVAIL)
520 return self.packer.get_buf()
521 vers = self.unpacker.unpack_uint()
522 if vers <> self.vers:
523 self.packer.pack_uint(PROG_MISMATCH)
524 self.packer.pack_uint(self.vers)
525 self.packer.pack_uint(self.vers)
526 return self.packer.get_buf()
527 proc = self.unpacker.unpack_uint()
528 methname = 'handle_' + `proc`
529 try:
530 meth = getattr(self, methname)
531 except AttributeError:
532 self.packer.pack_uint(PROC_UNAVAIL)
533 return self.packer.get_buf()
534 cred = self.unpacker.unpack_auth()
535 verf = self.unpacker.unpack_auth()
536 try:
537 meth() # Unpack args, call turn_around(), pack reply
538 except (EOFError, GarbageArgs):
539 # Too few or too many arguments
540 self.packer.reset()
541 self.packer.pack_uint(xid)
542 self.packer.pack_uint(REPLY)
543 self.packer.pack_uint(MSG_ACCEPTED)
544 self.packer.pack_auth((AUTH_NULL, make_auth_null()))
545 self.packer.pack_uint(GARBAGE_ARGS)
546 return self.packer.get_buf()
547
548 def turn_around(self):
549 try:
550 self.unpacker.done()
551 except RuntimeError:
552 raise GarbageArgs
553 self.packer.pack_uint(SUCCESS)
554
555 def handle_0(self): # Handle NULL message
556 self.turn_around()
557
558 # Functions that may be overridden by specific derived classes
559
560 def addpackers(self):
561 self.packer = Packer().init()
562 self.unpacker = Unpacker().init('')
563
564
565class TCPServer(Server):
566
567 def init(self, host, prog, vers, port):
568 return Server.init(self, host, prog, vers, port, \
569 socket.SOCK_STREAM)
570
571 def loop(self):
572 self.sock.listen(0)
573 while 1:
574 self.session(self.sock.accept())
575
576 def session(self, connection):
577 sock, (host, port) = connection
578 while 1:
579 try:
580 call = recvrecord(sock)
581 except EOFError:
582 break
583 reply = self.handle(call)
584 if reply <> None:
585 sendrecord(sock, reply)
586
587
588class UDPServer(Server):
589
590 def init(self, host, prog, vers, port):
591 return Server.init(self, host, prog, vers, port, \
592 socket.SOCK_DGRAM)
593
594 def loop(self):
595 while 1:
596 session()
597
598 def session(self):
599 call, host_port = self.sock.recvfrom(8192)
600 reply = self.handle(call)
601 if reply <> None:
602 self.sock.sendto(reply, host_port)
603
604
605# Simple test program -- dump local portmapper status
606
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000607def test():
608 import T
609 T.TSTART()
610 pmap = UDPPortMapperClient().init('')
611 T.TSTOP()
612 pmap.Null()
613 T.TSTOP()
614 list = pmap.Dump()
615 T.TSTOP()
616 list.sort()
617 for prog, vers, prot, port in list:
618 print prog, vers,
619 if prot == IPPROTO_TCP: print 'tcp',
620 elif prot == IPPROTO_UDP: print 'udp',
621 else: print prot,
622 print port
Guido van Rossum424c6731992-12-19 00:05:55 +0000623
624
625# Server and client test program.
626# On machine A: python -c 'import rpc; rpc.testsvr()'
627# On machine B: python -c 'import rpc; rpc.testclt()' A
628# (A may be == B)
629
630def testsvr():
631 # Simple test class -- proc 1 doubles its string argument as reply
632 class S(TCPServer):
633 def handle_1(self):
634 arg = self.unpacker.unpack_string()
635 self.turn_around()
636 print 'RPC function 1 called, arg', `arg`
637 self.packer.pack_string(arg + arg)
638 #
639 s = S().init('', 0x20000000, 1, 0)
640 try:
641 s.unregister()
642 except RuntimeError, msg:
643 print 'RuntimeError:', msg, '(ignored)'
644 s.register()
645 print 'Service started...'
646 try:
647 s.loop()
648 finally:
649 s.unregister()
650 print 'Service interrupted.'
651
652
653def testclt():
654 import sys
655 if sys.argv[1:]: host = sys.argv[1]
656 else: host = ''
657 # Client for above server
658 class C(TCPClient):
659 def call_1(self, arg):
660 self.start_call(1)
661 self.packer.pack_string(arg)
662 self.do_call()
663 reply = self.unpacker.unpack_string()
664 self.end_call()
665 return reply
666 c = C().init(host, 0x20000000, 1)
667 print 'making call...'
668 reply = c.call_1('hello, world, ')
669 print 'call returned', `reply`