$search
00001 #! /usr/bin/env python 00002 00003 # TODO To cleanup: 00004 # - Mark on ping packets in not being used. 00005 # - Most gateway packets should go through the tunnel. 00006 # - Are we black-holing packets properly when there is nowhere to send them? 00007 # - We could trigger a restrategize as soon as a link goes away. 00008 # - Need to add priorities to different interfaces (wan better than 610n on 00009 # pr2) 00010 # - Refactor so that the link/address state machine is cleaner. 00011 # - Make sure we don't crash if logging fail. 00012 # - Add error checking to logging. 00013 00014 import subprocess 00015 import os 00016 import os.path 00017 import atexit 00018 import select 00019 import time 00020 import sys 00021 import threading 00022 import signal 00023 import ipaddr 00024 import network_monitor_udp.udpmoncli 00025 import pythonwifi.iwlibs 00026 import traceback 00027 import errno 00028 import yaml 00029 import math 00030 import inspect 00031 import fcntl 00032 import socket 00033 import logging 00034 import logging.handlers 00035 00036 null_file = open('/dev/null') 00037 00038 LOCAL_RULE=50 00039 FIRST_IFACE_RULE=100 00040 TUNNEL_RULE2=75 00041 TUNNEL_RULE=150 00042 BLACKHOLE_BASESTATION_RULE=175 00043 DEFAULT_RULE=200 00044 BLOCK_NON_TUNNEL_RULE=250 00045 00046 NOLINK = -2 00047 NOIP = -1 00048 NOCHECK = 0 00049 00050 STATUSES = { 00051 NOLINK : "Establishing Link", 00052 NOIP : "Getting an IP address", 00053 NOCHECK : "Ready" 00054 } 00055 00056 # Set up logging 00057 00058 class LoggerStream: 00059 def __init__(self, func): 00060 self.lock = threading.Lock() 00061 self.buffer = "" 00062 self.func = func 00063 00064 def write(self, str): 00065 with self.lock: 00066 self.buffer += str 00067 while True: 00068 pos = self.buffer.find('\n') 00069 if pos == -1: 00070 break 00071 self.func(self.buffer[0:pos]) 00072 self.buffer = self.buffer[pos+1:] 00073 00074 def flush(self): 00075 if self.buffer: 00076 with self.lock: 00077 self.func(self.buffer) 00078 00079 logdir = '/var/log/roam' 00080 00081 try: 00082 os.makedirs(logdir) 00083 except: 00084 pass 00085 logfilecount = 10 00086 00087 file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 00088 00089 console_logger = logging.getLogger('console') 00090 console_logger.setLevel(logging.DEBUG) 00091 strategy_logger = logging.getLogger('console.strategy') 00092 strategy_logger.setLevel(logging.DEBUG) 00093 00094 console_handler = logging.StreamHandler(sys.stdout) 00095 console_handler.setLevel(logging.DEBUG) 00096 console_formatter = logging.Formatter('%(message)s') 00097 console_handler.setFormatter(console_formatter) 00098 console_logger.addHandler(console_handler) 00099 00100 console_file_handler = logging.handlers.TimedRotatingFileHandler(os.path.join(logdir,'console-output.log'), when='midnight', backupCount=logfilecount) 00101 console_file_handler.setFormatter(file_formatter) 00102 console_file_handler.setLevel(logging.DEBUG) 00103 console_logger.addHandler(console_file_handler) 00104 00105 all_logger = logging.getLogger('') 00106 all_logger_handler = logging.handlers.TimedRotatingFileHandler(os.path.join(logdir,'all.log'), when='midnight', backupCount=logfilecount) 00107 all_logger_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 00108 all_logger_handler.setFormatter(all_logger_formatter) 00109 all_logger_handler.setLevel(logging.DEBUG) 00110 all_logger.addHandler(all_logger_handler) 00111 00112 # Redirect stdout and stderr to the logging system 00113 sys.stdout = LoggerStream(console_logger.info) 00114 sys.stderr = LoggerStream(console_logger.error) 00115 strategy_str = LoggerStream(strategy_logger.info) 00116 00117 def log_time_string(t): 00118 return time.strftime("%a, %d %b %Y %H:%M:%S.", time.localtime(t))+("%.6f"%t).split('.')[1] 00119 00120 def flushiprule(rule_prio, leave_one = False): 00121 rules = RunCommand('ip', 'rule').stdout.split('\n') 00122 prefix = "%i:"%rule_prio 00123 count = 0 00124 for line in rules: 00125 if line.startswith(prefix): 00126 count += 1 00127 if leave_one: 00128 count -= 1 00129 for i in range(0, count): 00130 System('ip rule del priority %i'%rule_prio) 00131 00132 def safe_shutdown(method, *args, **nargs): 00133 try: 00134 method(*args, **nargs) 00135 except Exception, e: 00136 print "Caught exception while shutting down instance.", method 00137 traceback.print_exc(10) 00138 print 00139 00140 class System: 00141 def __init__(self, arg): 00142 #print arg 00143 self.errcode = subprocess.call(arg.split(), stdout = null_file, stderr = null_file, close_fds = True) 00144 #self.errcode = os.system(arg + " 2>&1 > /dev/null") 00145 00146 class RunCommand: 00147 def __init__(self, *args): 00148 proc = subprocess.Popen(list(args), stdout = subprocess.PIPE, stderr = subprocess.PIPE, close_fds = True) 00149 (self.stdout, self.stderr) = proc.communicate() 00150 00151 class CommandWithOutput(threading.Thread): 00152 def __init__(self, args, name): 00153 self.restart_delay = 0.2 00154 threading.Thread.__init__(self, name = name) 00155 #logname = os.path.join(logdir, '%s.log'%name) 00156 #try: 00157 # os.makedirs(logdir) 00158 #except OSError, e: 00159 # if e.errno != errno.EEXIST: 00160 # raise 00161 #print "Creating log file:", logname 00162 #self.log = open(os.path.join(logname), 'a') 00163 #self.log.write("\n\n\nStarting new session...\n") 00164 self.logger = logging.getLogger(name) 00165 self.console_logger = logging.getLogger('console.%s'%name) 00166 logger_handler = logging.handlers.TimedRotatingFileHandler(os.path.join(logdir,'%s.log'%name), when='midnight', backupCount=logfilecount) 00167 logger_handler.setFormatter(file_formatter) 00168 self.logger.addHandler(logger_handler) 00169 self.console_logger.addHandler(logger_handler) 00170 self.logger.setLevel(logging.DEBUG) 00171 self.console_logger.setLevel(logging.DEBUG) 00172 logger_handler.setLevel(logging.DEBUG) 00173 self.proc_args = args 00174 self.start_proc() 00175 self.running = True 00176 self.start() 00177 00178 def start_proc(self): 00179 try: 00180 self.proc = subprocess.Popen(self.proc_args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, close_fds = True) 00181 flags = fcntl.fcntl(self.proc.stdout, fcntl.F_GETFL) 00182 fcntl.fcntl(self.proc.stdout, fcntl.F_SETFL, flags| os.O_NONBLOCK) 00183 self.child_restart() 00184 except OSError: 00185 self.console_logger.fatal("Error trying to run: %s"%(" ".join(self.proc_args))) 00186 raise KeyboardInterrupt 00187 00188 def child_restart(self): 00189 pass # Can be overridden by derived classes. 00190 00191 def run(self): 00192 read_buffer = {} 00193 try: 00194 while True: 00195 (rd, wr, err) = select.select([self.proc.stdout], [], [], 0.2) 00196 if not self.running: 00197 #print "Exiting CommandWithOutput", self.proc.pid, self.proc_args 00198 try: 00199 self.proc.send_signal(signal.SIGINT) 00200 except OSError, e: 00201 if str(e).find('[Errno 3]') == -1: 00202 raise 00203 #print "Starting Communicate", self.proc_args 00204 try: 00205 self.proc.communicate() 00206 except IOError: 00207 pass 00208 #print "Ending Communicate", self.proc_args 00209 return 00210 for fd in rd: 00211 #print >> sys.stderr, "About to read" 00212 try: 00213 newdata = fd.read() 00214 except IOError: 00215 newdata = "" 00216 #print >> sys.stderr, "Done read", newdata 00217 if len(newdata) == 0: 00218 self.proc.kill() 00219 self.proc.communicate() 00220 time.sleep(self.restart_delay) 00221 if not self.running: 00222 return 00223 self.console_logger.info("Process died, restarting: %s"%(" ".join(self.proc_args))) 00224 self.start_proc() 00225 continue 00226 00227 if fd in read_buffer: 00228 newdata = read_buffer[fd] + newdata 00229 while True: 00230 splitpos = newdata.find('\n') 00231 if splitpos == -1: 00232 read_buffer[fd] = newdata 00233 break 00234 line = newdata[0:splitpos] 00235 newdata = newdata[splitpos+1:] 00236 00237 self.logger.info(line) 00238 #now = time.time() 00239 #time_str = log_time_string(now) 00240 #self.log.write(time_str+": "+line+"\n") 00241 #self.log.flush() 00242 #sys.stdout.write("%s %s: %s"%(time_str, self.name, line)) 00243 #sys.stdout.flush() 00244 try: 00245 self.got_line(line) 00246 except Exception, e: 00247 self.console_logger.fatal("Caught exception in CommandWithOutput.run: %s"%str(e)) 00248 raise # FIXME Remove this? 00249 except: # FIXME Be more persistent? 00250 traceback.print_exc(10) 00251 print 00252 self.console_logger.fatal("Command with output triggering shutdown after exception.") 00253 os.kill(os.getpid(), signal.SIGINT) 00254 raise 00255 00256 def shutdown(self): 00257 self.running = False 00258 #print "Shutting down command with output:", self.proc.pid, self.proc_args 00259 #try: 00260 # self.proc.kill() 00261 # print "Process killed", self.proc_args 00262 #except OSError: 00263 # print "Process already dead", self.proc_args 00264 00265 class WpaSupplicant(CommandWithOutput): 00266 def __init__(self, iface, config): 00267 self.iface = iface 00268 script = os.path.join(os.path.dirname(__file__), 'wpa_supplicant.sh') 00269 CommandWithOutput.__init__(self, [script, '-i', iface, '-c', config, '-C', '/var/run/wpa_supplicant', '-dd'], "wpa_supplicant_"+iface) 00270 self.restart_delay = 1 00271 00272 def got_line(self, line): 00273 pass 00274 00275 def command(self, cmd): 00276 System('wpa_cli -p /var/run/wpa_supplicant -i %s %s'%(self.iface, cmd)) 00277 00278 def restart(self): 00279 print >> sys.stderr, "Restarting supplicant on %s"%self.iface 00280 try: 00281 self.proc.send_signal(signal.SIGINT) 00282 except OSError, e: 00283 if str(e).find('[Errno 3]') == -1: 00284 raise 00285 00286 class DhcpClient(CommandWithOutput): 00287 def __init__(self, iface, bound_callback = None, deconfig_callback = None, leasefail_callback = None): 00288 self.iface = iface 00289 self.bound_callback = bound_callback 00290 self.deconfig_callback = deconfig_callback 00291 self.leasefail_callback = leasefail_callback 00292 script = os.path.join(os.path.dirname(__file__), 'udhcpc_echo.sh') 00293 udhcpc_script = os.path.join(os.path.dirname(__file__), 'udhcpc.sh') 00294 CommandWithOutput.__init__(self, [udhcpc_script, '-s', script, '-i', iface, '-f'], "udhcpc_"+iface) 00295 self.restart_delay = 0 00296 00297 def got_line(self, line): 00298 boundprefix = "UDHCPC: bound " 00299 if line.startswith(boundprefix): 00300 [iface, ip, netmask, gateway] = line[len(boundprefix):].split() # FIXME may have more than one router 00301 if self.bound_callback is not None: 00302 self.bound_callback(iface, ip, netmask, gateway) 00303 00304 deconfigprefix = "UDHCPC: deconfig " 00305 if line.startswith(deconfigprefix): 00306 [iface] = line[len(deconfigprefix):].split() 00307 if self.deconfig_callback is not None: 00308 self.deconfig_callback(iface) 00309 00310 leasefailprefix = "UDHCPC: leasefail " 00311 if line.startswith(leasefailprefix): 00312 [iface] = line[len(leasefailprefix):].split() 00313 if self.leasefail_callback is not None: 00314 self.leasefail_callback(iface) 00315 00316 def renew(self): 00317 # Using SIGINT here because until ralink adapters are first brought up 00318 # they don't have a configured MAC address, which prevents udhcpc from 00319 # ever working. 00320 self.proc.send_signal(signal.SIGINT) 00321 00322 def release(self): 00323 try: 00324 self.proc.send_signal(signal.SIGUSR2) 00325 except OSError, e: 00326 if str(e).find('[Errno 3]') == -1: 00327 raise 00328 00329 class NetlinkMonitor(CommandWithOutput): 00330 def __init__(self): 00331 self.lock = threading.RLock() 00332 self.link_callbacks = {} 00333 self.addr_callbacks = {} 00334 self.addr_state = {} 00335 self.link_state = {} 00336 self.cur_iface = None 00337 self.deleted = None 00338 CommandWithOutput.__init__(self, ['ip', 'monitor', 'link', 'addr'], 'ip_monitor') 00339 # FIXME FIXME Blaise is here. 00340 00341 def child_restart(self): 00342 time.sleep(0.2) # Limit race conditions on getting the startup state. 00343 current_state = RunCommand('ip', 'addr') 00344 with self.lock: 00345 old_cur_iface = self.cur_iface 00346 old_deleted = self.deleted 00347 for line in current_state.stdout.split('\n'): 00348 self.got_line(line) 00349 self.deleted = old_deleted 00350 self.cur_iface = old_cur_iface 00351 00352 def register_link(self, iface, callback): 00353 if ':' in iface: 00354 iface = iface.split(':')[0] # Handle aliases. 00355 if iface not in self.link_callbacks: 00356 self.link_callbacks[iface] = [] 00357 with self.lock: 00358 self.link_callbacks[iface].append(callback) 00359 if iface not in self.link_state: 00360 self.link_state[iface] = False 00361 callback(self.link_state[iface]) 00362 00363 def register_addr(self, iface, callback): 00364 print "register_addr", iface 00365 if iface not in self.addr_callbacks: 00366 self.addr_callbacks[iface] = [] 00367 with self.lock: 00368 self.addr_callbacks[iface].append(callback) 00369 if iface not in self.addr_state: 00370 self.addr_state[iface] = [None, None] 00371 callback(*self.addr_state[iface]) 00372 00373 def got_line(self, line): 00374 with self.lock: 00375 try: 00376 # Figure out the interface, and whether this is a delete 00377 # event. 00378 00379 #print "*****************Got line", line 00380 if len(line) == 0 or (line[0] == ' ' and self.cur_iface == None): 00381 return 00382 tokens = line.rstrip().split() 00383 if line[0] != ' ': 00384 if tokens[0] == 'Deleted': 00385 self.deleted = True 00386 tokens.pop(0) 00387 else: 00388 self.deleted = False 00389 self.cur_iface = tokens[1].rstrip(':') 00390 tokens.pop(0) 00391 tokens.pop(0) 00392 00393 # Find the link state. 00394 try: 00395 state_idx = tokens.index('state') 00396 state = tokens[state_idx + 1] 00397 link_state = state != 'DOWN' # Use DOWN because tun shows up as UNKNOWN 00398 if self.cur_iface not in self.link_state or self.link_state[self.cur_iface] != link_state: 00399 self.link_state[self.cur_iface] = link_state 00400 if self.cur_iface in self.link_callbacks: 00401 for callback in self.link_callbacks[self.cur_iface]: 00402 callback(link_state) 00403 print "********************Link change", self.cur_iface, link_state # FIXME Remove this. 00404 #else: 00405 # print "********************No link change", self.cur_iface, link_state 00406 except ValueError: 00407 #print "********************No link", self.cur_iface 00408 pass 00409 00410 00411 # Find the address. 00412 try: 00413 addr_idx = tokens.index('inet') 00414 if self.deleted: 00415 addr_state = [None, None] 00416 else: 00417 addr_state = tokens[addr_idx + 1].split('/') 00418 full_iface = tokens[-1] 00419 if full_iface not in self.addr_state or self.addr_state[full_iface] != addr_state: 00420 print "********************Address change", full_iface, addr_state # FIXME Remove this. 00421 self.addr_state[full_iface] = addr_state 00422 if full_iface in self.addr_callbacks: 00423 for callback in self.addr_callbacks[full_iface]: 00424 callback(*addr_state) 00425 #else: 00426 # print "********************No addr change", self.cur_iface, addr_state 00427 except ValueError: 00428 #print "********************No addr", self.cur_iface 00429 pass 00430 except Exception, e: 00431 print "Caught exception in NetlinkMonitor.run:", e 00432 traceback.print_exc(10) 00433 print 00434 00435 class NetworkConnection: 00436 def __init__(self, name, iface, config, tableid, pingtarget): 00437 self.timeout_time = 1e1000 00438 self.status = NOLINK 00439 if 'name' in config: 00440 self.name = config['name'] 00441 else: 00442 self.name = name 00443 self.iface = iface 00444 self.tableid = tableid 00445 self.shutting_down = False 00446 self.bssid = "NoLink" 00447 self.priority = config['priority'] if 'priority' in config else 0 00448 00449 self.udp_monitor = network_monitor_udp.udpmoncli.MonitorClient([.2], pingtarget, 20, 32, True) 00450 netlink_monitor.register_addr(self.iface, self.addrchange) 00451 00452 def addrchange(self, addr, netbits): 00453 if addr: 00454 self.status = NOCHECK 00455 # It is critical that set_routes be called to set up the routes 00456 # before monitor_start. Otherwise, the UDP connection will get 00457 # started with the wrong route, and conntrack will cache the 00458 # incorrect route, preventing the routing table change from 00459 # being properly considered. 00460 self.set_routes(addr, netbits) 00461 try: 00462 print 'start_monitor', addr 00463 self.udp_monitor.start_monitor((addr, 0)) 00464 except: 00465 pass # Address gets set twice (first address then netmask). The first time often results in a bind failure. 00466 elif self.status == NOCHECK: 00467 print 'stop_monitor' 00468 self.status = NOIP 00469 self.udp_monitor.stop_monitor() 00470 00471 def update1(self): 00472 self.monitor_output = self.udp_monitor.get_smart_bins(1) 00473 (bins, latency1, latency2) = self.monitor_output 00474 00475 self.diags = [] 00476 00477 self.diags.append(("Link status", (STATUSES[self.status] if self.status in STATUSES else "unknown"))) 00478 00479 self.diags.append(("Packets under 200ms latency (%)", 100 * bins[0])) 00480 self.diags.append(("Average round trip latency (ms)", "%.1f"%(latency1*1000))) 00481 self.diags.append(("Average latency for packets under 200ms (ms)", "%.1f"%(latency2*1000))) 00482 00483 return (bins, latency1, latency2) 00484 00485 def update2(self): 00486 (bins, latency1, latency2) = self.monitor_output 00487 00488 if self.status < 0: 00489 self.goodness = self.status 00490 self.reliability = self.status 00491 else: 00492 self.goodness = 100 * bins[0] - latency2 # Goodness is how many packets made it through then average latency. 00493 00494 self.diags.append(("Goodness:", self.goodness)) 00495 self.diags.append(("Reliability:", self.reliability)) 00496 00497 if self.status < 0: 00498 self.diag_summary = STATUSES[self.status] 00499 else: 00500 self.diag_summary = "Connected (goodness %f, reliability %f)"%(self.goodness, self.reliability) 00501 00502 def shutdown(self): 00503 self.shutting_down = True 00504 safe_shutdown(self.udp_monitor.shutdown) 00505 00506 class StaticRoute(NetworkConnection): 00507 def __init__(self, route, config, tableid, pingtarget): 00508 self.gateway, iface = route.split('@') 00509 NetworkConnection.__init__(self, route, iface, config, tableid, pingtarget) 00510 netlink_monitor.register_link(self.iface, self.linkchange) 00511 00512 def increase_timeout(self, delay = 1e1000): 00513 pass 00514 00515 def set_timeout(self, delay): 00516 pass 00517 00518 def decrease_timeout(self, delay): 00519 pass 00520 00521 def set_routes(self, addr, netbits): 00522 flushiprule(self.tableid) 00523 System("ip rule add priority %i from %s table %i"%(self.tableid,addr,self.tableid)) 00524 System("ip route flush table %i"%self.tableid) 00525 try: 00526 gatewayip = socket.gethostbyname(self.gateway) 00527 except socket.gaierror, e: 00528 # TODO Should I bail out at this point? 00529 print >> sys.stderr, "Error resolving gateway host %s. Connection self.name will not work."%self.gateway 00530 return 00531 System("ip route add table %i default dev %s via %s src %s onlink"%(self.tableid, self.iface, gatewayip, addr)) 00532 System("ip route add table %i %s dev %s src %s"%(self.tableid, gatewayip, self.iface, addr)) 00533 00534 def linkchange(self, up): 00535 if not up: 00536 self.bssid = "NoLink" 00537 self.status = NOLINK 00538 else: 00539 self.bssid = "Wired" 00540 if self.status == NOLINK: 00541 self.status = NOIP 00542 00543 def update(self): 00544 self.update1() 00545 self.reliability = 100 00546 self.update2() 00547 00548 class DhcpInterface(NetworkConnection): 00549 def __init__(self, iface, config, tableid, pingtarget): 00550 self.is_bound = False 00551 NetworkConnection.__init__(self, iface, iface, config, tableid, pingtarget) 00552 self.start_over_timeout = 15 00553 self.dhcp_timeout = 7 00554 self.timeout_time = 0 00555 self.increase_timeout(self.start_over_timeout) 00556 self.timed_out_time = 0 00557 00558 self.dhclient = DhcpClient(iface, self.bound, self.deconfigured, self.leasefail) 00559 self.startover() 00560 00561 def update1(self): 00562 time_to_timeout = self.timeout_time - time.time() 00563 if time_to_timeout < 0: 00564 print "Interface", self.name, "timed out." 00565 self.timed_out_time = time.time() 00566 self.startover() 00567 00568 NetworkConnection.update1(self) 00569 00570 self.diags.append(("Time to interface timeout (s)", time_to_timeout)) 00571 00572 def linkchange(self, up): 00573 # We want to be sure some timeout is set here. 00574 self.set_timeout(self.start_over_timeout) 00575 #print "linkchange", self.name, address 00576 if up: 00577 if self.status == NOLINK: 00578 self.bssid = "Wired" 00579 self.status = NOIP 00580 self.set_timeout(self.dhcp_timeout) 00581 self.dhclient.renew() 00582 else: 00583 if self.status != NOLINK: 00584 self.bssid = "NoLink" 00585 self.status = NOLINK 00586 self.udp_monitor.stop_monitor() 00587 self.dhclient.release() 00588 pass 00589 00590 def set_routes(self, addr, netbits): 00591 if not self.is_bound: 00592 return 00593 flushiprule(self.tableid) 00594 #System("ip rule add priority %i from %s table %i fwmark 2"%(self.tableid,ip,self.tableid)) 00595 System("ip rule add priority %i from %s table %i"%(self.tableid,addr,self.tableid)) 00596 System("ip route flush table %i"%self.tableid) 00597 System("ip route add table %i default dev %s via %s src %s onlink"%(self.tableid, self.iface, self.gateway, addr)) 00598 net = ipaddr.IPv4Network("%s/%s"%(addr,netbits)) 00599 #self.network = "%s/%s"%(net.network,net.prefixlen) 00600 print("ip route add table %i %s/%s dev %s src %s"%(self.tableid, net.network, net.prefixlen, self.iface, addr)) 00601 System("ip route add table %i %s/%s dev %s src %s"%(self.tableid, net.network, net.prefixlen, self.iface, addr)) 00602 00603 def bound(self, iface, ip, netmask, gateway): 00604 print "Bound!", self.status 00605 #if self.is_bound == True: 00606 # self.deconfigured(iface) 00607 self.increase_timeout(self.start_over_timeout) 00608 self.is_bound = True 00609 self.ip = ip 00610 self.netmask = netmask 00611 self.gateway = gateway 00612 00613 # Configure the address on the interface, and make sure ARP is up 00614 # to date on peers. 00615 System("ifconfig %s 0.0.0.0"%iface) 00616 net = ipaddr.IPv4Network("%s/%s"%(ip, netmask)) 00617 System("ip addr add %s/%s dev %s"%(ip, net.prefixlen, iface)) 00618 #System("ifconfig %s %s netmask %s"%(iface,ip,netmask)) 00619 System("arping -q -c 1 -A -I %s %s"%(iface,ip)) 00620 ConfigureRpFilter(iface) # FIXME Do this here in case the interface was unplugged and replugged. 00621 print "Bound done!", self.status 00622 00623 def startover(self): # Deconfigure the interface and start over from scratch 00624 flushiprule(self.tableid) 00625 System("ifconfig %s 0.0.0.0 down"%self.iface) # This should clear all routing table entries. 00626 System("ifconfig %s up"%self.iface) 00627 self.set_timeout(self.start_over_timeout) 00628 00629 def leasefail(self, iface): 00630 if self.status > NOLINK: 00631 self.startover() 00632 00633 def deconfigured(self, iface): 00634 if self.is_bound: 00635 self.is_bound = False 00636 flushiprule(self.tableid) 00637 System("ifconfig %s 0.0.0.0 down"%iface) # This should clear all routing table entries. 00638 System("ifconfig %s up"%iface) 00639 00640 def shutdown(self): 00641 NetworkConnection.shutdown(self) 00642 System("ifconfig %s down"%self.iface) # This should clear all routing table entries. 00643 safe_shutdown(self.dhclient.shutdown) 00644 00645 def decrease_timeout(self, delay): 00646 # Call this if things are not doing well, and you want to pull in the timeout horizon. 00647 self.timeout_time = min(time.time() + delay, self.timeout_time) 00648 00649 def increase_timeout(self, delay = 1e1000): 00650 # Call this if things are doing well and you want to push back the timeout horizon. 00651 self.timeout_time = max(time.time() + delay, self.timeout_time) 00652 00653 def set_timeout(self, delay): 00654 # Call this if you want to set the timeout exactly. 00655 self.timeout_time = time.time() + delay 00656 00657 class WirelessInterface(DhcpInterface): 00658 def __init__(self, iface, config, tableid, pingtarget): 00659 self.startover_count = 0 00660 00661 DhcpInterface.__init__(self, iface, config, tableid, pingtarget) 00662 00663 self.wifi = pythonwifi.iwlibs.Wireless(iface) 00664 self.iwinfo = pythonwifi.iwlibs.WirelessInfo(iface) 00665 00666 self.supplicant = WpaSupplicant(iface, config['wpa_config']) 00667 self.initialized = True 00668 netlink_monitor.register_link(iface, self.linkchange) 00669 00670 def linkchange(self, up): 00671 if not self.initialized: 00672 return 00673 00674 if up: 00675 self.startover_count = 0 00676 # if self.status == NOLINK and up: 00677 # self.supplicant.command('reassociate') 00678 # print "************** reassociate" 00679 DhcpInterface.linkchange(self, up) 00680 00681 def startover(self): 00682 if self.startover_count > 1: 00683 self.startover_count = 0 00684 self.supplicant.restart() 00685 else: 00686 self.startover_count += 1 00687 00688 DhcpInterface.startover(self) 00689 00690 def update(self): 00691 self.update1() 00692 00693 try: 00694 self.essid = self.wifi.getEssid() 00695 self.diags.append(('ESSID', self.essid)) 00696 except Exception, e: 00697 if self.status != NOLINK: 00698 traceback.print_exc(10) 00699 print 00700 self.diags.append(('ESSID', 'Error collecting data.')) 00701 self.essid = "###ERROR-COLLECTING-DATA###" 00702 00703 try: 00704 self.bssid = self.wifi.getAPaddr() 00705 self.diags.append(('BSSID', self.bssid)) 00706 except Exception, e: 00707 if self.status != NOLINK: 00708 traceback.print_exc(10) 00709 print 00710 self.bssid = "00:00:00:00:00:00" 00711 self.diags.append(('BSSID', 'Error collecting data.')) 00712 00713 try: 00714 self.wifi_txpower = 10**(self.wifi.wireless_info.getTXPower().value/10.) 00715 self.diags.append(('TX Power (mW)', self.wifi_txpower)) 00716 self.wifi_txpower = "%.1f mW"%self.wifi_txpower 00717 except Exception, e: 00718 if str(e).find("Operation not supported") == -1 and self.status != NOLINK: 00719 traceback.print_exc(10) 00720 print 00721 self.diags.append(('TX Power (mW)', 'Error collecting data.')) 00722 self.wifi_txpower = "unknown" 00723 00724 try: 00725 self.wifi_frequency = self.wifi.wireless_info.getFrequency().getFrequency() 00726 self.diags.append(('Frequency (Gz)', "%.4f"%(self.wifi_frequency/1e9))) 00727 except Exception, e: 00728 if self.status != NOLINK: 00729 traceback.print_exc(10) 00730 print 00731 self.wifi_frequency = 0 00732 self.diags.append(('Frequency', 'Error collecting data.')) 00733 00734 got_stats = False 00735 if self.status != NOLINK: 00736 try: 00737 stat, qual, discard, missed_beacon = self.wifi.getStatistics() 00738 max_quality = self.wifi.getQualityMax().quality 00739 quality = qual.quality * 100 / max_quality 00740 self.diags.append(('Quality', quality)) 00741 self.diags.append(('Signal (dB)', qual.siglevel)) 00742 self.diags.append(('Noise (dB)', qual.nlevel)) 00743 self.diags.append(('SNR (dB)', qual.siglevel - qual.nlevel)) 00744 self.wifi_signal = qual.siglevel 00745 self.wifi_noise = qual.nlevel 00746 self.wifi_quality = quality 00747 self.reliability = quality 00748 got_stats = True 00749 except Exception, e: 00750 print "Error getting wireless stats on interface %s: %s"%(self.iface, str(e)) 00751 00752 if not got_stats: 00753 #print self.name, "could not collect wireless data", e 00754 print 00755 self.reliability = 0 00756 self.wifi_quality = -1 00757 self.wifi_noise = 1e1000 00758 self.wifi_signal = -1e1000 00759 for s in [ 'Quality', 'Signal', 'Noise' ]: 00760 if self.status != NOLINK: 00761 self.diags.append((s, 'Error collecting data.')) 00762 else: 00763 self.diags.append((s, 'Unknown')) 00764 00765 self.wifi_rate = None 00766 if self.status != NOLINK: 00767 try: 00768 self.wifi_rate = self.wifi.wireless_info.getBitrate().value 00769 except: 00770 pass 00771 if self.wifi_rate is None: 00772 try: 00773 self.wifi_rate = self.iwinfo.getBitrate().value 00774 except: 00775 pass 00776 if self.wifi_rate is not None: 00777 self.diags.append(('TX Rate (Mbps)', self.wifi_rate / 1e6)) 00778 self.wifi_rate = self.wifi._formatBitrate(self.wifi_rate) 00779 else: 00780 if self.status != NOLINK: 00781 print "Unable to determine TX rate on interface", self.iface 00782 self.diags.append(('TX Rate (Mbps)', 'Error collecting data.')) 00783 else: 00784 self.diags.append(('TX Rate (Mbps)', 'Unknown')) 00785 self.wifi_rate = "Unknown" 00786 00787 self.update2() 00788 00789 def shutdown(self): 00790 DhcpInterface.shutdown(self) 00791 safe_shutdown(self.supplicant.shutdown) 00792 00793 class WiredInterface(DhcpInterface): 00794 def __init__(self, iface, config, tableid, pingtarget): 00795 self.initialized = False 00796 DhcpInterface.__init__(self, iface, config, tableid, pingtarget) 00797 self.initialized = True 00798 netlink_monitor.register_link(iface, self.linkchange) 00799 00800 def linkchange(self, up): 00801 if up != (self.status != NOLINK) and self.initialized and time.time() - self.timed_out_time > 10: 00802 os.system('beep') 00803 DhcpInterface.linkchange(self, up) 00804 00805 def update(self): 00806 self.update1() 00807 self.reliability = 100 00808 self.update2() 00809 00810 def shutdown(self): 00811 self.initialized = False 00812 DhcpInterface.shutdown(self) 00813 00814 # class PacketMarker: 00815 # def __init__(self, basestation): 00816 # print "Initializing PacketMarker." 00817 # self.basestation = basestation 00818 # self.rules = [] 00819 # # Packets for the VPN get a mark of 1. 00820 # self.rules.append("OUTPUT -t mangle -d %s -p udp --dport 1194 -j MARK --set-mark 1"%basestation) 00821 # # Packets for the ping check get a mark of 2. 00822 # self.rules.append("OUTPUT -t mangle -d %s -p udp --dport 6868 -j MARK --set-mark 2"%basestation) 00823 # self.playrules("-D", True) 00824 # self.playrules("-A", False) 00825 # print "PacketMarker initialized." 00826 00827 # def playrules(self, command, repeat): 00828 # for rule in self.rules: 00829 # while True: 00830 # if System("iptables %s %s"%(command, rule)).errcode: 00831 # break 00832 # if not repeat: 00833 # break 00834 00835 # def shutdown(self): 00836 # self.playrules("-D", False) 00837 00838 class RoutingRules: 00839 def __init__(self, numinterfaces, localnets, tuniface, basestation): 00840 self.numinterfaces = numinterfaces 00841 self.tuniface = tuniface 00842 self.flushall() 00843 00844 System("ip rule add priority %i to %s blackhole"%(BLACKHOLE_BASESTATION_RULE, basestation)) 00845 00846 #System("ip rule add priority %i fwmark 1 table %i"%(BLOCK_TUNNEL_RULE,BLOCK_NON_TUNNEL_RULE)) 00847 #flushiprule(BLOCK_TUNNEL_RULE, True) 00848 #System("ip route replace blackhole default table %i src 127.0.0.1"%BLOCK_NON_TUNNEL_RULE) 00849 #System("ip rule add priority %i table %i"%(BLOCK_NON_TUNNEL_RULE,BLOCK_NON_TUNNEL_RULE)) 00850 #flushiprule(BLOCK_NON_TUNNEL_RULE, True) 00851 for net in localnets: 00852 System("ip rule add priority %i to %s lookup main"%(LOCAL_RULE, net)) 00853 System("ip rule add priority %i lookup %i"%(DEFAULT_RULE,DEFAULT_RULE)) 00854 #System("ip rule add priority %i blackhole fwmark 1"%(TUNNEL_RULE2)) 00855 #System("ip rule add priority %i blackhole fwmark 1"%(TUNNEL_RULE)) 00856 netlink_monitor.register_link(self.tuniface, self.refresh) 00857 00858 def refresh(self, up): # Call this often in case an interface goes away for a bit. 00859 if up: 00860 System("ip route replace table %i default dev %s"%(DEFAULT_RULE, self.tuniface)) 00861 00862 def flushall(self): 00863 # Make sure that all the rules from an earlier run are flushed out. 00864 for i in range(0,self.numinterfaces): 00865 flushiprule(FIRST_IFACE_RULE + i) 00866 flushiprule(TUNNEL_RULE + i) 00867 flushiprule(DEFAULT_RULE) 00868 flushiprule(LOCAL_RULE) 00869 flushiprule(BLACKHOLE_BASESTATION_RULE) 00870 #flushiprule(TUNNEL_RULE2) 00871 #flushiprule(BLOCK_TUNNEL_RULE, True) 00872 #flushiprule(BLOCK_NON_TUNNEL_RULE, True) 00873 00874 # Make sure that all the tables from an earlier run are flushed out. 00875 for i in range(0,self.numinterfaces): 00876 self.flushtable(FIRST_IFACE_RULE + i) 00877 self.flushtable(DEFAULT_RULE) 00878 # Don't flush the BLOCK_NON_TUNNEL_RULE table as we want it to persist. 00879 00880 def flushtable(self, tab_id): 00881 System("ip route flush table %i"%tab_id) 00882 00883 def shutdown(self): 00884 self.flushall() 00885 00886 class ConfigureArp: 00887 def __init__(self): 00888 System("sysctl net.ipv4.conf.all.arp_filter=1") 00889 00890 class ConfigureRpFilter: 00891 def __init__(self, iface): 00892 System("sysctl net.ipv4.conf.%s.rp_filter=0"%iface) 00893 00894 class KillServices: 00895 def __init__(self): 00896 System("killall wpa_supplicant udhcpc 2> /dev/null") 00897 00898 class NetworkSelector: 00899 def __init__(self, config): 00900 interfaces_config = config['interfaces'] 00901 self.base_station = socket.gethostbyname(config['base_station']) 00902 pingaddr = (self.base_station, int(config['ping_port'])) 00903 self.tunnel_interface = config['tunnel_interface'] 00904 local_networks = config['local_networks'] 00905 00906 KillServices() 00907 global netlink_monitor 00908 netlink_monitor = NetlinkMonitor() 00909 #self.pkt_marker = PacketMarker(self.base_station) 00910 ConfigureArp() 00911 ConfigureRpFilter('all') 00912 self.routing_rules = RoutingRules(len(interfaces_config), local_networks, self.tunnel_interface, self.base_station) 00913 self.interfaces = [] 00914 self.active_iface = -1 00915 self._tunnel_rules = {} 00916 self._set_tunnel_rule(TUNNEL_RULE, "blackhole") 00917 00918 i = -1 00919 for iface in interfaces_config: 00920 i += 1 00921 opts = interfaces_config[iface] 00922 type = opts['type'] 00923 if type == "wireless": 00924 if 'wpa_config' not in opts and 'wpa_config' in config: 00925 opts['wpa_config'] = config['wpa_config'] 00926 self.interfaces.append(WirelessInterface(iface, opts, FIRST_IFACE_RULE + i, pingaddr)) 00927 elif type == "wired": 00928 self.interfaces.append(WiredInterface(iface, opts, FIRST_IFACE_RULE + i, pingaddr)) 00929 elif type == "static": 00930 self.interfaces.append(StaticRoute(iface, opts, FIRST_IFACE_RULE + i, pingaddr)) 00931 else: 00932 raise Exception("Unknown type for an interface: %s"%type) 00933 00934 self.interface_names = [ iface.name for iface in self.interfaces ] 00935 00936 def update(self): 00937 # Update interfaces 00938 for iface in self.interfaces: 00939 iface.update() 00940 00941 # We use information from the interface that was active for the preceding time slice. 00942 self.diags = [] 00943 self.diags.append(('Tunnel Interface', self.tunnel_interface)) 00944 if self.active_iface >= 0: 00945 act_iface = self.interfaces[self.active_iface] 00946 self.diags.append(('Active Interface', act_iface.iface )) 00947 self.diags += act_iface.diags 00948 if act_iface.goodness > 95: 00949 self.diag_summary = "Active interface %s running strong"%act_iface.iface 00950 self.diag_level = 0 00951 elif act_iface.goodness > 50: 00952 self.diag_summary = "Active interface %s is lossy"%act_iface.iface 00953 self.diag_level = 1 00954 else: 00955 self.diag_summary = "Active interface %s is very poor"%act_iface.iface 00956 self.diag_level = 2 00957 else: 00958 self.diags.append(('Active Interface', "none")) 00959 self.diag_summary = 'No active interface' 00960 self.diag_level = 2 00961 00962 def prepare_diagnostics(self): 00963 pass 00964 00965 def shutdown(self): # Add exception handling so that one failure won't prevent others from shutting down. 00966 #safe_shutdown(rospy.signal_shutdown, "Node is going down.") 00967 if hasattr(self, "interfaces"): 00968 for iface in self.interfaces: 00969 #print "Shutting down interface %s."%iface.name 00970 safe_shutdown(iface.shutdown) 00971 safe_shutdown(netlink_monitor.shutdown) 00972 #print "Shutting down packet marker." 00973 #safe_shutdown(self.pkt_marker.shutdown) 00974 #print "Shutting down routing rules." 00975 safe_shutdown(self.routing_rules.shutdown) 00976 00977 def make_active(self, iface): 00978 if not iface: 00979 self.active = -1 00980 return 00981 self.make_active_multiple([iface]) 00982 00983 def _set_tunnel_rule(self, priority, rule): 00984 System("ip rule add priority %i %s"%(priority, rule)) 00985 if priority in self._tunnel_rules: 00986 System("ip rule del priority %i %s"%(priority, self._tunnel_rules[priority])) 00987 self._tunnel_rules[priority] = rule 00988 00989 def make_active_non_tunnel(self, iface): 00990 if not iface: 00991 self.active = -1 00992 return 00993 00994 self.active_iface = self.interface_names.index(iface.name) 00995 self._set_tunnel_rule(TUNNEL_RULE,"table %i"%iface.tableid) 00996 00997 def make_active_multiple(self, iface): 00998 n = len(iface) 00999 if not n: 01000 self.active_iface = -1 01001 return 01002 01003 self.active_iface = self.interface_names.index(iface[0].name) 01004 for i in range(0, n): 01005 self._set_tunnel_rule(TUNNEL_RULE+i,"table %i to %s"%(iface[i].tableid, self.base_station)) 01006 #System("ip rule add priority %i table %i fwmark 1"%(TUNNEL_RULE2,iface.tableid)) 01007 #System("ip rule del priority %i"%TUNNEL_RULE2) 01008 01009 class SelectionStrategy: 01010 def __init__(self, config): 01011 self.ns = NetworkSelector(config) 01012 01013 def update(self): 01014 ns = self.ns 01015 ns.update() 01016 print >> strategy_str 01017 print >> strategy_str, log_time_string(time.time()) 01018 self.do_update() 01019 ns.prepare_diagnostics() 01020 01021 def shutdown(self): 01022 safe_shutdown(self.ns.shutdown) 01023 01024 class StatGatherer: 01025 def __init__(self): 01026 self.max = -1e1000 01027 self.min = 1e1000 01028 self.sum = 0. 01029 self.count = 0 01030 self.avg = 0. 01031 self.dev = 0. 01032 self.sqrsum = 0. 01033 01034 def append(self, value): 01035 self.max = max(self.max, value) 01036 self.min = min(self.min, value) 01037 self.sum += value 01038 self.sqrsum += value * value 01039 self.count += 1 01040 self.avg = self.sum / self.count 01041 self.dev = math.sqrt(self.sqrsum / self.count - self.avg * self.avg) 01042 01043 def to_string(self): 01044 return "avg: %5.1f dev: %5.1f min: %5.1s max: %5.1f count: %5i"%(self.avg, self.dev, self.min, self.max, self.count) 01045 01046 class LinkBringupTimeMeasurementStrategy(SelectionStrategy): 01047 def __init__(self, config): 01048 SelectionStrategy.__init__(self, config) 01049 self.start_times = len(self.ns.interfaces) * [time.time()] 01050 self.stats = [ StatGatherer() for iface in self.ns.interfaces] 01051 01052 def do_update(self): 01053 ns = self.ns 01054 now = time.time() 01055 for i in range(0, len(ns.interfaces)): 01056 iface = ns.interfaces[i] 01057 if iface.goodness >= 90: 01058 self.stats[i].append(now - self.start_times[i]) 01059 iface.startover() 01060 self.start_times[i] = now 01061 print >> strategy_str, "%s %5.0f %5.1fs %4.1fs %s"%(iface.name, iface.goodness, now - self.start_times[i], iface.timeout_time - now, self.stats[i].to_string()) 01062 print >> strategy_str 01063 01064 class LinkStabilityTimeMeasurementStrategy(SelectionStrategy): 01065 def __init__(self, config): 01066 SelectionStrategy.__init__(self, config) 01067 self.is_up = len(self.ns.interfaces) * [ False ] 01068 self.start_times = len(self.ns.interfaces) * [time.time()] 01069 self.start_stats = [ StatGatherer() for iface in self.ns.interfaces] 01070 self.longevity_stats = [ StatGatherer() for iface in self.ns.interfaces ] 01071 01072 def do_update(self): 01073 ns = self.ns 01074 now = time.time() 01075 for i in range(0, len(ns.interfaces)): 01076 iface = ns.interfaces[i] 01077 is_now_up = self.is_up[i] 01078 if iface.goodness >= 90: 01079 is_now_up = True 01080 elif iface.goodness < 0: 01081 is_now_up = False 01082 if is_now_up: 01083 if iface.goodness >= 90: 01084 iface.increase_timeout() 01085 else: 01086 iface.decrease_timeout(5) 01087 if self.is_up[i] != is_now_up: 01088 (self.start_stats if is_now_up else self.longevity_stats)[i].append(now - self.start_times[i]) 01089 self.start_times[i] = now 01090 self.is_up[i] = is_now_up 01091 01092 #print iface.name, ("up " if is_now_up else "down"), "%5.1fs"%(now - self.start_times[i]), "%4.1fs"%(iface.timeout_time - now), "START", self.start_stats[i].to_string(), "LONGEVITY", self.longevity_stats[i].to_string() 01093 print >> strategy_str, iface.name, "%5.0f"%iface.goodness, "%5.1fs"%(now - self.start_times[i]), "%4.1fs"%(iface.timeout_time - now), "START", self.start_stats[i].to_string(), "LONGEVITY", self.longevity_stats[i].to_string() 01094 01095 class SimpleSelectionStrategy(SelectionStrategy): 01096 def __init__(self, config): 01097 self.best_wireless = -1 01098 SelectionStrategy.__init__(self, config) 01099 try: 01100 self.reliability_thresh = config['reliability_threshold'] 01101 except: 01102 self.reliability_thresh = 90 01103 01104 def do_update(self): 01105 ns = self.ns 01106 # Get a sorted list of working interfaces. 01107 iface_with_sort_param = [] 01108 best_bssid = ns.interfaces[self.best_wireless].bssid if self.best_wireless != -1 else None 01109 for i in ns.interfaces: 01110 if i.goodness <= 0: 01111 continue 01112 bonus = 0 01113 if self.best_wireless != -1: 01114 if i == ns.interfaces[self.best_wireless]: 01115 bonus += 5 01116 elif i.bssid != best_bssid: 01117 bonus -= 5 01118 #if i == ns.interfaces[ns.active_iface]: 01119 # bonus += 10 01120 sort_param = i.goodness + i.reliability + i.priority + bonus 01121 iface_with_sort_param.append((i, sort_param)) 01122 if iface_with_sort_param: 01123 iface_with_sort_param.sort(key = lambda tuple: tuple[1], reverse = True) 01124 iface_sorted, _ = zip(*iface_with_sort_param) 01125 else: 01126 iface_sorted = [] 01127 01128 ns.make_active_multiple(iface_sorted) 01129 01130 # Figure out the best wireless interface 01131 iface_types = map(lambda x: x.__class__, iface_sorted) 01132 try: 01133 wireless_index = iface_types.index(WirelessInterface) 01134 self.best_wireless = ns.interfaces.index(iface_sorted[wireless_index]) 01135 except: 01136 self.best_wireless = -1 01137 01138 # Decide if the other interfaces should go down 01139 for i in range(0,len(ns.interfaces)): 01140 interface = ns.interfaces[i] 01141 if interface.goodness < 0: 01142 pass # Card not configured, we are not in charge. 01143 elif i == self.best_wireless: 01144 if interface.goodness > 0: 01145 # Do not reset the active connection if it is slightly alive. 01146 interface.increase_timeout() 01147 elif interface.reliability < self.reliability_thresh or interface.goodness < 50: 01148 # Restart unreliable non-active connections. 01149 interface.decrease_timeout(3) 01150 else: 01151 # This non-active interface is doing fine. No timeout. 01152 interface.increase_timeout() 01153 01154 # Print active_iface status 01155 for iface in ns.interfaces: 01156 try: 01157 rank = iface_sorted.index(iface) + 1 01158 if rank == 1: 01159 is_active = "active" 01160 else: 01161 is_active = "#%i"%rank 01162 if self.best_wireless != -1 and iface == ns.interfaces[self.best_wireless]: 01163 is_active += ", best wifi" 01164 except ValueError: 01165 is_active = "" 01166 print >> strategy_str, "%10s %5.1f %17s %7.3f %3.0f %s"%(iface.name, (iface.timeout_time - time.time()), iface.bssid, iface.goodness, iface.reliability, is_active) 01167 01168 class AlwaysSwitchSelectionStrategy(SelectionStrategy): 01169 def __init__(self, config): 01170 SelectionStrategy.__init__(self, config) 01171 01172 def do_update(self): 01173 ns = self.ns 01174 # Update metrics 01175 goodnesses = [ i.goodness for i in ns.interfaces ] 01176 reliabilities = [ i.reliability for i in ns.interfaces ] 01177 priorities = [ i.priority for i in ns.interfaces ] 01178 01179 # Pick new active interface 01180 sort_param = [ sum(tuple) for tuple in zip(goodnesses, reliabilities, priorities) ] 01181 next_iface = (ns.active_iface + 1) % len(sort_param) 01182 if sort_param[next_iface] > 180: 01183 best_iface = next_iface 01184 else: 01185 best_sort_param = max(sort_param) 01186 best_iface = sort_param.index(best_sort_param) 01187 if goodnesses[best_iface] < 0: 01188 ns.active_iface = -1 01189 elif best_iface != ns.active_iface: 01190 ns.active_iface = best_iface 01191 ns.make_active(ns.interfaces[ns.active_iface]) 01192 01193 # Decide if the other interfaces should go down 01194 for i in range(0,len(ns.interfaces)): 01195 interface = ns.interfaces[i] 01196 if interface.goodness < 0: 01197 pass # Card not configured, we are not in charge. 01198 elif i == ns.active_iface: 01199 if interface.goodness > 0: 01200 # Do not reset the active connection if it is slightly alive. 01201 interface.increase_timeout() 01202 elif interface.reliability < 90 or interface.goodness < 50: 01203 # Restart unreliable non-active connections. 01204 interface.decrease_timeout(3) 01205 else: 01206 # This non-active interface is doing fine. No timeout. 01207 interface.increase_timeout() 01208 01209 # Print active_iface status 01210 for iface in ns.interfaces: 01211 if ns.active_iface != -1 and iface == ns.interfaces[ns.active_iface]: 01212 is_active = "active" 01213 else: 01214 is_active = "" 01215 print >> strategy_str, "%s %.1f %s %.3f %.0f %s"%(iface.name, (iface.timeout_time - time.time()), iface.bssid, iface.goodness, iface.reliability, is_active) 01216 01217 def main(config_file, strategy, supervisor_function = None): 01218 if os.getuid() != 0: 01219 print >> console, "roam.py must be run as root!" 01220 sys.exit(1) 01221 with open(config_file, 'r') as f: 01222 config = yaml.load(f.read()) 01223 try: 01224 try: 01225 try: 01226 s = strategy(config) 01227 01228 while True: 01229 s.update() 01230 if supervisor_function: 01231 try: 01232 supervisor_function(s) 01233 except: 01234 print "Exception in supervisor_function." 01235 traceback.print_exc(10) 01236 print 01237 time.sleep(1) 01238 01239 except KeyboardInterrupt: 01240 print "Exiting on CTRL-C" 01241 except: 01242 traceback.print_exc(10) 01243 print 01244 raise 01245 finally: 01246 try: 01247 safe_shutdown(s.shutdown) 01248 except: 01249 traceback.print_exc(10) 01250 print 01251 01252 print 01253 print "End of main reached. Waiting for threads to shut down." 01254 time.sleep(0.1) 01255 while True: 01256 threads = threading.enumerate() 01257 non_daemon = sum(0 if t.daemon else 1 for t in threads) 01258 if non_daemon == 1: 01259 break 01260 print 01261 print "Remaining threads:", non_daemon, len(threads) 01262 for t in threads: 01263 print ("daemon: " if t.daemon else "regular:"), t.name 01264 time.sleep(1) 01265 01266 if __name__ == "__main__": 01267 main('roam_config.yaml', SimpleSelectionStrategy) 01268 #main('roam_config.yaml', LinkBringupTimeMeasurementStrategy) 01269 #main('roam_config.yaml', LinkStabilityTimeMeasurementStrategy) 01270