1992-12-21 10:32:06 -04:00
|
|
|
# Sun RPC version 2 -- RFC1057.
|
1992-12-14 19:25:04 -04:00
|
|
|
|
1992-12-17 13:12:17 -04:00
|
|
|
# XXX There should be separate exceptions for the various reasons why
|
|
|
|
# XXX an RPC can fail, rather than using RuntimeError for everything
|
|
|
|
|
2005-04-10 13:21:07 -03:00
|
|
|
# XXX Need to use class based exceptions rather than string exceptions
|
|
|
|
|
1992-12-17 13:12:17 -04:00
|
|
|
# XXX The UDP version of the protocol resends requests when it does
|
|
|
|
# XXX not receive a timely reply -- use only for idempotent calls!
|
|
|
|
|
1992-12-20 10:56:32 -04:00
|
|
|
# XXX There is no provision for call timeout on TCP connections
|
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
import xdr
|
|
|
|
import socket
|
|
|
|
import os
|
|
|
|
|
|
|
|
RPCVERSION = 2
|
|
|
|
|
|
|
|
CALL = 0
|
|
|
|
REPLY = 1
|
|
|
|
|
|
|
|
AUTH_NULL = 0
|
|
|
|
AUTH_UNIX = 1
|
|
|
|
AUTH_SHORT = 2
|
|
|
|
AUTH_DES = 3
|
|
|
|
|
|
|
|
MSG_ACCEPTED = 0
|
|
|
|
MSG_DENIED = 1
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
SUCCESS = 0 # RPC executed successfully
|
|
|
|
PROG_UNAVAIL = 1 # remote hasn't exported program
|
|
|
|
PROG_MISMATCH = 2 # remote can't support version #
|
|
|
|
PROC_UNAVAIL = 3 # program can't support procedure
|
|
|
|
GARBAGE_ARGS = 4 # procedure can't decode params
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
RPC_MISMATCH = 0 # RPC version number != 2
|
|
|
|
AUTH_ERROR = 1 # remote can't authenticate caller
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
AUTH_BADCRED = 1 # bad credentials (seal broken)
|
|
|
|
AUTH_REJECTEDCRED = 2 # client must begin new session
|
|
|
|
AUTH_BADVERF = 3 # bad verifier (seal broken)
|
|
|
|
AUTH_REJECTEDVERF = 4 # verifier expired or replayed
|
|
|
|
AUTH_TOOWEAK = 5 # rejected for security reasons
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
|
|
|
class Packer(xdr.Packer):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def pack_auth(self, auth):
|
|
|
|
flavor, stuff = auth
|
|
|
|
self.pack_enum(flavor)
|
|
|
|
self.pack_opaque(stuff)
|
|
|
|
|
|
|
|
def pack_auth_unix(self, stamp, machinename, uid, gid, gids):
|
|
|
|
self.pack_uint(stamp)
|
|
|
|
self.pack_string(machinename)
|
|
|
|
self.pack_uint(uid)
|
|
|
|
self.pack_uint(gid)
|
|
|
|
self.pack_uint(len(gids))
|
|
|
|
for i in gids:
|
|
|
|
self.pack_uint(i)
|
|
|
|
|
|
|
|
def pack_callheader(self, xid, prog, vers, proc, cred, verf):
|
|
|
|
self.pack_uint(xid)
|
|
|
|
self.pack_enum(CALL)
|
|
|
|
self.pack_uint(RPCVERSION)
|
|
|
|
self.pack_uint(prog)
|
|
|
|
self.pack_uint(vers)
|
|
|
|
self.pack_uint(proc)
|
|
|
|
self.pack_auth(cred)
|
|
|
|
self.pack_auth(verf)
|
|
|
|
# Caller must add procedure-specific part of call
|
|
|
|
|
|
|
|
def pack_replyheader(self, xid, verf):
|
|
|
|
self.pack_uint(xid)
|
|
|
|
self.pack_enum(REPLY)
|
|
|
|
self.pack_uint(MSG_ACCEPTED)
|
|
|
|
self.pack_auth(verf)
|
|
|
|
self.pack_enum(SUCCESS)
|
|
|
|
# Caller must add procedure-specific part of reply
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
1992-12-18 20:05:55 -04:00
|
|
|
# Exceptions
|
|
|
|
BadRPCFormat = 'rpc.BadRPCFormat'
|
|
|
|
BadRPCVersion = 'rpc.BadRPCVersion'
|
|
|
|
GarbageArgs = 'rpc.GarbageArgs'
|
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
class Unpacker(xdr.Unpacker):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def unpack_auth(self):
|
|
|
|
flavor = self.unpack_enum()
|
|
|
|
stuff = self.unpack_opaque()
|
|
|
|
return (flavor, stuff)
|
|
|
|
|
|
|
|
def unpack_callheader(self):
|
2005-04-10 13:21:07 -03:00
|
|
|
xid = self.unpack_uint()
|
2004-07-18 02:56:09 -03:00
|
|
|
temp = self.unpack_enum()
|
2005-04-10 13:21:07 -03:00
|
|
|
if temp != CALL:
|
2004-07-18 02:56:09 -03:00
|
|
|
raise BadRPCFormat, 'no CALL but %r' % (temp,)
|
|
|
|
temp = self.unpack_uint()
|
2005-04-10 13:21:07 -03:00
|
|
|
if temp != RPCVERSION:
|
|
|
|
raise BadRPCVersion, 'bad RPC version %r' % (temp,)
|
2004-07-18 02:56:09 -03:00
|
|
|
prog = self.unpack_uint()
|
|
|
|
vers = self.unpack_uint()
|
|
|
|
proc = self.unpack_uint()
|
|
|
|
cred = self.unpack_auth()
|
|
|
|
verf = self.unpack_auth()
|
|
|
|
return xid, prog, vers, proc, cred, verf
|
|
|
|
# Caller must add procedure-specific part of call
|
|
|
|
|
|
|
|
def unpack_replyheader(self):
|
|
|
|
xid = self.unpack_uint()
|
|
|
|
mtype = self.unpack_enum()
|
2005-04-10 13:21:07 -03:00
|
|
|
if mtype != REPLY:
|
2004-07-18 02:56:09 -03:00
|
|
|
raise RuntimeError, 'no REPLY but %r' % (mtype,)
|
|
|
|
stat = self.unpack_enum()
|
|
|
|
if stat == MSG_DENIED:
|
|
|
|
stat = self.unpack_enum()
|
|
|
|
if stat == RPC_MISMATCH:
|
|
|
|
low = self.unpack_uint()
|
|
|
|
high = self.unpack_uint()
|
|
|
|
raise RuntimeError, \
|
|
|
|
'MSG_DENIED: RPC_MISMATCH: %r' % ((low, high),)
|
|
|
|
if stat == AUTH_ERROR:
|
|
|
|
stat = self.unpack_uint()
|
|
|
|
raise RuntimeError, \
|
|
|
|
'MSG_DENIED: AUTH_ERROR: %r' % (stat,)
|
|
|
|
raise RuntimeError, 'MSG_DENIED: %r' % (stat,)
|
2005-04-10 13:21:07 -03:00
|
|
|
if stat != MSG_ACCEPTED:
|
2004-07-18 02:56:09 -03:00
|
|
|
raise RuntimeError, \
|
|
|
|
'Neither MSG_DENIED nor MSG_ACCEPTED: %r' % (stat,)
|
|
|
|
verf = self.unpack_auth()
|
|
|
|
stat = self.unpack_enum()
|
|
|
|
if stat == PROG_UNAVAIL:
|
|
|
|
raise RuntimeError, 'call failed: PROG_UNAVAIL'
|
|
|
|
if stat == PROG_MISMATCH:
|
|
|
|
low = self.unpack_uint()
|
|
|
|
high = self.unpack_uint()
|
|
|
|
raise RuntimeError, \
|
|
|
|
'call failed: PROG_MISMATCH: %r' % ((low, high),)
|
|
|
|
if stat == PROC_UNAVAIL:
|
|
|
|
raise RuntimeError, 'call failed: PROC_UNAVAIL'
|
|
|
|
if stat == GARBAGE_ARGS:
|
|
|
|
raise RuntimeError, 'call failed: GARBAGE_ARGS'
|
2005-04-10 13:21:07 -03:00
|
|
|
if stat != SUCCESS:
|
2004-07-18 02:56:09 -03:00
|
|
|
raise RuntimeError, 'call failed: %r' % (stat,)
|
|
|
|
return xid, verf
|
|
|
|
# Caller must get procedure-specific part of reply
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
1992-12-15 16:52:53 -04:00
|
|
|
# Subroutines to create opaque authentication objects
|
|
|
|
|
|
|
|
def make_auth_null():
|
2004-07-18 02:56:09 -03:00
|
|
|
return ''
|
1992-12-15 16:52:53 -04:00
|
|
|
|
|
|
|
def make_auth_unix(seed, host, uid, gid, groups):
|
2004-07-18 02:56:09 -03:00
|
|
|
p = Packer()
|
|
|
|
p.pack_auth_unix(seed, host, uid, gid, groups)
|
|
|
|
return p.get_buf()
|
1992-12-15 16:52:53 -04:00
|
|
|
|
|
|
|
def make_auth_unix_default():
|
2004-07-18 02:56:09 -03:00
|
|
|
try:
|
|
|
|
from os import getuid, getgid
|
|
|
|
uid = getuid()
|
|
|
|
gid = getgid()
|
|
|
|
except ImportError:
|
|
|
|
uid = gid = 0
|
|
|
|
import time
|
|
|
|
return make_auth_unix(int(time.time()-unix_epoch()), \
|
|
|
|
socket.gethostname(), uid, gid, [])
|
1992-12-15 16:52:53 -04:00
|
|
|
|
1996-07-20 23:09:54 -03:00
|
|
|
_unix_epoch = -1
|
|
|
|
def unix_epoch():
|
|
|
|
"""Very painful calculation of when the Unix Epoch is.
|
|
|
|
|
|
|
|
This is defined as the return value of time.time() on Jan 1st,
|
|
|
|
1970, 00:00:00 GMT.
|
|
|
|
|
|
|
|
On a Unix system, this should always return 0.0. On a Mac, the
|
|
|
|
calculations are needed -- and hard because of integer overflow
|
|
|
|
and other limitations.
|
|
|
|
|
|
|
|
"""
|
|
|
|
global _unix_epoch
|
|
|
|
if _unix_epoch >= 0: return _unix_epoch
|
|
|
|
import time
|
|
|
|
now = time.time()
|
2004-07-18 02:56:09 -03:00
|
|
|
localt = time.localtime(now) # (y, m, d, hh, mm, ss, ..., ..., ...)
|
1996-07-20 23:09:54 -03:00
|
|
|
gmt = time.gmtime(now)
|
|
|
|
offset = time.mktime(localt) - time.mktime(gmt)
|
|
|
|
y, m, d, hh, mm, ss = 1970, 1, 1, 0, 0, 0
|
|
|
|
offset, ss = divmod(ss + offset, 60)
|
|
|
|
offset, mm = divmod(mm + offset, 60)
|
|
|
|
offset, hh = divmod(hh + offset, 24)
|
|
|
|
d = d + offset
|
|
|
|
_unix_epoch = time.mktime((y, m, d, hh, mm, ss, 0, 0, 0))
|
|
|
|
print "Unix epoch:", time.ctime(_unix_epoch)
|
|
|
|
return _unix_epoch
|
|
|
|
|
1992-12-15 16:52:53 -04:00
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
# Common base class for clients
|
|
|
|
|
|
|
|
class Client:
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, host, prog, vers, port):
|
|
|
|
self.host = host
|
|
|
|
self.prog = prog
|
|
|
|
self.vers = vers
|
|
|
|
self.port = port
|
|
|
|
self.makesocket() # Assigns to self.sock
|
|
|
|
self.bindsocket()
|
|
|
|
self.connsocket()
|
|
|
|
self.lastxid = 0 # XXX should be more random?
|
|
|
|
self.addpackers()
|
|
|
|
self.cred = None
|
|
|
|
self.verf = None
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.sock.close()
|
|
|
|
|
|
|
|
def makesocket(self):
|
|
|
|
# This MUST be overridden
|
|
|
|
raise RuntimeError, 'makesocket not defined'
|
|
|
|
|
|
|
|
def connsocket(self):
|
|
|
|
# Override this if you don't want/need a connection
|
|
|
|
self.sock.connect((self.host, self.port))
|
|
|
|
|
|
|
|
def bindsocket(self):
|
|
|
|
# Override this to bind to a different port (e.g. reserved)
|
|
|
|
self.sock.bind(('', 0))
|
|
|
|
|
|
|
|
def addpackers(self):
|
|
|
|
# Override this to use derived classes from Packer/Unpacker
|
|
|
|
self.packer = Packer()
|
|
|
|
self.unpacker = Unpacker('')
|
|
|
|
|
|
|
|
def make_call(self, proc, args, pack_func, unpack_func):
|
|
|
|
# Don't normally override this (but see Broadcast)
|
|
|
|
if pack_func is None and args is not None:
|
|
|
|
raise TypeError, 'non-null args with null pack_func'
|
|
|
|
self.start_call(proc)
|
|
|
|
if pack_func:
|
|
|
|
pack_func(args)
|
|
|
|
self.do_call()
|
|
|
|
if unpack_func:
|
|
|
|
result = unpack_func()
|
|
|
|
else:
|
|
|
|
result = None
|
|
|
|
self.unpacker.done()
|
|
|
|
return result
|
|
|
|
|
|
|
|
def start_call(self, proc):
|
|
|
|
# Don't override this
|
|
|
|
self.lastxid = xid = self.lastxid + 1
|
|
|
|
cred = self.mkcred()
|
|
|
|
verf = self.mkverf()
|
|
|
|
p = self.packer
|
|
|
|
p.reset()
|
|
|
|
p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
|
|
|
|
|
|
|
|
def do_call(self):
|
|
|
|
# This MUST be overridden
|
|
|
|
raise RuntimeError, 'do_call not defined'
|
|
|
|
|
|
|
|
def mkcred(self):
|
|
|
|
# Override this to use more powerful credentials
|
2008-03-29 12:24:25 -03:00
|
|
|
if self.cred is None:
|
2004-07-18 02:56:09 -03:00
|
|
|
self.cred = (AUTH_NULL, make_auth_null())
|
|
|
|
return self.cred
|
|
|
|
|
|
|
|
def mkverf(self):
|
|
|
|
# Override this to use a more powerful verifier
|
2008-03-29 12:24:25 -03:00
|
|
|
if self.verf is None:
|
2004-07-18 02:56:09 -03:00
|
|
|
self.verf = (AUTH_NULL, make_auth_null())
|
|
|
|
return self.verf
|
|
|
|
|
|
|
|
def call_0(self): # Procedure 0 is always like this
|
|
|
|
return self.make_call(0, None, None, None)
|
1992-12-20 10:56:32 -04:00
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
# Record-Marking standard support
|
|
|
|
|
|
|
|
def sendfrag(sock, last, frag):
|
2004-07-18 02:56:09 -03:00
|
|
|
x = len(frag)
|
|
|
|
if last: x = x | 0x80000000L
|
|
|
|
header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
|
|
|
|
chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
|
|
|
|
sock.send(header + frag)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
def sendrecord(sock, record):
|
2004-07-18 02:56:09 -03:00
|
|
|
sendfrag(sock, 1, record)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
def recvfrag(sock):
|
2004-07-18 02:56:09 -03:00
|
|
|
header = sock.recv(4)
|
|
|
|
if len(header) < 4:
|
|
|
|
raise EOFError
|
|
|
|
x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \
|
|
|
|
ord(header[2])<<8 | ord(header[3])
|
|
|
|
last = ((x & 0x80000000) != 0)
|
|
|
|
n = int(x & 0x7fffffff)
|
|
|
|
frag = ''
|
|
|
|
while n > 0:
|
|
|
|
buf = sock.recv(n)
|
|
|
|
if not buf: raise EOFError
|
|
|
|
n = n - len(buf)
|
|
|
|
frag = frag + buf
|
|
|
|
return last, frag
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
def recvrecord(sock):
|
2004-07-18 02:56:09 -03:00
|
|
|
record = ''
|
|
|
|
last = 0
|
|
|
|
while not last:
|
|
|
|
last, frag = recvfrag(sock)
|
|
|
|
record = record + frag
|
|
|
|
return record
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
1992-12-20 10:56:32 -04:00
|
|
|
# Try to bind to a reserved port (must be root)
|
|
|
|
|
|
|
|
last_resv_port_tried = None
|
|
|
|
def bindresvport(sock, host):
|
2004-07-18 02:56:09 -03:00
|
|
|
global last_resv_port_tried
|
|
|
|
FIRST, LAST = 600, 1024 # Range of ports to try
|
2008-03-29 12:24:25 -03:00
|
|
|
if last_resv_port_tried is None:
|
2004-07-18 02:56:09 -03:00
|
|
|
import os
|
|
|
|
last_resv_port_tried = FIRST + os.getpid() % (LAST-FIRST)
|
|
|
|
for i in range(last_resv_port_tried, LAST) + \
|
|
|
|
range(FIRST, last_resv_port_tried):
|
|
|
|
last_resv_port_tried = i
|
|
|
|
try:
|
|
|
|
sock.bind((host, i))
|
|
|
|
return last_resv_port_tried
|
|
|
|
except socket.error, (errno, msg):
|
2005-04-10 13:21:07 -03:00
|
|
|
if errno != 114:
|
2004-07-18 02:56:09 -03:00
|
|
|
raise socket.error, (errno, msg)
|
|
|
|
raise RuntimeError, 'can\'t assign reserved port'
|
1992-12-20 10:56:32 -04:00
|
|
|
|
|
|
|
|
1992-12-21 10:32:06 -04:00
|
|
|
# Client using TCP to a specific port
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
class RawTCPClient(Client):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def makesocket(self):
|
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def do_call(self):
|
|
|
|
call = self.packer.get_buf()
|
|
|
|
sendrecord(self.sock, call)
|
|
|
|
reply = recvrecord(self.sock)
|
|
|
|
u = self.unpacker
|
|
|
|
u.reset(reply)
|
|
|
|
xid, verf = u.unpack_replyheader()
|
2005-04-10 13:21:07 -03:00
|
|
|
if xid != self.lastxid:
|
2004-07-18 02:56:09 -03:00
|
|
|
# Can't really happen since this is TCP...
|
|
|
|
raise RuntimeError, 'wrong xid in reply %r instead of %r' % (
|
|
|
|
xid, self.lastxid)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
1992-12-21 10:32:06 -04:00
|
|
|
# Client using UDP to a specific port
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
class RawUDPClient(Client):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def makesocket(self):
|
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
|
|
|
|
def do_call(self):
|
|
|
|
call = self.packer.get_buf()
|
|
|
|
self.sock.send(call)
|
|
|
|
try:
|
|
|
|
from select import select
|
|
|
|
except ImportError:
|
|
|
|
print 'WARNING: select not found, RPC may hang'
|
|
|
|
select = None
|
|
|
|
BUFSIZE = 8192 # Max UDP buffer size
|
|
|
|
timeout = 1
|
|
|
|
count = 5
|
|
|
|
while 1:
|
|
|
|
r, w, x = [self.sock], [], []
|
|
|
|
if select:
|
|
|
|
r, w, x = select(r, w, x, timeout)
|
|
|
|
if self.sock not in r:
|
|
|
|
count = count - 1
|
|
|
|
if count < 0: raise RuntimeError, 'timeout'
|
|
|
|
if timeout < 25: timeout = timeout *2
|
|
|
|
## print 'RESEND', timeout, count
|
|
|
|
self.sock.send(call)
|
|
|
|
continue
|
|
|
|
reply = self.sock.recv(BUFSIZE)
|
|
|
|
u = self.unpacker
|
|
|
|
u.reset(reply)
|
|
|
|
xid, verf = u.unpack_replyheader()
|
2005-04-10 13:21:07 -03:00
|
|
|
if xid != self.lastxid:
|
2004-07-18 02:56:09 -03:00
|
|
|
## print 'BAD xid'
|
|
|
|
continue
|
|
|
|
break
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
1992-12-21 10:32:06 -04:00
|
|
|
# Client using UDP broadcast to a specific port
|
1992-12-14 19:25:04 -04:00
|
|
|
|
1992-12-21 10:32:06 -04:00
|
|
|
class RawBroadcastUDPClient(RawUDPClient):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, bcastaddr, prog, vers, port):
|
|
|
|
RawUDPClient.__init__(self, bcastaddr, prog, vers, port)
|
|
|
|
self.reply_handler = None
|
|
|
|
self.timeout = 30
|
|
|
|
|
|
|
|
def connsocket(self):
|
|
|
|
# Don't connect -- use sendto
|
|
|
|
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
|
|
|
|
|
|
def set_reply_handler(self, reply_handler):
|
|
|
|
self.reply_handler = reply_handler
|
|
|
|
|
|
|
|
def set_timeout(self, timeout):
|
|
|
|
self.timeout = timeout # Use None for infinite timeout
|
|
|
|
|
|
|
|
def make_call(self, proc, args, pack_func, unpack_func):
|
|
|
|
if pack_func is None and args is not None:
|
|
|
|
raise TypeError, 'non-null args with null pack_func'
|
|
|
|
self.start_call(proc)
|
|
|
|
if pack_func:
|
|
|
|
pack_func(args)
|
|
|
|
call = self.packer.get_buf()
|
|
|
|
self.sock.sendto(call, (self.host, self.port))
|
|
|
|
try:
|
|
|
|
from select import select
|
|
|
|
except ImportError:
|
|
|
|
print 'WARNING: select not found, broadcast will hang'
|
|
|
|
select = None
|
|
|
|
BUFSIZE = 8192 # Max UDP buffer size (for reply)
|
|
|
|
replies = []
|
|
|
|
if unpack_func is None:
|
|
|
|
def dummy(): pass
|
|
|
|
unpack_func = dummy
|
|
|
|
while 1:
|
|
|
|
r, w, x = [self.sock], [], []
|
|
|
|
if select:
|
|
|
|
if self.timeout is None:
|
|
|
|
r, w, x = select(r, w, x)
|
|
|
|
else:
|
|
|
|
r, w, x = select(r, w, x, self.timeout)
|
|
|
|
if self.sock not in r:
|
|
|
|
break
|
|
|
|
reply, fromaddr = self.sock.recvfrom(BUFSIZE)
|
|
|
|
u = self.unpacker
|
|
|
|
u.reset(reply)
|
|
|
|
xid, verf = u.unpack_replyheader()
|
2005-04-10 13:21:07 -03:00
|
|
|
if xid != self.lastxid:
|
2004-07-18 02:56:09 -03:00
|
|
|
## print 'BAD xid'
|
|
|
|
continue
|
|
|
|
reply = unpack_func()
|
|
|
|
self.unpacker.done()
|
|
|
|
replies.append((reply, fromaddr))
|
|
|
|
if self.reply_handler:
|
|
|
|
self.reply_handler(reply, fromaddr)
|
|
|
|
return replies
|
1992-12-21 10:32:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
# Port mapper interface
|
1992-12-20 10:56:32 -04:00
|
|
|
|
|
|
|
# Program number, version and (fixed!) port number
|
1992-12-14 19:25:04 -04:00
|
|
|
PMAP_PROG = 100000
|
|
|
|
PMAP_VERS = 2
|
1992-12-20 10:56:32 -04:00
|
|
|
PMAP_PORT = 111
|
|
|
|
|
|
|
|
# Procedure numbers
|
2004-07-18 02:56:09 -03:00
|
|
|
PMAPPROC_NULL = 0 # (void) -> void
|
|
|
|
PMAPPROC_SET = 1 # (mapping) -> bool
|
|
|
|
PMAPPROC_UNSET = 2 # (mapping) -> bool
|
|
|
|
PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int
|
|
|
|
PMAPPROC_DUMP = 4 # (void) -> pmaplist
|
|
|
|
PMAPPROC_CALLIT = 5 # (call_args) -> call_result
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
# A mapping is (prog, vers, prot, port) and prot is one of:
|
|
|
|
|
|
|
|
IPPROTO_TCP = 6
|
|
|
|
IPPROTO_UDP = 17
|
|
|
|
|
|
|
|
# A pmaplist is a variable-length list of mappings, as follows:
|
|
|
|
# either (1, mapping, pmaplist) or (0).
|
|
|
|
|
|
|
|
# A call_args is (prog, vers, proc, args) where args is opaque;
|
|
|
|
# a call_result is (port, res) where res is opaque.
|
|
|
|
|
|
|
|
|
|
|
|
class PortMapperPacker(Packer):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def pack_mapping(self, mapping):
|
|
|
|
prog, vers, prot, port = mapping
|
|
|
|
self.pack_uint(prog)
|
|
|
|
self.pack_uint(vers)
|
|
|
|
self.pack_uint(prot)
|
|
|
|
self.pack_uint(port)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def pack_pmaplist(self, list):
|
|
|
|
self.pack_list(list, self.pack_mapping)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def pack_call_args(self, ca):
|
|
|
|
prog, vers, proc, args = ca
|
|
|
|
self.pack_uint(prog)
|
|
|
|
self.pack_uint(vers)
|
|
|
|
self.pack_uint(proc)
|
|
|
|
self.pack_opaque(args)
|
1992-12-21 10:32:06 -04:00
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
class PortMapperUnpacker(Unpacker):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def unpack_mapping(self):
|
|
|
|
prog = self.unpack_uint()
|
|
|
|
vers = self.unpack_uint()
|
|
|
|
prot = self.unpack_uint()
|
|
|
|
port = self.unpack_uint()
|
|
|
|
return prog, vers, prot, port
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def unpack_pmaplist(self):
|
|
|
|
return self.unpack_list(self.unpack_mapping)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def unpack_call_result(self):
|
|
|
|
port = self.unpack_uint()
|
|
|
|
res = self.unpack_opaque()
|
|
|
|
return port, res
|
1992-12-21 10:32:06 -04:00
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
class PartialPortMapperClient:
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def addpackers(self):
|
|
|
|
self.packer = PortMapperPacker()
|
|
|
|
self.unpacker = PortMapperUnpacker('')
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def Set(self, mapping):
|
|
|
|
return self.make_call(PMAPPROC_SET, mapping, \
|
|
|
|
self.packer.pack_mapping, \
|
|
|
|
self.unpacker.unpack_uint)
|
1992-12-18 20:05:55 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def Unset(self, mapping):
|
|
|
|
return self.make_call(PMAPPROC_UNSET, mapping, \
|
|
|
|
self.packer.pack_mapping, \
|
|
|
|
self.unpacker.unpack_uint)
|
1992-12-18 20:05:55 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def Getport(self, mapping):
|
|
|
|
return self.make_call(PMAPPROC_GETPORT, mapping, \
|
|
|
|
self.packer.pack_mapping, \
|
|
|
|
self.unpacker.unpack_uint)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def Dump(self):
|
|
|
|
return self.make_call(PMAPPROC_DUMP, None, \
|
|
|
|
None, \
|
|
|
|
self.unpacker.unpack_pmaplist)
|
1992-12-21 10:32:06 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def Callit(self, ca):
|
|
|
|
return self.make_call(PMAPPROC_CALLIT, ca, \
|
|
|
|
self.packer.pack_call_args, \
|
|
|
|
self.unpacker.unpack_call_result)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
|
|
|
class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, host):
|
|
|
|
RawTCPClient.__init__(self, \
|
|
|
|
host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
|
|
|
class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, host):
|
|
|
|
RawUDPClient.__init__(self, \
|
|
|
|
host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
1992-12-21 10:32:06 -04:00
|
|
|
class BroadcastUDPPortMapperClient(PartialPortMapperClient, \
|
2004-07-18 02:56:09 -03:00
|
|
|
RawBroadcastUDPClient):
|
1992-12-21 10:32:06 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, bcastaddr):
|
|
|
|
RawBroadcastUDPClient.__init__(self, \
|
|
|
|
bcastaddr, PMAP_PROG, PMAP_VERS, PMAP_PORT)
|
1992-12-21 10:32:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
# Generic clients that find their server through the Port mapper
|
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
class TCPClient(RawTCPClient):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, host, prog, vers):
|
|
|
|
pmap = TCPPortMapperClient(host)
|
|
|
|
port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
|
|
|
|
pmap.close()
|
|
|
|
if port == 0:
|
|
|
|
raise RuntimeError, 'program not registered'
|
|
|
|
RawTCPClient.__init__(self, host, prog, vers, port)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
|
|
|
class UDPClient(RawUDPClient):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, host, prog, vers):
|
|
|
|
pmap = UDPPortMapperClient(host)
|
|
|
|
port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
|
|
|
|
pmap.close()
|
|
|
|
if port == 0:
|
|
|
|
raise RuntimeError, 'program not registered'
|
|
|
|
RawUDPClient.__init__(self, host, prog, vers, port)
|
1992-12-14 19:25:04 -04:00
|
|
|
|
|
|
|
|
1992-12-21 10:32:06 -04:00
|
|
|
class BroadcastUDPClient(Client):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, bcastaddr, prog, vers):
|
|
|
|
self.pmap = BroadcastUDPPortMapperClient(bcastaddr)
|
|
|
|
self.pmap.set_reply_handler(self.my_reply_handler)
|
|
|
|
self.prog = prog
|
|
|
|
self.vers = vers
|
|
|
|
self.user_reply_handler = None
|
|
|
|
self.addpackers()
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.pmap.close()
|
|
|
|
|
|
|
|
def set_reply_handler(self, reply_handler):
|
|
|
|
self.user_reply_handler = reply_handler
|
|
|
|
|
|
|
|
def set_timeout(self, timeout):
|
|
|
|
self.pmap.set_timeout(timeout)
|
|
|
|
|
|
|
|
def my_reply_handler(self, reply, fromaddr):
|
|
|
|
port, res = reply
|
|
|
|
self.unpacker.reset(res)
|
|
|
|
result = self.unpack_func()
|
|
|
|
self.unpacker.done()
|
|
|
|
self.replies.append((result, fromaddr))
|
|
|
|
if self.user_reply_handler is not None:
|
|
|
|
self.user_reply_handler(result, fromaddr)
|
|
|
|
|
|
|
|
def make_call(self, proc, args, pack_func, unpack_func):
|
|
|
|
self.packer.reset()
|
|
|
|
if pack_func:
|
|
|
|
pack_func(args)
|
|
|
|
if unpack_func is None:
|
|
|
|
def dummy(): pass
|
|
|
|
self.unpack_func = dummy
|
|
|
|
else:
|
|
|
|
self.unpack_func = unpack_func
|
|
|
|
self.replies = []
|
|
|
|
packed_args = self.packer.get_buf()
|
|
|
|
dummy_replies = self.pmap.Callit( \
|
|
|
|
(self.prog, self.vers, proc, packed_args))
|
|
|
|
return self.replies
|
1992-12-21 10:32:06 -04:00
|
|
|
|
|
|
|
|
1992-12-18 20:05:55 -04:00
|
|
|
# Server classes
|
|
|
|
|
1992-12-20 10:56:32 -04:00
|
|
|
# These are not symmetric to the Client classes
|
|
|
|
# XXX No attempt is made to provide authorization hooks yet
|
|
|
|
|
1992-12-18 20:05:55 -04:00
|
|
|
class Server:
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def __init__(self, host, prog, vers, port):
|
|
|
|
self.host = host # Should normally be '' for default interface
|
|
|
|
self.prog = prog
|
|
|
|
self.vers = vers
|
|
|
|
self.port = port # Should normally be 0 for random port
|
|
|
|
self.makesocket() # Assigns to self.sock and self.prot
|
|
|
|
self.bindsocket()
|
|
|
|
self.host, self.port = self.sock.getsockname()
|
|
|
|
self.addpackers()
|
|
|
|
|
|
|
|
def register(self):
|
|
|
|
mapping = self.prog, self.vers, self.prot, self.port
|
|
|
|
p = TCPPortMapperClient(self.host)
|
|
|
|
if not p.Set(mapping):
|
|
|
|
raise RuntimeError, 'register failed'
|
|
|
|
|
|
|
|
def unregister(self):
|
|
|
|
mapping = self.prog, self.vers, self.prot, self.port
|
|
|
|
p = TCPPortMapperClient(self.host)
|
|
|
|
if not p.Unset(mapping):
|
|
|
|
raise RuntimeError, 'unregister failed'
|
|
|
|
|
|
|
|
def handle(self, call):
|
|
|
|
# Don't use unpack_header but parse the header piecewise
|
|
|
|
# XXX I have no idea if I am using the right error responses!
|
|
|
|
self.unpacker.reset(call)
|
|
|
|
self.packer.reset()
|
|
|
|
xid = self.unpacker.unpack_uint()
|
|
|
|
self.packer.pack_uint(xid)
|
|
|
|
temp = self.unpacker.unpack_enum()
|
2005-04-10 13:21:07 -03:00
|
|
|
if temp != CALL:
|
2004-07-18 02:56:09 -03:00
|
|
|
return None # Not worthy of a reply
|
|
|
|
self.packer.pack_uint(REPLY)
|
|
|
|
temp = self.unpacker.unpack_uint()
|
2005-04-10 13:21:07 -03:00
|
|
|
if temp != RPCVERSION:
|
2004-07-18 02:56:09 -03:00
|
|
|
self.packer.pack_uint(MSG_DENIED)
|
|
|
|
self.packer.pack_uint(RPC_MISMATCH)
|
|
|
|
self.packer.pack_uint(RPCVERSION)
|
|
|
|
self.packer.pack_uint(RPCVERSION)
|
|
|
|
return self.packer.get_buf()
|
|
|
|
self.packer.pack_uint(MSG_ACCEPTED)
|
|
|
|
self.packer.pack_auth((AUTH_NULL, make_auth_null()))
|
|
|
|
prog = self.unpacker.unpack_uint()
|
2005-04-10 13:21:07 -03:00
|
|
|
if prog != self.prog:
|
2004-07-18 02:56:09 -03:00
|
|
|
self.packer.pack_uint(PROG_UNAVAIL)
|
|
|
|
return self.packer.get_buf()
|
|
|
|
vers = self.unpacker.unpack_uint()
|
2005-04-10 13:21:07 -03:00
|
|
|
if vers != self.vers:
|
2004-07-18 02:56:09 -03:00
|
|
|
self.packer.pack_uint(PROG_MISMATCH)
|
|
|
|
self.packer.pack_uint(self.vers)
|
|
|
|
self.packer.pack_uint(self.vers)
|
|
|
|
return self.packer.get_buf()
|
|
|
|
proc = self.unpacker.unpack_uint()
|
|
|
|
methname = 'handle_' + repr(proc)
|
|
|
|
try:
|
|
|
|
meth = getattr(self, methname)
|
|
|
|
except AttributeError:
|
|
|
|
self.packer.pack_uint(PROC_UNAVAIL)
|
|
|
|
return self.packer.get_buf()
|
|
|
|
cred = self.unpacker.unpack_auth()
|
|
|
|
verf = self.unpacker.unpack_auth()
|
|
|
|
try:
|
|
|
|
meth() # Unpack args, call turn_around(), pack reply
|
|
|
|
except (EOFError, GarbageArgs):
|
|
|
|
# Too few or too many arguments
|
|
|
|
self.packer.reset()
|
|
|
|
self.packer.pack_uint(xid)
|
|
|
|
self.packer.pack_uint(REPLY)
|
|
|
|
self.packer.pack_uint(MSG_ACCEPTED)
|
|
|
|
self.packer.pack_auth((AUTH_NULL, make_auth_null()))
|
|
|
|
self.packer.pack_uint(GARBAGE_ARGS)
|
|
|
|
return self.packer.get_buf()
|
|
|
|
|
|
|
|
def turn_around(self):
|
|
|
|
try:
|
|
|
|
self.unpacker.done()
|
|
|
|
except RuntimeError:
|
|
|
|
raise GarbageArgs
|
|
|
|
self.packer.pack_uint(SUCCESS)
|
|
|
|
|
|
|
|
def handle_0(self): # Handle NULL message
|
|
|
|
self.turn_around()
|
|
|
|
|
|
|
|
def makesocket(self):
|
|
|
|
# This MUST be overridden
|
|
|
|
raise RuntimeError, 'makesocket not defined'
|
|
|
|
|
|
|
|
def bindsocket(self):
|
|
|
|
# Override this to bind to a different port (e.g. reserved)
|
|
|
|
self.sock.bind((self.host, self.port))
|
|
|
|
|
|
|
|
def addpackers(self):
|
|
|
|
# Override this to use derived classes from Packer/Unpacker
|
|
|
|
self.packer = Packer()
|
|
|
|
self.unpacker = Unpacker('')
|
1992-12-18 20:05:55 -04:00
|
|
|
|
|
|
|
|
|
|
|
class TCPServer(Server):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def makesocket(self):
|
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
self.prot = IPPROTO_TCP
|
|
|
|
|
|
|
|
def loop(self):
|
|
|
|
self.sock.listen(0)
|
|
|
|
while 1:
|
|
|
|
self.session(self.sock.accept())
|
|
|
|
|
|
|
|
def session(self, connection):
|
|
|
|
sock, (host, port) = connection
|
|
|
|
while 1:
|
|
|
|
try:
|
|
|
|
call = recvrecord(sock)
|
|
|
|
except EOFError:
|
|
|
|
break
|
|
|
|
except socket.error, msg:
|
|
|
|
print 'socket error:', msg
|
|
|
|
break
|
|
|
|
reply = self.handle(call)
|
|
|
|
if reply is not None:
|
|
|
|
sendrecord(sock, reply)
|
|
|
|
|
|
|
|
def forkingloop(self):
|
|
|
|
# Like loop but uses forksession()
|
|
|
|
self.sock.listen(0)
|
|
|
|
while 1:
|
|
|
|
self.forksession(self.sock.accept())
|
|
|
|
|
|
|
|
def forksession(self, connection):
|
|
|
|
# Like session but forks off a subprocess
|
|
|
|
import os
|
|
|
|
# Wait for deceased children
|
|
|
|
try:
|
|
|
|
while 1:
|
|
|
|
pid, sts = os.waitpid(0, 1)
|
|
|
|
except os.error:
|
|
|
|
pass
|
|
|
|
pid = None
|
|
|
|
try:
|
|
|
|
pid = os.fork()
|
|
|
|
if pid: # Parent
|
|
|
|
connection[0].close()
|
|
|
|
return
|
|
|
|
# Child
|
|
|
|
self.session(connection)
|
|
|
|
finally:
|
|
|
|
# Make sure we don't fall through in the parent
|
|
|
|
if pid == 0:
|
|
|
|
os._exit(0)
|
1993-12-17 10:32:26 -04:00
|
|
|
|
1992-12-18 20:05:55 -04:00
|
|
|
|
|
|
|
class UDPServer(Server):
|
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def makesocket(self):
|
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
self.prot = IPPROTO_UDP
|
1992-12-18 20:05:55 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def loop(self):
|
|
|
|
while 1:
|
|
|
|
self.session()
|
1992-12-18 20:05:55 -04:00
|
|
|
|
2004-07-18 02:56:09 -03:00
|
|
|
def session(self):
|
|
|
|
call, host_port = self.sock.recvfrom(8192)
|
|
|
|
reply = self.handle(call)
|
2008-03-29 12:24:25 -03:00
|
|
|
if reply is not None:
|
2004-07-18 02:56:09 -03:00
|
|
|
self.sock.sendto(reply, host_port)
|
1992-12-18 20:05:55 -04:00
|
|
|
|
|
|
|
|
|
|
|
# Simple test program -- dump local portmapper status
|
|
|
|
|
1992-12-14 19:25:04 -04:00
|
|
|
def test():
|
2004-07-18 02:56:09 -03:00
|
|
|
pmap = UDPPortMapperClient('')
|
|
|
|
list = pmap.Dump()
|
|
|
|
list.sort()
|
|
|
|
for prog, vers, prot, port in list:
|
|
|
|
print prog, vers,
|
|
|
|
if prot == IPPROTO_TCP: print 'tcp',
|
|
|
|
elif prot == IPPROTO_UDP: print 'udp',
|
|
|
|
else: print prot,
|
|
|
|
print port
|
1992-12-18 20:05:55 -04:00
|
|
|
|
|
|
|
|
1992-12-21 10:32:06 -04:00
|
|
|
# Test program for broadcast operation -- dump everybody's portmapper status
|
|
|
|
|
|
|
|
def testbcast():
|
2004-07-18 02:56:09 -03:00
|
|
|
import sys
|
|
|
|
if sys.argv[1:]:
|
|
|
|
bcastaddr = sys.argv[1]
|
|
|
|
else:
|
|
|
|
bcastaddr = '<broadcast>'
|
|
|
|
def rh(reply, fromaddr):
|
|
|
|
host, port = fromaddr
|
|
|
|
print host + '\t' + repr(reply)
|
|
|
|
pmap = BroadcastUDPPortMapperClient(bcastaddr)
|
|
|
|
pmap.set_reply_handler(rh)
|
|
|
|
pmap.set_timeout(5)
|
|
|
|
replies = pmap.Getport((100002, 1, IPPROTO_UDP, 0))
|
1992-12-21 10:32:06 -04:00
|
|
|
|
|
|
|
|
|
|
|
# Test program for server, with corresponding client
|
1992-12-18 20:05:55 -04:00
|
|
|
# On machine A: python -c 'import rpc; rpc.testsvr()'
|
|
|
|
# On machine B: python -c 'import rpc; rpc.testclt()' A
|
|
|
|
# (A may be == B)
|
|
|
|
|
|
|
|
def testsvr():
|
2004-07-18 02:56:09 -03:00
|
|
|
# Simple test class -- proc 1 doubles its string argument as reply
|
|
|
|
class S(UDPServer):
|
|
|
|
def handle_1(self):
|
|
|
|
arg = self.unpacker.unpack_string()
|
|
|
|
self.turn_around()
|
|
|
|
print 'RPC function 1 called, arg', repr(arg)
|
|
|
|
self.packer.pack_string(arg + arg)
|
|
|
|
#
|
|
|
|
s = S('', 0x20000000, 1, 0)
|
|
|
|
try:
|
|
|
|
s.unregister()
|
|
|
|
except RuntimeError, msg:
|
|
|
|
print 'RuntimeError:', msg, '(ignored)'
|
|
|
|
s.register()
|
|
|
|
print 'Service started...'
|
|
|
|
try:
|
|
|
|
s.loop()
|
|
|
|
finally:
|
|
|
|
s.unregister()
|
|
|
|
print 'Service interrupted.'
|
1992-12-18 20:05:55 -04:00
|
|
|
|
|
|
|
|
|
|
|
def testclt():
|
2004-07-18 02:56:09 -03:00
|
|
|
import sys
|
|
|
|
if sys.argv[1:]: host = sys.argv[1]
|
|
|
|
else: host = ''
|
|
|
|
# Client for above server
|
|
|
|
class C(UDPClient):
|
|
|
|
def call_1(self, arg):
|
|
|
|
return self.make_call(1, arg, \
|
|
|
|
self.packer.pack_string, \
|
|
|
|
self.unpacker.unpack_string)
|
|
|
|
c = C(host, 0x20000000, 1)
|
|
|
|
print 'making call...'
|
|
|
|
reply = c.call_1('hello, world, ')
|
|
|
|
print 'call returned', repr(reply)
|