multi_interface_roam.py
Go to the documentation of this file.
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         


multi_interface_roam
Author(s): Blaise Gassend
autogenerated on Thu Jan 2 2014 11:26:15