1# Copyright 2009 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Tests to determine connection quality.""" 16 17__author__ = 'tstromberg@google.com (Thomas Stromberg)' 18 19import time 20 21import nameserver 22import nameserver_list 23import util 24 25OPENDNS_NS = '208.67.220.220' 26GOOGLE_NS = '8.8.8.8' 27EXPECTED_CONGESTION_DURATION = 40.0 28CONGESTION_OFFSET_MULTIPLIER = 1 29MAX_CONGESTION_MULTIPLIER = 4.5 30 31 32class OfflineConnection(Exception): 33 34 def __init__(self, value): 35 self.value = value 36 37 def __str__(self): 38 return str(self.value) 39 40 41class ConnectionQuality(object): 42 43 """Methods related to connection quality detection.""" 44 45 def __init__(self, status_callback=None): 46 self.status_callback = status_callback 47 self.primary = self.GetSystemPrimaryNameServer() 48 49 def msg(self, msg, **kwargs): 50 if self.status_callback: 51 self.status_callback(msg, **kwargs) 52 else: 53 print '- %s' % msg 54 55 def GetInterceptionStatus(self): 56 """Check if our packets are actually getting to the correct servers.""" 57 58 opendns = nameserver.NameServer(OPENDNS_NS) 59 (node_id, duration, unused_error) = opendns.RequestNodeId() 60 if 'I am not an OpenDNS resolver' in node_id: 61 return (True, duration) 62 elif node_id: 63 return (False, duration) 64 else: 65 self.msg('DNS interception test failed (no response)') 66 return (False, duration) 67 68 return (False, duration) 69 70 def GetSystemPrimaryNameServer(self): 71 """Return a nameserver object for the system primary.""" 72 73 internal = nameserver_list.InternalNameServers() 74 # In rare cases, we may not find any to use. 75 if not internal: 76 print 'Odd - no built-in nameservers found.' 77 return None 78 else: 79 primary_ip = internal[0] 80 return nameserver.NameServer(primary_ip) 81 82 def GetNegativeResponseDuration(self): 83 """Use the built-in DNS server to query for a negative response.""" 84 if self.primary: 85 return self.primary.TestNegativeResponse() 86 87 def GetGoogleResponseDuration(self): 88 """See how quickly we can query for www.google.com using a remote nameserver.""" 89 gdns = nameserver.NameServer(GOOGLE_NS) 90 return gdns.TimedRequest('A', 'www.google.com.') 91 92 def CheckConnectionQuality(self): 93 """Look how healthy our DNS connection quality. Averages check durations.""" 94 95 is_connection_offline = True 96 durations = [] 97 self.msg('Checking query interception status...') 98 (intercepted, i_duration) = self.GetInterceptionStatus() 99 if i_duration: 100 is_connection_offline = False 101 102 durations.append(i_duration) 103 104 try_count = 3 105 for i in range(try_count): 106 self.msg('Checking connection quality', count=i+1, total=try_count) 107 if self.primary: 108 (broken, unused_warning, n_duration) = self.GetNegativeResponseDuration() 109 if not broken: 110 is_connection_offline = False 111 durations.append(n_duration) 112 113 (unused_response, g_duration, error_msg) = self.GetGoogleResponseDuration() 114 durations.append(g_duration) 115 if not error_msg: 116 is_connection_offline = False 117 118 if is_connection_offline and (i+1) != try_count: 119 self.msg('The internet connection appears to be offline (%s of %s)' % (i+1, try_count)) 120 time.sleep(0.2) 121 122 if is_connection_offline: 123 raise OfflineConnection('It would appear that your internet connection is offline. ' 124 'namebench is not gettng a response for DNS queries to ' 125 '%s, %s, or %s.' % (self.primary.ip, GOOGLE_NS, OPENDNS_NS)) 126 127 duration = util.CalculateListAverage(durations) 128 congestion = duration / EXPECTED_CONGESTION_DURATION 129 self.msg('Congestion level is %2.2fX (check duration: %2.2fms)' % (congestion, duration)) 130 if congestion > 1: 131 # multiplier is 132 multiplier = 1 + ((congestion-1) * CONGESTION_OFFSET_MULTIPLIER) 133 if multiplier > MAX_CONGESTION_MULTIPLIER: 134 multiplier = MAX_CONGESTION_MULTIPLIER 135 else: 136 multiplier = 1 137 return (intercepted, congestion, multiplier, duration) 138