Pages

Tuesday, October 13, 2009

Trivial wireless AP selector

(K)ubuntu's NetworkManager is not good enough when the APs are half-broken (e.g., don't associate, don't give DHCP leases or don't route). Too often it simply stops responding, when the user clearly sees that the network is not working.

Manual (open) wireless network configuration is simple:
  iwconfig (iface) essid (essid) ap (mac)
  dhclient (iface)
... except that you need to parse `iwlist (iface) scan` for the relevant data identifying the AP. This quickly becomes tedious when there are n-teen cells in the scan results and most of them are broken.

Here is a really shoddy script that replaces NetworkManager with a trivial interactive loop to attempt to connect to one of the detected AP.

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
  szym / Oct 2009

  NetworkManager is not good enough when the APs are half-broken
  (e.g., don't associate, don't give DHCP leases or don't route).

  This is a really shoddy script that replaces NetworkManager.
"""

from os import popen, system

class WifiSelector:
  def __init__(self, iface):
    # test iface first
    if(system("ls /sys/class/net/%s/wireless >/dev/null" % iface) != 0):
      raise Exception("%s is not wireless" % iface)
    self.iface = iface
    self.aps = []


  def scan(self):
    """ iwlist <iface> scan
        parse output and store in aps
    """
    aps = []
    cur = None
    for l in popen("iwlist %s scan" % self.iface):
      l = l.strip()
      if (not l):
        continue
      if (l.startswith('Cell')):
        if (cur != None):
          aps.append(cur)
        keys = [ 'essid','mode','chan','key','q','sig','noise','last' ]
        cur = dict([(x, '???') for x in keys])
        cur['ap'] = l.split()[4]
      if (cur == None):
        continue
      if (l.startswith('ESSID')):
        cur['essid'] = l.split(':')[1]
      if (l.startswith('Mode')):
        cur['mode'] = l.split(':')[1]
      if (l.startswith('Channel')):
        cur['chan'] = int(l.split(':')[1])
      if (l.startswith('Encryption')):
        cur['key'] = l.split(':')[1]
      if (l.startswith('Quality')):
        # Quality=15/100  Signal level:-84 dBm  Noise level=-90 dBm 
        ls = l.replace(':', ' ').replace('/', ' ').replace('=', ' ').split()     
        cur['q'] = int(ls[1])
        cur['sig'] = int(ls[5])
        #cur['noise'] = int(ls[9])
      if (l.startswith('Extra: Last beacon:')):
        #Extra: Last beacon: 1888ms ago        
        cur['last'] = int(l.split()[3][:-2])
    self.aps = aps

  def show(self):
    """ pretty print aps
    """
    for i, ap in enumerate(self.aps):
      print i, "%(mode)-6s %(key)-3s %(q)2d %(essid)-30s %(chan)4s %(ap)s %(last)5d" % ap
  
  def sortq(self):
    """ sort aps by quality
    """
    self.aps.sort(key=lambda d: d['q'], reverse=True)

  def refresh(self):
    self.scan()
    self.sortq()
    self.show()

  def select(self, i):
    """ attempt to associate with aps[i]
    """
    ap = self.aps[i]
    system("iwconfig wlan0 essid %(essid)s ap %(ap)s" % ap)
    system("dhclient %s" % self.iface)

  def kill_nm(self):
    if (system("/etc/init.d/network-manager status") == 0):
      system("/etc/init.d/network-manager stop")
      system("ifconfig %s up" % self.iface)

def main(args):
  from sys import stderr
  if (len(args) > 1 and args[1] == '-h'):
    print >> stderr, "Usage: %s <iface>" % args[0]
    return -1

  if len(args) < 2:
    iface = 'wlan0'
  else:
    iface = args[1]
  ws = WifiSelector(iface)
  ws.kill_nm()
  ws.refresh()

  while(True):
    try:
      which = int(raw_input('select (<0 refresh): '))
    except:
      break
    if (which >= 0 and which < len(ws.aps)):
      ws.select(which)
    else:
      ws.refresh()

if (__name__ == '__main__'):
  from sys import argv
  main(argv)

1 comment:

  1. I need to figure out a better way to post small source code online. Any recommendations?

    ReplyDelete