PageRenderTime 62ms CodeModel.GetById 3ms app.highlight 47ms RepoModel.GetById 1ms app.codeStats 1ms

/emulcomm.py

https://github.com/aaaaalbert/repy_v2
Python | 2239 lines | 1860 code | 120 blank | 259 comment | 89 complexity | 3e72f39803a24e384708e4ae476268fa MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1"""
   2   Author: Justin Cappos, Armon Dadgar
   3
   4   Start Date: 27 June 2008
   5
   6   Description:
   7
   8   This is a collection of communications routines that provide a programmer 
   9   with a reasonable environment.   This is used by repy.py to provide a 
  10   highly restricted (but usable) environment.
  11"""
  12
  13import socket
  14
  15# Armon: Used to check if a socket is ready
  16import select
  17
  18# socket uses getattr and setattr.   We need to make these available to it...
  19socket.getattr = getattr
  20socket.setattr = setattr
  21
  22
  23# needed to set threads for recvmess and waitforconn
  24import threading
  25# threading in python2.7 uses hasattr. It needs to be made available.
  26threading.hasattr = hasattr
  27
  28
  29# So I can exit all threads when an error occurs or do select
  30import harshexit
  31
  32# Needed for finding out info about sockets, available interfaces, etc
  33import nonportable
  34
  35# So I can print a clean traceback when an error happens
  36import tracebackrepy
  37
  38# accounting
  39# id(sock) will be used to register and unregister sockets with nanny 
  40import nanny
  41
  42# give me uniqueIDs for the comminfo table
  43import idhelper
  44
  45# for sleep
  46import time 
  47
  48# Armon: Used for decoding the error messages
  49import errno
  50
  51# Armon: Used for getting the constant IP values for resolving our external IP
  52import repy_constants 
  53
  54# Get the exceptions
  55from exception_hierarchy import *
  56
  57###### Module Data
  58
  59# This is a library of all currently bound sockets. Since multiple 
  60# UDP bindings on a single port is hairy, we store bound sockets 
  61# here, and use them for both sending and receiving if they are 
  62# available. This feels slightly byzantine, but it allows us to 
  63# avoid modifying the repy API.
  64#
  65# Format of entries is as follows:
  66# Key - 3-tuple of ("UDP", IP, Port)
  67# Val - Bound socket object
  68_BOUND_SOCKETS = {} # Ticket = 1015 (Resolved)
  69
  70# If we have a preference for an IP/Interface this flag is set to True
  71user_ip_interface_preferences = False
  72
  73# Do we allow non-specified IPs
  74allow_nonspecified_ips = True
  75
  76# Armon: Specified the list of allowed IP and Interfaces in order of their preference
  77# The basic structure is list of tuples (IP, Value), IP is True if its an IP, False if its an interface
  78user_specified_ip_interface_list = []
  79
  80# This list caches the allowed IP's
  81# It is updated at the launch of repy, or by calls to getmyip and update_ip_cache
  82# NOTE: The loopback address 127.0.0.1 is always permitted. update_ip_cache will always add this
  83# if it is not specified explicitly by the user
  84allowediplist = []
  85cachelock = threading.Lock()  # This allows only a single simultaneous cache update
  86
  87
  88##### Internal Functions
  89
  90# Determines if a specified IP address is allowed in the context of user settings
  91def _ip_is_allowed(ip):
  92  """
  93  <Purpose>
  94    Determines if a given IP is allowed, by checking against the cached allowed IP's.
  95  
  96  <Arguments>
  97    ip: The IP address to search for.
  98  
  99  <Returns>
 100    True, if allowed. False, otherwise.
 101  """
 102  global allowediplist
 103  global user_ip_interface_preferences
 104  global allow_nonspecified_ips
 105  
 106  # If there is no preference, anything goes
 107  # same with allow_nonspecified_ips
 108  if not user_ip_interface_preferences or allow_nonspecified_ips:
 109    return True
 110  
 111  # Check the list of allowed IP's
 112  return (ip in allowediplist)
 113
 114
 115# Only appends the elem to lst if the elem is unique
 116def _unique_append(lst, elem):
 117  if elem not in lst:
 118    lst.append(elem)
 119      
 120# This function updates the allowed IP cache
 121# It iterates through all possible IP's and stores ones which are bindable as part of the allowediplist
 122def update_ip_cache():
 123  global allowediplist
 124  global user_ip_interface_preferences
 125  global user_specified_ip_interface_list
 126  global allow_nonspecified_ips
 127  
 128  # If there is no preference, this is a no-op
 129  if not user_ip_interface_preferences:
 130    return
 131    
 132  # Acquire the lock to update the cache
 133  cachelock.acquire()
 134  
 135  # If there is any exception release the cachelock
 136  try:  
 137    # Stores the IP's
 138    allowed_list = []
 139  
 140    # Iterate through the allowed list, handle each element
 141    for (is_ip_addr, value) in user_specified_ip_interface_list:
 142      # Handle normal IP's
 143      if is_ip_addr:
 144        _unique_append(allowed_list, value)
 145    
 146      # Handle interfaces
 147      else:
 148        try:
 149          # Get the IP's associated with the NIC
 150          interface_ips = nonportable.os_api.get_interface_ip_addresses(value)
 151          for interface_ip in interface_ips:
 152            _unique_append(allowed_list, interface_ip)
 153        except:
 154          # Catch exceptions if the NIC does not exist
 155          pass
 156  
 157    # This will store all the IP's that we are able to bind to
 158    bindable_list = []
 159        
 160    # Try binding to every ip
 161    for ip in allowed_list:
 162      sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 163      try:
 164        sock.bind((ip,0))
 165      except:
 166        pass # Not a good ip, skip it
 167      else:
 168        bindable_list.append(ip) # This is a good ip, store it
 169      finally:
 170        sock.close()
 171
 172    # Add loopback
 173    _unique_append(bindable_list, "127.0.0.1")
 174  
 175    # Update the global cache
 176    allowediplist = bindable_list
 177  
 178  finally:      
 179    # Release the lock
 180    cachelock.release()
 181 
 182
 183############## General Purpose socket functions ##############
 184
 185def _is_already_connected_exception(exceptionobj):
 186  """
 187  <Purpose>
 188    Determines if a given error number indicates that the socket
 189    is already connected.
 190
 191  <Arguments>
 192    An exception object from a network call.
 193
 194  <Returns>
 195    True if already connected, false otherwise
 196  """
 197  # Get the type
 198  exception_type = type(exceptionobj)
 199
 200  # Only continue if the type is socket.error
 201  if exception_type is not socket.error:
 202    return False
 203
 204  # Get the error number
 205  errnum = exceptionobj[0]
 206
 207  # Store a list of error messages meaning we are connected
 208  connected_errors = ["EISCONN", "WSAEISCONN"]
 209
 210  # Convert the errno to and error string name
 211  try:
 212    errname = errno.errorcode[errnum]
 213  except Exception,e:
 214    # The error is unknown for some reason...
 215    errname = None
 216  
 217  # Return if the error name is in our white list
 218  return (errname in connected_errors)
 219
 220
 221def _is_addr_in_use_exception(exceptionobj):
 222  """
 223  <Purpose>
 224    Determines if a given error number indicates that the provided
 225    localip / localport are already bound and that the unique
 226    tuple is already in use.
 227
 228  <Arguments>
 229    An exception object from a network call.
 230
 231  <Returns>
 232    True if already in use, false otherwise
 233  """
 234  # Get the type
 235  exception_type = type(exceptionobj)
 236
 237  # Only continue if the type is socket.error
 238  if exception_type is not socket.error:
 239    return False
 240
 241  # Get the error number
 242  errnum = exceptionobj[0]
 243
 244  # Store a list of error messages meaning we are in use
 245  in_use_errors = ["EADDRINUSE", "WSAEADDRINUSE"]
 246
 247  # Convert the errno to and error string name
 248  try:
 249    errname = errno.errorcode[errnum]
 250  except Exception,e:
 251    # The error is unknown for some reason...
 252    errname = None
 253  
 254  # Return if the error name is in our white list
 255  return (errname in in_use_errors)
 256
 257
 258def _is_addr_unavailable_exception(exceptionobj):
 259  """
 260  <Purpose>
 261    Determines if a given error number indicates that the provided
 262    localip is not available during a bind() call.
 263    This indicates an AddressBindingError should be raised.
 264
 265  <Arguments>
 266    An exception object from a network call.
 267
 268  <Returns>
 269    True if already in use, false otherwise
 270  """
 271  # Get the type
 272  exception_type = type(exceptionobj)
 273
 274  # Only continue if the type is socket.error
 275  if exception_type is not socket.error:
 276    return False
 277
 278  # Get the error number
 279  errnum = exceptionobj[0]
 280
 281  # Store a list of error messages meaning the address is not available
 282  not_avail_errors = ["EADDRNOTAVAIL", "WSAEADDRNOTAVAIL"]
 283
 284  # Convert the errno to and error string name
 285  try:
 286    errname = errno.errorcode[errnum]
 287  except Exception,e:
 288    # The error is unknown for some reason...
 289    errname = None
 290  
 291  # Return if the error name is in our white list
 292  return (errname in not_avail_errors)
 293
 294
 295def _is_conn_refused_exception(exceptionobj):
 296  """
 297  <Purpose>
 298    Determines if a given error number indicates that the remote
 299    host has actively refused the connection. E.g.
 300    ECONNREFUSED
 301
 302  <Arguments>
 303    An exception object from a network call.
 304
 305  <Returns>
 306    True if the error indicates the connection was refused, false otherwise
 307  """
 308  # Get the type
 309  exception_type = type(exceptionobj)
 310
 311  # Only continue if the type is socket.error
 312  if exception_type is not socket.error:
 313    return False
 314
 315  # Get the error number
 316  errnum = exceptionobj[0]
 317
 318  # Store a list of error messages meaning the host refused
 319  refused_errors = ["ECONNREFUSED", "WSAECONNREFUSED"]
 320
 321  # Convert the errno to and error string name
 322  try:
 323    errname = errno.errorcode[errnum]
 324  except Exception,e:
 325    # The error is unknown for some reason...
 326    errname = None
 327  
 328  # Return if the error name is in our white list
 329  return (errname in refused_errors)
 330
 331
 332def _is_network_down_exception(exceptionobj):
 333  """
 334  <Purpose>
 335    Determines if a given error number indicates that the
 336    network is down.
 337
 338  <Arguments>
 339    An exception object from a network call.
 340
 341  <Returns>
 342    True if the network is down, false otherwise
 343  """
 344  # Get the type
 345  exception_type = type(exceptionobj)
 346
 347  # Only continue if the type is socket.error
 348  if exception_type is not socket.error:
 349    return False
 350
 351  # Get the error number
 352  errnum = exceptionobj[0]
 353
 354  # Store a list of error messages meaning we are disconnected
 355  net_down_errors = ["ENETDOWN","ENETUNREACH","WSAENETDOWN", "WSAENETUNREACH"]
 356
 357  # Convert the errno to and error string name
 358  try:
 359    errname = errno.errorcode[errnum]
 360  except Exception,e:
 361    # The error is unknown for some reason...
 362    errname = None
 363  
 364  # Return if the error name is in our white list
 365  return (errname in net_down_errors)
 366
 367
 368def _is_recoverable_network_exception(exceptionobj):
 369  """
 370  <Purpose>
 371    Determines if a given error number is recoverable or fatal.
 372
 373  <Arguments>
 374    An exception object from a network call.
 375
 376  <Returns>
 377    True if potentially recoverable, False if fatal.
 378  """
 379  # Get the type
 380  exception_type = type(exceptionobj)
 381
 382  # socket.timeout is recoverable always
 383  if exception_type == socket.timeout:
 384    return True
 385
 386  # Only continue if the type is socket.error or select.error
 387  elif exception_type != socket.error and exception_type != select.error:
 388    return False
 389  
 390  # Get the error number
 391  errnum = exceptionobj[0]
 392
 393  # Store a list of recoverable error numbers
 394  recoverable_errors = ["EINTR","EAGAIN","EBUSY","EWOULDBLOCK","ETIMEDOUT","ERESTART",
 395                        "WSAEINTR","WSAEWOULDBLOCK","WSAETIMEDOUT","EALREADY","WSAEALREADY",
 396                       "EINPROGRESS","WSAEINPROGRESS"]
 397
 398  # Convert the errno to and error string name
 399  try:
 400    errname = errno.errorcode[errnum]
 401  except Exception,e:
 402    # The error is unknown for some reason...
 403    errname = None
 404  
 405  # Return if the error name is in our white list
 406  return (errname in recoverable_errors)
 407
 408
 409# Determines based on exception if the connection has been terminated
 410def _is_terminated_connection_exception(exceptionobj):
 411  """
 412  <Purpose>
 413    Determines if the exception is indicated the connection is terminated.
 414
 415  <Arguments>
 416    An exception object from a network call.
 417
 418  <Returns>
 419    True if the connection is terminated, False otherwise.
 420    False means we could not determine with certainty if the socket is closed.
 421  """
 422  # Get the type
 423  exception_type = type(exceptionobj)
 424
 425  # We only want to continue if it is socket.error or select.error
 426  if exception_type != socket.error and exception_type != select.error:
 427    return False
 428
 429  # Get the error number
 430  errnum = exceptionobj[0]
 431
 432  # Store a list of errors which indicate connection closed
 433  connection_closed_errors = ["EPIPE","EBADF","EBADR","ENOLINK","EBADFD","ENETRESET",
 434                              "ECONNRESET","WSAEBADF","WSAENOTSOCK","WSAECONNRESET",]
 435
 436  # Convert the errnum to an error string
 437  try:
 438    errname = errno.errorcode[errnum]
 439  except:
 440    # The error number is not defined...
 441    errname = None
 442
 443  # Return whether the errname is in our pre-defined list
 444  return (errname in connection_closed_errors)
 445
 446
 447
 448# Armon: This is used for semantics, to determine if we have a valid IP.
 449def _is_valid_ip_address(ipaddr):
 450  """
 451  <Purpose>
 452    Determines if ipaddr is a valid IP address.
 453    0.X and 224-255.X addresses are not allowed.
 454    Additionally, 192.168.0.0 is not allowed.
 455
 456  <Arguments>
 457    ipaddr: String to check for validity. (It will check that this is a string).
 458
 459  <Returns>
 460    True if a valid IP, False otherwise.
 461  """
 462  # Argument must be of the string type
 463  if not type(ipaddr) == str:
 464    return False
 465
 466  if ipaddr == '192.168.0.0':
 467    return False
 468
 469  # A valid IP should have 4 segments, explode on the period
 470  octets = ipaddr.split(".")
 471
 472  # Check that we have 4 parts
 473  if len(octets) != 4:
 474    return False
 475
 476  # Check that each segment is a number between 0 and 255 inclusively.
 477  for octet in octets:
 478    # Attempt to convert to an integer
 479    try:
 480      ipnumber = int(octet)
 481    except ValueError:
 482      # There was an error converting to an integer, not an IP
 483      return False
 484
 485    # IP addresses octets must be between 0 and 255
 486    if not (ipnumber >= 0 and ipnumber <= 255):
 487      return False
 488
 489  # should not have a ValueError (I already checked)
 490  firstipnumber = int(octets[0])
 491
 492  # IP addresses with the first octet 0 refer to all local IPs.   These are
 493  # not allowed
 494  if firstipnumber == 0:
 495    return False
 496
 497  # IP addresses with the first octet >=224 are either Multicast or reserved.
 498  # These are not allowed
 499  if firstipnumber >= 224:
 500    return False
 501
 502  # At this point, assume the IP is valid
 503  return True
 504
 505
 506# Armon: This is used for semantics, to determine if the given port is valid
 507def _is_valid_network_port(port):
 508  """
 509  <Purpose>
 510    Determines if a given network port is valid. 
 511
 512  <Arguments>
 513    port: A numeric type (this will be checked) port number.
 514
 515  <Returns>
 516    True if valid, False otherwise.
 517  """
 518  # Check the type is int or long
 519  if not (type(port) == long or type(port) == int):
 520    return False
 521
 522  if port >= 1 and port <= 65535:
 523    return True
 524  else:
 525    return False
 526
 527
 528# Used to decide if an IP is the loopback IP or not.   This is needed for 
 529# accounting
 530def _is_loopback_ipaddr(host):
 531  if not host.startswith('127.'):
 532    return False
 533  if len(host.split('.')) != 4:
 534    return False
 535
 536  octets = host.split('.')
 537  if len(octets) != 4:
 538    return False
 539  for octet in octets:
 540    try:
 541      if int(octet) > 255 or int(octet) < 0:
 542        return False
 543    except ValueError:
 544      return False
 545 
 546  return True
 547
 548
 549# Checks if binding to the local port is allowed
 550# type should be "TCP" or "UDP".
 551def _is_allowed_localport(type, localport):
 552  # Switch to the proper resource
 553  if type == "TCP":
 554    resource = "connport"
 555  elif type == "UDP":
 556    resource = "messport"
 557  else:
 558    raise InternalRepyError("Bad type specified for _is_allowed_localport()")
 559
 560  # Check what is allowed by nanny
 561  return nanny.is_item_allowed(resource, float(localport))
 562
 563
 564
 565
 566######################### Simple Public Functions ##########################
 567
 568
 569
 570# Public interface
 571def gethostbyname(name):
 572  """
 573   <Purpose>
 574      Provides information about a hostname. Calls socket.gethostbyname().
 575      Translate a host name to IPv4 address format. The IPv4 address is
 576      returned as a string, such as '100.50.200.5'. If the host name is an
 577      IPv4 address itself it is returned unchanged.
 578
 579   <Arguments>
 580     name:
 581         The host name to translate.
 582
 583   <Exceptions>
 584     RepyArgumentError (descends from NetworkError) if the name is not a string
 585     NetworkAddressError (descends from NetworkError) if the address cannot
 586     be resolved.
 587
 588   <Side Effects>
 589     None.
 590
 591   <Resource Consumption>
 592     This operation consumes network bandwidth of 4K netrecv, 1K netsend.
 593     (It's hard to tell how much was actually sent / received at this level.)
 594
 595   <Returns>
 596     The IPv4 address as a string.
 597  """
 598
 599  if type(name) is not str:
 600    raise RepyArgumentError("gethostbyname() takes a string as argument.")
 601
 602  # charge 4K for a look up...   I don't know the right number, but we should
 603  # charge something.   We'll always charge to the netsend interface...
 604  nanny.tattle_quantity('netsend', 1024) 
 605  nanny.tattle_quantity('netrecv', 4096)
 606
 607  try:
 608    return socket.gethostbyname(name)
 609  except socket.gaierror:
 610    raise NetworkAddressError("The hostname '"+name+"' could not be resolved.")
 611
 612
 613
 614# Public interface
 615def getmyip():
 616  """
 617   <Purpose>
 618      Provides the IP of this computer on its public facing interface.  
 619      Does some clever trickery. 
 620
 621   <Arguments>
 622      None
 623
 624   <Exceptions>
 625      InternetConnectivityError is the host is not connected to the internet.
 626
 627   <Side Effects>
 628      None.
 629
 630   <Resource Consumption>
 631      This operations consumes 256 netsend and 128 netrecv.
 632
 633   <Returns>
 634      The localhost's IP address
 635  """
 636  # Charge for the resources
 637  nanny.tattle_quantity("netsend", 256)
 638  nanny.tattle_quantity("netrecv", 128)
 639
 640  # I got some of this from: http://groups.google.com/group/comp.lang.python/browse_thread/thread/d931cdc326d7032b?hl=en
 641  
 642  # Update the cache and return the first allowed IP
 643  # Only if a preference is set
 644  if user_ip_interface_preferences:
 645    update_ip_cache()
 646    # Return the first allowed ip, there is always at least 1 element (loopback)
 647    return allowediplist[0]
 648
 649  # Initialize these to None, so we can detect a failure
 650  myip = None
 651  
 652  # It's possible on some platforms (Windows Mobile) that the IP will be
 653  # 0.0.0.0 even when I have a public IP and the external IP is up. However, if
 654  # I get a real connection with SOCK_STREAM, then I should get the real
 655  # answer.
 656        
 657  # Try each stable IP  
 658  for ip_addr in repy_constants.STABLE_PUBLIC_IPS:  
 659    try:
 660      # Try to resolve using the current connection type and 
 661      # stable IP, using port 80 since some platforms panic
 662      # when given 0 (FreeBSD)
 663      myip = _get_localIP_to_remoteIP(socket.SOCK_DGRAM, ip_addr, 80)
 664    except (socket.error, socket.timeout):
 665      # We can ignore any networking related errors, since we want to try 
 666      # the other connection types and IP addresses. If we fail,
 667      # we will eventually raise an exception anyways.
 668      pass
 669    else:
 670      # Return immediately if the IP address is good
 671      if _is_valid_ip_address(myip): 
 672        return myip
 673
 674
 675  # Since we haven't returned yet, we must have failed.
 676  # Raise an exception, we must not be connected to the internet
 677  raise InternetConnectivityError("Cannot detect a connection to the Internet.")
 678
 679
 680
 681def _get_localIP_to_remoteIP(connection_type, external_ip, external_port=80):
 682  """
 683  <Purpose>
 684    Resolve the local ip used when connecting outbound to an external ip.
 685  
 686  <Arguments>
 687    connection_type:
 688      The type of connection to attempt. See socket.socket().
 689    
 690    external_ip:
 691      The external IP to attempt to connect to.
 692      
 693    external_port:
 694      The port on the remote host to attempt to connect to.
 695  
 696  <Exceptions>
 697    As with socket.socket(), socketobj.connect(), etc.
 698  
 699  <Returns>
 700    The locally assigned IP for the connection.
 701  """
 702  # Open a socket
 703  sockobj = socket.socket(socket.AF_INET, connection_type)
 704
 705  # Make sure that the socket obj doesn't hang forever in 
 706  # case connect() is blocking. Fix to #1003
 707  sockobj.settimeout(1.0)
 708
 709  try:
 710    sockobj.connect((external_ip, external_port))
 711
 712    # Get the local connection information for this socket
 713    (myip, localport) = sockobj.getsockname()
 714      
 715  # Always close the socket
 716  finally:
 717    sockobj.close()
 718  
 719  return myip
 720
 721
 722
 723
 724###################### Shared message / connection items ###################
 725
 726
 727
 728
 729# Armon: How frequently should we check for the availability of the socket?
 730RETRY_INTERVAL = 0.2 # In seconds
 731
 732
 733def _cleanup_socket(self):
 734  """
 735  <Purpose>
 736    Internal cleanup method for open sockets. The socket
 737    lock for the socket should be acquired prior to
 738    calling.
 739
 740  <Arguments>
 741    None
 742  <Side Effects>
 743    The insocket/outsocket handle will be released.
 744
 745  <Exceptions>
 746    InternalRepyError is raised if the socket lock is not held
 747    prior to calling the function.
 748
 749  <Returns>
 750    None
 751  """
 752  sock = self.socketobj
 753  socket_lock = self.sock_lock
 754  # Make sure the lock is already acquired
 755  # BUG: We don't know which thread exactly acquired the lock.
 756  if socket_lock.acquire(False):
 757    socket_lock.release()
 758    raise InternalRepyError("Socket lock should be acquired before calling _cleanup_socket!")
 759
 760  if (sock == None):  
 761    # Already cleaned up
 762    return
 763  # Shutdown the socket for writing prior to close
 764  # to unblock any threads that are writing
 765  try:
 766    sock.shutdown(socket.SHUT_WR)
 767  except:
 768    pass
 769
 770  # Close the socket
 771  try:
 772    sock.close()
 773  except:
 774    pass
 775  # socket id is used to unregister socket with nanny
 776  sockid = id(sock)
 777  # Re-store resources
 778  nanny.tattle_remove_item('insockets', sockid)
 779  nanny.tattle_remove_item('outsockets', sockid)
 780
 781
 782####################### Message sending #############################
 783
 784
 785
 786# Public interface!!!
 787def sendmessage(destip, destport, message, localip, localport):
 788  """
 789   <Purpose>
 790      Send a message to a host / port
 791
 792   <Arguments>
 793      destip:
 794         The host to send a message to
 795      destport:
 796         The port to send the message to
 797      message:
 798         The message to send
 799      localhost:
 800         The local IP to send the message from 
 801      localport:
 802         The local port to send the message from
 803
 804   <Exceptions>
 805      AddressBindingError (descends NetworkError) when the local IP isn't
 806        a local IP.
 807
 808      ResourceForbiddenError (descends ResourceException?) when the local
 809        port isn't allowed
 810
 811      RepyArgumentError when the local IP and port aren't valid types
 812        or values
 813
 814      AlreadyListeningError if there is an existing listening UDP socket
 815      on the same local IP and port.
 816
 817      DuplicateTupleError if there is another sendmessage on the same
 818      local IP and port to the same remote host.
 819
 820   <Side Effects>
 821      None.
 822
 823   <Resource Consumption>
 824      This operation consumes 64 bytes + number of bytes of the message that
 825      were transmitted. This requires that the localport is allowed.
 826
 827   <Returns>
 828      The number of bytes sent on success
 829  """
 830  # Check the input arguments (type)
 831  if type(destip) is not str:
 832    raise RepyArgumentError("Provided destip must be a string!")
 833  if type(localip) is not str:
 834    raise RepyArgumentError("Provided localip must be a string!")
 835
 836  if type(destport) is not int:
 837    raise RepyArgumentError("Provided destport must be an int!")
 838  if type(localport) is not int:
 839    raise RepyArgumentError("Provided localport must be an int!")
 840
 841  if type(message) is not str:
 842    raise RepyArgumentError("Provided message must be a string!")
 843
 844
 845  # Check the input arguments (sanity)
 846  if not _is_valid_ip_address(destip):
 847    raise RepyArgumentError("Provided destip is not valid! IP: '"+destip+"'")
 848  if not _is_valid_ip_address(localip):
 849    raise RepyArgumentError("Provided localip is not valid! IP: '"+localip+"'")
 850
 851  if not _is_valid_network_port(destport):
 852    raise RepyArgumentError("Provided destport is not valid! Port: "+str(destport))
 853  if not _is_valid_network_port(localport):
 854    raise RepyArgumentError("Provided localport is not valid! Port: "+str(localport))
 855
 856
 857  # Check that if localip == destip, then localport != destport
 858  if localip == destip and localport == destport:
 859    raise RepyArgumentError("Local socket name cannot match destination socket name! Local/Dest IP and Port match.")
 860
 861  # Check the input arguments (permission)
 862  update_ip_cache()
 863  if not _ip_is_allowed(localip):
 864    raise ResourceForbiddenError("Provided localip is not allowed! IP: "+localip)
 865
 866  if not _is_allowed_localport("UDP", localport):
 867    raise ResourceForbiddenError("Provided localport is not allowed! Port: "+str(localport))
 868
 869  # Wait for netsend
 870  if _is_loopback_ipaddr(destip):
 871    nanny.tattle_quantity('loopsend', 0)
 872  else:
 873    nanny.tattle_quantity('netsend', 0)
 874
 875  try:
 876    sock = None
 877
 878    if ("UDP", localip, localport) in _BOUND_SOCKETS:
 879      sock = _BOUND_SOCKETS[("UDP", localip, localport)]       
 880    else:
 881      # Get the socket
 882      sock = _get_udp_socket(localip, localport)      
 883      # Register this socket with nanny
 884      nanny.tattle_add_item("outsockets", id(sock))
 885    # Send the message
 886    bytessent = sock.sendto(message, (destip, destport))
 887
 888    # Account for the resources
 889    if _is_loopback_ipaddr(destip):
 890      nanny.tattle_quantity('loopsend', bytessent + 64)
 891    else:
 892      nanny.tattle_quantity('netsend', bytessent + 64)
 893
 894    return bytessent
 895
 896  except Exception, e:
 897        
 898    try:
 899      # If we're borrowing the socket, closing is not appropriate.
 900      if not ("UDP", localip, localport) in _BOUND_SOCKETS:
 901        sock.close()
 902    except:
 903      pass
 904
 905    # Check if address is already in use
 906    if _is_addr_in_use_exception(e):
 907      raise DuplicateTupleError("Provided Local IP and Local Port is already in use!")
 908 
 909    if _is_addr_unavailable_exception(e):
 910      raise AddressBindingError("Cannot bind to the specified local ip, invalid!")
 911
 912    # Unknown error...
 913    else:
 914      raise
 915
 916
 917
 918
 919# Public interface!!!
 920def listenformessage(localip, localport):
 921  """
 922    <Purpose>
 923        Sets up a UDPServerSocket to receive incoming UDP messages.
 924
 925    <Arguments>
 926        localip:
 927            The local IP to register the handler on.
 928        localport:
 929            The port to listen on.
 930
 931    <Exceptions>
 932        DuplicateTupleError (descends NetworkError) if the port cannot be
 933        listened on because some other process on the system is listening on
 934        it.
 935
 936        AlreadyListeningError if there is already a UDPServerSocket with the same
 937        IP and port.
 938
 939        RepyArgumentError if the port number or ip is wrong type or obviously
 940        invalid.
 941
 942        AddressBindingError (descends NetworkError) if the IP address isn't a
 943        local IP.
 944
 945        ResourceForbiddenError if the port is not allowed.
 946
 947    <Side Effects>
 948        Prevents other UDPServerSockets from using this port / IP
 949
 950    <Resource Consumption>
 951        This operation consumes an insocket and requires that the provided messport is allowed.
 952
 953    <Returns>
 954        The UDPServerSocket.
 955  """
 956  # Check the input arguments (type)
 957  if type(localip) is not str:
 958    raise RepyArgumentError("Provided localip must be a string!")
 959
 960  if type(localport) is not int:
 961    raise RepyArgumentError("Provided localport must be a int!")
 962
 963
 964  # Check the input arguments (sanity)
 965  if not _is_valid_ip_address(localip):
 966    raise RepyArgumentError("Provided localip is not valid! IP: '"+localip+"'")
 967
 968  if not _is_valid_network_port(localport):
 969    raise RepyArgumentError("Provided localport is not valid! Port: "+str(localport))
 970
 971
 972  # Check the input arguments (permission)
 973  update_ip_cache()
 974  if not _ip_is_allowed(localip):
 975    raise ResourceForbiddenError("Provided localip is not allowed! IP: '"+localip+"'")
 976
 977  if not _is_allowed_localport("UDP", localport):
 978    raise ResourceForbiddenError("Provided localport is not allowed! Port: "+str(localport))
 979  # This identity tuple will be used to check for an existing connection with same identity
 980  identity = ("UDP", localip, localport, None, None)
 981
 982  try:
 983    # Check if localip is on loopback
 984    on_loopback = _is_loopback_ipaddr(localip) 
 985
 986    # Get the socket
 987    sock = _get_udp_socket(localip,localport)
 988    
 989    # Register this socket as an insocket
 990    nanny.tattle_add_item('insockets',id(sock))
 991
 992    # Add the socket to _BOUND_SOCKETS so that we can 
 993    # preserve send functionality on this port.
 994    _BOUND_SOCKETS[("UDP", localip, localport)] = sock
 995
 996  except Exception, e:    
 997
 998    # Check if this an already in use error
 999    if _is_addr_in_use_exception(e):  
