|
|
@ -31,119 +31,119 @@ DEBUG = False |
|
|
|
|
|
|
|
# called by each thread |
|
|
|
def do_socket_connect(queue, ip, PORT, SSL, ipv4delay): |
|
|
|
# connect to the ip, and put the result into the queue |
|
|
|
if DEBUG: logging.debug("Input for thread is %s %s %s", ip, PORT, SSL) |
|
|
|
|
|
|
|
try: |
|
|
|
# CREATE SOCKET |
|
|
|
if ip.find(':') >= 0: |
|
|
|
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) |
|
|
|
if ip.find('.') >= 0: |
|
|
|
time.sleep(ipv4delay) # IPv4 ... so a delay for IPv4 as we prefer IPv6. Note: ipv4delay could be 0 |
|
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
|
|
|
|
|
|
s.settimeout(3) |
|
|
|
if not SSL: |
|
|
|
# Connect ... |
|
|
|
s.connect((ip, PORT)) |
|
|
|
# ... and close |
|
|
|
s.close() |
|
|
|
else: |
|
|
|
# WRAP SOCKET |
|
|
|
wrappedSocket = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1) |
|
|
|
# CONNECT |
|
|
|
wrappedSocket.connect((ip, PORT)) |
|
|
|
# CLOSE SOCKET CONNECTION |
|
|
|
wrappedSocket.close() |
|
|
|
queue.put((ip, True)) |
|
|
|
if DEBUG: logging.debug("connect to %s OK", ip) |
|
|
|
except: |
|
|
|
queue.put((ip, False)) |
|
|
|
if DEBUG: logging.debug("connect to %s not OK", ip) |
|
|
|
pass |
|
|
|
# connect to the ip, and put the result into the queue |
|
|
|
if DEBUG: logging.debug("Input for thread is %s %s %s", ip, PORT, SSL) |
|
|
|
|
|
|
|
try: |
|
|
|
# CREATE SOCKET |
|
|
|
if ip.find(':') >= 0: |
|
|
|
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) |
|
|
|
if ip.find('.') >= 0: |
|
|
|
time.sleep(ipv4delay) # IPv4 ... so a delay for IPv4 as we prefer IPv6. Note: ipv4delay could be 0 |
|
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
|
|
|
|
|
|
s.settimeout(3) |
|
|
|
if not SSL: |
|
|
|
# Connect ... |
|
|
|
s.connect((ip, PORT)) |
|
|
|
# ... and close |
|
|
|
s.close() |
|
|
|
else: |
|
|
|
# WRAP SOCKET |
|
|
|
wrappedSocket = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1) |
|
|
|
# CONNECT |
|
|
|
wrappedSocket.connect((ip, PORT)) |
|
|
|
# CLOSE SOCKET CONNECTION |
|
|
|
wrappedSocket.close() |
|
|
|
queue.put((ip, True)) |
|
|
|
if DEBUG: logging.debug("connect to %s OK", ip) |
|
|
|
except: |
|
|
|
queue.put((ip, False)) |
|
|
|
if DEBUG: logging.debug("connect to %s not OK", ip) |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def happyeyeballs(HOST, **kwargs): |
|
|
|
# Happyeyeballs function, with caching of the results |
|
|
|
|
|
|
|
# Fill out the parameters into the variables |
|
|
|
try: |
|
|
|
PORT = kwargs['port'] |
|
|
|
except: |
|
|
|
PORT = 80 |
|
|
|
try: |
|
|
|
SSL = kwargs['ssl'] |
|
|
|
except: |
|
|
|
SSL = False |
|
|
|
try: |
|
|
|
preferipv6 = kwargs['preferipv6'] |
|
|
|
except: |
|
|
|
preferipv6 = True # prefer IPv6, so give IPv6 connects a head start by delaying IPv4 |
|
|
|
|
|
|
|
|
|
|
|
# Find out if a cached result is available, and recent enough: |
|
|
|
timecurrent = int(time.time()) # current time in seconds since epoch |
|
|
|
retentionseconds = 100 |
|
|
|
hostkey = (HOST, PORT, SSL, preferipv6) # Example key: (u'ssl.astraweb.com', 563, True, True) |
|
|
|
try: |
|
|
|
happyeyeballs.happylist[hostkey] # just to check: does it exist? |
|
|
|
# No exception, so entry exists, so let's check the time: |
|
|
|
timecached = happyeyeballs.happylist[hostkey][1] |
|
|
|
if timecurrent - timecached <= retentionseconds: |
|
|
|
if DEBUG: logging.debug("existing cached result recent enough") |
|
|
|
return happyeyeballs.happylist[hostkey][0] |
|
|
|
else: |
|
|
|
if DEBUG: logging.debug("existing cached result too old. Find a new one") |
|
|
|
# Continue a few lines down |
|
|
|
except: |
|
|
|
# Exception, so entry not there, so we have to fill it out |
|
|
|
if DEBUG: logging.debug("Host not yet in the cache. Find entry") |
|
|
|
pass |
|
|
|
# we only arrive here if the entry has to be determined. So let's do that: |
|
|
|
|
|
|
|
# We have to determine the (new) best IP address |
|
|
|
start = time.clock() |
|
|
|
if DEBUG: logging.debug("\n\n%s %s %s %s", HOST, PORT, SSL, preferipv6) |
|
|
|
|
|
|
|
ipv4delay = 0 |
|
|
|
try: |
|
|
|
# Check if there is an AAAA / IPv6 result for this host: |
|
|
|
info = socket.getaddrinfo(HOST, PORT, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME) |
|
|
|
if DEBUG: logging.debug("IPv6 address found for %s", HOST) |
|
|
|
if preferipv6: |
|
|
|
ipv4delay=0.1 # preferipv6, AND at least one IPv6 found, so give IPv4 (!) a delay so that IPv6 has a head start and is preferred |
|
|
|
except: |
|
|
|
if DEBUG: logging.debug("No IPv6 address found for %s", HOST) |
|
|
|
|
|
|
|
myqueue = Queue.Queue() # queue used for threads giving back the results |
|
|
|
|
|
|
|
try: |
|
|
|
# Happyeyeballs function, with caching of the results |
|
|
|
|
|
|
|
# Fill out the parameters into the variables |
|
|
|
try: |
|
|
|
PORT = kwargs['port'] |
|
|
|
except: |
|
|
|
PORT = 80 |
|
|
|
try: |
|
|
|
SSL = kwargs['ssl'] |
|
|
|
except: |
|
|
|
SSL = False |
|
|
|
try: |
|
|
|
preferipv6 = kwargs['preferipv6'] |
|
|
|
except: |
|
|
|
preferipv6 = True # prefer IPv6, so give IPv6 connects a head start by delaying IPv4 |
|
|
|
|
|
|
|
|
|
|
|
# Find out if a cached result is available, and recent enough: |
|
|
|
timecurrent = int(time.time()) # current time in seconds since epoch |
|
|
|
retentionseconds = 100 |
|
|
|
hostkey = (HOST, PORT, SSL, preferipv6) # Example key: (u'ssl.astraweb.com', 563, True, True) |
|
|
|
try: |
|
|
|
happyeyeballs.happylist[hostkey] # just to check: does it exist? |
|
|
|
# No exception, so entry exists, so let's check the time: |
|
|
|
timecached = happyeyeballs.happylist[hostkey][1] |
|
|
|
if timecurrent - timecached <= retentionseconds: |
|
|
|
if DEBUG: logging.debug("existing cached result recent enough") |
|
|
|
return happyeyeballs.happylist[hostkey][0] |
|
|
|
else: |
|
|
|
if DEBUG: logging.debug("existing cached result too old. Find a new one") |
|
|
|
# Continue a few lines down |
|
|
|
except: |
|
|
|
# Exception, so entry not there, so we have to fill it out |
|
|
|
if DEBUG: logging.debug("Host not yet in the cache. Find entry") |
|
|
|
pass |
|
|
|
# we only arrive here if the entry has to be determined. So let's do that: |
|
|
|
|
|
|
|
# We have to determine the (new) best IP address |
|
|
|
start = time.clock() |
|
|
|
if DEBUG: logging.debug("\n\n%s %s %s %s", HOST, PORT, SSL, preferipv6) |
|
|
|
|
|
|
|
ipv4delay = 0 |
|
|
|
try: |
|
|
|
# Check if there is an AAAA / IPv6 result for this host: |
|
|
|
socket.getaddrinfo(HOST, PORT, socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_IP, socket.AI_CANONNAME) |
|
|
|
if DEBUG: logging.debug("IPv6 address found for %s", HOST) |
|
|
|
if preferipv6: |
|
|
|
ipv4delay=0.1 # preferipv6, AND at least one IPv6 found, so give IPv4 (!) a delay so that IPv6 has a head start and is preferred |
|
|
|
except: |
|
|
|
if DEBUG: logging.debug("No IPv6 address found for %s", HOST) |
|
|
|
|
|
|
|
myqueue = Queue.Queue() # queue used for threads giving back the results |
|
|
|
|
|
|
|
try: |
|
|
|
# Get all IP (IPv4 and IPv6) addresses: |
|
|
|
allinfo = socket.getaddrinfo(HOST, PORT, 0, 0, socket.IPPROTO_TCP) |
|
|
|
for info in allinfo: |
|
|
|
address = info[4][0] |
|
|
|
thisthread = threading.Thread(target=do_socket_connect, args=(myqueue, address, PORT, SSL, ipv4delay)) |
|
|
|
thisthread.daemon = True |
|
|
|
thisthread.start() |
|
|
|
result = None # default return value, used if none of threads says True/"OK", so no connect on any IP address |
|
|
|
# start reading from the Queue for message from the threads: |
|
|
|
for i in range(len(allinfo)): |
|
|
|
s = myqueue.get() # get a response |
|
|
|
if s[1] == True: |
|
|
|
result = s[0] |
|
|
|
break # the first True/"OK" is enough, so break out of for loop |
|
|
|
except: |
|
|
|
if DEBUG: logging.debug("something went wrong in the try block") |
|
|
|
result = None |
|
|
|
logging.info("Quickest IP address for %s (port %s, ssl %s, preferipv6 %s) is %s", HOST, PORT, SSL, preferipv6, result) |
|
|
|
delay = int(1000 * (time.clock() - start)) |
|
|
|
logging.debug("Happy Eyeballs lookup and port connect took %s ms", delay) |
|
|
|
|
|
|
|
# We're done. Store and return the result |
|
|
|
if result: |
|
|
|
happyeyeballs.happylist[hostkey] = ( result, timecurrent ) |
|
|
|
if DEBUG: logging.debug("Determined new result for %s with result %s", (hostkey, happyeyeballs.happylist[hostkey]) ) |
|
|
|
return result |
|
|
|
allinfo = socket.getaddrinfo(HOST, PORT, 0, 0, socket.IPPROTO_TCP) |
|
|
|
for info in allinfo: |
|
|
|
address = info[4][0] |
|
|
|
thisthread = threading.Thread(target=do_socket_connect, args=(myqueue, address, PORT, SSL, ipv4delay)) |
|
|
|
thisthread.daemon = True |
|
|
|
thisthread.start() |
|
|
|
result = None # default return value, used if none of threads says True/"OK", so no connect on any IP address |
|
|
|
# start reading from the Queue for message from the threads: |
|
|
|
for i in range(len(allinfo)): |
|
|
|
s = myqueue.get() # get a response |
|
|
|
if s[1] == True: |
|
|
|
result = s[0] |
|
|
|
break # the first True/"OK" is enough, so break out of for loop |
|
|
|
except: |
|
|
|
if DEBUG: logging.debug("something went wrong in the try block") |
|
|
|
result = None |
|
|
|
logging.info("Quickest IP address for %s (port %s, ssl %s, preferipv6 %s) is %s", HOST, PORT, SSL, preferipv6, result) |
|
|
|
delay = int(1000 * (time.clock() - start)) |
|
|
|
logging.debug("Happy Eyeballs lookup and port connect took %s ms", delay) |
|
|
|
|
|
|
|
# We're done. Store and return the result |
|
|
|
if result: |
|
|
|
happyeyeballs.happylist[hostkey] = ( result, timecurrent ) |
|
|
|
if DEBUG: logging.debug("Determined new result for %s with result %s", (hostkey, happyeyeballs.happylist[hostkey]) ) |
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
happyeyeballs.happylist = {} # The cached results. This static variable must be after the def happyeyeballs() |
|
|
@ -152,27 +152,27 @@ happyeyeballs.happylist = {} # The cached results. This static variable must |
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|
|
|
|
logger = logging.getLogger('') |
|
|
|
logger.setLevel(logging.INFO) |
|
|
|
if DEBUG: logger.setLevel(logging.DEBUG) |
|
|
|
|
|
|
|
# plain HTTP/HTTPS sites: |
|
|
|
print happyeyeballs('www.google.com') |
|
|
|
print happyeyeballs('www.google.com', port=443, ssl=True) |
|
|
|
print happyeyeballs('www.nu.nl') |
|
|
|
|
|
|
|
# newsservers: |
|
|
|
print happyeyeballs('newszilla6.xs4all.nl', port=119) |
|
|
|
print happyeyeballs('newszilla.xs4all.nl', port=119) |
|
|
|
print happyeyeballs('block.cheapnews.eu', port=119) |
|
|
|
print happyeyeballs('block.cheapnews.eu', port=443, ssl=True) |
|
|
|
print happyeyeballs('sslreader.eweka.nl', port=563, ssl=True) |
|
|
|
print happyeyeballs('news.thundernews.com', port=119) |
|
|
|
print happyeyeballs('news.thundernews.com', port=119, preferipv6=False) |
|
|
|
print happyeyeballs('secure.eu.thundernews.com', port=563, ssl=True) |
|
|
|
|
|
|
|
# Strange cases |
|
|
|
print happyeyeballs('does.not.resolve', port=443, ssl=True) |
|
|
|
print happyeyeballs('www.google.com', port=119) |
|
|
|
print happyeyeballs('216.58.211.164') |
|
|
|
logger = logging.getLogger('') |
|
|
|
logger.setLevel(logging.INFO) |
|
|
|
if DEBUG: logger.setLevel(logging.DEBUG) |
|
|
|
|
|
|
|
# plain HTTP/HTTPS sites: |
|
|
|
print happyeyeballs('www.google.com') |
|
|
|
print happyeyeballs('www.google.com', port=443, ssl=True) |
|
|
|
print happyeyeballs('www.nu.nl') |
|
|
|
|
|
|
|
# newsservers: |
|
|
|
print happyeyeballs('newszilla6.xs4all.nl', port=119) |
|
|
|
print happyeyeballs('newszilla.xs4all.nl', port=119) |
|
|
|
print happyeyeballs('block.cheapnews.eu', port=119) |
|
|
|
print happyeyeballs('block.cheapnews.eu', port=443, ssl=True) |
|
|
|
print happyeyeballs('sslreader.eweka.nl', port=563, ssl=True) |
|
|
|
print happyeyeballs('news.thundernews.com', port=119) |
|
|
|
print happyeyeballs('news.thundernews.com', port=119, preferipv6=False) |
|
|
|
print happyeyeballs('secure.eu.thundernews.com', port=563, ssl=True) |
|
|
|
|
|
|
|
# Strange cases |
|
|
|
print happyeyeballs('does.not.resolve', port=443, ssl=True) |
|
|
|
print happyeyeballs('www.google.com', port=119) |
|
|
|
print happyeyeballs('216.58.211.164') |
|
|
|
|
|
|
|