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