interface_selector.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 import interface
00004 import event
00005 from twisted.internet import reactor
00006 from twisted.internet.defer import inlineCallbacks
00007 import ip_rule
00008 import config
00009 import time
00010 import sys
00011 import radio_manager
00012 import logging_config
00013 import socket
00014 import system
00015 import re
00016 from netlink_monitor import netlink_monitor, IFSTATE, RunCommand
00017 from async_helpers import mainThreadCallback
00018 
00019 summary_logger = logging_config.get_logger_stream_for_file('console.summary')
00020 
00021 class RULEID:
00022     LOCAL=100
00023     FIRST_IFACE=50
00024     TUNNEL=150
00025     BLOCK_TUNNEL=175
00026     DEFAULT=200 
00027     BLOCK_NON_TUNNEL=250
00028 
00029 class InterfaceSelector:
00030     def __init__(self):
00031         self.interfaces = {}
00032         self.update_event = event.Event()
00033         self.update_interval = 1
00034         self.radio_manager = radio_manager.RadioManager()
00035         self.inactive_penalty = config.get_parameter('inactive_penalty', 50)
00036         self.forced_interface = ""
00037         self.tunnel_interface = config.get_parameter('tunnel_interface', "")
00038         self.use_tunnel = True
00039         self.active_interfaces = []
00040 
00041         self.basestation = config.get_parameter('base_station')
00042 #        print "Resolving basestation IP. (Blocking operation.)"
00043 #        self.basestation_ip = socket.gethostbyname(config.get_parameter('base_station'))
00044 
00045         # Add rules to guarantee that local routes go to the main table.
00046 #        self.local_net_rule = ip_rule.IpRule(RULEID.LOCAL)
00047 #        for subnet in config.get_parameter('local_networks'):
00048 #            self.local_net_rule.add('to', subnet, 'lookup', 'main')
00049 
00050         self.local_net_rules = []
00051         for (i, subnet) in enumerate(config.get_parameter('local_networks')):
00052             self.local_net_rules.append(ip_rule.IpRule(RULEID.LOCAL+i))
00053             self.local_net_rules[i].add('to', subnet, 'lookup', 'main')
00054 
00055         # Add a rule to send through the vpn.
00056         if self.tunnel_interface:
00057             self.vpn_rule = ip_rule.IpRule(RULEID.DEFAULT)
00058             # Use LINK here because netlink_monitor's parsing rules don't
00059             # currently work on vpn interfaces.
00060             system.system('ip', 'route', 'flush', 'table', str(RULEID.DEFAULT))
00061             netlink_monitor.get_state_publisher(self.tunnel_interface,
00062                     IFSTATE.LINK).subscribe(self._refresh_default_route)
00063 
00064 
00065         # Create all the interfaces.
00066         interface_names = config.get_parameter('interfaces').keys()
00067         ifaceid = RULEID.FIRST_IFACE
00068         for iface in interface_names:
00069             try:
00070                 new_iface = self.interfaces[iface] = interface.construct(iface, ifaceid)
00071                 new_iface.score = InterfaceSelector.TERRIBLE_INTERFACE 
00072                 new_iface.prescore = InterfaceSelector.TERRIBLE_INTERFACE
00073                 ifaceid += 1
00074             except interface.NoType:
00075                 print >> sys.stderr, "Interface %s has no type."%iface
00076                 sys.exit(1)
00077             except interface.UnknownType, e:
00078                 print >> sys.stderr, "Interface %s has unknown type %s."%(iface, e)
00079                 sys.exit(1)
00080             except:
00081                 print >> sys.stderr, "Error creating interface %s."%iface
00082                 raise
00083 
00084         # Register the radios with the radio manager
00085         for i in self.interfaces.itervalues():
00086             if isinstance(i, interface.WirelessInterface):
00087                 self.radio_manager.add_iface(i)
00088         
00089         # Prepare the rules that select a particular interface.
00090         self.tun_ip_rules = [ip_rule.IpRule(RULEID.TUNNEL+i) for i in range(len(self.interfaces) + 1)]
00091         
00092         # Set up periodic updates, and run the first one.
00093         self.shutting_down = False
00094         self._periodic_update()
00095         reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
00096 
00097     def _shutdown(self):
00098         self.shutting_down = True
00099         # remove all of our routing rules
00100         for tir in self.tun_ip_rules:
00101             tir.set()
00102         for lnr in self.local_net_rules:
00103             lnr.set()
00104 
00105     @mainThreadCallback
00106     def set_mode(self, ssid = "", bssid = "", sel_interface = "", use_tunnel = True, band = 3, scan_only = False):
00107         print >> sys.stderr, "Dynamic reconfiguration ssid: %s bssid: %s iface: %s tun: %s band: %s scan_only: %s"%(ssid, bssid, sel_interface, use_tunnel, band, scan_only)
00108         self.goodness_weight = config.get_parameter('ping_weighting', 0.5)
00109         self.radio_manager.set_mode(ssid, bssid, band, scan_only, sel_interface)
00110         self.forced_interface = sel_interface
00111         self.use_tunnel = use_tunnel
00112         if self.tunnel_interface:
00113             if use_tunnel:
00114                 self.vpn_rule.set('lookup', str(RULEID.DEFAULT))
00115             else:
00116                 self.vpn_rule.set()
00117 
00118     def _refresh_default_route(self, old_state, new_state):
00119         if new_state:
00120             system.system('ip', 'route', 'replace', 'table', str(RULEID.DEFAULT), 'default', "dev", self.tunnel_interface)
00121 
00122     def _periodic_update(self):
00123         if self.shutting_down:
00124             return
00125         self.periodic_update_handle = reactor.callLater(self.update_interval, self._periodic_update)
00126         
00127         # Update all the interfaces.
00128         for iface in self.interfaces.values():
00129             iface.update(self.update_interval)
00130         
00131         # Update the radio manager
00132         self.radio_manager.update()
00133 
00134         # Rank the interfaces.
00135         self.rank_interfaces()
00136 
00137         # Broadcast the fact that an update has just completed.
00138         self.update_event.trigger()
00139     
00140     @inlineCallbacks
00141     def set_tun_rules(self, selected_interfaces):
00142         # Set the interfaces we are given in order.
00143 
00144         use_tunnel = self.use_tunnel
00145         # If we can resolve the IP of the base station, use the tunnel
00146         # otherwise, things are probably bad. don't use the tunnel 
00147         # - if the tunnel (or DNS over the tunnel) is broken, this will
00148         #   diable the tunnel
00149         # - if we didn't have DNS and it starts working, this will enable
00150         #   the tunnel
00151         # - the basestation is a raw IP, this should always succeed and enable
00152         #   the tunnel
00153         if use_tunnel:
00154             try:
00155                 self.basestation_ip = socket.gethostbyname(self.basestation)
00156             except socket.error:
00157                 use_tunnel = False
00158 
00159         for i, iface in enumerate(selected_interfaces):
00160             if use_tunnel:
00161                 self.tun_ip_rules[i].set('to', self.basestation_ip, 'lookup', iface.tableid)
00162             else:
00163                 self.tun_ip_rules[i].set('lookup', iface.tableid)
00164 
00165         last_rule = len(selected_interfaces)
00166         # set up blackhole rule if we're using a tunnel
00167         if use_tunnel:
00168             self.tun_ip_rules[last_rule].set('to', self.basestation_ip, 'blackhole')
00169             last_rule += 1
00170             
00171         # Clear the remaining rules.
00172         for tir in self.tun_ip_rules[last_rule:]:
00173             yield tir.set()
00174     
00175     TERRIBLE_INTERFACE = -1e1000
00176 
00177     def score_interface(self, iface):
00178         # score is used for selecting the interface.
00179         # prescore is used by radio manager to decide which interface to
00180         # activate.
00181         
00182         if iface.goodness <= 0 and self.forced_interface != iface.iface:
00183             iface.prescore = iface.score = InterfaceSelector.TERRIBLE_INTERFACE
00184             return
00185 
00186         # If an interface is being forced, other interfaces
00187         # should all have terrible scores.
00188         if self.forced_interface and self.forced_interface != iface.iface:
00189             iface.prescore = iface.score = InterfaceSelector.TERRIBLE_INTERFACE
00190             return
00191 
00192         iface.prescore = iface.score = self.goodness_weight * iface.goodness + (1 - self.goodness_weight) * iface.reliability + iface.priority
00193         if not iface.active:
00194             iface.score -= self.inactive_penalty
00195         
00196     def rank_interfaces(self):        
00197         # Score interfaces
00198         interfaces = self.interfaces.values()
00199         for iface in interfaces:
00200             self.score_interface(iface)
00201 
00202         # Sort, and forget about the scores
00203         interfaces.sort(key = lambda iface: iface.score, reverse = True)
00204         active_interfaces = [ iface for iface in interfaces if iface.score != self.TERRIBLE_INTERFACE ]
00205 
00206         for iface in interfaces:
00207             if iface in active_interfaces:
00208                 iface.rank = active_interfaces.index(iface)
00209             else:
00210                 iface.rank = -1
00211 
00212         # Set the interfaces
00213         self.set_tun_rules(active_interfaces)
00214 
00215         # Print active_iface status
00216         now = time.time()
00217         print >> summary_logger
00218         print >> summary_logger, time.ctime(now), now
00219         #print >> summary_logger, netlink_monitor.get_status_publisher(self.tunnel_interface).get()
00220         for rank, iface in enumerate(interfaces):
00221             # FIXME
00222             iface.timeout_time = now
00223             active = "active" if iface.active else ""
00224             rule = "rule  " if iface in active_interfaces else "norule"
00225             print >> summary_logger, "#% 2i %10.10s %7.1f %7.3f %7.3f %17.17s %7.3f %3.0f %s %s"% \
00226                     (rank, iface.prettyname, (iface.timeout_time - now), iface.score, iface.prescore, iface.bssid, iface.goodness, iface.reliability, rule, active)
00227 
00228         self.active_interfaces = active_interfaces


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