00001 import radio
00002 import time
00003 import state_publisher
00004 import config
00005 from twisted.internet import reactor
00006 import mac_addr
00007 import event
00008 import multiset
00009 from twisted.internet.defer import inlineCallbacks
00010 import async_helpers
00011 import random
00012 import logging_config
00013
00014 scan_periods_log = logging_config.get_logger_stream_for_file('scan_periods')
00015 known_bsses_log = logging_config.get_logger_stream_for_file('known_bsses')
00016 radio_manager_decisions = logging_config.get_logger_stream_for_file('console.radio_manager_decisions')
00017
00018 def check_band(freq, bands):
00019 if freq < 3000 and bands & 1:
00020 return True
00021 if freq > 4000 and bands & 2:
00022 return True
00023 return False
00024
00025 class Frequency:
00026 def __init__(self, freq, period):
00027 self.frequency = freq
00028 self.period = period
00029 self.next_scan_time_by_iface = {}
00030 self.next_scan_time = 0
00031 self.do_not_retry_before = {}
00032 self.interfaces = multiset.Multiset()
00033 self.last_hit = 0
00034
00035 def update_next_scan_time(self, iface, when):
00036 self.next_scan_time = when
00037 self.next_scan_time_by_iface[iface] = when
00038
00039 def set_period(self, period):
00040 when = time.time() + period
00041 self.period = period
00042 self.next_scan_time = min(self.next_scan_time, when)
00043 for iface in self.next_scan_time_by_iface:
00044 self.next_scan_time_by_iface[iface] = min(self.next_scan_time_by_iface[iface], when)
00045
00046 class NoFrequenciesReady(Exception):
00047 def __init__(self, next_time):
00048 Exception.__init__(self)
00049 self.next_time = next_time
00050
00051 class FrequencyList:
00052 def __init__(self):
00053 self.frequencies = {}
00054 self.bands = 0
00055 self.scan_period_cold = config.get_parameter('scan_period_cold', 10)
00056 self.scan_reschedule_delay = config.get_parameter('scan_reschedule_delay', 2)
00057 self.scan_period_randomness = config.get_parameter('scan_period_randomness', 0)
00058 self.min_scan_interval = config.get_parameter('min_scan_interval', 1)
00059
00060 def add(self, freq, iface):
00061 if freq not in self.frequencies:
00062 self.frequencies[freq] = Frequency(freq, self.scan_period_cold)
00063 self.frequencies[freq].interfaces.add(iface)
00064
00065 def remove(self, freq, iface):
00066 self.frequencies[freq].interfaces.remove(iface)
00067
00068 def next_scan_freq(self, iface, now = None, allow_early = False):
00069 if now == None:
00070 now = time.time()
00071
00072
00073 active_freqs = filter(lambda f: iface in f.interfaces and check_band(f.frequency, self.bands), self.frequencies.itervalues())
00074
00075
00076 try:
00077 freq = min(active_freqs, key = lambda f: f.next_scan_time)
00078 except ValueError:
00079 raise NoFrequenciesReady(None)
00080
00081
00082 if freq.next_scan_time > now:
00083
00084 freq = min(active_freqs, key = lambda f: f.next_scan_time_by_iface.get(iface,0))
00085
00086 earliest_scan_time = freq.do_not_retry_before.get(iface, 0)
00087 if not allow_early:
00088 earliest_scan_time = max(earliest_scan_time, freq.next_scan_time)
00089 if earliest_scan_time > now:
00090 raise NoFrequenciesReady(earliest_scan_time)
00091 rand_period = freq.period * (0.5 + self.scan_period_randomness * random.random())
00092 freq.update_next_scan_time(iface, now + rand_period)
00093 freq.do_not_retry_before[iface] = now + self.min_scan_interval
00094 return freq.frequency
00095
00096 def next_scan_freqs(self, iface, count, allow_early = False):
00097 now = time.time()
00098 freqs = []
00099 for i in range(count):
00100 try:
00101 f = self.next_scan_freq(iface, now, allow_early)
00102 except NoFrequenciesReady, nfr:
00103 if freqs:
00104 break
00105 raise
00106 freqs.append(f)
00107 return freqs
00108
00109 def set_freq_period(self, freq, period):
00110 f = self.frequencies[freq]
00111 f.set_period(period)
00112
00113 def reschedule(self, iface, freqs):
00114 when = time.time() + self.scan_reschedule_delay
00115 for f in freqs:
00116 self.frequencies[f].update_next_scan_time(iface, when)
00117
00118 def hit(self, freq, stamp):
00119 """Called each time a bss is seen."""
00120 try:
00121 self.frequencies[freq].last_hit = max(self.frequencies[freq].last_hit, stamp)
00122 except KeyError:
00123 print "Got a scan on an unexpected frequency."
00124 pass
00125
00126 def make_id(bss):
00127 return (bss.ssid, bss.bssid)
00128
00129 class Bss:
00130 def __init__(self, bss):
00131 self.id = make_id(bss)
00132 self.ssid = bss.ssid
00133 self.bssid = bss.bssid
00134 self.by_iface = {}
00135 self.frequency = bss.frequency
00136
00137 def update(self, bss, iface):
00138 assert bss.ssid == self.ssid
00139 assert bss.bssid == self.bssid
00140 if self.frequency != bss.frequency:
00141 print "Frequency for bss %s, %s has changed from %i to %i MHz."%(mac_addr.pretty(bss.bssid), bss.ssid, self.frequency, bss.frequency)
00142 self.frequency = bss.frequency
00143 self.by_iface[iface] = bss
00144
00145 def last_seen(self, iface = None):
00146 if iface is None:
00147 return max(bss.stamp.to_sec() for bss in self.by_iface.itervalues())
00148 if iface in self.by_iface:
00149 return self.by_iface[iface].stamp.to_sec()
00150 else:
00151 return 0
00152
00153 class BssList:
00154 def __init__(self, freq_list):
00155 self.bsses = {}
00156 self.freq_list = freq_list
00157 pass
00158
00159 def update(self, bsses, iface):
00160 for bss in bsses:
00161 id = (bss.ssid, bss.bssid)
00162 if id not in self.bsses:
00163 self.bsses[id] = Bss(bss)
00164 self.bsses[id].update(bss, iface)
00165 self.freq_list.hit(bss.frequency, bss.stamp.to_sec())
00166
00167 if True:
00168 all = self.bsses.items()
00169 all.sort(key=lambda (x,y):(y.frequency, x))
00170 now = time.time()
00171 print >> known_bsses_log, "\033[2J\033[0;0H"
00172 print >> known_bsses_log, "Known BSSes bsses:"
00173 for _, bss in all:
00174 print >> known_bsses_log, mac_addr.pretty(bss.bssid), "%20.20s"%bss.ssid, bss.frequency,
00175 ifaces = bss.by_iface.keys()
00176 ifaces.sort()
00177 min_stamp = now - max(bss.by_iface.itervalues(), key = lambda bss: bss.stamp).stamp.to_sec()
00178 max_level = max(bss.by_iface.itervalues(), key = lambda bss: bss.level).level
00179 print >> known_bsses_log, "%5.1f/%3i"%(min_stamp,max_level),
00180 for iface in ifaces:
00181 print >> known_bsses_log, iface.iface, "%5.1f/%3i"%(now - bss.by_iface[iface].stamp.to_sec(), bss.by_iface[iface].level),
00182 print >> known_bsses_log
00183 print >> known_bsses_log
00184
00185 fl = self.freq_list.frequencies.keys()
00186 fl.sort()
00187 for f in fl:
00188 fc = self.freq_list.frequencies[f]
00189 if fc.last_hit:
00190 print >> known_bsses_log, f, "%5.1f"%(now - fc.last_hit),
00191 else:
00192 print >> known_bsses_log, f, "never",
00193 print >> known_bsses_log, "%5.1f"%(fc.next_scan_time - now),
00194 for iface in fc.next_scan_time_by_iface:
00195 print >> known_bsses_log, "%s %5.1f"%(iface.iface, now - fc.next_scan_time_by_iface[iface]),
00196 print >> known_bsses_log
00197 print >> known_bsses_log
00198
00199 class ScanManager:
00200 def __init__(self):
00201 self.failed_scan_delay = config.get_parameter('failed_scan_delay', 1)
00202 self.scan_results = {}
00203 self.frequencies = FrequencyList()
00204 self.num_scan_frequencies = config.get_parameter('num_scan_frequencies', 4)
00205 self.scheduled_scan = {}
00206 self.bss_list = BssList(self.frequencies)
00207 self.new_scan_data = event.Event()
00208
00209 def add_iface(self, iface):
00210
00211 iface.radio_sm.scanning.subscribe(self._scanning_state_cb, iface)
00212 iface.radio_sm.associated.subscribe(self._scanning_state_cb, iface)
00213 iface.radio_sm.associated.subscribe(self._log_associations, iface)
00214 iface.radio_sm.scanning_enabled.subscribe(self._scanning_state_cb, iface)
00215
00216
00217 iface.radio_sm.scan_results_event.subscribe_repeating(self._scan_event_cb, iface)
00218
00219 iface.radio_sm.frequency_list.subscribe(self._frequency_list_cb, iface)
00220
00221 def _scanning_state_cb(self, iface, old_state, new_state):
00222 """A state change that might cause us to start scanning has occurred."""
00223 self._trigger_scan(iface)
00224
00225 def _log_associations(self, iface, old_state, new_state):
00226 if new_state:
00227 print >> radio_manager_decisions, "--> Associated to %s on %s."%(mac_addr.pretty(new_state.bssid), iface.iface)
00228 elif new_state == radio.Associating:
00229 print >> radio_manager_decisions, "--> Associating on %s."%iface.iface
00230 elif new_state == radio.Unassociated:
00231 print >> radio_manager_decisions, "--> Unassociated on %s."%iface.iface
00232 else:
00233 print >> radio_manager_decisions, "--> ERROR!! Unknown association state %s on %s."%(new_state, iface.iface)
00234
00235 @inlineCallbacks
00236 def _trigger_scan(self, iface):
00237
00238
00239
00240 if iface in self.scheduled_scan:
00241 if self.scheduled_scan[iface].active():
00242 self.scheduled_scan[iface].cancel()
00243 del self.scheduled_scan[iface]
00244 if not iface.radio_sm.scanning_enabled.get() or iface.radio_sm.scanning.get():
00245
00246 return
00247 try:
00248
00249
00250
00251 if iface.radio_sm.associated.get():
00252 freqs = self.frequencies.next_scan_freqs(iface, 1, False)
00253 else:
00254 freqs = self.frequencies.next_scan_freqs(iface, self.num_scan_frequencies, True)
00255 except NoFrequenciesReady, nfr:
00256 if nfr.next_time:
00257 self.scheduled_scan[iface] = reactor.callLater(max(0.1, nfr.next_time - time.time()), self._trigger_scan, iface)
00258
00259
00260
00261 else:
00262 print "Triggering scan", iface.iface, freqs
00263 rslt = yield iface.radio_sm.scan(freqs)
00264 if not rslt:
00265 print "Scan failed", iface.iface, freqs
00266 yield async_helpers.async_sleep(self.failed_scan_delay)
00267 self.frequencies.reschedule(iface, freqs)
00268
00269 def _scan_event_cb(self, iface, bsses):
00270
00271 self.bss_list.update(bsses, iface)
00272 self.new_scan_data.trigger()
00273
00274 def _frequency_list_cb(self, iface, old_state, new_state):
00275 now = time.time()
00276 for f in new_state:
00277 self.frequencies.add(f, iface)
00278
00279 if old_state != state_publisher.JustSubscribed:
00280 for f in old_state:
00281 self.frequencies.remove(f, iface)
00282
00283 self._trigger_scan(iface)
00284
00285 class RadioManager:
00286 def __init__(self):
00287 self.forced_ssid = ""
00288 self.forced_bssid = ""
00289 self.scan_manager = ScanManager()
00290 self.iface_associations = {}
00291 self.initial_inhibit_end = time.time() + config.get_parameter('initial_assoc_inhibit', 5)
00292 self.bss_expiry_time = config.get_parameter('bss_expiry_time', 5)
00293 self.activate_hysteresis = config.get_parameter('activate_hysteresis', 5)
00294 self.max_hot_frequencies = config.get_parameter('max_hot_frequencies', 3)
00295 self.scan_period_warm = config.get_parameter('scan_period_warm', 10)
00296 self.scan_period_hot = config.get_parameter('scan_period_hot', 4)
00297 self.reassociate_hysteresis = config.get_parameter('reassociate_hysteresis', 5)
00298 self.same_bss_penalty = config.get_parameter('same_bss_penalty', 20)
00299 self.interfaces = set()
00300
00301 self.set_mode()
00302
00303 self.scan_manager.new_scan_data.subscribe_repeating(self._new_scan_data)
00304 self.hot_bss_expiry_time = config.get_parameter('hot_bss_expiry_time', 5)
00305 self.warm_bss_expiry_time = config.get_parameter('warm_bss_expiry_time', 3 * self.scan_period_warm)
00306
00307 def add_iface(self, iface):
00308 self.scan_manager.add_iface(iface)
00309 self.interfaces.add(iface)
00310 iface.radio_sm.associated.subscribe(self._associated_cb, iface)
00311 iface.dhcpdata.error_event.subscribe_repeating(self._dhcp_fail, iface)
00312
00313 def set_mode(self, ssid="", bssid="", band=0):
00314 self.forced_ssid = ssid
00315 self.forced_bssid = bssid
00316 self.forced_band = band
00317 self.scan_manager.frequencies.bands = band
00318 for iface in self.interfaces:
00319 self._check_cur_bss(iface)
00320 self.scan_manager._trigger_scan(iface)
00321
00322 def _associated_cb(self, iface, old_state, new_state):
00323 if new_state == radio.Unassociated:
00324 if iface in self.iface_associations:
00325 del self.iface_associations[iface]
00326 self._check_cur_bss(iface)
00327
00328 def _check_cur_bss(self, iface):
00329 """Makes sure that the current bss satisfies forcing constraints."""
00330
00331 cur_bss = iface.radio_sm.associated.get()
00332 if not cur_bss:
00333 return
00334 if not self.check_bss_matches_forcing(cur_bss):
00335 print iface.iface, "associated to", cur_bss.ssid, mac_addr.pretty(cur_bss.bssid), cur_bss.frequency
00336 print "but want", self.forced_ssid, self.forced_ssid, self.forced_band
00337 print "Unassociating because bss does not match requirements."
00338 iface.radio_sm.unassociate()
00339
00340 def check_bss_matches_forcing(self, bss):
00341 """Checks that the bss matches the forced bssid, ssid and band."""
00342 if not check_band(bss.frequency, self.forced_band):
00343 return False
00344 if self.forced_bssid and self.forced_bssid != bss.bssid:
00345 return False
00346 if self.forced_ssid and self.forced_ssid != bss.ssid:
00347 return False
00348 return True
00349
00350 def _new_scan_data(self):
00351
00352
00353
00354 now = time.time()
00355 if now < self.initial_inhibit_end:
00356
00357 return
00358 for iface in self.interfaces:
00359 cur_assoc = iface.radio_sm.associated.get()
00360 if iface.radio_sm.scanning_enabled.get() and cur_assoc != radio.Associating:
00361
00362 candidate_bsses = filter(self.check_bss_matches_forcing,
00363 self.scan_manager.bss_list.bsses.itervalues())
00364
00365 if candidate_bsses:
00366 expiry_time = now - self.bss_expiry_time
00367 best_bss = max(candidate_bsses, key = lambda bss: self.desirability(bss, expiry_time, iface))
00368 if best_bss.by_iface[iface].stamp.to_sec() < expiry_time:
00369 print "Best bss is expired.", mac_addr.pretty(best_bss.bssid)
00370 best_bss = None
00371 else:
00372 print "No candidate bsses."
00373 best_bss = None
00374 if best_bss is None:
00375 print "No candidate bsses or expired."
00376
00377
00378 continue
00379
00380 best_desirability = self.desirability(best_bss, expiry_time, iface)
00381 print "Best bss:", mac_addr.pretty(best_bss.bssid), best_desirability
00382
00383 if cur_assoc:
00384
00385 if make_id(cur_assoc) == best_bss.id:
00386 print "Already associated to best bss."
00387 continue
00388
00389
00390 cur_id = make_id(cur_assoc)
00391 if cur_id in self.scan_manager.bss_list.bsses:
00392 cur_bss = self.scan_manager.bss_list.bsses[make_id(cur_assoc)]
00393 cur_desirability = self.desirability(cur_bss, expiry_time, iface)
00394 else:
00395 cur_desirability = -1e1000
00396
00397
00398 if cur_desirability + self.reassociate_hysteresis > best_desirability:
00399 print "Best bss not over hysteresis threshold: %s %f > %s %f"%(mac_addr.pretty(cur_assoc.bssid), cur_desirability, mac_addr.pretty(best_bss.bssid), self.desirability(best_bss))
00400 continue
00401
00402
00403 print >> radio_manager_decisions, "Associating to %s (%f) on %s"%(mac_addr.pretty(best_bss.bssid), best_desirability, iface.iface),
00404 if cur_assoc:
00405 print >> radio_manager_decisions, "from %s (%f)"%(mac_addr.pretty(cur_assoc.bssid), cur_desirability)
00406 else:
00407 print >> radio_manager_decisions, "from unassociated"
00408 iface.radio_sm.associate_request.trigger(best_bss.id)
00409 self.iface_associations[iface] = best_bss.id
00410
00411 def _dhcp_fail(self, iface):
00412 print >> radio_manager_decisions, "DHCP failed, taking down", iface.iface
00413 iface.interface_upper.restart()
00414
00415 def _ping_fail():
00416 pass
00417
00418 def update(self):
00419 """Called in the main update cycle after all the scores have been
00420 computed. Decides which interfaces should be activated."""
00421
00422 def iface_score(iface):
00423 """Used to decide what to activate and disactivate."""
00424 return iface.prescore
00425
00426 now = time.time()
00427
00428 active = set()
00429 verified = set()
00430 for iface in self.interfaces:
00431 iface.active = iface.radio_sm.is_active.get()
00432 if iface.active:
00433 active.add(iface)
00434 if iface.ping_monitor.is_verified.get():
00435 verified.add(iface)
00436 inactive_verified = verified - active
00437
00438
00439 if active:
00440 best_active = max(active, key = iface_score)
00441 else:
00442 best_active = None
00443 if len(active) > 1:
00444 for iface in active:
00445 if iface != best_active:
00446 iface.radio_sm.activate_request.set(False)
00447 print >> radio_manager_decisions, "XXX Disactivating %s because %s is active and better."%(iface.iface, best_active.iface)
00448
00449
00450
00451 if inactive_verified:
00452
00453
00454
00455 best_inactive_verified = max(inactive_verified, key = iface_score)
00456 if not best_active or iface_score(best_inactive_verified) > iface_score(best_active) + self.activate_hysteresis:
00457 if not best_active:
00458 print >> radio_manager_decisions, "XXX Activating %s because no current best active."%best_inactive_verified.iface
00459 else:
00460 print >> radio_manager_decisions, "XXX Activating %s because it is better than %s."%(best_inactive_verified.iface, best_active.iface)
00461 best_inactive_verified.radio_sm.activate_request.set(True)
00462
00463
00464
00465
00466 candidate_bsses = filter(self.check_bss_matches_forcing,
00467 self.scan_manager.bss_list.bsses.itervalues())
00468 expiry_time = now - self.hot_bss_expiry_time
00469 candidate_bsses.sort(key = lambda bss: self.desirability(bss, expiry_time), reverse = True)
00470
00471 periods = dict((f, self.scan_manager.frequencies.scan_period_cold)
00472 for f in self.scan_manager.frequencies.frequencies)
00473 hot_frequencies = 0
00474 print >> scan_periods_log, "\033[2J\033[0;0H"
00475 print >> scan_periods_log, "Candidate bsses:"
00476 for bss in candidate_bsses:
00477 print >> scan_periods_log, bss.ssid, mac_addr.pretty(bss.bssid), bss.frequency, self.desirability(bss, expiry_time), now - bss.last_seen()
00478 if hot_frequencies < self.max_hot_frequencies and periods[bss.frequency] != self.scan_period_hot:
00479 hot_frequencies += 1
00480 p = self.scan_period_hot
00481 elif bss.last_seen(iface) > now - self.warm_bss_expiry_time:
00482 p = self.scan_period_warm
00483 periods[bss.frequency] = min(periods[bss.frequency], p)
00484 print >> scan_periods_log, "Frequencies"
00485
00486 freqs = [ f for f in self.scan_manager.frequencies.frequencies if check_band(f, self.forced_band) ]
00487 freqs.sort()
00488 for f in freqs:
00489 print >> scan_periods_log, f, periods[f], self.scan_manager.frequencies.frequencies[f].next_scan_time - now
00490 self.scan_manager.frequencies.set_freq_period(f, periods[f])
00491
00492 def desirability(self, target_bss, expiry_time = 0, iface = None):
00493
00494 desirabilities = [bss.level for bss_iface, bss in target_bss.by_iface.iteritems() if bss.stamp.to_sec() > expiry_time and (iface is None or bss_iface == iface)]
00495
00496 if desirabilities:
00497
00498
00499
00500 penalty = 0
00501 if iface:
00502 target_id = make_id(target_bss)
00503 for i, id in self.iface_associations.iteritems():
00504 if i != iface and id == target_id:
00505 penalty = self.same_bss_penalty
00506
00507
00508 return max(desirabilities) - penalty
00509 else:
00510
00511 return -1e1000
00512