1000    # Call _conn_cleanup_check to determine if this is because
1001    # the socket is being cleaned up or if it is actively being used or
1002    # if there is an existing listening socket 
1003    # This will always raise DuplicateTupleError or
1004    # CleanupInProgressError or AlreadyListeningError  
1005      _conn_cleanup_check(identity)
1006
1007    # Check if this is a binding error
1008    if _is_addr_unavailable_exception(e):
1009      raise AddressBindingError("Cannot bind to the specified local ip, invalid!")
1010
1011    # Unknown error...
1012    else:
1013      raise
1014 
1015  # Create a UDPServerSocket
1016  server_sock = UDPServerSocket(sock, on_loopback)
1017
1018  # Return the UDPServerSocket
1019  return server_sock
1020  
1021
1022
1023####################### Connection oriented #############################
1024def _conn_alreadyexists_check(identity):
1025  """
1026  <Purpose>
1027    This private function checks if a socket that
1028    got EADDRINUSE is because the socket is active,
1029    or not
1030
1031  <Arguments>
1032    identity: A tuple to check for cleanup
1033
1034  <Exceptions>
1035    Raises DuplicateTupleError if the socket is actively being used.
1036
1037    Raises AddressBindingError if the binding is not allowed 
1038
1039  <Returns>
1040    None
1041  """
1042  # Decompose the tuple
1043  family, localip, localport, desthost, destport = identity
1044  
1045  # Check the sockets status
1046  (exists, status) = nonportable.os_api.exists_outgoing_network_socket(localip,localport,desthost,destport)
1047
1048  # Check if the socket is actively being used
1049  # If the socket is these states:
1050  #  ESTABLISHED : Connection is active
1051  #  CLOSE_WAIT : Connection is closed, but waiting on local program to close
1052  #  SYN_SENT (SENT) : Connection is just being established
1053  if exists and ("ESTABLISH" in status or "CLOSE_WAIT" in status or "SENT" in status):
1054    raise DuplicateTupleError("There is a duplicate connection which conflicts with the request!")
1055
1056  # Otherwise, the socket is being cleaned up
1057  raise AddressBindingError("Cannot bind to the specified local ip, invalid!")
1058
1059
1060def _conn_cleanup_check(identity):
1061  """
1062  <Purpose>
1063    This private function checks if a socket that
1064    got EADDRINUSE is because the socket is active,
1065    or because the socket is listening or 
1066    because the socket is being cleaned up.
1067
1068  <Arguments>
1069    identity: A tuple to check for cleanup
1070
1071  <Exceptions>
1072    Raises DuplicateTupleError if the socket is actively being used.
1073
1074    Raises AlreadyListeningError if the socket is listening.   
1075
1076    Raises CleanupInProgressError if the socket is being cleaned up
1077    or if the socket does not appear to exist. This is because there
1078    may be a race between getting EADDRINUSE and the call to this
1079    function.
1080
1081  <Returns>
1082    None
1083  """
1084  # Decompose the tuple
1085  family, localip, localport, desthost, destport = identity
1086  
1087  # Check the sockets status
1088  (exists, status) = nonportable.os_api.exists_outgoing_network_socket(localip,localport,desthost,destport)
1089
1090  # Check if the socket is actively being used
1091  # If the socket is these states:
1092  #  ESTABLISHED : Connection is active
1093  #  CLOSE_WAIT : Connection is closed, but waiting on local program to close
1094  #  SYN_SENT (SENT) : Connection is just being established
1095  if exists and ("ESTABLISH" in status or "CLOSE_WAIT" in status or "SENT" in status):
1096    raise DuplicateTupleError("There is a duplicate connection which conflicts with the request!")
1097  else:
1098    # Checking if a listening TCP or UDP socket exists with given local address
1099    # The third argument is True if socket type is TCP,False if socket type is UDP 
1100    if (nonportable.os_api.exists_listening_network_socket(localip, localport, (family == "TCP"))):
1101      raise AlreadyListeningError("There is a listening socket on the provided localip and localport!")
1102      # Otherwise, the socket is being cleaned up
1103    else:
1104      raise CleanupInProgressError("The socket is being cleaned up by the operating system!")
1105
1106
1107def _timed_conn_initialize(localip,localport,destip,destport, timeout):
1108  """
1109  <Purpose> 
1110    Tries to initialize an outgoing socket to match
1111    the given address parameters.
1112
1113  <Arguments>
1114    localip,localport: The local address of the socket
1115    destip,destport: The destination address to which socket has to be connected  
1116    timeout: Maximum time to try
1117
1118  <Exceptions>
1119    Raises TimeoutError if we timed out trying to connect.
1120    Raises ConnectionRefusedError if the connection was refused.
1121    Raises InternetConnectivityError if the network is down.
1122
1123    Raises any errors encountered calling _get_tcp_socket,
1124    or any non-recoverable network exception.
1125
1126  <Returns>
1127    A Python socket object connected to the dest,
1128    from the specified local tuple.
1129  """
1130
1131  # Store our start time
1132  starttime = nonportable.getruntime()
1133
1134  # Get a TCP socket bound to the local ip / port
1135  sock = _get_tcp_socket(localip, localport)
1136  sock.settimeout(timeout)
1137
1138  try:
1139    # Try to connect until we timeout
1140    connected = False
1141    while nonportable.getruntime() - starttime < timeout:
1142      try:
1143        sock.connect((destip, destport))
1144        connected = True
1145        break
1146      except Exception, e:
1147        # Check if we are already connected
1148        if _is_already_connected_exception(e):
1149          connected = True     
1150          raise DuplicateTupleError("There is a duplicate connection which conflicts with the request!")
1151          break
1152
1153        # Check if the network is down
1154        if _is_network_down_exception(e):
1155          raise InternetConnectivityError("The network is down or cannot be reached from the local IP!")
1156
1157        # Check if the connection was refused
1158        if _is_conn_refused_exception(e):
1159          raise ConnectionRefusedError("The connection was refused!") 
1160
1161        # Check if this is recoverable (try again, timeout, etc)
1162        elif not _is_recoverable_network_exception(e):
1163          raise
1164
1165        # Sleep and retry, avoid busy waiting
1166        time.sleep(RETRY_INTERVAL)
1167
1168    # Check if we timed out
1169    if not connected:
1170      raise TimeoutError("Timed-out connecting to the remote host!")
1171
1172    # Return the socket
1173    return sock
1174
1175  except:
1176    # Close the socket, and raise
1177    sock.close()
1178    raise
1179
1180
1181# Public interface!!!
1182def openconnection(destip, destport,localip, localport, timeout):
1183  """
1184    <Purpose>
1185      Opens a connection, returning a socket-like object
1186
1187
1188    <Arguments>
1189      destip: The destination ip to open communications with
1190
1191      destport: The destination port to use for communication
1192
1193      localip: The local ip to use for the communication
1194
1195      localport: The local port to use for communication
1196
1197      timeout: The maximum amount of time to wait to connect.   This may
1198               be a floating point number or an integer
1199
1200
1201    <Exceptions>
1202
1203      RepyArgumentError if the arguments are invalid.   This includes both
1204      the types and values of arguments. If the localip matches the destip,
1205      and the localport matches the destport this will also be raised.
1206
1207      AddressBindingError (descends NetworkError) if the localip isn't 
1208      associated with the local system or is not allowed.
1209
1210      ResourceForbiddenError (descends ResourceError) if the localport isn't 
1211      allowed.
1212
1213      DuplicateTupleError (descends NetworkError) if the (localip, localport, 
1214      destip, destport) tuple is already used.   This will also occur if the 
1215      operating system prevents the local IP / port from being used.
1216
1217      AlreadyListeningError if the (localip, localport) tuple is already used
1218      for a listening TCP socket.
1219
1220      CleanupInProgress if the (localip, localport, destip, destport) tuple is
1221      still being cleaned up by the OS.
1222
1223      ConnectionRefusedError (descends NetworkError) if the connection cannot 
1224      be established because the destination port isn't being listened on.
1225
1226      TimeoutError (common to all API functions that timeout) if the 
1227      connection times out
1228
1229      InternetConnectivityError if the network is down, or if the host
1230      cannot be reached from the local IP that has been bound to.
1231
1232
1233    <Side Effects>
1234      TODO
1235
1236    <Resource Consumption>
1237      This operation consumes 64*2 bytes of netsend (SYN, ACK) and 64 bytes 
1238      of netrecv (SYN/ACK). This requires that the localport is allowed. Upon 
1239      success, this call consumes an outsocket.
1240
1241    <Returns>
1242      A socket-like object that can be used for communication. Use send, 
1243      recv, and close just like you would an actual socket object in python.
1244  """
1245  # Check the input arguments (type)
1246  if type(destip) is not str:
1247    raise RepyArgumentError("Provided destip must be a string!")
1248  if type(localip) is not str:
1249    raise RepyArgumentError("Provided localip must be a string!")
1250
1251  if type(destport) is not int:
1252    raise RepyArgumentError("Provided destport must be an int!")
1253  if type(localport) is not int:
1254    raise RepyArgumentError("Provided localport must be an int!")
1255
1256  if type(timeout) not in [float, int]:
1257    raise RepyArgumentError("Provided timeout must be an int or float!")
1258
1259
1260  # Check the input arguments (sanity)
1261  if not _is_valid_ip_address(destip):
1262    raise RepyArgumentError("Provided destip is not valid! IP: '"+destip+"'")
1263  if not _is_valid_ip_address(localip):
1264    raise RepyArgumentError("Provided localip is not valid! IP: '"+localip+"'")
1265
1266  if not _is_valid_network_port(destport):
1267    raise RepyArgumentError("Provided destport is not valid! Port: "+str(destport))
1268  if not _is_valid_network_port(localport):
1269    raise RepyArgumentError("Provided localport is not valid! Port: "+str(localport))
1270
1271  if timeout <= 0:
1272    raise RepyArgumentError("Provided timeout is not valid, must be positive! Timeout: "+str(timeout))
1273
1274  # Check that if localip == destip, then localport != destport
1275  if localip == destip and localport == destport:
1276    raise RepyArgumentError("Local socket name cannot match destination socket name! Local/Dest IP and Port match.")
1277
1278  # Check the input arguments (permission)
1279  update_ip_cache()
1280  if not _ip_is_allowed(localip):
1281    raise ResourceForbiddenError("Provided localip is not allowed! IP: "+localip)
1282
1283  if not _is_allowed_localport("TCP", localport):
1284    raise ResourceForbiddenError("Provided localport is not allowed! Port: "+str(localport))
1285
1286
1287
1288  # use this tuple during connection clean up check
1289  identity = ("TCP", localip, localport, destip, destport)
1290  
1291  # Wait for netsend / netrecv
1292  if _is_loopback_ipaddr(destip):
1293    nanny.tattle_quantity('loopsend', 0)
1294    nanny.tattle_quantity('looprecv', 0)
1295  else:
1296    nanny.tattle_quantity('netsend', 0)
1297    nanny.tattle_quantity('netrecv', 0)
1298
1299  try:
1300    # To Know if remote IP is on loopback or not
1301    on_loopback = _is_loopback_ipaddr(destip)
1302
1303    # Get the socket
1304    sock = _timed_conn_initialize(localip,localport,destip,destport, timeout)
1305    
1306    # Register this socket as an outsocket
1307    nanny.tattle_add_item('outsockets',id(sock))
1308  except Exception, e:
1309
1310    # Check if this an already in use error
1311    if _is_addr_in_use_exception(e):
1312      # Call _conn_cleanup_check to determine if this is because
1313      # the socket is being cleaned up or if it is actively being used
1314      # This will always raise DuplicateTupleError or
1315      # CleanupInProgressError or AlreadyListeningError
1316      _conn_cleanup_check(identity)
1317 
1318    # Check if this is a binding error
1319    if _is_addr_unavailable_exception(e):
1320      # Call _conn_alreadyexists_check to determine if this is because
1321      # the connection is active or not
1322      _conn_alreadyexists_check(identity)
1323      
1324
1325    # Unknown error...
1326    else:
1327      raise
1328
1329  emul_sock = EmulatedSocket(sock, on_loopback)
1330
1331  # Tattle the resources used
1332  if _is_loopback_ipaddr(destip):
1333    nanny.tattle_quantity('loopsend', 128)
1334    nanny.tattle_quantity('looprecv', 64)
1335  else:
1336    nanny.tattle_quantity('netsend', 128)
1337    nanny.tattle_quantity('netrecv', 64)
1338
1339  # Return the EmulatedSocket
1340  return emul_sock
1341
1342
1343def listenforconnection(localip, localport):
1344  """
1345  <Purpose>
1346    Sets up a TCPServerSocket to recieve incoming TCP connections. 
1347
1348  <Arguments>
1349    localip:
1350        The local IP to listen on
1351    localport:
1352        The local port to listen on
1353
1354  <Exceptions>
1355    Raises AlreadyListeningError if another TCPServerSocket or process has bound
1356    to the provided localip and localport.
1357
1358    Raises DuplicateTupleError if another process has bound to the
1359    provided localip and localport.
1360
1361    Raises RepyArgumentError if the localip or localport are invalid
1362    Raises ResourceForbiddenError if the ip or port is not allowed.
1363    Raises AddressBindingError if the IP address isn't a local ip.
1364
1365  <Side Effects>
1366    The IP / Port combination cannot be used until the TCPServerSocket
1367    is closed.
1368
1369  <Resource Consumption>
1370    Uses an insocket for the TCPServerSocket.
1371
1372  <Returns>
1373    A TCPServerSocket object.
1374  """
1375  # Check the input arguments (type)
1376  if type(localip) is not str:
1377    raise RepyArgumentError("Provided localip must be a string!")
1378
1379  if type(localport) is not int:
1380    raise RepyArgumentError("Provided localport must be a int!")
1381
1382
1383  # Check the input arguments (sanity)
1384  if not _is_valid_ip_address(localip):
1385    raise RepyArgumentError("Provided localip is not valid! IP: '"+localip+"'")
1386
1387  if not _is_valid_network_port(localport):
1388    raise RepyArgumentError("Provided localport is not valid! Port: "+str(localport))
1389
1390
1391  # Check the input arguments (permission)
1392  update_ip_cache()
1393  if not _ip_is_allowed(localip):
1394    raise ResourceForbiddenError("Provided localip is not allowed! IP: '"+localip+"'")
1395
1396  if not _is_allowed_localport("TCP", localport):
1397    raise ResourceForbiddenError("Provided localport is not allowed! Port: "+str(localport))
1398
1399  # This is used to check if there is an existing connection with the same identity
1400  identity = ("TCP", localip, localport, None, None) 
1401
1402  try:
1403    # Check if localip is on loopback
1404    on_loopback = _is_loopback_ipaddr(localip)
1405    # Get the socket
1406    sock = _get_tcp_socket(localip,localport)     
1407    nanny.tattle_add_item('insockets',id(sock))
1408    # Get the maximum number of outsockets
1409    max_outsockets = nanny.get_resource_limit("outsockets")        
1410    # If we have restrictions, then we want to set the outsocket
1411    # limit
1412    if max_outsockets:
1413      # Set the backlog to be the maximum number of outsockets
1414      sock.listen(max_outsockets)
1415    else:
1416      sock.listen(5)
1417
1418  except Exception, e:
1419    
1420    # Check if this an already in use error
1421    if _is_addr_in_use_exception(e): 
1422      # Call _conn_cleanup_check to determine if this is because
1423      # the socket is being cleaned up or if it is actively being used
1424      # This will always raise DuplicateTupleError or
1425      # CleanupInProgressError or AlreadyListeningError   
1426      _conn_cleanup_check(identity)
1427 
1428    # Check if this is a binding error
1429    if _is_addr_unavailable_exception(e):
1430      # Call _conn_alreadyexists_check to determine if this is because
1431      # the connection is active or not      
1432      _conn_alreadyexists_check(identity)
1433    # Unknown error...
1434    else:
1435        raise
1436
1437  server_sock = TCPServerSocket(sock, on_loopback)
1438
1439  # Return the TCPServerSocket
1440  return server_sock
1441
1442
1443# Private method to create a TCP socket and bind
1444# to a localip and localport.
1445# 
1446def _get_tcp_socket(localip, localport):
1447  # Create the TCP socket
1448  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
1449  # Reuse the socket if it's "pseudo-availible"
1450  s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1451
1452  if localip and localport:
1453    try:
1454      s.bind((localip,localport))
1455    except: # Raise the exception un-tainted
1456      # don't leak sockets
1457      s.close()
1458      raise
1459  return s
1460
1461
1462# Private method to create a UDP socket and bind
1463# to a localip and localport.
1464# 
1465def _get_udp_socket(localip, localport):
1466  # Create the UDP socket
1467  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1468  if localip and localport:
1469    try:
1470      s.bind((localip, localport))
1471    except:
1472      # don't leak sockets
1473      s.close()
1474      raise
1475  return s
1476
1477
1478# Checks if the given real socket would block
1479def _check_socket_state(realsock, waitfor="rw", timeout=0.0):
1480  """
1481  <Purpose>
1482    Checks if the given socket would block on a send() or recv().
1483    In the case of a listening socket, read_will_block equates to
1484    accept_will_block.
1485
1486  <Arguments>
1487    realsock:
1488              A real socket.socket() object to check for.
1489
1490    waitfor:
1491              An optional specifier of what to wait for. "r" for read only, "w" for write only,
1492              and "rw" for read or write. E.g. if timeout is 10, and wait is "r", this will block
1493              for up to 10 seconds until read_will_block is false. If you specify "r", then
1494              write_will_block is always true, and if you specify "w" then read_will_block is
1495              always true.
1496
1497    timeout:
1498              An optional timeout to wait for the socket to be read or write ready.
1499
1500  <Returns>
1501    A tuple, (read_will_block, write_will_block).
1502
1503  <Exceptions>
1504    As with select.select(). Probably best to wrap this with _is_recoverable_network_exception
1505    and _is_terminated_connection_exception. Throws an exception if waitfor is not in ["r","w","rw"]
1506  """
1507  # Check that waitfor is valid
1508  if waitfor not in ["rw","r","w"]:
1509    raise Exception, "Illegal waitfor argument!"
1510
1511  # Array to hold the socket
1512  sock_array = [realsock]
1513
1514  # Generate the read/write arrays
1515  read_array = []
1516  if "r" in waitfor:
1517    read_array = sock_array
1518
1519  write_array = []
1520  if "w" in waitfor:
1521    write_array = sock_array
1522
1523  # Call select()
1524  (readable, writeable, exception) = select.select(read_array,write_array,sock_array,timeout)
1525
1526  # If the socket is in the exception list, then assume its both read and writable
1527  if (realsock in exception):
1528    return (False, False)
1529
1530  # Return normally then
1531  return (realsock not in readable, realsock not in writeable)
1532
1533
1534##### Class Definitions
1535
1536# Public.   We pass these to the users for communication purposes
1537class EmulatedSocket:
1538  """
1539  This object is a wrapper around a tcp
1540  TCP socket. It allows for sending and
1541  recieving data, and closing the socket.
1542
1543  It operates in a strictly non-blocking mode,
1544  and uses Exceptions to indicate when an
1545  operation would result in blocking behavior.
1546  """
1547  # Fields:
1548  # socket: This is a TCP Socket  
1549  #
1550  # send_buffer_size: The size of the send buffer. We send less than
1551  #                  this to avoid a bug.
1552  #
1553  # on_loopback: true if the remote ip is a loopback address.
1554  #              this is used for resource accounting.
1555  # sock_lock: Threading Lock on socket object used for 
1556  #            synchronization.
1557  __slots__ = ["socketobj", "send_buffer_size", "on_loopback", "sock_lock"]
1558
1559  
1560  def __init__(self, sock, on_loopback):
1561    """
1562    <Purpose>
1563      Initializes a EmulatedSocket object.
1564
1565    <Arguments>
1566      sock: A TCP Socket
1567
1568      on_loopback: True/False based on whether remote IP is
1569                   on loopback oe not
1570      
1571    <Exceptions>
1572      InteralRepyError is raised if there is no table entry for
1573      the socket.
1574
1575    <Returns>
1576      A EmulatedSocket object.
1577    """
1578    # Store the parameters tuple
1579    self.socketobj = sock
1580    self.on_loopback = on_loopback
1581    self.sock_lock = threading.Lock()
1582    
1583    # Store the socket send buffer size and set to non-blocking
1584    self.send_buffer_size = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
1585    # locking should be unnecessary because there isn't another external
1586    # reference here yet
1587    sock.setblocking(0)
1588
1589    
1590  def _close(self):
1591    """
1592    <Purpose>
1593      Private close method. Called when socket lock is held.
1594      Does not perform any accounting / locking. Those should
1595      be done by the public methods.
1596
1597    <Arguments>
1598      None
1599
1600    <Side Effects>
1601      Closes the socket
1602
1603    <Returns>
1604      None
1605    """
1606    # Clean up the socket
1607    _cleanup_socket(self)
1608
1609    # Replace the socket
1610    self.socketobj = None
1611
1612
1613  def close(self):
1614    """
1615      <Purpose>
1616        Closes a socket.   Pending remote recv() calls will return with the 
1617        remaining information.   Local recv / send calls will fail after this.
1618
1619      <Arguments>
1620        None
1621
1622      <Exceptions>
1623        None
1624
1625      <Side Effects>
1626        Pending local recv calls will either return or have an exception.
1627
1628      <Resource Consumption>
1629        If the connection is closed, no resources are consumed. This operation
1630        uses 64 bytes of netrecv, and 128 bytes of netsend.
1631        This call also stops consuming an outsocket.
1632
1633      <Returns>
1634        True if this is the first close call to this socket, False otherwise.
1635    """
1636    # Get the socket lock
1637    socket_lock = self.sock_lock
1638   
1639    if (self.socketobj == None):
1640      return False
1641    # Wait for resources
1642    if self.on_loopback:
1643      nanny.tattle_quantity('looprecv', 0)
1644      nanny.tattle_quantity('loopsend', 0)
1645    else:
1646      nanny.tattle_quantity('netrecv', 0)
1647      nanny.tattle_quantity('netsend', 0)
1648
1649    # Acquire the lock
1650    socket_lock.acquire()
1651    try:
1652      # Internal close
1653      self._close()
1654
1655      # Tattle the resources
1656      if self.on_loopback:
1657        nanny.tattle_quantity('looprecv',64)
1658        nanny.tattle_quantity('loopsend',128)
1659      else:
1660        nanny.tattle_quantity('netrecv',64)
1661        nanny.tattle_quantity('netsend',128)
1662
1663      # Done
1664      return True
1665
1666    finally:
1667      socket_lock.release()
1668
1669
1670
1671  def recv(self,bytes):
1672    """
1673      <Purpose>
1674        Receives data from a socket.   It may receive fewer bytes than 
1675        requested.   
1676
1677      <Arguments>
1678        bytes: 
1679           The maximum number of bytes to read.   
1680
1681      <Exceptions>
1682        SocketClosedLocal is raised if the socket was closed locally.
1683        SocketClosedRemote is raised if the socket was closed remotely.
1684        SocketWouldBlockError is raised if the socket operation would block.
1685
1686      <Side Effects>
1687        None.
1688
1689      <Resource Consumptions>
1690        This operations consumes 64 + amount of data  in bytes
1691        wo…

Large files files are truncated, but you can click here to view the full file