2
# -*- coding: utf-8 -*-
4
""" Network interface control tools for wicd.
6
This module implements functions to control and obtain information from
9
class BaseInterface() -- Control a network interface.
10
class BaseWiredInterface() -- Control a wired network interface.
11
class BaseWirelessInterface() -- Control a wireless network interface.
16
# Copyright (C) 2007 - 2009 Adam Blackburn
17
# Copyright (C) 2007 - 2009 Dan O'Reilly
18
# Copyright (C) 2007 - 2009 Byron Hillis
19
# Copyright (C) 2009 Andrew Psaltis
21
# This program is free software; you can redistribute it and/or modify
22
# it under the terms of the GNU General Public License Version 2 as
23
# published by the Free Software Foundation.
25
# This program is distributed in the hope that it will be useful,
26
# but WITHOUT ANY WARRANTY; without even the implied warranty of
27
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
# GNU General Public License for more details.
30
# You should have received a copy of the GNU General Public License
31
# along with this program. If not, see <http://www.gnu.org/licenses/>.
38
from string import maketrans, translate
42
from misc import find_path
44
# Regular expressions.
45
_re_mode = (re.I | re.M | re.S)
46
essid_pattern = re.compile('.*ESSID:"?(.*?)"?\s*\n', _re_mode)
47
ap_mac_pattern = re.compile('.*Address: (.*?)\n', _re_mode)
48
channel_pattern = re.compile('.*Channel:?=? ?(\d\d?)', _re_mode)
49
strength_pattern = re.compile('.*Quality:?=? ?(\d+)\s*/?\s*(\d*)', _re_mode)
50
altstrength_pattern = re.compile('.*Signal level:?=? ?(\d+)\s*/?\s*(\d*)', _re_mode)
51
signaldbm_pattern = re.compile('.*Signal level:?=? ?(-\d\d*)', _re_mode)
52
bitrates_pattern = re.compile('(\d+\s+\S+/s)', _re_mode)
53
mode_pattern = re.compile('.*Mode:([A-Za-z-]*?)\n', _re_mode)
54
freq_pattern = re.compile('.*Frequency:(.*?)\n', _re_mode)
55
wep_pattern = re.compile('.*Encryption key:(.*?)\n', _re_mode)
56
altwpa_pattern = re.compile('(wpa_ie)', _re_mode)
57
wpa1_pattern = re.compile('(WPA Version 1)', _re_mode)
58
wpa2_pattern = re.compile('(WPA2)', _re_mode)
60
#iwconfig-only regular expressions.
61
ip_pattern = re.compile(r'inet [Aa]d?dr[^.]*:([^.]*\.[^.]*\.[^.]*\.[0-9]*)', re.S)
62
bssid_pattern = re.compile('.*Access Point: (([0-9A-Z]{2}:){5}[0-9A-Z]{2})', _re_mode)
63
bitrate_pattern = re.compile('.*Bit Rate[=:](.*?/s)', _re_mode)
64
opmode_pattern = re.compile('.*Mode:(.*?) ', _re_mode)
65
authmethods_pattern = re.compile('.*Authentication capabilities :\n(.*?)Current', _re_mode)
67
# Regular expressions for wpa_cli output
68
auth_pattern = re.compile('.*wpa_state=(.*?)\n', _re_mode)
70
RALINK_DRIVER = 'ralink legacy'
72
blacklist_strict = '!"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ '
73
blacklist_norm = ";`$!*|><&\\"
74
blank_trans = maketrans("", "")
76
def _sanitize_string(string):
78
return translate(str(string), blank_trans, blacklist_norm)
82
def _sanitize_string_strict(string):
84
return translate(str(string), blank_trans, blacklist_strict)
89
def timedcache(duration=5):
90
""" A caching decorator for use with wnettools methods.
92
Caches the results of a function for a given number of
93
seconds (defaults to 5).
97
def __timedcache(self, *args, **kwargs):
98
key = str(args) + str(kwargs) + str(f)
99
if hasattr(self, 'iface'):
101
if (key in _cache and
102
(time.time() - _cache[key]['time']) < duration):
103
return _cache[key]['value']
105
value = f(self, *args, **kwargs)
106
_cache[key] = { 'time' : time.time(), 'value' : value }
113
def GetDefaultGateway():
114
""" Attempts to determine the default gateway by parsing route -n. """
115
route_info = misc.Run("route -n")
116
lines = route_info.split('\n')
123
if words[0] == '0.0.0.0':
128
print 'couldn\'t retrieve default gateway from route -n'
131
def GetWirelessInterfaces():
132
""" Get available wireless interfaces.
134
Attempts to get an interface first by parsing /proc/net/wireless,
135
and should that fail, by parsing iwconfig.
137
The first interface available.
140
dev_dir = '/sys/class/net/'
141
ifnames = [iface for iface in os.listdir(dev_dir)
142
if os.path.isdir(dev_dir + iface) and
143
'wireless' in os.listdir(dev_dir + iface)]
147
def GetWiredInterfaces():
148
""" Returns a list of wired interfaces on the system. """
149
basedir = '/sys/class/net/'
150
return [iface for iface in os.listdir(basedir)
151
if os.path.isdir(basedir + iface) and not 'wireless'
152
in os.listdir(basedir + iface) and
153
open(basedir + iface + "/type").readlines()[0].strip() == "1"]
155
def NeedsExternalCalls():
156
""" Returns True if the backend needs to use an external program. """
157
raise NotImplementedError
159
def GetWpaSupplicantDrivers():
160
""" Returns a list of all valid wpa_supplicant drivers. """
161
output = misc.Run(["wpa_supplicant", "-h"])
163
output = output.split("drivers:")[1].split("options:")[0].strip()
165
print "Warning: Couldn't get list of valid wpa_supplicant drivers"
167
patt = re.compile("(\S+)\s+=.*")
168
drivers = patt.findall(output) or [""]
169
# We cannot use the "wired" driver for wireless interfaces.
170
if 'wired' in drivers:
171
drivers.remove('wired')
173
def IsValidWpaSuppDriver(driver):
174
""" Returns True if given string is a valid wpa_supplicant driver. """
175
output = misc.Run(["wpa_supplicant", "-D%s" % driver, "-iolan19",
176
"-c/etc/abcd%sdefzz.zconfz" % random.randint(1, 1000)])
177
return not "Unsupported driver" in output
179
def neediface(default_response):
180
""" A decorator for only running a method if self.iface is defined.
182
This decorator is wrapped around Interface methods, and will
183
return a provided default_response value if self.iface is not
188
def newfunc(self, *args, **kwargs):
189
if not self.iface or \
190
not os.path.exists('/sys/class/net/%s' % self.iface):
191
return default_response
192
return func(self, *args, **kwargs)
193
newfunc.__dict__ = func.__dict__
194
newfunc.__doc__ = func.__doc__
195
newfunc.__module__ = func.__module__
200
class BaseInterface(object):
201
""" Control a network interface. """
202
def __init__(self, iface, verbose=False):
203
""" Initialise the object.
206
iface -- the name of the interface
207
verbose -- whether to print every command run
210
self.iface = _sanitize_string_strict(iface)
211
self.verbose = verbose
212
self.DHCP_CLIENT = None
213
self.flush_tool = None
214
self.link_detect = None
215
self.dhcp_object = None
217
def SetDebugMode(self, value):
218
""" If True, verbose output is enabled. """
221
def SetInterface(self, iface):
222
""" Sets the interface.
225
iface -- the name of the interface.
228
self.iface = _sanitize_string_strict(str(iface))
230
def _find_program_path(self, program):
231
""" Determines the full path for the given program.
233
Searches for a given program name on the PATH.
236
program -- The name of the program to search for
239
The full path of the program or None
242
path = find_path(program)
243
if not path and self.verbose:
244
print "WARNING: No path found for %s" % program
248
def _get_dhcp_command(self, flavor=None, hostname=None):
249
""" Returns the correct DHCP client command.
251
Given a type of DHCP request (create or release a lease),
252
this method will build a command to complete the request
253
using the correct dhcp client, and cli options.
256
def get_client_name(cl):
257
""" Converts the integer value for a dhcp client to a string. """
258
if self.dhcpcd_cmd and cl in [misc.DHCPCD, misc.AUTO]:
260
cmd = self.dhcpcd_cmd
261
elif self.pump_cmd and cl in [misc.PUMP, misc.AUTO]:
264
elif self.dhclient_cmd and cl in [misc.DHCLIENT, misc.AUTO]:
266
cmd = self.dhclient_cmd
267
if self.dhclient_needs_verbose:
269
elif self.udhcpc_cmd and cl in [misc.UDHCPC, misc.AUTO]:
271
cmd = self.udhcpc_cmd
277
# probably /var/lib/wicd/dhclient.conf with defaults
278
dhclient_conf_path = os.path.join(
285
{'connect' : r"%(cmd)s -cf %(dhclientconf)s %(iface)s",
286
'release' : r"%(cmd)s -r %(iface)s",
287
'id' : misc.DHCLIENT,
290
{ 'connect' : r"%(cmd)s -i %(iface)s -h %(hostname)s",
291
'release' : r"%(cmd)s -r -i %(iface)s",
295
{'connect' : r"%(cmd)s -h %(hostname)s --noipv4ll %(iface)s ",
296
'release' : r"%(cmd)s -k %(iface)s",
300
{'connect' : r"%(cmd)s -n -i %(iface)s -H %(hostname)s ",
301
'release' : r"killall -SIGUSR2 %(cmd)s",
305
(client_name, cmd) = get_client_name(self.DHCP_CLIENT)
307
# cause dhclient doesn't have a handy dandy argument
308
# for specifing the hostname to be sent
309
if client_name == "dhclient" and flavor:
311
# <hostname> will use the system hostname
312
# we'll use that if there is hostname passed
313
# that shouldn't happen, though
314
hostname = '<hostname>'
315
print 'attempting to set hostname with dhclient'
316
print 'using dhcpcd or another supported client may work better'
317
dhclient_template = \
318
open(os.path.join(wpath.etc, 'dhclient.conf.template'), 'r')
320
output_conf = open(dhclient_conf_path, 'w')
322
for line in dhclient_template.readlines():
323
line = line.replace('$_HOSTNAME', hostname)
324
output_conf.write(line)
327
dhclient_template.close()
328
os.chmod(dhclient_conf_path, 0644)
330
if not client_name or not cmd:
331
print "WARNING: Failed to find a valid dhcp client!"
334
if flavor == "connect":
336
hostname = os.uname()[1]
337
return client_dict[client_name]['connect'] % \
339
"iface" : self.iface,
340
"hostname" : hostname,
341
'dhclientconf' : dhclient_conf_path }
342
elif flavor == "release":
343
return client_dict[client_name]['release'] % {"cmd":cmd, "iface":self.iface}
345
return client_dict[client_name]['id']
347
def AppAvailable(self, app):
348
""" Return whether a given app is available.
350
Given the name of an executable, determines if it is
351
available for use by checking for a defined 'app'_cmd
355
return bool(self.__dict__.get("%s_cmd" % app.replace("-", "")))
358
""" Check that all required tools are available. """
359
# THINGS TO CHECK FOR: ethtool, pptp-linux, dhclient, host
361
self.CheckWiredTools()
362
self.CheckWirelessTools()
363
self.CheckSudoApplications()
364
self.CheckRouteFlushTool()
365
self.CheckResolvConf()
367
def CheckResolvConf(self):
368
""" Checks for the existence of resolvconf."""
369
self.resolvconf_cmd = self._find_program_path("resolvconf")
372
""" Check for the existence of valid DHCP clients.
374
Checks for the existence of a supported DHCP client. If one is
375
found, the appropriate values for DHCP_CMD, DHCP_RELEASE, and
376
DHCP_CLIENT are set. If a supported client is not found, a
380
self.dhclient_cmd = self._find_program_path("dhclient")
381
if self.dhclient_cmd != None:
382
output = misc.Run(self.dhclient_cmd + " --version",
385
self.dhclient_needs_verbose = True
387
self.dhclient_needs_verbose = False
388
self.dhcpcd_cmd = self._find_program_path("dhcpcd")
389
self.pump_cmd = self._find_program_path("pump")
390
self.udhcpc_cmd = self._find_program_path("udhcpc")
392
def CheckWiredTools(self):
393
""" Check for the existence of ethtool and mii-tool. """
394
self.miitool_cmd = self._find_program_path("mii-tool")
395
self.ethtool_cmd = self._find_program_path("ethtool")
397
def CheckWirelessTools(self):
398
""" Check for the existence of wpa_cli """
399
self.wpa_cli_cmd = self._find_program_path("wpa_cli")
400
if not self.wpa_cli_cmd:
401
print "wpa_cli not found. Authentication will not be validated."
403
def CheckRouteFlushTool(self):
404
""" Check for a route flush tool. """
405
self.ip_cmd = self._find_program_path("ip")
406
self.route_cmd = self._find_program_path("route")
408
def CheckSudoApplications(self):
409
self.gksudo_cmd = self._find_program_path("gksudo")
410
self.kdesu_cmd = self._find_program_path("kdesu")
411
self.ktsuss_cmd = self._find_program_path("ktsuss")
415
""" Bring the network interface up.
421
cmd = 'ifconfig ' + self.iface + ' up'
422
if self.verbose: print cmd
428
""" Take down the network interface.
434
cmd = 'ifconfig ' + self.iface + ' down'
435
if self.verbose: print cmd
441
def GetIfconfig(self):
442
""" Runs ifconfig and returns the output. """
443
cmd = "ifconfig %s" % self.iface
444
if self.verbose: print cmd
448
def SetAddress(self, ip=None, netmask=None, broadcast=None):
449
""" Set the IP addresses of an interface.
452
ip -- interface IP address in dotted quad form
453
netmask -- netmask address in dotted quad form
454
broadcast -- broadcast address in dotted quad form
457
for val in [ip, netmask, broadcast]:
460
if not misc.IsValidIP(val):
461
print 'WARNING: Invalid IP address found, aborting!'
464
cmd = ''.join(['ifconfig ', self.iface, ' '])
466
cmd = ''.join([cmd, ip, ' '])
468
cmd = ''.join([cmd, 'netmask ', netmask, ' '])
470
cmd = ''.join([cmd, 'broadcast ', broadcast, ' '])
471
if self.verbose: print cmd
474
def _parse_dhclient(self, pipe):
475
""" Parse the output of dhclient.
477
Parses the output of dhclient and returns the status of
478
the connection attempt.
481
pipe -- stdout pipe to the dhclient process.
484
'success' if succesful', an error code string otherwise.
487
dhclient_complete = False
488
dhclient_success = False
490
while not dhclient_complete:
491
line = pipe.readline()
492
if line == '': # Empty string means dhclient is done.
493
dhclient_complete = True
495
print misc.to_unicode(line.strip('\n'))
496
if line.startswith('bound'):
497
dhclient_success = True
498
dhclient_complete = True
500
return self._check_dhcp_result(dhclient_success)
502
def _parse_pump(self, pipe):
503
""" Determines if obtaining an IP using pump succeeded.
506
pipe -- stdout pipe to the pump process.
509
'success' if succesful, an error code string otherwise.
512
pump_complete = False
515
while not pump_complete:
516
line = pipe.readline()
519
elif line.strip().lower().startswith('Operation failed.'):
522
print misc.to_unicode(line)
524
return self._check_dhcp_result(pump_success)
526
def _parse_dhcpcd(self, pipe):
527
""" Determines if obtaining an IP using dhcpcd succeeded.
530
pipe -- stdout pipe to the dhcpcd process.
533
'success' if succesful, an error code string otherwise.
536
dhcpcd_complete = False
537
dhcpcd_success = True
539
while not dhcpcd_complete:
540
line = pipe.readline()
541
if "Error" in line or "timed out" in line:
542
dhcpcd_success = False
543
dhcpcd_complete = True
545
dhcpcd_complete = True
546
print misc.to_unicode(line)
548
return self._check_dhcp_result(dhcpcd_success)
550
def _parse_udhcpc(self, pipe):
551
""" Determines if obtaining an IP using udhcpc succeeded.
554
pipe -- stdout pipe to the dhcpcd process.
557
'success' if successful, an error code string otherwise.
560
udhcpc_complete = False
561
udhcpc_success = True
563
while not udhcpc_complete:
564
line = pipe.readline()
565
if line.endswith("failing."):
566
udhcpc_success = False
567
udhcpc_complete = True
569
udhcpc_complete = True
572
return self._check_dhcp_result(udhcpc_success)
574
def _check_dhcp_result(self, success):
575
""" Print and return the correct DHCP connection result.
578
success -- boolean specifying if DHCP was succesful.
581
'success' if success == True, 'dhcp_failed' otherwise.
585
print 'DHCP connection successful'
588
print 'DHCP connection failed'
592
def StartDHCP(self, hostname):
593
""" Start the DHCP client to obtain an IP address.
596
hostname -- the hostname to send to the DHCP server
599
A string representing the result of the DHCP command. See
600
_check_dhcp_result for the possible values.
603
cmd = self._get_dhcp_command('connect', hostname)
604
if self.verbose: print cmd
605
self.dhcp_object = misc.Run(cmd, include_stderr=True, return_obj=True)
606
pipe = self.dhcp_object.stdout
607
client_dict = { misc.DHCLIENT : self._parse_dhclient,
608
misc.DHCPCD : self._parse_dhcpcd,
609
misc.PUMP : self._parse_pump,
610
misc.UDHCPC : self._parse_udhcpc,
613
DHCP_CLIENT = self._get_dhcp_command()
614
if DHCP_CLIENT in client_dict:
615
ret = client_dict[DHCP_CLIENT](pipe)
617
print "ERROR: no dhcp client found"
619
self.dhcp_object.wait()
623
def ReleaseDHCP(self):
624
""" Release the DHCP lease for this interface. """
625
cmd = self._get_dhcp_command("release")
626
if self.verbose: print cmd
630
def DelDefaultRoute(self):
631
""" Delete only the default route for a device. """
632
if self.ip_cmd and self.flush_tool in [misc.AUTO, misc.IP]:
633
cmd = '%s route del default dev %s' % (self.ip_cmd, self.iface)
634
elif self.route_cmd and self.flush_tool in [misc.AUTO, misc.ROUTE]:
635
cmd = '%s del default dev %s' % (self.route_cmd, self.iface)
637
print "No route manipulation command available!"
639
if self.verbose: print cmd
643
def SetDNS(self, dns1=None, dns2=None, dns3=None,
644
dns_dom=None, search_dom=None):
645
""" Set the DNS of the system to the specified DNS servers.
647
Opens up resolv.conf and writes in the nameservers.
650
dns1 -- IP address of DNS server 1
651
dns2 -- IP address of DNS server 2
652
dns3 -- IP address of DNS server 3
653
dns_dom -- DNS domain
654
search_dom -- DNS search domain
659
resolv_params += 'domain %s\n' % dns_dom
661
resolv_params += 'search %s\n' % search_dom
664
for dns in (dns1, dns2, dns3):
666
if misc.IsValidIP(dns):
668
print 'Setting DNS : ' + dns
669
valid_dns_list.append("nameserver %s\n" % dns)
671
print 'DNS IP %s is not a valid IP address, skipping' % dns
674
resolv_params += ''.join(valid_dns_list)
676
if self.resolvconf_cmd:
677
cmd = [self.resolvconf_cmd, '-a', self.iface]
678
if self.verbose: print cmd
679
p = misc.Run(cmd, include_stderr=True, return_obj=True)
680
p.communicate(input=resolv_params)
682
resolv = open("/etc/resolv.conf", "w")
683
resolv.write(resolv_params + "\n")
687
def FlushRoutes(self):
688
""" Flush network routes for this device. """
689
if self.ip_cmd and self.flush_tool in [misc.AUTO, misc.IP]:
690
cmds = ['%s route flush dev %s' % (self.ip_cmd, self.iface)]
691
elif self.route_cmd and self.flush_tool in [misc.AUTO, misc.ROUTE]:
692
cmds = ['%s del dev %s' % (self.route_cmd, self.iface)]
694
print "No flush command available!"
697
if self.verbose: print cmd
701
def SetDefaultRoute(self, gw):
702
""" Add a default route with the specified gateway.
705
gw -- gateway of the default route in dotted quad form
708
if not misc.IsValidIP(gw):
709
print 'WARNING: Invalid gateway found. Aborting!'
711
cmd = 'route add default gw %s dev %s' % (gw, self.iface)
712
if self.verbose: print cmd
716
def GetIP(self, ifconfig=""):
717
""" Get the IP address of the interface.
720
The IP address of the interface in dotted quad form.
724
output = self.GetIfconfig()
727
return misc.RunRegex(ip_pattern, output)
730
def VerifyAPAssociation(self, gateway):
731
""" Verify assocation with an access point.
733
Verifies that an access point can be contacted by
737
if "iputils" in misc.Run(["ping", "-V"]):
738
cmd = "ping -q -w 3 -c 1 %s" % gateway
740
# ping is from inetutils-ping (which doesn't support -w)
741
# or from some other package
743
# If there's no AP association, this will wait for
744
# timeout, while the above will wait (-w) 3 seconds at
746
cmd = "ping -q -c 1 %s" % gateway
747
if self.verbose: print cmd
748
return misc.LaunchAndWait(cmd)
751
def IsUp(self, ifconfig=None):
752
""" Determines if the interface is up.
755
True if the interface is up, False otherwise.
758
flags_file = '/sys/class/net/%s/flags' % self.iface
760
flags = open(flags_file, "r").read().strip()
762
print "Could not open %s, using ifconfig to determine status" % flags_file
763
return self._slow_is_up(ifconfig)
764
return bool(int(flags, 16) & 1)
767
def _slow_is_up(self, ifconfig=None):
768
""" Determine if an interface is up using ifconfig. """
770
output = self.GetIfconfig()
773
lines = output.split('\n')
776
for line in lines[1:4]:
777
if line.strip().startswith('UP'):
782
class BaseWiredInterface(BaseInterface):
783
""" Control a wired network interface. """
784
def __init__(self, iface, verbose=False):
785
""" Initialise the wired network interface class.
788
iface -- name of the interface
789
verbose -- print all commands
792
BaseInterface.__init__(self, iface, verbose)
795
def GetPluggedIn(self):
796
""" Get the current physical connection state.
798
The method will first attempt to use ethtool do determine
799
physical connection state. Should ethtool fail to run properly,
800
mii-tool will be used instead.
803
True if a link is detected, False otherwise.
806
# check for link using /sys/class/net/iface/carrier
807
# is usually more accurate
808
sys_device = '/sys/class/net/%s/' % self.iface
809
carrier_path = sys_device + 'carrier'
817
if self.IsUp() or tries > MAX_TRIES: break
819
if os.path.exists(carrier_path):
820
carrier = open(carrier_path, 'r')
822
link = carrier.read().strip()
828
except (IOError, ValueError, TypeError):
829
print 'Error checking link using /sys/class/net/%s/carrier' % self.iface
831
if self.ethtool_cmd and self.link_detect in [misc.ETHTOOL, misc.AUTO]:
832
return self._eth_get_plugged_in()
833
elif self.miitool_cmd and self.link_detect in [misc.MIITOOL, misc.AUTO]:
834
return self._mii_get_plugged_in()
836
print ('Error: No way of checking for a wired connection. Make ' +
837
'sure that either mii-tool or ethtool is installed.')
840
def _eth_get_plugged_in(self):
841
""" Use ethtool to determine the physical connection state.
844
True if a link is detected, False otherwise.
847
cmd = "%s %s" % (self.ethtool_cmd, self.iface)
849
print 'Wired Interface is down, putting it up'
852
if self.verbose: print cmd
853
tool_data = misc.Run(cmd, include_stderr=True)
854
if misc.RunRegex(re.compile('(Link detected: yes)', re.I | re.M | re.S),
860
def _mii_get_plugged_in(self):
861
""" Use mii-tool to determine the physical connection state.
864
True if a link is detected, False otherwise.
867
cmd = "%s %s" % (self.miitool_cmd, self.iface)
868
if self.verbose: print cmd
869
tool_data = misc.Run(cmd, include_stderr=True)
870
if misc.RunRegex(re.compile('(Invalid argument)', re.I | re.M | re.S),
871
tool_data) is not None:
872
print 'Wired Interface is down, putting it up'
875
if self.verbose: print cmd
876
tool_data = misc.Run(cmd, include_stderr=True)
878
if misc.RunRegex(re.compile('(link ok)', re.I | re.M | re.S),
879
tool_data) is not None:
885
class BaseWirelessInterface(BaseInterface):
886
""" Control a wireless network interface. """
887
def __init__(self, iface, verbose=False, wpa_driver='wext'):
888
""" Initialise the wireless network interface class.
891
iface -- name of the interface
892
verbose -- print all commands
895
BaseInterface.__init__(self, iface, verbose)
896
self.wpa_driver = wpa_driver
897
self.scan_iface = None
899
def SetWpaDriver(self, driver):
900
""" Sets the wpa_driver. """
901
self.wpa_driver = _sanitize_string(driver)
904
def SetEssid(self, essid):
905
""" Set the essid of the wireless interface.
908
essid -- essid to set the interface to
911
cmd = ['iwconfig', self.iface, 'essid', '--', str(essid)]
912
if self.verbose: print str(cmd)
916
def GetKillSwitchStatus(self):
917
""" Determines if the wireless killswitch is enabled.
920
True if the killswitch is enabled, False otherwise.
923
output = self.GetIwconfig()
925
killswitch_pattern = re.compile('.*radio off', re.I | re.M | re.S)
926
if killswitch_pattern.search(output):
935
def GetIwconfig(self):
936
""" Returns the output of iwconfig for this interface. """
937
cmd = "iwconfig " + self.iface
938
if self.verbose: print cmd
941
def _FreqToChannel(self, freq):
942
""" Translate the specified frequency to a channel.
944
Note: This function is simply a lookup dict and therefore the
945
freq argument must be in the dict to provide a valid channel.
948
freq -- string containing the specified frequency
951
The channel number, or None if not found.
955
freq_dict = {'2.412 GHz': 1, '2.417 GHz': 2, '2.422 GHz': 3,
956
'2.427 GHz': 4, '2.432 GHz': 5, '2.437 GHz': 6,
957
'2.442 GHz': 7, '2.447 GHz': 8, '2.452 GHz': 9,
958
'2.457 GHz': 10, '2.462 GHz': 11, '2.467 GHz': 12,
959
'2.472 GHz': 13, '2.484 GHz': 14 }
961
ret = freq_dict[freq]
963
print "Couldn't determine channel number for frequency: " + str(freq)
967
def _GetRalinkInfo(self):
968
""" Get a network info list used for ralink drivers
970
Calls iwpriv <wireless interface> get_site_survey, which
971
on some ralink cards will return encryption and signal
972
strength info for wireless networks in the area.
975
iwpriv = misc.Run('iwpriv ' + self.iface + ' get_site_survey')
978
lines = iwpriv.splitlines()[2:]
980
patt = re.compile("((?:[0-9A-Z]{2}:){5}[0-9A-Z]{2})")
984
info = filter(None, [x.strip() for x in info])
987
if re.match(patt, info[2].upper()):
988
bssid = info[2].upper()
990
elif re.match(patt, info[3].upper()):
991
bssid = info[3].upper()
994
print 'Invalid iwpriv line. Skipping it.'
996
ap['nettype'] = info[-1]
997
ap['strength'] = info[1]
998
if info[4 + offset] == 'WEP':
999
ap['encryption_method'] = 'WEP'
1000
ap['enctype'] = 'WEP'
1001
ap['keyname'] = 'Key1'
1002
ap['authmode'] = info[5 + offset]
1003
elif info[5 + offset] in ['WPA-PSK', 'WPA']:
1004
ap['encryption_method'] = 'WPA'
1005
ap['authmode'] = "WPAPSK"
1006
ap['keyname'] = "WPAPSK"
1007
ap['enctype'] = info[4 + offset]
1008
elif info[5 + offset] == 'WPA2-PSK':
1009
ap['encryption_method'] = 'WPA2'
1010
ap['authmode'] ="WPA2PSK"
1011
ap['keyname'] = "WPA2PSK"
1012
ap['enctype'] = info[4 + offset]
1013
elif info[4 + offset] == "NONE":
1014
ap['encryption_method'] = None
1016
print "Unknown AuthMode, can't assign encryption_method!"
1017
ap['encryption_method'] = 'Unknown'
1019
if self.verbose: print str(aps)
1022
def _ParseRalinkAccessPoint(self, ap, ralink_info, cell):
1023
""" Parse encryption and signal strength info for ralink cards
1026
ap -- array containing info about the current access point
1027
ralink_info -- dict containing available network info
1028
cell -- string containing cell information
1031
Updated array containing info about the current access point
1034
wep_pattern = re.compile('.*Encryption key:(.*?)\n', re.I | re.M | re.S)
1035
if ralink_info.has_key(ap['bssid']):
1036
info = ralink_info[ap['bssid']]
1037
for key in info.keys():
1039
if misc.RunRegex(wep_pattern, cell) == 'on':
1040
ap['encryption'] = True
1042
ap['encryption'] = False
1046
def SetMode(self, mode):
1047
""" Set the mode of the wireless interface.
1050
mode -- mode to set the interface to
1053
mode = _sanitize_string_strict(mode)
1054
if mode.lower() == 'master':
1056
cmd = 'iwconfig %s mode %s' % (self.iface, mode)
1057
if self.verbose: print cmd
1061
def SetChannel(self, channel):
1062
""" Set the channel of the wireless interface.
1065
channel -- channel to set the interface to
1068
if not channel.isdigit():
1069
print 'WARNING: Invalid channel found. Aborting!'
1072
cmd = 'iwconfig %s channel %s' % (self.iface, str(channel))
1073
if self.verbose: print cmd
1077
def SetKey(self, key):
1078
""" Set the encryption key of the wireless interface.
1081
key -- encryption key to set
1084
cmd = 'iwconfig %s key %s' % (self.iface, key)
1085
if self.verbose: print cmd
1089
def Associate(self, essid, channel=None, bssid=None):
1090
""" Associate with the specified wireless network.
1093
essid -- essid of the network
1094
channel -- channel of the network
1095
bssid -- bssid of the network
1098
self.SetEssid(essid)
1099
base = "iwconfig %s" % self.iface
1100
if channel and str(channel).isdigit():
1101
cmd = "%s channel %s" % (base, str(channel))
1102
if self.verbose: print cmd
1105
cmd = "%s ap %s" % (base, bssid)
1106
if self.verbose: print cmd
1109
def GeneratePSK(self, network):
1110
""" Generate a PSK using wpa_passphrase.
1113
network -- dictionary containing network info
1116
wpa_pass_path = misc.find_path('wpa_passphrase')
1117
if not wpa_pass_path: return None
1118
key_pattern = re.compile('network={.*?\spsk=(.*?)\n}.*',
1120
cmd = [wpa_pass_path, str(network['essid']), str(network['key'])]
1121
if self.verbose: print cmd
1122
return misc.RunRegex(key_pattern, misc.Run(cmd))
1125
def Authenticate(self, network):
1126
""" Authenticate with the specified wireless network.
1129
network -- dictionary containing network info
1132
misc.ParseEncryption(network)
1133
if self.wpa_driver == RALINK_DRIVER:
1134
self._AuthenticateRalinkLegacy(network)
1136
cmd = ['wpa_supplicant', '-B', '-i', self.iface, '-c',
1137
os.path.join(wpath.networks,
1138
network['bssid'].replace(':', '').lower()),
1139
'-D', self.wpa_driver]
1140
if self.verbose: print cmd
1143
def _AuthenticateRalinkLegacy(self, network):
1144
""" Authenticate with the specified wireless network.
1146
This function handles Ralink legacy cards that cannot use
1150
network -- dictionary containing network info
1153
if network.get('key') != None:
1155
info = self._GetRalinkInfo()[network.get('bssid')]
1157
print "Could not find current network in iwpriv " + \
1158
"get_site_survey results. Cannot authenticate."
1161
if info['enctype'] == "WEP" and info['authtype'] == 'OPEN':
1162
print 'Setting up WEP'
1163
cmd = ''.join(['iwconfig ', self.iface, ' key ',
1164
network.get('key')])
1165
if self.verbose: print cmd
1169
cmd_list.append('NetworkType=' + info['nettype'])
1170
cmd_list.append('AuthMode=' + info['authmode'])
1171
cmd_list.append('EncrypType=' + info['enctype'])
1172
cmd_list.append('SSID="%s"' % network['essid'])
1173
cmd_list.append('%s="%s"' % (network['keyname'], network['key']))
1174
if info['nettype'] == 'SHARED' and info['enctype'] == 'WEP':
1175
cmd_list.append('DefaultKeyID=1')
1177
for cmd in cmd_list:
1178
cmd = ['iwpriv', self.iface, 'set', cmd]
1179
if self.verbose: print ' '.join(cmd)
1183
def GetNetworks(self):
1184
""" Get a list of available wireless networks.
1187
A list containing available wireless networks.
1190
cmd = 'iwlist ' + self.iface + ' scan'
1191
if self.verbose: print cmd
1192
results = misc.Run(cmd)
1193
# Split the networks apart, using Cell as our split point
1194
# this way we can look at only one network at a time.
1195
# The spaces around ' Cell ' are to minimize the chance that someone
1196
# has an essid named Cell...
1197
networks = results.split( ' Cell ' )
1199
# Get available network info from iwpriv get_site_survey
1200
# if we're using a ralink card (needed to get encryption info)
1201
if self.wpa_driver == RALINK_DRIVER:
1202
ralink_info = self._GetRalinkInfo()
1206
# An array for the access points
1209
for cell in networks:
1210
# Only use sections where there is an ESSID.
1211
if 'ESSID:' in cell:
1212
# Add this network to the list of networks
1213
entry = self._ParseAccessPoint(cell, ralink_info)
1214
if entry is not None:
1215
# Normally we only get duplicate bssids with hidden
1216
# networks. If we hit this, we only want the entry
1217
# with the real essid to be in the network list.
1218
if (entry['bssid'] not in access_points
1219
or not entry['hidden']):
1220
access_points[entry['bssid']] = entry
1222
return access_points.values()
1224
def _ParseAccessPoint(self, cell, ralink_info):
1225
""" Parse a single cell from the output of iwlist.
1228
cell -- string containing the cell information
1229
ralink_info -- string contating network information needed
1233
A dictionary containing the cell networks properties.
1237
ap['essid'] = misc.RunRegex(essid_pattern, cell)
1239
ap['essid'] = misc.to_unicode(ap['essid'])
1240
except (UnicodeDecodeError, UnicodeEncodeError):
1241
print 'Unicode problem with current network essid, ignoring!!'
1243
if ap['essid'] in ['Hidden', '<hidden>', "", None]:
1246
ap['essid'] = "<hidden>"
1248
ap['hidden'] = False
1250
# Channel - For cards that don't have a channel number,
1251
# convert the frequency.
1252
ap['channel'] = misc.RunRegex(channel_pattern, cell)
1253
if ap['channel'] == None:
1254
freq = misc.RunRegex(freq_pattern, cell)
1255
ap['channel'] = self._FreqToChannel(freq)
1258
ap['bitrates'] = misc.RunRegex(bitrates_pattern,
1259
cell.split("Bit Rates")[-1])
1262
ap['bssid'] = misc.RunRegex(ap_mac_pattern, cell)
1265
ap['mode'] = misc.RunRegex(mode_pattern, cell)
1266
if ap['mode'] is None:
1267
print 'Invalid network mode string, ignoring!'
1270
# Break off here if we're using a ralink card
1271
if self.wpa_driver == RALINK_DRIVER:
1272
ap = self._ParseRalinkAccessPoint(ap, ralink_info, cell)
1273
elif misc.RunRegex(wep_pattern, cell) == 'on':
1274
# Encryption - Default to WEP
1275
ap['encryption'] = True
1276
ap['encryption_method'] = 'WEP'
1278
if misc.RunRegex(wpa1_pattern, cell) == 'WPA Version 1':
1279
ap['encryption_method'] = 'WPA'
1281
if misc.RunRegex(altwpa_pattern, cell) == 'wpa_ie':
1282
ap['encryption_method'] = 'WPA'
1284
if misc.RunRegex(wpa2_pattern, cell) == 'WPA2':
1285
ap['encryption_method'] = 'WPA2'
1287
ap['encryption'] = False
1290
# Set strength to -1 if the quality is not found
1291
ap['quality'] = self._get_link_quality(cell)
1292
if ap['quality'] is None:
1295
# Signal Strength (only used if user doesn't want link
1296
# quality displayed or it isn't found)
1297
if misc.RunRegex(signaldbm_pattern, cell):
1298
ap['strength'] = misc.RunRegex(signaldbm_pattern, cell)
1299
elif self.wpa_driver != RALINK_DRIVER: # This is already set for ralink
1304
def ValidateAuthentication(self, auth_time):
1305
""" Validate WPA authentication.
1307
Validate that the wpa_supplicant authentication
1308
process was successful.
1310
NOTE: It's possible this could return False,
1311
though in reality wpa_supplicant just isn't
1315
auth_time -- The time at which authentication began.
1318
True if wpa_supplicant authenticated succesfully,
1322
# Right now there's no way to do this for these drivers
1323
if self.wpa_driver == RALINK_DRIVER or not self.wpa_cli_cmd:
1327
MAX_DISCONNECTED_TIME = 3
1328
disconnected_time = 0
1329
forced_rescan = False
1330
while (time.time() - auth_time) < MAX_TIME:
1331
cmd = '%s -i %s status' % (self.wpa_cli_cmd, self.iface)
1332
output = misc.Run(cmd)
1333
result = misc.RunRegex(auth_pattern, output)
1335
print 'WPA_CLI RESULT IS', result
1339
if result == "COMPLETED":
1341
elif result == "DISCONNECTED" and not forced_rescan:
1342
disconnected_time += 1
1343
if disconnected_time > MAX_DISCONNECTED_TIME:
1344
disconnected_time = 0
1345
# Force a rescan to get wpa_supplicant moving again.
1346
forced_rescan = True
1347
self._ForceSupplicantScan()
1350
disconnected_time = 0
1353
print 'wpa_supplicant authentication may have failed.'
1357
def _ForceSupplicantScan(self):
1358
""" Force wpa_supplicant to rescan available networks.
1360
This function forces wpa_supplicant to rescan.
1361
This works around authentication validation sometimes failing for
1362
wpa_supplicant because it remains in a DISCONNECTED state for
1363
quite a while, after which a rescan is required, and then
1364
attempting to authenticate. This whole process takes a long
1365
time, so we manually speed it up if we see it happening.
1368
print 'wpa_supplicant rescan forced...'
1369
cmd = 'wpa_cli -i' + self.iface + ' scan'
1374
""" Terminates wpa using wpa_cli"""
1375
cmd = 'wpa_cli -i %s terminate' % self.iface
1376
if self.verbose: print cmd
1380
def GetBSSID(self, iwconfig=None):
1381
""" Get the MAC address for the interface. """
1383
output = self.GetIwconfig()
1387
bssid = misc.RunRegex(bssid_pattern, output)
1391
def GetCurrentBitrate(self, iwconfig=None):
1392
""" Get the current bitrate for the interface. """
1394
output = self.GetIwconfig()
1398
bitrate = misc.RunRegex(bitrate_pattern, output)
1402
def GetOperationalMode(self, iwconfig=None):
1403
""" Get the operational mode for the interface. """
1405
output = self.GetIwconfig()
1409
opmode = misc.RunRegex(opmode_pattern, output)
1411
opmode = opmode.strip()
1415
def GetAvailableAuthMethods(self, iwlistauth=None):
1416
""" Get the available authentication methods for the interface. """
1418
cmd = 'iwlist ' + self.iface + ' auth'
1419
if self.verbose: print cmd
1420
output = misc.Run(cmd)
1424
authm = misc.RunRegex(authmethods_pattern, output)
1425
authm_list = [m.strip() for m in authm.split('\n') if m.strip()]
1426
return ';'.join(authm_list)
1428
def _get_link_quality(self, output):
1429
""" Parse out the link quality from iwlist scan or iwconfig output. """
1431
[(strength, max_strength)] = strength_pattern.findall(output)
1433
(strength, max_strength) = (None, None)
1435
if strength in ['', None]:
1437
[(strength, max_strength)] = altstrength_pattern.findall(output)
1439
# if the pattern was unable to match anything
1440
# we'll return 101, which will allow us to stay
1441
# connected even though we don't know the strength
1442
# it also allows us to tell if
1444
if strength not in ['', None] and max_strength:
1445
return (100 * int(strength) // int(max_strength))
1446
elif strength not in ["", None]:
1447
return int(strength)
1452
def GetSignalStrength(self, iwconfig=None):
1453
""" Get the signal strength of the current network.
1456
The signal strength.
1460
output = self.GetIwconfig()
1463
return self._get_link_quality(output)
1466
def GetDBMStrength(self, iwconfig=None):
1467
""" Get the dBm signal strength of the current network.
1470
The dBm signal strength.
1474
output = self.GetIwconfig()
1477
signaldbm_pattern = re.compile('.*Signal level:?=? ?(-\d\d*)',
1479
dbm_strength = misc.RunRegex(signaldbm_pattern, output)
1483
def GetCurrentNetwork(self, iwconfig=None):
1484
""" Get the essid of the current network.
1487
The current network essid.
1491
output = self.GetIwconfig()
1494
network = misc.RunRegex(re.compile('.*ESSID:"(.*?)"',
1495
re.I | re.M | re.S), output)
1497
network = misc.to_unicode(network)