forked from SeattleTestbed/repy_v1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathemulcomm.py
executable file
·1995 lines (1474 loc) · 57.8 KB
/
emulcomm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Author: Justin Cappos
Start Date: 27 June 2008
Description:
This is a collection of communications routines that provide a programmer
with a reasonable environment. This is used by repy.py to provide a
highly restricted (but usable) environment.
Note that this module can also be used to place restrictions on the
interfaces and IP addresses that are used by Seattle. The order in which
the interfaces are specified is their preference.
One odd piece of behavior is that if an IP address changes, you can't use
it until calling getmyip. It's not clear how you would know about this
occurrence, but it should be noted.
"""
import restrictions
import socket
# Armon: Used to check if a socket is ready
import select
# socket uses getattr and setattr. We need to make these available to it...
socket.getattr = getattr
socket.setattr = setattr
# needed to set threads for recvmess and waitforconn
import threading
threading.hasattr = hasattr # Fix for #1039
# to force destruction of old sockets
import gc
# So I can exit all threads when an error occurs or do select
import harshexit
# Needed for finding out info about sockets, available interfaces, etc
import nonportable
# So I can print a clean traceback when an error happens
import tracebackrepy
# accounting
import nanny
# give me uniqueIDs for the comminfo table
import idhelper
# for sleep
import time
# Armon: Used for decoding the error messages
import errno
# Armon: Used for getting the constant IP values for resolving our external IP
import repy_constants
# The architecture is that I have a thread which "polls" all of the sockets
# that are being listened on using select. If a connection
# oriented socket has a connection pending, or a message-based socket has a
# message pending, and there are enough events it calls the appropriate
# function.
# Table of communications structures:
# {'type':'UDP','localip':ip, 'localport':port,'function':func,'socket':s, outgoing:True, 'closing_lock':lockobj}
# {'type':'TCP','remotehost':None, 'remoteport':None,'localip':None,'localport':None, 'socket':s, 'function':func, outgoing:False, 'closing_lock':lockobj}
comminfo = {}
# If we have a preference for an IP/Interface this flag is set to True
user_ip_interface_preferences = False
# Do we allow non-specified IPs
allow_nonspecified_ips = True
# Armon: Specified the list of allowed IP and Interfaces in order of their preference
# The basic structure is list of tuples (IP, Value), IP is True if its an IP, False if its an interface
user_specified_ip_interface_list = []
# This list caches the allowed IP's
# It is updated at the launch of repy, or by calls to getmyip and update_ip_cache
# NOTE: The loopback address 127.0.0.1 is always permitted. update_ip_cache will always add this
# if it is not specified explicitly by the user
allowediplist = []
cachelock = threading.Lock() # This allows only a single simultaneous cache update
# Determines if a specified IP address is allowed in the context of user settings
def ip_is_allowed(ip):
"""
<Purpose>
Determines if a given IP is allowed, by checking against the cached allowed IP's.
<Arguments>
ip: The IP address to search for.
<Returns>
True, if allowed. False, otherwise.
"""
global allowediplist
global user_ip_interface_preferences
global allow_nonspecified_ips
# If there is no preference, anything goes
# same with allow_nonspecified_ips
if not user_ip_interface_preferences or allow_nonspecified_ips:
return True
# Check the list of allowed IP's
return (ip in allowediplist)
# Only appends the elem to lst if the elem is unique
def unique_append(lst, elem):
if elem not in lst:
lst.append(elem)
# This function updates the allowed IP cache
# It iterates through all possible IP's and stores ones which are bindable as part of the allowediplist
def update_ip_cache():
global allowediplist
global user_ip_interface_preferences
global user_specified_ip_interface_list
global allow_nonspecified_ips
# If there is no preference, this is a no-op
if not user_ip_interface_preferences:
return
# Acquire the lock to update the cache
cachelock.acquire()
# If there is any exception release the cachelock
try:
# Stores the IP's
allowed_list = []
# Iterate through the allowed list, handle each element
for (is_ip_addr, value) in user_specified_ip_interface_list:
# Handle normal IP's
if is_ip_addr:
unique_append(allowed_list, value)
# Handle interfaces
else:
try:
# Get the IP's associated with the NIC
interface_ips = nonportable.os_api.get_interface_ip_addresses(value)
for interface_ip in interface_ips:
unique_append(allowed_list, interface_ip)
except:
# Catch exceptions if the NIC does not exist
pass
# This will store all the IP's that we are able to bind to
bindable_list = []
# Try binding to every ip
for ip in allowed_list:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
sock.bind((ip,0))
except:
pass # Not a good ip, skip it
else:
bindable_list.append(ip) # This is a good ip, store it
finally:
sock.close()
# Add loopback
unique_append(bindable_list, "127.0.0.1")
# Update the global cache
allowediplist = bindable_list
finally:
# Release the lock
cachelock.release()
########################### General Purpose socket functions #################
def is_already_connected_exception(exceptionobj):
"""
<Purpose>
Determines if a given error number indicates that the socket
is already connected.
<Arguments>
An exception object from a network call.
<Returns>
True if already connected, false otherwise
"""
# Get the type
exception_type = type(exceptionobj)
# Only continue if the type is socket.error
if exception_type is not socket.error:
return False
# Get the error number
errnum = exceptionobj[0]
# Store a list of error messages meaning we are connected
connected_errors = ["EISCONN", "WSAEISCONN"]
# Convert the errno to and error string name
try:
errname = errno.errorcode[errnum]
except Exception,e:
# The error is unknown for some reason...
errname = None
# Return if the error name is in our white list
return (errname in connected_errors)
def is_recoverable_network_exception(exceptionobj):
"""
<Purpose>
Determines if a given error number is recoverable or fatal.
<Arguments>
An exception object from a network call.
<Returns>
True if potentially recoverable, False if fatal.
"""
# Get the type
exception_type = type(exceptionobj)
# socket.timeout is recoverable always
if exception_type == socket.timeout:
return True
# Only continue if the type is socket.error or select.error
elif exception_type != socket.error and exception_type != select.error:
return False
# Get the error number
errnum = exceptionobj[0]
# Store a list of recoverable error numbers
recoverable_errors = ["EINTR","EAGAIN","EBUSY","EWOULDBLOCK","ETIMEDOUT","ERESTART",
"WSAEINTR","WSAEWOULDBLOCK","WSAETIMEDOUT","EALREADY","WSAEALREADY",
"EINPROGRESS","WSAEINPROGRESS"]
# Convert the errno to and error string name
try:
errname = errno.errorcode[errnum]
except Exception,e:
# The error is unknown for some reason...
errname = None
# Return if the error name is in our white list
return (errname in recoverable_errors)
# Determines based on exception if the connection has been terminated
def is_terminated_connection_exception(exceptionobj):
"""
<Purpose>
Determines if the exception is indicated the connection is terminated.
<Arguments>
An exception object from a network call.
<Returns>
True if the connection is terminated, False otherwise.
False means we could not determine with certainty if the socket is closed.
"""
# Get the type
exception_type = type(exceptionobj)
# We only want to continue if it is socket.error or select.error
if exception_type != socket.error and exception_type != select.error:
return False
# Get the error number
errnum = exceptionobj[0]
# Store a list of errors which indicate connection closed
connection_closed_errors = ["EPIPE","EBADF","EBADR","ENOLINK","EBADFD","ENETRESET",
"ECONNRESET","WSAEBADF","WSAENOTSOCK","WSAECONNRESET",]
# Convert the errnum to an error string
try:
errname = errno.errorcode[errnum]
except:
# The error number is not defined...
errname = None
# Return whether the errname is in our pre-defined list
return (errname in connection_closed_errors)
# Armon: This is used for semantics, to determine if we have a valid IP.
def is_valid_ip_address(ipaddr):
"""
<Purpose>
Determines if ipaddr is a valid IP address.
Address 0.0.0.0 is considered valid.
<Arguments>
ipaddr: String to check for validity. (It will check that this is a string).
<Returns>
True if a valid IP, False otherwise.
"""
# Argument must be of the string type
if not type(ipaddr) == str:
return False
# A valid IP should have 4 segments, explode on the period
parts = ipaddr.split(".")
# Check that we have 4 parts
if len(parts) != 4:
return False
# Check that each segment is a number between 0 and 255 inclusively.
for part in parts:
# Check the length of each segment
digits = len(part)
if digits >= 1 and digits <= 3:
# Attempt to convert to an integer
try:
number = int(part)
if not (number >= 0 and number <= 255):
return False
except:
# There was an error converting to an integer, not an IP
return False
else:
return False
# At this point, assume the IP is valid
return True
# Armon: This is used for semantics, to determine if the given port is valid
def is_valid_network_port(port, allowzero=False):
"""
<Purpose>
Determines if a given network port is valid.
<Arguments>
port: A numeric type (this will be checked) port number.
allowzero: Allows 0 as a valid port if true
<Returns>
True if valid, False otherwise.
"""
# Check the type is int or long
if not (type(port) == long or type(port) == int):
return False
return ((allowzero and port == 0) or (port >= 1 and port <= 65535))
# Constant prefix for comm handles.
COMM_PREFIX = "_COMMH:"
# Makes commhandles for networking functions
def generate_commhandle():
"""
<Purpose>
Generates a string commhandle that can be used to uniquely identify
a socket, while providing a means of "pseudo" verification.
<Returns>
A string handle.
"""
# Get a unique value from idhelper
uniqueid = idhelper.getuniqueid()
# Return the id prefixed by the COMM_PREFIX
return (COMM_PREFIX + uniqueid)
# Helps determine if a commhandle is valid
def is_valid_commhandle(commhandle):
"""
<Purpose>
Determines if the given commhandle is potentially valid.
This is not a guarentee of validity, e.g. the commhandle may not
exist.
<Arguments>
commhandle:
The handle to be checked for validity
<Returns>
True if the handle if valid, False otherwise.
"""
# Check if the handle is a string, this is a requirement
if type(commhandle) != str:
return False
# Return if the handle starts with the correct prefix
# This way we are not relying on the format of idhelper.getuniqueid()
return commhandle.startswith(COMM_PREFIX)
########################### SocketSelector functions #########################
# used to lock the methods that check to see if the thread is running
selectorlock = threading.Lock()
# is the selector thread started...
selectorstarted = False
#### helper functions
# return the table entry for this socketobject
def find_socket_entry(socketobject):
for commhandle in comminfo.keys():
if comminfo[commhandle]['socket'] is socketobject:
return comminfo[commhandle], commhandle
raise KeyError, "Can't find commhandle"
# wait until there is a free event
def wait_for_event(eventname):
while True:
try:
nanny.tattle_add_item('events',eventname)
break
except Exception:
# They must be over their event limit. I'll sleep and check later
time.sleep(.1)
def should_selector_exit():
global selectorstarted
# Let's check to see if we should exit... False means "nonblocking"
if selectorlock.acquire(False):
# Check that selector started is true. This should *always* be the case
# when I enter this function. This is to test for bugs in my code
if not selectorstarted:
# This will cause the program to exit and log things if logging is
# enabled. -Brent
tracebackrepy.handle_internalerror("SocketSelector is started when" +
' selectorstarted is False', 39)
# Got the lock...
for comm in comminfo.values():
# I'm listening and waiting so all is well
if not comm['outgoing']:
break
else:
# there is no listening function so I should exit...
selectorstarted = False
# I'm exiting...
nanny.tattle_remove_item('events',"SocketSelector")
selectorlock.release()
return True
# I should continue
selectorlock.release()
return False
# This function starts a thread to handle an entry with a readable socket in
# the comminfo table
def start_event(entry, handle,eventhandle):
if entry['type'] == 'UDP':
# some sort of socket error, I'll assume they closed the socket or it's
# not important
try:
# NOTE: 64K is the max UDP dgram size. Let's read it all
data, addr = entry['socket'].recvfrom(65535)
except socket.error:
# they closed in the meantime?
nanny.tattle_remove_item('events',eventhandle)
return
# wait if we're over the limit
if data:
if is_loopback(entry['localip']):
nanny.tattle_quantity('looprecv',len(data))
else:
# We will charge looprecv for UDP from the net, (see #887 for details)
nanny.tattle_quantity('looprecv',len(data))
else:
# no data... Let's stop this...
nanny.tattle_remove_item('events',eventhandle)
return
try:
EventDeliverer(entry['function'],(addr[0], addr[1], data, handle), eventhandle).start()
except Exception, e:
# This is an internal error I think...
# This will cause the program to exit and log things if logging is
# enabled. -Brent
tracebackrepy.handle_internalerror("Can't start UDP EventDeliverer '" + str(e)+"'", 29)
# or it's a TCP accept event...
elif entry['type'] == 'TCP':
try:
realsocket, addr = entry['socket'].accept()
except socket.error:
# they closed in the meantime?
nanny.tattle_remove_item('events',eventhandle)
return
# put this handle in the table
newhandle = generate_commhandle()
comminfo[newhandle] = {'type':'TCP','remotehost':addr[0], 'remoteport':addr[1],'localip':entry['localip'],'localport':entry['localport'],'socket':realsocket,'outgoing':True, 'closing_lock':threading.Lock()}
# I don't think it makes sense to count this as an outgoing socket, does
# it?
# Armon: Create the emulated socket after the table entry
safesocket = emulated_socket(newhandle)
try:
EventDeliverer(entry['function'],(addr[0], addr[1], safesocket, newhandle, handle),eventhandle).start()
except Exception, e:
# This is an internal error I think...
# This will cause the program to exit and log things if logging is
# enabled. -Brent
tracebackrepy.handle_internalerror("Can't start TCP EventDeliverer '"+str(e)+"'", 23)
else:
# Should never get here
# This will cause the program to exit and log things if logging is
# enabled. -Brent
tracebackrepy.handle_internalerror("In start event, Unknown entry type '"+entry['type']+"'", 51)
# Armon: What is the maximum number of samples to perform per second?
# This is to prevent excessive sampling if there is a bad socket and
# select() returns before timing out
MAX_SAMPLES_PER_SEC = 10
TIME_BETWEEN_SAMPLES = 1.0 / MAX_SAMPLES_PER_SEC
# Check for sockets using select and fire up user event threads as needed.
#
# This class holds nearly all of the complexity in this module. It's
# basically just a loop that gets pending sockets (using select) and then
# fires up events that call user provided functions
class SocketSelector(threading.Thread):
def __init__(self):
threading.Thread.__init__(self, name="SocketSelector")
# Gets a list of all the sockets which are ready to have
# accept() called on them
def get_acceptable_sockets(self):
# get the list of socket objects we might have a pending request on
requestlist = []
for comm in comminfo.values():
if not comm['outgoing']:
requestlist.append(comm['socket'])
# nothing to request. We should loop back around and check if all
# sockets have been closed
if requestlist == []:
return []
# Perform a select on these sockets
try:
# Call select
(acceptable, not_applic, has_excp) = select.select(requestlist,[],requestlist,0.5)
# Add all the sockets with exceptions to the acceptable list
for sock in has_excp:
if sock not in acceptable:
acceptable.append(sock)
# Return the acceptable list
return acceptable
# There was probably an exception on the socket level, check individually
except:
# Hold the ready sockets
readylist = []
# Check each requested socket
for socket in requestlist:
try:
(accept_will_block, write_will_block) = socket_state(socket, "r")
if not accept_will_block:
readylist.append(socket)
# Ignore errors, probably the socket is closed.
except:
pass
# Return the ready list
return readylist
def run(self):
# Keep track of the last sample time
# updated when there are no ready sockets
last_sample = 0
while True:
# I'll stop myself only when there are no active threads to monitor
if should_selector_exit():
return
# If the last sample with 0 ready sockets was less than TIME_BETWEEN_SAMPLES
# seconds ago, sleep a while. This is to prevent a tight loop from consuming
# CPU time doing nothing.
current_time = nonportable.getruntime()
time_diff = current_time - last_sample
if time_diff < TIME_BETWEEN_SAMPLES:
time.sleep(TIME_BETWEEN_SAMPLES - time_diff)
# Get all the ready sockets
readylist = self.get_acceptable_sockets()
# If there is nothing to do, potentially delay the next sample
if len(readylist) == 0:
last_sample = current_time
# go through the pending sockets, grab an event and then start a thread
# to handle the connection
for thisitem in readylist:
try:
commtableentry,commhandle = find_socket_entry(thisitem)
except KeyError:
# let's skip this one, it's likely it was closed in the interim
continue
# now it's time to get the event... I'll loop until there is a free
# event
eventhandle = idhelper.getuniqueid()
wait_for_event(eventhandle)
# wait if already oversubscribed
if is_loopback(commtableentry['localip']):
nanny.tattle_quantity('looprecv',0)
else:
nanny.tattle_quantity('netrecv',0)
# Now I can start a thread to run the user's code...
start_event(commtableentry,commhandle,eventhandle)
# this gives an actual event to the user's code
class EventDeliverer(threading.Thread):
func = None
args = None
eventid = None
def __init__(self, f, a,e):
self.func = f
self.args = a
self.eventid = e
# Initialize with a custom and unique thread name
threading.Thread.__init__(self,name=idhelper.get_new_thread_name(COMM_PREFIX))
def run(self):
try:
self.func(*(self.args))
except:
# we probably should exit if they raise an exception in a thread...
tracebackrepy.handle_exception()
harshexit.harshexit(14)
finally:
# our event is going away...
nanny.tattle_remove_item('events',self.eventid)
#### used by other threads to interact with the SocketSelector...
# private. Check if the SocketSelector is running and start it if it isn't
def check_selector():
global selectorstarted
# acquire the lock.
if selectorlock.acquire():
# If I've not started, then start me...
if not selectorstarted:
# wait until there is a free event...
wait_for_event("SocketSelector")
selectorstarted = True
SocketSelector().start()
# verify a thread with the name "SocketSelector" is running
for threadobj in threading.enumerate():
if threadobj.getName() == "SocketSelector":
# all is well
selectorlock.release()
return
# this is bad. The socketselector went away...
# This will cause the program to exit and log things if logging is
# enabled. -Brent
tracebackrepy.handle_internalerror("SocketSelector died", 59)
# return the table entry for this type of socket, ip, port
def find_tip_entry(socktype, ip, port):
for commhandle in comminfo.keys():
if comminfo[commhandle]['type'] == socktype and comminfo[commhandle]['localip'] == ip and comminfo[commhandle]['localport'] == port:
return comminfo[commhandle], commhandle
return (None,None)
# Find a commhandle, given TIPO: type, ip, port, outgoing
def find_tipo_commhandle(socktype, ip, port, outgoing):
for commhandle in comminfo.keys():
if comminfo[commhandle]['type'] == socktype and comminfo[commhandle]['localip'] == ip and comminfo[commhandle]['localport'] == port and comminfo[commhandle]['outgoing'] == outgoing:
return commhandle
return None
# Find an outgoing TCP commhandle, given local ip, local port, remote ip, remote port,
def find_outgoing_tcp_commhandle(localip, localport, remoteip, remoteport):
for commhandle in comminfo.keys():
if comminfo[commhandle]['type'] == "TCP" and comminfo[commhandle]['localip'] == localip \
and comminfo[commhandle]['localport'] == localport and comminfo[commhandle]['remotehost'] == remoteip \
and comminfo[commhandle]['remoteport'] == remoteport and comminfo[commhandle]['outgoing'] == True:
return commhandle
return None
######################### Simple Public Functions ##########################
# Public interface
def gethostbyname_ex(name):
"""
<Purpose>
Provides information about a hostname. Calls socket.gethostbyname_ex()
<Arguments>
name:
The host name to get information about
<Exceptions>
As from socket.gethostbyname_ex()
<Side Effects>
None.
<Returns>
A tuple containing (hostname, aliaslist, ipaddrlist). See the
python docs for socket.gethostbyname_ex()
"""
restrictions.assertisallowed('gethostbyname_ex',name)
# charge 4K for a look up... I don't know the right number, but we should
# charge something. We'll always charge to the netsend interface...
nanny.tattle_quantity('netsend',4096)
nanny.tattle_quantity('netrecv',4096)
return socket.gethostbyname_ex(name)
# Public interface
def getmyip():
"""
<Purpose>
Provides the external IP of this computer. Does some clever trickery.
<Arguments>
None
<Exceptions>
As from socket.gethostbyname_ex()
<Side Effects>
None.
<Returns>
The localhost's IP address
python docs for socket.gethostbyname_ex()
"""
restrictions.assertisallowed('getmyip')
# I got some of this from: http://groups.google.com/group/comp.lang.python/browse_thread/thread/d931cdc326d7032b?hl=en
# Update the cache and return the first allowed IP
# Only if a preference is set
if user_ip_interface_preferences:
update_ip_cache()
# Return the first allowed ip, there is always at least 1 element (loopback)
return allowediplist[0]
# Initialize these to None, so we can detect a failure
myip = None
# It's possible on some platforms (Windows Mobile) that the IP will be
# 0.0.0.0 even when I have a public IP and the external IP is up. However, if
# I get a real connection with SOCK_STREAM, then I should get the real
# answer.
for conn_type in [socket.SOCK_DGRAM, socket.SOCK_STREAM]:
# Try each stable IP
for ip_addr in repy_constants.STABLE_PUBLIC_IPS:
try:
# Try to resolve using the current connection type and
# stable IP, using port 80 since some platforms panic
# when given 0 (FreeBSD)
myip = get_localIP_to_remoteIP(conn_type, ip_addr, 80)
except (socket.error, socket.timeout):
# We can ignore any networking related errors, since we want to try
# the other connection types and IP addresses. If we fail,
# we will eventually raise an exception anyways.
pass
else:
# Return immediately if the IP address is good
if myip != None and myip != '' and myip != "0.0.0.0":
return myip
# Since we haven't returned yet, we must have failed.
# Raise an exception, we must not be connected to the internet
raise Exception("Cannot detect a connection to the Internet.")
def get_localIP_to_remoteIP(connection_type, external_ip, external_port=80):
"""
<Purpose>
Resolve the local ip used when connecting outbound to an external ip.
<Arguments>
connection_type:
The type of connection to attempt. See socket.socket().
external_ip:
The external IP to attempt to connect to.
external_port:
The port on the remote host to attempt to connect to.
<Exceptions>
As with socket.socket(), socketobj.connect(), etc.
<Returns>
The locally assigned IP for the connection.
"""
# Open a socket
sockobj = socket.socket(socket.AF_INET, connection_type)
try:
sockobj.connect((external_ip, external_port))
# Get the local connection information for this socket
(myip, localport) = sockobj.getsockname()
# Always close the socket
finally:
sockobj.close()
return myip
###################### Shared message / connection items ###################
# Used to decide if an IP is the loopback IP or not. This is needed for
# accounting
def is_loopback(host):
if not host.startswith('127.'):
return False
if len(host.split('.')) != 4:
return False
for number in host.split('.'):
for char in number:
if char not in '0123456789':
return False
try:
if int(number) > 255 or int(number) < 0:
return False
except ValueError:
return False
return True
# Public interface !!!
def stopcomm(commhandle):
"""
<Purpose>
Stop handling events for a commhandle. This works for both message and
connection based event handlers.
<Arguments>
commhandle:
A commhandle as returned by recvmess or waitforconn.
<Exceptions>
None.
<Side Effects>
This has an undefined effect on a socket-like object if it is currently
in use.
<Returns>
Returns True if commhandle was successfully closed, False if the handle
cannot be closed (i.e. it was already closed).
"""
# Armon: Check that the handle is valid, an exception needs to be raised otherwise.
if not is_valid_commhandle(commhandle):
raise Exception("Invalid commhandle specified!")
# if it has already been cleaned up, exit.
if commhandle not in comminfo:
# Armon: Semantic update, stopcomm needs to return True/False
# since the handle does not exist we will return False
return False
restrictions.assertisallowed('stopcomm',comminfo[commhandle])
cleanup(commhandle)
# Armon: Semantic update, we successfully closed
# if we made it here, since cleanup blocks.
return True
# Armon: How frequently should we check for the availability of the socket?
RETRY_INTERVAL = 0.2 # In seconds
# Private
def safe_delete_handle(handle):
# Armon: lock the cleanup so that only one thread will do the cleanup, but
# all the others will block as well
try:
handle_lock = comminfo[handle]['closing_lock']
except KeyError:
# Handle a possible race condition, the socket has already been cleaned up.
return
# Acquire the lock
handle_lock.acquire()
try:
# Prevent user from using this handle from this point on