19
+ − 1 "A an RPC module, with optimizations for VXI-11 rpc calls"
+ − 2 _rcsid="$Id: rpc.py 323 2011-04-06 19:10:03Z marcus $"
+ − 3
+ − 4 #this module is 99% from the python distribution, Demo/rpc/rpc.py, with a few modifications
+ − 5 #by Marcus Mendenhall to improve timeout handling, etc. It probably should follow any licensing
+ − 6 #intent of whoever originated it.
+ − 7
+ − 8 # Sun RPC version 2 -- RFC1057.
+ − 9
+ − 10 # XXX There should be separate exceptions for the various reasons why
+ − 11 # XXX an RPC can fail, rather than using RuntimeError for everything
+ − 12
+ − 13 # XXX The UDP version of the protocol resends requests when it does
+ − 14 # XXX not receive a timely reply -- use only for idempotent calls!
+ − 15
+ − 16 # XXX There is no provision for call timeout on TCP connections
+ − 17 # Now there is, on receives. 2003.02.20 Marcus Mendenhall, Vanderbilt University marcus.h.mendenhall@vanderbilt.edu
+ − 18
+ − 19 import xdrlib as xdr
+ − 20 import socket
+ − 21 import os
+ − 22 from exceptions import *
+ − 23
+ − 24 RPCVERSION = 2
+ − 25
+ − 26 CALL = 0
+ − 27 REPLY = 1
+ − 28
+ − 29 AUTH_NULL = 0
+ − 30 AUTH_UNIX = 1
+ − 31 AUTH_SHORT = 2
+ − 32 AUTH_DES = 3
+ − 33
+ − 34 MSG_ACCEPTED = 0
+ − 35 MSG_DENIED = 1
+ − 36
+ − 37 SUCCESS = 0 # RPC executed successfully
+ − 38 PROG_UNAVAIL = 1 # remote hasn't exported program
+ − 39 PROG_MISMATCH = 2 # remote can't support version #
+ − 40 PROC_UNAVAIL = 3 # program can't support procedure
+ − 41 GARBAGE_ARGS = 4 # procedure can't decode params
+ − 42
+ − 43 RPC_MISMATCH = 0 # RPC version number != 2
+ − 44 AUTH_ERROR = 1 # remote can't authenticate caller
+ − 45
+ − 46 AUTH_BADCRED = 1 # bad credentials (seal broken)
+ − 47 AUTH_REJECTEDCRED = 2 # client must begin new session
+ − 48 AUTH_BADVERF = 3 # bad verifier (seal broken)
+ − 49 AUTH_REJECTEDVERF = 4 # verifier expired or replayed
+ − 50 AUTH_TOOWEAK = 5 # rejected for security reasons
+ − 51
+ − 52
+ − 53 class Packer(xdr.Packer):
+ − 54
+ − 55 def pack_auth(self, auth):
+ − 56 flavor, stuff = auth
+ − 57 self.pack_enum(flavor)
+ − 58 self.pack_opaque(stuff)
+ − 59
+ − 60 def pack_auth_unix(self, stamp, machinename, uid, gid, gids):
+ − 61 self.pack_uint(stamp)
+ − 62 self.pack_string(machinename)
+ − 63 self.pack_uint(uid)
+ − 64 self.pack_uint(gid)
+ − 65 self.pack_uint(len(gids))
+ − 66 for i in gids:
+ − 67 self.pack_uint(i)
+ − 68
+ − 69 def pack_callheader(self, xid, prog, vers, proc, cred, verf):
+ − 70 self.pack_uint(xid)
+ − 71 self.pack_enum(CALL)
+ − 72 self.pack_uint(RPCVERSION)
+ − 73 self.pack_uint(prog)
+ − 74 self.pack_uint(vers)
+ − 75 self.pack_uint(proc)
+ − 76 self.pack_auth(cred)
+ − 77 self.pack_auth(verf)
+ − 78 # Caller must add procedure-specific part of call
+ − 79
+ − 80 def pack_replyheader(self, xid, verf):
+ − 81 self.pack_uint(xid)
+ − 82 self.pack_enum(REPLY)
+ − 83 self.pack_uint(MSG_ACCEPTED)
+ − 84 self.pack_auth(verf)
+ − 85 self.pack_enum(SUCCESS)
+ − 86 # Caller must add procedure-specific part of reply
+ − 87
+ − 88
+ − 89 # Exceptions
+ − 90 BadRPCFormat = 'rpc.BadRPCFormat'
+ − 91 BadRPCVersion = 'rpc.BadRPCVersion'
+ − 92 GarbageArgs = 'rpc.GarbageArgs'
+ − 93
+ − 94 class Unpacker(xdr.Unpacker):
+ − 95
+ − 96 def unpack_auth(self):
+ − 97 flavor = self.unpack_enum()
+ − 98 stuff = self.unpack_opaque()
+ − 99 return (flavor, stuff)
+ − 100
+ − 101 def unpack_callheader(self):
+ − 102 xid = self.unpack_uint(xid)
+ − 103 temp = self.unpack_enum()
+ − 104 if temp <> CALL:
+ − 105 raise BadRPCFormat, 'no CALL but ' + `temp`
+ − 106 temp = self.unpack_uint()
+ − 107 if temp <> RPCVERSION:
+ − 108 raise BadRPCVerspion, 'bad RPC version ' + `temp`
+ − 109 prog = self.unpack_uint()
+ − 110 vers = self.unpack_uint()
+ − 111 proc = self.unpack_uint()
+ − 112 cred = self.unpack_auth()
+ − 113 verf = self.unpack_auth()
+ − 114 return xid, prog, vers, proc, cred, verf
+ − 115 # Caller must add procedure-specific part of call
+ − 116
+ − 117 def unpack_replyheader(self):
+ − 118 xid = self.unpack_uint()
+ − 119 mtype = self.unpack_enum()
+ − 120 if mtype <> REPLY:
+ − 121 raise RuntimeError, 'no REPLY but ' + `mtype`
+ − 122 stat = self.unpack_enum()
+ − 123 if stat == MSG_DENIED:
+ − 124 stat = self.unpack_enum()
+ − 125 if stat == RPC_MISMATCH:
+ − 126 low = self.unpack_uint()
+ − 127 high = self.unpack_uint()
+ − 128 raise RuntimeError, \
+ − 129 'MSG_DENIED: RPC_MISMATCH: ' + `low, high`
+ − 130 if stat == AUTH_ERROR:
+ − 131 stat = self.unpack_uint()
+ − 132 raise RuntimeError, \
+ − 133 'MSG_DENIED: AUTH_ERROR: ' + `stat`
+ − 134 raise RuntimeError, 'MSG_DENIED: ' + `stat`
+ − 135 if stat <> MSG_ACCEPTED:
+ − 136 raise RuntimeError, \
+ − 137 'Neither MSG_DENIED nor MSG_ACCEPTED: ' + `stat`
+ − 138 verf = self.unpack_auth()
+ − 139 stat = self.unpack_enum()
+ − 140 if stat == PROG_UNAVAIL:
+ − 141 raise RuntimeError, 'call failed: PROG_UNAVAIL'
+ − 142 if stat == PROG_MISMATCH:
+ − 143 low = self.unpack_uint()
+ − 144 high = self.unpack_uint()
+ − 145 raise RuntimeError, \
+ − 146 'call failed: PROG_MISMATCH: ' + `low, high`
+ − 147 if stat == PROC_UNAVAIL:
+ − 148 raise RuntimeError, 'call failed: PROC_UNAVAIL'
+ − 149 if stat == GARBAGE_ARGS:
+ − 150 raise RuntimeError, 'call failed: GARBAGE_ARGS'
+ − 151 if stat <> SUCCESS:
+ − 152 raise RuntimeError, 'call failed: ' + `stat`
+ − 153 return xid, verf
+ − 154 # Caller must get procedure-specific part of reply
+ − 155
+ − 156
+ − 157 # Subroutines to create opaque authentication objects
+ − 158
+ − 159 def make_auth_null():
+ − 160 return ''
+ − 161
+ − 162 def make_auth_unix(seed, host, uid, gid, groups):
+ − 163 p = Packer()
+ − 164 p.pack_auth_unix(seed, host, uid, gid, groups)
+ − 165 return p.get_buf()
+ − 166
+ − 167 def make_auth_unix_default():
+ − 168 try:
+ − 169 from os import getuid, getgid
+ − 170 uid = getuid()
+ − 171 gid = getgid()
+ − 172 except ImportError:
+ − 173 uid = gid = 0
+ − 174 import time
+ − 175 return make_auth_unix(int(time.time()-unix_epoch()), \
+ − 176 socket.gethostname(), uid, gid, [])
+ − 177
+ − 178 _unix_epoch = -1
+ − 179 def unix_epoch():
+ − 180 """Very painful calculation of when the Unix Epoch is.
+ − 181
+ − 182 This is defined as the return value of time.time() on Jan 1st,
+ − 183 1970, 00:00:00 GMT.
+ − 184
+ − 185 On a Unix system, this should always return 0.0. On a Mac, the
+ − 186 calculations are needed -- and hard because of integer overflow
+ − 187 and other limitations.
+ − 188
+ − 189 """
+ − 190 global _unix_epoch
+ − 191 if _unix_epoch >= 0: return _unix_epoch
+ − 192 import time
+ − 193 now = time.time()
+ − 194 localt = time.localtime(now) # (y, m, d, hh, mm, ss, ..., ..., ...)
+ − 195 gmt = time.gmtime(now)
+ − 196 offset = time.mktime(localt) - time.mktime(gmt)
+ − 197 y, m, d, hh, mm, ss = 1970, 1, 1, 0, 0, 0
+ − 198 offset, ss = divmod(ss + offset, 60)
+ − 199 offset, mm = divmod(mm + offset, 60)
+ − 200 offset, hh = divmod(hh + offset, 24)
+ − 201 d = d + offset
+ − 202 _unix_epoch = time.mktime((y, m, d, hh, mm, ss, 0, 0, 0))
+ − 203 print "Unix epoch:", time.ctime(_unix_epoch)
+ − 204 return _unix_epoch
+ − 205
+ − 206
+ − 207 # Common base class for clients
+ − 208
+ − 209 class Client:
+ − 210
+ − 211 def __init__(self, host, prog, vers, port):
+ − 212 self.host = host
+ − 213 self.prog = prog
+ − 214 self.vers = vers
+ − 215 self.port = port
+ − 216 self.makesocket() # Assigns to self.sock
+ − 217 self.bindsocket()
+ − 218 self.connsocket()
+ − 219 self.lastxid = 0 # XXX should be more random?
+ − 220 self.addpackers()
+ − 221 self.cred = None
+ − 222 self.verf = None
+ − 223
+ − 224 def close(self):
+ − 225 self.sock.close()
+ − 226
+ − 227 def makesocket(self):
+ − 228 # This MUST be overridden
+ − 229 raise RuntimeError, 'makesocket not defined'
+ − 230
+ − 231 def connsocket(self):
+ − 232 # Override this if you don't want/need a connection
+ − 233 self.sock.connect((self.host, self.port))
+ − 234
+ − 235 def bindsocket(self):
+ − 236 # Override this to bind to a different port (e.g. reserved)
+ − 237 self.sock.bind(('', 0))
+ − 238
+ − 239 def addpackers(self):
+ − 240 # Override this to use derived classes from Packer/Unpacker
+ − 241 self.packer = Packer()
+ − 242 self.unpacker = Unpacker('')
+ − 243
+ − 244 def make_call(self, proc, args, pack_func, unpack_func):
+ − 245 #print "make_call() args = " + str(args)
+ − 246 # Don't normally override this (but see Broadcast)
+ − 247 if pack_func is None and args is not None:
+ − 248 raise TypeError, 'non-null args with null pack_func'
+ − 249 #print "packed args"
+ − 250 self.start_call(proc)
+ − 251 if pack_func:
+ − 252 pack_func(args)
+ − 253 self.do_call()
+ − 254 if unpack_func:
+ − 255 result = unpack_func()
+ − 256 else:
+ − 257 result = None
+ − 258 #print "result = " + str(result)
+ − 259 self.unpacker.done()
+ − 260 return result
+ − 261
+ − 262 def start_call(self, proc):
+ − 263 # Don't override this
+ − 264 self.lastxid = xid = self.lastxid + 1
+ − 265 cred = self.mkcred()
+ − 266 verf = self.mkverf()
+ − 267 p = self.packer
+ − 268 p.reset()
+ − 269 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
+ − 270
+ − 271 def do_call(self):
+ − 272 # This MUST be overridden
+ − 273 raise RuntimeError, 'do_call not defined'
+ − 274
+ − 275 def mkcred(self):
+ − 276 # Override this to use more powerful credentials
+ − 277 if self.cred == None:
+ − 278 self.cred = (AUTH_NULL, make_auth_null())
+ − 279 return self.cred
+ − 280
+ − 281 def mkverf(self):
+ − 282 # Override this to use a more powerful verifier
+ − 283 if self.verf == None:
+ − 284 self.verf = (AUTH_NULL, make_auth_null())
+ − 285 return self.verf
+ − 286
+ − 287 def call_0(self): # Procedure 0 is always like this
+ − 288 return self.make_call(0, None, None, None)
+ − 289
+ − 290
+ − 291 # Record-Marking standard support
+ − 292
+ − 293 try:
+ − 294 from select import select as _select
+ − 295 except:
+ − 296 _select=None
+ − 297
+ − 298 def sendfrag_with_timeout(sock, last, frag, timeout_seconds=None):
+ − 299 x = len(frag)
+ − 300 if last: x = x | 0x80000000L
+ − 301 header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
+ − 302 chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
+ − 303 block=header+frag
+ − 304 n=len(block)
+ − 305 nsent=0
+ − 306 while(nsent<n):
+ − 307 if _select and timeout_seconds:
+ − 308 rlist, wlist, xlist=_select([],[sock],[], timeout_seconds)
+ − 309 if not wlist:
+ − 310 raise EOFError, "Blocked write in sendfrag()"
+ − 311 nsent+=sock.send(block[nsent:])
+ − 312
+ − 313 def recvfrag_with_timeout(sock, timeout_seconds=None):
+ − 314 #print "receiving from ", sock
+ − 315 if _select and timeout_seconds:
+ − 316 #print "Selecting with timeout...", timeout_seconds
+ − 317 rlist, wlist, xlist=_select([sock],[],[], timeout_seconds)
+ − 318 if not rlist:
+ − 319 raise EOFError, "No header at all in recvfrag()"
+ − 320
+ − 321 header = sock.recv(4)
+ − 322 if len(header) < 4:
+ − 323 raise EOFError
+ − 324 x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \
+ − 325 ord(header[2])<<8 | ord(header[3])
+ − 326 last = ((x & 0x80000000L) != 0)
+ − 327 n = int(x & 0x7fffffff)
+ − 328
+ − 329 frag=''
+ − 330
+ − 331 while(len(frag) < n):
+ − 332 if _select and timeout_seconds:
+ − 333 #print "Selecting with timeout...", timeout_seconds
+ − 334 rlist, wlist, xlist=_select([sock],[],[], timeout_seconds)
+ − 335 if not rlist:
+ − 336 raise EOFError, "No data after header in recvfrag()"
+ − 337 frag += sock.recv(n-len(frag))
+ − 338
+ − 339 return last, frag
+ − 340
+ − 341
+ − 342 def recvrecord(sock, timeout_seconds=None):
+ − 343 record = ''
+ − 344 last = 0
+ − 345 while not last:
+ − 346 last, frag = recvfrag_with_timeout(sock, timeout_seconds)
+ − 347 record = record + frag
+ − 348 return record
+ − 349
+ − 350 def sendrecord(sock, record, timeout_seconds=None):
+ − 351 sendfrag_with_timeout(sock, 1, record, timeout_seconds)
+ − 352
+ − 353
+ − 354
+ − 355 # Try to bind to a reserved port (must be root)
+ − 356
+ − 357 last_resv_port_tried = None
+ − 358 def bindresvport(sock, host):
+ − 359 global last_resv_port_tried
+ − 360 FIRST, LAST = 600, 1024 # Range of ports to try
+ − 361 if last_resv_port_tried == None:
+ − 362 import os
+ − 363 last_resv_port_tried = FIRST + os.getpid() % (LAST-FIRST)
+ − 364 for i in range(last_resv_port_tried, LAST) + \
+ − 365 range(FIRST, last_resv_port_tried):
+ − 366 last_resv_port_tried = i
+ − 367 try:
+ − 368 sock.bind((host, i))
+ − 369 return last_resv_port_tried
+ − 370 except socket.error, (errno, msg):
+ − 371 if errno <> 114:
+ − 372 raise socket.error, (errno, msg)
+ − 373 raise RuntimeError, 'can\'t assign reserved port'
+ − 374
+ − 375
+ − 376 # Client using TCP to a specific port
+ − 377
+ − 378 class RawTCPClient(Client):
+ − 379
+ − 380 select_timeout_seconds=None
+ − 381
+ − 382 def makesocket(self):
+ − 383 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ − 384
+ − 385 def do_call(self):
+ − 386 call = self.packer.get_buf()
+ − 387 sendrecord(self.sock, call, self.select_timeout_seconds)
+ − 388 reply = recvrecord(self.sock, self.select_timeout_seconds)
+ − 389 u = self.unpacker
+ − 390 u.reset(reply)
+ − 391 xid, verf = u.unpack_replyheader()
+ − 392 if xid <> self.lastxid:
+ − 393 # Can't really happen since this is TCP...
+ − 394 raise RuntimeError, 'wrong xid in reply ' + `xid` + \
+ − 395 ' instead of ' + `self.lastxid`
+ − 396
+ − 397
+ − 398 # Client using UDP to a specific port
+ − 399
+ − 400 class RawUDPClient(Client):
+ − 401
+ − 402 def makesocket(self):
+ − 403 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ − 404
+ − 405 def do_call(self):
+ − 406 call = self.packer.get_buf()
+ − 407 self.sock.send(call)
+ − 408 try:
+ − 409 from select import select
+ − 410 except ImportError:
+ − 411 print 'WARNING: select not found, RPC may hang'
+ − 412 select = None
+ − 413 BUFSIZE = 8192 # Max UDP buffer size
+ − 414 timeout = 1
+ − 415 count = 5
+ − 416 while 1:
+ − 417 r, w, x = [self.sock], [], []
+ − 418 if select:
+ − 419 r, w, x = select(r, w, x, timeout)
+ − 420 if self.sock not in r:
+ − 421 count = count - 1
+ − 422 if count < 0: raise RuntimeError, 'timeout'
+ − 423 if timeout < 25: timeout = timeout *2
+ − 424 ## print 'RESEND', timeout, count
+ − 425 self.sock.send(call)
+ − 426 continue
+ − 427 reply = self.sock.recv(BUFSIZE)
+ − 428 u = self.unpacker
+ − 429 u.reset(reply)
+ − 430 xid, verf = u.unpack_replyheader()
+ − 431 if xid <> self.lastxid:
+ − 432 ## print 'BAD xid'
+ − 433 continue
+ − 434 break
+ − 435
+ − 436
+ − 437 # Client using UDP broadcast to a specific port
+ − 438
+ − 439 class RawBroadcastUDPClient(RawUDPClient):
+ − 440
+ − 441 def __init__(self, bcastaddr, prog, vers, port):
+ − 442 RawUDPClient.__init__(self, bcastaddr, prog, vers, port)
+ − 443 self.reply_handler = None
+ − 444 self.timeout = 30
+ − 445
+ − 446 def connsocket(self):
+ − 447 # Don't connect -- use sendto
+ − 448 self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ − 449
+ − 450 def set_reply_handler(self, reply_handler):
+ − 451 self.reply_handler = reply_handler
+ − 452
+ − 453 def set_timeout(self, timeout):
+ − 454 self.timeout = timeout # Use None for infinite timeout
+ − 455
+ − 456 def make_call(self, proc, args, pack_func, unpack_func):
+ − 457 if pack_func is None and args is not None:
+ − 458 raise TypeError, 'non-null args with null pack_func'
+ − 459 self.start_call(proc)
+ − 460 if pack_func:
+ − 461 pack_func(args)
+ − 462 call = self.packer.get_buf()
+ − 463 self.sock.sendto(call, (self.host, self.port))
+ − 464 try:
+ − 465 from select import select
+ − 466 except ImportError:
+ − 467 print 'WARNING: select not found, broadcast will hang'
+ − 468 select = None
+ − 469 BUFSIZE = 8192 # Max UDP buffer size (for reply)
+ − 470 replies = []
+ − 471 if unpack_func is None:
+ − 472 def dummy(): pass
+ − 473 unpack_func = dummy
+ − 474 while 1:
+ − 475 r, w, x = [self.sock], [], []
+ − 476 if select:
+ − 477 if self.timeout is None:
+ − 478 r, w, x = select(r, w, x)
+ − 479 else:
+ − 480 r, w, x = select(r, w, x, self.timeout)
+ − 481 if self.sock not in r:
+ − 482 break
+ − 483 reply, fromaddr = self.sock.recvfrom(BUFSIZE)
+ − 484 u = self.unpacker
+ − 485 u.reset(reply)
+ − 486 xid, verf = u.unpack_replyheader()
+ − 487 if xid <> self.lastxid:
+ − 488 ## print 'BAD xid'
+ − 489 continue
+ − 490 reply = unpack_func()
+ − 491 self.unpacker.done()
+ − 492 replies.append((reply, fromaddr))
+ − 493 if self.reply_handler:
+ − 494 self.reply_handler(reply, fromaddr)
+ − 495 return replies
+ − 496
+ − 497
+ − 498 # Port mapper interface
+ − 499
+ − 500 # Program number, version and (fixed!) port number
+ − 501 PMAP_PROG = 100000
+ − 502 PMAP_VERS = 2
+ − 503 PMAP_PORT = 111
+ − 504
+ − 505 # Procedure numbers
+ − 506 PMAPPROC_NULL = 0 # (void) -> void
+ − 507 PMAPPROC_SET = 1 # (mapping) -> bool
+ − 508 PMAPPROC_UNSET = 2 # (mapping) -> bool
+ − 509 PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int
+ − 510 PMAPPROC_DUMP = 4 # (void) -> pmaplist
+ − 511 PMAPPROC_CALLIT = 5 # (call_args) -> call_result
+ − 512
+ − 513 # A mapping is (prog, vers, prot, port) and prot is one of:
+ − 514
+ − 515 IPPROTO_TCP = 6
+ − 516 IPPROTO_UDP = 17
+ − 517
+ − 518 # A pmaplist is a variable-length list of mappings, as follows:
+ − 519 # either (1, mapping, pmaplist) or (0).
+ − 520
+ − 521 # A call_args is (prog, vers, proc, args) where args is opaque;
+ − 522 # a call_result is (port, res) where res is opaque.
+ − 523
+ − 524
+ − 525 class PortMapperPacker(Packer):
+ − 526
+ − 527 def pack_mapping(self, mapping):
+ − 528 prog, vers, prot, port = mapping
+ − 529 self.pack_uint(prog)
+ − 530 self.pack_uint(vers)
+ − 531 self.pack_uint(prot)
+ − 532 self.pack_uint(port)
+ − 533
+ − 534 def pack_pmaplist(self, list):
+ − 535 self.pack_list(list, self.pack_mapping)
+ − 536
+ − 537 def pack_call_args(self, ca):
+ − 538 prog, vers, proc, args = ca
+ − 539 self.pack_uint(prog)
+ − 540 self.pack_uint(vers)
+ − 541 self.pack_uint(proc)
+ − 542 self.pack_opaque(args)
+ − 543
+ − 544
+ − 545 class PortMapperUnpacker(Unpacker):
+ − 546
+ − 547 def unpack_mapping(self):
+ − 548 prog = self.unpack_uint()
+ − 549 vers = self.unpack_uint()
+ − 550 prot = self.unpack_uint()
+ − 551 port = self.unpack_uint()
+ − 552 return prog, vers, prot, port
+ − 553
+ − 554 def unpack_pmaplist(self):
+ − 555 return self.unpack_list(self.unpack_mapping)
+ − 556
+ − 557 def unpack_call_result(self):
+ − 558 port = self.unpack_uint()
+ − 559 res = self.unpack_opaque()
+ − 560 return port, res
+ − 561
+ − 562
+ − 563 class PartialPortMapperClient:
+ − 564
+ − 565 def addpackers(self):
+ − 566 self.packer = PortMapperPacker()
+ − 567 self.unpacker = PortMapperUnpacker('')
+ − 568
+ − 569 def Set(self, mapping):
+ − 570 return self.make_call(PMAPPROC_SET, mapping, \
+ − 571 self.packer.pack_mapping, \
+ − 572 self.unpacker.unpack_uint)
+ − 573
+ − 574 def Unset(self, mapping):
+ − 575 return self.make_call(PMAPPROC_UNSET, mapping, \
+ − 576 self.packer.pack_mapping, \
+ − 577 self.unpacker.unpack_uint)
+ − 578
+ − 579 def Getport(self, mapping):
+ − 580 return self.make_call(PMAPPROC_GETPORT, mapping, \
+ − 581 self.packer.pack_mapping, \
+ − 582 self.unpacker.unpack_uint)
+ − 583
+ − 584 def Dump(self):
+ − 585 return self.make_call(PMAPPROC_DUMP, None, \
+ − 586 None, \
+ − 587 self.unpacker.unpack_pmaplist)
+ − 588
+ − 589 def Callit(self, ca):
+ − 590 return self.make_call(PMAPPROC_CALLIT, ca, \
+ − 591 self.packer.pack_call_args, \
+ − 592 self.unpacker.unpack_call_result)
+ − 593
+ − 594
+ − 595 class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
+ − 596
+ − 597 def __init__(self, host, port=PMAP_PORT, timeout_seconds=None):
+ − 598 RawTCPClient.__init__(self, \
+ − 599 host, PMAP_PROG, PMAP_VERS, port)
+ − 600 self.select_timeout_seconds=timeout_seconds
+ − 601
+ − 602
+ − 603 class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
+ − 604
+ − 605 def __init__(self, host, port=PMAP_PORT):
+ − 606 RawUDPClient.__init__(self, \
+ − 607 host, PMAP_PROG, PMAP_VERS, port)
+ − 608
+ − 609 class BroadcastUDPPortMapperClient(PartialPortMapperClient, \
+ − 610 RawBroadcastUDPClient):
+ − 611
+ − 612 def __init__(self, bcastaddr):
+ − 613 RawBroadcastUDPClient.__init__(self, \
+ − 614 bcastaddr, PMAP_PROG, PMAP_VERS, PMAP_PORT)
+ − 615
+ − 616
+ − 617 # Generic clients that find their server through the Port mapper
+ − 618
+ − 619 class TCPClient(RawTCPClient):
+ − 620
+ − 621 def __init__(self, host, prog, vers, portmap_proxy_host=None, portmap_proxy_port=PMAP_PORT, timeout_seconds=None):
+ − 622
+ − 623 self.select_timeout_seconds=timeout_seconds
+ − 624 if portmap_proxy_host is None:
+ − 625 portmap_proxy_host=host #use a proxy to get around firewalled portmappers
+ − 626 pmap = TCPPortMapperClient(portmap_proxy_host, portmap_proxy_port,timeout_seconds)
+ − 627 port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
+ − 628 pmap.close()
+ − 629 if port == 0:
+ − 630 raise RuntimeError, 'program not registered'
+ − 631 RawTCPClient.__init__(self, host, prog, vers, port)
+ − 632
+ − 633
+ − 634 class UDPClient(RawUDPClient):
+ − 635
+ − 636 def __init__(self, host, prog, vers, portmap_proxy_host=None, portmap_proxy_port=PMAP_PORT):
+ − 637 if portmap_proxy_host is None:
+ − 638 portmap_proxy_host=host #use a proxy to get around firewalled portmappers
+ − 639 pmap = UDPPortMapperClient(portmap_proxy_host, portmap_proxy_port)
+ − 640 port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
+ − 641 pmap.close()
+ − 642 if port == 0:
+ − 643 raise RuntimeError, 'program not registered'
+ − 644 RawUDPClient.__init__(self, host, prog, vers, port)
+ − 645
+ − 646
+ − 647 class BroadcastUDPClient(Client):
+ − 648
+ − 649 def __init__(self, bcastaddr, prog, vers):
+ − 650 self.pmap = BroadcastUDPPortMapperClient(bcastaddr)
+ − 651 self.pmap.set_reply_handler(self.my_reply_handler)
+ − 652 self.prog = prog
+ − 653 self.vers = vers
+ − 654 self.user_reply_handler = None
+ − 655 self.addpackers()
+ − 656
+ − 657 def close(self):
+ − 658 self.pmap.close()
+ − 659
+ − 660 def set_reply_handler(self, reply_handler):
+ − 661 self.user_reply_handler = reply_handler
+ − 662
+ − 663 def set_timeout(self, timeout):
+ − 664 self.pmap.set_timeout(timeout)
+ − 665
+ − 666 def my_reply_handler(self, reply, fromaddr):
+ − 667 port, res = reply
+ − 668 self.unpacker.reset(res)
+ − 669 result = self.unpack_func()
+ − 670 self.unpacker.done()
+ − 671 self.replies.append((result, fromaddr))
+ − 672 if self.user_reply_handler is not None:
+ − 673 self.user_reply_handler(result, fromaddr)
+ − 674
+ − 675 def make_call(self, proc, args, pack_func, unpack_func):
+ − 676 self.packer.reset()
+ − 677 if pack_func:
+ − 678 pack_func(args)
+ − 679 if unpack_func is None:
+ − 680 def dummy(): pass
+ − 681 self.unpack_func = dummy
+ − 682 else:
+ − 683 self.unpack_func = unpack_func
+ − 684 self.replies = []
+ − 685 packed_args = self.packer.get_buf()
+ − 686 dummy_replies = self.pmap.Callit( \
+ − 687 (self.prog, self.vers, proc, packed_args))
+ − 688 return self.replies
+ − 689
+ − 690
+ − 691 # Server classes
+ − 692
+ − 693 # These are not symmetric to the Client classes
+ − 694 # XXX No attempt is made to provide authorization hooks yet
+ − 695
+ − 696 class Server:
+ − 697
+ − 698 def __init__(self, host, prog, vers, port):
+ − 699 self.host = host # Should normally be '' for default interface
+ − 700 self.prog = prog
+ − 701 self.vers = vers
+ − 702 self.port = port # Should normally be 0 for random port
+ − 703 self.makesocket() # Assigns to self.sock and self.prot
+ − 704 self.bindsocket()
+ − 705 self.host, self.port = self.sock.getsockname()
+ − 706 self.addpackers()
+ − 707
+ − 708 def handle(self, call):
+ − 709 # Don't use unpack_header but parse the header piecewise
+ − 710 # XXX I have no idea if I am using the right error responses!
+ − 711 self.unpacker.reset(call)
+ − 712 self.packer.reset()
+ − 713 xid = self.unpacker.unpack_uint()
+ − 714 self.packer.pack_uint(xid)
+ − 715 temp = self.unpacker.unpack_enum()
+ − 716 if temp <> CALL:
+ − 717 return None # Not worthy of a reply
+ − 718 self.packer.pack_uint(REPLY)
+ − 719 temp = self.unpacker.unpack_uint()
+ − 720 if temp <> RPCVERSION:
+ − 721 self.packer.pack_uint(MSG_DENIED)
+ − 722 self.packer.pack_uint(RPC_MISMATCH)
+ − 723 self.packer.pack_uint(RPCVERSION)
+ − 724 self.packer.pack_uint(RPCVERSION)
+ − 725 return self.packer.get_buf()
+ − 726 self.packer.pack_uint(MSG_ACCEPTED)
+ − 727 self.packer.pack_auth((AUTH_NULL, make_auth_null()))
+ − 728 prog = self.unpacker.unpack_uint()
+ − 729 if prog <> self.prog:
+ − 730 self.packer.pack_uint(PROG_UNAVAIL)
+ − 731 return self.packer.get_buf()
+ − 732 vers = self.unpacker.unpack_uint()
+ − 733 if vers <> self.vers:
+ − 734 self.packer.pack_uint(PROG_MISMATCH)
+ − 735 self.packer.pack_uint(self.vers)
+ − 736 self.packer.pack_uint(self.vers)
+ − 737 return self.packer.get_buf()
+ − 738 proc = self.unpacker.unpack_uint()
+ − 739 methname = 'handle_' + `proc`
+ − 740 try:
+ − 741 meth = getattr(self, methname)
+ − 742 except AttributeError:
+ − 743 self.packer.pack_uint(PROC_UNAVAIL)
+ − 744 return self.packer.get_buf()
+ − 745 cred = self.unpacker.unpack_auth()
+ − 746 verf = self.unpacker.unpack_auth()
+ − 747 try:
+ − 748 meth() # Unpack args, call turn_around(), pack reply
+ − 749 except (EOFError, GarbageArgs):
+ − 750 # Too few or too many arguments
+ − 751 self.packer.reset()
+ − 752 self.packer.pack_uint(xid)
+ − 753 self.packer.pack_uint(REPLY)
+ − 754 self.packer.pack_uint(MSG_ACCEPTED)
+ − 755 self.packer.pack_auth((AUTH_NULL, make_auth_null()))
+ − 756 self.packer.pack_uint(GARBAGE_ARGS)
+ − 757 return self.packer.get_buf()
+ − 758
+ − 759 def turn_around(self):
+ − 760 try:
+ − 761 self.unpacker.done()
+ − 762 except RuntimeError:
+ − 763 raise GarbageArgs
+ − 764 self.packer.pack_uint(SUCCESS)
+ − 765
+ − 766 def handle_0(self): # Handle NULL message
+ − 767 self.turn_around()
+ − 768
+ − 769 def makesocket(self):
+ − 770 # This MUST be overridden
+ − 771 raise RuntimeError, 'makesocket not defined'
+ − 772
+ − 773 def bindsocket(self):
+ − 774 # Override this to bind to a different port (e.g. reserved)
+ − 775 self.sock.bind((self.host, self.port))
+ − 776
+ − 777 def addpackers(self):
+ − 778 # Override this to use derived classes from Packer/Unpacker
+ − 779 self.packer = Packer()
+ − 780 self.unpacker = Unpacker('')
+ − 781
+ − 782
+ − 783 class TCPServer(Server):
+ − 784
+ − 785 def register(self):
+ − 786 mapping = self.prog, self.vers, self.prot, self.port
+ − 787 p = TCPPortMapperClient(self.host)
+ − 788 if not p.Set(mapping):
+ − 789 raise RuntimeError, 'register failed'
+ − 790
+ − 791 def unregister(self):
+ − 792 mapping = self.prog, self.vers, self.prot, self.port
+ − 793 p = TCPPortMapperClient(self.host)
+ − 794 if not p.Unset(mapping):
+ − 795 raise RuntimeError, 'unregister failed'
+ − 796
+ − 797 def makesocket(self):
+ − 798 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ − 799 self.prot = IPPROTO_TCP
+ − 800
+ − 801 def loop(self):
+ − 802 self.sock.listen(0)
+ − 803 while 1:
+ − 804 self.session(self.sock.accept())
+ − 805
+ − 806 def session(self, connection):
+ − 807 sock, (host, port) = connection
+ − 808 while 1:
+ − 809 try:
+ − 810 call = recvrecord(sock)
+ − 811 except EOFError:
+ − 812 break
+ − 813 except socket.error, msg:
+ − 814 print 'socket error:', msg
+ − 815 break
+ − 816 reply = self.handle(call)
+ − 817 if reply is not None:
+ − 818 sendrecord(sock, reply)
+ − 819
+ − 820 def forkingloop(self):
+ − 821 # Like loop but uses forksession()
+ − 822 self.sock.listen(0)
+ − 823 while 1:
+ − 824 self.forksession(self.sock.accept())
+ − 825
+ − 826 def forksession(self, connection):
+ − 827 # Like session but forks off a subprocess
+ − 828 import os
+ − 829 # Wait for deceased children
+ − 830 try:
+ − 831 while 1:
+ − 832 pid, sts = os.waitpid(0, 1)
+ − 833 except os.error:
+ − 834 pass
+ − 835 pid = None
+ − 836 try:
+ − 837 pid = os.fork()
+ − 838 if pid: # Parent
+ − 839 connection[0].close()
+ − 840 return
+ − 841 # Child
+ − 842 self.session(connection)
+ − 843 finally:
+ − 844 # Make sure we don't fall through in the parent
+ − 845 if pid == 0:
+ − 846 os._exit(0)
+ − 847
+ − 848
+ − 849 class UDPServer(Server):
+ − 850
+ − 851 def register(self):
+ − 852 mapping = self.prog, self.vers, self.prot, self.port
+ − 853 p = UDPPortMapperClient(self.host)
+ − 854 if not p.Set(mapping):
+ − 855 raise RuntimeError, 'register failed'
+ − 856
+ − 857 def unregister(self):
+ − 858 mapping = self.prog, self.vers, self.prot, self.port
+ − 859 p = UDPPortMapperClient(self.host)
+ − 860 if not p.Unset(mapping):
+ − 861 raise RuntimeError, 'unregister failed'
+ − 862
+ − 863 def makesocket(self):
+ − 864 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ − 865 self.prot = IPPROTO_UDP
+ − 866
+ − 867 def loop(self):
+ − 868 while 1:
+ − 869 self.session()
+ − 870
+ − 871 def session(self):
+ − 872 call, host_port = self.sock.recvfrom(8192)
+ − 873 reply = self.handle(call)
+ − 874 if reply <> None:
+ − 875 self.sock.sendto(reply, host_port)
+ − 876
+ − 877
+ − 878 # Simple test program -- dump local portmapper status
+ − 879
+ − 880 def test():
+ − 881 pmap = UDPPortMapperClient('')
+ − 882 list = pmap.Dump()
+ − 883 list.sort()
+ − 884 for prog, vers, prot, port in list:
+ − 885 print prog, vers,
+ − 886 if prot == IPPROTO_TCP: print 'tcp',
+ − 887 elif prot == IPPROTO_UDP: print 'udp',
+ − 888 else: print prot,
+ − 889 print port
+ − 890
+ − 891
+ − 892 # Test program for broadcast operation -- dump everybody's portmapper status
+ − 893
+ − 894 def testbcast():
+ − 895 import sys
+ − 896 if sys.argv[1:]:
+ − 897 bcastaddr = sys.argv[1]
+ − 898 else:
+ − 899 bcastaddr = '<broadcast>'
+ − 900 def rh(reply, fromaddr):
+ − 901 host, port = fromaddr
+ − 902 print host + '\t' + `reply`
+ − 903 pmap = BroadcastUDPPortMapperClient(bcastaddr)
+ − 904 pmap.set_reply_handler(rh)
+ − 905 pmap.set_timeout(5)
+ − 906 replies = pmap.Getport((100002, 1, IPPROTO_UDP, 0))
+ − 907
+ − 908
+ − 909 # Test program for server, with corresponding client
+ − 910 # On machine A: python -c 'import rpc; rpc.testsvr()'
+ − 911 # On machine B: python -c 'import rpc; rpc.testclt()' A
+ − 912 # (A may be == B)
+ − 913
+ − 914 def testsvr():
+ − 915 # Simple test class -- proc 1 doubles its string argument as reply
+ − 916 class S(UDPServer):
+ − 917 def handle_1(self):
+ − 918 arg = self.unpacker.unpack_string()
+ − 919 self.turn_around()
+ − 920 print 'RPC function 1 called, arg', `arg`
+ − 921 self.packer.pack_string(arg + arg)
+ − 922 #
+ − 923 s = S('', 0x20000000, 1, 0)
+ − 924 try:
+ − 925 s.unregister()
+ − 926 except RuntimeError, msg:
+ − 927 print 'RuntimeError:', msg, '(ignored)'
+ − 928 s.register()
+ − 929 print 'Service started...'
+ − 930 try:
+ − 931 s.loop()
+ − 932 finally:
+ − 933 s.unregister()
+ − 934 print 'Service interrupted.'
+ − 935
+ − 936
+ − 937 def testclt():
+ − 938 import sys
+ − 939 if sys.argv[1:]: host = sys.argv[1]
+ − 940 else: host = ''
+ − 941 # Client for above server
+ − 942 class C(UDPClient):
+ − 943 def call_1(self, arg):
+ − 944 return self.make_call(1, arg, \
+ − 945 self.packer.pack_string, \
+ − 946 self.unpacker.unpack_string)
+ − 947 c = C(host, 0x20000000, 1)
+ − 948 print 'making call...'
+ − 949 reply = c.call_1('hello, world, ')
+ − 950 print 'call returned', `reply`
+ − 951
+ − 952 def testclt2():
+ − 953 import sys
+ − 954 host = '127.0.0.1'
+ − 955 # Client for above server
+ − 956 class C(UDPClient):
+ − 957 def call_1(self, arg):
+ − 958 return self.make_call(1, arg, \
+ − 959 self.packer.pack_string, \
+ − 960 self.unpacker.unpack_string)
+ − 961 c = C(host, 0x20000000, 1)
+ − 962 print 'making call...'
+ − 963 reply = c.call_1('hello, world, ')
+ − 964 print 'call returned', `reply`
+ − 965